diff --git a/.clang-format b/.clang-format index add8fbd624..f201cdd689 100644 --- a/.clang-format +++ b/.clang-format @@ -49,6 +49,7 @@ SpacesInParentheses: false Standard: Cpp11 TabWidth: 2 UseTab: Never +DeriveLineEnding: true --- Language: ObjC DisableFormat: true diff --git a/.cmake-format.py b/.cmake-format.py new file mode 100644 index 0000000000..29245190b0 --- /dev/null +++ b/.cmake-format.py @@ -0,0 +1,109 @@ +# How wide to allow formatted cmake files +line_width = 120 + +# How many spaces to tab for indent +tab_size = 2 + +# If arglists are longer than this, break them always +max_subargs_per_line = 5 + +# If true, separate flow control names from their parentheses with a space +separate_ctrl_name_with_space = False + +# If true, separate function names from parentheses with a space +separate_fn_name_with_space = False + +# If a statement is wrapped to more than one line, than dangle the closing +# parenthesis on it's own line +dangle_parens = False + +# What character to use for bulleted lists +bullet_char = '*' + +# What character to use as punctuation after numerals in an enumerated list +enum_char = '.' + +# What style line endings to use in the output. +line_ending = 'unix' + +# Format command names consistently as 'lower' or 'upper' case +command_case = 'canonical' + +# Format keywords consistently as 'lower' or 'upper' case +keyword_case = 'upper' + +# Specify structure for custom cmake functions +# * = ZERO_OR_MORE +# + = ONE_OR_MORE +additional_commands = { + "add_root_dictionary": { + "kwargs": { + "LINKDEF": '+', + "HEADERS": '*', + "BASENAME": '*', + } + }, + "find_package_handle_standard_args": { + "flags": ["CONFIG_MODE"], + "kwargs": { + "DEFAULT_MSG": '*', + "REQUIRED_VARS": '*', + "VERSION_VAR": '*', + "HANDLE_COMPONENTS": '*', + "FAIL_MESSAGE": '*' + } + } +} + +# A list of command names which should always be wrapped +always_wrap = [] + +# Specify the order of wrapping algorithms during successive reflow attempts +algorithm_order = [0, 1, 2, 3, 4] + +# If true, the argument lists which are known to be sortable will be sorted +# lexicographicall +autosort = False + +# enable comment markup parsing and reflow +enable_markup = True + +# If comment markup is enabled, don't reflow the first comment block in +# eachlistfile. Use this to preserve formatting of your +# copyright/licensestatements. +first_comment_is_literal = False + +# If comment markup is enabled, don't reflow any comment block which matchesthis +# (regex) pattern. Default is `None` (disabled). +literal_comment_pattern = None + +# Regular expression to match preformat fences in comments +# default=r'^\s*([`~]{3}[`~]*)(.*)$' +fence_pattern = '^\\s*([`~]{3}[`~]*)(.*)$' + +# Regular expression to match rulers in comments +# default=r'^\s*[^\w\s]{3}.*[^\w\s]{3}$' +ruler_pattern = '^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$' + +# If true, emit the unicode byte-order mark (BOM) at the start of the file +emit_byteorder_mark = False + +# If a comment line starts with at least this many consecutive hash characters, +# then don't lstrip() them off. This allows for lazy hash rulers where the first +# hash char is not separated by space +hashruler_min_length = 10 + +# If true, then insert a space between the first hash char and remaining hash +# chars in a hash ruler, and normalize it's length to fill the column +canonicalize_hashrulers = True + +# Specify the encoding of the input file. Defaults to utf-8. +input_encoding = 'utf-8' + +# Specify the encoding of the output file. Defaults to utf-8. Note that cmake +# only claims to support utf-8 so be careful when using anything else +output_encoding = 'utf-8' + +# A dictionary containing any per-command configuration overrides. Currently +# only `command_case` is supported. +per_command = {} diff --git a/.cmake-format.yaml b/.cmake-format.yaml deleted file mode 100644 index cdb5bbea52..0000000000 --- a/.cmake-format.yaml +++ /dev/null @@ -1,31 +0,0 @@ -additional_commands: - foo: - flags: - - BAR - - BAZ - kwargs: - DEPENDS: '*' - HEADERS: '*' - SOURCES: '*' -algorithm_order: - - 0 - - 1 - - 2 - - 3 -always_wrap: [] -bullet_char: '*' -command_case: lower -dangle_parens: true -enable_markup: true -enum_char: . -fence_pattern: ^\s*([`~]{3}[`~]*)(.*)$ -first_comment_is_literal: false -keyword_case: unchanged -line_ending: unix -line_width: 120 -literal_comment_pattern: null -max_subargs_per_line: 3 -ruler_pattern: ^\s*[^\w\s]{3}.*[^\w\s]{3}$ -separate_ctrl_name_with_space: false -separate_fn_name_with_space: false -tab_size: 2 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..f9b76cbeea --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,12 @@ +# Global access +* @Barthelemy @knopers8 @justonedev1 + +# Detectors +/Modules/TOF/ @ercolessi +/Modules/EMCAL/ @jokonig @Barthelemy @knopers8 +/Modules/TPC/ @wiechula @makor +/Modules/MFT/ @AlexianL +/Modules/ITS/ @iravasen @IsakovAD +/Modules/TRD/ @martenole @bazinski +/Modules/FOCAL/ @mfasDa @fjonasALICE +/Modules/FIT/ @andreasmolander @afurs @jotwinow @sahilupadhyaya92 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..3a9665150b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,19 @@ +## ALICE data Quality Control framework and Modules + +This repository contains the ALICE O2 data Quality Control (QC) framework and modules. +It is divided into `Framework`, maintained by the framework developers and `Modules`, which are mostly developed by detector experts. + +### Copilot agent instructions + +- All the code should apply the [ALICE O2 Coding Guidelines](https://github.com/AliceO2Group/CodingGuidelines). +See [Formatting tool](https://github.com/AliceO2Group/CodingGuidelines?tab=readme-ov-file#formatting-tool) for details how to set up code formatter. +Do not attempt to install `clang` with `aliBuild`, use the one you have available on your system. +- Unless you are running on a developer's machine and you were provided specific instructions, do not attempt to build the code or run tests. Quality Control uses a large number of external dependencies, which are not available in the default Copilot environment on GitHub. +- When working on the C++ code-base, use the C++20 standard. +- When modifying class definitions for which a ROOT dictionary is generated, remember to update their version in macros such as `ClassDefOverride` (e.g. `ClassDefOverride(MonitorObject, 15)` -> `ClassDefOverride(MonitorObject, 16)`). +- Avoid changes which are not relevant to the current task. +- When adding a new feature, write a unit test if feasible. Unit tests should use catch2. +- When adding a new feature, extend the documentation accordingly and make sure that Tables of Contents are updated. +- When providing a fix, explain what was causing the issue and how it was fixed. +- When adding new code, make sure that necessary headers are included. Likewise, when removing code, remove the corresponding headers if they are not needed anymore. +- When dealing with the code in `Modules`, prefer minimal changes which do not break the existing functionality. \ No newline at end of file diff --git a/.github/workflows/clean-test.yml b/.github/workflows/clean-test.yml new file mode 100644 index 0000000000..b1490d3868 --- /dev/null +++ b/.github/workflows/clean-test.yml @@ -0,0 +1,57 @@ +--- +name: Clean PR checks + +'on': + workflow_dispatch: + inputs: + pr: + description: PR number in this repo to be cleaned + type: string # can't use number here + required: true + message: + description: Human-readable message displayed on the new pending status + type: string + required: false + default: '' + + # Warning: GitHub limits the total number of inputs to 10, so a maximum of + # 8 checks is allowed here! + # Warning: the check_* keys are magic and must consist of the string + # "check_" followed by the applicable check name exactly. The + # "description" field is only the human-readable label for the input. + 'check_build/QualityControl/o2': + description: build/QualityControl/o2 + type: boolean + default: true + 'check_build/QualityControl/o2-dataflow-cs8': + description: build/QualityControl/o2-dataflow-cs8 + type: boolean + default: true + 'check_build/QualityControl/o2-cs8': + description: build/QualityControl/o2-cs8 + type: boolean + default: true + 'check_build/QualityControl/o2-dataflow/macOS-arm': + description: build/QualityControl/o2-dataflow/macOS-arm + type: boolean + default: true + 'check_build/QualityControl/O2fst/o2': + description: Could you add build/QualityControl/O2fst/o2 + type: boolean + default: true + +permissions: {} + +jobs: + clean: + name: Clean PR checks + uses: alisw/ali-bot/.github/workflows/clean-pr-checks.yml@master + with: + owner: ${{ github.event.repository.owner.login }} + repo: ${{ github.event.repository.name }} + pr: ${{ github.event.inputs.pr }} + message: ${{ github.event.inputs.message }} + checks: ${{ toJSON(github.event.inputs) }} + permissions: + pull-requests: read # to get last commit for pr (octokit/graphql-action) + statuses: write # for set-github-status diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 0000000000..2d13710723 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,42 @@ +name: Clang format + +on: [pull_request] + +jobs: + clang-format: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + # To get the merge base, we need the full history. + fetch-depth: 0 + - name: Install prerequisites + env: + DEBIAN_FRONTEND: noninteractive + run: | + sudo apt update + sudo apt install -y clang-format-18 + sudo update-alternatives --install /usr/bin/clang-format \ + clang-format /usr/bin/clang-format-18 100 + sudo update-alternatives --install /usr/bin/git-clang-format \ + git-clang-format /usr/bin/git-clang-format-18 100 + - name: Run clang-format on changed files + run: | + set -x + git fetch origin ${{ github.event.pull_request.base.ref }} + git fetch origin pull/${{ github.event.pull_request.number }}/head:${{ github.event.pull_request.head.ref }} + BASE_COMMIT=$(git merge-base HEAD ${{ github.event.pull_request.base.sha }}) + COMMIT_FILES=$(git diff --diff-filter=d --name-only "$BASE_COMMIT" -- '*.cxx' '*.h' ':!*LinkDef*') + if [ "$COMMIT_FILES" == "" ]; then + exit 0 ;# nothing to check + fi + + RESULT_OUTPUT=$(git-clang-format --commit ${BASE_COMMIT} --diff ${COMMIT_FILES}) + if [ "$RESULT_OUTPUT" == "no modified files to format" ] || [ "$RESULT_OUTPUT" == "clang-format did not modify any files" ]; then + exit 0 ;# all good + else + git-clang-format --commit $BASE_COMMIT --diff + echo "$RESULT_OUTPUT" + exit 1 + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..c50e98ebf7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: Release + +on: + release: + types: [published] + +jobs: + bump_alidist: + runs-on: ubuntu-22.04 + steps: + - run: curl -L https://github.com/github/hub/releases/download/v2.12.7/hub-linux-amd64-2.12.7.tgz | tar xz + - run: | + git config --global user.email ${{ secrets.GH_EMAIL }} + git config --global user.name ${{ secrets.GH_USERNAME }} + - run: git clone https://github.com/alisw/alidist.git + - run: | + cd alidist + CURRENT_VERSION=`cat ${{ secrets.MODULE }}.sh | grep "tag:" | awk '{print $2}'` + sed -i "s/${CURRENT_VERSION}/${GITHUB_REF##*/}/g" ${{ secrets.MODULE }}.sh + - run: | + cd alidist + git add . + git commit -m "Bump ${{ secrets.MODULE }} to ${GITHUB_REF##*/}" + git push "https://${{ secrets.GH_TOKEN }}@github.com/${{ secrets.ORG }}/alidist" HEAD:refs/heads/${{ secrets.MODULE }}-${GITHUB_REF##*/} -f > /dev/null 2>&1 + - run: | + cd alidist + GITHUB_TOKEN=${{ secrets.GH_TOKEN }} ../hub-linux-amd64-2.12.7/bin/hub pull-request -h ${{ secrets.MODULE }}-${GITHUB_REF##*/} -b master -m "Bump ${{ secrets.MODULE }} to ${GITHUB_REF##*/}" + diff --git a/.gitignore b/.gitignore index ed225499fe..2e0f8addcf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ /build/ .cproject .project +.vscode +.cache *~ .idea *.jpg @@ -10,3 +12,10 @@ README.md.* cmake-build-* Framework/test/testCcdbApi2.cxx local_history.patch +compile_commands.json +.*.swp +*.orig +gh-md-toc +.clangd +/Framework/script/RepoCleaner/build/ +/Framework/script/RepoCleaner/qcrepocleaner.egg-info/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cbd8d3505a..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: cpp -matrix: - include: - - os: linux - dist: xenial - env: TOOL=clang-format - addons: - apt: - sources: - - llvm-toolchain-xenial-7 - packages: - - clang-format-7 - compiler: clang -script: - - if [[ $TOOL == "clang-format" ]] && [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - cd $TRAVIS_BUILD_DIR; - BASE_COMMIT=$(git rev-parse $TRAVIS_BRANCH); - COMMIT_FILES=$(git diff --name-only $BASE_COMMIT | grep -i -v LinkDef); - RESULT_OUTPUT="$(git-clang-format-7 --commit $BASE_COMMIT --diff --binary `which clang-format-7` $COMMIT_FILES)"; - if [ "$RESULT_OUTPUT" == "no modified files to format" ] || [ "$RESULT_OUTPUT" == "clang-format did not modify any files" ] ; then - exit 0; - else - echo "$RESULT_OUTPUT"; - exit 1; - fi - fi -notifications: - email: false diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..5063a55c4e --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,30 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: The ALICE Data Quality Control framework +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software +authors: + - given-names: Barthélémy + family-names: von Haller + email: barthelemy.von.haller@cern.ch + affiliation: CERN + orcid: 'https://orcid.org/0000-0002-3422-4585' + - given-names: Piotr + family-names: Konopka + email: piotr.jan.konopka@cern.ch + affiliation: CERN + orcid: 'https://orcid.org/0000-0001-8738-7268' +repository-code: 'https://github.com/AliceO2Group/QualityControl' +keywords: + - data quality monitoring + - data quality + - quality control + - quality assurance + - cern + - alice + - message passing +license: GPL-3.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 70f475b27a..850645813e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,51 +1,67 @@ # ---- CMake options ---- -cmake_minimum_required(VERSION 3.5.2 FATAL_ERROR) - -# Set cmake policy by version: https://cmake.org/cmake/help/latest/manual/cmake-policies.7.html -if(${CMAKE_VERSION} VERSION_LESS 3.12) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -else() - cmake_policy(VERSION 3.12) -endif() +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) enable_testing() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +cmake_policy(SET CMP0144 NEW) # find_package() uses upper-case _ROOT variables. include(CMakeParseArguments) -include(QCModulesUtils) include(GNUInstallDirs) # ---- Project ---- -project( - QualityControl - VERSION - 0.12.0 # TODO update this automatically when there are new releases - DESCRIPTION - "O2 Quality Control" - LANGUAGES CXX -) +project(QualityControl + VERSION 1.189.0 + DESCRIPTION "O2 Data Quality Control Framework" + LANGUAGES C CXX) + +if(ONLYDOC) + add_subdirectory(doc) + return() +endif() + +# Set CMAKE_INSTALL_LIBDIR explicitly to lib (to avoid lib64 on CC7) +set(CMAKE_INSTALL_LIBDIR lib) + +include(GNUInstallDirs) + +if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) +endif() +if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +endif() +if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) +endif() -set(LIBRARY_OUTPUT_PATH "${CMAKE_BINARY_DIR}/lib") -set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}/bin") -set(INCLUDE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/include") +# ---- End Project ---- +# The line above is necessary for the generation of doxygen by travis # ---- Compilation flags and build options ---- # Set the default build type to "RelWithDebInfo" if(NOT CMAKE_BUILD_TYPE) set( - CMAKE_BUILD_TYPE "RelWithDebInfo" - CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel Coverage." - FORCE - ) + CMAKE_BUILD_TYPE + "RelWithDebInfo" + CACHE + STRING + "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel Coverage." + FORCE) endif(NOT CMAKE_BUILD_TYPE) +option(BUILD_SHARED_LIBS "Build shared libs" ON) + # Build targets with install rpath on Mac to dramatically speed up installation set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) -list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) +list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" + isSystemDir) if("${isSystemDir}" STREQUAL "-1") if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(CMAKE_INSTALL_RPATH "@loader_path/../lib") @@ -58,55 +74,41 @@ unset(isSystemDir) # C++ standard if(NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED TRUE) endif() -# Add compiler flags for warnings and (more importantly) fPIC and debug symbols -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra") - # Set fPIC for all targets set(CMAKE_POSITION_INDEPENDENT_CODE ON) # ---- Dependencies ---- -find_package( - Boost 1.58 - COMPONENTS - container - unit_test_framework - program_options - system - log - signals - system -) +find_package(Boost 1.58 + COMPONENTS container + unit_test_framework + program_options + system + log + system) find_package(Git QUIET) find_package(Configuration REQUIRED) find_package(Monitoring REQUIRED) -find_package(MySQL) find_package(Common REQUIRED) find_package(InfoLogger REQUIRED) -find_package(AliceO2 REQUIRED) +find_package(BookkeepingApi REQUIRED) # it must be before O2 for some reasons. +find_package(O2 CONFIG REQUIRED) find_package(CURL REQUIRED) -find_package(ZeroMQ REQUIRED) -find_package(Arrow REQUIRED) -find_package(GLFW) -find_package(FairRoot REQUIRED) -find_package(FairMQ REQUIRED) +find_package(GLFW NAMES glfw3 CONFIG) +find_package(FairMQ 1.4.41 REQUIRED) find_package(FairLogger REQUIRED) -find_package( - ROOT 6.06.02 - COMPONENTS - RHTTP - Gui - REQUIRED -) - -set(ENABLE_MYSQL ON) -if(NOT (MYSQL_FOUND AND TARGET ROOT::RMySQL)) - set(ENABLE_MYSQL OFF) - message(WARNING "MySQL or ROOT::RMySQL not found, the corresponding classes won't be built.") -endif() +find_package(Occ REQUIRED) +find_package(ROOT 6.06.02 COMPONENTS RHTTP Gui REQUIRED) + +# rdkafka is built by configure and does not provide FindPackage.cmake file +find_library(RDKAFKA_LIB rdkafka REQUIRED PATHS ${RDKAFKA_ROOT}/lib) +set(RDKAFKA_INCLUDE "${RDKAFKA_ROOT}/include") + +configure_file(getTestDataDirectory.cxx.in getTestDataDirectory.cxx) # ---- Subdirectories ---- diff --git a/Framework/CMakeLists.txt b/Framework/CMakeLists.txt index 6ee7ada382..e2dd27055b 100644 --- a/Framework/CMakeLists.txt +++ b/Framework/CMakeLists.txt @@ -1,224 +1,432 @@ -# ---- Files ---- +# Produce the final Version.h using template Version.h.in and substituting +# variables. We don't want to pollute our source tree with it, thus putting it in +# binary tree. +configure_file("include/QualityControl/Version.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/include/QualityControl/Version.h" + @ONLY) + +# ---- Library for IL ---- +add_library(O2QualityControlInfoLogger STATIC + src/QcInfoLogger.cxx +) + +target_include_directories(O2QualityControlInfoLogger + PUBLIC + $ +) -set( - SRCS +target_link_libraries(O2QualityControlInfoLogger + PUBLIC + AliceO2::InfoLogger +) + +# ---- Library for the types ---- +add_library(O2QualityControlTypes src/MonitorObject.cxx + src/QualityObject.cxx src/Quality.cxx - src/ObjectsManager.cxx - src/Checker.cxx - src/CheckerFactory.cxx - src/CheckInterface.cxx - src/DatabaseFactory.cxx - src/CcdbDatabase.cxx - src/InformationService.cxx - src/InformationServiceDump.cxx - src/TaskRunner.cxx - src/TaskRunnerFactory.cxx - src/TaskInterface.cxx - src/RepositoryBenchmark.cxx - src/HistoMerger.cxx - src/InfrastructureGenerator.cxx - src/runnerUtils.h ) -set( - HEADERS # needed for the dictionary generation - include/QualityControl/MonitorObject.h - include/QualityControl/Quality.h - include/QualityControl/CheckInterface.h - include/QualityControl/Checker.h - include/QualityControl/CheckerFactory.h - include/QualityControl/DatabaseInterface.h - include/QualityControl/CcdbDatabase.h - include/QualityControl/TaskRunner.h - include/QualityControl/TaskRunnerFactory.h - include/QualityControl/HistoMerger.h - include/QualityControl/InfrastructureGenerator.h +target_include_directories( + O2QualityControlTypes + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ) -if(ENABLE_MYSQL) - list(APPEND SRCS src/MySqlDatabase.cxx) -endif() +target_link_libraries(O2QualityControlTypes + PRIVATE + O2QualityControlInfoLogger + PUBLIC + AliceO2::BookkeepingApi + AliceO2::Common + O2::DataFormatsQualityControl + ROOT::Hist +) + +add_root_dictionary(O2QualityControlTypes + HEADERS include/QualityControl/MonitorObject.h + include/QualityControl/QualityObject.h + include/QualityControl/Quality.h + include/QualityControl/Activity.h + LINKDEF include/QualityControl/TypesLinkDef.h) + +# ---- Kafka ---- + +add_library(O2QualityControlKafkaProtos OBJECT + proto/events.proto) -# Produce the final Version.h using template Version.h.in and substituting variables. We don't want to polute our source -# tree with it, thus putting it in binary tree. -configure_file( - "include/QualityControl/Version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/include/${MODULE_NAME}/Version.h" @ONLY +target_link_libraries(O2QualityControlKafkaProtos PUBLIC + protobuf::libprotobuf ) -# ---- ROOT dictionary ---- - -# ROOT dictionary the following root macros expect include dirs to be set as directory property TODO how to generate -# this automatically ? ? ? -get_directory_property(include_dirs INCLUDE_DIRECTORIES) -list(APPEND include_dirs "${CMAKE_CURRENT_SOURCE_DIR}/include") -get_target_property(config_inc_dir AliceO2::Configuration INTERFACE_INCLUDE_DIRECTORIES) -list(APPEND include_dirs "${config_inc_dir}") -get_target_property(arrow_inc_dir Arrow::Arrow INTERFACE_INCLUDE_DIRECTORIES) -list(APPEND include_dirs "${arrow_inc_dir}") -get_target_property(monitoring_inc_dir AliceO2::Monitoring INTERFACE_INCLUDE_DIRECTORIES) -list(APPEND include_dirs "${monitoring_inc_dir}") -get_target_property(infologger_inc_dir AliceO2::InfoLogger INTERFACE_INCLUDE_DIRECTORIES) -list(APPEND include_dirs "${infologger_inc_dir}") -get_target_property(o2_inc_dir AliceO2::AliceO2 INTERFACE_INCLUDE_DIRECTORIES) -list(APPEND include_dirs "${o2_inc_dir}") -get_target_property(common_inc_dir AliceO2::Common INTERFACE_INCLUDE_DIRECTORIES) -list(APPEND include_dirs "${common_inc_dir}") -get_target_property(boost_inc_dir Boost::container INTERFACE_INCLUDE_DIRECTORIES) -list(APPEND include_dirs "${boost_inc_dir}") -get_target_property(fairlogger_inc_dir FairLogger::FairLogger INTERFACE_INCLUDE_DIRECTORIES) -list(APPEND include_dirs "${fairlogger_inc_dir}") -list(APPEND include_dirs "${FAIRROOT_INCLUDE_DIR}") -list(APPEND include_dirs "${FairMQ_INCDIR}") -list(APPEND include_dirs "${FairMQ_INCDIR}/fairmq") -list(REMOVE_DUPLICATES include_dirs) -include_directories(${include_dirs}) - -set(dict "QualityControlDict") -set(dict_src ${CMAKE_CURRENT_BINARY_DIR}/${dict}.cxx) -set_source_files_properties(${dict_src} PROPERTIES COMPILE_FLAGS "-Wno-old-style-cast") -set_source_files_properties(${dict_src} PROPERTIES GENERATED TRUE) - -root_generate_dictionary("${dict}" ${HEADERS} LINKDEF include/QualityControl/LinkDef.h) - -# TODO review how and what to install for dictionary -install( - FILES ${CMAKE_CURRENT_BINARY_DIR}/lib${dict}_rdict.pcm ${CMAKE_CURRENT_BINARY_DIR}/lib${dict}.rootmap - DESTINATION ${CMAKE_INSTALL_LIBDIR} +target_include_directories(O2QualityControlKafkaProtos PUBLIC + "$" ) +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/proto") + +protobuf_generate( + TARGET O2QualityControlKafkaProtos + IMPORT_DIRS "${CMAKE_CURRENT_LIST_DIR}/proto" + PROTOC_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/proto") + # ---- Library ---- -add_library(QualityControl SHARED ${SRCS} QualityControlDict.cxx) +add_library(O2QualityControl + src/Activity.cxx + src/ActivityHelpers.cxx + src/ObjectsManager.cxx + src/CheckRunner.cxx + src/BookkeepingQualitySink.cxx + src/AggregatorRunner.cxx + src/CheckRunnerFactory.cxx + src/AggregatorRunnerFactory.cxx + src/CheckInterface.cxx + src/AggregatorInterface.cxx + src/DatabaseFactory.cxx + src/CcdbDatabase.cxx + src/TaskFactory.cxx + src/TaskRunner.cxx + src/TaskRunnerFactory.cxx + src/TaskInterface.cxx + src/UserCodeInterface.cxx + src/RepositoryBenchmark.cxx + src/RepoPathUtils.cxx + src/stringUtils.cxx + src/InfrastructureGenerator.cxx + src/InfrastructureSpecReader.cxx + src/Check.cxx + src/Aggregator.cxx + src/DataHeaderHelpers.cxx + src/Triggers.cxx + src/TriggerHelpers.cxx + src/PostProcessingRunner.cxx + src/PostProcessingFactory.cxx + src/PostProcessingConfig.cxx + src/PostProcessingInterface.cxx + src/PostProcessingDevice.cxx + src/TrendingTask.cxx + src/TrendingTaskConfig.cxx + src/DummyDatabase.cxx + src/DataProducer.cxx + src/HistoProducer.cxx + src/DataProducerExample.cxx + src/MonitorObjectCollection.cxx + src/UpdatePolicyManager.cxx + src/AdvancedWorkflow.cxx + src/QualitiesToFlagCollectionConverter.cxx + src/DataSourceSpec.cxx + src/RootFileSink.cxx + src/RootFileSource.cxx + src/UpdatePolicyType.cxx + src/RootClassFactory.cxx + src/ConfigParamGlo.cxx + src/SliceTrendingTask.cxx + src/SliceTrendingTaskConfig.cxx + src/Bookkeeping.cxx + src/CustomParameters.cxx + src/runnerUtils.cxx + src/Timekeeper.cxx + src/TimekeeperSynchronous.cxx + src/TimekeeperAsynchronous.cxx + src/WorkflowType.cxx + src/TimekeeperFactory.cxx + src/RootFileStorage.cxx + src/ReductorHelpers.cxx + src/KafkaPoller.cxx + src/FlagHelpers.cxx + src/ObjectMetadataHelpers.cxx + src/QCInputsAdapters.cxx + src/QCInputsFactory.cxx + src/UserInputOutput.cxx +) target_include_directories( - QualityControl - PUBLIC $ $ + O2QualityControl + PUBLIC $ + $ + ${MODERNCPPKAFKA_ROOT} + $ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src -) - -target_link_libraries( - QualityControl - PUBLIC - Boost::boost - FairLogger::FairLogger - FairMQ::FairMQ - ROOT::Hist - AliceO2::Common - AliceO2::InfoLogger - AliceO2::Monitoring - AliceO2::Configuration - ROOT::Net - AliceO2::AliceO2 - Boost::container - PRIVATE - Boost::system - $<$:MySQL::MySQL> - $<$:ROOT::RMySQL> - ROOT::Gui -) + $ + ) -target_compile_definitions(QualityControl - PRIVATE - $<$:_WITH_MYSQL> -) +target_link_libraries(O2QualityControl + PUBLIC Boost::boost + FairLogger::FairLogger + FairMQ::FairMQ + ROOT::Hist + ROOT::TreePlayer + AliceO2::Common + AliceO2::Monitoring + AliceO2::Configuration + AliceO2::Occ + ROOT::Net + Boost::container + O2::Framework + O2::CCDB + O2QualityControlTypes + O2::Mergers + O2::DataSampling + O2::DataFormatsQualityControl + O2::DetectorsBase + O2::GlobalTracking + O2QualityControlKafkaProtos + ${RDKAFKA_LIB} + PRIVATE CURL::libcurl + O2QualityControlInfoLogger + ) + +add_root_dictionary(O2QualityControl + HEADERS + include/QualityControl/CheckInterface.h + include/QualityControl/TaskInterface.h + include/QualityControl/UserCodeInterface.h + include/QualityControl/AggregatorInterface.h + include/QualityControl/PostProcessingInterface.h + include/QualityControl/TrendingTask.h + include/QualityControl/SliceInfoTrending.h + include/QualityControl/SliceTrendingTask.h + include/QualityControl/MonitorObjectCollection.h + LINKDEF include/QualityControl/LinkDef.h) # ---- Executables ---- -set( - EXE_SRCS - src/runInformationService.cxx - src/runInformationServiceDump.cxx +set(EXE_SRCS + src/runDataProducer.cxx + src/runDataProducerExample.cxx + src/runHistoProducer.cxx + src/runQC.cxx src/runBasic.cxx src/runAdvanced.cxx src/runReadout.cxx - src/runMergerTest.cxx - src/runReadoutForDataDump.cxx src/runRepositoryBenchmark.cxx -) - -set( - EXE_NAMES - qcInfoService - qcInfoServiceDump + src/runPostProcessing.cxx + src/runPostProcessingOCC.cxx + src/runUploadRootObjects.cxx + src/runFileMerger.cxx + src/runMetadataUpdater.cxx + src/runBookkeepingBenchmark.cxx) + +set(EXE_NAMES + o2-qc-run-producer + o2-qc-run-producer-basic + o2-qc-run-histo-producer + o2-qc + o2-qc-run-basic + o2-qc-run-advanced + o2-qc-run-readout + o2-qc-repository-benchmark + o2-qc-run-postprocessing + o2-qc-run-postprocessing-occ + o2-qc-upload-root-objects + o2-qc-file-merger + o2-qc-metadata-updater + o2-qc-bk-benchmark) + +# These were the original names before the convention changed. We will get rid +# of them but for the time being we want to create symlinks to avoid confusion. +set(EXE_OLD_NAMES + qcRunProducer + o2-qc-run-producer-basic + o2-qc-run-histo-producer + o2-qc-run-qc qcRunBasic qcRunAdvanced qcRunReadout - runMergerTest - qcRunReadoutForDataDump repositoryBenchmark -) + qcRunPostProcessing + qcRunPostProcessingOCC + o2-qc-upload-root-objects + o2-qc-file-merger + o2-qc-metadata-updater + o2-qc-bk-benchmark) + + +# As per https://stackoverflow.com/questions/35765106/symbolic-links-cmake +macro(install_symlink filepath sympath) + install(DIRECTORY DESTINATION bin) # just in case it is not there yet + install( + CODE + "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${filepath} ${sympath})" + ) + install(CODE "message(\"-- Created symlink: ${sympath} -> ${filepath}\")") +endmacro(install_symlink) list(LENGTH EXE_SRCS count) math(EXPR count "${count}-1") foreach(i RANGE ${count}) list(GET EXE_SRCS ${i} src) list(GET EXE_NAMES ${i} name) + list(GET EXE_OLD_NAMES ${i} oldname) add_executable(${name} ${src}) - target_link_libraries(${name} PRIVATE QualityControl) + target_link_libraries(${name} PRIVATE O2QualityControl CURL::libcurl ROOT::Tree) + install_symlink(${name} ${CMAKE_INSTALL_FULL_BINDIR}/${oldname}) endforeach() -# ---- Gui ---- - -set(DATADUMP "") -if(GLFW_FOUND) - set( - GUI_SRCS - src/imgui/imgui.cpp - src/imgui/imgui_draw.cpp - src/imgui/imgui_impl_glfw_gl3.cpp - src/imgui/gl3w.c - src/imgui/imgui_widgets.cpp - src/imgui/imgui_demo.cpp - src/imgui/BaseGui.cxx - src/DataDumpGui.cxx - src/runDataDump.cxx - ) - - add_executable(dataDump ${GUI_SRCS}) - - target_link_libraries(dataDump PRIVATE QualityControl GLFW::GLFW) - - set(DATADUMP "dataDump") -else() - message(STATUS "GLFW not found, DataDump will not be built") -endif() - # ---- Tests ---- -set( - TEST_SRCS - test/testDbFactory.cxx - test/testMonitorObject.cxx - test/testPublisher.cxx - test/testQcInfoLogger.cxx - test/testInfrastructureGenerator.cxx - test/testQCTask.cxx - test/testQuality.cxx +add_executable(o2-qc-test-core + test/testActivity.cxx + test/testActivityHelpers.cxx + test/testAggregatorInterface.cxx + test/testAggregatorRunner.cxx + test/testCheck.cxx + test/testCheckInterface.cxx + test/testCheckRunner.cxx + test/testCustomParameters.cxx + test/testDataHeaderHelpers.cxx + test/testInfrastructureGenerator.cxx + test/testMonitorObject.cxx + test/testPolicyManager.cxx + test/testPostProcessingRunner.cxx + test/testQuality.cxx + test/testQualityObject.cxx + test/testRootFileStorage.cxx + test/testTaskInterface.cxx + test/testTimekeeper.cxx + test/testTriggerHelpers.cxx + test/testVersion.cxx + test/testMonitorObjectCollection.cxx + test/testTrendingTask.cxx + test/testKafkaTests.cxx + test/testFlagHelpers.cxx + test/testQualitiesToFlagCollectionConverter.cxx + test/testQCInputs.cxx + test/testUserInputOutput.cxx ) +set_property(TARGET o2-qc-test-core + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) +target_link_libraries(o2-qc-test-core PRIVATE O2QualityControl O2::Catch2) +target_link_libraries(o2-qc-test-core PRIVATE O2::EMCALBase O2::EMCALCalib) +target_include_directories(o2-qc-test-core PRIVATE ${CMAKE_SOURCE_DIR}) + +add_test(NAME o2-qc-test-core COMMAND o2-qc-test-core) +set_tests_properties(o2-qc-test-core PROPERTIES TIMEOUT 30) +target_sources(o2-qc-test-core PRIVATE + ${CMAKE_BINARY_DIR}/getTestDataDirectory.cxx) +target_include_directories(o2-qc-test-core PRIVATE ${CMAKE_SOURCE_DIR}) +target_include_directories(o2-qc-test-core PRIVATE $) + +set(TEST_SRCS + test/testDbFactory.cxx + test/testPublisher.cxx + test/testQcInfoLogger.cxx + test/testTaskRunner.cxx + test/testObjectsManager.cxx + test/testCcdbDatabase.cxx + test/testCcdbDatabaseExtra.cxx + test/testTriggers.cxx + test/testPostProcessingInterface.cxx + test/testPostProcessingConfig.cxx + test/testReductor.cxx + test/testCheckWorkflow.cxx + test/testWorkflow.cxx + test/testRepoPathUtils.cxx + test/testUserCodeInterface.cxx + test/testStringUtils.cxx + test/testRunnerUtils.cxx + test/testBookkeepingQualitySink.cxx + ) -foreach(test ${TEST_SRCS}) +set(TEST_ARGS + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "-b --run" + "-b --run" + "" + "" + "" + "" + "" + "" + ) + +list(LENGTH TEST_SRCS count) +math(EXPR count "${count}-1") +foreach(i RANGE ${count}) + list(GET TEST_SRCS ${i} test) + list(GET TEST_ARGS ${i} arg) get_filename_component(test_name ${test} NAME) string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + string(REPLACE " " ";" arg "${arg}") # make list of string (arguments) out of + # one string add_executable(${test_name} ${test}) - target_link_libraries(${test_name} PRIVATE QualityControl Boost::unit_test_framework ) - add_test(NAME ${test_name} COMMAND ${test_name}) - set_tests_properties(${test_name} PROPERTIES TIMEOUT 60) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + target_link_libraries(${test_name} + PRIVATE O2QualityControl Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name} ${arg}) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 30) endforeach() -install( - FILES - test/testQCFactory.json - DESTINATION test -) +foreach(t testWorkflow testTaskRunner testCheckWorkflow + testPostProcessingConfig testPostProcessingInterface) + target_sources(${t} PRIVATE + ${CMAKE_BINARY_DIR}/getTestDataDirectory.cxx) + target_include_directories(${t} PRIVATE ${CMAKE_SOURCE_DIR}) +endforeach() + +target_include_directories(testCcdbDatabase PRIVATE $) + +set_property(TEST testWorkflow PROPERTY TIMEOUT 40) +set_property(TEST testWorkflow PROPERTY LABELS slow) +set_property(TEST testCheckWorkflow PROPERTY TIMEOUT 50) +set_property(TEST testCheckWorkflow PROPERTY LABELS slow) +set_property(TEST testObjectsManager PROPERTY TIMEOUT 30) +set_property(TEST testObjectsManager PROPERTY LABELS slow) +set_property(TEST testCcdbDatabase PROPERTY TIMEOUT 60) +set_property(TEST testCcdbDatabase PROPERTY LABELS slow CCDB) +set_property(TEST testCcdbDatabaseExtra PROPERTY LABELS manual CCDB) +set_property(TEST testDbFactory PROPERTY LABELS CCDB) +set_property(TEST testUserCodeInterface PROPERTY LABELS CCDB) + +# Add a functional test (QC-336) +string(RANDOM UNIQUE_ID) +configure_file(basic-functional.json.in ${CMAKE_BINARY_DIR}/tests/basic-functional.json) # substitute the unique id in the task name +add_test(NAME functional_test COMMAND o2-qc-functional-test.sh) +set_tests_properties(functional_test PROPERTIES ENVIRONMENT "JSON_DIR=${CMAKE_BINARY_DIR}/tests;UNIQUE_ID=${UNIQUE_ID}") +set_property(TEST functional_test PROPERTY LABELS slow) +set_property(TEST functional_test PROPERTY TIMEOUT 45) + +include(GenerateUniquePort) +o2_generate_unique_port(UNIQUE_PORT_1) +# UNIQUE_PORT_2 is UNIQUE_PORT_1 + 1 +# since we are guaranteed UNIQUE_PORT_1 is even. +math(EXPR UNIQUE_PORT_2 "${UNIQUE_PORT_1} + 1") +configure_file(multinode-test.json.in ${CMAKE_BINARY_DIR}/tests/multinode-test.json) +add_test(NAME multinode_test COMMAND o2-qc-multinode-test.sh) +set_tests_properties(multinode_test + PROPERTIES ENVIRONMENT "JSON_DIR=${CMAKE_BINARY_DIR}/tests;UNIQUE_PORT_1=${UNIQUE_PORT_1};UNIQUE_PORT_2=${UNIQUE_PORT_2}") +set_property(TEST multinode_test PROPERTY LABELS slow) +set_property(TEST multinode_test PROPERTY TIMEOUT 75) + +# Batch processing test +string(RANDOM UNIQUE_ID) +configure_file(batch-test.json.in ${CMAKE_BINARY_DIR}/tests/batch-test.json) # substitute the unique id in the task name +add_test(NAME batch_test COMMAND o2-qc-batch-test.sh) +set_tests_properties(batch_test PROPERTIES ENVIRONMENT "JSON_DIR=${CMAKE_BINARY_DIR}/tests;UNIQUE_ID=${UNIQUE_ID}") +set_property(TEST batch_test PROPERTY LABELS slow) +set_property(TEST batch_test PROPERTY TIMEOUT 60) # ---- Install ---- # Build targets with install rpath on Mac to dramatically speed up installation # https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/RPATH-handling set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) -list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" isSystemDir) +list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES + "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" isSystemDir) if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") if("${isSystemDir}" STREQUAL "-1") set(CMAKE_INSTALL_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}") @@ -228,32 +436,28 @@ endif() unset(isSystemDir) # Install library and binaries -install( - TARGETS QualityControl ${EXE_NAMES} ${DATADUMP} - EXPORT QualityControlTargets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) +install(TARGETS O2QualityControl O2QualityControlTypes O2QualityControlKafkaProtos ${EXE_NAMES} + EXPORT QualityControlTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) # Install headers -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/QualityControl DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/QualityControl + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") # Create version file include(CMakePackageConfigHelpers) write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/cmake/QualityControlConfigVersion.cmake" - VERSION ${PACKAGE_VERSION} - COMPATIBILITY AnyNewerVersion -) + "${CMAKE_CURRENT_BINARY_DIR}/cmake/QualityControlConfigVersion.cmake" + VERSION ${PACKAGE_VERSION} + COMPATIBILITY AnyNewerVersion) # Export targets -install( - EXPORT QualityControlTargets - FILE QualityControlTargets.cmake - NAMESPACE QualityControl:: - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QualityControl -) +install(EXPORT QualityControlTargets + FILE QualityControlTargets.cmake + NAMESPACE QualityControl:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QualityControl) # Configure and install Config files configure_package_config_file( @@ -262,33 +466,49 @@ configure_package_config_file( INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/QualityControl" PATH_VARS - CMAKE_INSTALL_PREFIX -) - -install( - FILES - "${CMAKE_CURRENT_BINARY_DIR}/cmake/QualityControlConfig.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/cmake/QualityControlConfigVersion.cmake" - DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QualityControl -) - -# ---- Extra scripts ---- + CMAKE_INSTALL_PREFIX) -install(PROGRAMS script/qcDatabaseSetup.sh DESTINATION bin) install( - FILES - example-default.json - alfa.json - dataDump.json - DESTINATION etc -) -install( - FILES - basic.json - basic-no-sampling.json - advanced.json - readout.json - readout-no-sampling.json - readoutForDataDump.json - DESTINATION etc -) + FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/QualityControlConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/QualityControlConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QualityControl) + +# ---- Install config files and scripts ---- + +install(FILES example-default.json + basic.json + basic-aggregator.json + basic-external-histo.json + basic-no-sampling.json + advanced.json + advanced-aggregator.json + advanced-external-histo.json + multiNode.json + readout.json + readout-no-sampling.json + postprocessing.json + DESTINATION etc) + +install(PROGRAMS script/o2-qc-functional-test.sh + script/o2-qc-multinode-test.sh + script/o2-qc-batch-test.sh + DESTINATION bin) + +# ---- Copy test files ---- +# +# Using file(COPY is wrong because cmake will not detect when the files are modified. +# Using install is also wrong because we don't want to install nor package the test files. +# The solution is to use configure_file with COPYONLY. + +set(TEST_FILES + "testSharedConfig.json" + "testEmptyConfig.json" + "testCheckWorkflow.json" + "testWorkflow.json" + "testThrowNameClash.json") +set(TEST_FILES_PREFIXED ${TEST_FILES}) +list(TRANSFORM TEST_FILES_PREFIXED PREPEND ${CMAKE_BINARY_DIR}/tests/) + +foreach(test_file ${TEST_FILES}) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/${test_file} ${CMAKE_BINARY_DIR}/tests/${test_file} COPYONLY) +endforeach() diff --git a/Framework/__init__.py b/Framework/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Framework/advanced-aggregator.json b/Framework/advanced-aggregator.json new file mode 100644 index 0000000000..c0d6fe83e0 --- /dev/null +++ b/Framework/advanced-aggregator.json @@ -0,0 +1,159 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "true", "": "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel": "21", "": "Message at this level or above are discarded (default: 21 - Trace)" + } + }, + "tasks": { + "dataSizeTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tst2" + }, + "location": "remote" + }, + "someNumbersTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tst1" + }, + "location": "remote" + } + }, + "checks": { + "dataSizeCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "dataSizeTask", + "MOs": ["example"] + }] + }, + "someNumbersCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnEachSeparately", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "someNumbersTask", + "MOs": ["example"] + }] + } + }, + "aggregators": { + "MyAggregator1": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", + "name": "dataSizeCheck" + }, { + "type": "Check", + "name": "someNumbersCheck" + }] + }, + "MyAggregator2": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", + "name": "dataSizeCheck" + }, { + "type": "Check", "": "this one is using onEachSeparately and thus it sends under a full path", + "name": "someNumbersCheck", + "QOs": ["someNumbersTask/example"], "": "also possible to ignore it altogether, meaning we take all objects" + }] + }, + "MyAggregator3": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Aggregator", + "name": "MyAggregator1", "": "no QOs parameter -> all QOs of this aggregator", + "QOs": ["newQuality"], "": "list all objects we are interested in" + }, { + "type": "Aggregator", + "name": "MyAggregator2", + "QOs": ["newQuality", "another"], "": "if we omitted the QOs for a data source we would default to OnAny" + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tst1", + "active": "true", + "machines": [], + "query" : "data:TST/SUM/2;param:TST/PARAM/2", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "32112332123" + } + ], + "blocking": "false" + }, + { + "id": "tst2", + "active": "true", + "machines": [], + "query" : "data:TST/DATA", + "samplingConditions": [ + { + "condition": "payloadSize", + "lowerLimit": "8000", + "upperLimit": "10000" + } + ], + "blocking": "false" + } + ] +} diff --git a/Framework/advanced-external-histo.json b/Framework/advanced-external-histo.json new file mode 100644 index 0000000000..ec93326a4f --- /dev/null +++ b/Framework/advanced-external-histo.json @@ -0,0 +1,96 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "QcTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tst-raw" + }, + "taskParameters": { + "myOwnKey": "myOwnValue" + }, + "location": "remote" + } + }, + "externalTasks": { + "External-1": { + "active": "true", + "query": "External-1:TST/HISTO/0", "": "Use the task name as binding (encouraged)" + }, + "External-2": { + "active": "true", + "query": "External-2:TST/HISTO/1", "": "Use the task name as binding (encouraged)" + } + }, + "checks": { + "QcCheck-external-1": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "ExternalTask", + "name": "External-1", + "MOs": ["hello_0", "hello_1"] + }] + }, + "QcCheck2": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "QcTask", + "MOs": ["example"] + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tst-raw", + "active": "true", + "machines": [], + "query": "data:TST/RAWDATA/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Framework/advanced.json b/Framework/advanced.json index 283656d5de..5a37ec8702 100644 --- a/Framework/advanced.json +++ b/Framework/advanced.json @@ -10,88 +10,290 @@ }, "Activity": { "number": "42", - "type": "2" + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "true", "": "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel": "21", "": "Message at this level or above are discarded (default: 21 - Trace)" } }, "tasks": { - "dataSizeTask": { + "AdvTaskA": { "active": "true", "className": "o2::quality_control_modules::skeleton::SkeletonTask", "moduleName": "QcSkeleton", + "detectorName": "TST", "cycleDurationSeconds": "10", - "maxNumberCycles": "-1", "dataSource": { "type": "dataSamplingPolicy", - "name": "tst2" - }, - "location": "local", - "machines": [ - "o2flptst1", - "o2flptst2", - "o2flptst3" - ] + "name": "tst" + } }, - "someNumbersTask": { + "AdvTaskB": { "active": "true", "className": "o2::quality_control_modules::skeleton::SkeletonTask", "moduleName": "QcSkeleton", - "cycleDurationSeconds": "10", - "maxNumberCycles": "-1", + "detectorName": "TST", + "cycleDurationSeconds": "25", "dataSource": { - "type": "dataSamplingPolicy", - "name": "tst1" + "type": "direct", + "query": "data:TST/SUM" + } + } + }, + "checks": { + "AdvCheckA1": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "AdvTaskA", + "MOs": ["example"] + }] + }, + "AdvCheckA2": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "AdvTaskA", + "MOs": ["example", "example2"] + }] + }, + "AdvCheckB": { + "active": "true", + "className": "o2::quality_control_modules::common::TrendCheck", + "moduleName": "QualityControl", + "detectorName": "TST", + "policy": "OnAll", + "extendedCheckParameters": { + "default": { + "default": { + "trendCheckMode": "DeviationFromMean", + "nPointsForAverage": "3", + "thresholdsBad": "-0.02,0.02", + "thresholdsMedium": "-0.005,0.005" + } + } }, - "location": "remote" + "dataSource": [ + { + "type": "PostProcessing", + "name": "AdvTrendB", + "MOs" : [ + "mean_of_histogram" + ] + } + ] + } + }, + "aggregators": { + "AdvAggregatorA": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", + "name": "AdvCheckA1" + }, { + "type": "Check", + "name": "AdvCheckA2" + }] + }, + "AdvAggregatorB": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", + "name": "AdvCheckB" + }] + }, + "AdvAggregatorC": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Aggregator", + "name": "AdvAggregatorA", + "QOs": [] + }, { + "type": "Aggregator", + "name": "AdvAggregatorB", + "QOs": [] + }] + } + + }, + "postprocessing": { + "AdvTrendB": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TST", + "dataSources": [ + { + "type": "repository", + "path": "TST/MO/AdvTaskB", + "names": [ "example" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_histogram", + "title": "Mean trend of the example histogram", + "graphAxisLabel": "Mean X:time", + "graphYRange": "0:10000", + "graphs" : [ + { + "name": "mean_trend", + "title": "mean trend", + "varexp": "example.mean:time", + "selection": "", + "option": "*L PLC PMC" + } + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:TST/MO/AdvTaskB/example" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "AdvTrendA": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TST", + "dataSources": [ + { + "type": "repository-quality", + "path": "TST/QO", + "names": [ "AdvCheckA1" ], + "reductorName": "o2::quality_control_modules::common::QualityReductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "example_quality", + "title": "Trend of the example histogram's quality", + "graphs" : [{ + "varexp": "AdvCheckA1.name:time", + "selection": "", + "option": "*" + }] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:TST/QO/AdvCheckA1" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "AdvQualityTask": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QualityControl", + "detectorName": "TST", + "qualityGroups": [ + { + "name" : "global", + "title" : "Advanced example: Aggregators", + "path": "TST/QO", + "ignoreQualitiesDetails" : [], + "inputObjects": [ + { + "name" : "AdvAggregatorC/AdvAggregatorC", + "title" : "Adv. Aggregator C (total)" + }, + { + "name" : "AdvAggregatorA/AdvAggregatorA", + "title" : "Adv. Aggregator A" + }, + { + "name" : "AdvAggregatorB/AdvAggregatorB", + "title" : "Adv. Aggregator B" + } + ] + }, + { + "name" : "details", + "title" : "Advanced example: Checks", + "path": "TST/QO", + "ignoreQualitiesDetails" : [], + "inputObjects": [ + { + "name" : "AdvCheckA1", + "title" : "" + }, + { + "name" : "AdvCheckA2", + "title" : "" + }, + { + "name" : "AdvCheckB", + "title" : "" + } + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:TST/QO/AdvAggregatorC/AdvAggregatorC" + ], + "stopTrigger": [ + "userorcontrol" + ] } } }, "dataSamplingPolicies": [ { - "id": "tst1", + "id": "tst", "active": "true", - "machines": [], - "dataHeaders": [ - { - "binding": "sum", - "dataOrigin": "TST", - "dataDescription": "SUM" - }, - { - "binding": "param", - "dataOrigin": "TST", - "dataDescription": "PARAM" - } - ], - "subSpec": "2", + "query" : "data:TST/DATA/1", "samplingConditions": [ { "condition": "random", "fraction": "0.1", - "seed": "32112332123" - } - ], - "blocking": "false" - }, - { - "id": "tst2", - "active": "true", - "machines": [], - "dataHeaders": [ - { - "binding": "data", - "dataOrigin": "TST", - "dataDescription": "DATA" - } - ], - "subSpec": "*", - "samplingConditions": [ - { - "condition": "payloadSize", - "lowerLimit": "8000", - "upperLimit": "10000" + "seed": "0" } - ], - "blocking": "false" + ] } ] -} \ No newline at end of file +} diff --git a/Framework/alfa.json b/Framework/alfa.json deleted file mode 100644 index b3f9148586..0000000000 --- a/Framework/alfa.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "fairMQOptions": { - "devices": [ - { - "id": "myTask_1", - "channels": [ - { - "name": "data-out", - "sockets": [ - { - "type": "pub", - "method": "bind", - "address": "tcp://*:5556", - "sndBufSize": 100, - "rcvBufSize": 100, - "rateLogging": 0 - } - ] - }, - { - "name": "information-service-out", - "sockets": [ - { - "type": "pub", - "method": "connect", - "address": "tcp://localhost:5560", - "sndBufSize": 100, - "rcvBufSize": 100, - "rateLogging": 0 - } - ] - } - ] - }, - { - "id": "daqTask", - "channels": [ - { - "name": "data-out", - "sockets": [ - { - "type": "pub", - "method": "bind", - "address": "tcp://*:5557", - "sndBufSize": 100, - "rcvBufSize": 100, - "rateLogging": 0 - } - ] - }, - { - "name": "information-service-out", - "sockets": [ - { - "type": "pub", - "method": "connect", - "address": "tcp://localhost:5560", - "sndBufSize": 100, - "rcvBufSize": 100, - "rateLogging": 0 - } - ] - } - ] - } - ] - } -} - diff --git a/Framework/alfaTestReceiver.json b/Framework/alfaTestReceiver.json deleted file mode 100644 index d1e098cb0d..0000000000 --- a/Framework/alfaTestReceiver.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "fairMQOptions": { - "devices": [ - { - "id": "receiver", - "channels": [ - { - "name": "data", - "sockets": [ - { - "type": "sub", - "method": "connect", - "address": "tcp://localhost:5556", - "sndBufSize": 100, - "rcvBufSize": 100, - "rateLogging": 0 - } - ] - } - ] - } - ] - } -} \ No newline at end of file diff --git a/Framework/basic-aggregator.json b/Framework/basic-aggregator.json new file mode 100644 index 0000000000..a5c553741f --- /dev/null +++ b/Framework/basic-aggregator.json @@ -0,0 +1,111 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", "": "Set to 1 to discard debug and trace messages (default: false)", + "filterDiscardLevel": "21", "": "Message at this level or above are discarded (default: 21 - Trace)" + } + }, + "tasks": { + "QcTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tst-raw" + }, + "taskParameters": { + "myOwnKey": "myOwnValue" + }, + "location": "remote" + } + }, + "checks": { + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "QcTask", + "MOs": ["example"] + }] + }, + "QcCheck2": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnEachSeparately", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "QcTask", + "MOs": ["example"] + }] + } + }, + "aggregators": { + "MyAggregator1": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", "": "for a check which produces a single result do it like that", + "name": "QcCheck", "": "no need to specify each object because there is only 1" + }, + { + "type": "Check", "": "for a check which produces a single result do it like that", + "name": "QcCheck2", "": "no need to specify each object because there is only 1", + "QOs": ["QcTask/example"], "": "also possible to ignore it altogether, meaning we take all objects" + }], + "aggregatorParameters": { + "myOwnKey": "myOwnValue" + } + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tst-raw", + "active": "true", + "machines": [], + "query": "data:TST/RAWDATA/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Framework/basic-external-histo.json b/Framework/basic-external-histo.json new file mode 100644 index 0000000000..bbf7e78d99 --- /dev/null +++ b/Framework/basic-external-histo.json @@ -0,0 +1,50 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + }, + "externalTasks": { + "External-1": { + "active": "true", + "query": "External-1:TST/HISTO/0", "": "Use the task name as binding (encouraged)" + } + }, + "checks": { + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "ExternalTask", + "name": "External-1", + "MOs": ["hello_0"] + }] + } + } + }, + "dataSamplingPolicies": [ + ] +} diff --git a/Framework/basic-functional.json.in b/Framework/basic-functional.json.in new file mode 100644 index 0000000000..2d47226ca6 --- /dev/null +++ b/Framework/basic-functional.json.in @@ -0,0 +1,91 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE", + "start": "8000000", + "end": "9000000", + "periodName": "LHC9000x", + "passName": "apass500" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "FunctionalTest@UNIQUE_ID@": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tst-raw" + }, + "taskParameters": { + "myOwnKey": "myOwnValue" + }, + "location": "remote" + } + }, + "checks": { + "FunctionalTest@UNIQUE_ID@": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "FunctionalTest@UNIQUE_ID@", + "MOs": ["example"] + }] + } + }, + "aggregators": { + "FunctionalTestAggregator@UNIQUE_ID@": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Check" , "": "for a check which produces a single result do it like that", + "name": "FunctionalTest@UNIQUE_ID@" , "": "no need to specify each object because there is only 1" + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tst-raw", + "active": "true", + "machines": [], + "query": "data:TST/RAWDATA/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Framework/basic-no-sampling.json b/Framework/basic-no-sampling.json index 1147fb581d..221ce7205c 100644 --- a/Framework/basic-no-sampling.json +++ b/Framework/basic-no-sampling.json @@ -10,7 +10,16 @@ }, "Activity": { "number": "42", - "type": "2" + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" } }, "tasks": { @@ -18,23 +27,34 @@ "active": "true", "className": "o2::quality_control_modules::skeleton::SkeletonTask", "moduleName": "QcSkeleton", + "detectorName": "TST", "cycleDurationSeconds": "10", - "maxNumberCycles": "-1", "dataSource": { "type": "direct", - "binding": "its-rawdata", - "dataOrigin": "ITS", - "dataDescription": "RAWDATA", - "subSpec": "0" + "query" : "data:TST/RAWDATA/0" }, "taskParameters": { "nothing": "rien" }, "location": "remote" } + }, + "checks": { + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "QcTask", + "MOs": ["example"] + }] + } } }, "dataSamplingPolicies": [ ] -} \ No newline at end of file +} diff --git a/Framework/basic.json b/Framework/basic.json index 38689eb5df..1d8ff36da3 100644 --- a/Framework/basic.json +++ b/Framework/basic.json @@ -6,53 +6,105 @@ "host": "ccdb-test.cern.ch:8080", "username": "not_applicable", "password": "not_applicable", - "name": "not_applicable" + "name": "not_applicable", + "maxObjectSize": "2097152", "": "[Bytes, default=2MB] Maximum size allowed, larger objects are rejected." }, "Activity": { "number": "42", - "type": "2" + "type": "NONE", + "periodName": "", "": "Period name - e.g. LHC22c, LHC22c1b_test", + "passName": "", "": "Pass type - e.g. spass, cpass1", + "provenance": "qc", "": "Provenance - qc or qc_mc depending whether it is normal data or monte carlo data" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", "": "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel": "12", "": "Message at this level or above are discarded (default: 21 - Trace)", + "filterDiscardFile": "/tmp/_ID_.txt", "": ["If set, the messages discarded because of filterDiscardLevel", + "will go to this file (default: ); The keyword _ID_ is replaced by the device id. Discarded Debug ", + "messages won't go there."] + }, + "bookkeeping": { + "url": "" } }, "tasks": { "QcTask": { "active": "true", + "critical": "false", "": "if false the task is allowed to die without stopping the workflow, default: true", "className": "o2::quality_control_modules::skeleton::SkeletonTask", "moduleName": "QcSkeleton", - "cycleDurationSeconds": "10", - "maxNumberCycles": "-1", - "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "detectorName": "TST", + "": "The last item in cycleDurations will be used for the rest of the duration whatever the period", + "cycleDurations": [ + {"cycleDurationSeconds": 10, "validitySeconds": 35}, + {"cycleDurationSeconds": 12, "validitySeconds": 1} + ], + "": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", "dataSource": { "type": "dataSamplingPolicy", - "name": "its-raw" + "name": "tst-raw" }, - "taskParameters": { - "nothing": "rien" + "extendedTaskParameters": { + "default": { + "default": { + "myOwnKey": "myOwnValue", + "myOwnKey2": "myOwnValue2" + } + }, + "PHYSICS": { + "default": { + "myOwnKey1": "myOwnValue1b", + "myOwnKey2": "myOwnValue2b" + } + } }, - "location": "remote" + "saveObjectsToFile": "", "": "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "checks": { + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "QcTask", + "MOs": ["example"] + }], + "extendedCheckParameters": { + "physics": { + "pp": { + "myOwnKey1": "myOwnValue1c" + } + } + } } } }, "dataSamplingPolicies": [ { - "id": "its-raw", + "id": "tst-raw", "active": "true", "machines": [], - "dataHeaders": [ - { - "binding": "random", - "dataOrigin": "ITS", - "dataDescription": "RAWDATA" - } - ], - "subSpec": "0", + "query": "data:TST/RAWDATA/0", "samplingConditions": [ { "condition": "random", "fraction": "0.1", "seed": "1234" } - ], - "blocking": "false" + ] } ] -} \ No newline at end of file +} diff --git a/Framework/batch-test.json.in b/Framework/batch-test.json.in new file mode 100644 index 0000000000..b0541e6e15 --- /dev/null +++ b/Framework/batch-test.json.in @@ -0,0 +1,74 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE", + "start": "8000000", + "end": "9000000", + "periodName": "LHC9000x", + "passName": "apass500" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "BatchTestTask@UNIQUE_ID@": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "100", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tst-raw" + }, + "movingWindows" : [ "example" ] + } + }, + "checks": { + "BatchTestCheck@UNIQUE_ID@": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "BatchTestTask@UNIQUE_ID@", + "MOs": ["example"] + }, + { + "type": "TaskMovingWindow", + "name": "BatchTestTask@UNIQUE_ID@", + "MOs": ["example"] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tst-raw", + "active": "true", + "machines": [], + "query": "data:TST/RAWDATA/0", + "samplingConditions": [], + "blocking": "false" + } + ] +} \ No newline at end of file diff --git a/Framework/example-default.json b/Framework/example-default.json index 22ea380c87..07bd335def 100644 --- a/Framework/example-default.json +++ b/Framework/example-default.json @@ -10,7 +10,16 @@ }, "Activity": { "number": "42", - "type": "2" + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" } }, "tasks": { @@ -18,7 +27,6 @@ "className": "o2::quality_control_modules::example::ExampleTask", "moduleName": "QcExample", "cycleDurationSeconds": "10", - "maxNumberCycles": "-1", "dataSource": { "type": "dataSamplingPolicy", "name": "ex1" @@ -28,7 +36,6 @@ "daqTask": { "className": "o2::quality_control_modules::daq::DaqTask", "moduleName": "QcDaq", - "maxNumberCycles": "-1", "cycleDurationSeconds": "10", "dataSource": { "type": "dataSamplingPolicy", @@ -45,41 +52,157 @@ "name": "ex1" }, "location": "local", - "machines": [ + "localMachines": [ "o2flp1", "o2flp2" - ] + ], + "remoteMachine": "o2qc1", + "remotePort": "30432" + } + }, + "checks": { + "example/checkMeanIsAbove": { + "active": "true", + "className": "o2::quality_control_modules::common::MeanIsAbove", + "moduleName": "QcCommon", + "policy": "OnAny", + "dataSource": [{ + "type": "Task", + "name": "myTask_1", "": "MOs parameter is not specfied, meaning we take all objects" + }] + }, + "example/checkNonEmpty": { + "active": "true", + "className": "o2::quality_control_modules::common::MeanIsAbove", + "moduleName": "QcCommon", + "policy": "OnAny", + "dataSource": [{ + "type": "Task", + "name": "myTask_1", "": "MOs parameter is not specfied, meaning we take all objects" + }] + }, + "example/checkFromExample": { + "active": "true", + "className": "o2::quality_control_modules::example::FakeCheck", + "moduleName": "QcCommon", + "policy": "OnAny", + "dataSource": [{ + "type": "Task", + "name": "myTask_1", "": "MOs parameter is not specfied, meaning we take all objects" + }] + }, + "daq/checkNonEmpty/payloadSize": { + "active": "true", + "className": "o2::quality_control_modules::common::NonEmpty", + "moduleName": "QcCommon", + "policy": "OnAny", + "dataSource": [{ + "type": "Task", + "name": "DaqTask", + "MOs": ["payloadSize"] + }] + }, + "daq/checkNonEmpty/IDs": { + "active": "true", + "className": "o2::quality_control_modules::common::NonEmpty", + "moduleName": "QcCommon", + "policy": "OnAny", + "dataSource": [{ + "type": "Task", + "name": "DaqTask", + "MOs": ["IDs"] + }] + }, + "daq/checkIncreasingIDs": { + "active": "true", + "className": "o2::quality_control_modules::daq::EverIncreasingGraph", + "moduleName": "QcDaq", + "policy": "OnAny", + "dataSource": [{ + "type": "Task", + "name": "DaqTask", + "MOs": ["IDs"] + }] + }, + "benchmark/fakeCheck_1": { + "active": "true", + "className": "o2::quality_control_modules::common::NonEmpty", + "moduleName": "QcCommon", + "policy": "OnAny", + "dataSource": [{ + "type": "Task", + "name": "myTask_1", + "MOs": ["histogram_myTask_1_0"] + }] + } + + }, + "aggregators": { + "MyAggregator1": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", + "name": "dataSizeCheck" + }, { + "type": "Check", + "name": "someNumbersCheck" + }] + }, + "MyAggregator2": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", + "name": "dataSizeCheck" + }, { + "type": "Check", "": "this one is using onEachSeparately and thus it sends under a full path", + "name": "someNumbersCheck", + "QOs": ["someNumbersTask/example"], "": "also possible to ignore it altogether, meaning we take all objects" + }] + }, + "MyAggregator3": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Aggregator", + "name": "MyAggregator1", "": "no QOs parameter -> all QOs of this aggregator" + }, { + "type": "Aggregator", + "name": "MyAggregator2", + "QOs": ["newQuality", "another"], "": "list all objects we are interested in" + }] } } }, - "dataSamplingPoliciesFile_comment": "In case that policies are stored in different file, specify its path below. When both dataSamplingPolicies and dataSamplingPoliciesFile are specified, the latter has higher priority", - "dataSamplingPoliciesFile": "json:///home/genghiskhan/alice/QualityControl/dataSamplingConfig.json", - "dataSamplingPolicies_comment": "this is ignored when dataSamplingPoliciesFile is specified", "dataSamplingPolicies": [ { "id": "ex1", "active": "true", - "dataHeaders": [ + "query_comment" : "query is in the format of binding1:origin1/description1/subSpec1;binding2:origin2/description2/subSpec2;...", + "query" : "data:TST/DATA/0", + "samplingConditions": [ { - "binding": "data", - "dataOrigin": "TST", - "dataDescription": "DATA" + "condition": "custom", + "moduleName": "QcExample", + "className": "o2::quality_control_modules::example::ExampleCondition", + "threshold": "120" } - ], - "subSpec": "0", - "samplingConditions": [] + ] }, { "id": "mftclusters", "active": "true", - "dataHeaders": [ - { - "binding": "mft-clusters", - "dataOrigin": "MFT", - "dataDescription": "CLUSTERS" - } - ], - "subSpec": "0", + "query" : "mft-clusters:MFT/CLUSTERS/0", "samplingConditions": [ { "condition": "payloadSize", diff --git a/Framework/include/QualityControl/Activity.h b/Framework/include/QualityControl/Activity.h index 846bd57c67..63fcc18370 100644 --- a/Framework/include/QualityControl/Activity.h +++ b/Framework/include/QualityControl/Activity.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file Activity.h /// \author Barthelemy von Haller @@ -6,17 +17,41 @@ #ifndef QC_CORE_ACTIVITY_H #define QC_CORE_ACTIVITY_H +#include +#include "QualityControl/ValidityInterval.h" + +#include "Rtypes.h" + namespace o2::quality_control::core { -/// \brief Dummy class that should be removed when there is the official one. -/// This corresponds to a Run1/2 "run". +/// \brief Class that represents a Run 3 activity such as a run. /// \author Barthelemy von Haller class Activity { public: Activity() = default; - Activity(int id, int type) : mId(id), mType(type) {} + Activity(int id, + const std::string& type, + const std::string& periodName = "", + const std::string& passName = "", + const std::string& provenance = "qc", + ValidityInterval validity = gFullValidityInterval, + const std::string& beamType = "", + const std::string& partitionName = "", + int fillNumber = 0, + int originalId = 0) + : mId(id), + mType(type), + mPeriodName(periodName), + mPassName(passName), + mProvenance(provenance), + mValidity(validity), + mBeamType(beamType), + mPartitionName(partitionName), + mFillNumber(fillNumber), + mOriginalId(originalId) {} + /// Copy constructor Activity(const Activity& other) = default; /// Move constructor @@ -25,11 +60,31 @@ class Activity Activity& operator=(const Activity& other) = default; /// Move assignment operator Activity& operator=(Activity&& other) noexcept = default; + /// Comparator. All fields should be exactly the same + bool operator==(const Activity& other) const; + + /// prints Activity + friend std::ostream& operator<<(std::ostream& out, const Activity& activity); + + /// Checks if the other activity matches this, taking into account that default values match any. + bool matches(const Activity& other) const; + /// Checks if the other object describes the same Activity (but e.g. in different time) + bool same(const Activity& other) const; virtual ~Activity() = default; int mId{ 0 }; - int mType{ 0 }; + std::string mType{ "NONE" }; + std::string mPeriodName{}; + std::string mPassName{}; + std::string mProvenance{ "qc" }; + ValidityInterval mValidity{ gFullValidityInterval }; + std::string mBeamType{}; + std::string mPartitionName{}; + int mFillNumber{ 0 }; + int mOriginalId{ 0 }; // original run number of REPLAY runs + + ClassDef(Activity, 6); }; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/ActivityHelpers.h b/Framework/include/QualityControl/ActivityHelpers.h new file mode 100644 index 0000000000..61268d729a --- /dev/null +++ b/Framework/include/QualityControl/ActivityHelpers.h @@ -0,0 +1,125 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ActivityHelpers.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_ACTIVITYHELPERS_H +#define QUALITYCONTROL_ACTIVITYHELPERS_H + +#include "QualityControl/Activity.h" + +#include +#include +#include +#include +#include + +namespace o2::quality_control::core::activity_helpers +{ + +template +concept RangeOfActivities = requires(Range range) +{ + requires std::same_as, Activity>; +}; + +namespace implementation +{ +Activity commonActivityFields(const RangeOfActivities auto& activities); +} + +/// Produces the most constrained Activity which will match all the provided +Activity strictestMatchingActivity(const RangeOfActivities auto& activities) +{ + if (std::ranges::empty(activities)) { + return {}; + } + if (std::ranges::size(activities) == 1) { + return *std::ranges::begin(activities); + } + Activity result = implementation::commonActivityFields(activities); + + result.mValidity = (*std::ranges::begin(activities)).mValidity; + for (const Activity& activity : activities | std::views::drop(1)) { + const auto& validity = activity.mValidity; + result.mValidity.update(validity.getMin()); + result.mValidity.update(validity.getMax()); + } + return result; +} + +/// Produces an Activity which matches all the provided, but the validity is an intersection. +/// Be sure to check if the result validity is valid, it might not if the argument validities do not overlap +Activity overlappingActivity(const RangeOfActivities auto& activities) +{ + if (std::ranges::empty(activities)) { + return {}; + } + if (std::ranges::size(activities) == 1) { + return *std::ranges::begin(activities); + } + + Activity result = implementation::commonActivityFields(activities); + result.mValidity = (*std::ranges::begin(activities)).mValidity; + for (const Activity& activity : activities | std::views::drop(1)) { + result.mValidity = result.mValidity.getOverlap(activity.mValidity); + } + + return result; +} + +std::map asDatabaseMetadata(const core::Activity&, bool putDefault = true); +core::Activity asActivity(const std::map& metadata, const std::string& provenance = "qc"); +core::Activity asActivity(const boost::property_tree::ptree&, const std::string& provenance = "qc"); + +std::function getCcdbSorTimeAccessor(uint64_t runNumber); +std::function getCcdbEorTimeAccessor(uint64_t runNumber); + +/// \brief checks if the provided validity uses old rules, where start is creation time, end is 10 years in the future. +bool isLegacyValidity(ValidityInterval); + +bool onNumericLimit(validity_time_t timestamp); + +namespace implementation +{ + +template +void setMemberIfCommon(Activity& result, const RangeType& activities, FieldType Activity::*memberPtr) +{ + const Activity& first = *std::ranges::begin(activities); + if (std::ranges::all_of(activities | std::views::drop(1), [&](const auto& other) { return first.*memberPtr == other.*memberPtr; })) { + result.*memberPtr = first.*memberPtr; + } +} + +Activity commonActivityFields(const RangeOfActivities auto& activities) +{ + Activity result; + setMemberIfCommon(result, activities, &Activity::mId); + setMemberIfCommon(result, activities, &Activity::mType); + setMemberIfCommon(result, activities, &Activity::mPassName); + setMemberIfCommon(result, activities, &Activity::mPeriodName); + setMemberIfCommon(result, activities, &Activity::mProvenance); + setMemberIfCommon(result, activities, &Activity::mValidity); + setMemberIfCommon(result, activities, &Activity::mBeamType); + setMemberIfCommon(result, activities, &Activity::mPartitionName); + setMemberIfCommon(result, activities, &Activity::mFillNumber); + setMemberIfCommon(result, activities, &Activity::mOriginalId); + return result; +} + +} // namespace implementation +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_ACTIVITYHELPERS_H diff --git a/Framework/include/QualityControl/AdvancedWorkflow.h b/Framework/include/QualityControl/AdvancedWorkflow.h new file mode 100644 index 0000000000..f98db819be --- /dev/null +++ b/Framework/include/QualityControl/AdvancedWorkflow.h @@ -0,0 +1,31 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AdvancedWorkflow.h +/// \author Barthelemy von Haller +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_AdvancedWorkflow_H +#define QUALITYCONTROL_AdvancedWorkflow_H + +#include + +/// These methods can be used to build a complex processing topology. It spawns 3 separate dummy processing chains. + +namespace o2::quality_control::core +{ +o2::framework::WorkflowSpec getProcessingTopology(o2::framework::DataAllocator::SubSpecificationType subspec); +o2::framework::WorkflowSpec getFullProcessingTopology(); +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_AdvancedWorkflow_H diff --git a/Framework/include/QualityControl/Aggregator.h b/Framework/include/QualityControl/Aggregator.h new file mode 100644 index 0000000000..34c5f6dcd6 --- /dev/null +++ b/Framework/include/QualityControl/Aggregator.h @@ -0,0 +1,99 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Aggregator.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_CHECKER_AGGREGATOR_H +#define QC_CHECKER_AGGREGATOR_H + +// std +#include +#include +// QC +#include "QualityControl/QualityObject.h" +#include "QualityControl/AggregatorConfig.h" +#include "QualityControl/AggregatorSource.h" +#include "QualityControl/UpdatePolicyType.h" + +namespace o2::configuration +{ +class ConfigurationInterface; +} + +namespace o2::quality_control::core +{ +struct CommonSpec; +struct Activity; +} // namespace o2::quality_control::core + +namespace o2::quality_control::checker +{ + +class AggregatorInterface; +struct AggregatorSpec; + +/// \brief An aggregator as found in the configuration. +/// +/// An instance of this class represents a single aggregator as defined in the config file. +/// It is in charge of loading/instantiating it from a module, +/// configure it and execute the aggregation call on the underlying module. +class Aggregator +{ + public: + /// Constructor + /** + * \brief Aggregator constructor + * + * Create an aggregator using the provided configuration. + * + * @param configuration configuration structure of an aggregator + */ + Aggregator(AggregatorConfig configuration); + + /** + * \brief Initialize the aggregator + */ + void init(); + + o2::quality_control::core::QualityObjectsType aggregate(core::QualityObjectsMapType& qoMap, const core::Activity& defaultActivity = {}); + + const std::string& getName() const; + UpdatePolicyType getUpdatePolicyType() const; + std::vector getObjectsNames() const; + bool getAllObjectsOption() const; + std::vector getSources() const; + std::vector getSources(core::DataSourceType type); + const std::string& getDetector() const { return mAggregatorConfig.detectorName; }; + const AggregatorConfig& getConfig() const noexcept { return mAggregatorConfig; } + void startOfActivity(const core::Activity& activity); + void endOfActivity(const core::Activity& activity); + + static AggregatorConfig extractConfig(const core::CommonSpec&, const AggregatorSpec&); + + private: + /** + * Filter out the list of QualityObjects and keep only the ones that have to be aggregated by this aggregator. + * @param qoMap + * @return + */ + core::QualityObjectsMapType filter(core::QualityObjectsMapType& qoMap); + + AggregatorConfig mAggregatorConfig; + AggregatorInterface* mAggregatorInterface = nullptr; + std::vector mSources; +}; + +} // namespace o2::quality_control::checker + +#endif // QC_CHECKER_AGGREGATOR_H diff --git a/Framework/include/QualityControl/AggregatorConfig.h b/Framework/include/QualityControl/AggregatorConfig.h new file mode 100644 index 0000000000..7e928d4210 --- /dev/null +++ b/Framework/include/QualityControl/AggregatorConfig.h @@ -0,0 +1,44 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AggregatorConfig.h +/// \author Piotr Konopka +/// + +#ifndef QC_CORE_AGGREGATORCONFIG_H +#define QC_CORE_AGGREGATORCONFIG_H + +#include +#include +#include + +#include +#include "QualityControl/UpdatePolicyType.h" +#include "QualityControl/AggregatorSource.h" +#include "QualityControl/UserCodeConfig.h" + +namespace o2::quality_control::checker +{ + +/// \brief Container for the configuration of an Aggregator. +struct AggregatorConfig : public o2::quality_control::core::UserCodeConfig { + UpdatePolicyType policyType = UpdatePolicyType::OnAny; + std::vector objectNames{}; // fixme: if object names are empty, allObjects are true, consider reducing to one var // fixme: duplicates "sources" + bool allObjects = false; + framework::Inputs inputSpecs{}; + framework::OutputSpec qoSpec{ "XXX", "INVALID" }; + std::vector sources; +}; + +} // namespace o2::quality_control::checker + +#endif // QC_CORE_AGGREGATORCONFIG_H diff --git a/Framework/include/QualityControl/AggregatorInterface.h b/Framework/include/QualityControl/AggregatorInterface.h new file mode 100644 index 0000000000..c91fc1da1b --- /dev/null +++ b/Framework/include/QualityControl/AggregatorInterface.h @@ -0,0 +1,62 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AggregatorInterface.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_CHECKER_AGGREGATORINTERFACE_H +#define QC_CHECKER_AGGREGATORINTERFACE_H + +#include + +#include "QualityControl/QualityObject.h" +#include "QualityControl/UserCodeInterface.h" +#include "QualityControl/Quality.h" +#include "QualityControl/Activity.h" +#include "QualityControl/QCInputs.h" + +namespace o2::quality_control::checker +{ + +/// \brief Skeleton of a quality aggregator user algorithm. +/// +/// \author Barthelemy von Haller +class AggregatorInterface : public o2::quality_control::core::UserCodeInterface +{ + public: + /// Default constructor + AggregatorInterface() = default; + /// Destructor + virtual ~AggregatorInterface() = default; + + /// \brief Returns new qualities (usually fewer) based on the input qualities + /// + /// @param qoMap A map of the the QualityObjects to aggregate and their full names. + /// @return The new qualities, associated with a name. + virtual std::map aggregate(std::map>& qoMap); + + /// \brief Returns new qualities (usually fewer) based on the input qualities stored in Data structure + /// + /// @param data A generic data structure containing QualityObjects or possible other inputs. + /// @return The new qualities, associated with a name. + virtual std::map aggregate(const core::QCInputs& data); + + virtual void startOfActivity(const core::Activity& activity); + virtual void endOfActivity(const core::Activity& activity); + + ClassDef(AggregatorInterface, 4) +}; + +} // namespace o2::quality_control::checker + +#endif /* QC_CHECKER_AGGREGATORINTERFACE_H */ diff --git a/Framework/include/QualityControl/AggregatorRunner.h b/Framework/include/QualityControl/AggregatorRunner.h new file mode 100644 index 0000000000..4066100325 --- /dev/null +++ b/Framework/include/QualityControl/AggregatorRunner.h @@ -0,0 +1,200 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AggregatorRunner.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_CHECKER_AGGREGATORRUNNER_H +#define QC_CHECKER_AGGREGATORRUNNER_H + +// stl +#include +#include +// O2 +#include +#include +#include +// QC +#include "QualityControl/QualityObject.h" +#include "QualityControl/UpdatePolicyManager.h" +#include "QualityControl/Activity.h" +#include "QualityControl/AggregatorRunnerConfig.h" +#include "QualityControl/AggregatorConfig.h" +#include "QualityControl/Activity.h" + +namespace o2::framework +{ +struct InputSpec; +struct OutputSpec; +class DataAllocator; +} // namespace o2::framework + +namespace o2::monitoring +{ +class Monitoring; +} + +namespace o2::configuration +{ +class ConfigurationInterface; +} + +namespace o2::quality_control +{ +namespace checker +{ +class Aggregator; +} +namespace repository +{ +class DatabaseInterface; +} +} // namespace o2::quality_control + +class TClass; + +namespace o2::quality_control::checker +{ +struct AggregatorSource; + +/// \brief The class in charge of running the aggregators on the QualityObjects. +/// +/// An AggregatorRunner is the device in charge of receiving data, handling the Aggregators and +/// calling them when the data is ready to be processed. It also initializes a few services such +/// as the monitoring. +/// At the moment, the aggregatorRunner also stores these new qualities in the repository. +/// At the moment, it is also a unique process although it could easily be updated to be able to run +/// in parallel. +/// +/// \author Barthélémy von Haller +class AggregatorRunner : public framework::Task +{ + public: + /// Constructor + /** + * \brief AggregatorRunner constructor + * Create the AggregatorRunner device. + * + * @param arc AggregatorRunner Config + * @param acs Aggregator configs + */ + AggregatorRunner(AggregatorRunnerConfig arc, const std::vector& acs); + + /// Destructor + ~AggregatorRunner() override; + + /// \brief AggregatorRunner init callback + void init(framework::InitContext& ctx) override; + + /// \brief AggregatorRunner process callback + void run(framework::ProcessingContext& ctx) override; + + framework::Inputs getInputs() { return mInputs; } + framework::Outputs getOutputs() { return mOutputs; } + std::string getDeviceName() { return mDeviceName; } + const std::vector>& getAggregators() const { return mAggregators; } + + static framework::DataProcessorLabel getLabel() { return { "qc-aggregator" }; } + static std::string createAggregatorRunnerIdString() { return "qc-aggregator"; }; + static std::string createAggregatorRunnerName(); + + /// \brief Compute the detector name to be used in the infologger for this runner. + /// Compute the detector name to be used in the infologger for this runner. + /// If all checks belong to the same detector we use it, otherwise we use "MANY" + static std::string getDetectorName(const std::vector>& aggregators); + + private: + /** + * \brief For each aggregator, check if the data is ready and, if so, call its own aggregation method. + * + * For each aggregator, evaluate if data is ready (i.e. if its policy is fulfilled) and, if so, + * call its `aggregate()` method. + * This method is usually called upon reception of fresh inputs data. + */ + using QualityObjectsWithAggregatorNameVector = std::vector>; + QualityObjectsWithAggregatorNameVector aggregate(); + + /** + * \brief Store the QualityObjects in the database. + * + * @param qualityObjects QOs to be stored in DB. + */ + void store(QualityObjectsWithAggregatorNameVector& qualityObjects); + + void send(const QualityObjectsWithAggregatorNameVector&, framework::DataAllocator&); + + /** + * Prepare the inputs, remove the duplicates + */ + void prepareInputs(); + void prepareOutputs(); + + void initDatabase(); + void initMonitoring(); + void initLibraries(); + void initAggregators(); + + /** + * Reorder the aggregators stored in mAggregators. + */ + void reorderAggregators(); + + /** + * Checks whether all sources provided are already in the aggregators vector. + * The match is done by name. + * @param sources + * @param aggregators + * @return true if all sources are found, by name, in the vector of aggregators. + */ + static bool areSourcesIn(const std::vector& sources, + const std::vector>& aggregators); + + /** + * Send metrics to the monitoring system if the time has come. + */ + void sendPeriodicMonitoring(); + + /// \brief Callback for CallbackService::Id::Start (DPL) a.k.a. RUN transition (FairMQ) + void start(framework::ServiceRegistryRef services); + /// \brief Callback for CallbackService::Id::Stop (DPL) a.k.a. STOP transition (FairMQ) + void stop() override; + /// \brief Callback for CallbackService::Id::Reset (DPL) a.k.a. RESET DEVICE transition (FairMQ) + void reset(); + + // General state + std::string mDeviceName; + std::shared_ptr mActivity; // shareable with the Aggregators + std::vector> mAggregators; + std::unordered_map> mAggregatorsMap; + std::shared_ptr mDatabase; + AggregatorRunnerConfig mRunnerConfig; + std::vector mAggregatorsConfig; + core::QualityObjectsMapType mQualityObjects; // where we cache the incoming quality objects and the output of the aggregators + UpdatePolicyManager mUpdatePolicyManager; + + // DPL + o2::framework::Inputs mInputs; + o2::framework::Outputs mOutputs; + + // monitoring + std::shared_ptr mCollector; + AliceO2::Common::Timer mTimer; + AliceO2::Common::Timer mTimerTotalDurationActivity; + int mTotalNumberObjectsReceived; + int mTotalNumberAggregatorExecuted; + int mTotalNumberObjectsProduced; +}; + +} // namespace o2::quality_control::checker + +#endif // QC_CHECKER_AGGREGATORRUNNER_H diff --git a/Framework/include/QualityControl/AggregatorRunnerConfig.h b/Framework/include/QualityControl/AggregatorRunnerConfig.h new file mode 100644 index 0000000000..0cba7fabc1 --- /dev/null +++ b/Framework/include/QualityControl/AggregatorRunnerConfig.h @@ -0,0 +1,41 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AggregatorRunnerConfig.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_AGGREGATORRUNNERCONFIG_H +#define QUALITYCONTROL_AGGREGATORRUNNERCONFIG_H + +#include +#include +#include +#include "QualityControl/Activity.h" +#include "QualityControl/LogDiscardParameters.h" + +namespace o2::quality_control::checker +{ + +struct AggregatorRunnerConfig { + std::unordered_map database; + std::string consulUrl{}; + std::string monitoringUrl{}; + std::string bookkeepingUrl{}; + core::LogDiscardParameters infologgerDiscardParameters; + core::Activity fallbackActivity; + framework::Options options{}; +}; + +} // namespace o2::quality_control::checker + +#endif //QUALITYCONTROL_AGGREGATORRUNNERCONFIG_H diff --git a/Framework/include/QualityControl/AggregatorRunnerFactory.h b/Framework/include/QualityControl/AggregatorRunnerFactory.h new file mode 100644 index 0000000000..b1e3ec9466 --- /dev/null +++ b/Framework/include/QualityControl/AggregatorRunnerFactory.h @@ -0,0 +1,51 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AggregatorRunnerFactory.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_AGGREGATORRUNNERFACTORY_H +#define QC_AGGREGATORRUNNERFACTORY_H + +#include +#include + +#include "QualityControl/CommonSpec.h" +#include "QualityControl/AggregatorRunnerConfig.h" +#include "QualityControl/AggregatorConfig.h" +#include "QualityControl/AggregatorSpec.h" + +#include + +namespace o2::quality_control::checker +{ + +/// \brief Factory in charge of creating the AggregatorRunners and their corresponding DataProcessorSpec. +class AggregatorRunnerFactory +{ + public: + AggregatorRunnerFactory() = default; + virtual ~AggregatorRunnerFactory() = default; + + static framework::DataProcessorSpec create(const core::CommonSpec& commonSpec, + const std::vector& aggregatorsSpec); + static void customizeInfrastructure(std::vector& policies); + + static AggregatorRunnerConfig extractRunnerConfig(const core::CommonSpec&); + static std::vector extractAggregatorsConfig(const core::CommonSpec& commonSpec, + const std::vector& aggregatorsSpec); +}; + +} // namespace o2::quality_control::checker + +#endif // QC_AGGREGATORRUNNERFACTORY_H diff --git a/Framework/include/QualityControl/AggregatorSource.h b/Framework/include/QualityControl/AggregatorSource.h new file mode 100644 index 0000000000..b65f30fec3 --- /dev/null +++ b/Framework/include/QualityControl/AggregatorSource.h @@ -0,0 +1,37 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AggregatorSource.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_AGGREGATORSOURCE_H +#define QUALITYCONTROL_AGGREGATORSOURCE_H + +#include "QualityControl/DataSourceSpec.h" +#include +#include + +namespace o2::quality_control::checker +{ + +struct AggregatorSource { + AggregatorSource() = default; + AggregatorSource(core::DataSourceType type, std::string name) : type(type), name(std::move(name)) {} + core::DataSourceType type{ core::DataSourceType::Invalid }; + std::string name{}; + std::vector objects{}; +}; + +} // namespace o2::quality_control::checker + +#endif //QUALITYCONTROL_AGGREGATORSOURCE_H diff --git a/Framework/include/QualityControl/AggregatorSpec.h b/Framework/include/QualityControl/AggregatorSpec.h new file mode 100644 index 0000000000..54c811ee0c --- /dev/null +++ b/Framework/include/QualityControl/AggregatorSpec.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_AGGREGATORSPEC_H +#define QUALITYCONTROL_AGGREGATORSPEC_H + +/// +/// \file AggregatorSpec.h +/// \author Piotr Konopka +/// + +#include + +#include "QualityControl/DataSourceSpec.h" +#include "QualityControl/UpdatePolicyType.h" +#include "QualityControl/CustomParameters.h" + +namespace o2::quality_control::checker +{ + +/// \brief Specification of an Aggregator, which should map the JSON configuration structure. +struct AggregatorSpec { + // default, invalid spec + AggregatorSpec() = default; + + // minimal valid spec + AggregatorSpec(std::string aggregatorName, std::string className, std::string moduleName, std::string detectorName, + std::vector dataSources, UpdatePolicyType updatePolicySpec) + : aggregatorName(std::move(aggregatorName)), + className(std::move(className)), + moduleName(std::move(moduleName)), + detectorName(std::move(detectorName)), + dataSources(std::move(dataSources)), + updatePolicy(updatePolicySpec) + { + } + + // basic + std::string aggregatorName = "Invalid"; + std::string className = "Invalid"; + std::string moduleName = "Invalid"; + std::string detectorName = "DET"; + std::vector dataSources; + UpdatePolicyType updatePolicy = UpdatePolicyType::OnAny; + // advanced + bool active = true; + bool exportToBookkeeping = false; + core::CustomParameters customParameters; +}; + +} // namespace o2::quality_control::checker +#endif //QUALITYCONTROL_AGGREGATORSPEC_H diff --git a/Framework/include/QualityControl/Bookkeeping.h b/Framework/include/QualityControl/Bookkeeping.h new file mode 100644 index 0000000000..c2c05ba4ac --- /dev/null +++ b/Framework/include/QualityControl/Bookkeeping.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// @file Bookkeeping.h +/// @author Barthelemy von Haller + +#ifndef QC_CORE_BOOKKEEPING_H +#define QC_CORE_BOOKKEEPING_H + +#include +#include "BookkeepingApi/BkpClient.h" + +namespace o2::quality_control::core +{ +class Activity; + +// \brief A singleton class to handle the bookkeeping service interactions. +// +// All calls in QC code to Bookkeeping service should go through this class. +class Bookkeeping +{ + public: + static Bookkeeping& getInstance() + { + static Bookkeeping instance; + return instance; + } + + // disable non-static + Bookkeeping& operator=(const Bookkeeping&) = delete; + Bookkeeping(const Bookkeeping&) = delete; + + void init(const std::string& url); + void registerProcess(int runNumber, const std::string& name, const std::string& detector, bkp::DplProcessType type, const std::string& args); + + // send QC flags to the bookkeeping service + std::vector sendFlagsForSynchronous(uint32_t runNumber, const std::string& detectorName, const std::vector& qcFlags); + std::vector sendFlagsForDataPass(uint32_t runNumber, const std::string& passName, const std::string& detectorName, const std::vector& qcFlags); + std::vector sendFlagsForSimulationPass(uint32_t runNumber, const std::string& productionName, const std::string& detectorName, const std::vector& qcFlags); + + private: + Bookkeeping() = default; + + bool mInitialized = false; + std::string mUrl; + std::unique_ptr mClient; +}; + +} // namespace o2::quality_control::core + +#endif diff --git a/Framework/include/QualityControl/BookkeepingQualitySink.h b/Framework/include/QualityControl/BookkeepingQualitySink.h new file mode 100644 index 0000000000..485b124dd2 --- /dev/null +++ b/Framework/include/QualityControl/BookkeepingQualitySink.h @@ -0,0 +1,66 @@ +// Copyright 2024 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file BookkeepingQualitySink.h +/// \author Michal Tichak +/// + +#ifndef QUALITYCONTROL_BOOKKEEPINGQUALITYSINK_H +#define QUALITYCONTROL_BOOKKEEPINGQUALITYSINK_H + +#include +#include +#include +#include "QualityControl/QualitiesToFlagCollectionConverter.h" +#include "QualityControl/Provenance.h" + +namespace o2::quality_control::core +{ + +// This class gathers all QualityObjects from it's inputs, converting them to flags + sending them to grpc RCT/BKP when stop() is invoked +class BookkeepingQualitySink : public framework::Task +{ + public: + // we are using map here instead of the set, because items in the map are changeable, however items of the set are not. + using FlagsMap = std::unordered_map>>; + using SendCallback = std::function; + + // sendCallback is mainly used for testing without the necessity to do grpc calls + BookkeepingQualitySink(const std::string& grpcUri, Provenance, SendCallback sendCallback = send); + + void run(framework::ProcessingContext&) override; + void init(framework::InitContext& iCtx) override; + + void endOfStream(framework::EndOfStreamContext& context) override; + void stop() override; + + static void customizeInfrastructure(std::vector& policies); + static framework::DataProcessorLabel getLabel() { return { "BookkeepingQualitySink" }; } + static void send(const std::string& grpcUri, const FlagsMap&, Provenance); + + private: + /// \brief Callback for CallbackService::Id::Start (DPL) a.k.a. RUN transition (FairMQ) + void start(framework::ServiceRegistryRef services); + + std::string mGrpcUri; + Provenance mProvenance; + SendCallback mSendCallback; + FlagsMap mFlagsMap; + + void sendAndClear(); +}; + +} // namespace o2::quality_control::core + +#endif diff --git a/Framework/include/QualityControl/CcdbDatabase.h b/Framework/include/QualityControl/CcdbDatabase.h index 0e3311c967..94d876d1de 100644 --- a/Framework/include/QualityControl/CcdbDatabase.h +++ b/Framework/include/QualityControl/CcdbDatabase.h @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -16,8 +17,16 @@ #ifndef QC_REPOSITORY_CCDBDATABASE_H #define QC_REPOSITORY_CCDBDATABASE_H -#include "CCDB/CcdbApi.h" #include "QualityControl/DatabaseInterface.h" +#include +#include +#include +#include + +namespace o2::ccdb +{ +class CcdbApi; +} namespace o2::quality_control::repository { @@ -52,30 +61,114 @@ class CcdbDatabase : public DatabaseInterface CcdbDatabase(); virtual ~CcdbDatabase(); - void connect(std::string host, std::string database, std::string username, std::string password) override; + void connect(const std::string& host, const std::string& database, const std::string& username, const std::string& password) override; void connect(const std::unordered_map& config) override; - void store(std::shared_ptr mo) override; - core::MonitorObject* retrieve(std::string taskName, std::string objectName) override; - std::string retrieveJson(std::string taskName, std::string objectName) override; + + // storage + void storeMO(std::shared_ptr q) override; + void storeQO(std::shared_ptr q) override; + void storeAny(const void* obj, std::type_info const& typeInfo, std::string const& path, std::map const& metadata, + std::string const& detectorName, std::string const& taskName, long from = -1, long to = -1) override; + + void* retrieveAny(std::type_info const& tinfo, std::string const& path, + std::map const& metadata, long timestamp = Timestamp::Current, + std::map* headers = nullptr, + const std::string& createdNotAfter = "", const std::string& createdNotBefore = "") override; + + // retrieval - MO - deprecated + std::shared_ptr retrieveMO(std::string objectPath, std::string objectName, + long timestamp = Timestamp::Current, + const core::Activity& activity = {}, + const std::map& metadata = {}) override; + // retrieval - QO - deprecated + std::shared_ptr retrieveQO(std::string qoPath, long timestamp = Timestamp::Current, + const core::Activity& activity = {}, + const std::map& metadata = {}) override; + + // retrieval - general + std::string retrieveJson(std::string path, long timestamp, const std::map& metadata) override; + TObject* retrieveTObject(std::string path, const std::map& metadata, long timestamp = Timestamp::Current, std::map* headers = nullptr) override; + void disconnect() override; void prepareTaskDataContainer(std::string taskName) override; - std::vector getListOfTasksWithPublications() override; std::vector getPublishedObjectNames(std::string taskName) override; - void truncate(std::string taskName, std::string objectName) override; + void truncate(std::string path, std::string objectName) override; + static long getCurrentTimestamp(); + static long getFutureTimestamp(int secondsInFuture); + /** + * Return the listing of folder and/or objects in the subpath. + * @param subpath The folder we want to list the children of. + * @return The listing of folder and/or objects at the subpath. + */ + std::vector getListing(const std::string& subpath = ""); + + /** + * Return the listing of folder and/or objects in the subpath + * @param path the folder we want to list the children of. + * @param metadata metadata to filter queried objects. + * @param latestOnly return only the latest object matching the path and metadata. + * @return The list of folder and/or objects as Ptree + */ + boost::property_tree::ptree getListingAsPtree(const std::string& path, const std::map& metadata = {}, bool latestOnly = false); + + /** + * Return validity of the latest matching object + * @param path the folder we want to list the children of. + * @param metadata metadata to filter queried objects. + * @return validity of the latest matching object + */ + core::ValidityInterval getLatestObjectValidity(const std::string& path, const std::map& metadata) override; + + /** + * \brief Returns a vector of all 'valid from' timestamps for an object. + * \path Path on an object. + * \return A vector of all 'valid from' timestamps for an object in non-descending order. + */ + std::vector getTimestampsForObject(const std::string& path); + + void setMaxObjectSize(size_t maxObjectSize) override; private: - long getCurrentTimestamp(); - std::string getTimestampString(long timestamp); - long getFutureTimestamp(int secondsInFuture); + void init(); + /** * Return the listing of folder and/or objects in the subpath. * @param subpath The folder we want to list the children of. * @param accept The format of the returned string as an \"Accept\", i.e. text/plain, application/json, text/xml + * @param latestOnly Return only the latest matching object/folder * @return The listing of folder and/or objects in the format requested and as returned by the http server. */ - std::string getListing(std::string subpath = "", std::string accept = "text/plain"); - o2::ccdb::CcdbApi ccdbApi; + std::string getListingAsString(const std::string& subpath = "", const std::string& accept = "text/plain", bool latestOnly = false); + + /** + * Takes care of the possible errors returned by the storage calls. + * @param path + * @param result + */ + void handleStorageError(const std::string& path, int result); + + /** + * Check whether the database has encountered a failure previously and if we are still in the + * period afterwards when no attempt should be done. + * @return + */ + bool isDbInFailure(); + + /** + * Add the metadata specific to the QC framework. + * @param fullMetadata + * @param detectorName + * @param taskName + * @param className + */ + static void addFrameworkMetadata(std::map& fullMetadata, std::string detectorName, std::string className); + + std::unique_ptr ccdbApi; std::string mUrl; + size_t mMaxObjectSize = 2097152; // 2MB by default + int mFailureDelay = 60; // 60 seconds delay between attempts to store things in the database + bool mDatabaseFailure = false; + AliceO2::Common::Timer mFailureTimer; }; } // namespace o2::quality_control::repository diff --git a/Framework/include/QualityControl/Check.h b/Framework/include/QualityControl/Check.h new file mode 100644 index 0000000000..c53eede8ba --- /dev/null +++ b/Framework/include/QualityControl/Check.h @@ -0,0 +1,93 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_CHECKER_CHECK_H +#define QC_CHECKER_CHECK_H + +// std +#include +#include +#include +// O2 +#include +// QC +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/QualityObject.h" +#include "QualityControl/CheckConfig.h" +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control::core +{ +class MonitorObject; +class Quality; +struct CommonSpec; +class Activity; +} // namespace o2::quality_control::core + +namespace o2::quality_control::checker +{ +class CheckInterface; +struct CheckSpec; + +/// \brief The class in charge of providing single check for a given map of MonitorObjects. +/// +/// A Check is in charge of loading/instantiating the single check from module, configure them and manage the check process: +/// shadow not required MonitorObjects, invoke beautify process if needed. +/// +/// \author Rafal Pacholek +class Check +{ + public: + /// Constructor + /** + * \brief Check constructor + * + * Create Check that will load single check from a module and run if invoked. + * + * @param checkName Check name from the configuration + * @param configurationSource Path to configuration + */ + Check(CheckConfig config); + + /** + * \brief Initialize the check state + * Expected to run in the init phase of the FairDevice + */ + void init(); + void reset(); + + core::QualityObjectsType check(std::map>& moMap); + + const std::string& getName() const { return mCheckConfig.name; }; + o2::framework::OutputSpec getOutputSpec() const { return mCheckConfig.qoSpec; }; + o2::framework::Inputs getInputs() const { return mCheckConfig.inputSpecs; }; + const std::string& getDetector() const { return mCheckConfig.detectorName; }; + const CheckConfig& getConfig() const { return mCheckConfig; }; + void startOfActivity(const core::Activity& activity); + void endOfActivity(const core::Activity& activity); + + UpdatePolicyType getUpdatePolicyType() const; + std::vector getObjectsNames() const; + bool getAllObjectsOption() const; + + // todo: probably make CheckFactory + static CheckConfig extractConfig(const core::CommonSpec&, const CheckSpec&); + + private: + void beautify(std::map>& moMap, const core::Quality& quality); + + CheckConfig mCheckConfig; + CheckInterface* mCheckInterface = nullptr; +}; + +} // namespace o2::quality_control::checker + +#endif diff --git a/Framework/include/QualityControl/CheckConfig.h b/Framework/include/QualityControl/CheckConfig.h new file mode 100644 index 0000000000..02f56eb202 --- /dev/null +++ b/Framework/include/QualityControl/CheckConfig.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckConfig.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_CORE_CHECKCONFIG_H +#define QC_CORE_CHECKCONFIG_H + +#include +#include + +#include +#include "QualityControl/UpdatePolicyType.h" +#include "QualityControl/UserCodeConfig.h" + +namespace o2::quality_control::checker +{ + +/// \brief Container for the configuration of a Check. +struct CheckConfig : public o2::quality_control::core::UserCodeConfig { + UpdatePolicyType policyType = UpdatePolicyType::OnAny; + std::vector objectNames{}; // fixme: if object names are empty, allObjects are true, consider reducing to one var + bool allObjects = false; + bool allowBeautify = false; + framework::Inputs inputSpecs{}; + framework::OutputSpec qoSpec{ "XXX", "INVALID" }; +}; + +} // namespace o2::quality_control::checker + +#endif // QC_CORE_CHECKCONFIG_H diff --git a/Framework/include/QualityControl/CheckInterface.h b/Framework/include/QualityControl/CheckInterface.h index 1d39d36315..16d8ac05ab 100644 --- a/Framework/include/QualityControl/CheckInterface.h +++ b/Framework/include/QualityControl/CheckInterface.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file CheckInterface.h /// \author Barthelemy von Haller @@ -6,8 +17,19 @@ #ifndef QC_CHECKER_CHECKINTERFACE_H #define QC_CHECKER_CHECKINTERFACE_H -#include "QualityControl/MonitorObject.h" +#include "QualityControl/DatabaseInterface.h" #include "QualityControl/Quality.h" +#include "QualityControl/UserCodeInterface.h" +#include "QualityControl/Activity.h" + +#include "QualityControl/QCInputs.h" + +namespace o2::quality_control::core +{ +class Activity; +class MonitorObject; + +} // namespace o2::quality_control::core using namespace o2::quality_control::core; @@ -16,13 +38,8 @@ namespace o2::quality_control::checker /// \brief Skeleton of a check. /// -/// Developer note (BvH) : the class is stateless and should stay so as we want to reuse the -/// same object several times in a row and call the methods in whatever order. -/// One could think that the methods should be static but then we would not be able -/// to use polymorphism. -/// /// \author Barthelemy von Haller -class CheckInterface +class CheckInterface : public core::UserCodeInterface { public: /// Default constructor @@ -30,19 +47,19 @@ class CheckInterface /// Destructor virtual ~CheckInterface() = default; - /// \brief Configure the check based on its name. + /// \brief Returns the quality associated with these objects. + /// \deprecated This function won't be deleted in future releases for compatibility reasons but users should + /// use check(const Data&) for any new Checks. /// - /// The configuration of the object can't be done in the constructor because - /// ROOT needs an argument-less constructor when streaming it. We use this method - /// to configure the object. The name might be used to ask the configuration system - /// for specific parameters. - virtual void configure(std::string name) = 0; + /// @param moMap A map of the the MonitorObjects to check and their full names (i.e. /) as keys. + /// @return The quality associated with these objects. + virtual core::Quality check(std::map>* moMap); - /// \brief Returns the quality associated with this object. + /// \brief Returns the quality associated with these objects. /// - /// @param mo The MonitorObject to check. - /// @return The quality of the object. - virtual Quality check(const MonitorObject* mo) = 0; + /// @param data An object with any type of data possible accesible via full names (i.e. / in case of MOs) as keys. + /// @return The quality associated with these objects. + virtual core::Quality check(const core::QCInputs& data); /// \brief Modify the aspect of the plot. /// @@ -53,24 +70,32 @@ class CheckInterface /// @param checkResult The quality returned by the check. It is not the same as the quality of the mo /// as the latter represents the combination of all the checks the mo passed. This /// parameter is to be used to pass the result of the check of the same class. - virtual void beautify(MonitorObject* mo, Quality checkResult = Quality::Null) = 0; + virtual void beautify(std::shared_ptr mo, core::Quality checkResult) = 0; - /// \brief Returns the name of the class that can be treated by this check. - /// - /// The name of the class returned by this method will be checked against the MonitorObject's encapsulated - /// object's class. If it is the same or a parent then the check will be applied. Therefore, this method - /// must return the highest class in the hierarchy that this check can use. - /// If the class does not override it, we return "TObject". + /// \brief Reset the state of this Check. /// - /// \author Barthelemy von Haller - virtual std::string getAcceptedType(); + /// This method should reset the state, if any, of the Check implemented here. + /// It will typically be called in between runs. + /// For example, if you have counters or you keep the state of an object from one call to the other, + /// then this should be reset here. + virtual void reset(); // not fully abstract because we don't want to change all the existing subclasses - bool isObjectCheckable(const MonitorObject* mo); + virtual void startOfActivity(const core::Activity& activity); // not fully abstract because we don't want to change all the existing subclasses + virtual void endOfActivity(const core::Activity& activity); // not fully abstract because we don't want to change all the existing subclasses - // private: - // std::string mName; + protected: + /// \brief Called each time mCustomParameters is updated. + virtual void configure() override; + + /// \brief Retrieve a reference plot at the provided path, matching the give activity and for the provided run. + /// the activity is the current one, while the run number is the reference run. + /// + /// \param path path to the object (no provenance) + /// \param referenceActivity Reference activity (usually a copy of the current activity with a different run number) + /// \return + std::shared_ptr retrieveReference(std::string path, Activity referenceActivity); - ClassDef(CheckInterface, 1) + ClassDef(CheckInterface, 6) }; } // namespace o2::quality_control::checker diff --git a/Framework/include/QualityControl/CheckRunner.h b/Framework/include/QualityControl/CheckRunner.h new file mode 100644 index 0000000000..80c281ab8b --- /dev/null +++ b/Framework/include/QualityControl/CheckRunner.h @@ -0,0 +1,252 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRunner.h +/// \author Barthelemy von Haller +/// \author Piotr Konopka +/// + +#ifndef QC_CHECKER_CHECKRUNNER_H +#define QC_CHECKER_CHECKRUNNER_H + +// std & boost +#include +#include +#include +#include +#include +#include +// O2 +#include +#include +#include +// QC +#include "QualityControl/Activity.h" +#include "QualityControl/CheckRunnerConfig.h" +#include "QualityControl/Check.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QualityObject.h" +#include "QualityControl/UpdatePolicyManager.h" + +namespace o2::quality_control::core +{ +class ServiceDiscovery; +} + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::framework +{ +struct InputSpec; +struct OutputSpec; +class DataAllocator; +} // namespace o2::framework + +namespace o2::monitoring +{ +class Monitoring; +} + +class TClass; + +// todo: do not expose other namespaces in headers +using namespace o2::quality_control::core; + +namespace o2::quality_control::checker +{ + +/// \brief The class in charge of running the checks on a MonitorObject. +/// +/// A CheckRunner is in charge of loading/instantiating the proper checks for a given MonitorObject, to configure them +/// and to run them on the MonitorObjects in order to generate a quality. +/// At the moment, a checker also stores quality in the repository. +/// +/// TODO Evaluate whether we should have a dedicated device to store in the database. +/// +/// \author Barthélémy von Haller +class CheckRunner : public framework::Task +{ + public: + /// Constructor + /** + * \brief CheckRunner constructor + * + * Create CheckRunner device that will perform check operation with defineds checks. + * Depending on the constructor, it can be a single check device or a group check device. + * Group check assumes that the input of the checks is the same! + * + * @param checkRunnerConfig configuration of CheckRunner + * @param checkConfigs configuration of all Checks that should run in this data processor + */ + CheckRunner(CheckRunnerConfig, const std::vector& checkConfigs); + + /** + * \brief CheckRunner constructor + * + * Create a sink for the Input. It is expected to receive Monitor Object to store. + * It will not run any checks on a given input. + * + * @param checkRunnerConfig configuration of CheckRunner + * @param input Monitor Object input spec. + */ + CheckRunner(CheckRunnerConfig, o2::framework::InputSpec input); + + /// Destructor + ~CheckRunner() override; + + /// \brief CheckRunner init callback + void init(framework::InitContext& ctx) override; + + /// \brief CheckRunner process callback + void run(framework::ProcessingContext& ctx) override; + + /// \brief Callback for CallbackService::Id::EndOfStream + void endOfStream(framework::EndOfStreamContext& eosContext) override; + + framework::Inputs getInputs() { return mInputs; }; + framework::Outputs getOutputs() { return mOutputs; }; + + void setTaskStoreSet(std::unordered_set storeSet) { mInputStoreSet = storeSet; } + std::string getDeviceName() { return mDeviceName; }; + + static framework::DataProcessorLabel getCheckRunnerLabel() { return { "qc-check" }; } + static std::string createCheckRunnerIdString() { return "qc-check"; }; + static std::string createCheckRunnerName(const std::vector& checks); + static std::string createSinkCheckRunnerName(o2::framework::InputSpec input); + static std::string createCheckRunnerFacility(std::string deviceName); + + /// \brief Compute the detector name to be used for this checkrunner. + /// Compute the detector name to be used for this checkrunner. + /// If all checks belong to the same detector we use it, otherwise we use "MANY" + static std::string getDetectorName(const std::vector checks); + + private: + /** + * \brief Evaluate the quality of a MonitorObject. + * + * The Check's associated with this MonitorObject are run and a global quality is built by + * taking the worse quality encountered. The MonitorObject is modified by setting its quality + * and by calling the "beautifying" methods of the Check's. + * + * @param mo The MonitorObject to evaluate and whose quality will be set according + * to the worse quality encountered while running the Check's. + */ + QualityObjectsType check(); + + /** + * \brief Store the QualityObjects in the database. + * + * @param qualityObjects QOs to be stored in DB. + */ + void store(QualityObjectsType& qualityObjects, long validFrom); + + /** + * \brief Store the MonitorObjects in the database. + * + * @param monitorObjects MOs to be stored in DB. + */ + void store(std::vector>& monitorObjects, long validFrom); + + /** + * \brief Send the QualityObjects on the DataProcessor output channel. + */ + void send(QualityObjectsType& qualityObjects, framework::DataAllocator& allocator); + + /** + * \brief Collect input specs from Checks + * + * \param checks List of all checks + */ + static o2::framework::Outputs collectOutputs(const std::vector& checks); + + void initDatabase(); + void initMonitoring(); + void initServiceDiscovery(); + void initLibraries(); + + /** + * Update the list of objects this TaskRunner is sending out. + * @param qualityObjects + */ + void updateServiceDiscovery(const QualityObjectsType& qualityObjects); + + /** + * \brief BSD checksum algorithm. + * + * \param input_string String intended to be hashed + */ + static std::size_t hash(const std::string& inputString); + + /** + * \brief Massage/Prepare data from the Context and store it in the cache. + * When data is received it can be 1. a TObjArray filled with MonitorObjects, + * 2. a TObjArray filled with TObjects or 3. a TObject. The two latter happen + * in case an external device is sending the data. + * This method first transform the data in order to have a TObjArray of MonitorObjects. + * It then stores these objects in the cache. + * @param ctx + */ + void prepareCacheData(framework::InputRecord& inputRecord); + /** + * Send metrics to the monitoring system if the time has come. + */ + void sendPeriodicMonitoring(); + + /// \brief Callback for CallbackService::Id::Start (DPL) a.k.a. RUN transition (FairMQ) + void start(framework::ServiceRegistryRef services); + /// \brief Callback for CallbackService::Id::Stop (DPL) a.k.a. STOP transition (FairMQ) + void stop() override; + /// \brief Callback for CallbackService::Id::Reset (DPL) a.k.a. RESET DEVICE transition (FairMQ) + void reset(); + + // General state + std::string mDeviceName; + std::map mChecks; + std::string mDetectorName; + std::shared_ptr mActivity; // shareable with the Checks + CheckRunnerConfig mConfig; + std::shared_ptr mDatabase; + std::unordered_set mInputStoreSet; + std::vector> mMonitorObjectStoreVector; + UpdatePolicyManager updatePolicyManager; + bool mReceivedEOS = false; + + // DPL + o2::framework::Inputs mInputs; + o2::framework::Outputs mOutputs; + + // Checks cache + std::map> mMonitorObjects; + + // Service discovery + std::shared_ptr mServiceDiscovery; + std::unordered_set mListAllQOPaths; // store the names of all the QOs the Checks have generated so far + + // monitoring + std::shared_ptr mCollector; + int mTotalNumberObjectsReceived; + int mTotalNumberCheckExecuted; + int mTotalNumberQOStored; + int mTotalNumberMOStored; + int mTotalQOSent; + int mNumberQOStored = 0; // since the last publication of the monitoring data + int mNumberMOStored = 0; // since the last publication of the monitoring data + AliceO2::Common::Timer mTimer; + AliceO2::Common::Timer mTimerTotalDurationActivity; +}; + +} // namespace o2::quality_control::checker + +#endif // QC_CHECKER_CHECKRUNNER_H diff --git a/Framework/include/QualityControl/CheckRunnerConfig.h b/Framework/include/QualityControl/CheckRunnerConfig.h new file mode 100644 index 0000000000..3b2cb90233 --- /dev/null +++ b/Framework/include/QualityControl/CheckRunnerConfig.h @@ -0,0 +1,40 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRunnerConfig.h +/// \author Piotr Konopka +/// +#ifndef QUALITYCONTROL_CHECKRUNNERCONFIG_H +#define QUALITYCONTROL_CHECKRUNNERCONFIG_H + +#include +#include + +#include "QualityControl/Activity.h" +#include "QualityControl/LogDiscardParameters.h" + +namespace o2::quality_control::checker +{ + +struct CheckRunnerConfig { + std::unordered_map database; + std::string consulUrl{}; + std::string monitoringUrl{}; + std::string bookkeepingUrl{}; + core::LogDiscardParameters infologgerDiscardParameters; + core::Activity fallbackActivity; + framework::Options options{}; +}; + +} // namespace o2::quality_control::checker + +#endif //QUALITYCONTROL_CHECKRUNNERCONFIG_H diff --git a/Framework/include/QualityControl/CheckRunnerFactory.h b/Framework/include/QualityControl/CheckRunnerFactory.h new file mode 100644 index 0000000000..ff3fba1859 --- /dev/null +++ b/Framework/include/QualityControl/CheckRunnerFactory.h @@ -0,0 +1,65 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRunnerFactory.h +/// \author Piotr Konopka +/// + +#ifndef QC_CHECKRUNNERFACTORY_H +#define QC_CHECKRUNNERFACTORY_H + +#include + +#include "Framework/DataProcessorSpec.h" +#include +#include "QualityControl/Check.h" +#include "QualityControl/CheckRunnerConfig.h" + +namespace o2::framework +{ +struct DataProcessorSpec; +} + +namespace o2::quality_control::core +{ +struct CommonSpec; +} +namespace o2::quality_control::checker +{ + +/// \brief Factory in charge of creating DataProcessorSpec of QC CheckRunner +class CheckRunnerFactory +{ + public: + CheckRunnerFactory() = default; + virtual ~CheckRunnerFactory() = default; + + static framework::DataProcessorSpec create(CheckRunnerConfig checkRunnerConfig, const std::vector& checkConfigs, std::vector storeVector = {}); + + /* + * \brief Create a CheckRunner sink DPL device. + * + * The purpose of this device is to receive and store the MO from task. + * + * @param input InputSpec with the content to store + * @param configurationSource + */ + static framework::DataProcessorSpec createSinkDevice(const CheckRunnerConfig& checkRunnerConfig, const o2::framework::InputSpec& input); + + static CheckRunnerConfig extractConfig(const core::CommonSpec&); + + static void customizeInfrastructure(std::vector& policies); +}; + +} // namespace o2::quality_control::checker + +#endif // QC_CHECKRUNNERFACTORY_H diff --git a/Framework/include/QualityControl/CheckSpec.h b/Framework/include/QualityControl/CheckSpec.h new file mode 100644 index 0000000000..b4cfada422 --- /dev/null +++ b/Framework/include/QualityControl/CheckSpec.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_CHECKSPEC_H +#define QUALITYCONTROL_CHECKSPEC_H + +/// +/// \file CheckSpec.h +/// \author Piotr Konopka +/// + +#include + +#include "QualityControl/DataSourceSpec.h" +#include "QualityControl/UpdatePolicyType.h" +#include "QualityControl/CustomParameters.h" + +namespace o2::quality_control::checker +{ + +/// \brief Specification of a Check, which should map the JSON configuration structure. +struct CheckSpec { + // default, invalid spec + CheckSpec() = default; + + // minimal valid spec + CheckSpec(std::string checkName, std::string className, std::string moduleName, std::string detectorName, + std::vector dataSources, UpdatePolicyType updatePolicySpec) + : checkName(std::move(checkName)), + className(std::move(className)), + moduleName(std::move(moduleName)), + detectorName(std::move(detectorName)), + dataSources(std::move(dataSources)), + updatePolicy(updatePolicySpec) + { + } + + // basic + std::string checkName = "Invalid"; + std::string className = "Invalid"; + std::string moduleName = "Invalid"; + std::string detectorName = "DET"; + std::vector dataSources; + UpdatePolicyType updatePolicy = UpdatePolicyType::OnAny; + // advanced + bool active = true; + bool exportToBookkeeping = false; + core::CustomParameters customParameters; +}; + +} // namespace o2::quality_control::checker + +#endif // QUALITYCONTROL_CHECKSPEC_H diff --git a/Framework/include/QualityControl/Checker.h b/Framework/include/QualityControl/Checker.h deleted file mode 100644 index 43e53b38ae..0000000000 --- a/Framework/include/QualityControl/Checker.h +++ /dev/null @@ -1,126 +0,0 @@ -/// -/// \file Checker.h -/// \author Barthelemy von Haller -/// \author Piotr Konopka -/// - -#ifndef QC_CHECKER_CHECKER_H -#define QC_CHECKER_CHECKER_H - -// std & boost -#include -#include -// O2 -#include -#include -#include -#include -// QC -#include "QualityControl/CheckInterface.h" -#include "QualityControl/DatabaseInterface.h" -#include "QualityControl/MonitorObject.h" -#include "QualityControl/QcInfoLogger.h" - -namespace o2::quality_control::checker -{ - -/// \brief The class in charge of running the checks on a MonitorObject. -/// -/// A Checker is in charge of loading/instantiating the proper checks for a given MonitorObject, to configure them -/// and to run them on the MonitorObject in order to generate a quality. At the moment, a checker also stores the MO -/// and its quality in the repository. -/// -/// TODO Evaluate whether we should have a dedicated device to store in the database. -/// -/// \author Barthélémy von Haller -class Checker : public framework::Task -{ - public: - /// Constructor - Checker(std::string checkerName, std::string taskName, std::string configurationSource); - - /// Destructor - ~Checker() override; - - /// \brief Checker init callback - void init(framework::InitContext& ctx) override; - - /// \brief Checker process callback - void run(framework::ProcessingContext& ctx) override; - - framework::InputSpec getInputSpec() { return mInputSpec; }; - - framework::OutputSpec getOutputSpec() { return mOutputSpec; }; - - /// \brief Unified DataDescription naming scheme for all checkers - static o2::header::DataDescription createCheckerDataDescription(const std::string taskName); - - private: - /** - * \brief Evaluate the quality of a MonitorObject. - * - * The Check's associated with this MonitorObject are run and a global quality is built by - * taking the worse quality encountered. The MonitorObject is modified by setting its quality - * and by calling the "beautifying" methods of the Check's. - * - * @param mo The MonitorObject to evaluate and whose quality will be set according - * to the worse quality encountered while running the Check's. - */ - void check(std::shared_ptr mo); - - /** - * \brief Store the MonitorObject in the database. - * - * @param mo The MonitorObject to be stored in the database. - */ - void store(std::shared_ptr mo); - - /** - * \brief Send the MonitorObject on FairMQ to whoever is listening. - */ - void send(std::unique_ptr& mo, framework::DataAllocator& allocator); - - /** - * \brief Load a library. - * Load a library if it is not already in the cache. - * \param libraryName The name of the library to load. - */ - void loadLibrary(const std::string libraryName); - - /** - * Get the check specified by its name and class. - * If it has never been asked for before it is instantiated and cached. There can be several copies - * of the same check but with different names in order to have them configured differently. - * @todo Pass the name of the task that will use it. It will help with getting the correct configuration. - * @param checkName - * @param className - * @return the check object - */ - CheckInterface* getCheck(std::string checkName, std::string className); - - // General state - std::string mCheckerName; - std::string mConfigurationSource; - o2::quality_control::core::QcInfoLogger& mLogger; - std::shared_ptr mDatabase; - - // DPL - o2::framework::InputSpec mInputSpec; - o2::framework::OutputSpec mOutputSpec; - - // Checks cache - std::vector mLibrariesLoaded; - std::map mChecksLoaded; - std::map mClassesLoaded; - - // monitoring - std::shared_ptr mCollector; - std::chrono::system_clock::time_point startFirstObject; - std::chrono::system_clock::time_point endLastObject; - int mTotalNumberHistosReceived; - AliceO2::Common::Timer timer; -}; - -} // namespace o2::quality_control::checker - -#endif // QC_CHECKER_CHECKER_H diff --git a/Framework/include/QualityControl/CheckerFactory.h b/Framework/include/QualityControl/CheckerFactory.h deleted file mode 100644 index e42c9b4ec2..0000000000 --- a/Framework/include/QualityControl/CheckerFactory.h +++ /dev/null @@ -1,25 +0,0 @@ -/// -/// \file CheckerFactory.h -/// \author Piotr Konopka -/// -#ifndef QC_CHECKERFACTORY_H -#define QC_CHECKERFACTORY_H - -#include "Framework/DataProcessorSpec.h" - -namespace o2::quality_control::checker -{ - -/// \brief Factory in charge of creating DataProcessorSpec of QC Checker -class CheckerFactory -{ - public: - CheckerFactory() = default; - virtual ~CheckerFactory() = default; - - framework::DataProcessorSpec create(std::string checkerName, std::string taskName, std::string configurationSource); -}; - -} // namespace o2::quality_control::checker - -#endif // QC_CHECKERFACTORY_H diff --git a/Framework/include/QualityControl/CommonSpec.h b/Framework/include/QualityControl/CommonSpec.h new file mode 100644 index 0000000000..3beb320820 --- /dev/null +++ b/Framework/include/QualityControl/CommonSpec.h @@ -0,0 +1,54 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_COMMONSPEC_H +#define QUALITYCONTROL_COMMONSPEC_H + +/// +/// \file CommonSpec.h +/// \author Piotr Konopka +/// + +#include +#include +#include +#include "QualityControl/LogDiscardParameters.h" + +namespace o2::quality_control::core +{ + +struct CommonSpec { + CommonSpec() = default; + + std::unordered_map database; + int activityNumber{}; + std::string activityType = "NONE"; + std::string activityPeriodName; + std::string activityPassName; + std::string activityProvenance = "qc"; + uint64_t activityStart = 0; + uint64_t activityEnd = -1; + std::string activityBeamType; + std::string activityPartitionName; + int activityFillNumber = 0; + int activityOriginalNumber = 0; + std::string monitoringUrl = "infologger:///debug?qc"; + std::string consulUrl; + std::string conditionDBUrl = "http://ccdb-test.cern.ch:8080"; + LogDiscardParameters infologgerDiscardParameters; + double postprocessingPeriod = 30.0; + std::string bookkeepingUrl; + std::string kafkaBrokersUrl; + std::string kafkaTopicAliECSRun = "aliecs.run"; +}; + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_COMMONSPEC_H diff --git a/Framework/include/QualityControl/ConditionAccess.h b/Framework/include/QualityControl/ConditionAccess.h new file mode 100644 index 0000000000..e1ed6346f0 --- /dev/null +++ b/Framework/include/QualityControl/ConditionAccess.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_CONDITIONACCESS_H +#define QUALITYCONTROL_CONDITIONACCESS_H + +#include +#include +#include + +namespace o2::quality_control::core +{ + +class ConditionAccess +{ + public: + /// Default constructor + ConditionAccess() = default; + /// Destructor + virtual ~ConditionAccess() = default; + + void setCcdbUrl(const std::string& url) + { + o2::ccdb::BasicCCDBManager::instance().setURL(url); + } + + /** + * Get an object from the CCDB. The object is owned by the CCDBManager, don't delete it ! + */ + template + T* retrieveConditionAny(std::string const& path, std::map const& metadata = {}, long timestamp = -1); +}; + +template +T* ConditionAccess::retrieveConditionAny(std::string const& path, std::map const& metadata, long timestamp) +{ + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); + mgr.setFatalWhenNull(false); + mgr.setTimestamp(timestamp); + return mgr.getSpecific(path, mgr.getTimestamp(), metadata); +} + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_CONDITIONACCESS_H diff --git a/Framework/include/QualityControl/ConfigParamGlo.h b/Framework/include/QualityControl/ConfigParamGlo.h new file mode 100644 index 0000000000..e8f50072fa --- /dev/null +++ b/Framework/include/QualityControl/ConfigParamGlo.h @@ -0,0 +1,29 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_CONFIGPARAMGLO_H +#define QUALITYCONTROL_CONFIGPARAMGLO_H + +#include + +// Quick and dirty way to pass the ConfigurableParam string parsed as a global +// option of the o2-qc workflow to device init methods + +namespace o2::quality_control +{ + +struct ConfigParamGlo { + static std::string keyValues; +}; + +} // namespace o2::quality_control + +#endif diff --git a/Framework/include/QualityControl/CustomParameters.h b/Framework/include/QualityControl/CustomParameters.h new file mode 100644 index 0000000000..bb0d251903 --- /dev/null +++ b/Framework/include/QualityControl/CustomParameters.h @@ -0,0 +1,212 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CustomParameters.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_CUSTOM_PARAMETERS_H +#define QC_CUSTOM_PARAMETERS_H + +#include "QualityControl/Activity.h" + +#include +#include +#include +#include + +namespace o2::quality_control::core +{ + +/** + * This class represents the parameters provided by the users in their config file. + * A value can be defined for a specific run type and/or beam type. It can also be set for any + * run type or beam type, in such a case we use the special runType and beamType `default`. + * We expect the strings runType and beamType to correspond to what is provided by the Bookkeeping. + * + * Example: + * CustomParameters cp; + * cp.set("key", "value"); // for the default run and beam type + * cout << "value for key `key` : " << cp.at("key") << endl; + * + * CustomParameters cp2; + * cp2.set("key", "value_run1_beam1", "physics", "pp"); + * cout << "value for key `key` : " << cp.at("key", "physics", "pp") << endl; + * + */ +class CustomParameters +{ + // runtype -> beamtype -> key -> value + using CustomParametersType = std::unordered_map>>; + + public: + CustomParameters(); + + void set(const std::string& key, const std::string& value, const std::string& runType = "default", const std::string& beamType = "default"); + + /** + * Return all the parameters (key-value pairs) for the given runType and beamType. + * @param runType + * @param beamType + * @return a map of the key-value pairs + * @throw std::out_of_range if no key-value pair correspond to these beamType and runType + */ + const std::unordered_map& getAllForRunBeam(const std::string& runType, const std::string& beamType); + + /** + * Return all the parameters (key-value pairs) for the default runType and the default beamType. + * @return a map of the key-value pairs for + * @throw std::out_of_range if no key-value pair correspond to these beamType and runType + */ + const std::unordered_map& getAllDefaults(); + + /** + * Return the value for the given key, runType and beamType. + * If no key is found for the runType and the Beamtype, the fallback is to substitute with "default", first for beamType then for runType. + * An exception is raised only if the key could not be found in any combination of the provided run and beam types with "default". + * @param key + * @param runType + * @param beamType + * @return the value for the given key, runType and beamType. + * @throw std::out_of_range if no key-value pair corresponds to this key and to these beamType and runType and that substitutions + * with "default" failed. + */ + std::string at(const std::string& key, const std::string& runType = "default", const std::string& beamType = "default") const; + + /** + * Return the value for the given key, runType and beamType. + * If no key is found for the runType and the Beamtype, the fallback is to substitute with "default", first for beamType then for runType. + * An exception is raised only if the key could not be found in any combination of the provided run and beam types with "default". + * @param key + * @param activity + * @return the value for the given key and for the given activity. + * @throw std::out_of_range if no key-value pair corresponds to this key and to this activity and that substitutions + * with "default" failed. + */ + std::string at(const std::string& key, const Activity& activity) const; + + /** + * Return the optional value for the given key, runType and beamType. + * If no key is found for the runType and the Beamtype, the fallback is to substitute with "default", first for beamType then for runType. + * Empty is only returned if the key could not be found in any combination of the provided run and beam types with "default". + * @param key + * @param runType + * @param beamType + * @return an optional with the value for the given key, runType and beamType or empty if not found. + */ + std::optional atOptional(const std::string& key, const std::string& runType = "default", const std::string& beamType = "default") const; + + /** + * Return the optional value for the given key, runType and beamType. + * If no key is found for the runType and the Beamtype, the fallback is to substitute with "default", first for beamType then for runType. + * Empty is only returned if the key could not be found in any combination of the provided run and beam types with "default". + * @param key + * @param activity + * @return an optional with the value for the given key and for the given activity. + */ + std::optional atOptional(const std::string& key, const Activity& activity) const; + + /** + * Return the ptree representation of the optional value for the given key, runType and beamType. + * If no key is found for the runType and the Beamtype, the fallback is to substitute with "default", first for beamType then for runType. + * Empty is only returned if the key could not be found in any combination of the provided run and beam types with "default". + * @param key + * @param runType + * @param beamType + * @return an optional with the ptree representation of the value for the given key, runType and beamType or empty if not found. + */ + std::optional getOptionalPtree(const std::string& key, const std::string& runType = "default", const std::string& beamType = "default") const; + + /** + * Return the ptree representation of the optional value for the given key, runType and beamType. + * If no key is found for the runType and the Beamtype, the fallback is to substitute with "default", first for beamType then for runType. + * Empty is only returned if the key could not be found in any combination of the provided run and beam types with "default". + * @param key + * @param activity + * @return an optional with the ptree representation of the value for the given key, runType and beamType or empty if not found. + */ + std::optional getOptionalPtree(const std::string& key, const Activity& activity) const; + + /** + * Return the value for the given key, runType and beamType (the two latter optional). If it does not exist, returns the default value if provided or an empty string. + * @param key + * @param runType + * + * @param beamType + * @param defaultValue + * @return the value for the given key, runType and beamType. If it is not found, it returns an empty string or defaultValue if provided. + */ + std::string atOrDefaultValue(const std::string& key, std::string defaultValue = "", const std::string& runType = "default", const std::string& beamType = "default") const; + + std::string atOrDefaultValue(const std::string& key, std::string defaultValue, const Activity& activity) const; + + /** + * Returns the number of items found for the provided key, beamType and runType. It can only be either 0 or 1. + * @param key + * @param runType + * @param beamType + * @return 0 or 1 depending if a value is found. + */ + int count(const std::string& key, const std::string& runType = "default", const std::string& beamType = "default") const; + + /** + * Finds the items whose key is `key`. + * A runType and/or a beamType can be set as well. + * If it is not found it returns end() + * @param key + * @param runType + * @param beamType + * @return the item or end() + */ + std::unordered_map::const_iterator find(const std::string& key, const std::string& runType = "default", const std::string& beamType = "default") const; + + std::unordered_map::const_iterator end() const; + + /** + * Returns the total count of all the kv pairs for all beam/type combinations + * @return + */ + size_t size() const; + + /** + * Return the value for the given key, and for beamType=default and runType=default. + * If the key does not exist, it will create it with a value="". + * @param key + * @return + */ + std::string operator[](const std::string& key) const; + + /** + * Assign the value to the key, and for beamType=default and runType=default. + * @param key + * @return + */ + std::string& operator[](const std::string& key); + + /** + * prints the CustomParameters + */ + friend std::ostream& operator<<(std::ostream& out, const CustomParameters& customParameters); + + /** + * \brief Provided the config subtree of the custom parameters, load its content and populate this CustomParameters. + * \param paramsTree The subtree corresponding to extendedTaskParameters, extendedCheckParameters, etc... + */ + void populateCustomParameters(const boost::property_tree::ptree& paramsTree); + + private: + CustomParametersType mCustomParameters; +}; + +} // namespace o2::quality_control::core + +#endif // QC_CUSTOM_PARAMETERS_H diff --git a/Framework/include/QualityControl/DataDumpGui.h b/Framework/include/QualityControl/DataDumpGui.h deleted file mode 100644 index e0db7869fd..0000000000 --- a/Framework/include/QualityControl/DataDumpGui.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -// - -/// -/// \author Barthelemy von Haller -/// \file DataDumpGui.h -/// - -#ifndef QC_CORE_DATADUMP_H -#define QC_CORE_DATADUMP_H - -#include "FairMQDevice.h" - -namespace o2::quality_control::core -{ - -/** - * A chunk of data - */ -struct Chunk { - size_t size; - unsigned char* data; - - Chunk() - { - size = 0; - data = nullptr; - } -}; - -/** - * Container for the state of the GUI. - * As we use Imgui it is stateless and we have to keep the state ourselves. - */ -struct GUIState { - GUIState() { newDataAvailable = false; } - - bool newDataAvailable; - std::string actionMessage; - std::string dataAvailableMessage; - Chunk current_payload; - Chunk next_payload; - Chunk current_header; - Chunk next_header; -}; - -/** - * A GUI to display the header and the payload of events sent by the Data Sampling. - */ -class DataDumpGui : public FairMQDevice -{ - public: - DataDumpGui() = default; - virtual ~DataDumpGui() = default; - - static GUIState guiState; - static void* window; - - protected: - void InitTask() override; - bool ConditionalRun() override; - bool handleParts(FairMQParts& parts); - - private: - void assignDataToChunk(void* data, size_t size, Chunk& chunk); -}; -} // namespace o2::quality_control::core - -#endif // QC_CORE_DATADUMP_H diff --git a/Framework/include/QualityControl/DataHeaderHelpers.h b/Framework/include/QualityControl/DataHeaderHelpers.h new file mode 100644 index 0000000000..fe1eb2c655 --- /dev/null +++ b/Framework/include/QualityControl/DataHeaderHelpers.h @@ -0,0 +1,57 @@ +// Copyright 2019-2024 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_DATA_HEADER_HELPERS_H +#define QC_DATA_HEADER_HELPERS_H + +#include +#include + +#include + +#include "QualityControl/DataSourceType.h" + +namespace o2::quality_control::core +{ + +/// \brief Creates DataOrigin for a QC Actor. +/// +/// Creates DataOrigin for a data source and detector code +header::DataOrigin createDataOrigin(DataSourceType, const std::string& detectorCode); + +/// \brief Creates DataDescription from given name for a QC actor +/// +/// If the length of the name is <= 16 (hardcoded in DataDescription) it creates DataDescription from the original name. +/// However, if the length of the name is > 16, it will create hash of the whole name and replace ending hashLength of bytes +/// of the name with hexa representation of computed hash. +/// eg.: name == "veryLongNameThatIsLongerThan16B" with hashLength == 4 will result in "veryLongNameABCD", where ABCD +/// is the hash create inside the function +/// +/// \param name - name which should cut and hashed +/// \param hashLength - number of bytes which will overwrite the end of the name +o2::header::DataDescription createDataDescription(const std::string& name, size_t hashLength); + +/// \brief Creates DataDescription from given name for a QC actor +/// +/// If the length of the name is <= 16 (hardcoded in DataDescription) it creates DataDescription from the original name. +/// However, if the length of the name is > 16, it will create hash of the whole name and replace ending hashLength of bytes +/// of the name with hexa representation of computed hash. +/// eg.: name == "veryLongNameThatIsLongerThan16B" with hashLength == 4 will result in "veryLongNameABCD", where ABCD +/// is the hash create inside the function. +/// This function deduces hash length for the provided data source type. +/// +/// \param name - name which should cut and hashed +/// \param type - data source type associated to an actor +o2::header::DataDescription createDataDescription(const std::string& name, DataSourceType type); + +} // namespace o2::quality_control::core + +#endif diff --git a/Framework/include/QualityControl/DataProducer.h b/Framework/include/QualityControl/DataProducer.h new file mode 100644 index 0000000000..a9cbabdd96 --- /dev/null +++ b/Framework/include/QualityControl/DataProducer.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DataProducer.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_DATAPRODUCER_H +#define QUALITYCONTROL_DATAPRODUCER_H + +#include +#include + +namespace o2::quality_control::core +{ + +/// \brief Returns a random data producer specification which publishes on {"TST", "RAWDATA", } +/// +/// \param minSize Minimum size of a message in bytes +/// \param maxSize Maximum size of a message in bytes +/// \param rate How much messages to produce in one second +/// \param amount How many messages should be produce in total (0 for inf). EndOfStream is sent at the end. +/// \param index SubSpecification of the data producer (useful when more than one needed) +/// \param monitoringUrl Where monitoring metrics should be sent +/// \param fill Should it fill messages with random data +/// +/// \return A random data producer specification +framework::DataProcessorSpec + getDataProducerSpec(size_t minSize, size_t maxSize, double rate, uint64_t amount = 0, size_t index = 0, + const std::string& monitoringUrl = "", bool fill = true, size_t timepipeline = 1); + +/// \brief Returns an algorithm generating random messages +/// +/// \param output Origin, Description and SubSpecification of data to be produced +/// \param minSize Minimum size of a message in bytes +/// \param maxSize Maximum size of a message in bytes +/// \param rate How much messages to produce in one second +/// \param amount How many messages should be produce in total (0 for inf). EndOfStream is sent at the end. +/// \param monitoringUrl Where monitoring metrics should be sent +/// \param fill Should it fill messages with random data +/// +/// \return A random data producer algorithm +framework::AlgorithmSpec + getDataProducerAlgorithm(framework::ConcreteDataMatcher output, size_t minSize, size_t maxSize, double rate, + uint64_t amount = 0, const std::string& monitoringUrl = "", bool fill = true); + +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_DATAPRODUCER_H diff --git a/Framework/include/QualityControl/DataProducerExample.h b/Framework/include/QualityControl/DataProducerExample.h new file mode 100644 index 0000000000..0f8c5cc6ea --- /dev/null +++ b/Framework/include/QualityControl/DataProducerExample.h @@ -0,0 +1,40 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DataProducerExample.h +/// \author Barthelemy von Haller +/// + +#ifndef QUALITYCONTROL_DATAPRODUCEREXAMPLE_H +#define QUALITYCONTROL_DATAPRODUCEREXAMPLE_H + +#include + +namespace o2::quality_control::core +{ + +/// \brief Returns a random data producer specification which publishes on {"TST", "RAWDATA", } +/// +/// \param myParam The value the producer should produce. +/// \return A fixed number producer specification +framework::DataProcessorSpec getDataProducerExampleSpec(size_t myParam); + +/// \brief Returns an algorithm generating random messages +/// +/// \param output Origin, Description and SubSpecification of data to be produced +/// \param myParam The value the producer should produce. +/// \return A fixed number producer algorithm +framework::AlgorithmSpec getDataProducerExampleAlgorithm(framework::ConcreteDataMatcher output, size_t myParam); + +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_DATAPRODUCEREXAMPLE_H diff --git a/Framework/include/QualityControl/DataSourceSpec.h b/Framework/include/QualityControl/DataSourceSpec.h new file mode 100644 index 0000000000..fd0629cd00 --- /dev/null +++ b/Framework/include/QualityControl/DataSourceSpec.h @@ -0,0 +1,48 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_DATASOURCESPEC_H +#define QUALITYCONTROL_DATASOURCESPEC_H + +/// +/// \file DataSourceSpec.h +/// \author Piotr Konopka +/// + +#include +#include +#include +#include + +#include "QualityControl/DataSourceType.h" + +namespace o2::quality_control::core +{ + +// this should allow us to represent all data sources which come from DPL (and maybe CCDB). +struct DataSourceSpec { + explicit DataSourceSpec(DataSourceType type = DataSourceType::Invalid); + + template + bool isOneOf(DataSourceType... dataSourceType) const + { + return (... || (dataSourceType == type)); + } + + DataSourceType type; + std::string id; + std::string name; + std::vector inputs; + std::vector subInputs; // can be MO or QO names +}; + +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_DATASOURCESPEC_H diff --git a/Framework/include/QualityControl/DataSourceType.h b/Framework/include/QualityControl/DataSourceType.h new file mode 100644 index 0000000000..8250fdac6b --- /dev/null +++ b/Framework/include/QualityControl/DataSourceType.h @@ -0,0 +1,37 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DataSourceType.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_DATASOURCETYPE_H +#define QUALITYCONTROL_DATASOURCETYPE_H + +namespace o2::quality_control::core +{ + +enum class DataSourceType { + DataSamplingPolicy, + Direct, + Task, + TaskMovingWindow, + Check, + Aggregator, + PostProcessingTask, + ExternalTask, + Invalid +}; + +} + +#endif // QUALITYCONTROL_DATASOURCETYPE_H \ No newline at end of file diff --git a/Framework/include/QualityControl/DatabaseFactory.h b/Framework/include/QualityControl/DatabaseFactory.h index 92afaff257..86a57eb2e0 100644 --- a/Framework/include/QualityControl/DatabaseFactory.h +++ b/Framework/include/QualityControl/DatabaseFactory.h @@ -1,3 +1,15 @@ + +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file DatabaseFactory.h /// \author Barthelemy von Haller @@ -7,8 +19,6 @@ #define QC_REPOSITORY_DATABASEFACTORY_H #include -// O2 -#include "Common/Exceptions.h" // QC #include "QualityControl/DatabaseInterface.h" diff --git a/Framework/include/QualityControl/DatabaseInterface.h b/Framework/include/QualityControl/DatabaseInterface.h index 6c504c09ff..3bc55b11ac 100644 --- a/Framework/include/QualityControl/DatabaseInterface.h +++ b/Framework/include/QualityControl/DatabaseInterface.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file DatabaseInterface.h /// \author Barthelemy von Haller @@ -6,10 +17,16 @@ #ifndef QC_REPOSITORY_DATABASEINTERFACE_H #define QC_REPOSITORY_DATABASEINTERFACE_H -#include "QualityControl/MonitorObject.h" +#include #include +#include #include -//#include + +#include + +#include "QualityControl/QualityObject.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Activity.h" namespace o2::quality_control::repository { @@ -20,10 +37,17 @@ namespace o2::quality_control::repository class DatabaseInterface { public: + constexpr static framework::ServiceKind service_kind = framework::ServiceKind::Global; + + enum Timestamp : long { + Current = -1, + Latest = 0 + }; + /// Default constructor - DatabaseInterface() {} + DatabaseInterface() = default; /// Destructor - virtual ~DatabaseInterface() {} + virtual ~DatabaseInterface() = default; /** * Connects to the database. @@ -34,7 +58,7 @@ class DatabaseInterface * @param password * @deprecated */ - virtual void connect(std::string host, std::string database, std::string username, std::string password) = 0; + virtual void connect(const std::string& host, const std::string& database, const std::string& username, const std::string& password) = 0; /** * Connects to the database. * For some implementations, this is a noop. @@ -42,39 +66,148 @@ class DatabaseInterface */ virtual void connect(const std::unordered_map& config) = 0; + /** + * Store an object of type `typeInfo` (which needs to have a ROOT dictionary). + * Example usage: `storeAny(reinterpret_cast(obj), typeid(T), path, metadata, "TST", "taskname", from, to);` + * + * Note that we cannot have a more elegant templated signature due to the fact that it is a virtual method. + * + * @param obj Raw pointer to the object to store. + * @param typeInfo The type of the object. + * @param path The path where the object is going to be stored. + * @param metadata Key-values representing the metadata for this object. + * @param detectorName The name of the detector (will appear in the metadata, not used in the path) + * @param taskName The name of the task (will appear in the metadata, not used in the path) + * @param from Start of validity. If omitted, current timestamp is used. + * @param to End of validity. If omitted, current timestamp + 1 year is used. + */ + virtual void storeAny(const void* obj, std::type_info const& typeInfo, std::string const& path, + std::map const& metadata, std::string const& detectorName, + std::string const& taskName, long from = -1, long to = -1) = 0; + + /** + * Retrieve an object of type tinfo at the given path for the given timestamp. + * Example usage: + * ``` + * std::map meta; + * void* rawResult = f.backend->retrieveAny(typeid(TH1F), "/qc/TST/asdf", meta); + * auto h1_back = static_cast(rawResult); + * ``` + * + * Note that we cannot have a more elegant templated signature due to the fact that it is a virtual method. + * + * @param typeInfo The type of the object. + * @param path The path where the object is to be found. + * @param metadata Key-values representing the metadata to filter out objects. + * @param timestamp Timestamp of the object to retrieve. If omitted, current timestamp is used. + * @param optional headers Map to be populated with the headers we received, if it is not null. + * @param optional createdNotAfter upper time limit for the object creation timestamp (TimeMachine mode) + * @param optional createdNotBefore lower time limit for the object creation timestamp (TimeMachine mode) + * @return the object, or nullptr if none were found or type does not match serialized type. + */ + virtual void* retrieveAny(std::type_info const& tinfo, std::string const& path, + std::map const& metadata, long timestamp = -1, + std::map* headers = nullptr, + const std::string& createdNotAfter = "", const std::string& createdNotBefore = "") = 0; + /** * Stores the serialized MonitorObject in the database. * @param mo The MonitorObject to serialize and store. + * @param from The timestamp indicating the start of object's validity (ms since epoch). + * @param to The timestamp indicating the end of object's validity (ms since epoch). + */ + virtual void storeMO(std::shared_ptr mo) = 0; + + /** + * Stores the serialized QualityObject in the database. + * @param qo The QualityObject to serialize and store. + * @param from The timestamp indicating the start of object's validity (ms since epoch). + * @param to The timestamp indicating the end of object's validity (ms since epoch). */ - virtual void store(std::shared_ptr mo) = 0; + virtual void storeQO(std::shared_ptr qo) = 0; /** - * Look up an object of a task and return it. - * \details It returns the object if found or nullptr if not. - * TODO evaluate whether we should have more methods to retrieve objects of different types (with or without - * templates) - * TODO evaluate whether we should have a method to retrieve a list of objects (optimization) + * \brief Look up a monitor object and return it. + * Look up a monitor object and return it if found or nullptr if not. + * @param objectPath Path to the object without the provenance prefix + * @param objectName Name of the object + * @param timestamp Timestamp of the object in ms since epoch + * @param activity Activity of the object + * @param metadata additional metadata to filter objects during retrieval + * @deprecated + */ + virtual std::shared_ptr retrieveMO(std::string objectPath, std::string objectName, + long timestamp = Timestamp::Current, const core::Activity& activity = {}, + const std::map& metadata = {}) = 0; + /** + * \brief Look up a quality object and return it. + * Look up a quality object and return it if found or nullptr if not. + * @param qoPath Path of the object without the provenance prefix + * @param timestamp Timestamp of the object in ms since epoch + * @param activity Activity of the object + * @param metadata additional metadata to filter objects during retrieval + * @deprecated + */ + virtual std::shared_ptr retrieveQO(std::string qoPath, long timestamp = Timestamp::Current, + const core::Activity& activity = {}, + const std::map& metadata = {}) = 0; + + /** + * \brief Look up an object and return it. + * Look up an object and return it if found or nullptr if not. It is a raw pointer because we might need it to build a MO. + * \param path the path of the object + * \param timestamp the timestamp to query the object + * \param headers Map to be populated with the headers we received, if it is not null. + * \param metadata filters under the form of key-value pairs to select data */ - virtual o2::quality_control::core::MonitorObject* retrieve(std::string taskName, std::string objectName) = 0; + virtual TObject* retrieveTObject(std::string path, const std::map& metadata, long timestamp = Timestamp::Current, std::map* headers = nullptr) = 0; /** - * Returns JSON encoded object + * \brief Look up an object and return it in JSON format. + * Look up an object and return it in JSON format if found or an empty string if not. + * The headers associated with the object are added to the JSON object under the key "metadata". + * \param path the path of the object + * \param timestamp the timestamp to query the object + * \param metadata filters under the form of key-value pairs to select data */ - virtual std::string retrieveJson(std::string taskName, std::string objectName) = 0; + virtual std::string retrieveJson(std::string path, long timestamp, const std::map& metadata) = 0; + + /** + * \brief Look up an object and return it in JSON format. + * Look up an object and return it in JSON format if found or an empty string if not. + * The headers associated with the object are added to the JSON object under the key "metadata". + * A default timestamp of -1 is used, usually meaning to use the current timestamp. + * \param path the path to the object + */ + virtual std::string retrieveJson(std::string path) + { + std::map metadata; + return retrieveJson(path, -1, metadata); + } + virtual void disconnect() = 0; /** * \brief Prepare the container, such as a table in a relational database, that will contain the MonitorObject's for * the given Task. If the container already exists, we do nothing. */ virtual void prepareTaskDataContainer(std::string taskName) = 0; - virtual std::vector getListOfTasksWithPublications() = 0; virtual std::vector getPublishedObjectNames(std::string taskName) = 0; /** * Delete all versions of a given object - * @param taskName Task sending the object + * @param path Path to the object to be removed * @param objectName Name of the object */ - virtual void truncate(std::string taskName, std::string objectName) = 0; + virtual void truncate(std::string path, std::string objectName) = 0; + + virtual void setMaxObjectSize(size_t maxObjectSize) = 0; + + /** + * Return validity of the latest matching object + * @param path the folder we want to list the children of. + * @param metadata metadata to filter queried objects. + * @return validity of the latest matching object + */ + virtual core::ValidityInterval getLatestObjectValidity(const std::string& path, const std::map& metadata = {}) = 0; }; } // namespace o2::quality_control::repository diff --git a/Framework/include/QualityControl/DummyDatabase.h b/Framework/include/QualityControl/DummyDatabase.h new file mode 100644 index 0000000000..7f1aacd61f --- /dev/null +++ b/Framework/include/QualityControl/DummyDatabase.h @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DummyDatabase.h +/// \author Piotr Konopka +/// + +#ifndef QC_REPOSITORY_DUMMYDATABASE_H +#define QC_REPOSITORY_DUMMYDATABASE_H + +#include "QualityControl/DatabaseInterface.h" + +namespace o2::quality_control::repository +{ + +/// \brief Dummy database which does nothing. Use it to avoid writing to QC repository. +class DummyDatabase : public DatabaseInterface +{ + public: + DummyDatabase() = default; + virtual ~DummyDatabase() = default; + + void connect(const std::string& host, const std::string& database, const std::string& username, const std::string& password) override; + void connect(const std::unordered_map& config) override; + void storeAny(const void* obj, std::type_info const& typeInfo, std::string const& path, std::map const& metadata, + std::string const& detectorName, std::string const& taskName, long from = -1, long to = -1) override; + // MonitorObject + void storeMO(std::shared_ptr q) override; + std::shared_ptr retrieveMO(std::string taskName, std::string objectName, long timestamp = -1, const core::Activity& activity = {}, const std::map& metadata = {}) override; + // QualityObject + void storeQO(std::shared_ptr q) override; + std::shared_ptr retrieveQO(std::string checkerName, long timestamp = -1, const core::Activity& activity = {}, const std::map& metadata = {}) override; + + // General + void* retrieveAny(std::type_info const& tinfo, std::string const& path, + std::map const& metadata, long timestamp = -1, + std::map* headers = nullptr, + const std::string& createdNotAfter = "", const std::string& createdNotBefore = "") override; + std::string retrieveJson(std::string path, long timestamp, const std::map& metadata) override; + TObject* retrieveTObject(std::string path, const std::map& metadata, long timestamp = -1, std::map* headers = nullptr) override; + + void disconnect() override; + void prepareTaskDataContainer(std::string taskName) override; + std::vector getPublishedObjectNames(std::string taskName) override; + void truncate(std::string path, std::string objectName) override; + void setMaxObjectSize(size_t maxObjectSize) override; + + core::ValidityInterval getLatestObjectValidity(const std::string& path, const std::map& metadata = {}) override; + + private: +}; + +} // namespace o2::quality_control::repository + +#endif // QC_REPOSITORY_DUMMYDATABASE_H diff --git a/Framework/include/QualityControl/ExamplePrinterSpec.h b/Framework/include/QualityControl/ExamplePrinterSpec.h new file mode 100644 index 0000000000..bd287d20f8 --- /dev/null +++ b/Framework/include/QualityControl/ExamplePrinterSpec.h @@ -0,0 +1,93 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ExamplePrinterSpec.h +/// \author Barthelemy von Haller +/// + +#ifndef QUALITYCONTROL_EXAMPLEPRINTERSPEC_H +#define QUALITYCONTROL_EXAMPLEPRINTERSPEC_H + +#include +#include + +#include +#include + +#include +#include + +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QualityObject.h" + +namespace o2::quality_control::example +{ + +/** + * \brief Example DPL task to be plugged after a QC task. + * + * This example DPL task takes a TObjArray of MonitorObjects as input (corresponding to the output of a checker) + * and prints the bins of the first element. The element needs to be a TH1 otherwise it is ignored. + */ +class ExamplePrinterSpec : public framework::Task +{ + public: + void run(ProcessingContext& processingContext) final + { + LOG(info) << "Received data"; + std::shared_ptr moArray{ DataRefUtils::as(*processingContext.inputs().begin()) }; + + if (moArray->IsEmpty()) { + LOG(info) << "Array is empty"; + return; + } + + // get the object + auto* mo = dynamic_cast(moArray->At(0)); + if (mo == nullptr) { + LOG(info) << "First element is not a MonitorObject"; + return; + } + auto* histo = dynamic_cast(mo->getObject()); + if (histo == nullptr) { + LOG(info) << "MonitorObject does not contain a TH1"; + return; + } + + std::string bins = "BINS:"; + for (int i = 0; i < histo->GetNbinsX(); i++) { + bins += " " + std::to_string((int)histo->GetBinContent(i)); + } + LOG(info) << bins; + } +}; + +/** + * \brief Example DPL task to be plugged after a QC check. + * + * This example DPL task takes a TObjArray of MonitorObjects as input (corresponding to the output of a checker) + * and prints the bins of the first element. The element needs to be a TH1 otherwise it is ignored. + */ +class ExampleQualityPrinterSpec : public framework::Task +{ + public: + void run(ProcessingContext& processingContext) final + { + auto qo = processingContext.inputs().get("checked-mo"); + + LOG(info) << "Received Quality: " << qo->getQuality(); + } +}; + +} // namespace o2::quality_control::example + +#endif //QUALITYCONTROL_EXAMPLEPRINTERSPEC_H diff --git a/Framework/include/QualityControl/ExternalTaskSpec.h b/Framework/include/QualityControl/ExternalTaskSpec.h new file mode 100644 index 0000000000..99aa12a620 --- /dev/null +++ b/Framework/include/QualityControl/ExternalTaskSpec.h @@ -0,0 +1,39 @@ +#include + +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_EXTERNALTASKSPEC_H +#define QUALITYCONTROL_EXTERNALTASKSPEC_H + +/// +/// \file ExternalTaskSpec.h +/// \author Piotr Konopka +/// + +namespace o2::quality_control::core +{ + +struct ExternalTaskSpec { + ExternalTaskSpec() = default; + + ExternalTaskSpec(std::string taskName, std::string query, bool active = true) : taskName(std::move(taskName)), query(std::move(query)), active(active) + { + } + + std::string taskName; + std::string query; + bool active = true; +}; + +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_EXTERNALTASKSPEC_H diff --git a/Framework/include/QualityControl/FlagHelpers.h b/Framework/include/QualityControl/FlagHelpers.h new file mode 100644 index 0000000000..b86d7bb8e0 --- /dev/null +++ b/Framework/include/QualityControl/FlagHelpers.h @@ -0,0 +1,47 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file FlagHelpers.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_FLAGHELPERS_H +#define QUALITYCONTROL_FLAGHELPERS_H + +#include "DataFormatsQualityControl/QualityControlFlag.h" +#include "QualityControl/ValidityInterval.h" +#include +#include + +namespace o2::quality_control::core::flag_helpers +{ + +/// \brief returns true if the provided intervals are valid and are overlapping or adjacent +bool intervalsConnect(const ValidityInterval& one, const ValidityInterval& other); + +/// \brief returns true if the provided intervals are valid and are overlapping (there is at least one 1ms common) +bool intervalsOverlap(const ValidityInterval& one, const ValidityInterval& other); + +/// \brief Removes the provided interval from the flag. +/// +/// Removes the provided interval from the flag. A result can be: +/// - an empty vector if the interval fully covers the flag's interval +/// - a vector with one flag if the interval covers the flag's interval on one side +/// - a vector with two flags if the interval is fully contained by the flag's interval +std::vector excludeInterval(const QualityControlFlag& flag, ValidityInterval interval); + +/// Trims the provided flag to the intersection with the provided interval. +/// If the intersection does not exist, it returns nullopt +std::optional intersection(const QualityControlFlag& flag, ValidityInterval interval); + +} // namespace o2::quality_control::core::flag_helpers +#endif // QUALITYCONTROL_FLAGHELPERS_H diff --git a/Framework/include/QualityControl/HistoMerger.h b/Framework/include/QualityControl/HistoMerger.h deleted file mode 100644 index 4ce09bfc47..0000000000 --- a/Framework/include/QualityControl/HistoMerger.h +++ /dev/null @@ -1,65 +0,0 @@ -/// -/// \file HistoMerger.h -/// \author Piotr Konopka -/// - -#ifndef QC_CORE_HISTOMERGER_H -#define QC_CORE_HISTOMERGER_H - -#include -#include - -#include "Common/Timer.h" -#include -#include -#include - -#include "QualityControl/MonitorObject.h" - -namespace o2::quality_control::core -{ - -/// \brief A crude histogram merger for development purposes. -/// -/// A crude histogram merger for development purposes - at some point, it will be substituted with more fine solution. -/// As inputs, it expects updates of MonitorObjects with one TH1 histogram each, which are accumulated into one -/// MonitorObject. The joined MO is published on regular basis with a period specified in the constructor. All inputs -/// should have the same DataOrigin and DataDescription and non-zero SubSpecification. Output has the same origin and -/// description as inputs, but SubSpec is fixed 0. -class HistoMerger : public framework::Task -{ - public: - /// Constructor - HistoMerger(std::string mergerName, double publicationPeriodSeconds = 10); - - /// Destructor - ~HistoMerger() override; - - /// \brief HistoMerger init callback - void init(framework::InitContext& ctx) override; - - /// \brief HistoMerger process callback - void run(framework::ProcessingContext& ctx) override; - - void configureInputsOutputs( - o2::header::DataOrigin origin, o2::header::DataDescription description, - std::pair subSpecRange); - - std::string getName() { return mMergerName; }; - std::vector getInputSpecs() { return mInputSpecs; }; - framework::OutputSpec getOutputSpec() { return mOutputSpec; }; - - private: - // General state - std::string mMergerName; - TObjArray mMergedArray; - AliceO2::Common::Timer mPublicationTimer; - - // DPL - std::vector mInputSpecs; - o2::framework::OutputSpec mOutputSpec; -}; - -} // namespace o2::quality_control::core - -#endif // QC_CORE_HISTOMERGER_H diff --git a/Framework/include/QualityControl/HistoProducer.h b/Framework/include/QualityControl/HistoProducer.h new file mode 100644 index 0000000000..3355ecb845 --- /dev/null +++ b/Framework/include/QualityControl/HistoProducer.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HistoProducer.h +/// \author Barthelemy von Haller +/// + +#ifndef QUALITYCONTROL_HISTOPRODUCER_H +#define QUALITYCONTROL_HISTOPRODUCER_H + +#include + +namespace o2::quality_control::core +{ + +/// \brief Returns an histogram producer specification which publishes on {"TST", "HISTO", }. +/// +/// \param index The index of this producer (i.e. the subspec). +/// \return An histograms producer specification. +framework::DataProcessorSpec getHistoProducerSpec(size_t index, size_t nbHistograms, bool noTobjArray); + +/// \brief Returns an algorithm generating histograms randomly filled. +/// The histograms have 100 bins and are named `hello`. +/// The histograms are embedded in a TObjArray. +/// +/// \param output Origin, Description and SubSpecification of data to be produced +/// \param index The value the producer should produce. +/// \return An histogram producer algorithm +framework::AlgorithmSpec getHistoProducerAlgorithm(framework::ConcreteDataMatcher output, size_t nbHistograms, bool noTobjArray); + +/// \brief Returns a printer that prints histograms coming from {"TST", "HISTO", } +/// +/// \param index The index of the producer (i.e. the subspec) to which the printer must connect. +/// \return A printer. +framework::DataProcessorSpec getHistoPrinterSpec(size_t index); + +/// \brief Returns an algorithm printing histograms. +/// +/// \return An algorithm printing histograms. +framework::AlgorithmSpec getHistoPrinterAlgorithm(); + +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_HISTOPRODUCER_H diff --git a/Framework/include/QualityControl/InfrastructureGenerator.h b/Framework/include/QualityControl/InfrastructureGenerator.h index ab60a814ec..1f696068e0 100644 --- a/Framework/include/QualityControl/InfrastructureGenerator.h +++ b/Framework/include/QualityControl/InfrastructureGenerator.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file InfrastructureGenerator.h /// \author Piotr Konopka @@ -6,7 +17,14 @@ #ifndef QC_CORE_INFRASTRUCTUREGENERATOR_H #define QC_CORE_INFRASTRUCTUREGENERATOR_H +#include #include +#include + +namespace o2::framework +{ +class CompletionPolicy; +} #include namespace o2::quality_control @@ -14,14 +32,17 @@ namespace o2::quality_control namespace core { +class TaskSpec; +struct InfrastructureSpec; + /// \brief A factory class which can generate QC topologies given a configuration file. /// /// A factory class which can generate QC topologies given a configuration file (example in Framework/basic.json and /// Framework/example-default.json). As QC topologies will be spread on both processing chain machines and dedicated /// QC servers, a _local_ vs. _remote_ distinction was introduced. Tasks which are _local_ should have taskRunners /// placed on FLP or EPN machines and their results should be merged and checked on QC servers. The 'remote' option -/// means, that full QC chain should be located on remote (QC) machines. For the laptop development, use 'remote' tasks -/// and generateRemoteInfrastructure() to obtain the full topology in one go. +/// means, that full QC chain should be located on remote (QC) machines. For the laptop development, use +/// generateStandaloneInfrastructure() to obtain the full topology in one go. /// /// \author Piotr Konopka class InfrastructureGenerator @@ -29,15 +50,53 @@ class InfrastructureGenerator public: InfrastructureGenerator() = delete; + /// \brief Generates a standalone QC infrastructure. + /// + /// Generates a full QC infrastructure from a configuration file. This function is aimed to use for standalone setups + /// and local development. It will create both local and remote QC tasks, and CheckRunners running associated Checks. + /// + /// \param configurationTree - full path to configuration file, preceded with the backend (e.g. "json://") + /// \return generated standalone QC workflow + static framework::WorkflowSpec generateStandaloneInfrastructure(const boost::property_tree::ptree& configurationTree); + + /// \brief Generates a standalone QC infrastructure. + /// + /// Generates a full QC infrastructure from a configuration file. This function is aimed to use for standalone setups + /// and local development. It will create both local and remote QC tasks, and CheckRunners running associated Checks. + /// + /// \param workflow - existing workflow where QC infrastructure should be placed + /// \param configurationTree - full QC config ptree + static void generateStandaloneInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree); + + /// \brief Generates a full QC chain infrastructure. + /// + /// Generates a full QC infrastructure from a configuration file. This function is aimed to use for standalone setups + /// and local development. It will create both local and remote QC tasks, and CheckRunners running associated Checks, + /// as well as Mergers between local QC tasks and Checks. + /// + /// \param configurationTree - full path to configuration file, preceded with the backend (e.g. "json://") + /// \return generated standalone QC workflow + static framework::WorkflowSpec generateFullChainInfrastructure(const boost::property_tree::ptree& configurationTree); + + /// \brief Generates a full QC chain infrastructure. + /// + /// Generates a full QC infrastructure from a configuration file. This function is aimed to use for standalone setups + /// and local development. It will create both local and remote QC tasks, and CheckRunners running associated Checks, + /// as well as Mergers between local QC tasks and Checks. + /// + /// \param workflow - existing workflow where QC infrastructure should be placed + /// \param configurationTree - full QC config ptree + static void generateFullChainInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree); + /// \brief Generates the local part of the QC infrastructure for a specified host. /// /// Generates the local part of the QC infrastructure for a specified host - taskRunners which are declared in the /// configuration to be 'local'. /// - /// \param configurationSource - full path to configuration file, preceded with the backend (f.e. "json://") - /// \param host - name of the machine + /// \param configurationTree - full QC config ptree + /// \param targetHost - name of the machine /// \return generated local QC workflow - static framework::WorkflowSpec generateLocalInfrastructure(std::string configurationSource, std::string host); + static framework::WorkflowSpec generateLocalInfrastructure(const boost::property_tree::ptree& configurationTree, const std::string& targetHost); /// \brief Generates the local part of the QC infrastructure for a specified host. /// @@ -45,19 +104,19 @@ class InfrastructureGenerator /// configuration to be 'local'. /// /// \param workflow - existing workflow where QC infrastructure should be placed - /// \param configurationSource - full path to configuration file, preceded with the backend (f.e. "json://") + /// \param configurationTree - full QC config ptree /// \param host - name of the machine /// \return generated local QC workflow - static void generateLocalInfrastructure(framework::WorkflowSpec& workflow, std::string configurationSource, std::string host); + static void generateLocalInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree, const std::string& host); /// \brief Generates the remote part of the QC infrastructure. /// /// Generates the remote part of the QC infrastructure - mergers and checkers for 'local' tasks and full QC chain for /// 'remote' tasks. /// - /// \param configurationSource - full path to configuration file, preceded with the backend (f.e. "json://") + /// \param configurationTree - full QC config ptree /// \return generated remote QC workflow - static o2::framework::WorkflowSpec generateRemoteInfrastructure(std::string configurationSource); + static o2::framework::WorkflowSpec generateRemoteInfrastructure(const boost::property_tree::ptree& configurationTree); /// \brief Generates the remote part of the QC infrastructure. /// @@ -65,35 +124,183 @@ class InfrastructureGenerator /// 'remote' tasks. /// /// \param workflow - existing workflow where QC infrastructure should be placed - /// \param configurationSource - full path to configuration file, preceded with the backend (f.e. "json://") - /// \return generated remote QC workflow - static void generateRemoteInfrastructure(framework::WorkflowSpec& workflow, std::string configurationSource); + /// \param configurationTree - full QC config ptree + static void generateRemoteInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree); + + /// \brief Generates the local batch part of the QC infrastructure. + /// + /// Generates the local batch part of the QC infrastructure - tasks and a file sink/merger. + /// + /// \param workflow - existing workflow where QC infrastructure should be placed + /// \param configurationTree - full QC config ptree + /// \param sinkFilePath - path to the output file + static void generateLocalBatchInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree, const std::string& sinkFilePath); + + /// \brief Generates the local batch part of the QC infrastructure. + /// + /// Generates the local batch part of the QC infrastructure - tasks and a file sink/merger. + /// + /// \param configurationTree - full QC config ptree + /// \param sinkFilePath - path to the output file + /// \return generated local QC workflow + static framework::WorkflowSpec generateLocalBatchInfrastructure(const boost::property_tree::ptree& configurationTree, const std::string& sinkFilePath); + + /// \brief Generates the remote batch part of the QC infrastructure. + /// + /// Generates the remote batch part of the QC infrastructure - file reader, check runners, aggregator runners. + /// + /// \param workflow - existing workflow where QC infrastructure should be placed + /// \param configurationTree - full QC config ptree + /// \param sourceFilePath - path to the input file + static void generateRemoteBatchInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree, const std::string& sourceFilePath); + + /// \brief Generates the remote batch part of the QC infrastructure. + /// + /// Generates the remote batch part of the QC infrastructure - file reader, check runners, aggregator runners. + /// + /// \param configurationTree - full QC config ptree + /// \param sourceFilePath - path to the input file + /// \return generated remote batch QC workflow + static framework::WorkflowSpec generateRemoteBatchInfrastructure(const boost::property_tree::ptree& configurationTree, const std::string& sourceFilePath); + + /// \brief Provides necessary customization of the QC infrastructure. + /// + /// Provides necessary customization of the Completion Policies of the QC infrastructure. This is necessary to make + /// the QC workflow work. Put it inside the following customize() function, before including : + /// \code{.cxx} + /// void customize(std::vector& policies) + /// { + /// quality_control::customizeInfrastructure(policies); + /// } + /// \endcode + /// \param policies - completion policies vector + static void customizeInfrastructure(std::vector& policies); + + static void printVersion(); + + /** + * If any aggregator has the same name as one of the checks, we throw a runtime_error. + * @param infrastructureSpec + */ + static void throwIfAggNamesClashCheckNames(const InfrastructureSpec& infrastructureSpec); + + private: + // Dedicated methods for creating each QC component to hide implementation details. + + static void generateDataSamplingPolicyLocalProxyBind(framework::WorkflowSpec& workflow, + const std::string& policyName, + const framework::Inputs& inputSpecs, + const std::string& localMachine, + const std::string& localPort, + const std::string& control); + static void generateDataSamplingPolicyRemoteProxyConnect(framework::WorkflowSpec& workflow, + const std::string& policyName, + const framework::Outputs& outputSpecs, + const std::string& localMachine, + const std::string& localPort, + const std::string& control); + static void generateDataSamplingPolicyLocalProxyConnect(framework::WorkflowSpec& workflow, + const std::string& policyName, + const framework::Inputs& inputSpecs, + const std::string& remoteMachine, + const std::string& remotePort, + const std::string& control); + static void generateDataSamplingPolicyRemoteProxyBind(framework::WorkflowSpec& workflow, + const std::string& policyName, + const framework::Outputs& outputSpecs, + const std::string& remotePort, + const std::string& control); + static void generateLocalTaskLocalProxy(framework::WorkflowSpec& workflow, + size_t id, + const TaskSpec& taskSpec); + static void generateLocalTaskRemoteProxy(framework::WorkflowSpec& workflow, + const TaskSpec& taskSpec, + size_t numberOfLocalMachines); + static void generateMergers(framework::WorkflowSpec& workflow, const std::string& taskName, + size_t numberOfLocalMachines, + std::vector> cycleDurationSeconds, + const std::string& mergingMode, + size_t resetAfterCycles, + std::string monitoringUrl, + const std::string& detectorName, + std::vector mergersPerLayer, + bool enableMovingWindows, + bool critical); + static void generateCheckRunners(framework::WorkflowSpec& workflow, const InfrastructureSpec& infrastructureSpec); + static void generateAggregator(framework::WorkflowSpec& workflow, const InfrastructureSpec& infrastructureSpec); + static void generatePostProcessing(framework::WorkflowSpec& workflow, const InfrastructureSpec& infrastructureSpec); + static void generateBookkeepingQualitySink(framework::WorkflowSpec& workflow, const InfrastructureSpec& infrastructureSpec); }; } // namespace core // exposing the class above as a main QC interface, syntactic sugar -inline framework::WorkflowSpec generateLocalInfrastructure(std::string configurationSource, std::string host) +inline framework::WorkflowSpec generateStandaloneInfrastructure(const boost::property_tree::ptree& configurationTree) +{ + return core::InfrastructureGenerator::generateStandaloneInfrastructure(configurationTree); +} + +inline void generateStandaloneInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree) +{ + core::InfrastructureGenerator::generateStandaloneInfrastructure(workflow, configurationTree); +} + +inline framework::WorkflowSpec generateFullChainInfrastructure(const boost::property_tree::ptree& configurationTree) +{ + return core::InfrastructureGenerator::generateFullChainInfrastructure(configurationTree); +} + +inline void generateFullChainInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree) +{ + core::InfrastructureGenerator::generateFullChainInfrastructure(workflow, configurationTree); +} + +inline framework::WorkflowSpec generateLocalInfrastructure(const boost::property_tree::ptree& configurationTree, std::string host) +{ + return core::InfrastructureGenerator::generateLocalInfrastructure(configurationTree, host); +} + +inline void generateLocalInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree, std::string host) +{ + core::InfrastructureGenerator::generateLocalInfrastructure(workflow, configurationTree, host); +} + +inline framework::WorkflowSpec generateRemoteInfrastructure(const boost::property_tree::ptree& configurationTree) +{ + return core::InfrastructureGenerator::generateRemoteInfrastructure(configurationTree); +} + +inline framework::WorkflowSpec generateLocalBatchInfrastructure(const boost::property_tree::ptree& configurationTree, std::string sinkFilePath) +{ + return core::InfrastructureGenerator::generateLocalBatchInfrastructure(configurationTree, std::move(sinkFilePath)); +} + +inline void generateLocalBatchInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree, std::string sinkFilePath) +{ + core::InfrastructureGenerator::generateLocalBatchInfrastructure(workflow, configurationTree, std::move(sinkFilePath)); +} + +inline framework::WorkflowSpec generateRemoteBatchInfrastructure(const boost::property_tree::ptree& configurationTree, std::string sourceFilePath) { - return core::InfrastructureGenerator::generateLocalInfrastructure(configurationSource, host); + return core::InfrastructureGenerator::generateRemoteBatchInfrastructure(configurationTree, std::move(sourceFilePath)); } -inline void generateLocalInfrastructure(framework::WorkflowSpec& workflow, std::string configurationSource, std::string host) +inline void generateRemoteBatchInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree, std::string sourceFilePath) { - core::InfrastructureGenerator::generateLocalInfrastructure(workflow, configurationSource, host); + core::InfrastructureGenerator::generateRemoteBatchInfrastructure(workflow, configurationTree, std::move(sourceFilePath)); } -inline framework::WorkflowSpec generateRemoteInfrastructure(std::string configurationSource) +inline void generateRemoteInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree) { - return core::InfrastructureGenerator::generateRemoteInfrastructure(configurationSource); + core::InfrastructureGenerator::generateRemoteInfrastructure(workflow, configurationTree); } -inline void generateRemoteInfrastructure(framework::WorkflowSpec& workflow, std::string configurationSource) +inline void customizeInfrastructure(std::vector& policies) { - core::InfrastructureGenerator::generateRemoteInfrastructure(workflow, configurationSource); + core::InfrastructureGenerator::customizeInfrastructure(policies); } } // namespace o2::quality_control -#endif //QC_CORE_INFRASTRUCTUREGENERATOR_H +#endif // QC_CORE_INFRASTRUCTUREGENERATOR_H diff --git a/Framework/include/QualityControl/InfrastructureSpec.h b/Framework/include/QualityControl/InfrastructureSpec.h new file mode 100644 index 0000000000..d764b80007 --- /dev/null +++ b/Framework/include/QualityControl/InfrastructureSpec.h @@ -0,0 +1,44 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_INFRASTRUCTURESPEC_H +#define QUALITYCONTROL_INFRASTRUCTURESPEC_H + +/// +/// \file InfrastructureSpec.h +/// \author Piotr Konopka +/// + +#include "QualityControl/WorkflowType.h" +#include "QualityControl/CommonSpec.h" +#include "QualityControl/TaskSpec.h" +#include "QualityControl/CheckSpec.h" +#include "QualityControl/AggregatorSpec.h" +#include "QualityControl/PostProcessingTaskSpec.h" +#include "QualityControl/ExternalTaskSpec.h" + +#include + +namespace o2::quality_control::core +{ + +struct InfrastructureSpec { + WorkflowType workflowType = WorkflowType::Standalone; + CommonSpec common; + std::vector tasks; + std::vector checks; + std::vector aggregators; + std::vector postProcessingTasks; + std::vector externalTasks; +}; + +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_INFRASTRUCTURESPEC_H diff --git a/Framework/include/QualityControl/InfrastructureSpecReader.h b/Framework/include/QualityControl/InfrastructureSpecReader.h new file mode 100644 index 0000000000..8cd381176d --- /dev/null +++ b/Framework/include/QualityControl/InfrastructureSpecReader.h @@ -0,0 +1,84 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_INFRASTRUCTURESPECREADER_H +#define QUALITYCONTROL_INFRASTRUCTURESPECREADER_H + +/// +/// \file InfrastructureSpecReader.h +/// \author Piotr Konopka +/// + +#include "QualityControl/InfrastructureSpec.h" +#include "QualityControl/TaskSpec.h" +#include "QualityControl/CommonSpec.h" +#include "QualityControl/DataSourceSpec.h" +#include "QualityControl/CheckSpec.h" +#include "QualityControl/PostProcessingTaskSpec.h" +#include "QualityControl/RecoRequestSpecs.h" +#include + +namespace o2::quality_control::core +{ + +// If we have to increase the performance of reading, +// we can probably improve it by writing a proper parser like for WorkflowSerializationHelpers in O2 +// Also, move operators could be implemented. + +namespace InfrastructureSpecReader +{ +/// \brief Reads the full QC configuration structure. +InfrastructureSpec readInfrastructureSpec(const boost::property_tree::ptree& wholeTree, WorkflowType workflowType); + +template +T readSpecEntry(const std::string& entryID, const boost::property_tree::ptree& entryTree, const boost::property_tree::ptree& wholeTree); + +// readers for separate parts +template <> +DataSourceSpec readSpecEntry(const std::string& entryID, const boost::property_tree::ptree& entryTree, const boost::property_tree::ptree& wholeTree); +template <> +TaskSpec readSpecEntry(const std::string& entryID, const boost::property_tree::ptree& entryTree, const boost::property_tree::ptree& wholeTree); +template <> +checker::CheckSpec readSpecEntry(const std::string& entryID, const boost::property_tree::ptree& entryTree, const boost::property_tree::ptree& wholeTree); +template <> +checker::AggregatorSpec readSpecEntry(const std::string& entryID, const boost::property_tree::ptree& entryTree, const boost::property_tree::ptree& wholeTree); +template <> +postprocessing::PostProcessingTaskSpec readSpecEntry(const std::string& entryID, const boost::property_tree::ptree& entryTree, const boost::property_tree::ptree& wholeTree); +template <> +ExternalTaskSpec readSpecEntry(const std::string& entryID, const boost::property_tree::ptree& entryTree, const boost::property_tree::ptree& wholeTree); +template <> +GRPGeomRequestSpec readSpecEntry(const std::string& entryID, const boost::property_tree::ptree& entryTree, const boost::property_tree::ptree& wholeTree); +template <> +GlobalTrackingDataRequestSpec readSpecEntry(const std::string& entryID, const boost::property_tree::ptree& entryTree, const boost::property_tree::ptree& wholeTree); +template <> +CommonSpec readSpecEntry(const std::string& entryID, const boost::property_tree::ptree& entryTree, const boost::property_tree::ptree& wholeTree); + +// todo: section names should be enum. +template +std::vector readSectionSpec(const boost::property_tree::ptree& wholeTree, const std::string& section) +{ + const auto& qcTree = wholeTree.get_child("qc"); + std::vector sectionSpec; + if (qcTree.find(section) != qcTree.not_found()) { + const auto& sectionTree = qcTree.get_child(section); + sectionSpec.reserve(sectionTree.size()); + for (const auto& [entryID, entryTree] : sectionTree) { + sectionSpec.push_back(readSpecEntry(entryID, entryTree, wholeTree)); + } + } + return sectionSpec; +} + +std::string validateDetectorName(std::string name); + +} // namespace InfrastructureSpecReader +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_INFRASTRUCTURESPECREADER_H diff --git a/Framework/include/QualityControl/InputUtils.h b/Framework/include/QualityControl/InputUtils.h new file mode 100644 index 0000000000..fca66c3ce7 --- /dev/null +++ b/Framework/include/QualityControl/InputUtils.h @@ -0,0 +1,31 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_INPUT_UTILS_H +#define QC_INPUT_UTILS_H + +// std +#include +#include +// o2 +#include +#include + +inline std::vector stringifyInput(const o2::framework::Inputs& inputs) +{ + std::vector vec; + for (const auto& input : inputs) { + vec.push_back(o2::framework::DataSpecUtils::describe(input)); + } + return vec; +} + +#endif diff --git a/Framework/include/QualityControl/KafkaPoller.h b/Framework/include/QualityControl/KafkaPoller.h new file mode 100644 index 0000000000..93de078a15 --- /dev/null +++ b/Framework/include/QualityControl/KafkaPoller.h @@ -0,0 +1,62 @@ +// Copyright 2019-2024 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// @file KafkaPoller.h +/// @author Michal Tichak + +#ifndef QC_CORE_KAFKA_CONSUMER_H +#define QC_CORE_KAFKA_CONSUMER_H + +#include +#include +#include "Activity.h" + +namespace o2::quality_control::core +{ + +namespace proto +{ + +auto recordToEvent(const kafka::Value&) -> std::optional; + +namespace start_of_run +{ +void fillActivity(const events::Event& event, Activity& activity); +bool isValid(const events::Event& event, const std::string& environmentID = "", int runNumber = 0); +} // namespace start_of_run + +namespace end_of_run +{ +void fillActivity(const events::Event& event, Activity& activity); +bool isValid(const events::Event& event, const std::string& environmentID = "", int runNumber = 0); +} // namespace end_of_run + +} // namespace proto + +class KafkaPoller +{ + public: + using KafkaRecords = std::vector; + + explicit KafkaPoller(const std::string& brokers, const std::string& groupId); + + void subscribe(const std::string& topic, size_t numberOfRetries = 5); + // timeout is used to wait if there are not messages. + auto poll(std::chrono::milliseconds timeout = std::chrono::milliseconds{ 10 }) -> KafkaRecords; + + private: + kafka::clients::consumer::KafkaConsumer mConsumer; +}; + +} // namespace o2::quality_control::core + +#endif diff --git a/Framework/include/QualityControl/LinkDef.h b/Framework/include/QualityControl/LinkDef.h index 0529374239..08fde33986 100644 --- a/Framework/include/QualityControl/LinkDef.h +++ b/Framework/include/QualityControl/LinkDef.h @@ -5,14 +5,18 @@ #pragma link C++ namespace o2::quality_control::core; #pragma link C++ namespace o2::quality_control::checker; +#pragma link C++ namespace o2::quality_control::postprocessing; -#pragma link C++ class o2::quality_control::core::MonitorObject + ; -#pragma link C++ class o2::quality_control::core::Quality + ; +#pragma link C++ class o2::quality_control::core::UserCodeInterface + ; #pragma link C++ class o2::quality_control::checker::CheckInterface + ; -#pragma link C++ class o2::quality_control::core::CheckDefinition + ; #pragma link C++ class o2::quality_control::core::TaskInterface + ; - -#pragma link C++ class std::pair < std::string, o2::quality_control::core::CheckDefinition>; -#pragma link C++ class std::map < std::string, o2::quality_control::core::CheckDefinition>; +#pragma link C++ class o2::quality_control::checker::AggregatorInterface + ; +#pragma link C++ class o2::quality_control::postprocessing::PostProcessingInterface + ; +#pragma link C++ class o2::quality_control::postprocessing::TrendingTask + ; +#pragma link C++ class o2::quality_control::core::MonitorObjectCollection + ; +#pragma link C++ class o2::quality_control::core::ValidityInterval + ; +#pragma link C++ class o2::quality_control::postprocessing::SliceInfo + ; +#pragma link C++ class std::vector + ; +#pragma link C++ class o2::quality_control::postprocessing::SliceTrendingTask + ; #endif diff --git a/Framework/include/QualityControl/LogDiscardParameters.h b/Framework/include/QualityControl/LogDiscardParameters.h new file mode 100644 index 0000000000..dcb75149ba --- /dev/null +++ b/Framework/include/QualityControl/LogDiscardParameters.h @@ -0,0 +1,36 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// @file LogDiscardParameters.h +/// @author Barthelemy von Haller +/// + +#ifndef QC_CORE_DISCARDPARAMETERS_H +#define QC_CORE_DISCARDPARAMETERS_H + +#include + +namespace o2::quality_control::core +{ + +struct LogDiscardParameters { + bool debug = true; // Discard debug messages + int fromLevel = 21; // Discard from this level up, default: Trace + std::string file; // Discard to this file (if set) the messages whose level are equal or above `fromLevel` + unsigned long rotateMaxBytes = 0; // Rotate the file to which we discard when it reaches this size + unsigned int rotateMaxFiles = 0; // Number of files we rotate over + bool debugInDiscardFile = false; // Discarded debug messages also go to the discard file +}; + +} // namespace o2::quality_control::core + +#endif // QC_CORE_DISCARDPARAMETERS_H diff --git a/Framework/include/QualityControl/MonitorObject.h b/Framework/include/QualityControl/MonitorObject.h index 5208ad5b0a..c4663b7a60 100644 --- a/Framework/include/QualityControl/MonitorObject.h +++ b/Framework/include/QualityControl/MonitorObject.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file MonitorObject.h /// \author Barthelemy von Haller @@ -7,27 +18,28 @@ #define QC_CORE_MONITOROBJECT_H // std -#include +#include +#include #include // ROOT +#include #include +// O2 +#include // QC -#include "QualityControl/Quality.h" +#include "QualityControl/Activity.h" namespace o2::quality_control::core { -/// \brief Container for the definition of a check -struct CheckDefinition { - CheckDefinition() : result(Quality::Null) {} - - std::string name; - std::string className; - std::string libraryName; - Quality result; +struct DuplicateObjectError : virtual AliceO2::Common::ExceptionBase { + const char* what() const noexcept override + { + return "Duplicate object error"; + } }; -/// \brief This class keeps the metadata about one published object. +/// \brief This class keeps the meta data about one published object. /// /// \author Barthelemy von Haller class MonitorObject : public TObject @@ -35,17 +47,16 @@ class MonitorObject : public TObject public: /// Default constructor MonitorObject(); - MonitorObject(TObject* object, const std::string& taskName); - + MonitorObject(TObject* object, const std::string& taskName, const std::string& taskClass, const std::string& detectorName, int runNumber = 0, const std::string& periodName = "", const std::string& passName = "", const std::string& provenance = "qc"); /// Destructor ~MonitorObject() override; - /// Copy constructor - MonitorObject(const MonitorObject& other) = default; + // /// Copy constructor + MonitorObject(const MonitorObject& other); /// Move constructor MonitorObject(MonitorObject&& other) /*noexcept*/ = default; /// Copy assignment operator - MonitorObject& operator=(const MonitorObject& other) = default; + MonitorObject& operator=(const MonitorObject& other); /// Move assignment operator MonitorObject& operator=(MonitorObject&& other) /*noexcept*/ = default; @@ -54,78 +65,96 @@ class MonitorObject : public TObject const std::string getName() const; /// \brief Overwrite the TObject's method just to avoid confusion. - /// One should rather use getName(). - const char* GetName() const override { return getName().c_str(); } - - const std::string& getTaskName() const { return mTaskName; } - - void setTaskName(const std::string& taskName) { mTaskName = taskName; } - - /// - /// \brief Get the quality of this object. - /// - /// The method returns the lowest quality met amongst all the checks listed in \ref mChecks. - /// If there are no checks, the method returns \ref Quality::Null. - /// - /// @return the quality of the object - /// - Quality getQuality() const; - - TObject* getObject() const { return mObject; } - - void setObject(TObject* object) { mObject = object; } - - std::map getChecks() const { return mChecks; } - - bool isIsOwner() const { return mIsOwner; } - - void setIsOwner(bool isOwner) { mIsOwner = isOwner; } - - /// \brief Add a check to be executed on this object when computing the quality. - /// If a check with the same name already exists it will be replaced by this check. - /// Several checks can be added for the same check class name, but with different names (and - /// they will get different configuration). - /// \author Barthelemy von Haller - /// \param name Arbitrary name to identify this Check. - /// \param checkClassName The name of the class of the Check. - /// \param checkLibraryName The name of the library containing the Check. If not specified it is taken from already - /// loaded libraries. - void addCheck(const std::string name, const std::string checkClassName, const std::string checkLibraryName = ""); - - /// \brief Add or update the check with the provided name. - /// @param checkName The name of the check. If another check has already been added with this name it will be - /// replaced. - /// @param check The check to add or replace. - void addOrReplaceCheck(std::string checkName, CheckDefinition check); - - /// \brief Set the given quality to the check called checkName. - /// If no check exists with this name, it throws a AliceO2::Common::ObjectNotFoundError. - /// @param checkName The name of the check - /// @param quality The new quality of the check. - /// \throw AliceO2::Common::ObjectNotFoundError - void setQualityForCheck(std::string checkName, Quality quality); - - /// Return the check (by value!) for the given name. - /// If no such check exists, AliceO2::Common::ObjectNotFoundError is thrown. - /// \param checkName The name of the check - /// \return The CheckDefinition of the check named checkName. - /// \throw AliceO2::Common::ObjectNotFoundError - CheckDefinition getCheck(std::string checkName) const; + /// @return The name of the encapsulated object or "" if there is no object. + const char* GetName() const override; + + /// \brief Return joined task name and name of the encapsulated object (if any). + /// @return The name as "{getTaskName()}/{getName())}. + std::string getFullName() const; + + TObject* getObject() const; + void setObject(TObject* object); + + bool isIsOwner() const; + void setIsOwner(bool isOwner); + + const std::string& getTaskName() const; + void setTaskName(const std::string& taskName); + + const std::string& getDetectorName() const; + void setDetectorName(const std::string& detectorName); + + const std::string& getTaskClass() const; + void setTaskClass(const std::string& taskClass); + + Activity& getActivity(); + const Activity& getActivity() const; + void setActivity(const Activity& activity); + void updateActivity(int runNumber, const std::string& periodName, const std::string& passName, const std::string& provenance); + + void setValidity(ValidityInterval); + void updateValidity(validity_time_t value); + ValidityInterval getValidity() const; + + void setCreateMovingWindow(bool); + bool getCreateMovingWindow() const; + + /// \brief Add key value pair that will end up in the database as metadata of the object + /// Add a metadata (key value pair) to the MonitorObject. It will be stored in the database as metadata. + /// If the key already exists the value will NOT be updated. + void addMetadata(std::string key, std::string value); + /// \brief Add key value pairs that will end up in the database as metadata of the object + /// Add all the key-value pairs in the map to the MonitorObject. It will be stored in the database as metadata. + /// If a key already exists the value will NOT be updated. + void addMetadata(std::map pairs); + /// \brief Update the value of metadata. + /// If the key does not exist it will ignore it. + void updateMetadata(std::string key, std::string value); + /// \brief Get the full map of user's metadata + const std::map& getMetadataMap() const; + /// \brief Update the value of metadata or add it if it does not exist yet. + void addOrUpdateMetadata(std::string key, std::string value); + /// \brief Get metadata value of given key, returns std::nullopt if none exists; + std::optional getMetadata(const std::string& key); + + /// \brief Check if the encapsulated object inherits from the given class name + /// \param className Name of the class to check inheritance from + /// \return true if the encapsulated object inherits from the given class, false otherwise + bool encapsulatedInheritsFrom(std::string_view className) const; void Draw(Option_t* option) override; TObject* DrawClone(Option_t* option) const override; + void Copy(TObject& object) const override; + + /// \brief Build the path to this object. + /// Build the path to this object as it will appear in the GUI. + /// \return A string containing the path. + std::string getPath() const; + + const std::string& getDescription() const; + void setDescription(const std::string& description); + private: - TObject* mObject; - std::map mChecks; + std::unique_ptr mObject; std::string mTaskName; + std::string mTaskClass; + std::string mDetectorName; + std::map mUserMetadata; + std::string mDescription; + Activity mActivity; // indicates that we are the owner of mObject. It is the case by default. It is not the case when a task creates the // object. // TODO : maybe we should always be the owner ? bool mIsOwner; + // tells Merger to create an object with data from the last cycle only on the side of the complete object + bool mCreateMovingWindow = false; + + void releaseObject(); + void cloneAndSetObject(const MonitorObject&); - ClassDefOverride(MonitorObject, 3); + ClassDefOverride(MonitorObject, 15); }; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/MonitorObjectCollection.h b/Framework/include/QualityControl/MonitorObjectCollection.h new file mode 100644 index 0000000000..2d41536d53 --- /dev/null +++ b/Framework/include/QualityControl/MonitorObjectCollection.h @@ -0,0 +1,56 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MonitorObjectCollection.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_MONITOROBJECTCOLLECTION_H +#define QUALITYCONTROL_MONITOROBJECTCOLLECTION_H + +#include +#include +#include + +namespace o2::quality_control::core +{ + +class MonitorObjectCollection : public TObjArray, public mergers::MergeInterface +{ + public: + MonitorObjectCollection() = default; + ~MonitorObjectCollection() = default; + + void merge(mergers::MergeInterface* const other) override; + + void postDeserialization() override; + + void setDetector(const std::string&); + const std::string& getDetector() const; + + void setTaskName(const std::string&); + const std::string& getTaskName() const; + + void addOrUpdateMetadata(const std::string& key, const std::string& value); + + MergeInterface* cloneMovingWindow() const override; + + private: + std::string mDetector = "TST"; + std::string mTaskName = "Test"; + + ClassDefOverride(MonitorObjectCollection, 3); +}; + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_MONITOROBJECTCOLLECTION_H diff --git a/Framework/include/QualityControl/MySqlDatabase.h b/Framework/include/QualityControl/MySqlDatabase.h deleted file mode 100644 index 047b055655..0000000000 --- a/Framework/include/QualityControl/MySqlDatabase.h +++ /dev/null @@ -1,74 +0,0 @@ -/// -/// \file MySqlDatabase.h -/// \author Barthelemy von Haller -/// - -#ifndef QC_REPOSITORY_MYSQLDATABASE_H -#define QC_REPOSITORY_MYSQLDATABASE_H - -#include "Common/Timer.h" -#include "QualityControl/DatabaseInterface.h" -#include "TMySQLServer.h" - -class TMySQLResult; - -namespace o2::quality_control::repository -{ - -/// \brief Implementation of the DatabaseInterface for MySQL -/// \todo consider storing directly the TObject, not the MonitorObject, and to put all its attributes as columns -/// \todo handle ROOT IO streamers -class MySqlDatabase : public DatabaseInterface -{ - public: - /// Default constructor - MySqlDatabase(); - /// Destructor - ~MySqlDatabase() override; - - void connect(std::string host, std::string database, std::string username, std::string password) override; - void connect(const std::unordered_map& config) override; - void store(std::shared_ptr mo) override; - o2::quality_control::core::MonitorObject* retrieve(std::string taskName, std::string objectName) override; - std::string retrieveJson(std::string taskName, std::string objectName) override; - void disconnect() override; - std::vector getPublishedObjectNames(std::string taskName) override; - std::vector getListOfTasksWithPublications() override; - void truncate(std::string taskName, std::string objectName) override; - - private: - /** - * \brief Execute the query. - * The result object must be deleted by the user. - */ - TMySQLResult* query(std::string sql); - - /** - * \brief Execute a query that doesn't return results; - * @return true if successful. - */ - bool execute(std::string sql); - - /** - * \brief Create a new index on table 'table'. - * The name of the index is \_i_\. - */ - void addIndex(std::string table, std::string column); - - void prepareTaskDataContainer(std::string taskName) override; - - void storeQueue(); - void storeForTask(std::string taskName); - - TMySQLServer* mServer; - - // Queue - // name of tasks -> vector of mo - std::map>> mObjectsQueue; - size_t queueSize; - AliceO2::Common::Timer lastStorage; -}; - -} // namespace o2::quality_control::repository - -#endif // QC_REPOSITORY_MYSQLDATABASE_H diff --git a/Framework/include/QualityControl/ObjectMetadataHelpers.h b/Framework/include/QualityControl/ObjectMetadataHelpers.h new file mode 100644 index 0000000000..0542ab3844 --- /dev/null +++ b/Framework/include/QualityControl/ObjectMetadataHelpers.h @@ -0,0 +1,34 @@ +// Copyright 2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectMetadataHelpers.h +/// \author Michal Tichak +/// + +#ifndef QUALITYCONTROL_OBJECTMETADATAHELPERS_H +#define QUALITYCONTROL_OBJECTMETADATAHELPERS_H + +#include +#include + +namespace o2::quality_control::repository +{ +/** + * \brief Parses metadata value stored under metadata_keys::cycle + * @param cycleStr string expecting unsigned number + * @return if parsing fails (eg. too big of a number, string wasn't a number) it returns nullopt + * + */ +std::optional parseCycle(const std::string& cycleStr); +} // namespace o2::quality_control::repository + +#endif diff --git a/Framework/include/QualityControl/ObjectMetadataKeys.h b/Framework/include/QualityControl/ObjectMetadataKeys.h new file mode 100644 index 0000000000..a111b6711b --- /dev/null +++ b/Framework/include/QualityControl/ObjectMetadataKeys.h @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectMetadataKeys.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_OBJECTMETADATAKEYS_H +#define QUALITYCONTROL_OBJECTMETADATAKEYS_H + +/// \brief Definitions of keys for metadata stored in the repository +namespace o2::quality_control::repository::metadata_keys +{ + +// CCDB +constexpr auto validFrom = "Valid-From"; +constexpr auto validUntil = "Valid-Until"; +constexpr auto created = "Created"; +constexpr auto md5sum = "Content-MD5"; +constexpr auto objectType = "ObjectType"; +constexpr auto lastModified = "lastModified"; + +// General QC framework +constexpr auto qcVersion = "qc_version"; +constexpr auto qcDetectorCode = "qc_detector_name"; +constexpr auto qcTaskName = "qc_task_name"; +constexpr auto qcTaskClass = "qc_task_class"; +constexpr auto qcQuality = "qc_quality"; +constexpr auto qcCheckName = "qc_check_name"; +constexpr auto qcAdjustableEOV = "adjustableEOV"; // this is a keyword for the CCDB +constexpr auto cycleNumber = "CycleNumber"; + +// QC Activity +constexpr auto runType = "RunType"; +constexpr auto runNumber = "RunNumber"; +constexpr auto passName = "PassName"; +constexpr auto periodName = "PeriodName"; +constexpr auto beamType = "BeamType"; +constexpr auto fillNumber = "FillNumber"; +constexpr auto partitionName = "PartitionName"; +constexpr auto originalRunNumber = "OriginalRunNumber"; + +} // namespace o2::quality_control::repository::metadata_keys + +#endif // QUALITYCONTROL_OBJECTMETADATAKEYS_H diff --git a/Framework/include/QualityControl/ObjectsManager.h b/Framework/include/QualityControl/ObjectsManager.h index cc3141a766..7a3f3bbd32 100644 --- a/Framework/include/QualityControl/ObjectsManager.h +++ b/Framework/include/QualityControl/ObjectsManager.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file ObjectsManager.h /// \author Barthelemy von Haller @@ -6,17 +17,35 @@ #ifndef QC_CORE_OBJECTMANAGER_H #define QC_CORE_OBJECTMANAGER_H +// QC +#include "QualityControl/Activity.h" #include "QualityControl/MonitorObject.h" -#include "QualityControl/Quality.h" -#include "QualityControl/TaskConfig.h" -#include -#include -#include +#include "QualityControl/MonitorObjectCollection.h" +#include +// stl #include +class TObject; + namespace o2::quality_control::core { +enum class PublicationPolicy { + // QC framework will publish the object once after TaskInterface::endOfCycle() or PostProcessingInterface::update() + // and then will remove it from the list of published objects. Typically to be used in TaskInterface::endOfCycle() + // and PostProcessingInterface::update() + Once, + // QC framework will continue publishing this object after each TaskInterface::endOfCycle() and + // PostProcessingInterface::update(), up to and including TaskInterface::endOfCycle() at EndOfStream and + // PostProcessingInterface::finalize(). It will remove it from the list of published objects after that. + // Typically to be used in TaskInterface::startOfActivity() and PostProcessingInterface::initialize() + ThroughStop, + // QC framework will continue publishing this object after each TaskInterface::endOfCycle() and + // PostProcessingInterface::update() until the user task is destructed. + // Usually to be used in TaskInterface::initialize() and PostProcessingInterface::configure() + Forever +}; + /// \brief Keeps the list of encapsulated objects to publish and does the actual publication. /// /// Keeps a list of the objects to publish, encapsulates them and does the actual publication. @@ -25,51 +54,175 @@ namespace o2::quality_control::core /// \author Barthelemy von Haller class ObjectsManager { - friend class TaskControl; // TaskControl must be able to call "publish()" whenever needed. Nobody else can. - public: - ObjectsManager(TaskConfig& taskConfig); + /** + * Constructor + * @param taskName Task name + * @param taskClass Task's class + * @param detectorName Detector 3-letter code + * @param consulUrl Consul URL, for the service discovery + * @param parallelTaskID ID of a parallel Task, use 0 if there is only one. + * @param noDiscovery If true disables the use of ServiceDiscovery + */ + ObjectsManager(std::string taskName, std::string taskClass, std::string detectorName, int parallelTaskID = 0); virtual ~ObjectsManager(); + static const std::string gDrawOptionsKey; + static const std::string gDisplayHintsKey; + /** * Start publishing the object obj, i.e. it will be pushed forward in the workflow at regular intervals. * The ownership remains to the caller. - * In most cases, objectName parameter can be ignored. + * @param IgnoreMergeable if you want to ignore static_assert check for Mergeable + * @param T type of object that we want to publish. * @param obj The object to publish. - * @param objectName Optional, to publish something under a different name. + * @throws DuplicateObjectError */ - void startPublishing(TObject* obj, std::string objectName = ""); - // todo stoppublishing + template + void startPublishing(T obj, PublicationPolicy policy = PublicationPolicy::Forever) + { + // We don't want to do this compile time check in PostProcessing, and we want to turn off runtime check as well + bool ignoreMergeableRuntime = IgnoreMergeable; +#ifndef QUALITYCONTROL_POSTPROCESSINTERFACE_H + static_assert(std::same_as, TObject> || + IgnoreMergeable || mergers::Mergeable, + "you are trying to startPublishing object that is not mergeable." + " If you know what you are doing use startPublishing(...)"); +#else + ignoreMergeableRuntime = true; +#endif + startPublishingImpl(obj, policy, ignoreMergeableRuntime); + } /** - * Return the quality of the object whose name is contained in objectName. + * Stop publishing this object + * @param obj + * @throw ObjectNotFoundError if object is not found. + */ + void stopPublishing(TObject* obj); + + /** + * Stop publishing this object + * @param obj + * @throw ObjectNotFoundError if object is not found. + */ + void stopPublishing(const std::string& objectName); + + /// \brief Stop publishing all objects with this publication policy + void stopPublishing(PublicationPolicy policy); + + /// \brief Stop publishing all registered objects + void stopPublishingAll(); + + /** + * Check whether an object is already being published * @param objectName - * @return The quality of the object if was found. + * @return true if the object is already being published + */ + bool isBeingPublished(const std::string& name); + + /** + * Returns the published MonitorObject specified by its name + * @param objectName The name of the object to find. + * @return A pointer to the MonitorObject. + * @throw ObjectNotFoundError if the object is not found. + */ + MonitorObject* getMonitorObject(const std::string& objectName); + + MonitorObjectCollection* getNonOwningArray() const; + + /** + * \brief Add metadata to a MonitorObject. + * Add a metadata pair to a MonitorObject. This is propagated to the database. + * @param objectName Name of the MonitorObject. + * @param key Key of the metadata. + * @param value Value of the metadata. * @throw ObjectNotFoundError if object is not found. */ - Quality getQuality(std::string objectName); + void addMetadata(const std::string& objectName, const std::string& key, const std::string& value); - /// \brief Add a check to the object defined by objectName. - /// If a check with the same already exist for this object, it will be replaced. - /// \param objectName - /// \param checkName - /// \param checkClassName - /// \param checkLibraryName - void addCheck(const std::string& objectName, const std::string& checkName, const std::string& checkClassName, - const std::string& checkLibraryName = ""); + /** + * \brief Add or update metadata to a MonitorObject. + * Add or update a metadata pair to a MonitorObject. This is propagated to the database. + * @param objectName Name of the MonitorObject. + * @param key Key of the metadata. + * @param value Value of the metadata. + * @throw ObjectNotFoundError if object is not found. + */ + void addOrUpdateMetadata(const std::string& objectName, const std::string& key, const std::string& value); + + /** + * \brief Set default draw options for this object. + * If possible, the object will be drawn with these options (in the ROOT sense). + * See for example https://root.cern/doc/master/classTHistPainter.html#HP01 + * E.g. manager->setDefaultDRawOptions("histo1", "colz"); + * @param objectName Name of the object affected by these drawOptions. + * @param options The list of options, space separated. + * @throw ObjectNotFoundError if object is not found. + */ + void setDefaultDrawOptions(const std::string& objectName, const std::string& options); + + /** + * See setDefaultDrawOptions(const std::string& objectName, const std::string& hioptionsnts). + */ + void setDefaultDrawOptions(TObject* obj, const std::string& options); + + /** + * \brief Indicate how to display this object. + * A number of options can be set on a canvas to influence the way the object is displayed. + * For drawOptions, use setDefaultDrawOptions, for others such as logarithmic scale or grid, use this method. + * Currently supported by QCG: logx, logy, logz, gridx, gridy, gridz + * @param objectName Name of the object affected by these drawOptions. + * @param options The list of hints, space separated. + * @throw ObjectNotFoundError if object is not found. + */ + void setDisplayHint(const std::string& objectName, const std::string& hints); + + /** + * See setDisplayHint(const std::string& objectName, const std::string& hints). + */ + void setDisplayHint(TObject* obj, const std::string& hints); - void addCheck(const TObject* object, const std::string& checkName, const std::string& checkClassName, - const std::string& checkLibraryName = ""); + /** + * Get the number of objects that have been published. + * @return an int with the number of objects. + */ + size_t getNumberPublishedObjects(); - MonitorObject* getMonitorObject(std::string objectName); + /** + * Returns the published MonitorObject specified by its index + * @param index + * @return A pointer to the MonitorObject. + * @throw ObjectNotFoundError if the object is not found. + */ + MonitorObject* getMonitorObject(size_t index); - TObject* getObject(std::string objectName); + /** + * \brief Sets the validity interval of all registered objects. + */ + void setValidity(ValidityInterval); + + /** + * \brief Extends the validity interval of all registered objects to the provided value. + */ + void updateValidity(validity_time_t); + + const Activity& getActivity() const; + void setActivity(const Activity& activity); - TObjArray* getNonOwningArray() const { return new TObjArray(mMonitorObjects); }; + void setMovingWindowsList(const std::vector&); + const std::vector& getMovingWindowsList() const; private: - TObjArray mMonitorObjects; + std::unique_ptr mMonitorObjects; + std::map mPublicationPoliciesForMOs; std::string mTaskName; + std::string mTaskClass; + std::string mDetectorName; + Activity mActivity; + std::vector mMovingWindowsList; + + void startPublishingImpl(TObject* obj, PublicationPolicy, bool ignoreMergeableWarning); }; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/PostProcessingConfig.h b/Framework/include/QualityControl/PostProcessingConfig.h new file mode 100644 index 0000000000..8261496478 --- /dev/null +++ b/Framework/include/QualityControl/PostProcessingConfig.h @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingConfig.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_POSTPROCESSINGCONFIG_H +#define QUALITYCONTROL_POSTPROCESSINGCONFIG_H + +#include +#include +#include +#include "QualityControl/Activity.h" +#include "QualityControl/UserCodeConfig.h" + +namespace o2::quality_control::postprocessing +{ + +// todo pretty print + +/// \brief Post-processing configuration structure +struct PostProcessingConfig : public o2::quality_control::core::UserCodeConfig { + PostProcessingConfig() = default; + PostProcessingConfig(const std::string& id, const boost::property_tree::ptree& config); + ~PostProcessingConfig() = default; + std::string id; + std::string taskName; + std::vector initTriggers = {}; + std::vector updateTriggers = {}; + std::vector stopTriggers = {}; + std::string kafkaBrokersUrl; + std::string kafkaTopicAliECSRun; + core::Activity activity; + bool matchAnyRunNumber = false; + bool validityFromLastTriggerOnly = false; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_POSTPROCESSINGCONFIG_H diff --git a/Framework/include/QualityControl/PostProcessingDevice.h b/Framework/include/QualityControl/PostProcessingDevice.h new file mode 100644 index 0000000000..c5c94f8d52 --- /dev/null +++ b/Framework/include/QualityControl/PostProcessingDevice.h @@ -0,0 +1,77 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingDevice.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_POSTPROCESSINGDEVICE_H +#define QUALITYCONTROL_POSTPROCESSINGDEVICE_H + +#include +#include +#include +#include "QualityControl/PostProcessingRunnerConfig.h" + +#include +#include + +namespace o2::quality_control::postprocessing +{ + +class PostProcessingRunner; +struct PostProcessingRunnerConfig; + +/// \brief A class driving the execution of a QC PostProcessing task inside DPL. +/// +/// \author Piotr Konopka +class PostProcessingDevice : public framework::Task +{ + public: + /// \brief Constructor + /// + /// \param taskName - name of the task, which exists in tasks list in the configuration file + /// \param configurationSource - absolute path to configuration file, preceded with backend (f.e. "json://") + PostProcessingDevice(const PostProcessingRunnerConfig& runnerConfig); + ~PostProcessingDevice() override = default; + + /// \brief PostProcessingDevice's init callback + void init(framework::InitContext&) override; + /// \brief PostProcessingDevice's process callback + void run(framework::ProcessingContext&) override; + + const std::string& getDeviceName(); + framework::Inputs getInputsSpecs(); + framework::Outputs getOutputSpecs() const; + framework::Options getOptions(); + + /// \brief Data Processor Label to identify all Task Runners + static framework::DataProcessorLabel getLabel() { return { "qc-pp-task-runner" }; } + static std::string createPostProcessingDeviceName(const std::string& taskName, const std::string& detectorName); + + private: + /// \brief Callback for CallbackService::Id::Start (DPL) a.k.a. RUN transition (FairMQ) + void start(framework::ServiceRegistryRef services); + /// \brief Callback for CallbackService::Id::Stop (DPL) a.k.a. STOP transition (FairMQ) + void stop(framework::ServiceRegistryRef services); + /// \brief Callback for CallbackService::Id::Reset (DPL) a.k.a. RESET DEVICE transition (FairMQ) + void reset(); + + private: + std::shared_ptr mRunner; + std::string mDeviceName; + PostProcessingRunnerConfig mRunnerConfig; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_POSTPROCESSINGDEVICE_H diff --git a/Framework/include/QualityControl/PostProcessingFactory.h b/Framework/include/QualityControl/PostProcessingFactory.h new file mode 100644 index 0000000000..cdcfd40d5d --- /dev/null +++ b/Framework/include/QualityControl/PostProcessingFactory.h @@ -0,0 +1,43 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingFactory.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_POSTPROCESSINGFACTORY_H +#define QUALITYCONTROL_POSTPROCESSINGFACTORY_H + +namespace o2::quality_control::postprocessing +{ + +class PostProcessingInterface; +class PostProcessingConfig; + +/// \brief Factory in charge of creating post-processing tasks +/// +/// The factory needs a library name and a class name provided as an object of type PostProcessingConfig. +/// The class loaded in the library must inherit from PostProcesingInterface +class PostProcessingFactory +{ + public: + PostProcessingFactory() = default; + virtual ~PostProcessingFactory() = default; + + /// \brief Create a new instance of a PostProcessingInterface. + /// The PostProcessingInterface actual class is decided based on the parameters passed. + PostProcessingInterface* create(const PostProcessingConfig& config); +}; + +} // namespace o2::quality_control::postprocessing + +#endif //QUALITYCONTROL_POSTPROCESSINGFACTORY_H diff --git a/Framework/include/QualityControl/PostProcessingInterface.h b/Framework/include/QualityControl/PostProcessingInterface.h new file mode 100644 index 0000000000..e253f9422c --- /dev/null +++ b/Framework/include/QualityControl/PostProcessingInterface.h @@ -0,0 +1,89 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingInterface.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_POSTPROCESSINTERFACE_H +#define QUALITYCONTROL_POSTPROCESSINTERFACE_H + +#include +#include +#include "QualityControl/Triggers.h" +#include "QualityControl/ObjectsManager.h" +#include "QualityControl/UserCodeInterface.h" +#include + +namespace o2::quality_control::postprocessing +{ + +/// \brief Skeleton of a post-processing task. +/// +/// Abstract class defining the skeleton and the common interface of a post-processing task. +/// It is therefore the parent class of any post-processing task. +/// It is responsible for retrieving, processing and storing the data, mainly from and to QC repository. +/// +/// \author Piotr Konopka +class PostProcessingInterface : public core::UserCodeInterface +{ + public: + PostProcessingInterface() = default; + virtual ~PostProcessingInterface() = default; + + /// \brief Configure the task with custom parameters, optional for PostProcessingInterface + /// + /// Users can use this method to configure their task with custom parameters + /// It is called each time mCustomParameters is updated, including the first time it is read. + virtual void configure() override; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config boost property with the full QC configuration file + virtual void configure(const boost::property_tree::ptree& config); + + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + virtual void initialize(Trigger trigger, framework::ServiceRegistryRef services) = 0; + + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + virtual void update(Trigger trigger, framework::ServiceRegistryRef services) = 0; + + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + virtual void finalize(Trigger trigger, framework::ServiceRegistryRef services) = 0; + + void setObjectsManager(std::shared_ptr objectsManager); + void setID(const std::string& id); + [[nodiscard]] const std::string& getID() const; + + protected: + std::shared_ptr getObjectsManager(); + + private: + std::string mID; + std::shared_ptr mObjectsManager; +}; + +} // namespace o2::quality_control::postprocessing + +#endif //QUALITYCONTROL_POSTPROCESSINTERFACE_H diff --git a/Framework/include/QualityControl/PostProcessingRunner.h b/Framework/include/QualityControl/PostProcessingRunner.h new file mode 100644 index 0000000000..779e0edb63 --- /dev/null +++ b/Framework/include/QualityControl/PostProcessingRunner.h @@ -0,0 +1,132 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingRunner.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_POSTPROCESSINGRUNNER_H +#define QUALITYCONTROL_POSTPROCESSINGRUNNER_H + +#include +#include +#include +#include +#include "QualityControl/PostProcessingConfig.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/PostProcessingRunnerConfig.h" +#include "QualityControl/Triggers.h" +#include "QualityControl/Activity.h" +#include "QualityControl/DatabaseInterface.h" +#include "WorkflowType.h" + +namespace o2::framework +{ +class DataAllocator; +} // namespace o2::framework + +namespace o2::quality_control::core +{ +struct CommonSpec; +class Activity; +class ObjectsManager; +class MonitorObjectCollection; +} // namespace o2::quality_control::core + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +class PostProcessingTaskSpec; +using MOCPublicationCallback = std::function; + +/// \brief A class driving the execution of a post-processing task +/// +/// It is responsible for setting up a post-processing task and executing its methods corresponding to its state. The +/// state transitions are determined by triggers defined by user. +/// +/// \author Piotr Konopka +class PostProcessingRunner +{ + public: + explicit PostProcessingRunner(std::string id); + ~PostProcessingRunner() = default; + + /// \brief Initialization. Creates configuration structures out of the ptree. Throws on errors. + void init(const boost::property_tree::ptree& config, o2::quality_control::core::WorkflowType workflowType); + /// \brief Initialization. Throws on errors. + void init(const PostProcessingRunnerConfig& runnerConfig, const PostProcessingConfig& taskConfig); + /// \brief One iteration over the event loop. Throws on errors. Returns false when it can gracefully exit. + bool run(); + /// \brief Start transition. Throws on errors. + void start(framework::ServiceRegistryRef dplServices); + /// \brief Stop transition. Throws on errors. + void stop(framework::ServiceRegistryRef dplServices); + /// \brief Reset transition. Throws on errors. + void reset(); + /// \brief Runs the task over selected timestamps, performing the full start, run, stop cycle. + /// + /// \param t A vector with timestamps (ms since epoch). + /// The first is used for task initialisation, the last for task finalisation, so at least two are required. + void runOverTimestamps(const std::vector& t); + + /// \brief Set how objects should be published. If not used, objects will be stored in repository. + /// + /// \param callback MonitorObjectCollection publication callback + void setPublicationCallback(MOCPublicationCallback callback); + + const std::string& getID() const; + + static PostProcessingRunnerConfig extractConfig(const core::CommonSpec& commonSpec, const PostProcessingTaskSpec& ppTaskSpec); + + private: + void updateValidity(const Trigger& trigger); + void doInitialize(const Trigger& trigger); + void doUpdate(const Trigger& trigger); + void doFinalize(const Trigger& trigger); + + enum class TaskState { + INVALID, + Created, + Running, + Finished + }; + TaskState mTaskState = TaskState::INVALID; + std::vector mInitTriggers; + std::vector mUpdateTriggers; + std::vector mStopTriggers; + + std::unique_ptr mTask; + framework::ServiceRegistry mServices; + std::shared_ptr mObjectManager; + // TODO in a longer run, we should store from/to in the MonitorObject itself and use them. + std::function mPublicationCallback = nullptr; + + std::string mID{}; + core::Activity mActivity; + PostProcessingConfig mTaskConfig; + PostProcessingRunnerConfig mRunnerConfig; + std::shared_ptr mSourceDatabase; + std::shared_ptr mDestinationDatabase; + std::unique_ptr configureDatabase(std::unordered_map& dbConfig, const std::string& name); +}; + +MOCPublicationCallback publishToDPL(o2::framework::DataAllocator&, std::string outputBinding); +MOCPublicationCallback publishToRepository(o2::quality_control::repository::DatabaseInterface&); + +} // namespace o2::quality_control::postprocessing + +#endif //QUALITYCONTROL_POSTPROCESSINGRUNNER_H diff --git a/Framework/include/QualityControl/PostProcessingRunnerConfig.h b/Framework/include/QualityControl/PostProcessingRunnerConfig.h new file mode 100644 index 0000000000..3c2285bec7 --- /dev/null +++ b/Framework/include/QualityControl/PostProcessingRunnerConfig.h @@ -0,0 +1,44 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingRunner.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_POSTPROCESSINGRUNNERCONFIG_H +#define QUALITYCONTROL_POSTPROCESSINGRUNNERCONFIG_H + +#include +#include +#include +#include "QualityControl/LogDiscardParameters.h" + +namespace o2::quality_control::postprocessing +{ + +struct PostProcessingRunnerConfig { + std::string id; + std::string taskName; + std::string detectorName; + std::unordered_map sourceDatabase; + std::unordered_map destinationDatabase; + std::string consulUrl{}; + std::string bookkeepingUrl{}; + core::LogDiscardParameters infologgerDiscardParameters; + double periodSeconds = 10.0; + std::string configKeyValues; // These are for ConfigurableParams, not for override-values! + boost::property_tree::ptree configTree{}; +}; + +} // namespace o2::quality_control::postprocessing + +#endif //QUALITYCONTROL_POSTPROCESSINGRUNNERCONFIG_H \ No newline at end of file diff --git a/Framework/include/QualityControl/PostProcessingTaskSpec.h b/Framework/include/QualityControl/PostProcessingTaskSpec.h new file mode 100644 index 0000000000..1d88578f0e --- /dev/null +++ b/Framework/include/QualityControl/PostProcessingTaskSpec.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_POSTPROCESSINGTASKSPEC_H +#define QUALITYCONTROL_POSTPROCESSINGTASKSPEC_H + +/// +/// \file PostProcessingTaskSpec.h +/// \author Piotr Konopka +/// + +#include +#include +#include + +namespace o2::quality_control::postprocessing +{ + +struct PostProcessingTaskSpec { + // default, invalid spec + PostProcessingTaskSpec() = default; + + // minimal valid spec + PostProcessingTaskSpec(std::string id, std::string taskName, std::string detectorName) + : id(std::move(id)), taskName(std::move(taskName)), detectorName(std::move(detectorName)) + { + } + + // Post Processing Tasks configure themselves with a ptree. + // While this is a lack of consequence with respect to other *Specs, + // I am afraid it is too late to change it, since also users rely on this (see ITS Trending Task). + + std::string id = "Invalid"; + std::string taskName = "Invalid"; + bool active = true; + bool critical = true; + std::string detectorName = "Invalid"; + boost::property_tree::ptree tree = {}; + core::CustomParameters customParameters; + std::unordered_map sourceDatabase; +}; + +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_POSTPROCESSINGTASKSPEC_H diff --git a/Framework/include/QualityControl/Provenance.h b/Framework/include/QualityControl/Provenance.h new file mode 100644 index 0000000000..dd3ad53cb9 --- /dev/null +++ b/Framework/include/QualityControl/Provenance.h @@ -0,0 +1,46 @@ +// Copyright 2024 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Provenance.h +/// \author Michal Tichak +/// + +#include +#include + +namespace o2::quality_control::core +{ + +enum class Provenance { + SyncQC, + AsyncQC, + MCQC +}; + +inline Provenance toEnum(const std::string& provenance) +{ + if (provenance == "qc_mc") { + return Provenance::MCQC; + } + + if (provenance == "qc") { + return Provenance::SyncQC; + } + + if (provenance == "qc_async") { + return Provenance::AsyncQC; + } + + throw std::runtime_error{ "unknown provenance flag: " + provenance }; +} + +} // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/QCInputs.h b/Framework/include/QualityControl/QCInputs.h new file mode 100644 index 0000000000..50f95ddf9c --- /dev/null +++ b/Framework/include/QualityControl/QCInputs.h @@ -0,0 +1,185 @@ +// Copyright 2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QCInputs.h +/// \author Michal Tichak +/// \brief Generic container for heterogeneous QC input data. +/// +/// \par Example +/// \code{.cpp} +/// QCInputs data; +/// auto* h1 = new TH1F("th11", "th11", 100, 0, 99); +/// data.insert("mo", std::make_shared(h1, "taskname", "class1", "TST")); +/// if (auto opt = data.get("mo")) { +/// MonitorObject& moObject = opt.value(); +/// std::cout << "mo name: " << moObject.getName() << std::endl; +/// } +/// for (const auto& mo : data.iterateByType()) { +/// // process each value +/// } +/// \endcode +/// + +#ifndef QC_CORE_DATA_H +#define QC_CORE_DATA_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::core +{ + +/// \brief Requires a callable to return exactly the specified type. +/// \tparam Function Callable type to invoke. +/// \tparam Result Expected return type. +/// \tparam Args Argument types for invocation. +template +concept invocable_r = std::invocable && + std::same_as, Result>; + +/// \brief Heterogeneous storage for named QC input objects. +/// +/// Stores values in an std::unordered_map while +/// offering type-safe get, iteration, filtering, and transformation. +class QCInputs +{ + public: + QCInputs() = default; + + /// \brief Retrieve the object stored under the given key with matching type. + /// \tparam Result Expected stored type. + /// \param key Identifier for the stored object. + /// \returns Optional reference to const Result if found desired item of type Result. + /// \par Example + /// \code{.cpp} + /// if (auto opt = data.get("mo")) { + /// if (opt.has_value()){ + /// const unsigned& value = opt.value(); // careful about using auto here as we want to invoke implicit conversion operator of reference_wrapper + /// } + /// } + /// \endcode + template + std::optional> get(std::string_view key); + + /// \brief Construct and store an object of type T under the given key. + /// \tparam T Type to construct and store. + /// \param key Identifier under which to store the object. + /// \param args Arguments forwarded to T's constructor. + /// \par Example + /// \code{.cpp} + /// auto* h1 = new TH1F("th11", "th11", 100, 0, 99); + /// data.emplace("mo", h1, "taskname", "class1", "TST"); + /// \endcode + template + void emplace(std::string_view key, Args&&... args); + + /// \brief Store a copy of value under the given key. + /// \tparam T Type of the value to store. + /// \param key Identifier under which to store the value. + /// \param value Const reference to the value to insert. + /// \par Example + /// \code{.cpp} + /// auto* h1 = new TH1F("th11", "th11", 100, 0, 99); + /// data.insert("mo", std::make_shared(h1, "taskname", "class1", "TST")); + /// \endcode + template + void insert(std::string_view key, const T& value); + + /// \brief Iterate over all stored objects matching type Result. + /// \tparam Result Type filter for iteration. + /// \returns Range of const references to stored Result instances. + /// \par Example + /// \code{.cpp} + /// for (auto& mo : data.iterateByType()) { + /// // use val + /// } + /// \endcode + template + auto iterateByType() const; + + /// \brief Iterate over stored objects of type Result satisfying a predicate. + /// \tparam Result type filter for iteration. + /// \tparam Pred Callable predicate on (key, Result*) pairs. + /// \param filter Predicate to apply for filtering entries. + /// \returns Range of const references to Result passing the filter. + /// \par Example + /// \code{.cpp} + /// auto nameFilter = [](auto const& pair) { return pair.second->getName() == "name"; }; + /// for (auto& mo : data.iterateByTypeAndFilter(nameFilter)) { + /// // use mo + /// } + /// \endcode + template &> Pred> + auto iterateByTypeAndFilter(Pred&& filter) const; + + /// \brief Filter entries of type Stored, then transform to type Result. + /// \tparam Stored Original stored type for filtering. + /// \tparam Result Target type after transformation. + /// \tparam Pred Callable predicate on (key, Stored*) pairs. + /// \tparam Transform Callable transforming Stored* to Result*. + /// This Callable can return nullptr but it will be filtered out + /// from results + /// \param filter Predicate to apply before transformation. + /// \param transform Callable to convert Stored to Result. + /// \returns Range of const references to resulting objects. + /// \par Example + /// \code{.cpp} + /// // if we stored some MOs that are not TH1F, these will be filtered out of results + /// auto toHistogram = [](auto const& p) -> const auto* { return dynamic_cast(p.second->getObject()); }; + /// auto nameFilter = [](auto const& p){ return p.first == "histo"; }; + /// for (auto& h : data.iterateByTypeFilterAndTransform(nameFilter, toHistogram)) { + /// // use histogram h + /// } + /// \endcode + template &> Pred, invocable_r Transform> + auto iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const; + + /// \brief Number of stored entries. + /// \returns Size of the underlying container. + /// \par Example + /// \code{.cpp} + /// size_t n = data.size(); + /// \endcode + size_t size() const noexcept; + + private: + /// \brief Transparent hash functor for string and string_view. + /// + /// Enables heterogeneous lookup in unordered maps keyed by std::string. + struct StringHash { + using is_transparent = void; // Required for heterogeneous lookup + + std::size_t operator()(const std::string& str) const + { + return std::hash{}(str); + } + + std::size_t operator()(std::string_view sv) const + { + return std::hash{}(sv); + } + }; + + std::unordered_map> mObjects; +}; + +} // namespace o2::quality_control::core + +#include "QCInputs.inl" + +#endif diff --git a/Framework/include/QualityControl/QCInputs.inl b/Framework/include/QualityControl/QCInputs.inl new file mode 100644 index 0000000000..79795fca09 --- /dev/null +++ b/Framework/include/QualityControl/QCInputs.inl @@ -0,0 +1,135 @@ +// Copyright 2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QCInputs.inl +/// \author Michal Tichak +/// + +#include +#include + +namespace o2::quality_control::core +{ + +template +std::optional> QCInputs::get(std::string_view key) +{ + if (const auto foundIt = mObjects.find(key); foundIt != mObjects.end()) { + if (auto* casted = std::any_cast(&foundIt->second); casted != nullptr) { + return { *casted }; + } + } + return std::nullopt; +} + +template +void QCInputs::emplace(std::string_view key, Args&&... args) +{ + mObjects.emplace(key, std::any{ std::in_place_type, std::forward(args)... }); +} + +template +void QCInputs::insert(std::string_view key, const T& value) +{ + mObjects.insert({ std::string{ key }, value }); +} + +namespace internal +{ + +template +static const T* any_cast_try_shared_raw_ptr(const std::any& value) +{ + // sadly it is necessary to check for any of these types if we want to test for + // shared_ptr, raw ptr and a value + if (auto* casted = std::any_cast>(&value); casted != nullptr) { + return casted->get(); + } + if (auto* casted = std::any_cast>(&value); casted != nullptr) { + return casted->get(); + } + if (auto* casted = std::any_cast(&value); casted != nullptr) { + return *casted; + } + if (auto* casted = std::any_cast(&value); casted != nullptr) { + return *casted; + } + return std::any_cast(&value); +} + +template +static constexpr auto any_to_specific = std::views::transform( + [](const auto& pair) -> std::pair { + return { pair.first, any_cast_try_shared_raw_ptr(pair.second) }; + }); + +static constexpr auto filter_nullptr_in_pair = std::views::filter( + [](const auto& pair) -> bool { + return pair.second != nullptr; + }); + +static constexpr auto filter_nullptr = std::views::filter( + [](const auto* ptr) -> bool { + return ptr != nullptr; + }); + +static constexpr auto pair_to_value_const_ref = std::views::transform( + [](const auto& pair) -> const auto& { + return *pair.second; + }); + +static constexpr auto pair_to_value = std::views::transform( + [](const auto& pair) -> const auto* { + return pair.second; + }); + +static constexpr auto pointer_to_reference = std::views::transform( + [](const auto* ptr) -> const auto& { + return *ptr; + }); + +} // namespace internal + +template +auto QCInputs::iterateByType() const +{ + using namespace internal; + return mObjects | any_to_specific | filter_nullptr_in_pair | pair_to_value_const_ref; +} + +template &> Pred> +auto QCInputs::iterateByTypeAndFilter(Pred&& filter) const +{ + using namespace internal; + return mObjects | any_to_specific | filter_nullptr_in_pair | std::views::filter(filter) | pair_to_value_const_ref; +} + +template &> Pred, invocable_r Transform> +auto QCInputs::iterateByTypeFilterAndTransform(Pred&& filter, Transform&& transform) const +{ + using namespace internal; + return mObjects | + any_to_specific | + filter_nullptr_in_pair | + std::views::filter(filter) | + pair_to_value | + std::views::transform(transform) | + filter_nullptr | + pointer_to_reference; +} + +inline size_t QCInputs::size() const noexcept +{ + return mObjects.size(); +} + +} // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/QCInputsAdapters.h b/Framework/include/QualityControl/QCInputsAdapters.h new file mode 100644 index 0000000000..444e218d09 --- /dev/null +++ b/Framework/include/QualityControl/QCInputsAdapters.h @@ -0,0 +1,99 @@ +// Copyright 2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QCInputsAdapters.h +/// \author Michal Tichak +/// \brief Adapters to build and query QCInputs from Monitor and Quality objects. +/// +/// \par Example +/// \code{.cpp} +/// // Iterate monitor objects by task name +/// for (const auto& mo : iterateMonitorObjects(data, "task1")) { +/// // use mo +/// } +/// // Retrieve a specific MonitorObject +/// if (auto opt = getMonitorObject(data, "objName", "task1")) { +/// const auto& mo = opt->get(); +/// // use mo +/// } +/// // Iterate and retrieve quality objects +/// for (const auto& qo : iterateQualityObjects(data)) { +/// // use qo +/// } +/// if (auto qoOpt = getQualityObject(data, "check1")) { +/// if(qoOpt.has_value()){ +/// QualityObject& qo = qoOpt->value(); +/// // ... +/// } +/// // use qoOpt->get() +/// } +/// \endcode +/// + +#ifndef QC_CORE_DATA_ADAPTERS_H +#define QC_CORE_DATA_ADAPTERS_H + +// Core inputs and adapters +#include "QCInputs.h" +#include "QualityControl/MonitorObject.h" +#include "QualityObject.h" +#include "QCInputsFactory.h" + +namespace o2::quality_control::core +{ + +/// \brief Iterate over all MonitorObject entries in QCInputs. +inline auto iterateMonitorObjects(const QCInputs& data); + +/// \brief Iterate over MonitorObject entries filtered by task name. +/// \param data QCInputs containing MonitorObjects. +/// \param taskName Task name to filter entries. +inline auto iterateMonitorObjects(const QCInputs& data, std::string_view taskName); + +/// \brief Retrieve the first MonitorObject of type StoredType matching both name and task. +/// \tparam StoredType Type of MonitorObject or stored class to retrieve. +/// \param data QCInputs to search. +/// \param objectName Name of the MonitorObject. +/// \param taskName Name of the originating task. +/// \returns Optional reference to const StoredType if found. +template +std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName, std::string_view taskName); + +// returns first occurence of MO with given name (possible name clash) +/// \brief Retrieve the first MonitorObject of type StoredType matching name. +/// \tparam StoredType Type of MonitorObject or stored class to retrieve. +/// \param data QCInputs to search. +/// \param objectName Name of the MonitorObject. +/// \returns Optional reference to const StoredType if found. +template +std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName); + +/// \brief Iterate over all QualityObject entries in QCInputs. +inline auto iterateQualityObjects(const QCInputs& data); + +/// \brief Iterate over QualityObject entries filtered by check name. +/// \param data QCInputs containing QualityObjects. +/// \param checkName Check name to filter entries. +inline auto iterateQualityObjects(const QCInputs& data, std::string_view checkName); + +/// \brief Retrieve the first QualityObject matching a given check name. +/// \param data QCInputs to search. +/// \param checkName Name of the quality check. +/// \returns Optional reference to const QualityObject if found. +std::optional> getQualityObject(const QCInputs& data, std::string_view checkName); + +} // namespace o2::quality_control::core + +// Templates definitions +#include "QCInputsAdapters.inl" + +#endif diff --git a/Framework/include/QualityControl/QCInputsAdapters.inl b/Framework/include/QualityControl/QCInputsAdapters.inl new file mode 100644 index 0000000000..26d2fe2e92 --- /dev/null +++ b/Framework/include/QualityControl/QCInputsAdapters.inl @@ -0,0 +1,101 @@ +// Copyright 2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QCInputsAdapters.inl +/// \author Michal Tichak +/// + +#ifndef QC_CORE_DATA_ADAPTERS_INL +#define QC_CORE_DATA_ADAPTERS_INL + +#include +#include +#include "QCInputs.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QualityObject.h" + +namespace o2::quality_control::core +{ + +inline auto iterateMonitorObjects(const o2::quality_control::core::QCInputs& data) +{ + return data.iterateByType(); +} + +inline auto iterateMonitorObjects(const QCInputs& data, std::string_view taskName) +{ + const auto filterMOByTaskName = [taskName](const auto& pair) { + return pair.second->getTaskName() == taskName; + }; + + return data.iterateByTypeAndFilter(filterMOByTaskName); +} + +namespace helpers +{ + +template +std::optional> getMonitorObjectCommon(const QCInputs& data, Filter&& filter) +{ + if constexpr (std::same_as) { + for (const auto& mo : data.iterateByTypeAndFilter(filter)) { + return { mo }; + } + } else { + const auto getInternalObject = [](const MonitorObject* ptr) -> const auto* { + return dynamic_cast(ptr->getObject()); + }; + for (const auto& v : data.iterateByTypeFilterAndTransform(filter, getInternalObject)) { + return { v }; + } + } + return std::nullopt; +} + +} // namespace helpers + +template +std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName, std::string_view taskName) +{ + const auto filterMOByNameAndTaskName = [objectName, taskName](const auto& pair) { + return std::tuple{ std::string_view{ pair.second->GetName() }, pair.second->getTaskName() } == std::tuple{ objectName, taskName }; + }; + + return helpers::getMonitorObjectCommon(data, filterMOByNameAndTaskName); +} + +template +std::optional> getMonitorObject(const QCInputs& data, std::string_view objectName) +{ + const auto filterMOByName = [objectName](const auto& pair) { + return std::string_view(pair.second->GetName()) == objectName; + }; + + return helpers::getMonitorObjectCommon(data, filterMOByName); +} + +inline auto iterateQualityObjects(const QCInputs& data) +{ + return data.iterateByType(); +} + +inline auto iterateQualityObjects(const QCInputs& data, std::string_view checkName) +{ + const auto filterQOByName = [checkName](const auto& pair) { + return std::string_view(pair.second->getCheckName()) == checkName; + }; + return data.iterateByTypeAndFilter(filterQOByName); +} + +} // namespace o2::quality_control::core + +#endif diff --git a/Framework/include/QualityControl/QCInputsFactory.h b/Framework/include/QualityControl/QCInputsFactory.h new file mode 100644 index 0000000000..a39ee15393 --- /dev/null +++ b/Framework/include/QualityControl/QCInputsFactory.h @@ -0,0 +1,41 @@ +// Copyright 2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QCInputsFactory.h +/// \author Michal Tichak +/// \brief Factory functions to populate QCInputs from object maps. +/// + +#ifndef QC_CORE_QCINPUTSFACTORY_H +#define QC_CORE_QCINPUTSFACTORY_H + +#include "QCInputs.h" +#include "QualityControl/MonitorObject.h" +#include "QualityObject.h" +#include +#include +#include + +namespace o2::quality_control::core +{ + +/// \brief Create QCInputs from a map of MonitorObject instances. +/// \param moMap Map from name to shared MonitorObject pointer. +QCInputs createData(const std::map>& moMap); + +/// \brief Create QCInputs from a map of QualityObject instances. +/// \param qoMap Map from name to shared QualityObject pointer. +QCInputs createData(const QualityObjectsMapType& qoMap); + +} // namespace o2::quality_control::core + +#endif diff --git a/Framework/include/QualityControl/QcInfoLogger.h b/Framework/include/QualityControl/QcInfoLogger.h index 9f46475ace..1c23bf049d 100644 --- a/Framework/include/QualityControl/QcInfoLogger.h +++ b/Framework/include/QualityControl/QcInfoLogger.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// @file QcInfoLogger.h /// @author Barthelemy von Haller @@ -6,11 +17,12 @@ #ifndef QC_CORE_QCINFOLOGGER_H #define QC_CORE_QCINFOLOGGER_H -#include "TaskInterface.h" #include -#include +#include +#include "QualityControl/LogDiscardParameters.h" -typedef AliceO2::InfoLogger::InfoLogger infologger; // not to have to type the full stuff each time -> log::endm +typedef AliceO2::InfoLogger::InfoLogger infologger; // not to have to type the full stuff each time +typedef AliceO2::InfoLogger::InfoLoggerContext infoContext; namespace o2::quality_control::core { @@ -21,33 +33,94 @@ namespace o2::quality_control::core /// and configure its own instance of InfoLogger. /// Independent InfoLogger instances can still be created when and if needed. /// Usage : QcInfoLogger::GetInstance() << "blabla" << infologger::endm; +/// ILOG(Info) << "info message with implicit level Support" << ENDM; // short version +/// ILOGI << "info message" << ENDM; // shorter +/// ILOG_INST << InfoLogger::InfoLoggerMessageOption{ InfoLogger::Fatal, 1, 1, "asdf", 3 } +/// << "fatal message with extra fields" << ENDM; // complex version +/// ILOG(Info, Ops) << "Test message with severity Info and level Ops, see InfoLoggerMacros.hxx" << ENDM; /// /// \author Barthelemy von Haller -class QcInfoLogger : public AliceO2::InfoLogger::InfoLogger +class QcInfoLogger { - public: - static QcInfoLogger& GetInstance() - { - // Guaranteed to be destroyed. Instantiated on first use - static QcInfoLogger foo; - return foo; - } - - private: - QcInfoLogger() + constexpr static size_t maxFacilityLength = 32; + static AliceO2::InfoLogger::InfoLogger& GetInfoLogger() { - // TODO configure the QC infologger, e.g. proper facility - *this << "QC infologger initialized" << infologger::endm; + return *instance; } - ~QcInfoLogger() override {} - - // Disallow copying + // disable non-static + QcInfoLogger() = delete; + ~QcInfoLogger() = delete; QcInfoLogger& operator=(const QcInfoLogger&) = delete; QcInfoLogger(const QcInfoLogger&) = delete; + + static void setFacility(const std::string& facility); + static void setDetector(const std::string& detector); + static void setRun(int run); + static void setPartition(const std::string& partitionName); + static void init(const std::string& facility, + const LogDiscardParameters& discardParameters, + AliceO2::InfoLogger::InfoLogger* dplInfoLogger = nullptr, + AliceO2::InfoLogger::InfoLoggerContext* dplContext = nullptr, + int run = -1, + const std::string& partitionName = ""); + static void init(const std::string& facility, + const boost::property_tree::ptree& config, + AliceO2::InfoLogger::InfoLogger* dplInfoLogger = nullptr, + AliceO2::InfoLogger::InfoLoggerContext* dplContext = nullptr, + int run = -1, + const std::string& partitionName = ""); + static void disable(); + + // build a default infologger + static class _init + { + public: + _init() + { + instance = new AliceO2::InfoLogger::InfoLogger(); + mContext = new AliceO2::InfoLogger::InfoLoggerContext(); + mContext->setField(infoContext::FieldName::Facility, "QC"); + mContext->setField(infoContext::FieldName::System, "QC"); + instance->setContext(*mContext); + } + } _initializer; + + private: + // raw pointers because we don't want to take ownership of dpl pointers and destroy them. + // if we keep the default infologger it will any ways be valid till the end of the process. + static AliceO2::InfoLogger::InfoLogger* instance; + static AliceO2::InfoLogger::InfoLoggerContext* mContext; + static bool disabled; // disabled basically means that we enforce discarding debug and level 1+ }; } // namespace o2::quality_control::core +// Define shortcuts to our instance using macros. +#define ILOG_INST o2::quality_control::core::QcInfoLogger::GetInfoLogger() +#define ILOGI ILOG_INST << AliceO2::InfoLogger::InfoLogger::Info +#define ILOGW ILOG_INST << AliceO2::InfoLogger::InfoLogger::Warning +#define ILOGE ILOG_INST << AliceO2::InfoLogger::InfoLogger::Error +#define ILOGF ILOG_INST << AliceO2::InfoLogger::InfoLogger::Fatal +#define ENDM AliceO2::InfoLogger::InfoLogger::endm; + +#define NUM_ARGS_(_1, _2, _3, _4, _5, _6, _7, _8, TOTAL, ...) TOTAL +#define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0) +#define CONCATENATE(X, Y) X##Y +#define CONCATE(MACRO, NUMBER) CONCATENATE(MACRO, NUMBER) +#define VA_MACRO(MACRO, ...) \ + CONCATE(MACRO, NUM_ARGS(__VA_ARGS__)) \ + (__VA_ARGS__) + +#define ILOG(...) VA_MACRO(ILOG, void, void, __VA_ARGS__) +// TODO understand why the zero argument does not work. +// the code is derived from https://stackoverflow.com/questions/16683146/can-macros-be-overloaded-by-number-of-arguments +#define ILOG0(s, t) \ + ILOG_INST << AliceO2::InfoLogger::InfoLogger::InfoLoggerMessageOption { AliceO2::InfoLogger::InfoLogger::Severity::Info, AliceO2::InfoLogger::InfoLogger::Level::Support, AliceO2::InfoLogger::InfoLogger::undefinedMessageOption.errorCode, __FILE__, __LINE__ } +#define ILOG1(s, t, severity) \ + ILOG_INST << AliceO2::InfoLogger::InfoLogger::InfoLoggerMessageOption { AliceO2::InfoLogger::InfoLogger::Severity::severity, AliceO2::InfoLogger::InfoLogger::Level::Support, AliceO2::InfoLogger::InfoLogger::undefinedMessageOption.errorCode, __FILE__, __LINE__ } +#define ILOG2(s, t, severity, level) \ + ILOG_INST << AliceO2::InfoLogger::InfoLogger::InfoLoggerMessageOption { AliceO2::InfoLogger::InfoLogger::Severity::severity, AliceO2::InfoLogger::InfoLogger::Level::level, AliceO2::InfoLogger::InfoLogger::undefinedMessageOption.errorCode, __FILE__, __LINE__ } + #endif // QC_CORE_QCINFOLOGGER_H diff --git a/Framework/include/QualityControl/QualitiesToFlagCollectionConverter.h b/Framework/include/QualityControl/QualitiesToFlagCollectionConverter.h new file mode 100644 index 0000000000..f50571cfcc --- /dev/null +++ b/Framework/include/QualityControl/QualitiesToFlagCollectionConverter.h @@ -0,0 +1,79 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualitiesToFlagCollectionConverter.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_QUALITIESTOQCFCOLLECTIONCONVERTER_H +#define QUALITYCONTROL_QUALITIESTOQCFCOLLECTIONCONVERTER_H + +#include +#include +#include + +#include +#include +#include + +namespace o2::quality_control::core +{ + +class QualityObject; + +/// \brief Converts series of Quality Objects from the same path into a QualityControlFlagCollection. +class QualitiesToFlagCollectionConverter +{ + public: + QualitiesToFlagCollectionConverter(std::unique_ptr emptyQcfc, std::string qoPath); + + ~QualitiesToFlagCollectionConverter() = default; + + /// \brief Converts a Quality into FlagCollection. The converter should get Qualities in chronological order. + void operator()(const QualityObject&); + + /// \brief Moves the final FlagCollection out and resets the converter. + std::unique_ptr getResult(); + + size_t getQOsIncluded() const; + size_t getWorseThanGoodQOs() const; + int getRunNumber() const; + + /// Sets the provided validity interval, trims affected flags and fills extensions with UnknownQuality + void updateValidityInterval(const ValidityInterval validityInterval); + + private: + /// \brief inserts the provided flag to the buffer, takes care of merging and trimming + void insert(QualityControlFlag&& flag); + + /// \brief trims all buffered flags which match the predicate using the provided interval + void trimBufferWithInterval( + ValidityInterval interval, + const std::function& predicate = [](const auto&) { return true; }); + + /// \brief trims the provided flag with all buffered flags which match the predicate + /// + /// The result is a vector, because a flag interval split in the middle becomes two flags. + std::vector trimFlagAgainstBuffer( + const QualityControlFlag& newFlag, + const std::function& predicate = [](const auto&) { return true; }); + + std::string mQOPath; // this is only to indicate what is the missing Quality in QC Flag + std::unique_ptr mConverted; + std::set mFlagBuffer; + size_t mQOsIncluded = 0; + size_t mWorseThanGoodQOs = 0; +}; + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_QUALITIESTOQCFCOLLECTIONCONVERTER_H diff --git a/Framework/include/QualityControl/Quality.h b/Framework/include/QualityControl/Quality.h index bd20c82526..55e0a971f2 100644 --- a/Framework/include/QualityControl/Quality.h +++ b/Framework/include/QualityControl/Quality.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file Quality.h /// \author Barthelemy von Haller @@ -6,12 +17,17 @@ #ifndef QC_CORE_QUALITY_H #define QC_CORE_QUALITY_H -#include -#include +#include +#include #include +#include +#include +#include +#include namespace o2::quality_control::core { +using CommentedFlagTypes = std::vector>; /// \brief Class representing the quality of a MonitorObject. /// @@ -20,13 +36,19 @@ class Quality { public: /// Default constructor - /// Not 'explicit', we allow implicit conversion from uint to Quality. - Quality(unsigned int level = Quality::NullLevel, std::string name = ""); + explicit Quality(unsigned int level = Quality::NullLevel, std::string name = ""); + /// Destructor - virtual ~Quality(); + virtual ~Quality() = default; + // Copy constructor + Quality(const Quality& q) = default; - unsigned int getLevel() const; - const std::string& getName() const; + /// Move constructor + Quality(Quality&& other) /*noexcept*/ = default; + /// Copy assignment operator + Quality& operator=(const Quality& other) = default; + /// Move assignment operator + Quality& operator=(Quality&& other) /*noexcept*/ = default; static const Quality Null; static const Quality Good; @@ -34,24 +56,26 @@ class Quality static const Quality Bad; static const unsigned int NullLevel; + /// \brief Sets the Quality (without Flags or Metadata). + void set(const Quality&); + + unsigned int getLevel() const; + const std::string& getName() const; + friend bool operator==(const Quality& lhs, const Quality& rhs) { return (lhs.getName() == rhs.getName() && lhs.getLevel() == rhs.getLevel()); } friend bool operator!=(const Quality& lhs, const Quality& rhs) { return !operator==(lhs, rhs); } - friend std::ostream& operator<<(std::ostream& out, const Quality& q) // output - { - out << "Quality: " << q.getName() << " (level " << q.getLevel() << ")\n"; - return out; - } + friend std::ostream& operator<<(std::ostream& out, const Quality& q); // output /** - * \brief Checks whether this quality object is worst than another one. + * \brief Checks whether this quality object is worse than another one. * If compared to Null it returns false. * @param quality * @return true if it is worse, false otherwise or if compared to Quality::Null. */ - bool isWorstThan(const Quality& quality) const { return this->mLevel > quality.getLevel(); } + bool isWorseThan(const Quality& quality) const { return this->mLevel > quality.getLevel(); } /** * \brief Checks whether this quality object is better than another one. * If compared to Null it returns false. @@ -60,11 +84,48 @@ class Quality */ bool isBetterThan(const Quality& quality) const { return this->mLevel < quality.getLevel(); } + /// \brief Add key value pair that will end up in the database + /// Add a metadata (key value pair) to the QualityObject. It will be stored in the database. + /// If the key already exists the value will be updated. + void addMetadata(const std::string& key, const std::string& value); + /// \brief Add key value pairs that will end up in the database as metadata of the object + /// Add all the key-value pairs in the map to the MonitorObject. It will be stored in the database as metadata. + /// If a key already exists the value will NOT be updated. + void addMetadata(std::map pairs); + /// \brief Update the value of metadata. + /// If the key does not exist it will ignore it. + void updateMetadata(const std::string& key, std::string value); + /// \brief Get the full map of user's metadata + const std::map& getMetadataMap() const; + /// \brief Overwrite the existing metadata. + void overwriteMetadata(std::map pairs); + /// \brief Get metadata + /// \return the value corresponding to the key if it was found. + /// \throw ObjectNotFoundError in case the key is not found. + std::string getMetadata(const std::string& key) const; + /// \brief Get metadata + /// \return the value corresponding to the key if it was found, default value otherwise + std::string getMetadata(const std::string& key, const std::string& defaultValue) const; + /// \brief Get metadata + /// \return the value corresponding to the key if it was found, nulopt otherwise + std::optional getMetadataOpt(const std::string&) const; + + /// \brief Associate the Quality with a new flag and an optional comment + /// \return reference to *this + Quality& addFlag(const FlagType& flag, std::string comment = ""); + /// \brief Get the flags with associated comments for the Quality + /// \return flag, if exists + const CommentedFlagTypes& getFlags() const; + + static Quality fromString(const std::string& str); + private: unsigned int mLevel; /// 0 is no quality, 1 is best quality, then it only goes downhill... std::string mName; + std::map mUserMetadata; + std::vector> mFlags; - ClassDef(Quality, 1); + ClassDef(Quality, 3); }; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/QualityObject.h b/Framework/include/QualityControl/QualityObject.h new file mode 100644 index 0000000000..4790e796fb --- /dev/null +++ b/Framework/include/QualityControl/QualityObject.h @@ -0,0 +1,160 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_CORE_QUALITYOBJECT_H +#define QC_CORE_QUALITYOBJECT_H + +// std +#include +#include +#include +#include +// ROOT +#include +// O2 +#include +// QC +#include "QualityControl/Quality.h" +#include "QualityControl/Activity.h" + +namespace o2::quality_control::core +{ + +/// \brief Encapsulation of a Quality into a TObject that can be streamed and stored. +/// +/// \author Barthelemy von Haller +class QualityObject : public TObject +{ + public: + /// Default constructor + QualityObject(); + + QualityObject( + Quality quality, + std::string checkName, + std::string detectorName = "DET", + std::string policyName = "", + std::vector inputs = {}, + std::vector monitorObjectsNames = {}, + std::map metadata = {}, + int runNumber = 0); + + /// Destructor + ~QualityObject() override; + + /// Copy constructor + QualityObject(const QualityObject& other) = default; + /// Move constructor + QualityObject(QualityObject&& other) /*noexcept*/ = default; + /// Copy assignment operator + QualityObject& operator=(const QualityObject& other) = default; + /// Move assignment operator + QualityObject& operator=(QualityObject&& other) /*noexcept*/ = default; + + friend std::ostream& operator<<(std::ostream& out, const QualityObject& q); // output + + /// \brief Return the name of the check. + /// @return The name of the check. + /// @deprecated Prefer getCheckName() + std::string getName() const; + + /// \brief Return the name of the check. + /// @return The name of the check. + /// @deprecated Prefer getCheckName() + const char* GetName() const override; + + /// + /// \brief Get the quality of this object. + /// + /// The method returns the lowest quality met amongst all the checks listed in \ref mChecks. + /// If there are no checks, the method returns \ref Quality::Null. + /// + /// @return the quality of the object + /// + void updateQuality(Quality quality); + Quality getQuality() const; + + /** + * Use o2::framework::DataSpecUtils::describe(input) to get string + */ + void setInputs(std::vector inputs) { mInputs = inputs; } + std::vector getInputs() const { return mInputs; } + + /// \brief Add key value pair that will end up in the database + /// Add a metadata (key value pair) to the QualityObject. It will be stored in the database. + /// If the key already exists the value will be updated. + void addMetadata(std::string key, std::string value); + /// \brief Add key value pairs that will end up in the database as metadata of the object + /// Add all the key-value pairs in the map to the MonitorObject. It will be stored in the database as metadata. + /// If a key already exists the value will NOT be updated. + void addMetadata(std::map pairs); + /// \brief Update the value of metadata. + /// If the key does not exist it will ignore it. + void updateMetadata(std::string key, std::string value); + /// \brief Get the full map of user's metadata + const std::map& getMetadataMap() const; + /// \brief Get a metadata + /// \return the value corresponding to the key if it was found. + /// \throw ObjectNotFoundError in case the key is not found. + std::string getMetadata(std::string key) const; + /// \brief Get a metadata + /// \return the value corresponding to the key if it was found, default value otherwise + std::string getMetadata(std::string key, std::string defaultValue) const; + /// \brief Get a metadata + /// \return the value corresponding to the key if it was found, nullopt otherwise + std::optional getMetadataOpt(const std::string& key) const; + + /// \brief Build the path to this object. + /// Build the path to this object as it will appear in the GUI. + /// If the QO was generated by the policy OnEachSeparately, it appends / at the end + /// \return A string containing the path. + std::string getPath() const; + + /// \brief Associate the Quality with a new flag and an optional comment + /// \return reference to *this + QualityObject& addFlag(const FlagType& flag, std::string comment = ""); + /// \brief Get the reasons with associated comments for the Quality + /// \return reason, if exists + const CommentedFlagTypes& getFlags() const; + + const std::string& getDetectorName() const; + void setDetectorName(const std::string& detectorName); + void setQuality(const Quality& quality); + const std::string& getCheckName() const; + const std::string& getPolicyName() const; + const std::vector& getMonitorObjectsNames() const; + Activity& getActivity(); + const Activity& getActivity() const; + void setActivity(const Activity& activity); + void updateActivity(int runNumber = 0, const std::string& periodName = "", const std::string& passName = "", const std::string& provenance = ""); + + void setValidity(ValidityInterval); + void updateValidity(validity_time_t value); + ValidityInterval getValidity() const; + + private: + Quality mQuality; + std::string mCheckName; + std::string mDetectorName; + std::string mPolicyName; + std::vector mInputs; + std::vector mMonitorObjectsNames; + Activity mActivity; + + ClassDefOverride(QualityObject, 7); +}; + +using QualityObjectsType = std::vector>; +using QualityObjectsMapType = std::map>; + +} // namespace o2::quality_control::core + +#endif diff --git a/Framework/include/QualityControl/RecoRequestSpecs.h b/Framework/include/QualityControl/RecoRequestSpecs.h new file mode 100644 index 0000000000..001bb50847 --- /dev/null +++ b/Framework/include/QualityControl/RecoRequestSpecs.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_RECOREQUESTSPECS_H +#define QUALITYCONTROL_RECOREQUESTSPECS_H + +/// +/// \file RecoRequestSpecs.h +/// \author Piotr Konopka +/// + +//#include +#include + +namespace o2::quality_control::core +{ + +// this reflects the GRPGeomRequest struct, but allows us to gather the values and fill the input specs at different stages +struct GRPGeomRequestSpec { + std::string geomRequest = "None"; + + bool askGRPECS = false; + bool askGRPLHCIF = false; + bool askGRPMagField = false; + bool askMatLUT = false; + bool askTime = false; // need orbit reset time for precise timestamp calculation + bool askOnceAllButField = false; // for all entries but field query only once + bool needPropagatorD = false; // init also PropagatorD + + bool anyRequestEnabled() const + { + return geomRequest != "None" || askGRPECS || askGRPLHCIF || askGRPMagField || askMatLUT || askTime || askOnceAllButField || needPropagatorD; + } +}; + +struct GlobalTrackingDataRequestSpec { + std::string canProcessTracks; + std::string requestTracks; + std::string canProcessClusters; + std::string requestClusters; + bool mc; +}; +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_RECOREQUESTSPECS_H diff --git a/Framework/include/QualityControl/Reductor.h b/Framework/include/QualityControl/Reductor.h new file mode 100644 index 0000000000..180b12715d --- /dev/null +++ b/Framework/include/QualityControl/Reductor.h @@ -0,0 +1,48 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Reductor.h +/// \author Piotr Konopka +/// +#ifndef QUALITYCONTROL_REDUCTOR_H +#define QUALITYCONTROL_REDUCTOR_H + +#include "CustomParameters.h" + +namespace o2::quality_control::postprocessing +{ + +/// \brief An interface for storing columnar data into a TTree +class Reductor +{ + protected: + core::CustomParameters mCustomParameters; + + public: + /// \brief Constructor + Reductor() = default; + /// \brief Destructor + virtual ~Reductor() = default; + + /// \brief Branch address getter + /// \return A pointer to a structure/variable which will be used to fill a TTree. It must not change later! + virtual void* getBranchAddress() = 0; + /// \brief Branch leaf list getter + /// \return A C string with a description of a branch format, formatted accordingly to the TTree interface + virtual const char* getBranchLeafList() = 0; + /// \brief setter for mCustomParameters + void setCustomConfig(const core::CustomParameters& parameters) { mCustomParameters = parameters; }; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_REDUCTOR_H diff --git a/Framework/include/QualityControl/ReductorConditionAny.h b/Framework/include/QualityControl/ReductorConditionAny.h new file mode 100644 index 0000000000..92489843d2 --- /dev/null +++ b/Framework/include/QualityControl/ReductorConditionAny.h @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReductorConditionAny.h +/// \author Piotr Konopka +/// +#ifndef QUALITYCONTROL_REDUCTORCONDITIONANY_H +#define QUALITYCONTROL_REDUCTORCONDITIONANY_H + +#include "QualityControl/Reductor.h" +#include + +namespace o2::quality_control::postprocessing +{ + +/// \brief An interface for storing data derived from any object into a TTree +class ReductorConditionAny : public Reductor +{ + public: + class ConditionRetriever; + + /// \brief Constructor + ReductorConditionAny() = default; + /// \brief Destructor + virtual ~ReductorConditionAny() = default; + + /// \brief A helper for the caller to have the ConditionRetriever created + virtual bool update(core::ConditionAccess& conditionAccess, uint64_t timestamp, const std::string& path) final + { + ConditionRetriever retriever{ conditionAccess, timestamp, path }; + return update(retriever); + } + + /// \brief Fill the data structure with new data + /// \param An object getter, object presence is not guaranteed + /// \return false if failed, true if success + virtual bool update(ConditionRetriever& retriever) = 0; + + /// \brief A wrapper class to allow implementations of ReductorConditionAny to state the expected type of the reduced object. + /// + /// A wrapper class to allow implementations of ReductorConditionAny to state the expected type of the reduced object. + /// It is declared within ReductorConditionAny, as it is intended to be used only in this context. + struct ConditionRetriever { + public: + ConditionRetriever(core::ConditionAccess& conditionAccess, uint64_t timestamp, const std::string& path) + : conditionAccess(conditionAccess), timestamp(timestamp), path(path){}; + ~ConditionRetriever() = default; + + /// \brief Gets the object with a specified type. The result can be nullptr, the pointer should not be deleted! + template + T* retrieve() + { + return conditionAccess.retrieveConditionAny(path, {}, timestamp); + } + + private: + core::ConditionAccess& conditionAccess; + uint64_t timestamp; + const std::string& path; + }; +}; + +} // namespace o2::quality_control::postprocessing +#endif // QUALITYCONTROL_REDUCTORCONDITIONANY_H diff --git a/Framework/include/QualityControl/ReductorHelpers.h b/Framework/include/QualityControl/ReductorHelpers.h new file mode 100644 index 0000000000..bf7ccde766 --- /dev/null +++ b/Framework/include/QualityControl/ReductorHelpers.h @@ -0,0 +1,71 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReductorHelpers.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_REDUCTORHELPERS_H +#define QUALITYCONTROL_REDUCTORHELPERS_H + +#include + +namespace o2::quality_control +{ +namespace postprocessing +{ +class Reductor; +struct Trigger; +} // namespace postprocessing +namespace core +{ +class ConditionAccess; +} +namespace repository +{ +class DatabaseInterface; +} +} // namespace o2::quality_control + +namespace o2::quality_control::postprocessing::reductor_helpers +{ + +namespace implementation +{ + +/// \brief implementation details of updateReductor, hiding some header inclusions +bool updateReductorImpl(Reductor* r, const Trigger& t, const std::string& path, const std::string& name, const std::string& type, + repository::DatabaseInterface& qcdb, core::ConditionAccess& ccdbAccess); + +} // namespace implementation + +/// \brief Updates the provided Reductor with implementation-specific procedures +/// +/// \tparam DataSourceT data source structure type to be accessed. path, name and type string members are required. +/// \param r reductor which is going to be type-checked +/// \param t trigger +/// \param ds data source +/// \param qcdb QCDB interface +/// \param ccdbAccess a class which has access to conditions +/// \return bool value indicating the success or failure in reducing an object +template +bool updateReductor(Reductor* r, const Trigger& t, const DataSourceT& ds, repository::DatabaseInterface& qcdb, core::ConditionAccess& ccdbAccess) +{ + const std::string& path = ds.path; + const std::string& name = ds.name; + const std::string& type = ds.type; + + return implementation::updateReductorImpl(r, t, path, name, type, qcdb, ccdbAccess); +} + +} // namespace o2::quality_control::postprocessing::reductor_helpers +#endif // QUALITYCONTROL_REDUCTORHELPERS_H diff --git a/Framework/include/QualityControl/ReductorTObject.h b/Framework/include/QualityControl/ReductorTObject.h new file mode 100644 index 0000000000..690d9e5510 --- /dev/null +++ b/Framework/include/QualityControl/ReductorTObject.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReductorTObject.h +/// \author Piotr Konopka +/// +#ifndef QUALITYCONTROL_REDUCTORTOBJECT_H +#define QUALITYCONTROL_REDUCTORTOBJECT_H + +#include "QualityControl/Reductor.h" +#include + +class TObject; + +namespace o2::quality_control::postprocessing +{ + +/// \brief An interface for storing data derived from TObjects into a TTree +class ReductorTObject : public Reductor +{ + public: + /// \brief Constructor + ReductorTObject() = default; + /// \brief Destructor + virtual ~ReductorTObject() = default; + + /// \brief Fill the data structure with new data + /// \param An object to be reduced into a limited set of observables + virtual void update(TObject* obj) = 0; +}; + +} // namespace o2::quality_control::postprocessing +#endif // QUALITYCONTROL_REDUCTORTOBJECT_H diff --git a/Framework/include/QualityControl/ReferenceUtils.h b/Framework/include/QualityControl/ReferenceUtils.h new file mode 100644 index 0000000000..27b20fce19 --- /dev/null +++ b/Framework/include/QualityControl/ReferenceUtils.h @@ -0,0 +1,114 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorUtils.h +/// \author Andrea Ferrero and Barthelemy von Haller +/// + +#ifndef QUALITYCONTROL_ReferenceUtils_H +#define QUALITYCONTROL_ReferenceUtils_H + +#include +#include "QualityControl/MonitorObject.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/Activity.h" +#include "QualityControl/ActivityHelpers.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/RepoPathUtils.h" + +#include +#include + +namespace o2::quality_control::checker +{ + +//_________________________________________________________________________________________ +// +// Get the reference plot for a given MonitorObject path + +static std::shared_ptr getReferencePlot(quality_control::repository::DatabaseInterface* qcdb, std::string& fullPath, + core::Activity referenceActivity) +{ + auto [success, path, name] = o2::quality_control::core::RepoPathUtils::splitObjectPath(fullPath); + if (!success) { + return nullptr; + } + return qcdb->retrieveMO(path, name, repository::DatabaseInterface::Timestamp::Latest, referenceActivity); +} + +//_________________________________________________________________________________________ +// +// Get the current and reference histograms from the container canvas. +// The two histograms are returned as a std::pair + +static std::pair getPlotsFromCanvas(TCanvas* canvas, std::string& message) +{ + // Get the pad containing the current histogram, as well as the reference one in the case of 1-D plots + TPad* padHist = dynamic_cast(canvas->GetPrimitive(TString::Format("%s_PadHist", canvas->GetName()))); + if (!padHist) { + message = "missing PadHist"; + return { nullptr, nullptr }; + } + // Get the pad containing the reference histogram. + // This pad is only present for 2-D histograms. + // 1-D histograms are drawn superimposed in the same pad + TPad* padHistRef = (TPad*)canvas->GetPrimitive(TString::Format("%s_PadHistRef", canvas->GetName())); + + // Get the current histogram + TH1* hist = dynamic_cast(padHist->GetPrimitive(TString::Format("%s_hist", canvas->GetName()))); + if (!hist) { + message = "missing histogram"; + return { nullptr, nullptr }; + } + + // Get the reference histogram, trying both pads + TH1* histRef = nullptr; + if (padHistRef) { + histRef = dynamic_cast(padHistRef->GetPrimitive(TString::Format("%s_hist_ref", canvas->GetName()))); + } else { + histRef = dynamic_cast(padHist->GetPrimitive(TString::Format("%s_hist_ref", canvas->GetName()))); + } + + if (!histRef) { + message = "missing reference histogram"; + return { nullptr, nullptr }; + } + + // return a pair with the two histograms + return { hist, histRef }; +} + +//_________________________________________________________________________________________ +// +// Get the ratio histogram from the container canvas + +static TH1* getRatioPlotFromCanvas(TCanvas* canvas) +{ + // Get the pad containing the current histogram, as well as the reference one in the case of 1-D plots + TPad* padHistRatio = dynamic_cast(canvas->GetPrimitive(TString::Format("%s_PadHistRatio", canvas->GetName()))); + if (!padHistRatio) { + return nullptr; + } + + // Get the current histogram + TH1* histRatio = dynamic_cast(padHistRatio->GetPrimitive(TString::Format("%s_hist_ratio", canvas->GetName()))); + if (!histRatio) { + return nullptr; + } + + // return a pair with the two histograms + return histRatio; +} + +} // namespace o2::quality_control::checker + +#endif // QUALITYCONTROL_ReferenceUtils_H diff --git a/Framework/include/QualityControl/RepoPathUtils.h b/Framework/include/QualityControl/RepoPathUtils.h new file mode 100644 index 0000000000..3fd69ade58 --- /dev/null +++ b/Framework/include/QualityControl/RepoPathUtils.h @@ -0,0 +1,144 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RepoPathUtils.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_REPOPATH_UTILS_H +#define QC_REPOPATH_UTILS_H + +#include +#include +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QualityObject.h" +#include + +namespace o2::quality_control::core +{ + +class RepoPathUtils +{ + public: + /** + * Compute and return the path to the MonitorObject. + * Current algorithm does //MO// + * @param detectorCode + * @param taskName + * @param moName + * @param provenance + * @param includeProvenance + * @return the path to the MonitorObject + */ + static std::string getMoPath(const std::string& detectorCode, + const std::string& taskName, + const std::string& moName, + const std::string& provenance = "qc", + bool includeProvenance = true) + { + std::string path = (includeProvenance ? provenance + "/" : "") + detectorCode + "/MO/" + taskName + (moName.empty() ? "" : ("/" + moName)); + return path; + } + + /** + * Compute and return the path to the MonitorObject. + * Current algorithm does //MO// + * @param mo + * @param includeProvenance + * @return the path to the MonitorObject + */ + static std::string getMoPath(const MonitorObject* mo, bool includeProvenance = true) + { + return getMoPath(mo->getDetectorName(), mo->getTaskName(), mo->getName(), mo->getActivity().mProvenance, includeProvenance); + } + /** + * Compute and return the path to the QualityObject. + * Current algorithm does //QO/[/]. + * The last, optional, part depends on policyName and uses the first element of the vector monitorObjectsNames. + * @param detectorCode + * @param checkName + * @param provenance + * @param policyName + * @param monitorObjectsNames + * @param includeProvenance + * @return the path to the QualityObject + */ + static std::string getQoPath(const std::string& detectorCode, + const std::string& checkName, + const std::string& policyName = "", + const std::vector& monitorObjectsNames = std::vector(), + const std::string& provenance = "qc", + bool includeProvenance = true) + { + std::string path = (includeProvenance ? provenance + "/" : "") + detectorCode + "/QO/" + checkName; + if (policyName == "OnEachSeparately") { + if (monitorObjectsNames.empty()) { + BOOST_THROW_EXCEPTION(AliceO2::Common::FatalException() << AliceO2::Common::errinfo_details("getQoPath: The vector of monitorObjectsNames is empty.")); + } + path += "/" + monitorObjectsNames[0]; + } + return path; + } + + /** + * Compute and return the path to the QualityObject. + * Current algorithm does //QO/[/]. + * The last, optional, part depends on policyName and uses the first element of the vector monitorObjectsNames. + * @param qo + * @return the path to the QualityObject + */ + static std::string getQoPath(const QualityObject* qo, bool includeProvenance = true) + { + return getQoPath(qo->getDetectorName(), + qo->getCheckName(), + qo->getPolicyName(), + qo->getMonitorObjectsNames(), + qo->getActivity().mProvenance, + includeProvenance); + } + + static constexpr auto allowedProvenancesMessage = R"(Allowed provenances are "qc" (real data processed synchronously), "qc_async" (real data processed asynchronously) and "qc_mc" (simulated data).)"; + static bool isProvenanceAllowed(const std::string& provenance); + + /** + * Splits the provided path and returns both the base path and the object name. + * @param fullPath + * @return A tuple with 1. a boolean to specify if we succeeded (i.e. whether we found a `/`) + * 2. the path + * 3. the object name + */ + static std::tuple splitObjectPath(const std::string& fullPath) + { + std::string delimiter = "/"; + std::string det; + size_t pos = fullPath.rfind(delimiter); + if (pos == std::string::npos) { + return { false, "", "" }; + } + std::string path = fullPath.substr(0, pos); + std::string name = fullPath.substr(pos + 1); + return { true, path, name }; + } + + static std::string getPathNoProvenance(std::shared_ptr mo) + { + std::string path = mo->getPath(); + size_t pos = path.find('/'); + if (pos != std::string::npos) { + path = path.substr(pos + 1); + } + return path; + } +}; +} // namespace o2::quality_control::core + +#endif // QC_REPOPATH_UTILS_H diff --git a/Framework/include/QualityControl/RootClassFactory.h b/Framework/include/QualityControl/RootClassFactory.h new file mode 100644 index 0000000000..9274e546fa --- /dev/null +++ b/Framework/include/QualityControl/RootClassFactory.h @@ -0,0 +1,70 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +// + +/// +/// \author Piotr Konopka +/// \file RootClassFactory.h +/// +#ifndef QUALITYCONTROL_ROOTCLASSFACTORY_H +#define QUALITYCONTROL_ROOTCLASSFACTORY_H + +#include +// O2 +#include +// ROOT +#include +// QC +#include "QualityControl/QcInfoLogger.h" + +namespace o2::quality_control::core +{ + +namespace root_class_factory +{ + +using FatalException = AliceO2::Common::FatalException; +using errinfo_details = AliceO2::Common::errinfo_details; + +void loadLibrary(const std::string& moduleName); + +template +T* create(const std::string& moduleName, const std::string& className) +{ + T* result = nullptr; + + loadLibrary(moduleName); + + // Get the class and instantiate + ILOG(Info, Devel) << "Loading class " << className << ENDM; + TClass* cl = TClass::GetClass(className.c_str()); + std::string tempString("Failed to instantiate Quality Control Module"); + if (!cl) { + tempString += " because no dictionary for class named \""; + tempString += className; + tempString += "\" could be retrieved"; + BOOST_THROW_EXCEPTION(FatalException() << errinfo_details(tempString)); + } + ILOG(Info, Devel) << "Instantiating class " << className << " (" << cl << ")" << ENDM; + result = static_cast(cl->New()); + if (!result) { + BOOST_THROW_EXCEPTION(FatalException() << errinfo_details(tempString)); + } + ILOG(Info, Devel) << "QualityControl Module " << moduleName << " loaded " << ENDM; + + return result; +} + +} // namespace root_class_factory + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_ROOTCLASSFACTORY_H diff --git a/Framework/include/QualityControl/RootFileSink.h b/Framework/include/QualityControl/RootFileSink.h new file mode 100644 index 0000000000..6cbfa3e1dc --- /dev/null +++ b/Framework/include/QualityControl/RootFileSink.h @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RootFileSink.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_ROOTFILESINK_H +#define QUALITYCONTROL_ROOTFILESINK_H + +#include +#include +#include + +namespace o2::quality_control::core +{ + +/// \brief A Data Processor which stores MonitorObjectCollections in a specified file +class RootFileSink : public framework::Task +{ + public: + explicit RootFileSink(std::string filePath); + ~RootFileSink() override = default; + + void init(framework::InitContext& ictx) override; + void run(framework::ProcessingContext& pctx) override; + + static framework::DataProcessorLabel getLabel() + { + return { "qc-root-file-sink" }; + } + + static void customizeInfrastructure(std::vector& policies); + + private: + std::string mFilePath; +}; + +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_ROOTFILESINK_H \ No newline at end of file diff --git a/Framework/include/QualityControl/RootFileSource.h b/Framework/include/QualityControl/RootFileSource.h new file mode 100644 index 0000000000..7e3f28eb27 --- /dev/null +++ b/Framework/include/QualityControl/RootFileSource.h @@ -0,0 +1,55 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RootFileSource.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_ROOTFILESOURCE_H +#define QUALITYCONTROL_ROOTFILESOURCE_H + +#include +#include +#include +#include + +namespace o2::quality_control::core +{ + +class RootFileStorage; +class IntegralMocWalker; +class MovingWindowMocWalker; + +/// \brief A Data Processor which reads MonitorObjectCollections from a specified file +class RootFileSource : public framework::Task +{ + public: + RootFileSource(std::string filePath); + ~RootFileSource() override = default; + + void init(framework::InitContext& ictx) override; + void run(framework::ProcessingContext& pctx) override; + + static framework::OutputLabel outputBinding(const std::string& detectorCode, const std::string& taskName, bool movingWindow = false); + + private: + std::string mFilePath; + std::vector mAllowedOutputs; + + std::shared_ptr mRootFileManager = nullptr; + std::shared_ptr mIntegralMocWalker = nullptr; + std::shared_ptr mMovingWindowMocWalker = nullptr; +}; + +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_ROOTFILESOURCE_H diff --git a/Framework/include/QualityControl/RootFileStorage.h b/Framework/include/QualityControl/RootFileStorage.h new file mode 100644 index 0000000000..4edf9ea293 --- /dev/null +++ b/Framework/include/QualityControl/RootFileStorage.h @@ -0,0 +1,102 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RootFileStorage.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_ROOTFILESTORAGE_H +#define QUALITYCONTROL_ROOTFILESTORAGE_H + +#include +#include +#include +#include +#include + +class TFile; +class TDirectory; + +namespace o2::quality_control::core +{ + +class MonitorObjectCollection; + +/// \brief Manager for storage and retrieval of MonitorObjectCollections in TFiles +class RootFileStorage +{ + public: + struct MonitorObjectCollectionNode { + std::string fullPath{}; + std::string name{}; + MonitorObjectCollection* moc = nullptr; + }; + struct DirectoryNode { + std::string fullPath{}; + std::string name{}; + std::map> children = {}; + }; + + enum class ReadMode { + Read, + Update + }; + + explicit RootFileStorage(const std::string& filePath, ReadMode); + ~RootFileStorage(); + + DirectoryNode readStructure(bool loadObjects = false) const; + MonitorObjectCollection* readMonitorObjectCollection(const std::string& path) const; + + void storeIntegralMOC(MonitorObjectCollection* const moc); + void storeMovingWindowMOC(MonitorObjectCollection* const moc); + + private: + DirectoryNode readStructureImpl(TDirectory* currentDir, bool loadObjects) const; + + private: + TFile* mFile = nullptr; +}; + +/// \brief walks over integral MOC paths in the alphabetical order of detectors and task names +class IntegralMocWalker +{ + public: + explicit IntegralMocWalker(const RootFileStorage::DirectoryNode& rootNode); + + bool hasNextPath(); + std::string nextPath(); + + private: + using child_iterator = decltype(std::declval().children.cbegin()); + std::vector mOrder; + std::vector::const_iterator mPathIterator; +}; + +/// \brief walks over moving window MOC paths in the chronological order +class MovingWindowMocWalker +{ + public: + explicit MovingWindowMocWalker(const RootFileStorage::DirectoryNode& rootNode); + + bool hasNextPath() const; + std::string nextPath(); + + private: + using child_iterator = decltype(std::declval().children.begin()); + std::multimap mOrder; + std::multimap::const_iterator mPathIterator; +}; + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_ROOTFILESTORAGE_H diff --git a/Framework/include/QualityControl/SliceInfoTrending.h b/Framework/include/QualityControl/SliceInfoTrending.h new file mode 100644 index 0000000000..3f9f93f1df --- /dev/null +++ b/Framework/include/QualityControl/SliceInfoTrending.h @@ -0,0 +1,94 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SliceInfoTrending.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// + +#ifndef QUALITYCONTROL_SLICEINFOTRENDING_H +#define QUALITYCONTROL_SLICEINFOTRENDING_H + +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include + +namespace o2::quality_control::postprocessing +{ +/// \brief Structure for the reductor quantities for a single pad of the TPC. +/// +/// Structure gathering all the reductor quantities related to the trending of +/// the 'pads' (ROCs, sectors, slices,...) of the TPC. The reductor receives a +/// vector of SliceInfo with one element per slice, and fills it accordingly to +/// the json configuration. +/// + +struct SliceInfo { + double entries = 0.; // Number of entries in the slice/canvas. + double meanX = 0.; // Standard mean for a given range in X. + double stddevX = 0.; // Standard deviation for the range in X. + double errMeanX = 0.; // Error on the mean along X. + double meanY = 0.; // Standard mean in Y. + double stddevY = 0.; // Standard deviation in Y. + double errMeanY = 0.; // Error on the mean along Y. + double sliceLabelX = 0.; // Stores numerical center of slice along X in case of slicing or pad number in case of canvas + double sliceLabelY = 0.; // Stores numerical center of slice along Y in case of slicing or pad number in case of canvas + std::string title = ""; + + /// \brief Check if the argument is a floating number or a string. + bool isStringFloating(std::string var) + { + std::istringstream iss(var); + float f; + iss >> std::noskipws >> f; + return iss.eof() && !iss.fail(); + } + + /// \brief Return the struct member/float corresponding to the argument. + double retrieveValue(std::string varType) + { + if (isStringFloating(varType)) { + return std::stod(varType); + } else { + if (varType == "entries") { + return entries; + } else if (varType == "meanX") { + return meanX; + } else if (varType == "stddevX") { + return stddevX; + } else if (varType == "errMeanX") { + return errMeanX; + } else if (varType == "meanY") { + return meanY; + } else if (varType == "stddevY") { + return stddevY; + } else if (varType == "errMeanY") { + return errMeanY; + } else if (varType == "sliceLabelX") { + return sliceLabelX; + } else if (varType == "sliceLabelY") { + return sliceLabelY; + } else { + ILOG(Error, Support) << "SliceInfo: 'varType' " << varType.data() + << " in 'retrieveValue' unknown. Breaking." << ENDM; + exit(0); + } + } + } + + ClassDefNV(SliceInfo, 1); +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_SLICEINFOTRENDING_H diff --git a/Framework/include/QualityControl/SliceReductor.h b/Framework/include/QualityControl/SliceReductor.h new file mode 100644 index 0000000000..c87fa47a31 --- /dev/null +++ b/Framework/include/QualityControl/SliceReductor.h @@ -0,0 +1,65 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SliceReductor.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_SLICEREDUCTOR_H +#define QUALITYCONTROL_SLICEREDUCTOR_H + +#include "QualityControl/SliceInfoTrending.h" +#include +#include +#include "TAxis.h" + +namespace o2::quality_control::postprocessing +{ +/// \brief An interface for storing data derived from QC objects into a TTree. +/// +/// A extended reductor class from which each reductor used for the extended trending. +/// + +class SliceReductor +{ + public: + /// \brief Constructor. + SliceReductor() = default; + /// \brief Destructor. + virtual ~SliceReductor() = default; + + /// \brief Methods from the reductor class adapted for the needs of the TPC. + virtual void update(TObject* obj, std::vector& reducedSource, + std::vector>& axis, + int& finalNumberPads){}; + + /// \brief Function to return proper bin numbers to avoid double counting if slicing is used + void getBinSlices(TAxis* histAxis, const float sliceLow, const float sliceUp, int& binLow, int& binUp, float& sliceLabel) + { + binLow = histAxis->FindBin(sliceLow); + if (sliceLow > histAxis->GetBinCenter(binLow)) { + binLow += 1; + } // Lower slice boundary is above bin center. Start at next higher bin + binUp = histAxis->FindBin(sliceUp); + if (sliceUp <= histAxis->GetBinCenter(binUp)) { + binUp -= 1; + } // Upper slice boundary is smaller equal bin center. Stop at next lower bin + + sliceLabel = (sliceLow + sliceUp) / 2.; + } +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_SLICEREDUCTOR_H diff --git a/Framework/include/QualityControl/SliceTrendingTask.h b/Framework/include/QualityControl/SliceTrendingTask.h new file mode 100644 index 0000000000..5f2bb62e0d --- /dev/null +++ b/Framework/include/QualityControl/SliceTrendingTask.h @@ -0,0 +1,114 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SliceTrendingTask.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_SLICETRENDINGTASK_H +#define QUALITYCONTROL_SLICETRENDINGTASK_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/SliceReductor.h" +#include "QualityControl/SliceTrendingTaskConfig.h" + +#include +#include +#include +#include +#include +#include + +class TCanvas; +class TObject; +class TLegend; + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} // namespace o2::quality_control::repository + +namespace o2::quality_control::postprocessing +{ +/// \brief A extended version of the trending post-processing task. +/// +/// A post-processing task which trends objects inside QC database (QCDB). +/// It extracts some values of one or multiple objects using the Reductor classes, +/// then stores them inside (a vector of) stuct "SliceInfo" which is saved in a TTree. +/// This class extended the trending with a subrange slicer of histograms, +/// and input/output canvas can be dealt with alongside normal histograms. +/// + +class SliceTrendingTask : public PostProcessingInterface +{ + public: + /// \brief Constructor. + SliceTrendingTask() = default; + /// \brief Destructor. + ~SliceTrendingTask() final = default; + + /// \brief Post-processing methods inherited from 'PostProcessingInterface'. + void configure(const boost::property_tree::ptree& config) final; + void initialize(Trigger, framework::ServiceRegistryRef) final; + void update(Trigger, framework::ServiceRegistryRef) final; + void finalize(Trigger, framework::ServiceRegistryRef) final; + + private: + static constexpr size_t MaxRunNumberStringLength = 6; + struct MetaData { + // we store run numbers both as an integer and as a string to allow users to select whether they need + // a trend in integer or label domain (the latter will contain evenly-spaced data points) + Int_t runNumber = 0; + char runNumberStr[MaxRunNumberStringLength + 1] = { 0 }; // 6 characters + null terminator + }; + struct TitleSettings { + std::string observableX; + std::string observableY; + std::string unitX; + std::string unitY; + std::string centmodeX; + std::string centmodeY; + }; + + /// \brief Methods specific to the trending itself. + void trendValues(const Trigger& t, o2::quality_control::repository::DatabaseInterface&); + void generatePlots(); + void drawCanvasMO(TCanvas* thisCanvas, const std::string& var, + const std::string& name, const std::string& opt, const std::string& err, const std::vector>& axis, const std::vector>& sliceLabels, const TitleSettings& titlesettings); + void getUserAxisRange(const std::string& graphAxisRange, float& limitLow, float& limitUp); + void setUserAxisLabel(TAxis* xAxis, TAxis* yAxis, const std::string& graphAxisLabel); + void getTrendVariables(const std::string& inputvar, std::string& sourceName, std::string& variableName, std::string& trend); + void getTrendErrors(const std::string& inputvar, std::string& errorX, std::string& errorY); + void saveObjectToPrimitives(TCanvas* canvas, const int padNumber, TObject* object); + + template + void beautifyGraph(T& graph, const SliceTrendingTaskConfig::Plot& plotconfig, TCanvas* canv); // beautify function for TGraphs and TMultiGraphs + void beautifyLegend(TLegend* geg, const SliceTrendingTaskConfig::Plot& plotconfig, TCanvas* canv); + std::string beautifyTitle(const std::string_view rawtitle, const TitleSettings& titleSettings); + + SliceTrendingTaskConfig mConfig; + MetaData mMetaData; + UInt_t mTime; + std::unique_ptr mTrend; + std::map mPlots; + std::unordered_map> mReductors; + std::unordered_map*> mSources; + std::unordered_map mNumberPads; + std::unordered_map>> mAxisDivision; + std::unordered_map>> mSliceLabel; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_SLICETRENDINGTASK_H diff --git a/Framework/include/QualityControl/SliceTrendingTaskConfig.h b/Framework/include/QualityControl/SliceTrendingTaskConfig.h new file mode 100644 index 0000000000..f9d8c43576 --- /dev/null +++ b/Framework/include/QualityControl/SliceTrendingTaskConfig.h @@ -0,0 +1,81 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SliceTrendingTaskConfig.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_SLICETRENDINGTASKCONFIG_H +#define QUALITYCONTROL_SLICETRENDINGTASKCONFIG_H + +#include "QualityControl/PostProcessingConfig.h" + +#include + +namespace o2::quality_control::postprocessing +{ +/// \brief SliceTrendingTask configuration structure +/// +/// Configuration structure for the trending objects: the data sources to trend +/// and the plots to produce and publish on the QCG. +/// This configuration structure allows +/// to input/output canvases, and slice TH1 (TH2) objects along x (x&y) axis. + +struct SliceTrendingTaskConfig : PostProcessingConfig { + /// \brief Constructors. + SliceTrendingTaskConfig() = default; + SliceTrendingTaskConfig(const std::string& name, const boost::property_tree::ptree& config); + /// \brief Destructor. + ~SliceTrendingTaskConfig() = default; + + struct Plot { + std::string name; + std::string title; + std::string varexp; + std::string selection; + std::string option; + std::string graphErrors; + std::string graphYRange; + std::string graphXRange; + std::string graphAxisLabel; + std::string legendNColums; + std::string legendTextSize; + std::string legendObservableX; + std::string legendObservableY; + std::string legendUnitX; + std::string legendUnitY; + std::string legendCentmodeX; + std::string legendCentmodeY; + }; + + struct DataSource { + std::string type; + std::string path; + std::string name; + std::string reductorName; + std::vector> axisDivision; + std::vector> sliceLabels; + std::string moduleName; + }; + + bool producePlotsOnUpdate; + bool resumeTrend; + std::string trendingTimestamp; + std::vector plots; + std::vector dataSources; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_SLICETRENDINGTASKCONFIG_H diff --git a/Framework/include/QualityControl/TaskConfig.h b/Framework/include/QualityControl/TaskConfig.h deleted file mode 100644 index 7236a2265a..0000000000 --- a/Framework/include/QualityControl/TaskConfig.h +++ /dev/null @@ -1,27 +0,0 @@ -/// -/// \file TaskConfig.h -/// \author Barthelemy von Haller -/// - -#ifndef QC_CORE_TASKCONFIG_H -#define QC_CORE_TASKCONFIG_H - -#include - -namespace o2::quality_control::core -{ - -/// \brief Container for the configuration of a Task -/// -/// \author Barthelemy von Haller -struct TaskConfig { - std::string taskName; - std::string moduleName; - std::string className; - int cycleDurationSeconds; - int maxNumberCycles; -}; - -} // namespace o2::quality_control::core - -#endif // QC_CORE_TASKCONFIG_H diff --git a/Framework/include/QualityControl/TaskFactory.h b/Framework/include/QualityControl/TaskFactory.h index e190194bad..225c32b208 100644 --- a/Framework/include/QualityControl/TaskFactory.h +++ b/Framework/include/QualityControl/TaskFactory.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file TaskFactory.h /// \author Barthelemy von Haller @@ -6,22 +17,16 @@ #ifndef QC_CORE_TASKFACTORY_H #define QC_CORE_TASKFACTORY_H -#include +// STL #include -// ROOT -#include -#include -#include -// O2 -#include "QualityControl/QcInfoLogger.h" -#include "QualityControl/TaskConfig.h" -#include +// QC +#include "QualityControl/TaskRunnerConfig.h" +#include "QualityControl/TaskInterface.h" namespace o2::quality_control::core { class TaskInterface; - class ObjectsManager; /// \brief Factory in charge of creating tasks @@ -34,49 +39,11 @@ class TaskFactory TaskFactory() = default; virtual ~TaskFactory() = default; - using FatalException = AliceO2::Common::FatalException; - using errinfo_details = AliceO2::Common::errinfo_details; - /// \brief Create a new instance of a TaskInterface. /// The TaskInterface actual class is decided based on the parameters passed. /// \todo make it static ? /// \author Barthelemy von Haller - template - T* create(TaskConfig& taskConfig, std::shared_ptr objectsManager) - { - T* result = nullptr; - QcInfoLogger& logger = QcInfoLogger::GetInstance(); - - // Load the library - std::string library = "lib" + taskConfig.moduleName; - logger << "Loading library " << library << AliceO2::InfoLogger::InfoLogger::endm; - int libLoaded = gSystem->Load(library.c_str(), "", true); - if (libLoaded < 0) { - BOOST_THROW_EXCEPTION(FatalException() << errinfo_details("Failed to load Detector Publisher Library")); - } - - // Get the class and instantiate - logger << "Loading class " << taskConfig.className << AliceO2::InfoLogger::InfoLogger::endm; - TClass* cl = TClass::GetClass(taskConfig.className.c_str()); - std::string tempString("Failed to instantiate Quality Control Module"); - if (!cl) { - tempString += " because no dictionary for class named \""; - tempString += taskConfig.className; - tempString += "\" could be retrieved"; - BOOST_THROW_EXCEPTION(FatalException() << errinfo_details(tempString)); - } - logger << "Instantiating class " << taskConfig.className << " (" << cl << ")" - << AliceO2::InfoLogger::InfoLogger::endm; - result = static_cast(cl->New()); - if (!result) { - BOOST_THROW_EXCEPTION(FatalException() << errinfo_details(tempString)); - } - result->setName(taskConfig.taskName); - result->setObjectsManager(objectsManager); - logger << "QualityControl Module " << taskConfig.moduleName << " loaded " << AliceO2::InfoLogger::InfoLogger::endm; - - return result; - } + static TaskInterface* create(const TaskRunnerConfig& taskConfig, std::shared_ptr objectsManager); }; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/TaskInterface.h b/Framework/include/QualityControl/TaskInterface.h index f43fd0df13..2a0caaf7f3 100644 --- a/Framework/include/QualityControl/TaskInterface.h +++ b/Framework/include/QualityControl/TaskInterface.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file TaskInterface.h /// \author Piotr Konopka @@ -8,16 +19,28 @@ #define QC_CORE_TASKINTERFACE_H #include -// fixes problem of ''assert' not declared in this scope' in Framework/InitContext.h. -// Maybe ROOT does some #undef assert? -#include - // O2 -#include "Framework/InitContext.h" -#include "Framework/ProcessingContext.h" +#include +#include // QC #include "QualityControl/Activity.h" #include "QualityControl/ObjectsManager.h" +#include "QualityControl/UserCodeInterface.h" + +namespace o2::monitoring +{ +class Monitoring; +} + +namespace o2::globaltracking +{ +struct DataRequest; +} + +namespace o2::framework +{ +struct ConcreteDataMatcher; +} namespace o2::quality_control::core { @@ -32,7 +55,7 @@ namespace o2::quality_control::core /// /// \author Barthelemy von Haller /// \author Piotr Konopka -class TaskInterface +class TaskInterface : public UserCodeInterface { public: /// \brief Constructor @@ -41,7 +64,7 @@ class TaskInterface explicit TaskInterface(ObjectsManager* objectsManager); /// \brief Default constructor - TaskInterface(); + TaskInterface() = default; /// \brief Destructor virtual ~TaskInterface() noexcept = default; @@ -56,25 +79,31 @@ class TaskInterface // Definition of the methods for the template method pattern virtual void initialize(o2::framework::InitContext& ctx) = 0; - virtual void startOfActivity(Activity& activity) = 0; + virtual void startOfActivity(const Activity& activity) = 0; virtual void startOfCycle() = 0; virtual void monitorData(o2::framework::ProcessingContext& ctx) = 0; virtual void endOfCycle() = 0; - virtual void endOfActivity(Activity& activity) = 0; + virtual void endOfActivity(const Activity& activity) = 0; virtual void reset() = 0; + virtual void finaliseCCDB(framework::ConcreteDataMatcher& matcher, void* obj); + + /// \brief Called each time mCustomParameters is updated. + virtual void configure() override; + // Setters and getters void setObjectsManager(std::shared_ptr objectsManager); - void setName(const std::string& name); - const std::string& getName() const; + void setMonitoring(const std::shared_ptr& mMonitoring); + void setGlobalTrackingDataRequest(std::shared_ptr); + const o2::globaltracking::DataRequest* getGlobalTrackingDataRequest() const; protected: std::shared_ptr getObjectsManager(); + std::shared_ptr mMonitoring; private: - // TODO should we rather have a global/singleton for the objectsManager ? std::shared_ptr mObjectsManager; - std::string mName; + std::shared_ptr mGlobalTrackingDataRequest; }; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/TaskRunner.h b/Framework/include/QualityControl/TaskRunner.h index 3cce7e02f3..472a11788c 100644 --- a/Framework/include/QualityControl/TaskRunner.h +++ b/Framework/include/QualityControl/TaskRunner.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file TaskRunner.h /// \author Piotr Konopka @@ -7,27 +18,41 @@ #ifndef QC_CORE_TASKRUNNER_H #define QC_CORE_TASKRUNNER_H -// boost (should be first but then it makes errors in fairmq) -#include -#include -#include -#include // O2 -#include "Common/Timer.h" -#include "Configuration/ConfigurationInterface.h" -#include "Framework/DataProcessorSpec.h" -#include "Monitoring/MonitoringFactory.h" +#include +#include +#include +#include +#include +#include +#include // QC -#include "QualityControl/TaskConfig.h" -#include "QualityControl/TaskInterface.h" +#include "QualityControl/TaskRunnerConfig.h" + +namespace o2::configuration +{ +class ConfigurationInterface; +} -namespace ba = boost::accumulators; +namespace o2::monitoring +{ +class Monitoring; +} + +namespace o2::framework +{ +class EndOfStreamContext; +class InputSpan; +class InitContext; +class ProcessingContext; +} // namespace o2::framework namespace o2::quality_control::core { -using namespace o2::framework; -using namespace std::chrono; +class Timekeeper; +class TaskInterface; +class ObjectsManager; /// \brief A class driving the execution of a QC task inside DPL. /// @@ -36,27 +61,21 @@ using namespace std::chrono; /// It finally publishes the MonitorObjects owned and filled by the QC task and managed by the ObjectsManager. /// Usage: /// \code{.cxx} -/// auto qcTask = std::make_shared(taskName, configurationSource); +/// TaskRunner qcTask{taskName, configurationSource, id}; /// DataProcessorSpec newTask{ /// taskName, -/// qcTask->getInputsSpecs(), -/// Outputs{ qcTask->getOutputSpec() }, -/// AlgorithmSpec{ -/// (AlgorithmSpec::InitCallback) [qcTask = std::move(qcTask)](InitContext& initContext) { -/// -/// qcTask->initCallback(initContext); -/// -/// return (AlgorithmSpec::ProcessCallback) [qcTask = std::move(qcTask)] (ProcessingContext &processingContext) { -/// qcTask->processCallback(processingContext); -/// }; -/// } -/// } +/// qcTask.getInputsSpecs(), +/// Outputs{ qcTask.getOutputSpec() }, +/// AlgorithmSpec{}, +/// qcTask.getOptions() /// }; +/// // this needs to be at the end +/// newTask.algorithm = adaptFromTask(std::move(qcTask)); /// \endcode /// /// \author Piotr Konopka /// \author Barthelemy von Haller -class TaskRunner +class TaskRunner : public framework::Task { public: /// \brief Constructor @@ -64,64 +83,77 @@ class TaskRunner /// \param taskName - name of the task, which exists in tasks list in the configuration file /// \param configurationSource - absolute path to configuration file, preceded with backend (f.e. "json://") /// \param id - subSpecification for taskRunner's OutputSpec, useful to avoid outputs collisions one more complex topologies - TaskRunner(const std::string& taskName, const std::string& configurationSource, size_t id = 0); - ~TaskRunner(); - - /// \brief To be invoked during initialization of Data Processor - void initCallback(InitContext& iCtx); - /// \brief To be invoked inside Data Processor's main ProcessCallback - void processCallback(ProcessingContext& pCtx); - /// \brief To be invoked inside Data Processor's TimerCallback - void timerCallback(ProcessingContext& pCtx); - - const Inputs& getInputsSpecs() { return mInputSpecs; }; - const OutputSpec getOutputSpec() { return mMonitorObjectsSpec; }; - - void setResetAfterPublish(bool); - - /// \brief Unified DataOrigin for Quality Control tasks - static header::DataOrigin createTaskDataOrigin(); - /// \brief Unified DataDescription naming scheme for all tasks - static header::DataDescription createTaskDataDescription(const std::string& taskName); + TaskRunner(const TaskRunnerConfig& config); + ~TaskRunner() override; + + /// \brief TaskRunner's init callback + void init(framework::InitContext& iCtx) override; + /// \brief TaskRunner's process callback + void run(framework::ProcessingContext& pCtx) override; + /// \brief TaskRunner's finaliseCCDB callback + void finaliseCCDB(framework::ConcreteDataMatcher& matcher, void* obj) override; + + /// \brief TaskRunner's completion policy callback + static framework::CompletionPolicy::CompletionOp completionPolicyCallback(o2::framework::InputSpan const& inputs, std::vector const&, framework::ServiceRegistryRef&); + + std::string getDeviceName() const { return mTaskConfig.deviceName; }; + const framework::Inputs& getInputsSpecs() const { return mTaskConfig.inputSpecs; }; + const framework::OutputSpec& getOutputSpec() const { return mTaskConfig.moSpec; }; + const framework::Options& getOptions() const { return mTaskConfig.options; }; + + /// \brief Data Processor Label to identify all Task Runners + static framework::DataProcessorLabel getTaskRunnerLabel() { return { "qc-task" }; } + /// \brief ID string for all TaskRunner devices + static std::string createTaskRunnerIdString(); + /// \brief Unified DataDescription naming scheme for all timers + static header::DataDescription createTimerDataDescription(const std::string& taskName); + + /// \brief Callback for CallbackService::Id::EndOfStream + void endOfStream(framework::EndOfStreamContext& eosContext) override; private: /// \brief Callback for CallbackService::Id::Start (DPL) a.k.a. RUN transition (FairMQ) - void start(); + void start(framework::ServiceRegistryRef services); /// \brief Callback for CallbackService::Id::Stop (DPL) a.k.a. STOP transition (FairMQ) - void stop(); + void stop(framework::ServiceRegistryRef services); /// \brief Callback for CallbackService::Id::Reset (DPL) a.k.a. RESET DEVICE transition (FairMQ) void reset(); - void populateConfig(std::string taskName); + /// \brief Checks if all the expected data inputs are present in the provided InputRecord + static bool isDataReady(const framework::InputRecord& inputs); + void printTaskConfig() const; void startOfActivity(); void endOfActivity(); - void finishCycle(DataAllocator& outputs); - unsigned long publish(DataAllocator& outputs); + void startCycle(); + void finishCycle(framework::DataAllocator& outputs); + int publish(framework::DataAllocator& outputs); + void publishCycleStats(); + void saveToFile(); private: - std::string mTaskName; - TaskConfig mTaskConfig; - std::shared_ptr mConfigFile; // used in init only + TaskRunnerConfig mTaskConfig; std::shared_ptr mCollector; - std::unique_ptr mTask; - bool mResetAfterPublish; + std::shared_ptr mTask; std::shared_ptr mObjectsManager; + std::shared_ptr mTimekeeper; + Activity mActivity; - // consider moving these two to TaskConfig - Inputs mInputSpecs; - OutputSpec mMonitorObjectsSpec; + void updateMonitoringStats(framework::ProcessingContext& pCtx); + void registerToBookkeeping(); - int mNumberBlocks; - int mLastNumberObjects; - bool mCycleOn; - int mCycleNumber; + bool mCycleOn = false; + bool mNoMoreCycles = false; + int mCycleNumber = 0; + framework::DeploymentMode mDeploymentMode = framework::DeploymentMode::Local; // stats - AliceO2::Common::Timer mStatsTimer; - int mTotalNumberObjectsPublished; + int mNumberMessagesReceivedInCycle = 0; + int mNumberObjectsPublishedInCycle = 0; + int mTotalNumberObjectsPublished = 0; // over a run + double mLastPublicationDuration = 0; + uint64_t mDataReceivedInCycle = 0; AliceO2::Common::Timer mTimerTotalDurationActivity; - ba::accumulator_set> mPCpus; - ba::accumulator_set> mPMems; + AliceO2::Common::Timer mTimerDurationCycle; }; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/TaskRunnerConfig.h b/Framework/include/QualityControl/TaskRunnerConfig.h new file mode 100644 index 0000000000..7c5bfb54f3 --- /dev/null +++ b/Framework/include/QualityControl/TaskRunnerConfig.h @@ -0,0 +1,66 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TaskRunnerConfig.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_CORE_TASKCONFIG_H +#define QC_CORE_TASKCONFIG_H + +#include +#include +#include + +#include +#include "QualityControl/Activity.h" +#include "QualityControl/LogDiscardParameters.h" +#include "QualityControl/UserCodeConfig.h" + +namespace o2::base +{ +class GRPGeomRequest; +} + +namespace o2::globaltracking +{ +struct DataRequest; +} + +namespace o2::quality_control::core +{ + +/// \brief Container for the configuration of a Task +struct TaskRunnerConfig : public UserCodeConfig { + std::string deviceName; + std::vector> cycleDurations = {}; + int maxNumberCycles; + bool critical; + std::string monitoringUrl{}; + std::string bookkeepingUrl{}; + framework::Inputs inputSpecs{}; + framework::OutputSpec moSpec{ "XXX", "INVALID" }; + framework::Options options{}; + int parallelTaskID = 0; // ID to differentiate parallel local Tasks from one another. 0 means this is the only one. + std::string saveToFile{}; + int resetAfterCycles = 0; + core::LogDiscardParameters infologgerDiscardParameters; + Activity fallbackActivity; + std::shared_ptr grpGeomRequest; + std::shared_ptr globalTrackingDataRequest; + std::vector movingWindows; + bool disableLastCycle = false; +}; + +} // namespace o2::quality_control::core + +#endif // QC_CORE_TASKCONFIG_H diff --git a/Framework/include/QualityControl/TaskRunnerFactory.h b/Framework/include/QualityControl/TaskRunnerFactory.h index a2362bf0a6..a942ed496f 100644 --- a/Framework/include/QualityControl/TaskRunnerFactory.h +++ b/Framework/include/QualityControl/TaskRunnerFactory.h @@ -1,33 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file TaskRunnerFactory.h /// \author Piotr Konopka /// -#ifndef QC_CORE_TASKFACTORY_H -#define QC_CORE_TASKFACTORY_H +#ifndef QC_CORE_TASKRUNNERFACTORY_H +#define QC_CORE_TASKRUNNERFACTORY_H + +#include +#include -#include "Framework/DataProcessorSpec.h" +#include + +namespace o2::framework +{ +class CompletionPolicy; +} namespace o2::quality_control::core { +struct TaskSpec; +struct TaskRunnerConfig; +struct CommonSpec; + /// \brief Factory in charge of creating DataProcessorSpec of QC task class TaskRunnerFactory { public: - TaskRunnerFactory(); - virtual ~TaskRunnerFactory(); + TaskRunnerFactory() = default; + virtual ~TaskRunnerFactory() = default; - /// \brief Creator of tasks + /// \brief Creates TaskRunner /// - /// \param taskName - name of the task, which exists in tasks list in the configuration file - /// \param configurationSource - absolute path to configuration file, preceded with backend (f.e. "json://") - /// \param id - subSpecification for taskRunner's OutputSpec, useful to avoid outputs collisions one more complex topologies - /// \param resetAfterPublish - should taskRunner reset the user's task after each MO publication - o2::framework::DataProcessorSpec - create(std::string taskName, std::string configurationSource, size_t id = 0, bool resetAfterPublish = false); + /// \param taskConfig + static o2::framework::DataProcessorSpec create(const TaskRunnerConfig&); + + /// \brief Knows how to create TaskConfig from Specs + static TaskRunnerConfig extractConfig(const CommonSpec&, const TaskSpec&, std::optional id = std::nullopt, std::optional resetAfterCycles = std::nullopt); + + static int computeResetAfterCycles(const TaskSpec& taskSpec, bool runningWithMergers); + + /// \brief Provides necessary customization of the TaskRunners. + /// + /// Provides necessary customization of the Completion Policies of the TaskRunners. This is necessary to make + /// them work. Put it inside customize() function before including . + /// \param policies - completion policies vector + static void customizeInfrastructure(std::vector& policies); + static framework::InputSpec createTimerInputSpec(const CommonSpec& globalConfig, std::vector>& cycleDurations, const std::string& detectorName, const std::string& taskName); + + /// \brief Extracts and sanitize the cycle duration of the task + static std::vector> getSanitizedCycleDurations(const CommonSpec& globalConfig, const TaskSpec& taskSpec); }; } // namespace o2::quality_control::core -#endif // QC_CORE_TASKFACTORY_H +#endif // QC_CORE_TASKRUNNERFACTORY_H diff --git a/Framework/include/QualityControl/TaskSpec.h b/Framework/include/QualityControl/TaskSpec.h new file mode 100644 index 0000000000..686635a400 --- /dev/null +++ b/Framework/include/QualityControl/TaskSpec.h @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_TASKSPEC_H +#define QUALITYCONTROL_TASKSPEC_H + +/// +/// \file TaskSpec.h +/// \author Piotr Konopka +/// + +#include +#include + +#include "QualityControl/DataSourceSpec.h" +#include "QualityControl/RecoRequestSpecs.h" +#include "QualityControl/CustomParameters.h" + +namespace o2::quality_control::core +{ + +enum class TaskLocationSpec { + Local, + Remote +}; + +/// \brief Specification of a Task, which should map the JSON configuration structure. +struct TaskSpec { + // default, invalid spec + TaskSpec() = default; + + // minimal valid spec + TaskSpec(std::string taskName, std::string className, std::string moduleName, std::string detectorName, + int cycleDurationSeconds, std::vector dataSources) + : taskName(std::move(taskName)), + className(std::move(className)), + moduleName(std::move(moduleName)), + detectorName(std::move(detectorName)), + cycleDurationSeconds(cycleDurationSeconds), + dataSources(std::move(dataSources)) + { + } + + // basic + std::string taskName = "Invalid"; + std::string className = "Invalid"; + std::string moduleName = "Invalid"; + std::string detectorName = "Invalid"; + int cycleDurationSeconds = -1; // simple syntax + std::vector> multipleCycleDurations = {}; // complex syntax: multiple durations can be set for different intervals + std::vector dataSources; + // advanced + bool active = true; + bool critical = true; + int maxNumberCycles = -1; + size_t resetAfterCycles = 0; + std::string saveObjectsToFile; + core::CustomParameters customParameters; + // multinode setups + TaskLocationSpec location = TaskLocationSpec::Remote; + std::vector localMachines = {}; + std::string remoteMachine = "any"; + uint16_t remotePort = 36543; + std::string localControl = "aliecs"; + std::string mergingMode = "delta"; // todo as enum? + int mergerCycleMultiplier = 1; + std::vector mergersPerLayer{ 1 }; + GRPGeomRequestSpec grpGeomRequestSpec; + GlobalTrackingDataRequestSpec globalTrackingDataRequest; + std::vector movingWindows; + bool disableLastCycle = false; +}; + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_TASKSPEC_H diff --git a/Framework/include/QualityControl/Timekeeper.h b/Framework/include/QualityControl/Timekeeper.h new file mode 100644 index 0000000000..2320240a1c --- /dev/null +++ b/Framework/include/QualityControl/Timekeeper.h @@ -0,0 +1,98 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Timekeeper.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TIMEKEEPER_H +#define QUALITYCONTROL_TIMEKEEPER_H + +#include "QualityControl/ValidityInterval.h" +#include + +namespace o2::framework +{ +struct ProcessingContext; +struct TimingInfo; +} // namespace o2::framework + +namespace o2::quality_control::core +{ + +// could be moved to a separate file +using TimeframeIdRange = o2::math_utils::detail::Bracket; +const static TimeframeIdRange gInvalidTimeframeIdRange{ + std::numeric_limits::max(), std::numeric_limits::min() +}; + +class Timekeeper +{ + public: + Timekeeper(); + virtual ~Timekeeper() = default; + + /// \brief sets activity (run) duration + void setActivityDuration(ValidityInterval); + /// \brief sets start of activity (run), but prioritises the source of information according to the class implementation + void setStartOfActivity(validity_time_t ecsTimestamp = 0, validity_time_t configTimestamp = 0, + validity_time_t currentTimestamp = 0, std::function ccdbTimestampAccessor = nullptr); + /// \brief sets end of activity (run), but prioritises the source of information according to the class implementation + void setEndOfActivity(validity_time_t ecsTimestamp = 0, validity_time_t configTimestamp = 0, validity_time_t currentTimestamp = 0, + std::function ccdbTimestampAccessor = nullptr); + + /// \brief sets an accessor to get the number of orbits per TF for the currently processed run + void setCCDBOrbitsPerTFAccessor(std::function); + + /// \brief updates the validity based on the provided timestamp (ms since epoch) + virtual void updateByCurrentTimestamp(validity_time_t timestampMs) = 0; + /// \brief updates the validity based on the provided TF ID + virtual void updateByTimeFrameID(uint32_t tfID) = 0; + + /// \brief decides if the current cycle should be finished and a new one started + /// + /// This method decides if the current cycle should be finished and a new one started. + /// In case that the cycle should be finished, the updateBy* methods should be called + /// after the cycle end has been done. + virtual bool shouldFinishCycle(const o2::framework::TimingInfo& timingInfo) = 0; + + /// \brief resets the state of the mCurrent* counters + virtual void reset() = 0; + + ValidityInterval getValidity() const; + ValidityInterval getSampleTimespan() const; + TimeframeIdRange getTimerangeIdRange() const; + ValidityInterval getActivityDuration() const; + + protected: + std::function getCCDBOrbitsPerTFAccessor(void); + /// \brief defines how a class implementation chooses the activity (run) boundaries + // by using an accessor to ccdb, we do not call if we are not interested + virtual validity_time_t + activityBoundarySelectionStrategy(validity_time_t ecsTimestamp, + validity_time_t configTimestamp, + validity_time_t currentTimestamp, + std::function ccdbTimestampAccessor) = 0; + + protected: + // children should set these members according to the updates they receive + ValidityInterval mActivityDuration = gInvalidValidityInterval; // from O2StartTime to O2EndTime or current timestamp + ValidityInterval mCurrentValidityTimespan = gInvalidValidityInterval; // since the last reset time until `update()` call + ValidityInterval mCurrentSampleTimespan = gInvalidValidityInterval; // since the last reset + TimeframeIdRange mCurrentTimeframeIdRange = gInvalidTimeframeIdRange; // since the last reset + + private: + std::function mCCDBOrbitsPerTFAccessor = nullptr; +}; + +} // namespace o2::quality_control::core +#endif // QUALITYCONTROL_TIMEKEEPER_H diff --git a/Framework/include/QualityControl/TimekeeperAsynchronous.h b/Framework/include/QualityControl/TimekeeperAsynchronous.h new file mode 100644 index 0000000000..6d0c4a8b29 --- /dev/null +++ b/Framework/include/QualityControl/TimekeeperAsynchronous.h @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TimekeeperAsynchronous.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TIMEKEEPERASYNCHRONOUS_H +#define QUALITYCONTROL_TIMEKEEPERASYNCHRONOUS_H + +#include "Timekeeper.h" + +namespace o2::quality_control::core +{ + +class TimekeeperAsynchronous : public Timekeeper +{ + public: + explicit TimekeeperAsynchronous(validity_time_t windowLengthMs = 0); + ~TimekeeperAsynchronous() = default; + + void updateByCurrentTimestamp(validity_time_t timestampMs) override; + void updateByTimeFrameID(uint32_t tfID) override; + void reset() override; + + bool shouldFinishCycle(const o2::framework::TimingInfo& timingInfo) override; + + protected: + validity_time_t activityBoundarySelectionStrategy(validity_time_t ecsTimestamp, validity_time_t configTimestamp, + validity_time_t currentTimestamp, + std::function ccdbTimestampAccessor) override; + + private: + /// \brief computes validity interval of the provided timeframe ID + ValidityInterval computeTimestampFromTimeframeID(uint32_t tfID); + + private: + validity_time_t mWindowLengthMs = 0; + uint64_t mOrbitsPerTF = 0; + bool mWarnedAboutTfIdZero = false; +}; + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_TIMEKEEPERASYNCHRONOUS_H diff --git a/Framework/include/QualityControl/TimekeeperFactory.h b/Framework/include/QualityControl/TimekeeperFactory.h new file mode 100644 index 0000000000..34f0b3f39f --- /dev/null +++ b/Framework/include/QualityControl/TimekeeperFactory.h @@ -0,0 +1,36 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TimekeeperFactory.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TIMEKEEPERFACTORY_H +#define QUALITYCONTROL_TIMEKEEPERFACTORY_H + +#include "QualityControl/Timekeeper.h" +#include +#include + +namespace o2::quality_control::core +{ + +class TimekeeperFactory +{ + public: + static std::unique_ptr create(framework::DeploymentMode, validity_time_t windowLengthMs = 0); + static bool needsGRPECS(framework::DeploymentMode); +}; + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_TIMEKEEPERFACTORY_H diff --git a/Framework/include/QualityControl/TimekeeperSynchronous.h b/Framework/include/QualityControl/TimekeeperSynchronous.h new file mode 100644 index 0000000000..9b95adac9d --- /dev/null +++ b/Framework/include/QualityControl/TimekeeperSynchronous.h @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TimekeeperSynchronous.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TIMEKEEPERSYNCHRONOUS_H +#define QUALITYCONTROL_TIMEKEEPERSYNCHRONOUS_H + +#include "Timekeeper.h" + +namespace o2::quality_control::core +{ + +class TimekeeperSynchronous : public Timekeeper +{ + public: + TimekeeperSynchronous(); + ~TimekeeperSynchronous() = default; + + void updateByCurrentTimestamp(validity_time_t timestampMs) override; + void updateByTimeFrameID(uint32_t tfID) override; + + void reset() override; + bool shouldFinishCycle(const o2::framework::TimingInfo& timingInfo) override; + + protected: + validity_time_t activityBoundarySelectionStrategy(validity_time_t ecsTimestamp, validity_time_t configTimestamp, + validity_time_t currentTimestamp, + std::function ccdbTimestampAccessor) override; + + private: + bool mWarnedAboutDataWithoutSOR = false; + bool mWarnedAboutTfIdZero = false; +}; + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_TIMEKEEPERSYNCHRONOUS_H diff --git a/Framework/include/QualityControl/TrendingTask.h b/Framework/include/QualityControl/TrendingTask.h new file mode 100644 index 0000000000..db1dcb485e --- /dev/null +++ b/Framework/include/QualityControl/TrendingTask.h @@ -0,0 +1,94 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTask.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASK_H +#define QUALITYCONTROL_TRENDINGTASK_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/TrendingTaskConfig.h" + +#include +#include +#include + +class TAxis; +class TCanvas; + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +/// \brief A post-processing task which trends values, stores them in a TTree and produces plots. +/// +/// A post-processing task which trends objects inside QC database (QCDB). It extracts some values of one or multiple +/// objects using the Reductor classes, then stores them inside a TTree. One can generate plots out the TTree - the +/// class exposes the TTree::Draw interface to the user. The TTree and plots are stored in the QCDB. The class is +/// configured with configuration files, see Framework/postprocessing.json as an example. +/// +/// \author Piotr Konopka +class TrendingTask : public PostProcessingInterface +{ + public: + TrendingTask() = default; + ~TrendingTask() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + static constexpr size_t MaxRunNumberStringLength = 6; + struct { + // we store run numbers both as an integer and as a string to allow users to select whether they need + // a trend in integer or label domain (the latter will contain evenly-spaced data points) + Long64_t runNumber = 0; + char runNumberStr[MaxRunNumberStringLength + 1] = { 0 }; // 6 characters + null terminator + static const char* getBranchLeafList() + { + return "runNumber/L:runNumberStr/C"; + } + } mMetaData; + + static void setUserAxesLabels(TAxis* xAxis, TAxis* yAxis, const std::string& graphAxesLabels); + static void setUserYAxisRange(TH1* hist, const std::string& graphYAxisRange); + static void formatTimeXAxis(TH1* background); + static void formatRunNumberXAxis(TH1* background); + static std::string deduceGraphLegendOptions(const TrendingTaskConfig::Graph& graphConfig); + static void applyStyleToGraph(TGraph* graph, const TrendingTaskConfig::GraphStyle& style); + + /// returns true only if all datasources were available to update reductor + bool trendValues(const Trigger& t, repository::DatabaseInterface&); + void generatePlots(); + TCanvas* drawPlot(const TrendingTaskConfig::Plot& plotConfig); + void initializeTrend(repository::DatabaseInterface& qcdb); + bool canContinueTrend(TTree* tree); + + TrendingTaskConfig mConfig; + UInt_t mTime; + std::unique_ptr mTrend; + std::map> mPlots; + std::unordered_map> mReductors; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRENDINGTASK_H diff --git a/Framework/include/QualityControl/TrendingTaskConfig.h b/Framework/include/QualityControl/TrendingTaskConfig.h new file mode 100644 index 0000000000..2d5b33820f --- /dev/null +++ b/Framework/include/QualityControl/TrendingTaskConfig.h @@ -0,0 +1,101 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskConfig.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKCONFIG_H +#define QUALITYCONTROL_TRENDINGTASKCONFIG_H + +#include +#include +#include "CustomParameters.h" +#include "QualityControl/PostProcessingConfig.h" + +namespace o2::quality_control::postprocessing +{ + +// todo pretty print +/// \brief TrendingTask configuration structure +struct TrendingTaskConfig : PostProcessingConfig { + TrendingTaskConfig() = default; + TrendingTaskConfig(std::string name, const boost::property_tree::ptree& config); + ~TrendingTaskConfig() = default; + + // graph style configuration + // colors as defined by ROOT TColor class: + // https://root.cern/doc/master/classTColor.html + // marker colors and styles are as defined by ROOT TAttMarker class + // https://root.cern/doc/master/classTAttMarker.html + // line styles are as defined by ROOT TAttLine class + // https://root.cern/doc/master/classTAttLine.html + // WARNING: Any parameters in this struct will override colliding parameters in option + struct GraphStyle { + int lineColor = -1; + int lineStyle = -1; + int lineWidth = -1; + int markerColor = -1; + int markerStyle = -1; + float markerSize = -1.f; + int fillColor = -1; + int fillStyle = -1; + }; + + // this corresponds to one TTree::Draw() call, i.e. one graph or histogram drawing + struct Graph { + std::string name; + std::string title; + std::string varexp; + std::string selection; + std::string option; // the list of possible options are documented in TGraphPainter and THistPainter + std::string errors; + GraphStyle style; + }; + + // legend configuration + struct LegendConfig { + int nColumns{ 1 }; + float x1{ -1.f }, y1{ -1.f }, x2{ -1.f }, y2{ -1.f }; // NDC coords + }; + + // this corresponds to one canvas which can include multiple graphs + struct Plot { + std::string name; + std::string title; + std::string graphAxisLabel; + std::string graphYRange; + int colorPalette = 0; + LegendConfig legend; + std::vector graphs; + }; + + struct DataSource { + std::string type; + std::string path; + std::string name; + std::string reductorName; + core::CustomParameters reductorParameters; + std::string moduleName; + }; + + bool producePlotsOnUpdate{}; + bool resumeTrend{}; + bool trendIfAllInputs{ false }; + std::string trendingTimestamp; + std::vector plots; + std::vector dataSources; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRENDINGTASKCONFIG_H diff --git a/Framework/include/QualityControl/TriggerHelpers.h b/Framework/include/QualityControl/TriggerHelpers.h new file mode 100644 index 0000000000..10144be357 --- /dev/null +++ b/Framework/include/QualityControl/TriggerHelpers.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TriggerHelpers.h +/// \author Piotr Konopka +/// +#ifndef QUALITYCONTROL_TRIGGERHELPERS_H +#define QUALITYCONTROL_TRIGGERHELPERS_H + +#include "QualityControl/Triggers.h" +#include +#include + +namespace o2::quality_control::postprocessing +{ +class PostProcessingConfig; +} +namespace o2::quality_control::postprocessing::trigger_helpers +{ + +/// \brief Creates a trigger function by taking its corresponding name. +TriggerFcn triggerFactory(const std::string& trigger, const PostProcessingConfig& config); +/// \brief Creates a trigger function vector given trigger names +std::vector createTriggers(const std::vector& triggerNames, const PostProcessingConfig& config); +/// \brief Executes a vector of triggers functions and returns the first trigger which is not TriggerType::No +Trigger tryTrigger(std::vector&); +/// \brief Checks if in a given trigger configuration vector there is a UserOrControl trigger. +/// This is trigger cannot be checked as all the others, so we just check if it is requested in the right moments. +bool hasUserOrControlTrigger(const std::vector&); + +} // namespace o2::quality_control::postprocessing::trigger_helpers + +#endif //QUALITYCONTROL_TRIGGERHELPERS_H diff --git a/Framework/include/QualityControl/Triggers.h b/Framework/include/QualityControl/Triggers.h new file mode 100644 index 0000000000..cedeb8f662 --- /dev/null +++ b/Framework/include/QualityControl/Triggers.h @@ -0,0 +1,108 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Triggers.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRIGGERS_H +#define QUALITYCONTROL_TRIGGERS_H + +#include +#include +#include +#include +#include +#include "QualityControl/Activity.h" + +namespace o2::quality_control::postprocessing +{ + +// todo: implement the rest +/// \brief Possible triggers +enum TriggerType { + No = 0, // casts to boolean false + Once, // triggers only first time it is asked + Always, // triggers always + StartOfRun, + EndOfRun, + StartOfFill, + EndOfFill, + Periodic, + NewObject, + ForEachObject, // iterates on each object version which matches an activity + ForEachLatest, // iterates on the latest object versions for each distinct activity + UserOrControl, // reacts start and stop transitions (not an update trigger). + INVALID +}; + +struct Trigger { + + /// \brief Constructor. Timestamp is generated from the time of construction. + Trigger(TriggerType triggerType, bool last = false, core::Activity activity = {}) + : triggerType(triggerType), last(last), activity(std::move(activity)), timestamp(msSinceEpoch()){}; + /// \brief Constructor. + Trigger(TriggerType triggerType, bool last, core::Activity activity, uint64_t timestamp, std::string config = {}) + : triggerType(triggerType), last(last), activity(std::move(activity)), timestamp(timestamp), config(std::move(config)){}; + /// \brief Constructor. + Trigger(TriggerType triggerType, bool last, uint64_t timestamp) : triggerType(triggerType), last(last), activity(), timestamp(timestamp){}; + + operator bool() const { return triggerType != TriggerType::No && triggerType != TriggerType::INVALID; } + friend std::ostream& operator<<(std::ostream& out, const Trigger& t); + bool operator==(TriggerType other) const + { + return triggerType == other; + } + + static uint64_t msSinceEpoch(); + + TriggerType triggerType; + bool last; + core::Activity activity; // if tracking an object, it contains also its validity start and end + uint64_t timestamp; // if tracking an object, it is the validity start (validFrom) + std::string config{}; + std::map metadata{}; // metadata to search in database +}; + +using TriggerFcn = std::function; + +namespace triggers +{ + +/// \brief Triggers when it detects a Start Of Run during its uptime (once per each) +TriggerFcn StartOfRun(const std::string& kafkaBrokers, const std::string& topic, const std::string& detector, const std::string& taskName, const core::Activity& = {}); +/// \brief Triggers when it detects an End Of Run during its uptime (once per each) +TriggerFcn EndOfRun(const std::string& kafkaBrokers, const std::string& topic, const std::string& detector, const std::string& taskName, const core::Activity& = {}); +/// \brief Triggers when it detects Stable Beams during its uptime (once per each) +TriggerFcn StartOfFill(const core::Activity& = {}); +/// \brief Triggers when it detects an event dump during its uptime (once per each) +TriggerFcn EndOfFill(const core::Activity& = {}); +/// \brief Triggers when a period of time passes +TriggerFcn Periodic(double seconds, const core::Activity& = {}, std::string config = {}); +/// \brief Triggers when it detect a new object in QC repository with given name +TriggerFcn NewObject(const std::string& databaseUrl, const std::string& databaseType, const std::string& objectPath, const core::Activity& = {}, const std::string& config = {}); +/// \brief Triggers for each object version in the path which match the activity. It retrieves the available list only once! +TriggerFcn ForEachObject(const std::string& databaseUrl, const std::string& databaseType, const std::string& objectPath, const core::Activity& = {}, const std::string& config = {}); +/// \brief Triggers for the latest object version for each distinct activity. It retrieves the available list only once! +TriggerFcn ForEachLatest(const std::string& databaseUrl, const std::string& databaseType, const std::string& objectPath, const core::Activity& = {}, const std::string& config = {}); +/// \brief Triggers only first time it is executed +TriggerFcn Once(const core::Activity& = {}); +/// \brief Triggers always +TriggerFcn Always(const core::Activity& = {}); +/// \brief Triggers never +TriggerFcn Never(const core::Activity& = {}); + +} // namespace triggers + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRIGGERS_H diff --git a/Framework/include/QualityControl/TypesLinkDef.h b/Framework/include/QualityControl/TypesLinkDef.h new file mode 100644 index 0000000000..0be370106c --- /dev/null +++ b/Framework/include/QualityControl/TypesLinkDef.h @@ -0,0 +1,12 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ namespace o2::quality_control::core; +#pragma link C++ class o2::quality_control::core::MonitorObject + ; +#pragma link C++ class o2::quality_control::core::QualityObject + ; +#pragma link C++ class o2::quality_control::core::Quality + ; +#pragma link C++ class o2::quality_control::core::Activity + ; + +#endif diff --git a/Framework/include/QualityControl/UpdatePolicyManager.h b/Framework/include/QualityControl/UpdatePolicyManager.h new file mode 100644 index 0000000000..667215e144 --- /dev/null +++ b/Framework/include/QualityControl/UpdatePolicyManager.h @@ -0,0 +1,147 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file UpdatePolicyManager.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_CHECKER_POLICYMANAGER_H +#define QC_CHECKER_POLICYMANAGER_H + +#include +#include +#include +#include +#include +#include + +#include "QualityControl/UpdatePolicyType.h" + +namespace o2::quality_control::checker +{ + +using IsReadyFunctionType = std::function; +typedef uint32_t RevisionType; + +/** + * Represents a policy and all its associated elements. + */ +struct UpdatePolicy { + std::string actorName; + IsReadyFunctionType isReady; + std::vector inputObjects; + bool allInputObjects; + // TODO this line makes me think that lambdas are not enough because we actually need to store a state... + bool policyHelperFlag; // the purpose might change depending on policy, + RevisionType revision = 0; + + friend std::ostream& operator<<(std::ostream& out, const UpdatePolicy& updatePolicy); // output +}; + +/** + * The UpdatePolicyManager is in charge of instantiating and keeping track of policies. + * + * Naming: + * - a `caller` (e.g. CheckRunner or AggregatorRunner) holds an instance of the UpdatePolicyManager and drives it. + * - a `policy` determines whether something is ready to be done or not. It is a function returning + * a boolean. + * - an `actor` (e.g. Check or Aggregator) is in charge of executing something when a policy is fulfilled. There + * can be several actors for a caller. The actor is not aware of the UpdatePolicyManager. + * - the `objects` are received by the caller. They are processed by the actors and their status (e.g. freshly received) + * is used by some policies. + * - a revision is a number associated to each object to determine when it was received and associated to + * each actor to determine when it was last time triggered. + * + * The following policies are available: + * - OnAny: triggers when an object is received that matches ANY object listed as a data source of the policy. + * - OnAnyNonZero: triggers only if all objects have been received at least once, then trigger the same way as onAny. + * - onAll: triggers when ALL objects listed as data source of the policy have been updated at least once. + * - onEachSeparately: synonym of 'onAny'. + * If "all" is specified as list of object, or the list is empty, we always trigger. + * + * A typical caller code looks like this: + * \code{.cpp} + * // when initializing + * UpdatePolicyManager.addPolicy("actor1", "OnAny", {"object1"}, false, false); + * + * // in run() loop : + * // upon receiving new data, i.e. object1 + * UpdatePolicyManager.updateObjectRevision("object1"); + * // check if we should do something + * if (UpdatePolicyManager.isReady("object1") { + * doSomething(); + * UpdatePolicyManager.updateActorRevision("actor1"); + * } + * + * UpdatePolicyManager.updateGlobalRevision(); + * // end run() loop + * \endcode + * + */ +class UpdatePolicyManager +{ + public: + /** + * \brief Update the global revision number. + * + * This function function should be called at the end of a processing loop (typically the run() method). + */ + void updateGlobalRevision(); + /** + * \brief Update the revision number associated with an actor. + * + * This function is typically called after the actor has been triggered based on its policy and its work is done. + * @param actorName + * @param revision + */ + void updateActorRevision(const std::string& actorName, RevisionType revision); + void updateActorRevision(const std::string& actorName); + /** + * \brief Update the revision number associated with an object. + * + * This function is typically called after a new object has been received. + * @param objectName + * @param revision + */ + void updateObjectRevision(const std::string& objectName, RevisionType revision); + void updateObjectRevision(const std::string& objectName); + /** + * Add a policy for the given actor. + * @param actorName + * @param policyType One of the policy names: OnAll, OnAnyNonZero, OnEachSeparately, OnAny + * @param objectNames + * @param allObjects + * @param policyHelper + */ + void addPolicy(const std::string& actorName, UpdatePolicyType policyType, std::vector objectNames, bool allObjects, bool policyHelper); + + /** + * Remove all policies and reset revisions. + */ + void reset(); + + /** + * Checks whether the given actor is ready or not. + * @param actorName + * @return + */ + bool isReady(const std::string& actorName); + + private: + std::map mPoliciesByActor; + RevisionType mGlobalRevision = 1; + std::map mObjectsRevision; +}; + +} // namespace o2::quality_control::checker + +#endif // QC_CHECKER_POLICYMANAGER_H diff --git a/Framework/include/QualityControl/UpdatePolicyType.h b/Framework/include/QualityControl/UpdatePolicyType.h new file mode 100644 index 0000000000..f2de8bbead --- /dev/null +++ b/Framework/include/QualityControl/UpdatePolicyType.h @@ -0,0 +1,39 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_UPDATEPOLICYTYPE_H +#define QUALITYCONTROL_UPDATEPOLICYTYPE_H + +/// +/// \file UpdatePolicyType.h +/// \author Piotr Konopka +/// + +#include + +namespace o2::quality_control::checker +{ + +enum class UpdatePolicyType { + OnAny, + OnAnyNonZero, + OnAll, + OnEachSeparately, + OnGlobalAny +}; + +struct UpdatePolicyTypeUtils { + static UpdatePolicyType FromString(const std::string&); + static std::string ToString(UpdatePolicyType); +}; + +} // namespace o2::quality_control::checker +#endif //QUALITYCONTROL_UPDATEPOLICYTYPE_H diff --git a/Framework/include/QualityControl/UserCodeConfig.h b/Framework/include/QualityControl/UserCodeConfig.h new file mode 100644 index 0000000000..dc2a789cf8 --- /dev/null +++ b/Framework/include/QualityControl/UserCodeConfig.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file UserCodeConfig.h +/// \author Barthelemy von Haller +/// + +#ifndef QUALITYCONTROL_USERCODECONFIG_H +#define QUALITYCONTROL_USERCODECONFIG_H + +#include "QualityControl/CustomParameters.h" +#include "QualityControl/stringUtils.h" +#include "QualityControl/DataSourceSpec.h" + +namespace o2::quality_control::core +{ + +/// \brief Container for the configuration of a Task +struct UserCodeConfig { + std::string name; // task name, check name, etc... + std::string moduleName; + std::string className; + std::string detectorName = "MISC"; // intended to be the 3 letters code; + std::string consulUrl; + CustomParameters customParameters; + std::string ccdbUrl; + std::unordered_map repository; // we need the full config of the database to build the database in the subclasses + std::vector dataSources; +}; + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_USERCODECONFIG_H diff --git a/Framework/include/QualityControl/UserCodeInterface.h b/Framework/include/QualityControl/UserCodeInterface.h new file mode 100644 index 0000000000..8cd26dd310 --- /dev/null +++ b/Framework/include/QualityControl/UserCodeInterface.h @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file UserCodeInterface.h +/// \author Barthelemy von Haller +/// + +#ifndef QUALITYCONTROL_USERCODEINTERFACE_H +#define QUALITYCONTROL_USERCODEINTERFACE_H + +#include +#include +#include + +#include "QualityControl/ConditionAccess.h" +#include "QualityControl/CustomParameters.h" +#include "QualityControl/DatabaseInterface.h" + +namespace o2::quality_control::core +{ + +/// \brief Common interface for Check and Task Interfaces. +/// +/// \author Barthelemy von Haller +class UserCodeInterface : public ConditionAccess +{ + public: + /// Default constructor + UserCodeInterface() = default; + /// Destructor + virtual ~UserCodeInterface() = default; + + void setCustomParameters(const CustomParameters& parameters); + + /// \brief Configure the object. + /// + /// Users can use this method to configure their object. + /// It is called each time mCustomParameters is updated, including the first time it is read. + virtual void configure() = 0; + + const std::string& getName() const; + void setName(const std::string& name); + void setDatabase(std::unordered_map dbConfig); + + protected: + CustomParameters mCustomParameters; + std::string mName; + std::shared_ptr mDatabase; + + ClassDef(UserCodeInterface, 4) +}; + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_USERCODEINTERFACE_H diff --git a/Framework/include/QualityControl/UserInputOutput.h b/Framework/include/QualityControl/UserInputOutput.h new file mode 100644 index 0000000000..eac2231342 --- /dev/null +++ b/Framework/include/QualityControl/UserInputOutput.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file UserInputOutput.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_USERINPUTOUTPUT_H +#define QUALITYCONTROL_USERINPUTOUTPUT_H + +#include + +#include +#include +#include +#include + +#include "QualityControl/DataHeaderHelpers.h" +#include "QualityControl/DataSourceType.h" + +namespace o2::quality_control::core +{ + +/// \brief returns a standard ConcreteDataMatcher for QC inputs and outputs +framework::ConcreteDataMatcher + createUserDataMatcher(DataSourceType dataSourceType, const std::string& detectorName, const std::string& userCodeName, + o2::header::DataHeader::SubSpecificationType subSpec = 0); + +/// \brief returns a standard InputSpec for QC user data +/// +/// Returns a standard InputSpec for QC user data. The combination of the first four arguments should be unique +/// in a QC workflow. When provided binding is empty, userCodeName is used. If a Data Processor asks for multiple +/// inputs with the same userCodeName, a custom binding should be set. +framework::InputSpec + createUserInputSpec(DataSourceType dataSourceType, const std::string& detectorName, const std::string& userCodeName, + o2::header::DataHeader::SubSpecificationType subSpec = 0, const std::string& binding = ""); + +/// \brief returns a standard OutputSpec for QC user data +/// +/// Returns a standard OutputSpec for QC user data. The combination of the first four arguments should be unique +/// in a QC workflow. When provided binding is empty, userCodeName is used. If a Data Processor asks for multiple +/// outputs with the same userCodeName, a custom binding should be set. +framework::OutputSpec + createUserOutputSpec(DataSourceType dataSourceType, const std::string& detectorName, const std::string& userCodeName, + o2::header::DataHeader::SubSpecificationType subSpec = 0, const framework::OutputLabel& binding = {}); + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_USERINPUTOUTPUT_H diff --git a/Framework/include/QualityControl/ValidityInterval.h b/Framework/include/QualityControl/ValidityInterval.h new file mode 100644 index 0000000000..67b5f7d08d --- /dev/null +++ b/Framework/include/QualityControl/ValidityInterval.h @@ -0,0 +1,37 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ValidityInterval.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_VALIDITYINTERVAL_H +#define QUALITYCONTROL_VALIDITYINTERVAL_H + +#include +#include + +namespace o2::quality_control::core +{ + +using validity_time_t = uint64_t; // ms since epoch +using ValidityInterval = o2::math_utils::detail::Bracket; +const static ValidityInterval gInvalidValidityInterval{ + std::numeric_limits::max(), std::numeric_limits::min() +}; +const static ValidityInterval gFullValidityInterval{ + std::numeric_limits::min() + 1, std::numeric_limits::max() +}; + +} // namespace o2::quality_control::core + +#endif //QUALITYCONTROL_VALIDITYINTERVAL_H \ No newline at end of file diff --git a/Framework/include/QualityControl/Version.h.in b/Framework/include/QualityControl/Version.h.in index b0253aa4aa..fb9b36de9d 100644 --- a/Framework/include/QualityControl/Version.h.in +++ b/Framework/include/QualityControl/Version.h.in @@ -1,7 +1,17 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file Version.h -/// \brief Report the version for this package. -/// \author bvonhall +/// \author Barthelemy von Haller /// #ifndef QC_CORE_VERSION_H @@ -9,85 +19,111 @@ #include #include +#include + +using namespace std; + +namespace o2::quality_control::core { -namespace o2::quality_control::core +/// Represents a software package version. +/// Version numbers can go up to 999. +class Version { + public: + + /// Create a version from a string. + /// @param version The version in the form X.Y.Z. If minor or patch is missing, it is replaced by 0. + Version(std::string version) + { + std::sscanf(version.c_str(), "%u.%u.%u", &mMajor, &mMinor, &mPatch); + } + + Version(unsigned int major, unsigned int minor, unsigned int patch): mMajor(major), mMinor(minor), mPatch(patch) + { + } + + ~Version() = default; + + /// \brief Returns the version of the QC framework. + /// Returns the version of the QC framework as found in CMake. + static Version& GetQcVersion() + { + // Guaranteed to be destroyed. Instantiated on first use + static Version qcVersion{@PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@}; + return qcVersion; + } + + unsigned int getMajor() const + { + return mMajor; + } + + unsigned int getMinor() const + { + return mMinor; + } + + unsigned int getPatch() const + { + return mPatch; + } + + bool operator<(const Version &other) + { + return getIntegerRepresentation() < other.getIntegerRepresentation(); + } + + bool operator>=(const Version &other) + { + return getIntegerRepresentation() >= other.getIntegerRepresentation(); + } + + bool operator>(const Version &other) + { + return getIntegerRepresentation() > other.getIntegerRepresentation(); + } + + bool operator<=(const Version &other) + { + return getIntegerRepresentation() <= other.getIntegerRepresentation(); + } + + bool operator==(const Version &other) + { + return getIntegerRepresentation() == other.getIntegerRepresentation(); + } + + bool operator!=(const Version &other) + { + return getIntegerRepresentation() != other.getIntegerRepresentation(); + } + + friend std::ostream &operator<<(std::ostream &stream, const Version &ver) + { + stream << ver.getString(); + return stream; + } + + std::string getString() const + { + std::stringstream result; + result << getMajor() << '.' << getMinor() << '.' << getPatch(); + return result.str(); + } + + unsigned int getIntegerRepresentation() const + { + return getMajor() * (getMaxVersion() * getMaxVersion()) + getMinor() * getMaxVersion() + getPatch() * 1; + } + + unsigned int getMaxVersion() const { + return mMaxVersion; + } + + private: + unsigned int mMajor = 0, mMinor = 0, mPatch = 0; + unsigned int mMaxVersion = 1000; -/// The current major version. -#define QUALITYCONTROL_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ - -/// The current minor version. -#define QUALITYCONTROL_VERSION_MINOR @PROJECT_VERSION_MINOR@ - -/// The current patch level. -#define QUALITYCONTROL_VERSION_PATCH @PROJECT_VERSION_PATCH@ - -/// True if the current version is newer than the given one. -#define QUALITYCONTROL_VERSION_GT(MAJOR, MINOR, PATCH) \ - ((QUALITYCONTROL_VERSION_MAJOR > MAJOR) || \ - (QUALITYCONTROL_VERSION_MAJOR == \ - MAJOR&&(QUALITYCONTROL_VERSION_MINOR > MINOR || (QUALITYCONTROL_VERSION_MINOR == MINOR&& QUALITYCONTROL_VERSION_PATCH > PATCH)))) - -/// True if the current version is equal or newer to the given. -#define QUALITYCONTROL_VERSION_GE(MAJOR, MINOR, PATCH) \ - ((QUALITYCONTROL_VERSION_MAJOR > MAJOR) || \ - (QUALITYCONTROL_VERSION_MAJOR == \ - MAJOR&&(QUALITYCONTROL_VERSION_MINOR > MINOR || (QUALITYCONTROL_VERSION_MINOR == MINOR&& QUALITYCONTROL_VERSION_PATCH >= PATCH)))) - -/// True if the current version is older than the given one. -#define QUALITYCONTROL_VERSION_LT(MAJOR, MINOR, PATCH) \ - ((QUALITYCONTROL_VERSION_MAJOR < MAJOR) || \ - (QUALITYCONTROL_VERSION_MAJOR == \ - MAJOR&&(QUALITYCONTROL_VERSION_MINOR < MINOR || (QUALITYCONTROL_VERSION_MINOR == MINOR&& QUALITYCONTROL_VERSION_PATCH < PATCH)))) - -/// True if the current version is older or equal to the given. -#define QUALITYCONTROL_VERSION_LE(MAJOR, MINOR, PATCH) \ - ((QUALITYCONTROL_VERSION_MAJOR < MAJOR) || \ - (QUALITYCONTROL_VERSION_MAJOR == \ - MAJOR&&(QUALITYCONTROL_VERSION_MINOR < MINOR || (QUALITYCONTROL_VERSION_MINOR == MINOR&& QUALITYCONTROL_VERSION_PATCH <= PATCH)))) - -/// Information about the current QualityControl version. -class Version { -public: - /// @return the current major version of QualityControl. - static int getMajor() - { - return QUALITYCONTROL_VERSION_MAJOR; - } - - /// @return the current minor version of QualityControl. - static int getMinor() - { - return QUALITYCONTROL_VERSION_MINOR; - } - - /// @return the current patch level of QualityControl. - static int getPatch() - { - return QUALITYCONTROL_VERSION_PATCH; - } - - /// @return the current QualityControl version (MM.mm.pp). - static std::string getString() - { - std::ostringstream version; - version << QUALITYCONTROL_VERSION_MAJOR << '.' << QUALITYCONTROL_VERSION_MINOR << '.' << QUALITYCONTROL_VERSION_PATCH; - return version.str(); - } - - /// @return the VCS revision. - static std::string getRevision() - { - return QUALITYCONTROL_VCS_REVISION; - } - - /// @return the current QualityControl version plus the VCS revision (MM.mm.pp.rev). - static std::string getRevString() - { - std::ostringstream version; - version << getString() << '.' << QUALITYCONTROL_VCS_REVISION; - return version.str(); - } }; } // namespace o2::quality_control::core diff --git a/Framework/include/QualityControl/WorkflowType.h b/Framework/include/QualityControl/WorkflowType.h new file mode 100644 index 0000000000..383eb6aa66 --- /dev/null +++ b/Framework/include/QualityControl/WorkflowType.h @@ -0,0 +1,43 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file WorkflowType.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_WORKFLOWTYPE_H +#define QUALITYCONTROL_WORKFLOWTYPE_H + +namespace o2::framework +{ +class ConfigParamRegistry; +} + +namespace o2::quality_control::core +{ + +enum class WorkflowType { + Standalone, + FullChain, + Local, + Remote, + LocalBatch, + RemoteBatch +}; + +namespace workflow_type_helpers +{ +WorkflowType getWorkflowType(const framework::ConfigParamRegistry& options); +} + +} // namespace o2::quality_control::core +#endif // QUALITYCONTROL_WORKFLOWTYPE_H diff --git a/Framework/include/QualityControl/runnerUtils.h b/Framework/include/QualityControl/runnerUtils.h new file mode 100644 index 0000000000..b8cbbe853c --- /dev/null +++ b/Framework/include/QualityControl/runnerUtils.h @@ -0,0 +1,91 @@ +// Copyright 2019-2022 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runnerUtils.h +/// \author Barthelemy von Haller +/// + +#ifndef QUALITYCONTROL_RUNNERUTILS_H +#define QUALITYCONTROL_RUNNERUTILS_H + +#include +#include +#include +#include +#include +#include +#include +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Activity.h" + +namespace o2::quality_control::core +{ + +/** + * Returns the name of the first task encountered in the config file. + * Ad-hoc solution to avoid hard-coding the task when we create the printer (he needs it to know the data description + * of the data coming out of the checker). + * @param config + * @return The name of the first task in the config file. + */ +std::string getFirstTaskName(const std::string& configurationSource); +std::string getFirstCheckName(const std::string& configurationSource); +bool hasChecks(const std::string& configSource); + +template + requires std::is_arithmetic_v +T computeNumericalActivityField(framework::ServiceRegistryRef services, const std::string& name, T fallbackNumber = 0) +{ + T result = 0; + + try { + auto temp = services.get().device()->fConfig->GetProperty(name); + ILOG(Info, Devel) << "Got this property '" << name << "' from RawDeviceService: '" << temp << "'" << ENDM; + result = boost::lexical_cast(temp); + } catch (std::invalid_argument& ia) { + ILOG(Info, Devel) << " " << name << " is not a number, using the fallback." << ENDM; + } catch (fair::mq::PropertyNotFoundError& err) { + ILOG(Info, Devel) << " " << name << " not found in options, using the fallback." << ENDM; + } catch (boost::bad_lexical_cast& err) { + ILOG(Info, Devel) << " " << name << " could not be cast to a number (" << err.what() << "), using the fallback." << ENDM; + } + result = result > 0 /* found it in service */ ? result : fallbackNumber; + ILOG(Info, Devel) << name << " returned by computeActivityField (default) : " << result << ENDM; + return result; +} + +std::string computeStringActivityField(framework::ServiceRegistryRef services, const std::string& name, const std::string& fallBack); + +std::string_view translateIntegerRunType(const std::string& runType); +Activity computeActivity(framework::ServiceRegistryRef services, const Activity& fallbackActivity); + +std::string indentTree(int level); +void printTree(const boost::property_tree::ptree& pt, int level = 0); + +std::vector> parseOverrideValues(const std::string& input); +void overrideValues(boost::property_tree::ptree& tree, const std::vector>& keyValues); + +/** + * template the param infologgerDiscardFile (_ID_->[device-id]) + * @param originalFile + * @param iCtx + * @return + */ +std::string templateILDiscardFile(std::string& originalFile, framework::InitContext& iCtx); + +uint64_t getCurrentTimestamp(); + +void initInfologger(framework::InitContext& iCtx, core::LogDiscardParameters infologgerDiscardParameters, std::string facility, std::string detectorName = ""); + +} // namespace o2::quality_control::core + +#endif // QUALITYCONTROL_RUNNERUTILS_H diff --git a/Framework/include/QualityControl/stringUtils.h b/Framework/include/QualityControl/stringUtils.h new file mode 100644 index 0000000000..82c5c8c8d5 --- /dev/null +++ b/Framework/include/QualityControl/stringUtils.h @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file stringUtils.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_STRING_UTILS_H +#define QC_STRING_UTILS_H + +#include +#include + +namespace o2::quality_control::core +{ +class CustomParameters; + +std::vector getBinRepresentation(unsigned char* data, size_t size); +std::vector getHexRepresentation(unsigned char* data, size_t size); + +/// \brief Decode key of a configurable parameter as boolean +/// \param value Value to be decoded (true or false, case-insensitive) +/// \return Boolean representation of the value +/// \throw std::runtime_error in case value is not a boolean value +bool decodeBool(const std::string& value); + +/// Utility methods to fetch boolean options from the custom parameters. +/// @param name name of the option as in the mCustomParameters and JSON file +/// @return true if the option was found in the config and it was set to true, false if it was set to false +/// @throw AliceO2::Common::ObjectNotFoundError if 'name' is not found in mCustomParameters +/// @throw std::runtime_error the value is not a bool +bool parseBoolParam(const CustomParameters& customParameters, const std::string& name, const std::string& runType = "default", const std::string& beamType = "default"); + +/** + * Check if the string contains only digits. + */ +bool isUnsignedInteger(const std::string& s); + +} // namespace o2::quality_control::core + +#endif // QC_STRING_UTILS_H diff --git a/Framework/include/QualityControl/testUtils.h b/Framework/include/QualityControl/testUtils.h new file mode 100644 index 0000000000..fbc6df06ce --- /dev/null +++ b/Framework/include/QualityControl/testUtils.h @@ -0,0 +1,32 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testUtils.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_TEST_UTILS_H +#define QC_TEST_UTILS_H + +#include +#include + +namespace o2::quality_control::test +{ +bool do_nothing(AliceO2::Common::FatalException const& fe) +{ + std::cout << boost::diagnostic_information(fe) << std::endl; + return true; +} +} // namespace o2::quality_control::test + +#endif // QC_TEST_UTILS_H diff --git a/Framework/multiNode.json b/Framework/multiNode.json new file mode 100644 index 0000000000..59c53f87ec --- /dev/null +++ b/Framework/multiNode.json @@ -0,0 +1,125 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "MultiNodeLocal": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "rnd-many" + }, + "taskParameters": {}, + "location": "local", + "localMachines": [ + "localnode1", + "localnode2" + ], + "remoteMachine": "qcnode1", + "remotePort": "30132", + "mergingMode": "delta" + }, + "MultiNodeRemote": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "rnd-little" + }, + "taskParameters": {}, + "location": "remote" + } + }, + "checks": { + "MultiNodeLocalCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "MultiNodeLocal", + "MOs": ["example"] + }] + }, + "MultiNodeRemoteCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "MultiNodeRemote", + "MOs": ["example"] + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "rnd-many", + "active": "true", + "machines": [ + "localnode1", + "localnode2" + ], + "query": "data:TST/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.5", + "seed": "1234" + } + ], + "blocking": "false" + }, + { + "id": "rnd-little", + "active": "true", + "machines": [ + "localnode2" + ], + "port": "30333", + "query": "data:TST/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.05", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Framework/multinode-test.json.in b/Framework/multinode-test.json.in new file mode 100644 index 0000000000..2e71d4d04f --- /dev/null +++ b/Framework/multinode-test.json.in @@ -0,0 +1,128 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE", + "start": "8000000", + "end": "9000000" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "MultiNodeLocalTest@UNIQUE_PORT_1@": { + "active": "true", + "taskName": "MNLTest@UNIQUE_PORT_1@", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "sampling1" + }, + "taskParameters": {}, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "localhost", + "remotePort": "@UNIQUE_PORT_1@", + "mergingMode": "delta" + }, + "MultiNodeRemoteTest@UNIQUE_PORT_2@": { + "active": "true", + "taskName": "MNRTest@UNIQUE_PORT_2@", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "sampling2" + }, + "taskParameters": {}, + "location": "remote", + "remoteMachine": "localhost" + } + }, + "checks": { + "MultiNodeLocalTest": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "MultiNodeLocalTest@UNIQUE_PORT_1@", + "MOs": ["example"] + }] + }, + "MultiNodeRemoteTest": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "MultiNodeRemoteTest@UNIQUE_PORT_2@", + "MOs": ["example"] + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "sampling1", + "active": "true", + "machines": [ + "localhost" + ], + "query": "data:TST/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1.0", + "seed": "1234" + } + ], + "blocking": "false" + }, + { + "id": "sampling2", + "active": "true", + "machines": [ + "localhost" + ], + "port": "@UNIQUE_PORT_2@", + "query": "data:TST/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1.0", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Framework/postprocessing-async.json b/Framework/postprocessing-async.json new file mode 100644 index 0000000000..c7fdb9cecc --- /dev/null +++ b/Framework/postprocessing-async.json @@ -0,0 +1,103 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "", + "passName": "", + "periodName" : "", + "provenance" : "qc" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "postprocessing": { + "periodSeconds": "0.0001" + } + }, + "checks": { + "ExamplePPCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "PostProcessing", + "name": "ExampleTrend", + "MOs": ["mean_of_histogram"] + }] + } + }, + "postprocessing": { + "ExampleTrendAsync": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TST", + "producePlotsOnUpdate" : "false", + "dataSources": [ + { + "type": "repository", + "path": "qc/TST/MO/QcTask", + "names": [ "example" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_histogram", + "title": "Mean trend of the example histogram", + "varexp": "example.mean:time", + "selection": "", + "option": "*L" + }, + { + "name": "histogram_of_means", + "title": "Distribution of mean values in the example histogram", + "varexp": "example.mean", + "selection": "", + "option": "" + }, + { + "name": "correlation_mean_stddev", + "title": "Correlation between the mean and stddev of the example histogram", + "varexp": "example.mean:example.stddev", + "selection": "", + "option": "*" + }, + { + "name": "correlation_stddev_entries", + "title": "Correlation between the stddev and entries of the example histogram", + "varexp": "example.stddev:example.entries", + "selection": "", + "option": "*" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "foreachobject:qcdb:TST/MO/QcTask/example" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Framework/postprocessing.json b/Framework/postprocessing.json new file mode 100644 index 0000000000..e72cd83e18 --- /dev/null +++ b/Framework/postprocessing.json @@ -0,0 +1,291 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "postprocessing": { + "periodSeconds": "10" + } + }, + "checks": { + "ExamplePPCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "PostProcessing", + "name": "ExampleTrend", + "MOs": ["mean_of_histogram"] + }] + } + }, + "postprocessing": { + "ExamplePostprocessing": { + "active": "true", + "critical": "false", "": "if false the task is allowed to die without stopping the workflow, default: true", + "className": "o2::quality_control_modules::skeleton::SkeletonPostProcessing", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "once" + ], + "stopTrigger": [ + "once" + ] + }, + "ExampleTrend": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TST", + "dataSources": [ + { + "type": "repository", + "path": "TST/MO/QcTask", + "names": [ "example" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + }, + { + "type": "repository-quality", + "path": "TST/QO", + "names": [ "QcCheck" ], + "reductorName": "o2::quality_control_modules::common::QualityReductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_histogram", + "title": "Mean trend of the example histogram", + "graphAxisLabel": "Mean X:time", + "graphYRange": "0:10000", + "graphs" : [ + { + "name": "mean_trend", + "title": "mean trend", + "varexp": "example.mean:time", + "selection": "", + "option": "*L PLC PMC" + }, { + "name": "mean_trend_1000", + "title": "mean trend + 1000", + "varexp": "example.mean + 1000:time", + "selection": "", + "option": "* PMC", + "graphErrors": "1:200" + } + ] + }, + { + "name": "histogram_of_means", + "title": "Distribution of mean values in the example histogram", + "graphs" : [{ + "varexp": "example.mean", + "selection": "", + "option": "" + }] + }, + { + "name": "correlation_mean_stddev", + "title": "Correlation between the mean and stddev of the example histogram", + "graphs" : [{ + "varexp": "example.mean:example.stddev", + "selection": "", + "option": "*" + }] + }, + { + "name": "correlation_stddev_entries", + "title": "Correlation between the stddev and entries of the example histogram", + "graphAxisLabel": "stddev:entries", + "graphs" : [{ + "varexp": "example.stddev:example.entries", + "selection": "", + "option": "*" + }] + }, + { + "name": "example_quality", + "title": "Trend of the example histogram's quality", + "graphs" : [{ + "varexp": "QcCheck.name:time", + "selection": "", + "option": "*" + }] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:TST/MO/QcTask/example" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "ExampleTrendExtended": { + "active": "true", + "className": "o2::quality_control::postprocessing::SliceTrendingTask", + "moduleName": "QualityControl", + "detectorName": "TST", + "resumeTrend": "false", + "dataSources": [ + { + "type": "repository", + "path": "TST/MO/QcTask", + "names": [ "example" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ [ "0", "4500", "10500" ] ], + "sliceLabels": [ [ "Slice 1", "Slice 2" ] ], + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "ExtendedTrending_meanX_of_histogram", + "title": "Mean X trend of the example histogram", + "varexp": "example.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.5", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "Mean X:time" + }, + { + "name": "ExtendedTrending_meanY_of_histogram", + "title": "Mean Y trend of the example histogram", + "varexp": "example.meanY:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:0.5", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "Mean Y:time" + }, + { + "name": "ExtendedTrending_meanY_of_histogram_slices", + "title": "Mean Y trend of the example histogram", + "varexp": "example.meanY:slices", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:errMeanX", + "graphYRange": "", + "graphXRange": "-500.0:10000", + "graphAxisLabel": "Mean Y:Center of slices along x" + }, + { + "name": "ExtendedTrending_meanY_of_histogram_timeMultigraph", + "title": "Mean Y trend of the example histogram", + "varexp": "example.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphErrors": "errMeanY:0.5", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "Mean Y:time", + "legendTextSize": "10.0" + }, + { + "name": "ExtendedTrending_NEntries_of_histogram", + "title": "Mean Y trend of the example histogram", + "varexp": "example.entries:slices", + "selection": "", + "option": "*L", + "graphErrors": "0:errMeanX", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "Number of Entries:Center of slices along x" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:TST/MO/QcTask/example" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "ExampleQualityTask": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QualityControl", + "detectorName": "TST", + "qualityGroups": [ + { + "name" : "global", + "title" : "GLOBAL TST QUALITY", + "path": "TST/QO", + "ignoreQualitiesDetails" : ["Null", "Good", "Medium", "Bad"], + "inputObjects": [ + { + "name" : "QcCheck", + "title" : "Aggregated TST Quality", + "messageBad" : "Inform XYZ on-call immediately", + "messageMedium": "Add bookkeeping entry", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty!!!" + } + ] + }, + { + "name" : "details", + "title" : "TST DETAILS", + "path": "TST/QO", + "ignoreQualitiesDetails" : [], + "inputObjects": [ + { + "name" : "QcCheck", + "title" : "" + }, + { + "name" : "someNumbersCheck", + "title" : "" + }, + { + "name" : "XYZCheck", + "title" : "" + } + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:TST/QO/QcCheck" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Framework/proto/events.proto b/Framework/proto/events.proto new file mode 100644 index 0000000000..38270e8bf1 --- /dev/null +++ b/Framework/proto/events.proto @@ -0,0 +1,142 @@ +/* + * === This file is part of ALICE O² === + * + * Copyright 2024 CERN and copyright holders of ALICE O². + * Author: Teo Mrnjavac + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * In applying this license CERN does not waive the privileges and + * immunities granted to it by virtue of its status as an + * Intergovernmental Organization or submit itself to any jurisdiction. + */ + +syntax = "proto3"; + +package events; +option java_package = "ch.cern.alice.o2.control.events"; +option go_package = "github.com/AliceO2Group/Control/common/protos;pb"; + +//////////////// Common event messages /////////////// + +enum OpStatus { + NULL = 0; + STARTED = 1; + ONGOING = 2; + DONE_OK = 3; + DONE_ERROR = 4; + DONE_TIMEOUT = 5; +} + +message Ev_MetaEvent_MesosHeartbeat { +} + +message Ev_MetaEvent_CoreStart { + string frameworkId = 1; +} + +message Ev_MetaEvent_FrameworkEvent { + string frameworkId = 1; + string message = 2; +} + +message Ev_EnvironmentEvent { + string environmentId = 1; + string state = 2; + uint32 runNumber = 3; // only when the environment is in the running state + string error = 4; + string message = 5; // any additional message concerning the current state or transition + string transition = 6; + string transitionStep = 7; + OpStatus transitionStatus = 8; + map vars = 9; // consolidated environment variables at the root role of the environment +} + +message Traits { + string trigger = 1; + string await = 2; + string timeout = 3; + bool critical = 4; +} + +message Ev_TaskEvent { + string name = 1; // task name, based on the name of the task class + string taskid = 2; // task id, unique + string state = 3; // state machine state for this task + string status = 4; // active/inactive etc. + string hostname = 5; + string className = 6; // name of the task class from which this task was spawned + Traits traits = 7; + string environmentId = 8; + string path = 9; // path to the parent taskRole of this task within the environment +} + +message Ev_CallEvent { + string func = 1; // name of the function being called, within the workflow template context + OpStatus callStatus = 2; // progress or success/failure state of the call + string return = 3; // return value of the function + Traits traits = 4; + string output = 5; // any additional output of the function + string error = 6; // error value, if returned + string environmentId = 7; + string path = 8; // path to the parent callRole of this call within the environment +} + +message Ev_RoleEvent { + string name = 1; // role name + string status = 2; // active/inactive etc., derived from the state of child tasks, calls or other roles + string state = 3; // state machine state for this role + string rolePath = 4; // path to this role within the environment + string environmentId = 5; +} + +message Ev_IntegratedServiceEvent { + string name = 1; // name of the context, usually the path of the callRole that calls a given integrated service function e.g. readout-dataflow.dd-scheduler.terminate + string error = 2; // error message, if any + string operationName = 3; // name of the operation, usually the name of the integrated service function being called e.g. ddsched.PartitionTerminate()" + OpStatus operationStatus = 4; // progress or success/failure state of the operation + string operationStep = 5; // if the operation has substeps, this is the name of the current substep, like an API call or polling phase + OpStatus operationStepStatus = 6; // progress or success/failure state of the current substep + string environmentId = 7; + string payload = 8; // any additional payload, depending on the integrated service; there is no schema, it can even be the raw return structure of a remote API call +} + +message Ev_RunEvent { + string environmentId = 1; + uint32 runNumber = 2; + string state = 3; + string error = 4; + string transition = 5; + OpStatus transitionStatus = 6; + map vars = 7; +} + +message Event { + int64 timestamp = 1; + reserved 2 to 10; + reserved 17 to 100; + + oneof Payload { + Ev_EnvironmentEvent environmentEvent = 11; + Ev_TaskEvent taskEvent = 12; + Ev_RoleEvent roleEvent = 13; + Ev_CallEvent callEvent = 14; + Ev_IntegratedServiceEvent integratedServiceEvent = 15; + Ev_RunEvent runEvent = 16; + + Ev_MetaEvent_FrameworkEvent frameworkEvent = 101; + Ev_MetaEvent_MesosHeartbeat mesosHeartbeatEvent = 102; + Ev_MetaEvent_CoreStart coreStartEvent = 103; + } +} diff --git a/Framework/readout-no-sampling.json b/Framework/readout-no-sampling.json index 91bdce967b..82612f9a80 100644 --- a/Framework/readout-no-sampling.json +++ b/Framework/readout-no-sampling.json @@ -10,7 +10,16 @@ }, "Activity": { "number": "42", - "type": "2" + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" } }, "tasks": { @@ -18,14 +27,11 @@ "active": "true", "className": "o2::quality_control_modules::daq::DaqTask", "moduleName": "QcDaq", + "detectorName": "TST", "cycleDurationSeconds": "10", - "maxNumberCycles": "-1", "dataSource": { "type": "direct", - "binding": "readout", - "dataOrigin": "ITS", - "dataDescription": "RAWDATA", - "subSpec": "0" + "query" : "readout:ROUT/RAWDATA" }, "location": "remote" } @@ -33,4 +39,4 @@ }, "dataSamplingPolicies": [ ] -} \ No newline at end of file +} diff --git a/Framework/readout.json b/Framework/readout.json index 59bec23672..c3f68b70f5 100644 --- a/Framework/readout.json +++ b/Framework/readout.json @@ -10,10 +10,16 @@ }, "Activity": { "number": "42", - "type": "2" + "type": "NONE" }, "monitoring": { - "url": "infologger:///debug?qc" + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" } }, "tasks": { @@ -21,13 +27,21 @@ "active": "true", "className": "o2::quality_control_modules::daq::DaqTask", "moduleName": "QcDaq", + "detectorName": "DAQ", "cycleDurationSeconds": "10", - "maxNumberCycles": "-1", "dataSource": { "type": "dataSamplingPolicy", "name": "readout" }, - "location": "remote" + "location": "remote", + "taskParameters": { + "": "All the printing options might significantly slow down your QC", + "printInputHeader": "false", "": "set to true to print all headers", + "printInputPayload": "false", "": "hex or bin (anything else means no)", + "printInputPayloadLimit": "-1", "": "only print the X first words (-1 means no limit)", + "printPageInfo": "false", "": "set to true to print information about pages", + "printRDH": "false", "": "set to true to print the RDHs" + } } } }, @@ -36,18 +50,11 @@ "id": "readout", "active": "true", "machines": [], - "dataHeaders": [ - { - "binding": "readout", - "dataOrigin": "ITS", - "dataDescription": "RAWDATA" - } - ], - "subSpec": "0", + "query" : "readout:ROUT/RAWDATA", "samplingConditions": [ { "condition": "random", - "fraction": "0.1", + "fraction": "1", "seed": "1441" } ], diff --git a/Framework/readoutForDataDump.json b/Framework/readoutForDataDump.json deleted file mode 100644 index 6fe00e15e2..0000000000 --- a/Framework/readoutForDataDump.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "dataSamplingPolicies": [ - { - "id": "readout", - "active": "true", - "machines": [], - "dataHeaders": [ - { - "binding": "readout", - "dataOrigin": "R/O", - "dataDescription": "RAWDATA" - } - ], - "subSpec": "0", - "samplingConditions": [], - "blocking": "false", - "fairMQOutput": "name=fairReadoutRawOut,type=pub,method=bind,address=tcp://127.0.0.1:26525,rateLogging=1" - } - ] -} diff --git a/Framework/script/.gitignore b/Framework/script/.gitignore new file mode 100644 index 0000000000..0dee62f059 --- /dev/null +++ b/Framework/script/.gitignore @@ -0,0 +1,2 @@ +.pydevproject +__pycache__ \ No newline at end of file diff --git a/Framework/script/RepoCleaner/README.md b/Framework/script/RepoCleaner/README.md new file mode 100644 index 0000000000..2fc6c5fbf4 --- /dev/null +++ b/Framework/script/RepoCleaner/README.md @@ -0,0 +1,95 @@ +Here are the tools to clean up the CCDB of the QC. + +## Entry point +It is `o2-qc-repo-cleaner`. See the long comment at the beginning. + +## Usage +``` +usage: o2-qc-repo-cleaner [-h] [--config CONFIG] [--config-git] [--config-consul CONFIG_CONSUL] [--log-level LOG_LEVEL] + [--dry-run] [--only-path ONLY_PATH] [--workers WORKERS] +``` + +## Configuration +The file `config.yaml` contains the CCDB URL and the rules to be followed to clean up the database. An example is provided along this README (`config.yaml`). +A typical rule in the config file looks like: +``` + - object_path: qc/ITS/.* + delay: 240 + policy: 1_per_hour +``` +There can be any number of these rules. The order is important as we use the first matching rule for each element in the QCDB (caveat the use of the flag `continue_with_next_rule`, see below). +- `object_path`: a pattern to be matched to know if the rule applies +- `delay`: the duration in minutes of the grace period during which an object is not removed, even if it matches the above path. +- `policy`: the name of a policy to apply on the matching objects. Here are the currently available policies (full description in the corresponding files): + - `1_per_hour`: keep the first and extend its validity to 1 hour, remove everything in the next hour, repeat. + - `1_per_run`: requires the "Run" or "RunNumber" metadata to be set. Keep only the most recent version of an object for a given run. + - `last_only`: keep only the last version, remove everything else. + - `none_kept`: keep none, remove everything + - `skip`: keep everything +- `from_timestamp`: the rule only applies to versions whose `valid_from` is older than this timestamp +- `to_timestamp`: the rule only applies to versions whose `valid_from` is younger than this timestamp +- `continue_with_next_rule`: if `True`, the next matching rule is also applied. +- `xyz`: any extra argument necessary for a given policy. This is the case of the argument `delete_when_no_run` required by the policy `1_per_run`. + +The configuration for ccdb-test is described [here](../../../doc/DevelopersTips.md). + +## Setup virtual environment for development and test (venv) + +1. `cd Framework/script/RepoCleaner` +2. `python3 -m venv env` +3. `source env/bin/activate` +4. `python -m pip install -r requirements.txt` +5. `python3 -m pip install . ` +6. You can execute and work. Next time just do "activate" and then you are good to go +7. If you modify the code, then rerun `python3 -m pip install .` + +## Unit Tests + +``` +cd Framework/script/RepoCleaner +source env/bin/activate + +# Run a test: +python -m unittest tests.test_Ccdb.TestCcdb.test_getObjectsList + +# Run all tests: +python3 -m unittest discover +``` + +In particular there is a test for the `production` rule that is pretty extensive. It hits the ccdb though and it needs the following path to be truncated: +` +qc/TST/MO/repo/test* +` + +## Other tests +Most of the classes and Rules have a main to help test them. To run do e.g. `python3 1_per_run.py`. + +## Installation +CMake will install the python scripts in bin and the config file in etc. + +## Example + +``` +PYTHONPATH=./rules:$PYTHONPATH ./o2-qc-repo-cleaner --dry-run --config config-test.yaml --dry-run --only-path qc/DAQ --log-level 10 +``` + +## Development + +To install locally +``` +cd Framework/script/RepoCleaner +python3 -m pip install . +``` + +## Upload new version + +Prerequisite + +1. Create an account on https://pypi.org + +Create new version + +1. Update version number in `setup.py` +2. `python3 setup.py sdist bdist_wheel` +3. `python3 -m twine upload --repository pypi dist/*` + diff --git a/Framework/script/RepoCleaner/ReleaseNotes.md b/Framework/script/RepoCleaner/ReleaseNotes.md new file mode 100644 index 0000000000..029478fe87 --- /dev/null +++ b/Framework/script/RepoCleaner/ReleaseNotes.md @@ -0,0 +1,56 @@ +# Release notes + +New +- + +1.10 +- Revive and clean up repocleaner tests +- Add option to ignore the last execution of the repocleaner +- repocleaner: none_kept: use creation time + +1.9 +- [QC-1229] - Repocleaner policy for the moving windows + +1.8 +- 3 small fixes (https://github.com/AliceO2Group/QualityControl/pull/2308) +- [QC-1097] - use metadata to filter what to delete with o2-qc-repo-delete-time-interval +- [QC-1226] - Make config file name in consul configurable + +1.7 +- [QC-1144] 1_per_run : Consider RunNumber=0 as no run number (#2238) +- [QC-1142 ]fix object preservation for 1_per_run policy (#2228) +- [QC-996] policy multiple_per_run can delete first and last (#1921) + +1.6 +- add option to set adjustableEOV when updating validity +- [QC-986] Do not touch the validity any more in rules 1_per_hour and production +- Add the possibility to Limit the script o2-qc-repo-delete-not-in-runs to a time period + +1.5 +- Add the binary `o2-qc-repo-delete-not-in-runs` to the install list. +- Remove excessive logging in `Ccdb.py` + +1.4 +- Add new tool to delete objects not belonging to a list of runs. + +1.3 +- Add new tool `o2-qc-repo-find-objects-not-updated` to find all the objects under a path that did not get a new + version in the past X days. + +1.2 + +- Add option `--only-path-no-subdir` to `o2-qc-repo-cleaner` to allow setting `--only-path` to an object rather than a + folder, or to ignore subfolders. +- Add option `--only-path-no-subdir` to `o2-qc-repo-delete-interval` to allow processing an object rather than a folder or + to ignore subfolders. + +1.1 + +- Add a new policy `multiple_per_run` that is simpler than `production`. +- Several rules can now be applied to the same path if the flag `continue_with_next_rule` is set to true on the first one(s). + It means that any rule that has `continue_with_next_rule` set to true will be applied as well as the next matching rule + and so on and so forth. + +1.0 + +- First version released with pip. \ No newline at end of file diff --git a/Framework/script/RepoCleaner/__init__.py b/Framework/script/RepoCleaner/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Framework/script/RepoCleaner/qcrepocleaner/Ccdb.py b/Framework/script/RepoCleaner/qcrepocleaner/Ccdb.py new file mode 100644 index 0000000000..d2822491af --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/Ccdb.py @@ -0,0 +1,250 @@ +import datetime +import logging +import traceback +from json import JSONDecodeError +from typing import List, Dict + +import dryable +import requests + +logger = logging # default logger + + +class ObjectVersion: + """ + A version of an object in the CCDB. + + In the CCDB an object can have many versions with different validity intervals. + This class represents a single version. + """ + print_details = False + + def __init__(self, path: str, valid_from, valid_to, created_at, uuid=None, metadata=None): + """ + Construct an ObjectVersion. + :param path: path to the object + :param uuid: unique id of the object + :param valid_from: validity range smaller limit (in ms) + :param valid_to: validity range bigger limit (in ms) + :param created_at: creation timestamp of the object + :param metadata: metadata of the object + """ + + self.path = path + self.uuid = uuid + self.valid_from = valid_from + # precomputed Datetime ("Dt") of the timestamp `validFrom` + self.valid_from_as_dt = datetime.datetime.fromtimestamp(int(valid_from) / 1000) # /1000 because we get ms + self.valid_to = valid_to + self.metadata = metadata + self.created_at = created_at + # precomputed Datetime ("Dt") of the timestamp `createdAt` + self.created_at_as_dt = datetime.datetime.fromtimestamp(int(created_at) / 1000) # /1000 because we get ms + + def __repr__(self): + if "Run" in self.metadata or "RunNumber" in self.metadata: + run_number = self.metadata["Run"] if "Run" in self.metadata else self.metadata["RunNumber"] + else: + run_number = "None" + + rperesentation = f"Version of object {self.path} created at {self.created_at_as_dt.strftime('%Y-%m-%d %H:%M:%S')}, valid from {self.valid_from_as_dt.strftime('%Y-%m-%d %H:%M:%S')}, run {run_number}, (uuid {self.uuid})" + if ObjectVersion.print_details: + representation += f", metadata: {self.metadata}" + return representation + + +class Ccdb: + """ + Class to interact with the CCDB. + """ + + counter_deleted: int = 0 + counter_validity_updated: int = 0 + counter_preserved: int = 0 + set_adjustable_eov: bool = False # if True, set the metadata adjustableEOV before change validity + + def __init__(self, url, print_details=False): + logger.info(f"Instantiate CCDB at {url}") + self.url = url + ObjectVersion.print_details = print_details + + def get_objects_list(self, added_since: int = 0, path: str = "", no_wildcard: bool = False) -> List[str]: + """ + Get the full list of objects in the CCDB that have been created since added_since. + + :param no_wildcard: if true, the path for which we get the list is not modified to add `/.*`. + Set it to true if you need to get the versions of an object and not a folder. + :param path: the path + :param added_since: if specified, only return objects added since this timestamp in epoch milliseconds. + :return A list of strings, each containing a path to an object in the CCDB. + """ + url_for_all_obj = self.url + '/latest/' + path + url_for_all_obj += '/' if path else '' + url_for_all_obj += '' if no_wildcard else '.*' + logger.debug(f"Ccdb::getObjectsList -> {url_for_all_obj}") + headers = {'Accept': 'application/json', 'If-Not-Before':str(added_since)} + r = requests.get(url_for_all_obj, headers=headers) + r.raise_for_status() + try: + json = r.json() + except JSONDecodeError as err: + logger.error(f"JSON decode error: {err}") + raise + paths = [] + for item in json['objects']: + paths.append(item['path']) + + return paths + + def get_full_objects_details(self, path: str = "") -> List[Dict]: + """ + Return the full json of all the objects found in the path. + :param path: + :return: + """ + url_for_all_obj = self.url + '/latest/' + path + '.*' + logger.debug(f"Ccdb::getFullObjectsDetails -> {url_for_all_obj}") + headers = {'Accept': 'application/json'} + r = requests.get(url_for_all_obj, headers=headers) + r.raise_for_status() + try: + json = r.json() + except JSONDecodeError as err: + logger.error(f"JSON decode error: {err}") + raise + return json['objects'] + + def get_versions_list(self, object_path: str, from_ts: str = "", to_ts: str = "", run: int = -1, metadata: str = "") \ + -> List[ObjectVersion]: + """ + Get the list of all versions for a given object sorted by CreatedAt. + :param metadata: only objects matching these metadata. Format: "[/key=value]*" + :param run: only objects for this run (based on metadata) + :param object_path: Path to the object for which we want the list of versions. + :param from_ts: only objects created at or after this timestamp + :param to_ts: only objects created before or at this timestamp + :return A list of ObjectVersion. + """ + url_browse_all_versions = self.url + '/browse/' + object_path + headers = {'Accept': 'application/json', 'Connection': 'close'} + if from_ts != "": + headers["If-Not-Before"] = from_ts + if to_ts != "": + headers["If-Not-After"] = to_ts + if run != -1: + url_browse_all_versions += '/RunNumber=' + str(run) + if metadata != "": + url_browse_all_versions += metadata + logger.debug(f"Ccdb::getVersionsList -> {url_browse_all_versions}") + logger.debug(f"{headers}") + r = requests.get(url_browse_all_versions, headers=headers) + r.raise_for_status() + try: + json_result = r.json(strict=False) # to survive bad characters in the strings of the json + except ValueError as e: + print(f"Error while reading json for object {object_path} from CCDB: {e}") + exit(1) + versions = [] + for object_path in json_result['objects']: + version = ObjectVersion(path=object_path['path'], uuid=object_path['id'], + valid_from=object_path['validFrom'], valid_to=object_path['validUntil'], + metadata=object_path, created_at=object_path['Created']) + versions.insert(0, version) + versions.sort(key=lambda v: v.created_at, reverse=False) + return versions + + @dryable.Dryable() + def delete_version(self, version: ObjectVersion): + """ + Delete the specified version of an object. + :param version: The version of the object to delete, as an instance of ObjectVersion. + """ + url_delete = self.url + '/' + version.path + '/' + str(version.valid_from) + '/' + version.uuid + logger.debug(f"Delete version at url {url_delete}") + headers = {'Connection': 'close'} + try: + r = requests.delete(url_delete, headers=headers) + r.raise_for_status() + self.counter_deleted += 1 + except requests.exceptions.RequestException: + logging.error(f"Exception in deleteVersion: {traceback.format_exc()}") + + @dryable.Dryable() + def move_version(self, version: ObjectVersion, to_path: str): + """ + Move the version to a different path. + :param version: The version of the object to move, as an instance of ObjectVersion. + :param to_path: The destination path + """ + url_move = self.url + '/' + version.path + '/' + str(version.valid_from) + '/' + version.uuid + logger.debug(f"Move version at url {url_move} to {to_path}") + headers = {'Connection': 'close', 'Destination': to_path} + try: + r = requests.request("MOVE", url_move, headers=headers) + r.raise_for_status() + self.counter_deleted += 1 + except requests.exceptions.RequestException: + logging.error(f"Exception in moveVersion: {traceback.format_exc()}") + + @dryable.Dryable() + def update_validity(self, version: ObjectVersion, valid_from: int, valid_to: int, metadata=None): + """ + Update the validity range of the specified version of an object. + :param version: The ObjectVersion to update. + :param valid_from: The new "from" validity. + :param valid_to: The new "to" validity. + :param metadata: Add or modify metadata + """ + full_path = self.url + '/' + version.path + '/' + str(valid_from) + '/' + str(valid_to) + '/' + str(version.uuid) + '?' + logger.debug(f"Update end limit validity of {version.path} ({version.uuid}) from {version.valid_to} to {valid_to}") + if metadata is not None: + logger.debug(f"{metadata}") + for key in metadata: + full_path += key + "=" + metadata[key] + "&" + if self.set_adjustable_eov: + logger.debug(f"As the parameter force is set, we add metadata adjustableEOV") + full_path += "adjustableEOV=1&" + try: + headers = {'Connection': 'close'} + r = requests.put(full_path, headers=headers) + r.raise_for_status() + self.counter_validity_updated += 1 + except requests.exceptions.RequestException: + logging.error(f"Exception in updateValidity: {traceback.format_exc()}") + + @dryable.Dryable() + def update_metadata(self, version: ObjectVersion, metadata): + logger.debug(f"update metadata : {metadata}") + full_path = self.url + '/' + version.path + '/' + str(version.valid_from) + '/' + str(version.uuid) + '?' + if metadata is not None: + for key in metadata: + full_path += key + "=" + metadata[key] + "&" + if self.set_adjustable_eov: + logger.debug(f"As the parameter force is set, we add metadata adjustableEOV") + full_path += "adjustableEOV=1&" + try: + headers = {'Connection': 'close'} + r = requests.put(full_path, headers=headers) + r.raise_for_status() + except requests.exceptions.RequestException: + logging.error(f"Exception in updateMetadata: {traceback.format_exc()}") + + @dryable.Dryable() + def put_version(self, version: ObjectVersion, data): + """ + :param version: An ObjectVersion that describes the data to be uploaded. + :param data: the actual data to send. E.g.:{'somekey': 'somevalue'} + :return A list of ObjectVersion. + """ + full_path= self.url + "/" + version.path + "/" + str(version.valid_from) + "/" + str(version.valid_to) + "/" + if version.metadata is not None: + for key in version.metadata: + full_path += key + "=" + version.metadata[key] + "/" + logger.debug(f"fullpath: {full_path}") + headers = {'Connection': 'close'} + r = requests.post(full_path, files=data, headers=headers) + if r.ok: + logger.debug(f"Version pushed to {version.path}") + else: + logger.error(f"Could not post a new version of {version.path}: {r.text}") + diff --git a/Framework/script/RepoCleaner/qcrepocleaner/__init__.py b/Framework/script/RepoCleaner/qcrepocleaner/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Framework/script/RepoCleaner/qcrepocleaner/binUtils.py b/Framework/script/RepoCleaner/qcrepocleaner/binUtils.py new file mode 100644 index 0000000000..c51566f1e5 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/binUtils.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +import logging +import sys + + +def prepare_main_logger(): + logger = logging.getLogger() + # Logging (split between stderr and stdout) + formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S') + h1 = logging.StreamHandler(sys.stdout) + h1.setLevel(logging.DEBUG) + h1.addFilter(lambda record: record.levelno <= logging.INFO) # filter out everything that is above INFO level + h1.setFormatter(formatter) + logger.addHandler(h1) + h2 = logging.StreamHandler(sys.stderr) + h2.setLevel(logging.WARNING) # take only warnings and error logs + h2.setFormatter(formatter) + logger.addHandler(h2) \ No newline at end of file diff --git a/Framework/script/RepoCleaner/qcrepocleaner/config.yaml b/Framework/script/RepoCleaner/qcrepocleaner/config.yaml new file mode 100644 index 0000000000..9540efa651 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/config.yaml @@ -0,0 +1,81 @@ +Rules: + - object_path: qc/TST/MO/repo/test + delay: 0 + policy: 1_per_hour + from_timestamp: 1674700609718 + continue_with_next_rule: True + - object_path: qc/TST/MO/repo/test + delay: 0 + policy: 1_per_run + to_timestamp: 1674700609718 + - object_path: qc/.*/mw/.* + delay: 1 + policy: multiple_per_run + mw_deletion_delay: 15 + - object_path: qc/TST/MO/QcTask + delay: 1440 + policy: multiple_per_run + mw_deletion_delay: 15 +# - object_path: qc/TST/MO/QcTask-barth/example3[/.*]{0,1} +# delay: 0 +# policy: none_kept +# from_timestamp: 1636091494278 +# continue_with_next_rule: True +# - object_path: qc/TST/MO/QcTask-barth/example3[/.*]{0,1} +# delay: 0 +# policy: none_kept +# from_timestamp: 1636054889780 +# to_timestamp: 1636095558142 +# - object_path: qc/ITS/.* +# delay: 240 +# policy: 1_per_run +# delete_when_no_run: True +# - object_path: qc/TST_KEEP/.* +# delay: 240 +# policy: 1_per_run +# delete_when_no_run: True +# - object_path: qc/.* # Path in the CCDB to a certain object +# delay: 1440 # Delay in minutes during which a new object is not touched. (1 day) +# policy: 1_per_hour # name of the policy to apply, must correspond to a python script. +# - object_path: QcCheck/.* +# delay: 60 +# policy: 1_per_hour +# - object_path: Test +# delay: 240 +# policy: none_kept +# - object_path: .* +# delay: 1440 +# policy: skip +# - object_path: no_cleanup/.* +# delay: 60 +# policy: skip +# - object_path: TRD_test/.* +# delay: 60 +# policy: skip +# - object_path: ZDC_test/.* +# delay: 60 +# policy: skip +# - object_path: ITSQcTask.* +# delay: 60 +# policy: 1_per_hour +# - object_path: Test/.* +# delay: 60 +# policy: 1_per_hour +# - object_path: ITSRAWDS.* +# delay: 60 +# policy: 1_per_hour +# - object_path: qc/MISC/QcTest1/.* +# delay: 60 +# policy: 1_per_hour +# - object_path: ITSQCTrhesholdTask/.* +# delay: 60 +# policy: 1_per_hour +# - object_path: .* # Path in the CCDB to a certain object +# delay: 1440 # Delay in minutes during which a new object is not touched. (1 day) +# policy: 1_per_hour # name of the policy to apply, must correspond to a python script. +# - object_path: QcTask/example +# delay: 120 +# policy: 1_per_hour + +Ccdb: + Url: http://ccdb-test.cern.ch:8080 diff --git a/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-cleaner b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-cleaner new file mode 100755 index 0000000000..c901c22e9e --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-cleaner @@ -0,0 +1,430 @@ +#!/usr/bin/env python3 + +# This script drives the cleanup process of the CCDB backend of the QC. +# +# It should ideally be run as a cron on a machine. It uses plugins to implement +# the actual actions defined in the config file config.yaml. Each item in the +# config file describes which plugin (by name of the file) should be used for +# a certain path in the CCDB. +# +# If several rules apply to an object, we pick the first one. Thus, mind carefully +# the order of the rules ! +# +# The plugins should have a function "process()" that takes 3 arguments : +# ccdb: Ccdb, object_path: str and delay: int +# +# We depend on requests, yaml, dryable, responses (to mock and test with requests) +# +# Usage +# # run with debug logs and don't actually touch the database +# PYTHONPATH=./rules:$PYTHONPATH./o2-qc-repo-cleaner --dry-run --log-level 10 + +import argparse +import importlib +import logging +import multiprocessing as mp +import re +import socket +import sys +import tempfile +import time +import traceback +from datetime import datetime +from pathlib import Path +from typing import List + +import consul +import dryable +import requests +import yaml + +from qcrepocleaner.Ccdb import Ccdb +from qcrepocleaner.pidfile import PIDFile, AlreadyRunningError + + +class Rule: + """A class to hold information about a "rule" defined in the config file.""" + + def __init__(self, object_path=None, delay=None, policy=None, from_timestamp=None, to_timestamp=None, + continue_with_next_rule=None, all_params=None): + """ + Constructor. + :param object_path: path to the object, or pattern, to which a rule will apply. + :param delay: the grace period during which a new object is never deleted. + :param policy: which policy to apply in order to clean up. It should correspond to a plugin. + :param all_params: a map with all the parameters from the config file for this rule. We will keep only the + extra ones. + """ + self.object_path = object_path + self.delay = delay + self.policy = policy + self.from_timestamp = from_timestamp + self.to_timestamp = to_timestamp + self.continue_with_next_rule = continue_with_next_rule + + self.extra_params = all_params + if all_params is not None: + self.extra_params.pop("object_path") + self.extra_params.pop("delay") + self.extra_params.pop("policy") + self.extra_params.pop("from_timestamp", 0) + self.extra_params.pop("to_timestamp", 0) + self.extra_params.pop("continue_with_next_rule", "False") + + def __repr__(self): + return 'Rule(object_path={.object_path}, delay={.delay}, policy={.policy}, from_timestamp={.from_timestamp}, ' \ + 'to_timestamp={.to_timestamp}, continue_with_next_rule={.continue_with_next_rule}, ' \ + 'extra_params={.extra_params})'\ + .format(self, self, self, self, self, self, self) + + +def parse_args(): + """Parse the arguments passed to the script.""" + logging.info("Parsing arguments") + parser = argparse.ArgumentParser(description='Clean the QC database.') + parser.add_argument('--config', dest='config', action='store', default="config.yaml", + help='Path to the config file') + parser.add_argument('--config-git', action='store_true', + help='Check out the config file from git (branch repo_cleaner), ignore --config.') + parser.add_argument('--config-consul', action='store', + help='Specify the consul url (without `http[s]://`), port and file in the form of ::,' + ' file must be stored in o2/components/qc/ANY/any/,' + ' if specified ignore both --config and --config-git.') + parser.add_argument('--log-level', dest='log_level', action='store', default="20", + help='Log level (CRITICAL->50, ERROR->40, WARNING->30, INFO->20,DEBUG->10)') + parser.add_argument('--dry-run', action='store_true', + help='Dry run, no actual deletion nor modification to the CCDB.') + parser.add_argument('--set-adjustableEOV', action='store_true', + help='When updating the validity, set adjustableEOV to make sure that we can update it.') + parser.add_argument('--only-path', dest='only_path', action='store', default="", + help='Only work on given path (omit the initial slash).') + parser.add_argument('--workers', dest='workers', action='store', default="1", + help='Number of parallel workers.') + parser.add_argument('--only-path-no-subdir', action='store_true', default=False, help='Set to true if the ' + 'only-path points to an object rather than a folder or if subdirectories must be ignored.') + parser.add_argument('--ignore-last-execution', dest='ignore_last_execution', action='store_true', default=False, + help='Do not check when was the last execution, run from timestamp 0.') + parser.add_argument('--print-versions-details', dest='print_versions_details', action='store_true', default=False, + help='Print extra details about the versions if enabled..') + args = parser.parse_args() + dryable.set(args.dry_run) + logging.info(args) + return args + + +def parse_config(config_file_path): + """ + Read the config file and prepare a list of rules. + + Return a dictionary containing the list of rules and other config elements from the file. + + :param config_file_path: Path to the config file + :raises yaml.YAMLError If the config file does not contain a valid yaml. + """ + + logging.info(f"Parsing config file {config_file_path}") + with open(config_file_path, 'r') as stream: + config_content = yaml.safe_load(stream) + + # also add something to the important logs file + message = datetime.today().strftime('%Y-%m-%d - %H:%M:%S') + store_crucial_log("\n" + message + " - Start of the cleaner") + + rules = [] + logging.debug("Rules found in the config file:") + + for rule_yaml in config_content["Rules"]: + logging.debug(f"rule_yaml: {rule_yaml}") + if "from_timestamp" in rule_yaml: + from_timestamp = rule_yaml["from_timestamp"] + else: + from_timestamp = 1 + if "to_timestamp" in rule_yaml: + to_timestamp = rule_yaml["to_timestamp"] + else: + to_timestamp = 2785655701000 # 2058 + + continue_with_next_rule = rule_yaml.get("continue_with_next_rule", False) + + rule = Rule(rule_yaml["object_path"], rule_yaml["delay"], rule_yaml["policy"], + from_timestamp, to_timestamp, continue_with_next_rule=continue_with_next_rule, + all_params=rule_yaml) + rules.append(rule) + logging.debug(f" * {rule}") + store_crucial_log(f" * {rule}") + + ccdb_url = config_content["Ccdb"]["Url"] + + return {'rules': rules, 'ccdb_url': ccdb_url} + + +def download_config_from_git(): + """ + Download a config file from git. + :return: the path to the config file + """ + + logging.debug("Get it from git") + r = requests.get( + 'https://raw.github.com/AliceO2Group/QualityControl/repo_cleaner/Framework/script/RepoCleaner/config.yaml') + logging.debug(f"config file from git : \n{r.text}") + path = "/tmp/config.yaml" + with open(path, 'w') as f: + f.write(r.text) + logging.info(f"Config path : {path}") + return path + + +def download_config_from_consul(consul_url: str, consul_port: str, file_name: str): + """ + Download a config file from consul. + :return: the path to the config file + """ + + logging.debug(f"Download config file from consul : {consul_url} {consul_port} {file_name}") + consul_server = consul.Consul(host=consul_url, port=consul_port) + file_path = 'o2/components/qc/ANY/any/' + file_name + index, data = consul_server.kv.get(key=file_path) + logging.debug(f"config file from consul : \n{data['Value']}") + text = data["Value"].decode() + logging.debug(f"config file from consul : \n{text}") + path = "/tmp/" + file_name + with open(path, 'w') as f: + f.write(text) + logging.info(f"Config path : {path}") + return path + + +def find_matching_rules(rules, object_path): + """Return a list of all matching rules for the given path.""" + + logging.debug(f"findMatchingRules for {object_path}") + + if object_path is None: + logging.error(f"findMatchingRules: object_path is None") + return [] + + result = [] + for rule in rules: + pattern = re.compile(rule.object_path) + matched = pattern.match(object_path) + if matched is not None: + logging.debug(f" Found! {rule}") + result.append(rule) + + return result + + +filepath = tempfile.gettempdir() + "/repoCleaner.txt" +currentTimeStamp = int(time.time() * 1000) + + +def get_ts_last_execution(ignore_last_execution: bool): + """ + Returns the timestamp of the last execution. + It is stored in a file in $TMP/repoCleaner.txt. + :return: the timestamp of the last execution or 0 if it cannot find it. + """ + if ignore_last_execution: + logging.info(f"Option ignore_last_execution set, we return 0 as timestamp.") + return 0 + + try: + f = open(filepath, "r") + except IOError: + logging.info(f"File {filepath} not readable, we return 0 as timestamp.") + return 0 + timestamp = f.read() + logging.info(f"Timestamp retrieved from {filepath}: {timestamp}") + f.close() + return timestamp + + +def store_saved_timestamp(): + """ + Store the timestamp we saved at the beginning of the execution of this script. + """ + try: + f = open(filepath, "w+") + except IOError: + logging.error(f"Could not write the saved timestamp to {filepath}") + return + f.write(str(currentTimeStamp)) + logging.info(f"Stored timestamp {currentTimeStamp} in {filepath}") + f.close() + + +def store_monitoring_metrics(success, duration): + """ + Store the status and the duration in influxdb via telegraf for monitoring purpose. + """ + socket_file="/tmp/telegraf.sock" + if Path(socket_file).exists(): + telegraf = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + telegraf.connect(socket_file) + telegraf.send(f"repoCleaner success={success}".encode('utf-8')) + telegraf.send(f"repoCleaner duration={duration}".encode('utf-8')) + logging.info(f"Monitoring metrics stored.") + else: + logging.warning(f"File {socket_file} does not exist, no monitoring metrics stored.") + + +def store_crucial_log(message): + """ + Store few but very important messages to the file ~/repocleaner_logs.txt + :param message: + :return: + """ + logs_filename = str(Path.home()) + "/repocleaner_logs.txt" # very limited but important logs + try: + f = open(logs_filename, "a") + f.write(message+"\n") + f.close() + except IOError as e: + logging.error(f"Could not write crucial log to {logs_filename} : {e}") + + +def prepare_main_logger(): + logger = logging.getLogger() + # Logging (split between stderr and stdout) + formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S') + h1 = logging.StreamHandler(sys.stdout) + h1.setLevel(logging.DEBUG) + h1.addFilter(lambda record: record.levelno <= logging.INFO) # filter out everything that is above INFO level + h1.setFormatter(formatter) + logger.addHandler(h1) + h2 = logging.StreamHandler(sys.stderr) + h2.setLevel(logging.WARNING) # take only warnings and error logs + h2.setFormatter(formatter) + logger.addHandler(h2) + + +def create_parallel_logger(): + # TODO merge with prepare_main_logger. It creates problems though. + logger = mp.get_logger() + logger.setLevel(logging.INFO) + formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S') + h1 = logging.StreamHandler(sys.stdout) + h1.setLevel(logging.DEBUG) + h1.addFilter(lambda record: record.levelno <= logging.INFO) # filter out everything that is above INFO level + h1.setFormatter(formatter) + h2 = logging.StreamHandler(sys.stderr) + h2.setLevel(logging.WARNING) # take only warnings and error logs + h2.setFormatter(formatter) + if not len(logger.handlers): + logger.addHandler(h2) + logger.addHandler(h1) + return logger + + +def read_config(args): + path = args.config + if args.config_consul: + items = args.config_consul.split(':') + if len(items) < 3: + logging.error(f"Incorrect format of Consul config file parameter. Exiting.") + exit(1) + path = download_config_from_consul(items[0], items[1], items[2]) + elif args.config_git: + path = download_config_from_git() + config = parse_config(path) + rules: List[Rule] = config['rules'] + ccdb_url = config['ccdb_url'] + return ccdb_url, rules + + +def process_object_wrapped(object_path, rules, ccdb, args): + """avoid getting blocked in parallel processing due to exceptions""" + try: + process_object(object_path, rules, ccdb, args) + except: + logging.error(f"Exception in process_object: {traceback.format_exc()}") + + +# @log_sparse +def process_object(object_path, rules, ccdb, args): + logger = create_parallel_logger() + logger.setLevel(int(args.log_level)) + logger.info(f"Processing {object_path}") + + # Take the first matching rule, if any + rules = find_matching_rules(rules, object_path) + logger.debug(f"Found {len(rules)} rules") + + if len(rules) == 0: + logger.info(f" no matching rule") + return + + for rule in rules: + logger.info(f"rule: {rule}") + # Apply rule on object (find the plug-in script and apply) + try: + module = importlib.import_module('qcrepocleaner.rules.' + rule.policy) + module.logger = logger + except ModuleNotFoundError: + logger.error(f"could not load module {rule.policy}") + return + try: + stats = module.process(ccdb, object_path, int(rule.delay), rule.from_timestamp, rule.to_timestamp, + rule.extra_params) + logger.info(f"{rule.policy} applied on {object_path}: {stats}") + except Exception as e: + logger.error(f"processing error: {e}") + + if not rule.continue_with_next_rule: + break + + +def run(args, ccdb_url, rules): + + # Get list of objects from CCDB + ccdb = Ccdb(ccdb_url, args.print_versions_details) + ccdb.logger = logging.getLogger + ccdb.set_adjustable_eov = args.set_adjustableEOV + logging.info(f"ccdb.set_adjustable_eov: {ccdb.set_adjustable_eov}") + paths = ccdb.get_objects_list(get_ts_last_execution(args.ignore_last_execution), args.only_path, args.only_path_no_subdir) + if args.only_path != '': + paths = [item for item in paths if item is not None and item.startswith(args.only_path)] + logging.debug(paths) + + # For each object call the first matching rule, do it in parallel + logging.info("Loop through the objects and apply first matching rule.") + logging.info(f"workers: {args.workers}") + pool = mp.Pool(processes=int(args.workers)) + [pool.apply_async(process_object_wrapped, args=(object_path, rules, ccdb, args)) for object_path in paths] + pool.close() + pool.join() + + logging.info(f" *** DONE *** (total deleted: {ccdb.counter_deleted}, total updated: {ccdb.counter_validity_updated})") + message = datetime.today().strftime('%Y-%m-%d-%H:%M:%S') + store_crucial_log(message + f" - End of the cleaner (total deleted: {ccdb.counter_deleted}, total updated: {ccdb.counter_validity_updated})") + if not args.dry_run and not args.ignore_last_execution: + store_saved_timestamp() + +# **************** +# We start here ! +# **************** + +def main(): + start_time = time.time() + prepare_main_logger() + + # Parse arguments + args = parse_args() + logging.getLogger().setLevel(int(args.log_level)) + + try: + with PIDFile(filename='o2-qc-repo-cleaner.pid'): + ccdb_url, rules = read_config(args) + run(args, ccdb_url, rules) + except AlreadyRunningError: + print('Already running. Exiting.') + except: + store_monitoring_metrics(success=0, duration=time.time() - start_time) + raise + + store_monitoring_metrics(success=1, duration=time.time() - start_time) + + +if __name__ == "__main__": # to be able to run the main code above + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-not-in-runs b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-not-in-runs new file mode 100755 index 0000000000..bcb4645507 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-not-in-runs @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +import argparse +import csv +import logging + +import dryable + +from qcrepocleaner.Ccdb import Ccdb +from qcrepocleaner.binUtils import prepare_main_logger + + +def parseArgs(): + """Parse the arguments passed to the script.""" + logging.info("Parsing arguments") + parser = argparse.ArgumentParser(description='Remove all objects in a given path, if they dont belong to any of ' + 'the runs. The runs are provided in a csv file whose format is the ' + 'one from the bookkeeping export.') + parser.add_argument('--url', dest='url', action='store', help='URL of the CCDB, with http[s]://', required=True) + parser.add_argument('--log-level', dest='log_level', action='store', default="20", + help='Log level (CRITICAL->50, ERROR->40, WARNING->30, INFO->20,DEBUG->10)') + parser.add_argument('--dry-run', action='store_true', + help='Dry run, no actual deletion nor modification to the CCDB.') + parser.add_argument('--path', dest='path', action='store', default="", + help='Clean this path (without initial slash and without .* at the end, e.g. qc/TST/MO/Bob)', + required=True) + parser.add_argument('--runs-csv-file', dest='runs_file', action='store', help='A csv file with a header and the ' + 'column `runNumber` contains the run', + required=True) + parser.add_argument('--one-by-one', action='store_true', help='Ask confirmation for each deletion') + parser.add_argument('--print-list', action='store_true', help='Only print the list of objects that would be deleted') + parser.add_argument('--from', dest='from_ts', action='store', help='From this timestamp.', default='0') + parser.add_argument('--to', dest='to_ts', action='store', help='To this timestamp.', default='1970066422947') + args = parser.parse_args() + dryable.set(args.dry_run) + logging.info(args) + return args + + +def run(args): + ccdb = Ccdb(args.url) + + file = open(args.runs_file, encoding='utf-8-sig') + csvreader = csv.DictReader(file) + list_runs = [] + for row in csvreader: + list_runs.append(row["runNumber"]) + logging.debug(f"List of runs in CSV: {list_runs}") + + versions = ccdb.get_versions_list(args.path + "/.*", args.from_ts, args.to_ts) + nb_deleted = 0 + for v in versions: + logging.debug(f"Processing {v}") + run_number = v.metadata["RunNumber"] + if run_number is not None and list_runs.count(run_number) == 0: + logging.info(f"Ready to delete {v}") + if args.one_by_one: + answer = input(" Continue? y/n\n ") + if answer.lower() in ["y", "yes"]: + ccdb.delete_version(v) + nb_deleted += 1 + elif answer.lower() in ["n", "no"]: + logging.info(" skipping") + else: + logging.error(" wrong input, skipping") + else: + if not args.print_list: + ccdb.delete_version(v) + nb_deleted += 1 + + logging.info(f"Deleted items: {nb_deleted}") + + +# **************** +# We start here ! +# **************** + +def main(): + + prepare_main_logger() + + # Parse arguments + args = parseArgs() + logging.getLogger().setLevel(int(args.log_level)) + + run(args) + + +if __name__ == "__main__": + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-objects b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-objects new file mode 100755 index 0000000000..6d69cc2754 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-objects @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 + +import argparse +import logging + +import dryable + +from qcrepocleaner.Ccdb import Ccdb +from qcrepocleaner.binUtils import prepare_main_logger + + +def parse_args(): + """Parse the arguments passed to the script.""" + logging.info("Parsing arguments") + parser = argparse.ArgumentParser(description='Delete all objects listed in file') + parser.add_argument('--url', dest='url', action='store', help='URL of the CCDB, with http[s]://', required=True) + parser.add_argument('--log-level', dest='log_level', action='store', default="20", + help='Log level (CRITICAL->50, ERROR->40, WARNING->30, INFO->20,DEBUG->10)') + parser.add_argument('--dry-run', action='store_true', + help='Dry run, no actual deletion nor modification to the CCDB.') + parser.add_argument('--one-by-one', action='store_true', help='Ask confirmation for each deletion') + parser.add_argument('--print-list', action='store_true', help='Only print the list of objects that would be deleted') + parser.add_argument('--objects-list-file', dest='objects_file', action='store', help='A text file with 1 object per ' + 'line', required=True) + parser.add_argument('--yes', action='store_true', help='Answers yes to all. You should really not use that.') + args = parser.parse_args() + dryable.set(args.dry_run) + logging.info(args) + return args + + +def run(args): + ccdb = Ccdb(args.url) + + total_deleted = 0 + + with open(args.objects_file, 'r') as file: + # Loop over each line in the file + for line in file: + nb_deleted = 0 + + # Print each line + stripped_line = line.strip() + if not stripped_line: + continue + logging.debug(f"line : '{stripped_line}'") + + # we have to make sure we take all objects in the subfolders --> we add `/.*` at the end + versions = ccdb.get_versions_list(stripped_line + "/.*", "", "") + # we also want to have all the versions at the root + versions += ccdb.get_versions_list(stripped_line, "", "") + + logging.info("Here are the objects that are going to be deleted: ") + for v in versions: + logging.info(v) + logging.info(f"Number of items: {len(versions)}") + + if args.print_list or len(versions) == 0: + continue + + if not args.yes: + logging.warning("****** ARE YOU ABSOLUTELY SURE YOU WANT TO CONTINUE ? ******") + answer = input("Yes/No \n ") + if answer.lower() not in ["y", "yes"]: + exit(0) + + for v in versions: + logging.info(f"Ready to delete {v}") + if args.one_by_one: + answer = input(" Continue? y/n\n ") + if answer.lower() in ["y", "yes"]: + ccdb.delete_version(v) + nb_deleted += 1 + elif answer.lower() in ["n", "no"]: + logging.info(" skipping") + else: + logging.error(" wrong input, skipping") + else: + ccdb.delete_version(v) + nb_deleted += 1 + + logging.info(f"Deleted items: {nb_deleted}") + total_deleted += nb_deleted + + logging.info(f"Total deleted items: {total_deleted}") + +# **************** +# We start here ! +# **************** + +def main(): + prepare_main_logger() + + # Parse arguments + args = parse_args() + logging.getLogger().setLevel(int(args.log_level)) + + run(args) + + +if __name__ == "__main__": + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-objects-in-runs b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-objects-in-runs new file mode 100755 index 0000000000..aa17de2c8e --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-objects-in-runs @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + +import argparse +import csv +import logging + +import dryable + +from qcrepocleaner.Ccdb import Ccdb +from qcrepocleaner.binUtils import prepare_main_logger + + +def parseArgs(): + """Parse the arguments passed to the script.""" + logging.info("Parsing arguments") + parser = argparse.ArgumentParser(description='Remove all objects for a set of runs in a given path. The runs are ' + 'provided in a csv file whose format is the one from the bookkeeping' + ' export.') + parser.add_argument('--url', dest='url', action='store', help='URL of the CCDB, with http[s]://', required=True) + parser.add_argument('--log-level', dest='log_level', action='store', default="20", + help='Log level (CRITICAL->50, ERROR->40, WARNING->30, INFO->20,DEBUG->10)') + parser.add_argument('--dry-run', action='store_true', + help='Dry run, no actual deletion nor modification to the CCDB.') + parser.add_argument('--path', dest='path', action='store', default="", + help='Clean this path (without initial slash and without .* at the end, e.g. qc/TST/MO/Bob)', required=True) + parser.add_argument('--runs-csv-file', dest='runs_file', action='store', help='A csv file with a header and the ' + 'column `runNumber` contains the run', + required=True) + parser.add_argument('--one-by-one', action='store_true', help='Ask confirmation for each deletion') + parser.add_argument('--print-list', action='store_true', help='Only print the list of objects that would be deleted') + parser.add_argument('--yes', action='store_true', help='Answers yes to all. You should really not use that.') + parser.add_argument('--metadata', dest='metadata', action='store', default="", + help='Delete only versions matching these metadata. Format: "[/key=value]*"') + parser.add_argument('--print-versions-details', dest='print_versions_details', action='store_true', default=False, + help='Print extra details about the versions if enabled..') + args = parser.parse_args() + dryable.set(args.dry_run) + logging.info(args) + return args + + +def run(args): + ccdb = Ccdb(args.url, args.print_versions_details) + + total_deleted = 0 + total_planned = 0 + + file = open(args.runs_file) + csvreader = csv.DictReader(file, delimiter=";") + for row in csvreader: + nb_deleted = 0 + run_number = row["runNumber"] + logging.info(f"Run : {run_number}") + + versions = ccdb.get_versions_list(args.path + "/.*", "", "", run_number, metadata=args.metadata) + logging.info("Here are the objects that are going to be deleted: ") + for v in versions: + logging.info(v) + logging.info(f"Number of items: {len(versions)}") + total_planned += len(versions) + + if args.print_list or len(versions) == 0: + continue + + if not args.yes: + logging.warning("****** ARE YOU ABSOLUTELY SURE YOU WANT TO CONTINUE ? ******") + answer = input("Yes/No \n ") + if answer.lower() not in ["y", "yes"]: + exit(0) + + for v in versions: + logging.info(f"Ready to delete {v}") + if args.one_by_one: + answer = input(" Continue? y/n\n ") + if answer.lower() in ["y", "yes"]: + ccdb.delete_version(v) + nb_deleted += 1 + elif answer.lower() in ["n", "no"]: + logging.info(" skipping") + else: + logging.error(" wrong input, skipping") + else: + ccdb.delete_version(v) + nb_deleted += 1 + + logging.info(f"Deleted items: {nb_deleted}") + total_deleted += nb_deleted + + logging.info(f"Total planned to be deleted: {total_planned}") + logging.info(f"Total deleted items: {total_deleted}") + + +# **************** +# We start here ! +# **************** + +def main(): + + prepare_main_logger() + + # prepare test data + # url = "http://ccdb-test.cern.ch:8080" + # data = {'part': 'part'} + # path = "qc/TST/MO/repo/test_delete_runs" + # current_timestamp = int(time.time() * 1000) + # ccdb = Ccdb(url) + # for x in range(30): + # metadata = {'RunNumber': str(x)} + # from_ts = current_timestamp - 60 * 1000 + # to_ts = current_timestamp + # version_info = ObjectVersion(path=path, validFrom=from_ts, validTo=to_ts, metadata=metadata) + # ccdb.put_version(version=version_info, data=data) + + # Parse arguments + args = parseArgs() + logging.getLogger().setLevel(int(args.log_level)) + + run(args) + + +if __name__ == "__main__": + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-time-interval b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-time-interval new file mode 100755 index 0000000000..910ee23171 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-time-interval @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +import argparse +import logging + +import dryable + +from qcrepocleaner.Ccdb import Ccdb +from qcrepocleaner.binUtils import prepare_main_logger + + +def parse_args(): + """Parse the arguments passed to the script.""" + logging.info("Parsing arguments") + parser = argparse.ArgumentParser(description='Remove all the objects in a given time interval in a given path') + parser.add_argument('--url', dest='url', action='store', help='URL of the CCDB, with http[s]://', + required=True) + parser.add_argument('--log-level', dest='log_level', action='store', default="20", + help='Log level (CRITICAL->50, ERROR->40, WARNING->30, INFO->20,DEBUG->10)') + parser.add_argument('--dry-run', action='store_true', + help='Dry run, no actual deletion nor modification to the CCDB.') + parser.add_argument('--path', dest='path', action='store', default="", + help='Delete this path (without initial slash and without .* at the end, e.g. qc/TST/MO/Bob).', + required=True) + parser.add_argument('--from', dest='from_ts', action='store', help='From this timestamp.', required=True) + parser.add_argument('--to', dest='to_ts', action='store', help='To this timestamp.', required=True) + parser.add_argument('--only-path-no-subdir', action='store_true', + help='Do not process the subfolders, i.e. do not add .* at the end of the path.') + parser.add_argument('--one-by-one', action='store_true', help='Ask confirmation for each deletion') + parser.add_argument('--print-list', action='store_true', + help='Only print the list of objects that would be deleted') + parser.add_argument('--metadata', dest='metadata', action='store', default="", + help='Delete only versions matching these metadata. Format: "[/key=value]*"') + parser.add_argument('--yes', action='store_true', help='Answers yes to all. You should really not use that.') + parser.add_argument('--preserve-one-out-of', dest='preserve_one_out_of', type=int, default=0, + help='Preserve 1 out of N versions (e.g. 100 means keep every 100th version). Default: 0 (disable).') + + args = parser.parse_args() + dryable.set(args.dry_run) + logging.info(args) + return args + + +def run(args): + ccdb = Ccdb(args.url) + path = args.path+"/" if args.only_path_no_subdir else args.path + "/.*" + versions = ccdb.get_versions_list(path, args.from_ts, args.to_ts, metadata=args.metadata) + logging.debug(versions) + logging.info("Here are the objects that are going to be deleted: ") + + if args.print_list: + for v in versions: + logging.info(v) + logging.info(f"Number of items: {len(versions)}") + exit(0) + + logging.info(f"Number of items: {len(versions)}") + + if not args.yes: + logging.warning("****** ARE YOU ABSOLUTELY SURE YOU WANT TO CONTINUE ? ******") + answer = input("Yes/No \n ") + if answer.lower() not in ["y", "yes"]: + exit(0) + + deleted_count = 0 + for i, v in enumerate(versions): + # Skip every Nth version if preservation is enabled + if args.preserve_one_out_of and i % args.preserve_one_out_of == 0: + logging.info(f"Preserving {v} (index {i})") + continue + + logging.info(f"Ready to delete {v}") + if args.one_by_one: + answer = input(" Continue? y/n\n ") + if answer.lower() in ["y", "yes"]: + ccdb.delete_version(v) + deleted_count = deleted_count + 1 + elif answer.lower() in ["n", "no"]: + logging.info(" skipping") + else: + logging.error(" wrong input, skipping") + else: + ccdb.delete_version(v) + deleted_count = deleted_count + 1 + + logging.info(f"Deleted items: {deleted_count}") + + +# **************** +# We start here ! +# **************** + +def main(): + prepare_main_logger() + + # Parse arguments + args = parse_args() + logging.getLogger().setLevel(int(args.log_level)) + + run(args) + + +if __name__ == "__main__": + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-versions-not-in-periods b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-versions-not-in-periods new file mode 100755 index 0000000000..cbd11776e7 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-delete-versions-not-in-periods @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +from collections import defaultdict +import logging +import argparse + +from qcrepocleaner import binUtils +from qcrepocleaner.Ccdb import Ccdb +import dryable + +def parse_args(): + """Parse the arguments passed to the script.""" + logging.info("Parsing arguments") + parser = argparse.ArgumentParser(description='Remove all the versions in the given path that don\'t match the given list of periodNames or has no periodName.') + parser.add_argument('--url', dest='url', action='store', help='URL of the CCDB, with http[s]://', required=True) + parser.add_argument('--log-level', dest='log_level', action='store', default="20", + help='Log level (CRITICAL->50, ERROR->40, WARNING->30, INFO->20,DEBUG->10)') + parser.add_argument('--dry-run', action='store_true', + help='Dry run, no actual deletion nor modification to the CCDB.') + parser.add_argument('--path', dest='path', action='store', default="", + help='The path to work with (without initial slash and without .* at the end, e.g. qc/TST/MO/Bob).', required=True) + parser.add_argument('--one-by-one', action='store_true', help='Ask confirmation for each deletion') + parser.add_argument('--yes', action='store_true', help='Answers yes to all. You should really not use that.') + parser.add_argument('--periods-list', dest='periods_list', action='store', default="", + help='The list of periods that will be spared, comma separated, no space.', required=True) + + args = parser.parse_args() + dryable.set(args.dry_run) + logging.info(args) + return args + + +def run(args): + ccdb = Ccdb(args.url) + ccdb.logger = logging.getLogger + global_deleted = 0 + global_skipped = 0 + global_spared = 0 + global_spared_dict = defaultdict(int) + periods_list = args.periods_list.split(',') + + # retrieve all the objects + path = args.path + ".*" + objects = ccdb.getFullObjectsDetails(path=path) + logging.debug(f"objects: {objects}") + + for o in objects: + deleted = 0 + skipped = 0 + spared = 0 + spared_dict = defaultdict(int) + logging.info(f"object: {o}") + # Retrieve the list of versions for this object + versions = ccdb.getVersionsList(o['path']) + logging.info(f" Number of versions: {len(versions)} - {periods_list}") + for v in versions: + if "PeriodName" not in v.metadata: + ccdb.deleteVersion(v) + deleted += 1 + elif v.metadata["PeriodName"] not in periods_list: + if args.one_by_one: + answer = input(" Continue? y/n\n ") + if answer.lower() in ["y", "yes"]: + ccdb.deleteVersion(v) + deleted += 1 + elif answer.lower() in ["n", "no"]: + logging.info(" skipping") + skipped += 1 + else: + logging.error(" wrong input, skipping") + skipped += 1 + else: + ccdb.deleteVersion(v) + deleted += 1 + else: + logging.debug(f"Not deleting {v} as it is in the periods list") + spared += 1 + spared_dict[v.metadata["PeriodName"]] += 1 + + logging.info(f"Number of deleted: {deleted}") + logging.info(f"Number of spared: {spared}") + logging.info(f"Number of skipped: {skipped}") + logging.info(f"Spared : {spared_dict}") + global_deleted += deleted + global_skipped += skipped + global_spared += spared + global_spared_dict = defaultdict(int, {key: spared_dict.get(key, 0) + global_spared_dict.get(key, 0) for key in + set(spared_dict) | set(global_spared_dict)}) + + logging.info(f"Global results : ") + logging.info(f" Number of deleted: {global_deleted}") + logging.info(f" Number of spared: {global_spared}") + logging.info(f" Number of skipped: {global_skipped}") + logging.info(f" Spared : {global_spared_dict}") + + # **************** + # We start here ! + # **************** + +def main(): + binUtils.prepare_main_logger() + + # Parse arguments + args = parse_args() + logging.getLogger().setLevel(int(args.log_level)) + + run(args) + +if __name__ == "__main__": + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-find-objects-less-versions-than b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-find-objects-less-versions-than new file mode 100755 index 0000000000..bb605c77ea --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-find-objects-less-versions-than @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +import argparse +import logging + +from qcrepocleaner import binUtils +from qcrepocleaner.Ccdb import Ccdb + + +def parse_args(): + """Parse the arguments passed to the script.""" + logging.info("Parsing arguments") + parser = argparse.ArgumentParser(description='Identify the objects that have less than X versions.') + parser.add_argument('--url', dest='url', action='store', help='URL of the CCDB, with http[s]://', required=True) + parser.add_argument('--log-level', dest='log_level', action='store', default="20", + help='Log level (CRITICAL->50, ERROR->40, WARNING->30, INFO->20,DEBUG->10)') + parser.add_argument('--path', dest='path', action='store', default="", + help='The path to work with (without initial slash and without .* at the end, e.g. qc/TST/MO/Bob).', required=True) + parser.add_argument('--number-versions', dest='threshold', action='store', default=10, + help='The threshold under which we report an object if it has less or equal versions than that') + args = parser.parse_args() + logging.info(args) + return args + + +def run(args): + ccdb = Ccdb(args.url) + ccdb.logger = logging.getLogger + + list_results = {} + threshold = int(args.threshold) + + path = args.path + ".*" + objects_paths = ccdb.get_objects_list(path=path) + counter = 1 + + for obj_path in objects_paths: + number_of_versions = len(ccdb.get_versions_list(object_path=obj_path)) + logging.debug(f"Number versions for {obj_path} : {number_of_versions}") + print(f"{counter}/{len(objects_paths)}", end='\r') + counter += 1 + if number_of_versions <= threshold: + list_results[obj_path] = number_of_versions + + logging.info(f"List of the objects in {args.path} with <= versions than {args.threshold} :") + for obj_path, number_of_versions in list_results.items(): + logging.info(f" {obj_path} ({number_of_versions})") + + +# **************** +# We start here ! +# **************** + +def main(): + binUtils.prepare_main_logger() + + # Parse arguments + args = parse_args() + logging.getLogger().setLevel(int(args.log_level)) + + run(args) + + +if __name__ == "__main__": + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-find-objects-not-updated b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-find-objects-not-updated new file mode 100755 index 0000000000..a6184eff19 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-find-objects-not-updated @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +import argparse +import datetime +import logging + +from qcrepocleaner import binUtils +from qcrepocleaner.Ccdb import Ccdb + + +def parse_args(): + """Parse the arguments passed to the script.""" + logging.info("Parsing arguments") + parser = argparse.ArgumentParser(description='Identify the objects that have not seen an update for a given ' + 'amount of time in the given path.') + parser.add_argument('--url', dest='url', action='store', help='URL of the CCDB, with http[s]://', required=True) + parser.add_argument('--log-level', dest='log_level', action='store', default="20", + help='Log level (CRITICAL->50, ERROR->40, WARNING->30, INFO->20,DEBUG->10)') + parser.add_argument('--path', dest='path', action='store', default="", + help='The path to work with (without initial slash and without .* at the end, e.g. qc/TST/MO/Bob).', required=True) + parser.add_argument('--span', dest='span', action='store', default="30", + help='The span of time (in days) for which objects without updates will be identified.') + args = parser.parse_args() + logging.info(args) + return args + + +def days_ago_timestamp(days): + """ + Returns the timestamp (milliseconds) corresponding to the number of days in the past. + """ + today = datetime.datetime.now() + days_ago = today - datetime.timedelta(days=days) + timestamp = int(days_ago.timestamp())*1000 + return timestamp + + +def run(args): + ccdb = Ccdb(args.url) + ccdb.logger = logging.getLogger + + ts_days_ago = days_ago_timestamp(int(args.span)) + logging.debug(f"ts : {ts_days_ago}") + + # retrieve all the objects + path = args.path + ".*" + objects = ccdb.get_full_objects_details(path=path) + + logging.info(f"List of the objects in {args.path} not touched in the past {args.span} days:") + counter = 0 + for o in objects: + last_updated = o['lastModified'] + logging.debug(f"{o['path']} : {last_updated}") + if last_updated < ts_days_ago: + logging.info(f" {o['path']}") + counter += 1 + + logging.info(f"Number of items: {counter}") + + +# **************** +# We start here ! +# **************** + +def main(): + binUtils.prepare_main_logger() + + # Parse arguments + args = parse_args() + logging.getLogger().setLevel(int(args.log_level)) + + run(args) + + +if __name__ == "__main__": + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-move-objects b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-move-objects new file mode 100755 index 0000000000..79fc7b5a90 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-move-objects @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +import argparse +import logging +import re + +import dryable + +from qcrepocleaner.Ccdb import Ccdb +from qcrepocleaner.binUtils import prepare_main_logger + + +def parse_args(): + """Parse the arguments passed to the script.""" + logging.info("Parsing arguments") + parser = argparse.ArgumentParser(description='Move all objects in path to new-path') + parser.add_argument('--url', dest='url', action='store', help='URL of the CCDB, with http[s]://', required=True) + parser.add_argument('--log-level', dest='log_level', action='store', default="20", + help='Log level (CRITICAL->50, ERROR->40, WARNING->30, INFO->20,DEBUG->10)') + parser.add_argument('--dry-run', action='store_true', + help='Dry run, no actual deletion nor modification to the CCDB.') + parser.add_argument('--path', dest='path', action='store', default="", + help='Origin path without initial slash and without .* at the end (e.g. qc/TST/MO/Bob).', required=True) + parser.add_argument('--new-path', dest='new_path', action='store', default="", + help='New path without initial slash and without .* at the end (e.g. qc/TST2/MO/Bob).', required=True) + parser.add_argument('--one-by-one', action='store_true', help='Ask confirmation for each deletion') + parser.add_argument('--print-list', action='store_true', help='Only print the list of objects that would be deleted') + args = parser.parse_args() + dryable.set(args.dry_run) + logging.info(args) + return args + + +def run(args): + ccdb = Ccdb(args.url) + + nb_moved = 0 + # we have to make sure we take all objects in the subfolders --> we add `/.*` at the end + versions = ccdb.get_versions_list(args.path + "/.*", "", "") + # we also want to have all the versions at the root + versions += ccdb.get_versions_list(args.path, "", "") + + logging.info("Here are the objects that are going to be moved: ") + for v in versions: + logging.info(v) + logging.info(f"Number of items: {len(versions)}") + + if args.print_list: + exit(0) + + logging.warning("****** ARE YOU ABSOLUTELY SURE YOU WANT TO CONTINUE ? ******") + answer = input("Yes/No \n ") + if answer.lower() not in ["y", "yes"]: + exit(0) + + for v in versions: + # here we need to make sure that we preserve the subdirectory structure and replace only the matching part + version_new_path = re.sub("^" + args.path, args.new_path.rstrip('/'), v.path.rstrip('/'), count=0, flags=0) + logging.info(f"Ready to move {v} to {version_new_path}") + if args.one_by_one: + answer = input(" Continue? y/n\n ") + if answer.lower() in ["y", "yes"]: + ccdb.move_version(v, version_new_path) + nb_moved += 1 + elif answer.lower() in ["n", "no"]: + logging.info(" skipping") + else: + logging.error(" wrong input, skipping") + else: + ccdb.move_version(v, version_new_path) + nb_moved += 1 + + logging.info(f"Moved items: {nb_moved}") + +# **************** +# We start here ! +# **************** + +def main(): + prepare_main_logger() + + # Parse arguments + args = parse_args() + logging.getLogger().setLevel(int(args.log_level)) + + run(args) + + +if __name__ == "__main__": + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-update-run-type b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-update-run-type new file mode 100755 index 0000000000..982aa5e63a --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/o2-qc-repo-update-run-type @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 + +import argparse +import csv +import logging + +import dryable + +from qcrepocleaner.Ccdb import Ccdb +from qcrepocleaner.binUtils import prepare_main_logger + + +def parse_args(): + """Parse the arguments passed to the script.""" + logging.info("Parsing arguments") + parser = argparse.ArgumentParser(description='Update the run type of all versions in a path. The run types for each run are ' + 'provided in a csv file whose format is the one from the bookkeeping, with headers.') + parser.add_argument('--url', dest='url', action='store', help='URL of the CCDB, with http[s]://', required=True) + parser.add_argument('--log-level', dest='log_level', action='store', default="20", + help='Log level (CRITICAL->50, ERROR->40, WARNING->30, INFO->20,DEBUG->10)') + parser.add_argument('--dry-run', action='store_true', + help='Dry run, no actual deletion nor modification to the CCDB.') + parser.add_argument('--path', dest='path', action='store', default="", + help='Update this path (without initial slash and without .* at the end, e.g. qc/TST/MO/Bob). ' + 'It will correspond to the exact name of a folder.', required=True) + parser.add_argument('--runs-csv-file', dest='runs_file', action='store', help='A csv file with a header and the ' + 'columns `runNumber` and `runType`', + required=True) + parser.add_argument('--fallback-runtype', dest='fallback_runtype', action='store', help=' default to this run type ' + 'if a run is not found in the csv file. If this argument is not specified, the unknown runs are simply skipped.', + default=None) + parser.add_argument('--print-list', action='store_true', help='Only print the list of objects that would be updated') + parser.add_argument('--yes', action='store_true', help='Answers yes to all. You should be really careful with that.') + parser.add_argument('--path-no-subdir', action='store_true', default=False, help='Set to true if the ' + 'path points to an object rather than a folder or if subdirectories must be ignored.') + parser.add_argument('--path-wildcard', action='store_true', default=False, help='Path to update, no initial ' + 'slash, no .* at the end. It does' + ' not need to match a full ' + 'directory name.') + parser.add_argument('--from-timestamp', action='store', dest='from_timestamp', help='only modify versions created' + 'since this timestamp') + args = parser.parse_args() + dryable.set(args.dry_run) + logging.info(args) + return args + + +def run(args): + ccdb = Ccdb(args.url) + + total_updated = 0 + total_planned = 0 + mapping_run_types = dict() + + # build the mapping + file = open(args.runs_file) + csvreader = csv.DictReader(file) + for row in csvreader: + try: + run_number = row["runNumber"] + run_type = row["runType"] + except KeyError: # pragma: no coverage + logging.fatal(f"Could not find columns `runNumber` and `runType` in {args.runs_file}, make sure the column headers are correct.") + exit(1) + + if run_type == "null" or run_type == "": + logging.debug(f"Skipping run {run_number} because its type is `null`") + continue + logging.debug(f"Run : {run_number} -> {run_type}") + mapping_run_types[run_number] = run_type + + # go through the versions and update them if possible + path = args.path + if args.path_wildcard: + path += ".*" + elif args.path_no_subdir is False: + path += "/.*" + versions = ccdb.get_versions_list(path, args.from_timestamp, "") + logging.info(f"Number of versions found: {len(versions)}") + total_planned += len(versions) + for version in versions: + if "RunNumber" not in version.metadata: + logging.debug(f"{version} misses metadata RunNumber") + continue + run_number = version.metadata["RunNumber"] + + if version.metadata["RunNumber"] not in mapping_run_types: + logging.debug(f"{version} : No mapping for run {version.metadata['RunNumber']}") + if not args.fallback_runtype: + logging.debug(f" fSkipping version as there is fallback_runtype is not set. ") + continue + else: + logging.debug(f" fUsing fallback_runtype instead ({args.fallback_runtype})") + run_type = args.fallback_runtype + else: + run_type = mapping_run_types[run_number] + + old_run_type = version.metadata['RunType'] if 'RunType' in version.metadata else "null" + if old_run_type != run_type: + logging.info(f"Ready to update {version} : \"{old_run_type}\" -> \"{run_type}\"") + else: + logging.info(f"Won't update {version} : run type is already {run_type}") + continue + + if args.print_list: + continue + + if args.yes is True: + ccdb.update_metadata(version, {"RunType": run_type}) + total_updated += 1 + else: + answer = input(" Continue? y/n\n ") + if answer.lower() in ["y", "yes"]: + ccdb.update_metadata(version, {"RunType": run_type}) + total_updated += 1 + elif answer.lower() in ["n", "no"]: + logging.info(" skipping") + else: + logging.error(" wrong input, skipping") + + logging.info(f"Total planned to be updated: {total_planned}") + logging.info(f"Total updated items: {total_updated}") + + +# **************** +# We start here ! +# **************** + +def main(): + + prepare_main_logger() + + # Parse arguments + args = parse_args() + logging.getLogger().setLevel(int(args.log_level)) + + run(args) + + +if __name__ == "__main__": + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/pidfile.py b/Framework/script/RepoCleaner/qcrepocleaner/pidfile.py new file mode 100644 index 0000000000..349ac353e7 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/pidfile.py @@ -0,0 +1,53 @@ +import os +import psutil + + +### Freely adapted from https://pypi.org/project/python-pidfile/ +# https://github.com/mosquito/python-pidfile +# MIT license + + +class AlreadyRunningError(Exception): + pass + + +class PIDFile(object): + def __init__(self, filename='pidfile'): + self._process_name = psutil.Process(os.getpid()).cmdline()[0] + self._file = filename + + @property + def is_running(self): + if not os.path.exists(self._file): + return False + + with open(self._file, "r") as f: + try: + pid = int(f.read()) + except (OSError, ValueError): + return False + + if not psutil.pid_exists(pid): + return False + + try: + cmd1 = psutil.Process(pid).cmdline()[0] + return cmd1 == self._process_name + except psutil.AccessDenied: + return False + + def __enter__(self): + if self.is_running: + raise AlreadyRunningError + + with open(self._file, "w") as f: + f.write(str(os.getpid())) + + return self + + def __exit__(self, *args): + if os.path.exists(self._file): + try: + os.remove(self._file) + except OSError: + pass diff --git a/Framework/script/RepoCleaner/qcrepocleaner/policies_utils.py b/Framework/script/RepoCleaner/qcrepocleaner/policies_utils.py new file mode 100644 index 0000000000..639cc3d30f --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/policies_utils.py @@ -0,0 +1,34 @@ +import logging +from datetime import datetime +from datetime import timedelta +from typing import DefaultDict, List + +from qcrepocleaner.Ccdb import ObjectVersion + +logger = logging # default logger + + +def in_grace_period(version: ObjectVersion, delay: int): + return version.created_at_as_dt >= datetime.now() - timedelta(minutes=delay) + + +def get_run(v: ObjectVersion) -> str: + run = "none" + if "Run" in v.metadata: + run = str(v.metadata['Run']) + elif "RunNumber" in v.metadata: + run = str(v.metadata['RunNumber']) + return run + + +def group_versions(ccdb, object_path, period_pass, versions_buckets_dict: DefaultDict[str, List[ObjectVersion]]): + # Find all the runs and group the versions (by run or by a combination of multiple attributes) + versions = ccdb.get_versions_list(object_path) + logger.debug(f"group_versions: found {len(versions)} versions") + for v in versions: + logger.debug(f"Assigning {v} to a bucket") + run = get_run(v) + period_name = v.metadata.get("PeriodName") or "" + pass_name = v.metadata.get("PassName") or "" + key = run + period_name + pass_name if period_pass else run + versions_buckets_dict[key].append(v) diff --git a/Framework/script/RepoCleaner/qcrepocleaner/rules/1_per_hour.py b/Framework/script/RepoCleaner/qcrepocleaner/rules/1_per_hour.py new file mode 100644 index 0000000000..9eb0b419a3 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/rules/1_per_hour.py @@ -0,0 +1,62 @@ +import logging +from datetime import datetime +from datetime import timedelta +from typing import Dict, List, Optional + +from qcrepocleaner.Ccdb import Ccdb, ObjectVersion + +logger = logging # default logger + + +def process(ccdb: Ccdb, object_path: str, delay: int, from_timestamp: int, to_timestamp: int, + extra_params: Dict[str, str]): + """ + Process this deletion rule on the object. We use the CCDB passed by argument. + Objects who have been created recently are spared (delay is expressed in minutes). + This specific policy, 1_per_hour, operates like this : take the first record, + delete everything for the next hour, find the next one and loop. + + :param ccdb: the ccdb in which objects are cleaned up. + :param object_path: path to the object, or pattern, to which a rule will apply. + :param delay: the grace period during which a new object is never deleted. + :param from_timestamp: only objects created after this timestamp are considered. + :param to_timestamp: only objects created before this timestamp are considered. + :param extra_params: a dictionary containing extra parameters for this rule. + :return a dictionary with the number of deleted, preserved and updated versions. Total = deleted+preserved. + """ + + logger.debug(f"Plugin 1_per_hour processing {object_path}") + + versions = ccdb.get_versions_list(object_path) + + last_preserved: Optional[ObjectVersion] = None + preservation_list: List[ObjectVersion] = [] + deletion_list: List[ObjectVersion] = [] + update_list: List[ObjectVersion] = [] + for v in versions: + if last_preserved is None or last_preserved.valid_from_as_dt < v.valid_from_as_dt - timedelta(hours=1): + last_preserved = v + preservation_list.append(last_preserved) + else: + if v.valid_from_as_dt < datetime.now() - timedelta(minutes=delay): # grace period + logger.debug(f"{v} not in the grace period") + if from_timestamp < v.valid_from < to_timestamp: # in the allowed period + logger.debug(f"{v} in the allowed period (from,to), we delete {v}") + deletion_list.append(v) + ccdb.delete_version(v) + continue + preservation_list.append(v) + + logger.debug(f"deleted ({len(deletion_list)}) : ") + for v in deletion_list: + logger.debug(f" {v}") + + logger.debug(f"preserved ({len(preservation_list)}) : ") + for v in preservation_list: + logger.debug(f" {v}") + + logger.debug(f"updated ({len(update_list)}) : ") + for v in update_list: + logger.debug(f" {v}") + + return {"deleted": len(deletion_list), "preserved": len(preservation_list), "updated": len(update_list)} diff --git a/Framework/script/RepoCleaner/qcrepocleaner/rules/1_per_run.py b/Framework/script/RepoCleaner/qcrepocleaner/rules/1_per_run.py new file mode 100644 index 0000000000..1e987def63 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/rules/1_per_run.py @@ -0,0 +1,114 @@ +import logging +from collections import defaultdict +from typing import Dict, List, DefaultDict, Optional + +from qcrepocleaner import policies_utils +from qcrepocleaner.Ccdb import Ccdb, ObjectVersion + +logger = logging # default logger + + +def process(ccdb: Ccdb, object_path: str, delay: int, from_timestamp: int, to_timestamp: int, + extra_params: Dict[str, str]): + """ + Process this deletion rule on the object. We use the CCDB passed by argument. + Objects which have been created recently are spared (delay is expressed in minutes). + This specific policy, 1_per_run, keeps only the most recent version for a given run based on the createdAt. + + Config Parameters: + - period_pass: Keep 1 version for a combination of run+pass+period if true. + - delete_when_no_run: Versions without runs are preserved if delete_when_no_run is set to false (default). + Otherwise, only the last one is preserved. + THEY CANNOT BE BOTH TRUE AT THE SAME TIME + + It is implemented like this : + Map of buckets: run[+pass+period] -> list of versions + Go through all objects: Add the object to the corresponding key (run[+pass+period]) + If delete_when_no_run is false, remove from the map the versions without run or with run==0 + Go through the map: for each run (resp. run+pass+period) keep the most recent object and delete the rest. + + :param ccdb: the ccdb in which objects are cleaned up. + :param object_path: path to the object, or pattern, to which a rule will apply. + :param delay: the grace period during which a new object is never deleted. + :param from_timestamp: only objects created after this timestamp are considered. + :param to_timestamp: only objects created before this timestamp are considered. + :param extra_params: a dictionary containing extra parameters for this rule. + :return a dictionary with the number of deleted, preserved and updated versions. Total = deleted+preserved. + """ + + logger.debug(f"Plugin 1_per_run processing {object_path}") + + preservation_list: List[ObjectVersion] = [] + deletion_list: List[ObjectVersion] = [] + update_list: List[ObjectVersion] = [] + versions_buckets_dict: DefaultDict[str, List[ObjectVersion]] = defaultdict(list) + + # config parameters + delete_when_no_run = (extra_params.get("delete_when_no_run", False) is True) + logger.debug(f"delete_when_no_run : {delete_when_no_run}") + period_pass = (extra_params.get("period_pass", False) is True) + logger.debug(f"period_pass : {period_pass}") + if delete_when_no_run is True and period_pass is True: + logger.error(f"1_per_run does not allow both delete_when_no_run and period_pass to be on at the same time") + return {"deleted": 0, "preserved": 0, "updated": 0} + + # Find all the runs and group the versions (by run or by a combination of multiple attributes) + policies_utils.group_versions(ccdb, object_path, period_pass, versions_buckets_dict) + + logger.debug(f"Number of buckets : {len(versions_buckets_dict)}") + if not period_pass: + logger.debug(f"Number of versions without runs : {len(versions_buckets_dict['none'])}") + + # if we should not touch the files with no runs, let's just remove them from the map + if not delete_when_no_run: + if 'none' in versions_buckets_dict: + del versions_buckets_dict['none'] + if '0' in versions_buckets_dict: + del versions_buckets_dict['0'] + + # Dispatch the versions to deletion and preservation lists + for bucket, run_versions in versions_buckets_dict.items(): + # logger.debug(f"- bucket {bucket}") + sorted_run_versions = sorted(run_versions, key=lambda x: x.created_at) + + freshest: Optional[ObjectVersion] = None + for v in sorted_run_versions: + if freshest is None: + freshest = v + elif freshest.created_at_as_dt < v.created_at_as_dt: + if policies_utils.in_grace_period(freshest, delay): + preservation_list.append(freshest) + else: + deletion_list.append(freshest) + freshest = v + else: + if policies_utils.in_grace_period(freshest, delay): + preservation_list.append(v) + else: + deletion_list.append(v) + preservation_list.append(freshest) + + # Actual deletion + logger.debug(f"Delete but preserve versions that are not in the period passed to the policy") + temp_deletion_list: List[ObjectVersion] = [] + for v in deletion_list: + if from_timestamp < v.valid_from < to_timestamp: # in the allowed period + temp_deletion_list.append(v) # we will delete any ways + ccdb.delete_version(v) + else: + preservation_list.append(v) # we preserve + deletion_list = temp_deletion_list + + logger.debug(f"deleted ({len(deletion_list)}) : ") + for v in deletion_list: + logger.debug(f" {v}") + + logger.debug(f"preserved ({len(preservation_list)}) : ") + for v in preservation_list: + logger.debug(f" {v}") + + logger.debug(f"updated ({len(update_list)}) : ") + for v in update_list: + logger.debug(f" {v}") + + return {"deleted": len(deletion_list), "preserved": len(preservation_list), "updated": len(update_list)} diff --git a/Framework/script/RepoCleaner/qcrepocleaner/rules/__init__.py b/Framework/script/RepoCleaner/qcrepocleaner/rules/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Framework/script/RepoCleaner/qcrepocleaner/rules/last_only.py b/Framework/script/RepoCleaner/qcrepocleaner/rules/last_only.py new file mode 100644 index 0000000000..99fb33c578 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/rules/last_only.py @@ -0,0 +1,64 @@ +import logging +from datetime import datetime +from datetime import timedelta +from typing import Dict, List, Optional + +from qcrepocleaner.Ccdb import Ccdb, ObjectVersion + +logger = logging # default logger + + +def process(ccdb: Ccdb, object_path: str, delay: int, from_timestamp: int, to_timestamp: int, extra_params: Dict[str, str]): + """ + Process this deletion rule on the object. We use the CCDB passed by argument. + + Only the last version of each object is preserved. Grace period is respected. + + :param ccdb: the ccdb in which objects are cleaned up. + :param object_path: path to the object, or pattern, to which a rule will apply. + :param delay: the grace period during which a new object is never deleted. + :param from_timestamp: only objects created after this timestamp are considered. + :param to_timestamp: only objects created before this timestamp are considered. + :param extra_params: a dictionary containing extra parameters (unused in this rule) + :return a dictionary with the number of deleted, preserved and updated versions. Total = deleted+preserved. + """ + + logger.debug(f"Plugin last_only processing {object_path}") + + versions = ccdb.get_versions_list(object_path) + + earliest: Optional[ObjectVersion] = None + preservation_list: List[ObjectVersion] = [] + deletion_list: List[ObjectVersion] = [] + # find the earliest + for v in versions: + if earliest is None or v.valid_from_as_dt > earliest.valid_from_as_dt: + earliest = v + logger.debug(f"earliest : {earliest}") + + # delete the non-earliest if we are not in the grace period + for v in versions: + logger.debug(f"{v} - {v.valid_from}") + if v == earliest: + preservation_list.append(v) + continue + + + if v.valid_from_as_dt < datetime.now() - timedelta(minutes=delay): # grace period + logger.debug(f" not in the grace period") + if from_timestamp < v.valid_from < to_timestamp: # in the allowed period + logger.debug(f"in the allowed period (from,to), we delete {v}") + deletion_list.append(v) + ccdb.delete_version(v) + continue + preservation_list.append(v) + + logger.debug(f"deleted ({len(deletion_list)}) : ") + for v in deletion_list: + logger.debug(f" {v}") + + logger.debug(f"preserved ({len(preservation_list)}) : ") + for v in preservation_list: + logger.debug(f" {v}") + + return {"deleted" : len(deletion_list), "preserved": len(preservation_list)} diff --git a/Framework/script/RepoCleaner/qcrepocleaner/rules/multiple_per_run.py b/Framework/script/RepoCleaner/qcrepocleaner/rules/multiple_per_run.py new file mode 100644 index 0000000000..0d4fb8d5cb --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/rules/multiple_per_run.py @@ -0,0 +1,171 @@ +import logging +from collections import defaultdict +from datetime import datetime +from datetime import timedelta +from typing import Dict, DefaultDict, List, Optional + +from qcrepocleaner import policies_utils +from qcrepocleaner.Ccdb import Ccdb, ObjectVersion + +logger = logging # default logger + + +def process(ccdb: Ccdb, object_path: str, delay: int, from_timestamp: int, to_timestamp: int, + extra_params: Dict[str, str]): + """ + Process this deletion rule on the object. We use the CCDB passed by argument. + Objects who have been created recently are spared (delay is expressed in minutes). + + This specific policy, multiple_per_run, operates like this : Keep the first and the last version of a run plus + 1 version every X minutes. + + Extra parameters: + - migrate_to_EOS: Migrate the object to EOS. (default: false) + - interval_between_versions: Period in minutes between the versions we will keep. (default: 90) + - period_pass: Keep 1 version for a combination of run+pass+period if true. (default: false) + - delete_first_last: delete the first and last of the run[+pass+period] before actually applying the rule. + Useful to keep the second and second to last instead of first and last. + - mw_deletion_delay: delete moving windows data entirely after this number of minutes. If not present or negative, don't delete. + As an extra safety, and because it is designed for Moving Windows, we only delete if the object has `mw` in the path. + + It is implemented like this : + Map of buckets: run[+pass+period] -> list of versions + Go through all objects: Add the object to the corresponding key (run[+pass+period]) + Sort the versions in the bucket + Remove the empty run from the map (we ignore objects without a run) + Go through the map: for each run (resp. run+pass+period) + Get SOR (validity of first object) + + if mw_deletion_delay != -1 and SOR < now - mw_deletion_delay + delete the data for this run + + if SOR < now - delay + if delete_first_last + Get flag cleaner_2nd from first object (if there) + if cleaner_2nd + continue # we do not want to reprocess the same run twice + flag second with `cleaner_2nd` + delete first and last versions in the bucket + + do + keep first + delete everything for the next interval_between_versions + until no more + move the last one, from delete to preserve + + :param ccdb: the ccdb in which objects are cleaned up. + :param object_path: path to the object, or pattern, to which a rule will apply. + :param delay: delay since SOR after which we process the run altogether. + :param from_timestamp: only objects created after this timestamp are considered. + :param to_timestamp: only objects created before this timestamp are considered. + :param extra_params: a dictionary containing extra parameters for this rule. + :return a dictionary with the number of deleted, preserved and updated versions. Total = deleted+preserved. + """ + + logger.debug(f"Plugin multiple_per_run processing {object_path}") + + preservation_list: List[ObjectVersion] = [] + deletion_list: List[ObjectVersion] = [] + update_list: List[ObjectVersion] = [] + versions_buckets_dict: DefaultDict[str, List[ObjectVersion]] = defaultdict(list) + metadata_for_preservation = {'preservation': 'true'} + + # config parameters + period_pass = (extra_params.get("period_pass", False) is True) + logger.debug(f"period_pass : {period_pass}") + interval_between_versions = int(extra_params.get("interval_between_versions", 90)) + logger.debug(f"interval_between_versions : {interval_between_versions}") + migrate_to_EOS = (extra_params.get("migrate_to_EOS", False) is True) + logger.debug(f"migrate_to_EOS : {migrate_to_EOS}") + delete_first_last = (extra_params.get("delete_first_last", False) is True) + logger.debug(f"delete_first_last : {delete_first_last}") + mw_deletion_delay = int(extra_params.get("mw_deletion_delay", -1)) + logger.debug(f"mw_deletion_delay : {mw_deletion_delay}") + + # Find all the runs and group the versions (by run or by a combination of multiple attributes) + policies_utils.group_versions(ccdb, object_path, period_pass, versions_buckets_dict) + + # Remove the empty run from the map (we ignore objects without a run) + logger.debug(f"Number of buckets : {len(versions_buckets_dict)}") + if not period_pass: + logger.debug(f"Number of versions without runs : {len(versions_buckets_dict['none'])}") + if 'none' in versions_buckets_dict: + del versions_buckets_dict['none'] + + # for each run or combination of pass and run + for bucket, run_versions in versions_buckets_dict.items(): + logger.debug(f"- bucket {bucket}") + + # Get SOR (validity of first object) and check if it is in the grace period and in the allowed period + first_object = run_versions[0] + if policies_utils.in_grace_period(first_object, delay): + logger.debug(f" in grace period, skip this bucket") + preservation_list.extend(run_versions) + elif not (from_timestamp < first_object.created_at < to_timestamp): # not in the allowed period + logger.debug(f" not in the allowed period, skip this bucket") + preservation_list.extend(run_versions) + elif mw_deletion_delay != -1 and first_object.created_at_as_dt < datetime.now() - timedelta(minutes=mw_deletion_delay): # moving windows case + logger.debug(f" after mw_deletion_delay period, delete this bucket") + for v in run_versions: + if "/mw/" in v.path: # this is because we really don't want to take the risk of batch deleting non-moving windows + logger.debug(f" deleting {v}") + deletion_list.append(v) + ccdb.delete_version(v) + else: + logger.debug(f" deletion is aborted as path does not contain `mw` ({v})") + preservation_list.append(v) + else: + logger.debug(f" not in the grace period") + + if delete_first_last: + logger.debug(f" delete_first_last is set") + run_versions.sort(key=lambda x: x.created_at) + # Get flag cleaner_2nd from first object (if there) + cleaner_2nd = "cleaner_2nd" in run_versions[0].metadata + if cleaner_2nd or len(run_versions) < 4: + logger.debug(f" first version has flag cleaner_2nd or there are less than 4 version, " + f"we continue to next bucket") + preservation_list.extend(run_versions) + continue + # flag second with `cleaner_2nd` + ccdb.update_metadata(run_versions[1], {'cleaner_2nd': 'true'}) + # delete first and last versions in the bucket + logger.debug(f" delete the first and last versions") + deletion_list.append(run_versions[-1]) + ccdb.delete_version(run_versions[-1]) + del run_versions[-1] + deletion_list.append(run_versions[0]) + ccdb.delete_version(run_versions[0]) + del run_versions[0] + + last_preserved: Optional[ObjectVersion] = None + for v in run_versions: + logger.debug(f"process {v}") + + # first or next after the period, or last one --> preserve + if last_preserved is None or \ + last_preserved.created_at_as_dt < v.created_at_as_dt - timedelta(minutes=interval_between_versions) or \ + v == run_versions[-1]: + logger.debug(f" --> preserve") + last_preserved = v + if migrate_to_EOS: + ccdb.update_metadata(v, metadata_for_preservation) + preservation_list.append(last_preserved) + else: # in between period --> delete + logger.debug(f" --> delete") + deletion_list.append(v) + ccdb.delete_version(v) + + logger.debug(f"deleted ({len(deletion_list)}) : ") + for v in deletion_list: + logger.debug(f" {v}") + + logger.debug(f"preserved ({len(preservation_list)}) : ") + for v in preservation_list: + logger.debug(f" {v}") + + logger.debug(f"updated ({len(update_list)}) : ") + for v in update_list: + logger.debug(f" {v}") + + return {"deleted": len(deletion_list), "preserved": len(preservation_list), "updated": len(update_list)} diff --git a/Framework/script/RepoCleaner/qcrepocleaner/rules/none_kept.py b/Framework/script/RepoCleaner/qcrepocleaner/rules/none_kept.py new file mode 100644 index 0000000000..1385f316dc --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/rules/none_kept.py @@ -0,0 +1,52 @@ +import logging +from typing import Dict, List + +from qcrepocleaner import policies_utils +from qcrepocleaner.Ccdb import Ccdb, ObjectVersion + +logger = logging # default logger + + +def process(ccdb: Ccdb, object_path: str, delay: int, from_timestamp: int, to_timestamp: int, extra_params: Dict[str, str]): + """ + Process this deletion rule on the object. We use the CCDB passed by argument. + + This policy deletes everything after `delay` minutes. + + :param ccdb: the ccdb in which objects are cleaned up. + :param object_path: path to the object, or pattern, to which a rule will apply. + :param delay: the grace period in minutes during which a new object is never deleted. + :param from_timestamp: only objects created after this timestamp are considered. + :param to_timestamp: only objects created before this timestamp are considered. + :param extra_params: a dictionary containing extra parameters (unused in this rule) + :return a dictionary with the number of deleted, preserved and updated versions. Total = deleted+preserved. + """ + + logger.debug(f"Plugin 'none' processing {object_path}") + + versions = ccdb.get_versions_list(object_path) + preservation_list: List[ObjectVersion] = [] + deletion_list: List[ObjectVersion] = [] + + for v in versions: + logger.debug(f"Processing version {v}") + if policies_utils.in_grace_period(v, delay): + logger.debug(f" in grace period, skip this version") + preservation_list.append(v) + else: + logger.debug(f" not in the grace period") + if from_timestamp < v.valid_from < to_timestamp: # in the allowed period + logger.debug(f" in the allowed period (from,to), we delete it") + deletion_list.append(v) + ccdb.delete_version(v) + continue + + logger.debug("deleted : ") + for v in deletion_list: + logger.debug(f" {v}") + + logger.debug("preserved : ") + for v in preservation_list: + logger.debug(f" {v}") + + return {"deleted": len(deletion_list), "preserved": len(preservation_list), "updated": 0} diff --git a/Framework/script/RepoCleaner/qcrepocleaner/rules/production.py b/Framework/script/RepoCleaner/qcrepocleaner/rules/production.py new file mode 100644 index 0000000000..40b0baf089 --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/rules/production.py @@ -0,0 +1,249 @@ +import logging +import time +from collections import defaultdict +from datetime import datetime +from datetime import timedelta +from typing import Dict, List, DefaultDict + +from qcrepocleaner.Ccdb import Ccdb, ObjectVersion + + +def in_grace_period(version: ObjectVersion, delay: int): + return version.valid_from_as_dt + timedelta(minutes=delay) > datetime.now() + + +eor_dict = {} # to have fake eor numbers +logger = logging # default logger + + +def process(ccdb: Ccdb, object_path: str, delay: int, from_timestamp: int, to_timestamp: int, extra_params: Dict[str, str]): + ''' + Process this deletion rule on the object. We use the CCDB passed by argument. + + This is the rule we use in production for the objects that need to be migrated. + + What it does: + - Versions without run number -> delete after the `delay` if the extra flag delete_when_no_run is not present + - (The run number is set in "RunNumber" metadata) + - For a given run + - Keep everything for 30 minutes (configurable: delay_first_trimming) + - Keep 1 per 10 minutes (configurable: period_btw_versions_first) after this delay. + - Keep 1 per 1 hour (configurable: period_btw_versions_final) + as well as first and last at EOR+3h (configurable: delay_final_trimming) + - What has not been deleted at this stage is marked to be migrated (preservation = true) + + Extra parameters: + - delay_first_trimming: Delay in minutes before first trimming. (default: 30) + - period_btw_versions_first: Period in minutes between the versions we will keep after first trimming. (default: 10) + - delay_final_trimming: Delay in minutes, counted from the EOR, before we do the final cleanup and mark for migration. (default: 180) + - period_btw_versions_final: Period in minutes between the versions we will migrate. (default: 60) + - delete_when_no_run: delete the objects after the delay if they don't have a run associated with them. (default: false). + + Implementation : + - Go through all objects: + - if a run is set, add the object to the corresponding map element. + - if not, if the delay has passed and delete_when_no_run is true, delete. + - Go through the map: for each run + - Check if run has finished and get the time of EOR if so. + - if run is over for more than 3 hours + - Final trimming + - else + - For each version + - First trimming (1 per 10 minutes) + During the first trimming we mark (trim1=true) the versions we have already treated to avoid redoing the work. + + + :param ccdb: the ccdb in which objects are cleaned up. + :param object_path: path to the object, or pattern, to which a rule will apply. + :param delay: the grace period during which a new object is not deleted although it has no run number. + :param from_timestamp: only objects created after this timestamp are considered. + :param to_timestamp: only objects created before this timestamp are considered. + :param extra_params: a dictionary containing extra parameters for this rule. + :return a dictionary with the number of deleted, preserved and updated versions. Total = deleted+preserved. + ''' + + logger.info(f"Plugin 'production' processing {object_path}") + + # Variables + preservation_list: List[ObjectVersion] = [] + deletion_list: List[ObjectVersion] = [] + update_list: List[ObjectVersion] = [] + runs_dict: DefaultDict[str, List[ObjectVersion]] = defaultdict(list) + + # Extra parameters + delay_first_trimming = int(extra_params.get("delay_first_trimming", 30)) + logger.debug(f"delay_first_trimming : {delay_first_trimming}") + period_btw_versions_first = int(extra_params.get("period_btw_versions_first", 10)) + logger.debug(f"period_btw_versions_first : {period_btw_versions_first}") + delay_final_trimming = int(extra_params.get("delay_final_trimming", 180)) + logger.debug(f"delay_final_trimming : {delay_final_trimming}") + period_btw_versions_final = int(extra_params.get("period_btw_versions_final", 60)) + logger.debug(f"period_btw_versions_final : {period_btw_versions_final}") + delete_when_no_run = (extra_params.get("delete_when_no_run", False) is True) + logger.debug(f"delete_when_no_run : {delete_when_no_run}") + + # Find all the runs and group the versions + versions = ccdb.get_versions_list(object_path) + logger.debug(f"Dispatching versions to runs") + for v in versions: + if "RunNumber" in v.metadata: + runs_dict[v.metadata['RunNumber']].append(v) + else: + runs_dict[-1].append(v) # the ones with no run specified + logger.debug(f" Number of runs : {len(runs_dict)}") + logger.debug(f" Number of versions without runs : {len(runs_dict[-1])}") + + # Versions without runs: spare if more recent than the delay + logger.debug(f"Eliminating versions without runs if older than the grace period") + for run_version in runs_dict[-1]: + if not delete_when_no_run or in_grace_period(run_version, delay): + preservation_list.append(run_version) + else: + logger.debug(f" delete {run_version}") + deletion_list.append(run_version) + del runs_dict[-1] # remove this "run" from the list + + # For each run + logger.debug(f"Trimming the versions with a run number") + for run, run_versions in runs_dict.items(): + if run == -1: + continue + logger.debug(f" Processing run {run}") + # TODO get the EOR if it happened, meanwhile we use `eor_dict` or compute first object time + 15 hours + eor = eor_dict.get(int(run), run_versions[0].valid_from_as_dt + timedelta(hours=15)) + logger.debug(f" EOR : {eor}") + + # run is finished for long enough + if eor is not None and datetime.now() > eor + timedelta(minutes=delay_final_trimming): + logger.debug(" Run is over for long enough, let's do the final trimming") + final_trimming(ccdb, period_btw_versions_final, run_versions, preservation_list, update_list, deletion_list, + from_timestamp, to_timestamp) + else: # trim the versions as the run is ongoing or too fresh + logger.debug(" Run is too fresh or still ongoing, we do the first trimming") + first_trimming(ccdb, delay_first_trimming, period_btw_versions_first, run_versions, + preservation_list, update_list, deletion_list, from_timestamp, to_timestamp) + + # deletion + logger.debug(f"Delete but preserve versions that are not in the period passed to the policy") + temp_deletion_list: List[ObjectVersion] = [] + for v in deletion_list: + logger.debug(f" {v}") + if from_timestamp < v.valid_from < to_timestamp: # in the allowed period + temp_deletion_list.append(v) # we will delete any ways + ccdb.delete_version(v) + else: # this should really never happen because we already skipped in the rest of the code but it is to be sure + preservation_list.append(v) # we preserve + deletion_list = temp_deletion_list + + # Print result + logger.debug("*** Results ***") + logger.debug(f"deleted ({len(deletion_list)}) : ") + for v in deletion_list: + logger.debug(f" {v}") + logger.debug(f"preserved ({len(preservation_list)}) : ") + for v in preservation_list: + logger.debug(f" {v}") + logger.debug(f"updated ({len(update_list)}) : ") + for v in update_list: + logger.debug(f" {v}") + + return {"deleted": len(deletion_list), "preserved": len(preservation_list), "updated": len(update_list)} + + +def first_trimming(ccdb, delay_first_trimming, period_btw_versions_first, run_versions, preservation_list, + update_list, deletion_list, from_timestamp, to_timestamp): + last_preserved: ObjectVersion = None + limit_first_trimming = datetime.now() - timedelta(minutes=delay_first_trimming) + metadata = {'trim1': 'done'} + + for v in run_versions: + if not from_timestamp < v.valid_from < to_timestamp: # make sure we are not touching data outside the acceptance period + logger.debug(f" Abort, it is outside the range {v.valid_from}") + preservation_list.append(v) + continue + + if 'trim1' in v.metadata: # check if it is already in the cache + logger.debug(f" Already processed - skip") + last_preserved = v + preservation_list.append(v) + continue + + if v.valid_from_as_dt < limit_first_trimming: # delay for 1st trimming is exhausted + # if it is the first or if it is "far enough" from the previous one + if last_preserved is None or \ + last_preserved.valid_from_as_dt < v.valid_from_as_dt - timedelta(minutes=period_btw_versions_first): + last_preserved = v + preservation_list.append(v) + else: # too close to the previous one, delete + deletion_list.append(v) + logger.debug(f" Possible deletion of {v} (if in the acceptance period)") + else: + preservation_list.append(v) + + +def final_trimming(ccdb, period_btw_versions_final, run_versions, preservation_list, update_list, deletion_list, + from_timestamp, to_timestamp): + # go through the whole run, keep only one version every period_btw_versions_final minutes + last_preserved: ObjectVersion = None + metadata = {'trim1': '', 'preservation':'true'} + for v in run_versions: + logger.debug(f" Processing {v} ") + + if not from_timestamp < v.valid_from < to_timestamp: # make sure we are not touching data outside the acceptance period + logger.debug(" Abort, it is outside the range") + preservation_list.append(v) + continue + + # if it is the first or if the last_preserved is older than `period_btw_versions_final` + if last_preserved is None \ + or last_preserved.valid_from_as_dt < v.valid_from_as_dt - timedelta(minutes=period_btw_versions_final) \ + or v == run_versions[-1]: # v is last element, which we must preserve + if v == run_versions[-1]: # last element, won't be extended but we update the metadata + ccdb.update_validity(v, v.valid_from, v.validTo, metadata) + update_list.append(v) + logger.debug(f" Flag last element with preservation=true") + last_preserved = v + preservation_list.append(v) + else: # too close to the previous one, delete + deletion_list.append(v) + logger.debug(f" Deletion of {v}") + + +def main(): + logger.basicConfig(level=logger.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logger.getLogger().setLevel(int(10)) + + ccdb = Ccdb('http://ccdb-test.cern.ch:8080') + extra = {"delay_first_trimming": "25", "period_btw_versions_first": "11", "delay_final_trimming": "179", + "period_btw_versions_final": "15"} + path = "qc/TST/MO/repo/test" + run = 123456 + + prepare_test_data(ccdb, path, run) + + process(ccdb, path, 15, extra) + + +def prepare_test_data(ccdb, path, run): + current_timestamp = int(time.time() * 1000) + data = {'part': 'part'} + metadata = {'RunNumber': str(run)} + # 1 version every 1 minutes starting 1 hour ago + for x in range(60): + from_ts = current_timestamp - (60 - x) * 60 * 1000 + to_ts = from_ts + 24 * 60 * 60 * 1000 # a day + version_info = ObjectVersion(path=path, valid_from=from_ts, valid_to=to_ts, metadata=metadata) + ccdb.put_version(version=version_info, data=data) + # 1 version every 1 minutes starting 1/2 hour ago WITHOUT run + current_timestamp = int(time.time() * 1000) + metadata = {} + for x in range(30): + from_ts = current_timestamp - (60 - x) * 60 * 1000 + to_ts = from_ts + 24 * 60 * 60 * 1000 # a day + version_info = ObjectVersion(path=path, valid_from=from_ts, valid_to=to_ts, metadata=metadata) + ccdb.put_version(version=version_info, data=data) + + +if __name__ == "__main__": # to be able to run the test code above when not imported. + main() diff --git a/Framework/script/RepoCleaner/qcrepocleaner/rules/skip.py b/Framework/script/RepoCleaner/qcrepocleaner/rules/skip.py new file mode 100644 index 0000000000..390c6f3fae --- /dev/null +++ b/Framework/script/RepoCleaner/qcrepocleaner/rules/skip.py @@ -0,0 +1,28 @@ +import logging +from typing import Dict + +from qcrepocleaner.Ccdb import Ccdb + +logger = logging # default logger + + +def process(ccdb: Ccdb, object_path: str, delay: int, from_timestamp: int, to_timestamp: int, extra_params: Dict[str, str]): + """ + Process this deletion rule on the object. We use the CCDB passed by argument. + + This policy does nothing and allows to preserve some directories. + + :param ccdb: the ccdb in which objects are cleaned up. + :param object_path: path to the object, or pattern, to which a rule will apply. + :param delay: the grace period during which a new object is never deleted. + :param from_timestamp: only objects created after this timestamp are considered. + :param to_timestamp: only objects created before this timestamp are considered. + :param extra_params: a dictionary containing extra parameters (unused in this rule) + :return a dictionary with the number of deleted, preserved and updated versions. Total = deleted+preserved. + """ + + logger.debug(f"Plugin 'skip' processing {object_path}") + + versions = ccdb.getVersionsList(object_path) + + return {"deleted": 0, "preserved": len(versions), "updated": 0} \ No newline at end of file diff --git a/Framework/script/RepoCleaner/requirements.txt b/Framework/script/RepoCleaner/requirements.txt new file mode 100644 index 0000000000..a35d78f800 --- /dev/null +++ b/Framework/script/RepoCleaner/requirements.txt @@ -0,0 +1,14 @@ +certifi==2024.7.4 +chardet==5.2.0 +charset-normalizer==3.3.2 +dryable==1.2.0 +idna==3.7 +psutil==6.1.0 +python-consul==1.1.0 +PyYAML==6.0.1 +requests==2.32.4 +responses==0.25.0 +six==1.16.0 +urllib3==2.6.0 +qcrepocleaner~=1.9 +setuptools~=70.3.0 \ No newline at end of file diff --git a/Framework/script/RepoCleaner/setup.py b/Framework/script/RepoCleaner/setup.py new file mode 100644 index 0000000000..c12b6962bb --- /dev/null +++ b/Framework/script/RepoCleaner/setup.py @@ -0,0 +1,40 @@ +from setuptools import setup, find_packages + +with open('README.md', 'r') as fh: + long_description = fh.read() + +setup( + name='qcrepocleaner', + version='1.10', + author='Barthelemy von Haller', + author_email='bvonhall@cern.ch', + url='https://gitlab.cern.ch/AliceO2Group/QualityControl/Framework/script/RepoCleaner', + license='GPLv3', + description='Set of tools to clean up the QCDB repository.', + long_description=long_description, + long_description_content_type='text/markdown', + packages=find_packages(), + classifiers=[ + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Programming Language :: Python :: 3', + 'Topic :: Utilities', + 'Environment :: Console', + 'Operating System :: Unix', + 'Development Status :: 5 - Production/Stable' + ], + python_requires='>=3.6', + install_requires=['requests', 'dryable', 'responses', 'PyYAML', 'python-consul', 'psutil'], + scripts=[ + 'qcrepocleaner/o2-qc-repo-cleaner', + 'qcrepocleaner/o2-qc-repo-delete-objects-in-runs', + 'qcrepocleaner/o2-qc-repo-delete-not-in-runs', + 'qcrepocleaner/o2-qc-repo-delete-objects', + 'qcrepocleaner/o2-qc-repo-delete-time-interval', + 'qcrepocleaner/o2-qc-repo-find-objects-not-updated', + 'qcrepocleaner/o2-qc-repo-move-objects', + 'qcrepocleaner/o2-qc-repo-update-run-type'], + include_package_data=True, + package_data={ + 'qcrepocleaner': ['qcrepocleaner/config.yaml'] + } +) diff --git a/Framework/script/RepoCleaner/tests/__init__.py b/Framework/script/RepoCleaner/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Framework/script/RepoCleaner/tests/config-test.yaml b/Framework/script/RepoCleaner/tests/config-test.yaml new file mode 100644 index 0000000000..dbb181a4ce --- /dev/null +++ b/Framework/script/RepoCleaner/tests/config-test.yaml @@ -0,0 +1,14 @@ +Rules: + - object_path: asdfasdf/.* # Path in the CCDB to a certain object + delay: 1440 # Delay in minutes during which a new object is not touched. (1 day) + policy: 1_per_hour # name of the policy to apply, must correspond to a python script. + - object_path: QcTask/example + delay: 120 + policy: 1_per_hour + - object_path: qc/TST/MO/repo/test/.* + delay: 60 + policy: production + delete_when_no_run: True + +Ccdb: + Url: http://128.142.249.62:8080 diff --git a/Framework/script/RepoCleaner/tests/objectsList.json b/Framework/script/RepoCleaner/tests/objectsList.json new file mode 100644 index 0000000000..e7698f9f95 --- /dev/null +++ b/Framework/script/RepoCleaner/tests/objectsList.json @@ -0,0 +1,51 @@ +{ + "objects": [ + { + "id": "a4ed3fa0-764a-11e9-8fb5-898a20055566", + "validFrom": "1557839857812", + "validUntil": "1589375857812", + "initialValidity": "1589375857812", + "createTime": "1557839857818", + "lastModified": "1557839857818", + "MD5": "4203f25c8a48368e6328d83171b479f9", + "fileName": "object1_1557839857812.root", + "contentType": "application/octet-stream", + "size": "967", + "path": "Test", + "partName": "send", + "replica0": "http://alissandra01.cern.ch:8080/download/a4ed3fa0-764a-11e9-8fb5-898a20055566" + }, + { + "id": "93699a50-7198-11e9-8d02-200114580202", + "validFrom": "1557323573256", + "validUntil": "1872683573256", + "initialValidity": "1872683573256", + "createTime": "1557323573365", + "lastModified": "1557323573365", + "MD5": "68165ce7dbf446afb97a373fd6bb9ac8", + "fileName": "ChipStav", + "contentType": "application/octet-stream", + "size": "9734", + "path": "ITSQcTask/ChipStaveCheck", + "partName": "send", + "quality": "10&", + "replica0": "http://alissandra01.cern.ch:8080/download/93699a50-7198-11e9-8d02-200114580202" + }, + { + "id": "936b20f0-7198-11e9-8d02-200114580202", + "validFrom": "1557323573366", + "validUntil": "1872683573366", + "initialValidity": "1872683573366", + "createTime": "1557323573375", + "lastModified": "1557323573375", + "MD5": "73675bd66738c2ec362cd05739b30ce6", + "fileName": "ErrorPlo", + "contentType": "application/octet-stream", + "size": "4404", + "path": "ITSQcTask/ErrorPlots", + "partName": "send", + "quality": "10&", + "replica0": "http://alissandra01.cern.ch:8080/download/936b20f0-7198-11e9-8d02-200114580202" + } + ] + } \ No newline at end of file diff --git a/Framework/script/RepoCleaner/tests/test_1_per_hour.py b/Framework/script/RepoCleaner/tests/test_1_per_hour.py new file mode 100644 index 0000000000..cae12fd26f --- /dev/null +++ b/Framework/script/RepoCleaner/tests/test_1_per_hour.py @@ -0,0 +1,85 @@ +import logging +import time +import unittest +from importlib import import_module +from qcrepocleaner.Ccdb import Ccdb +from tests import test_utils +from tests.test_utils import CCDB_TEST_URL + +one_per_hour = import_module(".1_per_hour", "qcrepocleaner.rules") # file names should not start with a number... + +class Test1PerHour(unittest.TestCase): + """ + This test pushes data to the CCDB and then run the Rule test_1_per_run and then check. + It does it for several use cases. + One should truncate /qc/TST/MO/repo/test before running it. + """ + + thirty_minutes = 1800000 + one_hour = 3600000 + in_ten_years = 1975323342000 + one_minute = 60000 + + def setUp(self): + self.ccdb = Ccdb(CCDB_TEST_URL) # ccdb-test but please use IP to avoid DNS alerts + self.path = "qc/TST/MO/repo/test" + self.run = 124321 + self.extra = {} + + + def test_1_per_hour(self): + """ + 120 versions + grace period of 15 minutes + First version is preserved (always). 14 are preserved during the grace period at the end. + One more is preserved after 1 hour. --> 16 preserved + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_1_per_hour" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [120], [0], 123) + + stats = one_per_hour.process(self.ccdb, test_path, 15, 1, self.in_ten_years, self.extra) + logging.info(stats) + self.assertEqual(stats["deleted"], 104) + self.assertEqual(stats["preserved"], 16) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 16) + + + def test_1_per_hour_period(self): + """ + 120 versions. + no grace period. + period of acceptance: 1 hour in the middle. + We have therefore 60 versions in the acceptance period. + Only 1 of them, the one 1 hour after the first version in the set, will be preserved, the others are deleted. + Thus, we have 59 deletion. Everything outside the acceptance period is kept. + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_1_per_hour_period" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [120], [0], 123) + current_timestamp = int(time.time() * 1000) + + stats = one_per_hour.process(self.ccdb, test_path, 15, current_timestamp-90*60*1000, + current_timestamp-30*60*1000, self.extra) + logging.info(stats) + self.assertEqual(stats["deleted"], 59) + self.assertEqual(stats["preserved"], 61) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 61) + + +if __name__ == '__main__': + unittest.main() diff --git a/Framework/script/RepoCleaner/tests/test_1_per_run.py b/Framework/script/RepoCleaner/tests/test_1_per_run.py new file mode 100644 index 0000000000..c6f6f9feef --- /dev/null +++ b/Framework/script/RepoCleaner/tests/test_1_per_run.py @@ -0,0 +1,82 @@ +import logging +import time +import unittest +from importlib import import_module + +from qcrepocleaner.Ccdb import Ccdb +from tests import test_utils +from tests.test_utils import CCDB_TEST_URL + +one_per_run = import_module(".1_per_run", "qcrepocleaner.rules") # file names should not start with a number... + +class Test1PerRun(unittest.TestCase): + """ + This test pushes data to the CCDB and then run the Rule test_1_per_run and then check. + It does it for several use cases. + One should truncate /qc/TST/MO/repo/test before running it. + """ + + thirty_minutes = 1800000 + one_hour = 3600000 + in_ten_years = 1975323342000 + one_minute = 60000 + + def setUp(self): + self.ccdb = Ccdb(CCDB_TEST_URL) + self.path = "qc/TST/MO/repo/test" + self.run = 124321 + self.extra = {} + + def test_1_per_run(self): + """ + 6 runs of 10 versions, versions 1 minute apart + grace period of 15 minutes + Preserved: 14 at the end (grace period), 6 for the runs, but 2 are in both sets --> 14+6-2=18 preserved + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_1_per_run" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [10, 10, 10, 10, 10, 10], [0, 0, 0, 0, 0, 0], 123) + + objects_versions = self.ccdb.get_versions_list(test_path) + created = len(objects_versions) + + stats = one_per_run.process(self.ccdb, test_path, 15, 1, self.in_ten_years, self.extra) + self.assertEqual(stats["deleted"], 42) + self.assertEqual(stats["preserved"], 18) + self.assertEqual(created, stats["deleted"] + stats["preserved"]) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 18) + + def test_1_per_run_period(self): + """ + 6 runs of 10 versions each, versions 1 minute apart + no grace period + acceptance period is only the 38 minutes in the middle + preserved: 6 runs + 11 first and 11 last, with an overlap of 2 --> 26 + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_1_per_run_period" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [10, 10, 10, 10, 10, 10], [0, 0, 0, 0, 0, 0], 123) + current_timestamp = int(time.time() * 1000) + + stats = one_per_run.process(self.ccdb, test_path, 0, current_timestamp - 49 * 60 * 1000, + current_timestamp - 11 * 60 * 1000, self.extra) + self.assertEqual(stats["deleted"], 34) + self.assertEqual(stats["preserved"], 26) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 26) + +if __name__ == '__main__': + unittest.main() diff --git a/Framework/script/RepoCleaner/tests/test_Ccdb.py b/Framework/script/RepoCleaner/tests/test_Ccdb.py new file mode 100644 index 0000000000..b01f8f5beb --- /dev/null +++ b/Framework/script/RepoCleaner/tests/test_Ccdb.py @@ -0,0 +1,48 @@ +import logging +import unittest +from typing import List + +import responses + +from qcrepocleaner.Ccdb import Ccdb, ObjectVersion +from tests.test_utils import CCDB_TEST_URL + + +class TestCcdb(unittest.TestCase): + + def setUp(self): + with open('objectsList.json') as f: # will close() when we leave this block + self.content_objects_list = f.read() + with open('versionsList.json') as f: # will close() when we leave this block + self.content_versions_list = f.read() + self.ccdb = Ccdb(CCDB_TEST_URL) + logging.getLogger().setLevel(logging.DEBUG) + + @responses.activate + def test_getObjectsList(self): + # Prepare mock response + responses.add(responses.GET, CCDB_TEST_URL + '/latest/.*', + self.content_objects_list, status=200) + # get list of objects + objects_list = self.ccdb.get_objects_list() + print(f"{objects_list}") + self.assertEqual(len(objects_list), 3) + self.assertEqual(objects_list[0], 'Test') + self.assertEqual(objects_list[1], 'ITSQcTask/ChipStaveCheck') + + @responses.activate + def test_getVersionsList(self): + # Prepare mock response + object_path='asdfasdf/example' + responses.add(responses.GET, CCDB_TEST_URL + '/browse/' + object_path, + self.content_versions_list, status=200) + # get versions for object + versions_list: List[ObjectVersion] = self.ccdb.get_versions_list(object_path) + print(f"{versions_list}") + self.assertEqual(len(versions_list), 2) + self.assertEqual(versions_list[0].path, object_path) + self.assertEqual(versions_list[1].path, object_path) + self.assertEqual(versions_list[1].metadata["custom"], "34") + +if __name__ == '__main__': + unittest.main() diff --git a/Framework/script/RepoCleaner/tests/test_MultiplePerRun.py b/Framework/script/RepoCleaner/tests/test_MultiplePerRun.py new file mode 100644 index 0000000000..8095148175 --- /dev/null +++ b/Framework/script/RepoCleaner/tests/test_MultiplePerRun.py @@ -0,0 +1,173 @@ +import logging +import time +import unittest + +from tests import test_utils +from qcrepocleaner.Ccdb import Ccdb +from qcrepocleaner.rules import multiple_per_run + + +class TestMultiplePerRun(unittest.TestCase): + """ + This test pushes data to the CCDB and then run the Rule Production and then check. + It does it for several use cases. + One should truncate /qc/TST/MO/repo/test before running it. + """ + + thirty_minutes = 1800000 + one_hour = 3600000 + in_ten_years = 1975323342000 + one_minute = 60000 + + def setUp(self): + self.ccdb = Ccdb(test_utils.CCDB_TEST_URL) # ccdb-test but please use IP to avoid DNS alerts + self.extra = {"interval_between_versions": "90", "migrate_to_EOS": False} + self.path = "qc/TST/MO/repo/test" + + def test_1_finished_run(self): + """ + 1 run of 2.5 hours finished 22 hours ago. + Expected output: SOR, EOR, 1 in the middle + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_1_finished_run" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [150], [22*60], 123) + + stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + to_timestamp=self.in_ten_years, extra_params=self.extra) + + self.assertEqual(stats["deleted"], 147) + self.assertEqual(stats["preserved"], 3) + self.assertEqual(stats["updated"], 0) + + def test_2_runs(self): + """ + 2 runs of 2.5 hours, separated by 3 hours, second finished 20h ago. + Expected output: SOR, EOR, 1 in the middle for the first one, all for the second + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_2_runs" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [150, 150], [3 * 60, 20 * 60], 123) + + stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + to_timestamp=self.in_ten_years, extra_params=self.extra) + + self.assertEqual(stats["deleted"], 147) + self.assertEqual(stats["preserved"], 3+150) + self.assertEqual(stats["updated"], 0) + + # def test_5_runs(self): + # """ + # 1 hour Run - 1h - 2 hours Run - 2h - 3h10 run - 3h10 - 4 hours run - 4 hours - 5 hours run - 5 h + # All more than 24 hours + # Expected output: 2 + 3 + 4 + 4 + 5 + # """ + # logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + # datefmt='%d-%b-%y %H:%M:%S') + # logging.getLogger().setLevel(int(10)) + # + # # Prepare data + # test_path = self.path + "/test_5_runs" + # test_utils.clean_data(self.ccdb, test_path) + # test_utils.prepare_data(self.ccdb, test_path, [1*60, 2*60, 3*60+10, 4*60, 5*60], + # [60, 120, 190, 240, 24*60], 123) + # + # stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + # to_timestamp=self.in_ten_years, extra_params=self.extra) + # self.assertEqual(stats["deleted"], 60+120+190+240+300-18) + # self.assertEqual(stats["preserved"], 18) + # self.assertEqual(stats["updated"], 0) + # + # # and now re-run it to make sure we preserve the state + # stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + # to_timestamp=self.in_ten_years, extra_params=self.extra) + # + # self.assertEqual(stats["deleted"], 0) + # self.assertEqual(stats["preserved"], 18) + # self.assertEqual(stats["updated"], 0) + + def test_run_one_object(self): + """ + A run with a single object + Expected output: keep the object + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_run_one_object" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [1], [25*60], 123) + + stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + to_timestamp=self.in_ten_years, extra_params=self.extra) + + self.assertEqual(stats["deleted"], 0) + self.assertEqual(stats["preserved"], 1) + self.assertEqual(stats["updated"], 0) + + def test_run_two_object(self): + """ + A run with 2 objects + Expected output: keep the 2 objects + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_run_two_object" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [2], [25*60], 123) + + stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + to_timestamp=self.in_ten_years, extra_params=self.extra) + + self.assertEqual(stats["deleted"], 0) + self.assertEqual(stats["preserved"], 2) + self.assertEqual(stats["updated"], 0) + + def test_3_runs_with_period(self): + """ + 3 runs more than 24h in the past but only the middle one starts in the period that is allowed. + Expected output: second run is trimmed, not the other + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_3_runs_with_period" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [30,30, 30], [120,120,25*60], 123) + + current_timestamp = int(time.time() * 1000) + stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=current_timestamp-29*60*60*1000, + to_timestamp=current_timestamp-26*60*60*1000, extra_params=self.extra) + + self.assertEqual(stats["deleted"], 28) + self.assertEqual(stats["preserved"], 90-28) + self.assertEqual(stats["updated"], 0) + + def test_asdf(self): + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + test_path = self.path + "/asdf" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [70, 70, 70], [6*60, 6*60, 25*60], 55555) + + +if __name__ == '__main__': + unittest.main() diff --git a/Framework/script/RepoCleaner/tests/test_MultiplePerRun_deleteFirstLast.py b/Framework/script/RepoCleaner/tests/test_MultiplePerRun_deleteFirstLast.py new file mode 100644 index 0000000000..61b92c5867 --- /dev/null +++ b/Framework/script/RepoCleaner/tests/test_MultiplePerRun_deleteFirstLast.py @@ -0,0 +1,179 @@ +import logging +import time +import unittest + +from qcrepocleaner.Ccdb import Ccdb +from qcrepocleaner.rules import multiple_per_run +from tests import test_utils +from tests.test_utils import CCDB_TEST_URL + + +class TestMultiplePerRunDeleteFirstLast(unittest.TestCase): + """ + This test pushes data to the CCDB and then run the Rule Production and then check. + It does it for several use cases. + One should truncate /qc/TST/MO/repo/test before running it. + """ + + thirty_minutes = 1800000 + one_hour = 3600000 + in_ten_years = 1975323342000 + one_minute = 60000 + + def setUp(self): + self.ccdb = Ccdb(CCDB_TEST_URL) # ccdb-test but please use IP to avoid DNS alerts + self.extra = {"interval_between_versions": "90", "migrate_to_EOS": False, "delete_first_last": True} + self.path = "qc/TST/MO/repo/test" + + def test_1_finished_run(self): + """ + 1 run of 2.5 hours finished 22 hours ago. + Expected output: SOR, EOR, 1 in the middle + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_1_finished_run" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [150], [22*60], 123) + objects_before = self.ccdb.get_versions_list(test_path) + + stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + to_timestamp=self.in_ten_years, extra_params=self.extra) + objects_after = self.ccdb.get_versions_list(test_path) + + self.assertEqual(stats["deleted"], 147) + self.assertEqual(stats["preserved"], 3) + self.assertEqual(stats["updated"], 0) + + self.assertEqual(objects_after[0].valid_from, objects_before[1].valid_from) + self.assertEqual(objects_after[2].valid_from, objects_before[-2].valid_from) + + def test_2_runs(self): + """ + 2 runs of 2.5 hours, separated by 3 hours, second finished 20h ago. + Expected output: SOR, EOR, 1 in the middle for the first one, all for the second + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_2_runs" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [150, 150], [3*60, 20*60], 123) + + stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + to_timestamp=self.in_ten_years, extra_params=self.extra) + + self.assertEqual(stats["deleted"], 147) + self.assertEqual(stats["preserved"], 3+150) + self.assertEqual(stats["updated"], 0) + + # def test_5_runs(self): + # """ + # 1 hour Run - 1h - 2 hours Run - 2h - 3h10 run - 3h10 - 4 hours run - 4 hours - 5 hours run - 5 h + # All more than 24 hours + # Expected output: 2 + 3 + 4 + 4 + 5 + # """ + # logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + # datefmt='%d-%b-%y %H:%M:%S') + # logging.getLogger().setLevel(int(10)) + # + # # Prepare data + # test_path = self.path + "/test_5_runs" + # test_utils.clean_data(self.ccdb, test_path) + # test_utils.prepare_data(self.ccdb, test_path, [1*60, 2*60, 3*60+10, 4*60, 5*60], + # [60, 120, 190, 240, 24*60], 123) + # + # stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + # to_timestamp=self.in_ten_years, extra_params=self.extra) + # self.assertEqual(stats["deleted"], 60+120+190+240+300-18) + # self.assertEqual(stats["preserved"], 18) + # self.assertEqual(stats["updated"], 0) + # + # # and now re-run it to make sure we preserve the state + # stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + # to_timestamp=self.in_ten_years, extra_params=self.extra) + # + # self.assertEqual(stats["deleted"], 0) + # self.assertEqual(stats["preserved"], 18) + # self.assertEqual(stats["updated"], 0) + + def test_run_one_object(self): + """ + A run with a single object + Expected output: keep the object + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_run_one_object" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [1], [25*60], 123) + + stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + to_timestamp=self.in_ten_years, extra_params=self.extra) + + self.assertEqual(stats["deleted"], 0) + self.assertEqual(stats["preserved"], 1) + self.assertEqual(stats["updated"], 0) + + def test_run_two_object(self): + """ + A run with 2 objects + Expected output: keep the 2 objects + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_run_two_object" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [2], [25*60], 123) + + stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=1, + to_timestamp=self.in_ten_years, extra_params=self.extra) + + self.assertEqual(stats["deleted"], 0) + self.assertEqual(stats["preserved"], 2) + self.assertEqual(stats["updated"], 0) + + def test_3_runs_with_period(self): + """ + 3 runs more than 24h in the past but only the middle one starts in the period that is allowed. + Expected output: second run is trimmed, not the other + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_3_runs_with_period" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [30,30, 30], [120,120,25*60], 123) + + current_timestamp = int(time.time() * 1000) + stats = multiple_per_run.process(self.ccdb, test_path, delay=60*24, from_timestamp=current_timestamp-29*60*60*1000, + to_timestamp=current_timestamp-26*60*60*1000, extra_params=self.extra) + + self.assertEqual(stats["deleted"], 28) + self.assertEqual(stats["preserved"], 90-28) + self.assertEqual(stats["updated"], 0) + + def test_asdf(self): + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + test_path = self.path + "/asdf" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [70, 70, 70], [6*60, 6*60, 25*60], 55555) + + +if __name__ == '__main__': + unittest.main() diff --git a/Framework/script/RepoCleaner/tests/test_Production.py b/Framework/script/RepoCleaner/tests/test_Production.py new file mode 100644 index 0000000000..261f5ebc0e --- /dev/null +++ b/Framework/script/RepoCleaner/tests/test_Production.py @@ -0,0 +1,276 @@ +import logging +import time +import unittest +from datetime import timedelta, datetime + +from qcrepocleaner.Ccdb import Ccdb, ObjectVersion +from qcrepocleaner.rules import production +from tests import test_utils +from tests.test_utils import CCDB_TEST_URL + + +class TestProduction(unittest.TestCase): + """ + This test pushes data to the CCDB and then run the Rule Production and then check. + It does it for several use cases. + One should truncate /qc/TST/MO/repo/test before running it. + """ + + thirty_minutes = 1800000 + one_hour = 3600000 + in_ten_years = 1975323342000 + one_minute = 60000 + + def setUp(self): + self.ccdb = Ccdb(CCDB_TEST_URL) + self.extra = {"delay_first_trimming": "30", "period_btw_versions_first": "10", "delay_final_trimming": "60", + "period_btw_versions_final": "60"} + self.path = "qc/TST/MO/repo/test" + self.run = 124321 + + def test_start_run(self): + """ + Ongoing run (25 minutes, 26 versions). + 0'-25': not trimmed + Expected output: nothing trimmed, then change parameter delay_first_trimming to 5' and trim + In the second processing, preserved: 5 at the end (delay_first_trimming) + 1 at the beginning + 1 after 10 min. + --> 7 + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_start_run" + test_utils.clean_data(self.ccdb, test_path) + self.prepare_data_for_prod_test(test_path, 25, 30, True, 60, False) + + production.eor_dict.pop(int(self.run), None) + stats = production.process(self.ccdb, test_path, 30, 1, self.in_ten_years, self.extra) + self.assertEqual(stats["deleted"], 0) + self.assertEqual(stats["preserved"], 26) + self.assertEqual(stats["updated"], 0) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 26) + + self.extra = {"delay_first_trimming": "5", "period_btw_versions_first": "10", "delay_final_trimming": "60", + "period_btw_versions_final": "15"} + stats = production.process(self.ccdb, test_path, 30, 1, self.in_ten_years, self.extra) + self.assertEqual(stats["deleted"], 19) + self.assertEqual(stats["preserved"], 7) + self.assertEqual(stats["updated"], 1) + + def test_start_run_period(self): + """ + Ongoing run (25 minutes, 26 versions). + 0'-25': not trimmed + two processings: the first one has a period that does not match anything in the run and the second has + the period at SOR+5 and SOR+20 + Preserved 2nd processing: 6 at the end (delay_first_trimming and outside period) + 6 at the end (outside period) + + the first in the period + 1 after 10 min + --> 14 + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_start_run_period" + test_utils.clean_data(self.ccdb, test_path) + first_ts = self.prepare_data_for_prod_test(test_path, 25, 30, True, 60, False) + logging.getLogger().debug(f"first_ts : {first_ts}") + + # everything outside the period + self.extra = {"delay_first_trimming": "5", "period_btw_versions_first": "10", "delay_final_trimming": "60", + "period_btw_versions_final": "15"} + stats = production.process(self.ccdb, test_path, 30, 1, 10, self.extra) + self.assertEqual(stats["deleted"], 0) + self.assertEqual(stats["preserved"], 26) + self.assertEqual(stats["updated"], 0) + + # with a period from 5 to 20 + self.extra = {"delay_first_trimming": "5", "period_btw_versions_first": "10", "delay_final_trimming": "60", + "period_btw_versions_final": "15"} + stats = production.process(self.ccdb, test_path, 30, first_ts + self.one_minute * 5, + first_ts + self.one_minute * 20, self.extra) + self.assertEqual(stats["deleted"], 12) + self.assertEqual(stats["preserved"], 14) + self.assertEqual(stats["updated"], 1) + + + def test_mid_run(self): + """ + Ongoing run (1h30). + 0-30' : already trimmed + 30-90': not trimmed + defaults params are used (delay_first_trimming = 30, period_btw_versions_first= 10) + Expected output: 0-60' trimmed + preserved: the 4 at the beginning already trimmed, 3 for the beginning of the run, 28 for the last 30 minutes + (delay_first_trimming) + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_mid_run" + test_utils.clean_data(self.ccdb, test_path) + self.prepare_data_for_prod_test(test_path, 90) + + production.eor_dict.pop(int(self.run), None) + stats = production.process(self.ccdb, test_path, 30, 1, self.in_ten_years, self.extra) + self.assertEqual(stats["deleted"], 28) + self.assertEqual(stats["preserved"], 35) + self.assertEqual(stats["updated"], 3) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 35) + self.assertTrue("trim1" in objects_versions[0].metadata) + + def test_mid_run_period(self): + """ + Ongoing run (1h30). + 0-30' : already trimmed + 30-90': not trimmed + only applies to the period: sOR+35 - SOR+80 + defaults params are used (delay_first_trimming = 30, period_btw_versions_first= 10) + Expected output: 0-60' trimmed + preserved: the 4 at the beginning already trimmed, 5 outside the period at the beginning, + 3 for the beginning of the run, 29 for the last 30 minutes + (delay_first_trimming) + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_mid_run_period" + test_utils.clean_data(self.ccdb, test_path) + first_ts = self.prepare_data_for_prod_test(test_path, 90) + logging.getLogger().debug(f"first_ts : {first_ts}") + + objects_versions = self.ccdb.get_versions_list(test_path) + created = len(objects_versions) + + production.eor_dict.pop(int(self.run), None) + stats = production.process(self.ccdb, test_path, 30, first_ts + 35 * self.one_minute, first_ts + 80 * self.one_minute, self.extra) + self.assertEqual(stats["deleted"], 22) + self.assertEqual(stats["preserved"], 41) + self.assertEqual(stats["updated"], 2) + self.assertEqual(created, stats["deleted"]+stats["preserved"]) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 41) + self.assertTrue("trim1" in objects_versions[0].metadata) + + def test_run_finished(self): + """ + Finished run (3h10). + 0'-190': trimmed + running after delay for final trimming + Expected output: 4 versions : SOR, EOR, 2 at 1 hour interval + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_run_finished" + test_utils.clean_data(self.ccdb, test_path) + self.prepare_data_for_prod_test(test_path, 290, 190, False, 0, True) + + production.eor_dict[int(self.run)] = datetime.now() - timedelta(minutes=100) + stats = production.process(self.ccdb, test_path, 30, 1, self.in_ten_years, self.extra) + self.assertEqual(stats["deleted"], 15) + self.assertEqual(stats["preserved"], 4) + self.assertEqual(stats["updated"], 4) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 4) + self.assertTrue("trim1" not in objects_versions[0].metadata) + self.assertTrue("preservation" in objects_versions[0].metadata) + + def test_run_finished_period(self): + """ + Finished run (3h10). + 0'-190': trimmed + running after delay for final trimming + Only the data after SOR+30 minutes is processed + Expected output: 4 versions : SOR, EOR, 4 outside the period, 1 in the middle of the period + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_run_finished_period" + test_utils.clean_data(self.ccdb, test_path) + first_ts = self.prepare_data_for_prod_test(test_path, 290, 190, False, 0, True) + logging.getLogger().debug(f"first_ts : {first_ts}") + + production.eor_dict[int(self.run)] = datetime.now() - timedelta(minutes=100) + stats = production.process(self.ccdb, test_path, 30, first_ts+self.thirty_minutes, self.in_ten_years, self.extra) + self.assertEqual(stats["deleted"], 12) + self.assertEqual(stats["preserved"], 7) + self.assertEqual(stats["updated"], 3) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 7) + logging.debug(f"{objects_versions[0].metadata}") + self.assertTrue("trim1" in objects_versions[0].metadata) # we did not touch this data thus it is not processed + self.assertTrue("trim1" not in objects_versions[6].metadata) + self.assertTrue("preservation" in objects_versions[6].metadata) + + + def prepare_data_for_prod_test(self, path, minutes_since_sor, duration_first_part=30, skip_first_part=False, + minutes_second_part=60, skip_second_part=False): + """ + Prepare a data set starting `minutes_since_sor` in the past. + The data is layed out in two parts + 1. `duration_first_part` minutes already trimmed data (every 10 minutes) + 2. `minutes_second_part` minutes untrimmed + Each part can be skipped with the respective parameter. + Depending how far in the past one starts, different outputs of the production rule are expected. + If `minutes_since_sor` is shorter than the run, we only create data in the past, never in the future. + """ + + current_timestamp = int(time.time() * 1000) + sor = current_timestamp - minutes_since_sor * 60 * 1000 + data = {'part': 'part'} + metadata = {'RunNumber': str(self.run), 'trim1': 'true'} + cursor = sor + first_ts = 1975323342000 + + # 1 version every 10 minutes starting at sor and finishing duration_first_part minutes after with trim1 flag set + if not skip_first_part: + for x in range(int(round(duration_first_part / 10))): + from_ts = cursor + x * 10 * 60 * 1000 + if from_ts > current_timestamp: + return first_ts + if first_ts > from_ts: + first_ts = from_ts + to_ts = from_ts + 24 * 60 * 60 * 1000 # a day + version_info = ObjectVersion(path=path, valid_from=from_ts, created_at=from_ts, valid_to=to_ts, metadata=metadata) + self.ccdb.put_version(version=version_info, data=data) + cursor = cursor + duration_first_part * 60 * 1000 + + # 1 version every 1 minutes starting after and lasting `minutes_second_part` minutes + if not skip_second_part: + current_timestamp = int(time.time() * 1000) + metadata = {'RunNumber': str(self.run)} + for x in range(minutes_second_part): + from_ts = cursor + x * 60 * 1000 + if from_ts > current_timestamp: + return first_ts + if first_ts > from_ts: + first_ts = from_ts + to_ts = from_ts + 24 * 60 * 60 * 1000 # a day + version_info = ObjectVersion(path=path, valid_from=from_ts, created_at=from_ts, valid_to=to_ts, metadata=metadata) + self.ccdb.put_version(version=version_info, data=data) + + return first_ts + + +if __name__ == '__main__': + unittest.main() diff --git a/Framework/script/RepoCleaner/tests/test_last_only.py b/Framework/script/RepoCleaner/tests/test_last_only.py new file mode 100644 index 0000000000..2e161b0965 --- /dev/null +++ b/Framework/script/RepoCleaner/tests/test_last_only.py @@ -0,0 +1,77 @@ +import logging +import time +import unittest + +from qcrepocleaner.Ccdb import Ccdb +from qcrepocleaner.rules import last_only +from tests import test_utils +from tests.test_utils import CCDB_TEST_URL + + +class TestLastOnly(unittest.TestCase): + """ + This test pushes data to the CCDB and then run the Rule last_only and then check. + It does it for several use cases. + One should truncate /qc/TST/MO/repo/test before running it. + """ + + thirty_minutes = 1800000 + one_hour = 3600000 + in_ten_years = 1975323342000 + one_minute = 60000 + + def setUp(self): + self.ccdb = Ccdb(CCDB_TEST_URL) # ccdb-test but please use IP to avoid DNS alerts + self.extra = {} + self.path = "qc/TST/MO/repo/test" + self.run = 124321 + + + def test_last_only(self): + """ + 60 versions + grace period of 30 minutes + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_last_only" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [60], [0], 123) + + stats = last_only.process(self.ccdb, test_path, 30, 1, self.in_ten_years, self.extra) + self.assertEqual(stats["deleted"], 31) # 31 because between the time we produced the 60 versions and now, there is a shift + self.assertEqual(stats["preserved"], 29) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 29) + + + def test_last_only_period(self): + """ + 60 versions + no grace period + only 20 minutes in the middle are in the period + """ + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', + datefmt='%d-%b-%y %H:%M:%S') + logging.getLogger().setLevel(int(10)) + + # Prepare data + test_path = self.path + "/test_last_only_period" + test_utils.clean_data(self.ccdb, test_path) + test_utils.prepare_data(self.ccdb, test_path, [60], [0], 123) + current_timestamp = int(time.time() * 1000) + + stats = last_only.process(self.ccdb, test_path, 0, current_timestamp-40*60*1000, current_timestamp-20*60*1000, self.extra) + self.assertEqual(stats["deleted"], 20) + self.assertEqual(stats["preserved"], 40) + + objects_versions = self.ccdb.get_versions_list(test_path) + self.assertEqual(len(objects_versions), 40) + + +if __name__ == '__main__': + unittest.main() diff --git a/Framework/script/RepoCleaner/tests/test_repoCleaner.py b/Framework/script/RepoCleaner/tests/test_repoCleaner.py new file mode 100644 index 0000000000..24d9ad06e9 --- /dev/null +++ b/Framework/script/RepoCleaner/tests/test_repoCleaner.py @@ -0,0 +1,75 @@ +import importlib +import os +import sys +import unittest +from importlib.machinery import SourceFileLoader +from importlib.util import spec_from_loader + +import yaml + +from tests.test_utils import CCDB_TEST_URL + + +def import_path(path): # needed because o2-qc-repo-cleaner has no suffix + module_name = os.path.basename(path).replace('-', '_') + spec = importlib.util.spec_from_loader( + module_name, + importlib.machinery.SourceFileLoader(module_name, path) + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + sys.modules[module_name] = module + return module + +repoCleaner = import_path("../qcrepocleaner/o2-qc-repo-cleaner") +parse_config = repoCleaner.parse_config +Rule = repoCleaner.Rule +find_matching_rules = repoCleaner.find_matching_rules + + +class TestRepoCleaner(unittest.TestCase): + + def test_parseConfig(self): + args = parse_config("config-test.yaml") + self.assertEqual(args['ccdb_url'], CCDB_TEST_URL) + rules = args['rules'] + self.assertEqual(len(rules), 3) + + def test_parseConfigFault(self): + document = """ + asdf + asdf asdf + Rulesss: + - object_path: asdfasdf/.* + delay: 1440 + policy: 1_per_hour + - object_path: QcTask/example + delay: 120 + policy: 1_per_hour + + """ + destination = "/tmp/faulty-config.yaml" + f = open(destination, "w+") + f.write(document) + f.close() + with self.assertRaises(yaml.YAMLError): + parse_config(destination) + + # now remove the faulty 2 lines at the beginning + with open(destination, 'w+') as out: + out.writelines(document.splitlines(True)[3:]) + with self.assertRaises(KeyError): + parse_config(destination) + + def test_findMatchingRule(self): + rules = [Rule('task1/obj1', '120', 'policy1'), Rule('task1/obj1', '120', 'policy2'), Rule('task2/.*', '120', 'policy3')] + self.assertEqual(find_matching_rules(rules, 'task1/obj1')[0].policy, 'policy1') + self.assertNotEqual(find_matching_rules(rules, 'task1/obj1')[0].policy, 'policy2') + self.assertEqual(find_matching_rules(rules, 'task3/obj1'), []) + self.assertEqual(find_matching_rules(rules, 'task2/obj1/obj1')[0].policy, 'policy3') + rules.append(Rule('.*', '0', 'policyAll')) + self.assertEqual(find_matching_rules(rules, 'task3/obj1')[0].policy, 'policyAll') + + +if __name__ == '__main__': + unittest.main() diff --git a/Framework/script/RepoCleaner/tests/test_utils.py b/Framework/script/RepoCleaner/tests/test_utils.py new file mode 100644 index 0000000000..a3133437c9 --- /dev/null +++ b/Framework/script/RepoCleaner/tests/test_utils.py @@ -0,0 +1,55 @@ +import logging +import time +from typing import List + +from qcrepocleaner.Ccdb import ObjectVersion + +CCDB_TEST_URL = 'http://128.142.249.62:8080' + +def clean_data(ccdb, path): + versions = ccdb.get_versions_list(path) + for v in versions: + ccdb.delete_version(v) + + +def prepare_data(ccdb, path, run_durations: List[int], time_till_next_run: List[int], first_run_number: int): + """ + Prepare a data set populated with a number of runs. + run_durations contains the duration of each of these runs in minutes + time_till_next_run is the time between two runs in minutes. + The first element of time_till_next_run is used to separate the first two runs. + Both lists must have the same number of elements. + """ + + if len(run_durations) != len(time_till_next_run): + logging.error(f"run_durations and time_till_next_run must have the same length") + exit(1) + + total_duration = 0 + for a, b in zip(run_durations, time_till_next_run): + total_duration += a + b + logging.info(f"Total duration : {total_duration}") + + current_timestamp = int(time.time() * 1000) + cursor = current_timestamp - total_duration * 60 * 1000 + first_ts = cursor + data = {'part': 'part'} + run = first_run_number + + for run_duration, time_till_next in zip(run_durations, time_till_next_run): + metadata = {'RunNumber': str(run)} + logging.debug(f"cursor: {cursor}") + logging.debug(f"time_till_next: {time_till_next}") + + for i in range(run_duration): + to_ts = cursor + 24 * 60 * 60 * 1000 # a day + metadata2 = {**metadata, 'Created': str(cursor)} + version_info = ObjectVersion(path=path, valid_from=cursor, valid_to=to_ts, metadata=metadata2, + created_at=cursor) + ccdb.put_version(version=version_info, data=data) + cursor += 1 * 60 * 1000 + + run += 1 + cursor += time_till_next * 60 * 1000 + + return first_ts diff --git a/Framework/script/RepoCleaner/tests/versionsList.json b/Framework/script/RepoCleaner/tests/versionsList.json new file mode 100644 index 0000000000..f5f0a8887a --- /dev/null +++ b/Framework/script/RepoCleaner/tests/versionsList.json @@ -0,0 +1,41 @@ +{ + "objects": [ + { + "id": "0c576bb0-7304-11e9-8d02-200114580202", + "validFrom": "1557479683554", + "Created": "1557479683554", + "validUntil": "1872839683554", + "initialValidity": "1872839683554", + "createTime": "1557479683563", + "lastModified": "1557479683563", + "MD5": "2c04ab14a46eda0a51d803577e3d6f61", + "fileName": "example_1557479683554.root", + "contentType": "application/octet-stream", + "size": "907", + "path": "asdfasdf/example", + "partName": "send", + "custom": "34", + "quality": "10", + "replica0": "http://alissandra01.cern.ch:8080/download/0c576bb0-7304-11e9-8d02-200114580202" + }, + { + "id": "06fb1e80-72f7-11e9-8d02-200114580202", + "validFrom": "1557474091106", + "Created": "1557474091106", + "validUntil": "1557479683553", + "initialValidity": "1872834091106", + "createTime": "1557474091112", + "lastModified": "1557474091112", + "MD5": "a1ae90a7a0a2d60fc38f498c9bb13596", + "fileName": "example_1557474091106.root", + "contentType": "application/octet-stream", + "size": "887", + "path": "asdfasdf/example", + "UpdatedFrom": "2001:1458:202:32:0:0:101:cd6b", + "partName": "send", + "quality": "10", + "replica0": "http://alissandra01.cern.ch:8080/download/06fb1e80-72f7-11e9-8d02-200114580202" + } + ], + "subfolders": [] +} \ No newline at end of file diff --git a/Framework/script/__init__.py b/Framework/script/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Framework/script/o2-qc-batch-test.sh b/Framework/script/o2-qc-batch-test.sh new file mode 100755 index 0000000000..4316d2d592 --- /dev/null +++ b/Framework/script/o2-qc-batch-test.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +set -e +set -x +set -u +set -m +# Arguments or expected variables +# UNIQUE_ID must be set. +# JSON_DIR must be set and point to the directory containing batch-test.json. + +# this is to make sure that we do not leave child processes behind +# https://unix.stackexchange.com/questions/240723/exit-trap-in-dash-vs-ksh-and-bash/240736#240736 +cleanup() { + # kill all processes whose parent is this process + pkill -P $$ +} +for sig in INT QUIT HUP TERM; do + trap " + cleanup + trap - $sig EXIT + kill -s $sig "'"$$"' "$sig" +done +#trap cleanup EXIT + +function delete_data() { + curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/MO/BatchTestTask${UNIQUE_ID}* + curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/QO/BatchTestCheck${UNIQUE_ID}* + + rm -f /tmp/batch_test_mergedA${UNIQUE_ID}.root + rm -f /tmp/batch_test_mergedB${UNIQUE_ID}.root + rm -f /tmp/batch_test_mergedC${UNIQUE_ID}.root + rm -f /tmp/batch_test_obj${UNIQUE_ID}.root + rm -f /tmp/batch_test_obj_mw${UNIQUE_ID}.root + rm -f /tmp/batch_test_check${UNIQUE_ID}.root +} + +if [ -z "$UNIQUE_ID" ] +then + echo "UNIQUE_ID must be set when calling o2-qc-batch-test.sh" + exit 1 +fi +if [ -z "$JSON_DIR" ] +then + echo "JSON_DIR must be set when calling o2-qc-batch-test.sh" + exit 2 +fi + +# make sure the CCDB is available otherwise we bail (no failure) +# we do not use ping because it will fail from outside CERN. +if curl --silent --connect-timeout 1 ccdb-test.cern.ch:8080 > /dev/null 2>&1 ; then + echo "CCDB is reachable." +else + echo "CCDB not reachable, batch test is cancelled." + exit 0 +fi + +delete_data + +# Run the Tasks 3 times, including twice with the same file. +o2-qc-run-producer --message-amount 100 --message-rate 100 | o2-qc --config json:/${JSON_DIR}/batch-test.json --local-batch /tmp/batch_test_mergedA${UNIQUE_ID}.root --run +o2-qc-run-producer --message-amount 100 --message-rate 100 | o2-qc --config json:/${JSON_DIR}/batch-test.json --local-batch /tmp/batch_test_mergedA${UNIQUE_ID}.root --run +o2-qc-run-producer --message-amount 100 --message-rate 100 | o2-qc --config json:/${JSON_DIR}/batch-test.json --local-batch /tmp/batch_test_mergedB${UNIQUE_ID}.root --run +# Run the file merger to produce the complete result +o2-qc-file-merger --input-files /tmp/batch_test_mergedA${UNIQUE_ID}.root /tmp/batch_test_mergedB${UNIQUE_ID}.root --output-file /tmp/batch_test_mergedC${UNIQUE_ID}.root +# Run Checks and Aggregators, publish results to QCDB +o2-qc --config json:/${JSON_DIR}/batch-test.json --remote-batch /tmp/batch_test_mergedC${UNIQUE_ID}.root --run + +# check the integrated MonitorObject +# first the return code must be 200 +code=$(curl -L ccdb-test.cern.ch:8080/qc/TST/MO/BatchTestTask${UNIQUE_ID}/example/8000000/PeriodName=LHC9000x/PassName=apass500 --write-out %{http_code} --silent --output /tmp/batch_test_obj${UNIQUE_ID}.root) +if (( $code != 200 )); then + echo "Error, monitor object of the QC Task could not be found." +# delete_data + exit 3 +fi +# try to check that we got a valid root object +root -b -l -q -e 'TFile f("/tmp/batch_test_obj${UNIQUE_ID}.root"); f.Print();' +if (( $? != 0 )); then + echo "Error, monitor object of the QC Task is invalid." + delete_data + exit 4 +fi +# try if it is a non empty histogram +entries=`root -b -l -q -e 'TFile f("/tmp/batch_test_obj${UNIQUE_ID}.root"); TH1F *h = (TH1F*)f.Get("ccdb_object"); cout << h->GetEntries() << endl;' | tail -n 1` +if [ $entries -lt 225 ] 2>/dev/null +then + echo "The histogram of the QC Task has less than 225 (75%) of expected samples." + delete_data + exit 5 +fi + +# check the moving window MonitorObject +# first the return code must be 200 +code=$(curl -L ccdb-test.cern.ch:8080/qc/TST/MO/BatchTestTask${UNIQUE_ID}/mw/example/8000000/PeriodName=LHC9000x/PassName=apass500 --write-out %{http_code} --silent --output /tmp/batch_test_obj_mw${UNIQUE_ID}.root) +if (( $code != 200 )); then + echo "Error, monitor object of the QC Task could not be found." + delete_data + exit 6 +fi +# try to check that we got a valid root object +root -b -l -q -e 'TFile f("/tmp/batch_test_obj_mw${UNIQUE_ID}.root"); f.Print();' +if (( $? != 0 )); then + echo "Error, monitor object of the QC Task is invalid." + delete_data + exit 7 +fi +# try if it is a non empty histogram +entries=`root -b -l -q -e 'TFile f("/tmp/batch_test_obj_mw${UNIQUE_ID}.root"); TH1F *h = (TH1F*)f.Get("ccdb_object"); cout << h->GetEntries() << endl;' | tail -n 1` +if [ $entries -lt 225 ] 2>/dev/null +then + echo "The histogram of the QC Task has less than 225 (75%) of expected samples." + delete_data + exit 8 +fi + +# check QualityObject +# first the return code must be 200 +code=$(curl -L ccdb-test.cern.ch:8080/qc/TST/QO/BatchTestCheck${UNIQUE_ID}/8000000/PeriodName=LHC9000x/PassName=apass500 --write-out %{http_code} --silent --output /tmp/batch_test_check${UNIQUE_ID}.root) +if (( $code != 200 )); then + echo "Error, quality object of the QC Task could not be found." + delete_data + exit 9 +fi + +echo "Batch test passed." +delete_data diff --git a/Framework/script/benchmark_machine_setup.sh b/Framework/script/o2-qc-benchmark-machine-setup.sh similarity index 100% rename from Framework/script/benchmark_machine_setup.sh rename to Framework/script/o2-qc-benchmark-machine-setup.sh diff --git a/Framework/script/benchmark_results_extraction.sh b/Framework/script/o2-qc-benchmark-results-extraction.sh similarity index 100% rename from Framework/script/benchmark_results_extraction.sh rename to Framework/script/o2-qc-benchmark-results-extraction.sh diff --git a/Framework/script/benchmark.sh b/Framework/script/o2-qc-benchmark.sh similarity index 100% rename from Framework/script/benchmark.sh rename to Framework/script/o2-qc-benchmark.sh diff --git a/Framework/script/o2-qc-functional-test.sh b/Framework/script/o2-qc-functional-test.sh new file mode 100755 index 0000000000..8560eee13e --- /dev/null +++ b/Framework/script/o2-qc-functional-test.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -e +set -x + +# Arguments or expected variables +# UNIQUE_ID must be set. +# JSON_DIR must be set and point to the directory containing basic-functional.json. + +if [ -z "$UNIQUE_ID" ] +then + echo "UNIQUE_ID must be set when calling functional_test.sh" + exit 1 +fi +if [ -z "$JSON_DIR" ] +then + echo "JSON_DIR must be set when calling functional_test.sh" + exit 1 +fi + +echo "ROOT_DYN_PATH : $ROOT_DYN_PATH" + +# make sure the CCDB is available otherwise we bail (no failure) +# we do not use ping because it will fail from outside CERN. +if curl --silent --connect-timeout 1 ccdb-test.cern.ch:8080 > /dev/null 2>&1 ; then + echo "CCDB is reachable." +else + echo "CCDB not reachable, functional test is cancelled." + exit 0 +fi + +# delete data +curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/MO/FunctionalTest${UNIQUE_ID}* +curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/QO/FunctionalTest${UNIQUE_ID}* +curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/QO/FunctionalTestAggregator${UNIQUE_ID}* + +# store data +DIR="$(dirname "${BASH_SOURCE[0]}")" # get the directory name +DIR="$(realpath "${DIR}")" # resolve its full path if need be +o2-qc-run-producer --message-amount 10 -b | o2-qc --config json://${JSON_DIR}/basic-functional.json -b --run + +# check MonitorObject +# first the return code must be 200 +code=$(curl -L ccdb-test.cern.ch:8080/qc/TST/MO/FunctionalTest${UNIQUE_ID}/example/8000000/PeriodName=LHC9000x/PassName=apass500 --write-out %{http_code} --silent --output /tmp/output${UNIQUE_ID}.root) +if (( $code != 200 )); then + echo "Error, monitor object could not be found." + exit 2 +fi +# try to check that we got a valid root object +root -b -l -q -e 'TFile f("/tmp/output${UNIQUE_ID}.root"); f.Print();' + +# check QualityObject created by the Check +# first the return code must be 200 +code=$(curl -L ccdb-test.cern.ch:8080/qc/TST/QO/FunctionalTest${UNIQUE_ID}/8000000/PeriodName=LHC9000x/PassName=apass500 --write-out %{http_code} --silent --output /tmp/output${UNIQUE_ID}.root) +if (( $code != 200 )); then + echo "Error, data not found." + exit 2 +fi +# try to check that we got a valid root object +root -b -l -q -e 'TFile f("/tmp/output${UNIQUE_ID}.root"); f.Print();' + +# check QualityObject created by the Aggregator +# first the return code must be 200 +code=$(curl -L ccdb-test.cern.ch:8080/qc/TST/QO/FunctionalTestAggregator${UNIQUE_ID}/newQuality/8000000/PeriodName=LHC9000x/PassName=apass500 --write-out %{http_code} --silent --output /tmp/output${UNIQUE_ID}.root) +if (( $code != 200 )); then + echo "Error, data not found." + exit 2 +fi +# try to check that we got a valid root object +root -b -l -q -e 'TFile f("/tmp/output${UNIQUE_ID}.root"); f.Print();' + +# delete the data +curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/MO/FunctionalTest${UNIQUE_ID}* +curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/QO/FunctionalTest${UNIQUE_ID}* +curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/QO/FunctionalTestAggregator${UNIQUE_ID}* diff --git a/Framework/script/o2-qc-multinode-test.sh b/Framework/script/o2-qc-multinode-test.sh new file mode 100755 index 0000000000..f409e968a4 --- /dev/null +++ b/Framework/script/o2-qc-multinode-test.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +#set -e +set -x +set -u +set -m +# Arguments or expected variables +# UNIQUE_PORT_1 and UNIQUE_PORT_2 must be set and not occupied by another process +# JSON_DIR must be set and point to the directory containing multinode-test.json. + +# this is to make sure that we do not leave child processes behind +# https://unix.stackexchange.com/questions/240723/exit-trap-in-dash-vs-ksh-and-bash/240736#240736 +cleanup() { + # kill all processes whose parent is this process + pkill -P $$ +} +for sig in INT QUIT HUP TERM; do + trap " + cleanup + trap - $sig EXIT + kill -s $sig "'"$$"' "$sig" +done +trap cleanup EXIT + +if [ -z "$UNIQUE_PORT_1" ] +then + echo "UNIQUE_PORT_1 must be set when calling o2-qc-multinode-test.sh" + exit 1 +fi +export UNIQUE_TEST_NAME="multinode-test-${UNIQUE_PORT_1}" + +function check_if_port_in_use() { + OS=`uname` + if [[ $OS == Linux ]] ; then + PORT_PRESENT="$(netstat -tulpn 2>/dev/null | grep LISTEN | grep -w $1)" + else #Darwin/BSD + PORT_PRESENT="$(netstat -an -ptcp | grep LISTEN | grep -w $1)" + fi + + if [[ ! -z "$PORT_PRESENT" ]]; then + echo 'Port '$1' is in use, exiting.' + echo 'If this port is always used in the build machines, ping the QC developers please' + exit 1 + fi +} + +function delete_data() { + curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/MO/MNLTest${UNIQUE_PORT_1}* + curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/MO/MNRTest${UNIQUE_PORT_2}* + curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/QO/MNLTest + curl -i -L ccdb-test.cern.ch:8080/truncate/qc/TST/QO/MNRTest + + cd /tmp + # mv in /tmp is guaranteed to be atomic + mv -f /tmp/${UNIQUE_TEST_NAME}{,.todelete} + rm -rf /tmp/${UNIQUE_TEST_NAME}.todelete +} + +delete_data +# mkdir in /tmp is guaranteed to be atomic +mkdir /tmp/${UNIQUE_TEST_NAME} || { echo "Concurrent usage of the same port ${UNIQUE_PORT_1} detected, exiting"; exit 1; } +pushd /tmp/${UNIQUE_TEST_NAME} + +UNIQUE_PORT_2=$((UNIQUE_PORT_1+1)) + +check_if_port_in_use $UNIQUE_PORT_1 +check_if_port_in_use $UNIQUE_PORT_2 +if [ -z "$JSON_DIR" ] +then + echo "JSON_DIR must be set when calling o2-qc-multinode-test.sh" + exit 1 +fi + +# make sure the CCDB is available otherwise we bail (no failure) +# we do not use ping because it will fail from outside CERN. +if curl --silent --connect-timeout 1 ccdb-test.cern.ch:8080 > /dev/null 2>&1 ; then + echo "CCDB is reachable." +else + echo "CCDB not reachable, multinode test is cancelled." + exit 0 +fi + +# store data +timeout -s INT 40s o2-qc --config json://${JSON_DIR}/multinode-test.json -b --remote --run & +o2-qc-run-producer --producers 2 --message-amount 20 --message-rate 1 -b | timeout -s INT 35s o2-qc --config json://${JSON_DIR}/multinode-test.json -b --local --host localhost --run & + + +# wait until the local QC quits before moving forward. +wait + +# check MonitorObject +# first the return code must be 200 +code=$(curl -L ccdb-test.cern.ch:8080/qc/TST/MO/MNLTest${UNIQUE_PORT_1}/example/8000000 --write-out %{http_code} --silent --output /tmp/${UNIQUE_TEST_NAME}/multinode_test_obj${UNIQUE_PORT_1}.root) +if (( $code != 200 )); then + echo "Error, monitor object of the local QC Task could not be found." + delete_data + exit 2 +fi +# try to check that we got a valid root object +root -b -l -q -e 'TFile f("/tmp/${UNIQUE_TEST_NAME}/multinode_test_obj${UNIQUE_PORT_1}.root"); f.Print();' +if (( $? != 0 )); then + echo "Error, monitor object of the local QC Task is invalid." + delete_data + exit 2 +fi +# try if it is a non empty histogram +entries=`root -b -l -q -e 'TFile f("/tmp/${UNIQUE_TEST_NAME}/multinode_test_obj${UNIQUE_PORT_1}.root"); TH1F *h = (TH1F*)f.Get("ccdb_object"); cout << h->GetEntries() << endl;' | tail -n 1` +if ! [ $entries -gt 0 ] 2>/dev/null +then + echo "The histogram of the local QC Task is empty or the object is not a histogram." + delete_data + exit 2 +fi + +# check MonitorObject +# first the return code must be 200 +code=$(curl -L ccdb-test.cern.ch:8080/qc/TST/MO/MNRTest${UNIQUE_PORT_2}/example/8000000 --write-out %{http_code} --silent --output /tmp/${UNIQUE_TEST_NAME}/multinode_test_obj${UNIQUE_PORT_2}.root) +if (( $code != 200 )); then + echo "Error, monitor object of the remote QC Task could not be found." + delete_data + exit 2 +fi +# try to check that we got a valid root object +root -b -l -q -e 'TFile f("/tmp/${UNIQUE_TEST_NAME}/multinode_test_obj${UNIQUE_PORT_2}.root"); f.Print();' +if (( $? != 0 )); then + echo "Error, monitor object of the remote QC Task is invalid." + delete_data + exit 2 +fi +# try if it is a non empty histogram +entries=`root -b -l -q -e 'TFile f("/tmp/${UNIQUE_TEST_NAME}/multinode_test_obj${UNIQUE_PORT_2}.root"); TH1F *h = (TH1F*)f.Get("ccdb_object"); cout << h->GetEntries() << endl;' | tail -n 1` +if ! [ $entries -gt 0 ] 2>/dev/null +then + echo "The histogram of the remote QC Task is empty or the object is not a histogram." + delete_data + exit 2 +fi + +delete_data diff --git a/Framework/script/repo_benchmark.sh b/Framework/script/o2-qc-repo-benchmark.sh similarity index 100% rename from Framework/script/repo_benchmark.sh rename to Framework/script/o2-qc-repo-benchmark.sh diff --git a/Framework/script/patch.sh b/Framework/script/patch.sh new file mode 100755 index 0000000000..1060c9773e --- /dev/null +++ b/Framework/script/patch.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -e ;# exit on error +set -u ;# exit when using undeclared variable +#set -x + +# Parameters +if [ "$#" -ne 1 ]; then + echo "A single parameter must be passed: the version to patch. $# provided." + exit 1 +fi +currentVersion=$1 +if [[ ! $currentVersion =~ ^v[0-9]+(\.[0-9]+){2,2}$ ]]; +then + echo "The parameter must be provided in the form vx.y.z" + exit 2 +fi +echo "Version to patch: $currentVersion"; + + +# create the patch version: take the current version x.y.z and do z+1 +delimiter=. +array=($(echo "$currentVersion" | tr $delimiter '\n')) +array[2]=$((array[2]+1)) +patchVersion=$(IFS=$delimiter ; echo "${array[*]}") +echo "Patched version: $patchVersion"; + + +# check +read -p "Do we continue ? [y|n]" -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]] +then + exit 3 +fi + + +# checkout last tagged version +cd /tmp +echo "cloning in /tmp/QualityControl if not already there" +if [[ ! -d "/tmp/QualityControl" ]]; +then + git clone git@github.com:AliceO2Group/QualityControl.git +fi +cd QualityControl +read -p "Github user ? " user +git remote rename origin upstream +git remote add origin git@github.com:user/QualityControl.git + + +echo "branch" +git checkout $currentVersion +git checkout -b branch_$patchVersion + + +echo "cherry-pick the commit from master" +read -p "Hash(es) to cherry-pick? (space separated if multiple)" hash +echo +git cherry-pick $hash + + +echo "change version in CMakeLists and commit" +patchVersionNumbers="${patchVersion:1}" +echo "patchVersionNumbers: $patchVersionNumbers" +case "$(uname -s)" in + Darwin) + sed -E -i '' 's/VERSION [0-9]+\.[0-9]+\.[0-9]+/VERSION '"${patchVersionNumbers}"'/g' CMakeLists.txt + ;; + + Linux) + sed -r -i 's/VERSION [0-9]+\.[0-9]+\.[0-9]+/VERSION '"${patchVersionNumbers}"'/g' CMakeLists.txt + ;; + + *) + echo 'Unknown OS' + exit 4 + ;; +esac +git add CMakeLists.txt +git commit -m "$patchVersion" + + +echo "push the branch upstream" +git push upstream -u branch_$patchVersion + + +echo "tag" +git tag -a $patchVersion -m "$patchVersion" + + +echo "push the tag upstream" +git push upstream $patchVersion + +echo "Go to Github https://github.com/AliceO2Group/QualityControl/releases/new?tag=${patchVersion}&title=${patchVersion}" + +read -p "Fill in the release notes and create the new release in GitHub" -n 1 -r +echo + +read -p "A PR is automatically made in alidist, go check here: https://github.com/alisw/alidist/pulls" + +echo "We are now done." diff --git a/Framework/script/qcDatabaseSetup.sh b/Framework/script/qcDatabaseSetup.sh deleted file mode 100755 index c82174bf66..0000000000 --- a/Framework/script/qcDatabaseSetup.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash - -# TODO : use a python script - -echo "Configuration of MySQL database for Quality Control" - -QC_DB_MYSQL_HOST="localhost" -QC_DB_MYSQL_DBNAME="quality_control" -QC_DB_MYSQL_USER="qc_user" -QC_DB_MYSQL_PASSWORD="qc_user" - -if [ "$QC_DB_MYSQL_ROOT_PASSWORD" != "" ]; then - QC_DB_MYSQL_ROOT_PASSWORD="-p$QC_DB_MYSQL_ROOT_PASSWORD" -fi - -mysqladmin -uroot $QC_DB_MYSQL_ROOT_PASSWORD ping | grep -q "is alive" -if [ "$?" != "0" ]; then - echo "Cannot authenticate with MySQL. If the root password of your MySQL instance is not empty, please provide it with the environment variable QC_DB_MYSQL_ROOT_PASSWORD." - exit 3 -fi - -# Create database -mysql -h $QC_DB_MYSQL_HOST -u root $QC_DB_MYSQL_ROOT_PASSWORD -e "create database \`$QC_DB_MYSQL_DBNAME\`" - -# try connection -mysql -h $QC_DB_MYSQL_HOST -u root $QC_DB_MYSQL_ROOT_PASSWORD -e "exit" -if [ "$?" != "0" ]; then - echo "Database could not be created." - exit 4 -else - echo "Database successfully created." -fi - -# Create account -#HERE=`hostname -f` -# grant INSERT,SELECT,UPDATE,DELETE on $QC_DB_MYSQL_DBNAME.* to \"$QC_DB_MYSQL_USER\"@\"%\" identified by \"$QC_DB_MYSQL_PASSWORD\"; -# grant all privileges on $QC_DB_MYSQL_DBNAME.* to \"$QC_DB_MYSQL_USER\"@\"$HERE\" identified by \"$QC_DB_MYSQL_PASSWORD\"; -mysql -h $QC_DB_MYSQL_HOST -u root $QC_DB_MYSQL_ROOT_PASSWORD -e " - grant all privileges on $QC_DB_MYSQL_DBNAME.* to \"$QC_DB_MYSQL_USER\"@\"localhost\" identified by \"$QC_DB_MYSQL_PASSWORD\"; -" - -# try connection -mysql -h $QC_DB_MYSQL_HOST -u $QC_DB_MYSQL_USER -p$QC_DB_MYSQL_PASSWORD -e "exit" -if [ "$?" != "0" ]; then - echo "Failure to create user $QC_DB_MYSQL_USER." - exit 5 -else - echo "User $QC_DB_MYSQL_USER created." -fi - -mysql -h $QC_DB_MYSQL_HOST -u $QC_DB_MYSQL_USER -p$QC_DB_MYSQL_PASSWORD $QC_DB_MYSQL_DBNAME << "EOF" -CREATE TABLE IF NOT EXISTS `layout` ( - `id` varchar(24) NOT NULL DEFAULT '', - `name` varchar(30) NOT NULL DEFAULT '', - `owner_id` int(11) NOT NULL, - `owner_name` varchar(200) NOT NULL DEFAULT '', - `tabs` text NOT NULL COMMENT 'JSON payload', - PRIMARY KEY (`id`), - UNIQUE KEY `index_name` (`name`), - KEY `index_owner_name` (`owner_name`), - KEY `index_owner_id` (`owner_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -EOF - -if [ "$?" != "0" ]; then - echo "Failure to create QCG table." - exit 6 -else - echo "QCG table created." -fi diff --git a/Framework/script/release.sh b/Framework/script/release.sh new file mode 100755 index 0000000000..553321e66d --- /dev/null +++ b/Framework/script/release.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +set -e ;# exit on error +set -u ;# exit when using undeclared variable +#set -x ;# uncomment to debug + + +# Check that we have `jq` +if ! command -v jq &> /dev/null +then + echo "jq could not be found, please install it." + exit +fi + + +# Parameter: if there we check that it is a proper version number, if not we propose a new one +if [ "$#" -gt 0 ]; then + newVersion=$1 + if [[ ! $newVersion =~ ^[0-9]+(\.[0-9]+){2,2}$ ]]; + then + echo "The version number must be provided in the form x.y.z" + exit 1 + fi +else + # create the patch version: take the current version x.y.z and do y+=1 and z=0 + echo "No version number provided, we use the CMakeLists to build one." + oldVersion=$(awk '/^ *VERSION/{print $2}' CMakeLists.txt) + delimiter=. + array=($(echo "$oldVersion" | tr $delimiter '\n')) + array[1]=$((array[1]+1)) + array[2]=0 + newVersion=$(IFS=$delimiter ; echo "${array[*]}") +fi +echo "Version to release: $newVersion"; + + +#Get the script dir +fullScriptName=$0 +scriptName=`basename $0` +scriptDir=`echo $fullScriptName | sed 's:'/$scriptName'::'` + + +# check +read -p "Do we continue ? (y|n)" -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]] +then + exit 3 +fi + + +echo "Cloning in /tmp/QualityControl" +cd /tmp +if [[ ! -d "/tmp/QualityControl" ]]; +then + git clone git@github.com:AliceO2Group/QualityControl.git +else + echo "/tmp/QualityControl already exists, please [re]move it" + exit 5 +fi +cd QualityControl + + +echo "Update version number in CMakeLists" +case "$(uname -s)" in + Darwin) + sed -E -i '' 's/VERSION [0-9]+\.[0-9]+\.[0-9]+/VERSION '"${newVersion}"'/g' CMakeLists.txt + ;; + + Linux) + sed -r -i 's/VERSION [0-9]+\.[0-9]+\.[0-9]+/VERSION '"${newVersion}"'/g' CMakeLists.txt + ;; + + *) + echo 'Unknown OS' + exit 4 + ;; +esac +echo "Commit it to master" +git add CMakeLists.txt +git commit -m "${newVersion}" + + +read -p "Ready to push, please confirm [y|n]" -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]] +then + exit 3 +fi +git push + + +read -p "Go to JIRA and release this version : https://alice.its.cern.ch/jira/projects/QC?selectedItem=com.atlassian.jira.jira-projects-plugin%3Arelease-page&status=released-unreleased +Press any key to continue" -n 1 -r +echo + + +echo "Go to Github https://github.com/AliceO2Group/QualityControl/releases/new?tag=v${newVersion}&title=v${newVersion}&body=$(cat ${scriptDir}/../../doc/ReleaseNotesTemplate.md |jq -sRr @uri)" + + +read -p "Fill in the release notes and create the new release in GitHub" -n 1 -r +echo + + +read -p "A PR is automatically made in alidist, go check here: https://github.com/alisw/alidist/pulls" + + +echo "We are now done. +Once the PR is merged, send an email to alice-o2-wp7@cern.ch, alice-o2-qc-contact@cern.ch and alice-dpg-qa-tools@cern.ch to announce the new release. Use the email from the previous release as a template." diff --git a/Framework/script/remotePortMapping.csv b/Framework/script/remotePortMapping.csv new file mode 100644 index 0000000000..97c1149fee --- /dev/null +++ b/Framework/script/remotePortMapping.csv @@ -0,0 +1,77 @@ +className,remotePort +o2::quality_control_modules::cpv::PedestalTask,29000 +o2::quality_control_modules::cpv::PhysicsTask,29001 +o2::quality_control_modules::ctp::CTPRawDataReaderTask,29050 +o2::quality_control_modules::daq::DaqTask,29100 +o2::quality_control_modules::emcal::CalibMonitoringTask,29150 +o2::quality_control_modules::emcal::CellTask,29151 +o2::quality_control_modules::emcal::ClusterTask,29152 +o2::quality_control_modules::emcal::RawErrorTask,29153 +o2::quality_control_modules::emcal::RawTask,29154 +o2::quality_control_modules::emcal::SupermoduleProjectorTask,29155 +o2::quality_control_modules::fdd::DigitQcTask,29200 +o2::quality_control_modules::ft0::DigitQcTask,29250 +o2::quality_control_modules::ft0::DigitQcTaskLaser,29251 +o2::quality_control_modules::ft0::OutOfBunchCollTask,29252 +o2::quality_control_modules::ft0::TriggerQcTask,29253 +o2::quality_control_modules::fv0::DigitQcTask,29300 +o2::quality_control_modules::fv0::DigitQcTaskLaser,29301 +o2::quality_control_modules::fv0::OutOfBunchCollTask,29302 +o2::quality_control_modules::fv0::TriggerQcTask,29303 +o2::quality_control_modules::glo::ITSTPCMatchingTask,29350 +o2::quality_control_modules::glo::VertexingQcTask,29351 +o2::quality_control_modules::hmpid::HmpidTask,29400 +o2::quality_control_modules::its::ITSClusterTask,29450 +o2::quality_control_modules::its::ITSDecodingErrorTask,29451 +o2::quality_control_modules::its::ITSFeeTask,29452 +o2::quality_control_modules::its::ITSFhrTask,29453 +o2::quality_control_modules::its::ITSNoisyPixelTask,29454 +o2::quality_control_modules::its::ITSThresholdCalibrationTask,29455 +o2::quality_control_modules::its::ITSTrackTask,29456 +o2::quality_control_modules::mft::QcMFTClusterTask,29550 +o2::quality_control_modules::mft::QcMFTDigitTask,29551 +o2::quality_control_modules::mft::QcMFTReadoutTask,29552 +o2::quality_control_modules::mft::QcMFTTrackTask,29553 +o2::quality_control_modules::mid::CalibMQcTask,29600 +o2::quality_control_modules::mid::CalibQcTask,29601 +o2::quality_control_modules::mid::ClustQcTask,29602 +o2::quality_control_modules::mid::DigitsQcTask,29603 +o2::quality_control_modules::mid::RawQcTask,29604 +o2::quality_control_modules::mid::TracksQcTask,29605 +o2::quality_control_modules::muon::TracksTask,29513 +o2::quality_control_modules::muonchambers::ClustersTask,29500 +o2::quality_control_modules::muonchambers::DecodingErrorsTask,29501 +o2::quality_control_modules::muonchambers::DecodingTask,29502 +o2::quality_control_modules::muonchambers::DigitsTask,29503 +o2::quality_control_modules::muonchambers::ErrorTask,29504 +o2::quality_control_modules::muonchambers::PedestalsTask,29505 +o2::quality_control_modules::muonchambers::PhysicsTaskDigits,29506 +o2::quality_control_modules::muonchambers::PhysicsTaskPreclusters,29507 +o2::quality_control_modules::muonchambers::PhysicsTaskRofs,29508 +o2::quality_control_modules::muonchambers::PreclustersTask,29509 +o2::quality_control_modules::muonchambers::RofsTask,29510 +o2::quality_control_modules::muonchambers::TracksTask,29511 +o2::quality_control_modules::phos::CalibQcTask,29650 +o2::quality_control_modules::phos::ClusterQcTask,29651 +o2::quality_control_modules::phos::RawQcTask,29652 +o2::quality_control_modules::tof::TOFMatchedTracks,29750 +o2::quality_control_modules::tof::TaskDigits,29751 +o2::quality_control_modules::tof::TaskRaw,29752 +o2::quality_control_modules::tpc::CalDetPublisher,29800 +o2::quality_control_modules::tpc::ClusterVisualizer,29801 +o2::quality_control_modules::tpc::Clusters,29802 +o2::quality_control_modules::tpc::DCSPTemperature,29803 +o2::quality_control_modules::tpc::IDCs,29804 +o2::quality_control_modules::tpc::LaserTracks,29805 +o2::quality_control_modules::tpc::PID,29806 +o2::quality_control_modules::tpc::RatioGeneratorTPC,29807 +o2::quality_control_modules::tpc::RawDigits,29808 +o2::quality_control_modules::tpc::Tracks,29809 +o2::quality_control_modules::trd::DigitsTask,29850 +o2::quality_control_modules::trd::PulseHeight,29851 +o2::quality_control_modules::trd::PulseHeightTrackMatch,29852 +o2::quality_control_modules::trd::RawData,29853 +o2::quality_control_modules::trd::TrackingTask,29854 +o2::quality_control_modules::trd::TrackletsTask,29855 +o2::quality_control_modules::zdc::ZDCRawDataTask,29900 +o2::quality_control_modules::zdc::ZDCRecDataTask,29901 diff --git a/Framework/script/updateCcdbConsul.sh b/Framework/script/updateCcdbConsul.sh new file mode 100755 index 0000000000..2708ed2026 --- /dev/null +++ b/Framework/script/updateCcdbConsul.sh @@ -0,0 +1,90 @@ +# This script updates the ccdb host in all config files in all head nodes. +# It is ad hoc and specific but it can easily be modified for similar purpose. + +# set -x + +HEAD_NODES=( + ali-staging-consul.cern.ch +#ali-consul.cern.ch +) +echo "Number of nodes: ${#HEAD_NODES[@]}" + +# Check that we have `jq` +if ! command -v jq &> /dev/null +then + echo "jq could not be found, please install it." + exit +fi + +# for each node +for ((nodeIndex = 0; nodeIndex < ${#HEAD_NODES[@]}; nodeIndex++)); do + + node=${HEAD_NODES[${nodeIndex}]} + echo "node: $node" + export CONSUL_HTTP_ADDR=${node}:8500 + echo $CONSUL_HTTP_ADDR + + # Get the list of config files for qc + list_files=$(curl -s ${node}:8500/v1/kv/o2/components/qc/ANY/any?keys=true | jq -c -r '.[]') + IFS=$'\n' read -rd '' -a array_files <<<"$list_files" + + # backup folder + backup_dir_name="backup-consul-`date +%Y.%m.%d`" + mkdir $backup_dir_name + cd $backup_dir_name + + # for each file + for file in "${array_files[@]}"; do + echo "file: $file" + # avoid touching the repo cleaner file + if [[ $file == *repoCleanerConfig.yaml* ]]; then + echo " it is the repo cleaner config file, we skip it" + continue + fi + + if [[ "$file" == */ ]]; then + echo " path finishes with / and is directory" + continue + fi + + # example how to use jq to do the job. + new_content=$(jq ' + if has("qc") and .qc?.config?.bookkeeping?.url == null then + .qc.config.bookkeeping.url = "alio2-cr1-hv-mvs00.cern.ch:4001" + else + . + end + ' $local_file) + if [ $? -eq 0 ]; then + echo "jq succeeded" + cat $local_file + echo $new_content | jq . + # consul kv put "$file" "$new_content" + else + echo "jq failed" + fi + + # download + local_file=$(basename $file) + consul kv get "$file" >$local_file + + # if we need to check the value before modifying : + current=$(cat $local_file | jq '.qc.config.conditionDB.url') + current=$(echo $current| tr -d '"') # remove quotes + echo "current: $current" +# unset new_content +# if (( $current != null && $current < 11 )); then +# # modify +# new_content=$(cat $local_file | jq '.qc.config.conditionDB.url |= "11"') +# echo $new_content +# consul kv put "$file" "$new_content" +# fi + + # or simply modify : + #new_content=$(sed 's/http:\/\/localhost:8084/o2-ccdb.internal/g' $local_file) + new_content=$(cat $local_file | jq '.qc.config.infologger.filterDiscardLevel |= "21"') + echo "new_content: $new_content" + # upload (uncomment) +# consul kv put "$file" "$new_content" + done +done diff --git a/Framework/script/updatePdpBeam.sh b/Framework/script/updatePdpBeam.sh new file mode 100755 index 0000000000..f4806e0ca7 --- /dev/null +++ b/Framework/script/updatePdpBeam.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# Default values +PREFIX="o2/" +TMP_DIR="./consul_kv_backups" +mkdir -p "$TMP_DIR" +UPDATE=false + +# --- Parse arguments --- +if [[ "$#" -eq 0 ]]; then + echo "Use -h or --help for usage" + exit 0 +fi + +while [[ "$#" -gt 0 ]]; do + case "$1" in + -u | --update) + UPDATE=true + ;; + -p | --prefix) + PREFIX="$2" + shift + ;; + -h | --help) + echo "Script to change any occurrence of \"PROTON-PROTON\" -> \"pp\", \"Pb-PROTON\" -> \"pPb\", \"Pb-Pb\" -> \"PbPb\"" + echo "Usage: $0 [-p|--prefix ] [-u|--update]" + echo + echo " -p, --prefix Prefix to search in Consul KV (default: o2/)" + echo " -u, --update Apply changes back to Consul" + exit 0 + ;; + *) + echo "❌ Unknown option: $1" + echo "Use -h or --help for usage" + exit 1 + ;; + esac + shift +done + +echo "🔍 Using Consul prefix: $PREFIX" +[[ "$UPDATE" == true ]] && echo "🚀 Update mode: ON (values will be written back)" || echo "🔒 Dry-run mode: changes only printed/saved" + +# --- Define replacement logic --- +replace() { + sed -e 's/\"PROTON-PROTON\"/"pp"/g' \ + -e 's/\"Pb-PROTON\"/"pPb"/g' \ + -e 's/\"Pb-Pb\"/"PbPb"/g' +} + +# --- Fetch all keys --- +KEYS=$(consul kv get -recurse -keys "$PREFIX") + +# --- Process each key --- +while IFS= read -r key; do + VALUE=$(consul kv get "$key") + MODIFIED=$(echo "$VALUE" | replace) + + if [[ "$VALUE" != "$MODIFIED" ]]; then + SAFE_NAME=$(echo "$key" | sed 's|/|__|g') + + ORIG_FILE="$TMP_DIR/$SAFE_NAME.orig" + NEW_FILE="$TMP_DIR/$SAFE_NAME.new" + DIFF_FILE="$TMP_DIR/$SAFE_NAME.diff" + + echo "$VALUE" >"$ORIG_FILE" + echo "$MODIFIED" >"$NEW_FILE" + diff -u "$ORIG_FILE" "$NEW_FILE" >"$DIFF_FILE" + + echo "✅ Changed key: $key" + echo " 📄 $ORIG_FILE" + echo " 🆕 $NEW_FILE" + echo " 📑 $DIFF_FILE" + + if [[ "$UPDATE" == true ]]; then + echo "$MODIFIED" | consul kv put "$key" - + echo " 🔁 Updated in Consul: $key" + fi + + echo "---------------------------------------" + fi +done <<<"$KEYS" diff --git a/Framework/script/updateRemotePorts.sh b/Framework/script/updateRemotePorts.sh new file mode 100755 index 0000000000..a5422b44dd --- /dev/null +++ b/Framework/script/updateRemotePorts.sh @@ -0,0 +1,94 @@ +# This script updates the ""remotePort" values in QC config files according to the mapping in remotePortMapping.csv +# The mapping is between className and remotePort. Thus, there are a few exceptions, where the same class is used +# for different tasks. The corresponding corrections are included in this script. +# It is ad hoc and specific but it can easily be modified for similar purpose. +# +# KNOWN ISSUES: +# - for very large configuration files, the consul upload fails + +#set -x + +HEAD_NODES=( +# alio2-cr1-flp162 +# alio2-cr1-flp146 +# alio2-cr1-flp160 +# alio2-cr1-flp187 +# alio2-cr1-flp148 +# alio2-cr1-flp182 +# alio2-cr1-flp159 +# alio2-cr1-flp164 +# alio2-cr1-flp178 +# alio2-cr1-hv-head01 +# alio2-cr1-flp166 +# alio2-cr1-flp181 +# alio2-cr1-mvs03 +# barth-test-cc7.cern.ch +#ali-consul.cern.ch +# ali-staging-consul.cern.ch +) +echo "Number of nodes: ${#HEAD_NODES[@]}" + +# Check that we have `jq` +if ! command -v jq &> /dev/null +then + echo "jq could not be found, please install it." + exit +fi + +# read csv file into an associative array +declare -A classPortMap +while IFS=, read -r class port +do + classPortMap["$class"]="$port" +done < remotePortMapping.csv + +# for each node +for ((nodeIndex = 0; nodeIndex < ${#HEAD_NODES[@]}; nodeIndex++)); do + + node=${HEAD_NODES[${nodeIndex}]} + echo "node: $node" + export CONSUL_HTTP_ADDR=${node}:8500 + echo $CONSUL_HTTP_ADDR + + # Get the list of config files for qc + list_files=$(curl -s ${node}:8500/v1/kv/o2/components/qc/ANY/any?keys=true | jq -c -r '.[]') + IFS=$'\n' read -rd '' -a array_files <<<"$list_files" + + # backup folder + backup_dir_name="backup-consul-`date +%Y.%m.%d`" + mkdir $backup_dir_name + cd $backup_dir_name + + # for each file + for file in "${array_files[@]}"; do + echo "file: $file" + + # download + local_file=$(basename $file) + consul kv get "$file" >$local_file + + for key in "${!classPortMap[@]}"; do + className="$key" + remotePort="${classPortMap[$key]}" + jq --arg className "$className" --arg remotePort "$remotePort" \ + '(.qc.tasks[]? | select(.className == $className and has("remotePort")?) .remotePort) = $remotePort' \ + $local_file > temp && mv temp $local_file + done + # special cases + jq '(if .qc.tasks.MCHFRofs | has("remotePort") then .qc.tasks.MCHFRofs.remotePort = "29512" else . end)' $local_file > temp && mv temp $local_file + jq '(if .qc.tasks.MergeMETOFwTRD | has("remotePort") then .qc.tasks.MergeMETOFwTRD.remotePort = "29753" else . end)' $local_file > temp && mv temp $local_file + jq '(if .qc.tasks.ExpertPedestalsOnFLP | has("remotePort") then .qc.tasks.ExpertPedestalsOnFLP.remotePort = "29002" else . end)' $local_file > temp && mv temp $local_file + jq '(if .qc.tasks.MUONTracks | has("remotePort") then .qc.tasks.MUONTracks.remotePort = "29514" else . end)' $local_file > temp && mv temp $local_file + jq '(if .qc.tasks.MCHStdTracks | has("remotePort") then .qc.tasks.MCHStdTracks.remotePort = "29515" else . end)' $local_file > temp && mv temp $local_file + + # or simply modify : + #new_content=$(sed 's/http:\/\/localhost:8084/o2-ccdb.internal/g' $local_file) + new_content=$(cat $local_file) + # echo "new_content: $new_content" + echo local_file: $local_file + echo new_content: $new_content + # upload (uncomment) + # consul kv put "$file" "$new_content" + done +done + diff --git a/Framework/script/updateStagingNodes.sh b/Framework/script/updateStagingNodes.sh new file mode 100644 index 0000000000..217f24615b --- /dev/null +++ b/Framework/script/updateStagingNodes.sh @@ -0,0 +1,91 @@ +# This script updates the qc nodes references in the config files and replace them with the staging qc nodes. + +# Usage: ./updateStagingNodes.sh # run but do not upload. See what changes are done in the local backup folder. +# Usage: ./updateStagingNodes.sh -x # debug mode +# Usage: ./updateStagingNodes.sh -u # upload changes + +updateFiles() +{ + # Replaces first argument by second argument in all the files in the current directory. + replaceThis="$1" + byThis="$2" + filesChanged=$(grep -l "$replaceThis" ./*) + if [ ! -z "$filesChanged" ]; then + echo " +updated files for ($replaceThis --> $byThis) : +$filesChanged" + sed -i "s/$replaceThis/$byThis/g" ./* + else + echo " +no files to update for ($replaceThis --> $byThis)" + fi +} + +while getopts xu flag +do + case "${flag}" in + u) upload=true;; + x) echo "debug mode" && set -x;; + *) echo "invalid flag" + esac +done +echo "Upload enabled: $upload"; + +staging_consul_node="ali-staging-consul:8500" +echo "staging_consul_node: staging_consul_node" +export CONSUL_HTTP_ADDR=${staging_consul_node} + +# Get the list of config files for qc +list_files=$(curl -s ${CONSUL_HTTP_ADDR}/v1/kv/o2/components/qc/ANY/any?keys=true | jq -c -r '.[]') +IFS=$'\n' read -rd '' -a array_files <<<"$list_files" + +# backup folder +backup_dir_name="backup-consul-$(date +%Y.%m.%d)" +mkdir "$backup_dir_name" +cd $backup_dir_name + +# download all files +for file in "${array_files[@]}"; do + echo "Downloading $file" + local_file=$(basename "$file") + consul kv get "$file" >"$local_file" +done + +# modify all files with proper qc nodes +echo "Apply the modifications" +updateFiles "alio2-cr1-qts01" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qts02" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qts03" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qc01" "alio2-cr1-qc06" +updateFiles "alio2-cr1-qc02" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qc03" "alio2-cr1-qc06" +updateFiles "alio2-cr1-qc05" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qc07" "alio2-cr1-qc06" +updateFiles "alio2-cr1-qc08" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qc09" "alio2-cr1-qc06" +updateFiles "alio2-cr1-qc10" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qc11" "alio2-cr1-qc06" +updateFiles "alio2-cr1-qc12" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qme01" "alio2-cr1-qc06" +updateFiles "alio2-cr1-qme02" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qme03" "alio2-cr1-qc06" +updateFiles "alio2-cr1-qme04" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qme05" "alio2-cr1-qc06" +updateFiles "alio2-cr1-qme06" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qme07" "alio2-cr1-qc06" +updateFiles "alio2-cr1-qme08" "alio2-cr1-qc04" +updateFiles "alio2-cr1-qme09" "alio2-cr1-qc06" + +# reupload files +if [[ $upload ]]; then + echo "upload enabled" + for file in "${array_files[@]}"; do + local_file=$(basename "$file") + content=$(cat $local_file) + + echo "uploading $file" + consul kv put "$file" "$content" + done +else + echo "upload disabled" +fi diff --git a/Framework/src/Activity.cxx b/Framework/src/Activity.cxx new file mode 100644 index 0000000000..f20b4536e6 --- /dev/null +++ b/Framework/src/Activity.cxx @@ -0,0 +1,80 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Activity.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/Activity.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include + +using namespace o2::quality_control::repository; + +namespace o2::quality_control::core +{ + +std::ostream& operator<<(std::ostream& out, const Activity& activity) +{ + out << metadata_keys::runNumber << ": " << activity.mId + << ", " << metadata_keys::runType << ": " << activity.mType + << ", " << metadata_keys::periodName << ": '" << activity.mPeriodName + << "', " << metadata_keys::passName << ": '" << activity.mPassName + << "', provenance: '" << activity.mProvenance + << "', " << metadata_keys::validFrom << ": " << activity.mValidity.getMin() + << ", " << metadata_keys::validUntil << ": " << activity.mValidity.getMax() + << ", " << metadata_keys::beamType << ": '" << activity.mBeamType << "'" + << ", " << metadata_keys::partitionName << ": '" << activity.mPartitionName << "'" + << ", " << metadata_keys::fillNumber << ": '" << activity.mFillNumber << "'" + << ", " << metadata_keys::originalRunNumber << ": '" << activity.mOriginalId << "'"; + return out; +} + +bool Activity::matches(const Activity& other) const +{ + // Note that 'this' can be 'any', but 'other' cannot. + // E.g. if we require that run number is concrete, it cannot match 'other' which has 'any' run number. + // Also, since we do not indicate the correct validity of objects, we require that the other Activity validity start + // is included in this validity. If checked for any overlaps, we would match with all past Activities, which is not + // what we want e.g. in Post-processing triggers. Once we indicate the correct validity, we can change this behaviour. + return (mId == 0 || mId == other.mId) && + (mType == "NONE" || mType == other.mType) && + (mPeriodName.empty() || mPeriodName == other.mPeriodName) && + (mPassName.empty() || mPassName == other.mPassName) && + (mProvenance == other.mProvenance) && // provenance has to match! + !mValidity.isOutside(other.mValidity.getMin()) && + (mBeamType.empty() || mBeamType == other.mBeamType); +} + +bool Activity::same(const Activity& other) const +{ + return mId == other.mId && + mType == other.mType && + mPeriodName == other.mPeriodName && + mPassName == other.mPassName && + mProvenance == other.mProvenance && + mBeamType == other.mBeamType; +} + +bool Activity::operator==(const Activity& other) const +{ + return mId == other.mId && + mType == other.mType && + mPeriodName == other.mPeriodName && + mPassName == other.mPassName && + mProvenance == other.mProvenance && + mValidity == other.mValidity && + mBeamType == other.mBeamType && + mOriginalId == other.mOriginalId; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/ActivityHelpers.cxx b/Framework/src/ActivityHelpers.cxx new file mode 100644 index 0000000000..e68114629f --- /dev/null +++ b/Framework/src/ActivityHelpers.cxx @@ -0,0 +1,133 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ActivityHelpers.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/ActivityHelpers.h" + +#include +#include +#include "QualityControl/ObjectMetadataKeys.h" + +#include +#include +#include + +using namespace o2::quality_control::repository; + +namespace o2::quality_control::core::activity_helpers +{ + +std::map asDatabaseMetadata(const core::Activity& activity, bool putDefault) +{ + std::map metadata; + if (putDefault || activity.mType != "NONE") { + metadata[metadata_keys::runType] = activity.mType; + } + if (putDefault || activity.mId != 0) { + metadata[metadata_keys::runNumber] = std::to_string(activity.mId); + } + if (putDefault || !activity.mPassName.empty()) { + metadata[metadata_keys::passName] = activity.mPassName; + } + if (putDefault || !activity.mPeriodName.empty()) { + metadata[metadata_keys::periodName] = activity.mPeriodName; + } + return metadata; +} + +core::Activity asActivity(const std::map& metadata, const std::string& provenance) +{ + core::Activity activity; + if (auto runType = metadata.find(metadata_keys::runType); runType != metadata.end()) { + if (isUnsignedInteger(runType->second)) { + // we probably got the former representation of run types, i.e. an integer. We convert it as best as we can. + activity.mType = translateIntegerRunType(runType->second); + } else { + activity.mType = runType->second; + } + } + if (auto runNumber = metadata.find(metadata_keys::runNumber); runNumber != metadata.end()) { + activity.mId = std::strtol(runNumber->second.c_str(), nullptr, 10); + } + if (auto passName = metadata.find(metadata_keys::passName); passName != metadata.end()) { + activity.mPassName = passName->second; + } + if (auto periodName = metadata.find(metadata_keys::periodName); periodName != metadata.end()) { + activity.mPeriodName = periodName->second; + } + if (auto validFrom = metadata.find(metadata_keys::validFrom); validFrom != metadata.end()) { + activity.mValidity.setMin(std::stoull(validFrom->second)); + } + if (auto validUntil = metadata.find(metadata_keys::validUntil); validUntil != metadata.end()) { + activity.mValidity.setMax(std::stoull(validUntil->second)); + } + activity.mProvenance = provenance; + return activity; +} + +core::Activity asActivity(const boost::property_tree::ptree& tree, const std::string& provenance) +{ + core::Activity activity; + if (auto runType = tree.get_optional(metadata_keys::runType); runType.has_value()) { + if (isUnsignedInteger(runType.value())) { + // we probably got the former representation of run types, i.e. an integer. We convert it as best + // as we can using O2's ECSDataAdapter + activity.mType = parameters::GRPECS::RunTypeNames[std::stoi(runType.value())]; + } else { + activity.mType = runType.value(); + } + } + if (auto runNumber = tree.get_optional(metadata_keys::runNumber); runNumber.has_value()) { + activity.mId = runNumber.value(); + } + if (auto passName = tree.get_optional(metadata_keys::passName); passName.has_value()) { + activity.mPassName = passName.value(); + } + if (auto periodName = tree.get_optional(metadata_keys::periodName); periodName.has_value()) { + activity.mPeriodName = periodName.value(); + } + if (auto validFrom = tree.get_optional(metadata_keys::validFrom); validFrom.has_value()) { + activity.mValidity.setMin(validFrom.value()); + } + if (auto validUntil = tree.get_optional(metadata_keys::validUntil); validUntil.has_value()) { + activity.mValidity.setMax(validUntil.value()); + } + activity.mProvenance = provenance; + return activity; +} + +std::function getCcdbSorTimeAccessor(uint64_t runNumber) +{ + return [runNumber]() { return static_cast(ccdb::BasicCCDBManager::instance().getRunDuration(runNumber, false).first); }; +} + +std::function getCcdbEorTimeAccessor(uint64_t runNumber) +{ + return [runNumber]() { + return static_cast(ccdb::BasicCCDBManager::instance().getRunDuration(runNumber, false).second); + }; +} + +bool isLegacyValidity(ValidityInterval validity) +{ + return validity.isValid() && validity.delta() > 9ull * 365 * 24 * 60 * 60 * 1000ull; +} + +bool onNumericLimit(validity_time_t value) +{ + return value == std::numeric_limits::min() || value == std::numeric_limits::max(); +} + +} // namespace o2::quality_control::core::activity_helpers \ No newline at end of file diff --git a/Framework/src/AdvancedWorkflow.cxx b/Framework/src/AdvancedWorkflow.cxx new file mode 100644 index 0000000000..92e0f9e0e2 --- /dev/null +++ b/Framework/src/AdvancedWorkflow.cxx @@ -0,0 +1,96 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AdvancedWorkflow.cxx +/// \author Piotr Konopka +/// \author Barthelemy von Haller +/// + +#include "QualityControl/AdvancedWorkflow.h" + +#include +#include +#include "QualityControl/QcInfoLogger.h" +#include + +using namespace o2; +using namespace o2::header; +using namespace o2::framework; +using SubSpecificationType = o2::header::DataHeader::SubSpecificationType; + +namespace o2::quality_control::core +{ + +// clang-format off +WorkflowSpec getProcessingTopology(SubSpecificationType subspec) +{ + DataProcessorSpec source{ + "source-" + std::to_string(subspec), + Inputs{}, + Outputs{{ "TST", "DATA", subspec }, + { "TST", "PARAM", subspec }}, + AlgorithmSpec{ + (AlgorithmSpec::ProcessCallback) + [generator = std::default_random_engine{ static_cast(time(nullptr)) }, subspec](ProcessingContext & ctx) mutable { + usleep(200000); + auto data = ctx.outputs().make(Output{ "TST", "DATA", subspec }, generator() % 10000); + for (auto&& item : data) { + item = static_cast(generator()); + } + ctx.outputs().make(Output{ "TST", "PARAM", subspec }, 1)[0] = 1 / static_cast(1 + generator()); + } + } + }; + + DataProcessorSpec step{ + "step-" + std::to_string(subspec), + Inputs{{ "data", "TST", "DATA", subspec }}, + Outputs{{ "TST", "SUM", subspec }}, + AlgorithmSpec{ + (AlgorithmSpec::ProcessCallback)[subspec](ProcessingContext & ctx) { + auto data = DataRefUtils::as(ctx.inputs().get("data")); + long long sum = 0; + for (auto d : data) { sum += d; } + ctx.outputs().snapshot(Output{ "TST", "SUM", subspec }, sum); + } + } + }; + + DataProcessorSpec sink{ + "sink-" + std::to_string(subspec), + Inputs{{ "sum", "TST", "SUM", subspec }, + { "param", "TST", "PARAM", subspec }}, + Outputs{}, + AlgorithmSpec{ + (AlgorithmSpec::ProcessCallback)[](ProcessingContext & ctx) { + ILOG(Debug, Trace) << "Sum is: " << DataRefUtils::as(ctx.inputs().get("sum"))[0] << ENDM; + ILOG(Debug, Trace) << "Param is: " << DataRefUtils::as(ctx.inputs().get("param"))[0] << ENDM; + } + } + }; + + return { source, step, sink }; +} +// clang-format on + +WorkflowSpec getFullProcessingTopology() +{ + WorkflowSpec specs; + // here we pretend to spawn topologies on three processing machines + for (int i = 1; i < 4; i++) { + auto localTopology = getProcessingTopology(i); + specs.insert(std::end(specs), std::begin(localTopology), std::end(localTopology)); + } + return specs; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/Aggregator.cxx b/Framework/src/Aggregator.cxx new file mode 100644 index 0000000000..f5d5e30f62 --- /dev/null +++ b/Framework/src/Aggregator.cxx @@ -0,0 +1,279 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Aggregator.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/Aggregator.h" +#include "QualityControl/AggregatorSpec.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/QualityObject.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/AggregatorInterface.h" +#include "QualityControl/ObjectMetadataHelpers.h" +#include "QualityControl/UpdatePolicyType.h" +#include "QualityControl/ActivityHelpers.h" +#include "QualityControl/Activity.h" +#include +#include "QualityControl/CommonSpec.h" +#include "QualityControl/UserInputOutput.h" + +#include +#include + +using namespace AliceO2::Common; +using namespace AliceO2::InfoLogger; + +using namespace o2::quality_control::checker; +using namespace o2::quality_control::core; +using namespace std; + +namespace o2::quality_control::checker +{ + +Aggregator::Aggregator(AggregatorConfig configuration) : mAggregatorConfig(std::move(configuration)) +{ +} + +void Aggregator::init() +{ + try { + ILOG(Info, Devel) << "Instantiating the user code for aggregator " << mAggregatorConfig.name + << " (" << mAggregatorConfig.moduleName << ", " << mAggregatorConfig.className << ")" << ENDM; + mAggregatorInterface = + root_class_factory::create(mAggregatorConfig.moduleName, mAggregatorConfig.className); + mAggregatorInterface->setName(mAggregatorConfig.name); + mAggregatorInterface->setCustomParameters(mAggregatorConfig.customParameters); + mAggregatorInterface->setCcdbUrl(mAggregatorConfig.ccdbUrl); + mAggregatorInterface->setDatabase(mAggregatorConfig.repository); + mAggregatorInterface->configure(); + } catch (...) { + std::string diagnostic = boost::current_exception_diagnostic_information(); + ILOG(Fatal, Ops) << "Unexpected exception, diagnostic information follows: " + << diagnostic << ENDM; + throw; + } + + // Print setting + ILOG(Info, Support) << mAggregatorConfig.name << ": Module " << mAggregatorConfig.moduleName << ENDM; + ILOG(Info, Support) << mAggregatorConfig.name << ": Class " << mAggregatorConfig.className << ENDM; + ILOG(Info, Support) << mAggregatorConfig.name << ": Detector " << mAggregatorConfig.detectorName << ENDM; + ILOG(Info, Support) << mAggregatorConfig.name << ": Policy " << UpdatePolicyTypeUtils::ToString(mAggregatorConfig.policyType) << ENDM; + ILOG(Info, Support) << mAggregatorConfig.name << ": QualityObjects : " << ENDM; + for (const auto& moname : mAggregatorConfig.objectNames) { + ILOG(Info, Support) << mAggregatorConfig.name << " - " << moname << ENDM; + } +} + +QualityObjectsMapType Aggregator::filter(QualityObjectsMapType& qoMap) +{ + // This is a basic implementation, if it needs to be more efficient it will have to be rethought. + // for each qo in the list we receive, check if a source of this aggregator contains it (or rather + // contains the first part of its checkName before `/`). + + QualityObjectsMapType result; + for (auto const& [name, qo] : qoMap) { + + // find the source for this qo + shared_ptr local = qo; + auto it = std::find_if(mAggregatorConfig.sources.begin(), mAggregatorConfig.sources.end(), + [&local](const AggregatorSource& source) { + const std::string token = local->getCheckName().substr(0, local->getCheckName().find('/')); + return token == source.name; + }); + + // if no source found, it is not here + if (it == mAggregatorConfig.sources.end()) { + continue; + } + + // search the qo in the objects of the source, if found we accept it. + // if the source has no qos specified we accept it. + auto source = *it; + if (source.objects.empty() || + find(source.objects.begin(), source.objects.end(), name) != source.objects.end()) { // no qo specified, we accept all + result[name] = qo; + } + } + + return result; +} + +std::optional getMaxCycle(const QualityObjectsMapType& qoMap) +{ + std::optional max{}; + for (const auto& [_, qo] : qoMap) { + auto cycle = qo->getMetadataOpt(repository::metadata_keys::cycleNumber); + if (cycle.has_value()) { + auto parsedCycle = repository::parseCycle(cycle.value()); + if (parsedCycle) { + max = std::max(parsedCycle.value(), max.value_or(0)); + } + } + } + return max; +} + +QualityObjectsType Aggregator::aggregate(QualityObjectsMapType& qoMap, const Activity& defaultActivity) +{ + auto filtered = filter(qoMap); + + Activity resultActivity; + if (filtered.empty()) { + resultActivity = defaultActivity; + } else { + // Aggregated Quality validity is an intersection of all Qualities used to produce it. + // This is to allow to trigger postprocessing on an update of the aggregated QualityObject + // and get a validFrom timestamp which allows to access all the input QualityObjects as well. + // Not sure if this is "correct", but I do not see a better solution at the moment... + resultActivity = activity_helpers::overlappingActivity( + filtered | std::views::transform( + [](const std::pair>& item) -> const Activity& { + return item.second->getActivity(); + })); + if (resultActivity.mValidity.isInvalid()) { + ILOG(Warning, Support) << "Overlapping validity of inputs QOs to aggregator " << mAggregatorConfig.name << " is invalid (disjoint validities of input objects). The last valid timestamp in the latest input object will be used instead." << ENDM; + auto lastTimestamp = std::ranges::max(filtered | std::views::values, {}, [](const std::shared_ptr& item) { + return item->getActivity().mValidity.getMax(); + })->getActivity() + .mValidity.getMax(); + resultActivity.mValidity = { lastTimestamp - 1, lastTimestamp }; + } + } + + const auto maxCycle = getMaxCycle(filtered); + const auto results = mAggregatorInterface->aggregate(filtered); + QualityObjectsType qualityObjects; + for (auto const& [qualityName, quality] : results) { + qualityObjects.emplace_back(std::make_shared( + quality, + mAggregatorConfig.name + "/" + qualityName, + mAggregatorConfig.detectorName, + UpdatePolicyTypeUtils::ToString(mAggregatorConfig.policyType))); + qualityObjects.back()->setActivity(resultActivity); + if (maxCycle.has_value()) { + qualityObjects.back()->addMetadata(repository::metadata_keys::cycleNumber, std::to_string(maxCycle.value())); + } + } + return qualityObjects; +} + +const std::string& Aggregator::getName() const +{ + return mAggregatorConfig.name; +} + +UpdatePolicyType Aggregator::getUpdatePolicyType() const +{ + return mAggregatorConfig.policyType; +} + +std::vector Aggregator::getObjectsNames() const +{ + return mAggregatorConfig.objectNames; +} + +bool Aggregator::getAllObjectsOption() const +{ + return mAggregatorConfig.allObjects; +} + +std::vector Aggregator::getSources() const +{ + return mAggregatorConfig.sources; +} + +std::vector Aggregator::getSources(core::DataSourceType type) +{ + std::vector matches; + std::copy_if(mAggregatorConfig.sources.begin(), mAggregatorConfig.sources.end(), std::back_inserter(matches), [&](const AggregatorSource& source) { + return source.type == type; + }); + return matches; +} + +AggregatorConfig Aggregator::extractConfig(const core::CommonSpec& commonSpec, const AggregatorSpec& aggregatorSpec) +{ + framework::Inputs inputs; + std::vector objectNames; + UpdatePolicyType updatePolicy = aggregatorSpec.updatePolicy; + bool checkAllObjects = false; + std::vector sources; + ILOG(Info, Devel) << "Extracting configuration of a new aggregator " << aggregatorSpec.aggregatorName << ENDM; + for (const auto& dataSource : aggregatorSpec.dataSources) { + if (!dataSource.isOneOf(DataSourceType::Check, DataSourceType::Aggregator)) { + throw std::runtime_error( + "Unsupported dataSource '" + dataSource.name + "' for an Aggregator '" + aggregatorSpec.aggregatorName + "'"); + } + ILOG(Info, Devel) << " Found a source : " << dataSource.name << ENDM; + AggregatorSource source(dataSource.type, dataSource.name); + + if (dataSource.type == DataSourceType::Check) { // Aggregator results do not come from DPL inputs + inputs.insert(inputs.end(), dataSource.inputs.begin(), dataSource.inputs.end()); + } + + // Subscribe on predefined MOs. + // If "MOs" are not set, the check function will be triggered whenever a new MO appears. + if (dataSource.subInputs.empty()) { + ILOG(Info, Devel) << " (no QOs specified, we take all)" << ENDM; + checkAllObjects = true; + updatePolicy = UpdatePolicyType::OnGlobalAny; + } else { + for (const auto& qoName : dataSource.subInputs) { + auto name = dataSource.name + "/" + qoName; + ILOG(Info, Devel) << " - " << name << ENDM; + objectNames.push_back(name); + source.objects.push_back(name); + } + } + sources.emplace_back(source); + } + + return { + aggregatorSpec.aggregatorName, + aggregatorSpec.moduleName, + aggregatorSpec.className, + aggregatorSpec.detectorName, + commonSpec.consulUrl, + aggregatorSpec.customParameters, + commonSpec.conditionDBUrl, + commonSpec.database, + aggregatorSpec.dataSources, + updatePolicy, + std::move(objectNames), + checkAllObjects, + std::move(inputs), + createUserOutputSpec(DataSourceType::Aggregator, aggregatorSpec.detectorName, aggregatorSpec.aggregatorName), + sources + }; +} + +void Aggregator::startOfActivity(const core::Activity& activity) +{ + if (mAggregatorInterface) { + mAggregatorInterface->startOfActivity(activity); + } else { + throw std::runtime_error("Trying to start an Activity on an empty AggregatorInterface '" + mAggregatorConfig.name + "'"); + } +} + +void Aggregator::endOfActivity(const core::Activity& activity) +{ + if (mAggregatorInterface) { + mAggregatorInterface->endOfActivity(activity); + } else { + throw std::runtime_error("Trying to end an Activity on an empty AggregatorInterface '" + mAggregatorConfig.name + "'"); + } +} + +} // namespace o2::quality_control::checker diff --git a/Framework/src/AggregatorInterface.cxx b/Framework/src/AggregatorInterface.cxx new file mode 100644 index 0000000000..26140fb4ef --- /dev/null +++ b/Framework/src/AggregatorInterface.cxx @@ -0,0 +1,47 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AggregatorInterface.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/AggregatorInterface.h" +#include "QualityControl/QCInputsAdapters.h" + +using namespace std; +using namespace o2::quality_control::core; + +namespace o2::quality_control::checker +{ + +std::map AggregatorInterface::aggregate(std::map>& qoMap) +{ + auto data = createData(qoMap); + return aggregate(data); +} + +std::map AggregatorInterface::aggregate(const core::QCInputs& data) +{ + return {}; +} + +void AggregatorInterface::startOfActivity(const Activity& activity) +{ + // noop, override it if you want. +} + +void AggregatorInterface::endOfActivity(const Activity& activity) +{ + // noop, override it if you want. +} + +} // namespace o2::quality_control::checker diff --git a/Framework/src/AggregatorRunner.cxx b/Framework/src/AggregatorRunner.cxx new file mode 100644 index 0000000000..1e27cfea01 --- /dev/null +++ b/Framework/src/AggregatorRunner.cxx @@ -0,0 +1,417 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AggregatorRunner.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/AggregatorRunner.h" + +// O2 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// QC +#include "QualityControl/DatabaseFactory.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Aggregator.h" +#include "QualityControl/runnerUtils.h" +#include "QualityControl/InfrastructureSpecReader.h" +#include "QualityControl/AggregatorRunnerFactory.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/ConfigParamGlo.h" +#include "QualityControl/Bookkeeping.h" +#include "QualityControl/WorkflowType.h" +#include "QualityControl/DataHeaderHelpers.h" + +using namespace AliceO2::Common; +using namespace AliceO2::InfoLogger; +using namespace o2::framework; +using namespace o2::configuration; +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; +using namespace std; +using namespace o2::monitoring; + +const auto current_diagnostic = boost::current_exception_diagnostic_information; + +namespace o2::quality_control::checker +{ + +AggregatorRunner::AggregatorRunner(AggregatorRunnerConfig arc, const std::vector& acs) + : mDeviceName(createAggregatorRunnerName()), + mRunnerConfig(std::move(arc)), + mAggregatorsConfig(acs), + mTotalNumberObjectsReceived(0), + mTotalNumberAggregatorExecuted(0), + mTotalNumberObjectsProduced(0) +{ + prepareInputs(); + prepareOutputs(); +} + +AggregatorRunner::~AggregatorRunner() +{ + ILOG(Debug, Trace) << "AggregatorRunner destructor (" << this << ")" << ENDM; +} + +void AggregatorRunner::prepareInputs() +{ + std::set alreadySeen; + int i = 0; + for (const auto& aggConfig : mAggregatorsConfig) { + for (auto input : aggConfig.inputSpecs) { + if (alreadySeen.count(input.binding) == 0) { + alreadySeen.insert(input.binding); + input.binding = "checkerOutput" + to_string(i++); + mInputs.emplace_back(input); + } + } + } +} + +void AggregatorRunner::prepareOutputs() +{ + for (const auto& aggConfig : mAggregatorsConfig) { + mOutputs.emplace_back(aggConfig.qoSpec); + } +} + +std::string AggregatorRunner::createAggregatorRunnerName() +{ + return AggregatorRunner::createAggregatorRunnerIdString(); // there is only one thus we can just take the idString +} + +void AggregatorRunner::init(framework::InitContext& iCtx) +{ + core::initInfologger(iCtx, mRunnerConfig.infologgerDiscardParameters, "aggregator"); + QcInfoLogger::setDetector(AggregatorRunner::getDetectorName(mAggregators)); + Bookkeeping::getInstance().init(mRunnerConfig.bookkeepingUrl); + + try { + initLibraries(); // we have to load libraries before we load ConfigurableParams, otherwise the corresponding ROOT dictionaries won't be found + // load config params + if (!ConfigParamGlo::keyValues.empty()) { + conf::ConfigurableParam::updateFromString(ConfigParamGlo::keyValues); + } + initDatabase(); + initMonitoring(); + initAggregators(); + } catch (...) { + ILOG(Fatal) << "Unexpected exception during initialization: " + << current_diagnostic(true) << ENDM; + throw; + } + + try { + // registering state machine callbacks + // FIXME: this is a workaround until we get some O2 PR in. + iCtx.services().get().set([this, services = iCtx.services()]() mutable { start(services); }); + iCtx.services().get().set([this]() { reset(); }); + iCtx.services().get().set([this]() { stop(); }); + } catch (o2::framework::RuntimeErrorRef& ref) { + ILOG(Error) << "Error during initialization: " << o2::framework::error_from_ref(ref).what << ENDM; + } +} + +void AggregatorRunner::run(framework::ProcessingContext& ctx) +{ + framework::InputRecord& inputs = ctx.inputs(); + for (auto const& ref : InputRecordWalker(inputs)) { // InputRecordWalker because the output of CheckRunner can be multi-part + ILOG(Debug, Trace) << "AggregatorRunner received data" << ENDM; + shared_ptr const qo = inputs.get(ref); + if (qo != nullptr) { + ILOG(Debug, Trace) << " It is a qo: " << qo->getName() << ENDM; + mQualityObjects[qo->getName()] = qo; + mTotalNumberObjectsReceived++; + mUpdatePolicyManager.updateObjectRevision(qo->getName()); + } + } + + auto qualityObjects = aggregate(); + store(qualityObjects); + send(qualityObjects, ctx.outputs()); + + mUpdatePolicyManager.updateGlobalRevision(); + + sendPeriodicMonitoring(); +} + +AggregatorRunner::QualityObjectsWithAggregatorNameVector AggregatorRunner::aggregate() +{ + ILOG(Debug, Trace) << "Aggregate called in AggregatorRunner, QOs in cache: " << mQualityObjects.size() << ENDM; + + QualityObjectsWithAggregatorNameVector allQOs; + for (auto const& aggregator : mAggregators) { + string aggregatorName = aggregator->getName(); + ILOG(Info, Devel) << "Processing aggregator: " << aggregatorName << ENDM; + + if (mUpdatePolicyManager.isReady(aggregatorName)) { + ILOG(Info, Devel) << " Quality Objects for the aggregator '" << aggregatorName << "' are ready, aggregating" << ENDM; + auto newQOs = aggregator->aggregate(mQualityObjects, *mActivity); // we give the whole list + mTotalNumberObjectsProduced += newQOs.size(); + mTotalNumberAggregatorExecuted++; + // we consider the output of the aggregators the same way we do the output of a check + for (const auto& qo : newQOs) { + mQualityObjects[qo->getName()] = qo; + mUpdatePolicyManager.updateObjectRevision(qo->getName()); + } + + allQOs.emplace_back(aggregatorName, newQOs); + + newQOs.clear(); + + mUpdatePolicyManager.updateActorRevision(aggregatorName); // Was aggregated, update latest revision + } else { + ILOG(Info, Devel) << " Quality Objects for the aggregator '" << aggregatorName << "' are not ready, ignoring" << ENDM; + } + } + return allQOs; +} + +void AggregatorRunner::store(QualityObjectsWithAggregatorNameVector& qualityObjectsWithAggregatorNames) +{ + const auto objectCount = std::accumulate(qualityObjectsWithAggregatorNames.begin(), qualityObjectsWithAggregatorNames.end(), 0, [](size_t count, const auto& namedQualityObject) { + return namedQualityObject.second.size() + count; + }); + + ILOG(Info, Devel) << "Storing " << objectCount << " QualityObjects" << ENDM; + + auto validFrom = getCurrentTimestamp(); + try { + for (const auto& [_, qualityObjects] : qualityObjectsWithAggregatorNames) { + for (const auto& qo : qualityObjects) { + mDatabase->storeQO(qo); + } + } + + if (!qualityObjectsWithAggregatorNames.empty() && !qualityObjectsWithAggregatorNames.front().second.empty()) { + const auto& qo = qualityObjectsWithAggregatorNames.front().second.front(); + ILOG(Debug, Devel) << "Validity of QO '" << qo->GetName() << "' is (" << qo->getValidity().getMin() << ", " << qo->getValidity().getMax() << ")" << ENDM; + } + + } catch (boost::exception& e) { + ILOG(Info, Devel) << "Unable to " << diagnostic_information(e) << ENDM; + } +} + +void AggregatorRunner::send(const QualityObjectsWithAggregatorNameVector& qualityObjectsWithAggregatorNames, framework::DataAllocator& allocator) +{ + for (const auto& [aggregatorName, qualityObjects] : qualityObjectsWithAggregatorNames) { + const auto concreteOutput = framework::DataSpecUtils::asConcreteDataMatcher(mAggregatorsMap.at(aggregatorName)->getConfig().qoSpec); + for (const auto& qualityObject : qualityObjects) { + allocator.snapshot(framework::Output{ concreteOutput.origin, concreteOutput.description, concreteOutput.subSpec }, *qualityObject); + } + } +} + +void AggregatorRunner::initDatabase() +{ + mDatabase = DatabaseFactory::create(mRunnerConfig.database.at("implementation")); + mDatabase->connect(mRunnerConfig.database); + ILOG(Info, Devel) << "Database that is going to be used > Implementation : " << mRunnerConfig.database.at("implementation") << " / Host : " << mRunnerConfig.database.at("host") << ENDM; +} + +void AggregatorRunner::initMonitoring() +{ + mCollector = MonitoringFactory::Get(mRunnerConfig.monitoringUrl); + mCollector->enableProcessMonitoring(); + mCollector->addGlobalTag(tags::Key::Subsystem, tags::Value::QC); + mCollector->addGlobalTag("AggregatorRunnerName", mDeviceName); + mTimer.reset(1000000); // 10 s. +} + +void AggregatorRunner::initAggregators() +{ + ILOG(Info, Devel) << "Initialization of the aggregators" << ENDM; + + // For every aggregator definition, create an Aggregator + for (const auto& aggregatorConfig : mAggregatorsConfig) { + ILOG(Info, Devel) << ">> Aggregator name : " << aggregatorConfig.name << ENDM; + try { + auto aggregator = make_shared(aggregatorConfig); + aggregator->init(); + mUpdatePolicyManager.addPolicy(aggregator->getName(), + aggregator->getUpdatePolicyType(), + aggregator->getObjectsNames(), + aggregator->getAllObjectsOption(), + false); + mAggregators.push_back(aggregator); + mAggregatorsMap.emplace(aggregator->getName(), aggregator); + } catch (...) { + // catch the configuration exception and print it to avoid losing it + ILOG(Error, Ops) << "Error creating aggregator '" << aggregatorConfig.name << "'" + << current_diagnostic(true) << ENDM; + continue; // skip this aggregator, it might still fail fatally later if another aggregator depended on it + } + } + + reorderAggregators(); +} + +void AggregatorRunner::initLibraries() +{ + std::set moduleNames; + for (const auto& config : mAggregatorsConfig) { + moduleNames.insert(config.moduleName); + } + for (const auto& moduleName : moduleNames) { + core::root_class_factory::loadLibrary(moduleName); + } +} + +bool AggregatorRunner::areSourcesIn(const std::vector& sources, + const std::vector>& aggregators) +{ + for (auto source : sources) { + auto it = find_if(aggregators.begin(), aggregators.end(), + [&](const std::shared_ptr& agg) { return (agg->getName() == source.name); }); + if (it == aggregators.end()) { + return false; + } + } + + return true; +} + +void AggregatorRunner::reorderAggregators() +{ + // Implementation + // This is a simple, light-weight, but sub-optimal implementation. + // One could build a proper tree (e.g. with boost Graph) and then apply a complex algorithm to order + // the nodes and find cycles. + // Instead this implementation goes through the aggregators and for each checks whether + // there are no dependencies or if they are all fulfilled. If it is the case the aggregator + // is moved at the end of the resulting vector. + // In case we have looped through all the remaining aggregators and nothing has been done, + // ie. no aggregator has its dependencies fulfilled, we stop and raise an error. + // This means that there is a cycle or that one of the dependencies does not exist. + // Note that by "fulfilled" we mean that all the sources of an aggregator are already + // in the result vector. + + std::vector> originals = mAggregators; + std::vector> results; + bool modificationLastIteration = true; + // As long as there are items in original and we did some modifications in the last iteration + while (!originals.empty() && modificationLastIteration) { + modificationLastIteration = false; + std::vector> toBeMoved; // we need it because we cannot modify the vectors while iterating over them + // Loop over remaining items in the original list + for (const auto& orig : originals) { + // if no Aggregator dependencies or Aggregator sources are all already in result + auto sources = orig->getSources(DataSourceType::Aggregator); + if (sources.empty() || areSourcesIn(sources, results)) { + // move from original to result + toBeMoved.push_back(orig); + modificationLastIteration = true; + } + } + // move the items from one vector to the other + for (const auto& item : toBeMoved) { + results.push_back(item); + originals.erase(std::remove(originals.begin(), originals.end(), item), originals.end()); + } + } + + if (!originals.empty()) { + string msg = + "Error in the aggregators definition : either there is a cycle " + "or an aggregator depends on an aggregator that does not exist."; + ILOG(Error, Ops) << msg << ENDM; + BOOST_THROW_EXCEPTION(FatalException() << errinfo_details(msg)); + } + assert(results.size() == mAggregators.size()); + mAggregators = results; + mAggregatorsMap.clear(); + for (const auto& aggregator : mAggregators) { + mAggregatorsMap.emplace(aggregator->getName(), aggregator); + } +} + +void AggregatorRunner::sendPeriodicMonitoring() +{ + if (mTimer.isTimeout()) { + mTimer.reset(1000000); // 10 s. + mCollector->send({ mTotalNumberObjectsReceived, "qc_aggregator_objects_received" }); + mCollector->send({ mTotalNumberAggregatorExecuted, "qc_aggregator_executed" }); + mCollector->send({ mTotalNumberObjectsProduced, "qc_aggregator_objects_produced" }); + mCollector->send({ mTimerTotalDurationActivity.getTime(), "qc_aggregator_duration" }); + } +} + +void AggregatorRunner::start(ServiceRegistryRef services) +{ + mActivity = std::make_shared(computeActivity(services, mRunnerConfig.fallbackActivity)); + mTimerTotalDurationActivity.reset(); + QcInfoLogger::setRun(mActivity->mId); + QcInfoLogger::setPartition(mActivity->mPartitionName); + ILOG(Info, Support) << "Starting run " << mActivity->mId << ENDM; + for (auto& aggregator : mAggregators) { + aggregator->startOfActivity(*mActivity); + } + + // register ourselves to the BK + if (!gSystem->Getenv("O2_QC_DONT_REGISTER_IN_BK")) { // Set this variable to disable the registration + ILOG(Debug, Devel) << "Registering aggregator to BookKeeping" << ENDM; + Bookkeeping::getInstance().registerProcess(mActivity->mId, mDeviceName, AggregatorRunner::getDetectorName(mAggregators), bkp::DplProcessType::QC_AGGREGATOR, ""); + } +} + +void AggregatorRunner::stop() +{ + ILOG(Info, Support) << "Stopping run " << mActivity->mId << ENDM; + for (auto& aggregator : mAggregators) { + aggregator->endOfActivity(*mActivity); + } +} + +void AggregatorRunner::reset() +{ + ILOG(Info, Devel) << "Reset" << ENDM; + + try { + mCollector.reset(); + mActivity = make_shared(); + } catch (...) { + // we catch here because we don't know where it will go in DPL's CallbackService + ILOG(Error, Support) << "Error caught in reset() : " + << current_diagnostic(true) << ENDM; + throw; + } +} + +std::string AggregatorRunner::getDetectorName(const std::vector>& aggregators) +{ + std::string detectorName; + for (auto& aggregator : aggregators) { + const std::string& thisDetector = aggregator->getDetector(); + if (detectorName.length() == 0) { + detectorName = thisDetector; + } else if (thisDetector != detectorName) { + detectorName = "MANY"; + break; + } + } + return detectorName; +} + +} // namespace o2::quality_control::checker diff --git a/Framework/src/AggregatorRunnerFactory.cxx b/Framework/src/AggregatorRunnerFactory.cxx new file mode 100644 index 0000000000..cdb05b1911 --- /dev/null +++ b/Framework/src/AggregatorRunnerFactory.cxx @@ -0,0 +1,111 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AggregatorRunnerFactory.cxx +/// \author Barthelemy von Haller +/// + +#include +#include + +#include +#include +#include +#include + +#include "QualityControl/AggregatorRunner.h" +#include "QualityControl/Aggregator.h" +#include "QualityControl/AggregatorRunnerFactory.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace std; +using namespace o2::framework; + +namespace o2::quality_control::checker +{ + +DataProcessorSpec AggregatorRunnerFactory::create(const core::CommonSpec& commonSpec, + const std::vector& aggregatorsSpec) +{ + AggregatorRunnerConfig const aggRunnerConfig = AggregatorRunnerFactory::extractRunnerConfig(commonSpec); + std::vector const aggConfigs = AggregatorRunnerFactory::extractAggregatorsConfig(commonSpec, aggregatorsSpec); + AggregatorRunner aggregatorRunner{ aggRunnerConfig, aggConfigs }; + + DataProcessorSpec newAggregatorRunner{ + aggregatorRunner.getDeviceName(), + aggregatorRunner.getInputs(), + aggregatorRunner.getOutputs(), + AlgorithmSpec{}, + aggRunnerConfig.options + }; + newAggregatorRunner.labels.emplace_back(AggregatorRunner::getLabel()); + framework::DataProcessorLabel resilientLabel = { "resilient" }; + newAggregatorRunner.labels.emplace_back(resilientLabel); + newAggregatorRunner.algorithm = adaptFromTask(std::move(aggregatorRunner)); + return newAggregatorRunner; +} + +// Specify a custom policy to trigger whenever something arrive regardless of the timeslice. +void AggregatorRunnerFactory::customizeInfrastructure(std::vector& policies) +{ + auto matcher = [label = AggregatorRunner::getLabel()](auto const& device) { + return std::find(device.labels.begin(), device.labels.end(), label) != device.labels.end(); + }; + policies.emplace_back(CompletionPolicyHelpers::consumeWhenAny("aggregatorRunnerCompletionPolicy", matcher)); +} + +AggregatorRunnerConfig AggregatorRunnerFactory::extractRunnerConfig(const core::CommonSpec& commonSpec) +{ + Options options{ + { "runNumber", framework::VariantType::String, { "Run number" } }, + { "qcConfiguration", VariantType::Dict, emptyDict(), { "Some dictionary configuration" } } + }; + + core::Activity fallbackActivity{ + commonSpec.activityNumber, + commonSpec.activityType, + commonSpec.activityPeriodName, + commonSpec.activityPassName, + commonSpec.activityProvenance, + { commonSpec.activityStart, commonSpec.activityEnd }, + commonSpec.activityBeamType, + commonSpec.activityPartitionName, + commonSpec.activityFillNumber, + commonSpec.activityOriginalNumber + }; + + return { + commonSpec.database, + commonSpec.consulUrl, + commonSpec.monitoringUrl, + commonSpec.bookkeepingUrl, + commonSpec.infologgerDiscardParameters, + fallbackActivity, + options + }; +} + +std::vector AggregatorRunnerFactory::extractAggregatorsConfig( + const core::CommonSpec& commonSpec, + const std::vector& aggregatorsSpec) +{ + std::vector aggConfigs; + for (const auto& aggregatorSpec : aggregatorsSpec) { + if (aggregatorSpec.active) { + ILOG(Debug, Devel) << ">> Aggregator name : " << aggregatorSpec.aggregatorName << ENDM; + aggConfigs.emplace_back(Aggregator::extractConfig(commonSpec, aggregatorSpec)); + } + } + return aggConfigs; +} + +} // namespace o2::quality_control::checker diff --git a/Framework/src/Bookkeeping.cxx b/Framework/src/Bookkeeping.cxx new file mode 100644 index 0000000000..d779ff1e57 --- /dev/null +++ b/Framework/src/Bookkeeping.cxx @@ -0,0 +1,151 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Bookkeeping.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/Bookkeeping.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Activity.h" +#include "BookkeepingApi/BkpClientFactory.h" +#include "BookkeepingApi/BkpClient.h" +#include +#include +#include +#include + +using namespace o2::bkp::api; + +namespace o2::quality_control::core +{ + +std::string readClientToken() +{ + // first we try to find the token in the environment variable + if (auto tokenEnv = std::getenv("QC_BKP_CLIENT_TOKEN"); tokenEnv != NULL && std::strlen(tokenEnv) > 0) { + ILOG(Info, Ops) << "Using token from environment variable QC_BKP_CLIENT_TOKEN" << ENDM; + return tokenEnv; + } + + // if the environment variable is not set, we try to read it from a file + const std::string tokenFileName = "qc_bkp_client_token.txt"; + std::filesystem::path tokenPath = std::filesystem::current_path() / tokenFileName; + + std::error_code ec; + if (std::filesystem::exists(tokenPath, ec) && !ec.value()) { + std::string token; + std::ifstream tokenFile(tokenPath); + // from now on, we throw if something goes wrong, because the user is clearly trying to use a token file + if (!tokenFile.is_open()) { + throw std::runtime_error("BKP token file '" + tokenFileName + "' was provided but cannot be opened, check permissions"); + } + std::getline(tokenFile, token); + if (token.empty()) { + throw std::runtime_error("BKP token file '" + tokenFileName + "' was provided but it is empty, please provide a valid token"); + } + ILOG(Debug, Devel) << "Using token from file qc_bkp_client_token.txt" << ENDM; + return token; + } + + ILOG(Debug, Devel) << "Could not find an env var QC_BKP_CLIENT_TOKEN nor a qc_bkp_client_token.txt file, using BKP client without an authentication token" << ENDM; + return ""; +} + +void Bookkeeping::init(const std::string& url) +{ + if (mInitialized) { + if (mUrl == url) { + ILOG(Debug, Devel) << "Bookkeeping already initialized with the same URL, ignoring." << ENDM; + return; + } else { + ILOG(Warning, Support) << "Initializing the Bookkeeping although it has already been initialized with a different URL (" << url << " vs " << mUrl << ENDM; + } + } + + if (url.empty()) { + ILOG(Warning, Support) << "No URL provided for Bookkeeping. Nothing will be stored nor retrieved." << ENDM; + return; + } + + const auto token = readClientToken(); + + try { + if (!token.empty()) { + mClient = BkpClientFactory::create(url, token); + } else { + mClient = BkpClientFactory::create(url); + } + } catch (std::runtime_error& error) { + ILOG(Warning, Support) << "Error connecting to Bookkeeping: " << error.what() << ENDM; + return; + } + + if (mClient == nullptr) { // make sure we did not get an empty pointer + ILOG(Warning, Support) << "Error - we got an empty pointer to Bookkeeping" << ENDM; + return; + } + + ILOG(Debug, Devel) << "Bookkeeping initialized" << ENDM; + mInitialized = true; +} + +std::string getHostName() +{ + char hostname[256]; + if (gethostname(hostname, sizeof(hostname)) == 0) { + return { hostname }; + } else { + return ""; + } +} + +void Bookkeeping::registerProcess(int runNumber, const std::string& name, const std::string& detector, bkp::DplProcessType type, const std::string& args) +{ + if (!mInitialized) { + return; + } + + std::thread([this, runNumber, type, name, args, detector]() { + try { + this->mClient->dplProcessExecution()->registerProcessExecution(runNumber, type, getHostName(), name, args, detector); + } catch (std::runtime_error& error) { // catch here because we are in a thread + ILOG(Warning, Devel) << "Failed registration to the BookKeeping: " << error.what() << ENDM; + } + }).detach(); +} + +std::vector Bookkeeping::sendFlagsForSynchronous(uint32_t runNumber, const std::string& detectorName, const std::vector& qcFlags) +{ + if (!mInitialized) { + return {}; + } + return mClient->qcFlag()->createForSynchronous(runNumber, detectorName, qcFlags); +} + +std::vector Bookkeeping::sendFlagsForDataPass(uint32_t runNumber, const std::string& passName, const std::string& detectorName, const std::vector& qcFlags) +{ + if (!mInitialized) { + return {}; + } + return mClient->qcFlag()->createForDataPass(runNumber, passName, detectorName, qcFlags); +} + +std::vector Bookkeeping::sendFlagsForSimulationPass(uint32_t runNumber, const std::string& productionName, const std::string& detectorName, const std::vector& qcFlags) +{ + if (!mInitialized) { + return {}; + } + return mClient->qcFlag()->createForSimulationPass(runNumber, productionName, detectorName, qcFlags); +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/BookkeepingQualitySink.cxx b/Framework/src/BookkeepingQualitySink.cxx new file mode 100644 index 0000000000..009ae216a7 --- /dev/null +++ b/Framework/src/BookkeepingQualitySink.cxx @@ -0,0 +1,189 @@ +// Copyright 2024 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file BookkeepingQualitySink.cxx +/// \author Michal Tichak +/// + +#include "QualityControl/BookkeepingQualitySink.h" +#include +#include +#include +#include +#include +#include "QualityControl/QualitiesToFlagCollectionConverter.h" +#include "QualityControl/QualityObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/runnerUtils.h" + +#include +#include +#include +#include +#include + +namespace o2::quality_control::core +{ + +void BookkeepingQualitySink::customizeInfrastructure(std::vector& policies) +{ + using namespace o2::framework; + auto matcher = [label = BookkeepingQualitySink::getLabel()](auto const& device) { + return std::find(device.labels.begin(), device.labels.end(), label) != device.labels.end(); + }; + policies.emplace_back(CompletionPolicyHelpers::consumeWhenAny("BookkeepingQualitySinkCompletionPolicy", matcher)); +} + +void BookkeepingQualitySink::init(framework::InitContext& iCtx) +{ + Bookkeeping::getInstance().init(mGrpcUri); + initInfologger(iCtx, {}, "bkqsink/", ""); + + try { // registering state machine callbacks + iCtx.services().get().set([this, services = iCtx.services()]() mutable { start(services); }); + } catch (o2::framework::RuntimeErrorRef& ref) { + ILOG(Error) << "Error during initialization: " << o2::framework::error_from_ref(ref).what << ENDM; + } + + ILOG(Info, Devel) << "Initialized BookkeepingQualitySink" << ENDM; +} + +void BookkeepingQualitySink::start(framework::ServiceRegistryRef services) +{ + Activity fallback; // no proper fallback as we don't have the config in this device + auto currentActivity = computeActivity(services, fallback); + QcInfoLogger::setRun(currentActivity.mId); +} + +void BookkeepingQualitySink::send(const std::string& grpcUri, const BookkeepingQualitySink::FlagsMap& flags, Provenance provenance) +{ + auto& bkpClient = o2::quality_control::core::Bookkeeping::getInstance(); + + std::optional runNumber; + std::optional passName; + std::optional periodName; + + for (auto& [detector, qoMap] : flags) { + ILOG(Info, Support) << "Processing flags for detector: " << detector << ENDM; + + std::vector bkpQcFlags{}; + for (auto& [qoName, converter] : qoMap) { + if (converter == nullptr) { + continue; + } + if (provenance == Provenance::AsyncQC || provenance == Provenance::MCQC) { + auto runDuration = ccdb::BasicCCDBManager::instance().getRunDuration(converter->getRunNumber(), false); + converter->updateValidityInterval({ static_cast(runDuration.first), static_cast(runDuration.second) }); + } + + auto flagCollection = converter->getResult(); + if (flagCollection == nullptr) { + continue; + } + if (!runNumber.has_value()) { + runNumber = flagCollection->getRunNumber(); + } + if (!passName.has_value()) { + passName = flagCollection->getPassName(); + } + if (!periodName.has_value()) { + periodName = flagCollection->getPeriodName(); + } + + for (const auto& flag : *flagCollection) { + // BKP uses start/end of run for missing time values, so we are using this functionality in order to avoid + // determining these values by ourselves (see TaskRunner::start() for details). mtichak checked with mboulais that + // it is okay to do so. + bkpQcFlags.emplace_back(QcFlag{ + .flagTypeId = flag.getFlag().getID(), + .from = flag.getStart() == gFullValidityInterval.getMin() ? std::nullopt : std::optional{ flag.getStart() }, + .to = flag.getEnd() == gFullValidityInterval.getMax() ? std::nullopt : std::optional{ flag.getEnd() }, + .origin = flag.getSource(), + .comment = flag.getComment() }); + } + } + + if (bkpQcFlags.empty()) { + ILOG(Info, Support) << "No flags for detector '" << detector << "', skipping" << ENDM; + continue; + } + try { + switch (provenance) { + case Provenance::SyncQC: + bkpClient.sendFlagsForSynchronous(runNumber.value(), detector, bkpQcFlags); + break; + case Provenance::AsyncQC: + bkpClient.sendFlagsForDataPass(runNumber.value(), passName.value(), detector, bkpQcFlags); + break; + case Provenance::MCQC: + bkpClient.sendFlagsForSimulationPass(runNumber.value(), periodName.value(), detector, bkpQcFlags); + break; + } + ILOG(Info, Support) << "Sent " << bkpQcFlags.size() << " flags for detector '" << detector << "'" << ENDM; + } catch (const std::runtime_error& err) { + ILOG(Error, Support) << "Encountered errors while sending flags for detector '" << detector << "', details: " << err.what() << ENDM; + } + } +} + +BookkeepingQualitySink::BookkeepingQualitySink(const std::string& grpcUri, Provenance provenance, SendCallback sendCallback) + : mGrpcUri{ grpcUri }, mProvenance{ provenance }, mSendCallback{ std::move(sendCallback) } {} + +auto collectionForQualityObject(const QualityObject& qualityObject) -> std::unique_ptr +{ + return std::make_unique( + qualityObject.getName(), + qualityObject.getDetectorName(), + gFullValidityInterval, + qualityObject.getActivity().mId, + qualityObject.getActivity().mPeriodName, + qualityObject.getActivity().mPassName, + qualityObject.getActivity().mProvenance); +} + +void BookkeepingQualitySink::run(framework::ProcessingContext& context) +{ + for (auto const& ref : framework::InputRecordWalker(context.inputs())) { + std::unique_ptr qualityObject; + try { + qualityObject = framework::DataRefUtils::as(ref); + } catch (...) { + ILOG(Warning, Support) << "Unexpected message received, QualityObject expected" << ENDM; + continue; + } + auto& converter = mFlagsMap[qualityObject->getDetectorName()][qualityObject->getName()]; + if (converter == nullptr) { + converter = std::make_unique(collectionForQualityObject(*qualityObject), qualityObject->getPath()); + } + (*converter)(*qualityObject); + } +} + +void BookkeepingQualitySink::endOfStream(framework::EndOfStreamContext&) +{ + sendAndClear(); +} + +void BookkeepingQualitySink::stop() +{ + sendAndClear(); +} + +void BookkeepingQualitySink::sendAndClear() +{ + if (!mFlagsMap.empty()) { + mSendCallback(mGrpcUri, mFlagsMap, mProvenance); + } + mFlagsMap.clear(); +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/CcdbDatabase.cxx b/Framework/src/CcdbDatabase.cxx index 821b176bda..3fd6a0fd5c 100644 --- a/Framework/src/CcdbDatabase.cxx +++ b/Framework/src/CcdbDatabase.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -14,86 +15,422 @@ /// #include "QualityControl/CcdbDatabase.h" -#include "Common/Exceptions.h" -#include +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Version.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/ActivityHelpers.h" +#include "QualityControl/ObjectMetadataKeys.h" + +// O2 +#include +#include +#include +// ROOT +#include +#include +#include +#include +#include +#include +// std #include #include -#include "TBufferJSON.h" +#include +// boost +#include +#include +#include +#include +#include +// misc +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +#include using namespace std::chrono; using namespace AliceO2::Common; +using namespace o2::quality_control::core; +using namespace std; +using namespace rapidjson; namespace o2::quality_control::repository { - -using namespace std; - -CcdbDatabase::CcdbDatabase() : mUrl("") {} +CcdbDatabase::CcdbDatabase() : ccdbApi(new o2::ccdb::CcdbApi()) +{ +} CcdbDatabase::~CcdbDatabase() { disconnect(); } -void CcdbDatabase::connect(std::string host, std::string database, std::string username, std::string password) +void CcdbDatabase::connect(const string& host, const string& /*database*/, const string& /*username*/, const string& /*password*/) { mUrl = host; - ccdbApi.init(mUrl); + init(); } void CcdbDatabase::connect(const std::unordered_map& config) { mUrl = config.at("host"); - ccdbApi.init(mUrl); + init(); + if (config.count("maxObjectSize")) { + mMaxObjectSize = std::stoi(config.at("maxObjectSize")); + } } -void CcdbDatabase::store(std::shared_ptr mo) +void CcdbDatabase::init() { - if (mo->getName().length() == 0 || mo->getTaskName().length() == 0) { + ccdbApi->init(mUrl); + ccdbApi->setCurlRetriesParameters(5); +} + +void CcdbDatabase::handleStorageError(const string& path, int result) +{ + if (result == -1 /* object bigger than maxObjectSize */) { + static AliceO2::InfoLogger::InfoLogger::AutoMuteToken msgLimit(LogWarningSupport, 1, 600); // send it once every 10 minutes + string msg = "object " + path + " is bigger than the maximum allowed size (" + to_string(mMaxObjectSize) + "B) - skipped"; + ILOG_INST.log(msgLimit, "%s", msg.c_str()); + } + + if (result == -2 /* curl initialization error */ || result > 0 /* curl error */) { + mDatabaseFailure = true; + mFailureTimer.reset(mFailureDelay * 1000000); + string msg = "Unable to store object " + path + ". Next attempt to store objects in " + to_string(mFailureDelay) + " seconds."; + ILOG(Warning, Ops) << msg << ENDM; + } +} + +bool CcdbDatabase::isDbInFailure() +{ + if (mDatabaseFailure) { + if (mFailureTimer.isTimeout()) { + mDatabaseFailure = false; + } else { + ILOG(Debug, Devel) << "Storage is disabled following a failure, this object won't be stored. New attempt in " << (int)mFailureTimer.getRemainingTime() << " seconds" << ENDM; + return true; + } + } + return false; +} + +void CcdbDatabase::addFrameworkMetadata(map& fullMetadata, string detectorName, string className) +{ + fullMetadata[metadata_keys::qcVersion] = Version::GetQcVersion().getString(); + fullMetadata[metadata_keys::qcDetectorCode] = std::move(detectorName); + fullMetadata[metadata_keys::qcAdjustableEOV] = "1"; // QC-936 : this is to allow the modification of the end of validity. + // ObjectType says TObject and not MonitorObject due to a quirk in the API. Once fixed, remove this. + fullMetadata[metadata_keys::objectType] = std::move(className); +} + +void CcdbDatabase::storeAny(const void* obj, std::type_info const& typeInfo, std::string const& path, std::map const& metadata, + std::string const& detectorName, std::string const& taskName, long from, long to) +{ + if (obj == nullptr) { + BOOST_THROW_EXCEPTION(DatabaseException() + << errinfo_details("Cannot store a null pointer.")); + } + if (path.length() == 0) { BOOST_THROW_EXCEPTION(DatabaseException() << errinfo_details("Object and task names can't be empty. Do not store.")); } + if (path.find_first_of("\t\n ") != string::npos) { + BOOST_THROW_EXCEPTION(DatabaseException() + << errinfo_details("Object and task names can't contain white spaces. Do not store.")); + } + + if (isDbInFailure()) { + return; + } + + // metadata + map fullMetadata(metadata); + addFrameworkMetadata(fullMetadata, detectorName, o2::utils::MemFileHelper::getClassName(typeInfo)); + fullMetadata[metadata_keys::qcTaskName] = taskName; + + // other attributes + if (from == -1) { + from = getCurrentTimestamp(); + } + if (to == -1) { + to = from + 1000l * 60 * 60 * 24 * 365 * 10; // ~10 years since the start of validity + } + + ILOG(Debug, Support) << "Storing object " << path << " of type " << fullMetadata[metadata_keys::objectType] << ENDM; + int result = ccdbApi->storeAsTFile_impl(obj, typeInfo, path, fullMetadata, from, to, mMaxObjectSize); + + handleStorageError(path, result); +} + +// Monitor object +void CcdbDatabase::storeMO(std::shared_ptr mo) +{ + if (mo->getName().length() == 0 || mo->getTaskName().length() == 0) { + BOOST_THROW_EXCEPTION(DatabaseException() + << errinfo_details("Object and task names can't be empty. Do not store. ")); + } if (mo->getName().find_first_of("\t\n ") != string::npos || mo->getTaskName().find_first_of("\t\n ") != string::npos) { BOOST_THROW_EXCEPTION(DatabaseException() - << errinfo_details("Object and task names can't contain white spaces. Do not store.")); + << errinfo_details("Object and task names can't contain white spaces. Do not store.")); + } + + if (isDbInFailure()) { + return; + } + + map metadata = activity_helpers::asDatabaseMetadata(mo->getActivity()); + + // user metadata + map userMetadata = mo->getMetadataMap(); + if (!userMetadata.empty()) { + metadata.insert(userMetadata.begin(), userMetadata.end()); + } + + // extract object and metadata from MonitorObject + TObject* obj = mo->getObject(); + + // QC metadata (prefix qc_) + addFrameworkMetadata(metadata, mo->getDetectorName(), mo->getObject()->IsA()->GetName()); + metadata[metadata_keys::qcTaskName] = mo->getTaskName(); + metadata[metadata_keys::qcTaskClass] = mo->getTaskClass(); + + // path attributes + string path = mo->getPath(); + auto validity = mo->getValidity(); + auto from = static_cast(validity.getMin()); + auto to = static_cast(validity.getMax()); + + if (from == -1 || from == 0 || validity.getMin() == gInvalidValidityInterval.getMin() || validity.getMin() == gFullValidityInterval.getMin()) { + from = getCurrentTimestamp(); + } + if (to == -1 || to == 0 || validity.getMax() == gInvalidValidityInterval.getMax() || validity.getMax() == gFullValidityInterval.getMax()) { + to = from + 1000l * 60 * 60 * 24 * 365 * 10; // ~10 years since the start of validity + } + if (from == to) { + ILOG(Warning, Support) << "The validity start of '" << mo->GetName() << "' is equal to validity end (" << from << ", " << to << "). The validity end will be extended by 1ms to allow for storage." << ENDM; + to += 1; + } + + if (from > to) { + ILOG(Error, Support) << "The validity start of '" << mo->GetName() << "' later than the end (" << from << ", " << to << "). The object will not be stored" << ENDM; + return; + } + + ILOG(Debug, Support) << "Storing MonitorObject " << path << ENDM; + int result = ccdbApi->storeAsTFileAny(obj, path, metadata, from, to, mMaxObjectSize); + + handleStorageError(path, result); +} + +void CcdbDatabase::storeQO(std::shared_ptr qo) +{ + if (isDbInFailure()) { + return; + } + + // metadata + map metadata = activity_helpers::asDatabaseMetadata(qo->getActivity()); + // QC metadata (prefix qc_) + addFrameworkMetadata(metadata, qo->getDetectorName(), qo->IsA()->GetName()); + metadata[metadata_keys::qcQuality] = std::to_string(qo->getQuality().getLevel()); + metadata[metadata_keys::qcCheckName] = qo->getCheckName(); + // user metadata + map userMetadata = qo->getMetadataMap(); + if (!userMetadata.empty()) { + metadata.insert(userMetadata.begin(), userMetadata.end()); + } + + // other attributes + string path = qo->getPath(); + auto validity = qo->getValidity(); + auto from = static_cast(validity.getMin()); + auto to = static_cast(validity.getMax()); + + if (from == -1 || from == 0 || validity.getMin() == gInvalidValidityInterval.getMin() || validity.getMin() == gFullValidityInterval.getMin()) { + from = getCurrentTimestamp(); + } + if (to == -1 || to == 0 || validity.getMax() == gInvalidValidityInterval.getMax() || validity.getMax() == gFullValidityInterval.getMax()) { + to = from + 1000l * 60 * 60 * 24 * 365 * 10; // ~10 years since the start of validity } - string path = mo->getTaskName() + "/" + mo->getName(); - map metadata; - metadata["quality"] = std::to_string(mo->getQuality().getLevel()); - long from = getCurrentTimestamp(); - long to = getFutureTimestamp(60 * 60 * 24 * 365 * 10); // todo set a proper timestamp for the end + if (from == to) { + ILOG(Warning, Support) << "The validity start of '" << qo->GetName() << "' is equal to validity end (" << from << ", " << to << "). The validity end will be extended by 1ms to allow for storage." << ENDM; + to += 1; + } - cout << "storing : " << path << endl; - ccdbApi.store(mo.get(), path, metadata, from, to); + if (from > to) { + ILOG(Error, Support) << "The validity start of '" << qo->GetName() << "' later than the end (" << from << ", " << to << "). The object will not be stored" << ENDM; + return; + } + + ILOG(Debug, Support) << "Storing quality object " << path << " (" << qo->getName() << ")" << ENDM; + int result = ccdbApi->storeAsTFileAny(qo.get(), path, metadata, from, to); + + handleStorageError(path, result); } -/** - * Struct to store the data we will receive from the CCDB with CURL. - */ -struct MemoryStruct { - char* memory; - unsigned int size; -}; +TObject* CcdbDatabase::retrieveTObject(std::string path, std::map const& metadata, long timestamp, std::map* headers) +{ + if (timestamp == Timestamp::Latest) { + auto latestValidity = getLatestObjectValidity(path, metadata); + if (latestValidity.isInvalid()) { + return nullptr; + } + timestamp = latestValidity.getMin(); + } + // we try first to load a TFile + auto* object = ccdbApi->retrieveFromTFileAny(path, metadata, timestamp, headers); + if (object == nullptr) { + ILOG(Warning, Support) << "We could NOT retrieve the object " << path << " with timestamp " << timestamp << "." << ENDM; + ILOG(Debug, Support) << "and with metadata:" << ENDM; + for (auto [metaKey, metaVal] : metadata) { + ILOG(Debug, Support) << metaKey << ", " << metaVal << ENDM; + } + return nullptr; + } + ILOG(Debug, Support) << "Retrieved object " << path << " with timestamp " << timestamp << ENDM; + return object; +} -core::MonitorObject* CcdbDatabase::retrieve(std::string taskName, std::string objectName) +void* CcdbDatabase::retrieveAny(const type_info& tinfo, const string& path, const map& metadata, long timestamp, std::map* headers, const string& createdNotAfter, const string& createdNotBefore) { + if (timestamp == Timestamp::Latest) { + auto latestValidity = getLatestObjectValidity(path, metadata); + if (latestValidity.isInvalid()) { + return nullptr; + } + timestamp = latestValidity.getMin(); + } + auto* object = ccdbApi->retrieveFromTFile(tinfo, path, metadata, timestamp, headers, "", createdNotAfter, createdNotBefore); + if (object == nullptr) { + ILOG(Warning, Support) << "We could NOT retrieve the object " << path << " with timestamp " << timestamp << "." << ENDM; + return nullptr; + } + ILOG(Debug, Support) << "Retrieved object " << path << " with timestamp " << timestamp << ENDM; + return object; +} - string path = taskName + "/" + objectName; - map metadata; +std::shared_ptr CcdbDatabase::retrieveMO(std::string objectPath, std::string objectName, + long timestamp, const core::Activity& activity, + const std::map& metadataToRetrieve) +{ + string fullPath = activity.mProvenance + "/" + objectPath + "/" + objectName; + map headers; + map metadata = activity_helpers::asDatabaseMetadata(activity, false); + metadata.insert(metadataToRetrieve.begin(), metadataToRetrieve.end()); + TObject* obj = retrieveTObject(fullPath, metadata, timestamp, &headers); + + // no object found + if (obj == nullptr) { + if (headers.count("Error") > 0) { + ILOG(Error, Support) << headers["Error"] << ENDM; + } + return nullptr; + } + + // retrieve headers to determine the version of the QC framework + Version objectVersion(headers[metadata_keys::qcVersion]); + ILOG(Debug, Devel) << "Version of object is " << objectVersion << ENDM; + + std::shared_ptr mo; + if (objectVersion == Version("0.0.0") || objectVersion < Version("0.25")) { + ILOG(Debug, Devel) << "Version of object " << fullPath << " is < 0.25" << ENDM; + // The object is either in a TFile or is a blob but it was stored with storeAsTFile as a full MO + mo.reset(dynamic_cast(obj)); + if (mo == nullptr) { + ILOG(Error, Devel) << "Could not cast the object " << fullPath << " to MonitorObject (objectVersion: " << objectVersion << ")" << ENDM; + return nullptr; + } + } else { + // Version >= 0.25 -> the object is stored directly unencapsulated + ILOG(Debug, Devel) << "Version of object " << fullPath << " is >= 0.25" << ENDM; + mo = make_shared(obj, headers[metadata_keys::qcTaskName], headers[metadata_keys::qcTaskClass], headers[metadata_keys::qcDetectorCode]); + // TODO should we remove the headers we know are general such as ETag and qc_task_name ? + mo->addMetadata(headers); + // we could just copy the argument here, but this would not cover cases where the activity in headers has more non-default fields + mo->setActivity(activity_helpers::asActivity(headers, activity.mProvenance)); + } + mo->setIsOwner(true); + return mo; +} - TObject* object = ccdbApi.retrieve(path, metadata, getCurrentTimestamp()); - return dynamic_cast(object); +std::shared_ptr CcdbDatabase::retrieveQO(std::string qoPath, long timestamp, + const core::Activity& activity, + const std::map& metadataToRetrieve) +{ + map headers; + map metadata = activity_helpers::asDatabaseMetadata(activity, false); + metadata.insert(metadataToRetrieve.begin(), metadataToRetrieve.end()); + auto fullPath = activity.mProvenance + "/" + qoPath; + TObject* obj = retrieveTObject(fullPath, metadata, timestamp, &headers); + if (obj == nullptr) { + return nullptr; + } + std::shared_ptr qo(dynamic_cast(obj)); + if (qo == nullptr) { + ILOG(Error, Devel) << "Could not cast the object " << fullPath << " to QualityObject" << ENDM; + } else { + // TODO should we remove the headers we know are general such as ETag and qc_task_name ? + qo->addMetadata(headers); + // we could just copy the argument here, but this would not cover cases where the activity in headers has more non-default fields + qo->setActivity(activity_helpers::asActivity(headers, activity.mProvenance)); + } + return qo; } -std::string CcdbDatabase::retrieveJson(std::string taskName, std::string objectName) +std::string CcdbDatabase::retrieveJson(std::string path, long timestamp, const std::map& metadata) { - std::unique_ptr monitor(retrieve(taskName, objectName)); - if (monitor == nullptr) { - return std::string(); + map headers; + Document jsonDocument; + + // Get object + auto* tobj = retrieveTObject(path, metadata, timestamp, &headers); + if (tobj == nullptr) { + return {}; + } + + // Convert object to JSON string + TObject* toConvert = nullptr; + if (tobj->IsA() == MonitorObject::Class()) { // a full MO -> pre-v0.25 + std::shared_ptr mo(dynamic_cast(tobj)); + toConvert = mo->getObject(); + mo->setIsOwner(false); + } else if (tobj->IsA() == QualityObject::Class()) { + toConvert = dynamic_cast(tobj); + } else { // something else but a TObject + toConvert = tobj; + } + if (toConvert == nullptr) { + ILOG(Error, Support) << "Unable to get the object to convert" << ENDM; + return {}; + } + TString json = TBufferJSON::ConvertToJSON(toConvert); + delete toConvert; + + // Prepare JSON document and add metadata + if (jsonDocument.Parse(json.Data()).HasParseError()) { + ILOG(Error, Support) << "Unable to parse the JSON returned by TBufferJSON for object " << path << ENDM; + return {}; } - std::unique_ptr obj(monitor->getObject()); - monitor->setIsOwner(false); - TString json = TBufferJSON::ConvertToJSON(obj.get()); - return json.Data(); + rapidjson::Document::AllocatorType& allocator = jsonDocument.GetAllocator(); + rapidjson::Value object(rapidjson::Type::kObjectType); + for (auto const& [key, value] : headers) { + rapidjson::Value k(key.c_str(), allocator); + rapidjson::Value v(value.c_str(), allocator); + object.AddMember(k, v, allocator); + } + jsonDocument.AddMember("metadata", object, allocator); + + // Convert to string + StringBuffer buffer; + buffer.Clear(); + Writer writer(buffer); + jsonDocument.Accept(writer); + if (auto maxSize = std::string().max_size(); buffer.GetSize() > maxSize) { + throw std::runtime_error("JSON buffer contains too large string: " + std::to_string(buffer.GetSize()) + ", while std::string::max_size() is " + maxSize); + } + return strdup(buffer.GetString()); } void CcdbDatabase::disconnect() @@ -101,16 +438,14 @@ void CcdbDatabase::disconnect() // NOOP for CCDB } -void CcdbDatabase::prepareTaskDataContainer(std::string taskName) +void CcdbDatabase::prepareTaskDataContainer(std::string /*taskName*/) { // NOOP for CCDB } -std::string CcdbDatabase::getListing(std::string path, std::string accept) +std::string CcdbDatabase::getListingAsString(const std::string& subpath, const std::string& accept, bool latestOnly) { - std::string tempString = ccdbApi.list(path, false, accept); - - return tempString; + return ccdbApi->list(subpath, latestOnly, accept); } /// trim from start (in place) @@ -127,12 +462,12 @@ static inline void rtrim(std::string& s) s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); } -std::vector CcdbDatabase::getListOfTasksWithPublications() +std::vector CcdbDatabase::getListing(const std::string& subpath) { std::vector result; - // Get the listing from CCDB - string listing = getListing(); + // Get the listing from CCDB (folder qc) + string listing = getListingAsString(subpath); // Split the string we received, by line. Also trim it and remove empty lines. std::stringstream ss(listing); @@ -148,23 +483,82 @@ std::vector CcdbDatabase::getListOfTasksWithPublications() return result; } +boost::property_tree::ptree CcdbDatabase::getListingAsPtree(const std::string& path, const std::map& metadata, bool latestOnly) +{ + // CCDB accepts metadata filters as slash-separated key=value pairs at the end of the object path + std::stringstream pathWithMetadata; + pathWithMetadata << path; + for (const auto& [key, value] : metadata) { + pathWithMetadata << '/' << key << '=' << value; + } + + std::stringstream listingAsStringStream{ getListingAsString(pathWithMetadata.str(), "application/json", latestOnly) }; + + boost::property_tree::ptree listingAsTree; + try { + boost::property_tree::read_json(listingAsStringStream, listingAsTree); + } catch (const boost::property_tree::json_parser::json_parser_error&) { + ILOG(Error, Support) << "Failed to parse json in CcdbDatabase::getListingAsPtree from data: " << listingAsStringStream.str() << ENDM; + } + + return listingAsTree; +} + +core::ValidityInterval CcdbDatabase::getLatestObjectValidity(const std::string& path, const std::map& metadata) +{ + auto listing = getListingAsPtree(path, metadata, true); + if (listing.count("objects") == 0) { + ILOG(Warning, Support) << "Could not get a valid listing from db '" << mUrl << "' for latestObjectMetadata '" << path << "'" << ENDM; + return gInvalidValidityInterval; + } + const auto& objects = listing.get_child("objects"); + if (objects.empty()) { + return gInvalidValidityInterval; + } else if (objects.size() > 1) { + ILOG(Warning, Support) << "Expected just one metadata entry for object '" << path << "'. Trying to continue by using the first." << ENDM; + } + const auto& latestObjectMetadata = objects.front().second; + + return { latestObjectMetadata.get(metadata_keys::validFrom), latestObjectMetadata.get(metadata_keys::validUntil) }; +} + +std::vector CcdbDatabase::getTimestampsForObject(const std::string& path) +{ + const auto& objects = getListingAsPtree(path).get_child("objects"); + std::vector timestamps; + timestamps.reserve(objects.size()); + + // As for today, we receive objects in the order of the newest to the oldest. + // We prefer the other order here. + for (auto rit = objects.rbegin(); rit != objects.rend(); ++rit) { + timestamps.emplace_back(rit->second.get(metadata_keys::validFrom)); + } + + // we make sure it is sorted. If it is already, it shouldn't cost much. + std::sort(timestamps.begin(), timestamps.end()); + return timestamps; +} + std::vector CcdbDatabase::getPublishedObjectNames(std::string taskName) { std::vector result; + string listing = ccdbApi->list(taskName + "/.*", true, "Application/JSON"); - string listing = ccdbApi.list(taskName + "/.*", true, "Application/JSON"); + boost::property_tree::ptree pt; + stringstream ss; + ss << listing; - // Split the string we received, by line. Also trim it and remove empty lines. Select the lines starting with "path". - std::stringstream ss(listing); - std::string line; - while (std::getline(ss, line, '\n')) { - ltrim(line); - rtrim(line); - if (line.length() > 0 && line.find("\"path\"") == 0) { - unsigned long objNameStart = 9 + taskName.length(); - string path = line.substr(objNameStart, line.length() - 2 /*final 2 char*/ - objNameStart); - result.push_back(path); - } + try { + boost::property_tree::read_json(ss, pt); + } catch (const boost::property_tree::json_parser::json_parser_error&) { + ILOG(Error, Support) << "Failed to parse json in CcdbDatabase::getTimestampsForObject from data: " << ss.str() << ENDM; + } + + BOOST_FOREACH (boost::property_tree::ptree::value_type& v, pt.get_child("objects")) { + assert(v.first.empty()); // array elements have no names + string data = v.second.get_child("path").data(); + string path = data.substr(taskName.size()); + result.push_back(path); } return result; @@ -189,11 +583,16 @@ long CcdbDatabase::getCurrentTimestamp() return value.count(); } -void CcdbDatabase::truncate(std::string taskName, std::string objectName) +void CcdbDatabase::truncate(std::string path, std::string objectName) { - cout << "truncating data for " << taskName << "/" << objectName << endl; + ILOG(Info, Support) << "Truncating data for " << path << "/" << objectName << ENDM; + + ccdbApi->truncate(path + "/" + objectName); +} - ccdbApi.truncate(taskName + "/" + objectName); +void CcdbDatabase::setMaxObjectSize(size_t maxObjectSize) +{ + CcdbDatabase::mMaxObjectSize = maxObjectSize; } } // namespace o2::quality_control::repository diff --git a/Framework/src/Check.cxx b/Framework/src/Check.cxx new file mode 100644 index 0000000000..416c7b4a17 --- /dev/null +++ b/Framework/src/Check.cxx @@ -0,0 +1,288 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "QualityControl/Check.h" + +#include +#include +#include +#include +#include +// O2 +#include +// QC +#include "QualityControl/ActivityHelpers.h" +#include "QualityControl/CheckInterface.h" +#include "QualityControl/CheckSpec.h" +#include "QualityControl/CommonSpec.h" +#include "QualityControl/InputUtils.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Quality.h" +#include "QualityControl/UserInputOutput.h" +#include "QualityControl/ObjectMetadataHelpers.h" + +#include + +using namespace AliceO2::Common; +using namespace AliceO2::InfoLogger; + +using namespace o2::quality_control::checker; +using namespace o2::quality_control::core; +using namespace std; + +namespace o2::quality_control::checker +{ + +/// Members +Check::Check(CheckConfig config) + : mCheckConfig(std::move(config)) +{ +} + +void Check::init() +{ + try { + mCheckInterface = root_class_factory::create(mCheckConfig.moduleName, mCheckConfig.className); + mCheckInterface->setName(mCheckConfig.name); + mCheckInterface->setDatabase(mCheckConfig.repository); + mCheckInterface->setCustomParameters(mCheckConfig.customParameters); + mCheckInterface->setCcdbUrl(mCheckConfig.ccdbUrl); + } catch (...) { + std::string diagnostic = boost::current_exception_diagnostic_information(); + ILOG(Fatal, Ops) << "Unexpected exception, diagnostic information follows: " + << diagnostic << ENDM; + throw; + } + + // Print setting + ILOG(Info, Devel) << "Check config: "; + ILOG(Info, Devel) << "Module " << mCheckConfig.moduleName; + ILOG(Info, Devel) << "; Name " << mCheckConfig.name; + ILOG(Info, Devel) << "; Class " << mCheckConfig.className; + ILOG(Info, Devel) << "; Detector " << mCheckConfig.detectorName; + ILOG(Info, Devel) << "; Policy " << UpdatePolicyTypeUtils::ToString(mCheckConfig.policyType); + ILOG(Info, Devel) << "; MonitorObjects : "; + for (const auto& moname : mCheckConfig.objectNames) { + ILOG(Info, Devel) << " / " << moname; + } + ILOG(Info, Devel) << ENDM; +} + +void Check::reset() +{ + mCheckInterface->reset(); +} + +QualityObjectsType Check::check(std::map>& moMap) +{ + if (mCheckInterface == nullptr) { + BOOST_THROW_EXCEPTION(FatalException() << errinfo_details("Attempting to check, but no CheckInterface is loaded")); + } + + std::map> shadowMap; + // Take only the MOs which are needed to be checked + if (mCheckConfig.allObjects) { + /* + * User didn't specify the MOs. + * All MOs are passed, no shadowing needed. + */ + shadowMap = moMap; + } else { + /* + * Shadow MOs. + * Don't pass MOs that weren't specified by user. + * The user might safely rely on getting only required MOs inside the map. + * + * Implementation: Copy to different map only required MOs. + */ + std::ranges::copy(mCheckConfig.objectNames | + std::views::filter([&](const auto& key) { return moMap.count(key) > 0; }) | + std::views::transform([&](const auto& key) { return std::pair{ key, moMap[key] }; }), + std::inserter(shadowMap, shadowMap.end())); + } + + // Prepare a vector of MO maps to be checked, each one will receive a separate Quality. + std::vector>> moMapsToCheck; + if (mCheckConfig.policyType == UpdatePolicyType::OnEachSeparately) { + // In this case we want to check all MOs separately and we get separate QOs for them. + for (auto mo : shadowMap) { + moMapsToCheck.push_back({ std::move(mo) }); + } + } else { + moMapsToCheck.emplace_back(shadowMap); + } + + QualityObjectsType qualityObjects; + for (auto& moMapToCheck : moMapsToCheck) { + if (std::ranges::any_of(moMapToCheck, [](const std::pair>& item) { + return item.second == nullptr || item.second->getObject() == nullptr; + })) { + ILOG(Warning, Devel) << "Some MOs in the map to check are nullptr, skipping check '" << mCheckInterface->getName() << "'" << ENDM; + continue; + } + + Quality quality; + try { + quality = mCheckInterface->check(&moMapToCheck); + } catch (...) { + std::string diagnostic = boost::current_exception_diagnostic_information(); + ILOG(Error, Ops) << "Unexpected exception in user code (check):" + << diagnostic << ENDM; + continue; + } + auto commonActivity = activity_helpers::strictestMatchingActivity( + moMapToCheck | std::views::transform([](const std::pair>& item) { + return item.second->getActivity(); + })); + ILOG(Debug, Devel) << "Check '" << mCheckConfig.name << "', quality '" << quality << "'" << ENDM; + std::vector monitorObjectsNames; + std::optional maxCycle{}; + for (const auto& [moName, mo] : moMapToCheck) { + monitorObjectsNames.emplace_back(moName); + if (const auto cycle = mo->getMetadata(repository::metadata_keys::cycleNumber)) { + const auto& cycleStr = cycle.value(); + if (const auto cycleVal = repository::parseCycle(cycleStr); cycleVal.has_value()) { + maxCycle = std::max(cycleVal.value(), maxCycle.value_or(0)); + } + } + } + // todo: take metadata from somewhere + qualityObjects.emplace_back(std::make_shared( + quality, + mCheckConfig.name, + mCheckConfig.detectorName, + UpdatePolicyTypeUtils::ToString(mCheckConfig.policyType), + stringifyInput(mCheckConfig.inputSpecs), + monitorObjectsNames)); + + qualityObjects.back()->setActivity(commonActivity); + if (maxCycle.has_value()) { + qualityObjects.back()->addMetadata(repository::metadata_keys::cycleNumber, std::to_string(maxCycle.value())); + } + beautify(moMapToCheck, quality); + } + + return qualityObjects; +} + +void Check::beautify(std::map>& moMap, const Quality& quality) +{ + if (!mCheckConfig.allowBeautify) { + return; + } + + for (auto const& item : moMap) { + try { + mCheckInterface->beautify(item.second /*mo*/, quality); + } catch (...) { + std::string diagnostic = boost::current_exception_diagnostic_information(); + ILOG(Error, Ops) << "Unexpected exception in user code (beautify):" + << diagnostic << ENDM; + continue; + } + } +} + +UpdatePolicyType Check::getUpdatePolicyType() const +{ + return mCheckConfig.policyType; +} + +std::vector Check::getObjectsNames() const +{ + return mCheckConfig.objectNames; +} + +bool Check::getAllObjectsOption() const +{ + return mCheckConfig.allObjects; +} + +CheckConfig Check::extractConfig(const CommonSpec& commonSpec, const CheckSpec& checkSpec) +{ + framework::Inputs inputs; + std::vector objectNames; + UpdatePolicyType updatePolicy = checkSpec.updatePolicy; + bool checkAllObjects = false; + for (const auto& dataSource : checkSpec.dataSources) { + if (!dataSource.isOneOf(DataSourceType::Task, DataSourceType::TaskMovingWindow, DataSourceType::ExternalTask, DataSourceType::PostProcessingTask)) { + throw std::runtime_error( + "Unsupported dataSource '" + dataSource.name + "' for a Check '" + checkSpec.checkName + "'"); + } + inputs.insert(inputs.end(), dataSource.inputs.begin(), dataSource.inputs.end()); + + // Subscribe on predefined MOs. + // If "MOs" are not set, the check function will be triggered whenever a new MO appears. + if (dataSource.subInputs.empty()) { + // fixme: this is a dirty fix. Policies should be refactored, so this check won't be needed. + if (checkSpec.updatePolicy != UpdatePolicyType::OnEachSeparately) { + updatePolicy = UpdatePolicyType::OnGlobalAny; + } + checkAllObjects = true; + } else { + // todo consider moving this to spec reader + for (const auto& moName : dataSource.subInputs) { + auto name = dataSource.name + "/" + moName; + // todo: consider making objectNames an std::set + if (std::find(objectNames.begin(), objectNames.end(), name) == objectNames.end()) { + objectNames.push_back(name); + } + } + } + } + + bool allowBeautify = checkSpec.dataSources.size() <= 1; + if (!allowBeautify) { + // See QC-299 for details + ILOG(Warning, Devel) << "Beautification disabled because more than one source is used in this Check (" << checkSpec.checkName << ")" << ENDM; + } + + return { + checkSpec.checkName, + checkSpec.moduleName, + checkSpec.className, + checkSpec.detectorName, + commonSpec.consulUrl, + checkSpec.customParameters, + commonSpec.conditionDBUrl, + commonSpec.database, + checkSpec.dataSources, + updatePolicy, + std::move(objectNames), + checkAllObjects, + allowBeautify, + std::move(inputs), + createUserOutputSpec(DataSourceType::Check, checkSpec.detectorName, checkSpec.checkName), + }; +} + +void Check::startOfActivity(const core::Activity& activity) +{ + if (mCheckInterface) { + mCheckInterface->startOfActivity(activity); + } else { + throw std::runtime_error("Trying to start an Activity on an empty CheckInterface '" + mCheckConfig.name + "'"); + } +} + +void Check::endOfActivity(const core::Activity& activity) +{ + if (mCheckInterface) { + mCheckInterface->endOfActivity(activity); + } else { + throw std::runtime_error("Trying to stop an Activity on an empty CheckInterface '" + mCheckConfig.name + "'"); + } +} + +} // namespace o2::quality_control::checker diff --git a/Framework/src/CheckInterface.cxx b/Framework/src/CheckInterface.cxx index 11dec9c6cb..3a35edf8cb 100644 --- a/Framework/src/CheckInterface.cxx +++ b/Framework/src/CheckInterface.cxx @@ -1,44 +1,65 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file CheckInterface.cxx /// \author Barthelemy von Haller /// #include "QualityControl/CheckInterface.h" -#include "TClass.h" -#include - -ClassImp(o2::quality_control::checker::CheckInterface) +#include "QualityControl/ReferenceUtils.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QCInputs.h" +#include "QualityControl/QCInputsAdapters.h" - using namespace std; +using namespace std; +using namespace o2::quality_control::core; namespace o2::quality_control::checker { -// CheckInterface::CheckInterface() -//// : mName("") -//{ -//} -// -// CheckInterface::~CheckInterface() -//{ -//} +core::Quality CheckInterface::check(std::map>* moMap) +{ + auto data = createData(*moMap); + return check(data); +}; + +core::Quality CheckInterface::check(const core::QCInputs& data) +{ + return core::Quality{}; +}; -// void CheckInterface::configure(std::string name) -//{ -// mName = name; -//} +void CheckInterface::configure() +{ + // noop, override it if you want. +} -std::string CheckInterface::getAcceptedType() { return "TObject"; } +void CheckInterface::reset() +{ + // noop, override it if you want. +} -bool CheckInterface::isObjectCheckable(const MonitorObject* mo) +void CheckInterface::startOfActivity(const Activity& activity) { - TObject* encapsulated = mo->getObject(); + // noop, override it if you want. +} - if (encapsulated->IsA()->InheritsFrom(getAcceptedType().c_str())) { - return true; - } +void CheckInterface::endOfActivity(const Activity& activity) +{ + // noop, override it if you want. +} - return false; +shared_ptr CheckInterface::retrieveReference(std::string path, Activity referenceActivity) +{ + return o2::quality_control::checker::getReferencePlot(mDatabase.get(), path, referenceActivity); } } // namespace o2::quality_control::checker diff --git a/Framework/src/CheckRunner.cxx b/Framework/src/CheckRunner.cxx new file mode 100644 index 0000000000..35e34f8de6 --- /dev/null +++ b/Framework/src/CheckRunner.cxx @@ -0,0 +1,515 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRunner.cxx +/// \author Barthelemy von Haller +/// \author Piotr Konopka +/// \author Rafal Pacholek +/// + +#include "QualityControl/CheckRunner.h" + +// O2 +#include +#include +#include +#include +#include +#include + +#include +// QC +#include "QualityControl/DatabaseFactory.h" +#include "QualityControl/runnerUtils.h" +#include "QualityControl/InfrastructureSpecReader.h" +#include "QualityControl/CheckRunnerFactory.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/ConfigParamGlo.h" +#include "QualityControl/Bookkeeping.h" + +#include + +using namespace std::chrono; +using namespace AliceO2::Common; +using namespace AliceO2::InfoLogger; +using namespace o2::framework; +using namespace o2::monitoring; +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; +using namespace std; + +const auto current_diagnostic = boost::current_exception_diagnostic_information; + +namespace o2::quality_control::checker +{ + +std::size_t CheckRunner::hash(const std::string& inputString) +{ + // BSD checksum + const int mode = 16; + std::size_t checksum = 0; + + const std::size_t mask = (1 << (mode + 1)) - 1; + for (char c : inputString) { + // Rotate the sum + checksum = (checksum >> 1) + ((checksum & 1) << (mode - 1)); + checksum = (checksum + (std::size_t)c) & mask; + } + return checksum; +} + +std::string CheckRunner::createCheckRunnerName(const std::vector& checks) +{ + static const std::string alphanumeric = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + const int NAME_LEN = 4; + std::string name(CheckRunner::createCheckRunnerIdString() + "-" + getDetectorName(checks) + "-"); + + if (checks.size() == 1) { + // If single check, use the check name + name += checks[0].name; + } else { + std::string hash_string; + std::vector names; + // Fill vector with check names + for (const auto& c : checks) { + names.push_back(c.name); + } + // Be sure that after configuration shuffle, the name will be the same + std::sort(names.begin(), names.end()); + + // Create a single string and hash it + for (auto& n : names) { + hash_string += n; + } + std::size_t num = hash(hash_string); + + // Change numerical to alphanumeric hash representation + for (int i = 0; i < NAME_LEN; ++i) { + name += alphanumeric[num % alphanumeric.size()]; + num = num / alphanumeric.size(); + } + } + return name; +} + +std::string CheckRunner::createCheckRunnerFacility(std::string deviceName) +{ + // it starts with "check/" and is followed by the unique part of the device name truncated to the maximum allowed length + string facilityName = "check/" + deviceName.substr(CheckRunner::createCheckRunnerIdString().length() + 1, string::npos); + facilityName = facilityName.substr(0, QcInfoLogger::maxFacilityLength); + return facilityName; +} + +std::string CheckRunner::createSinkCheckRunnerName(InputSpec input) +{ + // we need a shorter name, thus we only use "qc-sink" and not "qc-check-sink" + std::string name("qc-sink-"); + name += DataSpecUtils::label(input); + return name; +} + +o2::framework::Outputs CheckRunner::collectOutputs(const std::vector& checkConfigs) +{ + o2::framework::Outputs outputs; + for (auto& check : checkConfigs) { + outputs.push_back(check.qoSpec); + } + return outputs; +} + +CheckRunner::CheckRunner(CheckRunnerConfig checkRunnerConfig, const std::vector& checkConfigs) + : mDetectorName(getDetectorName(checkConfigs)), + mDeviceName(createCheckRunnerName(checkConfigs)), + mConfig(std::move(checkRunnerConfig)), + /* All checks have the same Input */ + mInputs(checkConfigs.front().inputSpecs), + mOutputs(CheckRunner::collectOutputs(checkConfigs)), + mTotalNumberObjectsReceived(0), + mTotalNumberCheckExecuted(0), + mTotalNumberQOStored(0), + mTotalNumberMOStored(0), + mTotalQOSent(0) +{ + for (auto& checkConfig : checkConfigs) { + mChecks.emplace(checkConfig.name, checkConfig); + } +} + +CheckRunner::CheckRunner(CheckRunnerConfig checkRunnerConfig, InputSpec input) + : mDeviceName(createSinkCheckRunnerName(input)), + mConfig(std::move(checkRunnerConfig)), + mInputs{ input }, + mOutputs{}, + mTotalNumberObjectsReceived(0), + mTotalNumberCheckExecuted(0), + mTotalNumberQOStored(0), + mTotalNumberMOStored(0), + mTotalQOSent(0) +{ +} + +CheckRunner::~CheckRunner() +{ + ILOG(Debug, Trace) << "CheckRunner destructor (" << this << ")" << ENDM; +} + +void CheckRunner::init(framework::InitContext& iCtx) +{ + try { + core::initInfologger(iCtx, mConfig.infologgerDiscardParameters, createCheckRunnerFacility(mDeviceName)); + Bookkeeping::getInstance().init(mConfig.bookkeepingUrl); + initDatabase(); + initMonitoring(); + initLibraries(); // we have to load libraries before we load ConfigurableParams, otherwise the corresponding ROOT dictionaries won't be found + + if (!ConfigParamGlo::keyValues.empty()) { + conf::ConfigurableParam::updateFromString(ConfigParamGlo::keyValues); + } + // registering state machine callbacks + iCtx.services().get().set([this, services = iCtx.services()]() mutable { start(services); }); + iCtx.services().get().set([this]() { reset(); }); + iCtx.services().get().set([this]() { stop(); }); + + updatePolicyManager.reset(); + for (auto& [checkName, check] : mChecks) { + check.init(); + updatePolicyManager.addPolicy(check.getName(), check.getUpdatePolicyType(), check.getObjectsNames(), check.getAllObjectsOption(), false); + } + } catch (...) { + // catch the exceptions and print it (the ultimate caller might not know how to display it) + ILOG(Fatal, Ops) << "Unexpected exception during initialization: " + << current_diagnostic(true) << ENDM; + throw; + } +} + +long getCurrentTimestamp() +{ + auto now = std::chrono::system_clock::now(); + auto now_ms = std::chrono::time_point_cast(now); + auto epoch = now_ms.time_since_epoch(); + auto value = std::chrono::duration_cast(epoch); + return value.count(); +} + +void CheckRunner::run(framework::ProcessingContext& ctx) +{ + prepareCacheData(ctx.inputs()); + + auto qualityObjects = check(); + + auto now = getCurrentTimestamp(); + store(qualityObjects, now); + store(mMonitorObjectStoreVector, now); + + send(qualityObjects, ctx.outputs()); + + updatePolicyManager.updateGlobalRevision(); + + sendPeriodicMonitoring(); + updateServiceDiscovery(qualityObjects); +} + +void CheckRunner::prepareCacheData(framework::InputRecord& inputRecord) +{ + mMonitorObjectStoreVector.clear(); + + for (const auto& input : mInputs) { + auto dataRef = inputRecord.get(input.binding.c_str()); + if (dataRef.header != nullptr && dataRef.payload != nullptr) { + + // We don't know what we receive, so we test for an array and then try a tobject. + // If we received a tobject, it gets encapsulated in the tobjarray. + shared_ptr array = nullptr; + auto tobj = DataRefUtils::as(dataRef); + // if the object has not been found, it will raise an exception that we just let go. + if (tobj->InheritsFrom("TObjArray")) { + array.reset(dynamic_cast(tobj.release())); + array->SetOwner(false); + ILOG(Debug, Devel) << "CheckRunner " << mDeviceName + << " received an array with " << array->GetEntries() + << " entries from " << input.binding << ENDM; + } else { + // it is just a TObject not embedded in a TObjArray. We build a TObjArray for it. + auto* newArray = new TObjArray(); // we cannot use `array` to add an object as it is const + TObject* newTObject = tobj->Clone(); // we need a copy to avoid that it gets deleted behind our back. + newArray->Add(newTObject); + array.reset(newArray); // now that the array is ready we can adopt it. + ILOG(Debug, Devel) << "CheckRunner " << mDeviceName + << " received a tobject named " << tobj->GetName() + << " from " << input.binding << ENDM; + } + + // for each item of the array, check whether it is a MonitorObject. If not, create one and encapsulate. + // Then, store the MonitorObject in the various maps and vectors we will use later. + bool store = mInputStoreSet.count(DataSpecUtils::label(input)) > 0; // Check if this CheckRunner stores this input + for (const auto tObject : *array) { + std::shared_ptr mo{ dynamic_cast(tObject) }; + + if (mo == nullptr) { + ILOG(Debug, Devel) << "The MO is null, probably a TObject could not be casted into an MO." << ENDM; + ILOG(Debug, Devel) << " Creating an ad hoc MO." << ENDM; + header::DataOrigin origin = DataSpecUtils::asConcreteOrigin(input); + mo = std::make_shared(tObject, input.binding, "CheckRunner", origin.str); + mo->setActivity(*mActivity); + } + + if (mo) { + mo->setIsOwner(true); + mMonitorObjects[mo->getFullName()] = mo; + updatePolicyManager.updateObjectRevision(mo->getFullName()); + mTotalNumberObjectsReceived++; + + if (store) { // Monitor Object will be stored later, after possible beautification + mMonitorObjectStoreVector.push_back(mo); + } + } + } + } + } +} + +void CheckRunner::sendPeriodicMonitoring() +{ + if (mTimer.isTimeout()) { + double timeSinceLastCall = mTimer.getTime(); + timeSinceLastCall = timeSinceLastCall == 0 ? 1 : timeSinceLastCall; + mTimer.reset(10000000); // 10 s. + double rateMOs = mNumberMOStored / timeSinceLastCall; + double rateQOs = mNumberQOStored / timeSinceLastCall; + mCollector->send({ mTotalNumberObjectsReceived, "qc_checkrunner_objects_received" }); + mCollector->send({ mTotalNumberCheckExecuted, "qc_checkrunner_checks_executed" }); + mCollector->send(Metric{ "qc_checkrunner_stored" } + .addValue(mTotalNumberMOStored, "mos") + .addValue(rateMOs, "mos_per_second") + .addValue(mTotalNumberQOStored, "qos") + .addValue(rateQOs, "qos_per_second")); + mCollector->send({ mTotalQOSent, "qc_checkrunner_qo_sent" }); + mCollector->send({ mTimerTotalDurationActivity.getTime(), "qc_checkrunner_duration" }); + mNumberQOStored = 0; + mNumberMOStored = 0; + } +} + +QualityObjectsType CheckRunner::check() +{ + ILOG(Debug, Devel) << "Trying " << mChecks.size() << " checks for " << mMonitorObjects.size() << " monitor objects" + << ENDM; + + QualityObjectsType allQOs; + for (auto& [checkName, check] : mChecks) { + if (updatePolicyManager.isReady(check.getName())) { + ILOG(Debug, Support) << "Monitor Objects for the check '" << checkName << "' are ready --> check()" << ENDM; + auto newQOs = check.check(mMonitorObjects); + mTotalNumberCheckExecuted += newQOs.size(); + + allQOs.insert(allQOs.end(), std::make_move_iterator(newQOs.begin()), std::make_move_iterator(newQOs.end())); + newQOs.clear(); + + // Was checked, update latest revision + updatePolicyManager.updateActorRevision(checkName); + } else { + ILOG(Debug, Support) << "Monitor Objects for the check '" << checkName << "' are not ready, ignoring" << ENDM; + } + } + return allQOs; +} + +void CheckRunner::store(QualityObjectsType& qualityObjects, long validFrom) +{ + ILOG(Debug, Devel) << "Storing " << qualityObjects.size() << " QualityObjects" << ENDM; + try { + for (auto& qo : qualityObjects) { + mDatabase->storeQO(qo); + mTotalNumberQOStored++; + mNumberQOStored++; + } + if (!qualityObjects.empty()) { + auto& qo = qualityObjects.at(0); + ILOG(Debug, Devel) << "Validity of QO '" << qo->GetName() << "' is (" << qo->getValidity().getMin() << ", " << qo->getValidity().getMax() << ")" << ENDM; + } + } catch (boost::exception& e) { + ILOG(Info, Support) << "Unable to " << diagnostic_information(e) << ENDM; + } +} + +void CheckRunner::store(std::vector>& monitorObjects, long validFrom) +{ + ILOG(Debug, Devel) << "Storing " << monitorObjects.size() << " MonitorObjects" << ENDM; + try { + for (auto& mo : monitorObjects) { + mDatabase->storeMO(mo); + + mTotalNumberMOStored++; + mNumberMOStored++; + } + if (!monitorObjects.empty()) { + auto& mo = monitorObjects.at(0); + ILOG(Debug, Devel) << "Validity of MO '" << mo->GetName() << "' is (" << mo->getValidity().getMin() << ", " << mo->getValidity().getMax() << ")" << ENDM; + } + } catch (boost::exception& e) { + ILOG(Info, Support) << "Unable to " << diagnostic_information(e) << ENDM; + } +} + +void CheckRunner::send(QualityObjectsType& qualityObjects, framework::DataAllocator& allocator) +{ + // Note that we might send multiple QOs in one output, as separate parts. + // This should be fine if they are retrieved on the other side with InputRecordWalker. + + ILOG(Debug, Devel) << "Sending " << qualityObjects.size() << " quality objects" << ENDM; + for (const auto& qo : qualityObjects) { + const auto& correspondingCheck = mChecks.at(qo->getCheckName()); + auto outputSpec = correspondingCheck.getOutputSpec(); + auto concreteOutput = framework::DataSpecUtils::asConcreteDataMatcher(outputSpec); + allocator.snapshot( + framework::Output{ concreteOutput.origin, concreteOutput.description, concreteOutput.subSpec }, *qo); + mTotalQOSent++; + } +} + +void CheckRunner::updateServiceDiscovery(const QualityObjectsType& qualityObjects) +{ + if (mServiceDiscovery == nullptr) { + return; + } + + // Insert into the list of paths the QOs' paths. + // The list of paths cannot be computed during initialization and is therefore updated here. + // It cannot be done in the init because of the case when the policy OnEachSeparately is used with a + // data source specifying "all" MOs. As a consequence we have to check the QOs we actually receive. + // A possible optimization would be to collect the list of QOs for all checks where it is possible (i.e. + // all but OnEachSeparately with "All" MOs). If we can get all of them, then no need to update the list + // after initialization. Otherwise, we set the list we know in init and then add to it as we go. + size_t formerNumberQOsNames = mListAllQOPaths.size(); + for (const auto& qo : qualityObjects) { + mListAllQOPaths.insert(qo->getPath()); + } + // if nothing was inserted, no need to update + if (mListAllQOPaths.size() == formerNumberQOsNames) { + return; + } + + // prepare the string of comma separated objects and publish it + std::string objects; + for (const auto& path : mListAllQOPaths) { + objects += path + ","; + } + objects.pop_back(); // remove last comma +} + +void CheckRunner::initDatabase() +{ + mDatabase = DatabaseFactory::create(mConfig.database.at("implementation")); + mDatabase->connect(mConfig.database); + ILOG(Info, Devel) << "Database that is going to be used > Implementation : " << mConfig.database.at("implementation") << " / Host : " << mConfig.database.at("host") << ENDM; +} + +void CheckRunner::initMonitoring() +{ + mCollector = MonitoringFactory::Get(mConfig.monitoringUrl); + mCollector->addGlobalTag(tags::Key::Subsystem, tags::Value::QC); + mCollector->addGlobalTag("CheckRunnerName", mDeviceName); + mTimer.reset(10000000); // 10 s. +} + +void CheckRunner::initLibraries() +{ + std::set moduleNames; + for (const auto& [_, check] : mChecks) { + (void)_; + moduleNames.insert(check.getConfig().moduleName); + } + for (const auto& moduleName : moduleNames) { + core::root_class_factory::loadLibrary(moduleName); + } +} + +void CheckRunner::endOfStream(framework::EndOfStreamContext& eosContext) +{ + mReceivedEOS = true; +} + +void CheckRunner::start(ServiceRegistryRef services) +{ + mActivity = std::make_shared(computeActivity(services, mConfig.fallbackActivity)); + QcInfoLogger::setRun(mActivity->mId); + QcInfoLogger::setPartition(mActivity->mPartitionName); + ILOG(Info, Support) << "Starting run " << mActivity->mId << ":" << ENDM; + mTimerTotalDurationActivity.reset(); + mCollector->setRunNumber(mActivity->mId); + mReceivedEOS = false; + for (auto& [checkName, check] : mChecks) { + check.startOfActivity(*mActivity); + } + + // register ourselves to the BK + if (!gSystem->Getenv("O2_QC_DONT_REGISTER_IN_BK")) { // Set this variable to disable the registration + ILOG(Debug, Devel) << "Registering checkRunner to BookKeeping" << ENDM; + Bookkeeping::getInstance().registerProcess(mActivity->mId, mDeviceName, mDetectorName, bkp::DplProcessType::QC_CHECKER, ""); + } +} + +void CheckRunner::stop() +{ + ILOG(Info, Support) << "Stopping run " << mActivity->mId << ENDM; + if (!mReceivedEOS) { + ILOG(Warning, Devel) << "The STOP transition happened before an EndOfStream was received. The very last QC objects in this run might not have been stored." << ENDM; + } + for (auto& [checkName, check] : mChecks) { + check.endOfActivity(*mActivity); + } +} + +void CheckRunner::reset() +{ + try { + mCollector.reset(); + mActivity = make_shared(); + for (auto& [checkName, check] : mChecks) { + check.reset(); + } + } catch (...) { + // we catch here because we don't know where it will go in DPL's CallbackService + ILOG(Error, Support) << "Error caught in reset() : " + << current_diagnostic(true) << ENDM; + throw; + } + + mTotalNumberObjectsReceived = 0; + mTotalNumberCheckExecuted = 0; + mTotalNumberMOStored = 0; + mNumberMOStored = 0; + mTotalNumberQOStored = 0; + mNumberQOStored = 0; + mTotalQOSent = 0; +} + +std::string CheckRunner::getDetectorName(const std::vector checks) +{ + std::string detectorName; + for (auto& check : checks) { + const std::string& thisDetector = check.detectorName; + if (detectorName.length() == 0) { + detectorName = thisDetector; + } else if (thisDetector != detectorName) { + detectorName = "MANY"; + break; + } + } + return detectorName; +} + +} // namespace o2::quality_control::checker diff --git a/Framework/src/CheckRunnerFactory.cxx b/Framework/src/CheckRunnerFactory.cxx new file mode 100644 index 0000000000..76813892d7 --- /dev/null +++ b/Framework/src/CheckRunnerFactory.cxx @@ -0,0 +1,105 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRunnerFactory.cxx +/// \author Piotr Konopka +/// +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "QualityControl/CheckRunner.h" +#include "QualityControl/CheckRunnerFactory.h" +#include "QualityControl/CommonSpec.h" + +namespace o2::quality_control::checker +{ + +using namespace o2::framework; + +DataProcessorSpec CheckRunnerFactory::create(CheckRunnerConfig checkRunnerConfig, const std::vector& checkConfigs, std::vector storeVector) +{ + auto options = checkRunnerConfig.options; + CheckRunner qcCheckRunner{ std::move(checkRunnerConfig), checkConfigs }; + qcCheckRunner.setTaskStoreSet({ storeVector.begin(), storeVector.end() }); + + DataProcessorSpec newCheckRunner{ qcCheckRunner.getDeviceName(), + qcCheckRunner.getInputs(), + Outputs{ qcCheckRunner.getOutputs() }, + AlgorithmSpec{}, + options }; + newCheckRunner.labels.emplace_back(CheckRunner::getCheckRunnerLabel()); + newCheckRunner.labels.emplace_back(framework::DataProcessorLabel{ "resilient" }); + newCheckRunner.algorithm = adaptFromTask(std::move(qcCheckRunner)); + return newCheckRunner; +} + +DataProcessorSpec CheckRunnerFactory::createSinkDevice(const CheckRunnerConfig& checkRunnerConfig, const o2::framework::InputSpec& input) +{ + CheckRunner qcCheckRunner{ checkRunnerConfig, input }; + qcCheckRunner.setTaskStoreSet({ DataSpecUtils::label(input) }); + + DataProcessorSpec newCheckRunner{ qcCheckRunner.getDeviceName(), + qcCheckRunner.getInputs(), + Outputs{ qcCheckRunner.getOutputs() }, + adaptFromTask(std::move(qcCheckRunner)), + checkRunnerConfig.options, + {} }; + newCheckRunner.labels.emplace_back(framework::DataProcessorLabel{ "resilient" }); + return newCheckRunner; +} + +void CheckRunnerFactory::customizeInfrastructure(std::vector& policies) +{ + auto matcher = [label = CheckRunner::getCheckRunnerLabel()](auto const& device) { + return std::find(device.labels.begin(), device.labels.end(), label) != device.labels.end(); + }; + policies.emplace_back(CompletionPolicyHelpers::consumeWhenAny("checkerCompletionPolicy", matcher)); +} + +CheckRunnerConfig CheckRunnerFactory::extractConfig(const CommonSpec& commonSpec) +{ + Options options{ + { "runNumber", framework::VariantType::String, { "Run number" } }, + { "qcConfiguration", VariantType::Dict, emptyDict(), { "Some dictionary configuration" } } + }; + + core::Activity fallbackActivity{ + commonSpec.activityNumber, + commonSpec.activityType, + commonSpec.activityPeriodName, + commonSpec.activityPassName, + commonSpec.activityProvenance, + { commonSpec.activityStart, commonSpec.activityEnd }, + commonSpec.activityBeamType, + commonSpec.activityPartitionName, + commonSpec.activityFillNumber, + commonSpec.activityOriginalNumber + }; + return { + commonSpec.database, + commonSpec.consulUrl, + commonSpec.monitoringUrl, + commonSpec.bookkeepingUrl, + commonSpec.infologgerDiscardParameters, + fallbackActivity, + options + }; +} + +} // namespace o2::quality_control::checker diff --git a/Framework/src/Checker.cxx b/Framework/src/Checker.cxx deleted file mode 100644 index 530ba8f698..0000000000 --- a/Framework/src/Checker.cxx +++ /dev/null @@ -1,246 +0,0 @@ -/// -/// \file Checker.cxx -/// \author Barthelemy von Haller -/// \author Piotr Konopka -/// - -#include "QualityControl/Checker.h" - -// ROOT -#include -#include -#include -// O2 -#include -#include -#include -#include -// QC -#include "QualityControl/DatabaseFactory.h" -#include "QualityControl/TaskRunner.h" - -using namespace std::chrono; -using namespace AliceO2::Common; -using namespace AliceO2::InfoLogger; -using namespace o2::configuration; -using namespace o2::monitoring; -using namespace o2::quality_control::core; -using namespace o2::quality_control::repository; - -namespace o2::quality_control::checker -{ - -// TODO do we need a CheckFactory ? here it is embedded in the Checker -// TODO maybe we could use the CheckerFactory - -Checker::Checker(std::string checkerName, std::string taskName, std::string configurationSource) - : mCheckerName(checkerName), - mConfigurationSource(configurationSource), - mInputSpec{ "mo", TaskRunner::createTaskDataOrigin(), TaskRunner::createTaskDataDescription(taskName), 0 }, - mOutputSpec{ "QC", Checker::createCheckerDataDescription(taskName), 0 }, - mLogger(QcInfoLogger::GetInstance()), - startFirstObject{ system_clock::time_point::min() }, - endLastObject{ system_clock::time_point::min() }, - mTotalNumberHistosReceived(0) -{ -} - -Checker::~Checker() -{ - // Monitoring - if (mCollector) { - std::chrono::duration diff = endLastObject - startFirstObject; - mCollector->send({ diff.count(), "QC_checker_Time_between_first_and_last_objects_received" }); - mCollector->send({ mTotalNumberHistosReceived, "QC_checker_Total_number_histos_treated" }); - double rate = mTotalNumberHistosReceived / diff.count(); - mCollector->send({ rate, "QC_checker_Rate_objects_treated_per_second_whole_run" }); - } -} - -void Checker::init(framework::InitContext&) -{ - // configuration - try { - std::unique_ptr config = ConfigurationFactory::getConfiguration(mConfigurationSource); - // configuration of the database - mDatabase = DatabaseFactory::create(config->get("qc.config.database.implementation")); - mDatabase->connect(config->getRecursiveMap("qc.config.database")); - LOG(INFO) << "Database that is going to be used : "; - LOG(INFO) << ">> Implementation : " << config->get("qc.config.database.implementation"); - LOG(INFO) << ">> Host : " << config->get("qc.config.database.host"); - } catch ( - std::string const& e) { // we have to catch here to print the exception because the device will make it disappear - LOG(ERROR) << "exception : " << e; - throw; - } catch (...) { - std::string diagnostic = boost::current_exception_diagnostic_information(); - LOG(ERROR) << "Unexpected exception, diagnostic information follows:\n" - << diagnostic; - throw; - } - - // monitoring - try { - mCollector = MonitoringFactory::Get("infologger://"); - } catch (...) { - std::string diagnostic = boost::current_exception_diagnostic_information(); - LOG(ERROR) << "Unexpected exception, diagnostic information follows:\n" << diagnostic; - throw; - } - startFirstObject = system_clock::time_point::min(); - timer.reset(1000000); // 10 s. -} - -void Checker::run(framework::ProcessingContext& ctx) -{ - mLogger << "Receiving " << ctx.inputs().size() << " MonitorObjects" << AliceO2::InfoLogger::InfoLogger::endm; - - // Save time of first object - if (startFirstObject == std::chrono::system_clock::time_point::min()) { - startFirstObject = system_clock::now(); - } - - std::shared_ptr moArray{ std::move(framework::DataRefUtils::as(*ctx.inputs().begin())) }; - moArray->SetOwner(false); - auto checkedMoArray = std::make_unique(); - checkedMoArray->SetOwner(); - - for (const auto& to : *moArray) { - std::shared_ptr mo{dynamic_cast(to)}; - moArray->RemoveFirst(); - if (mo) { - check(mo); - store(mo); - mTotalNumberHistosReceived++; - checkedMoArray->Add(new MonitorObject(*mo)); - } else { - mLogger << "the mo is null" << AliceO2::InfoLogger::InfoLogger::endm; - } - } - - send(checkedMoArray, ctx.outputs()); - - // monitoring - endLastObject = system_clock::now(); - - // if 10 seconds elapsed publish stats - if (timer.isTimeout()) { - timer.reset(1000000); // 10 s. - mCollector->send({ mTotalNumberHistosReceived, "objects" }, o2::monitoring::DerivedMetricMode::RATE); - } -} - -o2::header::DataDescription Checker::createCheckerDataDescription(const std::string taskName) -{ - o2::header::DataDescription description; - description.runtimeInit(std::string(taskName.substr(0, o2::header::DataDescription::size - 4) + "-chk").c_str()); - return description; -} - -void Checker::check(std::shared_ptr mo) -{ - std::map checks = mo->getChecks(); - - mLogger << "Running " << checks.size() << " checks for \"" << mo->getName() << "\"" - << AliceO2::InfoLogger::InfoLogger::endm; - // Get the Checks - - // Loop over the Checks and execute them followed by the beautification - for (const auto& [checkName, check] : checks) { - mLogger << " check name : " << checkName << AliceO2::InfoLogger::InfoLogger::endm; - mLogger << " check className : " << check.className << AliceO2::InfoLogger::InfoLogger::endm; - mLogger << " check libraryName : " << check.libraryName << AliceO2::InfoLogger::InfoLogger::endm; - - // load module, instantiate, use check - // TODO : preload modules and pre-instantiate, or keep a cache - loadLibrary(check.libraryName); - CheckInterface* checkInstance = getCheck(checkName, check.className); - Quality q = checkInstance->check(mo.get()); - - mLogger << " result of the check " << checkName << ": " << q.getName() - << AliceO2::InfoLogger::InfoLogger::endm; - - checkInstance->beautify(mo.get(), q); - } -} - -void Checker::store(std::shared_ptr mo) -{ - mLogger << "Storing \"" << mo->getName() << "\"" << AliceO2::InfoLogger::InfoLogger::endm; - try { - mDatabase->store(mo); - } catch (boost::exception& e) { - mLogger << "Unable to " << diagnostic_information(e) << AliceO2::InfoLogger::InfoLogger::endm; - } -} - -void Checker::send(std::unique_ptr& moArray, framework::DataAllocator& allocator) -{ - mLogger << "Sending Monitor Object array with " << moArray->GetEntries() << " objects inside." << AliceO2::InfoLogger::InfoLogger::endm; - - allocator.adopt( - framework::Output{ mOutputSpec.origin, mOutputSpec.description, mOutputSpec.subSpec, mOutputSpec.lifetime }, moArray.release()); -} - -void Checker::loadLibrary(const std::string libraryName) -{ - if (boost::algorithm::trim_copy(libraryName).empty()) { - mLogger << "no library name specified" << AliceO2::InfoLogger::InfoLogger::endm; - return; - } - - std::string library = "lib" + libraryName; - // if vector does not contain -> first time we see it - if (std::find(mLibrariesLoaded.begin(), mLibrariesLoaded.end(), library) == mLibrariesLoaded.end()) { - mLogger << "Loading library " << library << AliceO2::InfoLogger::InfoLogger::endm; - int libLoaded = gSystem->Load(library.c_str(), "", true); - if (libLoaded == 1) { - mLogger << "Already loaded before" << AliceO2::InfoLogger::InfoLogger::endm; - } else if (libLoaded < 0 || libLoaded > 1) { - BOOST_THROW_EXCEPTION(FatalException() << errinfo_details("Failed to load Detector Publisher Library")); - } - mLibrariesLoaded.push_back(library); - } -} - -CheckInterface* Checker::getCheck(std::string checkName, std::string className) -{ - CheckInterface* result = nullptr; - // Get the class and instantiate - TClass* cl; - std::string tempString("Failed to instantiate Quality Control Module"); - - if (mClassesLoaded.count(className) == 0) { - mLogger << "Loading class " << className << AliceO2::InfoLogger::InfoLogger::endm; - cl = TClass::GetClass(className.c_str()); - if (!cl) { - tempString += R"( because no dictionary for class named ")"; - tempString += className; - tempString += R"(" could be retrieved)"; - LOG(ERROR) << tempString; - BOOST_THROW_EXCEPTION(FatalException() << errinfo_details(tempString)); - } - mClassesLoaded[className] = cl; - } else { - cl = mClassesLoaded[className]; - } - - if (mChecksLoaded.count(checkName) == 0) { - mLogger << "Instantiating class " << className << " (" << cl << ")" << AliceO2::InfoLogger::InfoLogger::endm; - result = static_cast(cl->New()); - if (!result) { - tempString += R"( because the class named ")"; - tempString += className; - tempString += R"( because the class named ")"; - BOOST_THROW_EXCEPTION(FatalException() << errinfo_details(tempString)); - } - result->configure(checkName); - mChecksLoaded[checkName] = result; - } else { - result = mChecksLoaded[checkName]; - } - - return result; -} - -} // namespace o2::quality_control::checker diff --git a/Framework/src/CheckerFactory.cxx b/Framework/src/CheckerFactory.cxx deleted file mode 100644 index 1fece81e2c..0000000000 --- a/Framework/src/CheckerFactory.cxx +++ /dev/null @@ -1,32 +0,0 @@ -/// -/// \file CheckerFactory.cxx -/// \author Piotr Konopka -/// - -#include - -#include "QualityControl/Checker.h" -#include "QualityControl/CheckerFactory.h" - -namespace o2::quality_control::checker -{ - -using namespace o2::framework; -using namespace o2::quality_control::checker; - -DataProcessorSpec CheckerFactory::create(std::string checkerName, std::string taskName, std::string configurationSource) -{ - Checker qcChecker{ checkerName, taskName, configurationSource }; - - DataProcessorSpec newChecker{ checkerName, - Inputs{ qcChecker.getInputSpec() }, - Outputs{ qcChecker.getOutputSpec() }, - adaptFromTask(std::move(qcChecker)), - Options{}, - std::vector{}, - std::vector{} }; - - return std::move(newChecker); -} - -} // namespace o2::quality_control::checker diff --git a/Framework/src/ConfigParamGlo.cxx b/Framework/src/ConfigParamGlo.cxx new file mode 100644 index 0000000000..d594a81190 --- /dev/null +++ b/Framework/src/ConfigParamGlo.cxx @@ -0,0 +1,17 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "QualityControl/ConfigParamGlo.h" + +namespace o2::quality_control +{ +std::string ConfigParamGlo::keyValues = {}; +} diff --git a/Framework/src/CustomParameters.cxx b/Framework/src/CustomParameters.cxx new file mode 100644 index 0000000000..7534379dec --- /dev/null +++ b/Framework/src/CustomParameters.cxx @@ -0,0 +1,224 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "QualityControl/CustomParameters.h" +#include +#include +#include +#include +#include + +namespace o2::quality_control::core +{ + +std::ostream& operator<<(std::ostream& out, const CustomParameters& customParameters) +{ + // todo: should we swallow the exceptions here ? + for (const auto& runType : customParameters.mCustomParameters) { + for (const auto& beamType : runType.second) { + for (const auto& name : beamType.second) { + out << runType.first << " - " << beamType.first << " - " << name.first << " : " << name.second << "\n"; + } + } + } + return out; +} + +CustomParameters::CustomParameters() +{ + mCustomParameters["null"]["null"] = {}; +} + +void CustomParameters::set(const std::string& key, const std::string& value, const std::string& runType, const std::string& beamType) +{ + mCustomParameters[runType][beamType][key] = value; +} + +const std::unordered_map& CustomParameters::getAllForRunBeam(const std::string& runType, const std::string& beamType) +{ + if (mCustomParameters.count(runType) > 0 && mCustomParameters[runType].count(beamType) > 0) { + return mCustomParameters[runType][beamType]; + } + throw std::out_of_range("Unknown beam or run: " + runType + ", " + beamType); +} + +const std::unordered_map& CustomParameters::getAllDefaults() +{ + try { + return getAllForRunBeam("default", "default"); + } catch (std::out_of_range outOfRange) { + // we ignore the fact that we could not find anything and return an empty map + return mCustomParameters["null"]["null"]; + } +} + +std::string CustomParameters::at(const std::string& key, const std::string& runType, const std::string& beamType) const +{ + auto optionalResult = atOptional(key, runType, beamType); // just reuse the logic we developed in atOptional + if (!optionalResult.has_value()) { + return mCustomParameters.at(runType).at(beamType).at(key); // we know we will get a out_of_range exception + } + return optionalResult.value(); +} + +std::string CustomParameters::at(const std::string& key, const Activity& activity) const +{ + return at(key, activity.mType, activity.mBeamType); +} + +std::optional CustomParameters::atOptional(const std::string& key, const std::string& runType, const std::string& beamType) const +{ + std::optional result = std::nullopt; + const std::vector runTypes = { runType, std::string("default") }; + const std::vector beamTypes = { beamType, std::string("default") }; + for (const auto& rt : runTypes) { + for (const auto& bt : beamTypes) { + try { + result = mCustomParameters.at(rt).at(bt).at(key); + return result; + } catch (const std::out_of_range& exc) { // ignored on purpose + } + } + } + return result; +} + +std::optional CustomParameters::atOptional(const std::string& key, const Activity& activity) const +{ + return atOptional(key, activity.mType, activity.mBeamType); +} + +std::optional CustomParameters::getOptionalPtree(const std::string& key, const std::string& runType, const std::string& beamType) const +{ + std::optional result = std::nullopt; + + // get the text and make it a ptree + auto text = atOptional(key, runType, beamType); + if (text.has_value()) { + std::stringstream listingAsStringStream{ text.value() }; + boost::property_tree::ptree pt; + try { + boost::property_tree::read_json(listingAsStringStream, pt); + } catch (const boost::property_tree::json_parser::json_parser_error& e) { + return result; + } + result = pt; + } + + return result; +} + +std::optional CustomParameters::getOptionalPtree(const std::string& key, const Activity& activity) const +{ + return getOptionalPtree(key, activity.mType, activity.mBeamType); +} + +std::string CustomParameters::atOrDefaultValue(const std::string& key, std::string defaultValue, const std::string& runType, const std::string& beamType) const +{ + try { + return mCustomParameters.at(runType).at(beamType).at(key); + } catch (const std::out_of_range& exc) { + return defaultValue; + } +} + +std::string CustomParameters::atOrDefaultValue(const std::string& key, std::string defaultValue, const Activity& activity) const +{ + try { + return mCustomParameters.at(activity.mType).at(activity.mBeamType).at(key); + } catch (const std::out_of_range& exc) { + return defaultValue; + } +} + +int CustomParameters::count(const std::string& key, const std::string& runType, const std::string& beamType) const +{ + try { + at(key, runType, beamType); + } catch (const std::out_of_range& oor) { + return 0; + } + return 1; +} + +std::unordered_map::const_iterator CustomParameters::find(const std::string& key, const std::string& runType, const std::string& beamType) const +{ + auto subTreeRunType = mCustomParameters.find(runType); + if (subTreeRunType == mCustomParameters.end()) { + return end(); + } + auto subTreeBeamType = subTreeRunType->second.find(beamType); + if (subTreeBeamType == subTreeRunType->second.end()) { + return end(); + } + auto foundValue = subTreeBeamType->second.find(key); + if (foundValue == subTreeBeamType->second.end()) { + return end(); + } + return foundValue; +} + +std::unordered_map::const_iterator CustomParameters::end() const +{ + return mCustomParameters.at("null").at("null").end(); +} + +size_t CustomParameters::size() const +{ + size_t total = 0; + for (const auto& runType : mCustomParameters) { + for (const auto& beamType : runType.second) { + for (const auto& name : beamType.second) { + total++; + } + } + } + return total; +} + +std::string CustomParameters::operator[](const std::string& key) const +{ + return at(key); +} + +std::string& CustomParameters::operator[](const std::string& key) +{ + if (count(key) == 0) { + set(key, ""); + } + return mCustomParameters.at("default").at("default").at(key); +} + +void CustomParameters::populateCustomParameters(const boost::property_tree::ptree& tree) +{ + for (const auto& [runtype, subTreeRunType] : tree) { + for (const auto& [beamtype, subTreeBeamType] : subTreeRunType) { + for (const auto& [key, value] : subTreeBeamType) { + // Check if value has children (thus it is json) + if (value.empty()) { // just a simple value + set(key, value.get_value(), runtype, beamtype); + } else { + // It's some json, serialize it to string + std::stringstream ss; + boost::property_tree::write_json(ss, value, false); + std::string jsonString = ss.str(); + // Remove trailing newline if present + if (!jsonString.empty() && jsonString.back() == '\n') { + jsonString.pop_back(); + } + set(key, jsonString, runtype, beamtype); + } + } + } + } +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/DataDumpGui.cxx b/Framework/src/DataDumpGui.cxx deleted file mode 100644 index b6074ca22c..0000000000 --- a/Framework/src/DataDumpGui.cxx +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -// - -/// -/// \author Barthelemy von Haller -/// \file DataDumpGui.cxx -/// - -#include "QualityControl/DataDumpGui.h" - -#include "imgui/BaseGui.h" -#include "imgui/imgui.h" -#include -#include - -using namespace std; -using namespace o2::framework; - -namespace o2::quality_control::core -{ - -GUIState DataDumpGui::guiState; -void* DataDumpGui::window = nullptr; - -vector getBinRepresentation(unsigned char* data, size_t size) -{ - stringstream ss; - vector result; - result.reserve(size); - - for (int i = 0; i < size; i++) { - std::bitset<16> x(data[i]); - ss << x << " "; - result.push_back(ss.str()); - ss.str(std::string()); - } - return result; -} - -vector getHexRepresentation(unsigned char* data, size_t size) -{ - stringstream ss; - vector result; - result.reserve(size); - ss << std::hex << std::setfill('0'); - - for (int i = 0; i < size; i++) { - ss << std::setw(2) << static_cast(data[i]) << " "; - result.push_back(ss.str()); - ss.str(std::string()); - } - return result; -} - -void DataDumpGui::InitTask() { window = initGUI("O2 Data Inspector"); } - -void updateGuiState() -{ - if (ImGui::Button("Next")) { - // update view with latest data if any available - if (DataDumpGui::guiState.newDataAvailable) { - // do stuff : delete old data if any, move pointer next -> current - if (DataDumpGui::guiState.current_payload.data != nullptr) { - delete DataDumpGui::guiState.current_payload.data; - DataDumpGui::guiState.current_payload.data = nullptr; - DataDumpGui::guiState.current_payload.size = 0; - DataDumpGui::guiState.current_header.data = nullptr; - DataDumpGui::guiState.current_header.size = 0; - } - DataDumpGui::guiState.current_payload.data = DataDumpGui::guiState.next_payload.data; - DataDumpGui::guiState.current_payload.size = DataDumpGui::guiState.next_payload.size; - DataDumpGui::guiState.current_header.data = DataDumpGui::guiState.next_header.data; - DataDumpGui::guiState.current_header.size = DataDumpGui::guiState.next_header.size; - DataDumpGui::guiState.actionMessage = ""; - } else { - DataDumpGui::guiState.actionMessage = ""; - } - } else { - if (DataDumpGui::guiState.newDataAvailable) { - DataDumpGui::guiState.dataAvailableMessage = ""; - } else { - DataDumpGui::guiState.dataAvailableMessage = "No data available."; - } - } - if (DataDumpGui::guiState.dataAvailableMessage.length() > 0) { - ImGui::TextUnformatted(DataDumpGui::guiState.dataAvailableMessage.c_str()); - } - if (DataDumpGui::guiState.actionMessage.length() > 0) { - ImGui::TextUnformatted(DataDumpGui::guiState.actionMessage.c_str()); - } -} - -void resizeColumns(int representation, int old_representation) -{ - // static bool firstDrawColumns = true; - // if(firstDrawColumns || representation != old_representation) { - if (representation == 0) { - ImGui::SetColumnWidth(0, 40.0f); - ImGui::SetColumnWidth(1, 50.0f); - ImGui::SetColumnWidth(2, 50.0f); - ImGui::SetColumnWidth(3, 50.0f); - ImGui::SetColumnWidth(4, 50.0f); - } else if (representation == 1) { // binary - ImGui::SetColumnWidth(0, 40.0f); - ImGui::SetColumnWidth(1, 243.0f); - ImGui::SetColumnWidth(2, 243.0f); - ImGui::SetColumnWidth(3, 243.0f); - ImGui::SetColumnWidth(4, 243.0f); - } - // } - // firstDrawColumns = false; -} - -void updatePayloadGui() -{ - if (DataDumpGui::guiState.current_payload.data == nullptr) { - ImGui::Text("No data loaded yet, click Next."); - } else { // all the stuff below should go to a method - - static int representation = 0, old_representation = 1; - old_representation = representation; - ImGui::RadioButton("hexadecimal", &representation, 0); - ImGui::SameLine(); - ImGui::RadioButton("binary", &representation, 1); - - // scrollable area - ImGui::BeginChild("##ScrollingRegion", ImVec2(0, 430), false, ImGuiWindowFlags_HorizontalScrollbar); - // table - ImGui::Columns(5, "payload_display", true); - - // header row - ImGui::Separator(); - resizeColumns(representation, old_representation); - ImGui::Text(""); - ImGui::NextColumn(); - ImGui::Text("#1"); - ImGui::NextColumn(); - ImGui::Text("#2"); - ImGui::NextColumn(); - ImGui::Text("#3"); - ImGui::NextColumn(); - ImGui::Text("#4"); - ImGui::NextColumn(); - ImGui::Separator(); - - // print the hex/bin values in the columns and rows of the table - vector formattedData = - (representation == 0) - ? getHexRepresentation(DataDumpGui::guiState.current_payload.data, DataDumpGui::guiState.current_payload.size) - : getBinRepresentation(DataDumpGui::guiState.current_payload.data, DataDumpGui::guiState.current_payload.size); - int line = 0; - static int selected = -1; - for (unsigned long pos = 0; pos < formattedData.size();) { - char label[32]; - sprintf(label, "%04d", line * 4); - if (ImGui::Selectable(label, selected == line, ImGuiSelectableFlags_SpanAllColumns)) { - selected = line; - } - for (int i = 0; i < 4; i++) { // four columns - ImGui::NextColumn(); - string temp; - for (int j = 0; j < 2; j++) { // 2 x 16 bits words (char) - if (pos < formattedData.size()) { - temp += formattedData[pos]; - pos++; - } - } - ImGui::TextUnformatted(temp.c_str()); - } - ImGui::NextColumn(); - line++; - } - - // footer - ImGui::Columns(1); - ImGui::EndChild(); - ImGui::Separator(); - } -} - -void updateHeaderGui() -{ - if (DataDumpGui::guiState.current_header.data == nullptr) { - ImGui::Text("No data loaded yet, click Next."); - } else { - auto* header = header::get(DataDumpGui::guiState.current_header.data); - if (header == nullptr) { - ImGui::Text("No header available in this data."); - return; - } - - ImGui::BeginChild( - "Static", ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f, ImGui::GetTextLineHeightWithSpacing() * 4), false); - ImGui::Text("sMagicString : %d", o2::header::DataHeader::sMagicString); - ImGui::Text("sVersion : %d", o2::header::DataHeader::sVersion); - ImGui::Text("sHeaderType : %s", o2::header::DataHeader::sHeaderType.as().c_str()); - ImGui::Text("sSerializationMethod : %s", o2::header::DataHeader::sSerializationMethod.as().c_str()); - ImGui::EndChild(); - - ImGui::SameLine(); - - ImGui::BeginChild("Non-static", - ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f, ImGui::GetTextLineHeightWithSpacing() * 7), - false); - ImGui::Text("Header size : %d", header->headerSize); - ImGui::Text("Payload size : %ld", header->payloadSize); - ImGui::Text("Header version : %d", header->headerVersion); - ImGui::Text("flagsNextHeader : %d", header->flagsNextHeader); - ImGui::Text("dataDescription : %s", header->dataDescription.str); - ImGui::Text("dataOrigin : %s", header->dataOrigin.str); - ImGui::Text("serialization : %s", header->serialization.as().c_str()); - ImGui::EndChild(); - // TODO add the next headers (how to "discover" what headers is there ? ) - } -} - -void redrawGui() -{ - static bool firstDraw = true; - - if (firstDraw) { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImVec2(1100, 700)); - } - - ImGui::Begin("DataDumpGui", nullptr, ImGuiWindowFlags_NoTitleBar); - if (ImGui::CollapsingHeader("Actions", ImGuiTreeNodeFlags_DefaultOpen)) { - updateGuiState(); - } - - if (ImGui::CollapsingHeader("Header", ImGuiTreeNodeFlags_DefaultOpen)) { - updateHeaderGui(); - } - - if (ImGui::CollapsingHeader("Payload", ImGuiTreeNodeFlags_DefaultOpen)) { - updatePayloadGui(); - } - ImGui::End(); - - // ImGui::ShowTestWindow(); - - firstDraw = false; -} - -bool DataDumpGui::ConditionalRun() -{ - unique_ptr msg(fTransportFactory->CreateMessage()); - - FairMQParts parts; - auto result = fChannels.at("data-in").at(0).ReceiveAsync(parts); - if (result > 0) { - this->handleParts(parts); - } - - return pollGUI(window, redrawGui); -} - -bool DataDumpGui::handleParts(FairMQParts& parts) -{ - if (parts.Size() != 2) { - cout << "number of parts must be 2" << endl; - return false; - } - - DataDumpGui::guiState.newDataAvailable = true; - assignDataToChunk(parts.At(0)->GetData(), parts.At(0)->GetSize(), DataDumpGui::guiState.next_header); - assignDataToChunk(parts.At(1)->GetData(), parts.At(1)->GetSize(), DataDumpGui::guiState.next_payload); - return true; -} - -void DataDumpGui::assignDataToChunk(void* data, size_t size, Chunk& chunk) -{ - auto* copy = new unsigned char[size]; - memcpy(copy, data, size); - chunk.data = copy; - chunk.size = size; -} -} // namespace o2::quality_control::core \ No newline at end of file diff --git a/Framework/src/DataHeaderHelpers.cxx b/Framework/src/DataHeaderHelpers.cxx new file mode 100644 index 0000000000..c469bab13f --- /dev/null +++ b/Framework/src/DataHeaderHelpers.cxx @@ -0,0 +1,128 @@ +// Copyright 2019-2024 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include + +#include "QualityControl/DataHeaderHelpers.h" +#include "QualityControl/QcInfoLogger.h" + +namespace o2::quality_control::core +{ + +constexpr char CharIdFrom(DataSourceType type) +{ + switch (type) { + case DataSourceType::DataSamplingPolicy: + case DataSourceType::Direct: + case DataSourceType::ExternalTask: + throw std::invalid_argument("Provided data source type is not generated by QC, cannot provide a corresponding character"); + case DataSourceType::Task: + return 'Q'; + case DataSourceType::TaskMovingWindow: + return 'W'; + case DataSourceType::Check: + return 'C'; + case DataSourceType::Aggregator: + return 'A'; + case DataSourceType::PostProcessingTask: + return 'P'; + default: + throw std::invalid_argument("Unrecognized data source type"); + } +} + +header::DataOrigin createDataOrigin(DataSourceType dataSourceType, const std::string& detectorCode) +{ + std::string originStr{ CharIdFrom(dataSourceType) }; + if (detectorCode.empty()) { + throw std::invalid_argument{ "empty detector code for a data source origin" }; + } else if (detectorCode.size() > 3) { + ILOG(Warning, Support) << "too long detector code for a task data origin: " + detectorCode + ", trying to survive with: " + detectorCode.substr(0, 3) << ENDM; + originStr += detectorCode.substr(0, 3); + } else { + originStr += detectorCode; + } + o2::header::DataOrigin origin; + origin.runtimeInit(originStr.c_str()); + return origin; +} + +namespace hash +{ + +// djb2 is used instead of std::hash to be consistent over different architectures +auto djb2(const std::string& input) -> size_t +{ + size_t hash = 5381; + for (const auto c : input) { + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } + return hash; +} + +// creates hash of input string and returns hexadecimal representation +// if created hash has smaller amount of digits than requested, required number of zeros is appended +auto toHex(const std::string& input, size_t hashLength) -> std::string +{ + std::stringstream ss; + ss << std::setfill('0') << std::left << std::setw(hashLength) << std::noshowbase << std::hex << djb2(input); + return std::move(ss).str().substr(0, hashLength); +}; + +} // namespace hash + +std::string createDescriptionWithHash(const std::string& input, size_t hashLength) +{ + return input.substr(0, o2::header::DataDescription::size - hashLength).append(hash::toHex(input, hashLength)); +} + +auto createDataDescription(const std::string& name, size_t hashLength) -> o2::header::DataDescription +{ + o2::header::DataDescription description{}; + + if (name.size() <= o2::header::DataDescription::size) { + description.runtimeInit(name.c_str()); + return description; + } else { + const auto descriptionWithHash = createDescriptionWithHash(name, hashLength); + ILOG(Debug, Devel) << "Too long data description name [" << name << "] changed to [" << descriptionWithHash << "]" << ENDM; + description.runtimeInit(descriptionWithHash.c_str()); + return description; + } +} + +constexpr size_t descriptionHashLengthFor(DataSourceType type) +{ + size_t hashLength = 0; + switch (type) { + case DataSourceType::DataSamplingPolicy: + case DataSourceType::Direct: + case DataSourceType::ExternalTask: + throw std::invalid_argument("Provided data source type is not generated by QC, cannot provide a hash length"); + case DataSourceType::Task: + case DataSourceType::TaskMovingWindow: + case DataSourceType::Check: + case DataSourceType::Aggregator: + case DataSourceType::PostProcessingTask: + default: + hashLength = 4; + } + assert(hashLength <= o2::header::DataDescription::size); + return hashLength; +} + +auto createDataDescription(const std::string& name, DataSourceType type) -> o2::header::DataDescription +{ + return createDataDescription(name, descriptionHashLengthFor(type)); +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/DataProducer.cxx b/Framework/src/DataProducer.cxx new file mode 100644 index 0000000000..34c2eb0f02 --- /dev/null +++ b/Framework/src/DataProducer.cxx @@ -0,0 +1,110 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DataProducer.cxx +/// \author Piotr Konopka +/// +#include "QualityControl/DataProducer.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include +#include +#include + +using namespace o2::framework; +using namespace o2::monitoring; + +using SubSpec = o2::header::DataHeader::SubSpecificationType; +using namespace AliceO2::Common; + +namespace o2::quality_control::core +{ + +DataProcessorSpec getDataProducerSpec(size_t minSize, size_t maxSize, double rate, uint64_t amount, size_t index, + const std::string& monitoringUrl, bool fill, size_t timepipeline) +{ + DataProcessorSpec spec{ + "producer-" + std::to_string(index), + Inputs{}, + Outputs{ + { { "out" }, "TST", "RAWDATA", static_cast(index) } }, + getDataProducerAlgorithm({ "TST", "RAWDATA", static_cast(index) }, minSize, maxSize, rate, amount, + monitoringUrl, fill) + }; + spec.maxInputTimeslices = timepipeline; + + return spec; +} + +AlgorithmSpec getDataProducerAlgorithm(ConcreteDataMatcher output, size_t minSize, size_t maxSize, double rate, + uint64_t amount, const std::string& monitoringUrl, bool fill) +{ + return AlgorithmSpec{ + [=](InitContext&) { + // this is the initialization code + std::default_random_engine generator(time(nullptr)); + std::shared_ptr timer = nullptr; + + uint64_t messageCounter = 0; + std::shared_ptr collector; + if (!monitoringUrl.empty()) { + collector = MonitoringFactory::Get(monitoringUrl); + collector->enableProcessMonitoring(); + } + + // after the initialization, we return the processing callback + return [=](ProcessingContext& processingContext) mutable { + // everything inside this lambda function is invoked in a loop, because it this Data Processor has no inputs + + // checking if we have reached the maximum amount of messages + if (amount != 0 && messageCounter >= amount) { + ILOG(Info, Ops) << "Reached the maximum number of messages, requesting to quit the producer and sending an EndOfStream" << ENDM; + processingContext.services().get().endOfStream(); + processingContext.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // setting up the timer + if (!timer) { + timer = std::make_shared(); + timer->reset(static_cast(1000000.0 / rate)); + } + // keeping the message rate + double timeToSleep = timer->getRemainingTime(); + if (timeToSleep > 0) { + usleep(timeToSleep * 1000000.0); + } + timer->increment(); + + // generating data + size_t length = (minSize == maxSize) ? minSize : (minSize + (generator() % (maxSize - minSize))); + auto data = processingContext.outputs().make({ output.origin, output.description, output.subSpec }, + length); + ++messageCounter; + if (fill) { + for (auto&& item : data) { + item = static_cast(generator()); + } + } + + // send metrics + if (collector) { + collector->send({ messageCounter, "Data_producer_" + std::to_string(output.subSpec) + "_message_" }, + DerivedMetricMode::RATE); + } + }; + } + }; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/DataProducerExample.cxx b/Framework/src/DataProducerExample.cxx new file mode 100644 index 0000000000..b84c9dc80e --- /dev/null +++ b/Framework/src/DataProducerExample.cxx @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DataProducerExample.cxx +/// \author Barthelemy von Haller +/// +#include "QualityControl/DataProducerExample.h" + +using namespace o2::framework; + +using SubSpec = o2::header::DataHeader::SubSpecificationType; + +namespace o2::quality_control::core +{ + +DataProcessorSpec getDataProducerExampleSpec(size_t myParam) +{ + return DataProcessorSpec{ + "producer", + Inputs{}, + Outputs{ + { { "out" }, "TST", "RAWDATA", static_cast(0) } }, + getDataProducerExampleAlgorithm({ "TST", "RAWDATA", static_cast(0) }, myParam) + }; +} + +framework::AlgorithmSpec + getDataProducerExampleAlgorithm(framework::ConcreteDataMatcher output, size_t myParam) +{ + return AlgorithmSpec{ + [=](InitContext&) { + return [=](ProcessingContext& processingContext) mutable { + // everything inside this lambda function is invoked in a loop, because this Data Processor has no inputs + + // generating data (size 1, type size_t) + auto data = processingContext.outputs().make({ output.origin, output.description, output.subSpec }, + 1); + data[0] = myParam; // assigning the data + }; + } + }; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/imgui/BaseGui.h b/Framework/src/DataSourceSpec.cxx similarity index 58% rename from Framework/src/imgui/BaseGui.h rename to Framework/src/DataSourceSpec.cxx index 41cae78d1b..395740086b 100644 --- a/Framework/src/imgui/BaseGui.h +++ b/Framework/src/DataSourceSpec.cxx @@ -8,23 +8,20 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. -// Copied from AliceO2, work of Giulio +/// +/// \file DataSourceSpec.cxx +/// \author Piotr Konopka +/// -#ifndef FRAMEWORK_DEBUGGUI_H -#define FRAMEWORK_DEBUGGUI_H +#include "QualityControl/DataSourceSpec.h" -#include - -namespace o2 -{ -namespace framework +namespace o2::quality_control::core { -void* initGUI(const char* name); -bool pollGUI(void* context, std::function guiCallback); -void disposeGUI(); - -} // namespace framework -} // namespace o2 +DataSourceSpec::DataSourceSpec(DataSourceType type) + : type(type) +{ + // todo: validation? +} -#endif // FRAMEWORK_DEBUGGUI_H +} // namespace o2::quality_control::core \ No newline at end of file diff --git a/Framework/src/DatabaseFactory.cxx b/Framework/src/DatabaseFactory.cxx index 605c4cd017..a02c98bfa9 100644 --- a/Framework/src/DatabaseFactory.cxx +++ b/Framework/src/DatabaseFactory.cxx @@ -1,21 +1,27 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file DatabaseFactory.cxx /// \author Barthelemy von Haller /// -// std -#include -// ROOT -#include -#include -#include -#include +#include "QualityControl/DatabaseFactory.h" + // O2 -#include "Common/Exceptions.h" +#include // QC -#include "QualityControl/DatabaseFactory.h" +#include "QualityControl/CcdbDatabase.h" +#include "QualityControl/DummyDatabase.h" #include "QualityControl/QcInfoLogger.h" -#include "QualityControl/TaskInterface.h" #ifdef _WITH_MYSQL #include "QualityControl/MySqlDatabase.h" #endif @@ -30,7 +36,7 @@ namespace o2::quality_control::repository std::unique_ptr DatabaseFactory::create(std::string name) { if (name == "MySql") { - QcInfoLogger::GetInstance() << "MySQL backend selected" << QcInfoLogger::endm; + ILOG(Debug, Support) << "MySQL backend selected" << ENDM; #ifdef _WITH_MYSQL return std::make_unique(); #else @@ -39,8 +45,11 @@ std::unique_ptr DatabaseFactory::create(std::string name) #endif } else if (name == "CCDB") { // TODO check if CCDB installed - QcInfoLogger::GetInstance() << "CCDB backend selected" << QcInfoLogger::endm; + ILOG(Debug, Support) << "CCDB backend selected" << ENDM; return std::make_unique(); + } else if (name == "Dummy") { + ILOG(Debug, Support) << "Dummy backend selected, MonitorObjects will not be stored nor retrieved" << ENDM; + return std::make_unique(); } else { BOOST_THROW_EXCEPTION(FatalException() << errinfo_details("No database named " + name)); } diff --git a/Framework/src/DummyDatabase.cxx b/Framework/src/DummyDatabase.cxx new file mode 100644 index 0000000000..34ab6c23f4 --- /dev/null +++ b/Framework/src/DummyDatabase.cxx @@ -0,0 +1,96 @@ + +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DummyDatabase.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/DummyDatabase.h" + +using namespace o2::quality_control::core; +namespace o2::quality_control::repository +{ + +void DummyDatabase::connect(const std::string&, const std::string&, const std::string&, const std::string&) +{ +} + +void DummyDatabase::connect(const std::unordered_map&) +{ +} + +void DummyDatabase::storeAny(const void*, std::type_info const&, std::string const&, std::map const&, + std::string const&, std::string const&, long, long) +{ +} + +void DummyDatabase::storeMO(std::shared_ptr) +{ +} + +std::shared_ptr DummyDatabase::retrieveMO(std::string, std::string, long, const core::Activity& activity, const std::map&) +{ + return {}; +} + +void DummyDatabase::storeQO(std::shared_ptr) +{ +} + +std::shared_ptr DummyDatabase::retrieveQO(std::string, long, const core::Activity& activity, const std::map& metadata) +{ + return {}; +} + +void DummyDatabase::disconnect() +{ +} + +void DummyDatabase::prepareTaskDataContainer(std::string) +{ +} + +std::vector DummyDatabase::getPublishedObjectNames(std::string) +{ + return {}; +} + +void DummyDatabase::truncate(std::string, std::string) +{ +} + +TObject* DummyDatabase::retrieveTObject(std::string, const std::map&, long, std::map*) +{ + return nullptr; +} + +std::string DummyDatabase::retrieveJson(std::string, long, const std::map&) +{ + return {}; +} + +void* DummyDatabase::retrieveAny(const std::type_info&, const std::string&, const std::map&, long, std::map*, const std::string&, const std::string&) +{ + return nullptr; +} + +void DummyDatabase::setMaxObjectSize(size_t maxObjectSize) +{ +} + +core::ValidityInterval DummyDatabase::getLatestObjectValidity(const std::string& path, const std::map& metadata) +{ + return gInvalidValidityInterval; +} + +} // namespace o2::quality_control::repository diff --git a/Framework/src/ExamplePrinterSpec.h b/Framework/src/ExamplePrinterSpec.h deleted file mode 100644 index 6fde93b76f..0000000000 --- a/Framework/src/ExamplePrinterSpec.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -/// -/// \file ExamplePrinterSpec.h -/// \author Barthelemy von Haller -/// - -#ifndef QUALITYCONTROL_EXAMPLEPRINTERSPEC_H -#define QUALITYCONTROL_EXAMPLEPRINTERSPEC_H - -namespace o2::quality_control::example -{ - -/** - * \brief Example DPL task to be plugged after a QC checker. - * - * This example DPL task takes a TObjArray of MonitorObjects as input (corresponding to the output of a checker) - * and prints the bins of the first element. The element needs to be a TH1 otherwise it is ignored. - */ -class ExamplePrinterSpec : public framework::Task -{ - public: - void run(ProcessingContext& processingContext) final - { - LOG(INFO) << "Received data"; - std::shared_ptr moArray{ std::move(DataRefUtils::as(*processingContext.inputs().begin())) }; - - if (moArray->IsEmpty()) { - LOG(INFO) << "Array is empty"; - return; - } - - // get the object - auto* mo = dynamic_cast(moArray->At(0)); - if (mo == nullptr) { - LOG(INFO) << "First element is not a MonitorObject"; - return; - } - auto* histo = dynamic_cast(mo->getObject()); - if (histo == nullptr) { - LOG(INFO) << "MonitorObject does not contain a TH1"; - return; - } - - std::string bins = "BINS:"; - for (int i = 0; i < histo->GetNbinsX(); i++) { - bins += " " + std::to_string((int)histo->GetBinContent(i)); - } - LOG(INFO) << bins; - } -}; - -} // namespace o2::quality_control::example - -#endif //QUALITYCONTROL_EXAMPLEPRINTERSPEC_H diff --git a/Framework/src/FlagHelpers.cxx b/Framework/src/FlagHelpers.cxx new file mode 100644 index 0000000000..e5d40844f9 --- /dev/null +++ b/Framework/src/FlagHelpers.cxx @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file FlagHelpers.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/FlagHelpers.h" + +namespace o2::quality_control::core::flag_helpers +{ + +bool intervalsConnect(const ValidityInterval& one, const ValidityInterval& other) +{ + // Object validity in CCDB is a right-open range, which means it includes the beginning and excludes the ending. + // In other words, for the validity [1, 10), 9 is the last integer to be included. + // Thus, ranges [1, 10) and [10, 20) are considered adjacent, while [1, 10) and [11, 20) are already separate + // and should not be merged. + return one.isValid() && other.isValid() && one.getMax() >= other.getMin() && one.getMin() <= other.getMax(); +} + +bool intervalsOverlap(const ValidityInterval& one, const ValidityInterval& other) +{ + return one.isValid() && other.isValid() && one.getMax() > other.getMin() && one.getMin() < other.getMax(); +} + +std::vector excludeInterval(const QualityControlFlag& flag, ValidityInterval interval) +{ + std::vector result; + + if (flag.getInterval().isInvalid()) { + return result; + } + + if (auto overlap = flag.getInterval().getOverlap(interval); overlap.isInvalid() || overlap.isZeroLength()) { + result.push_back(flag); + return result; + } + + if (interval.getMin() > flag.getStart()) { + result.emplace_back(flag.getStart(), interval.getMin(), flag.getFlag(), flag.getComment(), flag.getSource()); + } + if (interval.getMax() < flag.getEnd()) { + result.emplace_back(interval.getMax(), flag.getEnd(), flag.getFlag(), flag.getComment(), flag.getSource()); + } + return result; +} + +std::optional intersection(const QualityControlFlag& flag, ValidityInterval interval) +{ + if (flag.getInterval().isInvalid()) { + return std::nullopt; + } + if (interval.isInvalid()) { + return flag; + } + auto intersection = flag.getInterval().getOverlap(interval); + if (intersection.isInvalid() || intersection.isZeroLength()) { + return std::nullopt; + } + return QualityControlFlag{ intersection.getMin(), intersection.getMax(), flag.getFlag(), flag.getComment(), flag.getSource() }; +} + +} // namespace o2::quality_control::core::flag_helpers diff --git a/Framework/src/HistoMerger.cxx b/Framework/src/HistoMerger.cxx deleted file mode 100644 index a6d3f4fc32..0000000000 --- a/Framework/src/HistoMerger.cxx +++ /dev/null @@ -1,77 +0,0 @@ -/// -/// \file HistoMerger.cxx -/// \author Piotr Konopka -/// - -#include "QualityControl/HistoMerger.h" - -#include -#include - -using o2::header::DataDescription; -using o2::header::DataOrigin; -using SubSpecificationType = o2::header::DataHeader::SubSpecificationType; -using namespace o2::framework; - -namespace o2::quality_control::core -{ - -HistoMerger::HistoMerger(std::string mergerName, double publicationPeriodSeconds) - : mMergerName(mergerName), mOutputSpec{ header::gDataOriginInvalid, header::gDataDescriptionInvalid } -{ - mPublicationTimer.reset(static_cast(publicationPeriodSeconds * 1000000)); - mMergedArray.SetOwner(true); -} - -HistoMerger::~HistoMerger() {} - -void HistoMerger::init(framework::InitContext&) { mMergedArray.Clear(); } - -void HistoMerger::run(framework::ProcessingContext& ctx) -{ - for (const auto& input : ctx.inputs()) { - if (input.header != nullptr && input.spec != nullptr) { - std::unique_ptr moArray = DataRefUtils::as(input); - - if (mMergedArray.IsEmpty()) { - mMergedArray = *moArray.release(); - } else { - if (mMergedArray.GetSize() != moArray->GetSize()) { - LOG(ERROR) << "array don't match in size, " << mMergedArray.GetSize() << " vs " << moArray->GetSize(); - return; - } - - for (int i = 0; i < mMergedArray.GetEntries(); i++) { - MonitorObject* mo = dynamic_cast((*moArray)[i]); - if (mo && std::strstr(mo->getObject()->ClassName(), "TH1") != nullptr) { - TH1* h = dynamic_cast(dynamic_cast(mMergedArray[i])->getObject()); - const TH1* hUpdate = dynamic_cast(mo->getObject()); - h->Add(hUpdate); - } - } - } - } - } - if (mPublicationTimer.isTimeout()) { - if (!mMergedArray.IsEmpty()) { - ctx.outputs().snapshot(Output{ mOutputSpec.origin, mOutputSpec.description, mOutputSpec.subSpec }, mMergedArray); - } - // avoid publishing mo many times consecutively because of too long initial waiting time - do { - mPublicationTimer.increment(); - } while (mPublicationTimer.isTimeout()); - } -} - -void HistoMerger::configureInputsOutputs(DataOrigin origin, DataDescription description, - std::pair subSpecRange) -{ - mInputSpecs.clear(); - - for (SubSpecificationType s = subSpecRange.first; s <= subSpecRange.second; s++) { - mInputSpecs.push_back({ "mo", origin, description, s }); - } - mOutputSpec = OutputSpec{ origin, description, 0 }; -} - -} // namespace o2::quality_control::core \ No newline at end of file diff --git a/Framework/src/HistoProducer.cxx b/Framework/src/HistoProducer.cxx new file mode 100644 index 0000000000..473ab101e7 --- /dev/null +++ b/Framework/src/HistoProducer.cxx @@ -0,0 +1,142 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HistoProducer.cxx +/// \author Barthelemy von Haller +/// +#include "QualityControl/HistoProducer.h" + +#include +#include +#include +#include +#include +#include + +using namespace o2::framework; + +using SubSpec = o2::header::DataHeader::SubSpecificationType; +using namespace AliceO2::Common; +using namespace std; + +namespace o2::quality_control::core +{ + +framework::DataProcessorSpec getHistoProducerSpec(size_t subspec, size_t nbHistograms, bool noTobjArray) +{ + return DataProcessorSpec{ + "histoProducer-" + std::to_string(subspec), + Inputs{}, + Outputs{ + { { "out" }, "TST", "HISTO", static_cast(subspec) } }, + getHistoProducerAlgorithm({ "TST", "HISTO", static_cast(subspec) }, nbHistograms, noTobjArray) + }; +} + +framework::AlgorithmSpec getHistoProducerAlgorithm(framework::ConcreteDataMatcher output, size_t nbHistograms, bool noTobjArray) +{ + return AlgorithmSpec{ + [=](InitContext&) { + // this is the initialization code + std::shared_ptr timer = nullptr; + double period = 2; // how many seconds between the updates of the histogram + vector allHistos; + allHistos.reserve(nbHistograms); + for (size_t i = 0; i < nbHistograms; i++) { + TH1F* histo = new TH1F(string("hello_") + i, "fromHistoProducer", 100, -3, 3); + allHistos.push_back(histo); + } + + return [=](ProcessingContext& processingContext) mutable { + // setting up the timer + if (!timer) { + timer = std::make_shared(); + timer->reset(static_cast(1000000.0 * period)); + } + // keeping the message rate + double timeToSleep = timer->getRemainingTime(); + if (timeToSleep > 0) { + usleep(timeToSleep * 1000000.0); + } + timer->increment(); + + if (noTobjArray) { // just send the histogram, not a tobjarray + TH1F& th1f = processingContext.outputs().make({ output.origin, output.description, output.subSpec }, "hello", "fromHistoProducer", 100, -3, 3); + allHistos[0]->FillRandom("gaus", 100); + th1f.Add(allHistos[0]); + ILOG(Info, Devel) << "sending 1 histo named `hello`." << ENDM; + return; + } + + // Prepare the tobjarray + MonitorObjectCollection& monitorObjects = processingContext.outputs().make({ output.origin, output.description, output.subSpec }); + // Fill histograms + for (size_t i = 0; i < nbHistograms; i++) { + allHistos[i]->FillRandom("gaus", 100); + monitorObjects.Add(allHistos[i]); + } + ILOG(Info, Devel) << "Sending a TObjArray with " << nbHistograms << " histos named `hello_`."; + }; + } + }; +} + +DataProcessorSpec getHistoPrinterSpec(size_t subspec) +{ + return DataProcessorSpec{ + "histoPrinter", + Inputs{ { { "in" }, "TST", "HISTO", static_cast(subspec) } }, + Outputs{}, + getHistoPrinterAlgorithm() + }; +} + +void printHisto(shared_ptr& histo) +{ + ILOG(Info, Devel) << "histo : " << histo->GetName() << " : " << histo->GetTitle() << ENDM; + std::string bins = "BINS:"; + for (int i = 1; i <= histo->GetNbinsX(); i++) { + bins += " " + std::to_string((int)histo->GetBinContent(i)); + } + ILOG(Info, Devel) << bins << ENDM; +} + +framework::AlgorithmSpec getHistoPrinterAlgorithm() +{ + return AlgorithmSpec{ + [=](InitContext&) { + // this is the initialization code + + return [=](ProcessingContext& processingContext) mutable { + // We don't know what we receive, so we test for an array and then try a TH1F. + shared_ptr array = nullptr; + shared_ptr th1f = nullptr; + try { + array = processingContext.inputs().get("in"); + } catch (std::runtime_error& e) { + // we failed to get the TObjArray, let's try a TH1F. If it fails it will throw. + th1f = processingContext.inputs().get("in"); + } + if (array != nullptr) { + for (auto* const tObject : *array) { + std::shared_ptr histo{ dynamic_cast(tObject) }; + printHisto(histo); + } + } else if (th1f != nullptr) { + printHisto(th1f); + } + }; + } + }; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/InformationService.cxx b/Framework/src/InformationService.cxx deleted file mode 100644 index c9aadd2ad9..0000000000 --- a/Framework/src/InformationService.cxx +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -// - -/// -/// \author Barthelemy von Haller -/// \file InformationService.cxx -/// - -#include "InformationService.h" -#include "QualityControl/QcInfoLogger.h" -#include -#include - -using namespace std; -typedef boost::tokenizer> t_tokenizer; -using namespace o2::quality_control::core; - -int timeOutIntervals = 5; // in seconds - -InformationService::InformationService() : th(nullptr), mFakeDataIndex(0) -{ - OnData("tasks_input", &InformationService::handleTaskInputData); - OnData("request_data", &InformationService::handleRequestData); -} - -void InformationService::Init() -{ - string fakeDataFile = fConfig->GetValue("fake-data-file"); - - // todo put this in a method - if (fakeDataFile != "") { - readFakeDataFile(fakeDataFile); - } -} - -InformationService::~InformationService() {} - -void InformationService::checkTimedOut() -{ - string line = mFakeData[mFakeDataIndex % mFakeData.size()]; - handleTaskInputData(line); - mFakeDataIndex++; - - // restart timer - mTimer->expires_at(mTimer->expires_at() + boost::posix_time::seconds(timeOutIntervals)); - mTimer->async_wait(boost::bind(&InformationService::checkTimedOut, this)); -} - -bool InformationService::handleRequestData(FairMQMessagePtr& request, int /*index*/) -{ - string requestParam = string(static_cast(request->GetData()), request->GetSize()); - LOG(INFO) << "Received request from client: \"" << requestParam << "\""; - - string* result = nullptr; - if (requestParam == "all") { - result = new string(produceJsonAll()); - } else { - if (mCacheTasksData.count(requestParam) > 0) { - result = new string(produceJson(requestParam)); - } else { - result = new string("{\"error\": \"no such task\"}"); - } - } - - LOG(INFO) << "Sending reply to client."; - FairMQMessagePtr reply( - NewMessage(const_cast(result->c_str()), // data - result->length(), // size - [](void* /*data*/, void* object) { delete static_cast(object); }, // deletion callback - result)); // object that manages the data - if (Send(reply, "request_data") <= 0) { - LOG(ERROR) << "error sending reply"; - } - return true; // keep running -} - -bool InformationService::handleTaskInputData(FairMQMessagePtr& msg, int /*index*/) -{ - string* receivedData = new std::string(static_cast(msg->GetData()), msg->GetSize()); - LOG(INFO) << "Received data, processing..."; - LOG(INFO) << " " << *receivedData; - - handleTaskInputData(*receivedData); - - return true; // keep running -} - -bool InformationService::handleTaskInputData(std::string receivedData) -{ - std::string taskName = getTaskName(&receivedData); - LOG(DEBUG) << "task : " << taskName; - - // check if new data - boost::hash string_hash; - size_t hash = string_hash(receivedData); - if (mCacheTasksObjectsHash.count(taskName) > 0) { - if (mCacheTasksObjectsHash.count(taskName) > 0 && hash == mCacheTasksObjectsHash[taskName]) { - LOG(INFO) << "Data already known, we skip it"; - return true; - } - } - mCacheTasksObjectsHash[taskName] = hash; - - // parse - vector objects = getObjects(&receivedData); - - // store - mCacheTasksData[taskName] = objects; - - // json - string* json = new std::string(produceJson(taskName)); - - // publish - sendJson(json); -} - -void InformationService::readFakeDataFile(std::string fakeDataFile) -{ - std::string line; - std::ifstream myfile(fakeDataFile); - if (!myfile) // Always test the file open. - { - LOG(ERROR) << "Error opening fake data file"; - return; - } - mFakeData.clear(); - while (std::getline(myfile, line)) { - mFakeData.push_back(line); - } - - // start a timer to use the fake data - mTimer = new boost::asio::deadline_timer(io, boost::posix_time::seconds(timeOutIntervals)); - mTimer->async_wait(boost::bind(&InformationService::checkTimedOut, this)); - th = new thread([&] { io.run(); }); -} - -vector InformationService::getObjects(string* receivedData) -{ - vector objects; - std::string objectsString = receivedData->substr(receivedData->find(":") + 1, receivedData->length()); - LOG(DEBUG) << "objects : " << objectsString; - boost::char_separator sep(","); - t_tokenizer tok(objectsString, sep); - for (t_tokenizer::iterator beg = tok.begin(); beg != tok.end(); ++beg) { - objects.push_back(*beg); - } - return objects; -} - -std::string InformationService::getTaskName(std::string* receivedData) -{ - return receivedData->substr(0, receivedData->find(":")); -} - -pt::ptree InformationService::buildTaskNode(std::string taskName) -{ - pt::ptree task_node; - task_node.put("name", taskName); - pt::ptree objects_node; - for (auto& object : mCacheTasksData[taskName]) { - pt::ptree object_node; - object_node.put("id", object); - objects_node.push_back(std::make_pair("", object_node)); - } - task_node.add_child("objects", objects_node); - return task_node; -} - -std::string InformationService::produceJson(std::string taskName) -{ - pt::ptree taskNode = buildTaskNode(taskName); - - std::stringstream ss; - pt::json_parser::write_json(ss, taskNode); - LOG(DEBUG) << "json : " << endl - << ss.str(); - // QcInfoLogger::GetInstance() << infologger::Debug << "json : \n" << *json << infologger::endm; - return ss.str(); -} - -std::string InformationService::produceJsonAll() -{ - string result; - pt::ptree main_node; - - pt::ptree tasksListNode; - for (const auto& taskTuple : mCacheTasksData) { - pt::ptree taskNode = buildTaskNode(taskTuple.first); - tasksListNode.push_back(std::make_pair("", taskNode)); - } - main_node.add_child("tasks", tasksListNode); - - std::stringstream ss; - pt::json_parser::write_json(ss, main_node); - LOG(DEBUG) << "json : " << endl - << ss.str(); - return ss.str(); -} - -void InformationService::sendJson(std::string* json) -{ - FairMQMessagePtr msg2(NewMessage(const_cast(json->c_str()), json->length(), - [](void* /*data*/, void* object) { delete static_cast(object); }, json)); - int ret = Send(msg2, "updates_output"); - if (ret < 0) { - LOG(ERROR) << "Error sending update"; - } -} diff --git a/Framework/src/InformationService.h b/Framework/src/InformationService.h deleted file mode 100644 index fa4674446b..0000000000 --- a/Framework/src/InformationService.h +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -// - -/// -/// \author Barthelemy von Haller -/// \file InformationService.h -/// - -#ifndef QC_INFORMATIONSERVICE_H -#define QC_INFORMATIONSERVICE_H - -#include "FairMQDevice.h" -#include - -#include -#include -#include - -namespace pt = boost::property_tree; - -/// \brief Collect the list of objects published by all the tasks and make it available to clients. -/// -/// The InformationService receives the list of objects published by each task. -/// It keeps a list of all tasks and objects and send it upon request to clients. It also publishes updates when -/// new information comes from the tasks. -/// -/// See InformationService.json to know the port where updates are published. -/// See InformationService.json to know the port where to request information for all tasks (param "all") or -/// for a specific task (param ""). -/// See runInformationService.cxx for the steering code. -/// -/// Example usage : -/// qcInfoService -c /absolute/path/to/InformationService.json -n information_service \\ -/// --id information_service --mq-config /absolute/path/to/InformationService.json -/// -/// Format of the string coming from the tasks : -/// `task_id:obj0,obj1,obj2` -/// Format of the JSON output for one task or all tasks : -/// See README -/// -/// \todo Handle tasks dying and their removal from the cache and the publication of an update (heartbeat ?). -/// \todo Handle tasks sending information that they are disappearing. - -class InformationService : public FairMQDevice -{ - public: - InformationService(); - virtual ~InformationService(); - - protected: - /// Callback for data coming from qcTasks - bool handleTaskInputData(FairMQMessagePtr&, int); - /// Callback for the requests coming from clients - bool handleRequestData(FairMQMessagePtr&, int); - void Init(); - - private: - /// Extract the list of objects from the string received from the tasks - std::vector getObjects(std::string* receivedData); - /// Extract the task name from the string received from the tasks - std::string getTaskName(std::string* receivedData); - /// Produce the JSON string for the specified task - std::string produceJson(std::string taskName); - /// Produce the JSON string for all tasks and objects - std::string produceJsonAll(); - /// Send the JSON string to all clients (subscribers) - void sendJson(std::string* json); - pt::ptree buildTaskNode(std::string taskName); - void checkTimedOut(); - /// Compute and send the JSON using the inputString from a task - bool handleTaskInputData(std::string inputString); - /// Reads a file containing data in format as received from the tasks. - /// Store the items and use them at regular intervals to simulate tasks inputs. - /// Calling again this method will delete the former fake data cache. - void readFakeDataFile(std::string filePath); - - private: - std::map> mCacheTasksData; /// the list of objects names for each task - std::map - mCacheTasksObjectsHash; /// used to check whether we already have received this list of objects - boost::asio::deadline_timer* mTimer; /// the asynchronous timer to check if agents have timed out - std::vector - mFakeData; /// container for the fake data (if any). Each line is in a string and used in turn. - int mFakeDataIndex; - // variables for the timer - boost::asio::io_service io; - std::thread* th; -}; - -#endif // QC_INFORMATIONSERVICE_H diff --git a/Framework/src/InformationServiceDump.cxx b/Framework/src/InformationServiceDump.cxx deleted file mode 100644 index 0b9c845017..0000000000 --- a/Framework/src/InformationServiceDump.cxx +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -// - -/// -/// \author Barthelemy von Haller -/// \file InformationServiceDump.cxx -/// - -#include "InformationServiceDump.h" - -#include -#include -#include - -#include "FairMQLogger.h" -#include - -using namespace std; -namespace pt = boost::property_tree; - -InformationServiceDump::InformationServiceDump() { OnData("info_service_input", &InformationServiceDump::HandleData); } - -InformationServiceDump::~InformationServiceDump() {} - -bool InformationServiceDump::HandleData(FairMQMessagePtr& msg, int /*index*/) -{ - string* receivedData = new std::string(static_cast(msg->GetData()), msg->GetSize()); - LOG(INFO) << "Received data : "; - LOG(INFO) << " " << *receivedData; - - string* text = new string(fConfig->GetValue("request-task")); - LOG(INFO) << "Preparing request for \"" << *text << "\""; - FairMQMessagePtr request( - NewMessage(const_cast(text->c_str()), // data - text->length(), // size - [](void* /*data*/, void* object) { delete static_cast(object); }, // deletion callback - text)); // object that manages the data - LOG(INFO) << "Sending request "; - if (Send(request, "send_request") > 0) { - FairMQMessagePtr reply(NewMessage()); - if (Receive(reply, "send_request") >= 0) { - LOG(INFO) << "Received reply from server: \"" << string(static_cast(reply->GetData()), reply->GetSize()) - << "\""; - } else { - LOG(ERROR) << "Problem receiving reply"; - } - } else { - LOG(ERROR) << "problem sending request"; - } - - return true; // keep running -} diff --git a/Framework/src/InformationServiceDump.h b/Framework/src/InformationServiceDump.h deleted file mode 100644 index df6e285600..0000000000 --- a/Framework/src/InformationServiceDump.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -// - -/// -/// \author Barthelemy von Haller -/// \file InformationServiceDump.h -/// - -#ifndef QC_INFORMATIONSERVICEDUMP_H -#define QC_INFORMATIONSERVICEDUMP_H - -#include "FairMQDevice.h" -#include - -/// \brief Dump the publications received from the InformationService. -/// -/// Useful for checking the InformationService. -/// It will receive the updates from the tasks. Upon reception , it dumps it and send a request for all or a single -/// task data and displays the reply. -/// To decide which task the request should target, use parameter "request-task". By default it asks for all. -/// -/// See runInformationServiceDump.cxx for the steering code. -/// -/// Example usage : -/// qcInfoServiceDump -c /absolute/path/to/InformationService.json -n information_service_dump -/// --id information_service_dump --mq-config /absolute/path/to/InformationService.json -/// --request-task myTask1 -class InformationServiceDump : public FairMQDevice -{ - public: - InformationServiceDump(); - virtual ~InformationServiceDump(); - - protected: - /// Callback for data coming from InformationService - bool HandleData(FairMQMessagePtr&, int); -}; - -#endif // QC_INFORMATIONSERVICEDUMP_H diff --git a/Framework/src/InfrastructureGenerator.cxx b/Framework/src/InfrastructureGenerator.cxx index b27c167158..b3ec45ee51 100644 --- a/Framework/src/InfrastructureGenerator.cxx +++ b/Framework/src/InfrastructureGenerator.cxx @@ -1,104 +1,803 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// -/// \file QualityControlFactory.cxx +/// \file InfrastructureGenerator.cxx /// \author Piotr Konopka /// -#include #include "QualityControl/InfrastructureGenerator.h" + +#include "QualityControl/Aggregator.h" +#include "QualityControl/AggregatorRunnerFactory.h" +#include "QualityControl/BookkeepingQualitySink.h" +#include "QualityControl/Check.h" +#include "QualityControl/CheckRunnerFactory.h" +#include "QualityControl/InfrastructureSpec.h" +#include "QualityControl/InfrastructureSpecReader.h" +#include "QualityControl/PostProcessingDevice.h" +#include "QualityControl/PostProcessingRunner.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/RootFileSink.h" +#include "QualityControl/RootFileSource.h" +#include "QualityControl/TaskRunner.h" #include "QualityControl/TaskRunnerFactory.h" -#include "QualityControl/CheckerFactory.h" +#include "QualityControl/Version.h" +#include "QualityControl/UserInputOutput.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include + +#include +#include +#include +#include +#include using namespace o2::framework; using namespace o2::configuration; +using namespace o2::mergers; +using namespace o2::utilities; using namespace o2::quality_control::checker; +using namespace o2::quality_control::postprocessing; using boost::property_tree::ptree; +using SubSpec = o2::header::DataHeader::SubSpecificationType; namespace o2::quality_control::core { -WorkflowSpec InfrastructureGenerator::generateLocalInfrastructure(std::string configurationSource, std::string host) +constexpr uint16_t defaultPolicyPort = 42349; +constexpr auto proxyMemoryKillThresholdMB = "5000"; + +struct DataSamplingPolicySpec { + DataSamplingPolicySpec(std::string name, std::string control, std::string remoteMachine = "") + : name(std::move(name)), control(std::move(control)), remoteMachine(std::move(remoteMachine)) {} + bool operator<(const DataSamplingPolicySpec& other) const + { + return std::tie(name, control, remoteMachine) < std::tie(other.name, other.control, other.remoteMachine); + } + std::string name; + std::string control; + std::string remoteMachine; +}; + +void enableDraining(framework::Options& options) +{ + if (auto readyStatePolicy = std::find_if(options.begin(), options.end(), [](const auto& option) { return option.name == "ready-state-policy"; }); + readyStatePolicy != options.end()) { + readyStatePolicy->defaultValue = "drain"; + } else { + ILOG(Error) << "Could not find 'ready-state-policy' option to enable draining in READY" << ENDM; + } +} + +framework::WorkflowSpec InfrastructureGenerator::generateStandaloneInfrastructure(const boost::property_tree::ptree& configurationTree) { + printVersion(); + + auto infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(configurationTree, WorkflowType::Standalone); + // todo: report the number of tasks/checks/etc once all are read there. + WorkflowSpec workflow; - TaskRunnerFactory taskRunnerFactory; - auto config = ConfigurationFactory::getConfiguration(configurationSource); - - for (const auto& [taskName, taskConfig] : config->getRecursive("qc.tasks")) { - if (taskConfig.get("active") && taskConfig.get("location") == "local") { - // ids are assigned to local tasks in order to distinguish monitor objects outputs from each other and be able to - // merge them. If there is no need to merge (only one qc task), it gets subspec 0. - // todo: use matcher for subspec when available in DPL - size_t id = taskConfig.get_child("machines").size() > 1 ? 1 : 0; - for (const auto& machine : taskConfig.get_child("machines")) { - - if (machine.second.get("") == host) { - // todo: optimize it by using the same ptree? - workflow.emplace_back(taskRunnerFactory.create(taskName, configurationSource, id, true)); + std::ranges::copy(infrastructureSpec.tasks | std::views::filter(&TaskSpec::active) | std::views::transform([&](const TaskSpec& taskSpec) { + // The "resetAfterCycles" parameters should be handled differently for standalone/remote and local tasks, + // thus we should not let TaskRunnerFactory read it and decide by itself, since it might not be aware of + // the context we run QC. + return TaskRunnerFactory::create(TaskRunnerFactory::extractConfig(infrastructureSpec.common, taskSpec, 0, taskSpec.resetAfterCycles)); + }), + std::back_inserter(workflow)); + + generateCheckRunners(workflow, infrastructureSpec); + generateAggregator(workflow, infrastructureSpec); + generatePostProcessing(workflow, infrastructureSpec); + generateBookkeepingQualitySink(workflow, infrastructureSpec); + + return workflow; +} + +void InfrastructureGenerator::generateStandaloneInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree) +{ + auto qcInfrastructure = InfrastructureGenerator::generateStandaloneInfrastructure(configurationTree); + workflow.insert(std::end(workflow), std::begin(qcInfrastructure), std::end(qcInfrastructure)); +} + +framework::WorkflowSpec InfrastructureGenerator::generateFullChainInfrastructure(const ptree& configurationTree) +{ + printVersion(); + + auto infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(configurationTree, WorkflowType::FullChain); + WorkflowSpec workflow; + + for (const auto& taskSpec : infrastructureSpec.tasks | std::views::filter(&TaskSpec::active)) { + if (taskSpec.location == TaskLocationSpec::Local) { + // If we use delta mergers, then the moving window is implemented by the last Merger layer. + // The QC Tasks should always send a delta covering one cycle. + auto taskConfig = TaskRunnerFactory::extractConfig(infrastructureSpec.common, taskSpec, 1, TaskRunnerFactory::computeResetAfterCycles(taskSpec, true)); + // Generate QC Task Runner + workflow.emplace_back(TaskRunnerFactory::create(taskConfig)); + + // In "delta" mode Mergers should implement moving window, in "entire" - QC Tasks. + size_t resetAfterCycles = taskSpec.mergingMode == "delta" ? taskSpec.resetAfterCycles : 0; + auto cycleDurationsMultiplied = TaskRunnerFactory::getSanitizedCycleDurations(infrastructureSpec.common, taskSpec); + std::for_each(cycleDurationsMultiplied.begin(), cycleDurationsMultiplied.end(), + [taskSpec](std::pair& p) { p.first *= taskSpec.mergerCycleMultiplier; }); + bool enableMovingWindows = !taskSpec.movingWindows.empty(); + generateMergers(workflow, taskSpec.taskName, 1, cycleDurationsMultiplied, + taskSpec.mergingMode, resetAfterCycles, infrastructureSpec.common.monitoringUrl, + taskSpec.detectorName, taskSpec.mergersPerLayer, enableMovingWindows, taskSpec.critical); + } else { // TaskLocationSpec::Remote + auto taskConfig = TaskRunnerFactory::extractConfig(infrastructureSpec.common, taskSpec, 0, taskSpec.resetAfterCycles); + workflow.emplace_back(TaskRunnerFactory::create(taskConfig)); + } + } + + generateCheckRunners(workflow, infrastructureSpec); + generateAggregator(workflow, infrastructureSpec); + generatePostProcessing(workflow, infrastructureSpec); + generateBookkeepingQualitySink(workflow, infrastructureSpec); + + return workflow; +} + +void InfrastructureGenerator::generateFullChainInfrastructure(WorkflowSpec& workflow, const ptree& configurationTree) +{ + auto qcInfrastructure = InfrastructureGenerator::generateFullChainInfrastructure(configurationTree); + workflow.insert(std::end(workflow), std::begin(qcInfrastructure), std::end(qcInfrastructure)); +} + +WorkflowSpec InfrastructureGenerator::generateLocalInfrastructure(const boost::property_tree::ptree& configurationTree, const std::string& targetHost) +{ + printVersion(); + + auto infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(configurationTree, WorkflowType::Local); + + WorkflowSpec workflow; + std::set samplingPoliciesForRemoteTasks; + + if (infrastructureSpec.tasks.empty()) { + return workflow; + } + + for (const auto& taskSpec : infrastructureSpec.tasks | std::views::filter(&TaskSpec::active)) { + + if (taskSpec.location == TaskLocationSpec::Local) { + if (taskSpec.localMachines.empty()) { + throw std::runtime_error("No local machines specified for task " + taskSpec.taskName + " in its configuration"); + } + + size_t id = 1; + for (const auto& machine : taskSpec.localMachines) { + // We spawn a task and proxy only if we are on the right machine. + if (machine == targetHost) { + // If we use delta mergers, then the moving window is implemented by the last Merger layer. + // The QC Tasks should always send a delta covering one cycle. + auto taskConfig = TaskRunnerFactory::extractConfig(infrastructureSpec.common, taskSpec, id, TaskRunnerFactory::computeResetAfterCycles(taskSpec, true)); + // Generate QC Task Runner + workflow.emplace_back(TaskRunnerFactory::create(taskConfig)); + // Generate an output proxy + // These should be removed when we are able to declare dangling output in normal DPL devices + generateLocalTaskLocalProxy(workflow, id, taskSpec); break; } id++; } + } else // TaskLocationSpec::Remote + { + // Collecting Data Sampling Policies + for (const auto& dataSource : taskSpec.dataSources) { + if (dataSource.isOneOf(DataSourceType::DataSamplingPolicy)) { + samplingPoliciesForRemoteTasks.insert({ dataSource.name, taskSpec.localControl, taskSpec.remoteMachine }); + } else { + throw std::runtime_error( + "Configuration error: dataSource '" + dataSource.name + "' for a remote QC Task '" + taskSpec.taskName + // + "' does not have a supported type. Remote QC tasks can subscribe only to data sampling policies outputs."); + } + } } } + + if (!samplingPoliciesForRemoteTasks.empty()) { + auto dataSamplingTree = configurationTree.get_child("dataSamplingPolicies"); + // Creating Data Sampling Policies proxies + for (const auto& [policyName, control, remoteMachine] : samplingPoliciesForRemoteTasks) { + std::string port = std::to_string(DataSampling::PortForPolicy(dataSamplingTree, policyName).value_or(defaultPolicyPort)); + Inputs inputSpecs = DataSampling::InputSpecsForPolicy(dataSamplingTree, policyName); + std::vector machines = DataSampling::MachinesForPolicy(dataSamplingTree, policyName); + + if (machines.empty() || std::ranges::find(machines, targetHost) != machines.end()) { + if (DataSampling::BindLocationForPolicy(dataSamplingTree, policyName) == "remote") { + generateDataSamplingPolicyLocalProxyConnect(workflow, policyName, inputSpecs, remoteMachine, port, control); + } else { + generateDataSamplingPolicyLocalProxyBind(workflow, policyName, inputSpecs, targetHost, port, control); + } + } + } + } + return workflow; } -void InfrastructureGenerator::generateLocalInfrastructure(framework::WorkflowSpec& workflow, std::string configurationSource, std::string host) +void InfrastructureGenerator::generateLocalInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree, const std::string& host) { - auto qcInfrastructure = InfrastructureGenerator::generateLocalInfrastructure(configurationSource, host); + auto qcInfrastructure = InfrastructureGenerator::generateLocalInfrastructure(configurationTree, host); workflow.insert(std::end(workflow), std::begin(qcInfrastructure), std::end(qcInfrastructure)); } - -o2::framework::WorkflowSpec InfrastructureGenerator::generateRemoteInfrastructure(std::string configurationSource) +o2::framework::WorkflowSpec InfrastructureGenerator::generateRemoteInfrastructure(const boost::property_tree::ptree& configurationTree) { + printVersion(); + + auto infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(configurationTree, WorkflowType::Remote); + WorkflowSpec workflow; - auto config = ConfigurationFactory::getConfiguration(configurationSource); - - TaskRunnerFactory taskRunnerFactory; - CheckerFactory checkerFactory; - for (const auto& [taskName, taskConfig] : config->getRecursive("qc.tasks")) { - // todo sanitize somehow this if-frenzy - if (taskConfig.get("active", true)) { - if (taskConfig.get("location") == "local") { - // if tasks are LOCAL, generate mergers + checkers - - //todo use real mergers when they are done - - // generate merger only, when there is a need to merge something - if (taskConfig.get_child("machines").size() > 1) { - HistoMerger merger(taskName + "-merger", 1); - merger.configureInputsOutputs(TaskRunner::createTaskDataOrigin(), - TaskRunner::createTaskDataDescription(taskName), - { 1, taskConfig.get_child("machines").size() }); - DataProcessorSpec mergerSpec{ - merger.getName(), - merger.getInputSpecs(), - Outputs{ merger.getOutputSpec() }, - adaptFromTask(std::move(merger)), - }; - - workflow.emplace_back(mergerSpec); + std::set samplingPoliciesForRemoteTasks; + + for (const auto& taskSpec : infrastructureSpec.tasks | std::views::filter(&TaskSpec::active)) { + if (taskSpec.location == TaskLocationSpec::Local) { + // if tasks are LOCAL, generate input proxies + mergers + checkers + + size_t numberOfLocalMachines = taskSpec.localMachines.size() > 1 ? taskSpec.localMachines.size() : 1; + // Generate an input proxy + // These should be removed when we are able to declare dangling inputs in normal DPL devices + generateLocalTaskRemoteProxy(workflow, taskSpec, numberOfLocalMachines); + + // In "delta" mode Mergers should implement moving window, in "entire" - QC Tasks. + size_t resetAfterCycles = taskSpec.mergingMode == "delta" ? taskSpec.resetAfterCycles : 0; + auto cycleDurationsMultiplied = TaskRunnerFactory::getSanitizedCycleDurations(infrastructureSpec.common, taskSpec); + std::for_each(cycleDurationsMultiplied.begin(), cycleDurationsMultiplied.end(), + [taskSpec](std::pair& p) { p.first *= taskSpec.mergerCycleMultiplier; }); + bool enableMovingWindows = !taskSpec.movingWindows.empty(); + generateMergers(workflow, taskSpec.taskName, numberOfLocalMachines, cycleDurationsMultiplied, taskSpec.mergingMode, + resetAfterCycles, infrastructureSpec.common.monitoringUrl, taskSpec.detectorName, taskSpec.mergersPerLayer, enableMovingWindows, taskSpec.critical); + + } else if (taskSpec.location == TaskLocationSpec::Remote) { + + // -- if tasks are REMOTE, generate dispatcher proxies + tasks + checkers + // (for the time being we don't foresee parallel tasks on QC servers, so no mergers here) + + // Collecting Data Sampling Policies + for (const auto& dataSource : taskSpec.dataSources) { + if (dataSource.isOneOf(DataSourceType::DataSamplingPolicy)) { + samplingPoliciesForRemoteTasks.insert({ dataSource.name, taskSpec.localControl, taskSpec.remoteMachine }); + } else { + throw std::runtime_error( + "Configuration error: dataSource '" + dataSource.name + "' for a remote QC Task '" + taskSpec.taskName + // + "' does not have a supported type. Remote QC tasks can subscribe only to data sampling policies outputs."); } + } - } else if (taskConfig.get("location") == "remote") { - // -- if tasks are REMOTE, generate tasks + mergers + checkers + // Creating the remote task + auto taskConfig = TaskRunnerFactory::extractConfig(infrastructureSpec.common, taskSpec, 0, taskSpec.resetAfterCycles); + workflow.emplace_back(TaskRunnerFactory::create(taskConfig)); + } + } - workflow.emplace_back(taskRunnerFactory.create(taskName, configurationSource, 0)); + if (!samplingPoliciesForRemoteTasks.empty()) { + auto dataSamplingTree = configurationTree.get_child("dataSamplingPolicies"); + // Creating Data Sampling Policies proxies + for (const auto& [policyName, control, remoteMachine] : samplingPoliciesForRemoteTasks) { + (void)remoteMachine; + std::string port = std::to_string(DataSampling::PortForPolicy(dataSamplingTree, policyName).value_or(defaultPolicyPort)); + Outputs outputSpecs = DataSampling::OutputSpecsForPolicy(dataSamplingTree, policyName); + if (DataSampling::BindLocationForPolicy(dataSamplingTree, policyName) == "remote") { + generateDataSamplingPolicyRemoteProxyBind(workflow, policyName, outputSpecs, port, control); + } else { + // todo now we have to generate one proxy per local machine and policy, because of the proxy limitations. + // Use one proxy per policy when it is possible. + std::vector localMachines = DataSampling::MachinesForPolicy(dataSamplingTree, policyName); + for (const auto& localMachine : localMachines) { + generateDataSamplingPolicyRemoteProxyConnect(workflow, policyName, outputSpecs, localMachine, port, control); + } } + } + } + + generateCheckRunners(workflow, infrastructureSpec); + generateAggregator(workflow, infrastructureSpec); + generatePostProcessing(workflow, infrastructureSpec); + generateBookkeepingQualitySink(workflow, infrastructureSpec); + + return workflow; +} + +void InfrastructureGenerator::generateRemoteInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree) +{ + auto qcInfrastructure = InfrastructureGenerator::generateRemoteInfrastructure(configurationTree); + workflow.insert(std::end(workflow), std::begin(qcInfrastructure), std::end(qcInfrastructure)); +} + +framework::WorkflowSpec InfrastructureGenerator::generateLocalBatchInfrastructure(const boost::property_tree::ptree& configurationTree, const std::string& sinkFilePath) +{ + printVersion(); + + auto infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(configurationTree, WorkflowType::LocalBatch); + std::vector fileSinkInputs; + + WorkflowSpec workflow; + + for (const auto& taskSpec : infrastructureSpec.tasks | std::views::filter(&TaskSpec::active)) { + // We will merge deltas, thus we need to reset after each cycle (resetAfterCycles==1) + auto taskConfig = TaskRunnerFactory::extractConfig(infrastructureSpec.common, taskSpec, 0, 1); + workflow.emplace_back(TaskRunnerFactory::create(taskConfig)); + + fileSinkInputs.emplace_back(createUserInputSpec(DataSourceType::Task, taskSpec.detectorName, taskSpec.taskName)); + } + + if (!fileSinkInputs.empty()) { + // todo: could be moved to a factory. + workflow.push_back({ "qc-root-file-sink", + std::move(fileSinkInputs), + Outputs{}, + adaptFromTask(sinkFilePath), + Options{}, + CommonServices::defaultServices(), + { RootFileSink::getLabel() } }); + } + + return workflow; +} + +void InfrastructureGenerator::generateLocalBatchInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree, const std::string& sinkFilePath) +{ + auto qcInfrastructure = InfrastructureGenerator::generateLocalBatchInfrastructure(configurationTree, sinkFilePath); + workflow.insert(std::end(workflow), std::begin(qcInfrastructure), std::end(qcInfrastructure)); +} + +framework::WorkflowSpec InfrastructureGenerator::generateRemoteBatchInfrastructure(const boost::property_tree::ptree& configurationTree, const std::string& sourceFilePath) +{ + printVersion(); - workflow.emplace_back(checkerFactory.create(taskName + "-checker", taskName, configurationSource)); + auto infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(configurationTree, WorkflowType::RemoteBatch); + + WorkflowSpec workflow; + + std::vector fileSourceOutputs; + for (const auto& taskSpec : infrastructureSpec.tasks | std::views::filter(&TaskSpec::active)) { + auto taskConfig = TaskRunnerFactory::extractConfig(infrastructureSpec.common, taskSpec, 0, 1); + fileSourceOutputs.push_back(taskConfig.moSpec); + fileSourceOutputs.back().binding = RootFileSource::outputBinding(taskSpec.detectorName, taskSpec.taskName); + + // We create an OutputSpec for moving windows for this task only if they are expected. + if (!taskConfig.movingWindows.empty()) { + fileSourceOutputs.push_back( + createUserOutputSpec(DataSourceType::TaskMovingWindow, taskSpec.detectorName, taskSpec.taskName, 0, + RootFileSource::outputBinding(taskSpec.detectorName, taskSpec.taskName, true))); } } + if (!fileSourceOutputs.empty()) { + workflow.push_back({ "qc-root-file-source", {}, std::move(fileSourceOutputs), adaptFromTask(sourceFilePath) }); + } + + generateCheckRunners(workflow, infrastructureSpec); + generateAggregator(workflow, infrastructureSpec); + generatePostProcessing(workflow, infrastructureSpec); + generateBookkeepingQualitySink(workflow, infrastructureSpec); + return workflow; } -void InfrastructureGenerator::generateRemoteInfrastructure(framework::WorkflowSpec& workflow, std::string configurationSource) +void InfrastructureGenerator::generateRemoteBatchInfrastructure(framework::WorkflowSpec& workflow, const boost::property_tree::ptree& configurationTree, const std::string& sourceFilePath) { - auto qcInfrastructure = InfrastructureGenerator::generateRemoteInfrastructure(configurationSource); + auto qcInfrastructure = InfrastructureGenerator::generateRemoteBatchInfrastructure(configurationTree, sourceFilePath); workflow.insert(std::end(workflow), std::begin(qcInfrastructure), std::end(qcInfrastructure)); } -} // namespace o2::quality_control::core \ No newline at end of file +void InfrastructureGenerator::customizeInfrastructure(std::vector& policies) +{ + TaskRunnerFactory::customizeInfrastructure(policies); + MergerBuilder::customizeInfrastructure(policies); + CheckRunnerFactory::customizeInfrastructure(policies); + AggregatorRunnerFactory::customizeInfrastructure(policies); + RootFileSink::customizeInfrastructure(policies); + BookkeepingQualitySink::customizeInfrastructure(policies); +} + +void InfrastructureGenerator::printVersion() +{ + ILOG(Debug, Devel) << "QC version " << o2::quality_control::core::Version::GetQcVersion().getString() << ENDM; +} + +void InfrastructureGenerator::generateDataSamplingPolicyLocalProxyBind(framework::WorkflowSpec& workflow, + const string& policyName, + const framework::Inputs& inputSpecs, + const std::string& localMachine, + const string& localPort, + const std::string& control) +{ + std::string proxyName = policyName + "-proxy"; + std::string channelName = policyName + "-" + localMachine; + std::string channelConfig = "name=" + channelName + ",type=pub,method=bind,address=tcp://*:" + localPort + + ",rateLogging=60,transport=zeromq,sndBufSize=4,autoBind=false"; + auto channelSelector = [channelName](InputSpec const&, const std::unordered_map>&) { + return channelName; + }; + + workflow.emplace_back( + specifyFairMQDeviceMultiOutputProxy( + proxyName.c_str(), + inputSpecs, + channelConfig.c_str(), + channelSelector)); + workflow.back().labels.emplace_back(control == "odc" ? ecs::preserveRawChannelsLabel : ecs::uniqueProxyLabel); + if (getenv("O2_QC_KILL_PROXIES") != nullptr) { + workflow.back().metadata.push_back(DataProcessorMetadata{ ecs::privateMemoryKillThresholdMB, proxyMemoryKillThresholdMB }); + } +} + +void InfrastructureGenerator::generateDataSamplingPolicyRemoteProxyConnect(framework::WorkflowSpec& workflow, + const std::string& policyName, + const Outputs& outputSpecs, + const std::string& localMachine, + const std::string& localPort, + const std::string& control) +{ + std::string channelName = policyName + "-" + localMachine; + const std::string& proxyName = channelName; // channel name has to match proxy name + + std::string channelConfig = "name=" + channelName + ",type=sub,method=connect,address=tcp://" + + localMachine + ":" + localPort + ",rateLogging=60,transport=zeromq,rcvBufSize=1"; + + auto proxy = specifyExternalFairMQDeviceProxy( + proxyName.c_str(), + outputSpecs, + channelConfig.c_str(), + dplModelAdaptor()); + proxy.labels.emplace_back(control == "odc" ? ecs::preserveRawChannelsLabel : ecs::uniqueProxyLabel); + proxy.labels.emplace_back(DataProcessorLabel{ "input-proxy" }); + // if not in RUNNING, we should drop all the incoming messages, we set the corresponding proxy option. + enableDraining(proxy.options); + workflow.emplace_back(std::move(proxy)); + if (getenv("O2_QC_KILL_PROXIES") != nullptr) { + workflow.back().metadata.push_back(DataProcessorMetadata{ ecs::privateMemoryKillThresholdMB, proxyMemoryKillThresholdMB }); + } +} + +void InfrastructureGenerator::generateDataSamplingPolicyLocalProxyConnect(framework::WorkflowSpec& workflow, + const string& policyName, + const framework::Inputs& inputSpecs, + const std::string& remoteMachine, + const string& remotePort, + const std::string& control) +{ + std::string proxyName = policyName + "-proxy"; + const std::string& channelName = policyName; + std::string channelConfig = "name=" + channelName + ",type=pub,method=connect,address=tcp://" + remoteMachine + ":" + remotePort + + ",rateLogging=60,transport=zeromq,sndBufSize=4"; + auto channelSelector = [channelName](InputSpec const&, const std::unordered_map>&) { + return channelName; + }; + + workflow.emplace_back( + specifyFairMQDeviceMultiOutputProxy( + proxyName.c_str(), + inputSpecs, + channelConfig.c_str(), + channelSelector)); + workflow.back().labels.emplace_back(control == "odc" ? ecs::preserveRawChannelsLabel : ecs::uniqueProxyLabel); + if (getenv("O2_QC_KILL_PROXIES") != nullptr) { + workflow.back().metadata.push_back(DataProcessorMetadata{ ecs::privateMemoryKillThresholdMB, proxyMemoryKillThresholdMB }); + } +} + +void InfrastructureGenerator::generateDataSamplingPolicyRemoteProxyBind(framework::WorkflowSpec& workflow, + const std::string& policyName, + const Outputs& outputSpecs, + const std::string& remotePort, + const std::string& control) +{ + const std::string& channelName = policyName; + const std::string& proxyName = channelName; // channel name has to match proxy name + + std::string channelConfig = "name=" + channelName + ",type=sub,method=bind,address=tcp://*:" + remotePort + ",rateLogging=60,transport=zeromq,rcvBufSize=1,autoBind=false"; + + auto proxy = specifyExternalFairMQDeviceProxy( + proxyName.c_str(), + outputSpecs, + channelConfig.c_str(), + dplModelAdaptor()); + proxy.labels.emplace_back(control == "odc" ? ecs::preserveRawChannelsLabel : ecs::uniqueProxyLabel); + proxy.labels.emplace_back(DataProcessorLabel{ "input-proxy" }); + // if not in RUNNING, we should drop all the incoming messages, we set the corresponding proxy option. + enableDraining(proxy.options); + if (getenv("O2_QC_KILL_PROXIES") != nullptr) { + proxy.metadata.push_back(DataProcessorMetadata{ ecs::privateMemoryKillThresholdMB, proxyMemoryKillThresholdMB }); + } + workflow.emplace_back(std::move(proxy)); +} + +void InfrastructureGenerator::generateLocalTaskLocalProxy(framework::WorkflowSpec& workflow, size_t id, + const TaskSpec& taskSpec) +{ + std::string taskName = taskSpec.taskName; + std::string remotePort = std::to_string(taskSpec.remotePort); + std::string proxyName = taskSpec.detectorName + "-" + taskName + "-proxy"; + std::string channelName = taskSpec.detectorName + "-" + taskName + "-proxy"; + InputSpec proxyInput = createUserInputSpec(DataSourceType::Task, taskSpec.detectorName, taskName, static_cast(id), channelName); + std::string channelConfig = "name=" + channelName + ",type=pub,method=connect,address=tcp://" + + taskSpec.remoteMachine + ":" + remotePort + ",rateLogging=60,transport=zeromq,sndBufSize=4"; + + workflow.emplace_back(specifyFairMQDeviceMultiOutputProxy(proxyName.c_str(), { proxyInput }, channelConfig.c_str())); + workflow.back().labels.emplace_back(taskSpec.localControl == "odc" ? ecs::preserveRawChannelsLabel : ecs::uniqueProxyLabel); + if (!taskSpec.critical) { + workflow.back().labels.emplace_back(framework::DataProcessorLabel{ "expendable" }); + } + if (getenv("O2_QC_KILL_PROXIES") != nullptr) { + workflow.back().metadata.push_back(DataProcessorMetadata{ ecs::privateMemoryKillThresholdMB, proxyMemoryKillThresholdMB }); + } +} + +void InfrastructureGenerator::generateLocalTaskRemoteProxy(framework::WorkflowSpec& workflow, const TaskSpec& taskSpec, size_t numberOfLocalMachines) +{ + std::string taskName = taskSpec.taskName; + std::string remotePort = std::to_string(taskSpec.remotePort); + std::string proxyName = taskSpec.detectorName + "-" + taskName + "-proxy"; // channel name has to match proxy name + std::string channelName = taskSpec.detectorName + "-" + taskName + "-proxy"; + + Outputs proxyOutputs; + for (size_t id = 1; id <= numberOfLocalMachines; id++) { + proxyOutputs.emplace_back( + createUserOutputSpec(DataSourceType::Task, taskSpec.detectorName, taskName, static_cast(id), { channelName })); + } + + std::string channelConfig = "name=" + channelName + ",type=sub,method=bind,address=tcp://*:" + remotePort + + ",rateLogging=60,transport=zeromq,rcvBufSize=1,autoBind=false"; + + auto proxy = specifyExternalFairMQDeviceProxy( + proxyName.c_str(), + proxyOutputs, + channelConfig.c_str(), + dplModelAdaptor()); + proxy.labels.emplace_back(taskSpec.localControl == "odc" ? ecs::preserveRawChannelsLabel : ecs::uniqueProxyLabel); + proxy.labels.emplace_back(DataProcessorLabel{ "input-proxy" }); + if (!taskSpec.critical) { + proxy.labels.emplace_back(framework::DataProcessorLabel{ "expendable" }); + } + proxy.labels.emplace_back(framework::suppressDomainInfoLabel); // QC-1320 + // if not in RUNNING, we should drop all the incoming messages, we set the corresponding proxy option. + enableDraining(proxy.options); + if (getenv("O2_QC_KILL_PROXIES") != nullptr) { + proxy.metadata.push_back(DataProcessorMetadata{ ecs::privateMemoryKillThresholdMB, proxyMemoryKillThresholdMB }); + } + workflow.emplace_back(std::move(proxy)); +} +void InfrastructureGenerator::generateMergers(framework::WorkflowSpec& workflow, const std::string& taskName, + size_t numberOfLocalMachines, std::vector> cycleDurations, + const std::string& mergingMode, size_t resetAfterCycles, std::string monitoringUrl, + const std::string& detectorName, std::vector mergersPerLayer, bool enableMovingWindows, bool critical) +{ + Inputs mergerInputs; + for (size_t id = 1; id <= numberOfLocalMachines; id++) { + mergerInputs.emplace_back( + createUserInputSpec(DataSourceType::Task, detectorName, taskName, static_cast(id), taskName + std::to_string(id))); + } + + MergerInfrastructureBuilder mergersBuilder; + mergersBuilder.setInfrastructureName(taskName); + mergersBuilder.setInputSpecs(mergerInputs); + mergersBuilder.setOutputSpec(createUserOutputSpec(DataSourceType::Task, detectorName, taskName, 0, { "main" })); + mergersBuilder.setOutputSpecMovingWindow(createUserOutputSpec(DataSourceType::TaskMovingWindow, detectorName, taskName, 0, { "main_mw" })); + MergerConfig mergerConfig; + // if we are to change the mode to Full, disable reseting tasks after each cycle. + mergerConfig.inputObjectTimespan = { (mergingMode.empty() || mergingMode == "delta") ? InputObjectsTimespan::LastDifference : InputObjectsTimespan::FullHistory }; + mergerConfig.publicationDecision = { PublicationDecision::EachNSeconds, cycleDurations }; + mergerConfig.mergedObjectTimespan = { MergedObjectTimespan::NCycles, (int)resetAfterCycles }; + // for now one merger should be enough, multiple layers to be supported later + mergerConfig.topologySize = { TopologySize::MergersPerLayer, mergersPerLayer }; + mergerConfig.monitoringUrl = std::move(monitoringUrl); + mergerConfig.detectorName = detectorName; + mergerConfig.labels.push_back({ "resilient" }); + mergerConfig.labels.push_back(framework::suppressDomainInfoLabel); // QC-1320 + mergerConfig.publishMovingWindow = { enableMovingWindows ? PublishMovingWindow::Yes : PublishMovingWindow::No }; + mergerConfig.parallelismType = { (mergerConfig.inputObjectTimespan.value == InputObjectsTimespan::LastDifference) ? ParallelismType::RoundRobin : ParallelismType::SplitInputs }; + mergersBuilder.setConfig(mergerConfig); + + mergersBuilder.generateInfrastructure(workflow); +} + +void InfrastructureGenerator::generateCheckRunners(framework::WorkflowSpec& workflow, const InfrastructureSpec& infrastructureSpec) +{ + // todo have a look if this complex procedure can be simplified. + // todo also make well defined and scoped functions to make it more readable and clearer. + typedef std::vector InputNames; + typedef std::vector CheckConfigs; + std::map tasksOutputMap; // all active tasks' output, as inputs, keyed by their label + std::map checksMap; // all the Checks defined in the config mapped keyed by their sorted inputNames + std::map storeVectorMap; + + // todo: avoid code repetition + for (const auto& taskSpec : infrastructureSpec.tasks | std::views::filter(&TaskSpec::active)) { + InputSpec taskOutput{ createUserInputSpec(DataSourceType::Task, taskSpec.detectorName, taskSpec.taskName) }; + tasksOutputMap.insert({ DataSpecUtils::label(taskOutput), taskOutput }); + + bool movingWindowsEnabled = !taskSpec.movingWindows.empty(); + bool synchronousRemote = taskSpec.location == TaskLocationSpec::Local && (infrastructureSpec.workflowType == WorkflowType::Remote || infrastructureSpec.workflowType == WorkflowType::FullChain); + bool asynchronousRemote = infrastructureSpec.workflowType == WorkflowType::RemoteBatch; + if (movingWindowsEnabled && (synchronousRemote || asynchronousRemote)) { + InputSpec taskMovingWindowOutput{ createUserInputSpec(DataSourceType::TaskMovingWindow, taskSpec.detectorName, taskSpec.taskName) }; + tasksOutputMap.insert({ DataSpecUtils::label(taskMovingWindowOutput), taskMovingWindowOutput }); + } + } + + for (const auto& ppTaskSpec : infrastructureSpec.postProcessingTasks | std::views::filter(&PostProcessingTaskSpec::active)) { + InputSpec ppTaskOutput{ createUserInputSpec(DataSourceType::PostProcessingTask, ppTaskSpec.detectorName, ppTaskSpec.taskName) }; + tasksOutputMap.insert({ DataSpecUtils::label(ppTaskOutput), ppTaskOutput }); + } + + for (const auto& externalTaskSpec : infrastructureSpec.externalTasks | std::views::filter(&ExternalTaskSpec::active)) { + auto query = externalTaskSpec.query; + Inputs inputs = DataDescriptorQueryBuilder::parse(query.c_str()); + for (const auto& taskOutput : inputs) { + tasksOutputMap.insert({ DataSpecUtils::label(taskOutput), taskOutput }); + } + } + + // Instantiate Checks based on the configuration and build a map of checks (keyed by their inputs names) + for (const auto& checkSpec : infrastructureSpec.checks | std::views::filter(&CheckSpec::active)) { + auto checkConfig = Check::extractConfig(infrastructureSpec.common, checkSpec); + InputNames inputNames; + + for (const auto& inputSpec : checkConfig.inputSpecs) { + inputNames.push_back(DataSpecUtils::label(inputSpec)); + } + // Create a grouping key - sorted vector of InputSpecs as strings //todo: consider std::set, which is sorted + std::ranges::sort(inputNames); + // Group checks + checksMap[inputNames].push_back(checkConfig); + } + + // For every Task output, find a Check to store the MOs in the database. + // If none is found we create a sink device. + for (const auto& label : tasksOutputMap | std::views::keys) { // for each task output + bool isStored = false; + // Look for this task as input in the Checks' inputs, if we found it then we are done + for (const auto& inputNames : checksMap | std::views::keys) { // for each set of inputs + if (std::ranges::find(inputNames, label) != inputNames.end() && inputNames.size() == 1) { + storeVectorMap[inputNames].push_back(label); + break; + } + } + if (!isStored) { // fixme: statement is always true + // If there is no Check for a given input, create a candidate for a sink device + InputNames singleEntry{ label }; + // Init empty Check vector to appear in the next step + checksMap[singleEntry]; + storeVectorMap[singleEntry].push_back(label); + } + } + + // Create CheckRunners: 1 per set of inputs + std::vector checkRunnerOutputs; + auto checkRunnerConfig = CheckRunnerFactory::extractConfig(infrastructureSpec.common); + for (auto& [inputNames, checkConfigs] : checksMap) { + // Logging + ILOG(Debug, Devel) << ">> Inputs (" << inputNames.size() << "): "; + for (const auto& name : inputNames) + ILOG(Debug, Devel) << name << " "; + ILOG(Debug, Devel) << " ; Checks (" << checkConfigs.size() << "): "; + for (const auto& checkConfig : checkConfigs) + ILOG(Debug, Devel) << checkConfig.name << " "; + ILOG(Debug, Devel) << " ; Stores (" << storeVectorMap[inputNames].size() << "): "; + for (const auto& input : storeVectorMap[inputNames]) + ILOG(Debug, Devel) << input << " "; + ILOG(Debug, Devel) << ENDM; + + DataProcessorSpec spec = checkConfigs.empty() + ? CheckRunnerFactory::createSinkDevice(checkRunnerConfig, tasksOutputMap.find(inputNames[0])->second) + : CheckRunnerFactory::create(checkRunnerConfig, checkConfigs, storeVectorMap[inputNames]); + workflow.emplace_back(spec); + checkRunnerOutputs.insert(checkRunnerOutputs.end(), spec.outputs.begin(), spec.outputs.end()); + } + + ILOG(Debug, Devel) << ">> Outputs (" << checkRunnerOutputs.size() << "): "; + for (const auto& output : checkRunnerOutputs) + ILOG(Debug, Devel) << DataSpecUtils::describe(output) << " "; + ILOG(Debug, Devel) << ENDM; +} + +void InfrastructureGenerator::throwIfAggNamesClashCheckNames(const InfrastructureSpec& infrastructureSpec) +{ + auto checksNames = infrastructureSpec.checks | std::views::transform([](const auto& check) { + return check.checkName; + }); + + auto conflictingAggregator = std::ranges::find_if(infrastructureSpec.aggregators, [&](const auto& aggregator) { + return std::ranges::find(checksNames, aggregator.aggregatorName) != checksNames.end(); + }); + + // If a conflict is found, log the error and throw an exception + if (conflictingAggregator != infrastructureSpec.aggregators.end()) { + ILOG(Error, Ops) << "The aggregator \"" << conflictingAggregator->aggregatorName << "\" has the same name as one of the Check. This is forbidden." << ENDM; + throw std::runtime_error(std::string("aggregator has the same name as a check: ") + conflictingAggregator->aggregatorName); + } +} + +void InfrastructureGenerator::generateAggregator(WorkflowSpec& workflow, const InfrastructureSpec& infrastructureSpec) +{ + if (infrastructureSpec.aggregators.empty()) { + ILOG(Debug, Devel) << "No \"aggregators\" structure found in the config file. If no quality aggregation is expected, then it is completely fine." << ENDM; + return; + } + + // Make sure we don't have duplicated names in the checks and aggregators + throwIfAggNamesClashCheckNames(infrastructureSpec); + + DataProcessorSpec spec = AggregatorRunnerFactory::create(infrastructureSpec.common, infrastructureSpec.aggregators); + workflow.emplace_back(spec); +} + +void InfrastructureGenerator::generatePostProcessing(WorkflowSpec& workflow, const InfrastructureSpec& infrastructureSpec) +{ + if (infrastructureSpec.postProcessingTasks.empty()) { + ILOG(Debug, Devel) << "No \"postprocessing\" structure found in the config file. If no postprocessing is expected, then it is completely fine." << ENDM; + return; + } + for (const auto& ppTaskSpec : infrastructureSpec.postProcessingTasks | std::views::filter(&PostProcessingTaskSpec::active)) { + PostProcessingDevice ppTask{ PostProcessingRunner::extractConfig(infrastructureSpec.common, ppTaskSpec) }; + + DataProcessorSpec dataProcessorSpec{ + ppTask.getDeviceName(), + ppTask.getInputsSpecs(), + ppTask.getOutputSpecs(), + {}, + ppTask.getOptions() + }; + dataProcessorSpec.labels.emplace_back(PostProcessingDevice::getLabel()); + if (!ppTaskSpec.critical) { + framework::DataProcessorLabel expendableLabel = { "expendable" }; + dataProcessorSpec.labels.emplace_back(expendableLabel); + } + dataProcessorSpec.algorithm = adaptFromTask(std::move(ppTask)); + + workflow.emplace_back(std::move(dataProcessorSpec)); + } +} + +void InfrastructureGenerator::generateBookkeepingQualitySink(WorkflowSpec& workflow, const InfrastructureSpec& infrastructureSpec) +{ + framework::Inputs sinkInputs{}; + + for (const auto& checkSpec : infrastructureSpec.checks | std::views::filter(&CheckSpec::active) | std::views::filter(&CheckSpec::exportToBookkeeping)) { + ILOG(Debug, Support) << "Adding input to BookkeepingSink from check " << checkSpec.checkName << " and detector: " << checkSpec.detectorName << ENDM; + sinkInputs.emplace_back(createUserInputSpec(DataSourceType::Check, checkSpec.detectorName, checkSpec.checkName)); + } + + for (const auto& aggregatorSpec : infrastructureSpec.aggregators | std::views::filter(&AggregatorSpec::active) | std::views::filter(&AggregatorSpec::exportToBookkeeping)) { + ILOG(Debug, Support) << "Adding input to BookkeepingSink from aggregator " << aggregatorSpec.aggregatorName << " and detector: " << aggregatorSpec.detectorName << ENDM; + sinkInputs.emplace_back(createUserInputSpec(DataSourceType::Aggregator, aggregatorSpec.detectorName, aggregatorSpec.aggregatorName)); + } + + if (sinkInputs.empty()) { + ILOG(Debug, Support) << "BookkeepingSink is not being created because we couldn't find any suitable inputs." << ENDM; + return; + } + + DataProcessorSpec sinkDataProcessor{ + .name = "BookkeepingSink", + .inputs = sinkInputs, + .outputs = Outputs{}, + .algorithm = adaptFromTask( + infrastructureSpec.common.bookkeepingUrl, + core::toEnum(infrastructureSpec.common.activityProvenance)), + .labels = { { "resilient" }, BookkeepingQualitySink::getLabel() } + }; + workflow.emplace_back(std::move(sinkDataProcessor)); +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/InfrastructureSpecReader.cxx b/Framework/src/InfrastructureSpecReader.cxx new file mode 100644 index 0000000000..91f18cd71c --- /dev/null +++ b/Framework/src/InfrastructureSpecReader.cxx @@ -0,0 +1,487 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file InfrastructureSpecReader.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/InfrastructureSpecReader.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/UserInputOutput.h" + +#include +#include + +using namespace o2::utilities; +using namespace o2::framework; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::checker; + +namespace o2::quality_control::core +{ + +InfrastructureSpec InfrastructureSpecReader::readInfrastructureSpec(const boost::property_tree::ptree& wholeTree, WorkflowType workflowType) +{ + InfrastructureSpec spec; + spec.workflowType = workflowType; + const auto& qcTree = wholeTree.get_child("qc"); + if (qcTree.find("config") != qcTree.not_found()) { + spec.common = readSpecEntry("", qcTree.get_child("config"), wholeTree); + } else { + ILOG(Error) << "The \"config\" section in the provided QC config file is missing." << ENDM; + } + + spec.tasks = readSectionSpec(wholeTree, "tasks"); + spec.checks = readSectionSpec(wholeTree, "checks"); + spec.aggregators = readSectionSpec(wholeTree, "aggregators"); + spec.postProcessingTasks = readSectionSpec(wholeTree, "postprocessing"); + spec.externalTasks = readSectionSpec(wholeTree, "externalTasks"); + + return spec; +} + +template <> +CommonSpec InfrastructureSpecReader::readSpecEntry(const std::string&, const boost::property_tree::ptree& commonTree, const boost::property_tree::ptree&) +{ + CommonSpec spec; + for (const auto& [key, value] : commonTree.get_child("database")) { + spec.database.emplace(key, value.get_value()); + } + spec.activityNumber = commonTree.get("Activity.number", spec.activityNumber); + spec.activityType = commonTree.get("Activity.type", spec.activityType); + spec.activityPassName = commonTree.get("Activity.passName", spec.activityPassName); + spec.activityPeriodName = commonTree.get("Activity.periodName", spec.activityPeriodName); + spec.activityProvenance = commonTree.get("Activity.provenance", spec.activityProvenance); + spec.activityStart = commonTree.get("Activity.start", spec.activityStart); + spec.activityEnd = commonTree.get("Activity.end", spec.activityEnd); + spec.activityBeamType = commonTree.get("Activity.beamType", spec.activityBeamType); + spec.activityPartitionName = commonTree.get("Activity.partitionName", spec.activityPartitionName); + spec.activityFillNumber = commonTree.get("Activity.fillNumber", spec.activityFillNumber); + spec.activityOriginalNumber = commonTree.get("Activity.originalNumber", spec.activityOriginalNumber); + spec.monitoringUrl = commonTree.get("monitoring.url", spec.monitoringUrl); + spec.consulUrl = commonTree.get("consul.url", spec.consulUrl); + spec.conditionDBUrl = commonTree.get("conditionDB.url", spec.conditionDBUrl); + spec.infologgerDiscardParameters = { + commonTree.get("infologger.filterDiscardDebug", spec.infologgerDiscardParameters.debug), + commonTree.get("infologger.filterDiscardLevel", spec.infologgerDiscardParameters.fromLevel), + commonTree.get("infologger.filterDiscardFile", spec.infologgerDiscardParameters.file), + commonTree.get("infologger.filterRotateMaxBytes", spec.infologgerDiscardParameters.rotateMaxBytes), + commonTree.get("infologger.filterRotateMaxFiles", spec.infologgerDiscardParameters.rotateMaxFiles), + commonTree.get("infologger.debugInDiscardFile", spec.infologgerDiscardParameters.debugInDiscardFile) + }; + spec.postprocessingPeriod = commonTree.get("postprocessing.periodSeconds", spec.postprocessingPeriod); + spec.bookkeepingUrl = commonTree.get("bookkeeping.url", spec.bookkeepingUrl); + spec.kafkaBrokersUrl = commonTree.get("kafka.url", spec.kafkaBrokersUrl); + spec.kafkaTopicAliECSRun = commonTree.get("kafka.topicAliecsRun", spec.kafkaTopicAliECSRun); + + return spec; +} + +template <> +TaskSpec InfrastructureSpecReader::readSpecEntry(const std::string& taskID, const boost::property_tree::ptree& taskTree, const boost::property_tree::ptree& wholeTree) +{ + static std::unordered_map const taskLocationFromString = { + { "local", TaskLocationSpec::Local }, + { "remote", TaskLocationSpec::Remote } + }; + + TaskSpec ts; + + ts.taskName = taskTree.get("taskName", taskID); + ts.className = taskTree.get("className"); + ts.moduleName = taskTree.get("moduleName"); + ts.detectorName = taskTree.get("detectorName"); + ts.disableLastCycle = taskTree.get("disableLastCycle", false); + ts.cycleDurationSeconds = taskTree.get("cycleDurationSeconds", -1); + if (taskTree.count("cycleDurations") > 0) { + for (const auto& cycleConfig : taskTree.get_child("cycleDurations")) { + auto cycleDuration = cycleConfig.second.get("cycleDurationSeconds"); + auto validity = cycleConfig.second.get("validitySeconds"); + ts.multipleCycleDurations.push_back(std::pair{ cycleDuration, validity }); + } + } + if (taskTree.count("dataSources") > 0) { + for (const auto& [_key, dataSourceTree] : taskTree.get_child("dataSources")) { + (void)_key; + ts.dataSources.push_back(readSpecEntry(taskID, dataSourceTree, wholeTree)); + } + } else { + ts.dataSources = { readSpecEntry(taskID, taskTree.get_child("dataSource"), wholeTree) }; + } + + ts.active = taskTree.get("active", ts.active); + ts.critical = taskTree.get("critical", ts.critical); + ts.maxNumberCycles = taskTree.get("maxNumberCycles", ts.maxNumberCycles); + ts.resetAfterCycles = taskTree.get("resetAfterCycles", ts.resetAfterCycles); + ts.saveObjectsToFile = taskTree.get("saveObjectsToFile", ts.saveObjectsToFile); + if (taskTree.count("extendedTaskParameters") > 0 && taskTree.count("taskParameters") > 0) { + ILOG(Warning, Devel) << "Both taskParameters and extendedTaskParameters are defined in the QC config file. We will use only extendedTaskParameters. " << ENDM; + } + if (taskTree.count("extendedTaskParameters") > 0) { + ts.customParameters.populateCustomParameters(taskTree.get_child("extendedTaskParameters")); + } else if (taskTree.count("taskParameters") > 0) { + for (const auto& [key, value] : taskTree.get_child("taskParameters")) { + ts.customParameters.set(key, value.get_value()); + } + } + + bool multinodeSetup = taskTree.find("location") != taskTree.not_found(); + ts.location = taskLocationFromString.at(taskTree.get("location", "remote")); + if (taskTree.count("localMachines") > 0) { + for (const auto& [key, value] : taskTree.get_child("localMachines")) { + ts.localMachines.emplace_back(value.get_value()); + } + } + // fixme: ideally we should print those only when we are running with '--local' and '--remote', + // but we do not have access to this information here. + if (multinodeSetup && taskTree.count("remoteMachine") == 0) { + ILOG(Warning, Trace) + << "No remote machine was specified for a multinode QC setup." + " This is fine if running with AliECS, but it will fail in standalone mode." + << ENDM; + } + ts.remoteMachine = taskTree.get("remoteMachine", ts.remoteMachine); + if (multinodeSetup && ts.location == TaskLocationSpec::Local && taskTree.count("remotePort") == 0) { + ILOG(Warning, Trace) + << "No remote port was specified for a task which should use Mergers in a multinode QC setup." + " This is fine if running with AliECS, but it might fail in standalone mode." + << ENDM; + } + ts.remotePort = taskTree.get("remotePort", ts.remotePort); + ts.localControl = taskTree.get("localControl", ts.localControl); + ts.mergingMode = taskTree.get("mergingMode", ts.mergingMode); + ts.mergerCycleMultiplier = taskTree.get("mergerCycleMultiplier", ts.mergerCycleMultiplier); + if (taskTree.count("mergersPerLayer") > 0) { + ts.mergersPerLayer.clear(); + for (const auto& [key, value] : taskTree.get_child("mergersPerLayer")) { + ts.mergersPerLayer.emplace_back(value.get_value()); + } + } + + if (taskTree.count("grpGeomRequest") > 0) { + ts.grpGeomRequestSpec = readSpecEntry(ts.taskName, taskTree.get_child("grpGeomRequest"), wholeTree); + } + + if (taskTree.count("globalTrackingDataRequest") > 0) { + ts.globalTrackingDataRequest = readSpecEntry(ts.taskName, taskTree.get_child("globalTrackingDataRequest"), wholeTree); + } + + if (taskTree.count("movingWindows") > 0) { + ts.movingWindows.clear(); + for (const auto& [key, value] : taskTree.get_child("movingWindows")) { + ts.movingWindows.emplace_back(value.get_value()); + } + } + + return ts; +} + +template <> +DataSourceSpec InfrastructureSpecReader::readSpecEntry(const std::string& dataRequestorId, + const boost::property_tree::ptree& dataSourceTree, + const boost::property_tree::ptree& wholeTree) +{ + static std::unordered_map const dataSourceTypeFromString = { + // fixme: the convention is inconsistent and it should be fixed in coordination with configuration files + { "dataSamplingPolicy", DataSourceType::DataSamplingPolicy }, + { "direct", DataSourceType::Direct }, + { "Task", DataSourceType::Task }, + { "TaskMovingWindow", DataSourceType::TaskMovingWindow }, + { "Check", DataSourceType::Check }, + { "Aggregator", DataSourceType::Aggregator }, + { "PostProcessing", DataSourceType::PostProcessingTask }, + { "ExternalTask", DataSourceType::ExternalTask } + }; + + DataSourceSpec dss; + dss.type = dataSourceTypeFromString.at(dataSourceTree.get("type")); + + switch (dss.type) { + case DataSourceType::DataSamplingPolicy: { + dss.id = dataSourceTree.get("name"); + dss.name = dss.id; + dss.inputs = DataSampling::InputSpecsForPolicy(wholeTree.get_child("dataSamplingPolicies"), dss.name); + break; + } + case DataSourceType::Direct: { + auto inputsQuery = dataSourceTree.get("query"); + dss.id = inputsQuery; + dss.name = dss.id; + dss.inputs = DataDescriptorQueryBuilder::parse(inputsQuery.c_str()); + break; + } + case DataSourceType::Task: { + dss.id = dataSourceTree.get("name"); + // this allows us to have tasks with the same name for different detectors + dss.name = wholeTree.get("qc.tasks." + dss.id + ".taskName", dss.id); + auto detectorName = wholeTree.get("qc.tasks." + dss.id + ".detectorName"); + + dss.inputs = { createUserInputSpec(DataSourceType::Task, detectorName, dss.name) }; + if (dataSourceTree.count("MOs") > 0) { + for (const auto& moName : dataSourceTree.get_child("MOs")) { + const auto mo = moName.second.get_value(); + if (!mo.empty()) { + dss.subInputs.push_back(std::move(mo)); + } else { + ILOG(Warning, Ops) << "Data source of type Task with name: " << dss.name << " contains empty mo, ignoring, but configuration should be fixed." << ENDM; + } + } + } + break; + } + case DataSourceType::TaskMovingWindow: { + dss.id = dataSourceTree.get("name"); + // this allows us to have tasks with the same name for different detectors + std::string taskName = wholeTree.get("qc.tasks." + dss.id + ".taskName", dss.id); + dss.name = taskName + "/mw"; + auto detectorName = wholeTree.get("qc.tasks." + dss.id + ".detectorName"); + + dss.inputs = { createUserInputSpec(DataSourceType::TaskMovingWindow, detectorName, taskName, 0, dss.name) }; + if (dataSourceTree.count("MOs") > 0) { + for (const auto& moName : dataSourceTree.get_child("MOs")) { + const auto mo = moName.second.get_value(); + if (!mo.empty()) { + dss.subInputs.push_back(std::move(mo)); + } else { + ILOG(Warning, Ops) << "Data source of type TaskMovingWindow with name: " << dss.name << " contains empty mo, ignoring, but configuration should be fixed." << ENDM; + } + } + } + break; + } + case DataSourceType::PostProcessingTask: { + dss.id = dataSourceTree.get("name"); + // this allows us to have tasks with the same name for different detectors + dss.name = wholeTree.get("qc.postprocessing." + dss.id + ".taskName", dss.id); + auto detectorName = wholeTree.get("qc.postprocessing." + dss.id + ".detectorName"); + dss.inputs = { createUserInputSpec(DataSourceType::PostProcessingTask, detectorName, dss.id, 0, dss.name) }; + if (dataSourceTree.count("MOs") > 0) { + for (const auto& moName : dataSourceTree.get_child("MOs")) { + dss.subInputs.push_back(moName.second.get_value()); + } + } + break; + } + case DataSourceType::Check: { + dss.id = dataSourceTree.get("name"); + dss.name = wholeTree.get("qc.checks." + dss.id + ".checkName", dss.id); + auto detectorName = wholeTree.get("qc.checks." + dss.id + ".detectorName"); + dss.inputs = { createUserInputSpec(DataSourceType::Check, detectorName, dss.name) }; + if (dataSourceTree.count("QOs") > 0) { + for (const auto& moName : dataSourceTree.get_child("QOs")) { + const auto qo = moName.second.get_value(); + if (!qo.empty()) { + dss.subInputs.push_back(std::move(qo)); + } else { + ILOG(Warning, Ops) << "Data source of type Check with name: " << dss.name << " contains empty qo, ignoring, but configuration should be fixed." << ENDM; + } + } + } + break; + } + case DataSourceType::Aggregator: { + dss.id = dataSourceTree.get("name"); + dss.name = wholeTree.get("qc.aggregators." + dss.id + ".checkName", dss.id); + auto detectorName = wholeTree.get("qc.aggregators." + dss.id + ".detectorName"); + dss.inputs = { createUserInputSpec(DataSourceType::Aggregator, detectorName, dss.name) }; + if (dataSourceTree.count("QOs") > 0) { + for (const auto& moName : dataSourceTree.get_child("QOs")) { + const auto qo = moName.second.get_value(); + if (!qo.empty()) { + dss.subInputs.push_back(std::move(qo)); + } else { + ILOG(Warning, Ops) << "Data source of type Aggregator with name: " << dss.name << " contains empty qo, ignoring, but configuration should be fixed." << ENDM; + } + } + } + break; + } + case DataSourceType::ExternalTask: { + dss.id = dataSourceTree.get("name"); + dss.name = dss.id; + auto query = wholeTree.get("qc.externalTasks." + dss.name + ".query"); + dss.inputs = o2::framework::DataDescriptorQueryBuilder::parse(query.c_str()); + break; + } + case DataSourceType::Invalid: + // todo: throw? + break; + } + + return dss; +} + +template <> +CheckSpec InfrastructureSpecReader::readSpecEntry(const std::string& checkID, const boost::property_tree::ptree& checkTree, const boost::property_tree::ptree& wholeTree) +{ + CheckSpec cs; + + cs.checkName = checkTree.get("checkName", checkID); + cs.className = checkTree.get("className"); + cs.moduleName = checkTree.get("moduleName"); + cs.detectorName = checkTree.get("detectorName", cs.detectorName); + + // errors of the past + const auto& dataSourcesTree = checkTree.count("dataSource") > 0 ? checkTree.get_child("dataSource") : checkTree.get_child("dataSources"); + for (const auto& [_key, dataSourceTree] : dataSourcesTree) { + (void)_key; + cs.dataSources.push_back(readSpecEntry(checkID, dataSourceTree, wholeTree)); + } + + if (auto policy = checkTree.get_optional("policy"); policy.has_value()) { + cs.updatePolicy = UpdatePolicyTypeUtils::FromString(policy.get()); + } + + cs.active = checkTree.get("active", cs.active); + cs.exportToBookkeeping = checkTree.get("exportToBookkeeping", cs.exportToBookkeeping); + + if (checkTree.count("extendedCheckParameters") > 0) { + cs.customParameters.populateCustomParameters(checkTree.get_child("extendedCheckParameters")); + } + if (checkTree.count("checkParameters") > 0) { + for (const auto& [key, value] : checkTree.get_child("checkParameters")) { + cs.customParameters.set(key, value.get_value()); + } + } + + return cs; +} + +template <> +AggregatorSpec InfrastructureSpecReader::readSpecEntry(const std::string& aggregatorID, const boost::property_tree::ptree& aggregatorTree, const boost::property_tree::ptree& wholeTree) +{ + AggregatorSpec as; + + as.aggregatorName = aggregatorTree.get("checkName", aggregatorID); + as.className = aggregatorTree.get("className"); + as.moduleName = aggregatorTree.get("moduleName"); + as.detectorName = aggregatorTree.get("detectorName", as.detectorName); + + // errors of the past + const auto& dataSourcesTree = aggregatorTree.count("dataSource") > 0 ? aggregatorTree.get_child("dataSource") : aggregatorTree.get_child("dataSources"); + for (const auto& [_key, dataSourceTree] : dataSourcesTree) { + (void)_key; + as.dataSources.push_back(readSpecEntry(aggregatorID, dataSourceTree, wholeTree)); + } + + if (auto policy = aggregatorTree.get_optional("policy"); policy.has_value()) { + as.updatePolicy = UpdatePolicyTypeUtils::FromString(policy.get()); + } + + as.active = aggregatorTree.get("active", as.active); + as.exportToBookkeeping = aggregatorTree.get("exportToBookkeeping", as.exportToBookkeeping); + + if (aggregatorTree.count("extendedAggregatorParameters") > 0) { + as.customParameters.populateCustomParameters(aggregatorTree.get_child("extendedAggregatorParameters")); + } + if (aggregatorTree.count("aggregatorParameters") > 0) { + for (const auto& [key, value] : aggregatorTree.get_child("aggregatorParameters")) { + as.customParameters.set(key, value.get_value()); + } + } + return as; +} + +template <> +PostProcessingTaskSpec + InfrastructureSpecReader::readSpecEntry(const std::string& ppTaskId, const boost::property_tree::ptree& ppTaskTree, const boost::property_tree::ptree& wholeTree) +{ + PostProcessingTaskSpec ppts; + + ppts.id = ppTaskId; + ppts.taskName = ppTaskTree.get("taskName", ppts.id); + ppts.active = ppTaskTree.get("active", ppts.active); + ppts.critical = ppTaskTree.get("critical", ppts.critical); + ppts.detectorName = ppTaskTree.get("detectorName", ppts.detectorName); + if (ppTaskTree.count("sourceRepo") > 0) { + for (const auto& [key, value] : ppTaskTree.get_child("sourceRepo")) { + ppts.sourceDatabase.emplace(key, value.get_value()); + } + } + ppts.tree = wholeTree; + + return ppts; +} + +template <> +ExternalTaskSpec + InfrastructureSpecReader::readSpecEntry(const std::string& externalTaskName, const boost::property_tree::ptree& externalTaskTree, const boost::property_tree::ptree&) +{ + ExternalTaskSpec ets; + + ets.taskName = externalTaskName; + ets.query = externalTaskTree.get("query"); + ets.active = externalTaskTree.get("active", ets.active); + + return ets; +} + +template <> +GRPGeomRequestSpec + InfrastructureSpecReader::readSpecEntry(const std::string&, const boost::property_tree::ptree& grpGeomRequestTree, const boost::property_tree::ptree&) +{ + GRPGeomRequestSpec grpSpec; + grpSpec.geomRequest = grpGeomRequestTree.get("geomRequest", grpSpec.geomRequest); + grpSpec.askGRPECS = grpGeomRequestTree.get("askGRPECS", grpSpec.askGRPECS); + grpSpec.askGRPLHCIF = grpGeomRequestTree.get("askGRPLHCIF", grpSpec.askGRPLHCIF); + grpSpec.askGRPMagField = grpGeomRequestTree.get("askGRPMagField", grpSpec.askGRPMagField); + grpSpec.askMatLUT = grpGeomRequestTree.get("askMatLUT", grpSpec.askMatLUT); + grpSpec.askTime = grpGeomRequestTree.get("askTime", grpSpec.askTime); + grpSpec.askOnceAllButField = grpGeomRequestTree.get("askOnceAllButField", grpSpec.askOnceAllButField); + grpSpec.needPropagatorD = grpGeomRequestTree.get("needPropagatorD", grpSpec.needPropagatorD); + + return grpSpec; +} + +template <> +GlobalTrackingDataRequestSpec + InfrastructureSpecReader::readSpecEntry(const std::string&, const boost::property_tree::ptree& dataRequestTree, const boost::property_tree::ptree&) +{ + GlobalTrackingDataRequestSpec gtdrSpec; + gtdrSpec.canProcessTracks = dataRequestTree.get("canProcessTracks", gtdrSpec.canProcessTracks); + gtdrSpec.requestTracks = dataRequestTree.get("requestTracks", gtdrSpec.requestTracks); + gtdrSpec.canProcessClusters = dataRequestTree.get("canProcessClusters", gtdrSpec.canProcessClusters); + gtdrSpec.requestClusters = dataRequestTree.get("requestClusters", gtdrSpec.requestClusters); + gtdrSpec.mc = dataRequestTree.get("mc", gtdrSpec.mc); + + return gtdrSpec; +} + +std::string InfrastructureSpecReader::validateDetectorName(std::string name) +{ + // name must be a detector code from DetID or one of the few allowed general names + int nDetectors = 17; + const char* detNames[17] = // once we can use DetID, remove this hard-coded list + { "ITS", "TPC", "TRD", "TOF", "PHS", "CPV", "EMC", "HMP", "MFT", "MCH", "MID", "ZDC", "FT0", "FV0", "FDD", "ACO", "FOC" }; + std::vector permitted = { "MISC", "DAQ", "GENERAL", "TST", "BMK", "CTP", "TRG", "DCS", "GLO", "FIT" }; + for (auto i = 0; i < nDetectors; i++) { + permitted.emplace_back(detNames[i]); + // permitted.push_back(o2::detectors::DetID::getName(i)); + } + auto it = std::find(permitted.begin(), permitted.end(), name); + + if (it == permitted.end()) { + std::string permittedString; + for (const auto& i : permitted) + permittedString += i + ' '; + ILOG(Error, Support) << "Invalid detector name : " << name << ". Placeholder 'MISC' will be used instead. Note: list of permitted detector names :" << permittedString << ENDM; + return "MISC"; + } + return name; +} + +template +T InfrastructureSpecReader::readSpecEntry(const std::string&, const boost::property_tree::ptree&, const boost::property_tree::ptree&) +{ + throw std::runtime_error("Unknown entry type: " + std::string(typeid(T).name())); +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/KafkaPoller.cxx b/Framework/src/KafkaPoller.cxx new file mode 100644 index 0000000000..36b3b259f2 --- /dev/null +++ b/Framework/src/KafkaPoller.cxx @@ -0,0 +1,167 @@ +#include "QualityControl/KafkaPoller.h" + +#include "QualityControl/QcInfoLogger.h" +#include "kafka/KafkaException.h" +#include "kafka/Properties.h" +#include "proto/events.pb.h" +#include +#include +#include + +namespace o2::quality_control::core +{ + +namespace proto +{ + +struct Ev_RunEventPartial { + std::string_view state; + events::OpStatus transitionStatus; + std::string_view environmentID; + int runNumber; +}; + +// return true if runEvent and runEventPartial have the same state and transition state and have +// DIFFERENT(!) runNumber AND environmentID +bool operator==(const events::Ev_RunEvent& runEvent, const Ev_RunEventPartial& runEventPartial) +{ + // TODO: Should we check whether the error is empty? + if (runEvent.state() != runEventPartial.state || + runEvent.transitionstatus() != runEventPartial.transitionStatus) { + return false; + } + + if (runEvent.runnumber() == runEventPartial.runNumber && runEvent.environmentid() == runEventPartial.environmentID) { + return false; + } + + return true; +} + +auto recordToEvent(const kafka::Value& kafkaRecord) -> std::optional +{ + events::Event event; + + if (!event.ParseFromArray(kafkaRecord.data(), kafkaRecord.size())) { + ILOG(Error, Ops) << "Received wrong or inconsistent data while parser Event from kafka proto" << ENDM; + return std::nullopt; + } + + return event; +} + +void fillActivityWithoutTimestamp(const events::Event& event, Activity& activity) +{ + if (event.has_runevent()) { + auto& runEvent = event.runevent(); + activity.mId = runEvent.runnumber(); + activity.mPartitionName = runEvent.environmentid(); + } +} + +void start_of_run::fillActivity(const events::Event& event, Activity& activity) +{ + fillActivityWithoutTimestamp(event, activity); + activity.mValidity = ValidityInterval{ uint64_t(event.timestamp()), std::numeric_limits::max() }; +} + +bool start_of_run::isValid(const events::Event& event, const std::string& environmentID, int runNumber) +{ + if (!event.has_runevent()) { + return false; + } + + const auto& runEvent = event.runevent(); + + if (runEvent.transition() != "START_ACTIVITY") { + return false; + } + + if (runEvent.transitionstatus() != events::OpStatus::STARTED) { + return false; + } + + return runEvent == Ev_RunEventPartial{ "CONFIGURED", events::OpStatus::STARTED, environmentID, runNumber }; +} + +void end_of_run::fillActivity(const events::Event& event, Activity& activity) +{ + fillActivityWithoutTimestamp(event, activity); + activity.mValidity = ValidityInterval{ std::numeric_limits::min(), uint64_t(event.timestamp()) }; +} + +bool end_of_run::isValid(const events::Event& event, const std::string& environmentID, int runNumber) +{ + if (!event.has_runevent()) { + return false; + } + + const auto& runEvent = event.runevent(); + + if (runEvent.transition() != "STOP_ACTIVITY" && runEvent.transition() != "TEARDOWN") { + return false; + } + + if (runEvent.transitionstatus() != events::OpStatus::STARTED) { + return false; + } + + return runEvent == Ev_RunEventPartial{ "RUNNING", events::OpStatus::STARTED, environmentID, runNumber }; +} + +} // namespace proto + +kafka::Properties createProperties(const std::string& brokers, const std::string& groupId) +{ + if (brokers.empty()) { + constexpr std::string_view message{ "You are trying to start KafkaPoller without any brokers" }; + ILOG(Fatal, Ops) << message << ENDM; + throw std::invalid_argument{ message.data() }; + } + + auto properties = kafka::Properties{ { { "bootstrap.servers", { brokers } }, + { "enable.auto.commit", { "true" } }, + { "auto.offset.reset", { "latest" } } } }; + + if (!groupId.empty()) { + properties.put("group.id", groupId); + } + + return properties; +} + +KafkaPoller::KafkaPoller(const std::string& brokers, const std::string& groupId) + : mConsumer(createProperties(brokers, groupId)) +{ +} + +void KafkaPoller::subscribe(const std::string& topic, size_t numberOfRetries) +{ + for (size_t retryNumber = 0; retryNumber != numberOfRetries; ++retryNumber) { + try { + mConsumer.subscribe({ topic }); + return; + } catch (const kafka::KafkaException& ex) { + // it sometimes happens that subscibe timeouts but another retry succeeds + if (ex.error().value() != RD_KAFKA_RESP_ERR__TIMED_OUT) { + throw; + } else { + ILOG(Warning, Ops) << "Failed to subscribe to kafka due to timeout " << retryNumber + 1 << "/" << numberOfRetries + 1 << " times, retrying..." << ENDM; + } + } + } + + throw std::runtime_error(std::string{ "Kafka Poller failed to subscribe after " }.append(std::to_string(numberOfRetries)).append(" retries")); +} + +auto KafkaPoller::poll(std::chrono::milliseconds timeout) -> KafkaRecords +{ + const auto records = mConsumer.poll(timeout); + auto filtered = records | std::ranges::views::filter([](const auto& record) { return !record.error(); }); + KafkaRecords result{}; + result.reserve(records.size()); + std::ranges::copy(filtered, std::back_inserter(result)); + return result; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/MonitorObject.cxx b/Framework/src/MonitorObject.cxx index 385068655f..bbc7b5c194 100644 --- a/Framework/src/MonitorObject.cxx +++ b/Framework/src/MonitorObject.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -13,35 +14,95 @@ /// \author Barthelemy von Haller /// -#include #include "QualityControl/MonitorObject.h" -#include "Common/Exceptions.h" +#include +#include +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/QcInfoLogger.h" -ClassImp(o2::quality_control::core::MonitorObject) +#include +#include using namespace std; namespace o2::quality_control::core { -MonitorObject::MonitorObject() : TObject(), mObject(nullptr), mTaskName(""), mIsOwner(true) {} +MonitorObject::MonitorObject() + : TObject{}, + mIsOwner{ true } +{ + mActivity.mProvenance = "qc"; + mActivity.mId = 0; + mActivity.mValidity = gInvalidValidityInterval; +} -MonitorObject::~MonitorObject() +MonitorObject::MonitorObject(TObject* object, const std::string& taskName, const std::string& taskClass, const std::string& detectorName, int runNumber, const std::string& periodName, const std::string& passName, const std::string& provenance) + : TObject{}, + mObject{ object }, + mTaskName{ taskName }, + mTaskClass{ taskClass }, + mDetectorName{ detectorName }, + mActivity{ runNumber, "NONE", periodName, passName, provenance, gInvalidValidityInterval }, + mIsOwner{ true } { - if (mIsOwner && mObject != nullptr) { - delete mObject; - } } -MonitorObject::MonitorObject(TObject* object, const std::string& taskName) - : TObject(), mObject(object), mTaskName(taskName), mIsOwner(true) +MonitorObject::MonitorObject(const MonitorObject& other) + : TObject{ other }, + mObject{}, + mTaskName{ other.mTaskName }, + mTaskClass{ other.mTaskClass }, + mDetectorName{ other.mDetectorName }, + mUserMetadata{ other.mUserMetadata }, + mDescription{ other.mDescription }, + mActivity{ other.mActivity }, + mCreateMovingWindow{ other.mCreateMovingWindow } +{ + cloneAndSetObject(other); +} + +MonitorObject& MonitorObject::operator=(const MonitorObject& other) +{ + TObject::operator=(other); + mTaskName = other.mTaskName; + mTaskClass = other.mTaskClass; + mDetectorName = other.mDetectorName; + mUserMetadata = other.mUserMetadata; + mDescription = other.mDescription; + mActivity = other.mActivity; + mCreateMovingWindow = other.mCreateMovingWindow; + cloneAndSetObject(other); + + return *this; +} + +void MonitorObject::Copy(TObject& object) const +{ + static_cast(object) = *this; +} + +MonitorObject::~MonitorObject() { + releaseObject(); } -void MonitorObject::Draw(Option_t* option) { mObject->Draw(option); } +void MonitorObject::Draw(Option_t* option) +{ + if (mObject) { + mObject->Draw(option); + } else { + ILOG(Error, Devel) << "MonitorObject::Draw() : You are trying to draw MonitorObject with no internal TObject" << ENDM; + } +} TObject* MonitorObject::DrawClone(Option_t* option) const { + if (!mObject) { + ILOG(Error, Devel) << "MonitorObject::DrawClone() : You are trying to draw MonitorObject with no internal TObject" << ENDM; + return nullptr; + } + auto* clone = new MonitorObject(); clone->setTaskName(this->getTaskName()); clone->setObject(mObject->DrawClone(option)); @@ -50,59 +111,202 @@ TObject* MonitorObject::DrawClone(Option_t* option) const const std::string MonitorObject::getName() const { - if (mObject == nullptr) { - cerr << "MonitorObject::getName() : No object in this MonitorObject, returning empty string"; + return string(GetName()); +} + +const char* MonitorObject::GetName() const +{ + if (!mObject) { + ILOG(Error, Ops) << "MonitorObject::getName() : No object in this MonitorObject, returning empty string" << ENDM; return ""; } return mObject->GetName(); } -void MonitorObject::setQualityForCheck(std::string checkName, Quality quality) +void MonitorObject::addMetadata(std::string key, std::string value) { - auto check = mChecks.find(checkName); - if (check != mChecks.end()) { - check->second.result = quality; - mChecks[checkName] = check->second; - } else { - throw AliceO2::Common::ObjectNotFoundError(); + mUserMetadata.insert({ key, value }); +} + +void MonitorObject::addMetadata(std::map pairs) +{ + // we do not use "merge" because it would ignore the items whose key already exist in mUserMetadata. + mUserMetadata.insert(pairs.begin(), pairs.end()); +} + +const std::map& MonitorObject::getMetadataMap() const +{ + return mUserMetadata; +} + +void MonitorObject::updateMetadata(std::string key, std::string value) +{ + if (mUserMetadata.count(key) > 0) { + mUserMetadata[key] = value; } } -CheckDefinition MonitorObject::getCheck(std::string checkName) const +void MonitorObject::addOrUpdateMetadata(std::string key, std::string value) { - if (mChecks.find(checkName) != mChecks.end()) { - return mChecks.at(checkName); + if (mUserMetadata.count(key) > 0) { + mUserMetadata[key] = value; } else { - throw AliceO2::Common::ObjectNotFoundError(); + mUserMetadata.insert({ key, value }); + } +} + +std::optional MonitorObject::getMetadata(const std::string& key) +{ + if (const auto foundIt = mUserMetadata.find(key); foundIt != std::end(mUserMetadata)) { + return foundIt->second; + } + return std::nullopt; +} + +bool MonitorObject::encapsulatedInheritsFrom(std::string_view className) const +{ + if (!mObject) { + return false; } + return mObject->IsA()->InheritsFrom(className.data()); } -void MonitorObject::addCheck(const std::string name, const std::string checkClassName, - const std::string checkLibraryName) +std::string MonitorObject::getPath() const { - CheckDefinition check; - check.name = name; - check.libraryName = checkLibraryName; - check.className = checkClassName; - mChecks[name] = check; + return RepoPathUtils::getMoPath(this); } -void MonitorObject::addOrReplaceCheck(std::string checkName, CheckDefinition check) { mChecks[checkName] = check; } +const string& MonitorObject::getDescription() const +{ + return mDescription; +} + +void MonitorObject::setDescription(const string& description) +{ + mDescription = description; +} + +const Activity& MonitorObject::getActivity() const +{ + return mActivity; +} -Quality MonitorObject::getQuality() const +Activity& MonitorObject::getActivity() { - Quality global = Quality::Null; + return mActivity; +} - for (const auto& checkPair : getChecks()) { - const CheckDefinition& checkDef = checkPair.second; - if (checkDef.result != Quality::Null) { - if (checkDef.result.isWorstThan(global) || global == Quality::Null) { - global = checkDef.result; - } - } +void MonitorObject::setActivity(const Activity& activity) +{ + MonitorObject::mActivity = activity; +} + +void MonitorObject::updateActivity(int runNumber, const std::string& periodName, const std::string& passName, const std::string& provenance) +{ + mActivity.mId = runNumber; + mActivity.mPeriodName = periodName; + mActivity.mPassName = passName; + mActivity.mProvenance = provenance; +} + +void MonitorObject::setValidity(ValidityInterval validityInterval) +{ + mActivity.mValidity = validityInterval; +} + +void MonitorObject::updateValidity(validity_time_t value) +{ + mActivity.mValidity.update(value); +} + +std::string MonitorObject::getFullName() const +{ + return getTaskName() + "/" + getName(); +} + +TObject* MonitorObject::getObject() const +{ + return mObject.get(); +} + +void MonitorObject::setObject(TObject* object) +{ + releaseObject(); + mObject.reset(object); +} + +bool MonitorObject::isIsOwner() const +{ + return mIsOwner; +} + +void MonitorObject::setIsOwner(bool isOwner) +{ + mIsOwner = isOwner; +} + +const std::string& MonitorObject::getTaskName() const +{ + return mTaskName; +} + +void MonitorObject::setTaskName(const std::string& taskName) +{ + mTaskName = taskName; +} + +const std::string& MonitorObject::getDetectorName() const +{ + return mDetectorName; +} + +void MonitorObject::setDetectorName(const std::string& detectorName) +{ + mDetectorName = detectorName; +} + +ValidityInterval MonitorObject::getValidity() const +{ + return mActivity.mValidity; +} + +const string& MonitorObject::getTaskClass() const +{ + return mTaskClass; +} + +void MonitorObject::setTaskClass(const string& taskClass) +{ + MonitorObject::mTaskClass = taskClass; +} + +void MonitorObject::setCreateMovingWindow(bool flag) +{ + mCreateMovingWindow = flag; +} + +bool MonitorObject::getCreateMovingWindow() const +{ + return mCreateMovingWindow; +} + +void MonitorObject::releaseObject() +{ + if (!mIsOwner) { + void(mObject.release()); } +} + +void MonitorObject::cloneAndSetObject(const MonitorObject& other) +{ + releaseObject(); - return global; + if (auto* otherObject = other.getObject(); otherObject != nullptr && other.isIsOwner()) { + mObject.reset(otherObject->Clone()); + } else { + mObject.reset(otherObject); + } + mIsOwner = other.isIsOwner(); } } // namespace o2::quality_control::core diff --git a/Framework/src/MonitorObjectCollection.cxx b/Framework/src/MonitorObjectCollection.cxx new file mode 100644 index 0000000000..1f21fcd86a --- /dev/null +++ b/Framework/src/MonitorObjectCollection.cxx @@ -0,0 +1,250 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MonitorObjectCollection.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/MonitorObjectCollection.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/ObjectMetadataHelpers.h" + +#include +#include +#include +#include + +using namespace o2::mergers; + +namespace o2::quality_control::core +{ + +void mergeCycles(MonitorObject* targetMO, MonitorObject* otherMO) +{ + const auto otherCycle = otherMO->getMetadata(repository::metadata_keys::cycleNumber); + const auto targetCycle = targetMO->getMetadata(repository::metadata_keys::cycleNumber); + if (otherCycle.has_value() && targetCycle.has_value()) { + const auto targetCycleParsed = repository::parseCycle(targetCycle.value()); + const auto otherCycleParsed = repository::parseCycle(otherCycle.value()); + + if (targetCycleParsed && otherCycleParsed) { + targetMO->addOrUpdateMetadata(repository::metadata_keys::cycleNumber, std::to_string(std::max(targetCycleParsed.value(), otherCycleParsed.value()))); + return; + } + + if (targetCycleParsed.value()) { + targetMO->addOrUpdateMetadata(repository::metadata_keys::cycleNumber, std::to_string(targetCycleParsed.value())); + return; + } + + if (otherCycleParsed.value()) { + otherMO->addOrUpdateMetadata(repository::metadata_keys::cycleNumber, std::to_string(otherCycleParsed.value())); + return; + } + } +} + +void MonitorObjectCollection::merge(mergers::MergeInterface* const other) +{ + auto otherCollection = dynamic_cast(other); // reinterpret_cast maybe? + if (otherCollection == nullptr) { + throw std::runtime_error("The other object is not a MonitorObjectCollection"); + } + + bool reportedMismatchingRunNumbers = false; + auto otherIterator = otherCollection->MakeIterator(); + while (auto otherObject = otherIterator->Next()) { + auto otherObjectName = otherObject->GetName(); + if (std::strlen(otherObjectName) == 0) { + ILOG(Warning, Devel) << "The other object does not have a name, probably it is empty. Skipping..." << ENDM; + } else if (auto targetObject = this->FindObject(otherObjectName)) { + // A corresponding object in the target collection was found, we try to merge. + auto otherMO = dynamic_cast(otherObject); + auto targetMO = dynamic_cast(targetObject); + if (!otherMO || !targetMO) { + throw std::runtime_error("The target object or the other object could not be casted to MonitorObject."); + } + + if (otherMO->getActivity().mId > targetMO->getActivity().mId) { + ILOG(Error, Ops) << "The run number of the input object '" << otherMO->GetName() << "' (" + << otherMO->getActivity().mId << ") " + << "is higher than the one of the target object '" + << targetMO->GetName() << "' (" << targetMO->getActivity().mId + << "). Replacing the merged object with input, " + << "but THIS SHOULD BE IMMEDIATELY ADDRESSED IN PRODUCTION. " + << "QC objects from other setups are reaching this one." + << ENDM; + otherMO->Copy(*targetMO); + continue; + } + + mergeCycles(targetMO, otherMO); + + if (!reportedMismatchingRunNumbers && otherMO->getActivity().mId < targetMO->getActivity().mId) { + ILOG(Error, Ops) << "The run number of the input object '" << otherMO->GetName() << "' (" + << otherMO->getActivity().mId << ") " + << "does not match the run number of the target object '" + << targetMO->GetName() << "' (" << targetMO->getActivity().mId + << "). Ignoring this object and trying to continue, but THIS SHOULD BE IMMEDIATELY ADDRESSED IN PRODUCTION. " + << "QC objects from other setups are reaching this one. Will not report more mismatches in this collection." + << ENDM; + reportedMismatchingRunNumbers = true; + continue; + } + + // That might be another collection or a concrete object to be merged, we walk on the collection recursively. + algorithm::merge(targetMO->getObject(), otherMO->getObject()); + if (otherMO->getValidity().isValid()) { + if (targetMO->getValidity().isInvalid()) { + targetMO->setValidity(otherMO->getValidity()); + } else { + targetMO->updateValidity(otherMO->getValidity().getMin()); + targetMO->updateValidity(otherMO->getValidity().getMax()); + } + } + } else { + // A corresponding object in the target collection could not be found. + // We prefer to clone instead of passing the pointer in order to simplify deleting the `other`. + this->Add(otherObject->Clone()); + } + } + delete otherIterator; +} + +void MonitorObjectCollection::postDeserialization() +{ + auto it = this->MakeIterator(); + while (auto obj = it->Next()) { + auto mo = dynamic_cast(obj); + if (mo == nullptr) { + ILOG(Warning) << "Could not cast an object of type '" << obj->ClassName() + << "' in MonitorObjectCollection to MonitorObject, skipping." << ENDM; + continue; + } + mo->setIsOwner(true); + if (auto objPtr = mo->getObject(); objPtr != nullptr) { + if (objPtr->InheritsFrom(MergeInterface::Class())) { + auto mergeable = dynamic_cast(mo->getObject()); + mergeable->postDeserialization(); + } else if (objPtr->InheritsFrom(TCollection::Class())) { + // if a class inherits from both MergeInterface and TCollection, we assume that MergeInterface does the correct job of setting the ownership. + auto collection = dynamic_cast(mo->getObject()); + collection->SetOwner(true); + } + } + } + this->SetOwner(true); + delete it; +} + +void MonitorObjectCollection::setDetector(const std::string& detector) +{ + mDetector = detector; +} + +const std::string& MonitorObjectCollection::getDetector() const +{ + return mDetector; +} + +void MonitorObjectCollection::setTaskName(const std::string& taskName) +{ + mTaskName = taskName; +} + +const std::string& MonitorObjectCollection::getTaskName() const +{ + return mTaskName; +} + +void MonitorObjectCollection::addOrUpdateMetadata(const std::string& key, const std::string& value) +{ + for (auto obj : *this) { + if (auto mo = dynamic_cast(obj)) { + mo->addOrUpdateMetadata(key, value); + } + } +} + +std::string formatDuration(uint64_t durationMs) +{ + auto remainder = durationMs; + uint64_t hours = remainder / (1000 * 60 * 60); + remainder %= (1000 * 60 * 60); + uint64_t minutes = remainder / (1000 * 60); + remainder %= (1000 * 60); + uint64_t seconds = remainder / 1000; + + std::stringstream result; + + if (hours > 0) { + result << hours << "h"; + } + if (minutes > 0 || hours > 0) { + result << minutes << "m"; + } + result << seconds << "s"; + if (durationMs < 1000) { + result << durationMs << "ms"; + } + + return result.str(); +} + +void decorateMovingWindowTitle(TObject* obj, uint64_t durationMs) +{ + if (!obj->InheritsFrom(TNamed::Class())) { + return; + } + auto objTNamed = reinterpret_cast(obj); + std::string newTitle = std::string(objTNamed->GetTitle()) + " (" + formatDuration(durationMs) + " window)"; + objTNamed->SetTitle(newTitle.c_str()); +} + +MergeInterface* MonitorObjectCollection::cloneMovingWindow() const +{ + auto mw = new MonitorObjectCollection(); + mw->SetOwner(true); + mw->setDetector(this->getDetector()); + mw->setTaskName(this->getTaskName()); + auto mwName = std::string(this->GetName()) + "/mw"; + mw->SetName(mwName.c_str()); + + auto it = this->MakeIterator(); + while (auto obj = it->Next()) { + auto mo = dynamic_cast(obj); + if (mo == nullptr) { + ILOG(Warning) << "Could not cast an object of type '" << obj->ClassName() + << "' in MonitorObjectCollection to MonitorObject, skipping." << ENDM; + continue; + } + if (!mo->getCreateMovingWindow()) { + continue; + } + if (mo->getValidity().isInvalid()) { + ILOG(Warning) << "MonitorObject '" << mo->getName() << "' validity is invalid, will not create a moving window" << ENDM; + continue; + } + auto clonedMO = dynamic_cast(mo->Clone()); + clonedMO->setTaskName(clonedMO->getTaskName() + "/mw"); + clonedMO->setIsOwner(true); + decorateMovingWindowTitle(clonedMO->getObject(), clonedMO->getValidity().delta()); + mw->Add(clonedMO); + } + delete it; + + return mw; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/MySqlDatabase.cxx b/Framework/src/MySqlDatabase.cxx deleted file mode 100644 index 7abbd142bb..0000000000 --- a/Framework/src/MySqlDatabase.cxx +++ /dev/null @@ -1,313 +0,0 @@ -/// -/// \file MySqlDatabase.cxx -/// \author Barthelemy von Haller -/// - -// std -#include -// ROOT -#include "TMessage.h" -#include "TMySQLResult.h" -#include "TMySQLRow.h" -#include "TMySQLStatement.h" -#include "TBufferJSON.h" -// O2 -#include "Common/Exceptions.h" -// QC -#include "QualityControl/MySqlDatabase.h" -#include "QualityControl/QcInfoLogger.h" - -using namespace AliceO2::Common; -using namespace std; -using namespace o2::quality_control::core; - -namespace o2::quality_control::repository -{ - -MySqlDatabase::MySqlDatabase() : mServer(nullptr), queueSize(0) { lastStorage.reset(); } - -MySqlDatabase::~MySqlDatabase() { disconnect(); } - -void MySqlDatabase::connect(std::string host, std::string database, std::string username, std::string password) -{ - if (mServer) { - if (mServer->IsConnected()) { - mServer->Close(); - } - delete mServer; - mServer = nullptr; - } - stringstream connectionString; - connectionString << "mysql://" << host << "/" << database; - // Important as the agent can be inactive for more than 8 hours and Mysql will drop idle connections older than 8 - // hours - connectionString << "?reconnect=1"; - mServer = new TMySQLServer(connectionString.str().c_str(), username.c_str(), password.c_str()); - if (!mServer || mServer->GetErrorCode()) { - string s = "Failed to connect to the database\n"; - if (mServer->GetErrorCode()) { - s += mServer->GetErrorMsg(); - } - BOOST_THROW_EXCEPTION(FatalException() << errinfo_details(s)); - } else { - QcInfoLogger::GetInstance() << "Connected to the database" << infologger::endm; - } -} - -void MySqlDatabase::connect(const std::unordered_map& config) -{ - this->connect(config.at("host"), - config.at("name"), - config.at("username"), - config.at("password")); -} - -void MySqlDatabase::prepareTaskDataContainer(std::string taskName) -{ - // one object per run - string query; - query += "CREATE TABLE IF NOT EXISTS `data_" + taskName + - "` (object_name CHAR(64), updatetime TIMESTAMP DEFAULT CURRENT_TIMESTAMP, data LONGBLOB, size INT, run INT, " - "fill INT, PRIMARY KEY(object_name, run)) ENGINE=MyISAM"; - if (!execute(query)) { - BOOST_THROW_EXCEPTION(FatalException() << errinfo_details("Failed to create data table")); - } else { - QcInfoLogger::GetInstance() << "Create data table for task " << taskName << infologger::endm; - } -} - -void MySqlDatabase::store(std::shared_ptr mo) -{ - // TODO we take ownership here to delete later -> clearly to be improved - // we execute grouped insertions. Here we just register that we should keep this mo in memory. - mObjectsQueue[mo->getTaskName()].push_back(mo); - queueSize++; - if (queueSize > 4 || lastStorage.getTime() > 10 /*sec*/) { // TODO use a configuration to set the max limits - storeQueue(); - } -} - -void MySqlDatabase::storeQueue() -{ - QcInfoLogger::GetInstance() << "Database queue will now be processed (" << queueSize << " objects)" - << infologger::endm; - - for (auto& kv : mObjectsQueue) { - storeForTask(kv.first); - } - mObjectsQueue.clear(); - queueSize = 0; - lastStorage.reset(); -} - -void MySqlDatabase::storeForTask(std::string taskName) -{ - std::vector> objects = mObjectsQueue[taskName]; - - if (objects.size() == 0) { - return; - } - - cout << "** Store for task " << taskName << endl; - cout << " # objects : " << objects.size() << endl; - - // build statement string - string query; - query += - "REPlACE INTO `data_" + taskName + "` (object_name, data, size, run, fill) values (?,?,octet_length(data),?,?)"; - - // Prepare statement - TMySQLStatement* statement; - // try to insert if it fails we check whether the table is there or not and create it if needed - statement = (TMySQLStatement*)mServer->Statement(query.c_str()); - if (mServer->IsError() && mServer->GetErrorCode() == 1146) { // table does not exist - // try to create the table - prepareTaskDataContainer(taskName); - statement = (TMySQLStatement*)mServer->Statement(query.c_str()); - } - if (mServer->IsError()) { - BOOST_THROW_EXCEPTION(DatabaseException() - << errinfo_details("Encountered an error when creating statement in MySqlDatabase") - << errinfo_db_message(mServer->GetErrorMsg()) << errinfo_db_errno(mServer->GetErrorCode())); - } - - // Assign data - TMessage message(kMESS_OBJECT); - for (auto mo : objects) { - message.Reset(); - message.WriteObjectAny(mo.get(), mo->IsA()); - statement->NextIteration(); - statement->SetString(0, mo->getName().c_str()); - statement->SetBinary(1, message.Buffer(), message.Length(), message.Length()); - statement->SetInt(2, 0); - statement->SetInt(3, 0); - } - statement->Process(); - delete statement; - - // for (auto mo : objects) { - // delete mo; - // } - objects.clear(); -} - -o2::quality_control::core::MonitorObject* MySqlDatabase::retrieve(std::string taskName, std::string objectName) -{ - string query; - TMySQLStatement* statement = nullptr; - - query += "SELECT object_name, data, updatetime, run, fill FROM data_" + taskName + " WHERE object_name = ?"; - statement = (TMySQLStatement*)mServer->Statement(query.c_str()); - if (mServer->IsError()) { - if (statement) { - delete statement; - } - BOOST_THROW_EXCEPTION(DatabaseException() - << errinfo_details("Encountered an error when creating statement in MySqlDatabase") - << errinfo_db_message(mServer->GetErrorMsg()) << errinfo_db_errno(mServer->GetErrorCode())); - } - statement->NextIteration(); - statement->SetString(0, objectName.c_str()); - - if (!(statement->Process() && statement->StoreResult())) { - delete statement; - BOOST_THROW_EXCEPTION(DatabaseException() - << errinfo_details( - "Encountered an error when processing and storing results in MySqlDatabase") - << errinfo_db_message(mServer->GetErrorMsg()) << errinfo_db_errno(mServer->GetErrorCode())); - } - - o2::quality_control::core::MonitorObject* mo = nullptr; - if (statement->NextResultRow()) { // Consider only the first result - string name = statement->GetString(0); - void* blob = nullptr; - Long_t blobSize; - statement->GetBinary(1, blob, blobSize); // retrieve the data - TDatime updatetime(statement->GetYear(2), statement->GetMonth(2), statement->GetDay(2), statement->GetHour(2), - statement->GetMinute(2), statement->GetSecond(2)); - int run = statement->IsNull(3) ? -1 : statement->GetInt(3); - int fill = statement->IsNull(4) ? -1 : statement->GetInt(4); - - TMessage mess(kMESS_OBJECT); - mess.SetBuffer(blob, blobSize, kFALSE); - mess.SetReadMode(); - mess.Reset(); - try { - mo = (o2::quality_control::core::MonitorObject*)(mess.ReadObjectAny(mess.GetClass())); - } catch (...) { - QcInfoLogger::GetInstance() << "Node: unable to parse TObject from MySQL" << infologger::endm; - throw; - } - } - delete statement; - - return mo; -} - -std::string MySqlDatabase::retrieveJson(std::string taskName, std::string objectName) -{ - std::unique_ptr monitor(retrieve(taskName, objectName)); - if (monitor == nullptr) { - return std::string(); - } - std::unique_ptr obj(monitor->getObject()); - monitor->setIsOwner(false); - TString json = TBufferJSON::ConvertToJSON(obj.get()); - return json.Data(); -} - -void MySqlDatabase::disconnect() -{ - storeQueue(); - - if (mServer) { - if (mServer->IsConnected()) { - mServer->Close(); - } - delete mServer; - mServer = nullptr; - } -} - -TMySQLResult* MySqlDatabase::query(string sql) -{ - if (mServer) { - return ((TMySQLResult*)mServer->Query(sql.c_str())); - } else { - return (nullptr); - } -} - -bool MySqlDatabase::execute(string sql) -{ - if (mServer) { - return mServer->Exec(sql.c_str()); - } - - return false; -} - -void MySqlDatabase::addIndex(string table, string column) -{ - std::ostringstream stringStream; - stringStream << "CREATE INDEX " << table << "_i_" << column << " on " << table << " (" << column << ")"; - TMySQLResult* res = query(stringStream.str().c_str()); - if (res) { - delete (res); - } else { - cerr << "Couldn't create the index on table " << table << " on column " << column << endl; - } -} - -std::vector MySqlDatabase::getPublishedObjectNames(std::string taskName) -{ - std::vector result; - - string queryString = "select distinct object_name from "; - queryString += "data_" + taskName; - - TMySQLResult* mysqlResult = query(queryString); - if (mysqlResult) { // only if we got a result - TMySQLRow* row; - while ((row = (TMySQLRow*)mysqlResult->Next())) { - result.push_back(row->GetField(0)); - delete row; - } - delete mysqlResult; - } - - return result; -} - -std::vector MySqlDatabase::getListOfTasksWithPublications() -{ - std::vector result; - - string queryString = "select table_name from information_schema.tables where table_schema='quality_control'"; - - TMySQLResult* mysqlResult = query(queryString); - if (mysqlResult) { // only if we got a result - TMySQLRow* row; - while ((row = (TMySQLRow*)mysqlResult->Next())) { - result.push_back(row->GetField(0)); - delete row; - } - delete mysqlResult; - } - - return result; -} - -void MySqlDatabase::truncate(std::string taskName, std::string objectName) -{ - string queryString = string("delete ignore from `data_") + taskName + "` where object_name='" + objectName + "'"; - - if (!execute(queryString)) { - string s = string("Failed to delete object ") + objectName + " from task " + taskName; - BOOST_THROW_EXCEPTION(FatalException() << errinfo_details(s)); - } else { - QcInfoLogger::GetInstance() << "Delete object " << objectName << " from task " << taskName << infologger::endm; - } -} - -} // namespace o2::quality_control::repository diff --git a/Framework/src/ObjectMetadataHelpers.cxx b/Framework/src/ObjectMetadataHelpers.cxx new file mode 100644 index 0000000000..76adf11de3 --- /dev/null +++ b/Framework/src/ObjectMetadataHelpers.cxx @@ -0,0 +1,35 @@ +// Copyright 2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectMetadataHelpers.cxx +/// \author Michal Tichak +/// + +#include +#include "QualityControl/ObjectMetadataHelpers.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/QcInfoLogger.h" + +namespace o2::quality_control::repository +{ +std::optional parseCycle(const std::string& cycleStr) +{ + unsigned long cycleVal{}; + if (auto parse_res = std::from_chars(cycleStr.c_str(), cycleStr.c_str() + cycleStr.size(), cycleVal); + parse_res.ec != std::errc{}) { + ILOG(Warning, Support) << "failed to decypher " << repository::metadata_keys::cycleNumber << " metadata with value " << cycleStr + << ", with std::errc " << std::make_error_code(parse_res.ec).message() << ENDM; + return std::nullopt; + } + return cycleVal; +} +} // namespace o2::quality_control::repository diff --git a/Framework/src/ObjectsManager.cxx b/Framework/src/ObjectsManager.cxx index b47fcb09ff..5cdbc095fc 100644 --- a/Framework/src/ObjectsManager.cxx +++ b/Framework/src/ObjectsManager.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -14,8 +15,15 @@ /// #include "QualityControl/ObjectsManager.h" -#include "Common/Exceptions.h" + #include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObjectCollection.h" +#include +#include + +#include +#include +#include using namespace o2::quality_control::core; using namespace AliceO2::Common; @@ -24,62 +32,228 @@ using namespace std; namespace o2::quality_control::core { -ObjectsManager::ObjectsManager(TaskConfig& taskConfig) : mTaskName(taskConfig.taskName) +const std::string ObjectsManager::gDrawOptionsKey = "drawOptions"; +const std::string ObjectsManager::gDisplayHintsKey = "displayHints"; + +ObjectsManager::ObjectsManager(std::string taskName, std::string taskClass, std::string detectorName, int parallelTaskID) + : mTaskName(std::move(taskName)), mTaskClass(std::move(taskClass)), mDetectorName(std::move(detectorName)) { - mMonitorObjects.SetOwner(true); + mMonitorObjects = std::make_unique(); + mMonitorObjects->SetOwner(true); + mMonitorObjects->SetName(mTaskName.c_str()); + mMonitorObjects->setDetector(mDetectorName); + mMonitorObjects->setTaskName(mTaskName); } ObjectsManager::~ObjectsManager() { + ILOG(Debug, Devel) << "ObjectsManager destructor" << ENDM; } -void ObjectsManager::startPublishing(TObject* object, std::string objectName) +void ObjectsManager::startPublishingImpl(TObject* object, PublicationPolicy publicationPolicy, bool ignoreMergeableWarning) { - auto* newObject = new MonitorObject(object, mTaskName); + if (!object) { + ILOG(Warning, Support) << "A nullptr provided to ObjectManager::startPublishing" << ENDM; + return; + } + + if (mMonitorObjects->FindObject(object->GetName()) != nullptr) { + ILOG(Warning, Support) << "Object is already being published (" << object->GetName() << "), will remove it and add the new one" << ENDM; + stopPublishing(object->GetName()); + } + + if (!ignoreMergeableWarning && !mergers::isMergeable(object)) { + ILOG(Warning, Support) << "Object '" + std::string(object->GetName()) + "' with type '" + std::string(object->ClassName()) + "' is not one of the mergeable types, it will not be correctly merged in distributed setups, such as P2 and Grid" << ENDM; + } + + auto* newObject = new MonitorObject(object, mTaskName, mTaskClass, mDetectorName); newObject->setIsOwner(false); - mMonitorObjects.Add(newObject); + newObject->setActivity(mActivity); + newObject->setCreateMovingWindow(std::find(mMovingWindowsList.begin(), mMovingWindowsList.end(), object->GetName()) != mMovingWindowsList.end()); + mMonitorObjects->Add(newObject); + mPublicationPoliciesForMOs[newObject] = publicationPolicy; +} + +void ObjectsManager::stopPublishing(TObject* object) +{ + if (!object) { + ILOG(Warning, Support) << "A nullptr provided to ObjectManager::stopPublishing" << ENDM; + return; + } + // We look for the MonitorObject which observes the provided object by comparing its address + // This way, we avoid invoking any methods of the provided object, thus we can stop publishing it even after it is deleted + MonitorObject* moToRemove = nullptr; + for (auto moAsTObject : *mMonitorObjects) { + auto mo = dynamic_cast(moAsTObject); + if (mo && mo->getObject() == object) { + moToRemove = mo; + continue; + } + } + if (moToRemove) { + mPublicationPoliciesForMOs.erase(moToRemove); + mMonitorObjects->Remove(moToRemove); + mMonitorObjects->Compress(); + } +} + +void ObjectsManager::stopPublishing(const string& objectName) +{ + auto* mo = dynamic_cast(getMonitorObject(objectName)); + mPublicationPoliciesForMOs.erase(mo); + mMonitorObjects->Remove(mo); + mMonitorObjects->Compress(); } -Quality ObjectsManager::getQuality(std::string objectName) +void ObjectsManager::stopPublishing(PublicationPolicy policy) { - if (mMonitorObjects.FindObject(objectName.c_str())) { + // we do not use directly the view, because deletions in the policy map would invalidate the iterators in the view + // c++23 will allow us to do std::ranges::to instead. + std::vector allObjectsMatchingPolicy; + for (auto* mo : mPublicationPoliciesForMOs | std::views::filter([policy](const auto& kv) { return kv.second == policy; }) | std::views::keys) { + allObjectsMatchingPolicy.push_back(mo); + } + + for (const auto mo : allObjectsMatchingPolicy) { + stopPublishing(mo->getObject()); + } +} + +void ObjectsManager::stopPublishingAll() +{ + mMonitorObjects->Clear(); + mPublicationPoliciesForMOs.clear(); +} + +bool ObjectsManager::isBeingPublished(const string& name) +{ + return (mMonitorObjects->FindObject(name.c_str()) != nullptr); +} + +MonitorObject* ObjectsManager::getMonitorObject(const std::string& objectName) +{ + TObject* object = mMonitorObjects->FindObject(objectName.c_str()); + if (object == nullptr) { + ILOG(Error, Support) << "ObjectsManager: Unable to find object \"" << objectName << "\"" << ENDM; BOOST_THROW_EXCEPTION(ObjectNotFoundError() << errinfo_object_name(objectName)); } + return dynamic_cast(object); +} + +MonitorObject* ObjectsManager::getMonitorObject(size_t index) +{ + TObject* object = mMonitorObjects->At(index); + if (object == nullptr) { + ILOG(Error, Support) << "ObjectsManager: Unable to find object at index \"" << index << "\"" << ENDM; + string fakeName = "at index " + to_string(index); + BOOST_THROW_EXCEPTION(ObjectNotFoundError() << errinfo_object_name(fakeName)); + } + return dynamic_cast(object); +} + +MonitorObjectCollection* ObjectsManager::getNonOwningArray() const +{ + return new MonitorObjectCollection(*mMonitorObjects); +} + +void ObjectsManager::addMetadata(const std::string& objectName, const std::string& key, const std::string& value) +{ MonitorObject* mo = getMonitorObject(objectName); - return mo->getQuality(); + mo->addMetadata(key, value); + ILOG(Debug, Devel) << "Added metadata on " << objectName << " : " << key << " -> " << value << ENDM; } -// fixme: keep user informed, that giving the same names for their objects is a bad idea -void ObjectsManager::addCheck(const std::string& objectName, const std::string& checkName, - const std::string& checkClassName, const std::string& checkLibraryName) + +void ObjectsManager::addOrUpdateMetadata(const std::string& objectName, const std::string& key, const std::string& value) { MonitorObject* mo = getMonitorObject(objectName); - mo->addCheck(checkName, checkClassName, checkLibraryName); + mo->addOrUpdateMetadata(key, value); + ILOG(Debug, Devel) << "Added/Modified metadata on " << objectName << " : " << key << " -> " << value << ENDM; +} - QcInfoLogger::GetInstance() << "Added check : " << objectName << " , " << checkName << " , " << checkClassName - << " , " << checkLibraryName << infologger::endm; +size_t ObjectsManager::getNumberPublishedObjects() +{ + return mMonitorObjects->GetLast() + 1; // GetLast returns the index } -MonitorObject* ObjectsManager::getMonitorObject(std::string objectName) +void ObjectsManager::setDefaultDrawOptions(const std::string& objectName, const std::string& options) { - TObject* mo = mMonitorObjects.FindObject(objectName.c_str()); + MonitorObject* mo = getMonitorObject(objectName); + mo->addOrUpdateMetadata(gDrawOptionsKey, options); +} - if (mo) { - return dynamic_cast(mo); - } else { - BOOST_THROW_EXCEPTION(ObjectNotFoundError() << errinfo_object_name(objectName)); +void ObjectsManager::setDefaultDrawOptions(TObject* obj, const std::string& options) +{ + if (!obj) { + ILOG(Warning, Support) << "A nullptr provided to ObjectManager::setDefaultDrawOptions" << ENDM; + return; } + MonitorObject* mo = getMonitorObject(obj->GetName()); + mo->addOrUpdateMetadata(gDrawOptionsKey, options); } -TObject* ObjectsManager::getObject(std::string objectName) +void ObjectsManager::setDisplayHint(const std::string& objectName, const std::string& hints) { MonitorObject* mo = getMonitorObject(objectName); - return mo->getObject(); + mo->addOrUpdateMetadata(gDisplayHintsKey, hints); +} + +void ObjectsManager::setDisplayHint(TObject* obj, const std::string& hints) +{ + if (!obj) { + ILOG(Warning, Support) << "A nullptr provided to ObjectManager::setDisplayHint" << ENDM; + return; + } + MonitorObject* mo = getMonitorObject(obj->GetName()); + mo->addOrUpdateMetadata(gDisplayHintsKey, hints); +} + +void ObjectsManager::setValidity(ValidityInterval validityInterval) +{ + for (auto* tobj : *mMonitorObjects) { + auto* mo = dynamic_cast(tobj); + if (mo) { + mo->setValidity(validityInterval); + } else { + ILOG(Error, Devel) << "ObjectsManager::setValidity : dynamic_cast returned nullptr." << ENDM; + } + } +} + +void ObjectsManager::updateValidity(validity_time_t validityTime) +{ + for (auto* tobj : *mMonitorObjects) { + auto* mo = dynamic_cast(tobj); + if (mo) { + mo->updateValidity(validityTime); + } else { + ILOG(Error, Devel) << "ObjectsManager::updateValidity : dynamic_cast returned nullptr." << ENDM; + } + } +} + +const Activity& ObjectsManager::getActivity() const +{ + return mActivity; +} + +void ObjectsManager::setActivity(const Activity& activity) +{ + mActivity = activity; + // update the activity of all the objects + for (auto tobj : *mMonitorObjects) { + auto* mo = dynamic_cast(tobj); + mo->setActivity(activity); + } +} + +void ObjectsManager::setMovingWindowsList(const vector& movingWindows) +{ + mMovingWindowsList = movingWindows; } -void ObjectsManager::addCheck(const TObject* object, const std::string& checkName, const std::string& checkClassName, - const std::string& checkLibraryName) +const std::vector& ObjectsManager::getMovingWindowsList() const { - addCheck(object->GetName(), checkName, checkClassName, checkLibraryName); + return mMovingWindowsList; } } // namespace o2::quality_control::core diff --git a/Framework/src/PostProcessingConfig.cxx b/Framework/src/PostProcessingConfig.cxx new file mode 100644 index 0000000000..f1b8abdaf4 --- /dev/null +++ b/Framework/src/PostProcessingConfig.cxx @@ -0,0 +1,76 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingConfig.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/PostProcessingConfig.h" + +#include + +namespace o2::quality_control::postprocessing +{ + +PostProcessingConfig::PostProcessingConfig(const std::string& id, const boost::property_tree::ptree& config) // + : id(id), + taskName(config.get("qc.postprocessing." + id + ".taskName", id)), + activity(config.get("qc.config.Activity.number", 0), + config.get("qc.config.Activity.type", "NONE"), + config.get("qc.config.Activity.periodName", ""), + config.get("qc.config.Activity.passName", ""), + config.get("qc.config.Activity.provenance", "qc"), + { config.get("qc.config.Activity.start", 0), + config.get("qc.config.Activity.end", -1) }), + matchAnyRunNumber(config.get("qc.config.postprocessing.matchAnyRunNumber", false)) +{ + auto ppTree = config.get_child("qc.postprocessing." + id); + + moduleName = ppTree.get("moduleName"); + className = ppTree.get("className"); + detectorName = ppTree.get("detectorName", "MISC"); + ccdbUrl = config.get("qc.config.conditionDB.url", ""); + consulUrl = config.get("qc.config.consul.url", ""); + kafkaBrokersUrl = config.get("qc.config.kafka.url", ""); + kafkaTopicAliECSRun = config.get("qc.config.kafka.topicAliecsRun", "aliecs.run"); + + // if available, use the source repo as defined in the postprocessing task, otherwise the general QCDB + auto sourceRepo = ppTree.get_child_optional("sourceRepo"); + auto databasePath = sourceRepo ? "qc.postprocessing." + id + ".sourceRepo" : "qc.config.database"; + auto qcdbUrl = config.get(databasePath + ".implementation") == "CCDB" ? config.get(databasePath + ".host") : ""; + // build the config of the qcdb + std::unordered_map dbConfig{ + { "implementation", config.get("qc.config.database.implementation") }, + { "host", qcdbUrl } + }; + repository = dbConfig; + + for (const auto& initTrigger : ppTree.get_child("initTrigger")) { + initTriggers.push_back(initTrigger.second.get_value()); + } + for (const auto& updateTrigger : ppTree.get_child("updateTrigger")) { + updateTriggers.push_back(updateTrigger.second.get_value()); + } + for (const auto& stopTrigger : ppTree.get_child("stopTrigger")) { + stopTriggers.push_back(stopTrigger.second.get_value()); + } + if (ppTree.count("extendedTaskParameters")) { + customParameters.populateCustomParameters(ppTree.get_child("extendedTaskParameters")); + } else if (ppTree.count("taskParameters") > 0) { + for (const auto& [key, value] : ppTree.get_child("taskParameters")) { + customParameters.set(key, value.get_value()); + } + } + validityFromLastTriggerOnly = ppTree.get("validityFromLastTriggerOnly", false); +} + +} // namespace o2::quality_control::postprocessing diff --git a/Framework/src/PostProcessingDevice.cxx b/Framework/src/PostProcessingDevice.cxx new file mode 100644 index 0000000000..b4b17e905a --- /dev/null +++ b/Framework/src/PostProcessingDevice.cxx @@ -0,0 +1,124 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingDevice.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/PostProcessingDevice.h" + +#include "QualityControl/PostProcessingRunner.h" +#include "QualityControl/PostProcessingConfig.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/PostProcessingRunnerConfig.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DataHeaderHelpers.h" +#include "QualityControl/UserInputOutput.h" +#include "QualityControl/runnerUtils.h" + +#include +#include +#include +#include +#include + +using namespace AliceO2::Common; +using namespace o2::framework; + +namespace o2::quality_control::postprocessing +{ + +PostProcessingDevice::PostProcessingDevice(const PostProcessingRunnerConfig& runnerConfig) + : mRunner(std::make_unique(runnerConfig.id)), + mDeviceName(createPostProcessingDeviceName(runnerConfig.taskName, runnerConfig.detectorName)), + mRunnerConfig(runnerConfig) +{ +} + +void PostProcessingDevice::init(framework::InitContext& ctx) +{ + core::initInfologger(ctx, mRunnerConfig.infologgerDiscardParameters, ("post/" + mRunnerConfig.taskName).substr(0, core::QcInfoLogger::maxFacilityLength), mRunnerConfig.detectorName); + + if (ctx.options().isSet("configKeyValues")) { + mRunnerConfig.configKeyValues = ctx.options().get("configKeyValues"); + } + + // todo: read the updated config from ctx, one available + mRunner->init(mRunnerConfig, PostProcessingConfig{ mRunner->getID(), mRunnerConfig.configTree }); + + // registering state machine callbacks + ctx.services().get().set([this, services = ctx.services()]() mutable { start(services); }); + ctx.services().get().set([this]() { reset(); }); + ctx.services().get().set([this, services = ctx.services()]() mutable { stop(services); }); +} + +void PostProcessingDevice::run(framework::ProcessingContext& ctx) +{ + // we set the publication callback each time, because we cannot be sure that + // the reference to DataAllocator does not change + mRunner->setPublicationCallback(publishToDPL(ctx.outputs(), mRunnerConfig.taskName)); + + // When run returns false, it has done its processing. + if (!mRunner->run()) { + ctx.services().get().endOfStream(); + ctx.services().get().readyToQuit(QuitRequest::Me); + } +} + +std::string PostProcessingDevice::createPostProcessingDeviceName(const std::string& taskName, const std::string& detectorName) +{ + return "qc-pp-" + detectorName + "-" + taskName; +} + +void PostProcessingDevice::start(ServiceRegistryRef services) +{ + mRunner->start(services); +} + +void PostProcessingDevice::stop(ServiceRegistryRef services) +{ + mRunner->stop(services); +} + +void PostProcessingDevice::reset() +{ + mRunner->reset(); +} + +const std::string& PostProcessingDevice::getDeviceName() +{ + return mDeviceName; +} + +framework::Inputs PostProcessingDevice::getInputsSpecs() +{ + o2::header::DataDescription timerDescription; + timerDescription.runtimeInit(std::string("T-" + mRunner->getID()).substr(0, o2::header::DataDescription::size).c_str()); + + return { { "timer-pp-" + mRunner->getID(), + createDataOrigin(core::DataSourceType::PostProcessingTask, mRunnerConfig.detectorName), + timerDescription, + 0, + Lifetime::Timer } }; +} + +framework::Outputs PostProcessingDevice::getOutputSpecs() const +{ + return { createUserOutputSpec(core::DataSourceType::PostProcessingTask, mRunnerConfig.detectorName, mRunnerConfig.taskName) }; +} + +framework::Options PostProcessingDevice::getOptions() +{ + return { { "period-timer-pp-" + mRunner->getID(), framework::VariantType::Int, static_cast(mRunnerConfig.periodSeconds * 1000000), { "PP task timer period" } } }; +} + +} // namespace o2::quality_control::postprocessing diff --git a/Framework/src/PostProcessingFactory.cxx b/Framework/src/PostProcessingFactory.cxx new file mode 100644 index 0000000000..7b7e2e9348 --- /dev/null +++ b/Framework/src/PostProcessingFactory.cxx @@ -0,0 +1,36 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingFactory.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/PostProcessingFactory.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/PostProcessingConfig.h" +#include "QualityControl/RootClassFactory.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control::postprocessing +{ + +// todo: consider having a common helper for each class which loads tasks like below (QC tasks, checks, pp) +PostProcessingInterface* PostProcessingFactory::create(const PostProcessingConfig& config) +{ + auto* result = root_class_factory::create(config.moduleName, config.className); + result->setCustomParameters(config.customParameters); + result->setDatabase(config.repository); + return result; +} + +} // namespace o2::quality_control::postprocessing \ No newline at end of file diff --git a/Framework/src/PostProcessingInterface.cxx b/Framework/src/PostProcessingInterface.cxx new file mode 100644 index 0000000000..5072980ffc --- /dev/null +++ b/Framework/src/PostProcessingInterface.cxx @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingInterface.cxx +/// \author Piotr Konopka +/// + +#include + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/ObjectsManager.h" + +namespace o2::quality_control::postprocessing +{ + +const std::string& PostProcessingInterface::getID() const { return mID; } + +void PostProcessingInterface::setID(const std::string& id) +{ + mID = id; +} + +void PostProcessingInterface::configure(const boost::property_tree::ptree& config) +{ +} + +void PostProcessingInterface::configure() +{ +} + +void PostProcessingInterface::setObjectsManager(std::shared_ptr objectsManager) +{ + mObjectsManager = std::move(objectsManager); +} + +std::shared_ptr PostProcessingInterface::getObjectsManager() +{ + return mObjectsManager; +} + +} // namespace o2::quality_control::postprocessing diff --git a/Framework/src/PostProcessingRunner.cxx b/Framework/src/PostProcessingRunner.cxx new file mode 100644 index 0000000000..294ef12075 --- /dev/null +++ b/Framework/src/PostProcessingRunner.cxx @@ -0,0 +1,376 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "QualityControl/PostProcessingRunner.h" + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/PostProcessingFactory.h" +#include "QualityControl/PostProcessingConfig.h" +#include "QualityControl/PostProcessingTaskSpec.h" +#include "QualityControl/TriggerHelpers.h" +#include "QualityControl/DatabaseFactory.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/CommonSpec.h" +#include "QualityControl/InfrastructureSpecReader.h" +#include "QualityControl/Activity.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/runnerUtils.h" +#include "QualityControl/ConfigParamGlo.h" +#include "QualityControl/MonitorObjectCollection.h" +#include "QualityControl/Bookkeeping.h" +#include "QualityControl/ActivityHelpers.h" + +#include +#include +#include +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; + +namespace o2::quality_control::postprocessing +{ + +constexpr long objectValidity = 1000l * 60 * 60 * 24 * 365 * 10; + +PostProcessingRunner::PostProcessingRunner(std::string id) // + : mID(std::move(id)) +{ +} + +void PostProcessingRunner::setPublicationCallback(MOCPublicationCallback callback) +{ + mPublicationCallback = std::move(callback); +} + +void PostProcessingRunner::init(const boost::property_tree::ptree& config, core::WorkflowType workflowType) +{ + auto specs = InfrastructureSpecReader::readInfrastructureSpec(config, workflowType); + auto ppTaskSpec = std::find_if(specs.postProcessingTasks.begin(), + specs.postProcessingTasks.end(), + [id = mID](const auto& spec) { + return spec.id == id; + }); + if (ppTaskSpec == specs.postProcessingTasks.end()) { + throw std::runtime_error("Could not find the configuration of the post-processing task '" + mID + "'"); + } + + init(PostProcessingRunner::extractConfig(specs.common, *ppTaskSpec), PostProcessingConfig{ mID, config }); +} + +std::unique_ptr PostProcessingRunner::configureDatabase(std::unordered_map& dbConfig, const std::string& name) +{ + auto database = DatabaseFactory::create(dbConfig.at("implementation")); + database->connect(dbConfig); + ILOG(Info, Devel) << name << " database that is going to be used > Implementation : " << dbConfig.at("implementation") << " / " + << " Host : " << dbConfig.at("host") << ENDM; + return database; +} + +void PostProcessingRunner::init(const PostProcessingRunnerConfig& runnerConfig, const PostProcessingConfig& taskConfig) +{ + QcInfoLogger::init(("post/" + taskConfig.taskName).substr(0, QcInfoLogger::maxFacilityLength), runnerConfig.infologgerDiscardParameters); + ILOG(Info, Support) << "Initializing PostProcessingRunner" << ENDM; + + mRunnerConfig = runnerConfig; + mTaskConfig = taskConfig; + mActivity = taskConfig.activity; + mActivity.mValidity = gInvalidValidityInterval; + + root_class_factory::loadLibrary(mTaskConfig.moduleName); + if (!ConfigParamGlo::keyValues.empty()) { + conf::ConfigurableParam::updateFromString(ConfigParamGlo::keyValues); + } + + // configuration of the database + mSourceDatabase = configureDatabase(mRunnerConfig.sourceDatabase, "Source"); + mDestinationDatabase = configureDatabase(mRunnerConfig.destinationDatabase, "Destination"); + + mObjectManager = std::make_shared(mTaskConfig.taskName, mTaskConfig.className, mTaskConfig.detectorName); + mObjectManager->setActivity(mActivity); + mServices.registerService(mSourceDatabase.get()); + if (mPublicationCallback == nullptr) { + mPublicationCallback = publishToRepository(*mDestinationDatabase); + } + Bookkeeping::getInstance().init(runnerConfig.bookkeepingUrl); + + // setup user's task + ILOG(Debug, Devel) << "Creating a user task '" << mTaskConfig.taskName << "'" << ENDM; + PostProcessingFactory f; + mTask.reset(f.create(mTaskConfig)); + if (mTask) { + ILOG(Debug, Devel) << "The user task '" << mTaskConfig.taskName << "' has been successfully created" << ENDM; + + mTaskState = TaskState::Created; + mTask->setObjectsManager(mObjectManager); + mTask->setID(mTaskConfig.id); + mTask->setName(mTaskConfig.taskName); + mTask->setCustomParameters(mTaskConfig.customParameters); + mTask->setCcdbUrl(mTaskConfig.ccdbUrl); + mTask->configure(mRunnerConfig.configTree); + } else { + throw std::runtime_error("Failed to create the task '" + mTaskConfig.taskName + "' (det " + mTaskConfig.detectorName + ")"); + } +} + +bool PostProcessingRunner::run() +{ + ILOG(Debug, Devel) << "Checking triggers of the task '" << mTask->getName() << "' (det " << mTaskConfig.detectorName << ")" << ENDM; + + if (mTaskState == TaskState::Created) { + if (Trigger trigger = trigger_helpers::tryTrigger(mInitTriggers)) { + doInitialize(trigger); + return true; + } + } + if (mTaskState == TaskState::Running) { + if (Trigger trigger = trigger_helpers::tryTrigger(mUpdateTriggers)) { + doUpdate(trigger); + return true; + } + if (mUpdateTriggers.empty()) { + doFinalize({ TriggerType::UserOrControl, true, mActivity }); + return false; + } else if (Trigger trigger = trigger_helpers::tryTrigger(mStopTriggers)) { + doFinalize(trigger); + return false; + } + } + if (mTaskState == TaskState::Finished) { + ILOG(Debug, Devel) << "The user task finished." << ENDM; + return false; + } + if (mTaskState == TaskState::INVALID) { + // That in principle shouldn't happen if we reach run() + throw std::runtime_error("The user task has INVALID state"); + } + + return true; +} + +void PostProcessingRunner::runOverTimestamps(const std::vector& timestamps) +{ + if (timestamps.size() < 2) { + throw std::runtime_error( + "At least two timestamps should be specified, " + std::to_string(timestamps.size()) + + " given. One is for the initialization, zero or more for update, one for finalization"); + } + + ILOG(Info, Support) << "Running the task '" << mTask->getName() << "' (det " << mRunnerConfig.detectorName << ") over " << timestamps.size() << " timestamps." << ENDM; + + doInitialize({ TriggerType::UserOrControl, false, mTaskConfig.activity, timestamps.front() }); + for (size_t i = 1; i < timestamps.size() - 1; i++) { + doUpdate({ TriggerType::UserOrControl, i == timestamps.size() - 2, mTaskConfig.activity, timestamps[i] }); + } + doFinalize({ TriggerType::UserOrControl, false, mTaskConfig.activity, timestamps.back() }); +} + +void PostProcessingRunner::start(framework::ServiceRegistryRef dplServices) +{ + Activity activityFromDriver = mTaskConfig.activity; + activityFromDriver.mValidity.setMin(getCurrentTimestamp()); + if (dplServices.active()) { + activityFromDriver = computeActivity(dplServices, activityFromDriver); + QcInfoLogger::setPartition(mTaskConfig.activity.mPartitionName); + } + mActivity = activityFromDriver; + mActivity.mValidity = gInvalidValidityInterval; // object validity shall be based on input objects, not run duration + mObjectManager->setActivity(mActivity); + QcInfoLogger::setRun(mActivity.mId); + + // register ourselves to the BK + if (!gSystem->Getenv("O2_QC_DONT_REGISTER_IN_BK")) { // Set this variable to disable the registration + ILOG(Debug, Devel) << "Registering pp task to BookKeeping" << ENDM; + Bookkeeping::getInstance().registerProcess(mActivity.mId, mRunnerConfig.taskName, mRunnerConfig.detectorName, bkp::DplProcessType::QC_POSTPROCESSING, ""); + } + + if (mTaskState == TaskState::Created || mTaskState == TaskState::Finished) { + auto taskConfigWithCorrectActivity = mTaskConfig; + taskConfigWithCorrectActivity.activity = activityFromDriver; + taskConfigWithCorrectActivity.activity.mValidity = gFullValidityInterval; + mInitTriggers = trigger_helpers::createTriggers(mTaskConfig.initTriggers, taskConfigWithCorrectActivity); + if (trigger_helpers::hasUserOrControlTrigger(mTaskConfig.initTriggers)) { + doInitialize({ TriggerType::UserOrControl, false, activityFromDriver, activityFromDriver.mValidity.getMin() }); + } + } else if (mTaskState == TaskState::Running) { + ILOG(Debug, Devel) << "Requested start, but the user task is already running - doing nothing." << ENDM; + } else if (mTaskState == TaskState::INVALID) { + throw std::runtime_error("The user task has INVALID state"); + } else { + throw std::runtime_error("Unknown task state"); + } +} + +void PostProcessingRunner::stop(framework::ServiceRegistryRef dplServices) +{ + if (mTaskState == TaskState::Created || mTaskState == TaskState::Running) { + if (trigger_helpers::hasUserOrControlTrigger(mTaskConfig.stopTriggers)) { + // We try to get SOR and EOR times for ECS, which could be needed by the user code. + auto activityFromDriver = mActivity; + activityFromDriver.mValidity.setMax(getCurrentTimestamp()); + if (dplServices.active()) { + activityFromDriver = computeActivity(dplServices, activityFromDriver); + } + doFinalize({ TriggerType::UserOrControl, false, activityFromDriver, activityFromDriver.mValidity.getMax() }); + } + } else if (mTaskState == TaskState::Finished) { + ILOG(Debug, Devel) << "Requested stop, but the user task is already finalized - doing nothing." << ENDM; + } else if (mTaskState == TaskState::INVALID) { + throw std::runtime_error("The user task has INVALID state"); + } else { + throw std::runtime_error("Unknown task state"); + } +} + +void PostProcessingRunner::reset() +{ + mTaskState = TaskState::INVALID; + + mTask.reset(); + mSourceDatabase.reset(); + mDestinationDatabase.reset(); + mServices = framework::ServiceRegistry(); + mObjectManager.reset(); + + mInitTriggers.clear(); + mUpdateTriggers.clear(); + mStopTriggers.clear(); +} + +void PostProcessingRunner::updateValidity(const Trigger& trigger) +{ + if (mTaskConfig.validityFromLastTriggerOnly) { + mActivity.mValidity = gInvalidValidityInterval; + } + + if (trigger == TriggerType::UserOrControl) { + // we ignore it, because it would not make sense to use current time in tracking objects from the past, + // especially in asynchronous postprocessing + ILOG(Debug, Trace) << "Ignoring UserOrControl trigger in tracking objects validity" << ENDM; + mObjectManager->setValidity(mActivity.mValidity); + return; + } + if (!trigger.activity.mValidity.isValid()) { + ILOG(Warning, Devel) << "Not updating objects validity, because the provided trigger validity is invalid (" + << trigger.activity.mValidity.getMin() << ", " << trigger.activity.mValidity.getMax() << ")" << ENDM; + return; + } + if (trigger.activity.mValidity == gFullValidityInterval) { + ILOG(Warning, Devel) << "Not updating objects validity, because the provided trigger validity covers the" + << " maximum possible validity, which is unexpected" << ENDM; + return; + } + if (!core::activity_helpers::onNumericLimit(trigger.activity.mValidity.getMin())) { + mActivity.mValidity.update(trigger.activity.mValidity.getMin()); + } + if (!core::activity_helpers::onNumericLimit(trigger.activity.mValidity.getMax())) { + mActivity.mValidity.update(trigger.activity.mValidity.getMax()); + } + mObjectManager->setValidity(mActivity.mValidity); +} + +void PostProcessingRunner::doInitialize(const Trigger& trigger) +{ + ILOG(Info, Support) << "Initializing the user task due to trigger '" << trigger << "'" << ENDM; + + mTask->initialize(trigger, mServices); + updateValidity(trigger); + mTaskState = TaskState::Running; + + // We create the triggers just after task init (and not any sooner), so the timer triggers work as expected. + auto taskConfigWithCorrectActivity = mTaskConfig; + taskConfigWithCorrectActivity.activity = mActivity; + taskConfigWithCorrectActivity.activity.mValidity = gFullValidityInterval; + mUpdateTriggers = trigger_helpers::createTriggers(mTaskConfig.updateTriggers, taskConfigWithCorrectActivity); + mStopTriggers = trigger_helpers::createTriggers(mTaskConfig.stopTriggers, taskConfigWithCorrectActivity); +} + +void PostProcessingRunner::doUpdate(const Trigger& trigger) +{ + ILOG(Info, Support) << "Updating the user task due to trigger '" << trigger << "'" << ENDM; + mTask->update(trigger, mServices); + updateValidity(trigger); + + if (mActivity.mValidity.isValid()) { + mPublicationCallback(mObjectManager->getNonOwningArray()); + mObjectManager->stopPublishing(PublicationPolicy::Once); + } else { + ILOG(Warning, Support) << "Objects will not be published because their validity is invalid. This should not happen." << ENDM; + } +} + +void PostProcessingRunner::doFinalize(const Trigger& trigger) +{ + if (mTaskState != TaskState::Running) { + ILOG(Warning, Support) << "Attempt at finalizing the user task although it was not initialized. Skipping the finalization." << ENDM; + return; + } + ILOG(Info, Support) << "Finalizing the user task due to trigger '" << trigger << "'" << ENDM; + mTask->finalize(trigger, mServices); + updateValidity(trigger); + + if (mActivity.mValidity.isValid()) { + mPublicationCallback(mObjectManager->getNonOwningArray()); + } else { + // TODO: we could consider using SOR, EOR as validity in such case, so empty objects are still stored in the QCDB. + ILOG(Warning, Devel) << "Objects will not be published because their validity is invalid. Most likely the task's update() method was never triggered." << ENDM; + } + mTaskState = TaskState::Finished; + mObjectManager->stopPublishing(PublicationPolicy::Once); + mObjectManager->stopPublishing(PublicationPolicy::ThroughStop); +} + +const std::string& PostProcessingRunner::getID() const +{ + return mID; +} + +PostProcessingRunnerConfig PostProcessingRunner::extractConfig(const CommonSpec& commonSpec, const PostProcessingTaskSpec& ppTaskSpec) +{ + auto sourceDatabase = ppTaskSpec.sourceDatabase.empty() ? commonSpec.database : ppTaskSpec.sourceDatabase; + + return { + ppTaskSpec.id, + ppTaskSpec.taskName, + ppTaskSpec.detectorName, + sourceDatabase, + commonSpec.database, + commonSpec.consulUrl, + commonSpec.bookkeepingUrl, + commonSpec.infologgerDiscardParameters, + commonSpec.postprocessingPeriod, + "", + ppTaskSpec.tree + }; +} + +MOCPublicationCallback publishToDPL(framework::DataAllocator& allocator, std::string outputBinding) +{ + return [&allocator = allocator, outputBinding = std::move(outputBinding)](const MonitorObjectCollection* moc) { + // TODO pass timestamps to objects, so they are later stored correctly. + ILOG(Debug, Support) << "Publishing " << moc->GetEntries() << " MonitorObjects" << ENDM; + allocator.snapshot(framework::OutputRef{ outputBinding }, *moc); + }; +} + +MOCPublicationCallback publishToRepository(o2::quality_control::repository::DatabaseInterface& repository) +{ + return [&](const MonitorObjectCollection* collection) { + ILOG(Debug, Support) << "Publishing " << collection->GetEntries() << " MonitorObjects" << ENDM; + for (const TObject* mo : *collection) { + // We have to copy the object so we can pass a shared_ptr. + // This is not ideal, but MySQL interface requires shared ptrs to queue the objects. + repository.storeMO(std::shared_ptr(dynamic_cast(mo->Clone()))); + } + }; +} + +} // namespace o2::quality_control::postprocessing diff --git a/Framework/src/QCInputsAdapters.cxx b/Framework/src/QCInputsAdapters.cxx new file mode 100644 index 0000000000..a8cc23e04e --- /dev/null +++ b/Framework/src/QCInputsAdapters.cxx @@ -0,0 +1,33 @@ +// Copyright 2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QCInputsAdapters.cxx +/// \author Michal Tichak +/// + +#include "QualityControl/QCInputsAdapters.h" + +namespace o2::quality_control::core +{ + +std::optional> getQualityObject(const QCInputs& data, std::string_view objectName) +{ + const auto filterQOByName = [objectName](const auto& pair) { + return std::string_view(pair.second->getCheckName()) == objectName; + }; + for (const auto& qo : data.iterateByTypeAndFilter(filterQOByName)) { + return { qo }; + } + return std::nullopt; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/QCInputsFactory.cxx b/Framework/src/QCInputsFactory.cxx new file mode 100644 index 0000000000..545cedf004 --- /dev/null +++ b/Framework/src/QCInputsFactory.cxx @@ -0,0 +1,40 @@ +// Copyright 2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QCInputsFactory.cxx +/// \author Michal Tichak +/// + +#include "QualityControl/QCInputsFactory.h" + +namespace o2::quality_control::core +{ + +QCInputs createData(const std::map>& moMap) +{ + QCInputs data; + for (const auto& [key, mo] : moMap) { + data.insert(key, mo); + } + return data; +} + +QCInputs createData(const QualityObjectsMapType& qoMap) +{ + QCInputs data; + for (const auto& [key, qo] : qoMap) { + data.insert(key, qo); + } + return data; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/QcInfoLogger.cxx b/Framework/src/QcInfoLogger.cxx new file mode 100644 index 0000000000..490ecda353 --- /dev/null +++ b/Framework/src/QcInfoLogger.cxx @@ -0,0 +1,122 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcInfoLogger.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/QcInfoLogger.h" +#include + +namespace o2::quality_control::core +{ + +AliceO2::InfoLogger::InfoLogger* QcInfoLogger::instance; +AliceO2::InfoLogger::InfoLoggerContext* QcInfoLogger::mContext; +QcInfoLogger::_init QcInfoLogger::_initializer; +bool QcInfoLogger::disabled = false; + +void QcInfoLogger::setFacility(const std::string& facility) +{ + mContext->setField(infoContext::FieldName::Facility, facility); + mContext->setField(infoContext::FieldName::System, "QC"); + instance->setContext(*mContext); + ILOG(Debug, Devel) << "IL: Facility set to " << facility << ENDM; +} + +void QcInfoLogger::setDetector(const std::string& detector) +{ + mContext->setField(infoContext::FieldName::Detector, detector); + instance->setContext(*mContext); + ILOG(Debug, Devel) << "IL: Detector set to " << detector << ENDM; +} + +void QcInfoLogger::setRun(int run) +{ + if (run > 0) { + mContext->setField(infoContext::FieldName::Run, std::to_string(run)); + } + instance->setContext(*mContext); + ILOG(Debug, Devel) << "IL: Run set to " << run << ENDM; +} + +void QcInfoLogger::setPartition(const std::string& partitionName) +{ + if (partitionName.empty()) { + ILOG(Debug, Devel) << "IL: Partition empty, we don't set it" << ENDM; + return; + } + mContext->setField(infoContext::FieldName::Partition, partitionName); + instance->setContext(*mContext); + ILOG(Debug, Devel) << "IL: Partition set to " << partitionName << ENDM; +} + +void QcInfoLogger::disable() +{ + QcInfoLogger::disabled = true; + ILOG_INST.filterDiscardDebug(true); + ILOG_INST.filterDiscardLevel(1); +} + +using namespace std; + +void QcInfoLogger::init(const std::string& facility, + const LogDiscardParameters& discardParameters, + AliceO2::InfoLogger::InfoLogger* dplInfoLogger, + AliceO2::InfoLogger::InfoLoggerContext* dplContext, + int run, + const std::string& partitionName) +{ + if (dplInfoLogger && dplContext) { + // we ignore the small memory leak that might occur if we are replacing the default InfoLogger + instance = dplInfoLogger; + mContext = dplContext; + } + + // Set the proper discard filters + ILOG_INST.filterDiscardDebug(discardParameters.debug); + ILOG_INST.filterDiscardLevel(discardParameters.fromLevel); + if (disabled) { + ILOG_INST.filterDiscardDebug(true); + ILOG_INST.filterDiscardLevel(1); + } + if (!discardParameters.file.empty()) { + ILOG_INST.filterDiscardSetFile(discardParameters.file.c_str(), discardParameters.rotateMaxBytes, discardParameters.rotateMaxFiles, 0, !discardParameters.debugInDiscardFile /*Do not store Debug messages in file*/); + } + ILOG(Debug, Support) << "QC infologger initialized : " << discardParameters.debug << " ; " << discardParameters.fromLevel << ENDM; + ILOG(Debug, Devel) << " Discard debug ? " << discardParameters.debug << " / Discard from level ? " << discardParameters.fromLevel << " / Discard to file ? " << (!discardParameters.file.empty() ? discardParameters.file : "No") << " / Discard max bytes and files ? " << discardParameters.rotateMaxBytes << " = " << discardParameters.rotateMaxFiles << " / Put discarded debug messages in file ? " << discardParameters.debugInDiscardFile << ENDM; + + setFacility(facility); + setRun(run); + setPartition(partitionName); +} + +void QcInfoLogger::init(const std::string& facility, + const boost::property_tree::ptree& config, + AliceO2::InfoLogger::InfoLogger* dplInfoLogger, + AliceO2::InfoLogger::InfoLoggerContext* dplContext, + int run, + const std::string& partitionName) +{ + LogDiscardParameters discardParameters; + auto discardDebugStr = config.get("qc.config.infologger.filterDiscardDebug", "true"); + discardParameters.debug = discardDebugStr == "true"; + discardParameters.fromLevel = config.get("qc.config.infologger.filterDiscardLevel", 21 /* Discard Trace */); + discardParameters.file = config.get("qc.config.infologger.filterDiscardFile", ""); + discardParameters.rotateMaxBytes = config.get("infologger.filterRotateMaxBytes", 0); + discardParameters.rotateMaxFiles = config.get("infologger.filterRotateMaxFiles", 0); + auto debugInDiscardFile = config.get("qc.config.infologger.debugInDiscardFile", "false"); + discardParameters.debugInDiscardFile = debugInDiscardFile == "true"; + init(facility, discardParameters, dplInfoLogger, dplContext, run, partitionName); +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/QualitiesToFlagCollectionConverter.cxx b/Framework/src/QualitiesToFlagCollectionConverter.cxx new file mode 100644 index 0000000000..bfabc2862e --- /dev/null +++ b/Framework/src/QualitiesToFlagCollectionConverter.cxx @@ -0,0 +1,297 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualitiesToFlagCollectionConverter.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/QualitiesToFlagCollectionConverter.h" + +#include +#include + +#include +#include +#include "fmt/core.h" +#include "QualityControl/QualityObject.h" +// #include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/FlagHelpers.h" +#include "QualityControl/QcInfoLogger.h" + +// using namespace o2::quality_control::repository; + +namespace o2::quality_control::core +{ + +const char* noQOComment = "Did not receive a Quality Object which covers this period"; +const char* toBeRemovedComment = "This flag should be removed before returning the QCFC"; + +QualitiesToFlagCollectionConverter::QualitiesToFlagCollectionConverter( + std::unique_ptr qcfc, std::string qoPath) + : mQOPath(std::move(qoPath)), + mConverted(std::move(qcfc)) +{ + if (mConverted == nullptr) { + throw std::runtime_error("nullptr QualityControlFlagCollection provided to QualitiesToFlagCollectionConverter"); + } + if (mConverted->size() > 0) { + throw std::runtime_error( + "QualityControlFlagCollection provided to QualitiesToFlagCollectionConverter should have no flags"); + } + if (auto v = mConverted->getInterval(); v.isInvalid()) { + ILOG(Warning, Support) << fmt::format( + "QualityControlFlagCollection provided to QualitiesToFlagCollectionConverter has invalid validity ({}, {})", + v.getMin(), v.getMax()) + << ENDM; + return; + } + + /// Timespans not covered by a given QO are filled with Flag 1 (Unknown Quality) + // This flag will be removed or trimmed by any other Flags received as input. + mFlagBuffer.insert({ mConverted->getInterval().getMin(), mConverted->getInterval().getMax(), + FlagTypeFactory::UnknownQuality(), noQOComment, mQOPath }); +} + +std::vector QO2Flags(const QualityObject& qo) +{ + if (qo.getValidity().isInvalid()) { + return {}; + } + + const auto& flags = qo.getFlags(); + const auto quality = qo.getQuality(); + const auto qoPath = qo.getPath(); + const auto startTime = qo.getValidity().getMin(); + const auto endTime = qo.getValidity().getMax(); + + if (!flags.empty()) { + /// All QOs *with* Flags are converted to Flags, while Quality is ignored. + std::vector result; + result.reserve(flags.size()); + for (const auto& flag : flags) { + result.emplace_back(startTime, endTime, flag.first, flag.second, qoPath); + } + return result; + } + + if (quality == Quality::Good) { + /// Good QOs with *no* Flags are not converted to any Flags + // ...BUT we create a dummy Good flag to mark the timespans that should cancel UnknownQuality flag. + // We remove these dummy Flags before returning the QCFC. I spent hours thinking if there is + // a more elegant approach and I could not think of anything better. + return { { startTime, endTime, FlagTypeFactory::Good(), toBeRemovedComment, qoPath } }; + } + if (quality.isWorseThan(Quality::Good) && quality.isBetterThan(Quality::Null)) { + /// Bad and Medium QOs with *no* Flags are converted to Flag 14 (Unknown) + return { { startTime, endTime, FlagTypeFactory::Unknown(), quality.getName() + " quality with no Flags associated", qoPath } }; + } + if (quality == Quality::Null) { + /// Null QOs with *no* Flags are converted to Flag 1 (UnknownQuality) + return { { startTime, endTime, FlagTypeFactory::UnknownQuality(), quality.getName() + " quality with no Flags associated", qoPath } }; + } + return {}; +} + +void QualitiesToFlagCollectionConverter::operator()(const QualityObject& newQO) +{ + if (mConverted->getDetector() != newQO.getDetectorName()) { + throw std::runtime_error("The FlagCollection '" + mConverted->getName() + + "' expects QOs from detector '" + mConverted->getDetector() + + "' but received a QO for '" + newQO.getDetectorName() + "'"); + } + if (mQOPath != newQO.getPath()) { + throw std::runtime_error("The FlagCollection '" + mConverted->getName() + + "' expects QOs for path '" + mQOPath + + "' but received a QO for '" + newQO.getPath() + "'"); + } + if (newQO.getValidity().isInvalid()) { + ILOG(Warning, Support) + << fmt::format("Received a QO '{}' with invalid validity interval ({}. {}), ignoring", newQO.GetName(), + newQO.getValidity().getMin(), newQO.getValidity().getMax()) + << ENDM; + return; + } + + if (mConverted->getInterval().isOutside(newQO.getValidity())) { + ILOG(Warning, Support) << fmt::format( + "The provided QO's validity ({}, {}) is outside of the validity interval accepted by the converter ({}, {})", + newQO.getValidity().getMin(), newQO.getValidity().getMax(), + mConverted->getInterval().getMin(), mConverted->getInterval().getMax()) + << ENDM; + return; + } + + mQOsIncluded++; + if (newQO.getQuality().isWorseThan(Quality::Good)) { + mWorseThanGoodQOs++; + } + + for (auto&& newFlag : QO2Flags(newQO)) { + insert(std::move(newFlag)); + } +} + +void QualitiesToFlagCollectionConverter::trimBufferWithInterval(ValidityInterval interval, const std::function& predicate) +{ + auto toTrimPredicate = [&](const QualityControlFlag& flag) { + return flag_helpers::intervalsOverlap(flag.getInterval(), interval) && predicate(flag); + }; + + auto flagsToTrim = mFlagBuffer | std::views::filter(toTrimPredicate); + for (const auto& flagToTrimOrRemove : flagsToTrim) { + auto trimmedFlags = flag_helpers::excludeInterval(flagToTrimOrRemove, interval); + mFlagBuffer.insert(std::make_move_iterator(trimmedFlags.begin()), std::make_move_iterator(trimmedFlags.end())); + } + // we have inserted any trimmed flags which remained, we remove the old ones. + std::erase_if(mFlagBuffer, toTrimPredicate); +} + +std::vector QualitiesToFlagCollectionConverter::trimFlagAgainstBuffer(const QualityControlFlag& newFlag, const std::function& predicate) +{ + auto trimmerPredicate = [&](const QualityControlFlag& flag) { + return flag_helpers::intervalsOverlap(flag.getInterval(), newFlag.getInterval()) && predicate(flag); + }; + std::vector trimmedNewFlags{ newFlag }; + for (const auto& overlappingFlag : mFlagBuffer | std::views::filter(trimmerPredicate)) { + std::vector updatedTrimmedFlags; + for (const auto& trimmedFlag : trimmedNewFlags) { + auto result = flag_helpers::excludeInterval(trimmedFlag, overlappingFlag.getInterval()); + updatedTrimmedFlags.insert(updatedTrimmedFlags.end(), std::make_move_iterator(result.begin()), std::make_move_iterator(result.end())); + } + trimmedNewFlags = std::move(updatedTrimmedFlags); + } + + return trimmedNewFlags; +} + +void QualitiesToFlagCollectionConverter::insert(QualityControlFlag&& newFlag) +{ + // We trim the flag to the current QCFC duration + auto trimmedFlagOptional = flag_helpers::intersection(newFlag, mConverted->getInterval()); + if (!trimmedFlagOptional.has_value()) { + return; + } + newFlag = trimmedFlagOptional.value(); + + // We look for any existing flags with could be merged, including cases when there is more than one to be merged. + // Existing flags: [-----) [---------) + // New flag: [--------) + // Correct result: [----------------------) + auto canBeMergedWithNewFlag = [&](const QualityControlFlag& other) { + return newFlag.getFlag() == other.getFlag() && + newFlag.getComment() == other.getComment() && + flag_helpers::intervalsConnect(newFlag.getInterval(), other.getInterval()); + }; + + auto flagsToMerge = mFlagBuffer | std::views::filter(canBeMergedWithNewFlag); + for (const auto& flag : flagsToMerge) { + newFlag.getInterval().update(flag.getStart()); + newFlag.getInterval().update(flag.getEnd()); + } + std::erase_if(mFlagBuffer, canBeMergedWithNewFlag); + + if (newFlag.getFlag() != FlagTypeFactory::UnknownQuality()) { + // We trim any UnknownQuality flags which become obsolete due to the presence of the new flag + trimBufferWithInterval(newFlag.getInterval(), + [](const auto& f) { return f.getFlag() == FlagTypeFactory::UnknownQuality(); }); + mFlagBuffer.insert(newFlag); + } else { + // If the new Flag is UnknownQuality, we will apply it only for intervals not covered by other types of Flags + auto trimmedNewFlags = trimFlagAgainstBuffer(newFlag, [](const auto& f) { return f.getFlag() != FlagTypeFactory::UnknownQuality(); }); + mFlagBuffer.insert(std::make_move_iterator(trimmedNewFlags.begin()), std::make_move_iterator(trimmedNewFlags.end())); + + // And then, we trim also the default UnknownQuality flag (no QO). + if (newFlag.getComment() != noQOComment) { + trimBufferWithInterval(newFlag.getInterval(), [](const auto& f) { + return f.getFlag() == FlagTypeFactory::UnknownQuality() && f.getComment() == noQOComment; + }); + } + } +} + +std::unique_ptr QualitiesToFlagCollectionConverter::getResult() +{ + std::erase_if(mFlagBuffer, [](const QualityControlFlag& flag) { return flag.getComment() == toBeRemovedComment; }); + for (const auto& flag : mFlagBuffer) { + mConverted->insert(flag); + } + + ILOG(Debug, Devel) << fmt::format("converted flags for det '{}' and QO '{}' from {} QOs, incl. {} QOs worse than Good", mConverted->getDetector(), mQOPath, mQOsIncluded, mWorseThanGoodQOs) << ENDM; + ILOG(Debug, Devel) << *mConverted << ENDM; + + auto result = std::make_unique( + mConverted->getName(), mConverted->getDetector(), mConverted->getInterval(), + mConverted->getRunNumber(), mConverted->getPeriodName(), mConverted->getPassName(), mConverted->getProvenance()); + result.swap(mConverted); + + mFlagBuffer.clear(); + mFlagBuffer.insert({ mConverted->getInterval().getMin(), mConverted->getInterval().getMax(), FlagTypeFactory::UnknownQuality(), noQOComment, mQOPath }); + mQOsIncluded = 0; + mWorseThanGoodQOs = 0; + + return result; +} + +size_t QualitiesToFlagCollectionConverter::getQOsIncluded() const +{ + return mQOsIncluded; +} + +size_t QualitiesToFlagCollectionConverter::getWorseThanGoodQOs() const +{ + return mWorseThanGoodQOs; +} + +void QualitiesToFlagCollectionConverter::updateValidityInterval(const ValidityInterval interval) +{ + // input validation + if (interval.isInvalid() || mConverted->getInterval().getOverlap(interval).isZeroLength()) { + mFlagBuffer.clear(); + mConverted->setInterval(interval); + return; + } + + // trimming existing flags + if (mConverted->getStart() < interval.getMin() || mConverted->getEnd() > interval.getMax()) { + std::set trimmedFlags; + for (const auto& flag : mFlagBuffer) { + if (auto&& trimmedFlag = flag_helpers::intersection(flag, interval); trimmedFlag.has_value()) { + trimmedFlags.insert(std::move(trimmedFlag.value())); + } + } + mFlagBuffer.swap(trimmedFlags); + } + + // adding UnknownQuality to new intervals + if (mConverted->getStart() > interval.getMin()) { + QualityControlFlag flag{ + interval.getMin(), mConverted->getStart(), FlagTypeFactory::UnknownQuality(), noQOComment, mQOPath + }; + mConverted->setStart(interval.getMin()); + insert(std::move(flag)); + } + if (mConverted->getEnd() < interval.getMax()) { + QualityControlFlag flag{ + mConverted->getEnd(), interval.getMax(), FlagTypeFactory::UnknownQuality(), noQOComment, mQOPath + }; + mConverted->setEnd(interval.getMax()); + insert(std::move(flag)); + } + mConverted->setInterval(interval); +} + +int QualitiesToFlagCollectionConverter::getRunNumber() const +{ + return mConverted ? mConverted->getRunNumber() : -1; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/Quality.cxx b/Framework/src/Quality.cxx index c3b0cb282d..c37aca0c7e 100644 --- a/Framework/src/Quality.cxx +++ b/Framework/src/Quality.cxx @@ -1,30 +1,133 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file Quality.cxx /// \author Barthelemy von Haller /// #include "QualityControl/Quality.h" +#include +#include +#include +#include +#include +#include -ClassImp(o2::quality_control::core::Quality) - - // clang-format off namespace o2::quality_control::core { - // clang-format on - const unsigned int Quality::NullLevel = - 10; // could be changed if needed but I don't see why we would need more than 10 levels - const Quality Quality::Good(1, "Good"); - const Quality Quality::Medium(2, "Medium"); - const Quality Quality::Bad(3, "Bad"); - const Quality Quality::Null(NullLevel, "Null"); // we consider it the worst of the worst +// could be changed if needed, but I don't see why we would need more than 10 levels +const unsigned int Quality::NullLevel = 10; + +const Quality Quality::Good(1, "Good"); +const Quality Quality::Medium(2, "Medium"); +const Quality Quality::Bad(3, "Bad"); +const Quality Quality::Null(NullLevel, "Null"); // we consider it the worst of the worst + +Quality::Quality(unsigned int level, std::string name) : mLevel(level), mName(std::move(name)), mUserMetadata{} {} + +void Quality::set(const Quality& q) +{ + mLevel = q.mLevel; + mName = q.mName; +} + +unsigned int Quality::getLevel() const { return mLevel; } - Quality::Quality(unsigned int level, std::string name) : mLevel(level), mName(name) {} +const std::string& Quality::getName() const { return mName; } - Quality::~Quality() {} +std::ostream& operator<<(std::ostream& out, const Quality& q) // output +{ + out << "Quality: " << q.getName() << " (level " << q.getLevel() << ")"; + return out; +} + +void Quality::addMetadata(const std::string& key, const std::string& value) +{ + mUserMetadata.emplace(key, value); +} - unsigned int Quality::getLevel() const { return mLevel; } +void Quality::addMetadata(std::map pairs) +{ + // we do not use "merge" because it would ignore the items whose key already exist in mUserMetadata. + mUserMetadata.insert(pairs.begin(), pairs.end()); +} - const std::string& Quality::getName() const { return mName; } +const std::map& Quality::getMetadataMap() const +{ + return mUserMetadata; +} + +void Quality::updateMetadata(const std::string& key, std::string value) +{ + if (mUserMetadata.count(key) > 0) { + mUserMetadata[key] = std::move(value); + } +} + +void Quality::overwriteMetadata(std::map pairs) +{ + mUserMetadata.clear(); + addMetadata(std::move(pairs)); +} + +std::string Quality::getMetadata(const std::string& key) const +{ + if (mUserMetadata.count(key) == 0) { + std::cerr << "Could not get the metadata with key \"" << key << "\"" << std::endl; + BOOST_THROW_EXCEPTION(AliceO2::Common::ObjectNotFoundError() << AliceO2::Common::errinfo_object_name(key)); + } + return mUserMetadata.at(key); +} + +std::string Quality::getMetadata(const std::string& key, const std::string& defaultValue) const +{ + return mUserMetadata.count(key) > 0 ? mUserMetadata.at(key) : defaultValue; +} + +std::optional Quality::getMetadataOpt(const std::string& key) const +{ + if (auto found = mUserMetadata.find(key); found != mUserMetadata.end()) { + return found->second; + } + return std::nullopt; +} + +Quality& Quality::addFlag(const FlagType& flag, std::string comment) +{ + if (isWorseThan(Quality::Medium) && !flag.getBad()) { + std::cerr << "Warning: assigning good flag to " << mName << " quality" << std::endl; + } else if (isBetterThan(Quality::Medium) && flag.getBad()) { + std::cerr << "Warning: assigning bad flag to " << mName << " quality" << std::endl; + } + mFlags.emplace_back(flag, std::move(comment)); + return *this; +} +const CommentedFlagTypes& Quality::getFlags() const +{ + return mFlags; +} + +Quality Quality::fromString(const std::string& str) +{ + if (str == Quality::Good.getName()) { + return Quality::Good; + } else if (str == Quality::Medium.getName()) { + return Quality::Medium; + } else if (str == Quality::Bad.getName()) { + return Quality::Bad; + } else { + return Quality::Null; + } +} } // namespace o2::quality_control::core diff --git a/Framework/src/QualityObject.cxx b/Framework/src/QualityObject.cxx new file mode 100644 index 0000000000..758f24ef7e --- /dev/null +++ b/Framework/src/QualityObject.cxx @@ -0,0 +1,219 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include +#include +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/QualityObject.h" + +using namespace AliceO2::Common; + +namespace o2::quality_control::core +{ + +QualityObject::QualityObject( + Quality quality, + std::string checkName, + std::string detectorName, + std::string policyName, + std::vector inputs, + std::vector monitorObjectsNames, + std::map metadata, + int runNumber) + : mQuality{ std::move(quality) }, + mCheckName{ std::move(checkName) }, + mDetectorName{ std::move(detectorName) }, + mPolicyName{ std::move(policyName) }, + mInputs{ std::move(inputs) }, + mMonitorObjectsNames{ std::move(monitorObjectsNames) }, + mActivity(runNumber, "NONE", "", "", "qc", gInvalidValidityInterval) +{ + mQuality.addMetadata(std::move(metadata)); +} + +QualityObject::~QualityObject() = default; + +constexpr auto anonChecker = "anonymousChecker"; +QualityObject::QualityObject() + : QualityObject(Quality(), anonChecker) +{ + mActivity.mProvenance = "qc"; +} + +const char* QualityObject::GetName() const +{ + std::string name = getName(); + return strdup(name.c_str()); +} + +std::string QualityObject::getName() const +{ + if (mPolicyName == "OnEachSeparately") { + if (mMonitorObjectsNames.size() != 1) { + BOOST_THROW_EXCEPTION(FatalException() << errinfo_details("QualityObject::getName: " + "The vector of monitorObjectsNames must contain a single object")); + } + return mCheckName + "/" + mMonitorObjectsNames[0]; + } + return mCheckName; +} + +void QualityObject::updateQuality(Quality quality) +{ + // TODO: Update timestamp + mQuality = std::move(quality); +} +Quality QualityObject::getQuality() const +{ + return mQuality; +} + +void QualityObject::addMetadata(std::string key, std::string value) +{ + mQuality.addMetadata(std::move(key), std::move(value)); +} + +void QualityObject::addMetadata(std::map pairs) +{ + mQuality.addMetadata(std::move(pairs)); +} + +const std::map& QualityObject::getMetadataMap() const +{ + return mQuality.getMetadataMap(); +} + +void QualityObject::updateMetadata(std::string key, std::string value) +{ + mQuality.updateMetadata(std::move(key), std::move(value)); +} + +std::string QualityObject::getMetadata(std::string key) const +{ + return mQuality.getMetadata(std::move(key)); +} + +std::string QualityObject::getMetadata(std::string key, std::string defaultValue) const +{ + return mQuality.getMetadata(std::move(key), std::move(defaultValue)); +} + +std::optional QualityObject::getMetadataOpt(const std::string& key) const +{ + return mQuality.getMetadataOpt(key); +} + +std::string QualityObject::getPath() const +{ + std::string path; + try { + path = RepoPathUtils::getQoPath(this); + } catch (FatalException& fe) { + fe << errinfo_details("Only one MO should be assigned to one QO With the policy OnEachSeparately"); // update error info + throw; + } + return path; +} + +QualityObject& QualityObject::addFlag(const FlagType& flag, std::string comment) +{ + mQuality.addFlag(flag, std::move(comment)); + return *this; +} + +const CommentedFlagTypes& QualityObject::getFlags() const +{ + return mQuality.getFlags(); +} + +const std::string& QualityObject::getDetectorName() const +{ + return mDetectorName; +} + +void QualityObject::setDetectorName(const std::string& detectorName) +{ + QualityObject::mDetectorName = detectorName; +} + +void QualityObject::setQuality(const Quality& quality) +{ + updateQuality(quality); +} +const std::string& QualityObject::getCheckName() const +{ + return mCheckName; +} + +const std::string& QualityObject::getPolicyName() const +{ + return mPolicyName; +} + +const std::vector& QualityObject::getMonitorObjectsNames() const +{ + return mMonitorObjectsNames; +} + +std::ostream& operator<<(std::ostream& out, const QualityObject& q) // output +{ + out << "QualityObject: " << q.getName() << ":\n" + << " - checkName : " << q.getCheckName() << "\n" + << " - detectorName : " << q.getDetectorName() << "\n" + // << " - runNumber : " << q.getRunNumber() << "\n" + << " - quality : " << q.getQuality() << "\n" + << " - monitorObjectsNames : "; + for (auto item : q.getMonitorObjectsNames()) { + out << item << ", "; + } + return out; +} + +void QualityObject::updateActivity(int runNumber, const std::string& periodName, const std::string& passName, const std::string& provenance) +{ + mActivity.mId = runNumber; + mActivity.mPeriodName = periodName; + mActivity.mPassName = passName; + mActivity.mProvenance = provenance; +} + +const Activity& QualityObject::getActivity() const +{ + return mActivity; +} + +Activity& QualityObject::getActivity() +{ + return mActivity; +} + +void QualityObject::setActivity(const Activity& activity) +{ + mActivity = activity; +} + +void QualityObject::setValidity(ValidityInterval validityInterval) +{ + mActivity.mValidity = validityInterval; +} + +void QualityObject::updateValidity(validity_time_t value) +{ + mActivity.mValidity.update(value); +} + +ValidityInterval QualityObject::getValidity() const +{ + return mActivity.mValidity; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/ReductorHelpers.cxx b/Framework/src/ReductorHelpers.cxx new file mode 100644 index 0000000000..aa31f9acfe --- /dev/null +++ b/Framework/src/ReductorHelpers.cxx @@ -0,0 +1,63 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReductorHelpers.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/ReductorHelpers.h" + +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/ReductorConditionAny.h" +#include "QualityControl/Triggers.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/ConditionAccess.h" + +namespace o2::quality_control::postprocessing::reductor_helpers::implementation +{ + +bool updateReductorImpl(Reductor* r, const Trigger& t, const std::string& path, const std::string& name, const std::string& type, + repository::DatabaseInterface& qcdb, core::ConditionAccess& ccdbAccess) +{ + if (r == nullptr) { + return false; + } + + if (type == "repository") { + auto mo = qcdb.retrieveMO(path, name, t.timestamp, t.activity, t.metadata); + TObject* obj = mo ? mo->getObject() : nullptr; + auto reductorTObject = dynamic_cast(r); + if (obj && reductorTObject) { + reductorTObject->update(obj); + return true; + } + } else if (type == "repository-quality") { + auto qo = qcdb.retrieveQO(path + "/" + name, t.timestamp, t.activity, t.metadata); + auto reductorTObject = dynamic_cast(r); + if (qo && reductorTObject) { + reductorTObject->update(qo.get()); + return true; + } + } else if (type == "condition") { + auto reductorConditionAny = dynamic_cast(r); + if (reductorConditionAny) { + auto conditionPath = name.empty() || path.empty() ? path + name : path + "/" + name; + reductorConditionAny->update(ccdbAccess, t.timestamp, conditionPath); + return true; + } + } + return false; +} + +} // namespace o2::quality_control::postprocessing::reductor_helpers::implementation diff --git a/Framework/src/RepoPathUtils.cxx b/Framework/src/RepoPathUtils.cxx new file mode 100644 index 0000000000..8400832e7f --- /dev/null +++ b/Framework/src/RepoPathUtils.cxx @@ -0,0 +1,23 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "QualityControl/RepoPathUtils.h" +#include + +namespace o2::quality_control::core +{ + +bool RepoPathUtils::isProvenanceAllowed(const std::string& provenance) +{ + return provenance == "qc" || provenance == "qc_async" || provenance == "qc_mc"; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/RepositoryBenchmark.cxx b/Framework/src/RepositoryBenchmark.cxx index 478edf755d..4ff8482945 100644 --- a/Framework/src/RepositoryBenchmark.cxx +++ b/Framework/src/RepositoryBenchmark.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2022 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -18,13 +19,12 @@ #include #include // this_thread::sleep_for -#include #include -#include -#include // device->fConfig +#include // device->fConfig + +#include -#include "Common/Exceptions.h" #include "QualityControl/DatabaseFactory.h" #include "QualityControl/QcInfoLogger.h" @@ -37,16 +37,6 @@ using namespace o2::monitoring; namespace o2::quality_control::core { -RepositoryBenchmark::RepositoryBenchmark() - : mMaxIterations(0), - mNumIterations(0), - mNumberObjects(1), - mSizeObjects(1), - mThreadedMonitoring(true), - mTotalNumberObjects(0) -{ -} - TH1* RepositoryBenchmark::createHisto(uint64_t sizeObjects, string name) { TH1* myHisto; @@ -95,8 +85,8 @@ void RepositoryBenchmark::InitTask() mDatabase->prepareTaskDataContainer(mTaskName); } catch (boost::exception& exc) { string diagnostic = boost::current_exception_diagnostic_information(); - std::cerr << "Unexpected exception, diagnostic information follows:\n" - << diagnostic << endl; + ILOG(Error, Support) << "Unexpected exception, diagnostic information follows: " + << diagnostic << ENDM; if (diagnostic == "No diagnostic information available.") { throw; } @@ -119,20 +109,21 @@ void RepositoryBenchmark::InitTask() mMonitoring->addGlobalTag("numberObject", to_string(mNumberObjects)); mMonitoring->addGlobalTag("sizeObject", to_string(mSizeObjects)); if (mTaskName == "benchmarkTask_0") { // send these parameters to monitoring only once per benchmark run - mMonitoring->sendGrouped("ccdb-benchmark-parameters", { { mNumberObjects, "number-objects" }, - { mSizeObjects * 1000, "size-objects" }, - { numberTasks, "number-tasks" } }); + mMonitoring->send(Metric{ "ccdb_benchmark" } + .addValue(mNumberObjects, "number_objects") + .addValue(mSizeObjects * 1000, "size_objects") + .addValue(numberTasks, "number_tasks")); } if (mDeletionMode) { - QcInfoLogger::GetInstance() << "Deletion mode..." << infologger::endm; + ILOG(Info, Support) << "Deletion mode..." << infologger::endm; emptyDatabase(); } // prepare objects for (uint64_t i = 0; i < mNumberObjects; i++) { TH1* histo = createHisto(mSizeObjects, mObjectName + to_string(i)); - shared_ptr mo = make_shared(histo, mTaskName); + shared_ptr mo = make_shared(histo, mTaskName, "Benchmark", "BMK"); mo->setIsOwner(true); mMyObjects.push_back(mo); } @@ -147,7 +138,7 @@ void RepositoryBenchmark::InitTask() void RepositoryBenchmark::checkTimedOut() { - mMonitoring->send({ mTotalNumberObjects, "objectsSent" }, DerivedMetricMode::RATE); + mMonitoring->send({ mTotalNumberObjects, "ccdb_benchmark_objects_sent" }, DerivedMetricMode::RATE); // restart timer mTimer->expires_at(mTimer->expires_at() + boost::posix_time::seconds(mThreadedMonitoringInterval)); @@ -164,30 +155,30 @@ bool RepositoryBenchmark::ConditionalRun() // Store the object for (unsigned int i = 0; i < mNumberObjects; i++) { - mDatabase->store(mMyObjects[i]); + mDatabase->storeMO(mMyObjects[i]); mTotalNumberObjects++; } if (!mThreadedMonitoring) { - mMonitoring->send({ mTotalNumberObjects, "objectsSent" }, DerivedMetricMode::RATE); + mMonitoring->send({ mTotalNumberObjects, "ccdb_benchmark_objects_sent" }, DerivedMetricMode::RATE); } high_resolution_clock::time_point t2 = high_resolution_clock::now(); long duration = duration_cast(t2 - t1).count(); - mMonitoring->send({ duration / mNumberObjects, "storeDurationForOneObject_ms" }, DerivedMetricMode::NONE); + mMonitoring->send({ duration / mNumberObjects, "ccdb_benchmark_store_duration_for_one_object_ms" }); // determine how long we should wait till next iteration in order to have 1 sec between storage auto duration2 = duration_cast(t2 - t1); auto remaining = duration_cast(std::chrono::seconds(1) - duration2); - // QcInfoLogger::GetInstance() << "Remaining duration : " << remaining.count() << " us" << infologger::endm; + // ILOG(Info, Support) <<"Remaining duration : " << remaining.count() << " us" << infologger::endm; if (remaining.count() < 0) { - QcInfoLogger::GetInstance() << "Remaining duration is negative, we don't sleep " << infologger::endm; + ILOG(Info, Support) << "Remaining duration is negative, we don't sleep " << infologger::endm; } else { this_thread::sleep_for(chrono::microseconds(remaining)); } if (mMaxIterations > 0 && ++mNumIterations >= mMaxIterations) { - QcInfoLogger::GetInstance() << "Configured maximum number of iterations reached. Leaving RUNNING state." - << infologger::endm; + ILOG(Info, Support) << "Configured maximum number of iterations reached. Leaving RUNNING state." + << infologger::endm; return false; } @@ -202,4 +193,4 @@ void RepositoryBenchmark::emptyDatabase() } } -} // namespace o2::quality_control::core \ No newline at end of file +} // namespace o2::quality_control::core diff --git a/Framework/src/RepositoryBenchmark.h b/Framework/src/RepositoryBenchmark.h index 32cda69ca5..34e09e01ab 100644 --- a/Framework/src/RepositoryBenchmark.h +++ b/Framework/src/RepositoryBenchmark.h @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2022 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -16,19 +17,22 @@ #ifndef QC_REPOSITORYBENCHMARK_H #define QC_REPOSITORYBENCHMARK_H -#include "QualityControl/CcdbDatabase.h" -#include +#include "QualityControl/DatabaseInterface.h" +#include +#include #include -#include #include +#include +#include +#include namespace o2::quality_control::core { -class RepositoryBenchmark : public FairMQDevice +class RepositoryBenchmark : public fair::mq::Device { public: - RepositoryBenchmark(); + RepositoryBenchmark() = default; virtual ~RepositoryBenchmark() = default; protected: @@ -40,28 +44,28 @@ class RepositoryBenchmark : public FairMQDevice private: // user params - uint64_t mMaxIterations; - uint64_t mNumIterations; - uint64_t mNumberObjects; - uint64_t mSizeObjects; + uint64_t mMaxIterations = 0; + uint64_t mNumIterations = 0; + uint64_t mNumberObjects = 1; + uint64_t mSizeObjects = 1; std::string mTaskName; std::string mObjectName; - bool mDeletionMode; + bool mDeletionMode = false; // todo: is false ok as default? // monitoring std::unique_ptr mMonitoring; - uint64_t mTotalNumberObjects; - bool mThreadedMonitoring; - uint64_t mThreadedMonitoringInterval; + uint64_t mTotalNumberObjects = 0; + bool mThreadedMonitoring = true; + uint64_t mThreadedMonitoringInterval = 10; // internal state std::unique_ptr mDatabase; std::vector> mMyObjects; -// TH1* mMyHisto; + // TH1* mMyHisto; // variables for the timer boost::asio::deadline_timer* mTimer; /// the asynchronous timer to send monitoring data - boost::asio::io_service io; + boost::asio::io_context io; std::thread* th; }; diff --git a/Framework/src/RootClassFactory.cxx b/Framework/src/RootClassFactory.cxx new file mode 100644 index 0000000000..fa4fdb022d --- /dev/null +++ b/Framework/src/RootClassFactory.cxx @@ -0,0 +1,38 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RootClassFactory.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/RootClassFactory.h" + +#include +#include + +namespace bfs = boost::filesystem; + +namespace o2::quality_control::core::root_class_factory +{ + +void loadLibrary(const std::string& moduleName) +{ + // Load the library + std::string library = bfs::path(moduleName).is_absolute() ? moduleName : "libO2" + moduleName; + ILOG(Info, Devel) << "Loading library " << library << ENDM; + int libLoaded = gSystem->Load(library.c_str(), "", true); + if (libLoaded < 0) { + BOOST_THROW_EXCEPTION(FatalException() << errinfo_details("Failed to load the library " + library)); + } +} + +} // namespace o2::quality_control::core::root_class_factory \ No newline at end of file diff --git a/Framework/src/RootFileSink.cxx b/Framework/src/RootFileSink.cxx new file mode 100644 index 0000000000..fc0ef2dcb4 --- /dev/null +++ b/Framework/src/RootFileSink.cxx @@ -0,0 +1,93 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RootFileSink.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/RootFileSink.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObjectCollection.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/RootFileStorage.h" +#include +#include +#include +#include + +#if defined(__linux__) && __has_include() +#include +#endif + +using namespace o2::framework; + +namespace o2::quality_control::core +{ + +RootFileSink::RootFileSink(std::string filePath) + : mFilePath(std::move(filePath)) +{ +} + +void RootFileSink::customizeInfrastructure(std::vector& policies) +{ + auto matcher = [label = RootFileSink::getLabel()](auto const& device) { + return std::find(device.labels.begin(), device.labels.end(), label) != device.labels.end(); + }; + + policies.emplace_back(CompletionPolicyHelpers::consumeWhenAny("qcRootFileSinkCompletionPolicy", matcher)); +} + +void RootFileSink::init(framework::InitContext& ictx) +{ +} + +void RootFileSink::run(framework::ProcessingContext& pctx) +{ + try { + RootFileStorage mStorage{ mFilePath, RootFileStorage::ReadMode::Update }; + for (const auto& input : InputRecordWalker(pctx.inputs())) { + auto moc = DataRefUtils::as(input); + if (moc == nullptr) { + ILOG(Error) << "Could not cast the input object to MonitorObjectCollection, skipping." << ENDM; + continue; + } + ILOG(Info, Support) << "Received MonitorObjectCollection '" << moc->GetName() << "'" << ENDM; + moc->postDeserialization(); + auto mwMOC = dynamic_cast(moc->cloneMovingWindow()); + + if (moc->GetEntries() > 0) { + mStorage.storeIntegralMOC(moc.get()); + } + if (mwMOC->GetEntries() > 0) { + mStorage.storeMovingWindowMOC(mwMOC); + } + } + } catch (const std::bad_alloc& ex) { + ILOG(Error, Ops) << "Caught a bad_alloc exception, there is probably a huge file or object present, but I will try to survive" << ENDM; + ILOG(Error, Support) << "Details: " << ex.what() << ENDM; + } catch (...) { + throw; + } + +#if defined(__linux__) && __has_include() + // Once we write object to TFile, the OS does not actually release the array memory from the heap, + // despite deleting the pointers. This function encourages the system to release it. + // Unfortunately there is no platform-independent method for this, while we see a similar + // (or even worse) behaviour on MacOS. + // See the ROOT forum issues for additional details: + // https://root-forum.cern.ch/t/should-the-result-of-tdirectory-getdirectory-be-deleted/53427 + malloc_trim(0); +#endif +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/RootFileSource.cxx b/Framework/src/RootFileSource.cxx new file mode 100644 index 0000000000..85cd1a9b27 --- /dev/null +++ b/Framework/src/RootFileSource.cxx @@ -0,0 +1,122 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RootFileSource.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/RootFileSource.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObjectCollection.h" +#include "QualityControl/RootFileStorage.h" + +#include +#include +#include +#include + +using namespace o2::framework; + +namespace o2::quality_control::core +{ +RootFileSource::RootFileSource(std::string filePath) + : mFilePath(std::move(filePath)) +{ +} + +void RootFileSource::init(framework::InitContext& ctx) +{ + auto const& deviceSpec = ctx.services().get(); + mAllowedOutputs.clear(); + mAllowedOutputs.reserve(deviceSpec.outputs.size()); + for (const auto& outputRoute : deviceSpec.outputs) { + mAllowedOutputs.push_back(outputRoute.matcher.binding); + } + + mRootFileManager = std::make_shared(mFilePath, RootFileStorage::ReadMode::Read); + if (mRootFileManager == nullptr) { + ILOG(Fatal, Ops) << "Could not open file '" << mFilePath << "'" << ENDM; + ctx.services().get().endOfStream(); + ctx.services().get().readyToQuit(false); + return; + } + ILOG(Info) << "Input file '" << mFilePath << "' successfully open." << ENDM; + + auto fileStructure = mRootFileManager->readStructure(false); + + mIntegralMocWalker = std::make_shared(fileStructure); + mMovingWindowMocWalker = std::make_shared(fileStructure); +} + +void RootFileSource::run(framework::ProcessingContext& ctx) +{ + if (mIntegralMocWalker->hasNextPath()) { + const auto& path = mIntegralMocWalker->nextPath(); + auto moc = mRootFileManager->readMonitorObjectCollection(path); + auto binding = outputBinding(moc->getDetector(), moc->getTaskName(), false); + + if (std::find_if(mAllowedOutputs.begin(), mAllowedOutputs.end(), + [binding](const auto& other) { return other.value == binding.value; }) == mAllowedOutputs.end()) { + ILOG(Error) << "The MonitorObjectCollection '" << binding.value << "' is not among declared output bindings: "; + for (const auto& output : mAllowedOutputs) { + ILOG(Error) << output.value << " "; + } + ILOG(Error) << ", skipping." << ENDM; + return; + } + // snapshot does a shallow copy, so we cannot let it delete elements in MOC when it deletes the MOC + moc->SetOwner(false); + ctx.outputs().snapshot(OutputRef{ binding.value, 0 }, *moc); + moc->postDeserialization(); + ILOG(Info) << "Read and published object '" << path << "'" << ENDM; + delete moc; + + return; + } + + if (mMovingWindowMocWalker->hasNextPath()) { + const auto& path = mMovingWindowMocWalker->nextPath(); + auto moc = mRootFileManager->readMonitorObjectCollection(path); + auto binding = outputBinding(moc->getDetector(), moc->getTaskName(), true); + + if (std::find_if(mAllowedOutputs.begin(), mAllowedOutputs.end(), + [binding](const auto& other) { return other.value == binding.value; }) == mAllowedOutputs.end()) { + ILOG(Error) << "The MonitorObjectCollection '" << binding.value << "' is not among declared output bindings: "; + for (const auto& output : mAllowedOutputs) { + ILOG(Error) << output.value << " "; + } + ILOG(Error) << ", skipping." << ENDM; + return; + } + // snapshot does a shallow copy, so we cannot let it delete elements in MOC when it deletes the MOC + moc->SetOwner(false); + ctx.outputs().snapshot(OutputRef{ binding.value, 0 }, *moc); + moc->postDeserialization(); + ILOG(Info) << "Read and published object '" << path << "'" << ENDM; + delete moc; + + return; + } + + mRootFileManager.reset(); + + ctx.services().get().endOfStream(); + ctx.services().get().readyToQuit(QuitRequest::Me); +} + +framework::OutputLabel + RootFileSource::outputBinding(const std::string& detectorCode, const std::string& taskName, bool movingWindow) +{ + return movingWindow ? framework::OutputLabel{ detectorCode + "-MW-" + taskName } : framework::OutputLabel{ detectorCode + "-" + taskName }; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/RootFileStorage.cxx b/Framework/src/RootFileStorage.cxx new file mode 100644 index 0000000000..ecfbc0a5cd --- /dev/null +++ b/Framework/src/RootFileStorage.cxx @@ -0,0 +1,373 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RootFileStorage.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/RootFileStorage.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObjectCollection.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/ValidityInterval.h" + +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::core +{ + +constexpr auto integralsDirectoryName = "int"; +constexpr auto movingWindowsDirectoryName = "mw"; + +RootFileStorage::RootFileStorage(const std::string& filePath, ReadMode readMode) +{ + switch (readMode) { + case ReadMode::Update: + mFile = new TFile(filePath.c_str(), "UPDATE"); + break; + case ReadMode::Read: + default: + mFile = new TFile(filePath.c_str(), "READ"); + } + if (mFile->IsZombie()) { + throw std::runtime_error("File '" + filePath + "' is zombie."); + } + if (!mFile->IsOpen()) { + throw std::runtime_error("Failed to open the file: " + filePath); + } + if (readMode == ReadMode::Update && !mFile->IsWritable()) { + throw std::runtime_error("File '" + filePath + "' is not writable."); + } + ILOG(Info) << "Output file '" << filePath << "' successfully open." << ENDM; +} + +RootFileStorage::DirectoryNode RootFileStorage::readStructure(bool loadObjects) const +{ + return readStructureImpl(mFile, loadObjects); +} + +RootFileStorage::DirectoryNode RootFileStorage::readStructureImpl(TDirectory* currentDir, bool loadObjects) const +{ + auto fullPath = currentDir->GetPath(); + auto pathToPos = std::strstr(fullPath, ":/"); + if (pathToPos == nullptr) { + ILOG(Error, Support) << "Could not extract path to node in string '" << currentDir->GetPath() << "', skipping" << ENDM; + return {}; + } + DirectoryNode currentNode{ pathToPos + 2, currentDir->GetName() }; + + TIter nextKey(currentDir->GetListOfKeys()); + TKey* key; + while ((key = (TKey*)nextKey())) { + if (!loadObjects && std::strcmp(key->GetClassName(), MonitorObjectCollection::Class_Name()) == 0) { + std::string mocPath = currentNode.fullPath + std::filesystem::path::preferred_separator + key->GetName(); + currentNode.children[key->GetName()] = MonitorObjectCollectionNode{ mocPath, key->GetName() }; + continue; + } + + ILOG(Debug, Devel) << "Getting the value for key '" << key->GetName() << "'" << ENDM; + auto* value = currentDir->Get(key->GetName()); + if (value == nullptr) { + ILOG(Error) << "Could not get the value '" << key->GetName() << "', skipping." << ENDM; + continue; + } + if (auto moc = dynamic_cast(value)) { + moc->postDeserialization(); + std::string mocPath = currentNode.fullPath + std::filesystem::path::preferred_separator + key->GetName(); + currentNode.children[moc->GetName()] = MonitorObjectCollectionNode{ mocPath, key->GetName(), moc }; + ILOG(Debug, Support) << "Read object '" << moc->GetName() << "' in path '" << currentNode.fullPath << "'" << ENDM; + } else if (auto childDir = dynamic_cast(value)) { + currentNode.children[key->GetName()] = readStructureImpl(childDir, loadObjects); + } else { + ILOG(Warning, Support) << "Could not cast the node to MonitorObjectCollection nor TDirectory, skipping." << ENDM; + delete value; + continue; + } + } + + return currentNode; +} + +MonitorObjectCollection* RootFileStorage::readMonitorObjectCollection(const std::string& path) const +{ + auto storedTObj = mFile->Get(path.c_str()); + if (storedTObj == nullptr) { + ILOG(Error, Ops) << "Could not read object '" << path << "'" << ENDM; + return nullptr; + } + auto storedMOC = dynamic_cast(storedTObj); + if (storedMOC == nullptr) { + ILOG(Error, Ops) << "Could not cast the stored object to MonitorObjectCollection" << ENDM; + delete storedTObj; + } + return storedMOC; +} + +RootFileStorage::~RootFileStorage() +{ + if (mFile != nullptr) { + if (mFile->IsOpen()) { + ILOG(Info, Support) << "Closing file '" << mFile->GetName() << "'." << ENDM; + mFile->Write(); + mFile->Close(); + } + delete mFile; + } +} + +void deleteTDirectory(TDirectory* d) +{ + if (d != nullptr) { + d->Write(); + d->Close(); + delete d; + } +} + +std::unique_ptr getOrCreateDirectory(TDirectory* parentDir, const char* dirName) +{ + auto dir = std::unique_ptr(parentDir->GetDirectory(dirName), deleteTDirectory); + if (dir == nullptr) { + ILOG(Debug, Support) << "Creating a new directory '" << dirName << "'." << ENDM; + dir = std::unique_ptr(parentDir->mkdir(dirName), deleteTDirectory); + } + return dir; +} + +validity_time_t earliestValidFrom(const MonitorObjectCollection* moc) +{ + validity_time_t earliest = std::numeric_limits::max(); + for (const auto& obj : *moc) { + if (auto mo = dynamic_cast(obj)) { + earliest = std::min(mo->getValidity().getMin(), earliest); + } + } + return earliest; +} + +bool validObjectValidities(const MonitorObjectCollection* moc) +{ + for (const auto& obj : *moc) { + if (auto mo = dynamic_cast(obj); mo->getValidity().isInvalid()) { + return false; + } + } + return true; +} + +// fixme we should not have to change the name! +void RootFileStorage::storeIntegralMOC(MonitorObjectCollection* const moc) +{ + const auto& mocStorageName = moc->getTaskName(); + if (mocStorageName.empty()) { + ILOG(Error, Support) << "taskName empty, not storing MOC '" << moc->GetName() << "' for detector '" << moc->getDetector() << "'" << ENDM; + return; + } + moc->SetName(mocStorageName.c_str()); + // directory level: int + auto integralDir = getOrCreateDirectory(mFile, integralsDirectoryName); + if (integralDir == nullptr) { + ILOG(Error, Support) << "Could not create the directory '" << integralsDirectoryName << "', skipping." << ENDM; + return; + } + + // directory level: int/DET + auto detector = moc->getDetector(); + auto detDir = getOrCreateDirectory(integralDir.get(), detector.c_str()); + if (detDir == nullptr) { + ILOG(Error, Support) << "Could not create directory '" << detector << "', skipping." << ENDM; + return; + } + + // directory level: int/DET/TASK + ILOG(Debug, Support) << "Checking for existing objects in the file." << ENDM; + int nbytes = 0; + auto storedMOC = std::unique_ptr(detDir->Get(mocStorageName.c_str())); + if (storedMOC != nullptr) { + storedMOC->postDeserialization(); + ILOG(Info, Support) << "Merging objects for task '" << detector << "/" << moc->getTaskName() << "' with the existing ones in the file." << ENDM; + storedMOC->merge(moc); + nbytes = detDir->WriteObject(storedMOC.get(), storedMOC->GetName(), "Overwrite"); + } else { + ILOG(Info, Support) << "Storing objects for task '" << detector << "/" << moc->getTaskName() << "' in the file." << ENDM; + nbytes = detDir->WriteObject(moc, moc->GetName(), "Overwrite"); + } + ILOG(Info, Support) << "Integrated objects '" << moc->GetName() << "' have been stored in the file (" << nbytes << " bytes)." << ENDM; +} + +void RootFileStorage::storeMovingWindowMOC(MonitorObjectCollection* const moc) +{ + if (moc->GetEntries() == 0) { + ILOG(Warning, Support) << "The provided MonitorObjectCollection '" << moc->GetName() << "' is empty, will not store." << ENDM; + return; + } + if (!validObjectValidities(moc)) { + // this should not happen, because we have a protection in MonitorObjectCollection::cloneMovingWindow() against it. + // thus, we should raise some concern if this occurs anyway. + ILOG(Warning, Ops) << "The provided MonitorObjectCollection '" << moc->GetName() << "' contains at least one object with invalid validity!!!" << ENDM; + } + // directory level: mw + auto mwDir = getOrCreateDirectory(mFile, movingWindowsDirectoryName); + if (mwDir == nullptr) { + ILOG(Error, Support) << "Could not create the directory '" << movingWindowsDirectoryName << "', skipping." << ENDM; + return; + } + + // directory level: mw/DET + auto detector = moc->getDetector(); + auto detDir = getOrCreateDirectory(mwDir.get(), detector.c_str()); + if (detDir == nullptr) { + ILOG(Error, Support) << "Could not create directory '" << detector << "', skipping." << ENDM; + return; + } + + // directory level: mw/DET/TASK + auto taskDir = getOrCreateDirectory(detDir.get(), moc->getTaskName().c_str()); + if (taskDir == nullptr) { + ILOG(Error, Support) << "Could not create directory '" << moc->getTaskName() << "', skipping." << ENDM; + return; + } + + // directory level: mw/DET/TASK/ + auto mocStorageName = std::to_string(earliestValidFrom(moc)); + moc->SetName(mocStorageName.c_str()); + ILOG(Info, Support) << "Checking for existing moving windows '" << mocStorageName << "' for task '" << detector << "/" << moc->getTaskName() << "' in the file." << ENDM; + int nbytes = 0; + auto storedMOC = std::unique_ptr(taskDir->Get(mocStorageName.c_str())); + if (storedMOC != nullptr) { + storedMOC->postDeserialization(); + ILOG(Info, Support) << "Merging moving windows '" << moc->GetName() << "' for task '" << moc->getDetector() << "/" << moc->getTaskName() << "' with the existing one in the file." << ENDM; + storedMOC->merge(moc); + nbytes = taskDir->WriteObject(storedMOC.get(), storedMOC->GetName(), "Overwrite"); + } else { + ILOG(Info, Support) << "Storing moving windows '" << moc->GetName() << "' for task '" << moc->getDetector() << "/" << moc->getTaskName() << "' in the file." << ENDM; + nbytes = taskDir->WriteObject(moc, moc->GetName(), "Overwrite"); + } + ILOG(Info, Support) << "Moving windows '" << moc->GetName() << "' for task '" << detector << "/" << moc->getTaskName() << "' has been stored in the file (" << nbytes << " bytes)." << ENDM; +} + +IntegralMocWalker::IntegralMocWalker(const RootFileStorage::DirectoryNode& rootNode) +{ + auto integralDirIt = rootNode.children.find(integralsDirectoryName); + if (integralDirIt == rootNode.children.end()) { + mPathIterator = mOrder.cbegin(); + return; + } + if (!std::holds_alternative(integralDirIt->second)) { + mPathIterator = mOrder.cbegin(); + return; + } + const auto& integralMocNode = std::get(integralDirIt->second); + std::stack> stack{}; + stack.push({ integralMocNode, integralMocNode.children.cbegin() }); + + while (!stack.empty()) { + auto& [currentNode, childIt] = stack.top(); + if (childIt == currentNode.children.end()) { + // move to the next child of the parent node + stack.pop(); + if (!stack.empty()) { + stack.top().second++; + } + } else if (std::holds_alternative(childIt->second)) { + // move to a child of the current node + const auto& childNode = std::get(childIt->second); + stack.push({ childNode, childNode.children.cbegin() }); + } else if (std::holds_alternative(childIt->second)) { + // move to the next child in the currentNode and return a path + const auto& childNode = std::get(childIt->second); + ++childIt; + mOrder.push_back(childNode.fullPath); + } else { + // unrecognized child node, move to the next child in the currentNode + ++childIt; + } + } + mPathIterator = mOrder.cbegin(); +} + +bool IntegralMocWalker::hasNextPath() +{ + return mPathIterator != mOrder.cend(); +} + +std::string IntegralMocWalker::nextPath() +{ + if (hasNextPath()) { + return *mPathIterator++; + } + return {}; +} + +MovingWindowMocWalker::MovingWindowMocWalker(const RootFileStorage::DirectoryNode& rootNode) +{ + auto movingWindowDirIt = rootNode.children.find(movingWindowsDirectoryName); + if (movingWindowDirIt == rootNode.children.end()) { + mPathIterator = mOrder.cbegin(); + return; + } + if (!std::holds_alternative(movingWindowDirIt->second)) { + mPathIterator = mOrder.cbegin(); + return; + } + auto movingWindowMocNode = std::get(movingWindowDirIt->second); + + std::stack> stack{}; + stack.push({ movingWindowMocNode, movingWindowMocNode.children.begin() }); + + // we walk over all the MOCs in the tree and we save them in chronological order + while (!stack.empty()) { + auto& [currentNode, childIt] = stack.top(); + + if (childIt == currentNode.children.end()) { + // move to the next child of the parent node + stack.pop(); + if (!stack.empty()) { + stack.top().second++; + } + } else if (std::holds_alternative(childIt->second)) { + // move to a child of the current node + auto& childNode = std::get(childIt->second); + stack.push({ childNode, childNode.children.begin() }); + } else if (std::holds_alternative(childIt->second)) { + // move to the next child in the currentNode and return a path + auto timestamp = std::stoull(childIt->first); + auto& childNode = std::get(childIt->second); + mOrder.emplace(timestamp, childNode.fullPath); + childIt++; + } else { + // unrecognized child node, move to the next child in the currentNode + childIt++; + } + } + mPathIterator = mOrder.cbegin(); +} + +bool MovingWindowMocWalker::hasNextPath() const +{ + return mPathIterator != mOrder.cend(); +} + +std::string MovingWindowMocWalker::nextPath() +{ + if (hasNextPath()) { + return (mPathIterator++)->second; + } + return {}; +} + +} // namespace o2::quality_control::core \ No newline at end of file diff --git a/Framework/src/SliceTrendingTask.cxx b/Framework/src/SliceTrendingTask.cxx new file mode 100644 index 0000000000..625257ad4b --- /dev/null +++ b/Framework/src/SliceTrendingTask.cxx @@ -0,0 +1,677 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SliceTrendingTask.cxx +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#include "QualityControl/SliceTrendingTask.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/ActivityHelpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; + +void SliceTrendingTask::configure(const boost::property_tree::ptree& config) +{ + mConfig = SliceTrendingTaskConfig(getID(), config); +} + +void SliceTrendingTask::initialize(Trigger t, framework::ServiceRegistryRef services) +{ + // removing leftovers from any previous runs + mTrend.reset(); + for (auto& [name, object] : mPlots) { + delete object; + object = nullptr; + } + + mPlots.clear(); + mReductors.clear(); + mSources.clear(); + + // Prepare the data structure of the trending TTree. + if (mConfig.resumeTrend) { + ILOG(Info, Support) << "Trying to retrieve an existing TTree for this task to continue the trend." << ENDM; + auto& qcdb = services.get(); + auto path = RepoPathUtils::getMoPath(mConfig.detectorName, PostProcessingInterface::getName(), "", "", false); + auto mo = qcdb.retrieveMO(path, PostProcessingInterface::getName(), repository::DatabaseInterface::Timestamp::Latest); + if (mo && mo->getObject()) { + auto tree = dynamic_cast(mo->getObject()); + if (tree) { + mTrend = std::unique_ptr(tree); + mo->setIsOwner(false); + } + } else { + ILOG(Warning, Support) << "Could not retrieve an existing TTree for this task." << ENDM; + } + } + if (mTrend == nullptr) { + ILOG(Info, Support) << "Generating new TTree for SliceTrending" << ENDM; + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + + mTrend->Branch("meta", &mMetaData, "runNumber/I"); + mTrend->Branch("time", &mTime); + for (const auto& source : mConfig.dataSources) { + mSources[source.name] = new std::vector(); + mTrend->Branch(source.name.c_str(), &mSources[source.name]); + } + } else { // we picked up an older TTree + mTrend->SetBranchAddress("meta", &(mMetaData.runNumber)); // TO-DO: Find reason why simply &mMetaData does not work + mTrend->SetBranchAddress("time", &mTime); + for (const auto& source : mConfig.dataSources) { + bool existingBranch = mTrend->GetBranchStatus(source.name.c_str()); + mSources[source.name] = new std::vector(); + if (existingBranch) { + mTrend->SetBranchAddress(source.name.c_str(), &mSources[source.name]); + } else { + mTrend->Branch(source.name.c_str(), &mSources[source.name]); + } + } + } + // Reductors + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create( + source.moduleName, source.reductorName)); + mReductors[source.name] = std::move(reductor); + } + + if (mConfig.producePlotsOnUpdate) { + getObjectsManager()->startPublishing(mTrend.get(), PublicationPolicy::ThroughStop); + } +} + +void SliceTrendingTask::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + trendValues(t, qcdb); + if (mConfig.producePlotsOnUpdate) { + generatePlots(); + } +} + +void SliceTrendingTask::finalize(Trigger t, framework::ServiceRegistryRef) +{ + if (!mConfig.producePlotsOnUpdate) { + getObjectsManager()->startPublishing(mTrend.get(), PublicationPolicy::ThroughStop); + } + + generatePlots(); + + for (const auto& source : mConfig.dataSources) { + delete mSources[source.name]; + mSources[source.name] = nullptr; + } +} + +void SliceTrendingTask::trendValues(const Trigger& t, + repository::DatabaseInterface& qcdb) +{ + if (mConfig.trendingTimestamp == "trigger") { + // ROOT expects seconds since epoch. + mTime = t.timestamp / 1000; + } else if (mConfig.trendingTimestamp == "validFrom") { + mTime = t.activity.mValidity.getMin() / 1000; + } else { // validUntil + mTime = t.activity.mValidity.getMax() / 1000; + } + mMetaData.runNumber = t.activity.mId; + std::snprintf(mMetaData.runNumberStr, MaxRunNumberStringLength + 1, "%d", t.activity.mId); + + for (auto& dataSource : mConfig.dataSources) { + mNumberPads[dataSource.name] = 0; + mSources[dataSource.name]->clear(); + if (dataSource.type == "repository") { + auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name, t.timestamp, t.activity, t.metadata); + TObject* obj = mo ? mo->getObject() : nullptr; + + mAxisDivision[dataSource.name] = dataSource.axisDivision; + mSliceLabel[dataSource.name] = dataSource.sliceLabels; + + if (obj) { + mReductors[dataSource.name]->update(obj, *mSources[dataSource.name], + dataSource.axisDivision, mNumberPads[dataSource.name]); + } else { + ILOG(Error, Support) << "Some objects could not be retrieved, will skip this trending cycle" << ENDM; + return; + } + + } else { + ILOG(Error, Support) << "Data source '" << dataSource.type << "' is not of type repository." << ENDM; + } + } + + mTrend->Fill(); +} // void SliceTrendingTask::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) + +void SliceTrendingTask::generatePlots() +{ + if (mTrend == nullptr) { + ILOG(Info, Support) << "The trend object is not there, won't generate any plots." << ENDM; + return; + } + + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, no plot generated." << ENDM; + return; + } + + ILOG(Info, Support) << "Generating " << mConfig.plots.size() << " plots." << ENDM; + for (const auto& plot : mConfig.plots) { + // Delete the existing plots before regenerating them. + if (mPlots.count(plot.name)) { + delete mPlots[plot.name]; + mPlots[plot.name] = nullptr; + } + + // Postprocess each pad (titles, axes, flushing buffers). + const std::size_t posEndVar = plot.varexp.find('.'); // Find the end of the dataSource. + const std::string varName(plot.varexp.substr(0, posEndVar)); + + // Draw the trending on a new canvas. + auto* c = new TCanvas(); + c->SetName(plot.name.c_str()); + c->SetTitle(plot.title.c_str()); + + TitleSettings titlesettings{ plot.legendObservableX, plot.legendObservableY, plot.legendUnitX, plot.legendUnitY, plot.legendCentmodeX, plot.legendCentmodeY }; + drawCanvasMO(c, plot.varexp, plot.name, plot.option, plot.graphErrors, mAxisDivision[varName], mSliceLabel[varName], titlesettings); + + int NumberPlots = 1; + if (plot.varexp.find(":time") != std::string::npos || plot.varexp.find(":run") != std::string::npos) { // we plot vs time, multiple plots on canvas possible + NumberPlots = mNumberPads[varName]; + } + for (int p = 0; p < NumberPlots; p++) { + c->cd(p + 1); + if (auto histo = dynamic_cast(c->cd(p + 1)->GetPrimitive("Graph"))) { + beautifyGraph(histo, plot, c); + // Manually empty the buffers before visualising the plot. + // histo->BufferEmpty(); // TBD: Should we keep it or not? Graph does not have this method.c + } else if (auto multigraph = dynamic_cast(c->cd(p + 1)->GetPrimitive("MultiGraph"))) { + if (auto legend = dynamic_cast(c->cd(2)->GetPrimitive("MultiGraphLegend"))) { + c->cd(1); + beautifyGraph(multigraph, plot, c); + c->cd(1)->SetLeftMargin(0.15); + c->cd(1)->SetRightMargin(0.01); + c->cd(2)->SetLeftMargin(0.01); + c->cd(2)->SetRightMargin(0.01); + beautifyLegend(legend, plot, c); + } else { + ILOG(Error, Support) << "No legend in multigraph-time" << ENDM; + c->cd(1); + beautifyGraph(multigraph, plot, c); + } + c->Modified(); + c->Update(); + } else if (auto histo = dynamic_cast(c->cd(p + 1)->GetPrimitive("Graph2D"))) { + + const std::string thisTitle = fmt::format("{0:s}", plot.title.data()); + histo->SetTitle(thisTitle.data()); + + if (!plot.graphAxisLabel.empty()) { + setUserAxisLabel(histo->GetXaxis(), histo->GetYaxis(), plot.graphAxisLabel); + c->Modified(); + c->Update(); + } + + if (!plot.graphYRange.empty()) { + float yMin, yMax; + getUserAxisRange(plot.graphYRange, yMin, yMax); + histo->SetMinimum(yMin); + histo->SetMaximum(yMax); + c->Modified(); + c->Update(); + } + + gStyle->SetPalette(kBird); + histo->SetStats(kFALSE); + } else { + ILOG(Error, Devel) << "Could not get the 'Graph' of the plot '" + << plot.name << "'." << ENDM; + } + } + + mPlots[plot.name] = c; + getObjectsManager()->startPublishing(c, PublicationPolicy::Once); + } +} // void SliceTrendingTask::generatePlots() + +void SliceTrendingTask::drawCanvasMO(TCanvas* thisCanvas, const std::string& var, + const std::string& name, const std::string& opt, const std::string& err, const std::vector>& axis, const std::vector>& sliceLabels, const TitleSettings& titlesettings) +{ + // Determine the order of the plot (1 - histo, 2 - graph, ...) + const size_t plotOrder = std::count(var.begin(), var.end(), ':') + 1; + + // Prepare the strings for the dataSource and its trending quantity. + std::string varName, typeName, trendType; + getTrendVariables(var, varName, typeName, trendType); + + std::string errXName, errYName; + getTrendErrors(err, errXName, errYName); + + // Divide the canvas into the correct number of pads. + if (trendType == "time" || trendType == "run") { + thisCanvas->DivideSquare(mNumberPads[varName]); // trending vs time: multiple plots per canvas possible + } else if (trendType == "multigraphtime" || trendType == "multigraphrun") { + thisCanvas->Divide(2, 1); + } else { + thisCanvas->DivideSquare(1); + } + + // Delete the graph errors after the plot is saved. //To-Do check if ownership is now taken + // Unfortunately the canvas does not take its ownership. + TGraphErrors* graphErrors = nullptr; + + // Setup the tree reader with the needed values. + TTreeReader myReader(mTrend.get()); + TTreeReaderValue retrieveTime(myReader, "time"); + TTreeReaderValue retrieveRun(myReader, "meta.runNumber"); + TTreeReaderValue> dataRetrieveVector(myReader, varName.data()); + + const int nuPa = mNumberPads[varName]; + const int nEntries = mTrend->GetEntriesFast(); + const int nEntriesTime = mTrend->GetBranch("time")->GetEntries(); + const int nEntriesRuns = mTrend->GetBranch("meta")->GetEntries(); + const int nEntriesData = mTrend->GetBranch(varName.data())->GetEntries(); + + bool useSliceLabels = false; + if (axis.size() == 1 && sliceLabels.size() == 1) { // currently we use custom labels only in the 1D case + if (axis[0].size() - 1 != sliceLabels[0].size() && sliceLabels[0].size() > 0) { + ILOG(Warning, Support) << "Slicing of 1D Objects: Labels do not match number of slices, using ranges as slice names" << ENDM; + } else { + useSliceLabels = true; + } + } + + // Fill the graph(errors) to be published. + if (trendType == "time" || trendType == "run") { + + const int nEffectiveEntries = (trendType == "time") ? std::min(nEntriesTime, nEntriesData) : std::min(nEntriesRuns, nEntriesData); + const int startPoint = (trendType == "time") ? nEntriesTime - nEffectiveEntries : nEntriesRuns - nEffectiveEntries; + + for (int p = 0; p < nuPa; p++) { + thisCanvas->cd(p + 1); + int iEntry = 0; + graphErrors = new TGraphErrors(nEffectiveEntries); + myReader.SetEntry(startPoint - 1); // startPoint-1 as myReader.Next() increments by one so that we then start at startPoint + + while (myReader.Next()) { + const double timeStamp = (trendType == "time") ? (double)(*retrieveTime) : (double)(*retrieveRun); + const double dataPoint = (dataRetrieveVector->at(p)).retrieveValue(typeName); + double errorX = 0.; + double errorY = 0.; + + if (!err.empty()) { + errorX = (dataRetrieveVector->at(p)).retrieveValue(errXName); + errorY = (dataRetrieveVector->at(p)).retrieveValue(errYName); + } + + graphErrors->SetPoint(iEntry, timeStamp, dataPoint); + graphErrors->SetPointError(iEntry, errorX, errorY); // Add Error to the last added point + + iEntry++; + } + + if (!useSliceLabels) { + graphErrors->SetTitle((dataRetrieveVector->at(p)).title.data()); + } else { + graphErrors->SetTitle(sliceLabels[0][p].data()); + } + + myReader.Restart(); + + if (!err.empty()) { + if (plotOrder != 2) { + ILOG(Info, Support) << "Non empty graphErrors seen for the plot '" << name + << "', which is not a graph, ignoring." << ENDM; + } else { + graphErrors->Draw(opt.data()); + } + } + } + } // Trending vs time + else if (trendType == "multigraphtime" || trendType == "multigraphrun") { + + auto multigraph = new TMultiGraph(); + multigraph->SetName("MultiGraph"); + + const int nEffectiveEntries = (trendType == "multigraphtime") ? std::min(nEntriesTime, nEntriesData) : std::min(nEntriesRuns, nEntriesData); + const int startPoint = (trendType == "multigraphtime") ? nEntriesTime - nEffectiveEntries : nEntriesRuns - nEffectiveEntries; + + for (int p = 0; p < nuPa; p++) { + int iEntry = 0; + auto gr = new TGraphErrors(nEffectiveEntries); + myReader.SetEntry(startPoint - 1); // startPoint-1 as myReader.Next() increments by one so that we then start at startPoint + + while (myReader.Next()) { + const double timeStamp = (trendType == "multigraphtime") ? (double)(*retrieveTime) : (double)(*retrieveRun); + const double dataPoint = (dataRetrieveVector->at(p)).retrieveValue(typeName); + double errorX = 0.; + double errorY = 0.; + + if (!err.empty()) { + errorX = (dataRetrieveVector->at(p)).retrieveValue(errXName); + errorY = (dataRetrieveVector->at(p)).retrieveValue(errYName); + } + + gr->SetPoint(iEntry, timeStamp, dataPoint); + gr->SetPointError(iEntry, errorX, errorY); // Add Error to the last added point + iEntry++; + } + + const std::string_view title = useSliceLabels ? sliceLabels[0][p] : (dataRetrieveVector->at(p)).title; + // const std::string_view title = (dataRetrieveVector->at(p)).title; + const auto posDivider = title.find("RangeX"); + if (posDivider != std::string_view::npos) { + auto rawtitle = title.substr(posDivider, -1); + gr->SetName(beautifyTitle(rawtitle, titlesettings).data()); + } else { + gr->SetName(title.data()); + } + + myReader.Restart(); + multigraph->Add(gr); + } // for (int p = 0; p < nuPa; p++) + + thisCanvas->cd(1); + std::string drawOpt = opt.empty() ? "A*L PMC PLC" : opt; + multigraph->Draw(drawOpt.c_str()); + + auto legend = new TLegend(0., 0.1, 0.95, 0.9); + legend->SetName("MultiGraphLegend"); + legend->SetNColumns(2); + legend->SetTextSize(2.0); + for (auto obj : *multigraph->GetListOfGraphs()) { + legend->AddEntry(obj, obj->GetName(), "lpf"); + } + thisCanvas->cd(2); + legend->Draw(); + + } // Trending vs Time as Multigraph + else if (trendType == "slices") { + + graphErrors = new TGraphErrors(nuPa); + thisCanvas->cd(1); + + myReader.SetEntry(nEntries - 1); // set event to last entry with index nEntries-1 + + int iEntry = 0; + for (int p = 0; p < nuPa; p++) { + + const double dataPoint = (dataRetrieveVector->at(p)).retrieveValue(typeName); + double errorX = 0.; + double errorY = 0.; + if (!err.empty()) { + errorX = (dataRetrieveVector->at(p)).retrieveValue(errXName); + errorY = (dataRetrieveVector->at(p)).retrieveValue(errYName); + } + const double xLabel = (dataRetrieveVector->at(p)).retrieveValue("sliceLabelX"); + + graphErrors->SetPoint(iEntry, xLabel, dataPoint); + graphErrors->SetPointError(iEntry, errorX, errorY); // Add Error to the last added point + + iEntry++; + } + + if (myReader.Next()) { + ILOG(Error, Devel) << "Entry beyond expected last entry" << ENDM; + } + + myReader.Restart(); + + if (!err.empty()) { + if (plotOrder != 2) { + ILOG(Info, Support) << "Non empty graphErrors seen for the plot '" << name + << "', which is not a graph, ignoring." << ENDM; + } else { + graphErrors->Draw(opt.data()); + } + } + } // Trending vs Slices + else if (trendType == "slices2D") { + + thisCanvas->cd(1); + const int xBins = axis[0].size(); + float xBoundaries[xBins]; + for (int i = 0; i < xBins; i++) { + xBoundaries[i] = axis[0][i]; + } + const int yBins = axis[1].size(); + float yBoundaries[yBins]; + for (int i = 0; i < yBins; i++) { + yBoundaries[i] = axis[1][i]; + } + + TH2F* graph2D = new TH2F("", "", xBins - 1, xBoundaries, yBins - 1, yBoundaries); + graph2D->SetName("Graph2D"); + thisCanvas->cd(1); + myReader.SetEntry(nEntries - 1); // set event to last entry with index nEntries-1 + + int iEntry = 0; + for (int p = 0; p < nuPa; p++) { + + const double dataPoint = (double)(dataRetrieveVector->at(p)).retrieveValue(typeName); + double error = 0.; + if (!err.empty()) { + error = (double)(dataRetrieveVector->at(p)).retrieveValue(errYName); + } + const double xLabel = (double)(dataRetrieveVector->at(p)).retrieveValue("sliceLabelX"); + const double yLabel = (double)(dataRetrieveVector->at(p)).retrieveValue("sliceLabelY"); + + graph2D->Fill(xLabel, yLabel, dataPoint); + graph2D->SetBinError(graph2D->GetXaxis()->FindBin(xLabel), graph2D->GetYaxis()->FindBin(yLabel), error); + + iEntry++; + } + + if (myReader.Next()) { + ILOG(Error, Devel) << "Entry beyond expected last entry" << ENDM; + } + + myReader.Restart(); + gStyle->SetPalette(kBird); + graph2D->Draw(opt.data()); + } // Trending vs Slices2D +} + +void SliceTrendingTask::getUserAxisRange(const std::string& graphAxisRange, float& limitLow, float& limitUp) +{ + const std::size_t posDivider = graphAxisRange.find(':'); + const std::string minString(graphAxisRange.substr(0, posDivider)); + const std::string maxString(graphAxisRange.substr(posDivider + 1)); + + limitLow = std::stof(minString); + limitUp = std::stof(maxString); +} + +void SliceTrendingTask::setUserAxisLabel(TAxis* xAxis, TAxis* yAxis, const std::string& graphAxisLabel) +{ + const std::size_t posDivider = graphAxisLabel.find(':'); + const std::string yLabel(graphAxisLabel.substr(0, posDivider)); + const std::string xLabel(graphAxisLabel.substr(posDivider + 1)); + + xAxis->SetTitle(xLabel.data()); + yAxis->SetTitle(yLabel.data()); +} + +void SliceTrendingTask::getTrendVariables(const std::string& inputvar, std::string& sourceName, std::string& variableName, std::string& trend) +{ + const std::size_t posEndVar = inputvar.find('.'); // Find the end of the dataSource. + const std::size_t posEndType = inputvar.find(':'); // Find the end of the quantity. + sourceName = inputvar.substr(0, posEndVar); + variableName = inputvar.substr(posEndVar + 1, posEndType - posEndVar - 1); + trend = inputvar.substr(posEndType + 1, -1); +} + +void SliceTrendingTask::getTrendErrors(const std::string& inputvar, std::string& errorX, std::string& errorY) +{ + const std::size_t posEndType_err = inputvar.find(':'); // Find the end of the error. + errorX = inputvar.substr(posEndType_err + 1); + errorY = inputvar.substr(0, posEndType_err); +} + +template +void SliceTrendingTask::beautifyGraph(T& graph, const SliceTrendingTaskConfig::Plot& plotconfig, TCanvas* canv) +{ + + // Set the title of the graph in a proper way. + std::string thisTitle; + if (plotconfig.varexp.find(":time") != std::string::npos) { + thisTitle = fmt::format("{0:s} - {1:s}", plotconfig.title.data(), graph->GetTitle()); // for plots vs time slicing might be applied for the title + } else { + thisTitle = fmt::format("{0:s}", plotconfig.title.data()); + } + graph->SetTitle(thisTitle.data()); + + // Set the user-defined range on the y axis if needed. + if (!plotconfig.graphYRange.empty()) { + float yMin, yMax; + getUserAxisRange(plotconfig.graphYRange, yMin, yMax); + graph->SetMinimum(yMin); + graph->SetMaximum(yMax); + canv->Modified(); + canv->Update(); + } + + if (!plotconfig.graphXRange.empty()) { + float xMin, xMax; + getUserAxisRange(plotconfig.graphXRange, xMin, xMax); + graph->GetXaxis()->SetLimits(xMin, xMax); + canv->Modified(); + canv->Update(); + } + + if (!plotconfig.graphAxisLabel.empty()) { + setUserAxisLabel(graph->GetXaxis(), graph->GetYaxis(), plotconfig.graphAxisLabel); + canv->Modified(); + canv->Update(); + } + + // Configure the time for the x axis. + if (plotconfig.varexp.find(":time") != std::string::npos || plotconfig.varexp.find(":multigraphtime") != std::string::npos) { + graph->GetXaxis()->SetTimeDisplay(1); + graph->GetXaxis()->SetNdivisions(505); + graph->GetXaxis()->SetTimeOffset(0.0); + graph->GetXaxis()->SetLabelOffset(0.02); + graph->GetXaxis()->SetTimeFormat("#splitline{%d.%m.%y}{%H:%M}"); + } else if (plotconfig.varexp.find(":meta.runNumber") != std::string::npos || plotconfig.varexp.find(":run") != std::string::npos || plotconfig.varexp.find(":multigraphrun") != std::string::npos) { + graph->GetXaxis()->SetNoExponent(true); + } +} + +void SliceTrendingTask::beautifyLegend(TLegend* leg, const SliceTrendingTaskConfig::Plot& plotconfig, TCanvas* canv) +{ + int ncolums = 2; + try { + ncolums = std::stoi(plotconfig.legendNColums); + } catch (...) { + ILOG(Error, Support) << "key legNColums must be integer" << ENDM; + } + leg->SetNColumns(ncolums); + + double textsize = 2.0; + try { + textsize = std::stod(plotconfig.legendTextSize); + } catch (...) { + ILOG(Error, Support) << "key legendTextSize must be double" << ENDM; + } + leg->SetTextSize(textsize); + + canv->Update(); + canv->Modified(); +} + +std::string SliceTrendingTask::beautifyTitle(const std::string_view rawtitle, const TitleSettings& settings) +{ + auto rangehandler = [](const std::string_view rangestring, const std::string_view observable, const std::string_view unit, bool centmode) -> std::string { + auto valuestring = rangestring.substr(rangestring.find("[")); + valuestring = valuestring.substr(1, valuestring.size() - 2); + std::stringstream parser(static_cast(valuestring)); + std::string tmp; + std::vector values; + while (std::getline(parser, tmp, ',')) { + values.emplace_back(std::stod(tmp)); + } + std::stringstream titlebuilder; + if (centmode) { + // centmode: only use observable and mean of the ranges (integer binning) + // usefull for indexed observable like hardware indices (modules, sectors, ...) + titlebuilder << observable << " " << (values[0] + values[1]) / 2; + if (unit.length()) { + titlebuilder << " " << unit; + } + } else { + // conventional range + titlebuilder << values[0]; + if (unit.length()) { + titlebuilder << " " << unit; + } + titlebuilder << " <= " << observable << "< " << values[1]; + if (unit.length()) { + titlebuilder << " " << unit; + } + } + return titlebuilder.str(); + }; + + std::string beautified; + int indexrangeX = rawtitle.find("RangeX"), + indexrangeY = rawtitle.find("RangeY"); + if (settings.observableX != "None" && indexrangeX != std::string::npos) { + auto rangestring = rawtitle.substr(indexrangeX); + rangestring = rangestring.substr(0, rangestring.find("]") + 1); + if (!settings.observableX.length()) { + beautified += rangestring.data(); + } else { + bool centmode = settings.centmodeX == "True"; + beautified += rangehandler(rangestring, settings.observableX, settings.unitX, centmode); + } + } + if (settings.observableY != "None" && indexrangeY != std::string::npos) { + if (beautified.length()) { + beautified += " and"; + } + auto rangestring = rawtitle.substr(indexrangeY); + rangestring = rangestring.substr(0, rangestring.find("]") + 1); + if (!settings.observableY.length()) { + beautified += rangestring.data(); + } else { + bool centmode = settings.centmodeY == "True"; + beautified += " " + rangehandler(rangestring, settings.observableY, settings.unitY, centmode); + } + } + + if (beautified == "") { + beautified = rawtitle; + } + + return beautified; +} diff --git a/Framework/src/SliceTrendingTaskConfig.cxx b/Framework/src/SliceTrendingTaskConfig.cxx new file mode 100644 index 0000000000..d5648b67e9 --- /dev/null +++ b/Framework/src/SliceTrendingTaskConfig.cxx @@ -0,0 +1,110 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SliceTrendingTaskConfig.cxx +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#include "QualityControl/SliceTrendingTaskConfig.h" +#include + +namespace o2::quality_control::postprocessing +{ + +SliceTrendingTaskConfig::SliceTrendingTaskConfig(const std::string& id, + const boost::property_tree::ptree& config) + : PostProcessingConfig(id, config) +{ + producePlotsOnUpdate = config.get("qc.postprocessing." + id + ".producePlotsOnUpdate", true); + resumeTrend = config.get("qc.postprocessing." + id + ".resumeTrend", false); + trendingTimestamp = config.get("qc.postprocessing." + id + ".trendingTimestamp", "validUntil"); + for (const auto& plotConfig : config.get_child("qc.postprocessing." + id + ".plots")) { + plots.push_back({ plotConfig.second.get("name"), + plotConfig.second.get("title", ""), + plotConfig.second.get("varexp"), + plotConfig.second.get("selection", ""), + plotConfig.second.get("option", ""), + plotConfig.second.get("graphErrors", ""), + plotConfig.second.get("graphYRange", ""), + plotConfig.second.get("graphXRange", ""), + plotConfig.second.get("graphAxisLabel", ""), + plotConfig.second.get("legendNColums", "2"), + plotConfig.second.get("legendTextSize", "2.0"), + plotConfig.second.get("legendObservableX", ""), + plotConfig.second.get("legendObservableY", ""), + plotConfig.second.get("legendUnitX", ""), + plotConfig.second.get("legendUnitY", ""), + plotConfig.second.get("legendCentmodeX", "False"), + plotConfig.second.get("legendCentmodeY", "False") }); + } + + // Loop over all the data sources to trend. + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + id + ".dataSources")) { + // Prepare the vector(vector) for the slicing. + std::vector> axisBoundaries; + std::vector singleAxis; + + std::vector> sliceLabels; + std::vector singleAxisLabels; + + if (const auto& multiAxisValues = dataSourceConfig.second.get_child_optional("axisDivision"); multiAxisValues.has_value()) { + for (const auto& multiAxisValue : multiAxisValues.value()) { + for (const auto& axis : multiAxisValue.second) { + singleAxis.push_back(std::stof(axis.second.data())); + } + axisBoundaries.push_back(singleAxis); + singleAxis.clear(); + } + } + + if (const auto& multiAxisLabels = dataSourceConfig.second.get_child_optional("sliceLabels"); multiAxisLabels.has_value()) { + for (const auto& multiAxisLabel : multiAxisLabels.value()) { + for (const auto& axis : multiAxisLabel.second) { + singleAxisLabels.push_back(std::string(axis.second.data())); + } + sliceLabels.push_back(singleAxisLabels); + singleAxisLabels.clear(); + } + } + + // Parse the vector of "names" or just get the "name" of sources. + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + sourceName.second.data(), + dataSourceConfig.second.get("reductorName"), + axisBoundaries, + sliceLabels, + dataSourceConfig.second.get("moduleName") }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here. + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name"), + dataSourceConfig.second.get("reductorName"), + axisBoundaries, + sliceLabels, + dataSourceConfig.second.get("moduleName") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + id + ".dataSources'"); + } + + axisBoundaries.clear(); + sliceLabels.clear(); + } +} + +} // namespace o2::quality_control::postprocessing diff --git a/Framework/src/TaskFactory.cxx b/Framework/src/TaskFactory.cxx new file mode 100644 index 0000000000..cf62fd1ea4 --- /dev/null +++ b/Framework/src/TaskFactory.cxx @@ -0,0 +1,35 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TaskFactory.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/TaskFactory.h" + +#include "QualityControl/RootClassFactory.h" + +namespace o2::quality_control::core +{ + +TaskInterface* TaskFactory::create(const TaskRunnerConfig& taskConfig, std::shared_ptr objectsManager) +{ + auto* result = root_class_factory::create(taskConfig.moduleName, taskConfig.className); + result->setName(taskConfig.name); + result->setObjectsManager(objectsManager); + result->setCustomParameters(taskConfig.customParameters); + result->setCcdbUrl(taskConfig.ccdbUrl); + + return result; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/TaskInterface.cxx b/Framework/src/TaskInterface.cxx index 4f1bb52f01..398ca070b1 100644 --- a/Framework/src/TaskInterface.cxx +++ b/Framework/src/TaskInterface.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -19,13 +20,9 @@ namespace o2::quality_control::core { -TaskInterface::TaskInterface(ObjectsManager* objectsManager) : mObjectsManager(objectsManager) {} - -TaskInterface::TaskInterface() : mObjectsManager(nullptr) {} - -const std::string& TaskInterface::getName() const { return mName; } - -void TaskInterface::setName(const std::string& name) { mName = name; } +TaskInterface::TaskInterface(ObjectsManager* objectsManager) : mObjectsManager(objectsManager) +{ +} void TaskInterface::setObjectsManager(std::shared_ptr objectsManager) { @@ -34,4 +31,28 @@ void TaskInterface::setObjectsManager(std::shared_ptr objectsMan std::shared_ptr TaskInterface::getObjectsManager() { return mObjectsManager; } +void TaskInterface::setMonitoring(const std::shared_ptr& mMonitoring) +{ + TaskInterface::mMonitoring = mMonitoring; +} + +void TaskInterface::setGlobalTrackingDataRequest(std::shared_ptr request) +{ + mGlobalTrackingDataRequest = std::move(request); +} + +const o2::globaltracking::DataRequest* TaskInterface::getGlobalTrackingDataRequest() const +{ + return mGlobalTrackingDataRequest.get(); +} + +void TaskInterface::finaliseCCDB(framework::ConcreteDataMatcher& matcher, void* obj) +{ +} + +void TaskInterface::configure() +{ + // noop, override it if you want. +} + } // namespace o2::quality_control::core diff --git a/Framework/src/TaskRunner.cxx b/Framework/src/TaskRunner.cxx index a9d06d8825..82baae8a3d 100644 --- a/Framework/src/TaskRunner.cxx +++ b/Framework/src/TaskRunner.cxx @@ -1,300 +1,522 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. /// -/// \file TaskDataProcessor.cxx +/// \file TaskRunner.cxx /// \author Barthelemy von Haller /// \author Piotr Konopka /// -#include -#include +#include "QualityControl/TaskRunner.h" -#include +#include // O2 -#include "Common/Exceptions.h" -#include "Configuration/ConfigurationFactory.h" -#include "Framework/RawDeviceService.h" -#include "Framework/DataSampling.h" -#include "Framework/CallbackService.h" -#include "Framework/DataSamplingPolicy.h" -#include "Monitoring/MonitoringFactory.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QualityControl/ObjectMetadataKeys.h" #include "QualityControl/QcInfoLogger.h" #include "QualityControl/TaskFactory.h" -#include "QualityControl/TaskRunner.h" +#include "QualityControl/runnerUtils.h" +#include "QualityControl/InfrastructureSpecReader.h" +#include "QualityControl/TaskRunnerFactory.h" +#include "QualityControl/ConfigParamGlo.h" +#include "QualityControl/ObjectsManager.h" +#include "QualityControl/Bookkeeping.h" +#include "QualityControl/TimekeeperFactory.h" +#include "QualityControl/ActivityHelpers.h" +#include "QualityControl/WorkflowType.h" +#include "QualityControl/runnerUtils.h" + +#include +#include +#include +#include + +using namespace std; + +const auto current_diagnostic = boost::current_exception_diagnostic_information; namespace o2::quality_control::core { +using namespace o2::framework; +using namespace o2::header; +using namespace o2::base; using namespace o2::configuration; using namespace o2::monitoring; using namespace std::chrono; +using namespace AliceO2::Common; -TaskRunner::TaskRunner(const std::string& taskName, const std::string& configurationSource, size_t id) - : mTaskName(taskName), - mMonitorObjectsSpec({ "mo" }, createTaskDataOrigin(), createTaskDataDescription(taskName), id), - mTask(nullptr), - mNumberBlocks(0), - mResetAfterPublish(false), - mLastNumberObjects(0), - mCycleOn(false), - mCycleNumber(0), - mTotalNumberObjectsPublished(0) +TaskRunner::TaskRunner(const TaskRunnerConfig& config) + : mTaskConfig(config) { - // setup configuration - mConfigFile = ConfigurationFactory::getConfiguration(configurationSource); - populateConfig(mTaskName); + o2::ccdb::BasicCCDBManager::instance().setFatalWhenNull(false); } -TaskRunner::~TaskRunner() = default; +TaskRunner::~TaskRunner() +{ + ILOG(Debug, Trace) << "TaskRunner destructor (" << this << ")" << ENDM; +} -void TaskRunner::initCallback(InitContext& iCtx) +void TaskRunner::init(InitContext& iCtx) { - QcInfoLogger::GetInstance() << "initializing TaskRunner" << AliceO2::InfoLogger::InfoLogger::endm; + core::initInfologger(iCtx, mTaskConfig.infologgerDiscardParameters, "task/" + mTaskConfig.name, mTaskConfig.detectorName); + ILOG(Info, Devel) << "Initializing TaskRunner" << ENDM; + + printTaskConfig(); + Bookkeeping::getInstance().init(mTaskConfig.bookkeepingUrl); // registering state machine callbacks - iCtx.services().get().set(framework::CallbackService::Id::Start, [this]() { start(); }); - iCtx.services().get().set(framework::CallbackService::Id::Stop, [this]() { stop(); }); - iCtx.services().get().set(framework::CallbackService::Id::Reset, [this]() { reset(); }); + try { + iCtx.services().get().set([this, services = iCtx.services()]() mutable { start(services); }); + iCtx.services().get().set([this, services = iCtx.services()]() { stop(services); }); + iCtx.services().get().set([this]() { reset(); }); + } catch (o2::framework::RuntimeErrorRef& ref) { + ILOG(Error) << "Error during initialization: " << o2::framework::error_from_ref(ref).what << ENDM; + } // setup monitoring - std::string monitoringUrl = mConfigFile->get("qc.config.monitoring.url", "infologger:///debug?qc"); // "influxdb-udp://aido2mon-gpn.cern.ch:8087" - mCollector = MonitoringFactory::Get(monitoringUrl); - mCollector->enableProcessMonitoring(); + mCollector = MonitoringFactory::Get(mTaskConfig.monitoringUrl); + mCollector->addGlobalTag(tags::Key::Subsystem, tags::Value::QC); + mCollector->addGlobalTag("TaskName", mTaskConfig.name); + mCollector->addGlobalTag("DetectorName", mTaskConfig.detectorName); // setup publisher - mObjectsManager = std::make_shared(mTaskConfig); + mObjectsManager = std::make_shared(mTaskConfig.name, mTaskConfig.className, mTaskConfig.detectorName, mTaskConfig.parallelTaskID); + mObjectsManager->setMovingWindowsList(mTaskConfig.movingWindows); + + // setup timekeeping + mDeploymentMode = DefaultsHelpers::deploymentMode(); + auto windowLengthMs = mTaskConfig.movingWindows.empty() ? 0 : (mTaskConfig.cycleDurations.back().first * 1000); + mTimekeeper = TimekeeperFactory::create(mDeploymentMode, windowLengthMs); + mTimekeeper->setCCDBOrbitsPerTFAccessor([]() { + // getNHBFPerTF() returns 128 if it does not know, which can be very misleading. + // instead we use 0, which will trigger another try when processing another timeslice. + return o2::base::GRPGeomHelper::instance().getGRPECS() != nullptr ? o2::base::GRPGeomHelper::getNHBFPerTF() : 0; + }); // setup user's task - TaskFactory f; - mTask.reset(f.create(mTaskConfig, mObjectsManager)); + mTask.reset(TaskFactory::create(mTaskConfig, mObjectsManager)); + mTask->setMonitoring(mCollector); + mTask->setGlobalTrackingDataRequest(mTaskConfig.globalTrackingDataRequest); + mTask->setDatabase(mTaskConfig.repository); + + // load config params + if (!ConfigParamGlo::keyValues.empty()) { + conf::ConfigurableParam::updateFromString(ConfigParamGlo::keyValues); + } + // load reco helpers + if (mTaskConfig.grpGeomRequest) { + GRPGeomHelper::instance().setRequest(mTaskConfig.grpGeomRequest); + } // init user's task mTask->initialize(iCtx); + + mNoMoreCycles = false; + mCycleNumber = 0; } -void TaskRunner::processCallback(ProcessingContext& pCtx) +void TaskRunner::run(ProcessingContext& pCtx) { - if (mTaskConfig.maxNumberCycles >= 0 && mCycleNumber >= mTaskConfig.maxNumberCycles) { - LOG(INFO) << "The maximum number of cycles (" << mTaskConfig.maxNumberCycles << ") has been reached."; + if (mNoMoreCycles) { + ILOG(Info, Support) << "The maximum number of cycles (" << mTaskConfig.maxNumberCycles << ") has been reached" + << " or the device has received an EndOfStream signal. Won't start a new cycle." << ENDM; return; } if (!mCycleOn) { - QcInfoLogger::GetInstance() << "cycle " << mCycleNumber << AliceO2::InfoLogger::InfoLogger::endm; - - mTask->startOfCycle(); + startCycle(); + } - mNumberBlocks = 0; - mCycleOn = true; + if (mTaskConfig.grpGeomRequest) { + GRPGeomHelper::instance().checkUpdates(pCtx); } - mTask->monitorData(pCtx); - mNumberBlocks++; + if (mTimekeeper->shouldFinishCycle(pCtx.services().get())) { + mTimekeeper->updateByCurrentTimestamp(pCtx.services().get().timeslice / 1000); + finishCycle(pCtx.outputs()); + if (mTaskConfig.resetAfterCycles > 0 && (mCycleNumber % mTaskConfig.resetAfterCycles == 0)) { + mTask->reset(); + mTimekeeper->reset(); + } + if (mTaskConfig.maxNumberCycles < 0 || mCycleNumber < mTaskConfig.maxNumberCycles) { + startCycle(); + } else { + mNoMoreCycles = true; + } + } - // if 10 s we publish stats - if (mStatsTimer.isTimeout()) { - double current = mStatsTimer.getTime(); - int objectsPublished = (mTotalNumberObjectsPublished - mLastNumberObjects); - mLastNumberObjects = mTotalNumberObjectsPublished; - mCollector->send({ objectsPublished / current, "QC_task_Rate_objects_published_per_10_seconds" }); - mStatsTimer.increment(); + if (isDataReady(pCtx.inputs())) { + mTimekeeper->updateByTimeFrameID(pCtx.services().get().tfCounter); + mTask->monitorData(pCtx); + updateMonitoringStats(pCtx); + } +} - // temporarily here, until timer callback is implemented in dpl - timerCallback(pCtx); - if (mResetAfterPublish) { - mTask->reset(); +void TaskRunner::finaliseCCDB(ConcreteDataMatcher& matcher, void* obj) +{ + if (mTaskConfig.grpGeomRequest) { + if (!GRPGeomHelper::instance().finaliseCCDB(matcher, obj)) { + ILOG(Warning, Devel) << "Could not update CCDB objects requested by GRPGeomHelper" << ENDM; } } + mTask->finaliseCCDB(matcher, obj); } -void TaskRunner::timerCallback(ProcessingContext& pCtx) { finishCycle(pCtx.outputs()); } +CompletionPolicy::CompletionOp TaskRunner::completionPolicyCallback(o2::framework::InputSpan const& inputs, std::vector const& specs, ServiceRegistryRef&) +{ + struct InputCount { + size_t seen = 0; + size_t expected = 0; + }; + + InputCount dataInputs; + InputCount timerInputs; + InputCount conditionInputs; + CompletionPolicy::CompletionOp action = CompletionPolicy::CompletionOp::Wait; + + assert(inputs.size() == specs.size()); + for (size_t i = 0; i < inputs.size(); ++i) { + const auto header = inputs.header(i); + const auto& spec = specs[i]; + const bool headerPresent = header != nullptr; + + if (spec.lifetime == Lifetime::Timer) { + timerInputs.seen += headerPresent; + timerInputs.expected += 1; + } else if (spec.lifetime == Lifetime::Condition) { + conditionInputs.seen += headerPresent; + conditionInputs.expected += 1; + } else { + // we do not expect any concrete Lifetimes to be data to leave the room open for new ones + dataInputs.seen += headerPresent; + dataInputs.expected += 1; + } + } -void TaskRunner::setResetAfterPublish(bool resetAfterPublish) { mResetAfterPublish = resetAfterPublish; } + if ((dataInputs.expected == dataInputs.seen && conditionInputs.expected == conditionInputs.seen) || timerInputs.seen > 0) { + action = CompletionPolicy::CompletionOp::Consume; + } -header::DataOrigin TaskRunner::createTaskDataOrigin() + ILOG(Debug, Trace) << "Input summary (seen/expected): " + << "data " << dataInputs.seen << "/" << dataInputs.expected << ", " + << "timer " << timerInputs.seen << "/" << timerInputs.expected << ", " + << "condition " << conditionInputs.seen << "/" << conditionInputs.expected + << ". Action taken: " << action << ENDM; + + return action; +} + +std::string TaskRunner::createTaskRunnerIdString() { - return header::DataOrigin{ "QC" }; + return { "qc-task" }; } -header::DataDescription TaskRunner::createTaskDataDescription(const std::string& taskName) +header::DataDescription TaskRunner::createTimerDataDescription(const std::string& taskName) { + if (taskName.empty()) { + BOOST_THROW_EXCEPTION(FatalException() << errinfo_details("Empty taskName for timers's data description")); + } + // hash the taskName to avoid clashing if the name is long and the beginning is identical + auto hashedName = std::hash{}(taskName); + hashedName = hashedName % 10000000000LU; // 10 characters max + std::ostringstream ss; + ss << std::setw(10) << std::setfill('0') << hashedName; // 10 characters min o2::header::DataDescription description; - description.runtimeInit(std::string(taskName.substr(0, header::DataDescription::size - 3) + "-mo").c_str()); + description.runtimeInit(std::string("TIMER-" + ss.str()).substr(0, header::DataDescription::size).c_str()); return description; } -void TaskRunner::start() +void TaskRunner::endOfStream(framework::EndOfStreamContext& eosContext) { - startOfActivity(); + if (!mCycleOn && mCycleNumber == 0) { + ILOG(Error, Support) << "An EndOfStream was received before TaskRunner could start the first cycle, probably the device was not started. Something is wrong, doing nothing." << ENDM; + } else { + ILOG(Info, Trace) << "Updating timekeeper with a current timestamp upon receiving an EoS message" << ENDM; + mTimekeeper->updateByCurrentTimestamp(getCurrentTimestamp()); + if (mTaskConfig.disableLastCycle) { + ILOG(Info, Devel) << "Received an EndOfStream, but the last cycle is disabled" << ENDM; + } else { + ILOG(Info, Devel) << "Received an EndOfStream, finishing the current cycle" << ENDM; + finishCycle(eosContext.outputs()); + } + } + mNoMoreCycles = true; +} - mStatsTimer.reset(10000000); // 10 s. - mLastNumberObjects = 0; +void TaskRunner::start(ServiceRegistryRef services) +{ + mActivity = o2::quality_control::core::computeActivity(services, mTaskConfig.fallbackActivity); + QcInfoLogger::setRun(mActivity.mId); + QcInfoLogger::setPartition(mActivity.mPartitionName); - QcInfoLogger::GetInstance() << "cycle " << mCycleNumber << AliceO2::InfoLogger::InfoLogger::endm; - mNumberBlocks = 0; - mCycleOn = true; + mNoMoreCycles = false; + mCycleNumber = 0; + + try { + startOfActivity(); + startCycle(); + } catch (...) { + // we catch here because we don't know where it will go in DPL's CallbackService + ILOG(Error, Support) << "Error caught in start() :" + << current_diagnostic(true) << ENDM; + throw; + } } -void TaskRunner::stop() +void TaskRunner::stop(ServiceRegistryRef services) { - if (mCycleOn) { - mTask->endOfCycle(); - mCycleNumber++; - mCycleOn = false; + try { + mActivity = o2::quality_control::core::computeActivity(services, mActivity); + if (mCycleOn) { + mTask->endOfCycle(); + mCycleNumber++; + mCycleOn = false; + } + endOfActivity(); + mTask->reset(); + } catch (...) { + // we catch here because we don't know where it will go in DPL's CallbackService + ILOG(Error, Support) << "Error caught in stop() : " + << current_diagnostic(true) << ENDM; + throw; } - endOfActivity(); - mTask->reset(); } void TaskRunner::reset() { - mTask.reset(); - mCollector.reset(); - mObjectsManager.reset(); + try { + mTask.reset(); + mCollector.reset(); + mObjectsManager.reset(); + mTimekeeper.reset(); + mActivity = Activity(); + } catch (...) { + // we catch here because we don't know where it will go in DPL's CallbackService + ILOG(Error, Support) << "Error caught in reset() : " + << current_diagnostic(true) << ENDM; + throw; + } } -void TaskRunner::populateConfig(std::string taskName) +bool TaskRunner::isDataReady(const framework::InputRecord& inputs) { - try { - auto tasksConfigList = mConfigFile->getRecursive("qc.tasks"); - auto taskConfigTree = tasksConfigList.find(taskName); - if (taskConfigTree == tasksConfigList.not_found()) { - throw; - } + size_t dataInputsPresent = 0; - mTaskConfig.taskName = taskName; - mTaskConfig.moduleName = taskConfigTree->second.get("moduleName"); - mTaskConfig.className = taskConfigTree->second.get("className"); - mTaskConfig.cycleDurationSeconds = taskConfigTree->second.get("cycleDurationSeconds", 10); - mTaskConfig.maxNumberCycles = taskConfigTree->second.get("maxNumberCycles", -1); - - auto policiesFilePath = mConfigFile->get("dataSamplingPolicyFile", ""); - ConfigurationInterface* config = policiesFilePath.empty() ? mConfigFile.get() : ConfigurationFactory::getConfiguration(policiesFilePath).get(); - auto policiesTree = config->getRecursive("dataSamplingPolicies"); - auto dataSourceTree = taskConfigTree->second.get_child("dataSource"); - std::string type = dataSourceTree.get("type"); - - if (type == "dataSamplingPolicy") { - auto policyName = dataSourceTree.get("name"); - LOG(INFO) << "policyName : " << policyName; - mInputSpecs = framework::DataSampling::InputSpecsForPolicy(config, policyName); - } else if (type == "direct") { - - auto subSpecString = dataSourceTree.get("subSpec"); - auto subSpec = std::strtoull(subSpecString.c_str(), nullptr, 10); - - header::DataOrigin origin; - header::DataDescription description; - origin.runtimeInit(dataSourceTree.get("dataOrigin").c_str()); - description.runtimeInit(dataSourceTree.get("dataDescription").c_str()); - - mInputSpecs.push_back( - InputSpec{ - dataSourceTree.get("binding"), - origin, - description, - subSpec }); + for (auto& input : inputs) { + if (input.header != nullptr) { - } else { - std::string message = std::string("Configuration error : dataSource type unknown : ") + type; // TODO pass this message to the exception - BOOST_THROW_EXCEPTION(AliceO2::Common::FatalException() << AliceO2::Common::errinfo_details(message)); + const auto* dataHeader = get(input.header); + assert(dataHeader); + + if (strncmp(dataHeader->dataDescription.str, "TIMER", 5)) { + dataInputsPresent++; + } } + } - } catch (...) { // catch already here the configuration exception and print it - // because if we are in a constructor, the exception could be lost - std::string diagnostic = boost::current_exception_diagnostic_information(); - LOG(ERROR) << "Unexpected exception, diagnostic information follows:\n" << diagnostic; - throw; + return dataInputsPresent == inputs.size() - 1; +} + +void TaskRunner::printTaskConfig() const +{ + ILOG(Info, Devel) << "Configuration loaded > Task name : " << mTaskConfig.name // + << " / Module name : " << mTaskConfig.moduleName // + << " / Detector name : " << mTaskConfig.detectorName // + << " / Max number cycles : " << mTaskConfig.maxNumberCycles // + << " / critical : " << mTaskConfig.critical // + << " / Save to file : " << mTaskConfig.saveToFile + << " / Cycle duration seconds : "; + for (auto& [cycleDuration, period] : mTaskConfig.cycleDurations) { + ILOG(Info, Devel) << cycleDuration << "s during " << period << "s, "; } - LOG(INFO) << "Configuration loaded : "; - LOG(INFO) << ">> Task name : " << mTaskConfig.taskName; - LOG(INFO) << ">> Module name : " << mTaskConfig.moduleName; - LOG(INFO) << ">> Cycle duration seconds : " << mTaskConfig.cycleDurationSeconds; - LOG(INFO) << ">> Max number cycles : " << mTaskConfig.maxNumberCycles; + ILOG(Info, Devel) << ENDM; } void TaskRunner::startOfActivity() { + // stats mTimerTotalDurationActivity.reset(); - Activity activity(mConfigFile->get("qc.config.Activity.number"), - mConfigFile->get("qc.config.Activity.type")); - mTask->startOfActivity(activity); + mTotalNumberObjectsPublished = 0; + + // Start activity in module's task and update objectsManager + ILOG(Info, Support) << "Starting run " << mActivity.mId << ENDM; + mObjectsManager->setActivity(mActivity); + + auto now = getCurrentTimestamp(); + mTimekeeper->setStartOfActivity(mActivity.mValidity.getMin(), mTaskConfig.fallbackActivity.mValidity.getMin(), now, activity_helpers::getCcdbSorTimeAccessor(mActivity.mId)); + mTimekeeper->updateByCurrentTimestamp(mTimekeeper->getActivityDuration().getMin()); + mTimekeeper->setEndOfActivity(mActivity.mValidity.getMax(), mTaskConfig.fallbackActivity.mValidity.getMax(), now, activity_helpers::getCcdbEorTimeAccessor(mActivity.mId)); + + mCollector->setRunNumber(mActivity.mId); + mTask->startOfActivity(mActivity); } void TaskRunner::endOfActivity() { - Activity activity(mConfigFile->get("qc.config.Activity.number"), - mConfigFile->get("qc.config.Activity.type")); - mTask->endOfActivity(activity); + ILOG(Info, Support) << "Stopping run " << mActivity.mId << ENDM; + + auto now = getCurrentTimestamp(); + mTimekeeper->updateByCurrentTimestamp(now); + mTimekeeper->setEndOfActivity(mActivity.mValidity.getMax(), mTaskConfig.fallbackActivity.mValidity.getMax(), now, activity_helpers::getCcdbEorTimeAccessor(mActivity.mId)); + + mTask->endOfActivity(mObjectsManager->getActivity()); + mObjectsManager->stopPublishing(PublicationPolicy::ThroughStop); double rate = mTotalNumberObjectsPublished / mTimerTotalDurationActivity.getTime(); - mCollector->send({ rate, "QC_task_Rate_objects_published_per_second_whole_run" }); - mCollector->send({ ba::mean(mPCpus), "QC_task_Mean_pcpu_whole_run" }); - mCollector->send({ ba::mean(mPMems), "QC_task_Mean_pmem_whole_run" }); + mCollector->send(Metric{ "qc_objects_published" }.addValue(rate, "per_second_whole_run")); +} + +void TaskRunner::startCycle() +{ + ILOG(Debug, Support) << "Start cycle " << mCycleNumber << ENDM; + mTask->startOfCycle(); + mNumberMessagesReceivedInCycle = 0; + mNumberObjectsPublishedInCycle = 0; + mDataReceivedInCycle = 0; + mTimerDurationCycle.reset(); + mCycleOn = true; +} + +void TaskRunner::registerToBookkeeping() +{ + if (!gSystem->Getenv("O2_QC_DONT_REGISTER_IN_BK")) { // Set this variable to disable the registration + // register ourselves to the BK at the first cycle + ILOG(Debug, Devel) << "Registering taskRunner to BookKeeping" << ENDM; + Bookkeeping::getInstance().registerProcess(mActivity.mId, mTaskConfig.name, mTaskConfig.detectorName, bkp::DplProcessType::QC_TASK, ""); + } } void TaskRunner::finishCycle(DataAllocator& outputs) { + ILOG(Debug, Support) << "Finish cycle " << mCycleNumber << ENDM; + // in the async context we print only info/ops logs, it's easier to temporarily elevate this log + ((mDeploymentMode == DeploymentMode::Grid) ? ILOG(Info, Ops) : ILOG(Info, Devel)) // + << "The objects validity is " + << "(" << mTimekeeper->getValidity().getMin() << ", " << mTimekeeper->getValidity().getMax() << "), " + << "(" << mTimekeeper->getSampleTimespan().getMin() << ", " << mTimekeeper->getSampleTimespan().getMax() << "), " + << "(" << mTimekeeper->getTimerangeIdRange().getMin() << ", " << mTimekeeper->getTimerangeIdRange().getMax() << ")" << ENDM; mTask->endOfCycle(); - double durationCycle = 0; // (boost::posix_time::seconds(mTaskConfig.cycleDurationSeconds) - - // mCycleTimer->expires_from_now()).total_nanoseconds() / double(1e9); - // mCycleTimer->expires_at(mCycleTimer->expires_at() + - // boost::posix_time::seconds(mTaskConfig.cycleDurationSeconds)); - - // publication - unsigned long numberObjectsPublished = publish(outputs); - - // monitoring metrics - double durationPublication = 0; // (boost::posix_time::seconds(mTaskConfig.cycleDurationSeconds) - - // mCycleTimer->expires_from_now()).total_nanoseconds() / double(1e9); - mCollector->send({ mNumberBlocks, "QC_task_Numberofblocks_in_cycle" }); - mCollector->send({ durationCycle, "QC_task_Module_cycle_duration" }); - mCollector->send({ durationPublication, "QC_task_Publication_duration" }); - mCollector->send({ (int)numberObjectsPublished, - "QC_task_Number_objects_published_in_cycle" }); // cast due to Monitoring accepting only int - double rate = numberObjectsPublished / (durationCycle + durationPublication); - mCollector->send({ rate, "QC_task_Rate_objects_published_per_second" }); - mTotalNumberObjectsPublished += numberObjectsPublished; - // std::vector pidStatus = mMonitor->getPIDStatus(::getpid()); - // mPCpus(std::stod(pidStatus[3])); - // mPMems(std::stod(pidStatus[4])); - double whole_run_rate = mTotalNumberObjectsPublished / mTimerTotalDurationActivity.getTime(); - mCollector->send({ mTotalNumberObjectsPublished, "QC_task_Total_objects_published_whole_run" }); - mCollector->send({ mTimerTotalDurationActivity.getTime(), "QC_task_Total_duration_activity_whole_run" }); - mCollector->send({ whole_run_rate, "QC_task_Rate_objects_published_per_second_whole_run" }); - // mCollector->send({std::stod(pidStatus[3]), "QC_task_Mean_pcpu_whole_run"}); - mCollector->send({ ba::mean(mPMems), "QC_task_Mean_pmem_whole_run" }); + if (mCycleNumber == 0) { // register at the end of the first cycle + registerToBookkeeping(); + } + + mObjectsManager->setValidity(mTimekeeper->getValidity()); + mNumberObjectsPublishedInCycle += publish(outputs); + mTotalNumberObjectsPublished += mNumberObjectsPublishedInCycle; + saveToFile(); + + publishCycleStats(); mCycleNumber++; mCycleOn = false; if (mTaskConfig.maxNumberCycles == mCycleNumber) { - LOG(INFO) << "The maximum number of cycles (" << mTaskConfig.maxNumberCycles << ") has been reached." - << " The task will not do anything from now on."; + ILOG(Info, Support) << "The maximum number of cycles (" << mTaskConfig.maxNumberCycles << ") has been reached." + << " The task will not do anything from now on." << ENDM; } } -unsigned long TaskRunner::publish(DataAllocator& outputs) +void TaskRunner::updateMonitoringStats(ProcessingContext& pCtx) { - outputs.adopt( - Output{ mMonitorObjectsSpec.origin, - mMonitorObjectsSpec.description, - mMonitorObjectsSpec.subSpec, - mMonitorObjectsSpec.lifetime }, - dynamic_cast(mObjectsManager->getNonOwningArray()) - ); - - return 1; + mNumberMessagesReceivedInCycle++; + for (const auto& input : InputRecordWalker(pCtx.inputs())) { + const auto* inputHeader = DataRefUtils::getHeader(input); + auto payloadSize = DataRefUtils::getPayloadSize(input); + if (inputHeader == nullptr) { + ILOG(Warning, Devel) << "No DataHeader found in message, ignoring this one for the statistics." << ENDM; + continue; + } + mDataReceivedInCycle += inputHeader->headerSize + payloadSize; + } +} + +void TaskRunner::publishCycleStats() +{ + double cycleDuration = mTimerDurationCycle.getTime(); + double rate = mNumberObjectsPublishedInCycle / (cycleDuration + mLastPublicationDuration); + double rateMessagesReceived = mNumberMessagesReceivedInCycle / (cycleDuration + mLastPublicationDuration); + double rateDataReceived = mDataReceivedInCycle / (cycleDuration + mLastPublicationDuration); + double wholeRunRate = mTotalNumberObjectsPublished / mTimerTotalDurationActivity.getTime(); + double totalDurationActivity = mTimerTotalDurationActivity.getTime(); + + mCollector->send(Metric{ "qc_data_received" } + .addValue(mNumberMessagesReceivedInCycle, "messages_in_cycle") + .addValue(rateMessagesReceived, "messages_per_second") + .addValue(mDataReceivedInCycle, "data_in_cycle") + .addValue(rateDataReceived, "data_per_second")); + + mCollector->send(Metric{ "qc_duration" } + .addValue(cycleDuration, "module_cycle") + .addValue(mLastPublicationDuration, "publication") + .addValue(totalDurationActivity, "activity_whole_run")); + + mCollector->send(Metric{ "qc_objects_published" } + .addValue(mNumberObjectsPublishedInCycle, "in_cycle") + .addValue(rate, "per_second") + .addValue(mTotalNumberObjectsPublished, "whole_run") + .addValue(wholeRunRate, "per_second_whole_run")); +} + +int TaskRunner::publish(DataAllocator& outputs) +{ + ILOG(Debug, Support) << "Publishing " << mObjectsManager->getNumberPublishedObjects() << " MonitorObjects" << ENDM; + AliceO2::Common::Timer publicationDurationTimer; + + auto concreteOutput = framework::DataSpecUtils::asConcreteDataMatcher(mTaskConfig.moSpec); + // getNonOwningArray creates a TObjArray containing the monitoring objects, but not + // owning them. The array is created by new and must be cleaned up by the caller + std::unique_ptr array(mObjectsManager->getNonOwningArray()); + array->addOrUpdateMetadata(repository::metadata_keys::cycleNumber, std::to_string(mCycleNumber)); + int objectsPublished = array->GetEntries(); + + outputs.snapshot( + Output{ concreteOutput.origin, + concreteOutput.description, + concreteOutput.subSpec }, + *array); + + mLastPublicationDuration = publicationDurationTimer.getTime(); + mObjectsManager->stopPublishing(PublicationPolicy::Once); + return objectsPublished; +} + +void TaskRunner::saveToFile() +{ + if (!mTaskConfig.saveToFile.empty()) { + ILOG(Debug, Support) << "Save data to file " << mTaskConfig.saveToFile << ENDM; + TFile f(mTaskConfig.saveToFile.c_str(), "RECREATE"); + for (size_t i = 0; i < mObjectsManager->getNumberPublishedObjects(); i++) { + mObjectsManager->getMonitorObject(i)->getObject()->Write(); + } + f.Close(); + } } } // namespace o2::quality_control::core diff --git a/Framework/src/TaskRunnerFactory.cxx b/Framework/src/TaskRunnerFactory.cxx index 57abd8d0ac..91ff6a3930 100644 --- a/Framework/src/TaskRunnerFactory.cxx +++ b/Framework/src/TaskRunnerFactory.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -13,41 +14,224 @@ /// \author Piotr Konopka /// +#include "QualityControl/CommonSpec.h" #include "QualityControl/TaskRunnerFactory.h" #include "QualityControl/TaskRunner.h" +#include "QualityControl/TaskRunnerConfig.h" +#include "QualityControl/TaskSpec.h" +#include "QualityControl/InfrastructureSpecReader.h" +#include "QualityControl/TimekeeperFactory.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DataHeaderHelpers.h" +#include "QualityControl/UserInputOutput.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include namespace o2::quality_control::core { using namespace o2::framework; -TaskRunnerFactory::TaskRunnerFactory() {} +o2::framework::DataProcessorSpec TaskRunnerFactory::create(const TaskRunnerConfig& taskConfig) +{ + TaskRunner qcTask{ taskConfig }; + + DataProcessorSpec newTask{ + taskConfig.deviceName, + taskConfig.inputSpecs, + { taskConfig.moSpec }, + adaptFromTask(std::move(qcTask)), + taskConfig.options + }; + newTask.labels.emplace_back(TaskRunner::getTaskRunnerLabel()); + if (!taskConfig.critical) { + framework::DataProcessorLabel expendableLabel = { "expendable" }; + newTask.labels.emplace_back(expendableLabel); + } -TaskRunnerFactory::~TaskRunnerFactory() {} + return newTask; +} -o2::framework::DataProcessorSpec -TaskRunnerFactory::create(std::string taskName, std::string configurationSource, size_t id, bool resetAfterPublish) +std::vector> TaskRunnerFactory::getSanitizedCycleDurations(const CommonSpec& globalConfig, const TaskSpec& taskSpec) { - auto qcTask = std::make_shared(taskName, configurationSource, id); - qcTask->setResetAfterPublish(resetAfterPublish); + // Two ways of configuring, incompatible. + // 1. simple, old, way: cycleDurationSeconds is the duration in seconds for all cycles + // 2. complex, new, way: cycleDurations: a list of tuples specifying different durations to be applied for a certain time - DataProcessorSpec newTask{ - taskName, - qcTask->getInputsSpecs(), - Outputs{ qcTask->getOutputSpec() }, - AlgorithmSpec{ - (AlgorithmSpec::InitCallback) [qcTask = std::move(qcTask)](InitContext& initContext) { + // First make sure that we have one and only one cycle duration definition + if (taskSpec.cycleDurationSeconds > 0 && taskSpec.multipleCycleDurations.size() > 0) { + throw std::runtime_error("Both cycleDurationSeconds and cycleDurations have been defined for task '" + taskSpec.taskName + "'. Pick one. Sheepishly bailing out."); + } + if (taskSpec.cycleDurationSeconds <= 0 && taskSpec.multipleCycleDurations.size() == 0) { + throw std::runtime_error("Neither cycleDurationSeconds nor cycleDurations have been defined for task '" + taskSpec.taskName + "'. Pick one. Sheepishly bailing out."); + } - qcTask->initCallback(initContext); + // Convert old style into new style if needed + std::vector> multipleCycleDurations = taskSpec.multipleCycleDurations; // this is the new style + if (taskSpec.cycleDurationSeconds > 0) { // if it was actually the old style, then we convert it to the new style + multipleCycleDurations = { { taskSpec.cycleDurationSeconds, 1 } }; + } - return (AlgorithmSpec::ProcessCallback) [qcTask = std::move(qcTask)] (ProcessingContext &processingContext) { - qcTask->processCallback(processingContext); - }; + // Check that the durations are not below 10 seconds except when using a dummy database + auto dummyDatabaseUsed = globalConfig.database.count("implementation") > 0 && globalConfig.database.at("implementation") == "Dummy"; + if (!dummyDatabaseUsed) { + for (auto& [cycleDuration, validity] : multipleCycleDurations) { + if (cycleDuration < 10) { + ILOG(Error, Support) << "Cycle duration is too short (" << cycleDuration << "), replaced by a duration of 10 seconds." << ENDM; + cycleDuration = 10; } } + } + return multipleCycleDurations; +} + +TaskRunnerConfig TaskRunnerFactory::extractConfig(const CommonSpec& globalConfig, const TaskSpec& taskSpec, std::optional id, std::optional resetAfterCycles) +{ + std::string deviceName{ TaskRunner::createTaskRunnerIdString() + "-" + InfrastructureSpecReader::validateDetectorName(taskSpec.detectorName) + "-" + taskSpec.taskName }; + + int parallelTaskID = id.value_or(0); + + std::vector inputs; + for (const auto& ds : taskSpec.dataSources) { + if (!ds.isOneOf(DataSourceType::DataSamplingPolicy, DataSourceType::Direct)) { + throw std::runtime_error("This data source of the task '" + taskSpec.taskName + "' is not supported."); + } + inputs.insert(inputs.end(), ds.inputs.begin(), ds.inputs.end()); + } + + // cycle duration + auto multipleCycleDurations = getSanitizedCycleDurations(globalConfig, taskSpec); + inputs.emplace_back(createTimerInputSpec(globalConfig, multipleCycleDurations, taskSpec.detectorName, taskSpec.taskName)); + + static std::unordered_map const geomRequestFromString = { + { "None", o2::base::GRPGeomRequest::GeomRequest::None }, + { "Aligned", o2::base::GRPGeomRequest::GeomRequest::Aligned }, + { "Ideal", o2::base::GRPGeomRequest::GeomRequest::Ideal }, + { "Alignments", o2::base::GRPGeomRequest::GeomRequest::Alignments } }; + auto grp = taskSpec.grpGeomRequestSpec; + grp.askGRPECS |= TimekeeperFactory::needsGRPECS(DefaultsHelpers::deploymentMode()); + auto grpGeomRequest = grp.anyRequestEnabled() // + ? std::make_shared( // + grp.askTime, grp.askGRPECS, grp.askGRPLHCIF, grp.askGRPMagField, grp.askMatLUT, // + geomRequestFromString.at(grp.geomRequest), inputs, grp.askOnceAllButField, // + grp.needPropagatorD) // + : nullptr; + + const auto& dr = taskSpec.globalTrackingDataRequest; + auto globalTrackingDataRequest = dr.requestTracks.empty() && dr.requestClusters.empty() ? nullptr : std::make_shared(); + if (globalTrackingDataRequest) { + auto canProcessTracksMask = o2::dataformats::GlobalTrackID::getSourcesMask(dr.canProcessTracks); + auto requestTracksMask = o2::dataformats::GlobalTrackID::getSourcesMask(dr.requestTracks); + auto requestedTracksMask = canProcessTracksMask & requestTracksMask; + globalTrackingDataRequest->requestTracks(requestedTracksMask, dr.mc); + + auto canProcessClustersMask = o2::dataformats::GlobalTrackID::getSourcesMask(dr.canProcessClusters); + auto requestClustersMask = o2::dataformats::GlobalTrackID::getSourcesMask(dr.requestClusters); + auto requestedClustersMask = canProcessClustersMask & requestClustersMask; + globalTrackingDataRequest->requestTracks(requestedClustersMask, dr.mc); + inputs.insert(inputs.begin(), globalTrackingDataRequest->inputs.begin(), globalTrackingDataRequest->inputs.end()); + } + + OutputSpec monitorObjectsSpec = createUserOutputSpec( + DataSourceType::Task, + taskSpec.detectorName, + taskSpec.taskName, + static_cast(parallelTaskID)); - return std::move(newTask); + Options options{ + { "period-timer-cycle", framework::VariantType::Int, static_cast(taskSpec.cycleDurationSeconds * 1000000), { "timer period" } }, + { "runNumber", framework::VariantType::String, { "Run number" } }, + { "qcConfiguration", VariantType::Dict, emptyDict(), { "Some dictionary configuration" } } + }; + + Activity fallbackActivity{ + globalConfig.activityNumber, + globalConfig.activityType, + globalConfig.activityPeriodName, + globalConfig.activityPassName, + globalConfig.activityProvenance, + { globalConfig.activityStart, globalConfig.activityEnd }, + globalConfig.activityBeamType, + globalConfig.activityPartitionName, + globalConfig.activityFillNumber, + globalConfig.activityOriginalNumber + }; + + o2::globaltracking::RecoContainer rd; + + return { + taskSpec.taskName, + taskSpec.moduleName, + taskSpec.className, + InfrastructureSpecReader::validateDetectorName(taskSpec.detectorName), + globalConfig.consulUrl, + taskSpec.customParameters, + globalConfig.conditionDBUrl, + globalConfig.database, + taskSpec.dataSources, + deviceName, + multipleCycleDurations, + taskSpec.maxNumberCycles, + taskSpec.critical, + globalConfig.monitoringUrl, + globalConfig.bookkeepingUrl, + inputs, + monitorObjectsSpec, + options, + parallelTaskID, + taskSpec.saveObjectsToFile, + resetAfterCycles.value_or(taskSpec.resetAfterCycles), + globalConfig.infologgerDiscardParameters, + fallbackActivity, + grpGeomRequest, + globalTrackingDataRequest, + taskSpec.movingWindows, + taskSpec.disableLastCycle, + }; +} + +InputSpec TaskRunnerFactory::createTimerInputSpec(const CommonSpec& globalConfig, std::vector>& cycleDurations, + const std::string& detectorName, const std::string& taskName) +{ + // Create the TimerSpec for cycleDurations + std::vector timers; + for (auto& [cycleDuration, period] : cycleDurations) { + timers.push_back({ cycleDuration * 1000000000 /*µs*/, period }); + } + + return { "timer-cycle", + createDataOrigin(DataSourceType::Task, detectorName), + TaskRunner::createTimerDataDescription(taskName), + 0, + Lifetime::Timer, + timerSpecs(timers) }; +} + +void TaskRunnerFactory::customizeInfrastructure(std::vector& policies) +{ + auto matcher = [label = TaskRunner::getTaskRunnerLabel()](auto const& device) { + return std::find(device.labels.begin(), device.labels.end(), label) != device.labels.end(); + }; + auto callback = TaskRunner::completionPolicyCallback; + + framework::CompletionPolicy taskRunnerCompletionPolicy{ "taskRunnerCompletionPolicy", matcher, callback }; + policies.push_back(taskRunnerCompletionPolicy); +} + +int TaskRunnerFactory::computeResetAfterCycles(const TaskSpec& taskSpec, bool runningWithMergers) +{ + return (runningWithMergers && taskSpec.mergingMode == "delta") ? 1 : (int)taskSpec.resetAfterCycles; } } // namespace o2::quality_control::core diff --git a/Framework/src/Timekeeper.cxx b/Framework/src/Timekeeper.cxx new file mode 100644 index 0000000000..fbc1ebc7fa --- /dev/null +++ b/Framework/src/Timekeeper.cxx @@ -0,0 +1,75 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Timekeeper.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/Timekeeper.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/runnerUtils.h" + +namespace o2::quality_control::core +{ + +Timekeeper::Timekeeper() +{ +} + +void Timekeeper::setActivityDuration(ValidityInterval validity) +{ + mActivityDuration = validity; +} + +ValidityInterval Timekeeper::getValidity() const +{ + return mCurrentValidityTimespan; +} + +ValidityInterval Timekeeper::getSampleTimespan() const +{ + return mCurrentSampleTimespan; +} + +TimeframeIdRange Timekeeper::getTimerangeIdRange() const +{ + return mCurrentTimeframeIdRange; +} + +void Timekeeper::setStartOfActivity(validity_time_t ecsTimestamp, validity_time_t configTimestamp, + validity_time_t currentTimestamp, std::function ccdbTimestampAccessor) +{ + mActivityDuration.setMin(activityBoundarySelectionStrategy(ecsTimestamp, configTimestamp, currentTimestamp, ccdbTimestampAccessor)); +} + +void Timekeeper::setEndOfActivity(validity_time_t ecsTimestamp, validity_time_t configTimestamp, + validity_time_t currentTimestamp, std::function ccdbTimestampAccessor) +{ + mActivityDuration.setMax(activityBoundarySelectionStrategy(ecsTimestamp, configTimestamp, currentTimestamp, ccdbTimestampAccessor)); +} + +ValidityInterval Timekeeper::getActivityDuration() const +{ + return mActivityDuration; +} + +void Timekeeper::setCCDBOrbitsPerTFAccessor(std::function accessor) +{ + mCCDBOrbitsPerTFAccessor = std::move(accessor); +} + +std::function Timekeeper::getCCDBOrbitsPerTFAccessor(void) +{ + return mCCDBOrbitsPerTFAccessor; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/TimekeeperAsynchronous.cxx b/Framework/src/TimekeeperAsynchronous.cxx new file mode 100644 index 0000000000..94f2d6af0b --- /dev/null +++ b/Framework/src/TimekeeperAsynchronous.cxx @@ -0,0 +1,149 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TimekeeperAsynchronous.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/TimekeeperAsynchronous.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include + +namespace o2::quality_control::core +{ + +TimekeeperAsynchronous::TimekeeperAsynchronous(validity_time_t windowLengthMs) + : Timekeeper(), mWindowLengthMs(windowLengthMs) +{ +} + +void TimekeeperAsynchronous::updateByCurrentTimestamp(validity_time_t timestampMs) +{ + // async QC should ignore current timestamp +} + +void TimekeeperAsynchronous::updateByTimeFrameID(uint32_t tfid) +{ + // fixme: We might want to use this once we know how to get orbitResetTime: + // std::ceil((timingInfo.firstTForbit * o2::constants::lhc::LHCOrbitNS / 1000 + orbitResetTime) / 1000); + // Until then, we use a less precise method: + if (mActivityDuration.isInvalid()) { + ILOG(Warning, Support) + << "trying to update the validity range with TF ID without having set the activity duration, returning" << ENDM; + return; + } + if (tfid == 0) { + if (!mWarnedAboutTfIdZero) { + ILOG(Warning, Support) << "Seen TFID equal to 0, which is not expected in production data. Will use 1 instead, will not warn further." << ENDM; + mWarnedAboutTfIdZero = true; + tfid = 1; + } + } + + auto tfValidity = computeTimestampFromTimeframeID(tfid); + mCurrentSampleTimespan.update(tfValidity.getMin()); + mCurrentSampleTimespan.update(tfValidity.getMax()); + + mCurrentTimeframeIdRange.update(tfid); + + if (mActivityDuration.isOutside(tfValidity.getMin())) { + ILOG(Warning, Support) << "Timestamp " << tfValidity.getMin() << " is outside of the assumed run duration (" + << mActivityDuration.getMin() << ", " << mActivityDuration.getMax() << ")" << ENDM; + return; + } + + if (mWindowLengthMs == 0) { + mCurrentValidityTimespan = mActivityDuration; + } else { + size_t subdivisionIdx = (tfValidity.getMin() - mActivityDuration.getMin()) / mWindowLengthMs; + size_t fullSubdivisions = mActivityDuration.delta() / mWindowLengthMs; + if (subdivisionIdx < fullSubdivisions - 1) { + mCurrentValidityTimespan.update(mActivityDuration.getMin() + subdivisionIdx * mWindowLengthMs); + mCurrentValidityTimespan.update(mActivityDuration.getMin() + (subdivisionIdx + 1) * mWindowLengthMs); + } else if (subdivisionIdx == fullSubdivisions - 1) { + mCurrentValidityTimespan.update(mActivityDuration.getMin() + subdivisionIdx * mWindowLengthMs); + mCurrentValidityTimespan.update(mActivityDuration.getMax()); + } else { // subdivisionIdx == fullSubdivisions + mCurrentValidityTimespan.update(mActivityDuration.getMin() + (subdivisionIdx - 1) * mWindowLengthMs); + mCurrentValidityTimespan.update(mActivityDuration.getMax()); + } + } +} + +void TimekeeperAsynchronous::reset() +{ + mCurrentSampleTimespan = gInvalidValidityInterval; + mCurrentValidityTimespan = gInvalidValidityInterval; + mCurrentTimeframeIdRange = gInvalidTimeframeIdRange; +} + +template +bool not_on_limit(T value) +{ + return value != std::numeric_limits::min() && value != std::numeric_limits::max(); +} + +validity_time_t + TimekeeperAsynchronous::activityBoundarySelectionStrategy(validity_time_t ecsTimestamp, + validity_time_t configTimestamp, + validity_time_t currentTimestamp, + std::function ccdbTimestampAccessor) +{ + validity_time_t selected = 0; + auto ccdbTimestamp = ccdbTimestampAccessor == nullptr ? std::numeric_limits::min() : ccdbTimestampAccessor(); + if (not_on_limit(ccdbTimestamp)) { + selected = ccdbTimestamp; + } else if (not_on_limit(ecsTimestamp)) { + selected = ecsTimestamp; + } else if (not_on_limit(configTimestamp)) { + selected = configTimestamp; + } else { + // an exception could be thrown here once the values above are set correctly in production + } + ILOG(Debug, Devel) << "Received the following activity boundary propositions: " << ccdbTimestamp << ", " << ecsTimestamp + << ", " << configTimestamp << ", " << currentTimestamp << ". Selected: " << selected << ENDM; + return selected; +} + +bool TimekeeperAsynchronous::shouldFinishCycle(const framework::TimingInfo& timingInfo) +{ + // we should start a new window whenever the new data falls outside of the current one. + // if the window covers the whole run, there is never a reason to finish before we receive an end of stream + return mCurrentValidityTimespan.isValid() && + mWindowLengthMs != 0 && + !timingInfo.isTimer() && + mCurrentValidityTimespan.isOutside(computeTimestampFromTimeframeID(timingInfo.tfCounter).getMin()); +} + +ValidityInterval TimekeeperAsynchronous::computeTimestampFromTimeframeID(uint32_t tfid) +{ + if (mOrbitsPerTF == 0) { + if (auto accessor = getCCDBOrbitsPerTFAccessor()) { + mOrbitsPerTF = accessor(); + ILOG(Debug, Support) << "Got nOrbitsPerTF " << mOrbitsPerTF << " for TF " << tfid << ENDM; + } else { + ILOG(Error, Ops) << "CCDB OrbitsPerTF accessor is not available" << ENDM; + } + if (mOrbitsPerTF == 0) { + ILOG(Error, Ops) << "nHBFperTF from CCDB GRP is 0, object validity will be incorrect" << ENDM; + } + } + + auto tfDurationMs = constants::lhc::LHCOrbitNS / 1000000 * mOrbitsPerTF; + auto tfStart = static_cast(mActivityDuration.getMin() + tfDurationMs * (tfid - 1)); + auto tfEnd = static_cast(mActivityDuration.getMin() + tfDurationMs * tfid - 1); + return { tfStart, tfEnd }; +} + +} // namespace o2::quality_control::core \ No newline at end of file diff --git a/Framework/src/TimekeeperFactory.cxx b/Framework/src/TimekeeperFactory.cxx new file mode 100644 index 0000000000..74bd1d395b --- /dev/null +++ b/Framework/src/TimekeeperFactory.cxx @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TimekeeperFactory.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/TimekeeperFactory.h" +#include "QualityControl/TimekeeperSynchronous.h" +#include "QualityControl/TimekeeperAsynchronous.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace o2::framework; + +namespace o2::quality_control::core +{ + +std::unique_ptr TimekeeperFactory::create(framework::DeploymentMode deploymentMode, validity_time_t windowLengthMs) +{ + switch (deploymentMode) { + case DeploymentMode::Grid: { + ILOG(Info, Devel) << "Detected async deployment, object validity will be based on incoming data and available SOR/EOR times" << ENDM; + return std::make_unique(windowLengthMs); + break; + } + case DeploymentMode::Local: + case DeploymentMode::OnlineECS: + case DeploymentMode::OnlineDDS: + case DeploymentMode::OnlineAUX: + case DeploymentMode::FST: + default: { + ILOG(Info, Devel) << "Detected sync deployment, object validity will be based primarily on current time" << ENDM; + return std::make_unique(); + } + } +} + +bool TimekeeperFactory::needsGRPECS(framework::DeploymentMode deploymentMode) +{ + return deploymentMode == DeploymentMode::Grid; +} + +} // namespace o2::quality_control::core \ No newline at end of file diff --git a/Framework/src/TimekeeperSynchronous.cxx b/Framework/src/TimekeeperSynchronous.cxx new file mode 100644 index 0000000000..daa67f61bc --- /dev/null +++ b/Framework/src/TimekeeperSynchronous.cxx @@ -0,0 +1,107 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TimekeeperSynchronous.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/TimekeeperSynchronous.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include + +namespace o2::quality_control::core +{ + +TimekeeperSynchronous::TimekeeperSynchronous() : Timekeeper() +{ +} + +void TimekeeperSynchronous::updateByCurrentTimestamp(validity_time_t timestampMs) +{ + mCurrentValidityTimespan.update(timestampMs); + mActivityDuration.update(timestampMs); +} + +void TimekeeperSynchronous::updateByTimeFrameID(uint32_t tfid) +{ + if (tfid == 0) { + if (!mWarnedAboutTfIdZero) { + ILOG(Debug, Devel) << "Seen TFID equal to 0, which is unexpected in production, but normal in test QC setups. Will not update TF-based validity, will not warn further." << ENDM; + mWarnedAboutTfIdZero = true; + } + return; + } + + mCurrentTimeframeIdRange.update(tfid); + + if (mActivityDuration.getMin() == gInvalidValidityInterval.getMin() || mActivityDuration.isInvalid()) { + if (!mWarnedAboutDataWithoutSOR) { + ILOG(Warning, Devel) + << "Data arrived before SOR time was set, cannot proceed with creating sample timespan. Will not warn further." << ENDM; + mWarnedAboutDataWithoutSOR = true; + } + return; + } + + // fixme: We might want to use this once we know how to get orbitResetTime: + // std::ceil((timingInfo.firstTForbit * o2::constants::lhc::LHCOrbitNS / 1000 + orbitResetTime) / 1000); + // Until then, we use a less precise method: + constexpr uint64_t nOrbitsPerTF = 32; // naively assuming it's 32 for any new data. anyway, it is not crucial for sync QC + auto tfDuration = constants::lhc::LHCOrbitNS / 1000000 * nOrbitsPerTF; + auto tfStart = mActivityDuration.getMin() + tfDuration * (tfid - 1); + auto tfEnd = tfStart + tfDuration - 1; + mCurrentSampleTimespan.update(tfStart); + mCurrentSampleTimespan.update(tfEnd); +} + +void TimekeeperSynchronous::reset() +{ + mCurrentSampleTimespan = gInvalidValidityInterval; + if (mCurrentValidityTimespan.isValid()) { + mCurrentValidityTimespan.set(mCurrentValidityTimespan.getMax(), mCurrentValidityTimespan.getMax()); + } + mCurrentTimeframeIdRange = gInvalidTimeframeIdRange; +} + +template +bool not_on_limit(T value) +{ + return value != std::numeric_limits::min() && value != std::numeric_limits::max(); +} + +validity_time_t + TimekeeperSynchronous::activityBoundarySelectionStrategy(validity_time_t ecsTimestamp, + validity_time_t configTimestamp, + validity_time_t currentTimestamp, + std::function) +{ + validity_time_t selected = 0; + if (not_on_limit(ecsTimestamp)) { + selected = ecsTimestamp; + } else if (not_on_limit(configTimestamp)) { + selected = configTimestamp; + } else { + selected = currentTimestamp; + } + ILOG(Info, Devel) << "Received the following activity boundary propositions: " << ecsTimestamp + << ", " << configTimestamp << ", " << currentTimestamp << ". Selected: " << selected << ENDM; + return selected; +} + +bool TimekeeperSynchronous::shouldFinishCycle(const framework::TimingInfo& timingInfo) +{ + return timingInfo.isTimer(); +} + +} // namespace o2::quality_control::core \ No newline at end of file diff --git a/Framework/src/TrendingTask.cxx b/Framework/src/TrendingTask.cxx new file mode 100644 index 0000000000..d92e7baba5 --- /dev/null +++ b/Framework/src/TrendingTask.cxx @@ -0,0 +1,497 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTask.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/TrendingTask.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorHelpers.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/ActivityHelpers.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; + +void TrendingTask::configure(const boost::property_tree::ptree& config) +{ + // we clear any existing objects, which would be there only in case of reconfiguration + // at the time of writing, this not even supported by ECS + mReductors.clear(); + mTrend.reset(); + + // configuration + mConfig = TrendingTaskConfig(getID(), config); + for (const auto& source : mConfig.dataSources) { + auto&& [emplaced, _] = mReductors.emplace(source.name, root_class_factory::create(source.moduleName, source.reductorName)); + emplaced->second->setCustomConfig(source.reductorParameters); + } +} + +bool TrendingTask::canContinueTrend(TTree* tree) +{ + if (tree == nullptr) { + return false; + } + + size_t expectedNBranches = 1 /* meta */ + 1 /* time */ + mConfig.dataSources.size(); + if (tree->GetNbranches() != expectedNBranches) { + ILOG(Warning, Support) << "The retrieved TTree has different number of branches than expected (" + << tree->GetNbranches() << " vs. " << expectedNBranches << "). " + << "Filling the tree with mismatching branches might produce invalid plots, " + << "thus a new tree will be created" << ENDM; + return false; + } + + std::set expectedBranchNames{ "time", "meta" }; + for (const auto& dataSource : mConfig.dataSources) { + expectedBranchNames.insert(dataSource.name); + } + + std::set existingBranchNames; + for (const auto& branch : *tree->GetListOfBranches()) { + existingBranchNames.insert(branch->GetName()); + } + + if (expectedBranchNames != existingBranchNames) { + ILOG(Warning, Support) << "The retrieved TTree has the same number of branches," + << " but at least one has a different name." + << " Filling the tree with mismatching branches might produce invalid plots, " + << "thus a new tree will be created" << ENDM; + return false; + } + + return true; +} + +void TrendingTask::initializeTrend(o2::quality_control::repository::DatabaseInterface& qcdb) +{ + // tree exists and we can reuse it + if (canContinueTrend(mTrend.get())) { + if (mConfig.resumeTrend == false) { + mTrend->Reset(); + } else { + ILOG(Info, Support) << "Will continue the trend from the previous run." << ENDM; + } + return; + } + + // tree is not reusable or does not exist => if we want to reuse the latest, we look for it in QCDB + mTrend.reset(); + if (mConfig.resumeTrend) { + ILOG(Info, Support) << "Trying to retrieve an existing TTree for this task to continue the trend." << ENDM; + auto path = RepoPathUtils::getMoPath(mConfig.detectorName, PostProcessingInterface::getName(), "", "", false); + auto mo = qcdb.retrieveMO(path, PostProcessingInterface::getName(), repository::DatabaseInterface::Timestamp::Latest); + if (mo && mo->getObject()) { + auto tree = dynamic_cast(mo->getObject()); + if (tree) { + mTrend = std::unique_ptr(tree); + mo->setIsOwner(false); + } + } else { + ILOG(Warning, Support) << "Could not retrieve an existing TTree for this task" << ENDM; + } + if (canContinueTrend(mTrend.get())) { + mTrend->SetBranchAddress("meta", &mMetaData); + mTrend->SetBranchAddress("time", &mTime); + for (const auto& [sourceName, reductor] : mReductors) { + mTrend->SetBranchAddress(sourceName.c_str(), reductor->getBranchAddress()); + } + ILOG(Info, Support) << "Will use the latest TTree from QCDB for this task to continue the trend." << ENDM; + return; + } else { + mTrend.reset(); + } + } + + // we could not reuse the tree or never had one => we create a new one + if (mTrend == nullptr) { + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + + mTrend->Branch("meta", &mMetaData, mMetaData.getBranchLeafList()); + mTrend->Branch("time", &mTime); + for (const auto& [sourceName, reductor] : mReductors) { + mTrend->Branch(sourceName.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + } + } +} + +void TrendingTask::applyStyleToGraph(TGraph* graph, const TrendingTaskConfig::GraphStyle& style) +{ + if (!graph) { + return; + } + + if (style.lineColor >= 0) { + graph->SetLineColor(style.lineColor); + } + if (style.lineStyle >= 0) { + graph->SetLineStyle(style.lineStyle); + } + if (style.lineWidth >= 0) { + graph->SetLineWidth(style.lineWidth); + } + + if (style.markerColor >= 0) { + graph->SetMarkerColor(style.markerColor); + } + if (style.markerStyle >= 0) { + graph->SetMarkerStyle(style.markerStyle); + } + if (style.markerSize >= 0.f) { + graph->SetMarkerSize(style.markerSize); + } + + if (style.fillColor >= 0) { + graph->SetFillColor(style.fillColor); + } + if (style.fillStyle >= 0) { + graph->SetFillStyle(style.fillStyle); + } +} + +void TrendingTask::initialize(Trigger, framework::ServiceRegistryRef services) +{ + // removing leftovers from any previous runs + mPlots.clear(); + + initializeTrend(services.get()); + + if (mConfig.producePlotsOnUpdate) { + getObjectsManager()->startPublishing(mTrend.get(), PublicationPolicy::ThroughStop); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TrendingTask::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + const auto allSourcesInvoked = trendValues(t, qcdb); + if (mConfig.producePlotsOnUpdate && (!mConfig.trendIfAllInputs || allSourcesInvoked)) { + generatePlots(); + } +} + +void TrendingTask::finalize(Trigger, framework::ServiceRegistryRef) +{ + if (!mConfig.producePlotsOnUpdate) { + getObjectsManager()->startPublishing(mTrend.get()); + } + generatePlots(); +} + +bool TrendingTask::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + if (mConfig.trendingTimestamp == "trigger") { + // ROOT expects seconds since epoch. + mTime = t.timestamp / 1000; + } else if (mConfig.trendingTimestamp == "validFrom") { + mTime = t.activity.mValidity.getMin() / 1000; + } else { // validUntil + mTime = t.activity.mValidity.getMax() / 1000; + } + mMetaData.runNumber = t.activity.mId; + std::snprintf(mMetaData.runNumberStr, MaxRunNumberStringLength + 1, "%d", t.activity.mId); + + bool wereAllSourcesInvoked = true; + for (auto& dataSource : mConfig.dataSources) { + if (!reductor_helpers::updateReductor(mReductors[dataSource.name].get(), t, dataSource, qcdb, *this)) { + wereAllSourcesInvoked = false; + ILOG(Error, Support) << "Failed to update reductor for data sources with path '" << dataSource.path + << "', name '" << dataSource.name + << "', type '" << dataSource.type << "'." << ENDM; + } + } + + if (!mConfig.trendIfAllInputs || wereAllSourcesInvoked) { + mTrend->Fill(); + } + + return wereAllSourcesInvoked; +} + +void TrendingTask::setUserAxesLabels(TAxis* xAxis, TAxis* yAxis, const std::string& graphAxesLabels) +{ + // todo if we keep adding this method to pp classes we should move it up somewhere + if (std::count(graphAxesLabels.begin(), graphAxesLabels.end(), ':') != 1 && graphAxesLabels != "") { + ILOG(Error, Support) << "In setup of graphAxesLabels yLabel:xLabel should be divided by one ':'" << ENDM; + return; + } + const std::size_t posDivider = graphAxesLabels.find(':'); + const std::string yLabel(graphAxesLabels.substr(0, posDivider)); + const std::string xLabel(graphAxesLabels.substr(posDivider + 1)); + + xAxis->SetTitle(xLabel.data()); + yAxis->SetTitle(yLabel.data()); +} + +void TrendingTask::setUserYAxisRange(TH1* hist, const std::string& graphYAxisRange) +{ + if (std::count(graphYAxisRange.begin(), graphYAxisRange.end(), ':') != 1 && graphYAxisRange != "") { + ILOG(Error, Support) << "In setup of graphYRange yMin:yMax should be divided by one ':'" << ENDM; + return; + } + const std::size_t posDivider = graphYAxisRange.find(':'); + const std::string minString(graphYAxisRange.substr(0, posDivider)); + const std::string maxString(graphYAxisRange.substr(posDivider + 1)); + + const float yMin = std::stof(minString); + const float yMax = std::stof(maxString); + hist->GetYaxis()->SetLimits(yMin, yMax); +} + +void TrendingTask::formatRunNumberXAxis(TH1* background) +{ + background->GetXaxis()->SetNoExponent(true); +} + +void TrendingTask::formatTimeXAxis(TH1* background) +{ + background->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + background->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + background->GetXaxis()->SetTimeOffset(0.0); + background->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); +} + +void TrendingTask::generatePlots() +{ + if (mTrend == nullptr) { + ILOG(Info, Support) << "The trend object is not there, won't generate any plots." << ENDM; + return; + } + + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, won't generate any plots." << ENDM; + return; + } + + ILOG(Info, Support) << "Generating " << mConfig.plots.size() << " plots." << ENDM; + for (const auto& plotConfig : mConfig.plots) { + + // Before we generate any new plots, we have to delete existing under the same names. + // It seems that ROOT cannot handle an existence of two canvases with a common name in the same process. + if (mPlots.count(plotConfig.name)) { + mPlots[plotConfig.name].reset(); + } + auto c = drawPlot(plotConfig); + mPlots[plotConfig.name].reset(c); + getObjectsManager()->startPublishing(c, PublicationPolicy::Once); + } +} + +std::string TrendingTask::deduceGraphLegendOptions(const TrendingTaskConfig::Graph& graphConfig) +{ + // Looking at TGraphPainter documentation, the number of TGraph options is rather small, + // so we can try to be smart and deduce the corresponding legend options. + // I am not aware of any ROOT helper which can do this. + std::string options = graphConfig.option; + boost::algorithm::to_lower(options); + // these three options have only an influence on colours but not what is drawn and what not. + for (const auto& toRemove : { "pfc", "plc", "pmc" }) { + if (size_t pos = options.find(toRemove); pos != std::string::npos) { + options.erase(pos, 3); + } + } + auto optionsHave = [&](std::string_view seq) { + return options.find(seq) != std::string::npos; + }; + + std::string out; + if (optionsHave("l") || optionsHave("c")) { + out += "l"; // line + } + if (optionsHave("*") || optionsHave("p")) { + out += "p"; // point + } + if (optionsHave("f") || optionsHave("b")) { + out += "f"; // fill + } + if (!graphConfig.errors.empty()) { + out += "e"; // error bars + } + return out; +} + +TCanvas* TrendingTask::drawPlot(const TrendingTaskConfig::Plot& plotConfig) +{ + auto* c = new TCanvas(); + + // Legend + TLegend* legend = nullptr; + if (plotConfig.legend.x1 >= 0 && plotConfig.legend.y1 >= 0 && plotConfig.legend.x2 >= 0 && plotConfig.legend.y2 >= 0) { + legend = new TLegend(plotConfig.legend.x1, plotConfig.legend.y1, + plotConfig.legend.x2, plotConfig.legend.y2, + nullptr, "NDC"); + if (plotConfig.legend.nColumns > 0) { + legend->SetNColumns(plotConfig.legend.nColumns); + } + } else { + legend = new TLegend(0.3, 0.2); + } + legend->SetBorderSize(0); + legend->SetFillStyle(0); + legend->SetTextSize(0.03); + legend->SetMargin(0.15); + + // Keep palette behavior unless user forces explicit colors via per-graph style + if (plotConfig.colorPalette != 0) { + gStyle->SetPalette(plotConfig.colorPalette); + // This makes ROOT store the selected palette for each generated plot. + // TColor::DefinedColors(1); // TODO enable when available + } else { + gStyle->SetPalette(); // default + } + + // regardless whether we draw a graph or a histogram, a histogram is always used by TTree::Draw to draw axes and title + // we attempt to keep it to do some modifications later + TH1* background = nullptr; + bool firstGraphInPlot = true; + // by "graph" we consider anything we can draw, not necessarily TGraph, and we draw all on the same canvas + for (const auto& graphConfig : plotConfig.graphs) { + // we determine the order of the plotConfig, i.e. if it is a histogram (1), graphConfig (2), or any higher dimension. + const size_t plotOrder = std::count(graphConfig.varexp.begin(), graphConfig.varexp.end(), ':') + 1; + + // having "SAME" at the first TTree::Draw() call will not work, we have to add it only in subsequent Draw calls + std::string option = firstGraphInPlot ? graphConfig.option : "SAME " + graphConfig.option; + + // Draw main series + mTrend->Draw(graphConfig.varexp.c_str(), graphConfig.selection.c_str(), option.c_str()); + + // For graphs, we allow to draw errors if they are specified. + TGraphErrors* graphErrors = nullptr; + if (!graphConfig.errors.empty()) { + if (plotOrder != 2) { + ILOG(Error, Support) << "Non empty graphErrors seen for the plotConfig '" << plotConfig.name << "', which is not a graphConfig, ignoring." << ENDM; + } else { + // We generate some 4-D points, where 2 dimensions represent graph points and 2 others are the error bars + std::string varexpWithErrors(graphConfig.varexp + ":" + graphConfig.errors); + mTrend->Draw(varexpWithErrors.c_str(), graphConfig.selection.c_str(), "goff"); + graphErrors = new TGraphErrors(mTrend->GetSelectedRows(), mTrend->GetVal(1), mTrend->GetVal(0), + mTrend->GetVal(2), mTrend->GetVal(3)); + graphErrors->SetName((graphConfig.name + "_errors").c_str()); + graphErrors->SetTitle((graphConfig.title + " errors").c_str()); + // We draw on the same plotConfig as the main graphConfig, but only error bars + graphErrors->Draw("SAME E"); + } + } + + // Legend entry and styling for graphs + if (auto graph = dynamic_cast(c->FindObject("Graph"))) { + if (plotOrder >= 2) { + // Style objects after Draw so we override palette/auto styling when requested + applyStyleToGraph(graph, graphConfig.style); + // Keep errors visually consistent with the main series + if (graphErrors) { + applyStyleToGraph(graphErrors, graphConfig.style); + } + } + graph->SetName(graphConfig.name.c_str()); + graph->SetTitle(graphConfig.title.c_str()); + legend->AddEntry(graph, graphConfig.title.c_str(), + deduceGraphLegendOptions(graphConfig).c_str()); + } + + // Legend entry and styling for histograms + if (auto htemp = dynamic_cast(c->FindObject("htemp"))) { + if (plotOrder == 1) { + htemp->SetName(graphConfig.name.c_str()); + htemp->SetTitle(graphConfig.title.c_str()); + legend->AddEntry(htemp, graphConfig.title.c_str(), "lpf"); + } else { + htemp->SetName("background"); + htemp->SetTitle("background"); + // htemp was used by TTree::Draw only to draw axes and title, not to plot data, no need to add it to legend + } + // QCG doesn't empty the buffers before visualizing the plotConfig, nor does ROOT when saving the file, + // so we have to do it here. + htemp->BufferEmpty(); + // we keep the pointer to bg histogram for later postprocessing + if (!background) { + background = htemp; + } + } + + firstGraphInPlot = false; + } + + c->SetName(plotConfig.name.c_str()); + c->SetTitle(plotConfig.title.c_str()); + + // Postprocessing the plotConfig - adding specified titles, configuring time-based plots, flushing buffers. + // Notice that axes and title are drawn using a histogram, even in the case of graphs. + if (background) { + // The title of background histogram is printed, not the title of canvas => we set it as well. + background->SetTitle(plotConfig.title.c_str()); + // We have to update the canvas to make the title appear and access it in the next step. + c->Update(); + + // After the update, the title has a different size and it is not in the center anymore. We have to fix that. + if (auto title = dynamic_cast(c->GetPrimitive("title"))) { + title->SetBBoxCenterX(c->GetBBoxCenter().fX); + c->Modified(); + c->Update(); + } else { + ILOG(Error, Devel) << "Could not get the title TPaveText of the plotConfig '" << plotConfig.name << "'." << ENDM; + } + + if (!plotConfig.graphAxisLabel.empty()) { + setUserAxesLabels(background->GetXaxis(), background->GetYaxis(), plotConfig.graphAxisLabel); + } + + if (plotConfig.graphs.back().varexp.find(":time") != std::string::npos) { + // We have to explicitly configure showing time on x axis. + formatTimeXAxis(background); + } else if (plotConfig.graphs.back().varexp.find(":meta.runNumber") != std::string::npos) { + formatRunNumberXAxis(background); + } + + // Set the user-defined range on the y axis if needed. + if (!plotConfig.graphYRange.empty()) { + setUserYAxisRange(background, plotConfig.graphYRange); + c->Modified(); + c->Update(); + } + } else { + ILOG(Error, Devel) << "Could not get the htemp histogram of the plotConfig '" << plotConfig.name << "'." << ENDM; + } + + if (plotConfig.graphs.size() > 1) { + legend->Draw(); + } else { + delete legend; + } + + c->Modified(); + c->Update(); + + return c; +} diff --git a/Framework/src/TrendingTaskConfig.cxx b/Framework/src/TrendingTaskConfig.cxx new file mode 100644 index 0000000000..8b79ced4b6 --- /dev/null +++ b/Framework/src/TrendingTaskConfig.cxx @@ -0,0 +1,123 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskConfig.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/TrendingTaskConfig.h" +#include + +namespace o2::quality_control::postprocessing +{ + +TrendingTaskConfig::TrendingTaskConfig(std::string id, const boost::property_tree::ptree& config) + : PostProcessingConfig(id, config) +{ + producePlotsOnUpdate = config.get("qc.postprocessing." + id + ".producePlotsOnUpdate", true); + resumeTrend = config.get("qc.postprocessing." + id + ".resumeTrend", false); + trendIfAllInputs = config.get("qc.postprocessing." + id + ".trendIfAllInputs", false); + trendingTimestamp = config.get("qc.postprocessing." + id + ".trendingTimestamp", "validUntil"); + + for (const auto& [_, plotConfig] : config.get_child("qc.postprocessing." + id + ".plots")) { + // since QC-1155 we allow for more than one graph in a single plot (canvas). we support both the new and old ways + // of configuring the expected plots. + std::vector graphs; + if (const auto& graphsConfig = plotConfig.get_child_optional("graphs"); graphsConfig.has_value()) { + for (const auto& [_, graphConfig] : graphsConfig.value()) { + // first we use name of the graph, if absent, we use graph title, if absent, we use plot (object) name. + const auto& name = graphConfig.get("name", graphConfig.get("title", plotConfig.get("name"))); + GraphStyle style; + style.lineColor = graphConfig.get("style.lineColor", -1); + style.lineStyle = graphConfig.get("style.lineStyle", -1); + style.lineWidth = graphConfig.get("style.lineWidth", -1); + style.markerColor = graphConfig.get("style.markerColor", -1); + style.markerStyle = graphConfig.get("style.markerStyle", -1); + style.markerSize = graphConfig.get("style.markerSize", -1.f); + style.fillColor = graphConfig.get("style.fillColor", -1); + style.fillStyle = graphConfig.get("style.fillStyle", -1); + + graphs.push_back({ name, + graphConfig.get("title", ""), + graphConfig.get("varexp"), + graphConfig.get("selection", ""), + graphConfig.get("option", ""), + graphConfig.get("graphErrors", ""), + style }); + } + } else { + GraphStyle style; + style.lineColor = plotConfig.get("style.lineColor", -1); + style.lineStyle = plotConfig.get("style.lineStyle", -1); + style.lineWidth = plotConfig.get("style.lineWidth", -1); + style.markerColor = plotConfig.get("style.markerColor", -1); + style.markerStyle = plotConfig.get("style.markerStyle", -1); + style.markerSize = plotConfig.get("style.markerSize", -1.f); + style.fillColor = plotConfig.get("style.fillColor", -1); + style.fillStyle = plotConfig.get("style.fillStyle", -1); + graphs.push_back({ plotConfig.get("name", ""), + plotConfig.get("title", ""), + plotConfig.get("varexp"), + plotConfig.get("selection", ""), + plotConfig.get("option", ""), + plotConfig.get("graphErrors", "") }); + } + + LegendConfig leg; + leg.nColumns = plotConfig.get("legend.nColumns", 1); + leg.x1 = plotConfig.get("legend.x1", -1.f); + leg.y1 = plotConfig.get("legend.y1", -1.f); + leg.x2 = plotConfig.get("legend.x2", -1.f); + leg.y2 = plotConfig.get("legend.y2", -1.f); + + plots.push_back({ plotConfig.get("name"), + plotConfig.get("title", ""), + plotConfig.get("graphAxisLabel", ""), + plotConfig.get("graphYRange", ""), + plotConfig.get("colorPalette", 0), + leg, + graphs }); + } + + const auto extractReductorParams = [](const boost::property_tree::ptree& dataSourceConfig) -> core::CustomParameters { + core::CustomParameters result; + if (const auto reductorParams = dataSourceConfig.get_child_optional("reductorParameters"); reductorParams.has_value()) { + result.populateCustomParameters(reductorParams.value()); + } + return result; + }; + + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + id + ".dataSources")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + sourceName.second.data(), + dataSourceConfig.second.get("reductorName"), + extractReductorParams(dataSourceConfig.second), + dataSourceConfig.second.get("moduleName") }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name"), + dataSourceConfig.second.get("reductorName"), + extractReductorParams(dataSourceConfig.second), + dataSourceConfig.second.get("moduleName") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + id + ".dataSources'"); + } + } +} + +} // namespace o2::quality_control::postprocessing diff --git a/Framework/src/TriggerHelpers.cxx b/Framework/src/TriggerHelpers.cxx new file mode 100644 index 0000000000..2c263908c4 --- /dev/null +++ b/Framework/src/TriggerHelpers.cxx @@ -0,0 +1,162 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TriggerHelpers.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/TriggerHelpers.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/PostProcessingConfig.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control::postprocessing::trigger_helpers +{ + +std::optional string2Seconds(std::string str) +{ + constexpr static char secondsStr[] = "sec"; + constexpr static char minutesStr[] = "min"; + constexpr static char hoursStr[] = "hour"; + + try { + if (size_t p = str.find(secondsStr); p != std::string::npos) { + return 1.0 * std::stod(str.substr(0, p)); + } else if (p = str.find(minutesStr); p != std::string::npos) { + return 60.0 * std::stod(str.substr(0, p)); + } else if (p = str.find(hoursStr); p != std::string::npos) { + return 3600.0 * std::stod(str.substr(0, p)); + } else { + return {}; + } + } catch (std::invalid_argument& ex) { + ILOG(Error, Support) << "Unexpected format of string describing time '" << str << "'" << ENDM; + throw ex; + } catch (std::out_of_range& ex) { + ILOG(Error, Support) << "Trying to convert time, which is out of supported range '" << str << "'" << ENDM; + throw ex; + } +} + +std::pair parseDbTriggers(const std::string& trigger, const std::string& type) +{ + // we expect the config string to be: + // type:[qcdb/ccdb]:qc/path/to/object + std::vector tokens; + boost::split(tokens, trigger, boost::is_any_of(":")); + + if (tokens.size() != 3) { + throw std::invalid_argument( + "The " + type + + " trigger is configured incorrectly. The expected format is " + "'" + + type + ":[qcdb/ccdb]:qc/path/to/object', received `" + + trigger + "'"); + } + + boost::algorithm::to_lower(tokens[1]); + const std::string& db = tokens[1]; + if (db != "qcdb" && db != "ccdb") { + throw std::invalid_argument("The second token in '" + trigger + "' should be either qcdb or ccdb"); + } + + const std::string& objPath = tokens[2]; + if (objPath.empty()) { + throw std::invalid_argument("The third token in '" + trigger + "' is empty, but it should contain the object path"); + } + + return { db, objPath }; +} + +TriggerFcn triggerFactory(const std::string& trigger, const PostProcessingConfig& config) +{ + // todo: should we accept many versions of trigger names? + std::string triggerLowerCase = trigger; + boost::algorithm::to_lower(triggerLowerCase); + auto activity = config.activity; + if (config.matchAnyRunNumber) { + activity.mId = 0; + } + + if (triggerLowerCase == "once") { + return triggers::Once(activity); + } else if (triggerLowerCase == "always") { + return triggers::Always(activity); + } else if (triggerLowerCase == "sor" || triggerLowerCase == "startofrun") { + return triggers::StartOfRun(config.kafkaBrokersUrl, config.kafkaTopicAliECSRun, config.detectorName, config.taskName, activity); + } else if (triggerLowerCase == "eor" || triggerLowerCase == "endofrun") { + return triggers::EndOfRun(config.kafkaBrokersUrl, config.kafkaTopicAliECSRun, config.detectorName, config.taskName, activity); + } else if (triggerLowerCase == "sof" || triggerLowerCase == "startoffill") { + return triggers::StartOfFill(activity); + } else if (triggerLowerCase == "eof" || triggerLowerCase == "endoffill") { + return triggers::EndOfFill(activity); + } else if (triggerLowerCase.find("newobject") != std::string::npos) { + const auto [db, objectPath] = parseDbTriggers(trigger, "newobject"); + const std::string& dbUrl = db == "qcdb" ? config.repository.at("host") : config.ccdbUrl; + return triggers::NewObject(dbUrl, db, objectPath, activity, trigger); + } else if (triggerLowerCase.find("foreachobject") != std::string::npos) { + const auto [db, objectPath] = parseDbTriggers(trigger, "foreachobject"); + const std::string& dbUrl = db == "qcdb" ? config.repository.at("host") : config.ccdbUrl; + return triggers::ForEachObject(dbUrl, db, objectPath, activity, trigger); + } else if (triggerLowerCase.find("foreachlatest") != std::string::npos) { + const auto [db, objectPath] = parseDbTriggers(trigger, "foreachlatest"); + const std::string& dbUrl = db == "qcdb" ? config.repository.at("host") : config.ccdbUrl; + return triggers::ForEachLatest(dbUrl, db, objectPath, activity, trigger); + } else if (auto seconds = string2Seconds(triggerLowerCase); seconds.has_value()) { + if (seconds.value() < 0) { + throw std::invalid_argument("negative number of seconds in trigger '" + trigger + "'"); + } + return triggers::Periodic(seconds.value(), activity, trigger); + } else if (triggerLowerCase.find("user") != std::string::npos || triggerLowerCase.find("control") != std::string::npos) { + return triggers::Never(activity); + } else { + throw std::invalid_argument("unknown trigger: " + trigger); + } +} + +Trigger tryTrigger(std::vector& triggerFcns) +{ + Trigger result(TriggerType::No); + auto it = triggerFcns.begin(); + while (it != triggerFcns.end()) { + auto trigger = (*it)(); + it = trigger.last ? triggerFcns.erase(it) : it + 1; + if (trigger) { + return trigger; + } + } + return { TriggerType::No }; +} + +std::vector createTriggers(const std::vector& triggerNames, const PostProcessingConfig& config) +{ + std::vector triggerFcns; + triggerFcns.reserve(triggerNames.size()); + for (const auto& triggerName : triggerNames) { + triggerFcns.push_back(triggerFactory(triggerName, config)); + } + return triggerFcns; +} + +bool hasUserOrControlTrigger(const std::vector& triggerNames) +{ + return std::find_if(triggerNames.begin(), triggerNames.end(), [](std::string name) { + boost::algorithm::to_lower(name); + return name.find("user") != std::string::npos || name.find("control") != std::string::npos; + }) != triggerNames.end(); +} + +} // namespace o2::quality_control::postprocessing::trigger_helpers diff --git a/Framework/src/Triggers.cxx b/Framework/src/Triggers.cxx new file mode 100644 index 0000000000..39476d3744 --- /dev/null +++ b/Framework/src/Triggers.cxx @@ -0,0 +1,374 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Triggers.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/Triggers.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/ActivityHelpers.h" +#include "QualityControl/CcdbDatabase.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/KafkaPoller.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono; +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; + +namespace o2::quality_control::postprocessing +{ + +std::ostream& operator<<(std::ostream& out, const Trigger& t) +{ + out << "triggerType: " << t.triggerType << ", timestamp: " << t.timestamp; + return out; +} + +uint64_t Trigger::msSinceEpoch() +{ + return duration_cast(system_clock::now().time_since_epoch()).count(); +} + +namespace triggers +{ + +TriggerFcn NotImplemented(std::string triggerName) +{ + ILOG(Warning, Support) << "TriggerType '" << triggerName << "' is not implemented yet. It will always return TriggerType::No" << ENDM; + return [triggerName]() mutable -> Trigger { + return { TriggerType::No, true }; + }; +} + +std::string createKafkaGroupId(std::string_view prefix, std::string_view detector, std::string_view taskName) +{ + std::string groupId; + constexpr int numberOfUnderscores = 2; + groupId.reserve(prefix.size() + detector.size() + taskName.size() + numberOfUnderscores); + groupId.append(prefix).append("_").append(detector).append("_").append(taskName); + + return groupId; +} + +bool checkKafkaParams(const std::string& kafkaBrokers, const std::string& topic, const std::string_view triggerTypeLogId) +{ + if (kafkaBrokers.empty()) { + ILOG(Error, Support) << "You are tring to create " << triggerTypeLogId << " trigger using Kafka without any brokers, fill config value 'kafkaBrokersUrl'" << ENDM; + return false; + } + if (topic.empty()) { + ILOG(Error, Support) << "You are tring to consume empty Kafka topic from '" << triggerTypeLogId << "' trigger, fill config value 'kafkaTopic'" << ENDM; + return false; + } + return true; +} + +TriggerFcn StartOfRun(const std::string& kafkaBrokers, const std::string& topic, const std::string& detector, const std::string& taskName, const core::Activity& activity) +{ + if (!checkKafkaParams(kafkaBrokers, topic, "SOR")) { + throw std::invalid_argument{ "We don't have enough information to consume Kafka. Check IL" }; + } + + auto copiedActivity = activity; + auto poller = std::make_shared(kafkaBrokers, createKafkaGroupId("SOR_postprocessing", detector, taskName)); + poller->subscribe(topic); + return [poller, copiedActivity]() mutable -> Trigger { + for (const auto& record : poller->poll()) { + if (auto event = proto::recordToEvent(record.value())) { + if (proto::start_of_run::isValid(*event, copiedActivity.mPartitionName, copiedActivity.mId)) { + auto newActivityForTrigger = copiedActivity; + proto::start_of_run::fillActivity(*event, newActivityForTrigger); + return { TriggerType::StartOfRun, false, newActivityForTrigger, static_cast(event->timestamp()), "sor" }; + } + } + } + return { TriggerType::No, false, copiedActivity, Trigger::msSinceEpoch(), "sor" }; + }; +} + +TriggerFcn Once(const Activity& activity) +{ + Activity returnedActivity = activity; + returnedActivity.mValidity = gInvalidValidityInterval; + return [hasTriggered = false, returnedActivity]() mutable -> Trigger { + if (hasTriggered) { + return { TriggerType::No, true, returnedActivity, Trigger::msSinceEpoch(), "once" }; + } else { + hasTriggered = true; + return { TriggerType::Once, true, returnedActivity, Trigger::msSinceEpoch(), "once" }; + } + }; +} + +TriggerFcn Always(const Activity& activity) +{ + return [activity]() mutable -> Trigger { + return { TriggerType::Always, false, activity, Trigger::msSinceEpoch(), "always" }; + }; +} + +TriggerFcn Never(const Activity& activity) +{ + return [activity]() mutable -> Trigger { + return { TriggerType::No, true, activity, Trigger::msSinceEpoch(), "never" }; + }; +} + +TriggerFcn EndOfRun(const std::string& kafkaBrokers, const std::string& topic, const std::string& detector, const std::string& taskName, const Activity& activity) +{ + if (!checkKafkaParams(kafkaBrokers, topic, "EOR")) { + throw std::invalid_argument{ "We don't have enough information to consume Kafka. Check IL" }; + } + + auto copiedActivity = activity; + auto poller = std::make_shared(kafkaBrokers, createKafkaGroupId("EOR_postprocessing", detector, taskName)); + poller->subscribe(topic); + return [poller, copiedActivity]() mutable -> Trigger { + for (const auto& record : poller->poll()) { + if (auto event = proto::recordToEvent(record.value())) { + if (proto::end_of_run::isValid(*event, copiedActivity.mPartitionName, copiedActivity.mId)) { + auto newActivityForTrigger = copiedActivity; + proto::end_of_run::fillActivity(*event, newActivityForTrigger); + return { TriggerType::EndOfRun, false, newActivityForTrigger, static_cast(event->timestamp()), "eor" }; + } + } + } + return { TriggerType::No, false, copiedActivity, Trigger::msSinceEpoch(), "eor" }; + }; +} + +TriggerFcn StartOfFill(const Activity&) +{ + return NotImplemented("StartOfFill"); +} + +TriggerFcn EndOfFill(const Activity&) +{ + return NotImplemented("EndOfFill"); +} + +TriggerFcn Periodic(double seconds, const Activity& activity, std::string config) +{ + AliceO2::Common::Timer timer; + timer.reset(static_cast(seconds * 1000000)); + auto resultActivity = activity; + resultActivity.mValidity = gInvalidValidityInterval; + + return [timer, resultActivity, config]() mutable -> Trigger { + if (timer.isTimeout()) { + // We calculate the exact time when timer has passed + uint64_t timestamp = Trigger::msSinceEpoch() + static_cast(timer.getRemainingTime() * 1000); + // increment until it is cleared (in case that more than one cycle has passed) + // let's hope there is no bug, which would make us stay in that loop forever + while (timer.isTimeout()) { + timer.increment(); + } + resultActivity.mValidity.update(timestamp); + return { TriggerType::Periodic, false, resultActivity, timestamp, config }; + } else { + return { TriggerType::No, false, resultActivity, Trigger::msSinceEpoch(), config }; + } + }; +} + +TriggerFcn NewObject(const std::string& databaseUrl, const std::string& databaseType, const std::string& objectPath, const Activity& activity, const std::string& config) +{ + // Key names in the header map. + constexpr auto timestampKey = metadata_keys::validFrom; + auto fullObjectPath = (databaseType == "qcdb" ? activity.mProvenance + "/" : "") + objectPath; + auto metadata = databaseType == "qcdb" ? activity_helpers::asDatabaseMetadata(activity, false) : std::map(); + auto objectActivity = activity; + + ILOG(Debug, Support) << "Initializing newObject trigger for the object '" << fullObjectPath << "' and Activity '" << activity << "'" << ENDM; + // We support only CCDB here. + auto db = std::make_shared(); + db->connect(databaseUrl, "", "", ""); + + // Returns "Valid-From" of an object if there is a new one, otherwise 0. + auto newObjectValidity = [db, fullObjectPath, metadata, databaseUrl, activity, lastModified = validity_time_t{ 0 }]() mutable -> ValidityInterval { + const auto listing = db->getListingAsPtree(fullObjectPath, metadata, true); + if (listing.count("objects") == 0) { + ILOG(Warning, Support) << "Could not get a valid listing from db '" << databaseUrl << "' for object '" << fullObjectPath << "'" << ENDM; + return gInvalidValidityInterval; + } + const auto& objects = listing.get_child("objects"); + if (objects.empty()) { + // We don't make a fuss over it, because we might be just waiting for the first version of such object. + // Apparently it happens always for a few iterations at SOR, so Warnings might be too annoying. + ILOG(Debug, Devel) << "Could not find the file '" << fullObjectPath << "' in the db '" + << databaseUrl << "' for given Activity settings (" << activity << "). Zeroes and empty strings are treated as wildcards." << ENDM; + return gInvalidValidityInterval; + } else if (objects.size() > 1) { + ILOG(Warning, Support) << "Expected just one metadata entry for object '" << fullObjectPath << "'. Trying to continue by using the first." << ENDM; + } + + const auto& object = objects.front().second; + validity_time_t newLastModified = object.get(metadata_keys::lastModified, 0); + if (newLastModified > lastModified) { + lastModified = newLastModified; + return { object.get(metadata_keys::validFrom, 0), object.get(metadata_keys::validUntil) }; + } + return gInvalidValidityInterval; + }; + // we execute it once before in order to know about the latest existing object. + newObjectValidity(); + + return [objectActivity, config, newObjectValidity]() mutable -> Trigger { + if (auto validity = newObjectValidity(); validity.isValid()) { + if (getenv("QC_DISABLE_NEWOBJECT_DELAY") == nullptr) { + // On rare occasions we might run into the following race condition: + // 1) A CheckRunner starts to publish a collection of MOs for a QC Task + // 2) A PostProcessing task receives a newobject trigger for a just-published object + // 3) The PP task tries to retrieve also other objects normally published by the same QC task, it fails + // because not all were published yet. + // 4) The CheckRunner finishes publishing the collection of MOs + // To avoid this scenario, a small delay is added before returning the trigger. Considerations about other + // possible solutions are included in the commit message. + std::this_thread::sleep_for(std::chrono::seconds{ 1 }); + } + objectActivity.mValidity = validity; + auto timestamp = activity_helpers::isLegacyValidity(validity) ? validity.getMin() : (validity.getMax() - 1); + return { TriggerType::NewObject, false, objectActivity, timestamp, config }; + } + objectActivity.mValidity = gInvalidValidityInterval; + return { TriggerType::No, false, objectActivity, Trigger::msSinceEpoch(), config }; + }; +} + +TriggerFcn ForEachObject(const std::string& databaseUrl, const std::string& databaseType, const std::string& objectPath, const Activity& activity, const std::string& config) +{ + // Key names in the header map. + constexpr auto timestampSortKey = metadata_keys::validFrom; + auto fullObjectPath = (databaseType == "qcdb" ? activity.mProvenance + "/" : "") + objectPath; + + // We support only CCDB here. + auto db = std::make_shared(); + db->connect(databaseUrl, "", "", ""); + + auto objects = db->getListingAsPtree(fullObjectPath).get_child("objects"); + ILOG(Info, Support) << "Got " << objects.size() << " objects for the path '" << fullObjectPath << "'" << ENDM; + auto filteredObjects = std::make_shared>(); + const auto filter = databaseType == "qcdb" ? activity : Activity(); + + ILOG(Debug, Devel) << "Filter activity: " << activity << ENDM; + + // As for today, we receive objects in the order of the newest to the oldest. + // We prefer the other order here. + for (auto rit = objects.rbegin(); rit != objects.rend(); ++rit) { + auto objectActivity = activity_helpers::asActivity(rit->second, activity.mProvenance); + ILOG(Debug, Trace) << "Matching the filter with object's activity: " << objectActivity << ENDM; + if (filter.matches(objectActivity)) { + filteredObjects->emplace_back(rit->second); + ILOG(Debug, Devel) << "Matched an object with activity: " << activity << ENDM; + } + } + ILOG(Info, Support) << filteredObjects->size() << " objects matched the specified activity" << ENDM; + + // we make sure it is sorted. If it is already, it shouldn't cost much. + std::sort(filteredObjects->begin(), filteredObjects->end(), + [](const boost::property_tree::ptree& a, const boost::property_tree::ptree& b) { + return a.get(timestampSortKey) < b.get(timestampSortKey); + }); + + return [filteredObjects, activity, currentObject = filteredObjects->begin(), config]() mutable -> Trigger { + if (currentObject != filteredObjects->end()) { + auto currentActivity = activity_helpers::asActivity(*currentObject, activity.mProvenance); + bool last = currentObject + 1 == filteredObjects->end(); + Trigger trigger(TriggerType::ForEachObject, last, currentActivity, currentObject->get(timestampSortKey)); + if (auto cycle = currentObject->get_optional(metadata_keys::cycleNumber); cycle.has_value()) { + trigger.metadata.emplace(metadata_keys::cycleNumber, cycle.value()); + } + ++currentObject; + + return trigger; + } else { + return { TriggerType::No, true, activity, Trigger::msSinceEpoch(), config }; + } + }; +} + +TriggerFcn ForEachLatest(const std::string& databaseUrl, const std::string& databaseType, const std::string& objectPath, const Activity& activity, const std::string& config) +{ + // Key names in the header map. + constexpr auto timestampSortKey = metadata_keys::created; + auto fullObjectPath = (databaseType == "qcdb" ? activity.mProvenance + "/" : "") + objectPath; + + // We support only CCDB here. + auto db = std::make_shared(); + db->connect(databaseUrl, "", "", ""); + + auto objects = db->getListingAsPtree(fullObjectPath).get_child("objects"); + ILOG(Info, Support) << "Got " << objects.size() << " objects for the path '" << fullObjectPath << "'" << ENDM; + auto filteredObjects = std::make_shared>>(); + const auto filter = databaseType == "qcdb" ? activity : Activity(); + + ILOG(Debug, Devel) << "Filter activity: " << activity << ENDM; + + // As for today, we receive objects in the order of the newest to the oldest. + // The inverse order is more likely to follow what we want (ascending by period/pass/run), + // thus sorting may take less time. + for (auto rit = objects.rbegin(); rit != objects.rend(); ++rit) { + auto objectActivity = activity_helpers::asActivity(rit->second, activity.mProvenance); + ILOG(Debug, Trace) << "Matching the filter with object's activity: " << objectActivity << ENDM; + if (filter.matches(objectActivity)) { + auto latestObject = std::find_if(filteredObjects->begin(), filteredObjects->end(), [&](const std::pair& entry) { + return entry.first.same(objectActivity); + }); + if (latestObject != filteredObjects->end() && latestObject->second.get(timestampSortKey) < rit->second.get(timestampSortKey)) { + *latestObject = { objectActivity, rit->second }; + ILOG(Debug, Devel) << "Updated the object with activity: " << objectActivity << ENDM; + } else { + filteredObjects->emplace_back(objectActivity, rit->second); + ILOG(Debug, Devel) << "Matched an object with activity: " << objectActivity << ENDM; + } + } + } + ILOG(Info, Support) << filteredObjects->size() << " objects matched the specified activity" << ENDM; + + // Since we select concrete objects per each combination of run/pass/period, + // we sort the entries in the ascending order by period, pass and run. + std::sort(filteredObjects->begin(), filteredObjects->end(), + [](const std::pair& a, const std::pair& b) { + return std::forward_as_tuple(a.second.get(metadata_keys::periodName, ""), + a.second.get(metadata_keys::passName, ""), + a.second.get(metadata_keys::runNumber, 0)) < + std::forward_as_tuple(b.second.get(metadata_keys::periodName, ""), + b.second.get(metadata_keys::passName, ""), + b.second.get(metadata_keys::runNumber, 0)); + }); + + return [filteredObjects, activity, currentObject = filteredObjects->begin(), config]() mutable -> Trigger { + if (currentObject != filteredObjects->end()) { + const auto& currentActivity = currentObject->first; + const auto& currentPtree = currentObject->second; + bool last = currentObject + 1 == filteredObjects->end(); + Trigger trigger(TriggerType::ForEachLatest, last, currentActivity, currentPtree.get(metadata_keys::validFrom), config); + ++currentObject; + return trigger; + } else { + return { TriggerType::No, true, activity, Trigger::msSinceEpoch(), config }; + } + }; +} + +} // namespace triggers + +} // namespace o2::quality_control::postprocessing diff --git a/Framework/src/UpdatePolicyManager.cxx b/Framework/src/UpdatePolicyManager.cxx new file mode 100644 index 0000000000..47c87c734a --- /dev/null +++ b/Framework/src/UpdatePolicyManager.cxx @@ -0,0 +1,212 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file UpdatePolicyManager.cxx +/// \author Barthelemy von Haller +/// + +#include + +#include "QualityControl/UpdatePolicyManager.h" + +#include "QualityControl/QcInfoLogger.h" +#include "Common/Exceptions.h" + +using namespace AliceO2::Common; + +namespace o2::quality_control::checker +{ + +void UpdatePolicyManager::updateGlobalRevision() +{ + ++mGlobalRevision; + if (mGlobalRevision == 0) { + // mGlobalRevision cannot be 0 + // 0 means overflow, increment and update all check revisions to 0 + ++mGlobalRevision; + for (auto& actor : mPoliciesByActor) { + updateActorRevision(actor.second.actorName, 0); + } + } +} + +void UpdatePolicyManager::updateActorRevision(const std::string& actorName, RevisionType revision) +{ + if (mPoliciesByActor.count(actorName) == 0) { + ILOG(Error, Support) << "Cannot update revision for " << actorName << " : object not found" << ENDM; + BOOST_THROW_EXCEPTION(ObjectNotFoundError() << errinfo_object_name(actorName)); + } + mPoliciesByActor.at(actorName).revision = revision; +} + +void UpdatePolicyManager::updateActorRevision(const std::string& actorName) +{ + updateActorRevision(actorName, mGlobalRevision); +} + +void UpdatePolicyManager::updateObjectRevision(const std::string& objectName, RevisionType revision) +{ + mObjectsRevision[objectName] = revision; +} + +void UpdatePolicyManager::updateObjectRevision(const std::string& objectName) +{ + updateObjectRevision(objectName, mGlobalRevision); +} + +void UpdatePolicyManager::addPolicy(const std::string& actorName, UpdatePolicyType policyType, std::vector objectNames, bool allObjects, bool policyHelper) +{ + IsReadyFunctionType isReadyFunction; + switch (policyType) { + case UpdatePolicyType::OnAll: { + /** + * Run check if all MOs are updated + */ + isReadyFunction = [&, actorName]() { + for (const auto& objectName : mPoliciesByActor.at(actorName).inputObjects) { + // QC-1033 - failure to use this policy with checks producing single QO + std::string objectNameLocal = objectName; + if (objectNameLocal.back() == '/') { + ILOG(Debug, Devel) << "OnAll - remove the final slash" << ENDM; + objectNameLocal.pop_back(); + } + if (mObjectsRevision.count(objectNameLocal) == 0 || mObjectsRevision.at(objectNameLocal) <= mPoliciesByActor.at(actorName).revision) { + return false; + } + } + return true; + }; + break; + } + case UpdatePolicyType::OnAnyNonZero: { + /** + * Return true if any declared MOs were updated + * Guarantee that all declared MOs are available + */ + isReadyFunction = [&, actorName]() { + if (!mPoliciesByActor.at(actorName).policyHelperFlag) { + // Check if all monitor objects are available + for (const auto& objectName : mPoliciesByActor.at(actorName).inputObjects) { + // QC-1033 - failure to use this policy with checks producing single QO + std::string objectNameLocal = objectName; + if (objectNameLocal.back() == '/') { + ILOG(Debug, Devel) << "OnAnyNonZero - remove the final slash" << ENDM; + objectNameLocal.pop_back(); + } + + if (!mObjectsRevision.count(objectNameLocal)) { + return false; + } + } + // From now on all MOs are available + mPoliciesByActor.at(actorName).policyHelperFlag = true; + } + + for (const auto& objectName : mPoliciesByActor.at(actorName).inputObjects) { + std::string objectNameLocal = objectName; + if (objectNameLocal.back() == '/') { + ILOG(Debug, Devel) << "OnAnyNonZero - remove the final slash" << ENDM; + objectNameLocal.pop_back(); + } + if (mObjectsRevision[objectNameLocal] > mPoliciesByActor.at(actorName).revision) { + return true; + } + } + return false; + }; + break; + } + case UpdatePolicyType::OnEachSeparately: { + /** + * Return true if any declared object were updated. + * This is the same behaviour as OnAny. + */ + isReadyFunction = [&, actorName]() { + if (mPoliciesByActor.at(actorName).allInputObjects) { + return true; + } + + for (const auto& objectName : mPoliciesByActor.at(actorName).inputObjects) { + if (mObjectsRevision.count(objectName) && mObjectsRevision[objectName] > mPoliciesByActor.at(actorName).revision) { + return true; + } + } + return false; + }; + break; + } + case UpdatePolicyType::OnGlobalAny: { + /** + * Return true if any MOs were updated. + * Inner policy - used for `"MOs": "all"` + * Might return true even if MO is not used in actor. + */ + + isReadyFunction = []() { + // Expecting check of this policy only if any change + return true; + }; + break; + } + case UpdatePolicyType::OnAny: { + /** + * Default behaviour + * + * Return true if any MOs are updated + */ + isReadyFunction = [&, actorName]() { + for (const auto& objectName : mPoliciesByActor.at(actorName).inputObjects) { + if (mObjectsRevision.count(objectName) && mObjectsRevision[objectName] > mPoliciesByActor.at(actorName).revision) { + return true; + } + } + return false; + }; + break; + } + } + + mPoliciesByActor[actorName] = { actorName, isReadyFunction, std::move(objectNames), allObjects, policyHelper }; + + ILOG(Info, Devel) << "Added a policy : " << mPoliciesByActor[actorName] << ENDM; +} + +bool UpdatePolicyManager::isReady(const std::string& actorName) +{ + if (mPoliciesByActor.count(actorName) == 0) { + ILOG(Error, Support) << "Cannot check if " << actorName << " is ready : object not found" << ENDM; + BOOST_THROW_EXCEPTION(ObjectNotFoundError() << errinfo_object_name(actorName)); + } + return mPoliciesByActor.at(actorName).isReady(); +} + +std::ostream& operator<<(std::ostream& out, const UpdatePolicy& updatePolicy) // output +{ + out << "actorName: " << updatePolicy.actorName + << "; allInputObjects: " << updatePolicy.allInputObjects + << "; policyHelperFlag: " << updatePolicy.policyHelperFlag + << "; revision: " << updatePolicy.revision + << "; inputObjects: "; + for (const auto& item : updatePolicy.inputObjects) { + out << item << ", "; + } + return out; +} + +void UpdatePolicyManager::reset() +{ + mPoliciesByActor.clear(); + mObjectsRevision.clear(); + mGlobalRevision = 1; +} + +} // namespace o2::quality_control::checker \ No newline at end of file diff --git a/Framework/src/UpdatePolicyType.cxx b/Framework/src/UpdatePolicyType.cxx new file mode 100644 index 0000000000..baaef5a3a7 --- /dev/null +++ b/Framework/src/UpdatePolicyType.cxx @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file UpdatePolicyType.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/UpdatePolicyType.h" + +#include + +namespace o2::quality_control::checker +{ + +UpdatePolicyType UpdatePolicyTypeUtils::FromString(const std::string& str) +{ + static std::unordered_map const updatePolicyTypeFromString = { + { "OnAny", UpdatePolicyType::OnAny }, + { "OnAnyNonZero", UpdatePolicyType::OnAnyNonZero }, + { "OnAll", UpdatePolicyType::OnAll }, + { "OnEachSeparately", UpdatePolicyType::OnEachSeparately }, + { "OnGlobalAny", UpdatePolicyType::OnGlobalAny } + }; + + return updatePolicyTypeFromString.at(str); +} + +std::string UpdatePolicyTypeUtils::ToString(UpdatePolicyType policyType) +{ + static std::unordered_map const stringFromUpdatePolicyType = { + { UpdatePolicyType::OnAny, "OnAny" }, + { UpdatePolicyType::OnAnyNonZero, "OnAnyNonZero" }, + { UpdatePolicyType::OnAll, "OnAll" }, + { UpdatePolicyType::OnEachSeparately, "OnEachSeparately" }, + { UpdatePolicyType::OnGlobalAny, "OnGlobalAny" } + }; + return stringFromUpdatePolicyType.at(policyType); +} + +} // namespace o2::quality_control::checker \ No newline at end of file diff --git a/Framework/src/UserCodeInterface.cxx b/Framework/src/UserCodeInterface.cxx new file mode 100644 index 0000000000..96ee5bcdb3 --- /dev/null +++ b/Framework/src/UserCodeInterface.cxx @@ -0,0 +1,56 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file UserCodeInterface.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/UserCodeInterface.h" +#include +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseFactory.h" + +using namespace o2::ccdb; +using namespace std; + +namespace o2::quality_control::core +{ + +void UserCodeInterface::setCustomParameters(const CustomParameters& parameters) +{ + mCustomParameters = parameters; + configure(); +} + +const std::string& UserCodeInterface::getName() const +{ + return mName; +} + +void UserCodeInterface::setName(const std::string& name) +{ + mName = name; +} + +void UserCodeInterface::setDatabase(std::unordered_map dbConfig) +{ + if (dbConfig.count("implementation") == 0 || dbConfig.count("host") == 0) { + ILOG(Error, Devel) << "dbConfig is incomplete, we don't build the user code database instance" << ENDM; + throw std::invalid_argument("Cannot set database in UserCodeInterface"); + } + + mDatabase = repository::DatabaseFactory::create(dbConfig.at("implementation")); + mDatabase->connect(dbConfig); + ILOG(Debug, Devel) << "Database that is going to be used > Implementation : " << dbConfig.at("implementation") << " / Host : " << dbConfig.at("host") << ENDM; +} + +} // namespace o2::quality_control::core \ No newline at end of file diff --git a/Framework/src/UserInputOutput.cxx b/Framework/src/UserInputOutput.cxx new file mode 100644 index 0000000000..e7859602eb --- /dev/null +++ b/Framework/src/UserInputOutput.cxx @@ -0,0 +1,57 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file UserInputOutput.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/UserInputOutput.h" + +namespace o2::quality_control::core +{ + +framework::ConcreteDataMatcher + createUserDataMatcher(DataSourceType dataSourceType, const std::string& detectorName, const std::string& userCodeName, + o2::header::DataHeader::SubSpecificationType subSpec) +{ + return { + createDataOrigin(dataSourceType, detectorName), + createDataDescription(userCodeName, dataSourceType), + subSpec + }; +} + +framework::InputSpec + createUserInputSpec(DataSourceType dataSourceType, const std::string& detectorName, const std::string& userCodeName, + o2::header::DataHeader::SubSpecificationType subSpec, const std::string& binding) +{ + // currently all of our outputs are Lifetime::Sporadic, so we don't allow for customization, but it could be factored out. + return { + binding.empty() ? userCodeName : binding, + createUserDataMatcher(dataSourceType, detectorName, userCodeName, subSpec), + framework::Lifetime::Sporadic + }; +} + +framework::OutputSpec + createUserOutputSpec(DataSourceType dataSourceType, const std::string& detectorName, const std::string& userCodeName, + o2::header::DataHeader::SubSpecificationType subSpec, const framework::OutputLabel& binding) +{ + // currently all of our outputs are Lifetime::Sporadic, so we don't allow for customization, but it could be factored out. + return { + binding.value.empty() ? framework::OutputLabel{ userCodeName } : binding, + createUserDataMatcher(dataSourceType, detectorName, userCodeName, subSpec), + framework::Lifetime::Sporadic + }; +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/WorkflowType.cxx b/Framework/src/WorkflowType.cxx new file mode 100644 index 0000000000..ec6c96613e --- /dev/null +++ b/Framework/src/WorkflowType.cxx @@ -0,0 +1,43 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file WorkflowType.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/WorkflowType.h" +#include + +namespace o2::quality_control::core +{ + +namespace workflow_type_helpers +{ +WorkflowType getWorkflowType(const framework::ConfigParamRegistry& options) +{ + if (options.get("local")) { + return WorkflowType::Local; + } else if (options.get("remote")) { + return WorkflowType::Remote; + } else if (options.get("full-chain")) { + return WorkflowType::FullChain; + } else if (!options.get("local-batch").empty()) { + return WorkflowType::LocalBatch; + } else if (!options.get("remote-batch").empty()) { + return WorkflowType::RemoteBatch; + } else { + return WorkflowType::Standalone; + } +} +} // namespace workflow_type_helpers + +} // namespace o2::quality_control::core \ No newline at end of file diff --git a/Framework/src/imgui/BaseGui.cxx b/Framework/src/imgui/BaseGui.cxx deleted file mode 100644 index 2986621837..0000000000 --- a/Framework/src/imgui/BaseGui.cxx +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -// Copied from AliceO2, work of Giulio - -#include "GL/gl3w.h" // This example is using gl3w to access OpenGL functions (because it is small). You may use glew/glad/glLoadGen/etc. whatever already works for you. -#include "imgui.h" -#include "imgui_impl_glfw_gl3.h" -#include -#include -#include - -static void error_callback(int error, const char* description) -{ - fprintf(stderr, "Error %d: %s\n", error, description); -} - -namespace o2 -{ -namespace framework -{ - -// @return an object of kind GLFWwindow* as void* to avoid having a direct dependency -void* initGUI(const char* name) -{ - // Setup window - glfwSetErrorCallback(error_callback); - if (!glfwInit()) - return nullptr; - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); -#if __APPLE__ - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); -#endif - GLFWwindow* window = glfwCreateWindow(1280, 720, name, NULL, NULL); - glfwMakeContextCurrent(window); - gl3wInit(); - - // Setup ImGui binding - ImGui_ImplGlfwGL3_Init(window, true); - - // Load Fonts - // (there is a default font, this is only if you want to change it. see extra_fonts/README.txt for more details) - // ImGuiIO& io = ImGui::GetIO(); - // io.Fonts->AddFontDefault(); - // io.Fonts->AddFontFromFileTTF("../../extra_fonts/Cousine-Regular.ttf", 15.0f); - // io.Fonts->AddFontFromFileTTF("../../extra_fonts/DroidSans.ttf", 16.0f); - // io.Fonts->AddFontFromFileTTF("../../extra_fonts/ProggyClean.ttf", 13.0f); - // io.Fonts->AddFontFromFileTTF("../../extra_fonts/ProggyTiny.ttf", 10.0f); - // io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese()); - - return window; -} - -/// @return true if we do not need to exit, false if we do. -bool pollGUI(void* context, std::function guiCallback) -{ - GLFWwindow* window = reinterpret_cast(context); - if (glfwWindowShouldClose(window)) { - return false; - } - glfwPollEvents(); - ImGui_ImplGlfwGL3_NewFrame(); - - // This is where the magic actually happens... - if (guiCallback) { - guiCallback(); - } - ImVec4 clear_color = ImColor(114, 144, 154); - - // Rendering - int display_w, display_h; - glfwGetFramebufferSize(window, &display_w, &display_h); - glViewport(0, 0, display_w, display_h); - glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w); - glClear(GL_COLOR_BUFFER_BIT); - ImGui::Render(); - glfwSwapBuffers(window); - return true; -} - -void disposeGUI() -{ - // Cleanup - ImGui_ImplGlfwGL3_Shutdown(); - glfwTerminate(); -} - -} // namespace framework -} // namespace o2 diff --git a/Framework/src/imgui/GL/gl3w.h b/Framework/src/imgui/GL/gl3w.h deleted file mode 100644 index 1ca2a39551..0000000000 --- a/Framework/src/imgui/GL/gl3w.h +++ /dev/null @@ -1,1374 +0,0 @@ -/* - - This file was generated with gl3w_gen.py, part of gl3w - (hosted at https://github.com/skaslev/gl3w) - - This is free and unencumbered software released into the public domain. - - Anyone is free to copy, modify, publish, use, compile, sell, or - distribute this software, either in source code form or as a compiled - binary, for any purpose, commercial or non-commercial, and by any - means. - - In jurisdictions that recognize copyright laws, the author or authors - of this software dedicate any and all copyright interest in the - software to the public domain. We make this dedication for the benefit - of the public at large and to the detriment of our heirs and - successors. We intend this dedication to be an overt act of - relinquishment in perpetuity of all present and future rights to this - software under copyright law. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR - OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef __gl3w_h_ -#define __gl3w_h_ - -#include "glcorearb.h" - -#ifndef __gl_h_ -#define __gl_h_ -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void (*GL3WglProc)(void); -typedef GL3WglProc (*GL3WGetProcAddressProc)(const char* proc); - -/* gl3w api */ -int gl3wInit(void); -int gl3wInit2(GL3WGetProcAddressProc proc); -int gl3wIsSupported(int major, int minor); -GL3WglProc gl3wGetProcAddress(const char* proc); - -/* gl3w internal state */ -union GL3WProcs { - GL3WglProc ptr[653]; - struct { - PFNGLACTIVESHADERPROGRAMPROC ActiveShaderProgram; - PFNGLACTIVETEXTUREPROC ActiveTexture; - PFNGLATTACHSHADERPROC AttachShader; - PFNGLBEGINCONDITIONALRENDERPROC BeginConditionalRender; - PFNGLBEGINQUERYPROC BeginQuery; - PFNGLBEGINQUERYINDEXEDPROC BeginQueryIndexed; - PFNGLBEGINTRANSFORMFEEDBACKPROC BeginTransformFeedback; - PFNGLBINDATTRIBLOCATIONPROC BindAttribLocation; - PFNGLBINDBUFFERPROC BindBuffer; - PFNGLBINDBUFFERBASEPROC BindBufferBase; - PFNGLBINDBUFFERRANGEPROC BindBufferRange; - PFNGLBINDBUFFERSBASEPROC BindBuffersBase; - PFNGLBINDBUFFERSRANGEPROC BindBuffersRange; - PFNGLBINDFRAGDATALOCATIONPROC BindFragDataLocation; - PFNGLBINDFRAGDATALOCATIONINDEXEDPROC BindFragDataLocationIndexed; - PFNGLBINDFRAMEBUFFERPROC BindFramebuffer; - PFNGLBINDIMAGETEXTUREPROC BindImageTexture; - PFNGLBINDIMAGETEXTURESPROC BindImageTextures; - PFNGLBINDPROGRAMPIPELINEPROC BindProgramPipeline; - PFNGLBINDRENDERBUFFERPROC BindRenderbuffer; - PFNGLBINDSAMPLERPROC BindSampler; - PFNGLBINDSAMPLERSPROC BindSamplers; - PFNGLBINDTEXTUREPROC BindTexture; - PFNGLBINDTEXTUREUNITPROC BindTextureUnit; - PFNGLBINDTEXTURESPROC BindTextures; - PFNGLBINDTRANSFORMFEEDBACKPROC BindTransformFeedback; - PFNGLBINDVERTEXARRAYPROC BindVertexArray; - PFNGLBINDVERTEXBUFFERPROC BindVertexBuffer; - PFNGLBINDVERTEXBUFFERSPROC BindVertexBuffers; - PFNGLBLENDCOLORPROC BlendColor; - PFNGLBLENDEQUATIONPROC BlendEquation; - PFNGLBLENDEQUATIONSEPARATEPROC BlendEquationSeparate; - PFNGLBLENDEQUATIONSEPARATEIPROC BlendEquationSeparatei; - PFNGLBLENDEQUATIONIPROC BlendEquationi; - PFNGLBLENDFUNCPROC BlendFunc; - PFNGLBLENDFUNCSEPARATEPROC BlendFuncSeparate; - PFNGLBLENDFUNCSEPARATEIPROC BlendFuncSeparatei; - PFNGLBLENDFUNCIPROC BlendFunci; - PFNGLBLITFRAMEBUFFERPROC BlitFramebuffer; - PFNGLBLITNAMEDFRAMEBUFFERPROC BlitNamedFramebuffer; - PFNGLBUFFERDATAPROC BufferData; - PFNGLBUFFERSTORAGEPROC BufferStorage; - PFNGLBUFFERSUBDATAPROC BufferSubData; - PFNGLCHECKFRAMEBUFFERSTATUSPROC CheckFramebufferStatus; - PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC CheckNamedFramebufferStatus; - PFNGLCLAMPCOLORPROC ClampColor; - PFNGLCLEARPROC Clear; - PFNGLCLEARBUFFERDATAPROC ClearBufferData; - PFNGLCLEARBUFFERSUBDATAPROC ClearBufferSubData; - PFNGLCLEARBUFFERFIPROC ClearBufferfi; - PFNGLCLEARBUFFERFVPROC ClearBufferfv; - PFNGLCLEARBUFFERIVPROC ClearBufferiv; - PFNGLCLEARBUFFERUIVPROC ClearBufferuiv; - PFNGLCLEARCOLORPROC ClearColor; - PFNGLCLEARDEPTHPROC ClearDepth; - PFNGLCLEARDEPTHFPROC ClearDepthf; - PFNGLCLEARNAMEDBUFFERDATAPROC ClearNamedBufferData; - PFNGLCLEARNAMEDBUFFERSUBDATAPROC ClearNamedBufferSubData; - PFNGLCLEARNAMEDFRAMEBUFFERFIPROC ClearNamedFramebufferfi; - PFNGLCLEARNAMEDFRAMEBUFFERFVPROC ClearNamedFramebufferfv; - PFNGLCLEARNAMEDFRAMEBUFFERIVPROC ClearNamedFramebufferiv; - PFNGLCLEARNAMEDFRAMEBUFFERUIVPROC ClearNamedFramebufferuiv; - PFNGLCLEARSTENCILPROC ClearStencil; - PFNGLCLEARTEXIMAGEPROC ClearTexImage; - PFNGLCLEARTEXSUBIMAGEPROC ClearTexSubImage; - PFNGLCLIENTWAITSYNCPROC ClientWaitSync; - PFNGLCLIPCONTROLPROC ClipControl; - PFNGLCOLORMASKPROC ColorMask; - PFNGLCOLORMASKIPROC ColorMaski; - PFNGLCOMPILESHADERPROC CompileShader; - PFNGLCOMPRESSEDTEXIMAGE1DPROC CompressedTexImage1D; - PFNGLCOMPRESSEDTEXIMAGE2DPROC CompressedTexImage2D; - PFNGLCOMPRESSEDTEXIMAGE3DPROC CompressedTexImage3D; - PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC CompressedTexSubImage1D; - PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC CompressedTexSubImage2D; - PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC CompressedTexSubImage3D; - PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC CompressedTextureSubImage1D; - PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC CompressedTextureSubImage2D; - PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC CompressedTextureSubImage3D; - PFNGLCOPYBUFFERSUBDATAPROC CopyBufferSubData; - PFNGLCOPYIMAGESUBDATAPROC CopyImageSubData; - PFNGLCOPYNAMEDBUFFERSUBDATAPROC CopyNamedBufferSubData; - PFNGLCOPYTEXIMAGE1DPROC CopyTexImage1D; - PFNGLCOPYTEXIMAGE2DPROC CopyTexImage2D; - PFNGLCOPYTEXSUBIMAGE1DPROC CopyTexSubImage1D; - PFNGLCOPYTEXSUBIMAGE2DPROC CopyTexSubImage2D; - PFNGLCOPYTEXSUBIMAGE3DPROC CopyTexSubImage3D; - PFNGLCOPYTEXTURESUBIMAGE1DPROC CopyTextureSubImage1D; - PFNGLCOPYTEXTURESUBIMAGE2DPROC CopyTextureSubImage2D; - PFNGLCOPYTEXTURESUBIMAGE3DPROC CopyTextureSubImage3D; - PFNGLCREATEBUFFERSPROC CreateBuffers; - PFNGLCREATEFRAMEBUFFERSPROC CreateFramebuffers; - PFNGLCREATEPROGRAMPROC CreateProgram; - PFNGLCREATEPROGRAMPIPELINESPROC CreateProgramPipelines; - PFNGLCREATEQUERIESPROC CreateQueries; - PFNGLCREATERENDERBUFFERSPROC CreateRenderbuffers; - PFNGLCREATESAMPLERSPROC CreateSamplers; - PFNGLCREATESHADERPROC CreateShader; - PFNGLCREATESHADERPROGRAMVPROC CreateShaderProgramv; - PFNGLCREATETEXTURESPROC CreateTextures; - PFNGLCREATETRANSFORMFEEDBACKSPROC CreateTransformFeedbacks; - PFNGLCREATEVERTEXARRAYSPROC CreateVertexArrays; - PFNGLCULLFACEPROC CullFace; - PFNGLDEBUGMESSAGECALLBACKPROC DebugMessageCallback; - PFNGLDEBUGMESSAGECONTROLPROC DebugMessageControl; - PFNGLDEBUGMESSAGEINSERTPROC DebugMessageInsert; - PFNGLDELETEBUFFERSPROC DeleteBuffers; - PFNGLDELETEFRAMEBUFFERSPROC DeleteFramebuffers; - PFNGLDELETEPROGRAMPROC DeleteProgram; - PFNGLDELETEPROGRAMPIPELINESPROC DeleteProgramPipelines; - PFNGLDELETEQUERIESPROC DeleteQueries; - PFNGLDELETERENDERBUFFERSPROC DeleteRenderbuffers; - PFNGLDELETESAMPLERSPROC DeleteSamplers; - PFNGLDELETESHADERPROC DeleteShader; - PFNGLDELETESYNCPROC DeleteSync; - PFNGLDELETETEXTURESPROC DeleteTextures; - PFNGLDELETETRANSFORMFEEDBACKSPROC DeleteTransformFeedbacks; - PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays; - PFNGLDEPTHFUNCPROC DepthFunc; - PFNGLDEPTHMASKPROC DepthMask; - PFNGLDEPTHRANGEPROC DepthRange; - PFNGLDEPTHRANGEARRAYVPROC DepthRangeArrayv; - PFNGLDEPTHRANGEINDEXEDPROC DepthRangeIndexed; - PFNGLDEPTHRANGEFPROC DepthRangef; - PFNGLDETACHSHADERPROC DetachShader; - PFNGLDISABLEPROC Disable; - PFNGLDISABLEVERTEXARRAYATTRIBPROC DisableVertexArrayAttrib; - PFNGLDISABLEVERTEXATTRIBARRAYPROC DisableVertexAttribArray; - PFNGLDISABLEIPROC Disablei; - PFNGLDISPATCHCOMPUTEPROC DispatchCompute; - PFNGLDISPATCHCOMPUTEINDIRECTPROC DispatchComputeIndirect; - PFNGLDRAWARRAYSPROC DrawArrays; - PFNGLDRAWARRAYSINDIRECTPROC DrawArraysIndirect; - PFNGLDRAWARRAYSINSTANCEDPROC DrawArraysInstanced; - PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEPROC DrawArraysInstancedBaseInstance; - PFNGLDRAWBUFFERPROC DrawBuffer; - PFNGLDRAWBUFFERSPROC DrawBuffers; - PFNGLDRAWELEMENTSPROC DrawElements; - PFNGLDRAWELEMENTSBASEVERTEXPROC DrawElementsBaseVertex; - PFNGLDRAWELEMENTSINDIRECTPROC DrawElementsIndirect; - PFNGLDRAWELEMENTSINSTANCEDPROC DrawElementsInstanced; - PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEPROC DrawElementsInstancedBaseInstance; - PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC DrawElementsInstancedBaseVertex; - PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC DrawElementsInstancedBaseVertexBaseInstance; - PFNGLDRAWRANGEELEMENTSPROC DrawRangeElements; - PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC DrawRangeElementsBaseVertex; - PFNGLDRAWTRANSFORMFEEDBACKPROC DrawTransformFeedback; - PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDPROC DrawTransformFeedbackInstanced; - PFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC DrawTransformFeedbackStream; - PFNGLDRAWTRANSFORMFEEDBACKSTREAMINSTANCEDPROC DrawTransformFeedbackStreamInstanced; - PFNGLENABLEPROC Enable; - PFNGLENABLEVERTEXARRAYATTRIBPROC EnableVertexArrayAttrib; - PFNGLENABLEVERTEXATTRIBARRAYPROC EnableVertexAttribArray; - PFNGLENABLEIPROC Enablei; - PFNGLENDCONDITIONALRENDERPROC EndConditionalRender; - PFNGLENDQUERYPROC EndQuery; - PFNGLENDQUERYINDEXEDPROC EndQueryIndexed; - PFNGLENDTRANSFORMFEEDBACKPROC EndTransformFeedback; - PFNGLFENCESYNCPROC FenceSync; - PFNGLFINISHPROC Finish; - PFNGLFLUSHPROC Flush; - PFNGLFLUSHMAPPEDBUFFERRANGEPROC FlushMappedBufferRange; - PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC FlushMappedNamedBufferRange; - PFNGLFRAMEBUFFERPARAMETERIPROC FramebufferParameteri; - PFNGLFRAMEBUFFERRENDERBUFFERPROC FramebufferRenderbuffer; - PFNGLFRAMEBUFFERTEXTUREPROC FramebufferTexture; - PFNGLFRAMEBUFFERTEXTURE1DPROC FramebufferTexture1D; - PFNGLFRAMEBUFFERTEXTURE2DPROC FramebufferTexture2D; - PFNGLFRAMEBUFFERTEXTURE3DPROC FramebufferTexture3D; - PFNGLFRAMEBUFFERTEXTURELAYERPROC FramebufferTextureLayer; - PFNGLFRONTFACEPROC FrontFace; - PFNGLGENBUFFERSPROC GenBuffers; - PFNGLGENFRAMEBUFFERSPROC GenFramebuffers; - PFNGLGENPROGRAMPIPELINESPROC GenProgramPipelines; - PFNGLGENQUERIESPROC GenQueries; - PFNGLGENRENDERBUFFERSPROC GenRenderbuffers; - PFNGLGENSAMPLERSPROC GenSamplers; - PFNGLGENTEXTURESPROC GenTextures; - PFNGLGENTRANSFORMFEEDBACKSPROC GenTransformFeedbacks; - PFNGLGENVERTEXARRAYSPROC GenVertexArrays; - PFNGLGENERATEMIPMAPPROC GenerateMipmap; - PFNGLGENERATETEXTUREMIPMAPPROC GenerateTextureMipmap; - PFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC GetActiveAtomicCounterBufferiv; - PFNGLGETACTIVEATTRIBPROC GetActiveAttrib; - PFNGLGETACTIVESUBROUTINENAMEPROC GetActiveSubroutineName; - PFNGLGETACTIVESUBROUTINEUNIFORMNAMEPROC GetActiveSubroutineUniformName; - PFNGLGETACTIVESUBROUTINEUNIFORMIVPROC GetActiveSubroutineUniformiv; - PFNGLGETACTIVEUNIFORMPROC GetActiveUniform; - PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC GetActiveUniformBlockName; - PFNGLGETACTIVEUNIFORMBLOCKIVPROC GetActiveUniformBlockiv; - PFNGLGETACTIVEUNIFORMNAMEPROC GetActiveUniformName; - PFNGLGETACTIVEUNIFORMSIVPROC GetActiveUniformsiv; - PFNGLGETATTACHEDSHADERSPROC GetAttachedShaders; - PFNGLGETATTRIBLOCATIONPROC GetAttribLocation; - PFNGLGETBOOLEANI_VPROC GetBooleani_v; - PFNGLGETBOOLEANVPROC GetBooleanv; - PFNGLGETBUFFERPARAMETERI64VPROC GetBufferParameteri64v; - PFNGLGETBUFFERPARAMETERIVPROC GetBufferParameteriv; - PFNGLGETBUFFERPOINTERVPROC GetBufferPointerv; - PFNGLGETBUFFERSUBDATAPROC GetBufferSubData; - PFNGLGETCOMPRESSEDTEXIMAGEPROC GetCompressedTexImage; - PFNGLGETCOMPRESSEDTEXTUREIMAGEPROC GetCompressedTextureImage; - PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC GetCompressedTextureSubImage; - PFNGLGETDEBUGMESSAGELOGPROC GetDebugMessageLog; - PFNGLGETDOUBLEI_VPROC GetDoublei_v; - PFNGLGETDOUBLEVPROC GetDoublev; - PFNGLGETERRORPROC GetError; - PFNGLGETFLOATI_VPROC GetFloati_v; - PFNGLGETFLOATVPROC GetFloatv; - PFNGLGETFRAGDATAINDEXPROC GetFragDataIndex; - PFNGLGETFRAGDATALOCATIONPROC GetFragDataLocation; - PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC GetFramebufferAttachmentParameteriv; - PFNGLGETFRAMEBUFFERPARAMETERIVPROC GetFramebufferParameteriv; - PFNGLGETGRAPHICSRESETSTATUSPROC GetGraphicsResetStatus; - PFNGLGETINTEGER64I_VPROC GetInteger64i_v; - PFNGLGETINTEGER64VPROC GetInteger64v; - PFNGLGETINTEGERI_VPROC GetIntegeri_v; - PFNGLGETINTEGERVPROC GetIntegerv; - PFNGLGETINTERNALFORMATI64VPROC GetInternalformati64v; - PFNGLGETINTERNALFORMATIVPROC GetInternalformativ; - PFNGLGETMULTISAMPLEFVPROC GetMultisamplefv; - PFNGLGETNAMEDBUFFERPARAMETERI64VPROC GetNamedBufferParameteri64v; - PFNGLGETNAMEDBUFFERPARAMETERIVPROC GetNamedBufferParameteriv; - PFNGLGETNAMEDBUFFERPOINTERVPROC GetNamedBufferPointerv; - PFNGLGETNAMEDBUFFERSUBDATAPROC GetNamedBufferSubData; - PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC GetNamedFramebufferAttachmentParameteriv; - PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC GetNamedFramebufferParameteriv; - PFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC GetNamedRenderbufferParameteriv; - PFNGLGETOBJECTLABELPROC GetObjectLabel; - PFNGLGETOBJECTPTRLABELPROC GetObjectPtrLabel; - PFNGLGETPOINTERVPROC GetPointerv; - PFNGLGETPROGRAMBINARYPROC GetProgramBinary; - PFNGLGETPROGRAMINFOLOGPROC GetProgramInfoLog; - PFNGLGETPROGRAMINTERFACEIVPROC GetProgramInterfaceiv; - PFNGLGETPROGRAMPIPELINEINFOLOGPROC GetProgramPipelineInfoLog; - PFNGLGETPROGRAMPIPELINEIVPROC GetProgramPipelineiv; - PFNGLGETPROGRAMRESOURCEINDEXPROC GetProgramResourceIndex; - PFNGLGETPROGRAMRESOURCELOCATIONPROC GetProgramResourceLocation; - PFNGLGETPROGRAMRESOURCELOCATIONINDEXPROC GetProgramResourceLocationIndex; - PFNGLGETPROGRAMRESOURCENAMEPROC GetProgramResourceName; - PFNGLGETPROGRAMRESOURCEIVPROC GetProgramResourceiv; - PFNGLGETPROGRAMSTAGEIVPROC GetProgramStageiv; - PFNGLGETPROGRAMIVPROC GetProgramiv; - PFNGLGETQUERYBUFFEROBJECTI64VPROC GetQueryBufferObjecti64v; - PFNGLGETQUERYBUFFEROBJECTIVPROC GetQueryBufferObjectiv; - PFNGLGETQUERYBUFFEROBJECTUI64VPROC GetQueryBufferObjectui64v; - PFNGLGETQUERYBUFFEROBJECTUIVPROC GetQueryBufferObjectuiv; - PFNGLGETQUERYINDEXEDIVPROC GetQueryIndexediv; - PFNGLGETQUERYOBJECTI64VPROC GetQueryObjecti64v; - PFNGLGETQUERYOBJECTIVPROC GetQueryObjectiv; - PFNGLGETQUERYOBJECTUI64VPROC GetQueryObjectui64v; - PFNGLGETQUERYOBJECTUIVPROC GetQueryObjectuiv; - PFNGLGETQUERYIVPROC GetQueryiv; - PFNGLGETRENDERBUFFERPARAMETERIVPROC GetRenderbufferParameteriv; - PFNGLGETSAMPLERPARAMETERIIVPROC GetSamplerParameterIiv; - PFNGLGETSAMPLERPARAMETERIUIVPROC GetSamplerParameterIuiv; - PFNGLGETSAMPLERPARAMETERFVPROC GetSamplerParameterfv; - PFNGLGETSAMPLERPARAMETERIVPROC GetSamplerParameteriv; - PFNGLGETSHADERINFOLOGPROC GetShaderInfoLog; - PFNGLGETSHADERPRECISIONFORMATPROC GetShaderPrecisionFormat; - PFNGLGETSHADERSOURCEPROC GetShaderSource; - PFNGLGETSHADERIVPROC GetShaderiv; - PFNGLGETSTRINGPROC GetString; - PFNGLGETSTRINGIPROC GetStringi; - PFNGLGETSUBROUTINEINDEXPROC GetSubroutineIndex; - PFNGLGETSUBROUTINEUNIFORMLOCATIONPROC GetSubroutineUniformLocation; - PFNGLGETSYNCIVPROC GetSynciv; - PFNGLGETTEXIMAGEPROC GetTexImage; - PFNGLGETTEXLEVELPARAMETERFVPROC GetTexLevelParameterfv; - PFNGLGETTEXLEVELPARAMETERIVPROC GetTexLevelParameteriv; - PFNGLGETTEXPARAMETERIIVPROC GetTexParameterIiv; - PFNGLGETTEXPARAMETERIUIVPROC GetTexParameterIuiv; - PFNGLGETTEXPARAMETERFVPROC GetTexParameterfv; - PFNGLGETTEXPARAMETERIVPROC GetTexParameteriv; - PFNGLGETTEXTUREIMAGEPROC GetTextureImage; - PFNGLGETTEXTURELEVELPARAMETERFVPROC GetTextureLevelParameterfv; - PFNGLGETTEXTURELEVELPARAMETERIVPROC GetTextureLevelParameteriv; - PFNGLGETTEXTUREPARAMETERIIVPROC GetTextureParameterIiv; - PFNGLGETTEXTUREPARAMETERIUIVPROC GetTextureParameterIuiv; - PFNGLGETTEXTUREPARAMETERFVPROC GetTextureParameterfv; - PFNGLGETTEXTUREPARAMETERIVPROC GetTextureParameteriv; - PFNGLGETTEXTURESUBIMAGEPROC GetTextureSubImage; - PFNGLGETTRANSFORMFEEDBACKVARYINGPROC GetTransformFeedbackVarying; - PFNGLGETTRANSFORMFEEDBACKI64_VPROC GetTransformFeedbacki64_v; - PFNGLGETTRANSFORMFEEDBACKI_VPROC GetTransformFeedbacki_v; - PFNGLGETTRANSFORMFEEDBACKIVPROC GetTransformFeedbackiv; - PFNGLGETUNIFORMBLOCKINDEXPROC GetUniformBlockIndex; - PFNGLGETUNIFORMINDICESPROC GetUniformIndices; - PFNGLGETUNIFORMLOCATIONPROC GetUniformLocation; - PFNGLGETUNIFORMSUBROUTINEUIVPROC GetUniformSubroutineuiv; - PFNGLGETUNIFORMDVPROC GetUniformdv; - PFNGLGETUNIFORMFVPROC GetUniformfv; - PFNGLGETUNIFORMIVPROC GetUniformiv; - PFNGLGETUNIFORMUIVPROC GetUniformuiv; - PFNGLGETVERTEXARRAYINDEXED64IVPROC GetVertexArrayIndexed64iv; - PFNGLGETVERTEXARRAYINDEXEDIVPROC GetVertexArrayIndexediv; - PFNGLGETVERTEXARRAYIVPROC GetVertexArrayiv; - PFNGLGETVERTEXATTRIBIIVPROC GetVertexAttribIiv; - PFNGLGETVERTEXATTRIBIUIVPROC GetVertexAttribIuiv; - PFNGLGETVERTEXATTRIBLDVPROC GetVertexAttribLdv; - PFNGLGETVERTEXATTRIBPOINTERVPROC GetVertexAttribPointerv; - PFNGLGETVERTEXATTRIBDVPROC GetVertexAttribdv; - PFNGLGETVERTEXATTRIBFVPROC GetVertexAttribfv; - PFNGLGETVERTEXATTRIBIVPROC GetVertexAttribiv; - PFNGLGETNCOMPRESSEDTEXIMAGEPROC GetnCompressedTexImage; - PFNGLGETNTEXIMAGEPROC GetnTexImage; - PFNGLGETNUNIFORMDVPROC GetnUniformdv; - PFNGLGETNUNIFORMFVPROC GetnUniformfv; - PFNGLGETNUNIFORMIVPROC GetnUniformiv; - PFNGLGETNUNIFORMUIVPROC GetnUniformuiv; - PFNGLHINTPROC Hint; - PFNGLINVALIDATEBUFFERDATAPROC InvalidateBufferData; - PFNGLINVALIDATEBUFFERSUBDATAPROC InvalidateBufferSubData; - PFNGLINVALIDATEFRAMEBUFFERPROC InvalidateFramebuffer; - PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC InvalidateNamedFramebufferData; - PFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC InvalidateNamedFramebufferSubData; - PFNGLINVALIDATESUBFRAMEBUFFERPROC InvalidateSubFramebuffer; - PFNGLINVALIDATETEXIMAGEPROC InvalidateTexImage; - PFNGLINVALIDATETEXSUBIMAGEPROC InvalidateTexSubImage; - PFNGLISBUFFERPROC IsBuffer; - PFNGLISENABLEDPROC IsEnabled; - PFNGLISENABLEDIPROC IsEnabledi; - PFNGLISFRAMEBUFFERPROC IsFramebuffer; - PFNGLISPROGRAMPROC IsProgram; - PFNGLISPROGRAMPIPELINEPROC IsProgramPipeline; - PFNGLISQUERYPROC IsQuery; - PFNGLISRENDERBUFFERPROC IsRenderbuffer; - PFNGLISSAMPLERPROC IsSampler; - PFNGLISSHADERPROC IsShader; - PFNGLISSYNCPROC IsSync; - PFNGLISTEXTUREPROC IsTexture; - PFNGLISTRANSFORMFEEDBACKPROC IsTransformFeedback; - PFNGLISVERTEXARRAYPROC IsVertexArray; - PFNGLLINEWIDTHPROC LineWidth; - PFNGLLINKPROGRAMPROC LinkProgram; - PFNGLLOGICOPPROC LogicOp; - PFNGLMAPBUFFERPROC MapBuffer; - PFNGLMAPBUFFERRANGEPROC MapBufferRange; - PFNGLMAPNAMEDBUFFERPROC MapNamedBuffer; - PFNGLMAPNAMEDBUFFERRANGEPROC MapNamedBufferRange; - PFNGLMEMORYBARRIERPROC MemoryBarrier; - PFNGLMEMORYBARRIERBYREGIONPROC MemoryBarrierByRegion; - PFNGLMINSAMPLESHADINGPROC MinSampleShading; - PFNGLMULTIDRAWARRAYSPROC MultiDrawArrays; - PFNGLMULTIDRAWARRAYSINDIRECTPROC MultiDrawArraysIndirect; - PFNGLMULTIDRAWELEMENTSPROC MultiDrawElements; - PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC MultiDrawElementsBaseVertex; - PFNGLMULTIDRAWELEMENTSINDIRECTPROC MultiDrawElementsIndirect; - PFNGLNAMEDBUFFERDATAPROC NamedBufferData; - PFNGLNAMEDBUFFERSTORAGEPROC NamedBufferStorage; - PFNGLNAMEDBUFFERSUBDATAPROC NamedBufferSubData; - PFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC NamedFramebufferDrawBuffer; - PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC NamedFramebufferDrawBuffers; - PFNGLNAMEDFRAMEBUFFERPARAMETERIPROC NamedFramebufferParameteri; - PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC NamedFramebufferReadBuffer; - PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC NamedFramebufferRenderbuffer; - PFNGLNAMEDFRAMEBUFFERTEXTUREPROC NamedFramebufferTexture; - PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC NamedFramebufferTextureLayer; - PFNGLNAMEDRENDERBUFFERSTORAGEPROC NamedRenderbufferStorage; - PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC NamedRenderbufferStorageMultisample; - PFNGLOBJECTLABELPROC ObjectLabel; - PFNGLOBJECTPTRLABELPROC ObjectPtrLabel; - PFNGLPATCHPARAMETERFVPROC PatchParameterfv; - PFNGLPATCHPARAMETERIPROC PatchParameteri; - PFNGLPAUSETRANSFORMFEEDBACKPROC PauseTransformFeedback; - PFNGLPIXELSTOREFPROC PixelStoref; - PFNGLPIXELSTOREIPROC PixelStorei; - PFNGLPOINTPARAMETERFPROC PointParameterf; - PFNGLPOINTPARAMETERFVPROC PointParameterfv; - PFNGLPOINTPARAMETERIPROC PointParameteri; - PFNGLPOINTPARAMETERIVPROC PointParameteriv; - PFNGLPOINTSIZEPROC PointSize; - PFNGLPOLYGONMODEPROC PolygonMode; - PFNGLPOLYGONOFFSETPROC PolygonOffset; - PFNGLPOPDEBUGGROUPPROC PopDebugGroup; - PFNGLPRIMITIVERESTARTINDEXPROC PrimitiveRestartIndex; - PFNGLPROGRAMBINARYPROC ProgramBinary; - PFNGLPROGRAMPARAMETERIPROC ProgramParameteri; - PFNGLPROGRAMUNIFORM1DPROC ProgramUniform1d; - PFNGLPROGRAMUNIFORM1DVPROC ProgramUniform1dv; - PFNGLPROGRAMUNIFORM1FPROC ProgramUniform1f; - PFNGLPROGRAMUNIFORM1FVPROC ProgramUniform1fv; - PFNGLPROGRAMUNIFORM1IPROC ProgramUniform1i; - PFNGLPROGRAMUNIFORM1IVPROC ProgramUniform1iv; - PFNGLPROGRAMUNIFORM1UIPROC ProgramUniform1ui; - PFNGLPROGRAMUNIFORM1UIVPROC ProgramUniform1uiv; - PFNGLPROGRAMUNIFORM2DPROC ProgramUniform2d; - PFNGLPROGRAMUNIFORM2DVPROC ProgramUniform2dv; - PFNGLPROGRAMUNIFORM2FPROC ProgramUniform2f; - PFNGLPROGRAMUNIFORM2FVPROC ProgramUniform2fv; - PFNGLPROGRAMUNIFORM2IPROC ProgramUniform2i; - PFNGLPROGRAMUNIFORM2IVPROC ProgramUniform2iv; - PFNGLPROGRAMUNIFORM2UIPROC ProgramUniform2ui; - PFNGLPROGRAMUNIFORM2UIVPROC ProgramUniform2uiv; - PFNGLPROGRAMUNIFORM3DPROC ProgramUniform3d; - PFNGLPROGRAMUNIFORM3DVPROC ProgramUniform3dv; - PFNGLPROGRAMUNIFORM3FPROC ProgramUniform3f; - PFNGLPROGRAMUNIFORM3FVPROC ProgramUniform3fv; - PFNGLPROGRAMUNIFORM3IPROC ProgramUniform3i; - PFNGLPROGRAMUNIFORM3IVPROC ProgramUniform3iv; - PFNGLPROGRAMUNIFORM3UIPROC ProgramUniform3ui; - PFNGLPROGRAMUNIFORM3UIVPROC ProgramUniform3uiv; - PFNGLPROGRAMUNIFORM4DPROC ProgramUniform4d; - PFNGLPROGRAMUNIFORM4DVPROC ProgramUniform4dv; - PFNGLPROGRAMUNIFORM4FPROC ProgramUniform4f; - PFNGLPROGRAMUNIFORM4FVPROC ProgramUniform4fv; - PFNGLPROGRAMUNIFORM4IPROC ProgramUniform4i; - PFNGLPROGRAMUNIFORM4IVPROC ProgramUniform4iv; - PFNGLPROGRAMUNIFORM4UIPROC ProgramUniform4ui; - PFNGLPROGRAMUNIFORM4UIVPROC ProgramUniform4uiv; - PFNGLPROGRAMUNIFORMMATRIX2DVPROC ProgramUniformMatrix2dv; - PFNGLPROGRAMUNIFORMMATRIX2FVPROC ProgramUniformMatrix2fv; - PFNGLPROGRAMUNIFORMMATRIX2X3DVPROC ProgramUniformMatrix2x3dv; - PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC ProgramUniformMatrix2x3fv; - PFNGLPROGRAMUNIFORMMATRIX2X4DVPROC ProgramUniformMatrix2x4dv; - PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC ProgramUniformMatrix2x4fv; - PFNGLPROGRAMUNIFORMMATRIX3DVPROC ProgramUniformMatrix3dv; - PFNGLPROGRAMUNIFORMMATRIX3FVPROC ProgramUniformMatrix3fv; - PFNGLPROGRAMUNIFORMMATRIX3X2DVPROC ProgramUniformMatrix3x2dv; - PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC ProgramUniformMatrix3x2fv; - PFNGLPROGRAMUNIFORMMATRIX3X4DVPROC ProgramUniformMatrix3x4dv; - PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC ProgramUniformMatrix3x4fv; - PFNGLPROGRAMUNIFORMMATRIX4DVPROC ProgramUniformMatrix4dv; - PFNGLPROGRAMUNIFORMMATRIX4FVPROC ProgramUniformMatrix4fv; - PFNGLPROGRAMUNIFORMMATRIX4X2DVPROC ProgramUniformMatrix4x2dv; - PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC ProgramUniformMatrix4x2fv; - PFNGLPROGRAMUNIFORMMATRIX4X3DVPROC ProgramUniformMatrix4x3dv; - PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC ProgramUniformMatrix4x3fv; - PFNGLPROVOKINGVERTEXPROC ProvokingVertex; - PFNGLPUSHDEBUGGROUPPROC PushDebugGroup; - PFNGLQUERYCOUNTERPROC QueryCounter; - PFNGLREADBUFFERPROC ReadBuffer; - PFNGLREADPIXELSPROC ReadPixels; - PFNGLREADNPIXELSPROC ReadnPixels; - PFNGLRELEASESHADERCOMPILERPROC ReleaseShaderCompiler; - PFNGLRENDERBUFFERSTORAGEPROC RenderbufferStorage; - PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC RenderbufferStorageMultisample; - PFNGLRESUMETRANSFORMFEEDBACKPROC ResumeTransformFeedback; - PFNGLSAMPLECOVERAGEPROC SampleCoverage; - PFNGLSAMPLEMASKIPROC SampleMaski; - PFNGLSAMPLERPARAMETERIIVPROC SamplerParameterIiv; - PFNGLSAMPLERPARAMETERIUIVPROC SamplerParameterIuiv; - PFNGLSAMPLERPARAMETERFPROC SamplerParameterf; - PFNGLSAMPLERPARAMETERFVPROC SamplerParameterfv; - PFNGLSAMPLERPARAMETERIPROC SamplerParameteri; - PFNGLSAMPLERPARAMETERIVPROC SamplerParameteriv; - PFNGLSCISSORPROC Scissor; - PFNGLSCISSORARRAYVPROC ScissorArrayv; - PFNGLSCISSORINDEXEDPROC ScissorIndexed; - PFNGLSCISSORINDEXEDVPROC ScissorIndexedv; - PFNGLSHADERBINARYPROC ShaderBinary; - PFNGLSHADERSOURCEPROC ShaderSource; - PFNGLSHADERSTORAGEBLOCKBINDINGPROC ShaderStorageBlockBinding; - PFNGLSTENCILFUNCPROC StencilFunc; - PFNGLSTENCILFUNCSEPARATEPROC StencilFuncSeparate; - PFNGLSTENCILMASKPROC StencilMask; - PFNGLSTENCILMASKSEPARATEPROC StencilMaskSeparate; - PFNGLSTENCILOPPROC StencilOp; - PFNGLSTENCILOPSEPARATEPROC StencilOpSeparate; - PFNGLTEXBUFFERPROC TexBuffer; - PFNGLTEXBUFFERRANGEPROC TexBufferRange; - PFNGLTEXIMAGE1DPROC TexImage1D; - PFNGLTEXIMAGE2DPROC TexImage2D; - PFNGLTEXIMAGE2DMULTISAMPLEPROC TexImage2DMultisample; - PFNGLTEXIMAGE3DPROC TexImage3D; - PFNGLTEXIMAGE3DMULTISAMPLEPROC TexImage3DMultisample; - PFNGLTEXPARAMETERIIVPROC TexParameterIiv; - PFNGLTEXPARAMETERIUIVPROC TexParameterIuiv; - PFNGLTEXPARAMETERFPROC TexParameterf; - PFNGLTEXPARAMETERFVPROC TexParameterfv; - PFNGLTEXPARAMETERIPROC TexParameteri; - PFNGLTEXPARAMETERIVPROC TexParameteriv; - PFNGLTEXSTORAGE1DPROC TexStorage1D; - PFNGLTEXSTORAGE2DPROC TexStorage2D; - PFNGLTEXSTORAGE2DMULTISAMPLEPROC TexStorage2DMultisample; - PFNGLTEXSTORAGE3DPROC TexStorage3D; - PFNGLTEXSTORAGE3DMULTISAMPLEPROC TexStorage3DMultisample; - PFNGLTEXSUBIMAGE1DPROC TexSubImage1D; - PFNGLTEXSUBIMAGE2DPROC TexSubImage2D; - PFNGLTEXSUBIMAGE3DPROC TexSubImage3D; - PFNGLTEXTUREBARRIERPROC TextureBarrier; - PFNGLTEXTUREBUFFERPROC TextureBuffer; - PFNGLTEXTUREBUFFERRANGEPROC TextureBufferRange; - PFNGLTEXTUREPARAMETERIIVPROC TextureParameterIiv; - PFNGLTEXTUREPARAMETERIUIVPROC TextureParameterIuiv; - PFNGLTEXTUREPARAMETERFPROC TextureParameterf; - PFNGLTEXTUREPARAMETERFVPROC TextureParameterfv; - PFNGLTEXTUREPARAMETERIPROC TextureParameteri; - PFNGLTEXTUREPARAMETERIVPROC TextureParameteriv; - PFNGLTEXTURESTORAGE1DPROC TextureStorage1D; - PFNGLTEXTURESTORAGE2DPROC TextureStorage2D; - PFNGLTEXTURESTORAGE2DMULTISAMPLEPROC TextureStorage2DMultisample; - PFNGLTEXTURESTORAGE3DPROC TextureStorage3D; - PFNGLTEXTURESTORAGE3DMULTISAMPLEPROC TextureStorage3DMultisample; - PFNGLTEXTURESUBIMAGE1DPROC TextureSubImage1D; - PFNGLTEXTURESUBIMAGE2DPROC TextureSubImage2D; - PFNGLTEXTURESUBIMAGE3DPROC TextureSubImage3D; - PFNGLTEXTUREVIEWPROC TextureView; - PFNGLTRANSFORMFEEDBACKBUFFERBASEPROC TransformFeedbackBufferBase; - PFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC TransformFeedbackBufferRange; - PFNGLTRANSFORMFEEDBACKVARYINGSPROC TransformFeedbackVaryings; - PFNGLUNIFORM1DPROC Uniform1d; - PFNGLUNIFORM1DVPROC Uniform1dv; - PFNGLUNIFORM1FPROC Uniform1f; - PFNGLUNIFORM1FVPROC Uniform1fv; - PFNGLUNIFORM1IPROC Uniform1i; - PFNGLUNIFORM1IVPROC Uniform1iv; - PFNGLUNIFORM1UIPROC Uniform1ui; - PFNGLUNIFORM1UIVPROC Uniform1uiv; - PFNGLUNIFORM2DPROC Uniform2d; - PFNGLUNIFORM2DVPROC Uniform2dv; - PFNGLUNIFORM2FPROC Uniform2f; - PFNGLUNIFORM2FVPROC Uniform2fv; - PFNGLUNIFORM2IPROC Uniform2i; - PFNGLUNIFORM2IVPROC Uniform2iv; - PFNGLUNIFORM2UIPROC Uniform2ui; - PFNGLUNIFORM2UIVPROC Uniform2uiv; - PFNGLUNIFORM3DPROC Uniform3d; - PFNGLUNIFORM3DVPROC Uniform3dv; - PFNGLUNIFORM3FPROC Uniform3f; - PFNGLUNIFORM3FVPROC Uniform3fv; - PFNGLUNIFORM3IPROC Uniform3i; - PFNGLUNIFORM3IVPROC Uniform3iv; - PFNGLUNIFORM3UIPROC Uniform3ui; - PFNGLUNIFORM3UIVPROC Uniform3uiv; - PFNGLUNIFORM4DPROC Uniform4d; - PFNGLUNIFORM4DVPROC Uniform4dv; - PFNGLUNIFORM4FPROC Uniform4f; - PFNGLUNIFORM4FVPROC Uniform4fv; - PFNGLUNIFORM4IPROC Uniform4i; - PFNGLUNIFORM4IVPROC Uniform4iv; - PFNGLUNIFORM4UIPROC Uniform4ui; - PFNGLUNIFORM4UIVPROC Uniform4uiv; - PFNGLUNIFORMBLOCKBINDINGPROC UniformBlockBinding; - PFNGLUNIFORMMATRIX2DVPROC UniformMatrix2dv; - PFNGLUNIFORMMATRIX2FVPROC UniformMatrix2fv; - PFNGLUNIFORMMATRIX2X3DVPROC UniformMatrix2x3dv; - PFNGLUNIFORMMATRIX2X3FVPROC UniformMatrix2x3fv; - PFNGLUNIFORMMATRIX2X4DVPROC UniformMatrix2x4dv; - PFNGLUNIFORMMATRIX2X4FVPROC UniformMatrix2x4fv; - PFNGLUNIFORMMATRIX3DVPROC UniformMatrix3dv; - PFNGLUNIFORMMATRIX3FVPROC UniformMatrix3fv; - PFNGLUNIFORMMATRIX3X2DVPROC UniformMatrix3x2dv; - PFNGLUNIFORMMATRIX3X2FVPROC UniformMatrix3x2fv; - PFNGLUNIFORMMATRIX3X4DVPROC UniformMatrix3x4dv; - PFNGLUNIFORMMATRIX3X4FVPROC UniformMatrix3x4fv; - PFNGLUNIFORMMATRIX4DVPROC UniformMatrix4dv; - PFNGLUNIFORMMATRIX4FVPROC UniformMatrix4fv; - PFNGLUNIFORMMATRIX4X2DVPROC UniformMatrix4x2dv; - PFNGLUNIFORMMATRIX4X2FVPROC UniformMatrix4x2fv; - PFNGLUNIFORMMATRIX4X3DVPROC UniformMatrix4x3dv; - PFNGLUNIFORMMATRIX4X3FVPROC UniformMatrix4x3fv; - PFNGLUNIFORMSUBROUTINESUIVPROC UniformSubroutinesuiv; - PFNGLUNMAPBUFFERPROC UnmapBuffer; - PFNGLUNMAPNAMEDBUFFERPROC UnmapNamedBuffer; - PFNGLUSEPROGRAMPROC UseProgram; - PFNGLUSEPROGRAMSTAGESPROC UseProgramStages; - PFNGLVALIDATEPROGRAMPROC ValidateProgram; - PFNGLVALIDATEPROGRAMPIPELINEPROC ValidateProgramPipeline; - PFNGLVERTEXARRAYATTRIBBINDINGPROC VertexArrayAttribBinding; - PFNGLVERTEXARRAYATTRIBFORMATPROC VertexArrayAttribFormat; - PFNGLVERTEXARRAYATTRIBIFORMATPROC VertexArrayAttribIFormat; - PFNGLVERTEXARRAYATTRIBLFORMATPROC VertexArrayAttribLFormat; - PFNGLVERTEXARRAYBINDINGDIVISORPROC VertexArrayBindingDivisor; - PFNGLVERTEXARRAYELEMENTBUFFERPROC VertexArrayElementBuffer; - PFNGLVERTEXARRAYVERTEXBUFFERPROC VertexArrayVertexBuffer; - PFNGLVERTEXARRAYVERTEXBUFFERSPROC VertexArrayVertexBuffers; - PFNGLVERTEXATTRIB1DPROC VertexAttrib1d; - PFNGLVERTEXATTRIB1DVPROC VertexAttrib1dv; - PFNGLVERTEXATTRIB1FPROC VertexAttrib1f; - PFNGLVERTEXATTRIB1FVPROC VertexAttrib1fv; - PFNGLVERTEXATTRIB1SPROC VertexAttrib1s; - PFNGLVERTEXATTRIB1SVPROC VertexAttrib1sv; - PFNGLVERTEXATTRIB2DPROC VertexAttrib2d; - PFNGLVERTEXATTRIB2DVPROC VertexAttrib2dv; - PFNGLVERTEXATTRIB2FPROC VertexAttrib2f; - PFNGLVERTEXATTRIB2FVPROC VertexAttrib2fv; - PFNGLVERTEXATTRIB2SPROC VertexAttrib2s; - PFNGLVERTEXATTRIB2SVPROC VertexAttrib2sv; - PFNGLVERTEXATTRIB3DPROC VertexAttrib3d; - PFNGLVERTEXATTRIB3DVPROC VertexAttrib3dv; - PFNGLVERTEXATTRIB3FPROC VertexAttrib3f; - PFNGLVERTEXATTRIB3FVPROC VertexAttrib3fv; - PFNGLVERTEXATTRIB3SPROC VertexAttrib3s; - PFNGLVERTEXATTRIB3SVPROC VertexAttrib3sv; - PFNGLVERTEXATTRIB4NBVPROC VertexAttrib4Nbv; - PFNGLVERTEXATTRIB4NIVPROC VertexAttrib4Niv; - PFNGLVERTEXATTRIB4NSVPROC VertexAttrib4Nsv; - PFNGLVERTEXATTRIB4NUBPROC VertexAttrib4Nub; - PFNGLVERTEXATTRIB4NUBVPROC VertexAttrib4Nubv; - PFNGLVERTEXATTRIB4NUIVPROC VertexAttrib4Nuiv; - PFNGLVERTEXATTRIB4NUSVPROC VertexAttrib4Nusv; - PFNGLVERTEXATTRIB4BVPROC VertexAttrib4bv; - PFNGLVERTEXATTRIB4DPROC VertexAttrib4d; - PFNGLVERTEXATTRIB4DVPROC VertexAttrib4dv; - PFNGLVERTEXATTRIB4FPROC VertexAttrib4f; - PFNGLVERTEXATTRIB4FVPROC VertexAttrib4fv; - PFNGLVERTEXATTRIB4IVPROC VertexAttrib4iv; - PFNGLVERTEXATTRIB4SPROC VertexAttrib4s; - PFNGLVERTEXATTRIB4SVPROC VertexAttrib4sv; - PFNGLVERTEXATTRIB4UBVPROC VertexAttrib4ubv; - PFNGLVERTEXATTRIB4UIVPROC VertexAttrib4uiv; - PFNGLVERTEXATTRIB4USVPROC VertexAttrib4usv; - PFNGLVERTEXATTRIBBINDINGPROC VertexAttribBinding; - PFNGLVERTEXATTRIBDIVISORPROC VertexAttribDivisor; - PFNGLVERTEXATTRIBFORMATPROC VertexAttribFormat; - PFNGLVERTEXATTRIBI1IPROC VertexAttribI1i; - PFNGLVERTEXATTRIBI1IVPROC VertexAttribI1iv; - PFNGLVERTEXATTRIBI1UIPROC VertexAttribI1ui; - PFNGLVERTEXATTRIBI1UIVPROC VertexAttribI1uiv; - PFNGLVERTEXATTRIBI2IPROC VertexAttribI2i; - PFNGLVERTEXATTRIBI2IVPROC VertexAttribI2iv; - PFNGLVERTEXATTRIBI2UIPROC VertexAttribI2ui; - PFNGLVERTEXATTRIBI2UIVPROC VertexAttribI2uiv; - PFNGLVERTEXATTRIBI3IPROC VertexAttribI3i; - PFNGLVERTEXATTRIBI3IVPROC VertexAttribI3iv; - PFNGLVERTEXATTRIBI3UIPROC VertexAttribI3ui; - PFNGLVERTEXATTRIBI3UIVPROC VertexAttribI3uiv; - PFNGLVERTEXATTRIBI4BVPROC VertexAttribI4bv; - PFNGLVERTEXATTRIBI4IPROC VertexAttribI4i; - PFNGLVERTEXATTRIBI4IVPROC VertexAttribI4iv; - PFNGLVERTEXATTRIBI4SVPROC VertexAttribI4sv; - PFNGLVERTEXATTRIBI4UBVPROC VertexAttribI4ubv; - PFNGLVERTEXATTRIBI4UIPROC VertexAttribI4ui; - PFNGLVERTEXATTRIBI4UIVPROC VertexAttribI4uiv; - PFNGLVERTEXATTRIBI4USVPROC VertexAttribI4usv; - PFNGLVERTEXATTRIBIFORMATPROC VertexAttribIFormat; - PFNGLVERTEXATTRIBIPOINTERPROC VertexAttribIPointer; - PFNGLVERTEXATTRIBL1DPROC VertexAttribL1d; - PFNGLVERTEXATTRIBL1DVPROC VertexAttribL1dv; - PFNGLVERTEXATTRIBL2DPROC VertexAttribL2d; - PFNGLVERTEXATTRIBL2DVPROC VertexAttribL2dv; - PFNGLVERTEXATTRIBL3DPROC VertexAttribL3d; - PFNGLVERTEXATTRIBL3DVPROC VertexAttribL3dv; - PFNGLVERTEXATTRIBL4DPROC VertexAttribL4d; - PFNGLVERTEXATTRIBL4DVPROC VertexAttribL4dv; - PFNGLVERTEXATTRIBLFORMATPROC VertexAttribLFormat; - PFNGLVERTEXATTRIBLPOINTERPROC VertexAttribLPointer; - PFNGLVERTEXATTRIBP1UIPROC VertexAttribP1ui; - PFNGLVERTEXATTRIBP1UIVPROC VertexAttribP1uiv; - PFNGLVERTEXATTRIBP2UIPROC VertexAttribP2ui; - PFNGLVERTEXATTRIBP2UIVPROC VertexAttribP2uiv; - PFNGLVERTEXATTRIBP3UIPROC VertexAttribP3ui; - PFNGLVERTEXATTRIBP3UIVPROC VertexAttribP3uiv; - PFNGLVERTEXATTRIBP4UIPROC VertexAttribP4ui; - PFNGLVERTEXATTRIBP4UIVPROC VertexAttribP4uiv; - PFNGLVERTEXATTRIBPOINTERPROC VertexAttribPointer; - PFNGLVERTEXBINDINGDIVISORPROC VertexBindingDivisor; - PFNGLVIEWPORTPROC Viewport; - PFNGLVIEWPORTARRAYVPROC ViewportArrayv; - PFNGLVIEWPORTINDEXEDFPROC ViewportIndexedf; - PFNGLVIEWPORTINDEXEDFVPROC ViewportIndexedfv; - PFNGLWAITSYNCPROC WaitSync; - } gl; -}; - -extern union GL3WProcs gl3wProcs; - -/* OpenGL functions */ -#define glActiveShaderProgram gl3wProcs.gl.ActiveShaderProgram -#define glActiveTexture gl3wProcs.gl.ActiveTexture -#define glAttachShader gl3wProcs.gl.AttachShader -#define glBeginConditionalRender gl3wProcs.gl.BeginConditionalRender -#define glBeginQuery gl3wProcs.gl.BeginQuery -#define glBeginQueryIndexed gl3wProcs.gl.BeginQueryIndexed -#define glBeginTransformFeedback gl3wProcs.gl.BeginTransformFeedback -#define glBindAttribLocation gl3wProcs.gl.BindAttribLocation -#define glBindBuffer gl3wProcs.gl.BindBuffer -#define glBindBufferBase gl3wProcs.gl.BindBufferBase -#define glBindBufferRange gl3wProcs.gl.BindBufferRange -#define glBindBuffersBase gl3wProcs.gl.BindBuffersBase -#define glBindBuffersRange gl3wProcs.gl.BindBuffersRange -#define glBindFragDataLocation gl3wProcs.gl.BindFragDataLocation -#define glBindFragDataLocationIndexed gl3wProcs.gl.BindFragDataLocationIndexed -#define glBindFramebuffer gl3wProcs.gl.BindFramebuffer -#define glBindImageTexture gl3wProcs.gl.BindImageTexture -#define glBindImageTextures gl3wProcs.gl.BindImageTextures -#define glBindProgramPipeline gl3wProcs.gl.BindProgramPipeline -#define glBindRenderbuffer gl3wProcs.gl.BindRenderbuffer -#define glBindSampler gl3wProcs.gl.BindSampler -#define glBindSamplers gl3wProcs.gl.BindSamplers -#define glBindTexture gl3wProcs.gl.BindTexture -#define glBindTextureUnit gl3wProcs.gl.BindTextureUnit -#define glBindTextures gl3wProcs.gl.BindTextures -#define glBindTransformFeedback gl3wProcs.gl.BindTransformFeedback -#define glBindVertexArray gl3wProcs.gl.BindVertexArray -#define glBindVertexBuffer gl3wProcs.gl.BindVertexBuffer -#define glBindVertexBuffers gl3wProcs.gl.BindVertexBuffers -#define glBlendColor gl3wProcs.gl.BlendColor -#define glBlendEquation gl3wProcs.gl.BlendEquation -#define glBlendEquationSeparate gl3wProcs.gl.BlendEquationSeparate -#define glBlendEquationSeparatei gl3wProcs.gl.BlendEquationSeparatei -#define glBlendEquationi gl3wProcs.gl.BlendEquationi -#define glBlendFunc gl3wProcs.gl.BlendFunc -#define glBlendFuncSeparate gl3wProcs.gl.BlendFuncSeparate -#define glBlendFuncSeparatei gl3wProcs.gl.BlendFuncSeparatei -#define glBlendFunci gl3wProcs.gl.BlendFunci -#define glBlitFramebuffer gl3wProcs.gl.BlitFramebuffer -#define glBlitNamedFramebuffer gl3wProcs.gl.BlitNamedFramebuffer -#define glBufferData gl3wProcs.gl.BufferData -#define glBufferStorage gl3wProcs.gl.BufferStorage -#define glBufferSubData gl3wProcs.gl.BufferSubData -#define glCheckFramebufferStatus gl3wProcs.gl.CheckFramebufferStatus -#define glCheckNamedFramebufferStatus gl3wProcs.gl.CheckNamedFramebufferStatus -#define glClampColor gl3wProcs.gl.ClampColor -#define glClear gl3wProcs.gl.Clear -#define glClearBufferData gl3wProcs.gl.ClearBufferData -#define glClearBufferSubData gl3wProcs.gl.ClearBufferSubData -#define glClearBufferfi gl3wProcs.gl.ClearBufferfi -#define glClearBufferfv gl3wProcs.gl.ClearBufferfv -#define glClearBufferiv gl3wProcs.gl.ClearBufferiv -#define glClearBufferuiv gl3wProcs.gl.ClearBufferuiv -#define glClearColor gl3wProcs.gl.ClearColor -#define glClearDepth gl3wProcs.gl.ClearDepth -#define glClearDepthf gl3wProcs.gl.ClearDepthf -#define glClearNamedBufferData gl3wProcs.gl.ClearNamedBufferData -#define glClearNamedBufferSubData gl3wProcs.gl.ClearNamedBufferSubData -#define glClearNamedFramebufferfi gl3wProcs.gl.ClearNamedFramebufferfi -#define glClearNamedFramebufferfv gl3wProcs.gl.ClearNamedFramebufferfv -#define glClearNamedFramebufferiv gl3wProcs.gl.ClearNamedFramebufferiv -#define glClearNamedFramebufferuiv gl3wProcs.gl.ClearNamedFramebufferuiv -#define glClearStencil gl3wProcs.gl.ClearStencil -#define glClearTexImage gl3wProcs.gl.ClearTexImage -#define glClearTexSubImage gl3wProcs.gl.ClearTexSubImage -#define glClientWaitSync gl3wProcs.gl.ClientWaitSync -#define glClipControl gl3wProcs.gl.ClipControl -#define glColorMask gl3wProcs.gl.ColorMask -#define glColorMaski gl3wProcs.gl.ColorMaski -#define glCompileShader gl3wProcs.gl.CompileShader -#define glCompressedTexImage1D gl3wProcs.gl.CompressedTexImage1D -#define glCompressedTexImage2D gl3wProcs.gl.CompressedTexImage2D -#define glCompressedTexImage3D gl3wProcs.gl.CompressedTexImage3D -#define glCompressedTexSubImage1D gl3wProcs.gl.CompressedTexSubImage1D -#define glCompressedTexSubImage2D gl3wProcs.gl.CompressedTexSubImage2D -#define glCompressedTexSubImage3D gl3wProcs.gl.CompressedTexSubImage3D -#define glCompressedTextureSubImage1D gl3wProcs.gl.CompressedTextureSubImage1D -#define glCompressedTextureSubImage2D gl3wProcs.gl.CompressedTextureSubImage2D -#define glCompressedTextureSubImage3D gl3wProcs.gl.CompressedTextureSubImage3D -#define glCopyBufferSubData gl3wProcs.gl.CopyBufferSubData -#define glCopyImageSubData gl3wProcs.gl.CopyImageSubData -#define glCopyNamedBufferSubData gl3wProcs.gl.CopyNamedBufferSubData -#define glCopyTexImage1D gl3wProcs.gl.CopyTexImage1D -#define glCopyTexImage2D gl3wProcs.gl.CopyTexImage2D -#define glCopyTexSubImage1D gl3wProcs.gl.CopyTexSubImage1D -#define glCopyTexSubImage2D gl3wProcs.gl.CopyTexSubImage2D -#define glCopyTexSubImage3D gl3wProcs.gl.CopyTexSubImage3D -#define glCopyTextureSubImage1D gl3wProcs.gl.CopyTextureSubImage1D -#define glCopyTextureSubImage2D gl3wProcs.gl.CopyTextureSubImage2D -#define glCopyTextureSubImage3D gl3wProcs.gl.CopyTextureSubImage3D -#define glCreateBuffers gl3wProcs.gl.CreateBuffers -#define glCreateFramebuffers gl3wProcs.gl.CreateFramebuffers -#define glCreateProgram gl3wProcs.gl.CreateProgram -#define glCreateProgramPipelines gl3wProcs.gl.CreateProgramPipelines -#define glCreateQueries gl3wProcs.gl.CreateQueries -#define glCreateRenderbuffers gl3wProcs.gl.CreateRenderbuffers -#define glCreateSamplers gl3wProcs.gl.CreateSamplers -#define glCreateShader gl3wProcs.gl.CreateShader -#define glCreateShaderProgramv gl3wProcs.gl.CreateShaderProgramv -#define glCreateTextures gl3wProcs.gl.CreateTextures -#define glCreateTransformFeedbacks gl3wProcs.gl.CreateTransformFeedbacks -#define glCreateVertexArrays gl3wProcs.gl.CreateVertexArrays -#define glCullFace gl3wProcs.gl.CullFace -#define glDebugMessageCallback gl3wProcs.gl.DebugMessageCallback -#define glDebugMessageControl gl3wProcs.gl.DebugMessageControl -#define glDebugMessageInsert gl3wProcs.gl.DebugMessageInsert -#define glDeleteBuffers gl3wProcs.gl.DeleteBuffers -#define glDeleteFramebuffers gl3wProcs.gl.DeleteFramebuffers -#define glDeleteProgram gl3wProcs.gl.DeleteProgram -#define glDeleteProgramPipelines gl3wProcs.gl.DeleteProgramPipelines -#define glDeleteQueries gl3wProcs.gl.DeleteQueries -#define glDeleteRenderbuffers gl3wProcs.gl.DeleteRenderbuffers -#define glDeleteSamplers gl3wProcs.gl.DeleteSamplers -#define glDeleteShader gl3wProcs.gl.DeleteShader -#define glDeleteSync gl3wProcs.gl.DeleteSync -#define glDeleteTextures gl3wProcs.gl.DeleteTextures -#define glDeleteTransformFeedbacks gl3wProcs.gl.DeleteTransformFeedbacks -#define glDeleteVertexArrays gl3wProcs.gl.DeleteVertexArrays -#define glDepthFunc gl3wProcs.gl.DepthFunc -#define glDepthMask gl3wProcs.gl.DepthMask -#define glDepthRange gl3wProcs.gl.DepthRange -#define glDepthRangeArrayv gl3wProcs.gl.DepthRangeArrayv -#define glDepthRangeIndexed gl3wProcs.gl.DepthRangeIndexed -#define glDepthRangef gl3wProcs.gl.DepthRangef -#define glDetachShader gl3wProcs.gl.DetachShader -#define glDisable gl3wProcs.gl.Disable -#define glDisableVertexArrayAttrib gl3wProcs.gl.DisableVertexArrayAttrib -#define glDisableVertexAttribArray gl3wProcs.gl.DisableVertexAttribArray -#define glDisablei gl3wProcs.gl.Disablei -#define glDispatchCompute gl3wProcs.gl.DispatchCompute -#define glDispatchComputeIndirect gl3wProcs.gl.DispatchComputeIndirect -#define glDrawArrays gl3wProcs.gl.DrawArrays -#define glDrawArraysIndirect gl3wProcs.gl.DrawArraysIndirect -#define glDrawArraysInstanced gl3wProcs.gl.DrawArraysInstanced -#define glDrawArraysInstancedBaseInstance gl3wProcs.gl.DrawArraysInstancedBaseInstance -#define glDrawBuffer gl3wProcs.gl.DrawBuffer -#define glDrawBuffers gl3wProcs.gl.DrawBuffers -#define glDrawElements gl3wProcs.gl.DrawElements -#define glDrawElementsBaseVertex gl3wProcs.gl.DrawElementsBaseVertex -#define glDrawElementsIndirect gl3wProcs.gl.DrawElementsIndirect -#define glDrawElementsInstanced gl3wProcs.gl.DrawElementsInstanced -#define glDrawElementsInstancedBaseInstance gl3wProcs.gl.DrawElementsInstancedBaseInstance -#define glDrawElementsInstancedBaseVertex gl3wProcs.gl.DrawElementsInstancedBaseVertex -#define glDrawElementsInstancedBaseVertexBaseInstance gl3wProcs.gl.DrawElementsInstancedBaseVertexBaseInstance -#define glDrawRangeElements gl3wProcs.gl.DrawRangeElements -#define glDrawRangeElementsBaseVertex gl3wProcs.gl.DrawRangeElementsBaseVertex -#define glDrawTransformFeedback gl3wProcs.gl.DrawTransformFeedback -#define glDrawTransformFeedbackInstanced gl3wProcs.gl.DrawTransformFeedbackInstanced -#define glDrawTransformFeedbackStream gl3wProcs.gl.DrawTransformFeedbackStream -#define glDrawTransformFeedbackStreamInstanced gl3wProcs.gl.DrawTransformFeedbackStreamInstanced -#define glEnable gl3wProcs.gl.Enable -#define glEnableVertexArrayAttrib gl3wProcs.gl.EnableVertexArrayAttrib -#define glEnableVertexAttribArray gl3wProcs.gl.EnableVertexAttribArray -#define glEnablei gl3wProcs.gl.Enablei -#define glEndConditionalRender gl3wProcs.gl.EndConditionalRender -#define glEndQuery gl3wProcs.gl.EndQuery -#define glEndQueryIndexed gl3wProcs.gl.EndQueryIndexed -#define glEndTransformFeedback gl3wProcs.gl.EndTransformFeedback -#define glFenceSync gl3wProcs.gl.FenceSync -#define glFinish gl3wProcs.gl.Finish -#define glFlush gl3wProcs.gl.Flush -#define glFlushMappedBufferRange gl3wProcs.gl.FlushMappedBufferRange -#define glFlushMappedNamedBufferRange gl3wProcs.gl.FlushMappedNamedBufferRange -#define glFramebufferParameteri gl3wProcs.gl.FramebufferParameteri -#define glFramebufferRenderbuffer gl3wProcs.gl.FramebufferRenderbuffer -#define glFramebufferTexture gl3wProcs.gl.FramebufferTexture -#define glFramebufferTexture1D gl3wProcs.gl.FramebufferTexture1D -#define glFramebufferTexture2D gl3wProcs.gl.FramebufferTexture2D -#define glFramebufferTexture3D gl3wProcs.gl.FramebufferTexture3D -#define glFramebufferTextureLayer gl3wProcs.gl.FramebufferTextureLayer -#define glFrontFace gl3wProcs.gl.FrontFace -#define glGenBuffers gl3wProcs.gl.GenBuffers -#define glGenFramebuffers gl3wProcs.gl.GenFramebuffers -#define glGenProgramPipelines gl3wProcs.gl.GenProgramPipelines -#define glGenQueries gl3wProcs.gl.GenQueries -#define glGenRenderbuffers gl3wProcs.gl.GenRenderbuffers -#define glGenSamplers gl3wProcs.gl.GenSamplers -#define glGenTextures gl3wProcs.gl.GenTextures -#define glGenTransformFeedbacks gl3wProcs.gl.GenTransformFeedbacks -#define glGenVertexArrays gl3wProcs.gl.GenVertexArrays -#define glGenerateMipmap gl3wProcs.gl.GenerateMipmap -#define glGenerateTextureMipmap gl3wProcs.gl.GenerateTextureMipmap -#define glGetActiveAtomicCounterBufferiv gl3wProcs.gl.GetActiveAtomicCounterBufferiv -#define glGetActiveAttrib gl3wProcs.gl.GetActiveAttrib -#define glGetActiveSubroutineName gl3wProcs.gl.GetActiveSubroutineName -#define glGetActiveSubroutineUniformName gl3wProcs.gl.GetActiveSubroutineUniformName -#define glGetActiveSubroutineUniformiv gl3wProcs.gl.GetActiveSubroutineUniformiv -#define glGetActiveUniform gl3wProcs.gl.GetActiveUniform -#define glGetActiveUniformBlockName gl3wProcs.gl.GetActiveUniformBlockName -#define glGetActiveUniformBlockiv gl3wProcs.gl.GetActiveUniformBlockiv -#define glGetActiveUniformName gl3wProcs.gl.GetActiveUniformName -#define glGetActiveUniformsiv gl3wProcs.gl.GetActiveUniformsiv -#define glGetAttachedShaders gl3wProcs.gl.GetAttachedShaders -#define glGetAttribLocation gl3wProcs.gl.GetAttribLocation -#define glGetBooleani_v gl3wProcs.gl.GetBooleani_v -#define glGetBooleanv gl3wProcs.gl.GetBooleanv -#define glGetBufferParameteri64v gl3wProcs.gl.GetBufferParameteri64v -#define glGetBufferParameteriv gl3wProcs.gl.GetBufferParameteriv -#define glGetBufferPointerv gl3wProcs.gl.GetBufferPointerv -#define glGetBufferSubData gl3wProcs.gl.GetBufferSubData -#define glGetCompressedTexImage gl3wProcs.gl.GetCompressedTexImage -#define glGetCompressedTextureImage gl3wProcs.gl.GetCompressedTextureImage -#define glGetCompressedTextureSubImage gl3wProcs.gl.GetCompressedTextureSubImage -#define glGetDebugMessageLog gl3wProcs.gl.GetDebugMessageLog -#define glGetDoublei_v gl3wProcs.gl.GetDoublei_v -#define glGetDoublev gl3wProcs.gl.GetDoublev -#define glGetError gl3wProcs.gl.GetError -#define glGetFloati_v gl3wProcs.gl.GetFloati_v -#define glGetFloatv gl3wProcs.gl.GetFloatv -#define glGetFragDataIndex gl3wProcs.gl.GetFragDataIndex -#define glGetFragDataLocation gl3wProcs.gl.GetFragDataLocation -#define glGetFramebufferAttachmentParameteriv gl3wProcs.gl.GetFramebufferAttachmentParameteriv -#define glGetFramebufferParameteriv gl3wProcs.gl.GetFramebufferParameteriv -#define glGetGraphicsResetStatus gl3wProcs.gl.GetGraphicsResetStatus -#define glGetInteger64i_v gl3wProcs.gl.GetInteger64i_v -#define glGetInteger64v gl3wProcs.gl.GetInteger64v -#define glGetIntegeri_v gl3wProcs.gl.GetIntegeri_v -#define glGetIntegerv gl3wProcs.gl.GetIntegerv -#define glGetInternalformati64v gl3wProcs.gl.GetInternalformati64v -#define glGetInternalformativ gl3wProcs.gl.GetInternalformativ -#define glGetMultisamplefv gl3wProcs.gl.GetMultisamplefv -#define glGetNamedBufferParameteri64v gl3wProcs.gl.GetNamedBufferParameteri64v -#define glGetNamedBufferParameteriv gl3wProcs.gl.GetNamedBufferParameteriv -#define glGetNamedBufferPointerv gl3wProcs.gl.GetNamedBufferPointerv -#define glGetNamedBufferSubData gl3wProcs.gl.GetNamedBufferSubData -#define glGetNamedFramebufferAttachmentParameteriv gl3wProcs.gl.GetNamedFramebufferAttachmentParameteriv -#define glGetNamedFramebufferParameteriv gl3wProcs.gl.GetNamedFramebufferParameteriv -#define glGetNamedRenderbufferParameteriv gl3wProcs.gl.GetNamedRenderbufferParameteriv -#define glGetObjectLabel gl3wProcs.gl.GetObjectLabel -#define glGetObjectPtrLabel gl3wProcs.gl.GetObjectPtrLabel -#define glGetPointerv gl3wProcs.gl.GetPointerv -#define glGetProgramBinary gl3wProcs.gl.GetProgramBinary -#define glGetProgramInfoLog gl3wProcs.gl.GetProgramInfoLog -#define glGetProgramInterfaceiv gl3wProcs.gl.GetProgramInterfaceiv -#define glGetProgramPipelineInfoLog gl3wProcs.gl.GetProgramPipelineInfoLog -#define glGetProgramPipelineiv gl3wProcs.gl.GetProgramPipelineiv -#define glGetProgramResourceIndex gl3wProcs.gl.GetProgramResourceIndex -#define glGetProgramResourceLocation gl3wProcs.gl.GetProgramResourceLocation -#define glGetProgramResourceLocationIndex gl3wProcs.gl.GetProgramResourceLocationIndex -#define glGetProgramResourceName gl3wProcs.gl.GetProgramResourceName -#define glGetProgramResourceiv gl3wProcs.gl.GetProgramResourceiv -#define glGetProgramStageiv gl3wProcs.gl.GetProgramStageiv -#define glGetProgramiv gl3wProcs.gl.GetProgramiv -#define glGetQueryBufferObjecti64v gl3wProcs.gl.GetQueryBufferObjecti64v -#define glGetQueryBufferObjectiv gl3wProcs.gl.GetQueryBufferObjectiv -#define glGetQueryBufferObjectui64v gl3wProcs.gl.GetQueryBufferObjectui64v -#define glGetQueryBufferObjectuiv gl3wProcs.gl.GetQueryBufferObjectuiv -#define glGetQueryIndexediv gl3wProcs.gl.GetQueryIndexediv -#define glGetQueryObjecti64v gl3wProcs.gl.GetQueryObjecti64v -#define glGetQueryObjectiv gl3wProcs.gl.GetQueryObjectiv -#define glGetQueryObjectui64v gl3wProcs.gl.GetQueryObjectui64v -#define glGetQueryObjectuiv gl3wProcs.gl.GetQueryObjectuiv -#define glGetQueryiv gl3wProcs.gl.GetQueryiv -#define glGetRenderbufferParameteriv gl3wProcs.gl.GetRenderbufferParameteriv -#define glGetSamplerParameterIiv gl3wProcs.gl.GetSamplerParameterIiv -#define glGetSamplerParameterIuiv gl3wProcs.gl.GetSamplerParameterIuiv -#define glGetSamplerParameterfv gl3wProcs.gl.GetSamplerParameterfv -#define glGetSamplerParameteriv gl3wProcs.gl.GetSamplerParameteriv -#define glGetShaderInfoLog gl3wProcs.gl.GetShaderInfoLog -#define glGetShaderPrecisionFormat gl3wProcs.gl.GetShaderPrecisionFormat -#define glGetShaderSource gl3wProcs.gl.GetShaderSource -#define glGetShaderiv gl3wProcs.gl.GetShaderiv -#define glGetString gl3wProcs.gl.GetString -#define glGetStringi gl3wProcs.gl.GetStringi -#define glGetSubroutineIndex gl3wProcs.gl.GetSubroutineIndex -#define glGetSubroutineUniformLocation gl3wProcs.gl.GetSubroutineUniformLocation -#define glGetSynciv gl3wProcs.gl.GetSynciv -#define glGetTexImage gl3wProcs.gl.GetTexImage -#define glGetTexLevelParameterfv gl3wProcs.gl.GetTexLevelParameterfv -#define glGetTexLevelParameteriv gl3wProcs.gl.GetTexLevelParameteriv -#define glGetTexParameterIiv gl3wProcs.gl.GetTexParameterIiv -#define glGetTexParameterIuiv gl3wProcs.gl.GetTexParameterIuiv -#define glGetTexParameterfv gl3wProcs.gl.GetTexParameterfv -#define glGetTexParameteriv gl3wProcs.gl.GetTexParameteriv -#define glGetTextureImage gl3wProcs.gl.GetTextureImage -#define glGetTextureLevelParameterfv gl3wProcs.gl.GetTextureLevelParameterfv -#define glGetTextureLevelParameteriv gl3wProcs.gl.GetTextureLevelParameteriv -#define glGetTextureParameterIiv gl3wProcs.gl.GetTextureParameterIiv -#define glGetTextureParameterIuiv gl3wProcs.gl.GetTextureParameterIuiv -#define glGetTextureParameterfv gl3wProcs.gl.GetTextureParameterfv -#define glGetTextureParameteriv gl3wProcs.gl.GetTextureParameteriv -#define glGetTextureSubImage gl3wProcs.gl.GetTextureSubImage -#define glGetTransformFeedbackVarying gl3wProcs.gl.GetTransformFeedbackVarying -#define glGetTransformFeedbacki64_v gl3wProcs.gl.GetTransformFeedbacki64_v -#define glGetTransformFeedbacki_v gl3wProcs.gl.GetTransformFeedbacki_v -#define glGetTransformFeedbackiv gl3wProcs.gl.GetTransformFeedbackiv -#define glGetUniformBlockIndex gl3wProcs.gl.GetUniformBlockIndex -#define glGetUniformIndices gl3wProcs.gl.GetUniformIndices -#define glGetUniformLocation gl3wProcs.gl.GetUniformLocation -#define glGetUniformSubroutineuiv gl3wProcs.gl.GetUniformSubroutineuiv -#define glGetUniformdv gl3wProcs.gl.GetUniformdv -#define glGetUniformfv gl3wProcs.gl.GetUniformfv -#define glGetUniformiv gl3wProcs.gl.GetUniformiv -#define glGetUniformuiv gl3wProcs.gl.GetUniformuiv -#define glGetVertexArrayIndexed64iv gl3wProcs.gl.GetVertexArrayIndexed64iv -#define glGetVertexArrayIndexediv gl3wProcs.gl.GetVertexArrayIndexediv -#define glGetVertexArrayiv gl3wProcs.gl.GetVertexArrayiv -#define glGetVertexAttribIiv gl3wProcs.gl.GetVertexAttribIiv -#define glGetVertexAttribIuiv gl3wProcs.gl.GetVertexAttribIuiv -#define glGetVertexAttribLdv gl3wProcs.gl.GetVertexAttribLdv -#define glGetVertexAttribPointerv gl3wProcs.gl.GetVertexAttribPointerv -#define glGetVertexAttribdv gl3wProcs.gl.GetVertexAttribdv -#define glGetVertexAttribfv gl3wProcs.gl.GetVertexAttribfv -#define glGetVertexAttribiv gl3wProcs.gl.GetVertexAttribiv -#define glGetnCompressedTexImage gl3wProcs.gl.GetnCompressedTexImage -#define glGetnTexImage gl3wProcs.gl.GetnTexImage -#define glGetnUniformdv gl3wProcs.gl.GetnUniformdv -#define glGetnUniformfv gl3wProcs.gl.GetnUniformfv -#define glGetnUniformiv gl3wProcs.gl.GetnUniformiv -#define glGetnUniformuiv gl3wProcs.gl.GetnUniformuiv -#define glHint gl3wProcs.gl.Hint -#define glInvalidateBufferData gl3wProcs.gl.InvalidateBufferData -#define glInvalidateBufferSubData gl3wProcs.gl.InvalidateBufferSubData -#define glInvalidateFramebuffer gl3wProcs.gl.InvalidateFramebuffer -#define glInvalidateNamedFramebufferData gl3wProcs.gl.InvalidateNamedFramebufferData -#define glInvalidateNamedFramebufferSubData gl3wProcs.gl.InvalidateNamedFramebufferSubData -#define glInvalidateSubFramebuffer gl3wProcs.gl.InvalidateSubFramebuffer -#define glInvalidateTexImage gl3wProcs.gl.InvalidateTexImage -#define glInvalidateTexSubImage gl3wProcs.gl.InvalidateTexSubImage -#define glIsBuffer gl3wProcs.gl.IsBuffer -#define glIsEnabled gl3wProcs.gl.IsEnabled -#define glIsEnabledi gl3wProcs.gl.IsEnabledi -#define glIsFramebuffer gl3wProcs.gl.IsFramebuffer -#define glIsProgram gl3wProcs.gl.IsProgram -#define glIsProgramPipeline gl3wProcs.gl.IsProgramPipeline -#define glIsQuery gl3wProcs.gl.IsQuery -#define glIsRenderbuffer gl3wProcs.gl.IsRenderbuffer -#define glIsSampler gl3wProcs.gl.IsSampler -#define glIsShader gl3wProcs.gl.IsShader -#define glIsSync gl3wProcs.gl.IsSync -#define glIsTexture gl3wProcs.gl.IsTexture -#define glIsTransformFeedback gl3wProcs.gl.IsTransformFeedback -#define glIsVertexArray gl3wProcs.gl.IsVertexArray -#define glLineWidth gl3wProcs.gl.LineWidth -#define glLinkProgram gl3wProcs.gl.LinkProgram -#define glLogicOp gl3wProcs.gl.LogicOp -#define glMapBuffer gl3wProcs.gl.MapBuffer -#define glMapBufferRange gl3wProcs.gl.MapBufferRange -#define glMapNamedBuffer gl3wProcs.gl.MapNamedBuffer -#define glMapNamedBufferRange gl3wProcs.gl.MapNamedBufferRange -#define glMemoryBarrier gl3wProcs.gl.MemoryBarrier -#define glMemoryBarrierByRegion gl3wProcs.gl.MemoryBarrierByRegion -#define glMinSampleShading gl3wProcs.gl.MinSampleShading -#define glMultiDrawArrays gl3wProcs.gl.MultiDrawArrays -#define glMultiDrawArraysIndirect gl3wProcs.gl.MultiDrawArraysIndirect -#define glMultiDrawElements gl3wProcs.gl.MultiDrawElements -#define glMultiDrawElementsBaseVertex gl3wProcs.gl.MultiDrawElementsBaseVertex -#define glMultiDrawElementsIndirect gl3wProcs.gl.MultiDrawElementsIndirect -#define glNamedBufferData gl3wProcs.gl.NamedBufferData -#define glNamedBufferStorage gl3wProcs.gl.NamedBufferStorage -#define glNamedBufferSubData gl3wProcs.gl.NamedBufferSubData -#define glNamedFramebufferDrawBuffer gl3wProcs.gl.NamedFramebufferDrawBuffer -#define glNamedFramebufferDrawBuffers gl3wProcs.gl.NamedFramebufferDrawBuffers -#define glNamedFramebufferParameteri gl3wProcs.gl.NamedFramebufferParameteri -#define glNamedFramebufferReadBuffer gl3wProcs.gl.NamedFramebufferReadBuffer -#define glNamedFramebufferRenderbuffer gl3wProcs.gl.NamedFramebufferRenderbuffer -#define glNamedFramebufferTexture gl3wProcs.gl.NamedFramebufferTexture -#define glNamedFramebufferTextureLayer gl3wProcs.gl.NamedFramebufferTextureLayer -#define glNamedRenderbufferStorage gl3wProcs.gl.NamedRenderbufferStorage -#define glNamedRenderbufferStorageMultisample gl3wProcs.gl.NamedRenderbufferStorageMultisample -#define glObjectLabel gl3wProcs.gl.ObjectLabel -#define glObjectPtrLabel gl3wProcs.gl.ObjectPtrLabel -#define glPatchParameterfv gl3wProcs.gl.PatchParameterfv -#define glPatchParameteri gl3wProcs.gl.PatchParameteri -#define glPauseTransformFeedback gl3wProcs.gl.PauseTransformFeedback -#define glPixelStoref gl3wProcs.gl.PixelStoref -#define glPixelStorei gl3wProcs.gl.PixelStorei -#define glPointParameterf gl3wProcs.gl.PointParameterf -#define glPointParameterfv gl3wProcs.gl.PointParameterfv -#define glPointParameteri gl3wProcs.gl.PointParameteri -#define glPointParameteriv gl3wProcs.gl.PointParameteriv -#define glPointSize gl3wProcs.gl.PointSize -#define glPolygonMode gl3wProcs.gl.PolygonMode -#define glPolygonOffset gl3wProcs.gl.PolygonOffset -#define glPopDebugGroup gl3wProcs.gl.PopDebugGroup -#define glPrimitiveRestartIndex gl3wProcs.gl.PrimitiveRestartIndex -#define glProgramBinary gl3wProcs.gl.ProgramBinary -#define glProgramParameteri gl3wProcs.gl.ProgramParameteri -#define glProgramUniform1d gl3wProcs.gl.ProgramUniform1d -#define glProgramUniform1dv gl3wProcs.gl.ProgramUniform1dv -#define glProgramUniform1f gl3wProcs.gl.ProgramUniform1f -#define glProgramUniform1fv gl3wProcs.gl.ProgramUniform1fv -#define glProgramUniform1i gl3wProcs.gl.ProgramUniform1i -#define glProgramUniform1iv gl3wProcs.gl.ProgramUniform1iv -#define glProgramUniform1ui gl3wProcs.gl.ProgramUniform1ui -#define glProgramUniform1uiv gl3wProcs.gl.ProgramUniform1uiv -#define glProgramUniform2d gl3wProcs.gl.ProgramUniform2d -#define glProgramUniform2dv gl3wProcs.gl.ProgramUniform2dv -#define glProgramUniform2f gl3wProcs.gl.ProgramUniform2f -#define glProgramUniform2fv gl3wProcs.gl.ProgramUniform2fv -#define glProgramUniform2i gl3wProcs.gl.ProgramUniform2i -#define glProgramUniform2iv gl3wProcs.gl.ProgramUniform2iv -#define glProgramUniform2ui gl3wProcs.gl.ProgramUniform2ui -#define glProgramUniform2uiv gl3wProcs.gl.ProgramUniform2uiv -#define glProgramUniform3d gl3wProcs.gl.ProgramUniform3d -#define glProgramUniform3dv gl3wProcs.gl.ProgramUniform3dv -#define glProgramUniform3f gl3wProcs.gl.ProgramUniform3f -#define glProgramUniform3fv gl3wProcs.gl.ProgramUniform3fv -#define glProgramUniform3i gl3wProcs.gl.ProgramUniform3i -#define glProgramUniform3iv gl3wProcs.gl.ProgramUniform3iv -#define glProgramUniform3ui gl3wProcs.gl.ProgramUniform3ui -#define glProgramUniform3uiv gl3wProcs.gl.ProgramUniform3uiv -#define glProgramUniform4d gl3wProcs.gl.ProgramUniform4d -#define glProgramUniform4dv gl3wProcs.gl.ProgramUniform4dv -#define glProgramUniform4f gl3wProcs.gl.ProgramUniform4f -#define glProgramUniform4fv gl3wProcs.gl.ProgramUniform4fv -#define glProgramUniform4i gl3wProcs.gl.ProgramUniform4i -#define glProgramUniform4iv gl3wProcs.gl.ProgramUniform4iv -#define glProgramUniform4ui gl3wProcs.gl.ProgramUniform4ui -#define glProgramUniform4uiv gl3wProcs.gl.ProgramUniform4uiv -#define glProgramUniformMatrix2dv gl3wProcs.gl.ProgramUniformMatrix2dv -#define glProgramUniformMatrix2fv gl3wProcs.gl.ProgramUniformMatrix2fv -#define glProgramUniformMatrix2x3dv gl3wProcs.gl.ProgramUniformMatrix2x3dv -#define glProgramUniformMatrix2x3fv gl3wProcs.gl.ProgramUniformMatrix2x3fv -#define glProgramUniformMatrix2x4dv gl3wProcs.gl.ProgramUniformMatrix2x4dv -#define glProgramUniformMatrix2x4fv gl3wProcs.gl.ProgramUniformMatrix2x4fv -#define glProgramUniformMatrix3dv gl3wProcs.gl.ProgramUniformMatrix3dv -#define glProgramUniformMatrix3fv gl3wProcs.gl.ProgramUniformMatrix3fv -#define glProgramUniformMatrix3x2dv gl3wProcs.gl.ProgramUniformMatrix3x2dv -#define glProgramUniformMatrix3x2fv gl3wProcs.gl.ProgramUniformMatrix3x2fv -#define glProgramUniformMatrix3x4dv gl3wProcs.gl.ProgramUniformMatrix3x4dv -#define glProgramUniformMatrix3x4fv gl3wProcs.gl.ProgramUniformMatrix3x4fv -#define glProgramUniformMatrix4dv gl3wProcs.gl.ProgramUniformMatrix4dv -#define glProgramUniformMatrix4fv gl3wProcs.gl.ProgramUniformMatrix4fv -#define glProgramUniformMatrix4x2dv gl3wProcs.gl.ProgramUniformMatrix4x2dv -#define glProgramUniformMatrix4x2fv gl3wProcs.gl.ProgramUniformMatrix4x2fv -#define glProgramUniformMatrix4x3dv gl3wProcs.gl.ProgramUniformMatrix4x3dv -#define glProgramUniformMatrix4x3fv gl3wProcs.gl.ProgramUniformMatrix4x3fv -#define glProvokingVertex gl3wProcs.gl.ProvokingVertex -#define glPushDebugGroup gl3wProcs.gl.PushDebugGroup -#define glQueryCounter gl3wProcs.gl.QueryCounter -#define glReadBuffer gl3wProcs.gl.ReadBuffer -#define glReadPixels gl3wProcs.gl.ReadPixels -#define glReadnPixels gl3wProcs.gl.ReadnPixels -#define glReleaseShaderCompiler gl3wProcs.gl.ReleaseShaderCompiler -#define glRenderbufferStorage gl3wProcs.gl.RenderbufferStorage -#define glRenderbufferStorageMultisample gl3wProcs.gl.RenderbufferStorageMultisample -#define glResumeTransformFeedback gl3wProcs.gl.ResumeTransformFeedback -#define glSampleCoverage gl3wProcs.gl.SampleCoverage -#define glSampleMaski gl3wProcs.gl.SampleMaski -#define glSamplerParameterIiv gl3wProcs.gl.SamplerParameterIiv -#define glSamplerParameterIuiv gl3wProcs.gl.SamplerParameterIuiv -#define glSamplerParameterf gl3wProcs.gl.SamplerParameterf -#define glSamplerParameterfv gl3wProcs.gl.SamplerParameterfv -#define glSamplerParameteri gl3wProcs.gl.SamplerParameteri -#define glSamplerParameteriv gl3wProcs.gl.SamplerParameteriv -#define glScissor gl3wProcs.gl.Scissor -#define glScissorArrayv gl3wProcs.gl.ScissorArrayv -#define glScissorIndexed gl3wProcs.gl.ScissorIndexed -#define glScissorIndexedv gl3wProcs.gl.ScissorIndexedv -#define glShaderBinary gl3wProcs.gl.ShaderBinary -#define glShaderSource gl3wProcs.gl.ShaderSource -#define glShaderStorageBlockBinding gl3wProcs.gl.ShaderStorageBlockBinding -#define glStencilFunc gl3wProcs.gl.StencilFunc -#define glStencilFuncSeparate gl3wProcs.gl.StencilFuncSeparate -#define glStencilMask gl3wProcs.gl.StencilMask -#define glStencilMaskSeparate gl3wProcs.gl.StencilMaskSeparate -#define glStencilOp gl3wProcs.gl.StencilOp -#define glStencilOpSeparate gl3wProcs.gl.StencilOpSeparate -#define glTexBuffer gl3wProcs.gl.TexBuffer -#define glTexBufferRange gl3wProcs.gl.TexBufferRange -#define glTexImage1D gl3wProcs.gl.TexImage1D -#define glTexImage2D gl3wProcs.gl.TexImage2D -#define glTexImage2DMultisample gl3wProcs.gl.TexImage2DMultisample -#define glTexImage3D gl3wProcs.gl.TexImage3D -#define glTexImage3DMultisample gl3wProcs.gl.TexImage3DMultisample -#define glTexParameterIiv gl3wProcs.gl.TexParameterIiv -#define glTexParameterIuiv gl3wProcs.gl.TexParameterIuiv -#define glTexParameterf gl3wProcs.gl.TexParameterf -#define glTexParameterfv gl3wProcs.gl.TexParameterfv -#define glTexParameteri gl3wProcs.gl.TexParameteri -#define glTexParameteriv gl3wProcs.gl.TexParameteriv -#define glTexStorage1D gl3wProcs.gl.TexStorage1D -#define glTexStorage2D gl3wProcs.gl.TexStorage2D -#define glTexStorage2DMultisample gl3wProcs.gl.TexStorage2DMultisample -#define glTexStorage3D gl3wProcs.gl.TexStorage3D -#define glTexStorage3DMultisample gl3wProcs.gl.TexStorage3DMultisample -#define glTexSubImage1D gl3wProcs.gl.TexSubImage1D -#define glTexSubImage2D gl3wProcs.gl.TexSubImage2D -#define glTexSubImage3D gl3wProcs.gl.TexSubImage3D -#define glTextureBarrier gl3wProcs.gl.TextureBarrier -#define glTextureBuffer gl3wProcs.gl.TextureBuffer -#define glTextureBufferRange gl3wProcs.gl.TextureBufferRange -#define glTextureParameterIiv gl3wProcs.gl.TextureParameterIiv -#define glTextureParameterIuiv gl3wProcs.gl.TextureParameterIuiv -#define glTextureParameterf gl3wProcs.gl.TextureParameterf -#define glTextureParameterfv gl3wProcs.gl.TextureParameterfv -#define glTextureParameteri gl3wProcs.gl.TextureParameteri -#define glTextureParameteriv gl3wProcs.gl.TextureParameteriv -#define glTextureStorage1D gl3wProcs.gl.TextureStorage1D -#define glTextureStorage2D gl3wProcs.gl.TextureStorage2D -#define glTextureStorage2DMultisample gl3wProcs.gl.TextureStorage2DMultisample -#define glTextureStorage3D gl3wProcs.gl.TextureStorage3D -#define glTextureStorage3DMultisample gl3wProcs.gl.TextureStorage3DMultisample -#define glTextureSubImage1D gl3wProcs.gl.TextureSubImage1D -#define glTextureSubImage2D gl3wProcs.gl.TextureSubImage2D -#define glTextureSubImage3D gl3wProcs.gl.TextureSubImage3D -#define glTextureView gl3wProcs.gl.TextureView -#define glTransformFeedbackBufferBase gl3wProcs.gl.TransformFeedbackBufferBase -#define glTransformFeedbackBufferRange gl3wProcs.gl.TransformFeedbackBufferRange -#define glTransformFeedbackVaryings gl3wProcs.gl.TransformFeedbackVaryings -#define glUniform1d gl3wProcs.gl.Uniform1d -#define glUniform1dv gl3wProcs.gl.Uniform1dv -#define glUniform1f gl3wProcs.gl.Uniform1f -#define glUniform1fv gl3wProcs.gl.Uniform1fv -#define glUniform1i gl3wProcs.gl.Uniform1i -#define glUniform1iv gl3wProcs.gl.Uniform1iv -#define glUniform1ui gl3wProcs.gl.Uniform1ui -#define glUniform1uiv gl3wProcs.gl.Uniform1uiv -#define glUniform2d gl3wProcs.gl.Uniform2d -#define glUniform2dv gl3wProcs.gl.Uniform2dv -#define glUniform2f gl3wProcs.gl.Uniform2f -#define glUniform2fv gl3wProcs.gl.Uniform2fv -#define glUniform2i gl3wProcs.gl.Uniform2i -#define glUniform2iv gl3wProcs.gl.Uniform2iv -#define glUniform2ui gl3wProcs.gl.Uniform2ui -#define glUniform2uiv gl3wProcs.gl.Uniform2uiv -#define glUniform3d gl3wProcs.gl.Uniform3d -#define glUniform3dv gl3wProcs.gl.Uniform3dv -#define glUniform3f gl3wProcs.gl.Uniform3f -#define glUniform3fv gl3wProcs.gl.Uniform3fv -#define glUniform3i gl3wProcs.gl.Uniform3i -#define glUniform3iv gl3wProcs.gl.Uniform3iv -#define glUniform3ui gl3wProcs.gl.Uniform3ui -#define glUniform3uiv gl3wProcs.gl.Uniform3uiv -#define glUniform4d gl3wProcs.gl.Uniform4d -#define glUniform4dv gl3wProcs.gl.Uniform4dv -#define glUniform4f gl3wProcs.gl.Uniform4f -#define glUniform4fv gl3wProcs.gl.Uniform4fv -#define glUniform4i gl3wProcs.gl.Uniform4i -#define glUniform4iv gl3wProcs.gl.Uniform4iv -#define glUniform4ui gl3wProcs.gl.Uniform4ui -#define glUniform4uiv gl3wProcs.gl.Uniform4uiv -#define glUniformBlockBinding gl3wProcs.gl.UniformBlockBinding -#define glUniformMatrix2dv gl3wProcs.gl.UniformMatrix2dv -#define glUniformMatrix2fv gl3wProcs.gl.UniformMatrix2fv -#define glUniformMatrix2x3dv gl3wProcs.gl.UniformMatrix2x3dv -#define glUniformMatrix2x3fv gl3wProcs.gl.UniformMatrix2x3fv -#define glUniformMatrix2x4dv gl3wProcs.gl.UniformMatrix2x4dv -#define glUniformMatrix2x4fv gl3wProcs.gl.UniformMatrix2x4fv -#define glUniformMatrix3dv gl3wProcs.gl.UniformMatrix3dv -#define glUniformMatrix3fv gl3wProcs.gl.UniformMatrix3fv -#define glUniformMatrix3x2dv gl3wProcs.gl.UniformMatrix3x2dv -#define glUniformMatrix3x2fv gl3wProcs.gl.UniformMatrix3x2fv -#define glUniformMatrix3x4dv gl3wProcs.gl.UniformMatrix3x4dv -#define glUniformMatrix3x4fv gl3wProcs.gl.UniformMatrix3x4fv -#define glUniformMatrix4dv gl3wProcs.gl.UniformMatrix4dv -#define glUniformMatrix4fv gl3wProcs.gl.UniformMatrix4fv -#define glUniformMatrix4x2dv gl3wProcs.gl.UniformMatrix4x2dv -#define glUniformMatrix4x2fv gl3wProcs.gl.UniformMatrix4x2fv -#define glUniformMatrix4x3dv gl3wProcs.gl.UniformMatrix4x3dv -#define glUniformMatrix4x3fv gl3wProcs.gl.UniformMatrix4x3fv -#define glUniformSubroutinesuiv gl3wProcs.gl.UniformSubroutinesuiv -#define glUnmapBuffer gl3wProcs.gl.UnmapBuffer -#define glUnmapNamedBuffer gl3wProcs.gl.UnmapNamedBuffer -#define glUseProgram gl3wProcs.gl.UseProgram -#define glUseProgramStages gl3wProcs.gl.UseProgramStages -#define glValidateProgram gl3wProcs.gl.ValidateProgram -#define glValidateProgramPipeline gl3wProcs.gl.ValidateProgramPipeline -#define glVertexArrayAttribBinding gl3wProcs.gl.VertexArrayAttribBinding -#define glVertexArrayAttribFormat gl3wProcs.gl.VertexArrayAttribFormat -#define glVertexArrayAttribIFormat gl3wProcs.gl.VertexArrayAttribIFormat -#define glVertexArrayAttribLFormat gl3wProcs.gl.VertexArrayAttribLFormat -#define glVertexArrayBindingDivisor gl3wProcs.gl.VertexArrayBindingDivisor -#define glVertexArrayElementBuffer gl3wProcs.gl.VertexArrayElementBuffer -#define glVertexArrayVertexBuffer gl3wProcs.gl.VertexArrayVertexBuffer -#define glVertexArrayVertexBuffers gl3wProcs.gl.VertexArrayVertexBuffers -#define glVertexAttrib1d gl3wProcs.gl.VertexAttrib1d -#define glVertexAttrib1dv gl3wProcs.gl.VertexAttrib1dv -#define glVertexAttrib1f gl3wProcs.gl.VertexAttrib1f -#define glVertexAttrib1fv gl3wProcs.gl.VertexAttrib1fv -#define glVertexAttrib1s gl3wProcs.gl.VertexAttrib1s -#define glVertexAttrib1sv gl3wProcs.gl.VertexAttrib1sv -#define glVertexAttrib2d gl3wProcs.gl.VertexAttrib2d -#define glVertexAttrib2dv gl3wProcs.gl.VertexAttrib2dv -#define glVertexAttrib2f gl3wProcs.gl.VertexAttrib2f -#define glVertexAttrib2fv gl3wProcs.gl.VertexAttrib2fv -#define glVertexAttrib2s gl3wProcs.gl.VertexAttrib2s -#define glVertexAttrib2sv gl3wProcs.gl.VertexAttrib2sv -#define glVertexAttrib3d gl3wProcs.gl.VertexAttrib3d -#define glVertexAttrib3dv gl3wProcs.gl.VertexAttrib3dv -#define glVertexAttrib3f gl3wProcs.gl.VertexAttrib3f -#define glVertexAttrib3fv gl3wProcs.gl.VertexAttrib3fv -#define glVertexAttrib3s gl3wProcs.gl.VertexAttrib3s -#define glVertexAttrib3sv gl3wProcs.gl.VertexAttrib3sv -#define glVertexAttrib4Nbv gl3wProcs.gl.VertexAttrib4Nbv -#define glVertexAttrib4Niv gl3wProcs.gl.VertexAttrib4Niv -#define glVertexAttrib4Nsv gl3wProcs.gl.VertexAttrib4Nsv -#define glVertexAttrib4Nub gl3wProcs.gl.VertexAttrib4Nub -#define glVertexAttrib4Nubv gl3wProcs.gl.VertexAttrib4Nubv -#define glVertexAttrib4Nuiv gl3wProcs.gl.VertexAttrib4Nuiv -#define glVertexAttrib4Nusv gl3wProcs.gl.VertexAttrib4Nusv -#define glVertexAttrib4bv gl3wProcs.gl.VertexAttrib4bv -#define glVertexAttrib4d gl3wProcs.gl.VertexAttrib4d -#define glVertexAttrib4dv gl3wProcs.gl.VertexAttrib4dv -#define glVertexAttrib4f gl3wProcs.gl.VertexAttrib4f -#define glVertexAttrib4fv gl3wProcs.gl.VertexAttrib4fv -#define glVertexAttrib4iv gl3wProcs.gl.VertexAttrib4iv -#define glVertexAttrib4s gl3wProcs.gl.VertexAttrib4s -#define glVertexAttrib4sv gl3wProcs.gl.VertexAttrib4sv -#define glVertexAttrib4ubv gl3wProcs.gl.VertexAttrib4ubv -#define glVertexAttrib4uiv gl3wProcs.gl.VertexAttrib4uiv -#define glVertexAttrib4usv gl3wProcs.gl.VertexAttrib4usv -#define glVertexAttribBinding gl3wProcs.gl.VertexAttribBinding -#define glVertexAttribDivisor gl3wProcs.gl.VertexAttribDivisor -#define glVertexAttribFormat gl3wProcs.gl.VertexAttribFormat -#define glVertexAttribI1i gl3wProcs.gl.VertexAttribI1i -#define glVertexAttribI1iv gl3wProcs.gl.VertexAttribI1iv -#define glVertexAttribI1ui gl3wProcs.gl.VertexAttribI1ui -#define glVertexAttribI1uiv gl3wProcs.gl.VertexAttribI1uiv -#define glVertexAttribI2i gl3wProcs.gl.VertexAttribI2i -#define glVertexAttribI2iv gl3wProcs.gl.VertexAttribI2iv -#define glVertexAttribI2ui gl3wProcs.gl.VertexAttribI2ui -#define glVertexAttribI2uiv gl3wProcs.gl.VertexAttribI2uiv -#define glVertexAttribI3i gl3wProcs.gl.VertexAttribI3i -#define glVertexAttribI3iv gl3wProcs.gl.VertexAttribI3iv -#define glVertexAttribI3ui gl3wProcs.gl.VertexAttribI3ui -#define glVertexAttribI3uiv gl3wProcs.gl.VertexAttribI3uiv -#define glVertexAttribI4bv gl3wProcs.gl.VertexAttribI4bv -#define glVertexAttribI4i gl3wProcs.gl.VertexAttribI4i -#define glVertexAttribI4iv gl3wProcs.gl.VertexAttribI4iv -#define glVertexAttribI4sv gl3wProcs.gl.VertexAttribI4sv -#define glVertexAttribI4ubv gl3wProcs.gl.VertexAttribI4ubv -#define glVertexAttribI4ui gl3wProcs.gl.VertexAttribI4ui -#define glVertexAttribI4uiv gl3wProcs.gl.VertexAttribI4uiv -#define glVertexAttribI4usv gl3wProcs.gl.VertexAttribI4usv -#define glVertexAttribIFormat gl3wProcs.gl.VertexAttribIFormat -#define glVertexAttribIPointer gl3wProcs.gl.VertexAttribIPointer -#define glVertexAttribL1d gl3wProcs.gl.VertexAttribL1d -#define glVertexAttribL1dv gl3wProcs.gl.VertexAttribL1dv -#define glVertexAttribL2d gl3wProcs.gl.VertexAttribL2d -#define glVertexAttribL2dv gl3wProcs.gl.VertexAttribL2dv -#define glVertexAttribL3d gl3wProcs.gl.VertexAttribL3d -#define glVertexAttribL3dv gl3wProcs.gl.VertexAttribL3dv -#define glVertexAttribL4d gl3wProcs.gl.VertexAttribL4d -#define glVertexAttribL4dv gl3wProcs.gl.VertexAttribL4dv -#define glVertexAttribLFormat gl3wProcs.gl.VertexAttribLFormat -#define glVertexAttribLPointer gl3wProcs.gl.VertexAttribLPointer -#define glVertexAttribP1ui gl3wProcs.gl.VertexAttribP1ui -#define glVertexAttribP1uiv gl3wProcs.gl.VertexAttribP1uiv -#define glVertexAttribP2ui gl3wProcs.gl.VertexAttribP2ui -#define glVertexAttribP2uiv gl3wProcs.gl.VertexAttribP2uiv -#define glVertexAttribP3ui gl3wProcs.gl.VertexAttribP3ui -#define glVertexAttribP3uiv gl3wProcs.gl.VertexAttribP3uiv -#define glVertexAttribP4ui gl3wProcs.gl.VertexAttribP4ui -#define glVertexAttribP4uiv gl3wProcs.gl.VertexAttribP4uiv -#define glVertexAttribPointer gl3wProcs.gl.VertexAttribPointer -#define glVertexBindingDivisor gl3wProcs.gl.VertexBindingDivisor -#define glViewport gl3wProcs.gl.Viewport -#define glViewportArrayv gl3wProcs.gl.ViewportArrayv -#define glViewportIndexedf gl3wProcs.gl.ViewportIndexedf -#define glViewportIndexedfv gl3wProcs.gl.ViewportIndexedfv -#define glWaitSync gl3wProcs.gl.WaitSync - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/Framework/src/imgui/GL/glcorearb.h b/Framework/src/imgui/GL/glcorearb.h deleted file mode 100644 index 8de38faec7..0000000000 --- a/Framework/src/imgui/GL/glcorearb.h +++ /dev/null @@ -1,6539 +0,0 @@ -#ifndef __glcorearb_h_ -#define __glcorearb_h_ 1 - -#ifdef __cplusplus -extern "C" { -#endif - -/* -** Copyright (c) 2013-2017 The Khronos Group Inc. -** -** Permission is hereby granted, free of charge, to any person obtaining a -** copy of this software and/or associated documentation files (the -** "Materials"), to deal in the Materials without restriction, including -** without limitation the rights to use, copy, modify, merge, publish, -** distribute, sublicense, and/or sell copies of the Materials, and to -** permit persons to whom the Materials are furnished to do so, subject to -** the following conditions: -** -** The above copyright notice and this permission notice shall be included -** in all copies or substantial portions of the Materials. -** -** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. -*/ -/* -** This header is generated from the Khronos OpenGL / OpenGL ES XML -** API Registry. The current version of the Registry, generator scripts -** used to make the header, and the header can be found at -** https://github.com/KhronosGroup/OpenGL-Registry -*/ - -#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN 1 -#endif -#include -#endif - -#ifndef APIENTRY -#define APIENTRY -#endif -#ifndef APIENTRYP -#define APIENTRYP APIENTRY* -#endif -#ifndef GLAPI -#define GLAPI extern -#endif - -/* glcorearb.h is for use with OpenGL core profile implementations. -** It should should be placed in the same directory as gl.h and -** included as . -** -** glcorearb.h includes only APIs in the latest OpenGL core profile -** implementation together with APIs in newer ARB extensions which -** can be supported by the core profile. It does not, and never will -** include functionality removed from the core profile, such as -** fixed-function vertex and fragment processing. -** -** Do not #include both and either of or -** in the same source file. -*/ - -/* Generated C header for: - * API: gl - * Profile: core - * Versions considered: .* - * Versions emitted: .* - * Default extensions included: glcore - * Additional extensions included: _nomatch_^ - * Extensions removed: _nomatch_^ - */ - -#ifndef GL_VERSION_1_0 -#define GL_VERSION_1_0 1 -typedef void GLvoid; -typedef unsigned int GLenum; -typedef float GLfloat; -typedef int GLint; -typedef int GLsizei; -typedef unsigned int GLbitfield; -typedef double GLdouble; -typedef unsigned int GLuint; -typedef unsigned char GLboolean; -typedef unsigned char GLubyte; -#define GL_DEPTH_BUFFER_BIT 0x00000100 -#define GL_STENCIL_BUFFER_BIT 0x00000400 -#define GL_COLOR_BUFFER_BIT 0x00004000 -#define GL_FALSE 0 -#define GL_TRUE 1 -#define GL_POINTS 0x0000 -#define GL_LINES 0x0001 -#define GL_LINE_LOOP 0x0002 -#define GL_LINE_STRIP 0x0003 -#define GL_TRIANGLES 0x0004 -#define GL_TRIANGLE_STRIP 0x0005 -#define GL_TRIANGLE_FAN 0x0006 -#define GL_QUADS 0x0007 -#define GL_NEVER 0x0200 -#define GL_LESS 0x0201 -#define GL_EQUAL 0x0202 -#define GL_LEQUAL 0x0203 -#define GL_GREATER 0x0204 -#define GL_NOTEQUAL 0x0205 -#define GL_GEQUAL 0x0206 -#define GL_ALWAYS 0x0207 -#define GL_ZERO 0 -#define GL_ONE 1 -#define GL_SRC_COLOR 0x0300 -#define GL_ONE_MINUS_SRC_COLOR 0x0301 -#define GL_SRC_ALPHA 0x0302 -#define GL_ONE_MINUS_SRC_ALPHA 0x0303 -#define GL_DST_ALPHA 0x0304 -#define GL_ONE_MINUS_DST_ALPHA 0x0305 -#define GL_DST_COLOR 0x0306 -#define GL_ONE_MINUS_DST_COLOR 0x0307 -#define GL_SRC_ALPHA_SATURATE 0x0308 -#define GL_NONE 0 -#define GL_FRONT_LEFT 0x0400 -#define GL_FRONT_RIGHT 0x0401 -#define GL_BACK_LEFT 0x0402 -#define GL_BACK_RIGHT 0x0403 -#define GL_FRONT 0x0404 -#define GL_BACK 0x0405 -#define GL_LEFT 0x0406 -#define GL_RIGHT 0x0407 -#define GL_FRONT_AND_BACK 0x0408 -#define GL_NO_ERROR 0 -#define GL_INVALID_ENUM 0x0500 -#define GL_INVALID_VALUE 0x0501 -#define GL_INVALID_OPERATION 0x0502 -#define GL_OUT_OF_MEMORY 0x0505 -#define GL_CW 0x0900 -#define GL_CCW 0x0901 -#define GL_POINT_SIZE 0x0B11 -#define GL_POINT_SIZE_RANGE 0x0B12 -#define GL_POINT_SIZE_GRANULARITY 0x0B13 -#define GL_LINE_SMOOTH 0x0B20 -#define GL_LINE_WIDTH 0x0B21 -#define GL_LINE_WIDTH_RANGE 0x0B22 -#define GL_LINE_WIDTH_GRANULARITY 0x0B23 -#define GL_POLYGON_MODE 0x0B40 -#define GL_POLYGON_SMOOTH 0x0B41 -#define GL_CULL_FACE 0x0B44 -#define GL_CULL_FACE_MODE 0x0B45 -#define GL_FRONT_FACE 0x0B46 -#define GL_DEPTH_RANGE 0x0B70 -#define GL_DEPTH_TEST 0x0B71 -#define GL_DEPTH_WRITEMASK 0x0B72 -#define GL_DEPTH_CLEAR_VALUE 0x0B73 -#define GL_DEPTH_FUNC 0x0B74 -#define GL_STENCIL_TEST 0x0B90 -#define GL_STENCIL_CLEAR_VALUE 0x0B91 -#define GL_STENCIL_FUNC 0x0B92 -#define GL_STENCIL_VALUE_MASK 0x0B93 -#define GL_STENCIL_FAIL 0x0B94 -#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 -#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 -#define GL_STENCIL_REF 0x0B97 -#define GL_STENCIL_WRITEMASK 0x0B98 -#define GL_VIEWPORT 0x0BA2 -#define GL_DITHER 0x0BD0 -#define GL_BLEND_DST 0x0BE0 -#define GL_BLEND_SRC 0x0BE1 -#define GL_BLEND 0x0BE2 -#define GL_LOGIC_OP_MODE 0x0BF0 -#define GL_DRAW_BUFFER 0x0C01 -#define GL_READ_BUFFER 0x0C02 -#define GL_SCISSOR_BOX 0x0C10 -#define GL_SCISSOR_TEST 0x0C11 -#define GL_COLOR_CLEAR_VALUE 0x0C22 -#define GL_COLOR_WRITEMASK 0x0C23 -#define GL_DOUBLEBUFFER 0x0C32 -#define GL_STEREO 0x0C33 -#define GL_LINE_SMOOTH_HINT 0x0C52 -#define GL_POLYGON_SMOOTH_HINT 0x0C53 -#define GL_UNPACK_SWAP_BYTES 0x0CF0 -#define GL_UNPACK_LSB_FIRST 0x0CF1 -#define GL_UNPACK_ROW_LENGTH 0x0CF2 -#define GL_UNPACK_SKIP_ROWS 0x0CF3 -#define GL_UNPACK_SKIP_PIXELS 0x0CF4 -#define GL_UNPACK_ALIGNMENT 0x0CF5 -#define GL_PACK_SWAP_BYTES 0x0D00 -#define GL_PACK_LSB_FIRST 0x0D01 -#define GL_PACK_ROW_LENGTH 0x0D02 -#define GL_PACK_SKIP_ROWS 0x0D03 -#define GL_PACK_SKIP_PIXELS 0x0D04 -#define GL_PACK_ALIGNMENT 0x0D05 -#define GL_MAX_TEXTURE_SIZE 0x0D33 -#define GL_MAX_VIEWPORT_DIMS 0x0D3A -#define GL_SUBPIXEL_BITS 0x0D50 -#define GL_TEXTURE_1D 0x0DE0 -#define GL_TEXTURE_2D 0x0DE1 -#define GL_TEXTURE_WIDTH 0x1000 -#define GL_TEXTURE_HEIGHT 0x1001 -#define GL_TEXTURE_BORDER_COLOR 0x1004 -#define GL_DONT_CARE 0x1100 -#define GL_FASTEST 0x1101 -#define GL_NICEST 0x1102 -#define GL_BYTE 0x1400 -#define GL_UNSIGNED_BYTE 0x1401 -#define GL_SHORT 0x1402 -#define GL_UNSIGNED_SHORT 0x1403 -#define GL_INT 0x1404 -#define GL_UNSIGNED_INT 0x1405 -#define GL_FLOAT 0x1406 -#define GL_STACK_OVERFLOW 0x0503 -#define GL_STACK_UNDERFLOW 0x0504 -#define GL_CLEAR 0x1500 -#define GL_AND 0x1501 -#define GL_AND_REVERSE 0x1502 -#define GL_COPY 0x1503 -#define GL_AND_INVERTED 0x1504 -#define GL_NOOP 0x1505 -#define GL_XOR 0x1506 -#define GL_OR 0x1507 -#define GL_NOR 0x1508 -#define GL_EQUIV 0x1509 -#define GL_INVERT 0x150A -#define GL_OR_REVERSE 0x150B -#define GL_COPY_INVERTED 0x150C -#define GL_OR_INVERTED 0x150D -#define GL_NAND 0x150E -#define GL_SET 0x150F -#define GL_TEXTURE 0x1702 -#define GL_COLOR 0x1800 -#define GL_DEPTH 0x1801 -#define GL_STENCIL 0x1802 -#define GL_STENCIL_INDEX 0x1901 -#define GL_DEPTH_COMPONENT 0x1902 -#define GL_RED 0x1903 -#define GL_GREEN 0x1904 -#define GL_BLUE 0x1905 -#define GL_ALPHA 0x1906 -#define GL_RGB 0x1907 -#define GL_RGBA 0x1908 -#define GL_POINT 0x1B00 -#define GL_LINE 0x1B01 -#define GL_FILL 0x1B02 -#define GL_KEEP 0x1E00 -#define GL_REPLACE 0x1E01 -#define GL_INCR 0x1E02 -#define GL_DECR 0x1E03 -#define GL_VENDOR 0x1F00 -#define GL_RENDERER 0x1F01 -#define GL_VERSION 0x1F02 -#define GL_EXTENSIONS 0x1F03 -#define GL_NEAREST 0x2600 -#define GL_LINEAR 0x2601 -#define GL_NEAREST_MIPMAP_NEAREST 0x2700 -#define GL_LINEAR_MIPMAP_NEAREST 0x2701 -#define GL_NEAREST_MIPMAP_LINEAR 0x2702 -#define GL_LINEAR_MIPMAP_LINEAR 0x2703 -#define GL_TEXTURE_MAG_FILTER 0x2800 -#define GL_TEXTURE_MIN_FILTER 0x2801 -#define GL_TEXTURE_WRAP_S 0x2802 -#define GL_TEXTURE_WRAP_T 0x2803 -#define GL_REPEAT 0x2901 -typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode); -typedef void(APIENTRYP PFNGLFRONTFACEPROC)(GLenum mode); -typedef void(APIENTRYP PFNGLHINTPROC)(GLenum target, GLenum mode); -typedef void(APIENTRYP PFNGLLINEWIDTHPROC)(GLfloat width); -typedef void(APIENTRYP PFNGLPOINTSIZEPROC)(GLfloat size); -typedef void(APIENTRYP PFNGLPOLYGONMODEPROC)(GLenum face, GLenum mode); -typedef void(APIENTRYP PFNGLSCISSORPROC)(GLint x, GLint y, GLsizei width, GLsizei height); -typedef void(APIENTRYP PFNGLTEXPARAMETERFPROC)(GLenum target, GLenum pname, GLfloat param); -typedef void(APIENTRYP PFNGLTEXPARAMETERFVPROC)(GLenum target, GLenum pname, const GLfloat* params); -typedef void(APIENTRYP PFNGLTEXPARAMETERIPROC)(GLenum target, GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLTEXPARAMETERIVPROC)(GLenum target, GLenum pname, const GLint* params); -typedef void(APIENTRYP PFNGLTEXIMAGE1DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, - GLint border, GLenum format, GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLTEXIMAGE2DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, - GLsizei height, GLint border, GLenum format, GLenum type, - const void* pixels); -typedef void(APIENTRYP PFNGLDRAWBUFFERPROC)(GLenum buf); -typedef void(APIENTRYP PFNGLCLEARPROC)(GLbitfield mask); -typedef void(APIENTRYP PFNGLCLEARCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); -typedef void(APIENTRYP PFNGLCLEARSTENCILPROC)(GLint s); -typedef void(APIENTRYP PFNGLCLEARDEPTHPROC)(GLdouble depth); -typedef void(APIENTRYP PFNGLSTENCILMASKPROC)(GLuint mask); -typedef void(APIENTRYP PFNGLCOLORMASKPROC)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); -typedef void(APIENTRYP PFNGLDEPTHMASKPROC)(GLboolean flag); -typedef void(APIENTRYP PFNGLDISABLEPROC)(GLenum cap); -typedef void(APIENTRYP PFNGLENABLEPROC)(GLenum cap); -typedef void(APIENTRYP PFNGLFINISHPROC)(void); -typedef void(APIENTRYP PFNGLFLUSHPROC)(void); -typedef void(APIENTRYP PFNGLBLENDFUNCPROC)(GLenum sfactor, GLenum dfactor); -typedef void(APIENTRYP PFNGLLOGICOPPROC)(GLenum opcode); -typedef void(APIENTRYP PFNGLSTENCILFUNCPROC)(GLenum func, GLint ref, GLuint mask); -typedef void(APIENTRYP PFNGLSTENCILOPPROC)(GLenum fail, GLenum zfail, GLenum zpass); -typedef void(APIENTRYP PFNGLDEPTHFUNCPROC)(GLenum func); -typedef void(APIENTRYP PFNGLPIXELSTOREFPROC)(GLenum pname, GLfloat param); -typedef void(APIENTRYP PFNGLPIXELSTOREIPROC)(GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLREADBUFFERPROC)(GLenum src); -typedef void(APIENTRYP PFNGLREADPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, - void* pixels); -typedef void(APIENTRYP PFNGLGETBOOLEANVPROC)(GLenum pname, GLboolean* data); -typedef void(APIENTRYP PFNGLGETDOUBLEVPROC)(GLenum pname, GLdouble* data); -typedef GLenum(APIENTRYP PFNGLGETERRORPROC)(void); -typedef void(APIENTRYP PFNGLGETFLOATVPROC)(GLenum pname, GLfloat* data); -typedef void(APIENTRYP PFNGLGETINTEGERVPROC)(GLenum pname, GLint* data); -typedef const GLubyte*(APIENTRYP PFNGLGETSTRINGPROC)(GLenum name); -typedef void(APIENTRYP PFNGLGETTEXIMAGEPROC)(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); -typedef void(APIENTRYP PFNGLGETTEXPARAMETERFVPROC)(GLenum target, GLenum pname, GLfloat* params); -typedef void(APIENTRYP PFNGLGETTEXPARAMETERIVPROC)(GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETTEXLEVELPARAMETERFVPROC)(GLenum target, GLint level, GLenum pname, GLfloat* params); -typedef void(APIENTRYP PFNGLGETTEXLEVELPARAMETERIVPROC)(GLenum target, GLint level, GLenum pname, GLint* params); -typedef GLboolean(APIENTRYP PFNGLISENABLEDPROC)(GLenum cap); -typedef void(APIENTRYP PFNGLDEPTHRANGEPROC)(GLdouble near, GLdouble far); -typedef void(APIENTRYP PFNGLVIEWPORTPROC)(GLint x, GLint y, GLsizei width, GLsizei height); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glCullFace(GLenum mode); -GLAPI void APIENTRY glFrontFace(GLenum mode); -GLAPI void APIENTRY glHint(GLenum target, GLenum mode); -GLAPI void APIENTRY glLineWidth(GLfloat width); -GLAPI void APIENTRY glPointSize(GLfloat size); -GLAPI void APIENTRY glPolygonMode(GLenum face, GLenum mode); -GLAPI void APIENTRY glScissor(GLint x, GLint y, GLsizei width, GLsizei height); -GLAPI void APIENTRY glTexParameterf(GLenum target, GLenum pname, GLfloat param); -GLAPI void APIENTRY glTexParameterfv(GLenum target, GLenum pname, const GLfloat* params); -GLAPI void APIENTRY glTexParameteri(GLenum target, GLenum pname, GLint param); -GLAPI void APIENTRY glTexParameteriv(GLenum target, GLenum pname, const GLint* params); -GLAPI void APIENTRY glTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, - GLenum format, GLenum type, const void* pixels); -GLAPI void APIENTRY glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, - GLint border, GLenum format, GLenum type, const void* pixels); -GLAPI void APIENTRY glDrawBuffer(GLenum buf); -GLAPI void APIENTRY glClear(GLbitfield mask); -GLAPI void APIENTRY glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); -GLAPI void APIENTRY glClearStencil(GLint s); -GLAPI void APIENTRY glClearDepth(GLdouble depth); -GLAPI void APIENTRY glStencilMask(GLuint mask); -GLAPI void APIENTRY glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); -GLAPI void APIENTRY glDepthMask(GLboolean flag); -GLAPI void APIENTRY glDisable(GLenum cap); -GLAPI void APIENTRY glEnable(GLenum cap); -GLAPI void APIENTRY glFinish(void); -GLAPI void APIENTRY glFlush(void); -GLAPI void APIENTRY glBlendFunc(GLenum sfactor, GLenum dfactor); -GLAPI void APIENTRY glLogicOp(GLenum opcode); -GLAPI void APIENTRY glStencilFunc(GLenum func, GLint ref, GLuint mask); -GLAPI void APIENTRY glStencilOp(GLenum fail, GLenum zfail, GLenum zpass); -GLAPI void APIENTRY glDepthFunc(GLenum func); -GLAPI void APIENTRY glPixelStoref(GLenum pname, GLfloat param); -GLAPI void APIENTRY glPixelStorei(GLenum pname, GLint param); -GLAPI void APIENTRY glReadBuffer(GLenum src); -GLAPI void APIENTRY glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, - void* pixels); -GLAPI void APIENTRY glGetBooleanv(GLenum pname, GLboolean* data); -GLAPI void APIENTRY glGetDoublev(GLenum pname, GLdouble* data); -GLAPI GLenum APIENTRY glGetError(void); -GLAPI void APIENTRY glGetFloatv(GLenum pname, GLfloat* data); -GLAPI void APIENTRY glGetIntegerv(GLenum pname, GLint* data); -GLAPI const GLubyte* APIENTRY glGetString(GLenum name); -GLAPI void APIENTRY glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); -GLAPI void APIENTRY glGetTexParameterfv(GLenum target, GLenum pname, GLfloat* params); -GLAPI void APIENTRY glGetTexParameteriv(GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat* params); -GLAPI void APIENTRY glGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint* params); -GLAPI GLboolean APIENTRY glIsEnabled(GLenum cap); -GLAPI void APIENTRY glDepthRange(GLdouble near, GLdouble far); -GLAPI void APIENTRY glViewport(GLint x, GLint y, GLsizei width, GLsizei height); -#endif -#endif /* GL_VERSION_1_0 */ - -#ifndef GL_VERSION_1_1 -#define GL_VERSION_1_1 1 -typedef float GLclampf; -typedef double GLclampd; -#define GL_COLOR_LOGIC_OP 0x0BF2 -#define GL_POLYGON_OFFSET_UNITS 0x2A00 -#define GL_POLYGON_OFFSET_POINT 0x2A01 -#define GL_POLYGON_OFFSET_LINE 0x2A02 -#define GL_POLYGON_OFFSET_FILL 0x8037 -#define GL_POLYGON_OFFSET_FACTOR 0x8038 -#define GL_TEXTURE_BINDING_1D 0x8068 -#define GL_TEXTURE_BINDING_2D 0x8069 -#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 -#define GL_TEXTURE_RED_SIZE 0x805C -#define GL_TEXTURE_GREEN_SIZE 0x805D -#define GL_TEXTURE_BLUE_SIZE 0x805E -#define GL_TEXTURE_ALPHA_SIZE 0x805F -#define GL_DOUBLE 0x140A -#define GL_PROXY_TEXTURE_1D 0x8063 -#define GL_PROXY_TEXTURE_2D 0x8064 -#define GL_R3_G3_B2 0x2A10 -#define GL_RGB4 0x804F -#define GL_RGB5 0x8050 -#define GL_RGB8 0x8051 -#define GL_RGB10 0x8052 -#define GL_RGB12 0x8053 -#define GL_RGB16 0x8054 -#define GL_RGBA2 0x8055 -#define GL_RGBA4 0x8056 -#define GL_RGB5_A1 0x8057 -#define GL_RGBA8 0x8058 -#define GL_RGB10_A2 0x8059 -#define GL_RGBA12 0x805A -#define GL_RGBA16 0x805B -#define GL_VERTEX_ARRAY 0x8074 -typedef void(APIENTRYP PFNGLDRAWARRAYSPROC)(GLenum mode, GLint first, GLsizei count); -typedef void(APIENTRYP PFNGLDRAWELEMENTSPROC)(GLenum mode, GLsizei count, GLenum type, const void* indices); -typedef void(APIENTRYP PFNGLGETPOINTERVPROC)(GLenum pname, void** params); -typedef void(APIENTRYP PFNGLPOLYGONOFFSETPROC)(GLfloat factor, GLfloat units); -typedef void(APIENTRYP PFNGLCOPYTEXIMAGE1DPROC)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, - GLsizei width, GLint border); -typedef void(APIENTRYP PFNGLCOPYTEXIMAGE2DPROC)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, - GLsizei width, GLsizei height, GLint border); -typedef void(APIENTRYP PFNGLCOPYTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, - GLsizei width); -typedef void(APIENTRYP PFNGLCOPYTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, - GLint y, GLsizei width, GLsizei height); -typedef void(APIENTRYP PFNGLTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, - GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, - GLsizei height, GLenum format, GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLBINDTEXTUREPROC)(GLenum target, GLuint texture); -typedef void(APIENTRYP PFNGLDELETETEXTURESPROC)(GLsizei n, const GLuint* textures); -typedef void(APIENTRYP PFNGLGENTEXTURESPROC)(GLsizei n, GLuint* textures); -typedef GLboolean(APIENTRYP PFNGLISTEXTUREPROC)(GLuint texture); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glDrawArrays(GLenum mode, GLint first, GLsizei count); -GLAPI void APIENTRY glDrawElements(GLenum mode, GLsizei count, GLenum type, const void* indices); -GLAPI void APIENTRY glGetPointerv(GLenum pname, void** params); -GLAPI void APIENTRY glPolygonOffset(GLfloat factor, GLfloat units); -GLAPI void APIENTRY glCopyTexImage1D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, - GLint border); -GLAPI void APIENTRY glCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, - GLsizei height, GLint border); -GLAPI void APIENTRY glCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); -GLAPI void APIENTRY glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, - GLsizei width, GLsizei height); -GLAPI void APIENTRY glTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, - GLenum type, const void* pixels); -GLAPI void APIENTRY glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, - GLsizei height, GLenum format, GLenum type, const void* pixels); -GLAPI void APIENTRY glBindTexture(GLenum target, GLuint texture); -GLAPI void APIENTRY glDeleteTextures(GLsizei n, const GLuint* textures); -GLAPI void APIENTRY glGenTextures(GLsizei n, GLuint* textures); -GLAPI GLboolean APIENTRY glIsTexture(GLuint texture); -#endif -#endif /* GL_VERSION_1_1 */ - -#ifndef GL_VERSION_1_2 -#define GL_VERSION_1_2 1 -#define GL_UNSIGNED_BYTE_3_3_2 0x8032 -#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 -#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 -#define GL_UNSIGNED_INT_8_8_8_8 0x8035 -#define GL_UNSIGNED_INT_10_10_10_2 0x8036 -#define GL_TEXTURE_BINDING_3D 0x806A -#define GL_PACK_SKIP_IMAGES 0x806B -#define GL_PACK_IMAGE_HEIGHT 0x806C -#define GL_UNPACK_SKIP_IMAGES 0x806D -#define GL_UNPACK_IMAGE_HEIGHT 0x806E -#define GL_TEXTURE_3D 0x806F -#define GL_PROXY_TEXTURE_3D 0x8070 -#define GL_TEXTURE_DEPTH 0x8071 -#define GL_TEXTURE_WRAP_R 0x8072 -#define GL_MAX_3D_TEXTURE_SIZE 0x8073 -#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 -#define GL_UNSIGNED_SHORT_5_6_5 0x8363 -#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 -#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 -#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 -#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 -#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 -#define GL_BGR 0x80E0 -#define GL_BGRA 0x80E1 -#define GL_MAX_ELEMENTS_VERTICES 0x80E8 -#define GL_MAX_ELEMENTS_INDICES 0x80E9 -#define GL_CLAMP_TO_EDGE 0x812F -#define GL_TEXTURE_MIN_LOD 0x813A -#define GL_TEXTURE_MAX_LOD 0x813B -#define GL_TEXTURE_BASE_LEVEL 0x813C -#define GL_TEXTURE_MAX_LEVEL 0x813D -#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 -#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 -#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 -#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 -#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E -typedef void(APIENTRYP PFNGLDRAWRANGEELEMENTSPROC)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, - const void* indices); -typedef void(APIENTRYP PFNGLTEXIMAGE3DPROC)(GLenum target, GLint level, GLint internalformat, GLsizei width, - GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, - const void* pixels); -typedef void(APIENTRYP PFNGLTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, - const void* pixels); -typedef void(APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, - const void* indices); -GLAPI void APIENTRY glTexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, - GLsizei depth, GLint border, GLenum format, GLenum type, const void* pixels); -GLAPI void APIENTRY glTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, - const void* pixels); -GLAPI void APIENTRY glCopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLint x, GLint y, GLsizei width, GLsizei height); -#endif -#endif /* GL_VERSION_1_2 */ - -#ifndef GL_VERSION_1_3 -#define GL_VERSION_1_3 1 -#define GL_TEXTURE0 0x84C0 -#define GL_TEXTURE1 0x84C1 -#define GL_TEXTURE2 0x84C2 -#define GL_TEXTURE3 0x84C3 -#define GL_TEXTURE4 0x84C4 -#define GL_TEXTURE5 0x84C5 -#define GL_TEXTURE6 0x84C6 -#define GL_TEXTURE7 0x84C7 -#define GL_TEXTURE8 0x84C8 -#define GL_TEXTURE9 0x84C9 -#define GL_TEXTURE10 0x84CA -#define GL_TEXTURE11 0x84CB -#define GL_TEXTURE12 0x84CC -#define GL_TEXTURE13 0x84CD -#define GL_TEXTURE14 0x84CE -#define GL_TEXTURE15 0x84CF -#define GL_TEXTURE16 0x84D0 -#define GL_TEXTURE17 0x84D1 -#define GL_TEXTURE18 0x84D2 -#define GL_TEXTURE19 0x84D3 -#define GL_TEXTURE20 0x84D4 -#define GL_TEXTURE21 0x84D5 -#define GL_TEXTURE22 0x84D6 -#define GL_TEXTURE23 0x84D7 -#define GL_TEXTURE24 0x84D8 -#define GL_TEXTURE25 0x84D9 -#define GL_TEXTURE26 0x84DA -#define GL_TEXTURE27 0x84DB -#define GL_TEXTURE28 0x84DC -#define GL_TEXTURE29 0x84DD -#define GL_TEXTURE30 0x84DE -#define GL_TEXTURE31 0x84DF -#define GL_ACTIVE_TEXTURE 0x84E0 -#define GL_MULTISAMPLE 0x809D -#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E -#define GL_SAMPLE_ALPHA_TO_ONE 0x809F -#define GL_SAMPLE_COVERAGE 0x80A0 -#define GL_SAMPLE_BUFFERS 0x80A8 -#define GL_SAMPLES 0x80A9 -#define GL_SAMPLE_COVERAGE_VALUE 0x80AA -#define GL_SAMPLE_COVERAGE_INVERT 0x80AB -#define GL_TEXTURE_CUBE_MAP 0x8513 -#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 -#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 -#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A -#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B -#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C -#define GL_COMPRESSED_RGB 0x84ED -#define GL_COMPRESSED_RGBA 0x84EE -#define GL_TEXTURE_COMPRESSION_HINT 0x84EF -#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 -#define GL_TEXTURE_COMPRESSED 0x86A1 -#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 -#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 -#define GL_CLAMP_TO_BORDER 0x812D -typedef void(APIENTRYP PFNGLACTIVETEXTUREPROC)(GLenum texture); -typedef void(APIENTRYP PFNGLSAMPLECOVERAGEPROC)(GLfloat value, GLboolean invert); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, - GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, - const void* data); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, - GLsizei height, GLint border, GLsizei imageSize, - const void* data); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC)(GLenum target, GLint level, GLenum internalformat, GLsizei width, - GLint border, GLsizei imageSize, const void* data); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, - GLenum format, GLsizei imageSize, const void* data); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLsizei width, GLsizei height, GLenum format, - GLsizei imageSize, const void* data); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC)(GLenum target, GLint level, GLint xoffset, GLsizei width, - GLenum format, GLsizei imageSize, const void* data); -typedef void(APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC)(GLenum target, GLint level, void* img); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glActiveTexture(GLenum texture); -GLAPI void APIENTRY glSampleCoverage(GLfloat value, GLboolean invert); -GLAPI void APIENTRY glCompressedTexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, - GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, - const void* data); -GLAPI void APIENTRY glCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, - GLsizei height, GLint border, GLsizei imageSize, const void* data); -GLAPI void APIENTRY glCompressedTexImage1D(GLenum target, GLint level, GLenum internalformat, GLsizei width, - GLint border, GLsizei imageSize, const void* data); -GLAPI void APIENTRY glCompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, GLenum format, - GLsizei imageSize, const void* data); -GLAPI void APIENTRY glCompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, - GLsizei height, GLenum format, GLsizei imageSize, const void* data); -GLAPI void APIENTRY glCompressedTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, - GLsizei imageSize, const void* data); -GLAPI void APIENTRY glGetCompressedTexImage(GLenum target, GLint level, void* img); -#endif -#endif /* GL_VERSION_1_3 */ - -#ifndef GL_VERSION_1_4 -#define GL_VERSION_1_4 1 -#define GL_BLEND_DST_RGB 0x80C8 -#define GL_BLEND_SRC_RGB 0x80C9 -#define GL_BLEND_DST_ALPHA 0x80CA -#define GL_BLEND_SRC_ALPHA 0x80CB -#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 -#define GL_DEPTH_COMPONENT16 0x81A5 -#define GL_DEPTH_COMPONENT24 0x81A6 -#define GL_DEPTH_COMPONENT32 0x81A7 -#define GL_MIRRORED_REPEAT 0x8370 -#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD -#define GL_TEXTURE_LOD_BIAS 0x8501 -#define GL_INCR_WRAP 0x8507 -#define GL_DECR_WRAP 0x8508 -#define GL_TEXTURE_DEPTH_SIZE 0x884A -#define GL_TEXTURE_COMPARE_MODE 0x884C -#define GL_TEXTURE_COMPARE_FUNC 0x884D -#define GL_FUNC_ADD 0x8006 -#define GL_FUNC_SUBTRACT 0x800A -#define GL_FUNC_REVERSE_SUBTRACT 0x800B -#define GL_MIN 0x8007 -#define GL_MAX 0x8008 -#define GL_CONSTANT_COLOR 0x8001 -#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 -#define GL_CONSTANT_ALPHA 0x8003 -#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 -typedef void(APIENTRYP PFNGLBLENDFUNCSEPARATEPROC)(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, - GLenum dfactorAlpha); -typedef void(APIENTRYP PFNGLMULTIDRAWARRAYSPROC)(GLenum mode, const GLint* first, const GLsizei* count, - GLsizei drawcount); -typedef void(APIENTRYP PFNGLMULTIDRAWELEMENTSPROC)(GLenum mode, const GLsizei* count, GLenum type, - const void* const* indices, GLsizei drawcount); -typedef void(APIENTRYP PFNGLPOINTPARAMETERFPROC)(GLenum pname, GLfloat param); -typedef void(APIENTRYP PFNGLPOINTPARAMETERFVPROC)(GLenum pname, const GLfloat* params); -typedef void(APIENTRYP PFNGLPOINTPARAMETERIPROC)(GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLPOINTPARAMETERIVPROC)(GLenum pname, const GLint* params); -typedef void(APIENTRYP PFNGLBLENDCOLORPROC)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); -typedef void(APIENTRYP PFNGLBLENDEQUATIONPROC)(GLenum mode); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBlendFuncSeparate(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); -GLAPI void APIENTRY glMultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount); -GLAPI void APIENTRY glMultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, - GLsizei drawcount); -GLAPI void APIENTRY glPointParameterf(GLenum pname, GLfloat param); -GLAPI void APIENTRY glPointParameterfv(GLenum pname, const GLfloat* params); -GLAPI void APIENTRY glPointParameteri(GLenum pname, GLint param); -GLAPI void APIENTRY glPointParameteriv(GLenum pname, const GLint* params); -GLAPI void APIENTRY glBlendColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); -GLAPI void APIENTRY glBlendEquation(GLenum mode); -#endif -#endif /* GL_VERSION_1_4 */ - -#ifndef GL_VERSION_1_5 -#define GL_VERSION_1_5 1 -#include -typedef ptrdiff_t GLsizeiptr; -typedef ptrdiff_t GLintptr; -#define GL_BUFFER_SIZE 0x8764 -#define GL_BUFFER_USAGE 0x8765 -#define GL_QUERY_COUNTER_BITS 0x8864 -#define GL_CURRENT_QUERY 0x8865 -#define GL_QUERY_RESULT 0x8866 -#define GL_QUERY_RESULT_AVAILABLE 0x8867 -#define GL_ARRAY_BUFFER 0x8892 -#define GL_ELEMENT_ARRAY_BUFFER 0x8893 -#define GL_ARRAY_BUFFER_BINDING 0x8894 -#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 -#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F -#define GL_READ_ONLY 0x88B8 -#define GL_WRITE_ONLY 0x88B9 -#define GL_READ_WRITE 0x88BA -#define GL_BUFFER_ACCESS 0x88BB -#define GL_BUFFER_MAPPED 0x88BC -#define GL_BUFFER_MAP_POINTER 0x88BD -#define GL_STREAM_DRAW 0x88E0 -#define GL_STREAM_READ 0x88E1 -#define GL_STREAM_COPY 0x88E2 -#define GL_STATIC_DRAW 0x88E4 -#define GL_STATIC_READ 0x88E5 -#define GL_STATIC_COPY 0x88E6 -#define GL_DYNAMIC_DRAW 0x88E8 -#define GL_DYNAMIC_READ 0x88E9 -#define GL_DYNAMIC_COPY 0x88EA -#define GL_SAMPLES_PASSED 0x8914 -#define GL_SRC1_ALPHA 0x8589 -typedef void(APIENTRYP PFNGLGENQUERIESPROC)(GLsizei n, GLuint* ids); -typedef void(APIENTRYP PFNGLDELETEQUERIESPROC)(GLsizei n, const GLuint* ids); -typedef GLboolean(APIENTRYP PFNGLISQUERYPROC)(GLuint id); -typedef void(APIENTRYP PFNGLBEGINQUERYPROC)(GLenum target, GLuint id); -typedef void(APIENTRYP PFNGLENDQUERYPROC)(GLenum target); -typedef void(APIENTRYP PFNGLGETQUERYIVPROC)(GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETQUERYOBJECTIVPROC)(GLuint id, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETQUERYOBJECTUIVPROC)(GLuint id, GLenum pname, GLuint* params); -typedef void(APIENTRYP PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer); -typedef void(APIENTRYP PFNGLDELETEBUFFERSPROC)(GLsizei n, const GLuint* buffers); -typedef void(APIENTRYP PFNGLGENBUFFERSPROC)(GLsizei n, GLuint* buffers); -typedef GLboolean(APIENTRYP PFNGLISBUFFERPROC)(GLuint buffer); -typedef void(APIENTRYP PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void* data, GLenum usage); -typedef void(APIENTRYP PFNGLBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, const void* data); -typedef void(APIENTRYP PFNGLGETBUFFERSUBDATAPROC)(GLenum target, GLintptr offset, GLsizeiptr size, void* data); -typedef void*(APIENTRYP PFNGLMAPBUFFERPROC)(GLenum target, GLenum access); -typedef GLboolean(APIENTRYP PFNGLUNMAPBUFFERPROC)(GLenum target); -typedef void(APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETBUFFERPOINTERVPROC)(GLenum target, GLenum pname, void** params); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glGenQueries(GLsizei n, GLuint* ids); -GLAPI void APIENTRY glDeleteQueries(GLsizei n, const GLuint* ids); -GLAPI GLboolean APIENTRY glIsQuery(GLuint id); -GLAPI void APIENTRY glBeginQuery(GLenum target, GLuint id); -GLAPI void APIENTRY glEndQuery(GLenum target); -GLAPI void APIENTRY glGetQueryiv(GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetQueryObjectiv(GLuint id, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint* params); -GLAPI void APIENTRY glBindBuffer(GLenum target, GLuint buffer); -GLAPI void APIENTRY glDeleteBuffers(GLsizei n, const GLuint* buffers); -GLAPI void APIENTRY glGenBuffers(GLsizei n, GLuint* buffers); -GLAPI GLboolean APIENTRY glIsBuffer(GLuint buffer); -GLAPI void APIENTRY glBufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage); -GLAPI void APIENTRY glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data); -GLAPI void APIENTRY glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data); -GLAPI void* APIENTRY glMapBuffer(GLenum target, GLenum access); -GLAPI GLboolean APIENTRY glUnmapBuffer(GLenum target); -GLAPI void APIENTRY glGetBufferParameteriv(GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetBufferPointerv(GLenum target, GLenum pname, void** params); -#endif -#endif /* GL_VERSION_1_5 */ - -#ifndef GL_VERSION_2_0 -#define GL_VERSION_2_0 1 -typedef char GLchar; -typedef short GLshort; -typedef signed char GLbyte; -typedef unsigned short GLushort; -#define GL_BLEND_EQUATION_RGB 0x8009 -#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 -#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 -#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 -#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 -#define GL_CURRENT_VERTEX_ATTRIB 0x8626 -#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 -#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 -#define GL_STENCIL_BACK_FUNC 0x8800 -#define GL_STENCIL_BACK_FAIL 0x8801 -#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 -#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 -#define GL_MAX_DRAW_BUFFERS 0x8824 -#define GL_DRAW_BUFFER0 0x8825 -#define GL_DRAW_BUFFER1 0x8826 -#define GL_DRAW_BUFFER2 0x8827 -#define GL_DRAW_BUFFER3 0x8828 -#define GL_DRAW_BUFFER4 0x8829 -#define GL_DRAW_BUFFER5 0x882A -#define GL_DRAW_BUFFER6 0x882B -#define GL_DRAW_BUFFER7 0x882C -#define GL_DRAW_BUFFER8 0x882D -#define GL_DRAW_BUFFER9 0x882E -#define GL_DRAW_BUFFER10 0x882F -#define GL_DRAW_BUFFER11 0x8830 -#define GL_DRAW_BUFFER12 0x8831 -#define GL_DRAW_BUFFER13 0x8832 -#define GL_DRAW_BUFFER14 0x8833 -#define GL_DRAW_BUFFER15 0x8834 -#define GL_BLEND_EQUATION_ALPHA 0x883D -#define GL_MAX_VERTEX_ATTRIBS 0x8869 -#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A -#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 -#define GL_FRAGMENT_SHADER 0x8B30 -#define GL_VERTEX_SHADER 0x8B31 -#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 -#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A -#define GL_MAX_VARYING_FLOATS 0x8B4B -#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C -#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D -#define GL_SHADER_TYPE 0x8B4F -#define GL_FLOAT_VEC2 0x8B50 -#define GL_FLOAT_VEC3 0x8B51 -#define GL_FLOAT_VEC4 0x8B52 -#define GL_INT_VEC2 0x8B53 -#define GL_INT_VEC3 0x8B54 -#define GL_INT_VEC4 0x8B55 -#define GL_BOOL 0x8B56 -#define GL_BOOL_VEC2 0x8B57 -#define GL_BOOL_VEC3 0x8B58 -#define GL_BOOL_VEC4 0x8B59 -#define GL_FLOAT_MAT2 0x8B5A -#define GL_FLOAT_MAT3 0x8B5B -#define GL_FLOAT_MAT4 0x8B5C -#define GL_SAMPLER_1D 0x8B5D -#define GL_SAMPLER_2D 0x8B5E -#define GL_SAMPLER_3D 0x8B5F -#define GL_SAMPLER_CUBE 0x8B60 -#define GL_SAMPLER_1D_SHADOW 0x8B61 -#define GL_SAMPLER_2D_SHADOW 0x8B62 -#define GL_DELETE_STATUS 0x8B80 -#define GL_COMPILE_STATUS 0x8B81 -#define GL_LINK_STATUS 0x8B82 -#define GL_VALIDATE_STATUS 0x8B83 -#define GL_INFO_LOG_LENGTH 0x8B84 -#define GL_ATTACHED_SHADERS 0x8B85 -#define GL_ACTIVE_UNIFORMS 0x8B86 -#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 -#define GL_SHADER_SOURCE_LENGTH 0x8B88 -#define GL_ACTIVE_ATTRIBUTES 0x8B89 -#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A -#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B -#define GL_SHADING_LANGUAGE_VERSION 0x8B8C -#define GL_CURRENT_PROGRAM 0x8B8D -#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 -#define GL_LOWER_LEFT 0x8CA1 -#define GL_UPPER_LEFT 0x8CA2 -#define GL_STENCIL_BACK_REF 0x8CA3 -#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 -#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 -typedef void(APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC)(GLenum modeRGB, GLenum modeAlpha); -typedef void(APIENTRYP PFNGLDRAWBUFFERSPROC)(GLsizei n, const GLenum* bufs); -typedef void(APIENTRYP PFNGLSTENCILOPSEPARATEPROC)(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); -typedef void(APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC)(GLenum face, GLenum func, GLint ref, GLuint mask); -typedef void(APIENTRYP PFNGLSTENCILMASKSEPARATEPROC)(GLenum face, GLuint mask); -typedef void(APIENTRYP PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader); -typedef void(APIENTRYP PFNGLBINDATTRIBLOCATIONPROC)(GLuint program, GLuint index, const GLchar* name); -typedef void(APIENTRYP PFNGLCOMPILESHADERPROC)(GLuint shader); -typedef GLuint(APIENTRYP PFNGLCREATEPROGRAMPROC)(void); -typedef GLuint(APIENTRYP PFNGLCREATESHADERPROC)(GLenum type); -typedef void(APIENTRYP PFNGLDELETEPROGRAMPROC)(GLuint program); -typedef void(APIENTRYP PFNGLDELETESHADERPROC)(GLuint shader); -typedef void(APIENTRYP PFNGLDETACHSHADERPROC)(GLuint program, GLuint shader); -typedef void(APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC)(GLuint index); -typedef void(APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index); -typedef void(APIENTRYP PFNGLGETACTIVEATTRIBPROC)(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, - GLint* size, GLenum* type, GLchar* name); -typedef void(APIENTRYP PFNGLGETACTIVEUNIFORMPROC)(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, - GLint* size, GLenum* type, GLchar* name); -typedef void(APIENTRYP PFNGLGETATTACHEDSHADERSPROC)(GLuint program, GLsizei maxCount, GLsizei* count, GLuint* shaders); -typedef GLint(APIENTRYP PFNGLGETATTRIBLOCATIONPROC)(GLuint program, const GLchar* name); -typedef void(APIENTRYP PFNGLGETPROGRAMIVPROC)(GLuint program, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETPROGRAMINFOLOGPROC)(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog); -typedef void(APIENTRYP PFNGLGETSHADERIVPROC)(GLuint shader, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETSHADERINFOLOGPROC)(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); -typedef void(APIENTRYP PFNGLGETSHADERSOURCEPROC)(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* source); -typedef GLint(APIENTRYP PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, const GLchar* name); -typedef void(APIENTRYP PFNGLGETUNIFORMFVPROC)(GLuint program, GLint location, GLfloat* params); -typedef void(APIENTRYP PFNGLGETUNIFORMIVPROC)(GLuint program, GLint location, GLint* params); -typedef void(APIENTRYP PFNGLGETVERTEXATTRIBDVPROC)(GLuint index, GLenum pname, GLdouble* params); -typedef void(APIENTRYP PFNGLGETVERTEXATTRIBFVPROC)(GLuint index, GLenum pname, GLfloat* params); -typedef void(APIENTRYP PFNGLGETVERTEXATTRIBIVPROC)(GLuint index, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC)(GLuint index, GLenum pname, void** pointer); -typedef GLboolean(APIENTRYP PFNGLISPROGRAMPROC)(GLuint program); -typedef GLboolean(APIENTRYP PFNGLISSHADERPROC)(GLuint shader); -typedef void(APIENTRYP PFNGLLINKPROGRAMPROC)(GLuint program); -typedef void(APIENTRYP PFNGLSHADERSOURCEPROC)(GLuint shader, GLsizei count, const GLchar* const* string, - const GLint* length); -typedef void(APIENTRYP PFNGLUSEPROGRAMPROC)(GLuint program); -typedef void(APIENTRYP PFNGLUNIFORM1FPROC)(GLint location, GLfloat v0); -typedef void(APIENTRYP PFNGLUNIFORM2FPROC)(GLint location, GLfloat v0, GLfloat v1); -typedef void(APIENTRYP PFNGLUNIFORM3FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); -typedef void(APIENTRYP PFNGLUNIFORM4FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); -typedef void(APIENTRYP PFNGLUNIFORM1IPROC)(GLint location, GLint v0); -typedef void(APIENTRYP PFNGLUNIFORM2IPROC)(GLint location, GLint v0, GLint v1); -typedef void(APIENTRYP PFNGLUNIFORM3IPROC)(GLint location, GLint v0, GLint v1, GLint v2); -typedef void(APIENTRYP PFNGLUNIFORM4IPROC)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); -typedef void(APIENTRYP PFNGLUNIFORM1FVPROC)(GLint location, GLsizei count, const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORM2FVPROC)(GLint location, GLsizei count, const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORM3FVPROC)(GLint location, GLsizei count, const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORM4FVPROC)(GLint location, GLsizei count, const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORM1IVPROC)(GLint location, GLsizei count, const GLint* value); -typedef void(APIENTRYP PFNGLUNIFORM2IVPROC)(GLint location, GLsizei count, const GLint* value); -typedef void(APIENTRYP PFNGLUNIFORM3IVPROC)(GLint location, GLsizei count, const GLint* value); -typedef void(APIENTRYP PFNGLUNIFORM4IVPROC)(GLint location, GLsizei count, const GLint* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX2FVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX3FVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX4FVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -typedef void(APIENTRYP PFNGLVALIDATEPROGRAMPROC)(GLuint program); -typedef void(APIENTRYP PFNGLVERTEXATTRIB1DPROC)(GLuint index, GLdouble x); -typedef void(APIENTRYP PFNGLVERTEXATTRIB1DVPROC)(GLuint index, const GLdouble* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB1FPROC)(GLuint index, GLfloat x); -typedef void(APIENTRYP PFNGLVERTEXATTRIB1FVPROC)(GLuint index, const GLfloat* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB1SPROC)(GLuint index, GLshort x); -typedef void(APIENTRYP PFNGLVERTEXATTRIB1SVPROC)(GLuint index, const GLshort* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB2DPROC)(GLuint index, GLdouble x, GLdouble y); -typedef void(APIENTRYP PFNGLVERTEXATTRIB2DVPROC)(GLuint index, const GLdouble* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB2FPROC)(GLuint index, GLfloat x, GLfloat y); -typedef void(APIENTRYP PFNGLVERTEXATTRIB2FVPROC)(GLuint index, const GLfloat* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB2SPROC)(GLuint index, GLshort x, GLshort y); -typedef void(APIENTRYP PFNGLVERTEXATTRIB2SVPROC)(GLuint index, const GLshort* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB3DPROC)(GLuint index, GLdouble x, GLdouble y, GLdouble z); -typedef void(APIENTRYP PFNGLVERTEXATTRIB3DVPROC)(GLuint index, const GLdouble* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB3FPROC)(GLuint index, GLfloat x, GLfloat y, GLfloat z); -typedef void(APIENTRYP PFNGLVERTEXATTRIB3FVPROC)(GLuint index, const GLfloat* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB3SPROC)(GLuint index, GLshort x, GLshort y, GLshort z); -typedef void(APIENTRYP PFNGLVERTEXATTRIB3SVPROC)(GLuint index, const GLshort* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4NBVPROC)(GLuint index, const GLbyte* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4NIVPROC)(GLuint index, const GLint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4NSVPROC)(GLuint index, const GLshort* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4NUBPROC)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC)(GLuint index, const GLubyte* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC)(GLuint index, const GLuint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC)(GLuint index, const GLushort* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4BVPROC)(GLuint index, const GLbyte* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4DPROC)(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4DVPROC)(GLuint index, const GLdouble* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4FPROC)(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4FVPROC)(GLuint index, const GLfloat* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4IVPROC)(GLuint index, const GLint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4SPROC)(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4SVPROC)(GLuint index, const GLshort* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4UBVPROC)(GLuint index, const GLubyte* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4UIVPROC)(GLuint index, const GLuint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIB4USVPROC)(GLuint index, const GLushort* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index, GLint size, GLenum type, GLboolean normalized, - GLsizei stride, const void* pointer); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha); -GLAPI void APIENTRY glDrawBuffers(GLsizei n, const GLenum* bufs); -GLAPI void APIENTRY glStencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); -GLAPI void APIENTRY glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask); -GLAPI void APIENTRY glStencilMaskSeparate(GLenum face, GLuint mask); -GLAPI void APIENTRY glAttachShader(GLuint program, GLuint shader); -GLAPI void APIENTRY glBindAttribLocation(GLuint program, GLuint index, const GLchar* name); -GLAPI void APIENTRY glCompileShader(GLuint shader); -GLAPI GLuint APIENTRY glCreateProgram(void); -GLAPI GLuint APIENTRY glCreateShader(GLenum type); -GLAPI void APIENTRY glDeleteProgram(GLuint program); -GLAPI void APIENTRY glDeleteShader(GLuint shader); -GLAPI void APIENTRY glDetachShader(GLuint program, GLuint shader); -GLAPI void APIENTRY glDisableVertexAttribArray(GLuint index); -GLAPI void APIENTRY glEnableVertexAttribArray(GLuint index); -GLAPI void APIENTRY glGetActiveAttrib(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, - GLenum* type, GLchar* name); -GLAPI void APIENTRY glGetActiveUniform(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, GLint* size, - GLenum* type, GLchar* name); -GLAPI void APIENTRY glGetAttachedShaders(GLuint program, GLsizei maxCount, GLsizei* count, GLuint* shaders); -GLAPI GLint APIENTRY glGetAttribLocation(GLuint program, const GLchar* name); -GLAPI void APIENTRY glGetProgramiv(GLuint program, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog); -GLAPI void APIENTRY glGetShaderiv(GLuint shader, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog); -GLAPI void APIENTRY glGetShaderSource(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* source); -GLAPI GLint APIENTRY glGetUniformLocation(GLuint program, const GLchar* name); -GLAPI void APIENTRY glGetUniformfv(GLuint program, GLint location, GLfloat* params); -GLAPI void APIENTRY glGetUniformiv(GLuint program, GLint location, GLint* params); -GLAPI void APIENTRY glGetVertexAttribdv(GLuint index, GLenum pname, GLdouble* params); -GLAPI void APIENTRY glGetVertexAttribfv(GLuint index, GLenum pname, GLfloat* params); -GLAPI void APIENTRY glGetVertexAttribiv(GLuint index, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetVertexAttribPointerv(GLuint index, GLenum pname, void** pointer); -GLAPI GLboolean APIENTRY glIsProgram(GLuint program); -GLAPI GLboolean APIENTRY glIsShader(GLuint shader); -GLAPI void APIENTRY glLinkProgram(GLuint program); -GLAPI void APIENTRY glShaderSource(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); -GLAPI void APIENTRY glUseProgram(GLuint program); -GLAPI void APIENTRY glUniform1f(GLint location, GLfloat v0); -GLAPI void APIENTRY glUniform2f(GLint location, GLfloat v0, GLfloat v1); -GLAPI void APIENTRY glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); -GLAPI void APIENTRY glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); -GLAPI void APIENTRY glUniform1i(GLint location, GLint v0); -GLAPI void APIENTRY glUniform2i(GLint location, GLint v0, GLint v1); -GLAPI void APIENTRY glUniform3i(GLint location, GLint v0, GLint v1, GLint v2); -GLAPI void APIENTRY glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); -GLAPI void APIENTRY glUniform1fv(GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glUniform2fv(GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glUniform3fv(GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glUniform4fv(GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glUniform1iv(GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glUniform2iv(GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glUniform3iv(GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glUniform4iv(GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); -GLAPI void APIENTRY glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); -GLAPI void APIENTRY glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); -GLAPI void APIENTRY glValidateProgram(GLuint program); -GLAPI void APIENTRY glVertexAttrib1d(GLuint index, GLdouble x); -GLAPI void APIENTRY glVertexAttrib1dv(GLuint index, const GLdouble* v); -GLAPI void APIENTRY glVertexAttrib1f(GLuint index, GLfloat x); -GLAPI void APIENTRY glVertexAttrib1fv(GLuint index, const GLfloat* v); -GLAPI void APIENTRY glVertexAttrib1s(GLuint index, GLshort x); -GLAPI void APIENTRY glVertexAttrib1sv(GLuint index, const GLshort* v); -GLAPI void APIENTRY glVertexAttrib2d(GLuint index, GLdouble x, GLdouble y); -GLAPI void APIENTRY glVertexAttrib2dv(GLuint index, const GLdouble* v); -GLAPI void APIENTRY glVertexAttrib2f(GLuint index, GLfloat x, GLfloat y); -GLAPI void APIENTRY glVertexAttrib2fv(GLuint index, const GLfloat* v); -GLAPI void APIENTRY glVertexAttrib2s(GLuint index, GLshort x, GLshort y); -GLAPI void APIENTRY glVertexAttrib2sv(GLuint index, const GLshort* v); -GLAPI void APIENTRY glVertexAttrib3d(GLuint index, GLdouble x, GLdouble y, GLdouble z); -GLAPI void APIENTRY glVertexAttrib3dv(GLuint index, const GLdouble* v); -GLAPI void APIENTRY glVertexAttrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z); -GLAPI void APIENTRY glVertexAttrib3fv(GLuint index, const GLfloat* v); -GLAPI void APIENTRY glVertexAttrib3s(GLuint index, GLshort x, GLshort y, GLshort z); -GLAPI void APIENTRY glVertexAttrib3sv(GLuint index, const GLshort* v); -GLAPI void APIENTRY glVertexAttrib4Nbv(GLuint index, const GLbyte* v); -GLAPI void APIENTRY glVertexAttrib4Niv(GLuint index, const GLint* v); -GLAPI void APIENTRY glVertexAttrib4Nsv(GLuint index, const GLshort* v); -GLAPI void APIENTRY glVertexAttrib4Nub(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); -GLAPI void APIENTRY glVertexAttrib4Nubv(GLuint index, const GLubyte* v); -GLAPI void APIENTRY glVertexAttrib4Nuiv(GLuint index, const GLuint* v); -GLAPI void APIENTRY glVertexAttrib4Nusv(GLuint index, const GLushort* v); -GLAPI void APIENTRY glVertexAttrib4bv(GLuint index, const GLbyte* v); -GLAPI void APIENTRY glVertexAttrib4d(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); -GLAPI void APIENTRY glVertexAttrib4dv(GLuint index, const GLdouble* v); -GLAPI void APIENTRY glVertexAttrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); -GLAPI void APIENTRY glVertexAttrib4fv(GLuint index, const GLfloat* v); -GLAPI void APIENTRY glVertexAttrib4iv(GLuint index, const GLint* v); -GLAPI void APIENTRY glVertexAttrib4s(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); -GLAPI void APIENTRY glVertexAttrib4sv(GLuint index, const GLshort* v); -GLAPI void APIENTRY glVertexAttrib4ubv(GLuint index, const GLubyte* v); -GLAPI void APIENTRY glVertexAttrib4uiv(GLuint index, const GLuint* v); -GLAPI void APIENTRY glVertexAttrib4usv(GLuint index, const GLushort* v); -GLAPI void APIENTRY glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, - const void* pointer); -#endif -#endif /* GL_VERSION_2_0 */ - -#ifndef GL_VERSION_2_1 -#define GL_VERSION_2_1 1 -#define GL_PIXEL_PACK_BUFFER 0x88EB -#define GL_PIXEL_UNPACK_BUFFER 0x88EC -#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED -#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF -#define GL_FLOAT_MAT2x3 0x8B65 -#define GL_FLOAT_MAT2x4 0x8B66 -#define GL_FLOAT_MAT3x2 0x8B67 -#define GL_FLOAT_MAT3x4 0x8B68 -#define GL_FLOAT_MAT4x2 0x8B69 -#define GL_FLOAT_MAT4x3 0x8B6A -#define GL_SRGB 0x8C40 -#define GL_SRGB8 0x8C41 -#define GL_SRGB_ALPHA 0x8C42 -#define GL_SRGB8_ALPHA8 0x8C43 -#define GL_COMPRESSED_SRGB 0x8C48 -#define GL_COMPRESSED_SRGB_ALPHA 0x8C49 -typedef void(APIENTRYP PFNGLUNIFORMMATRIX2X3FVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX3X2FVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX2X4FVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX4X2FVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX3X4FVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX4X3FVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); -GLAPI void APIENTRY glUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); -GLAPI void APIENTRY glUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); -GLAPI void APIENTRY glUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); -GLAPI void APIENTRY glUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); -GLAPI void APIENTRY glUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value); -#endif -#endif /* GL_VERSION_2_1 */ - -#ifndef GL_VERSION_3_0 -#define GL_VERSION_3_0 1 -typedef unsigned short GLhalf; -#define GL_COMPARE_REF_TO_TEXTURE 0x884E -#define GL_CLIP_DISTANCE0 0x3000 -#define GL_CLIP_DISTANCE1 0x3001 -#define GL_CLIP_DISTANCE2 0x3002 -#define GL_CLIP_DISTANCE3 0x3003 -#define GL_CLIP_DISTANCE4 0x3004 -#define GL_CLIP_DISTANCE5 0x3005 -#define GL_CLIP_DISTANCE6 0x3006 -#define GL_CLIP_DISTANCE7 0x3007 -#define GL_MAX_CLIP_DISTANCES 0x0D32 -#define GL_MAJOR_VERSION 0x821B -#define GL_MINOR_VERSION 0x821C -#define GL_NUM_EXTENSIONS 0x821D -#define GL_CONTEXT_FLAGS 0x821E -#define GL_COMPRESSED_RED 0x8225 -#define GL_COMPRESSED_RG 0x8226 -#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x00000001 -#define GL_RGBA32F 0x8814 -#define GL_RGB32F 0x8815 -#define GL_RGBA16F 0x881A -#define GL_RGB16F 0x881B -#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD -#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF -#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904 -#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905 -#define GL_CLAMP_READ_COLOR 0x891C -#define GL_FIXED_ONLY 0x891D -#define GL_MAX_VARYING_COMPONENTS 0x8B4B -#define GL_TEXTURE_1D_ARRAY 0x8C18 -#define GL_PROXY_TEXTURE_1D_ARRAY 0x8C19 -#define GL_TEXTURE_2D_ARRAY 0x8C1A -#define GL_PROXY_TEXTURE_2D_ARRAY 0x8C1B -#define GL_TEXTURE_BINDING_1D_ARRAY 0x8C1C -#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D -#define GL_R11F_G11F_B10F 0x8C3A -#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B -#define GL_RGB9_E5 0x8C3D -#define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E -#define GL_TEXTURE_SHARED_SIZE 0x8C3F -#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76 -#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F -#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80 -#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83 -#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84 -#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85 -#define GL_PRIMITIVES_GENERATED 0x8C87 -#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88 -#define GL_RASTERIZER_DISCARD 0x8C89 -#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A -#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B -#define GL_INTERLEAVED_ATTRIBS 0x8C8C -#define GL_SEPARATE_ATTRIBS 0x8C8D -#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E -#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F -#define GL_RGBA32UI 0x8D70 -#define GL_RGB32UI 0x8D71 -#define GL_RGBA16UI 0x8D76 -#define GL_RGB16UI 0x8D77 -#define GL_RGBA8UI 0x8D7C -#define GL_RGB8UI 0x8D7D -#define GL_RGBA32I 0x8D82 -#define GL_RGB32I 0x8D83 -#define GL_RGBA16I 0x8D88 -#define GL_RGB16I 0x8D89 -#define GL_RGBA8I 0x8D8E -#define GL_RGB8I 0x8D8F -#define GL_RED_INTEGER 0x8D94 -#define GL_GREEN_INTEGER 0x8D95 -#define GL_BLUE_INTEGER 0x8D96 -#define GL_RGB_INTEGER 0x8D98 -#define GL_RGBA_INTEGER 0x8D99 -#define GL_BGR_INTEGER 0x8D9A -#define GL_BGRA_INTEGER 0x8D9B -#define GL_SAMPLER_1D_ARRAY 0x8DC0 -#define GL_SAMPLER_2D_ARRAY 0x8DC1 -#define GL_SAMPLER_1D_ARRAY_SHADOW 0x8DC3 -#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4 -#define GL_SAMPLER_CUBE_SHADOW 0x8DC5 -#define GL_UNSIGNED_INT_VEC2 0x8DC6 -#define GL_UNSIGNED_INT_VEC3 0x8DC7 -#define GL_UNSIGNED_INT_VEC4 0x8DC8 -#define GL_INT_SAMPLER_1D 0x8DC9 -#define GL_INT_SAMPLER_2D 0x8DCA -#define GL_INT_SAMPLER_3D 0x8DCB -#define GL_INT_SAMPLER_CUBE 0x8DCC -#define GL_INT_SAMPLER_1D_ARRAY 0x8DCE -#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF -#define GL_UNSIGNED_INT_SAMPLER_1D 0x8DD1 -#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2 -#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3 -#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4 -#define GL_UNSIGNED_INT_SAMPLER_1D_ARRAY 0x8DD6 -#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7 -#define GL_QUERY_WAIT 0x8E13 -#define GL_QUERY_NO_WAIT 0x8E14 -#define GL_QUERY_BY_REGION_WAIT 0x8E15 -#define GL_QUERY_BY_REGION_NO_WAIT 0x8E16 -#define GL_BUFFER_ACCESS_FLAGS 0x911F -#define GL_BUFFER_MAP_LENGTH 0x9120 -#define GL_BUFFER_MAP_OFFSET 0x9121 -#define GL_DEPTH_COMPONENT32F 0x8CAC -#define GL_DEPTH32F_STENCIL8 0x8CAD -#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD -#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 -#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 -#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 -#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 -#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 -#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 -#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 -#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 -#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 -#define GL_FRAMEBUFFER_DEFAULT 0x8218 -#define GL_FRAMEBUFFER_UNDEFINED 0x8219 -#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A -#define GL_MAX_RENDERBUFFER_SIZE 0x84E8 -#define GL_DEPTH_STENCIL 0x84F9 -#define GL_UNSIGNED_INT_24_8 0x84FA -#define GL_DEPTH24_STENCIL8 0x88F0 -#define GL_TEXTURE_STENCIL_SIZE 0x88F1 -#define GL_TEXTURE_RED_TYPE 0x8C10 -#define GL_TEXTURE_GREEN_TYPE 0x8C11 -#define GL_TEXTURE_BLUE_TYPE 0x8C12 -#define GL_TEXTURE_ALPHA_TYPE 0x8C13 -#define GL_TEXTURE_DEPTH_TYPE 0x8C16 -#define GL_UNSIGNED_NORMALIZED 0x8C17 -#define GL_FRAMEBUFFER_BINDING 0x8CA6 -#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 -#define GL_RENDERBUFFER_BINDING 0x8CA7 -#define GL_READ_FRAMEBUFFER 0x8CA8 -#define GL_DRAW_FRAMEBUFFER 0x8CA9 -#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA -#define GL_RENDERBUFFER_SAMPLES 0x8CAB -#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 -#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 -#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 -#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 -#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 -#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 -#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 -#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 -#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB -#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC -#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD -#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF -#define GL_COLOR_ATTACHMENT0 0x8CE0 -#define GL_COLOR_ATTACHMENT1 0x8CE1 -#define GL_COLOR_ATTACHMENT2 0x8CE2 -#define GL_COLOR_ATTACHMENT3 0x8CE3 -#define GL_COLOR_ATTACHMENT4 0x8CE4 -#define GL_COLOR_ATTACHMENT5 0x8CE5 -#define GL_COLOR_ATTACHMENT6 0x8CE6 -#define GL_COLOR_ATTACHMENT7 0x8CE7 -#define GL_COLOR_ATTACHMENT8 0x8CE8 -#define GL_COLOR_ATTACHMENT9 0x8CE9 -#define GL_COLOR_ATTACHMENT10 0x8CEA -#define GL_COLOR_ATTACHMENT11 0x8CEB -#define GL_COLOR_ATTACHMENT12 0x8CEC -#define GL_COLOR_ATTACHMENT13 0x8CED -#define GL_COLOR_ATTACHMENT14 0x8CEE -#define GL_COLOR_ATTACHMENT15 0x8CEF -#define GL_COLOR_ATTACHMENT16 0x8CF0 -#define GL_COLOR_ATTACHMENT17 0x8CF1 -#define GL_COLOR_ATTACHMENT18 0x8CF2 -#define GL_COLOR_ATTACHMENT19 0x8CF3 -#define GL_COLOR_ATTACHMENT20 0x8CF4 -#define GL_COLOR_ATTACHMENT21 0x8CF5 -#define GL_COLOR_ATTACHMENT22 0x8CF6 -#define GL_COLOR_ATTACHMENT23 0x8CF7 -#define GL_COLOR_ATTACHMENT24 0x8CF8 -#define GL_COLOR_ATTACHMENT25 0x8CF9 -#define GL_COLOR_ATTACHMENT26 0x8CFA -#define GL_COLOR_ATTACHMENT27 0x8CFB -#define GL_COLOR_ATTACHMENT28 0x8CFC -#define GL_COLOR_ATTACHMENT29 0x8CFD -#define GL_COLOR_ATTACHMENT30 0x8CFE -#define GL_COLOR_ATTACHMENT31 0x8CFF -#define GL_DEPTH_ATTACHMENT 0x8D00 -#define GL_STENCIL_ATTACHMENT 0x8D20 -#define GL_FRAMEBUFFER 0x8D40 -#define GL_RENDERBUFFER 0x8D41 -#define GL_RENDERBUFFER_WIDTH 0x8D42 -#define GL_RENDERBUFFER_HEIGHT 0x8D43 -#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 -#define GL_STENCIL_INDEX1 0x8D46 -#define GL_STENCIL_INDEX4 0x8D47 -#define GL_STENCIL_INDEX8 0x8D48 -#define GL_STENCIL_INDEX16 0x8D49 -#define GL_RENDERBUFFER_RED_SIZE 0x8D50 -#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 -#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 -#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 -#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 -#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 -#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 -#define GL_MAX_SAMPLES 0x8D57 -#define GL_FRAMEBUFFER_SRGB 0x8DB9 -#define GL_HALF_FLOAT 0x140B -#define GL_MAP_READ_BIT 0x0001 -#define GL_MAP_WRITE_BIT 0x0002 -#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 -#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 -#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 -#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020 -#define GL_COMPRESSED_RED_RGTC1 0x8DBB -#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC -#define GL_COMPRESSED_RG_RGTC2 0x8DBD -#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE -#define GL_RG 0x8227 -#define GL_RG_INTEGER 0x8228 -#define GL_R8 0x8229 -#define GL_R16 0x822A -#define GL_RG8 0x822B -#define GL_RG16 0x822C -#define GL_R16F 0x822D -#define GL_R32F 0x822E -#define GL_RG16F 0x822F -#define GL_RG32F 0x8230 -#define GL_R8I 0x8231 -#define GL_R8UI 0x8232 -#define GL_R16I 0x8233 -#define GL_R16UI 0x8234 -#define GL_R32I 0x8235 -#define GL_R32UI 0x8236 -#define GL_RG8I 0x8237 -#define GL_RG8UI 0x8238 -#define GL_RG16I 0x8239 -#define GL_RG16UI 0x823A -#define GL_RG32I 0x823B -#define GL_RG32UI 0x823C -#define GL_VERTEX_ARRAY_BINDING 0x85B5 -typedef void(APIENTRYP PFNGLCOLORMASKIPROC)(GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); -typedef void(APIENTRYP PFNGLGETBOOLEANI_VPROC)(GLenum target, GLuint index, GLboolean* data); -typedef void(APIENTRYP PFNGLGETINTEGERI_VPROC)(GLenum target, GLuint index, GLint* data); -typedef void(APIENTRYP PFNGLENABLEIPROC)(GLenum target, GLuint index); -typedef void(APIENTRYP PFNGLDISABLEIPROC)(GLenum target, GLuint index); -typedef GLboolean(APIENTRYP PFNGLISENABLEDIPROC)(GLenum target, GLuint index); -typedef void(APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKPROC)(GLenum primitiveMode); -typedef void(APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC)(void); -typedef void(APIENTRYP PFNGLBINDBUFFERRANGEPROC)(GLenum target, GLuint index, GLuint buffer, GLintptr offset, - GLsizeiptr size); -typedef void(APIENTRYP PFNGLBINDBUFFERBASEPROC)(GLenum target, GLuint index, GLuint buffer); -typedef void(APIENTRYP PFNGLTRANSFORMFEEDBACKVARYINGSPROC)(GLuint program, GLsizei count, const GLchar* const* varyings, - GLenum bufferMode); -typedef void(APIENTRYP PFNGLGETTRANSFORMFEEDBACKVARYINGPROC)(GLuint program, GLuint index, GLsizei bufSize, - GLsizei* length, GLsizei* size, GLenum* type, - GLchar* name); -typedef void(APIENTRYP PFNGLCLAMPCOLORPROC)(GLenum target, GLenum clamp); -typedef void(APIENTRYP PFNGLBEGINCONDITIONALRENDERPROC)(GLuint id, GLenum mode); -typedef void(APIENTRYP PFNGLENDCONDITIONALRENDERPROC)(void); -typedef void(APIENTRYP PFNGLVERTEXATTRIBIPOINTERPROC)(GLuint index, GLint size, GLenum type, GLsizei stride, - const void* pointer); -typedef void(APIENTRYP PFNGLGETVERTEXATTRIBIIVPROC)(GLuint index, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETVERTEXATTRIBIUIVPROC)(GLuint index, GLenum pname, GLuint* params); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI1IPROC)(GLuint index, GLint x); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI2IPROC)(GLuint index, GLint x, GLint y); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI3IPROC)(GLuint index, GLint x, GLint y, GLint z); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI4IPROC)(GLuint index, GLint x, GLint y, GLint z, GLint w); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI1UIPROC)(GLuint index, GLuint x); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI2UIPROC)(GLuint index, GLuint x, GLuint y); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI3UIPROC)(GLuint index, GLuint x, GLuint y, GLuint z); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI4UIPROC)(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI1IVPROC)(GLuint index, const GLint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI2IVPROC)(GLuint index, const GLint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI3IVPROC)(GLuint index, const GLint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI4IVPROC)(GLuint index, const GLint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI1UIVPROC)(GLuint index, const GLuint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI2UIVPROC)(GLuint index, const GLuint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI3UIVPROC)(GLuint index, const GLuint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI4UIVPROC)(GLuint index, const GLuint* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI4BVPROC)(GLuint index, const GLbyte* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI4SVPROC)(GLuint index, const GLshort* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI4UBVPROC)(GLuint index, const GLubyte* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBI4USVPROC)(GLuint index, const GLushort* v); -typedef void(APIENTRYP PFNGLGETUNIFORMUIVPROC)(GLuint program, GLint location, GLuint* params); -typedef void(APIENTRYP PFNGLBINDFRAGDATALOCATIONPROC)(GLuint program, GLuint color, const GLchar* name); -typedef GLint(APIENTRYP PFNGLGETFRAGDATALOCATIONPROC)(GLuint program, const GLchar* name); -typedef void(APIENTRYP PFNGLUNIFORM1UIPROC)(GLint location, GLuint v0); -typedef void(APIENTRYP PFNGLUNIFORM2UIPROC)(GLint location, GLuint v0, GLuint v1); -typedef void(APIENTRYP PFNGLUNIFORM3UIPROC)(GLint location, GLuint v0, GLuint v1, GLuint v2); -typedef void(APIENTRYP PFNGLUNIFORM4UIPROC)(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); -typedef void(APIENTRYP PFNGLUNIFORM1UIVPROC)(GLint location, GLsizei count, const GLuint* value); -typedef void(APIENTRYP PFNGLUNIFORM2UIVPROC)(GLint location, GLsizei count, const GLuint* value); -typedef void(APIENTRYP PFNGLUNIFORM3UIVPROC)(GLint location, GLsizei count, const GLuint* value); -typedef void(APIENTRYP PFNGLUNIFORM4UIVPROC)(GLint location, GLsizei count, const GLuint* value); -typedef void(APIENTRYP PFNGLTEXPARAMETERIIVPROC)(GLenum target, GLenum pname, const GLint* params); -typedef void(APIENTRYP PFNGLTEXPARAMETERIUIVPROC)(GLenum target, GLenum pname, const GLuint* params); -typedef void(APIENTRYP PFNGLGETTEXPARAMETERIIVPROC)(GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETTEXPARAMETERIUIVPROC)(GLenum target, GLenum pname, GLuint* params); -typedef void(APIENTRYP PFNGLCLEARBUFFERIVPROC)(GLenum buffer, GLint drawbuffer, const GLint* value); -typedef void(APIENTRYP PFNGLCLEARBUFFERUIVPROC)(GLenum buffer, GLint drawbuffer, const GLuint* value); -typedef void(APIENTRYP PFNGLCLEARBUFFERFVPROC)(GLenum buffer, GLint drawbuffer, const GLfloat* value); -typedef void(APIENTRYP PFNGLCLEARBUFFERFIPROC)(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); -typedef const GLubyte*(APIENTRYP PFNGLGETSTRINGIPROC)(GLenum name, GLuint index); -typedef GLboolean(APIENTRYP PFNGLISRENDERBUFFERPROC)(GLuint renderbuffer); -typedef void(APIENTRYP PFNGLBINDRENDERBUFFERPROC)(GLenum target, GLuint renderbuffer); -typedef void(APIENTRYP PFNGLDELETERENDERBUFFERSPROC)(GLsizei n, const GLuint* renderbuffers); -typedef void(APIENTRYP PFNGLGENRENDERBUFFERSPROC)(GLsizei n, GLuint* renderbuffers); -typedef void(APIENTRYP PFNGLRENDERBUFFERSTORAGEPROC)(GLenum target, GLenum internalformat, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname, GLint* params); -typedef GLboolean(APIENTRYP PFNGLISFRAMEBUFFERPROC)(GLuint framebuffer); -typedef void(APIENTRYP PFNGLBINDFRAMEBUFFERPROC)(GLenum target, GLuint framebuffer); -typedef void(APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC)(GLsizei n, const GLuint* framebuffers); -typedef void(APIENTRYP PFNGLGENFRAMEBUFFERSPROC)(GLsizei n, GLuint* framebuffers); -typedef GLenum(APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC)(GLenum target); -typedef void(APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DPROC)(GLenum target, GLenum attachment, GLenum textarget, - GLuint texture, GLint level); -typedef void(APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC)(GLenum target, GLenum attachment, GLenum textarget, - GLuint texture, GLint level); -typedef void(APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DPROC)(GLenum target, GLenum attachment, GLenum textarget, - GLuint texture, GLint level, GLint zoffset); -typedef void(APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFERPROC)(GLenum target, GLenum attachment, GLenum renderbuffertarget, - GLuint renderbuffer); -typedef void(APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)(GLenum target, GLenum attachment, GLenum pname, - GLint* params); -typedef void(APIENTRYP PFNGLGENERATEMIPMAPPROC)(GLenum target); -typedef void(APIENTRYP PFNGLBLITFRAMEBUFFERPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, - GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); -typedef void(APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height); -typedef void(APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERPROC)(GLenum target, GLenum attachment, GLuint texture, GLint level, - GLint layer); -typedef void*(APIENTRYP PFNGLMAPBUFFERRANGEPROC)(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); -typedef void(APIENTRYP PFNGLFLUSHMAPPEDBUFFERRANGEPROC)(GLenum target, GLintptr offset, GLsizeiptr length); -typedef void(APIENTRYP PFNGLBINDVERTEXARRAYPROC)(GLuint array); -typedef void(APIENTRYP PFNGLDELETEVERTEXARRAYSPROC)(GLsizei n, const GLuint* arrays); -typedef void(APIENTRYP PFNGLGENVERTEXARRAYSPROC)(GLsizei n, GLuint* arrays); -typedef GLboolean(APIENTRYP PFNGLISVERTEXARRAYPROC)(GLuint array); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glColorMaski(GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a); -GLAPI void APIENTRY glGetBooleani_v(GLenum target, GLuint index, GLboolean* data); -GLAPI void APIENTRY glGetIntegeri_v(GLenum target, GLuint index, GLint* data); -GLAPI void APIENTRY glEnablei(GLenum target, GLuint index); -GLAPI void APIENTRY glDisablei(GLenum target, GLuint index); -GLAPI GLboolean APIENTRY glIsEnabledi(GLenum target, GLuint index); -GLAPI void APIENTRY glBeginTransformFeedback(GLenum primitiveMode); -GLAPI void APIENTRY glEndTransformFeedback(void); -GLAPI void APIENTRY glBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size); -GLAPI void APIENTRY glBindBufferBase(GLenum target, GLuint index, GLuint buffer); -GLAPI void APIENTRY glTransformFeedbackVaryings(GLuint program, GLsizei count, const GLchar* const* varyings, - GLenum bufferMode); -GLAPI void APIENTRY glGetTransformFeedbackVarying(GLuint program, GLuint index, GLsizei bufSize, GLsizei* length, - GLsizei* size, GLenum* type, GLchar* name); -GLAPI void APIENTRY glClampColor(GLenum target, GLenum clamp); -GLAPI void APIENTRY glBeginConditionalRender(GLuint id, GLenum mode); -GLAPI void APIENTRY glEndConditionalRender(void); -GLAPI void APIENTRY glVertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer); -GLAPI void APIENTRY glGetVertexAttribIiv(GLuint index, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetVertexAttribIuiv(GLuint index, GLenum pname, GLuint* params); -GLAPI void APIENTRY glVertexAttribI1i(GLuint index, GLint x); -GLAPI void APIENTRY glVertexAttribI2i(GLuint index, GLint x, GLint y); -GLAPI void APIENTRY glVertexAttribI3i(GLuint index, GLint x, GLint y, GLint z); -GLAPI void APIENTRY glVertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w); -GLAPI void APIENTRY glVertexAttribI1ui(GLuint index, GLuint x); -GLAPI void APIENTRY glVertexAttribI2ui(GLuint index, GLuint x, GLuint y); -GLAPI void APIENTRY glVertexAttribI3ui(GLuint index, GLuint x, GLuint y, GLuint z); -GLAPI void APIENTRY glVertexAttribI4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w); -GLAPI void APIENTRY glVertexAttribI1iv(GLuint index, const GLint* v); -GLAPI void APIENTRY glVertexAttribI2iv(GLuint index, const GLint* v); -GLAPI void APIENTRY glVertexAttribI3iv(GLuint index, const GLint* v); -GLAPI void APIENTRY glVertexAttribI4iv(GLuint index, const GLint* v); -GLAPI void APIENTRY glVertexAttribI1uiv(GLuint index, const GLuint* v); -GLAPI void APIENTRY glVertexAttribI2uiv(GLuint index, const GLuint* v); -GLAPI void APIENTRY glVertexAttribI3uiv(GLuint index, const GLuint* v); -GLAPI void APIENTRY glVertexAttribI4uiv(GLuint index, const GLuint* v); -GLAPI void APIENTRY glVertexAttribI4bv(GLuint index, const GLbyte* v); -GLAPI void APIENTRY glVertexAttribI4sv(GLuint index, const GLshort* v); -GLAPI void APIENTRY glVertexAttribI4ubv(GLuint index, const GLubyte* v); -GLAPI void APIENTRY glVertexAttribI4usv(GLuint index, const GLushort* v); -GLAPI void APIENTRY glGetUniformuiv(GLuint program, GLint location, GLuint* params); -GLAPI void APIENTRY glBindFragDataLocation(GLuint program, GLuint color, const GLchar* name); -GLAPI GLint APIENTRY glGetFragDataLocation(GLuint program, const GLchar* name); -GLAPI void APIENTRY glUniform1ui(GLint location, GLuint v0); -GLAPI void APIENTRY glUniform2ui(GLint location, GLuint v0, GLuint v1); -GLAPI void APIENTRY glUniform3ui(GLint location, GLuint v0, GLuint v1, GLuint v2); -GLAPI void APIENTRY glUniform4ui(GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); -GLAPI void APIENTRY glUniform1uiv(GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glUniform2uiv(GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glUniform3uiv(GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glUniform4uiv(GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glTexParameterIiv(GLenum target, GLenum pname, const GLint* params); -GLAPI void APIENTRY glTexParameterIuiv(GLenum target, GLenum pname, const GLuint* params); -GLAPI void APIENTRY glGetTexParameterIiv(GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetTexParameterIuiv(GLenum target, GLenum pname, GLuint* params); -GLAPI void APIENTRY glClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value); -GLAPI void APIENTRY glClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value); -GLAPI void APIENTRY glClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value); -GLAPI void APIENTRY glClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); -GLAPI const GLubyte* APIENTRY glGetStringi(GLenum name, GLuint index); -GLAPI GLboolean APIENTRY glIsRenderbuffer(GLuint renderbuffer); -GLAPI void APIENTRY glBindRenderbuffer(GLenum target, GLuint renderbuffer); -GLAPI void APIENTRY glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers); -GLAPI void APIENTRY glGenRenderbuffers(GLsizei n, GLuint* renderbuffers); -GLAPI void APIENTRY glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); -GLAPI void APIENTRY glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params); -GLAPI GLboolean APIENTRY glIsFramebuffer(GLuint framebuffer); -GLAPI void APIENTRY glBindFramebuffer(GLenum target, GLuint framebuffer); -GLAPI void APIENTRY glDeleteFramebuffers(GLsizei n, const GLuint* framebuffers); -GLAPI void APIENTRY glGenFramebuffers(GLsizei n, GLuint* framebuffers); -GLAPI GLenum APIENTRY glCheckFramebufferStatus(GLenum target); -GLAPI void APIENTRY glFramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, - GLint level); -GLAPI void APIENTRY glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, - GLint level); -GLAPI void APIENTRY glFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, - GLint level, GLint zoffset); -GLAPI void APIENTRY glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, - GLuint renderbuffer); -GLAPI void APIENTRY glGetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, - GLint* params); -GLAPI void APIENTRY glGenerateMipmap(GLenum target); -GLAPI void APIENTRY glBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, - GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); -GLAPI void APIENTRY glRenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height); -GLAPI void APIENTRY glFramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, - GLint layer); -GLAPI void* APIENTRY glMapBufferRange(GLenum target, GLintptr offset, GLsizeiptr length, GLbitfield access); -GLAPI void APIENTRY glFlushMappedBufferRange(GLenum target, GLintptr offset, GLsizeiptr length); -GLAPI void APIENTRY glBindVertexArray(GLuint array); -GLAPI void APIENTRY glDeleteVertexArrays(GLsizei n, const GLuint* arrays); -GLAPI void APIENTRY glGenVertexArrays(GLsizei n, GLuint* arrays); -GLAPI GLboolean APIENTRY glIsVertexArray(GLuint array); -#endif -#endif /* GL_VERSION_3_0 */ - -#ifndef GL_VERSION_3_1 -#define GL_VERSION_3_1 1 -#define GL_SAMPLER_2D_RECT 0x8B63 -#define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 -#define GL_SAMPLER_BUFFER 0x8DC2 -#define GL_INT_SAMPLER_2D_RECT 0x8DCD -#define GL_INT_SAMPLER_BUFFER 0x8DD0 -#define GL_UNSIGNED_INT_SAMPLER_2D_RECT 0x8DD5 -#define GL_UNSIGNED_INT_SAMPLER_BUFFER 0x8DD8 -#define GL_TEXTURE_BUFFER 0x8C2A -#define GL_MAX_TEXTURE_BUFFER_SIZE 0x8C2B -#define GL_TEXTURE_BINDING_BUFFER 0x8C2C -#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING 0x8C2D -#define GL_TEXTURE_RECTANGLE 0x84F5 -#define GL_TEXTURE_BINDING_RECTANGLE 0x84F6 -#define GL_PROXY_TEXTURE_RECTANGLE 0x84F7 -#define GL_MAX_RECTANGLE_TEXTURE_SIZE 0x84F8 -#define GL_R8_SNORM 0x8F94 -#define GL_RG8_SNORM 0x8F95 -#define GL_RGB8_SNORM 0x8F96 -#define GL_RGBA8_SNORM 0x8F97 -#define GL_R16_SNORM 0x8F98 -#define GL_RG16_SNORM 0x8F99 -#define GL_RGB16_SNORM 0x8F9A -#define GL_RGBA16_SNORM 0x8F9B -#define GL_SIGNED_NORMALIZED 0x8F9C -#define GL_PRIMITIVE_RESTART 0x8F9D -#define GL_PRIMITIVE_RESTART_INDEX 0x8F9E -#define GL_COPY_READ_BUFFER 0x8F36 -#define GL_COPY_WRITE_BUFFER 0x8F37 -#define GL_UNIFORM_BUFFER 0x8A11 -#define GL_UNIFORM_BUFFER_BINDING 0x8A28 -#define GL_UNIFORM_BUFFER_START 0x8A29 -#define GL_UNIFORM_BUFFER_SIZE 0x8A2A -#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B -#define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C -#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D -#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E -#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F -#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 -#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 -#define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 -#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 -#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 -#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 -#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 -#define GL_UNIFORM_TYPE 0x8A37 -#define GL_UNIFORM_SIZE 0x8A38 -#define GL_UNIFORM_NAME_LENGTH 0x8A39 -#define GL_UNIFORM_BLOCK_INDEX 0x8A3A -#define GL_UNIFORM_OFFSET 0x8A3B -#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C -#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D -#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E -#define GL_UNIFORM_BLOCK_BINDING 0x8A3F -#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 -#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 -#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 -#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 -#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 -#define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 -#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 -#define GL_INVALID_INDEX 0xFFFFFFFFu -typedef void(APIENTRYP PFNGLDRAWARRAYSINSTANCEDPROC)(GLenum mode, GLint first, GLsizei count, GLsizei instancecount); -typedef void(APIENTRYP PFNGLDRAWELEMENTSINSTANCEDPROC)(GLenum mode, GLsizei count, GLenum type, const void* indices, - GLsizei instancecount); -typedef void(APIENTRYP PFNGLTEXBUFFERPROC)(GLenum target, GLenum internalformat, GLuint buffer); -typedef void(APIENTRYP PFNGLPRIMITIVERESTARTINDEXPROC)(GLuint index); -typedef void(APIENTRYP PFNGLCOPYBUFFERSUBDATAPROC)(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, - GLintptr writeOffset, GLsizeiptr size); -typedef void(APIENTRYP PFNGLGETUNIFORMINDICESPROC)(GLuint program, GLsizei uniformCount, - const GLchar* const* uniformNames, GLuint* uniformIndices); -typedef void(APIENTRYP PFNGLGETACTIVEUNIFORMSIVPROC)(GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, - GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETACTIVEUNIFORMNAMEPROC)(GLuint program, GLuint uniformIndex, GLsizei bufSize, - GLsizei* length, GLchar* uniformName); -typedef GLuint(APIENTRYP PFNGLGETUNIFORMBLOCKINDEXPROC)(GLuint program, const GLchar* uniformBlockName); -typedef void(APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKIVPROC)(GLuint program, GLuint uniformBlockIndex, GLenum pname, - GLint* params); -typedef void(APIENTRYP PFNGLGETACTIVEUNIFORMBLOCKNAMEPROC)(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, - GLsizei* length, GLchar* uniformBlockName); -typedef void(APIENTRYP PFNGLUNIFORMBLOCKBINDINGPROC)(GLuint program, GLuint uniformBlockIndex, - GLuint uniformBlockBinding); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount); -GLAPI void APIENTRY glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, - GLsizei instancecount); -GLAPI void APIENTRY glTexBuffer(GLenum target, GLenum internalformat, GLuint buffer); -GLAPI void APIENTRY glPrimitiveRestartIndex(GLuint index); -GLAPI void APIENTRY glCopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, - GLintptr writeOffset, GLsizeiptr size); -GLAPI void APIENTRY glGetUniformIndices(GLuint program, GLsizei uniformCount, const GLchar* const* uniformNames, - GLuint* uniformIndices); -GLAPI void APIENTRY glGetActiveUniformsiv(GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, - GLenum pname, GLint* params); -GLAPI void APIENTRY glGetActiveUniformName(GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei* length, - GLchar* uniformName); -GLAPI GLuint APIENTRY glGetUniformBlockIndex(GLuint program, const GLchar* uniformBlockName); -GLAPI void APIENTRY glGetActiveUniformBlockiv(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetActiveUniformBlockName(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, - GLsizei* length, GLchar* uniformBlockName); -GLAPI void APIENTRY glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); -#endif -#endif /* GL_VERSION_3_1 */ - -#ifndef GL_VERSION_3_2 -#define GL_VERSION_3_2 1 -typedef struct __GLsync* GLsync; -#ifndef GLEXT_64_TYPES_DEFINED -/* This code block is duplicated in glxext.h, so must be protected */ -#define GLEXT_64_TYPES_DEFINED -/* Define int32_t, int64_t, and uint64_t types for UST/MSC */ -/* (as used in the GL_EXT_timer_query extension). */ -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L -#include -#elif defined(__sun__) || defined(__digital__) -#include -#if defined(__STDC__) -#if defined(__arch64__) || defined(_LP64) -typedef long int int64_t; -typedef unsigned long int uint64_t; -#else -typedef long long int int64_t; -typedef unsigned long long int uint64_t; -#endif /* __arch64__ */ -#endif /* __STDC__ */ -#elif defined(__VMS) || defined(__sgi) -#include -#elif defined(__SCO__) || defined(__USLC__) -#include -#elif defined(__UNIXOS2__) || defined(__SOL64__) -typedef long int int32_t; -typedef long long int int64_t; -typedef unsigned long long int uint64_t; -#elif defined(_WIN32) && defined(__GNUC__) -#include -#elif defined(_WIN32) -typedef __int32 int32_t; -typedef __int64 int64_t; -typedef unsigned __int64 uint64_t; -#else -/* Fallback if nothing above works */ -#include -#endif -#endif -typedef uint64_t GLuint64; -typedef int64_t GLint64; -#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 -#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 -#define GL_LINES_ADJACENCY 0x000A -#define GL_LINE_STRIP_ADJACENCY 0x000B -#define GL_TRIANGLES_ADJACENCY 0x000C -#define GL_TRIANGLE_STRIP_ADJACENCY 0x000D -#define GL_PROGRAM_POINT_SIZE 0x8642 -#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS 0x8C29 -#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED 0x8DA7 -#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS 0x8DA8 -#define GL_GEOMETRY_SHADER 0x8DD9 -#define GL_GEOMETRY_VERTICES_OUT 0x8916 -#define GL_GEOMETRY_INPUT_TYPE 0x8917 -#define GL_GEOMETRY_OUTPUT_TYPE 0x8918 -#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS 0x8DDF -#define GL_MAX_GEOMETRY_OUTPUT_VERTICES 0x8DE0 -#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS 0x8DE1 -#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122 -#define GL_MAX_GEOMETRY_INPUT_COMPONENTS 0x9123 -#define GL_MAX_GEOMETRY_OUTPUT_COMPONENTS 0x9124 -#define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125 -#define GL_CONTEXT_PROFILE_MASK 0x9126 -#define GL_DEPTH_CLAMP 0x864F -#define GL_QUADS_FOLLOW_PROVOKING_VERTEX_CONVENTION 0x8E4C -#define GL_FIRST_VERTEX_CONVENTION 0x8E4D -#define GL_LAST_VERTEX_CONVENTION 0x8E4E -#define GL_PROVOKING_VERTEX 0x8E4F -#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F -#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111 -#define GL_OBJECT_TYPE 0x9112 -#define GL_SYNC_CONDITION 0x9113 -#define GL_SYNC_STATUS 0x9114 -#define GL_SYNC_FLAGS 0x9115 -#define GL_SYNC_FENCE 0x9116 -#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117 -#define GL_UNSIGNALED 0x9118 -#define GL_SIGNALED 0x9119 -#define GL_ALREADY_SIGNALED 0x911A -#define GL_TIMEOUT_EXPIRED 0x911B -#define GL_CONDITION_SATISFIED 0x911C -#define GL_WAIT_FAILED 0x911D -#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull -#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001 -#define GL_SAMPLE_POSITION 0x8E50 -#define GL_SAMPLE_MASK 0x8E51 -#define GL_SAMPLE_MASK_VALUE 0x8E52 -#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59 -#define GL_TEXTURE_2D_MULTISAMPLE 0x9100 -#define GL_PROXY_TEXTURE_2D_MULTISAMPLE 0x9101 -#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102 -#define GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9103 -#define GL_TEXTURE_BINDING_2D_MULTISAMPLE 0x9104 -#define GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY 0x9105 -#define GL_TEXTURE_SAMPLES 0x9106 -#define GL_TEXTURE_FIXED_SAMPLE_LOCATIONS 0x9107 -#define GL_SAMPLER_2D_MULTISAMPLE 0x9108 -#define GL_INT_SAMPLER_2D_MULTISAMPLE 0x9109 -#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE 0x910A -#define GL_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910B -#define GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910C -#define GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY 0x910D -#define GL_MAX_COLOR_TEXTURE_SAMPLES 0x910E -#define GL_MAX_DEPTH_TEXTURE_SAMPLES 0x910F -#define GL_MAX_INTEGER_SAMPLES 0x9110 -typedef void(APIENTRYP PFNGLDRAWELEMENTSBASEVERTEXPROC)(GLenum mode, GLsizei count, GLenum type, const void* indices, - GLint basevertex); -typedef void(APIENTRYP PFNGLDRAWRANGEELEMENTSBASEVERTEXPROC)(GLenum mode, GLuint start, GLuint end, GLsizei count, - GLenum type, const void* indices, GLint basevertex); -typedef void(APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXPROC)(GLenum mode, GLsizei count, GLenum type, - const void* indices, GLsizei instancecount, - GLint basevertex); -typedef void(APIENTRYP PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC)(GLenum mode, const GLsizei* count, GLenum type, - const void* const* indices, GLsizei drawcount, - const GLint* basevertex); -typedef void(APIENTRYP PFNGLPROVOKINGVERTEXPROC)(GLenum mode); -typedef GLsync(APIENTRYP PFNGLFENCESYNCPROC)(GLenum condition, GLbitfield flags); -typedef GLboolean(APIENTRYP PFNGLISSYNCPROC)(GLsync sync); -typedef void(APIENTRYP PFNGLDELETESYNCPROC)(GLsync sync); -typedef GLenum(APIENTRYP PFNGLCLIENTWAITSYNCPROC)(GLsync sync, GLbitfield flags, GLuint64 timeout); -typedef void(APIENTRYP PFNGLWAITSYNCPROC)(GLsync sync, GLbitfield flags, GLuint64 timeout); -typedef void(APIENTRYP PFNGLGETINTEGER64VPROC)(GLenum pname, GLint64* data); -typedef void(APIENTRYP PFNGLGETSYNCIVPROC)(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values); -typedef void(APIENTRYP PFNGLGETINTEGER64I_VPROC)(GLenum target, GLuint index, GLint64* data); -typedef void(APIENTRYP PFNGLGETBUFFERPARAMETERI64VPROC)(GLenum target, GLenum pname, GLint64* params); -typedef void(APIENTRYP PFNGLFRAMEBUFFERTEXTUREPROC)(GLenum target, GLenum attachment, GLuint texture, GLint level); -typedef void(APIENTRYP PFNGLTEXIMAGE2DMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height, GLboolean fixedsamplelocations); -typedef void(APIENTRYP PFNGLTEXIMAGE3DMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height, GLsizei depth, - GLboolean fixedsamplelocations); -typedef void(APIENTRYP PFNGLGETMULTISAMPLEFVPROC)(GLenum pname, GLuint index, GLfloat* val); -typedef void(APIENTRYP PFNGLSAMPLEMASKIPROC)(GLuint maskNumber, GLbitfield mask); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glDrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, - GLint basevertex); -GLAPI void APIENTRY glDrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, - const void* indices, GLint basevertex); -GLAPI void APIENTRY glDrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, - GLsizei instancecount, GLint basevertex); -GLAPI void APIENTRY glMultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, - const void* const* indices, GLsizei drawcount, - const GLint* basevertex); -GLAPI void APIENTRY glProvokingVertex(GLenum mode); -GLAPI GLsync APIENTRY glFenceSync(GLenum condition, GLbitfield flags); -GLAPI GLboolean APIENTRY glIsSync(GLsync sync); -GLAPI void APIENTRY glDeleteSync(GLsync sync); -GLAPI GLenum APIENTRY glClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout); -GLAPI void APIENTRY glWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout); -GLAPI void APIENTRY glGetInteger64v(GLenum pname, GLint64* data); -GLAPI void APIENTRY glGetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values); -GLAPI void APIENTRY glGetInteger64i_v(GLenum target, GLuint index, GLint64* data); -GLAPI void APIENTRY glGetBufferParameteri64v(GLenum target, GLenum pname, GLint64* params); -GLAPI void APIENTRY glFramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level); -GLAPI void APIENTRY glTexImage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, - GLsizei height, GLboolean fixedsamplelocations); -GLAPI void APIENTRY glTexImage3DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, - GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); -GLAPI void APIENTRY glGetMultisamplefv(GLenum pname, GLuint index, GLfloat* val); -GLAPI void APIENTRY glSampleMaski(GLuint maskNumber, GLbitfield mask); -#endif -#endif /* GL_VERSION_3_2 */ - -#ifndef GL_VERSION_3_3 -#define GL_VERSION_3_3 1 -#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE -#define GL_SRC1_COLOR 0x88F9 -#define GL_ONE_MINUS_SRC1_COLOR 0x88FA -#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB -#define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS 0x88FC -#define GL_ANY_SAMPLES_PASSED 0x8C2F -#define GL_SAMPLER_BINDING 0x8919 -#define GL_RGB10_A2UI 0x906F -#define GL_TEXTURE_SWIZZLE_R 0x8E42 -#define GL_TEXTURE_SWIZZLE_G 0x8E43 -#define GL_TEXTURE_SWIZZLE_B 0x8E44 -#define GL_TEXTURE_SWIZZLE_A 0x8E45 -#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46 -#define GL_TIME_ELAPSED 0x88BF -#define GL_TIMESTAMP 0x8E28 -#define GL_INT_2_10_10_10_REV 0x8D9F -typedef void(APIENTRYP PFNGLBINDFRAGDATALOCATIONINDEXEDPROC)(GLuint program, GLuint colorNumber, GLuint index, - const GLchar* name); -typedef GLint(APIENTRYP PFNGLGETFRAGDATAINDEXPROC)(GLuint program, const GLchar* name); -typedef void(APIENTRYP PFNGLGENSAMPLERSPROC)(GLsizei count, GLuint* samplers); -typedef void(APIENTRYP PFNGLDELETESAMPLERSPROC)(GLsizei count, const GLuint* samplers); -typedef GLboolean(APIENTRYP PFNGLISSAMPLERPROC)(GLuint sampler); -typedef void(APIENTRYP PFNGLBINDSAMPLERPROC)(GLuint unit, GLuint sampler); -typedef void(APIENTRYP PFNGLSAMPLERPARAMETERIPROC)(GLuint sampler, GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLSAMPLERPARAMETERIVPROC)(GLuint sampler, GLenum pname, const GLint* param); -typedef void(APIENTRYP PFNGLSAMPLERPARAMETERFPROC)(GLuint sampler, GLenum pname, GLfloat param); -typedef void(APIENTRYP PFNGLSAMPLERPARAMETERFVPROC)(GLuint sampler, GLenum pname, const GLfloat* param); -typedef void(APIENTRYP PFNGLSAMPLERPARAMETERIIVPROC)(GLuint sampler, GLenum pname, const GLint* param); -typedef void(APIENTRYP PFNGLSAMPLERPARAMETERIUIVPROC)(GLuint sampler, GLenum pname, const GLuint* param); -typedef void(APIENTRYP PFNGLGETSAMPLERPARAMETERIVPROC)(GLuint sampler, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETSAMPLERPARAMETERIIVPROC)(GLuint sampler, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETSAMPLERPARAMETERFVPROC)(GLuint sampler, GLenum pname, GLfloat* params); -typedef void(APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVPROC)(GLuint sampler, GLenum pname, GLuint* params); -typedef void(APIENTRYP PFNGLQUERYCOUNTERPROC)(GLuint id, GLenum target); -typedef void(APIENTRYP PFNGLGETQUERYOBJECTI64VPROC)(GLuint id, GLenum pname, GLint64* params); -typedef void(APIENTRYP PFNGLGETQUERYOBJECTUI64VPROC)(GLuint id, GLenum pname, GLuint64* params); -typedef void(APIENTRYP PFNGLVERTEXATTRIBDIVISORPROC)(GLuint index, GLuint divisor); -typedef void(APIENTRYP PFNGLVERTEXATTRIBP1UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value); -typedef void(APIENTRYP PFNGLVERTEXATTRIBP1UIVPROC)(GLuint index, GLenum type, GLboolean normalized, - const GLuint* value); -typedef void(APIENTRYP PFNGLVERTEXATTRIBP2UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value); -typedef void(APIENTRYP PFNGLVERTEXATTRIBP2UIVPROC)(GLuint index, GLenum type, GLboolean normalized, - const GLuint* value); -typedef void(APIENTRYP PFNGLVERTEXATTRIBP3UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value); -typedef void(APIENTRYP PFNGLVERTEXATTRIBP3UIVPROC)(GLuint index, GLenum type, GLboolean normalized, - const GLuint* value); -typedef void(APIENTRYP PFNGLVERTEXATTRIBP4UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value); -typedef void(APIENTRYP PFNGLVERTEXATTRIBP4UIVPROC)(GLuint index, GLenum type, GLboolean normalized, - const GLuint* value); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBindFragDataLocationIndexed(GLuint program, GLuint colorNumber, GLuint index, const GLchar* name); -GLAPI GLint APIENTRY glGetFragDataIndex(GLuint program, const GLchar* name); -GLAPI void APIENTRY glGenSamplers(GLsizei count, GLuint* samplers); -GLAPI void APIENTRY glDeleteSamplers(GLsizei count, const GLuint* samplers); -GLAPI GLboolean APIENTRY glIsSampler(GLuint sampler); -GLAPI void APIENTRY glBindSampler(GLuint unit, GLuint sampler); -GLAPI void APIENTRY glSamplerParameteri(GLuint sampler, GLenum pname, GLint param); -GLAPI void APIENTRY glSamplerParameteriv(GLuint sampler, GLenum pname, const GLint* param); -GLAPI void APIENTRY glSamplerParameterf(GLuint sampler, GLenum pname, GLfloat param); -GLAPI void APIENTRY glSamplerParameterfv(GLuint sampler, GLenum pname, const GLfloat* param); -GLAPI void APIENTRY glSamplerParameterIiv(GLuint sampler, GLenum pname, const GLint* param); -GLAPI void APIENTRY glSamplerParameterIuiv(GLuint sampler, GLenum pname, const GLuint* param); -GLAPI void APIENTRY glGetSamplerParameteriv(GLuint sampler, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetSamplerParameterIiv(GLuint sampler, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetSamplerParameterfv(GLuint sampler, GLenum pname, GLfloat* params); -GLAPI void APIENTRY glGetSamplerParameterIuiv(GLuint sampler, GLenum pname, GLuint* params); -GLAPI void APIENTRY glQueryCounter(GLuint id, GLenum target); -GLAPI void APIENTRY glGetQueryObjecti64v(GLuint id, GLenum pname, GLint64* params); -GLAPI void APIENTRY glGetQueryObjectui64v(GLuint id, GLenum pname, GLuint64* params); -GLAPI void APIENTRY glVertexAttribDivisor(GLuint index, GLuint divisor); -GLAPI void APIENTRY glVertexAttribP1ui(GLuint index, GLenum type, GLboolean normalized, GLuint value); -GLAPI void APIENTRY glVertexAttribP1uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value); -GLAPI void APIENTRY glVertexAttribP2ui(GLuint index, GLenum type, GLboolean normalized, GLuint value); -GLAPI void APIENTRY glVertexAttribP2uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value); -GLAPI void APIENTRY glVertexAttribP3ui(GLuint index, GLenum type, GLboolean normalized, GLuint value); -GLAPI void APIENTRY glVertexAttribP3uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value); -GLAPI void APIENTRY glVertexAttribP4ui(GLuint index, GLenum type, GLboolean normalized, GLuint value); -GLAPI void APIENTRY glVertexAttribP4uiv(GLuint index, GLenum type, GLboolean normalized, const GLuint* value); -#endif -#endif /* GL_VERSION_3_3 */ - -#ifndef GL_VERSION_4_0 -#define GL_VERSION_4_0 1 -#define GL_SAMPLE_SHADING 0x8C36 -#define GL_MIN_SAMPLE_SHADING_VALUE 0x8C37 -#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5E -#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET 0x8E5F -#define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009 -#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A -#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY 0x900B -#define GL_SAMPLER_CUBE_MAP_ARRAY 0x900C -#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW 0x900D -#define GL_INT_SAMPLER_CUBE_MAP_ARRAY 0x900E -#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY 0x900F -#define GL_DRAW_INDIRECT_BUFFER 0x8F3F -#define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43 -#define GL_GEOMETRY_SHADER_INVOCATIONS 0x887F -#define GL_MAX_GEOMETRY_SHADER_INVOCATIONS 0x8E5A -#define GL_MIN_FRAGMENT_INTERPOLATION_OFFSET 0x8E5B -#define GL_MAX_FRAGMENT_INTERPOLATION_OFFSET 0x8E5C -#define GL_FRAGMENT_INTERPOLATION_OFFSET_BITS 0x8E5D -#define GL_MAX_VERTEX_STREAMS 0x8E71 -#define GL_DOUBLE_VEC2 0x8FFC -#define GL_DOUBLE_VEC3 0x8FFD -#define GL_DOUBLE_VEC4 0x8FFE -#define GL_DOUBLE_MAT2 0x8F46 -#define GL_DOUBLE_MAT3 0x8F47 -#define GL_DOUBLE_MAT4 0x8F48 -#define GL_DOUBLE_MAT2x3 0x8F49 -#define GL_DOUBLE_MAT2x4 0x8F4A -#define GL_DOUBLE_MAT3x2 0x8F4B -#define GL_DOUBLE_MAT3x4 0x8F4C -#define GL_DOUBLE_MAT4x2 0x8F4D -#define GL_DOUBLE_MAT4x3 0x8F4E -#define GL_ACTIVE_SUBROUTINES 0x8DE5 -#define GL_ACTIVE_SUBROUTINE_UNIFORMS 0x8DE6 -#define GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS 0x8E47 -#define GL_ACTIVE_SUBROUTINE_MAX_LENGTH 0x8E48 -#define GL_ACTIVE_SUBROUTINE_UNIFORM_MAX_LENGTH 0x8E49 -#define GL_MAX_SUBROUTINES 0x8DE7 -#define GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS 0x8DE8 -#define GL_NUM_COMPATIBLE_SUBROUTINES 0x8E4A -#define GL_COMPATIBLE_SUBROUTINES 0x8E4B -#define GL_PATCHES 0x000E -#define GL_PATCH_VERTICES 0x8E72 -#define GL_PATCH_DEFAULT_INNER_LEVEL 0x8E73 -#define GL_PATCH_DEFAULT_OUTER_LEVEL 0x8E74 -#define GL_TESS_CONTROL_OUTPUT_VERTICES 0x8E75 -#define GL_TESS_GEN_MODE 0x8E76 -#define GL_TESS_GEN_SPACING 0x8E77 -#define GL_TESS_GEN_VERTEX_ORDER 0x8E78 -#define GL_TESS_GEN_POINT_MODE 0x8E79 -#define GL_ISOLINES 0x8E7A -#define GL_FRACTIONAL_ODD 0x8E7B -#define GL_FRACTIONAL_EVEN 0x8E7C -#define GL_MAX_PATCH_VERTICES 0x8E7D -#define GL_MAX_TESS_GEN_LEVEL 0x8E7E -#define GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E7F -#define GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E80 -#define GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS 0x8E81 -#define GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS 0x8E82 -#define GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS 0x8E83 -#define GL_MAX_TESS_PATCH_COMPONENTS 0x8E84 -#define GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS 0x8E85 -#define GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS 0x8E86 -#define GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS 0x8E89 -#define GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS 0x8E8A -#define GL_MAX_TESS_CONTROL_INPUT_COMPONENTS 0x886C -#define GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS 0x886D -#define GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS 0x8E1E -#define GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS 0x8E1F -#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER 0x84F0 -#define GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER 0x84F1 -#define GL_TESS_EVALUATION_SHADER 0x8E87 -#define GL_TESS_CONTROL_SHADER 0x8E88 -#define GL_TRANSFORM_FEEDBACK 0x8E22 -#define GL_TRANSFORM_FEEDBACK_BUFFER_PAUSED 0x8E23 -#define GL_TRANSFORM_FEEDBACK_BUFFER_ACTIVE 0x8E24 -#define GL_TRANSFORM_FEEDBACK_BINDING 0x8E25 -#define GL_MAX_TRANSFORM_FEEDBACK_BUFFERS 0x8E70 -typedef void(APIENTRYP PFNGLMINSAMPLESHADINGPROC)(GLfloat value); -typedef void(APIENTRYP PFNGLBLENDEQUATIONIPROC)(GLuint buf, GLenum mode); -typedef void(APIENTRYP PFNGLBLENDEQUATIONSEPARATEIPROC)(GLuint buf, GLenum modeRGB, GLenum modeAlpha); -typedef void(APIENTRYP PFNGLBLENDFUNCIPROC)(GLuint buf, GLenum src, GLenum dst); -typedef void(APIENTRYP PFNGLBLENDFUNCSEPARATEIPROC)(GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, - GLenum dstAlpha); -typedef void(APIENTRYP PFNGLDRAWARRAYSINDIRECTPROC)(GLenum mode, const void* indirect); -typedef void(APIENTRYP PFNGLDRAWELEMENTSINDIRECTPROC)(GLenum mode, GLenum type, const void* indirect); -typedef void(APIENTRYP PFNGLUNIFORM1DPROC)(GLint location, GLdouble x); -typedef void(APIENTRYP PFNGLUNIFORM2DPROC)(GLint location, GLdouble x, GLdouble y); -typedef void(APIENTRYP PFNGLUNIFORM3DPROC)(GLint location, GLdouble x, GLdouble y, GLdouble z); -typedef void(APIENTRYP PFNGLUNIFORM4DPROC)(GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); -typedef void(APIENTRYP PFNGLUNIFORM1DVPROC)(GLint location, GLsizei count, const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORM2DVPROC)(GLint location, GLsizei count, const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORM3DVPROC)(GLint location, GLsizei count, const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORM4DVPROC)(GLint location, GLsizei count, const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX2DVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX3DVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX4DVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX2X3DVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX2X4DVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX3X2DVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX3X4DVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX4X2DVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -typedef void(APIENTRYP PFNGLUNIFORMMATRIX4X3DVPROC)(GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -typedef void(APIENTRYP PFNGLGETUNIFORMDVPROC)(GLuint program, GLint location, GLdouble* params); -typedef GLint(APIENTRYP PFNGLGETSUBROUTINEUNIFORMLOCATIONPROC)(GLuint program, GLenum shadertype, const GLchar* name); -typedef GLuint(APIENTRYP PFNGLGETSUBROUTINEINDEXPROC)(GLuint program, GLenum shadertype, const GLchar* name); -typedef void(APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMIVPROC)(GLuint program, GLenum shadertype, GLuint index, - GLenum pname, GLint* values); -typedef void(APIENTRYP PFNGLGETACTIVESUBROUTINEUNIFORMNAMEPROC)(GLuint program, GLenum shadertype, GLuint index, - GLsizei bufsize, GLsizei* length, GLchar* name); -typedef void(APIENTRYP PFNGLGETACTIVESUBROUTINENAMEPROC)(GLuint program, GLenum shadertype, GLuint index, - GLsizei bufsize, GLsizei* length, GLchar* name); -typedef void(APIENTRYP PFNGLUNIFORMSUBROUTINESUIVPROC)(GLenum shadertype, GLsizei count, const GLuint* indices); -typedef void(APIENTRYP PFNGLGETUNIFORMSUBROUTINEUIVPROC)(GLenum shadertype, GLint location, GLuint* params); -typedef void(APIENTRYP PFNGLGETPROGRAMSTAGEIVPROC)(GLuint program, GLenum shadertype, GLenum pname, GLint* values); -typedef void(APIENTRYP PFNGLPATCHPARAMETERIPROC)(GLenum pname, GLint value); -typedef void(APIENTRYP PFNGLPATCHPARAMETERFVPROC)(GLenum pname, const GLfloat* values); -typedef void(APIENTRYP PFNGLBINDTRANSFORMFEEDBACKPROC)(GLenum target, GLuint id); -typedef void(APIENTRYP PFNGLDELETETRANSFORMFEEDBACKSPROC)(GLsizei n, const GLuint* ids); -typedef void(APIENTRYP PFNGLGENTRANSFORMFEEDBACKSPROC)(GLsizei n, GLuint* ids); -typedef GLboolean(APIENTRYP PFNGLISTRANSFORMFEEDBACKPROC)(GLuint id); -typedef void(APIENTRYP PFNGLPAUSETRANSFORMFEEDBACKPROC)(void); -typedef void(APIENTRYP PFNGLRESUMETRANSFORMFEEDBACKPROC)(void); -typedef void(APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKPROC)(GLenum mode, GLuint id); -typedef void(APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC)(GLenum mode, GLuint id, GLuint stream); -typedef void(APIENTRYP PFNGLBEGINQUERYINDEXEDPROC)(GLenum target, GLuint index, GLuint id); -typedef void(APIENTRYP PFNGLENDQUERYINDEXEDPROC)(GLenum target, GLuint index); -typedef void(APIENTRYP PFNGLGETQUERYINDEXEDIVPROC)(GLenum target, GLuint index, GLenum pname, GLint* params); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glMinSampleShading(GLfloat value); -GLAPI void APIENTRY glBlendEquationi(GLuint buf, GLenum mode); -GLAPI void APIENTRY glBlendEquationSeparatei(GLuint buf, GLenum modeRGB, GLenum modeAlpha); -GLAPI void APIENTRY glBlendFunci(GLuint buf, GLenum src, GLenum dst); -GLAPI void APIENTRY glBlendFuncSeparatei(GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); -GLAPI void APIENTRY glDrawArraysIndirect(GLenum mode, const void* indirect); -GLAPI void APIENTRY glDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect); -GLAPI void APIENTRY glUniform1d(GLint location, GLdouble x); -GLAPI void APIENTRY glUniform2d(GLint location, GLdouble x, GLdouble y); -GLAPI void APIENTRY glUniform3d(GLint location, GLdouble x, GLdouble y, GLdouble z); -GLAPI void APIENTRY glUniform4d(GLint location, GLdouble x, GLdouble y, GLdouble z, GLdouble w); -GLAPI void APIENTRY glUniform1dv(GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glUniform2dv(GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glUniform3dv(GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glUniform4dv(GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glUniformMatrix2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); -GLAPI void APIENTRY glUniformMatrix3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); -GLAPI void APIENTRY glUniformMatrix4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); -GLAPI void APIENTRY glUniformMatrix2x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); -GLAPI void APIENTRY glUniformMatrix2x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); -GLAPI void APIENTRY glUniformMatrix3x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); -GLAPI void APIENTRY glUniformMatrix3x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); -GLAPI void APIENTRY glUniformMatrix4x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); -GLAPI void APIENTRY glUniformMatrix4x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); -GLAPI void APIENTRY glGetUniformdv(GLuint program, GLint location, GLdouble* params); -GLAPI GLint APIENTRY glGetSubroutineUniformLocation(GLuint program, GLenum shadertype, const GLchar* name); -GLAPI GLuint APIENTRY glGetSubroutineIndex(GLuint program, GLenum shadertype, const GLchar* name); -GLAPI void APIENTRY glGetActiveSubroutineUniformiv(GLuint program, GLenum shadertype, GLuint index, GLenum pname, - GLint* values); -GLAPI void APIENTRY glGetActiveSubroutineUniformName(GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, - GLsizei* length, GLchar* name); -GLAPI void APIENTRY glGetActiveSubroutineName(GLuint program, GLenum shadertype, GLuint index, GLsizei bufsize, - GLsizei* length, GLchar* name); -GLAPI void APIENTRY glUniformSubroutinesuiv(GLenum shadertype, GLsizei count, const GLuint* indices); -GLAPI void APIENTRY glGetUniformSubroutineuiv(GLenum shadertype, GLint location, GLuint* params); -GLAPI void APIENTRY glGetProgramStageiv(GLuint program, GLenum shadertype, GLenum pname, GLint* values); -GLAPI void APIENTRY glPatchParameteri(GLenum pname, GLint value); -GLAPI void APIENTRY glPatchParameterfv(GLenum pname, const GLfloat* values); -GLAPI void APIENTRY glBindTransformFeedback(GLenum target, GLuint id); -GLAPI void APIENTRY glDeleteTransformFeedbacks(GLsizei n, const GLuint* ids); -GLAPI void APIENTRY glGenTransformFeedbacks(GLsizei n, GLuint* ids); -GLAPI GLboolean APIENTRY glIsTransformFeedback(GLuint id); -GLAPI void APIENTRY glPauseTransformFeedback(void); -GLAPI void APIENTRY glResumeTransformFeedback(void); -GLAPI void APIENTRY glDrawTransformFeedback(GLenum mode, GLuint id); -GLAPI void APIENTRY glDrawTransformFeedbackStream(GLenum mode, GLuint id, GLuint stream); -GLAPI void APIENTRY glBeginQueryIndexed(GLenum target, GLuint index, GLuint id); -GLAPI void APIENTRY glEndQueryIndexed(GLenum target, GLuint index); -GLAPI void APIENTRY glGetQueryIndexediv(GLenum target, GLuint index, GLenum pname, GLint* params); -#endif -#endif /* GL_VERSION_4_0 */ - -#ifndef GL_VERSION_4_1 -#define GL_VERSION_4_1 1 -#define GL_FIXED 0x140C -#define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A -#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B -#define GL_LOW_FLOAT 0x8DF0 -#define GL_MEDIUM_FLOAT 0x8DF1 -#define GL_HIGH_FLOAT 0x8DF2 -#define GL_LOW_INT 0x8DF3 -#define GL_MEDIUM_INT 0x8DF4 -#define GL_HIGH_INT 0x8DF5 -#define GL_SHADER_COMPILER 0x8DFA -#define GL_SHADER_BINARY_FORMATS 0x8DF8 -#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9 -#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB -#define GL_MAX_VARYING_VECTORS 0x8DFC -#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD -#define GL_RGB565 0x8D62 -#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 -#define GL_PROGRAM_BINARY_LENGTH 0x8741 -#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE -#define GL_PROGRAM_BINARY_FORMATS 0x87FF -#define GL_VERTEX_SHADER_BIT 0x00000001 -#define GL_FRAGMENT_SHADER_BIT 0x00000002 -#define GL_GEOMETRY_SHADER_BIT 0x00000004 -#define GL_TESS_CONTROL_SHADER_BIT 0x00000008 -#define GL_TESS_EVALUATION_SHADER_BIT 0x00000010 -#define GL_ALL_SHADER_BITS 0xFFFFFFFF -#define GL_PROGRAM_SEPARABLE 0x8258 -#define GL_ACTIVE_PROGRAM 0x8259 -#define GL_PROGRAM_PIPELINE_BINDING 0x825A -#define GL_MAX_VIEWPORTS 0x825B -#define GL_VIEWPORT_SUBPIXEL_BITS 0x825C -#define GL_VIEWPORT_BOUNDS_RANGE 0x825D -#define GL_LAYER_PROVOKING_VERTEX 0x825E -#define GL_VIEWPORT_INDEX_PROVOKING_VERTEX 0x825F -#define GL_UNDEFINED_VERTEX 0x8260 -typedef void(APIENTRYP PFNGLRELEASESHADERCOMPILERPROC)(void); -typedef void(APIENTRYP PFNGLSHADERBINARYPROC)(GLsizei count, const GLuint* shaders, GLenum binaryformat, - const void* binary, GLsizei length); -typedef void(APIENTRYP PFNGLGETSHADERPRECISIONFORMATPROC)(GLenum shadertype, GLenum precisiontype, GLint* range, - GLint* precision); -typedef void(APIENTRYP PFNGLDEPTHRANGEFPROC)(GLfloat n, GLfloat f); -typedef void(APIENTRYP PFNGLCLEARDEPTHFPROC)(GLfloat d); -typedef void(APIENTRYP PFNGLGETPROGRAMBINARYPROC)(GLuint program, GLsizei bufSize, GLsizei* length, - GLenum* binaryFormat, void* binary); -typedef void(APIENTRYP PFNGLPROGRAMBINARYPROC)(GLuint program, GLenum binaryFormat, const void* binary, GLsizei length); -typedef void(APIENTRYP PFNGLPROGRAMPARAMETERIPROC)(GLuint program, GLenum pname, GLint value); -typedef void(APIENTRYP PFNGLUSEPROGRAMSTAGESPROC)(GLuint pipeline, GLbitfield stages, GLuint program); -typedef void(APIENTRYP PFNGLACTIVESHADERPROGRAMPROC)(GLuint pipeline, GLuint program); -typedef GLuint(APIENTRYP PFNGLCREATESHADERPROGRAMVPROC)(GLenum type, GLsizei count, const GLchar* const* strings); -typedef void(APIENTRYP PFNGLBINDPROGRAMPIPELINEPROC)(GLuint pipeline); -typedef void(APIENTRYP PFNGLDELETEPROGRAMPIPELINESPROC)(GLsizei n, const GLuint* pipelines); -typedef void(APIENTRYP PFNGLGENPROGRAMPIPELINESPROC)(GLsizei n, GLuint* pipelines); -typedef GLboolean(APIENTRYP PFNGLISPROGRAMPIPELINEPROC)(GLuint pipeline); -typedef void(APIENTRYP PFNGLGETPROGRAMPIPELINEIVPROC)(GLuint pipeline, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1IPROC)(GLuint program, GLint location, GLint v0); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1IVPROC)(GLuint program, GLint location, GLsizei count, const GLint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1FPROC)(GLuint program, GLint location, GLfloat v0); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1FVPROC)(GLuint program, GLint location, GLsizei count, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1DPROC)(GLuint program, GLint location, GLdouble v0); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1DVPROC)(GLuint program, GLint location, GLsizei count, - const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1UIPROC)(GLuint program, GLint location, GLuint v0); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1UIVPROC)(GLuint program, GLint location, GLsizei count, const GLuint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2IPROC)(GLuint program, GLint location, GLint v0, GLint v1); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2IVPROC)(GLuint program, GLint location, GLsizei count, const GLint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2FPROC)(GLuint program, GLint location, GLfloat v0, GLfloat v1); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2FVPROC)(GLuint program, GLint location, GLsizei count, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2DPROC)(GLuint program, GLint location, GLdouble v0, GLdouble v1); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2DVPROC)(GLuint program, GLint location, GLsizei count, - const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2UIPROC)(GLuint program, GLint location, GLuint v0, GLuint v1); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2UIVPROC)(GLuint program, GLint location, GLsizei count, const GLuint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3IPROC)(GLuint program, GLint location, GLint v0, GLint v1, GLint v2); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3IVPROC)(GLuint program, GLint location, GLsizei count, const GLint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3FPROC)(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3FVPROC)(GLuint program, GLint location, GLsizei count, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3DPROC)(GLuint program, GLint location, GLdouble v0, GLdouble v1, - GLdouble v2); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3DVPROC)(GLuint program, GLint location, GLsizei count, - const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3UIPROC)(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3UIVPROC)(GLuint program, GLint location, GLsizei count, const GLuint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4IPROC)(GLuint program, GLint location, GLint v0, GLint v1, GLint v2, - GLint v3); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4IVPROC)(GLuint program, GLint location, GLsizei count, const GLint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4FPROC)(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, - GLfloat v3); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4FVPROC)(GLuint program, GLint location, GLsizei count, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4DPROC)(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, - GLdouble v3); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4DVPROC)(GLuint program, GLint location, GLsizei count, - const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4UIPROC)(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, - GLuint v3); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4UIVPROC)(GLuint program, GLint location, GLsizei count, const GLuint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLVALIDATEPROGRAMPIPELINEPROC)(GLuint pipeline); -typedef void(APIENTRYP PFNGLGETPROGRAMPIPELINEINFOLOGPROC)(GLuint pipeline, GLsizei bufSize, GLsizei* length, - GLchar* infoLog); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL1DPROC)(GLuint index, GLdouble x); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL2DPROC)(GLuint index, GLdouble x, GLdouble y); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL3DPROC)(GLuint index, GLdouble x, GLdouble y, GLdouble z); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL4DPROC)(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL1DVPROC)(GLuint index, const GLdouble* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL2DVPROC)(GLuint index, const GLdouble* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL3DVPROC)(GLuint index, const GLdouble* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL4DVPROC)(GLuint index, const GLdouble* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBLPOINTERPROC)(GLuint index, GLint size, GLenum type, GLsizei stride, - const void* pointer); -typedef void(APIENTRYP PFNGLGETVERTEXATTRIBLDVPROC)(GLuint index, GLenum pname, GLdouble* params); -typedef void(APIENTRYP PFNGLVIEWPORTARRAYVPROC)(GLuint first, GLsizei count, const GLfloat* v); -typedef void(APIENTRYP PFNGLVIEWPORTINDEXEDFPROC)(GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); -typedef void(APIENTRYP PFNGLVIEWPORTINDEXEDFVPROC)(GLuint index, const GLfloat* v); -typedef void(APIENTRYP PFNGLSCISSORARRAYVPROC)(GLuint first, GLsizei count, const GLint* v); -typedef void(APIENTRYP PFNGLSCISSORINDEXEDPROC)(GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); -typedef void(APIENTRYP PFNGLSCISSORINDEXEDVPROC)(GLuint index, const GLint* v); -typedef void(APIENTRYP PFNGLDEPTHRANGEARRAYVPROC)(GLuint first, GLsizei count, const GLdouble* v); -typedef void(APIENTRYP PFNGLDEPTHRANGEINDEXEDPROC)(GLuint index, GLdouble n, GLdouble f); -typedef void(APIENTRYP PFNGLGETFLOATI_VPROC)(GLenum target, GLuint index, GLfloat* data); -typedef void(APIENTRYP PFNGLGETDOUBLEI_VPROC)(GLenum target, GLuint index, GLdouble* data); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glReleaseShaderCompiler(void); -GLAPI void APIENTRY glShaderBinary(GLsizei count, const GLuint* shaders, GLenum binaryformat, const void* binary, - GLsizei length); -GLAPI void APIENTRY glGetShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision); -GLAPI void APIENTRY glDepthRangef(GLfloat n, GLfloat f); -GLAPI void APIENTRY glClearDepthf(GLfloat d); -GLAPI void APIENTRY glGetProgramBinary(GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, - void* binary); -GLAPI void APIENTRY glProgramBinary(GLuint program, GLenum binaryFormat, const void* binary, GLsizei length); -GLAPI void APIENTRY glProgramParameteri(GLuint program, GLenum pname, GLint value); -GLAPI void APIENTRY glUseProgramStages(GLuint pipeline, GLbitfield stages, GLuint program); -GLAPI void APIENTRY glActiveShaderProgram(GLuint pipeline, GLuint program); -GLAPI GLuint APIENTRY glCreateShaderProgramv(GLenum type, GLsizei count, const GLchar* const* strings); -GLAPI void APIENTRY glBindProgramPipeline(GLuint pipeline); -GLAPI void APIENTRY glDeleteProgramPipelines(GLsizei n, const GLuint* pipelines); -GLAPI void APIENTRY glGenProgramPipelines(GLsizei n, GLuint* pipelines); -GLAPI GLboolean APIENTRY glIsProgramPipeline(GLuint pipeline); -GLAPI void APIENTRY glGetProgramPipelineiv(GLuint pipeline, GLenum pname, GLint* params); -GLAPI void APIENTRY glProgramUniform1i(GLuint program, GLint location, GLint v0); -GLAPI void APIENTRY glProgramUniform1iv(GLuint program, GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glProgramUniform1f(GLuint program, GLint location, GLfloat v0); -GLAPI void APIENTRY glProgramUniform1fv(GLuint program, GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glProgramUniform1d(GLuint program, GLint location, GLdouble v0); -GLAPI void APIENTRY glProgramUniform1dv(GLuint program, GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glProgramUniform1ui(GLuint program, GLint location, GLuint v0); -GLAPI void APIENTRY glProgramUniform1uiv(GLuint program, GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glProgramUniform2i(GLuint program, GLint location, GLint v0, GLint v1); -GLAPI void APIENTRY glProgramUniform2iv(GLuint program, GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glProgramUniform2f(GLuint program, GLint location, GLfloat v0, GLfloat v1); -GLAPI void APIENTRY glProgramUniform2fv(GLuint program, GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glProgramUniform2d(GLuint program, GLint location, GLdouble v0, GLdouble v1); -GLAPI void APIENTRY glProgramUniform2dv(GLuint program, GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glProgramUniform2ui(GLuint program, GLint location, GLuint v0, GLuint v1); -GLAPI void APIENTRY glProgramUniform2uiv(GLuint program, GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glProgramUniform3i(GLuint program, GLint location, GLint v0, GLint v1, GLint v2); -GLAPI void APIENTRY glProgramUniform3iv(GLuint program, GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glProgramUniform3f(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); -GLAPI void APIENTRY glProgramUniform3fv(GLuint program, GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glProgramUniform3d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2); -GLAPI void APIENTRY glProgramUniform3dv(GLuint program, GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glProgramUniform3ui(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); -GLAPI void APIENTRY glProgramUniform3uiv(GLuint program, GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glProgramUniform4i(GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); -GLAPI void APIENTRY glProgramUniform4iv(GLuint program, GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glProgramUniform4f(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); -GLAPI void APIENTRY glProgramUniform4fv(GLuint program, GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glProgramUniform4d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, - GLdouble v3); -GLAPI void APIENTRY glProgramUniform4dv(GLuint program, GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glProgramUniform4ui(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); -GLAPI void APIENTRY glProgramUniform4uiv(GLuint program, GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glProgramUniformMatrix2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix2x3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix3x2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix2x4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix4x2fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix3x4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix4x3fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix2x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix3x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix2x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix4x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix3x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix4x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glValidateProgramPipeline(GLuint pipeline); -GLAPI void APIENTRY glGetProgramPipelineInfoLog(GLuint pipeline, GLsizei bufSize, GLsizei* length, GLchar* infoLog); -GLAPI void APIENTRY glVertexAttribL1d(GLuint index, GLdouble x); -GLAPI void APIENTRY glVertexAttribL2d(GLuint index, GLdouble x, GLdouble y); -GLAPI void APIENTRY glVertexAttribL3d(GLuint index, GLdouble x, GLdouble y, GLdouble z); -GLAPI void APIENTRY glVertexAttribL4d(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); -GLAPI void APIENTRY glVertexAttribL1dv(GLuint index, const GLdouble* v); -GLAPI void APIENTRY glVertexAttribL2dv(GLuint index, const GLdouble* v); -GLAPI void APIENTRY glVertexAttribL3dv(GLuint index, const GLdouble* v); -GLAPI void APIENTRY glVertexAttribL4dv(GLuint index, const GLdouble* v); -GLAPI void APIENTRY glVertexAttribLPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer); -GLAPI void APIENTRY glGetVertexAttribLdv(GLuint index, GLenum pname, GLdouble* params); -GLAPI void APIENTRY glViewportArrayv(GLuint first, GLsizei count, const GLfloat* v); -GLAPI void APIENTRY glViewportIndexedf(GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h); -GLAPI void APIENTRY glViewportIndexedfv(GLuint index, const GLfloat* v); -GLAPI void APIENTRY glScissorArrayv(GLuint first, GLsizei count, const GLint* v); -GLAPI void APIENTRY glScissorIndexed(GLuint index, GLint left, GLint bottom, GLsizei width, GLsizei height); -GLAPI void APIENTRY glScissorIndexedv(GLuint index, const GLint* v); -GLAPI void APIENTRY glDepthRangeArrayv(GLuint first, GLsizei count, const GLdouble* v); -GLAPI void APIENTRY glDepthRangeIndexed(GLuint index, GLdouble n, GLdouble f); -GLAPI void APIENTRY glGetFloati_v(GLenum target, GLuint index, GLfloat* data); -GLAPI void APIENTRY glGetDoublei_v(GLenum target, GLuint index, GLdouble* data); -#endif -#endif /* GL_VERSION_4_1 */ - -#ifndef GL_VERSION_4_2 -#define GL_VERSION_4_2 1 -#define GL_COPY_READ_BUFFER_BINDING 0x8F36 -#define GL_COPY_WRITE_BUFFER_BINDING 0x8F37 -#define GL_TRANSFORM_FEEDBACK_ACTIVE 0x8E24 -#define GL_TRANSFORM_FEEDBACK_PAUSED 0x8E23 -#define GL_UNPACK_COMPRESSED_BLOCK_WIDTH 0x9127 -#define GL_UNPACK_COMPRESSED_BLOCK_HEIGHT 0x9128 -#define GL_UNPACK_COMPRESSED_BLOCK_DEPTH 0x9129 -#define GL_UNPACK_COMPRESSED_BLOCK_SIZE 0x912A -#define GL_PACK_COMPRESSED_BLOCK_WIDTH 0x912B -#define GL_PACK_COMPRESSED_BLOCK_HEIGHT 0x912C -#define GL_PACK_COMPRESSED_BLOCK_DEPTH 0x912D -#define GL_PACK_COMPRESSED_BLOCK_SIZE 0x912E -#define GL_NUM_SAMPLE_COUNTS 0x9380 -#define GL_MIN_MAP_BUFFER_ALIGNMENT 0x90BC -#define GL_ATOMIC_COUNTER_BUFFER 0x92C0 -#define GL_ATOMIC_COUNTER_BUFFER_BINDING 0x92C1 -#define GL_ATOMIC_COUNTER_BUFFER_START 0x92C2 -#define GL_ATOMIC_COUNTER_BUFFER_SIZE 0x92C3 -#define GL_ATOMIC_COUNTER_BUFFER_DATA_SIZE 0x92C4 -#define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTERS 0x92C5 -#define GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES 0x92C6 -#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_VERTEX_SHADER 0x92C7 -#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_CONTROL_SHADER 0x92C8 -#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TESS_EVALUATION_SHADER 0x92C9 -#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_GEOMETRY_SHADER 0x92CA -#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_FRAGMENT_SHADER 0x92CB -#define GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS 0x92CC -#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS 0x92CD -#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS 0x92CE -#define GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS 0x92CF -#define GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS 0x92D0 -#define GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS 0x92D1 -#define GL_MAX_VERTEX_ATOMIC_COUNTERS 0x92D2 -#define GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS 0x92D3 -#define GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS 0x92D4 -#define GL_MAX_GEOMETRY_ATOMIC_COUNTERS 0x92D5 -#define GL_MAX_FRAGMENT_ATOMIC_COUNTERS 0x92D6 -#define GL_MAX_COMBINED_ATOMIC_COUNTERS 0x92D7 -#define GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE 0x92D8 -#define GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS 0x92DC -#define GL_ACTIVE_ATOMIC_COUNTER_BUFFERS 0x92D9 -#define GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX 0x92DA -#define GL_UNSIGNED_INT_ATOMIC_COUNTER 0x92DB -#define GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT 0x00000001 -#define GL_ELEMENT_ARRAY_BARRIER_BIT 0x00000002 -#define GL_UNIFORM_BARRIER_BIT 0x00000004 -#define GL_TEXTURE_FETCH_BARRIER_BIT 0x00000008 -#define GL_SHADER_IMAGE_ACCESS_BARRIER_BIT 0x00000020 -#define GL_COMMAND_BARRIER_BIT 0x00000040 -#define GL_PIXEL_BUFFER_BARRIER_BIT 0x00000080 -#define GL_TEXTURE_UPDATE_BARRIER_BIT 0x00000100 -#define GL_BUFFER_UPDATE_BARRIER_BIT 0x00000200 -#define GL_FRAMEBUFFER_BARRIER_BIT 0x00000400 -#define GL_TRANSFORM_FEEDBACK_BARRIER_BIT 0x00000800 -#define GL_ATOMIC_COUNTER_BARRIER_BIT 0x00001000 -#define GL_ALL_BARRIER_BITS 0xFFFFFFFF -#define GL_MAX_IMAGE_UNITS 0x8F38 -#define GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS 0x8F39 -#define GL_IMAGE_BINDING_NAME 0x8F3A -#define GL_IMAGE_BINDING_LEVEL 0x8F3B -#define GL_IMAGE_BINDING_LAYERED 0x8F3C -#define GL_IMAGE_BINDING_LAYER 0x8F3D -#define GL_IMAGE_BINDING_ACCESS 0x8F3E -#define GL_IMAGE_1D 0x904C -#define GL_IMAGE_2D 0x904D -#define GL_IMAGE_3D 0x904E -#define GL_IMAGE_2D_RECT 0x904F -#define GL_IMAGE_CUBE 0x9050 -#define GL_IMAGE_BUFFER 0x9051 -#define GL_IMAGE_1D_ARRAY 0x9052 -#define GL_IMAGE_2D_ARRAY 0x9053 -#define GL_IMAGE_CUBE_MAP_ARRAY 0x9054 -#define GL_IMAGE_2D_MULTISAMPLE 0x9055 -#define GL_IMAGE_2D_MULTISAMPLE_ARRAY 0x9056 -#define GL_INT_IMAGE_1D 0x9057 -#define GL_INT_IMAGE_2D 0x9058 -#define GL_INT_IMAGE_3D 0x9059 -#define GL_INT_IMAGE_2D_RECT 0x905A -#define GL_INT_IMAGE_CUBE 0x905B -#define GL_INT_IMAGE_BUFFER 0x905C -#define GL_INT_IMAGE_1D_ARRAY 0x905D -#define GL_INT_IMAGE_2D_ARRAY 0x905E -#define GL_INT_IMAGE_CUBE_MAP_ARRAY 0x905F -#define GL_INT_IMAGE_2D_MULTISAMPLE 0x9060 -#define GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x9061 -#define GL_UNSIGNED_INT_IMAGE_1D 0x9062 -#define GL_UNSIGNED_INT_IMAGE_2D 0x9063 -#define GL_UNSIGNED_INT_IMAGE_3D 0x9064 -#define GL_UNSIGNED_INT_IMAGE_2D_RECT 0x9065 -#define GL_UNSIGNED_INT_IMAGE_CUBE 0x9066 -#define GL_UNSIGNED_INT_IMAGE_BUFFER 0x9067 -#define GL_UNSIGNED_INT_IMAGE_1D_ARRAY 0x9068 -#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY 0x9069 -#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY 0x906A -#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE 0x906B -#define GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY 0x906C -#define GL_MAX_IMAGE_SAMPLES 0x906D -#define GL_IMAGE_BINDING_FORMAT 0x906E -#define GL_IMAGE_FORMAT_COMPATIBILITY_TYPE 0x90C7 -#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE 0x90C8 -#define GL_IMAGE_FORMAT_COMPATIBILITY_BY_CLASS 0x90C9 -#define GL_MAX_VERTEX_IMAGE_UNIFORMS 0x90CA -#define GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS 0x90CB -#define GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS 0x90CC -#define GL_MAX_GEOMETRY_IMAGE_UNIFORMS 0x90CD -#define GL_MAX_FRAGMENT_IMAGE_UNIFORMS 0x90CE -#define GL_MAX_COMBINED_IMAGE_UNIFORMS 0x90CF -#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C -#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D -#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E -#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F -#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F -typedef void(APIENTRYP PFNGLDRAWARRAYSINSTANCEDBASEINSTANCEPROC)(GLenum mode, GLint first, GLsizei count, - GLsizei instancecount, GLuint baseinstance); -typedef void(APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEPROC)(GLenum mode, GLsizei count, GLenum type, - const void* indices, GLsizei instancecount, - GLuint baseinstance); -typedef void(APIENTRYP PFNGLDRAWELEMENTSINSTANCEDBASEVERTEXBASEINSTANCEPROC)(GLenum mode, GLsizei count, GLenum type, - const void* indices, GLsizei instancecount, - GLint basevertex, GLuint baseinstance); -typedef void(APIENTRYP PFNGLGETINTERNALFORMATIVPROC)(GLenum target, GLenum internalformat, GLenum pname, - GLsizei bufSize, GLint* params); -typedef void(APIENTRYP PFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC)(GLuint program, GLuint bufferIndex, GLenum pname, - GLint* params); -typedef void(APIENTRYP PFNGLBINDIMAGETEXTUREPROC)(GLuint unit, GLuint texture, GLint level, GLboolean layered, - GLint layer, GLenum access, GLenum format); -typedef void(APIENTRYP PFNGLMEMORYBARRIERPROC)(GLbitfield barriers); -typedef void(APIENTRYP PFNGLTEXSTORAGE1DPROC)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); -typedef void(APIENTRYP PFNGLTEXSTORAGE2DPROC)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLTEXSTORAGE3DPROC)(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, - GLsizei height, GLsizei depth); -typedef void(APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDPROC)(GLenum mode, GLuint id, GLsizei instancecount); -typedef void(APIENTRYP PFNGLDRAWTRANSFORMFEEDBACKSTREAMINSTANCEDPROC)(GLenum mode, GLuint id, GLuint stream, - GLsizei instancecount); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glDrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount, - GLuint baseinstance); -GLAPI void APIENTRY glDrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, - GLsizei instancecount, GLuint baseinstance); -GLAPI void APIENTRY glDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, - const void* indices, GLsizei instancecount, - GLint basevertex, GLuint baseinstance); -GLAPI void APIENTRY glGetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, - GLint* params); -GLAPI void APIENTRY glGetActiveAtomicCounterBufferiv(GLuint program, GLuint bufferIndex, GLenum pname, GLint* params); -GLAPI void APIENTRY glBindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, - GLenum access, GLenum format); -GLAPI void APIENTRY glMemoryBarrier(GLbitfield barriers); -GLAPI void APIENTRY glTexStorage1D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width); -GLAPI void APIENTRY glTexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); -GLAPI void APIENTRY glTexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, - GLsizei depth); -GLAPI void APIENTRY glDrawTransformFeedbackInstanced(GLenum mode, GLuint id, GLsizei instancecount); -GLAPI void APIENTRY glDrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, - GLsizei instancecount); -#endif -#endif /* GL_VERSION_4_2 */ - -#ifndef GL_VERSION_4_3 -#define GL_VERSION_4_3 1 -typedef void(APIENTRY* GLDEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, - const GLchar* message, const void* userParam); -#define GL_NUM_SHADING_LANGUAGE_VERSIONS 0x82E9 -#define GL_VERTEX_ATTRIB_ARRAY_LONG 0x874E -#define GL_COMPRESSED_RGB8_ETC2 0x9274 -#define GL_COMPRESSED_SRGB8_ETC2 0x9275 -#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 -#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 -#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 -#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 -#define GL_COMPRESSED_R11_EAC 0x9270 -#define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 -#define GL_COMPRESSED_RG11_EAC 0x9272 -#define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 -#define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69 -#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE 0x8D6A -#define GL_MAX_ELEMENT_INDEX 0x8D6B -#define GL_COMPUTE_SHADER 0x91B9 -#define GL_MAX_COMPUTE_UNIFORM_BLOCKS 0x91BB -#define GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS 0x91BC -#define GL_MAX_COMPUTE_IMAGE_UNIFORMS 0x91BD -#define GL_MAX_COMPUTE_SHARED_MEMORY_SIZE 0x8262 -#define GL_MAX_COMPUTE_UNIFORM_COMPONENTS 0x8263 -#define GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS 0x8264 -#define GL_MAX_COMPUTE_ATOMIC_COUNTERS 0x8265 -#define GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS 0x8266 -#define GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS 0x90EB -#define GL_MAX_COMPUTE_WORK_GROUP_COUNT 0x91BE -#define GL_MAX_COMPUTE_WORK_GROUP_SIZE 0x91BF -#define GL_COMPUTE_WORK_GROUP_SIZE 0x8267 -#define GL_UNIFORM_BLOCK_REFERENCED_BY_COMPUTE_SHADER 0x90EC -#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_COMPUTE_SHADER 0x90ED -#define GL_DISPATCH_INDIRECT_BUFFER 0x90EE -#define GL_DISPATCH_INDIRECT_BUFFER_BINDING 0x90EF -#define GL_COMPUTE_SHADER_BIT 0x00000020 -#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 -#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH 0x8243 -#define GL_DEBUG_CALLBACK_FUNCTION 0x8244 -#define GL_DEBUG_CALLBACK_USER_PARAM 0x8245 -#define GL_DEBUG_SOURCE_API 0x8246 -#define GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247 -#define GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248 -#define GL_DEBUG_SOURCE_THIRD_PARTY 0x8249 -#define GL_DEBUG_SOURCE_APPLICATION 0x824A -#define GL_DEBUG_SOURCE_OTHER 0x824B -#define GL_DEBUG_TYPE_ERROR 0x824C -#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D -#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E -#define GL_DEBUG_TYPE_PORTABILITY 0x824F -#define GL_DEBUG_TYPE_PERFORMANCE 0x8250 -#define GL_DEBUG_TYPE_OTHER 0x8251 -#define GL_MAX_DEBUG_MESSAGE_LENGTH 0x9143 -#define GL_MAX_DEBUG_LOGGED_MESSAGES 0x9144 -#define GL_DEBUG_LOGGED_MESSAGES 0x9145 -#define GL_DEBUG_SEVERITY_HIGH 0x9146 -#define GL_DEBUG_SEVERITY_MEDIUM 0x9147 -#define GL_DEBUG_SEVERITY_LOW 0x9148 -#define GL_DEBUG_TYPE_MARKER 0x8268 -#define GL_DEBUG_TYPE_PUSH_GROUP 0x8269 -#define GL_DEBUG_TYPE_POP_GROUP 0x826A -#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B -#define GL_MAX_DEBUG_GROUP_STACK_DEPTH 0x826C -#define GL_DEBUG_GROUP_STACK_DEPTH 0x826D -#define GL_BUFFER 0x82E0 -#define GL_SHADER 0x82E1 -#define GL_PROGRAM 0x82E2 -#define GL_QUERY 0x82E3 -#define GL_PROGRAM_PIPELINE 0x82E4 -#define GL_SAMPLER 0x82E6 -#define GL_MAX_LABEL_LENGTH 0x82E8 -#define GL_DEBUG_OUTPUT 0x92E0 -#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002 -#define GL_MAX_UNIFORM_LOCATIONS 0x826E -#define GL_FRAMEBUFFER_DEFAULT_WIDTH 0x9310 -#define GL_FRAMEBUFFER_DEFAULT_HEIGHT 0x9311 -#define GL_FRAMEBUFFER_DEFAULT_LAYERS 0x9312 -#define GL_FRAMEBUFFER_DEFAULT_SAMPLES 0x9313 -#define GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS 0x9314 -#define GL_MAX_FRAMEBUFFER_WIDTH 0x9315 -#define GL_MAX_FRAMEBUFFER_HEIGHT 0x9316 -#define GL_MAX_FRAMEBUFFER_LAYERS 0x9317 -#define GL_MAX_FRAMEBUFFER_SAMPLES 0x9318 -#define GL_INTERNALFORMAT_SUPPORTED 0x826F -#define GL_INTERNALFORMAT_PREFERRED 0x8270 -#define GL_INTERNALFORMAT_RED_SIZE 0x8271 -#define GL_INTERNALFORMAT_GREEN_SIZE 0x8272 -#define GL_INTERNALFORMAT_BLUE_SIZE 0x8273 -#define GL_INTERNALFORMAT_ALPHA_SIZE 0x8274 -#define GL_INTERNALFORMAT_DEPTH_SIZE 0x8275 -#define GL_INTERNALFORMAT_STENCIL_SIZE 0x8276 -#define GL_INTERNALFORMAT_SHARED_SIZE 0x8277 -#define GL_INTERNALFORMAT_RED_TYPE 0x8278 -#define GL_INTERNALFORMAT_GREEN_TYPE 0x8279 -#define GL_INTERNALFORMAT_BLUE_TYPE 0x827A -#define GL_INTERNALFORMAT_ALPHA_TYPE 0x827B -#define GL_INTERNALFORMAT_DEPTH_TYPE 0x827C -#define GL_INTERNALFORMAT_STENCIL_TYPE 0x827D -#define GL_MAX_WIDTH 0x827E -#define GL_MAX_HEIGHT 0x827F -#define GL_MAX_DEPTH 0x8280 -#define GL_MAX_LAYERS 0x8281 -#define GL_MAX_COMBINED_DIMENSIONS 0x8282 -#define GL_COLOR_COMPONENTS 0x8283 -#define GL_DEPTH_COMPONENTS 0x8284 -#define GL_STENCIL_COMPONENTS 0x8285 -#define GL_COLOR_RENDERABLE 0x8286 -#define GL_DEPTH_RENDERABLE 0x8287 -#define GL_STENCIL_RENDERABLE 0x8288 -#define GL_FRAMEBUFFER_RENDERABLE 0x8289 -#define GL_FRAMEBUFFER_RENDERABLE_LAYERED 0x828A -#define GL_FRAMEBUFFER_BLEND 0x828B -#define GL_READ_PIXELS 0x828C -#define GL_READ_PIXELS_FORMAT 0x828D -#define GL_READ_PIXELS_TYPE 0x828E -#define GL_TEXTURE_IMAGE_FORMAT 0x828F -#define GL_TEXTURE_IMAGE_TYPE 0x8290 -#define GL_GET_TEXTURE_IMAGE_FORMAT 0x8291 -#define GL_GET_TEXTURE_IMAGE_TYPE 0x8292 -#define GL_MIPMAP 0x8293 -#define GL_MANUAL_GENERATE_MIPMAP 0x8294 -#define GL_AUTO_GENERATE_MIPMAP 0x8295 -#define GL_COLOR_ENCODING 0x8296 -#define GL_SRGB_READ 0x8297 -#define GL_SRGB_WRITE 0x8298 -#define GL_FILTER 0x829A -#define GL_VERTEX_TEXTURE 0x829B -#define GL_TESS_CONTROL_TEXTURE 0x829C -#define GL_TESS_EVALUATION_TEXTURE 0x829D -#define GL_GEOMETRY_TEXTURE 0x829E -#define GL_FRAGMENT_TEXTURE 0x829F -#define GL_COMPUTE_TEXTURE 0x82A0 -#define GL_TEXTURE_SHADOW 0x82A1 -#define GL_TEXTURE_GATHER 0x82A2 -#define GL_TEXTURE_GATHER_SHADOW 0x82A3 -#define GL_SHADER_IMAGE_LOAD 0x82A4 -#define GL_SHADER_IMAGE_STORE 0x82A5 -#define GL_SHADER_IMAGE_ATOMIC 0x82A6 -#define GL_IMAGE_TEXEL_SIZE 0x82A7 -#define GL_IMAGE_COMPATIBILITY_CLASS 0x82A8 -#define GL_IMAGE_PIXEL_FORMAT 0x82A9 -#define GL_IMAGE_PIXEL_TYPE 0x82AA -#define GL_SIMULTANEOUS_TEXTURE_AND_DEPTH_TEST 0x82AC -#define GL_SIMULTANEOUS_TEXTURE_AND_STENCIL_TEST 0x82AD -#define GL_SIMULTANEOUS_TEXTURE_AND_DEPTH_WRITE 0x82AE -#define GL_SIMULTANEOUS_TEXTURE_AND_STENCIL_WRITE 0x82AF -#define GL_TEXTURE_COMPRESSED_BLOCK_WIDTH 0x82B1 -#define GL_TEXTURE_COMPRESSED_BLOCK_HEIGHT 0x82B2 -#define GL_TEXTURE_COMPRESSED_BLOCK_SIZE 0x82B3 -#define GL_CLEAR_BUFFER 0x82B4 -#define GL_TEXTURE_VIEW 0x82B5 -#define GL_VIEW_COMPATIBILITY_CLASS 0x82B6 -#define GL_FULL_SUPPORT 0x82B7 -#define GL_CAVEAT_SUPPORT 0x82B8 -#define GL_IMAGE_CLASS_4_X_32 0x82B9 -#define GL_IMAGE_CLASS_2_X_32 0x82BA -#define GL_IMAGE_CLASS_1_X_32 0x82BB -#define GL_IMAGE_CLASS_4_X_16 0x82BC -#define GL_IMAGE_CLASS_2_X_16 0x82BD -#define GL_IMAGE_CLASS_1_X_16 0x82BE -#define GL_IMAGE_CLASS_4_X_8 0x82BF -#define GL_IMAGE_CLASS_2_X_8 0x82C0 -#define GL_IMAGE_CLASS_1_X_8 0x82C1 -#define GL_IMAGE_CLASS_11_11_10 0x82C2 -#define GL_IMAGE_CLASS_10_10_10_2 0x82C3 -#define GL_VIEW_CLASS_128_BITS 0x82C4 -#define GL_VIEW_CLASS_96_BITS 0x82C5 -#define GL_VIEW_CLASS_64_BITS 0x82C6 -#define GL_VIEW_CLASS_48_BITS 0x82C7 -#define GL_VIEW_CLASS_32_BITS 0x82C8 -#define GL_VIEW_CLASS_24_BITS 0x82C9 -#define GL_VIEW_CLASS_16_BITS 0x82CA -#define GL_VIEW_CLASS_8_BITS 0x82CB -#define GL_VIEW_CLASS_S3TC_DXT1_RGB 0x82CC -#define GL_VIEW_CLASS_S3TC_DXT1_RGBA 0x82CD -#define GL_VIEW_CLASS_S3TC_DXT3_RGBA 0x82CE -#define GL_VIEW_CLASS_S3TC_DXT5_RGBA 0x82CF -#define GL_VIEW_CLASS_RGTC1_RED 0x82D0 -#define GL_VIEW_CLASS_RGTC2_RG 0x82D1 -#define GL_VIEW_CLASS_BPTC_UNORM 0x82D2 -#define GL_VIEW_CLASS_BPTC_FLOAT 0x82D3 -#define GL_UNIFORM 0x92E1 -#define GL_UNIFORM_BLOCK 0x92E2 -#define GL_PROGRAM_INPUT 0x92E3 -#define GL_PROGRAM_OUTPUT 0x92E4 -#define GL_BUFFER_VARIABLE 0x92E5 -#define GL_SHADER_STORAGE_BLOCK 0x92E6 -#define GL_VERTEX_SUBROUTINE 0x92E8 -#define GL_TESS_CONTROL_SUBROUTINE 0x92E9 -#define GL_TESS_EVALUATION_SUBROUTINE 0x92EA -#define GL_GEOMETRY_SUBROUTINE 0x92EB -#define GL_FRAGMENT_SUBROUTINE 0x92EC -#define GL_COMPUTE_SUBROUTINE 0x92ED -#define GL_VERTEX_SUBROUTINE_UNIFORM 0x92EE -#define GL_TESS_CONTROL_SUBROUTINE_UNIFORM 0x92EF -#define GL_TESS_EVALUATION_SUBROUTINE_UNIFORM 0x92F0 -#define GL_GEOMETRY_SUBROUTINE_UNIFORM 0x92F1 -#define GL_FRAGMENT_SUBROUTINE_UNIFORM 0x92F2 -#define GL_COMPUTE_SUBROUTINE_UNIFORM 0x92F3 -#define GL_TRANSFORM_FEEDBACK_VARYING 0x92F4 -#define GL_ACTIVE_RESOURCES 0x92F5 -#define GL_MAX_NAME_LENGTH 0x92F6 -#define GL_MAX_NUM_ACTIVE_VARIABLES 0x92F7 -#define GL_MAX_NUM_COMPATIBLE_SUBROUTINES 0x92F8 -#define GL_NAME_LENGTH 0x92F9 -#define GL_TYPE 0x92FA -#define GL_ARRAY_SIZE 0x92FB -#define GL_OFFSET 0x92FC -#define GL_BLOCK_INDEX 0x92FD -#define GL_ARRAY_STRIDE 0x92FE -#define GL_MATRIX_STRIDE 0x92FF -#define GL_IS_ROW_MAJOR 0x9300 -#define GL_ATOMIC_COUNTER_BUFFER_INDEX 0x9301 -#define GL_BUFFER_BINDING 0x9302 -#define GL_BUFFER_DATA_SIZE 0x9303 -#define GL_NUM_ACTIVE_VARIABLES 0x9304 -#define GL_ACTIVE_VARIABLES 0x9305 -#define GL_REFERENCED_BY_VERTEX_SHADER 0x9306 -#define GL_REFERENCED_BY_TESS_CONTROL_SHADER 0x9307 -#define GL_REFERENCED_BY_TESS_EVALUATION_SHADER 0x9308 -#define GL_REFERENCED_BY_GEOMETRY_SHADER 0x9309 -#define GL_REFERENCED_BY_FRAGMENT_SHADER 0x930A -#define GL_REFERENCED_BY_COMPUTE_SHADER 0x930B -#define GL_TOP_LEVEL_ARRAY_SIZE 0x930C -#define GL_TOP_LEVEL_ARRAY_STRIDE 0x930D -#define GL_LOCATION 0x930E -#define GL_LOCATION_INDEX 0x930F -#define GL_IS_PER_PATCH 0x92E7 -#define GL_SHADER_STORAGE_BUFFER 0x90D2 -#define GL_SHADER_STORAGE_BUFFER_BINDING 0x90D3 -#define GL_SHADER_STORAGE_BUFFER_START 0x90D4 -#define GL_SHADER_STORAGE_BUFFER_SIZE 0x90D5 -#define GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS 0x90D6 -#define GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS 0x90D7 -#define GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS 0x90D8 -#define GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS 0x90D9 -#define GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS 0x90DA -#define GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS 0x90DB -#define GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS 0x90DC -#define GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS 0x90DD -#define GL_MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE -#define GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT 0x90DF -#define GL_SHADER_STORAGE_BARRIER_BIT 0x00002000 -#define GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES 0x8F39 -#define GL_DEPTH_STENCIL_TEXTURE_MODE 0x90EA -#define GL_TEXTURE_BUFFER_OFFSET 0x919D -#define GL_TEXTURE_BUFFER_SIZE 0x919E -#define GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT 0x919F -#define GL_TEXTURE_VIEW_MIN_LEVEL 0x82DB -#define GL_TEXTURE_VIEW_NUM_LEVELS 0x82DC -#define GL_TEXTURE_VIEW_MIN_LAYER 0x82DD -#define GL_TEXTURE_VIEW_NUM_LAYERS 0x82DE -#define GL_TEXTURE_IMMUTABLE_LEVELS 0x82DF -#define GL_VERTEX_ATTRIB_BINDING 0x82D4 -#define GL_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D5 -#define GL_VERTEX_BINDING_DIVISOR 0x82D6 -#define GL_VERTEX_BINDING_OFFSET 0x82D7 -#define GL_VERTEX_BINDING_STRIDE 0x82D8 -#define GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET 0x82D9 -#define GL_MAX_VERTEX_ATTRIB_BINDINGS 0x82DA -#define GL_VERTEX_BINDING_BUFFER 0x8F4F -typedef void(APIENTRYP PFNGLCLEARBUFFERDATAPROC)(GLenum target, GLenum internalformat, GLenum format, GLenum type, - const void* data); -typedef void(APIENTRYP PFNGLCLEARBUFFERSUBDATAPROC)(GLenum target, GLenum internalformat, GLintptr offset, - GLsizeiptr size, GLenum format, GLenum type, const void* data); -typedef void(APIENTRYP PFNGLDISPATCHCOMPUTEPROC)(GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); -typedef void(APIENTRYP PFNGLDISPATCHCOMPUTEINDIRECTPROC)(GLintptr indirect); -typedef void(APIENTRYP PFNGLCOPYIMAGESUBDATAPROC)(GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, - GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, - GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, - GLsizei srcHeight, GLsizei srcDepth); -typedef void(APIENTRYP PFNGLFRAMEBUFFERPARAMETERIPROC)(GLenum target, GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVPROC)(GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETINTERNALFORMATI64VPROC)(GLenum target, GLenum internalformat, GLenum pname, - GLsizei bufSize, GLint64* params); -typedef void(APIENTRYP PFNGLINVALIDATETEXSUBIMAGEPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth); -typedef void(APIENTRYP PFNGLINVALIDATETEXIMAGEPROC)(GLuint texture, GLint level); -typedef void(APIENTRYP PFNGLINVALIDATEBUFFERSUBDATAPROC)(GLuint buffer, GLintptr offset, GLsizeiptr length); -typedef void(APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer); -typedef void(APIENTRYP PFNGLINVALIDATEFRAMEBUFFERPROC)(GLenum target, GLsizei numAttachments, - const GLenum* attachments); -typedef void(APIENTRYP PFNGLINVALIDATESUBFRAMEBUFFERPROC)(GLenum target, GLsizei numAttachments, - const GLenum* attachments, GLint x, GLint y, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTPROC)(GLenum mode, const void* indirect, GLsizei drawcount, - GLsizei stride); -typedef void(APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTPROC)(GLenum mode, GLenum type, const void* indirect, - GLsizei drawcount, GLsizei stride); -typedef void(APIENTRYP PFNGLGETPROGRAMINTERFACEIVPROC)(GLuint program, GLenum programInterface, GLenum pname, - GLint* params); -typedef GLuint(APIENTRYP PFNGLGETPROGRAMRESOURCEINDEXPROC)(GLuint program, GLenum programInterface, const GLchar* name); -typedef void(APIENTRYP PFNGLGETPROGRAMRESOURCENAMEPROC)(GLuint program, GLenum programInterface, GLuint index, - GLsizei bufSize, GLsizei* length, GLchar* name); -typedef void(APIENTRYP PFNGLGETPROGRAMRESOURCEIVPROC)(GLuint program, GLenum programInterface, GLuint index, - GLsizei propCount, const GLenum* props, GLsizei bufSize, - GLsizei* length, GLint* params); -typedef GLint(APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONPROC)(GLuint program, GLenum programInterface, - const GLchar* name); -typedef GLint(APIENTRYP PFNGLGETPROGRAMRESOURCELOCATIONINDEXPROC)(GLuint program, GLenum programInterface, - const GLchar* name); -typedef void(APIENTRYP PFNGLSHADERSTORAGEBLOCKBINDINGPROC)(GLuint program, GLuint storageBlockIndex, - GLuint storageBlockBinding); -typedef void(APIENTRYP PFNGLTEXBUFFERRANGEPROC)(GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, - GLsizeiptr size); -typedef void(APIENTRYP PFNGLTEXSTORAGE2DMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height, GLboolean fixedsamplelocations); -typedef void(APIENTRYP PFNGLTEXSTORAGE3DMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height, GLsizei depth, - GLboolean fixedsamplelocations); -typedef void(APIENTRYP PFNGLTEXTUREVIEWPROC)(GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, - GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); -typedef void(APIENTRYP PFNGLBINDVERTEXBUFFERPROC)(GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); -typedef void(APIENTRYP PFNGLVERTEXATTRIBFORMATPROC)(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, - GLuint relativeoffset); -typedef void(APIENTRYP PFNGLVERTEXATTRIBIFORMATPROC)(GLuint attribindex, GLint size, GLenum type, - GLuint relativeoffset); -typedef void(APIENTRYP PFNGLVERTEXATTRIBLFORMATPROC)(GLuint attribindex, GLint size, GLenum type, - GLuint relativeoffset); -typedef void(APIENTRYP PFNGLVERTEXATTRIBBINDINGPROC)(GLuint attribindex, GLuint bindingindex); -typedef void(APIENTRYP PFNGLVERTEXBINDINGDIVISORPROC)(GLuint bindingindex, GLuint divisor); -typedef void(APIENTRYP PFNGLDEBUGMESSAGECONTROLPROC)(GLenum source, GLenum type, GLenum severity, GLsizei count, - const GLuint* ids, GLboolean enabled); -typedef void(APIENTRYP PFNGLDEBUGMESSAGEINSERTPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, - GLsizei length, const GLchar* buf); -typedef void(APIENTRYP PFNGLDEBUGMESSAGECALLBACKPROC)(GLDEBUGPROC callback, const void* userParam); -typedef GLuint(APIENTRYP PFNGLGETDEBUGMESSAGELOGPROC)(GLuint count, GLsizei bufSize, GLenum* sources, GLenum* types, - GLuint* ids, GLenum* severities, GLsizei* lengths, - GLchar* messageLog); -typedef void(APIENTRYP PFNGLPUSHDEBUGGROUPPROC)(GLenum source, GLuint id, GLsizei length, const GLchar* message); -typedef void(APIENTRYP PFNGLPOPDEBUGGROUPPROC)(void); -typedef void(APIENTRYP PFNGLOBJECTLABELPROC)(GLenum identifier, GLuint name, GLsizei length, const GLchar* label); -typedef void(APIENTRYP PFNGLGETOBJECTLABELPROC)(GLenum identifier, GLuint name, GLsizei bufSize, GLsizei* length, - GLchar* label); -typedef void(APIENTRYP PFNGLOBJECTPTRLABELPROC)(const void* ptr, GLsizei length, const GLchar* label); -typedef void(APIENTRYP PFNGLGETOBJECTPTRLABELPROC)(const void* ptr, GLsizei bufSize, GLsizei* length, GLchar* label); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glClearBufferData(GLenum target, GLenum internalformat, GLenum format, GLenum type, - const void* data); -GLAPI void APIENTRY glClearBufferSubData(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, - GLenum format, GLenum type, const void* data); -GLAPI void APIENTRY glDispatchCompute(GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); -GLAPI void APIENTRY glDispatchComputeIndirect(GLintptr indirect); -GLAPI void APIENTRY glCopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, - GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, - GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth); -GLAPI void APIENTRY glFramebufferParameteri(GLenum target, GLenum pname, GLint param); -GLAPI void APIENTRY glGetFramebufferParameteriv(GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetInternalformati64v(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, - GLint64* params); -GLAPI void APIENTRY glInvalidateTexSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth); -GLAPI void APIENTRY glInvalidateTexImage(GLuint texture, GLint level); -GLAPI void APIENTRY glInvalidateBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr length); -GLAPI void APIENTRY glInvalidateBufferData(GLuint buffer); -GLAPI void APIENTRY glInvalidateFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments); -GLAPI void APIENTRY glInvalidateSubFramebuffer(GLenum target, GLsizei numAttachments, const GLenum* attachments, - GLint x, GLint y, GLsizei width, GLsizei height); -GLAPI void APIENTRY glMultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride); -GLAPI void APIENTRY glMultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, - GLsizei stride); -GLAPI void APIENTRY glGetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params); -GLAPI GLuint APIENTRY glGetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name); -GLAPI void APIENTRY glGetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, - GLsizei* length, GLchar* name); -GLAPI void APIENTRY glGetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, - const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params); -GLAPI GLint APIENTRY glGetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name); -GLAPI GLint APIENTRY glGetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name); -GLAPI void APIENTRY glShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding); -GLAPI void APIENTRY glTexBufferRange(GLenum target, GLenum internalformat, GLuint buffer, GLintptr offset, - GLsizeiptr size); -GLAPI void APIENTRY glTexStorage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, - GLsizei height, GLboolean fixedsamplelocations); -GLAPI void APIENTRY glTexStorage3DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, - GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); -GLAPI void APIENTRY glTextureView(GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, - GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers); -GLAPI void APIENTRY glBindVertexBuffer(GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride); -GLAPI void APIENTRY glVertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, - GLuint relativeoffset); -GLAPI void APIENTRY glVertexAttribIFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); -GLAPI void APIENTRY glVertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset); -GLAPI void APIENTRY glVertexAttribBinding(GLuint attribindex, GLuint bindingindex); -GLAPI void APIENTRY glVertexBindingDivisor(GLuint bindingindex, GLuint divisor); -GLAPI void APIENTRY glDebugMessageControl(GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, - GLboolean enabled); -GLAPI void APIENTRY glDebugMessageInsert(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, - const GLchar* buf); -GLAPI void APIENTRY glDebugMessageCallback(GLDEBUGPROC callback, const void* userParam); -GLAPI GLuint APIENTRY glGetDebugMessageLog(GLuint count, GLsizei bufSize, GLenum* sources, GLenum* types, GLuint* ids, - GLenum* severities, GLsizei* lengths, GLchar* messageLog); -GLAPI void APIENTRY glPushDebugGroup(GLenum source, GLuint id, GLsizei length, const GLchar* message); -GLAPI void APIENTRY glPopDebugGroup(void); -GLAPI void APIENTRY glObjectLabel(GLenum identifier, GLuint name, GLsizei length, const GLchar* label); -GLAPI void APIENTRY glGetObjectLabel(GLenum identifier, GLuint name, GLsizei bufSize, GLsizei* length, GLchar* label); -GLAPI void APIENTRY glObjectPtrLabel(const void* ptr, GLsizei length, const GLchar* label); -GLAPI void APIENTRY glGetObjectPtrLabel(const void* ptr, GLsizei bufSize, GLsizei* length, GLchar* label); -#endif -#endif /* GL_VERSION_4_3 */ - -#ifndef GL_VERSION_4_4 -#define GL_VERSION_4_4 1 -#define GL_MAX_VERTEX_ATTRIB_STRIDE 0x82E5 -#define GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED 0x8221 -#define GL_TEXTURE_BUFFER_BINDING 0x8C2A -#define GL_MAP_PERSISTENT_BIT 0x0040 -#define GL_MAP_COHERENT_BIT 0x0080 -#define GL_DYNAMIC_STORAGE_BIT 0x0100 -#define GL_CLIENT_STORAGE_BIT 0x0200 -#define GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT 0x00004000 -#define GL_BUFFER_IMMUTABLE_STORAGE 0x821F -#define GL_BUFFER_STORAGE_FLAGS 0x8220 -#define GL_CLEAR_TEXTURE 0x9365 -#define GL_LOCATION_COMPONENT 0x934A -#define GL_TRANSFORM_FEEDBACK_BUFFER_INDEX 0x934B -#define GL_TRANSFORM_FEEDBACK_BUFFER_STRIDE 0x934C -#define GL_QUERY_BUFFER 0x9192 -#define GL_QUERY_BUFFER_BARRIER_BIT 0x00008000 -#define GL_QUERY_BUFFER_BINDING 0x9193 -#define GL_QUERY_RESULT_NO_WAIT 0x9194 -#define GL_MIRROR_CLAMP_TO_EDGE 0x8743 -typedef void(APIENTRYP PFNGLBUFFERSTORAGEPROC)(GLenum target, GLsizeiptr size, const void* data, GLbitfield flags); -typedef void(APIENTRYP PFNGLCLEARTEXIMAGEPROC)(GLuint texture, GLint level, GLenum format, GLenum type, - const void* data); -typedef void(APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, - GLenum format, GLenum type, const void* data); -typedef void(APIENTRYP PFNGLBINDBUFFERSBASEPROC)(GLenum target, GLuint first, GLsizei count, const GLuint* buffers); -typedef void(APIENTRYP PFNGLBINDBUFFERSRANGEPROC)(GLenum target, GLuint first, GLsizei count, const GLuint* buffers, - const GLintptr* offsets, const GLsizeiptr* sizes); -typedef void(APIENTRYP PFNGLBINDTEXTURESPROC)(GLuint first, GLsizei count, const GLuint* textures); -typedef void(APIENTRYP PFNGLBINDSAMPLERSPROC)(GLuint first, GLsizei count, const GLuint* samplers); -typedef void(APIENTRYP PFNGLBINDIMAGETEXTURESPROC)(GLuint first, GLsizei count, const GLuint* textures); -typedef void(APIENTRYP PFNGLBINDVERTEXBUFFERSPROC)(GLuint first, GLsizei count, const GLuint* buffers, - const GLintptr* offsets, const GLsizei* strides); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBufferStorage(GLenum target, GLsizeiptr size, const void* data, GLbitfield flags); -GLAPI void APIENTRY glClearTexImage(GLuint texture, GLint level, GLenum format, GLenum type, const void* data); -GLAPI void APIENTRY glClearTexSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, - const void* data); -GLAPI void APIENTRY glBindBuffersBase(GLenum target, GLuint first, GLsizei count, const GLuint* buffers); -GLAPI void APIENTRY glBindBuffersRange(GLenum target, GLuint first, GLsizei count, const GLuint* buffers, - const GLintptr* offsets, const GLsizeiptr* sizes); -GLAPI void APIENTRY glBindTextures(GLuint first, GLsizei count, const GLuint* textures); -GLAPI void APIENTRY glBindSamplers(GLuint first, GLsizei count, const GLuint* samplers); -GLAPI void APIENTRY glBindImageTextures(GLuint first, GLsizei count, const GLuint* textures); -GLAPI void APIENTRY glBindVertexBuffers(GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, - const GLsizei* strides); -#endif -#endif /* GL_VERSION_4_4 */ - -#ifndef GL_VERSION_4_5 -#define GL_VERSION_4_5 1 -#define GL_CONTEXT_LOST 0x0507 -#define GL_NEGATIVE_ONE_TO_ONE 0x935E -#define GL_ZERO_TO_ONE 0x935F -#define GL_CLIP_ORIGIN 0x935C -#define GL_CLIP_DEPTH_MODE 0x935D -#define GL_QUERY_WAIT_INVERTED 0x8E17 -#define GL_QUERY_NO_WAIT_INVERTED 0x8E18 -#define GL_QUERY_BY_REGION_WAIT_INVERTED 0x8E19 -#define GL_QUERY_BY_REGION_NO_WAIT_INVERTED 0x8E1A -#define GL_MAX_CULL_DISTANCES 0x82F9 -#define GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES 0x82FA -#define GL_TEXTURE_TARGET 0x1006 -#define GL_QUERY_TARGET 0x82EA -#define GL_GUILTY_CONTEXT_RESET 0x8253 -#define GL_INNOCENT_CONTEXT_RESET 0x8254 -#define GL_UNKNOWN_CONTEXT_RESET 0x8255 -#define GL_RESET_NOTIFICATION_STRATEGY 0x8256 -#define GL_LOSE_CONTEXT_ON_RESET 0x8252 -#define GL_NO_RESET_NOTIFICATION 0x8261 -#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT 0x00000004 -#define GL_CONTEXT_RELEASE_BEHAVIOR 0x82FB -#define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH 0x82FC -typedef void(APIENTRYP PFNGLCLIPCONTROLPROC)(GLenum origin, GLenum depth); -typedef void(APIENTRYP PFNGLCREATETRANSFORMFEEDBACKSPROC)(GLsizei n, GLuint* ids); -typedef void(APIENTRYP PFNGLTRANSFORMFEEDBACKBUFFERBASEPROC)(GLuint xfb, GLuint index, GLuint buffer); -typedef void(APIENTRYP PFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC)(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, - GLsizeiptr size); -typedef void(APIENTRYP PFNGLGETTRANSFORMFEEDBACKIVPROC)(GLuint xfb, GLenum pname, GLint* param); -typedef void(APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC)(GLuint xfb, GLenum pname, GLuint index, GLint* param); -typedef void(APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC)(GLuint xfb, GLenum pname, GLuint index, GLint64* param); -typedef void(APIENTRYP PFNGLCREATEBUFFERSPROC)(GLsizei n, GLuint* buffers); -typedef void(APIENTRYP PFNGLNAMEDBUFFERSTORAGEPROC)(GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags); -typedef void(APIENTRYP PFNGLNAMEDBUFFERDATAPROC)(GLuint buffer, GLsizeiptr size, const void* data, GLenum usage); -typedef void(APIENTRYP PFNGLNAMEDBUFFERSUBDATAPROC)(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data); -typedef void(APIENTRYP PFNGLCOPYNAMEDBUFFERSUBDATAPROC)(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, - GLintptr writeOffset, GLsizeiptr size); -typedef void(APIENTRYP PFNGLCLEARNAMEDBUFFERDATAPROC)(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, - const void* data); -typedef void(APIENTRYP PFNGLCLEARNAMEDBUFFERSUBDATAPROC)(GLuint buffer, GLenum internalformat, GLintptr offset, - GLsizeiptr size, GLenum format, GLenum type, const void* data); -typedef void*(APIENTRYP PFNGLMAPNAMEDBUFFERPROC)(GLuint buffer, GLenum access); -typedef void*(APIENTRYP PFNGLMAPNAMEDBUFFERRANGEPROC)(GLuint buffer, GLintptr offset, GLsizeiptr length, - GLbitfield access); -typedef GLboolean(APIENTRYP PFNGLUNMAPNAMEDBUFFERPROC)(GLuint buffer); -typedef void(APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC)(GLuint buffer, GLintptr offset, GLsizeiptr length); -typedef void(APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVPROC)(GLuint buffer, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERI64VPROC)(GLuint buffer, GLenum pname, GLint64* params); -typedef void(APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVPROC)(GLuint buffer, GLenum pname, void** params); -typedef void(APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAPROC)(GLuint buffer, GLintptr offset, GLsizeiptr size, void* data); -typedef void(APIENTRYP PFNGLCREATEFRAMEBUFFERSPROC)(GLsizei n, GLuint* framebuffers); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC)(GLuint framebuffer, GLenum attachment, - GLenum renderbuffertarget, GLuint renderbuffer); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERPARAMETERIPROC)(GLuint framebuffer, GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREPROC)(GLuint framebuffer, GLenum attachment, GLuint texture, - GLint level); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC)(GLuint framebuffer, GLenum attachment, GLuint texture, - GLint level, GLint layer); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC)(GLuint framebuffer, GLenum buf); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC)(GLuint framebuffer, GLsizei n, const GLenum* bufs); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC)(GLuint framebuffer, GLenum src); -typedef void(APIENTRYP PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC)(GLuint framebuffer, GLsizei numAttachments, - const GLenum* attachments); -typedef void(APIENTRYP PFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC)(GLuint framebuffer, GLsizei numAttachments, - const GLenum* attachments, GLint x, GLint y, - GLsizei width, GLsizei height); -typedef void(APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERIVPROC)(GLuint framebuffer, GLenum buffer, GLint drawbuffer, - const GLint* value); -typedef void(APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERUIVPROC)(GLuint framebuffer, GLenum buffer, GLint drawbuffer, - const GLuint* value); -typedef void(APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERFVPROC)(GLuint framebuffer, GLenum buffer, GLint drawbuffer, - const GLfloat* value); -typedef void(APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERFIPROC)(GLuint framebuffer, GLenum buffer, GLint drawbuffer, - GLfloat depth, GLint stencil); -typedef void(APIENTRYP PFNGLBLITNAMEDFRAMEBUFFERPROC)(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, - GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, - GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); -typedef GLenum(APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC)(GLuint framebuffer, GLenum target); -typedef void(APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC)(GLuint framebuffer, GLenum pname, GLint* param); -typedef void(APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC)(GLuint framebuffer, GLenum attachment, - GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLCREATERENDERBUFFERSPROC)(GLsizei n, GLuint* renderbuffers); -typedef void(APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEPROC)(GLuint renderbuffer, GLenum internalformat, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC)(GLuint renderbuffer, GLsizei samples, - GLenum internalformat, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC)(GLuint renderbuffer, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLCREATETEXTURESPROC)(GLenum target, GLsizei n, GLuint* textures); -typedef void(APIENTRYP PFNGLTEXTUREBUFFERPROC)(GLuint texture, GLenum internalformat, GLuint buffer); -typedef void(APIENTRYP PFNGLTEXTUREBUFFERRANGEPROC)(GLuint texture, GLenum internalformat, GLuint buffer, - GLintptr offset, GLsizeiptr size); -typedef void(APIENTRYP PFNGLTEXTURESTORAGE1DPROC)(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width); -typedef void(APIENTRYP PFNGLTEXTURESTORAGE2DPROC)(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLTEXTURESTORAGE3DPROC)(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, - GLsizei height, GLsizei depth); -typedef void(APIENTRYP PFNGLTEXTURESTORAGE2DMULTISAMPLEPROC)(GLuint texture, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height, - GLboolean fixedsamplelocations); -typedef void(APIENTRYP PFNGLTEXTURESTORAGE3DMULTISAMPLEPROC)(GLuint texture, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height, GLsizei depth, - GLboolean fixedsamplelocations); -typedef void(APIENTRYP PFNGLTEXTURESUBIMAGE1DPROC)(GLuint texture, GLint level, GLint xoffset, GLsizei width, - GLenum format, GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLTEXTURESUBIMAGE2DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLsizei width, GLsizei height, GLenum format, GLenum type, - const void* pixels); -typedef void(APIENTRYP PFNGLTEXTURESUBIMAGE3DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, - GLenum format, GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC)(GLuint texture, GLint level, GLint xoffset, GLsizei width, - GLenum format, GLsizei imageSize, const void* data); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLsizei width, GLsizei height, GLenum format, - GLsizei imageSize, const void* data); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, - GLsizei depth, GLenum format, GLsizei imageSize, - const void* data); -typedef void(APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DPROC)(GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, - GLsizei width); -typedef void(APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint x, GLint y, GLsizei width, GLsizei height); -typedef void(APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERFPROC)(GLuint texture, GLenum pname, GLfloat param); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERFVPROC)(GLuint texture, GLenum pname, const GLfloat* param); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERIPROC)(GLuint texture, GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERIIVPROC)(GLuint texture, GLenum pname, const GLint* params); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERIUIVPROC)(GLuint texture, GLenum pname, const GLuint* params); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERIVPROC)(GLuint texture, GLenum pname, const GLint* param); -typedef void(APIENTRYP PFNGLGENERATETEXTUREMIPMAPPROC)(GLuint texture); -typedef void(APIENTRYP PFNGLBINDTEXTUREUNITPROC)(GLuint unit, GLuint texture); -typedef void(APIENTRYP PFNGLGETTEXTUREIMAGEPROC)(GLuint texture, GLint level, GLenum format, GLenum type, - GLsizei bufSize, void* pixels); -typedef void(APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEPROC)(GLuint texture, GLint level, GLsizei bufSize, void* pixels); -typedef void(APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVPROC)(GLuint texture, GLint level, GLenum pname, GLfloat* params); -typedef void(APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVPROC)(GLuint texture, GLint level, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETTEXTUREPARAMETERFVPROC)(GLuint texture, GLenum pname, GLfloat* params); -typedef void(APIENTRYP PFNGLGETTEXTUREPARAMETERIIVPROC)(GLuint texture, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVPROC)(GLuint texture, GLenum pname, GLuint* params); -typedef void(APIENTRYP PFNGLGETTEXTUREPARAMETERIVPROC)(GLuint texture, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLCREATEVERTEXARRAYSPROC)(GLsizei n, GLuint* arrays); -typedef void(APIENTRYP PFNGLDISABLEVERTEXARRAYATTRIBPROC)(GLuint vaobj, GLuint index); -typedef void(APIENTRYP PFNGLENABLEVERTEXARRAYATTRIBPROC)(GLuint vaobj, GLuint index); -typedef void(APIENTRYP PFNGLVERTEXARRAYELEMENTBUFFERPROC)(GLuint vaobj, GLuint buffer); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXBUFFERPROC)(GLuint vaobj, GLuint bindingindex, GLuint buffer, - GLintptr offset, GLsizei stride); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXBUFFERSPROC)(GLuint vaobj, GLuint first, GLsizei count, - const GLuint* buffers, const GLintptr* offsets, - const GLsizei* strides); -typedef void(APIENTRYP PFNGLVERTEXARRAYATTRIBBINDINGPROC)(GLuint vaobj, GLuint attribindex, GLuint bindingindex); -typedef void(APIENTRYP PFNGLVERTEXARRAYATTRIBFORMATPROC)(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, - GLboolean normalized, GLuint relativeoffset); -typedef void(APIENTRYP PFNGLVERTEXARRAYATTRIBIFORMATPROC)(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, - GLuint relativeoffset); -typedef void(APIENTRYP PFNGLVERTEXARRAYATTRIBLFORMATPROC)(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, - GLuint relativeoffset); -typedef void(APIENTRYP PFNGLVERTEXARRAYBINDINGDIVISORPROC)(GLuint vaobj, GLuint bindingindex, GLuint divisor); -typedef void(APIENTRYP PFNGLGETVERTEXARRAYIVPROC)(GLuint vaobj, GLenum pname, GLint* param); -typedef void(APIENTRYP PFNGLGETVERTEXARRAYINDEXEDIVPROC)(GLuint vaobj, GLuint index, GLenum pname, GLint* param); -typedef void(APIENTRYP PFNGLGETVERTEXARRAYINDEXED64IVPROC)(GLuint vaobj, GLuint index, GLenum pname, GLint64* param); -typedef void(APIENTRYP PFNGLCREATESAMPLERSPROC)(GLsizei n, GLuint* samplers); -typedef void(APIENTRYP PFNGLCREATEPROGRAMPIPELINESPROC)(GLsizei n, GLuint* pipelines); -typedef void(APIENTRYP PFNGLCREATEQUERIESPROC)(GLenum target, GLsizei n, GLuint* ids); -typedef void(APIENTRYP PFNGLGETQUERYBUFFEROBJECTI64VPROC)(GLuint id, GLuint buffer, GLenum pname, GLintptr offset); -typedef void(APIENTRYP PFNGLGETQUERYBUFFEROBJECTIVPROC)(GLuint id, GLuint buffer, GLenum pname, GLintptr offset); -typedef void(APIENTRYP PFNGLGETQUERYBUFFEROBJECTUI64VPROC)(GLuint id, GLuint buffer, GLenum pname, GLintptr offset); -typedef void(APIENTRYP PFNGLGETQUERYBUFFEROBJECTUIVPROC)(GLuint id, GLuint buffer, GLenum pname, GLintptr offset); -typedef void(APIENTRYP PFNGLMEMORYBARRIERBYREGIONPROC)(GLbitfield barriers); -typedef void(APIENTRYP PFNGLGETTEXTURESUBIMAGEPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, - GLenum format, GLenum type, GLsizei bufSize, void* pixels); -typedef void(APIENTRYP PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, - GLsizei depth, GLsizei bufSize, void* pixels); -typedef GLenum(APIENTRYP PFNGLGETGRAPHICSRESETSTATUSPROC)(void); -typedef void(APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEPROC)(GLenum target, GLint lod, GLsizei bufSize, void* pixels); -typedef void(APIENTRYP PFNGLGETNTEXIMAGEPROC)(GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, - void* pixels); -typedef void(APIENTRYP PFNGLGETNUNIFORMDVPROC)(GLuint program, GLint location, GLsizei bufSize, GLdouble* params); -typedef void(APIENTRYP PFNGLGETNUNIFORMFVPROC)(GLuint program, GLint location, GLsizei bufSize, GLfloat* params); -typedef void(APIENTRYP PFNGLGETNUNIFORMIVPROC)(GLuint program, GLint location, GLsizei bufSize, GLint* params); -typedef void(APIENTRYP PFNGLGETNUNIFORMUIVPROC)(GLuint program, GLint location, GLsizei bufSize, GLuint* params); -typedef void(APIENTRYP PFNGLREADNPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, - GLenum type, GLsizei bufSize, void* data); -typedef void(APIENTRYP PFNGLTEXTUREBARRIERPROC)(void); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glClipControl(GLenum origin, GLenum depth); -GLAPI void APIENTRY glCreateTransformFeedbacks(GLsizei n, GLuint* ids); -GLAPI void APIENTRY glTransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer); -GLAPI void APIENTRY glTransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, - GLsizeiptr size); -GLAPI void APIENTRY glGetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param); -GLAPI void APIENTRY glGetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param); -GLAPI void APIENTRY glGetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param); -GLAPI void APIENTRY glCreateBuffers(GLsizei n, GLuint* buffers); -GLAPI void APIENTRY glNamedBufferStorage(GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags); -GLAPI void APIENTRY glNamedBufferData(GLuint buffer, GLsizeiptr size, const void* data, GLenum usage); -GLAPI void APIENTRY glNamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data); -GLAPI void APIENTRY glCopyNamedBufferSubData(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, - GLintptr writeOffset, GLsizeiptr size); -GLAPI void APIENTRY glClearNamedBufferData(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, - const void* data); -GLAPI void APIENTRY glClearNamedBufferSubData(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, - GLenum format, GLenum type, const void* data); -GLAPI void* APIENTRY glMapNamedBuffer(GLuint buffer, GLenum access); -GLAPI void* APIENTRY glMapNamedBufferRange(GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); -GLAPI GLboolean APIENTRY glUnmapNamedBuffer(GLuint buffer); -GLAPI void APIENTRY glFlushMappedNamedBufferRange(GLuint buffer, GLintptr offset, GLsizeiptr length); -GLAPI void APIENTRY glGetNamedBufferParameteriv(GLuint buffer, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetNamedBufferParameteri64v(GLuint buffer, GLenum pname, GLint64* params); -GLAPI void APIENTRY glGetNamedBufferPointerv(GLuint buffer, GLenum pname, void** params); -GLAPI void APIENTRY glGetNamedBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, void* data); -GLAPI void APIENTRY glCreateFramebuffers(GLsizei n, GLuint* framebuffers); -GLAPI void APIENTRY glNamedFramebufferRenderbuffer(GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, - GLuint renderbuffer); -GLAPI void APIENTRY glNamedFramebufferParameteri(GLuint framebuffer, GLenum pname, GLint param); -GLAPI void APIENTRY glNamedFramebufferTexture(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); -GLAPI void APIENTRY glNamedFramebufferTextureLayer(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, - GLint layer); -GLAPI void APIENTRY glNamedFramebufferDrawBuffer(GLuint framebuffer, GLenum buf); -GLAPI void APIENTRY glNamedFramebufferDrawBuffers(GLuint framebuffer, GLsizei n, const GLenum* bufs); -GLAPI void APIENTRY glNamedFramebufferReadBuffer(GLuint framebuffer, GLenum src); -GLAPI void APIENTRY glInvalidateNamedFramebufferData(GLuint framebuffer, GLsizei numAttachments, - const GLenum* attachments); -GLAPI void APIENTRY glInvalidateNamedFramebufferSubData(GLuint framebuffer, GLsizei numAttachments, - const GLenum* attachments, GLint x, GLint y, GLsizei width, - GLsizei height); -GLAPI void APIENTRY glClearNamedFramebufferiv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint* value); -GLAPI void APIENTRY glClearNamedFramebufferuiv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, - const GLuint* value); -GLAPI void APIENTRY glClearNamedFramebufferfv(GLuint framebuffer, GLenum buffer, GLint drawbuffer, - const GLfloat* value); -GLAPI void APIENTRY glClearNamedFramebufferfi(GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, - GLint stencil); -GLAPI void APIENTRY glBlitNamedFramebuffer(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, - GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, - GLbitfield mask, GLenum filter); -GLAPI GLenum APIENTRY glCheckNamedFramebufferStatus(GLuint framebuffer, GLenum target); -GLAPI void APIENTRY glGetNamedFramebufferParameteriv(GLuint framebuffer, GLenum pname, GLint* param); -GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameteriv(GLuint framebuffer, GLenum attachment, GLenum pname, - GLint* params); -GLAPI void APIENTRY glCreateRenderbuffers(GLsizei n, GLuint* renderbuffers); -GLAPI void APIENTRY glNamedRenderbufferStorage(GLuint renderbuffer, GLenum internalformat, GLsizei width, - GLsizei height); -GLAPI void APIENTRY glNamedRenderbufferStorageMultisample(GLuint renderbuffer, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height); -GLAPI void APIENTRY glGetNamedRenderbufferParameteriv(GLuint renderbuffer, GLenum pname, GLint* params); -GLAPI void APIENTRY glCreateTextures(GLenum target, GLsizei n, GLuint* textures); -GLAPI void APIENTRY glTextureBuffer(GLuint texture, GLenum internalformat, GLuint buffer); -GLAPI void APIENTRY glTextureBufferRange(GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, - GLsizeiptr size); -GLAPI void APIENTRY glTextureStorage1D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width); -GLAPI void APIENTRY glTextureStorage2D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, - GLsizei height); -GLAPI void APIENTRY glTextureStorage3D(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, - GLsizei height, GLsizei depth); -GLAPI void APIENTRY glTextureStorage2DMultisample(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, - GLsizei height, GLboolean fixedsamplelocations); -GLAPI void APIENTRY glTextureStorage3DMultisample(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, - GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); -GLAPI void APIENTRY glTextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, - GLenum type, const void* pixels); -GLAPI void APIENTRY glTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, - GLsizei height, GLenum format, GLenum type, const void* pixels); -GLAPI void APIENTRY glTextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, - const void* pixels); -GLAPI void APIENTRY glCompressedTextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLsizei width, - GLenum format, GLsizei imageSize, const void* data); -GLAPI void APIENTRY glCompressedTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, - const void* data); -GLAPI void APIENTRY glCompressedTextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, - GLenum format, GLsizei imageSize, const void* data); -GLAPI void APIENTRY glCopyTextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, - GLsizei width); -GLAPI void APIENTRY glCopyTextureSubImage2D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, - GLsizei width, GLsizei height); -GLAPI void APIENTRY glCopyTextureSubImage3D(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLint x, GLint y, GLsizei width, GLsizei height); -GLAPI void APIENTRY glTextureParameterf(GLuint texture, GLenum pname, GLfloat param); -GLAPI void APIENTRY glTextureParameterfv(GLuint texture, GLenum pname, const GLfloat* param); -GLAPI void APIENTRY glTextureParameteri(GLuint texture, GLenum pname, GLint param); -GLAPI void APIENTRY glTextureParameterIiv(GLuint texture, GLenum pname, const GLint* params); -GLAPI void APIENTRY glTextureParameterIuiv(GLuint texture, GLenum pname, const GLuint* params); -GLAPI void APIENTRY glTextureParameteriv(GLuint texture, GLenum pname, const GLint* param); -GLAPI void APIENTRY glGenerateTextureMipmap(GLuint texture); -GLAPI void APIENTRY glBindTextureUnit(GLuint unit, GLuint texture); -GLAPI void APIENTRY glGetTextureImage(GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, - void* pixels); -GLAPI void APIENTRY glGetCompressedTextureImage(GLuint texture, GLint level, GLsizei bufSize, void* pixels); -GLAPI void APIENTRY glGetTextureLevelParameterfv(GLuint texture, GLint level, GLenum pname, GLfloat* params); -GLAPI void APIENTRY glGetTextureLevelParameteriv(GLuint texture, GLint level, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params); -GLAPI void APIENTRY glGetTextureParameterIiv(GLuint texture, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetTextureParameterIuiv(GLuint texture, GLenum pname, GLuint* params); -GLAPI void APIENTRY glGetTextureParameteriv(GLuint texture, GLenum pname, GLint* params); -GLAPI void APIENTRY glCreateVertexArrays(GLsizei n, GLuint* arrays); -GLAPI void APIENTRY glDisableVertexArrayAttrib(GLuint vaobj, GLuint index); -GLAPI void APIENTRY glEnableVertexArrayAttrib(GLuint vaobj, GLuint index); -GLAPI void APIENTRY glVertexArrayElementBuffer(GLuint vaobj, GLuint buffer); -GLAPI void APIENTRY glVertexArrayVertexBuffer(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, - GLsizei stride); -GLAPI void APIENTRY glVertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers, - const GLintptr* offsets, const GLsizei* strides); -GLAPI void APIENTRY glVertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex); -GLAPI void APIENTRY glVertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, - GLboolean normalized, GLuint relativeoffset); -GLAPI void APIENTRY glVertexArrayAttribIFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, - GLuint relativeoffset); -GLAPI void APIENTRY glVertexArrayAttribLFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, - GLuint relativeoffset); -GLAPI void APIENTRY glVertexArrayBindingDivisor(GLuint vaobj, GLuint bindingindex, GLuint divisor); -GLAPI void APIENTRY glGetVertexArrayiv(GLuint vaobj, GLenum pname, GLint* param); -GLAPI void APIENTRY glGetVertexArrayIndexediv(GLuint vaobj, GLuint index, GLenum pname, GLint* param); -GLAPI void APIENTRY glGetVertexArrayIndexed64iv(GLuint vaobj, GLuint index, GLenum pname, GLint64* param); -GLAPI void APIENTRY glCreateSamplers(GLsizei n, GLuint* samplers); -GLAPI void APIENTRY glCreateProgramPipelines(GLsizei n, GLuint* pipelines); -GLAPI void APIENTRY glCreateQueries(GLenum target, GLsizei n, GLuint* ids); -GLAPI void APIENTRY glGetQueryBufferObjecti64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset); -GLAPI void APIENTRY glGetQueryBufferObjectiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset); -GLAPI void APIENTRY glGetQueryBufferObjectui64v(GLuint id, GLuint buffer, GLenum pname, GLintptr offset); -GLAPI void APIENTRY glGetQueryBufferObjectuiv(GLuint id, GLuint buffer, GLenum pname, GLintptr offset); -GLAPI void APIENTRY glMemoryBarrierByRegion(GLbitfield barriers); -GLAPI void APIENTRY glGetTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, - GLsizei bufSize, void* pixels); -GLAPI void APIENTRY glGetCompressedTextureSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, - GLsizei bufSize, void* pixels); -GLAPI GLenum APIENTRY glGetGraphicsResetStatus(void); -GLAPI void APIENTRY glGetnCompressedTexImage(GLenum target, GLint lod, GLsizei bufSize, void* pixels); -GLAPI void APIENTRY glGetnTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, - void* pixels); -GLAPI void APIENTRY glGetnUniformdv(GLuint program, GLint location, GLsizei bufSize, GLdouble* params); -GLAPI void APIENTRY glGetnUniformfv(GLuint program, GLint location, GLsizei bufSize, GLfloat* params); -GLAPI void APIENTRY glGetnUniformiv(GLuint program, GLint location, GLsizei bufSize, GLint* params); -GLAPI void APIENTRY glGetnUniformuiv(GLuint program, GLint location, GLsizei bufSize, GLuint* params); -GLAPI void APIENTRY glReadnPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, - GLsizei bufSize, void* data); -GLAPI void APIENTRY glTextureBarrier(void); -#endif -#endif /* GL_VERSION_4_5 */ - -#ifndef GL_ARB_ES2_compatibility -#define GL_ARB_ES2_compatibility 1 -#endif /* GL_ARB_ES2_compatibility */ - -#ifndef GL_ARB_ES3_1_compatibility -#define GL_ARB_ES3_1_compatibility 1 -#endif /* GL_ARB_ES3_1_compatibility */ - -#ifndef GL_ARB_ES3_2_compatibility -#define GL_ARB_ES3_2_compatibility 1 -#define GL_PRIMITIVE_BOUNDING_BOX_ARB 0x92BE -#define GL_MULTISAMPLE_LINE_WIDTH_RANGE_ARB 0x9381 -#define GL_MULTISAMPLE_LINE_WIDTH_GRANULARITY_ARB 0x9382 -typedef void(APIENTRYP PFNGLPRIMITIVEBOUNDINGBOXARBPROC)(GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, - GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glPrimitiveBoundingBoxARB(GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, - GLfloat maxY, GLfloat maxZ, GLfloat maxW); -#endif -#endif /* GL_ARB_ES3_2_compatibility */ - -#ifndef GL_ARB_ES3_compatibility -#define GL_ARB_ES3_compatibility 1 -#endif /* GL_ARB_ES3_compatibility */ - -#ifndef GL_ARB_arrays_of_arrays -#define GL_ARB_arrays_of_arrays 1 -#endif /* GL_ARB_arrays_of_arrays */ - -#ifndef GL_ARB_base_instance -#define GL_ARB_base_instance 1 -#endif /* GL_ARB_base_instance */ - -#ifndef GL_ARB_bindless_texture -#define GL_ARB_bindless_texture 1 -typedef uint64_t GLuint64EXT; -#define GL_UNSIGNED_INT64_ARB 0x140F -typedef GLuint64(APIENTRYP PFNGLGETTEXTUREHANDLEARBPROC)(GLuint texture); -typedef GLuint64(APIENTRYP PFNGLGETTEXTURESAMPLERHANDLEARBPROC)(GLuint texture, GLuint sampler); -typedef void(APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTARBPROC)(GLuint64 handle); -typedef void(APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC)(GLuint64 handle); -typedef GLuint64(APIENTRYP PFNGLGETIMAGEHANDLEARBPROC)(GLuint texture, GLint level, GLboolean layered, GLint layer, - GLenum format); -typedef void(APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTARBPROC)(GLuint64 handle, GLenum access); -typedef void(APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC)(GLuint64 handle); -typedef void(APIENTRYP PFNGLUNIFORMHANDLEUI64ARBPROC)(GLint location, GLuint64 value); -typedef void(APIENTRYP PFNGLUNIFORMHANDLEUI64VARBPROC)(GLint location, GLsizei count, const GLuint64* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC)(GLuint program, GLint location, GLuint64 value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64* values); -typedef GLboolean(APIENTRYP PFNGLISTEXTUREHANDLERESIDENTARBPROC)(GLuint64 handle); -typedef GLboolean(APIENTRYP PFNGLISIMAGEHANDLERESIDENTARBPROC)(GLuint64 handle); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL1UI64ARBPROC)(GLuint index, GLuint64EXT x); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL1UI64VARBPROC)(GLuint index, const GLuint64EXT* v); -typedef void(APIENTRYP PFNGLGETVERTEXATTRIBLUI64VARBPROC)(GLuint index, GLenum pname, GLuint64EXT* params); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI GLuint64 APIENTRY glGetTextureHandleARB(GLuint texture); -GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleARB(GLuint texture, GLuint sampler); -GLAPI void APIENTRY glMakeTextureHandleResidentARB(GLuint64 handle); -GLAPI void APIENTRY glMakeTextureHandleNonResidentARB(GLuint64 handle); -GLAPI GLuint64 APIENTRY glGetImageHandleARB(GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); -GLAPI void APIENTRY glMakeImageHandleResidentARB(GLuint64 handle, GLenum access); -GLAPI void APIENTRY glMakeImageHandleNonResidentARB(GLuint64 handle); -GLAPI void APIENTRY glUniformHandleui64ARB(GLint location, GLuint64 value); -GLAPI void APIENTRY glUniformHandleui64vARB(GLint location, GLsizei count, const GLuint64* value); -GLAPI void APIENTRY glProgramUniformHandleui64ARB(GLuint program, GLint location, GLuint64 value); -GLAPI void APIENTRY glProgramUniformHandleui64vARB(GLuint program, GLint location, GLsizei count, - const GLuint64* values); -GLAPI GLboolean APIENTRY glIsTextureHandleResidentARB(GLuint64 handle); -GLAPI GLboolean APIENTRY glIsImageHandleResidentARB(GLuint64 handle); -GLAPI void APIENTRY glVertexAttribL1ui64ARB(GLuint index, GLuint64EXT x); -GLAPI void APIENTRY glVertexAttribL1ui64vARB(GLuint index, const GLuint64EXT* v); -GLAPI void APIENTRY glGetVertexAttribLui64vARB(GLuint index, GLenum pname, GLuint64EXT* params); -#endif -#endif /* GL_ARB_bindless_texture */ - -#ifndef GL_ARB_blend_func_extended -#define GL_ARB_blend_func_extended 1 -#endif /* GL_ARB_blend_func_extended */ - -#ifndef GL_ARB_buffer_storage -#define GL_ARB_buffer_storage 1 -#endif /* GL_ARB_buffer_storage */ - -#ifndef GL_ARB_cl_event -#define GL_ARB_cl_event 1 -struct _cl_context; -struct _cl_event; -#define GL_SYNC_CL_EVENT_ARB 0x8240 -#define GL_SYNC_CL_EVENT_COMPLETE_ARB 0x8241 -typedef GLsync(APIENTRYP PFNGLCREATESYNCFROMCLEVENTARBPROC)(struct _cl_context* context, struct _cl_event* event, - GLbitfield flags); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI GLsync APIENTRY glCreateSyncFromCLeventARB(struct _cl_context* context, struct _cl_event* event, - GLbitfield flags); -#endif -#endif /* GL_ARB_cl_event */ - -#ifndef GL_ARB_clear_buffer_object -#define GL_ARB_clear_buffer_object 1 -#endif /* GL_ARB_clear_buffer_object */ - -#ifndef GL_ARB_clear_texture -#define GL_ARB_clear_texture 1 -#endif /* GL_ARB_clear_texture */ - -#ifndef GL_ARB_clip_control -#define GL_ARB_clip_control 1 -#endif /* GL_ARB_clip_control */ - -#ifndef GL_ARB_compressed_texture_pixel_storage -#define GL_ARB_compressed_texture_pixel_storage 1 -#endif /* GL_ARB_compressed_texture_pixel_storage */ - -#ifndef GL_ARB_compute_shader -#define GL_ARB_compute_shader 1 -#endif /* GL_ARB_compute_shader */ - -#ifndef GL_ARB_compute_variable_group_size -#define GL_ARB_compute_variable_group_size 1 -#define GL_MAX_COMPUTE_VARIABLE_GROUP_INVOCATIONS_ARB 0x9344 -#define GL_MAX_COMPUTE_FIXED_GROUP_INVOCATIONS_ARB 0x90EB -#define GL_MAX_COMPUTE_VARIABLE_GROUP_SIZE_ARB 0x9345 -#define GL_MAX_COMPUTE_FIXED_GROUP_SIZE_ARB 0x91BF -typedef void(APIENTRYP PFNGLDISPATCHCOMPUTEGROUPSIZEARBPROC)(GLuint num_groups_x, GLuint num_groups_y, - GLuint num_groups_z, GLuint group_size_x, - GLuint group_size_y, GLuint group_size_z); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glDispatchComputeGroupSizeARB(GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z, - GLuint group_size_x, GLuint group_size_y, GLuint group_size_z); -#endif -#endif /* GL_ARB_compute_variable_group_size */ - -#ifndef GL_ARB_conditional_render_inverted -#define GL_ARB_conditional_render_inverted 1 -#endif /* GL_ARB_conditional_render_inverted */ - -#ifndef GL_ARB_conservative_depth -#define GL_ARB_conservative_depth 1 -#endif /* GL_ARB_conservative_depth */ - -#ifndef GL_ARB_copy_buffer -#define GL_ARB_copy_buffer 1 -#endif /* GL_ARB_copy_buffer */ - -#ifndef GL_ARB_copy_image -#define GL_ARB_copy_image 1 -#endif /* GL_ARB_copy_image */ - -#ifndef GL_ARB_cull_distance -#define GL_ARB_cull_distance 1 -#endif /* GL_ARB_cull_distance */ - -#ifndef GL_ARB_debug_output -#define GL_ARB_debug_output 1 -typedef void(APIENTRY* GLDEBUGPROCARB)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, - const GLchar* message, const void* userParam); -#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 -#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 -#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 -#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245 -#define GL_DEBUG_SOURCE_API_ARB 0x8246 -#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247 -#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248 -#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249 -#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A -#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B -#define GL_DEBUG_TYPE_ERROR_ARB 0x824C -#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D -#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E -#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F -#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250 -#define GL_DEBUG_TYPE_OTHER_ARB 0x8251 -#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143 -#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144 -#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145 -#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146 -#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 -#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 -typedef void(APIENTRYP PFNGLDEBUGMESSAGECONTROLARBPROC)(GLenum source, GLenum type, GLenum severity, GLsizei count, - const GLuint* ids, GLboolean enabled); -typedef void(APIENTRYP PFNGLDEBUGMESSAGEINSERTARBPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, - GLsizei length, const GLchar* buf); -typedef void(APIENTRYP PFNGLDEBUGMESSAGECALLBACKARBPROC)(GLDEBUGPROCARB callback, const void* userParam); -typedef GLuint(APIENTRYP PFNGLGETDEBUGMESSAGELOGARBPROC)(GLuint count, GLsizei bufSize, GLenum* sources, GLenum* types, - GLuint* ids, GLenum* severities, GLsizei* lengths, - GLchar* messageLog); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glDebugMessageControlARB(GLenum source, GLenum type, GLenum severity, GLsizei count, - const GLuint* ids, GLboolean enabled); -GLAPI void APIENTRY glDebugMessageInsertARB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, - const GLchar* buf); -GLAPI void APIENTRY glDebugMessageCallbackARB(GLDEBUGPROCARB callback, const void* userParam); -GLAPI GLuint APIENTRY glGetDebugMessageLogARB(GLuint count, GLsizei bufSize, GLenum* sources, GLenum* types, - GLuint* ids, GLenum* severities, GLsizei* lengths, GLchar* messageLog); -#endif -#endif /* GL_ARB_debug_output */ - -#ifndef GL_ARB_depth_buffer_float -#define GL_ARB_depth_buffer_float 1 -#endif /* GL_ARB_depth_buffer_float */ - -#ifndef GL_ARB_depth_clamp -#define GL_ARB_depth_clamp 1 -#endif /* GL_ARB_depth_clamp */ - -#ifndef GL_ARB_derivative_control -#define GL_ARB_derivative_control 1 -#endif /* GL_ARB_derivative_control */ - -#ifndef GL_ARB_direct_state_access -#define GL_ARB_direct_state_access 1 -#endif /* GL_ARB_direct_state_access */ - -#ifndef GL_ARB_draw_buffers_blend -#define GL_ARB_draw_buffers_blend 1 -typedef void(APIENTRYP PFNGLBLENDEQUATIONIARBPROC)(GLuint buf, GLenum mode); -typedef void(APIENTRYP PFNGLBLENDEQUATIONSEPARATEIARBPROC)(GLuint buf, GLenum modeRGB, GLenum modeAlpha); -typedef void(APIENTRYP PFNGLBLENDFUNCIARBPROC)(GLuint buf, GLenum src, GLenum dst); -typedef void(APIENTRYP PFNGLBLENDFUNCSEPARATEIARBPROC)(GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, - GLenum dstAlpha); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBlendEquationiARB(GLuint buf, GLenum mode); -GLAPI void APIENTRY glBlendEquationSeparateiARB(GLuint buf, GLenum modeRGB, GLenum modeAlpha); -GLAPI void APIENTRY glBlendFunciARB(GLuint buf, GLenum src, GLenum dst); -GLAPI void APIENTRY glBlendFuncSeparateiARB(GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha); -#endif -#endif /* GL_ARB_draw_buffers_blend */ - -#ifndef GL_ARB_draw_elements_base_vertex -#define GL_ARB_draw_elements_base_vertex 1 -#endif /* GL_ARB_draw_elements_base_vertex */ - -#ifndef GL_ARB_draw_indirect -#define GL_ARB_draw_indirect 1 -#endif /* GL_ARB_draw_indirect */ - -#ifndef GL_ARB_draw_instanced -#define GL_ARB_draw_instanced 1 -typedef void(APIENTRYP PFNGLDRAWARRAYSINSTANCEDARBPROC)(GLenum mode, GLint first, GLsizei count, GLsizei primcount); -typedef void(APIENTRYP PFNGLDRAWELEMENTSINSTANCEDARBPROC)(GLenum mode, GLsizei count, GLenum type, const void* indices, - GLsizei primcount); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glDrawArraysInstancedARB(GLenum mode, GLint first, GLsizei count, GLsizei primcount); -GLAPI void APIENTRY glDrawElementsInstancedARB(GLenum mode, GLsizei count, GLenum type, const void* indices, - GLsizei primcount); -#endif -#endif /* GL_ARB_draw_instanced */ - -#ifndef GL_ARB_enhanced_layouts -#define GL_ARB_enhanced_layouts 1 -#endif /* GL_ARB_enhanced_layouts */ - -#ifndef GL_ARB_explicit_attrib_location -#define GL_ARB_explicit_attrib_location 1 -#endif /* GL_ARB_explicit_attrib_location */ - -#ifndef GL_ARB_explicit_uniform_location -#define GL_ARB_explicit_uniform_location 1 -#endif /* GL_ARB_explicit_uniform_location */ - -#ifndef GL_ARB_fragment_coord_conventions -#define GL_ARB_fragment_coord_conventions 1 -#endif /* GL_ARB_fragment_coord_conventions */ - -#ifndef GL_ARB_fragment_layer_viewport -#define GL_ARB_fragment_layer_viewport 1 -#endif /* GL_ARB_fragment_layer_viewport */ - -#ifndef GL_ARB_fragment_shader_interlock -#define GL_ARB_fragment_shader_interlock 1 -#endif /* GL_ARB_fragment_shader_interlock */ - -#ifndef GL_ARB_framebuffer_no_attachments -#define GL_ARB_framebuffer_no_attachments 1 -#endif /* GL_ARB_framebuffer_no_attachments */ - -#ifndef GL_ARB_framebuffer_object -#define GL_ARB_framebuffer_object 1 -#endif /* GL_ARB_framebuffer_object */ - -#ifndef GL_ARB_framebuffer_sRGB -#define GL_ARB_framebuffer_sRGB 1 -#endif /* GL_ARB_framebuffer_sRGB */ - -#ifndef GL_ARB_geometry_shader4 -#define GL_ARB_geometry_shader4 1 -#define GL_LINES_ADJACENCY_ARB 0x000A -#define GL_LINE_STRIP_ADJACENCY_ARB 0x000B -#define GL_TRIANGLES_ADJACENCY_ARB 0x000C -#define GL_TRIANGLE_STRIP_ADJACENCY_ARB 0x000D -#define GL_PROGRAM_POINT_SIZE_ARB 0x8642 -#define GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB 0x8C29 -#define GL_FRAMEBUFFER_ATTACHMENT_LAYERED_ARB 0x8DA7 -#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_ARB 0x8DA8 -#define GL_FRAMEBUFFER_INCOMPLETE_LAYER_COUNT_ARB 0x8DA9 -#define GL_GEOMETRY_SHADER_ARB 0x8DD9 -#define GL_GEOMETRY_VERTICES_OUT_ARB 0x8DDA -#define GL_GEOMETRY_INPUT_TYPE_ARB 0x8DDB -#define GL_GEOMETRY_OUTPUT_TYPE_ARB 0x8DDC -#define GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB 0x8DDD -#define GL_MAX_VERTEX_VARYING_COMPONENTS_ARB 0x8DDE -#define GL_MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB 0x8DDF -#define GL_MAX_GEOMETRY_OUTPUT_VERTICES_ARB 0x8DE0 -#define GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB 0x8DE1 -typedef void(APIENTRYP PFNGLPROGRAMPARAMETERIARBPROC)(GLuint program, GLenum pname, GLint value); -typedef void(APIENTRYP PFNGLFRAMEBUFFERTEXTUREARBPROC)(GLenum target, GLenum attachment, GLuint texture, GLint level); -typedef void(APIENTRYP PFNGLFRAMEBUFFERTEXTURELAYERARBPROC)(GLenum target, GLenum attachment, GLuint texture, - GLint level, GLint layer); -typedef void(APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEARBPROC)(GLenum target, GLenum attachment, GLuint texture, - GLint level, GLenum face); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glProgramParameteriARB(GLuint program, GLenum pname, GLint value); -GLAPI void APIENTRY glFramebufferTextureARB(GLenum target, GLenum attachment, GLuint texture, GLint level); -GLAPI void APIENTRY glFramebufferTextureLayerARB(GLenum target, GLenum attachment, GLuint texture, GLint level, - GLint layer); -GLAPI void APIENTRY glFramebufferTextureFaceARB(GLenum target, GLenum attachment, GLuint texture, GLint level, - GLenum face); -#endif -#endif /* GL_ARB_geometry_shader4 */ - -#ifndef GL_ARB_get_program_binary -#define GL_ARB_get_program_binary 1 -#endif /* GL_ARB_get_program_binary */ - -#ifndef GL_ARB_get_texture_sub_image -#define GL_ARB_get_texture_sub_image 1 -#endif /* GL_ARB_get_texture_sub_image */ - -#ifndef GL_ARB_gl_spirv -#define GL_ARB_gl_spirv 1 -#define GL_SHADER_BINARY_FORMAT_SPIR_V_ARB 0x9551 -#define GL_SPIR_V_BINARY_ARB 0x9552 -typedef void(APIENTRYP PFNGLSPECIALIZESHADERARBPROC)(GLuint shader, const GLchar* pEntryPoint, - GLuint numSpecializationConstants, const GLuint* pConstantIndex, - const GLuint* pConstantValue); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glSpecializeShaderARB(GLuint shader, const GLchar* pEntryPoint, GLuint numSpecializationConstants, - const GLuint* pConstantIndex, const GLuint* pConstantValue); -#endif -#endif /* GL_ARB_gl_spirv */ - -#ifndef GL_ARB_gpu_shader5 -#define GL_ARB_gpu_shader5 1 -#endif /* GL_ARB_gpu_shader5 */ - -#ifndef GL_ARB_gpu_shader_fp64 -#define GL_ARB_gpu_shader_fp64 1 -#endif /* GL_ARB_gpu_shader_fp64 */ - -#ifndef GL_ARB_gpu_shader_int64 -#define GL_ARB_gpu_shader_int64 1 -#define GL_INT64_ARB 0x140E -#define GL_INT64_VEC2_ARB 0x8FE9 -#define GL_INT64_VEC3_ARB 0x8FEA -#define GL_INT64_VEC4_ARB 0x8FEB -#define GL_UNSIGNED_INT64_VEC2_ARB 0x8FF5 -#define GL_UNSIGNED_INT64_VEC3_ARB 0x8FF6 -#define GL_UNSIGNED_INT64_VEC4_ARB 0x8FF7 -typedef void(APIENTRYP PFNGLUNIFORM1I64ARBPROC)(GLint location, GLint64 x); -typedef void(APIENTRYP PFNGLUNIFORM2I64ARBPROC)(GLint location, GLint64 x, GLint64 y); -typedef void(APIENTRYP PFNGLUNIFORM3I64ARBPROC)(GLint location, GLint64 x, GLint64 y, GLint64 z); -typedef void(APIENTRYP PFNGLUNIFORM4I64ARBPROC)(GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); -typedef void(APIENTRYP PFNGLUNIFORM1I64VARBPROC)(GLint location, GLsizei count, const GLint64* value); -typedef void(APIENTRYP PFNGLUNIFORM2I64VARBPROC)(GLint location, GLsizei count, const GLint64* value); -typedef void(APIENTRYP PFNGLUNIFORM3I64VARBPROC)(GLint location, GLsizei count, const GLint64* value); -typedef void(APIENTRYP PFNGLUNIFORM4I64VARBPROC)(GLint location, GLsizei count, const GLint64* value); -typedef void(APIENTRYP PFNGLUNIFORM1UI64ARBPROC)(GLint location, GLuint64 x); -typedef void(APIENTRYP PFNGLUNIFORM2UI64ARBPROC)(GLint location, GLuint64 x, GLuint64 y); -typedef void(APIENTRYP PFNGLUNIFORM3UI64ARBPROC)(GLint location, GLuint64 x, GLuint64 y, GLuint64 z); -typedef void(APIENTRYP PFNGLUNIFORM4UI64ARBPROC)(GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); -typedef void(APIENTRYP PFNGLUNIFORM1UI64VARBPROC)(GLint location, GLsizei count, const GLuint64* value); -typedef void(APIENTRYP PFNGLUNIFORM2UI64VARBPROC)(GLint location, GLsizei count, const GLuint64* value); -typedef void(APIENTRYP PFNGLUNIFORM3UI64VARBPROC)(GLint location, GLsizei count, const GLuint64* value); -typedef void(APIENTRYP PFNGLUNIFORM4UI64VARBPROC)(GLint location, GLsizei count, const GLuint64* value); -typedef void(APIENTRYP PFNGLGETUNIFORMI64VARBPROC)(GLuint program, GLint location, GLint64* params); -typedef void(APIENTRYP PFNGLGETUNIFORMUI64VARBPROC)(GLuint program, GLint location, GLuint64* params); -typedef void(APIENTRYP PFNGLGETNUNIFORMI64VARBPROC)(GLuint program, GLint location, GLsizei bufSize, GLint64* params); -typedef void(APIENTRYP PFNGLGETNUNIFORMUI64VARBPROC)(GLuint program, GLint location, GLsizei bufSize, GLuint64* params); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1I64ARBPROC)(GLuint program, GLint location, GLint64 x); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2I64ARBPROC)(GLuint program, GLint location, GLint64 x, GLint64 y); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3I64ARBPROC)(GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4I64ARBPROC)(GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z, - GLint64 w); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1I64VARBPROC)(GLuint program, GLint location, GLsizei count, - const GLint64* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2I64VARBPROC)(GLuint program, GLint location, GLsizei count, - const GLint64* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3I64VARBPROC)(GLuint program, GLint location, GLsizei count, - const GLint64* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4I64VARBPROC)(GLuint program, GLint location, GLsizei count, - const GLint64* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1UI64ARBPROC)(GLuint program, GLint location, GLuint64 x); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2UI64ARBPROC)(GLuint program, GLint location, GLuint64 x, GLuint64 y); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3UI64ARBPROC)(GLuint program, GLint location, GLuint64 x, GLuint64 y, - GLuint64 z); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4UI64ARBPROC)(GLuint program, GLint location, GLuint64 x, GLuint64 y, - GLuint64 z, GLuint64 w); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1UI64VARBPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2UI64VARBPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3UI64VARBPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4UI64VARBPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64* value); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glUniform1i64ARB(GLint location, GLint64 x); -GLAPI void APIENTRY glUniform2i64ARB(GLint location, GLint64 x, GLint64 y); -GLAPI void APIENTRY glUniform3i64ARB(GLint location, GLint64 x, GLint64 y, GLint64 z); -GLAPI void APIENTRY glUniform4i64ARB(GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); -GLAPI void APIENTRY glUniform1i64vARB(GLint location, GLsizei count, const GLint64* value); -GLAPI void APIENTRY glUniform2i64vARB(GLint location, GLsizei count, const GLint64* value); -GLAPI void APIENTRY glUniform3i64vARB(GLint location, GLsizei count, const GLint64* value); -GLAPI void APIENTRY glUniform4i64vARB(GLint location, GLsizei count, const GLint64* value); -GLAPI void APIENTRY glUniform1ui64ARB(GLint location, GLuint64 x); -GLAPI void APIENTRY glUniform2ui64ARB(GLint location, GLuint64 x, GLuint64 y); -GLAPI void APIENTRY glUniform3ui64ARB(GLint location, GLuint64 x, GLuint64 y, GLuint64 z); -GLAPI void APIENTRY glUniform4ui64ARB(GLint location, GLuint64 x, GLuint64 y, GLuint64 z, GLuint64 w); -GLAPI void APIENTRY glUniform1ui64vARB(GLint location, GLsizei count, const GLuint64* value); -GLAPI void APIENTRY glUniform2ui64vARB(GLint location, GLsizei count, const GLuint64* value); -GLAPI void APIENTRY glUniform3ui64vARB(GLint location, GLsizei count, const GLuint64* value); -GLAPI void APIENTRY glUniform4ui64vARB(GLint location, GLsizei count, const GLuint64* value); -GLAPI void APIENTRY glGetUniformi64vARB(GLuint program, GLint location, GLint64* params); -GLAPI void APIENTRY glGetUniformui64vARB(GLuint program, GLint location, GLuint64* params); -GLAPI void APIENTRY glGetnUniformi64vARB(GLuint program, GLint location, GLsizei bufSize, GLint64* params); -GLAPI void APIENTRY glGetnUniformui64vARB(GLuint program, GLint location, GLsizei bufSize, GLuint64* params); -GLAPI void APIENTRY glProgramUniform1i64ARB(GLuint program, GLint location, GLint64 x); -GLAPI void APIENTRY glProgramUniform2i64ARB(GLuint program, GLint location, GLint64 x, GLint64 y); -GLAPI void APIENTRY glProgramUniform3i64ARB(GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z); -GLAPI void APIENTRY glProgramUniform4i64ARB(GLuint program, GLint location, GLint64 x, GLint64 y, GLint64 z, GLint64 w); -GLAPI void APIENTRY glProgramUniform1i64vARB(GLuint program, GLint location, GLsizei count, const GLint64* value); -GLAPI void APIENTRY glProgramUniform2i64vARB(GLuint program, GLint location, GLsizei count, const GLint64* value); -GLAPI void APIENTRY glProgramUniform3i64vARB(GLuint program, GLint location, GLsizei count, const GLint64* value); -GLAPI void APIENTRY glProgramUniform4i64vARB(GLuint program, GLint location, GLsizei count, const GLint64* value); -GLAPI void APIENTRY glProgramUniform1ui64ARB(GLuint program, GLint location, GLuint64 x); -GLAPI void APIENTRY glProgramUniform2ui64ARB(GLuint program, GLint location, GLuint64 x, GLuint64 y); -GLAPI void APIENTRY glProgramUniform3ui64ARB(GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z); -GLAPI void APIENTRY glProgramUniform4ui64ARB(GLuint program, GLint location, GLuint64 x, GLuint64 y, GLuint64 z, - GLuint64 w); -GLAPI void APIENTRY glProgramUniform1ui64vARB(GLuint program, GLint location, GLsizei count, const GLuint64* value); -GLAPI void APIENTRY glProgramUniform2ui64vARB(GLuint program, GLint location, GLsizei count, const GLuint64* value); -GLAPI void APIENTRY glProgramUniform3ui64vARB(GLuint program, GLint location, GLsizei count, const GLuint64* value); -GLAPI void APIENTRY glProgramUniform4ui64vARB(GLuint program, GLint location, GLsizei count, const GLuint64* value); -#endif -#endif /* GL_ARB_gpu_shader_int64 */ - -#ifndef GL_ARB_half_float_vertex -#define GL_ARB_half_float_vertex 1 -#endif /* GL_ARB_half_float_vertex */ - -#ifndef GL_ARB_imaging -#define GL_ARB_imaging 1 -#define GL_BLEND_COLOR 0x8005 -#define GL_BLEND_EQUATION 0x8009 -#endif /* GL_ARB_imaging */ - -#ifndef GL_ARB_indirect_parameters -#define GL_ARB_indirect_parameters 1 -#define GL_PARAMETER_BUFFER_ARB 0x80EE -#define GL_PARAMETER_BUFFER_BINDING_ARB 0x80EF -typedef void(APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTCOUNTARBPROC)(GLenum mode, GLintptr indirect, GLintptr drawcount, - GLsizei maxdrawcount, GLsizei stride); -typedef void(APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTARBPROC)(GLenum mode, GLenum type, GLintptr indirect, - GLintptr drawcount, GLsizei maxdrawcount, - GLsizei stride); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glMultiDrawArraysIndirectCountARB(GLenum mode, GLintptr indirect, GLintptr drawcount, - GLsizei maxdrawcount, GLsizei stride); -GLAPI void APIENTRY glMultiDrawElementsIndirectCountARB(GLenum mode, GLenum type, GLintptr indirect, GLintptr drawcount, - GLsizei maxdrawcount, GLsizei stride); -#endif -#endif /* GL_ARB_indirect_parameters */ - -#ifndef GL_ARB_instanced_arrays -#define GL_ARB_instanced_arrays 1 -#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB 0x88FE -typedef void(APIENTRYP PFNGLVERTEXATTRIBDIVISORARBPROC)(GLuint index, GLuint divisor); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glVertexAttribDivisorARB(GLuint index, GLuint divisor); -#endif -#endif /* GL_ARB_instanced_arrays */ - -#ifndef GL_ARB_internalformat_query -#define GL_ARB_internalformat_query 1 -#endif /* GL_ARB_internalformat_query */ - -#ifndef GL_ARB_internalformat_query2 -#define GL_ARB_internalformat_query2 1 -#define GL_SRGB_DECODE_ARB 0x8299 -#endif /* GL_ARB_internalformat_query2 */ - -#ifndef GL_ARB_invalidate_subdata -#define GL_ARB_invalidate_subdata 1 -#endif /* GL_ARB_invalidate_subdata */ - -#ifndef GL_ARB_map_buffer_alignment -#define GL_ARB_map_buffer_alignment 1 -#endif /* GL_ARB_map_buffer_alignment */ - -#ifndef GL_ARB_map_buffer_range -#define GL_ARB_map_buffer_range 1 -#endif /* GL_ARB_map_buffer_range */ - -#ifndef GL_ARB_multi_bind -#define GL_ARB_multi_bind 1 -#endif /* GL_ARB_multi_bind */ - -#ifndef GL_ARB_multi_draw_indirect -#define GL_ARB_multi_draw_indirect 1 -#endif /* GL_ARB_multi_draw_indirect */ - -#ifndef GL_ARB_occlusion_query2 -#define GL_ARB_occlusion_query2 1 -#endif /* GL_ARB_occlusion_query2 */ - -#ifndef GL_ARB_parallel_shader_compile -#define GL_ARB_parallel_shader_compile 1 -#define GL_MAX_SHADER_COMPILER_THREADS_ARB 0x91B0 -#define GL_COMPLETION_STATUS_ARB 0x91B1 -typedef void(APIENTRYP PFNGLMAXSHADERCOMPILERTHREADSARBPROC)(GLuint count); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glMaxShaderCompilerThreadsARB(GLuint count); -#endif -#endif /* GL_ARB_parallel_shader_compile */ - -#ifndef GL_ARB_pipeline_statistics_query -#define GL_ARB_pipeline_statistics_query 1 -#define GL_VERTICES_SUBMITTED_ARB 0x82EE -#define GL_PRIMITIVES_SUBMITTED_ARB 0x82EF -#define GL_VERTEX_SHADER_INVOCATIONS_ARB 0x82F0 -#define GL_TESS_CONTROL_SHADER_PATCHES_ARB 0x82F1 -#define GL_TESS_EVALUATION_SHADER_INVOCATIONS_ARB 0x82F2 -#define GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED_ARB 0x82F3 -#define GL_FRAGMENT_SHADER_INVOCATIONS_ARB 0x82F4 -#define GL_COMPUTE_SHADER_INVOCATIONS_ARB 0x82F5 -#define GL_CLIPPING_INPUT_PRIMITIVES_ARB 0x82F6 -#define GL_CLIPPING_OUTPUT_PRIMITIVES_ARB 0x82F7 -#endif /* GL_ARB_pipeline_statistics_query */ - -#ifndef GL_ARB_pixel_buffer_object -#define GL_ARB_pixel_buffer_object 1 -#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB -#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC -#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED -#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF -#endif /* GL_ARB_pixel_buffer_object */ - -#ifndef GL_ARB_post_depth_coverage -#define GL_ARB_post_depth_coverage 1 -#endif /* GL_ARB_post_depth_coverage */ - -#ifndef GL_ARB_program_interface_query -#define GL_ARB_program_interface_query 1 -#endif /* GL_ARB_program_interface_query */ - -#ifndef GL_ARB_provoking_vertex -#define GL_ARB_provoking_vertex 1 -#endif /* GL_ARB_provoking_vertex */ - -#ifndef GL_ARB_query_buffer_object -#define GL_ARB_query_buffer_object 1 -#endif /* GL_ARB_query_buffer_object */ - -#ifndef GL_ARB_robust_buffer_access_behavior -#define GL_ARB_robust_buffer_access_behavior 1 -#endif /* GL_ARB_robust_buffer_access_behavior */ - -#ifndef GL_ARB_robustness -#define GL_ARB_robustness 1 -#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB 0x00000004 -#define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 -#define GL_GUILTY_CONTEXT_RESET_ARB 0x8253 -#define GL_INNOCENT_CONTEXT_RESET_ARB 0x8254 -#define GL_UNKNOWN_CONTEXT_RESET_ARB 0x8255 -#define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 -#define GL_NO_RESET_NOTIFICATION_ARB 0x8261 -typedef GLenum(APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC)(void); -typedef void(APIENTRYP PFNGLGETNTEXIMAGEARBPROC)(GLenum target, GLint level, GLenum format, GLenum type, - GLsizei bufSize, void* img); -typedef void(APIENTRYP PFNGLREADNPIXELSARBPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, - GLenum type, GLsizei bufSize, void* data); -typedef void(APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEARBPROC)(GLenum target, GLint lod, GLsizei bufSize, void* img); -typedef void(APIENTRYP PFNGLGETNUNIFORMFVARBPROC)(GLuint program, GLint location, GLsizei bufSize, GLfloat* params); -typedef void(APIENTRYP PFNGLGETNUNIFORMIVARBPROC)(GLuint program, GLint location, GLsizei bufSize, GLint* params); -typedef void(APIENTRYP PFNGLGETNUNIFORMUIVARBPROC)(GLuint program, GLint location, GLsizei bufSize, GLuint* params); -typedef void(APIENTRYP PFNGLGETNUNIFORMDVARBPROC)(GLuint program, GLint location, GLsizei bufSize, GLdouble* params); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI GLenum APIENTRY glGetGraphicsResetStatusARB(void); -GLAPI void APIENTRY glGetnTexImageARB(GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, - void* img); -GLAPI void APIENTRY glReadnPixelsARB(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, - GLsizei bufSize, void* data); -GLAPI void APIENTRY glGetnCompressedTexImageARB(GLenum target, GLint lod, GLsizei bufSize, void* img); -GLAPI void APIENTRY glGetnUniformfvARB(GLuint program, GLint location, GLsizei bufSize, GLfloat* params); -GLAPI void APIENTRY glGetnUniformivARB(GLuint program, GLint location, GLsizei bufSize, GLint* params); -GLAPI void APIENTRY glGetnUniformuivARB(GLuint program, GLint location, GLsizei bufSize, GLuint* params); -GLAPI void APIENTRY glGetnUniformdvARB(GLuint program, GLint location, GLsizei bufSize, GLdouble* params); -#endif -#endif /* GL_ARB_robustness */ - -#ifndef GL_ARB_robustness_isolation -#define GL_ARB_robustness_isolation 1 -#endif /* GL_ARB_robustness_isolation */ - -#ifndef GL_ARB_sample_locations -#define GL_ARB_sample_locations 1 -#define GL_SAMPLE_LOCATION_SUBPIXEL_BITS_ARB 0x933D -#define GL_SAMPLE_LOCATION_PIXEL_GRID_WIDTH_ARB 0x933E -#define GL_SAMPLE_LOCATION_PIXEL_GRID_HEIGHT_ARB 0x933F -#define GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_ARB 0x9340 -#define GL_SAMPLE_LOCATION_ARB 0x8E50 -#define GL_PROGRAMMABLE_SAMPLE_LOCATION_ARB 0x9341 -#define GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_ARB 0x9342 -#define GL_FRAMEBUFFER_SAMPLE_LOCATION_PIXEL_GRID_ARB 0x9343 -typedef void(APIENTRYP PFNGLFRAMEBUFFERSAMPLELOCATIONSFVARBPROC)(GLenum target, GLuint start, GLsizei count, - const GLfloat* v); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVARBPROC)(GLuint framebuffer, GLuint start, GLsizei count, - const GLfloat* v); -typedef void(APIENTRYP PFNGLEVALUATEDEPTHVALUESARBPROC)(void); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glFramebufferSampleLocationsfvARB(GLenum target, GLuint start, GLsizei count, const GLfloat* v); -GLAPI void APIENTRY glNamedFramebufferSampleLocationsfvARB(GLuint framebuffer, GLuint start, GLsizei count, - const GLfloat* v); -GLAPI void APIENTRY glEvaluateDepthValuesARB(void); -#endif -#endif /* GL_ARB_sample_locations */ - -#ifndef GL_ARB_sample_shading -#define GL_ARB_sample_shading 1 -#define GL_SAMPLE_SHADING_ARB 0x8C36 -#define GL_MIN_SAMPLE_SHADING_VALUE_ARB 0x8C37 -typedef void(APIENTRYP PFNGLMINSAMPLESHADINGARBPROC)(GLfloat value); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glMinSampleShadingARB(GLfloat value); -#endif -#endif /* GL_ARB_sample_shading */ - -#ifndef GL_ARB_sampler_objects -#define GL_ARB_sampler_objects 1 -#endif /* GL_ARB_sampler_objects */ - -#ifndef GL_ARB_seamless_cube_map -#define GL_ARB_seamless_cube_map 1 -#endif /* GL_ARB_seamless_cube_map */ - -#ifndef GL_ARB_seamless_cubemap_per_texture -#define GL_ARB_seamless_cubemap_per_texture 1 -#endif /* GL_ARB_seamless_cubemap_per_texture */ - -#ifndef GL_ARB_separate_shader_objects -#define GL_ARB_separate_shader_objects 1 -#endif /* GL_ARB_separate_shader_objects */ - -#ifndef GL_ARB_shader_atomic_counter_ops -#define GL_ARB_shader_atomic_counter_ops 1 -#endif /* GL_ARB_shader_atomic_counter_ops */ - -#ifndef GL_ARB_shader_atomic_counters -#define GL_ARB_shader_atomic_counters 1 -#endif /* GL_ARB_shader_atomic_counters */ - -#ifndef GL_ARB_shader_ballot -#define GL_ARB_shader_ballot 1 -#endif /* GL_ARB_shader_ballot */ - -#ifndef GL_ARB_shader_bit_encoding -#define GL_ARB_shader_bit_encoding 1 -#endif /* GL_ARB_shader_bit_encoding */ - -#ifndef GL_ARB_shader_clock -#define GL_ARB_shader_clock 1 -#endif /* GL_ARB_shader_clock */ - -#ifndef GL_ARB_shader_draw_parameters -#define GL_ARB_shader_draw_parameters 1 -#endif /* GL_ARB_shader_draw_parameters */ - -#ifndef GL_ARB_shader_group_vote -#define GL_ARB_shader_group_vote 1 -#endif /* GL_ARB_shader_group_vote */ - -#ifndef GL_ARB_shader_image_load_store -#define GL_ARB_shader_image_load_store 1 -#endif /* GL_ARB_shader_image_load_store */ - -#ifndef GL_ARB_shader_image_size -#define GL_ARB_shader_image_size 1 -#endif /* GL_ARB_shader_image_size */ - -#ifndef GL_ARB_shader_precision -#define GL_ARB_shader_precision 1 -#endif /* GL_ARB_shader_precision */ - -#ifndef GL_ARB_shader_stencil_export -#define GL_ARB_shader_stencil_export 1 -#endif /* GL_ARB_shader_stencil_export */ - -#ifndef GL_ARB_shader_storage_buffer_object -#define GL_ARB_shader_storage_buffer_object 1 -#endif /* GL_ARB_shader_storage_buffer_object */ - -#ifndef GL_ARB_shader_subroutine -#define GL_ARB_shader_subroutine 1 -#endif /* GL_ARB_shader_subroutine */ - -#ifndef GL_ARB_shader_texture_image_samples -#define GL_ARB_shader_texture_image_samples 1 -#endif /* GL_ARB_shader_texture_image_samples */ - -#ifndef GL_ARB_shader_viewport_layer_array -#define GL_ARB_shader_viewport_layer_array 1 -#endif /* GL_ARB_shader_viewport_layer_array */ - -#ifndef GL_ARB_shading_language_420pack -#define GL_ARB_shading_language_420pack 1 -#endif /* GL_ARB_shading_language_420pack */ - -#ifndef GL_ARB_shading_language_include -#define GL_ARB_shading_language_include 1 -#define GL_SHADER_INCLUDE_ARB 0x8DAE -#define GL_NAMED_STRING_LENGTH_ARB 0x8DE9 -#define GL_NAMED_STRING_TYPE_ARB 0x8DEA -typedef void(APIENTRYP PFNGLNAMEDSTRINGARBPROC)(GLenum type, GLint namelen, const GLchar* name, GLint stringlen, - const GLchar* string); -typedef void(APIENTRYP PFNGLDELETENAMEDSTRINGARBPROC)(GLint namelen, const GLchar* name); -typedef void(APIENTRYP PFNGLCOMPILESHADERINCLUDEARBPROC)(GLuint shader, GLsizei count, const GLchar* const* path, - const GLint* length); -typedef GLboolean(APIENTRYP PFNGLISNAMEDSTRINGARBPROC)(GLint namelen, const GLchar* name); -typedef void(APIENTRYP PFNGLGETNAMEDSTRINGARBPROC)(GLint namelen, const GLchar* name, GLsizei bufSize, GLint* stringlen, - GLchar* string); -typedef void(APIENTRYP PFNGLGETNAMEDSTRINGIVARBPROC)(GLint namelen, const GLchar* name, GLenum pname, GLint* params); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glNamedStringARB(GLenum type, GLint namelen, const GLchar* name, GLint stringlen, - const GLchar* string); -GLAPI void APIENTRY glDeleteNamedStringARB(GLint namelen, const GLchar* name); -GLAPI void APIENTRY glCompileShaderIncludeARB(GLuint shader, GLsizei count, const GLchar* const* path, - const GLint* length); -GLAPI GLboolean APIENTRY glIsNamedStringARB(GLint namelen, const GLchar* name); -GLAPI void APIENTRY glGetNamedStringARB(GLint namelen, const GLchar* name, GLsizei bufSize, GLint* stringlen, - GLchar* string); -GLAPI void APIENTRY glGetNamedStringivARB(GLint namelen, const GLchar* name, GLenum pname, GLint* params); -#endif -#endif /* GL_ARB_shading_language_include */ - -#ifndef GL_ARB_shading_language_packing -#define GL_ARB_shading_language_packing 1 -#endif /* GL_ARB_shading_language_packing */ - -#ifndef GL_ARB_sparse_buffer -#define GL_ARB_sparse_buffer 1 -#define GL_SPARSE_STORAGE_BIT_ARB 0x0400 -#define GL_SPARSE_BUFFER_PAGE_SIZE_ARB 0x82F8 -typedef void(APIENTRYP PFNGLBUFFERPAGECOMMITMENTARBPROC)(GLenum target, GLintptr offset, GLsizeiptr size, - GLboolean commit); -typedef void(APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTEXTPROC)(GLuint buffer, GLintptr offset, GLsizeiptr size, - GLboolean commit); -typedef void(APIENTRYP PFNGLNAMEDBUFFERPAGECOMMITMENTARBPROC)(GLuint buffer, GLintptr offset, GLsizeiptr size, - GLboolean commit); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBufferPageCommitmentARB(GLenum target, GLintptr offset, GLsizeiptr size, GLboolean commit); -GLAPI void APIENTRY glNamedBufferPageCommitmentEXT(GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); -GLAPI void APIENTRY glNamedBufferPageCommitmentARB(GLuint buffer, GLintptr offset, GLsizeiptr size, GLboolean commit); -#endif -#endif /* GL_ARB_sparse_buffer */ - -#ifndef GL_ARB_sparse_texture -#define GL_ARB_sparse_texture 1 -#define GL_TEXTURE_SPARSE_ARB 0x91A6 -#define GL_VIRTUAL_PAGE_SIZE_INDEX_ARB 0x91A7 -#define GL_NUM_SPARSE_LEVELS_ARB 0x91AA -#define GL_NUM_VIRTUAL_PAGE_SIZES_ARB 0x91A8 -#define GL_VIRTUAL_PAGE_SIZE_X_ARB 0x9195 -#define GL_VIRTUAL_PAGE_SIZE_Y_ARB 0x9196 -#define GL_VIRTUAL_PAGE_SIZE_Z_ARB 0x9197 -#define GL_MAX_SPARSE_TEXTURE_SIZE_ARB 0x9198 -#define GL_MAX_SPARSE_3D_TEXTURE_SIZE_ARB 0x9199 -#define GL_MAX_SPARSE_ARRAY_TEXTURE_LAYERS_ARB 0x919A -#define GL_SPARSE_TEXTURE_FULL_ARRAY_CUBE_MIPMAPS_ARB 0x91A9 -typedef void(APIENTRYP PFNGLTEXPAGECOMMITMENTARBPROC)(GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, - GLboolean commit); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glTexPageCommitmentARB(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); -#endif -#endif /* GL_ARB_sparse_texture */ - -#ifndef GL_ARB_sparse_texture2 -#define GL_ARB_sparse_texture2 1 -#endif /* GL_ARB_sparse_texture2 */ - -#ifndef GL_ARB_sparse_texture_clamp -#define GL_ARB_sparse_texture_clamp 1 -#endif /* GL_ARB_sparse_texture_clamp */ - -#ifndef GL_ARB_stencil_texturing -#define GL_ARB_stencil_texturing 1 -#endif /* GL_ARB_stencil_texturing */ - -#ifndef GL_ARB_sync -#define GL_ARB_sync 1 -#endif /* GL_ARB_sync */ - -#ifndef GL_ARB_tessellation_shader -#define GL_ARB_tessellation_shader 1 -#endif /* GL_ARB_tessellation_shader */ - -#ifndef GL_ARB_texture_barrier -#define GL_ARB_texture_barrier 1 -#endif /* GL_ARB_texture_barrier */ - -#ifndef GL_ARB_texture_border_clamp -#define GL_ARB_texture_border_clamp 1 -#define GL_CLAMP_TO_BORDER_ARB 0x812D -#endif /* GL_ARB_texture_border_clamp */ - -#ifndef GL_ARB_texture_buffer_object -#define GL_ARB_texture_buffer_object 1 -#define GL_TEXTURE_BUFFER_ARB 0x8C2A -#define GL_MAX_TEXTURE_BUFFER_SIZE_ARB 0x8C2B -#define GL_TEXTURE_BINDING_BUFFER_ARB 0x8C2C -#define GL_TEXTURE_BUFFER_DATA_STORE_BINDING_ARB 0x8C2D -#define GL_TEXTURE_BUFFER_FORMAT_ARB 0x8C2E -typedef void(APIENTRYP PFNGLTEXBUFFERARBPROC)(GLenum target, GLenum internalformat, GLuint buffer); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glTexBufferARB(GLenum target, GLenum internalformat, GLuint buffer); -#endif -#endif /* GL_ARB_texture_buffer_object */ - -#ifndef GL_ARB_texture_buffer_object_rgb32 -#define GL_ARB_texture_buffer_object_rgb32 1 -#endif /* GL_ARB_texture_buffer_object_rgb32 */ - -#ifndef GL_ARB_texture_buffer_range -#define GL_ARB_texture_buffer_range 1 -#endif /* GL_ARB_texture_buffer_range */ - -#ifndef GL_ARB_texture_compression_bptc -#define GL_ARB_texture_compression_bptc 1 -#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C -#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D -#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E -#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F -#endif /* GL_ARB_texture_compression_bptc */ - -#ifndef GL_ARB_texture_compression_rgtc -#define GL_ARB_texture_compression_rgtc 1 -#endif /* GL_ARB_texture_compression_rgtc */ - -#ifndef GL_ARB_texture_cube_map_array -#define GL_ARB_texture_cube_map_array 1 -#define GL_TEXTURE_CUBE_MAP_ARRAY_ARB 0x9009 -#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY_ARB 0x900A -#define GL_PROXY_TEXTURE_CUBE_MAP_ARRAY_ARB 0x900B -#define GL_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900C -#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW_ARB 0x900D -#define GL_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900E -#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY_ARB 0x900F -#endif /* GL_ARB_texture_cube_map_array */ - -#ifndef GL_ARB_texture_filter_minmax -#define GL_ARB_texture_filter_minmax 1 -#define GL_TEXTURE_REDUCTION_MODE_ARB 0x9366 -#define GL_WEIGHTED_AVERAGE_ARB 0x9367 -#endif /* GL_ARB_texture_filter_minmax */ - -#ifndef GL_ARB_texture_gather -#define GL_ARB_texture_gather 1 -#define GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5E -#define GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET_ARB 0x8E5F -#define GL_MAX_PROGRAM_TEXTURE_GATHER_COMPONENTS_ARB 0x8F9F -#endif /* GL_ARB_texture_gather */ - -#ifndef GL_ARB_texture_mirror_clamp_to_edge -#define GL_ARB_texture_mirror_clamp_to_edge 1 -#endif /* GL_ARB_texture_mirror_clamp_to_edge */ - -#ifndef GL_ARB_texture_mirrored_repeat -#define GL_ARB_texture_mirrored_repeat 1 -#define GL_MIRRORED_REPEAT_ARB 0x8370 -#endif /* GL_ARB_texture_mirrored_repeat */ - -#ifndef GL_ARB_texture_multisample -#define GL_ARB_texture_multisample 1 -#endif /* GL_ARB_texture_multisample */ - -#ifndef GL_ARB_texture_non_power_of_two -#define GL_ARB_texture_non_power_of_two 1 -#endif /* GL_ARB_texture_non_power_of_two */ - -#ifndef GL_ARB_texture_query_levels -#define GL_ARB_texture_query_levels 1 -#endif /* GL_ARB_texture_query_levels */ - -#ifndef GL_ARB_texture_query_lod -#define GL_ARB_texture_query_lod 1 -#endif /* GL_ARB_texture_query_lod */ - -#ifndef GL_ARB_texture_rg -#define GL_ARB_texture_rg 1 -#endif /* GL_ARB_texture_rg */ - -#ifndef GL_ARB_texture_rgb10_a2ui -#define GL_ARB_texture_rgb10_a2ui 1 -#endif /* GL_ARB_texture_rgb10_a2ui */ - -#ifndef GL_ARB_texture_stencil8 -#define GL_ARB_texture_stencil8 1 -#endif /* GL_ARB_texture_stencil8 */ - -#ifndef GL_ARB_texture_storage -#define GL_ARB_texture_storage 1 -#endif /* GL_ARB_texture_storage */ - -#ifndef GL_ARB_texture_storage_multisample -#define GL_ARB_texture_storage_multisample 1 -#endif /* GL_ARB_texture_storage_multisample */ - -#ifndef GL_ARB_texture_swizzle -#define GL_ARB_texture_swizzle 1 -#endif /* GL_ARB_texture_swizzle */ - -#ifndef GL_ARB_texture_view -#define GL_ARB_texture_view 1 -#endif /* GL_ARB_texture_view */ - -#ifndef GL_ARB_timer_query -#define GL_ARB_timer_query 1 -#endif /* GL_ARB_timer_query */ - -#ifndef GL_ARB_transform_feedback2 -#define GL_ARB_transform_feedback2 1 -#endif /* GL_ARB_transform_feedback2 */ - -#ifndef GL_ARB_transform_feedback3 -#define GL_ARB_transform_feedback3 1 -#endif /* GL_ARB_transform_feedback3 */ - -#ifndef GL_ARB_transform_feedback_instanced -#define GL_ARB_transform_feedback_instanced 1 -#endif /* GL_ARB_transform_feedback_instanced */ - -#ifndef GL_ARB_transform_feedback_overflow_query -#define GL_ARB_transform_feedback_overflow_query 1 -#define GL_TRANSFORM_FEEDBACK_OVERFLOW_ARB 0x82EC -#define GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW_ARB 0x82ED -#endif /* GL_ARB_transform_feedback_overflow_query */ - -#ifndef GL_ARB_uniform_buffer_object -#define GL_ARB_uniform_buffer_object 1 -#endif /* GL_ARB_uniform_buffer_object */ - -#ifndef GL_ARB_vertex_array_bgra -#define GL_ARB_vertex_array_bgra 1 -#endif /* GL_ARB_vertex_array_bgra */ - -#ifndef GL_ARB_vertex_array_object -#define GL_ARB_vertex_array_object 1 -#endif /* GL_ARB_vertex_array_object */ - -#ifndef GL_ARB_vertex_attrib_64bit -#define GL_ARB_vertex_attrib_64bit 1 -#endif /* GL_ARB_vertex_attrib_64bit */ - -#ifndef GL_ARB_vertex_attrib_binding -#define GL_ARB_vertex_attrib_binding 1 -#endif /* GL_ARB_vertex_attrib_binding */ - -#ifndef GL_ARB_vertex_type_10f_11f_11f_rev -#define GL_ARB_vertex_type_10f_11f_11f_rev 1 -#endif /* GL_ARB_vertex_type_10f_11f_11f_rev */ - -#ifndef GL_ARB_vertex_type_2_10_10_10_rev -#define GL_ARB_vertex_type_2_10_10_10_rev 1 -#endif /* GL_ARB_vertex_type_2_10_10_10_rev */ - -#ifndef GL_ARB_viewport_array -#define GL_ARB_viewport_array 1 -#endif /* GL_ARB_viewport_array */ - -#ifndef GL_KHR_blend_equation_advanced -#define GL_KHR_blend_equation_advanced 1 -#define GL_MULTIPLY_KHR 0x9294 -#define GL_SCREEN_KHR 0x9295 -#define GL_OVERLAY_KHR 0x9296 -#define GL_DARKEN_KHR 0x9297 -#define GL_LIGHTEN_KHR 0x9298 -#define GL_COLORDODGE_KHR 0x9299 -#define GL_COLORBURN_KHR 0x929A -#define GL_HARDLIGHT_KHR 0x929B -#define GL_SOFTLIGHT_KHR 0x929C -#define GL_DIFFERENCE_KHR 0x929E -#define GL_EXCLUSION_KHR 0x92A0 -#define GL_HSL_HUE_KHR 0x92AD -#define GL_HSL_SATURATION_KHR 0x92AE -#define GL_HSL_COLOR_KHR 0x92AF -#define GL_HSL_LUMINOSITY_KHR 0x92B0 -typedef void(APIENTRYP PFNGLBLENDBARRIERKHRPROC)(void); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBlendBarrierKHR(void); -#endif -#endif /* GL_KHR_blend_equation_advanced */ - -#ifndef GL_KHR_blend_equation_advanced_coherent -#define GL_KHR_blend_equation_advanced_coherent 1 -#define GL_BLEND_ADVANCED_COHERENT_KHR 0x9285 -#endif /* GL_KHR_blend_equation_advanced_coherent */ - -#ifndef GL_KHR_context_flush_control -#define GL_KHR_context_flush_control 1 -#endif /* GL_KHR_context_flush_control */ - -#ifndef GL_KHR_debug -#define GL_KHR_debug 1 -#endif /* GL_KHR_debug */ - -#ifndef GL_KHR_no_error -#define GL_KHR_no_error 1 -#define GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR 0x00000008 -#endif /* GL_KHR_no_error */ - -#ifndef GL_KHR_robust_buffer_access_behavior -#define GL_KHR_robust_buffer_access_behavior 1 -#endif /* GL_KHR_robust_buffer_access_behavior */ - -#ifndef GL_KHR_robustness -#define GL_KHR_robustness 1 -#define GL_CONTEXT_ROBUST_ACCESS 0x90F3 -#endif /* GL_KHR_robustness */ - -#ifndef GL_KHR_texture_compression_astc_hdr -#define GL_KHR_texture_compression_astc_hdr 1 -#define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93B0 -#define GL_COMPRESSED_RGBA_ASTC_5x4_KHR 0x93B1 -#define GL_COMPRESSED_RGBA_ASTC_5x5_KHR 0x93B2 -#define GL_COMPRESSED_RGBA_ASTC_6x5_KHR 0x93B3 -#define GL_COMPRESSED_RGBA_ASTC_6x6_KHR 0x93B4 -#define GL_COMPRESSED_RGBA_ASTC_8x5_KHR 0x93B5 -#define GL_COMPRESSED_RGBA_ASTC_8x6_KHR 0x93B6 -#define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93B7 -#define GL_COMPRESSED_RGBA_ASTC_10x5_KHR 0x93B8 -#define GL_COMPRESSED_RGBA_ASTC_10x6_KHR 0x93B9 -#define GL_COMPRESSED_RGBA_ASTC_10x8_KHR 0x93BA -#define GL_COMPRESSED_RGBA_ASTC_10x10_KHR 0x93BB -#define GL_COMPRESSED_RGBA_ASTC_12x10_KHR 0x93BC -#define GL_COMPRESSED_RGBA_ASTC_12x12_KHR 0x93BD -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR 0x93D0 -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR 0x93D1 -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR 0x93D2 -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR 0x93D3 -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR 0x93D4 -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR 0x93D5 -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR 0x93D6 -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR 0x93D7 -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR 0x93D8 -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR 0x93D9 -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR 0x93DA -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR 0x93DB -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR 0x93DC -#define GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR 0x93DD -#endif /* GL_KHR_texture_compression_astc_hdr */ - -#ifndef GL_KHR_texture_compression_astc_ldr -#define GL_KHR_texture_compression_astc_ldr 1 -#endif /* GL_KHR_texture_compression_astc_ldr */ - -#ifndef GL_KHR_texture_compression_astc_sliced_3d -#define GL_KHR_texture_compression_astc_sliced_3d 1 -#endif /* GL_KHR_texture_compression_astc_sliced_3d */ - -#ifndef GL_AMD_performance_monitor -#define GL_AMD_performance_monitor 1 -#define GL_COUNTER_TYPE_AMD 0x8BC0 -#define GL_COUNTER_RANGE_AMD 0x8BC1 -#define GL_UNSIGNED_INT64_AMD 0x8BC2 -#define GL_PERCENTAGE_AMD 0x8BC3 -#define GL_PERFMON_RESULT_AVAILABLE_AMD 0x8BC4 -#define GL_PERFMON_RESULT_SIZE_AMD 0x8BC5 -#define GL_PERFMON_RESULT_AMD 0x8BC6 -typedef void(APIENTRYP PFNGLGETPERFMONITORGROUPSAMDPROC)(GLint* numGroups, GLsizei groupsSize, GLuint* groups); -typedef void(APIENTRYP PFNGLGETPERFMONITORCOUNTERSAMDPROC)(GLuint group, GLint* numCounters, GLint* maxActiveCounters, - GLsizei counterSize, GLuint* counters); -typedef void(APIENTRYP PFNGLGETPERFMONITORGROUPSTRINGAMDPROC)(GLuint group, GLsizei bufSize, GLsizei* length, - GLchar* groupString); -typedef void(APIENTRYP PFNGLGETPERFMONITORCOUNTERSTRINGAMDPROC)(GLuint group, GLuint counter, GLsizei bufSize, - GLsizei* length, GLchar* counterString); -typedef void(APIENTRYP PFNGLGETPERFMONITORCOUNTERINFOAMDPROC)(GLuint group, GLuint counter, GLenum pname, void* data); -typedef void(APIENTRYP PFNGLGENPERFMONITORSAMDPROC)(GLsizei n, GLuint* monitors); -typedef void(APIENTRYP PFNGLDELETEPERFMONITORSAMDPROC)(GLsizei n, GLuint* monitors); -typedef void(APIENTRYP PFNGLSELECTPERFMONITORCOUNTERSAMDPROC)(GLuint monitor, GLboolean enable, GLuint group, - GLint numCounters, GLuint* counterList); -typedef void(APIENTRYP PFNGLBEGINPERFMONITORAMDPROC)(GLuint monitor); -typedef void(APIENTRYP PFNGLENDPERFMONITORAMDPROC)(GLuint monitor); -typedef void(APIENTRYP PFNGLGETPERFMONITORCOUNTERDATAAMDPROC)(GLuint monitor, GLenum pname, GLsizei dataSize, - GLuint* data, GLint* bytesWritten); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glGetPerfMonitorGroupsAMD(GLint* numGroups, GLsizei groupsSize, GLuint* groups); -GLAPI void APIENTRY glGetPerfMonitorCountersAMD(GLuint group, GLint* numCounters, GLint* maxActiveCounters, - GLsizei counterSize, GLuint* counters); -GLAPI void APIENTRY glGetPerfMonitorGroupStringAMD(GLuint group, GLsizei bufSize, GLsizei* length, GLchar* groupString); -GLAPI void APIENTRY glGetPerfMonitorCounterStringAMD(GLuint group, GLuint counter, GLsizei bufSize, GLsizei* length, - GLchar* counterString); -GLAPI void APIENTRY glGetPerfMonitorCounterInfoAMD(GLuint group, GLuint counter, GLenum pname, void* data); -GLAPI void APIENTRY glGenPerfMonitorsAMD(GLsizei n, GLuint* monitors); -GLAPI void APIENTRY glDeletePerfMonitorsAMD(GLsizei n, GLuint* monitors); -GLAPI void APIENTRY glSelectPerfMonitorCountersAMD(GLuint monitor, GLboolean enable, GLuint group, GLint numCounters, - GLuint* counterList); -GLAPI void APIENTRY glBeginPerfMonitorAMD(GLuint monitor); -GLAPI void APIENTRY glEndPerfMonitorAMD(GLuint monitor); -GLAPI void APIENTRY glGetPerfMonitorCounterDataAMD(GLuint monitor, GLenum pname, GLsizei dataSize, GLuint* data, - GLint* bytesWritten); -#endif -#endif /* GL_AMD_performance_monitor */ - -#ifndef GL_APPLE_rgb_422 -#define GL_APPLE_rgb_422 1 -#define GL_RGB_422_APPLE 0x8A1F -#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA -#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB -#define GL_RGB_RAW_422_APPLE 0x8A51 -#endif /* GL_APPLE_rgb_422 */ - -#ifndef GL_EXT_debug_label -#define GL_EXT_debug_label 1 -#define GL_PROGRAM_PIPELINE_OBJECT_EXT 0x8A4F -#define GL_PROGRAM_OBJECT_EXT 0x8B40 -#define GL_SHADER_OBJECT_EXT 0x8B48 -#define GL_BUFFER_OBJECT_EXT 0x9151 -#define GL_QUERY_OBJECT_EXT 0x9153 -#define GL_VERTEX_ARRAY_OBJECT_EXT 0x9154 -typedef void(APIENTRYP PFNGLLABELOBJECTEXTPROC)(GLenum type, GLuint object, GLsizei length, const GLchar* label); -typedef void(APIENTRYP PFNGLGETOBJECTLABELEXTPROC)(GLenum type, GLuint object, GLsizei bufSize, GLsizei* length, - GLchar* label); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glLabelObjectEXT(GLenum type, GLuint object, GLsizei length, const GLchar* label); -GLAPI void APIENTRY glGetObjectLabelEXT(GLenum type, GLuint object, GLsizei bufSize, GLsizei* length, GLchar* label); -#endif -#endif /* GL_EXT_debug_label */ - -#ifndef GL_EXT_debug_marker -#define GL_EXT_debug_marker 1 -typedef void(APIENTRYP PFNGLINSERTEVENTMARKEREXTPROC)(GLsizei length, const GLchar* marker); -typedef void(APIENTRYP PFNGLPUSHGROUPMARKEREXTPROC)(GLsizei length, const GLchar* marker); -typedef void(APIENTRYP PFNGLPOPGROUPMARKEREXTPROC)(void); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glInsertEventMarkerEXT(GLsizei length, const GLchar* marker); -GLAPI void APIENTRY glPushGroupMarkerEXT(GLsizei length, const GLchar* marker); -GLAPI void APIENTRY glPopGroupMarkerEXT(void); -#endif -#endif /* GL_EXT_debug_marker */ - -#ifndef GL_EXT_direct_state_access -#define GL_EXT_direct_state_access 1 -#define GL_PROGRAM_MATRIX_EXT 0x8E2D -#define GL_TRANSPOSE_PROGRAM_MATRIX_EXT 0x8E2E -#define GL_PROGRAM_MATRIX_STACK_DEPTH_EXT 0x8E2F -typedef void(APIENTRYP PFNGLMATRIXLOADFEXTPROC)(GLenum mode, const GLfloat* m); -typedef void(APIENTRYP PFNGLMATRIXLOADDEXTPROC)(GLenum mode, const GLdouble* m); -typedef void(APIENTRYP PFNGLMATRIXMULTFEXTPROC)(GLenum mode, const GLfloat* m); -typedef void(APIENTRYP PFNGLMATRIXMULTDEXTPROC)(GLenum mode, const GLdouble* m); -typedef void(APIENTRYP PFNGLMATRIXLOADIDENTITYEXTPROC)(GLenum mode); -typedef void(APIENTRYP PFNGLMATRIXROTATEFEXTPROC)(GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); -typedef void(APIENTRYP PFNGLMATRIXROTATEDEXTPROC)(GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); -typedef void(APIENTRYP PFNGLMATRIXSCALEFEXTPROC)(GLenum mode, GLfloat x, GLfloat y, GLfloat z); -typedef void(APIENTRYP PFNGLMATRIXSCALEDEXTPROC)(GLenum mode, GLdouble x, GLdouble y, GLdouble z); -typedef void(APIENTRYP PFNGLMATRIXTRANSLATEFEXTPROC)(GLenum mode, GLfloat x, GLfloat y, GLfloat z); -typedef void(APIENTRYP PFNGLMATRIXTRANSLATEDEXTPROC)(GLenum mode, GLdouble x, GLdouble y, GLdouble z); -typedef void(APIENTRYP PFNGLMATRIXFRUSTUMEXTPROC)(GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, - GLdouble top, GLdouble zNear, GLdouble zFar); -typedef void(APIENTRYP PFNGLMATRIXORTHOEXTPROC)(GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, - GLdouble top, GLdouble zNear, GLdouble zFar); -typedef void(APIENTRYP PFNGLMATRIXPOPEXTPROC)(GLenum mode); -typedef void(APIENTRYP PFNGLMATRIXPUSHEXTPROC)(GLenum mode); -typedef void(APIENTRYP PFNGLCLIENTATTRIBDEFAULTEXTPROC)(GLbitfield mask); -typedef void(APIENTRYP PFNGLPUSHCLIENTATTRIBDEFAULTEXTPROC)(GLbitfield mask); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERFEXTPROC)(GLuint texture, GLenum target, GLenum pname, GLfloat param); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERFVEXTPROC)(GLuint texture, GLenum target, GLenum pname, - const GLfloat* params); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERIEXTPROC)(GLuint texture, GLenum target, GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERIVEXTPROC)(GLuint texture, GLenum target, GLenum pname, - const GLint* params); -typedef void(APIENTRYP PFNGLTEXTUREIMAGE1DEXTPROC)(GLuint texture, GLenum target, GLint level, GLint internalformat, - GLsizei width, GLint border, GLenum format, GLenum type, - const void* pixels); -typedef void(APIENTRYP PFNGLTEXTUREIMAGE2DEXTPROC)(GLuint texture, GLenum target, GLint level, GLint internalformat, - GLsizei width, GLsizei height, GLint border, GLenum format, - GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLTEXTURESUBIMAGE1DEXTPROC)(GLuint texture, GLenum target, GLint level, GLint xoffset, - GLsizei width, GLenum format, GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLTEXTURESUBIMAGE2DEXTPROC)(GLuint texture, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLsizei width, GLsizei height, GLenum format, - GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLCOPYTEXTUREIMAGE1DEXTPROC)(GLuint texture, GLenum target, GLint level, - GLenum internalformat, GLint x, GLint y, GLsizei width, - GLint border); -typedef void(APIENTRYP PFNGLCOPYTEXTUREIMAGE2DEXTPROC)(GLuint texture, GLenum target, GLint level, - GLenum internalformat, GLint x, GLint y, GLsizei width, - GLsizei height, GLint border); -typedef void(APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DEXTPROC)(GLuint texture, GLenum target, GLint level, GLint xoffset, - GLint x, GLint y, GLsizei width); -typedef void(APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DEXTPROC)(GLuint texture, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLint x, GLint y, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLGETTEXTUREIMAGEEXTPROC)(GLuint texture, GLenum target, GLint level, GLenum format, - GLenum type, void* pixels); -typedef void(APIENTRYP PFNGLGETTEXTUREPARAMETERFVEXTPROC)(GLuint texture, GLenum target, GLenum pname, GLfloat* params); -typedef void(APIENTRYP PFNGLGETTEXTUREPARAMETERIVEXTPROC)(GLuint texture, GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVEXTPROC)(GLuint texture, GLenum target, GLint level, GLenum pname, - GLfloat* params); -typedef void(APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVEXTPROC)(GLuint texture, GLenum target, GLint level, GLenum pname, - GLint* params); -typedef void(APIENTRYP PFNGLTEXTUREIMAGE3DEXTPROC)(GLuint texture, GLenum target, GLint level, GLint internalformat, - GLsizei width, GLsizei height, GLsizei depth, GLint border, - GLenum format, GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLTEXTURESUBIMAGE3DEXTPROC)(GLuint texture, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, - GLsizei depth, GLenum format, GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DEXTPROC)(GLuint texture, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLBINDMULTITEXTUREEXTPROC)(GLenum texunit, GLenum target, GLuint texture); -typedef void(APIENTRYP PFNGLMULTITEXCOORDPOINTEREXTPROC)(GLenum texunit, GLint size, GLenum type, GLsizei stride, - const void* pointer); -typedef void(APIENTRYP PFNGLMULTITEXENVFEXTPROC)(GLenum texunit, GLenum target, GLenum pname, GLfloat param); -typedef void(APIENTRYP PFNGLMULTITEXENVFVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, const GLfloat* params); -typedef void(APIENTRYP PFNGLMULTITEXENVIEXTPROC)(GLenum texunit, GLenum target, GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLMULTITEXENVIVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, const GLint* params); -typedef void(APIENTRYP PFNGLMULTITEXGENDEXTPROC)(GLenum texunit, GLenum coord, GLenum pname, GLdouble param); -typedef void(APIENTRYP PFNGLMULTITEXGENDVEXTPROC)(GLenum texunit, GLenum coord, GLenum pname, const GLdouble* params); -typedef void(APIENTRYP PFNGLMULTITEXGENFEXTPROC)(GLenum texunit, GLenum coord, GLenum pname, GLfloat param); -typedef void(APIENTRYP PFNGLMULTITEXGENFVEXTPROC)(GLenum texunit, GLenum coord, GLenum pname, const GLfloat* params); -typedef void(APIENTRYP PFNGLMULTITEXGENIEXTPROC)(GLenum texunit, GLenum coord, GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLMULTITEXGENIVEXTPROC)(GLenum texunit, GLenum coord, GLenum pname, const GLint* params); -typedef void(APIENTRYP PFNGLGETMULTITEXENVFVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, GLfloat* params); -typedef void(APIENTRYP PFNGLGETMULTITEXENVIVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETMULTITEXGENDVEXTPROC)(GLenum texunit, GLenum coord, GLenum pname, GLdouble* params); -typedef void(APIENTRYP PFNGLGETMULTITEXGENFVEXTPROC)(GLenum texunit, GLenum coord, GLenum pname, GLfloat* params); -typedef void(APIENTRYP PFNGLGETMULTITEXGENIVEXTPROC)(GLenum texunit, GLenum coord, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLMULTITEXPARAMETERIEXTPROC)(GLenum texunit, GLenum target, GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLMULTITEXPARAMETERIVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, - const GLint* params); -typedef void(APIENTRYP PFNGLMULTITEXPARAMETERFEXTPROC)(GLenum texunit, GLenum target, GLenum pname, GLfloat param); -typedef void(APIENTRYP PFNGLMULTITEXPARAMETERFVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, - const GLfloat* params); -typedef void(APIENTRYP PFNGLMULTITEXIMAGE1DEXTPROC)(GLenum texunit, GLenum target, GLint level, GLint internalformat, - GLsizei width, GLint border, GLenum format, GLenum type, - const void* pixels); -typedef void(APIENTRYP PFNGLMULTITEXIMAGE2DEXTPROC)(GLenum texunit, GLenum target, GLint level, GLint internalformat, - GLsizei width, GLsizei height, GLint border, GLenum format, - GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLMULTITEXSUBIMAGE1DEXTPROC)(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLsizei width, GLenum format, GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLMULTITEXSUBIMAGE2DEXTPROC)(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLsizei width, GLsizei height, GLenum format, - GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLCOPYMULTITEXIMAGE1DEXTPROC)(GLenum texunit, GLenum target, GLint level, - GLenum internalformat, GLint x, GLint y, GLsizei width, - GLint border); -typedef void(APIENTRYP PFNGLCOPYMULTITEXIMAGE2DEXTPROC)(GLenum texunit, GLenum target, GLint level, - GLenum internalformat, GLint x, GLint y, GLsizei width, - GLsizei height, GLint border); -typedef void(APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE1DEXTPROC)(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLint x, GLint y, GLsizei width); -typedef void(APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE2DEXTPROC)(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLint x, GLint y, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLGETMULTITEXIMAGEEXTPROC)(GLenum texunit, GLenum target, GLint level, GLenum format, - GLenum type, void* pixels); -typedef void(APIENTRYP PFNGLGETMULTITEXPARAMETERFVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, - GLfloat* params); -typedef void(APIENTRYP PFNGLGETMULTITEXPARAMETERIVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERFVEXTPROC)(GLenum texunit, GLenum target, GLint level, - GLenum pname, GLfloat* params); -typedef void(APIENTRYP PFNGLGETMULTITEXLEVELPARAMETERIVEXTPROC)(GLenum texunit, GLenum target, GLint level, - GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLMULTITEXIMAGE3DEXTPROC)(GLenum texunit, GLenum target, GLint level, GLint internalformat, - GLsizei width, GLsizei height, GLsizei depth, GLint border, - GLenum format, GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLMULTITEXSUBIMAGE3DEXTPROC)(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, - GLsizei depth, GLenum format, GLenum type, const void* pixels); -typedef void(APIENTRYP PFNGLCOPYMULTITEXSUBIMAGE3DEXTPROC)(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLint zoffset, GLint x, GLint y, - GLsizei width, GLsizei height); -typedef void(APIENTRYP PFNGLENABLECLIENTSTATEINDEXEDEXTPROC)(GLenum array, GLuint index); -typedef void(APIENTRYP PFNGLDISABLECLIENTSTATEINDEXEDEXTPROC)(GLenum array, GLuint index); -typedef void(APIENTRYP PFNGLGETFLOATINDEXEDVEXTPROC)(GLenum target, GLuint index, GLfloat* data); -typedef void(APIENTRYP PFNGLGETDOUBLEINDEXEDVEXTPROC)(GLenum target, GLuint index, GLdouble* data); -typedef void(APIENTRYP PFNGLGETPOINTERINDEXEDVEXTPROC)(GLenum target, GLuint index, void** data); -typedef void(APIENTRYP PFNGLENABLEINDEXEDEXTPROC)(GLenum target, GLuint index); -typedef void(APIENTRYP PFNGLDISABLEINDEXEDEXTPROC)(GLenum target, GLuint index); -typedef GLboolean(APIENTRYP PFNGLISENABLEDINDEXEDEXTPROC)(GLenum target, GLuint index); -typedef void(APIENTRYP PFNGLGETINTEGERINDEXEDVEXTPROC)(GLenum target, GLuint index, GLint* data); -typedef void(APIENTRYP PFNGLGETBOOLEANINDEXEDVEXTPROC)(GLenum target, GLuint index, GLboolean* data); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE3DEXTPROC)(GLuint texture, GLenum target, GLint level, - GLenum internalformat, GLsizei width, GLsizei height, - GLsizei depth, GLint border, GLsizei imageSize, - const void* bits); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE2DEXTPROC)(GLuint texture, GLenum target, GLint level, - GLenum internalformat, GLsizei width, GLsizei height, - GLint border, GLsizei imageSize, const void* bits); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXTUREIMAGE1DEXTPROC)(GLuint texture, GLenum target, GLint level, - GLenum internalformat, GLsizei width, GLint border, - GLsizei imageSize, const void* bits); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DEXTPROC)(GLuint texture, GLenum target, GLint level, - GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, - GLenum format, GLsizei imageSize, const void* bits); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DEXTPROC)(GLuint texture, GLenum target, GLint level, - GLint xoffset, GLint yoffset, GLsizei width, - GLsizei height, GLenum format, GLsizei imageSize, - const void* bits); -typedef void(APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DEXTPROC)(GLuint texture, GLenum target, GLint level, - GLint xoffset, GLsizei width, GLenum format, - GLsizei imageSize, const void* bits); -typedef void(APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEEXTPROC)(GLuint texture, GLenum target, GLint lod, void* img); -typedef void(APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE3DEXTPROC)(GLenum texunit, GLenum target, GLint level, - GLenum internalformat, GLsizei width, GLsizei height, - GLsizei depth, GLint border, GLsizei imageSize, - const void* bits); -typedef void(APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE2DEXTPROC)(GLenum texunit, GLenum target, GLint level, - GLenum internalformat, GLsizei width, GLsizei height, - GLint border, GLsizei imageSize, const void* bits); -typedef void(APIENTRYP PFNGLCOMPRESSEDMULTITEXIMAGE1DEXTPROC)(GLenum texunit, GLenum target, GLint level, - GLenum internalformat, GLsizei width, GLint border, - GLsizei imageSize, const void* bits); -typedef void(APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE3DEXTPROC)(GLenum texunit, GLenum target, GLint level, - GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, - GLenum format, GLsizei imageSize, const void* bits); -typedef void(APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE2DEXTPROC)(GLenum texunit, GLenum target, GLint level, - GLint xoffset, GLint yoffset, GLsizei width, - GLsizei height, GLenum format, GLsizei imageSize, - const void* bits); -typedef void(APIENTRYP PFNGLCOMPRESSEDMULTITEXSUBIMAGE1DEXTPROC)(GLenum texunit, GLenum target, GLint level, - GLint xoffset, GLsizei width, GLenum format, - GLsizei imageSize, const void* bits); -typedef void(APIENTRYP PFNGLGETCOMPRESSEDMULTITEXIMAGEEXTPROC)(GLenum texunit, GLenum target, GLint lod, void* img); -typedef void(APIENTRYP PFNGLMATRIXLOADTRANSPOSEFEXTPROC)(GLenum mode, const GLfloat* m); -typedef void(APIENTRYP PFNGLMATRIXLOADTRANSPOSEDEXTPROC)(GLenum mode, const GLdouble* m); -typedef void(APIENTRYP PFNGLMATRIXMULTTRANSPOSEFEXTPROC)(GLenum mode, const GLfloat* m); -typedef void(APIENTRYP PFNGLMATRIXMULTTRANSPOSEDEXTPROC)(GLenum mode, const GLdouble* m); -typedef void(APIENTRYP PFNGLNAMEDBUFFERDATAEXTPROC)(GLuint buffer, GLsizeiptr size, const void* data, GLenum usage); -typedef void(APIENTRYP PFNGLNAMEDBUFFERSUBDATAEXTPROC)(GLuint buffer, GLintptr offset, GLsizeiptr size, - const void* data); -typedef void*(APIENTRYP PFNGLMAPNAMEDBUFFEREXTPROC)(GLuint buffer, GLenum access); -typedef GLboolean(APIENTRYP PFNGLUNMAPNAMEDBUFFEREXTPROC)(GLuint buffer); -typedef void(APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVEXTPROC)(GLuint buffer, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVEXTPROC)(GLuint buffer, GLenum pname, void** params); -typedef void(APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAEXTPROC)(GLuint buffer, GLintptr offset, GLsizeiptr size, void* data); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1FEXTPROC)(GLuint program, GLint location, GLfloat v0); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2FEXTPROC)(GLuint program, GLint location, GLfloat v0, GLfloat v1); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3FEXTPROC)(GLuint program, GLint location, GLfloat v0, GLfloat v1, - GLfloat v2); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4FEXTPROC)(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, - GLfloat v3); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1IEXTPROC)(GLuint program, GLint location, GLint v0); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2IEXTPROC)(GLuint program, GLint location, GLint v0, GLint v1); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3IEXTPROC)(GLuint program, GLint location, GLint v0, GLint v1, GLint v2); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4IEXTPROC)(GLuint program, GLint location, GLint v0, GLint v1, GLint v2, - GLint v3); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1FVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2FVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3FVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4FVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1IVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2IVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3IVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4IVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2FVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3FVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4FVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3FVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2FVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4FVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2FVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4FVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3FVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLfloat* value); -typedef void(APIENTRYP PFNGLTEXTUREBUFFEREXTPROC)(GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); -typedef void(APIENTRYP PFNGLMULTITEXBUFFEREXTPROC)(GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERIIVEXTPROC)(GLuint texture, GLenum target, GLenum pname, - const GLint* params); -typedef void(APIENTRYP PFNGLTEXTUREPARAMETERIUIVEXTPROC)(GLuint texture, GLenum target, GLenum pname, - const GLuint* params); -typedef void(APIENTRYP PFNGLGETTEXTUREPARAMETERIIVEXTPROC)(GLuint texture, GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVEXTPROC)(GLuint texture, GLenum target, GLenum pname, - GLuint* params); -typedef void(APIENTRYP PFNGLMULTITEXPARAMETERIIVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, - const GLint* params); -typedef void(APIENTRYP PFNGLMULTITEXPARAMETERIUIVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, - const GLuint* params); -typedef void(APIENTRYP PFNGLGETMULTITEXPARAMETERIIVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETMULTITEXPARAMETERIUIVEXTPROC)(GLenum texunit, GLenum target, GLenum pname, - GLuint* params); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1UIEXTPROC)(GLuint program, GLint location, GLuint v0); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2UIEXTPROC)(GLuint program, GLint location, GLuint v0, GLuint v1); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3UIEXTPROC)(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4UIEXTPROC)(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, - GLuint v3); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1UIVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLuint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2UIVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLuint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3UIVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLuint* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4UIVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLuint* value); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERS4FVEXTPROC)(GLuint program, GLenum target, GLuint index, - GLsizei count, const GLfloat* params); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IEXTPROC)(GLuint program, GLenum target, GLuint index, GLint x, - GLint y, GLint z, GLint w); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4IVEXTPROC)(GLuint program, GLenum target, GLuint index, - const GLint* params); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4IVEXTPROC)(GLuint program, GLenum target, GLuint index, - GLsizei count, const GLint* params); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIEXTPROC)(GLuint program, GLenum target, GLuint index, - GLuint x, GLuint y, GLuint z, GLuint w); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERI4UIVEXTPROC)(GLuint program, GLenum target, GLuint index, - const GLuint* params); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETERSI4UIVEXTPROC)(GLuint program, GLenum target, GLuint index, - GLsizei count, const GLuint* params); -typedef void(APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIIVEXTPROC)(GLuint program, GLenum target, GLuint index, - GLint* params); -typedef void(APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERIUIVEXTPROC)(GLuint program, GLenum target, GLuint index, - GLuint* params); -typedef void(APIENTRYP PFNGLENABLECLIENTSTATEIEXTPROC)(GLenum array, GLuint index); -typedef void(APIENTRYP PFNGLDISABLECLIENTSTATEIEXTPROC)(GLenum array, GLuint index); -typedef void(APIENTRYP PFNGLGETFLOATI_VEXTPROC)(GLenum pname, GLuint index, GLfloat* params); -typedef void(APIENTRYP PFNGLGETDOUBLEI_VEXTPROC)(GLenum pname, GLuint index, GLdouble* params); -typedef void(APIENTRYP PFNGLGETPOINTERI_VEXTPROC)(GLenum pname, GLuint index, void** params); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMSTRINGEXTPROC)(GLuint program, GLenum target, GLenum format, GLsizei len, - const void* string); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DEXTPROC)(GLuint program, GLenum target, GLuint index, - GLdouble x, GLdouble y, GLdouble z, GLdouble w); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4DVEXTPROC)(GLuint program, GLenum target, GLuint index, - const GLdouble* params); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FEXTPROC)(GLuint program, GLenum target, GLuint index, GLfloat x, - GLfloat y, GLfloat z, GLfloat w); -typedef void(APIENTRYP PFNGLNAMEDPROGRAMLOCALPARAMETER4FVEXTPROC)(GLuint program, GLenum target, GLuint index, - const GLfloat* params); -typedef void(APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERDVEXTPROC)(GLuint program, GLenum target, GLuint index, - GLdouble* params); -typedef void(APIENTRYP PFNGLGETNAMEDPROGRAMLOCALPARAMETERFVEXTPROC)(GLuint program, GLenum target, GLuint index, - GLfloat* params); -typedef void(APIENTRYP PFNGLGETNAMEDPROGRAMIVEXTPROC)(GLuint program, GLenum target, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGETNAMEDPROGRAMSTRINGEXTPROC)(GLuint program, GLenum target, GLenum pname, void* string); -typedef void(APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEEXTPROC)(GLuint renderbuffer, GLenum internalformat, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVEXTPROC)(GLuint renderbuffer, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)(GLuint renderbuffer, GLsizei samples, - GLenum internalformat, GLsizei width, - GLsizei height); -typedef void(APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLECOVERAGEEXTPROC)(GLuint renderbuffer, - GLsizei coverageSamples, - GLsizei colorSamples, - GLenum internalformat, GLsizei width, - GLsizei height); -typedef GLenum(APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSEXTPROC)(GLuint framebuffer, GLenum target); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE1DEXTPROC)(GLuint framebuffer, GLenum attachment, GLenum textarget, - GLuint texture, GLint level); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE2DEXTPROC)(GLuint framebuffer, GLenum attachment, GLenum textarget, - GLuint texture, GLint level); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURE3DEXTPROC)(GLuint framebuffer, GLenum attachment, GLenum textarget, - GLuint texture, GLint level, GLint zoffset); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC)(GLuint framebuffer, GLenum attachment, - GLenum renderbuffertarget, GLuint renderbuffer); -typedef void(APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC)(GLuint framebuffer, GLenum attachment, - GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLGENERATETEXTUREMIPMAPEXTPROC)(GLuint texture, GLenum target); -typedef void(APIENTRYP PFNGLGENERATEMULTITEXMIPMAPEXTPROC)(GLenum texunit, GLenum target); -typedef void(APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFEREXTPROC)(GLuint framebuffer, GLenum mode); -typedef void(APIENTRYP PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC)(GLuint framebuffer, GLsizei n, const GLenum* bufs); -typedef void(APIENTRYP PFNGLFRAMEBUFFERREADBUFFEREXTPROC)(GLuint framebuffer, GLenum mode); -typedef void(APIENTRYP PFNGLGETFRAMEBUFFERPARAMETERIVEXTPROC)(GLuint framebuffer, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLNAMEDCOPYBUFFERSUBDATAEXTPROC)(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, - GLintptr writeOffset, GLsizeiptr size); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC)(GLuint framebuffer, GLenum attachment, GLuint texture, - GLint level); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC)(GLuint framebuffer, GLenum attachment, GLuint texture, - GLint level, GLint layer); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREFACEEXTPROC)(GLuint framebuffer, GLenum attachment, GLuint texture, - GLint level, GLenum face); -typedef void(APIENTRYP PFNGLTEXTURERENDERBUFFEREXTPROC)(GLuint texture, GLenum target, GLuint renderbuffer); -typedef void(APIENTRYP PFNGLMULTITEXRENDERBUFFEREXTPROC)(GLenum texunit, GLenum target, GLuint renderbuffer); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXOFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLint size, GLenum type, - GLsizei stride, GLintptr offset); -typedef void(APIENTRYP PFNGLVERTEXARRAYCOLOROFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLint size, GLenum type, - GLsizei stride, GLintptr offset); -typedef void(APIENTRYP PFNGLVERTEXARRAYEDGEFLAGOFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLsizei stride, - GLintptr offset); -typedef void(APIENTRYP PFNGLVERTEXARRAYINDEXOFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, - GLintptr offset); -typedef void(APIENTRYP PFNGLVERTEXARRAYNORMALOFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, - GLintptr offset); -typedef void(APIENTRYP PFNGLVERTEXARRAYTEXCOORDOFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLint size, GLenum type, - GLsizei stride, GLintptr offset); -typedef void(APIENTRYP PFNGLVERTEXARRAYMULTITEXCOORDOFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLenum texunit, - GLint size, GLenum type, GLsizei stride, - GLintptr offset); -typedef void(APIENTRYP PFNGLVERTEXARRAYFOGCOORDOFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, - GLintptr offset); -typedef void(APIENTRYP PFNGLVERTEXARRAYSECONDARYCOLOROFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLint size, - GLenum type, GLsizei stride, GLintptr offset); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBOFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLuint index, GLint size, - GLenum type, GLboolean normalized, GLsizei stride, - GLintptr offset); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBIOFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLuint index, - GLint size, GLenum type, GLsizei stride, - GLintptr offset); -typedef void(APIENTRYP PFNGLENABLEVERTEXARRAYEXTPROC)(GLuint vaobj, GLenum array); -typedef void(APIENTRYP PFNGLDISABLEVERTEXARRAYEXTPROC)(GLuint vaobj, GLenum array); -typedef void(APIENTRYP PFNGLENABLEVERTEXARRAYATTRIBEXTPROC)(GLuint vaobj, GLuint index); -typedef void(APIENTRYP PFNGLDISABLEVERTEXARRAYATTRIBEXTPROC)(GLuint vaobj, GLuint index); -typedef void(APIENTRYP PFNGLGETVERTEXARRAYINTEGERVEXTPROC)(GLuint vaobj, GLenum pname, GLint* param); -typedef void(APIENTRYP PFNGLGETVERTEXARRAYPOINTERVEXTPROC)(GLuint vaobj, GLenum pname, void** param); -typedef void(APIENTRYP PFNGLGETVERTEXARRAYINTEGERI_VEXTPROC)(GLuint vaobj, GLuint index, GLenum pname, GLint* param); -typedef void(APIENTRYP PFNGLGETVERTEXARRAYPOINTERI_VEXTPROC)(GLuint vaobj, GLuint index, GLenum pname, void** param); -typedef void*(APIENTRYP PFNGLMAPNAMEDBUFFERRANGEEXTPROC)(GLuint buffer, GLintptr offset, GLsizeiptr length, - GLbitfield access); -typedef void(APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEEXTPROC)(GLuint buffer, GLintptr offset, GLsizeiptr length); -typedef void(APIENTRYP PFNGLNAMEDBUFFERSTORAGEEXTPROC)(GLuint buffer, GLsizeiptr size, const void* data, - GLbitfield flags); -typedef void(APIENTRYP PFNGLCLEARNAMEDBUFFERDATAEXTPROC)(GLuint buffer, GLenum internalformat, GLenum format, - GLenum type, const void* data); -typedef void(APIENTRYP PFNGLCLEARNAMEDBUFFERSUBDATAEXTPROC)(GLuint buffer, GLenum internalformat, GLsizeiptr offset, - GLsizeiptr size, GLenum format, GLenum type, - const void* data); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERPARAMETERIEXTPROC)(GLuint framebuffer, GLenum pname, GLint param); -typedef void(APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVEXTPROC)(GLuint framebuffer, GLenum pname, GLint* params); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1DEXTPROC)(GLuint program, GLint location, GLdouble x); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2DEXTPROC)(GLuint program, GLint location, GLdouble x, GLdouble y); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3DEXTPROC)(GLuint program, GLint location, GLdouble x, GLdouble y, - GLdouble z); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4DEXTPROC)(GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, - GLdouble w); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1DVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2DVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3DVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4DVEXTPROC)(GLuint program, GLint location, GLsizei count, - const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2DVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3DVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4DVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X3DVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX2X4DVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X2DVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX3X4DVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X2DVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMMATRIX4X3DVEXTPROC)(GLuint program, GLint location, GLsizei count, - GLboolean transpose, const GLdouble* value); -typedef void(APIENTRYP PFNGLTEXTUREBUFFERRANGEEXTPROC)(GLuint texture, GLenum target, GLenum internalformat, - GLuint buffer, GLintptr offset, GLsizeiptr size); -typedef void(APIENTRYP PFNGLTEXTURESTORAGE1DEXTPROC)(GLuint texture, GLenum target, GLsizei levels, - GLenum internalformat, GLsizei width); -typedef void(APIENTRYP PFNGLTEXTURESTORAGE2DEXTPROC)(GLuint texture, GLenum target, GLsizei levels, - GLenum internalformat, GLsizei width, GLsizei height); -typedef void(APIENTRYP PFNGLTEXTURESTORAGE3DEXTPROC)(GLuint texture, GLenum target, GLsizei levels, - GLenum internalformat, GLsizei width, GLsizei height, - GLsizei depth); -typedef void(APIENTRYP PFNGLTEXTURESTORAGE2DMULTISAMPLEEXTPROC)(GLuint texture, GLenum target, GLsizei samples, - GLenum internalformat, GLsizei width, GLsizei height, - GLboolean fixedsamplelocations); -typedef void(APIENTRYP PFNGLTEXTURESTORAGE3DMULTISAMPLEEXTPROC)(GLuint texture, GLenum target, GLsizei samples, - GLenum internalformat, GLsizei width, GLsizei height, - GLsizei depth, GLboolean fixedsamplelocations); -typedef void(APIENTRYP PFNGLVERTEXARRAYBINDVERTEXBUFFEREXTPROC)(GLuint vaobj, GLuint bindingindex, GLuint buffer, - GLintptr offset, GLsizei stride); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBFORMATEXTPROC)(GLuint vaobj, GLuint attribindex, GLint size, - GLenum type, GLboolean normalized, - GLuint relativeoffset); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBIFORMATEXTPROC)(GLuint vaobj, GLuint attribindex, GLint size, - GLenum type, GLuint relativeoffset); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLFORMATEXTPROC)(GLuint vaobj, GLuint attribindex, GLint size, - GLenum type, GLuint relativeoffset); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBBINDINGEXTPROC)(GLuint vaobj, GLuint attribindex, - GLuint bindingindex); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXBINDINGDIVISOREXTPROC)(GLuint vaobj, GLuint bindingindex, GLuint divisor); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBLOFFSETEXTPROC)(GLuint vaobj, GLuint buffer, GLuint index, - GLint size, GLenum type, GLsizei stride, - GLintptr offset); -typedef void(APIENTRYP PFNGLTEXTUREPAGECOMMITMENTEXTPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, - GLboolean commit); -typedef void(APIENTRYP PFNGLVERTEXARRAYVERTEXATTRIBDIVISOREXTPROC)(GLuint vaobj, GLuint index, GLuint divisor); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glMatrixLoadfEXT(GLenum mode, const GLfloat* m); -GLAPI void APIENTRY glMatrixLoaddEXT(GLenum mode, const GLdouble* m); -GLAPI void APIENTRY glMatrixMultfEXT(GLenum mode, const GLfloat* m); -GLAPI void APIENTRY glMatrixMultdEXT(GLenum mode, const GLdouble* m); -GLAPI void APIENTRY glMatrixLoadIdentityEXT(GLenum mode); -GLAPI void APIENTRY glMatrixRotatefEXT(GLenum mode, GLfloat angle, GLfloat x, GLfloat y, GLfloat z); -GLAPI void APIENTRY glMatrixRotatedEXT(GLenum mode, GLdouble angle, GLdouble x, GLdouble y, GLdouble z); -GLAPI void APIENTRY glMatrixScalefEXT(GLenum mode, GLfloat x, GLfloat y, GLfloat z); -GLAPI void APIENTRY glMatrixScaledEXT(GLenum mode, GLdouble x, GLdouble y, GLdouble z); -GLAPI void APIENTRY glMatrixTranslatefEXT(GLenum mode, GLfloat x, GLfloat y, GLfloat z); -GLAPI void APIENTRY glMatrixTranslatedEXT(GLenum mode, GLdouble x, GLdouble y, GLdouble z); -GLAPI void APIENTRY glMatrixFrustumEXT(GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, - GLdouble zNear, GLdouble zFar); -GLAPI void APIENTRY glMatrixOrthoEXT(GLenum mode, GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, - GLdouble zNear, GLdouble zFar); -GLAPI void APIENTRY glMatrixPopEXT(GLenum mode); -GLAPI void APIENTRY glMatrixPushEXT(GLenum mode); -GLAPI void APIENTRY glClientAttribDefaultEXT(GLbitfield mask); -GLAPI void APIENTRY glPushClientAttribDefaultEXT(GLbitfield mask); -GLAPI void APIENTRY glTextureParameterfEXT(GLuint texture, GLenum target, GLenum pname, GLfloat param); -GLAPI void APIENTRY glTextureParameterfvEXT(GLuint texture, GLenum target, GLenum pname, const GLfloat* params); -GLAPI void APIENTRY glTextureParameteriEXT(GLuint texture, GLenum target, GLenum pname, GLint param); -GLAPI void APIENTRY glTextureParameterivEXT(GLuint texture, GLenum target, GLenum pname, const GLint* params); -GLAPI void APIENTRY glTextureImage1DEXT(GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, - GLint border, GLenum format, GLenum type, const void* pixels); -GLAPI void APIENTRY glTextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, - GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels); -GLAPI void APIENTRY glTextureSubImage1DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLsizei width, - GLenum format, GLenum type, const void* pixels); -GLAPI void APIENTRY glTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLsizei width, GLsizei height, GLenum format, GLenum type, - const void* pixels); -GLAPI void APIENTRY glCopyTextureImage1DEXT(GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, - GLint y, GLsizei width, GLint border); -GLAPI void APIENTRY glCopyTextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLenum internalformat, GLint x, - GLint y, GLsizei width, GLsizei height, GLint border); -GLAPI void APIENTRY glCopyTextureSubImage1DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint x, - GLint y, GLsizei width); -GLAPI void APIENTRY glCopyTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLint x, GLint y, GLsizei width, GLsizei height); -GLAPI void APIENTRY glGetTextureImageEXT(GLuint texture, GLenum target, GLint level, GLenum format, GLenum type, - void* pixels); -GLAPI void APIENTRY glGetTextureParameterfvEXT(GLuint texture, GLenum target, GLenum pname, GLfloat* params); -GLAPI void APIENTRY glGetTextureParameterivEXT(GLuint texture, GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetTextureLevelParameterfvEXT(GLuint texture, GLenum target, GLint level, GLenum pname, - GLfloat* params); -GLAPI void APIENTRY glGetTextureLevelParameterivEXT(GLuint texture, GLenum target, GLint level, GLenum pname, - GLint* params); -GLAPI void APIENTRY glTextureImage3DEXT(GLuint texture, GLenum target, GLint level, GLint internalformat, GLsizei width, - GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, - const void* pixels); -GLAPI void APIENTRY glTextureSubImage3DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, - GLenum type, const void* pixels); -GLAPI void APIENTRY glCopyTextureSubImage3DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); -GLAPI void APIENTRY glBindMultiTextureEXT(GLenum texunit, GLenum target, GLuint texture); -GLAPI void APIENTRY glMultiTexCoordPointerEXT(GLenum texunit, GLint size, GLenum type, GLsizei stride, - const void* pointer); -GLAPI void APIENTRY glMultiTexEnvfEXT(GLenum texunit, GLenum target, GLenum pname, GLfloat param); -GLAPI void APIENTRY glMultiTexEnvfvEXT(GLenum texunit, GLenum target, GLenum pname, const GLfloat* params); -GLAPI void APIENTRY glMultiTexEnviEXT(GLenum texunit, GLenum target, GLenum pname, GLint param); -GLAPI void APIENTRY glMultiTexEnvivEXT(GLenum texunit, GLenum target, GLenum pname, const GLint* params); -GLAPI void APIENTRY glMultiTexGendEXT(GLenum texunit, GLenum coord, GLenum pname, GLdouble param); -GLAPI void APIENTRY glMultiTexGendvEXT(GLenum texunit, GLenum coord, GLenum pname, const GLdouble* params); -GLAPI void APIENTRY glMultiTexGenfEXT(GLenum texunit, GLenum coord, GLenum pname, GLfloat param); -GLAPI void APIENTRY glMultiTexGenfvEXT(GLenum texunit, GLenum coord, GLenum pname, const GLfloat* params); -GLAPI void APIENTRY glMultiTexGeniEXT(GLenum texunit, GLenum coord, GLenum pname, GLint param); -GLAPI void APIENTRY glMultiTexGenivEXT(GLenum texunit, GLenum coord, GLenum pname, const GLint* params); -GLAPI void APIENTRY glGetMultiTexEnvfvEXT(GLenum texunit, GLenum target, GLenum pname, GLfloat* params); -GLAPI void APIENTRY glGetMultiTexEnvivEXT(GLenum texunit, GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetMultiTexGendvEXT(GLenum texunit, GLenum coord, GLenum pname, GLdouble* params); -GLAPI void APIENTRY glGetMultiTexGenfvEXT(GLenum texunit, GLenum coord, GLenum pname, GLfloat* params); -GLAPI void APIENTRY glGetMultiTexGenivEXT(GLenum texunit, GLenum coord, GLenum pname, GLint* params); -GLAPI void APIENTRY glMultiTexParameteriEXT(GLenum texunit, GLenum target, GLenum pname, GLint param); -GLAPI void APIENTRY glMultiTexParameterivEXT(GLenum texunit, GLenum target, GLenum pname, const GLint* params); -GLAPI void APIENTRY glMultiTexParameterfEXT(GLenum texunit, GLenum target, GLenum pname, GLfloat param); -GLAPI void APIENTRY glMultiTexParameterfvEXT(GLenum texunit, GLenum target, GLenum pname, const GLfloat* params); -GLAPI void APIENTRY glMultiTexImage1DEXT(GLenum texunit, GLenum target, GLint level, GLint internalformat, - GLsizei width, GLint border, GLenum format, GLenum type, const void* pixels); -GLAPI void APIENTRY glMultiTexImage2DEXT(GLenum texunit, GLenum target, GLint level, GLint internalformat, - GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, - const void* pixels); -GLAPI void APIENTRY glMultiTexSubImage1DEXT(GLenum texunit, GLenum target, GLint level, GLint xoffset, GLsizei width, - GLenum format, GLenum type, const void* pixels); -GLAPI void APIENTRY glMultiTexSubImage2DEXT(GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLsizei width, GLsizei height, GLenum format, GLenum type, - const void* pixels); -GLAPI void APIENTRY glCopyMultiTexImage1DEXT(GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, - GLint y, GLsizei width, GLint border); -GLAPI void APIENTRY glCopyMultiTexImage2DEXT(GLenum texunit, GLenum target, GLint level, GLenum internalformat, GLint x, - GLint y, GLsizei width, GLsizei height, GLint border); -GLAPI void APIENTRY glCopyMultiTexSubImage1DEXT(GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint x, - GLint y, GLsizei width); -GLAPI void APIENTRY glCopyMultiTexSubImage2DEXT(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); -GLAPI void APIENTRY glGetMultiTexImageEXT(GLenum texunit, GLenum target, GLint level, GLenum format, GLenum type, - void* pixels); -GLAPI void APIENTRY glGetMultiTexParameterfvEXT(GLenum texunit, GLenum target, GLenum pname, GLfloat* params); -GLAPI void APIENTRY glGetMultiTexParameterivEXT(GLenum texunit, GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetMultiTexLevelParameterfvEXT(GLenum texunit, GLenum target, GLint level, GLenum pname, - GLfloat* params); -GLAPI void APIENTRY glGetMultiTexLevelParameterivEXT(GLenum texunit, GLenum target, GLint level, GLenum pname, - GLint* params); -GLAPI void APIENTRY glMultiTexImage3DEXT(GLenum texunit, GLenum target, GLint level, GLint internalformat, - GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, - GLenum type, const void* pixels); -GLAPI void APIENTRY glMultiTexSubImage3DEXT(GLenum texunit, GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, - GLenum type, const void* pixels); -GLAPI void APIENTRY glCopyMultiTexSubImage3DEXT(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, - GLsizei height); -GLAPI void APIENTRY glEnableClientStateIndexedEXT(GLenum array, GLuint index); -GLAPI void APIENTRY glDisableClientStateIndexedEXT(GLenum array, GLuint index); -GLAPI void APIENTRY glGetFloatIndexedvEXT(GLenum target, GLuint index, GLfloat* data); -GLAPI void APIENTRY glGetDoubleIndexedvEXT(GLenum target, GLuint index, GLdouble* data); -GLAPI void APIENTRY glGetPointerIndexedvEXT(GLenum target, GLuint index, void** data); -GLAPI void APIENTRY glEnableIndexedEXT(GLenum target, GLuint index); -GLAPI void APIENTRY glDisableIndexedEXT(GLenum target, GLuint index); -GLAPI GLboolean APIENTRY glIsEnabledIndexedEXT(GLenum target, GLuint index); -GLAPI void APIENTRY glGetIntegerIndexedvEXT(GLenum target, GLuint index, GLint* data); -GLAPI void APIENTRY glGetBooleanIndexedvEXT(GLenum target, GLuint index, GLboolean* data); -GLAPI void APIENTRY glCompressedTextureImage3DEXT(GLuint texture, GLenum target, GLint level, GLenum internalformat, - GLsizei width, GLsizei height, GLsizei depth, GLint border, - GLsizei imageSize, const void* bits); -GLAPI void APIENTRY glCompressedTextureImage2DEXT(GLuint texture, GLenum target, GLint level, GLenum internalformat, - GLsizei width, GLsizei height, GLint border, GLsizei imageSize, - const void* bits); -GLAPI void APIENTRY glCompressedTextureImage1DEXT(GLuint texture, GLenum target, GLint level, GLenum internalformat, - GLsizei width, GLint border, GLsizei imageSize, const void* bits); -GLAPI void APIENTRY glCompressedTextureSubImage3DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, - GLsizei depth, GLenum format, GLsizei imageSize, const void* bits); -GLAPI void APIENTRY glCompressedTextureSubImage2DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLsizei width, GLsizei height, GLenum format, - GLsizei imageSize, const void* bits); -GLAPI void APIENTRY glCompressedTextureSubImage1DEXT(GLuint texture, GLenum target, GLint level, GLint xoffset, - GLsizei width, GLenum format, GLsizei imageSize, const void* bits); -GLAPI void APIENTRY glGetCompressedTextureImageEXT(GLuint texture, GLenum target, GLint lod, void* img); -GLAPI void APIENTRY glCompressedMultiTexImage3DEXT(GLenum texunit, GLenum target, GLint level, GLenum internalformat, - GLsizei width, GLsizei height, GLsizei depth, GLint border, - GLsizei imageSize, const void* bits); -GLAPI void APIENTRY glCompressedMultiTexImage2DEXT(GLenum texunit, GLenum target, GLint level, GLenum internalformat, - GLsizei width, GLsizei height, GLint border, GLsizei imageSize, - const void* bits); -GLAPI void APIENTRY glCompressedMultiTexImage1DEXT(GLenum texunit, GLenum target, GLint level, GLenum internalformat, - GLsizei width, GLint border, GLsizei imageSize, const void* bits); -GLAPI void APIENTRY glCompressedMultiTexSubImage3DEXT(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, - GLsizei depth, GLenum format, GLsizei imageSize, - const void* bits); -GLAPI void APIENTRY glCompressedMultiTexSubImage2DEXT(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLint yoffset, GLsizei width, GLsizei height, GLenum format, - GLsizei imageSize, const void* bits); -GLAPI void APIENTRY glCompressedMultiTexSubImage1DEXT(GLenum texunit, GLenum target, GLint level, GLint xoffset, - GLsizei width, GLenum format, GLsizei imageSize, - const void* bits); -GLAPI void APIENTRY glGetCompressedMultiTexImageEXT(GLenum texunit, GLenum target, GLint lod, void* img); -GLAPI void APIENTRY glMatrixLoadTransposefEXT(GLenum mode, const GLfloat* m); -GLAPI void APIENTRY glMatrixLoadTransposedEXT(GLenum mode, const GLdouble* m); -GLAPI void APIENTRY glMatrixMultTransposefEXT(GLenum mode, const GLfloat* m); -GLAPI void APIENTRY glMatrixMultTransposedEXT(GLenum mode, const GLdouble* m); -GLAPI void APIENTRY glNamedBufferDataEXT(GLuint buffer, GLsizeiptr size, const void* data, GLenum usage); -GLAPI void APIENTRY glNamedBufferSubDataEXT(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data); -GLAPI void* APIENTRY glMapNamedBufferEXT(GLuint buffer, GLenum access); -GLAPI GLboolean APIENTRY glUnmapNamedBufferEXT(GLuint buffer); -GLAPI void APIENTRY glGetNamedBufferParameterivEXT(GLuint buffer, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetNamedBufferPointervEXT(GLuint buffer, GLenum pname, void** params); -GLAPI void APIENTRY glGetNamedBufferSubDataEXT(GLuint buffer, GLintptr offset, GLsizeiptr size, void* data); -GLAPI void APIENTRY glProgramUniform1fEXT(GLuint program, GLint location, GLfloat v0); -GLAPI void APIENTRY glProgramUniform2fEXT(GLuint program, GLint location, GLfloat v0, GLfloat v1); -GLAPI void APIENTRY glProgramUniform3fEXT(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2); -GLAPI void APIENTRY glProgramUniform4fEXT(GLuint program, GLint location, GLfloat v0, GLfloat v1, GLfloat v2, - GLfloat v3); -GLAPI void APIENTRY glProgramUniform1iEXT(GLuint program, GLint location, GLint v0); -GLAPI void APIENTRY glProgramUniform2iEXT(GLuint program, GLint location, GLint v0, GLint v1); -GLAPI void APIENTRY glProgramUniform3iEXT(GLuint program, GLint location, GLint v0, GLint v1, GLint v2); -GLAPI void APIENTRY glProgramUniform4iEXT(GLuint program, GLint location, GLint v0, GLint v1, GLint v2, GLint v3); -GLAPI void APIENTRY glProgramUniform1fvEXT(GLuint program, GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glProgramUniform2fvEXT(GLuint program, GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glProgramUniform3fvEXT(GLuint program, GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glProgramUniform4fvEXT(GLuint program, GLint location, GLsizei count, const GLfloat* value); -GLAPI void APIENTRY glProgramUniform1ivEXT(GLuint program, GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glProgramUniform2ivEXT(GLuint program, GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glProgramUniform3ivEXT(GLuint program, GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glProgramUniform4ivEXT(GLuint program, GLint location, GLsizei count, const GLint* value); -GLAPI void APIENTRY glProgramUniformMatrix2fvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix3fvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix4fvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix2x3fvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix3x2fvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix2x4fvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix4x2fvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix3x4fvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glProgramUniformMatrix4x3fvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLfloat* value); -GLAPI void APIENTRY glTextureBufferEXT(GLuint texture, GLenum target, GLenum internalformat, GLuint buffer); -GLAPI void APIENTRY glMultiTexBufferEXT(GLenum texunit, GLenum target, GLenum internalformat, GLuint buffer); -GLAPI void APIENTRY glTextureParameterIivEXT(GLuint texture, GLenum target, GLenum pname, const GLint* params); -GLAPI void APIENTRY glTextureParameterIuivEXT(GLuint texture, GLenum target, GLenum pname, const GLuint* params); -GLAPI void APIENTRY glGetTextureParameterIivEXT(GLuint texture, GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetTextureParameterIuivEXT(GLuint texture, GLenum target, GLenum pname, GLuint* params); -GLAPI void APIENTRY glMultiTexParameterIivEXT(GLenum texunit, GLenum target, GLenum pname, const GLint* params); -GLAPI void APIENTRY glMultiTexParameterIuivEXT(GLenum texunit, GLenum target, GLenum pname, const GLuint* params); -GLAPI void APIENTRY glGetMultiTexParameterIivEXT(GLenum texunit, GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetMultiTexParameterIuivEXT(GLenum texunit, GLenum target, GLenum pname, GLuint* params); -GLAPI void APIENTRY glProgramUniform1uiEXT(GLuint program, GLint location, GLuint v0); -GLAPI void APIENTRY glProgramUniform2uiEXT(GLuint program, GLint location, GLuint v0, GLuint v1); -GLAPI void APIENTRY glProgramUniform3uiEXT(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2); -GLAPI void APIENTRY glProgramUniform4uiEXT(GLuint program, GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3); -GLAPI void APIENTRY glProgramUniform1uivEXT(GLuint program, GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glProgramUniform2uivEXT(GLuint program, GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glProgramUniform3uivEXT(GLuint program, GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glProgramUniform4uivEXT(GLuint program, GLint location, GLsizei count, const GLuint* value); -GLAPI void APIENTRY glNamedProgramLocalParameters4fvEXT(GLuint program, GLenum target, GLuint index, GLsizei count, - const GLfloat* params); -GLAPI void APIENTRY glNamedProgramLocalParameterI4iEXT(GLuint program, GLenum target, GLuint index, GLint x, GLint y, - GLint z, GLint w); -GLAPI void APIENTRY glNamedProgramLocalParameterI4ivEXT(GLuint program, GLenum target, GLuint index, - const GLint* params); -GLAPI void APIENTRY glNamedProgramLocalParametersI4ivEXT(GLuint program, GLenum target, GLuint index, GLsizei count, - const GLint* params); -GLAPI void APIENTRY glNamedProgramLocalParameterI4uiEXT(GLuint program, GLenum target, GLuint index, GLuint x, GLuint y, - GLuint z, GLuint w); -GLAPI void APIENTRY glNamedProgramLocalParameterI4uivEXT(GLuint program, GLenum target, GLuint index, - const GLuint* params); -GLAPI void APIENTRY glNamedProgramLocalParametersI4uivEXT(GLuint program, GLenum target, GLuint index, GLsizei count, - const GLuint* params); -GLAPI void APIENTRY glGetNamedProgramLocalParameterIivEXT(GLuint program, GLenum target, GLuint index, GLint* params); -GLAPI void APIENTRY glGetNamedProgramLocalParameterIuivEXT(GLuint program, GLenum target, GLuint index, GLuint* params); -GLAPI void APIENTRY glEnableClientStateiEXT(GLenum array, GLuint index); -GLAPI void APIENTRY glDisableClientStateiEXT(GLenum array, GLuint index); -GLAPI void APIENTRY glGetFloati_vEXT(GLenum pname, GLuint index, GLfloat* params); -GLAPI void APIENTRY glGetDoublei_vEXT(GLenum pname, GLuint index, GLdouble* params); -GLAPI void APIENTRY glGetPointeri_vEXT(GLenum pname, GLuint index, void** params); -GLAPI void APIENTRY glNamedProgramStringEXT(GLuint program, GLenum target, GLenum format, GLsizei len, - const void* string); -GLAPI void APIENTRY glNamedProgramLocalParameter4dEXT(GLuint program, GLenum target, GLuint index, GLdouble x, - GLdouble y, GLdouble z, GLdouble w); -GLAPI void APIENTRY glNamedProgramLocalParameter4dvEXT(GLuint program, GLenum target, GLuint index, - const GLdouble* params); -GLAPI void APIENTRY glNamedProgramLocalParameter4fEXT(GLuint program, GLenum target, GLuint index, GLfloat x, GLfloat y, - GLfloat z, GLfloat w); -GLAPI void APIENTRY glNamedProgramLocalParameter4fvEXT(GLuint program, GLenum target, GLuint index, - const GLfloat* params); -GLAPI void APIENTRY glGetNamedProgramLocalParameterdvEXT(GLuint program, GLenum target, GLuint index, GLdouble* params); -GLAPI void APIENTRY glGetNamedProgramLocalParameterfvEXT(GLuint program, GLenum target, GLuint index, GLfloat* params); -GLAPI void APIENTRY glGetNamedProgramivEXT(GLuint program, GLenum target, GLenum pname, GLint* params); -GLAPI void APIENTRY glGetNamedProgramStringEXT(GLuint program, GLenum target, GLenum pname, void* string); -GLAPI void APIENTRY glNamedRenderbufferStorageEXT(GLuint renderbuffer, GLenum internalformat, GLsizei width, - GLsizei height); -GLAPI void APIENTRY glGetNamedRenderbufferParameterivEXT(GLuint renderbuffer, GLenum pname, GLint* params); -GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleEXT(GLuint renderbuffer, GLsizei samples, - GLenum internalformat, GLsizei width, GLsizei height); -GLAPI void APIENTRY glNamedRenderbufferStorageMultisampleCoverageEXT(GLuint renderbuffer, GLsizei coverageSamples, - GLsizei colorSamples, GLenum internalformat, - GLsizei width, GLsizei height); -GLAPI GLenum APIENTRY glCheckNamedFramebufferStatusEXT(GLuint framebuffer, GLenum target); -GLAPI void APIENTRY glNamedFramebufferTexture1DEXT(GLuint framebuffer, GLenum attachment, GLenum textarget, - GLuint texture, GLint level); -GLAPI void APIENTRY glNamedFramebufferTexture2DEXT(GLuint framebuffer, GLenum attachment, GLenum textarget, - GLuint texture, GLint level); -GLAPI void APIENTRY glNamedFramebufferTexture3DEXT(GLuint framebuffer, GLenum attachment, GLenum textarget, - GLuint texture, GLint level, GLint zoffset); -GLAPI void APIENTRY glNamedFramebufferRenderbufferEXT(GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, - GLuint renderbuffer); -GLAPI void APIENTRY glGetNamedFramebufferAttachmentParameterivEXT(GLuint framebuffer, GLenum attachment, GLenum pname, - GLint* params); -GLAPI void APIENTRY glGenerateTextureMipmapEXT(GLuint texture, GLenum target); -GLAPI void APIENTRY glGenerateMultiTexMipmapEXT(GLenum texunit, GLenum target); -GLAPI void APIENTRY glFramebufferDrawBufferEXT(GLuint framebuffer, GLenum mode); -GLAPI void APIENTRY glFramebufferDrawBuffersEXT(GLuint framebuffer, GLsizei n, const GLenum* bufs); -GLAPI void APIENTRY glFramebufferReadBufferEXT(GLuint framebuffer, GLenum mode); -GLAPI void APIENTRY glGetFramebufferParameterivEXT(GLuint framebuffer, GLenum pname, GLint* params); -GLAPI void APIENTRY glNamedCopyBufferSubDataEXT(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, - GLintptr writeOffset, GLsizeiptr size); -GLAPI void APIENTRY glNamedFramebufferTextureEXT(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); -GLAPI void APIENTRY glNamedFramebufferTextureLayerEXT(GLuint framebuffer, GLenum attachment, GLuint texture, - GLint level, GLint layer); -GLAPI void APIENTRY glNamedFramebufferTextureFaceEXT(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, - GLenum face); -GLAPI void APIENTRY glTextureRenderbufferEXT(GLuint texture, GLenum target, GLuint renderbuffer); -GLAPI void APIENTRY glMultiTexRenderbufferEXT(GLenum texunit, GLenum target, GLuint renderbuffer); -GLAPI void APIENTRY glVertexArrayVertexOffsetEXT(GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, - GLintptr offset); -GLAPI void APIENTRY glVertexArrayColorOffsetEXT(GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, - GLintptr offset); -GLAPI void APIENTRY glVertexArrayEdgeFlagOffsetEXT(GLuint vaobj, GLuint buffer, GLsizei stride, GLintptr offset); -GLAPI void APIENTRY glVertexArrayIndexOffsetEXT(GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, - GLintptr offset); -GLAPI void APIENTRY glVertexArrayNormalOffsetEXT(GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, - GLintptr offset); -GLAPI void APIENTRY glVertexArrayTexCoordOffsetEXT(GLuint vaobj, GLuint buffer, GLint size, GLenum type, GLsizei stride, - GLintptr offset); -GLAPI void APIENTRY glVertexArrayMultiTexCoordOffsetEXT(GLuint vaobj, GLuint buffer, GLenum texunit, GLint size, - GLenum type, GLsizei stride, GLintptr offset); -GLAPI void APIENTRY glVertexArrayFogCoordOffsetEXT(GLuint vaobj, GLuint buffer, GLenum type, GLsizei stride, - GLintptr offset); -GLAPI void APIENTRY glVertexArraySecondaryColorOffsetEXT(GLuint vaobj, GLuint buffer, GLint size, GLenum type, - GLsizei stride, GLintptr offset); -GLAPI void APIENTRY glVertexArrayVertexAttribOffsetEXT(GLuint vaobj, GLuint buffer, GLuint index, GLint size, - GLenum type, GLboolean normalized, GLsizei stride, - GLintptr offset); -GLAPI void APIENTRY glVertexArrayVertexAttribIOffsetEXT(GLuint vaobj, GLuint buffer, GLuint index, GLint size, - GLenum type, GLsizei stride, GLintptr offset); -GLAPI void APIENTRY glEnableVertexArrayEXT(GLuint vaobj, GLenum array); -GLAPI void APIENTRY glDisableVertexArrayEXT(GLuint vaobj, GLenum array); -GLAPI void APIENTRY glEnableVertexArrayAttribEXT(GLuint vaobj, GLuint index); -GLAPI void APIENTRY glDisableVertexArrayAttribEXT(GLuint vaobj, GLuint index); -GLAPI void APIENTRY glGetVertexArrayIntegervEXT(GLuint vaobj, GLenum pname, GLint* param); -GLAPI void APIENTRY glGetVertexArrayPointervEXT(GLuint vaobj, GLenum pname, void** param); -GLAPI void APIENTRY glGetVertexArrayIntegeri_vEXT(GLuint vaobj, GLuint index, GLenum pname, GLint* param); -GLAPI void APIENTRY glGetVertexArrayPointeri_vEXT(GLuint vaobj, GLuint index, GLenum pname, void** param); -GLAPI void* APIENTRY glMapNamedBufferRangeEXT(GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access); -GLAPI void APIENTRY glFlushMappedNamedBufferRangeEXT(GLuint buffer, GLintptr offset, GLsizeiptr length); -GLAPI void APIENTRY glNamedBufferStorageEXT(GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags); -GLAPI void APIENTRY glClearNamedBufferDataEXT(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, - const void* data); -GLAPI void APIENTRY glClearNamedBufferSubDataEXT(GLuint buffer, GLenum internalformat, GLsizeiptr offset, - GLsizeiptr size, GLenum format, GLenum type, const void* data); -GLAPI void APIENTRY glNamedFramebufferParameteriEXT(GLuint framebuffer, GLenum pname, GLint param); -GLAPI void APIENTRY glGetNamedFramebufferParameterivEXT(GLuint framebuffer, GLenum pname, GLint* params); -GLAPI void APIENTRY glProgramUniform1dEXT(GLuint program, GLint location, GLdouble x); -GLAPI void APIENTRY glProgramUniform2dEXT(GLuint program, GLint location, GLdouble x, GLdouble y); -GLAPI void APIENTRY glProgramUniform3dEXT(GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z); -GLAPI void APIENTRY glProgramUniform4dEXT(GLuint program, GLint location, GLdouble x, GLdouble y, GLdouble z, - GLdouble w); -GLAPI void APIENTRY glProgramUniform1dvEXT(GLuint program, GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glProgramUniform2dvEXT(GLuint program, GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glProgramUniform3dvEXT(GLuint program, GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glProgramUniform4dvEXT(GLuint program, GLint location, GLsizei count, const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix2dvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix3dvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix4dvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix2x3dvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix2x4dvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix3x2dvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix3x4dvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix4x2dvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glProgramUniformMatrix4x3dvEXT(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value); -GLAPI void APIENTRY glTextureBufferRangeEXT(GLuint texture, GLenum target, GLenum internalformat, GLuint buffer, - GLintptr offset, GLsizeiptr size); -GLAPI void APIENTRY glTextureStorage1DEXT(GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, - GLsizei width); -GLAPI void APIENTRY glTextureStorage2DEXT(GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, - GLsizei width, GLsizei height); -GLAPI void APIENTRY glTextureStorage3DEXT(GLuint texture, GLenum target, GLsizei levels, GLenum internalformat, - GLsizei width, GLsizei height, GLsizei depth); -GLAPI void APIENTRY glTextureStorage2DMultisampleEXT(GLuint texture, GLenum target, GLsizei samples, - GLenum internalformat, GLsizei width, GLsizei height, - GLboolean fixedsamplelocations); -GLAPI void APIENTRY glTextureStorage3DMultisampleEXT(GLuint texture, GLenum target, GLsizei samples, - GLenum internalformat, GLsizei width, GLsizei height, - GLsizei depth, GLboolean fixedsamplelocations); -GLAPI void APIENTRY glVertexArrayBindVertexBufferEXT(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, - GLsizei stride); -GLAPI void APIENTRY glVertexArrayVertexAttribFormatEXT(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, - GLboolean normalized, GLuint relativeoffset); -GLAPI void APIENTRY glVertexArrayVertexAttribIFormatEXT(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, - GLuint relativeoffset); -GLAPI void APIENTRY glVertexArrayVertexAttribLFormatEXT(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, - GLuint relativeoffset); -GLAPI void APIENTRY glVertexArrayVertexAttribBindingEXT(GLuint vaobj, GLuint attribindex, GLuint bindingindex); -GLAPI void APIENTRY glVertexArrayVertexBindingDivisorEXT(GLuint vaobj, GLuint bindingindex, GLuint divisor); -GLAPI void APIENTRY glVertexArrayVertexAttribLOffsetEXT(GLuint vaobj, GLuint buffer, GLuint index, GLint size, - GLenum type, GLsizei stride, GLintptr offset); -GLAPI void APIENTRY glTexturePageCommitmentEXT(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, GLboolean commit); -GLAPI void APIENTRY glVertexArrayVertexAttribDivisorEXT(GLuint vaobj, GLuint index, GLuint divisor); -#endif -#endif /* GL_EXT_direct_state_access */ - -#ifndef GL_EXT_draw_instanced -#define GL_EXT_draw_instanced 1 -typedef void(APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC)(GLenum mode, GLint start, GLsizei count, GLsizei primcount); -typedef void(APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC)(GLenum mode, GLsizei count, GLenum type, const void* indices, - GLsizei primcount); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glDrawArraysInstancedEXT(GLenum mode, GLint start, GLsizei count, GLsizei primcount); -GLAPI void APIENTRY glDrawElementsInstancedEXT(GLenum mode, GLsizei count, GLenum type, const void* indices, - GLsizei primcount); -#endif -#endif /* GL_EXT_draw_instanced */ - -#ifndef GL_EXT_polygon_offset_clamp -#define GL_EXT_polygon_offset_clamp 1 -#define GL_POLYGON_OFFSET_CLAMP_EXT 0x8E1B -typedef void(APIENTRYP PFNGLPOLYGONOFFSETCLAMPEXTPROC)(GLfloat factor, GLfloat units, GLfloat clamp); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glPolygonOffsetClampEXT(GLfloat factor, GLfloat units, GLfloat clamp); -#endif -#endif /* GL_EXT_polygon_offset_clamp */ - -#ifndef GL_EXT_post_depth_coverage -#define GL_EXT_post_depth_coverage 1 -#endif /* GL_EXT_post_depth_coverage */ - -#ifndef GL_EXT_raster_multisample -#define GL_EXT_raster_multisample 1 -#define GL_RASTER_MULTISAMPLE_EXT 0x9327 -#define GL_RASTER_SAMPLES_EXT 0x9328 -#define GL_MAX_RASTER_SAMPLES_EXT 0x9329 -#define GL_RASTER_FIXED_SAMPLE_LOCATIONS_EXT 0x932A -#define GL_MULTISAMPLE_RASTERIZATION_ALLOWED_EXT 0x932B -#define GL_EFFECTIVE_RASTER_SAMPLES_EXT 0x932C -typedef void(APIENTRYP PFNGLRASTERSAMPLESEXTPROC)(GLuint samples, GLboolean fixedsamplelocations); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glRasterSamplesEXT(GLuint samples, GLboolean fixedsamplelocations); -#endif -#endif /* GL_EXT_raster_multisample */ - -#ifndef GL_EXT_separate_shader_objects -#define GL_EXT_separate_shader_objects 1 -#define GL_ACTIVE_PROGRAM_EXT 0x8B8D -typedef void(APIENTRYP PFNGLUSESHADERPROGRAMEXTPROC)(GLenum type, GLuint program); -typedef void(APIENTRYP PFNGLACTIVEPROGRAMEXTPROC)(GLuint program); -typedef GLuint(APIENTRYP PFNGLCREATESHADERPROGRAMEXTPROC)(GLenum type, const GLchar* string); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glUseShaderProgramEXT(GLenum type, GLuint program); -GLAPI void APIENTRY glActiveProgramEXT(GLuint program); -GLAPI GLuint APIENTRY glCreateShaderProgramEXT(GLenum type, const GLchar* string); -#endif -#endif /* GL_EXT_separate_shader_objects */ - -#ifndef GL_EXT_shader_integer_mix -#define GL_EXT_shader_integer_mix 1 -#endif /* GL_EXT_shader_integer_mix */ - -#ifndef GL_EXT_texture_compression_s3tc -#define GL_EXT_texture_compression_s3tc 1 -#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 -#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 -#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 -#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 -#endif /* GL_EXT_texture_compression_s3tc */ - -#ifndef GL_EXT_texture_filter_minmax -#define GL_EXT_texture_filter_minmax 1 -#endif /* GL_EXT_texture_filter_minmax */ - -#ifndef GL_EXT_texture_sRGB_decode -#define GL_EXT_texture_sRGB_decode 1 -#define GL_TEXTURE_SRGB_DECODE_EXT 0x8A48 -#define GL_DECODE_EXT 0x8A49 -#define GL_SKIP_DECODE_EXT 0x8A4A -#endif /* GL_EXT_texture_sRGB_decode */ - -#ifndef GL_EXT_window_rectangles -#define GL_EXT_window_rectangles 1 -#define GL_INCLUSIVE_EXT 0x8F10 -#define GL_EXCLUSIVE_EXT 0x8F11 -#define GL_WINDOW_RECTANGLE_EXT 0x8F12 -#define GL_WINDOW_RECTANGLE_MODE_EXT 0x8F13 -#define GL_MAX_WINDOW_RECTANGLES_EXT 0x8F14 -#define GL_NUM_WINDOW_RECTANGLES_EXT 0x8F15 -typedef void(APIENTRYP PFNGLWINDOWRECTANGLESEXTPROC)(GLenum mode, GLsizei count, const GLint* box); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glWindowRectanglesEXT(GLenum mode, GLsizei count, const GLint* box); -#endif -#endif /* GL_EXT_window_rectangles */ - -#ifndef GL_INTEL_conservative_rasterization -#define GL_INTEL_conservative_rasterization 1 -#define GL_CONSERVATIVE_RASTERIZATION_INTEL 0x83FE -#endif /* GL_INTEL_conservative_rasterization */ - -#ifndef GL_INTEL_framebuffer_CMAA -#define GL_INTEL_framebuffer_CMAA 1 -typedef void(APIENTRYP PFNGLAPPLYFRAMEBUFFERATTACHMENTCMAAINTELPROC)(void); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glApplyFramebufferAttachmentCMAAINTEL(void); -#endif -#endif /* GL_INTEL_framebuffer_CMAA */ - -#ifndef GL_INTEL_performance_query -#define GL_INTEL_performance_query 1 -#define GL_PERFQUERY_SINGLE_CONTEXT_INTEL 0x00000000 -#define GL_PERFQUERY_GLOBAL_CONTEXT_INTEL 0x00000001 -#define GL_PERFQUERY_WAIT_INTEL 0x83FB -#define GL_PERFQUERY_FLUSH_INTEL 0x83FA -#define GL_PERFQUERY_DONOT_FLUSH_INTEL 0x83F9 -#define GL_PERFQUERY_COUNTER_EVENT_INTEL 0x94F0 -#define GL_PERFQUERY_COUNTER_DURATION_NORM_INTEL 0x94F1 -#define GL_PERFQUERY_COUNTER_DURATION_RAW_INTEL 0x94F2 -#define GL_PERFQUERY_COUNTER_THROUGHPUT_INTEL 0x94F3 -#define GL_PERFQUERY_COUNTER_RAW_INTEL 0x94F4 -#define GL_PERFQUERY_COUNTER_TIMESTAMP_INTEL 0x94F5 -#define GL_PERFQUERY_COUNTER_DATA_UINT32_INTEL 0x94F8 -#define GL_PERFQUERY_COUNTER_DATA_UINT64_INTEL 0x94F9 -#define GL_PERFQUERY_COUNTER_DATA_FLOAT_INTEL 0x94FA -#define GL_PERFQUERY_COUNTER_DATA_DOUBLE_INTEL 0x94FB -#define GL_PERFQUERY_COUNTER_DATA_BOOL32_INTEL 0x94FC -#define GL_PERFQUERY_QUERY_NAME_LENGTH_MAX_INTEL 0x94FD -#define GL_PERFQUERY_COUNTER_NAME_LENGTH_MAX_INTEL 0x94FE -#define GL_PERFQUERY_COUNTER_DESC_LENGTH_MAX_INTEL 0x94FF -#define GL_PERFQUERY_GPA_EXTENDED_COUNTERS_INTEL 0x9500 -typedef void(APIENTRYP PFNGLBEGINPERFQUERYINTELPROC)(GLuint queryHandle); -typedef void(APIENTRYP PFNGLCREATEPERFQUERYINTELPROC)(GLuint queryId, GLuint* queryHandle); -typedef void(APIENTRYP PFNGLDELETEPERFQUERYINTELPROC)(GLuint queryHandle); -typedef void(APIENTRYP PFNGLENDPERFQUERYINTELPROC)(GLuint queryHandle); -typedef void(APIENTRYP PFNGLGETFIRSTPERFQUERYIDINTELPROC)(GLuint* queryId); -typedef void(APIENTRYP PFNGLGETNEXTPERFQUERYIDINTELPROC)(GLuint queryId, GLuint* nextQueryId); -typedef void(APIENTRYP PFNGLGETPERFCOUNTERINFOINTELPROC)(GLuint queryId, GLuint counterId, GLuint counterNameLength, - GLchar* counterName, GLuint counterDescLength, - GLchar* counterDesc, GLuint* counterOffset, - GLuint* counterDataSize, GLuint* counterTypeEnum, - GLuint* counterDataTypeEnum, GLuint64* rawCounterMaxValue); -typedef void(APIENTRYP PFNGLGETPERFQUERYDATAINTELPROC)(GLuint queryHandle, GLuint flags, GLsizei dataSize, GLvoid* data, - GLuint* bytesWritten); -typedef void(APIENTRYP PFNGLGETPERFQUERYIDBYNAMEINTELPROC)(GLchar* queryName, GLuint* queryId); -typedef void(APIENTRYP PFNGLGETPERFQUERYINFOINTELPROC)(GLuint queryId, GLuint queryNameLength, GLchar* queryName, - GLuint* dataSize, GLuint* noCounters, GLuint* noInstances, - GLuint* capsMask); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBeginPerfQueryINTEL(GLuint queryHandle); -GLAPI void APIENTRY glCreatePerfQueryINTEL(GLuint queryId, GLuint* queryHandle); -GLAPI void APIENTRY glDeletePerfQueryINTEL(GLuint queryHandle); -GLAPI void APIENTRY glEndPerfQueryINTEL(GLuint queryHandle); -GLAPI void APIENTRY glGetFirstPerfQueryIdINTEL(GLuint* queryId); -GLAPI void APIENTRY glGetNextPerfQueryIdINTEL(GLuint queryId, GLuint* nextQueryId); -GLAPI void APIENTRY glGetPerfCounterInfoINTEL(GLuint queryId, GLuint counterId, GLuint counterNameLength, - GLchar* counterName, GLuint counterDescLength, GLchar* counterDesc, - GLuint* counterOffset, GLuint* counterDataSize, GLuint* counterTypeEnum, - GLuint* counterDataTypeEnum, GLuint64* rawCounterMaxValue); -GLAPI void APIENTRY glGetPerfQueryDataINTEL(GLuint queryHandle, GLuint flags, GLsizei dataSize, GLvoid* data, - GLuint* bytesWritten); -GLAPI void APIENTRY glGetPerfQueryIdByNameINTEL(GLchar* queryName, GLuint* queryId); -GLAPI void APIENTRY glGetPerfQueryInfoINTEL(GLuint queryId, GLuint queryNameLength, GLchar* queryName, GLuint* dataSize, - GLuint* noCounters, GLuint* noInstances, GLuint* capsMask); -#endif -#endif /* GL_INTEL_performance_query */ - -#ifndef GL_NV_bindless_multi_draw_indirect -#define GL_NV_bindless_multi_draw_indirect 1 -typedef void(APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSNVPROC)(GLenum mode, const void* indirect, GLsizei drawCount, - GLsizei stride, GLint vertexBufferCount); -typedef void(APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSNVPROC)(GLenum mode, GLenum type, const void* indirect, - GLsizei drawCount, GLsizei stride, - GLint vertexBufferCount); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glMultiDrawArraysIndirectBindlessNV(GLenum mode, const void* indirect, GLsizei drawCount, - GLsizei stride, GLint vertexBufferCount); -GLAPI void APIENTRY glMultiDrawElementsIndirectBindlessNV(GLenum mode, GLenum type, const void* indirect, - GLsizei drawCount, GLsizei stride, GLint vertexBufferCount); -#endif -#endif /* GL_NV_bindless_multi_draw_indirect */ - -#ifndef GL_NV_bindless_multi_draw_indirect_count -#define GL_NV_bindless_multi_draw_indirect_count 1 -typedef void(APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTBINDLESSCOUNTNVPROC)(GLenum mode, const void* indirect, - GLsizei drawCount, GLsizei maxDrawCount, - GLsizei stride, GLint vertexBufferCount); -typedef void(APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTBINDLESSCOUNTNVPROC)(GLenum mode, GLenum type, - const void* indirect, GLsizei drawCount, - GLsizei maxDrawCount, GLsizei stride, - GLint vertexBufferCount); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glMultiDrawArraysIndirectBindlessCountNV(GLenum mode, const void* indirect, GLsizei drawCount, - GLsizei maxDrawCount, GLsizei stride, - GLint vertexBufferCount); -GLAPI void APIENTRY glMultiDrawElementsIndirectBindlessCountNV(GLenum mode, GLenum type, const void* indirect, - GLsizei drawCount, GLsizei maxDrawCount, GLsizei stride, - GLint vertexBufferCount); -#endif -#endif /* GL_NV_bindless_multi_draw_indirect_count */ - -#ifndef GL_NV_bindless_texture -#define GL_NV_bindless_texture 1 -typedef GLuint64(APIENTRYP PFNGLGETTEXTUREHANDLENVPROC)(GLuint texture); -typedef GLuint64(APIENTRYP PFNGLGETTEXTURESAMPLERHANDLENVPROC)(GLuint texture, GLuint sampler); -typedef void(APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTNVPROC)(GLuint64 handle); -typedef void(APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTNVPROC)(GLuint64 handle); -typedef GLuint64(APIENTRYP PFNGLGETIMAGEHANDLENVPROC)(GLuint texture, GLint level, GLboolean layered, GLint layer, - GLenum format); -typedef void(APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTNVPROC)(GLuint64 handle, GLenum access); -typedef void(APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTNVPROC)(GLuint64 handle); -typedef void(APIENTRYP PFNGLUNIFORMHANDLEUI64NVPROC)(GLint location, GLuint64 value); -typedef void(APIENTRYP PFNGLUNIFORMHANDLEUI64VNVPROC)(GLint location, GLsizei count, const GLuint64* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64NVPROC)(GLuint program, GLint location, GLuint64 value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VNVPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64* values); -typedef GLboolean(APIENTRYP PFNGLISTEXTUREHANDLERESIDENTNVPROC)(GLuint64 handle); -typedef GLboolean(APIENTRYP PFNGLISIMAGEHANDLERESIDENTNVPROC)(GLuint64 handle); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI GLuint64 APIENTRY glGetTextureHandleNV(GLuint texture); -GLAPI GLuint64 APIENTRY glGetTextureSamplerHandleNV(GLuint texture, GLuint sampler); -GLAPI void APIENTRY glMakeTextureHandleResidentNV(GLuint64 handle); -GLAPI void APIENTRY glMakeTextureHandleNonResidentNV(GLuint64 handle); -GLAPI GLuint64 APIENTRY glGetImageHandleNV(GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); -GLAPI void APIENTRY glMakeImageHandleResidentNV(GLuint64 handle, GLenum access); -GLAPI void APIENTRY glMakeImageHandleNonResidentNV(GLuint64 handle); -GLAPI void APIENTRY glUniformHandleui64NV(GLint location, GLuint64 value); -GLAPI void APIENTRY glUniformHandleui64vNV(GLint location, GLsizei count, const GLuint64* value); -GLAPI void APIENTRY glProgramUniformHandleui64NV(GLuint program, GLint location, GLuint64 value); -GLAPI void APIENTRY glProgramUniformHandleui64vNV(GLuint program, GLint location, GLsizei count, - const GLuint64* values); -GLAPI GLboolean APIENTRY glIsTextureHandleResidentNV(GLuint64 handle); -GLAPI GLboolean APIENTRY glIsImageHandleResidentNV(GLuint64 handle); -#endif -#endif /* GL_NV_bindless_texture */ - -#ifndef GL_NV_blend_equation_advanced -#define GL_NV_blend_equation_advanced 1 -#define GL_BLEND_OVERLAP_NV 0x9281 -#define GL_BLEND_PREMULTIPLIED_SRC_NV 0x9280 -#define GL_BLUE_NV 0x1905 -#define GL_COLORBURN_NV 0x929A -#define GL_COLORDODGE_NV 0x9299 -#define GL_CONJOINT_NV 0x9284 -#define GL_CONTRAST_NV 0x92A1 -#define GL_DARKEN_NV 0x9297 -#define GL_DIFFERENCE_NV 0x929E -#define GL_DISJOINT_NV 0x9283 -#define GL_DST_ATOP_NV 0x928F -#define GL_DST_IN_NV 0x928B -#define GL_DST_NV 0x9287 -#define GL_DST_OUT_NV 0x928D -#define GL_DST_OVER_NV 0x9289 -#define GL_EXCLUSION_NV 0x92A0 -#define GL_GREEN_NV 0x1904 -#define GL_HARDLIGHT_NV 0x929B -#define GL_HARDMIX_NV 0x92A9 -#define GL_HSL_COLOR_NV 0x92AF -#define GL_HSL_HUE_NV 0x92AD -#define GL_HSL_LUMINOSITY_NV 0x92B0 -#define GL_HSL_SATURATION_NV 0x92AE -#define GL_INVERT_OVG_NV 0x92B4 -#define GL_INVERT_RGB_NV 0x92A3 -#define GL_LIGHTEN_NV 0x9298 -#define GL_LINEARBURN_NV 0x92A5 -#define GL_LINEARDODGE_NV 0x92A4 -#define GL_LINEARLIGHT_NV 0x92A7 -#define GL_MINUS_CLAMPED_NV 0x92B3 -#define GL_MINUS_NV 0x929F -#define GL_MULTIPLY_NV 0x9294 -#define GL_OVERLAY_NV 0x9296 -#define GL_PINLIGHT_NV 0x92A8 -#define GL_PLUS_CLAMPED_ALPHA_NV 0x92B2 -#define GL_PLUS_CLAMPED_NV 0x92B1 -#define GL_PLUS_DARKER_NV 0x9292 -#define GL_PLUS_NV 0x9291 -#define GL_RED_NV 0x1903 -#define GL_SCREEN_NV 0x9295 -#define GL_SOFTLIGHT_NV 0x929C -#define GL_SRC_ATOP_NV 0x928E -#define GL_SRC_IN_NV 0x928A -#define GL_SRC_NV 0x9286 -#define GL_SRC_OUT_NV 0x928C -#define GL_SRC_OVER_NV 0x9288 -#define GL_UNCORRELATED_NV 0x9282 -#define GL_VIVIDLIGHT_NV 0x92A6 -#define GL_XOR_NV 0x1506 -typedef void(APIENTRYP PFNGLBLENDPARAMETERINVPROC)(GLenum pname, GLint value); -typedef void(APIENTRYP PFNGLBLENDBARRIERNVPROC)(void); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBlendParameteriNV(GLenum pname, GLint value); -GLAPI void APIENTRY glBlendBarrierNV(void); -#endif -#endif /* GL_NV_blend_equation_advanced */ - -#ifndef GL_NV_blend_equation_advanced_coherent -#define GL_NV_blend_equation_advanced_coherent 1 -#define GL_BLEND_ADVANCED_COHERENT_NV 0x9285 -#endif /* GL_NV_blend_equation_advanced_coherent */ - -#ifndef GL_NV_clip_space_w_scaling -#define GL_NV_clip_space_w_scaling 1 -#define GL_VIEWPORT_POSITION_W_SCALE_NV 0x937C -#define GL_VIEWPORT_POSITION_W_SCALE_X_COEFF_NV 0x937D -#define GL_VIEWPORT_POSITION_W_SCALE_Y_COEFF_NV 0x937E -typedef void(APIENTRYP PFNGLVIEWPORTPOSITIONWSCALENVPROC)(GLuint index, GLfloat xcoeff, GLfloat ycoeff); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glViewportPositionWScaleNV(GLuint index, GLfloat xcoeff, GLfloat ycoeff); -#endif -#endif /* GL_NV_clip_space_w_scaling */ - -#ifndef GL_NV_command_list -#define GL_NV_command_list 1 -#define GL_TERMINATE_SEQUENCE_COMMAND_NV 0x0000 -#define GL_NOP_COMMAND_NV 0x0001 -#define GL_DRAW_ELEMENTS_COMMAND_NV 0x0002 -#define GL_DRAW_ARRAYS_COMMAND_NV 0x0003 -#define GL_DRAW_ELEMENTS_STRIP_COMMAND_NV 0x0004 -#define GL_DRAW_ARRAYS_STRIP_COMMAND_NV 0x0005 -#define GL_DRAW_ELEMENTS_INSTANCED_COMMAND_NV 0x0006 -#define GL_DRAW_ARRAYS_INSTANCED_COMMAND_NV 0x0007 -#define GL_ELEMENT_ADDRESS_COMMAND_NV 0x0008 -#define GL_ATTRIBUTE_ADDRESS_COMMAND_NV 0x0009 -#define GL_UNIFORM_ADDRESS_COMMAND_NV 0x000A -#define GL_BLEND_COLOR_COMMAND_NV 0x000B -#define GL_STENCIL_REF_COMMAND_NV 0x000C -#define GL_LINE_WIDTH_COMMAND_NV 0x000D -#define GL_POLYGON_OFFSET_COMMAND_NV 0x000E -#define GL_ALPHA_REF_COMMAND_NV 0x000F -#define GL_VIEWPORT_COMMAND_NV 0x0010 -#define GL_SCISSOR_COMMAND_NV 0x0011 -#define GL_FRONT_FACE_COMMAND_NV 0x0012 -typedef void(APIENTRYP PFNGLCREATESTATESNVPROC)(GLsizei n, GLuint* states); -typedef void(APIENTRYP PFNGLDELETESTATESNVPROC)(GLsizei n, const GLuint* states); -typedef GLboolean(APIENTRYP PFNGLISSTATENVPROC)(GLuint state); -typedef void(APIENTRYP PFNGLSTATECAPTURENVPROC)(GLuint state, GLenum mode); -typedef GLuint(APIENTRYP PFNGLGETCOMMANDHEADERNVPROC)(GLenum tokenID, GLuint size); -typedef GLushort(APIENTRYP PFNGLGETSTAGEINDEXNVPROC)(GLenum shadertype); -typedef void(APIENTRYP PFNGLDRAWCOMMANDSNVPROC)(GLenum primitiveMode, GLuint buffer, const GLintptr* indirects, - const GLsizei* sizes, GLuint count); -typedef void(APIENTRYP PFNGLDRAWCOMMANDSADDRESSNVPROC)(GLenum primitiveMode, const GLuint64* indirects, - const GLsizei* sizes, GLuint count); -typedef void(APIENTRYP PFNGLDRAWCOMMANDSSTATESNVPROC)(GLuint buffer, const GLintptr* indirects, const GLsizei* sizes, - const GLuint* states, const GLuint* fbos, GLuint count); -typedef void(APIENTRYP PFNGLDRAWCOMMANDSSTATESADDRESSNVPROC)(const GLuint64* indirects, const GLsizei* sizes, - const GLuint* states, const GLuint* fbos, GLuint count); -typedef void(APIENTRYP PFNGLCREATECOMMANDLISTSNVPROC)(GLsizei n, GLuint* lists); -typedef void(APIENTRYP PFNGLDELETECOMMANDLISTSNVPROC)(GLsizei n, const GLuint* lists); -typedef GLboolean(APIENTRYP PFNGLISCOMMANDLISTNVPROC)(GLuint list); -typedef void(APIENTRYP PFNGLLISTDRAWCOMMANDSSTATESCLIENTNVPROC)(GLuint list, GLuint segment, const void** indirects, - const GLsizei* sizes, const GLuint* states, - const GLuint* fbos, GLuint count); -typedef void(APIENTRYP PFNGLCOMMANDLISTSEGMENTSNVPROC)(GLuint list, GLuint segments); -typedef void(APIENTRYP PFNGLCOMPILECOMMANDLISTNVPROC)(GLuint list); -typedef void(APIENTRYP PFNGLCALLCOMMANDLISTNVPROC)(GLuint list); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glCreateStatesNV(GLsizei n, GLuint* states); -GLAPI void APIENTRY glDeleteStatesNV(GLsizei n, const GLuint* states); -GLAPI GLboolean APIENTRY glIsStateNV(GLuint state); -GLAPI void APIENTRY glStateCaptureNV(GLuint state, GLenum mode); -GLAPI GLuint APIENTRY glGetCommandHeaderNV(GLenum tokenID, GLuint size); -GLAPI GLushort APIENTRY glGetStageIndexNV(GLenum shadertype); -GLAPI void APIENTRY glDrawCommandsNV(GLenum primitiveMode, GLuint buffer, const GLintptr* indirects, - const GLsizei* sizes, GLuint count); -GLAPI void APIENTRY glDrawCommandsAddressNV(GLenum primitiveMode, const GLuint64* indirects, const GLsizei* sizes, - GLuint count); -GLAPI void APIENTRY glDrawCommandsStatesNV(GLuint buffer, const GLintptr* indirects, const GLsizei* sizes, - const GLuint* states, const GLuint* fbos, GLuint count); -GLAPI void APIENTRY glDrawCommandsStatesAddressNV(const GLuint64* indirects, const GLsizei* sizes, const GLuint* states, - const GLuint* fbos, GLuint count); -GLAPI void APIENTRY glCreateCommandListsNV(GLsizei n, GLuint* lists); -GLAPI void APIENTRY glDeleteCommandListsNV(GLsizei n, const GLuint* lists); -GLAPI GLboolean APIENTRY glIsCommandListNV(GLuint list); -GLAPI void APIENTRY glListDrawCommandsStatesClientNV(GLuint list, GLuint segment, const void** indirects, - const GLsizei* sizes, const GLuint* states, const GLuint* fbos, - GLuint count); -GLAPI void APIENTRY glCommandListSegmentsNV(GLuint list, GLuint segments); -GLAPI void APIENTRY glCompileCommandListNV(GLuint list); -GLAPI void APIENTRY glCallCommandListNV(GLuint list); -#endif -#endif /* GL_NV_command_list */ - -#ifndef GL_NV_conditional_render -#define GL_NV_conditional_render 1 -#define GL_QUERY_WAIT_NV 0x8E13 -#define GL_QUERY_NO_WAIT_NV 0x8E14 -#define GL_QUERY_BY_REGION_WAIT_NV 0x8E15 -#define GL_QUERY_BY_REGION_NO_WAIT_NV 0x8E16 -typedef void(APIENTRYP PFNGLBEGINCONDITIONALRENDERNVPROC)(GLuint id, GLenum mode); -typedef void(APIENTRYP PFNGLENDCONDITIONALRENDERNVPROC)(void); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBeginConditionalRenderNV(GLuint id, GLenum mode); -GLAPI void APIENTRY glEndConditionalRenderNV(void); -#endif -#endif /* GL_NV_conditional_render */ - -#ifndef GL_NV_conservative_raster -#define GL_NV_conservative_raster 1 -#define GL_CONSERVATIVE_RASTERIZATION_NV 0x9346 -#define GL_SUBPIXEL_PRECISION_BIAS_X_BITS_NV 0x9347 -#define GL_SUBPIXEL_PRECISION_BIAS_Y_BITS_NV 0x9348 -#define GL_MAX_SUBPIXEL_PRECISION_BIAS_BITS_NV 0x9349 -typedef void(APIENTRYP PFNGLSUBPIXELPRECISIONBIASNVPROC)(GLuint xbits, GLuint ybits); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glSubpixelPrecisionBiasNV(GLuint xbits, GLuint ybits); -#endif -#endif /* GL_NV_conservative_raster */ - -#ifndef GL_NV_conservative_raster_dilate -#define GL_NV_conservative_raster_dilate 1 -#define GL_CONSERVATIVE_RASTER_DILATE_NV 0x9379 -#define GL_CONSERVATIVE_RASTER_DILATE_RANGE_NV 0x937A -#define GL_CONSERVATIVE_RASTER_DILATE_GRANULARITY_NV 0x937B -typedef void(APIENTRYP PFNGLCONSERVATIVERASTERPARAMETERFNVPROC)(GLenum pname, GLfloat value); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glConservativeRasterParameterfNV(GLenum pname, GLfloat value); -#endif -#endif /* GL_NV_conservative_raster_dilate */ - -#ifndef GL_NV_conservative_raster_pre_snap_triangles -#define GL_NV_conservative_raster_pre_snap_triangles 1 -#define GL_CONSERVATIVE_RASTER_MODE_NV 0x954D -#define GL_CONSERVATIVE_RASTER_MODE_POST_SNAP_NV 0x954E -#define GL_CONSERVATIVE_RASTER_MODE_PRE_SNAP_TRIANGLES_NV 0x954F -typedef void(APIENTRYP PFNGLCONSERVATIVERASTERPARAMETERINVPROC)(GLenum pname, GLint param); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glConservativeRasterParameteriNV(GLenum pname, GLint param); -#endif -#endif /* GL_NV_conservative_raster_pre_snap_triangles */ - -#ifndef GL_NV_draw_vulkan_image -#define GL_NV_draw_vulkan_image 1 -typedef void(APIENTRY* GLVULKANPROCNV)(void); -typedef void(APIENTRYP PFNGLDRAWVKIMAGENVPROC)(GLuint64 vkImage, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, - GLfloat y1, GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); -typedef GLVULKANPROCNV(APIENTRYP PFNGLGETVKPROCADDRNVPROC)(const GLchar* name); -typedef void(APIENTRYP PFNGLWAITVKSEMAPHORENVPROC)(GLuint64 vkSemaphore); -typedef void(APIENTRYP PFNGLSIGNALVKSEMAPHORENVPROC)(GLuint64 vkSemaphore); -typedef void(APIENTRYP PFNGLSIGNALVKFENCENVPROC)(GLuint64 vkFence); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glDrawVkImageNV(GLuint64 vkImage, GLuint sampler, GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, - GLfloat z, GLfloat s0, GLfloat t0, GLfloat s1, GLfloat t1); -GLAPI GLVULKANPROCNV APIENTRY glGetVkProcAddrNV(const GLchar* name); -GLAPI void APIENTRY glWaitVkSemaphoreNV(GLuint64 vkSemaphore); -GLAPI void APIENTRY glSignalVkSemaphoreNV(GLuint64 vkSemaphore); -GLAPI void APIENTRY glSignalVkFenceNV(GLuint64 vkFence); -#endif -#endif /* GL_NV_draw_vulkan_image */ - -#ifndef GL_NV_fill_rectangle -#define GL_NV_fill_rectangle 1 -#define GL_FILL_RECTANGLE_NV 0x933C -#endif /* GL_NV_fill_rectangle */ - -#ifndef GL_NV_fragment_coverage_to_color -#define GL_NV_fragment_coverage_to_color 1 -#define GL_FRAGMENT_COVERAGE_TO_COLOR_NV 0x92DD -#define GL_FRAGMENT_COVERAGE_COLOR_NV 0x92DE -typedef void(APIENTRYP PFNGLFRAGMENTCOVERAGECOLORNVPROC)(GLuint color); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glFragmentCoverageColorNV(GLuint color); -#endif -#endif /* GL_NV_fragment_coverage_to_color */ - -#ifndef GL_NV_fragment_shader_interlock -#define GL_NV_fragment_shader_interlock 1 -#endif /* GL_NV_fragment_shader_interlock */ - -#ifndef GL_NV_framebuffer_mixed_samples -#define GL_NV_framebuffer_mixed_samples 1 -#define GL_COVERAGE_MODULATION_TABLE_NV 0x9331 -#define GL_COLOR_SAMPLES_NV 0x8E20 -#define GL_DEPTH_SAMPLES_NV 0x932D -#define GL_STENCIL_SAMPLES_NV 0x932E -#define GL_MIXED_DEPTH_SAMPLES_SUPPORTED_NV 0x932F -#define GL_MIXED_STENCIL_SAMPLES_SUPPORTED_NV 0x9330 -#define GL_COVERAGE_MODULATION_NV 0x9332 -#define GL_COVERAGE_MODULATION_TABLE_SIZE_NV 0x9333 -typedef void(APIENTRYP PFNGLCOVERAGEMODULATIONTABLENVPROC)(GLsizei n, const GLfloat* v); -typedef void(APIENTRYP PFNGLGETCOVERAGEMODULATIONTABLENVPROC)(GLsizei bufsize, GLfloat* v); -typedef void(APIENTRYP PFNGLCOVERAGEMODULATIONNVPROC)(GLenum components); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glCoverageModulationTableNV(GLsizei n, const GLfloat* v); -GLAPI void APIENTRY glGetCoverageModulationTableNV(GLsizei bufsize, GLfloat* v); -GLAPI void APIENTRY glCoverageModulationNV(GLenum components); -#endif -#endif /* GL_NV_framebuffer_mixed_samples */ - -#ifndef GL_NV_framebuffer_multisample_coverage -#define GL_NV_framebuffer_multisample_coverage 1 -#define GL_RENDERBUFFER_COVERAGE_SAMPLES_NV 0x8CAB -#define GL_RENDERBUFFER_COLOR_SAMPLES_NV 0x8E10 -#define GL_MAX_MULTISAMPLE_COVERAGE_MODES_NV 0x8E11 -#define GL_MULTISAMPLE_COVERAGE_MODES_NV 0x8E12 -typedef void(APIENTRYP PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC)(GLenum target, GLsizei coverageSamples, - GLsizei colorSamples, GLenum internalformat, - GLsizei width, GLsizei height); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glRenderbufferStorageMultisampleCoverageNV(GLenum target, GLsizei coverageSamples, - GLsizei colorSamples, GLenum internalformat, - GLsizei width, GLsizei height); -#endif -#endif /* GL_NV_framebuffer_multisample_coverage */ - -#ifndef GL_NV_geometry_shader_passthrough -#define GL_NV_geometry_shader_passthrough 1 -#endif /* GL_NV_geometry_shader_passthrough */ - -#ifndef GL_NV_gpu_shader5 -#define GL_NV_gpu_shader5 1 -typedef int64_t GLint64EXT; -#define GL_INT64_NV 0x140E -#define GL_UNSIGNED_INT64_NV 0x140F -#define GL_INT8_NV 0x8FE0 -#define GL_INT8_VEC2_NV 0x8FE1 -#define GL_INT8_VEC3_NV 0x8FE2 -#define GL_INT8_VEC4_NV 0x8FE3 -#define GL_INT16_NV 0x8FE4 -#define GL_INT16_VEC2_NV 0x8FE5 -#define GL_INT16_VEC3_NV 0x8FE6 -#define GL_INT16_VEC4_NV 0x8FE7 -#define GL_INT64_VEC2_NV 0x8FE9 -#define GL_INT64_VEC3_NV 0x8FEA -#define GL_INT64_VEC4_NV 0x8FEB -#define GL_UNSIGNED_INT8_NV 0x8FEC -#define GL_UNSIGNED_INT8_VEC2_NV 0x8FED -#define GL_UNSIGNED_INT8_VEC3_NV 0x8FEE -#define GL_UNSIGNED_INT8_VEC4_NV 0x8FEF -#define GL_UNSIGNED_INT16_NV 0x8FF0 -#define GL_UNSIGNED_INT16_VEC2_NV 0x8FF1 -#define GL_UNSIGNED_INT16_VEC3_NV 0x8FF2 -#define GL_UNSIGNED_INT16_VEC4_NV 0x8FF3 -#define GL_UNSIGNED_INT64_VEC2_NV 0x8FF5 -#define GL_UNSIGNED_INT64_VEC3_NV 0x8FF6 -#define GL_UNSIGNED_INT64_VEC4_NV 0x8FF7 -#define GL_FLOAT16_NV 0x8FF8 -#define GL_FLOAT16_VEC2_NV 0x8FF9 -#define GL_FLOAT16_VEC3_NV 0x8FFA -#define GL_FLOAT16_VEC4_NV 0x8FFB -typedef void(APIENTRYP PFNGLUNIFORM1I64NVPROC)(GLint location, GLint64EXT x); -typedef void(APIENTRYP PFNGLUNIFORM2I64NVPROC)(GLint location, GLint64EXT x, GLint64EXT y); -typedef void(APIENTRYP PFNGLUNIFORM3I64NVPROC)(GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); -typedef void(APIENTRYP PFNGLUNIFORM4I64NVPROC)(GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); -typedef void(APIENTRYP PFNGLUNIFORM1I64VNVPROC)(GLint location, GLsizei count, const GLint64EXT* value); -typedef void(APIENTRYP PFNGLUNIFORM2I64VNVPROC)(GLint location, GLsizei count, const GLint64EXT* value); -typedef void(APIENTRYP PFNGLUNIFORM3I64VNVPROC)(GLint location, GLsizei count, const GLint64EXT* value); -typedef void(APIENTRYP PFNGLUNIFORM4I64VNVPROC)(GLint location, GLsizei count, const GLint64EXT* value); -typedef void(APIENTRYP PFNGLUNIFORM1UI64NVPROC)(GLint location, GLuint64EXT x); -typedef void(APIENTRYP PFNGLUNIFORM2UI64NVPROC)(GLint location, GLuint64EXT x, GLuint64EXT y); -typedef void(APIENTRYP PFNGLUNIFORM3UI64NVPROC)(GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); -typedef void(APIENTRYP PFNGLUNIFORM4UI64NVPROC)(GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, - GLuint64EXT w); -typedef void(APIENTRYP PFNGLUNIFORM1UI64VNVPROC)(GLint location, GLsizei count, const GLuint64EXT* value); -typedef void(APIENTRYP PFNGLUNIFORM2UI64VNVPROC)(GLint location, GLsizei count, const GLuint64EXT* value); -typedef void(APIENTRYP PFNGLUNIFORM3UI64VNVPROC)(GLint location, GLsizei count, const GLuint64EXT* value); -typedef void(APIENTRYP PFNGLUNIFORM4UI64VNVPROC)(GLint location, GLsizei count, const GLuint64EXT* value); -typedef void(APIENTRYP PFNGLGETUNIFORMI64VNVPROC)(GLuint program, GLint location, GLint64EXT* params); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1I64NVPROC)(GLuint program, GLint location, GLint64EXT x); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2I64NVPROC)(GLuint program, GLint location, GLint64EXT x, GLint64EXT y); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3I64NVPROC)(GLuint program, GLint location, GLint64EXT x, GLint64EXT y, - GLint64EXT z); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4I64NVPROC)(GLuint program, GLint location, GLint64EXT x, GLint64EXT y, - GLint64EXT z, GLint64EXT w); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1I64VNVPROC)(GLuint program, GLint location, GLsizei count, - const GLint64EXT* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2I64VNVPROC)(GLuint program, GLint location, GLsizei count, - const GLint64EXT* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3I64VNVPROC)(GLuint program, GLint location, GLsizei count, - const GLint64EXT* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4I64VNVPROC)(GLuint program, GLint location, GLsizei count, - const GLint64EXT* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1UI64NVPROC)(GLuint program, GLint location, GLuint64EXT x); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2UI64NVPROC)(GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3UI64NVPROC)(GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, - GLuint64EXT z); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4UI64NVPROC)(GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, - GLuint64EXT z, GLuint64EXT w); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM1UI64VNVPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64EXT* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM2UI64VNVPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64EXT* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM3UI64VNVPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64EXT* value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORM4UI64VNVPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64EXT* value); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glUniform1i64NV(GLint location, GLint64EXT x); -GLAPI void APIENTRY glUniform2i64NV(GLint location, GLint64EXT x, GLint64EXT y); -GLAPI void APIENTRY glUniform3i64NV(GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); -GLAPI void APIENTRY glUniform4i64NV(GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); -GLAPI void APIENTRY glUniform1i64vNV(GLint location, GLsizei count, const GLint64EXT* value); -GLAPI void APIENTRY glUniform2i64vNV(GLint location, GLsizei count, const GLint64EXT* value); -GLAPI void APIENTRY glUniform3i64vNV(GLint location, GLsizei count, const GLint64EXT* value); -GLAPI void APIENTRY glUniform4i64vNV(GLint location, GLsizei count, const GLint64EXT* value); -GLAPI void APIENTRY glUniform1ui64NV(GLint location, GLuint64EXT x); -GLAPI void APIENTRY glUniform2ui64NV(GLint location, GLuint64EXT x, GLuint64EXT y); -GLAPI void APIENTRY glUniform3ui64NV(GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); -GLAPI void APIENTRY glUniform4ui64NV(GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); -GLAPI void APIENTRY glUniform1ui64vNV(GLint location, GLsizei count, const GLuint64EXT* value); -GLAPI void APIENTRY glUniform2ui64vNV(GLint location, GLsizei count, const GLuint64EXT* value); -GLAPI void APIENTRY glUniform3ui64vNV(GLint location, GLsizei count, const GLuint64EXT* value); -GLAPI void APIENTRY glUniform4ui64vNV(GLint location, GLsizei count, const GLuint64EXT* value); -GLAPI void APIENTRY glGetUniformi64vNV(GLuint program, GLint location, GLint64EXT* params); -GLAPI void APIENTRY glProgramUniform1i64NV(GLuint program, GLint location, GLint64EXT x); -GLAPI void APIENTRY glProgramUniform2i64NV(GLuint program, GLint location, GLint64EXT x, GLint64EXT y); -GLAPI void APIENTRY glProgramUniform3i64NV(GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z); -GLAPI void APIENTRY glProgramUniform4i64NV(GLuint program, GLint location, GLint64EXT x, GLint64EXT y, GLint64EXT z, - GLint64EXT w); -GLAPI void APIENTRY glProgramUniform1i64vNV(GLuint program, GLint location, GLsizei count, const GLint64EXT* value); -GLAPI void APIENTRY glProgramUniform2i64vNV(GLuint program, GLint location, GLsizei count, const GLint64EXT* value); -GLAPI void APIENTRY glProgramUniform3i64vNV(GLuint program, GLint location, GLsizei count, const GLint64EXT* value); -GLAPI void APIENTRY glProgramUniform4i64vNV(GLuint program, GLint location, GLsizei count, const GLint64EXT* value); -GLAPI void APIENTRY glProgramUniform1ui64NV(GLuint program, GLint location, GLuint64EXT x); -GLAPI void APIENTRY glProgramUniform2ui64NV(GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y); -GLAPI void APIENTRY glProgramUniform3ui64NV(GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, - GLuint64EXT z); -GLAPI void APIENTRY glProgramUniform4ui64NV(GLuint program, GLint location, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, - GLuint64EXT w); -GLAPI void APIENTRY glProgramUniform1ui64vNV(GLuint program, GLint location, GLsizei count, const GLuint64EXT* value); -GLAPI void APIENTRY glProgramUniform2ui64vNV(GLuint program, GLint location, GLsizei count, const GLuint64EXT* value); -GLAPI void APIENTRY glProgramUniform3ui64vNV(GLuint program, GLint location, GLsizei count, const GLuint64EXT* value); -GLAPI void APIENTRY glProgramUniform4ui64vNV(GLuint program, GLint location, GLsizei count, const GLuint64EXT* value); -#endif -#endif /* GL_NV_gpu_shader5 */ - -#ifndef GL_NV_internalformat_sample_query -#define GL_NV_internalformat_sample_query 1 -#define GL_MULTISAMPLES_NV 0x9371 -#define GL_SUPERSAMPLE_SCALE_X_NV 0x9372 -#define GL_SUPERSAMPLE_SCALE_Y_NV 0x9373 -#define GL_CONFORMANT_NV 0x9374 -typedef void(APIENTRYP PFNGLGETINTERNALFORMATSAMPLEIVNVPROC)(GLenum target, GLenum internalformat, GLsizei samples, - GLenum pname, GLsizei bufSize, GLint* params); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glGetInternalformatSampleivNV(GLenum target, GLenum internalformat, GLsizei samples, GLenum pname, - GLsizei bufSize, GLint* params); -#endif -#endif /* GL_NV_internalformat_sample_query */ - -#ifndef GL_NV_path_rendering -#define GL_NV_path_rendering 1 -#define GL_PATH_FORMAT_SVG_NV 0x9070 -#define GL_PATH_FORMAT_PS_NV 0x9071 -#define GL_STANDARD_FONT_NAME_NV 0x9072 -#define GL_SYSTEM_FONT_NAME_NV 0x9073 -#define GL_FILE_NAME_NV 0x9074 -#define GL_PATH_STROKE_WIDTH_NV 0x9075 -#define GL_PATH_END_CAPS_NV 0x9076 -#define GL_PATH_INITIAL_END_CAP_NV 0x9077 -#define GL_PATH_TERMINAL_END_CAP_NV 0x9078 -#define GL_PATH_JOIN_STYLE_NV 0x9079 -#define GL_PATH_MITER_LIMIT_NV 0x907A -#define GL_PATH_DASH_CAPS_NV 0x907B -#define GL_PATH_INITIAL_DASH_CAP_NV 0x907C -#define GL_PATH_TERMINAL_DASH_CAP_NV 0x907D -#define GL_PATH_DASH_OFFSET_NV 0x907E -#define GL_PATH_CLIENT_LENGTH_NV 0x907F -#define GL_PATH_FILL_MODE_NV 0x9080 -#define GL_PATH_FILL_MASK_NV 0x9081 -#define GL_PATH_FILL_COVER_MODE_NV 0x9082 -#define GL_PATH_STROKE_COVER_MODE_NV 0x9083 -#define GL_PATH_STROKE_MASK_NV 0x9084 -#define GL_COUNT_UP_NV 0x9088 -#define GL_COUNT_DOWN_NV 0x9089 -#define GL_PATH_OBJECT_BOUNDING_BOX_NV 0x908A -#define GL_CONVEX_HULL_NV 0x908B -#define GL_BOUNDING_BOX_NV 0x908D -#define GL_TRANSLATE_X_NV 0x908E -#define GL_TRANSLATE_Y_NV 0x908F -#define GL_TRANSLATE_2D_NV 0x9090 -#define GL_TRANSLATE_3D_NV 0x9091 -#define GL_AFFINE_2D_NV 0x9092 -#define GL_AFFINE_3D_NV 0x9094 -#define GL_TRANSPOSE_AFFINE_2D_NV 0x9096 -#define GL_TRANSPOSE_AFFINE_3D_NV 0x9098 -#define GL_UTF8_NV 0x909A -#define GL_UTF16_NV 0x909B -#define GL_BOUNDING_BOX_OF_BOUNDING_BOXES_NV 0x909C -#define GL_PATH_COMMAND_COUNT_NV 0x909D -#define GL_PATH_COORD_COUNT_NV 0x909E -#define GL_PATH_DASH_ARRAY_COUNT_NV 0x909F -#define GL_PATH_COMPUTED_LENGTH_NV 0x90A0 -#define GL_PATH_FILL_BOUNDING_BOX_NV 0x90A1 -#define GL_PATH_STROKE_BOUNDING_BOX_NV 0x90A2 -#define GL_SQUARE_NV 0x90A3 -#define GL_ROUND_NV 0x90A4 -#define GL_TRIANGULAR_NV 0x90A5 -#define GL_BEVEL_NV 0x90A6 -#define GL_MITER_REVERT_NV 0x90A7 -#define GL_MITER_TRUNCATE_NV 0x90A8 -#define GL_SKIP_MISSING_GLYPH_NV 0x90A9 -#define GL_USE_MISSING_GLYPH_NV 0x90AA -#define GL_PATH_ERROR_POSITION_NV 0x90AB -#define GL_ACCUM_ADJACENT_PAIRS_NV 0x90AD -#define GL_ADJACENT_PAIRS_NV 0x90AE -#define GL_FIRST_TO_REST_NV 0x90AF -#define GL_PATH_GEN_MODE_NV 0x90B0 -#define GL_PATH_GEN_COEFF_NV 0x90B1 -#define GL_PATH_GEN_COMPONENTS_NV 0x90B3 -#define GL_PATH_STENCIL_FUNC_NV 0x90B7 -#define GL_PATH_STENCIL_REF_NV 0x90B8 -#define GL_PATH_STENCIL_VALUE_MASK_NV 0x90B9 -#define GL_PATH_STENCIL_DEPTH_OFFSET_FACTOR_NV 0x90BD -#define GL_PATH_STENCIL_DEPTH_OFFSET_UNITS_NV 0x90BE -#define GL_PATH_COVER_DEPTH_FUNC_NV 0x90BF -#define GL_PATH_DASH_OFFSET_RESET_NV 0x90B4 -#define GL_MOVE_TO_RESETS_NV 0x90B5 -#define GL_MOVE_TO_CONTINUES_NV 0x90B6 -#define GL_CLOSE_PATH_NV 0x00 -#define GL_MOVE_TO_NV 0x02 -#define GL_RELATIVE_MOVE_TO_NV 0x03 -#define GL_LINE_TO_NV 0x04 -#define GL_RELATIVE_LINE_TO_NV 0x05 -#define GL_HORIZONTAL_LINE_TO_NV 0x06 -#define GL_RELATIVE_HORIZONTAL_LINE_TO_NV 0x07 -#define GL_VERTICAL_LINE_TO_NV 0x08 -#define GL_RELATIVE_VERTICAL_LINE_TO_NV 0x09 -#define GL_QUADRATIC_CURVE_TO_NV 0x0A -#define GL_RELATIVE_QUADRATIC_CURVE_TO_NV 0x0B -#define GL_CUBIC_CURVE_TO_NV 0x0C -#define GL_RELATIVE_CUBIC_CURVE_TO_NV 0x0D -#define GL_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0E -#define GL_RELATIVE_SMOOTH_QUADRATIC_CURVE_TO_NV 0x0F -#define GL_SMOOTH_CUBIC_CURVE_TO_NV 0x10 -#define GL_RELATIVE_SMOOTH_CUBIC_CURVE_TO_NV 0x11 -#define GL_SMALL_CCW_ARC_TO_NV 0x12 -#define GL_RELATIVE_SMALL_CCW_ARC_TO_NV 0x13 -#define GL_SMALL_CW_ARC_TO_NV 0x14 -#define GL_RELATIVE_SMALL_CW_ARC_TO_NV 0x15 -#define GL_LARGE_CCW_ARC_TO_NV 0x16 -#define GL_RELATIVE_LARGE_CCW_ARC_TO_NV 0x17 -#define GL_LARGE_CW_ARC_TO_NV 0x18 -#define GL_RELATIVE_LARGE_CW_ARC_TO_NV 0x19 -#define GL_RESTART_PATH_NV 0xF0 -#define GL_DUP_FIRST_CUBIC_CURVE_TO_NV 0xF2 -#define GL_DUP_LAST_CUBIC_CURVE_TO_NV 0xF4 -#define GL_RECT_NV 0xF6 -#define GL_CIRCULAR_CCW_ARC_TO_NV 0xF8 -#define GL_CIRCULAR_CW_ARC_TO_NV 0xFA -#define GL_CIRCULAR_TANGENT_ARC_TO_NV 0xFC -#define GL_ARC_TO_NV 0xFE -#define GL_RELATIVE_ARC_TO_NV 0xFF -#define GL_BOLD_BIT_NV 0x01 -#define GL_ITALIC_BIT_NV 0x02 -#define GL_GLYPH_WIDTH_BIT_NV 0x01 -#define GL_GLYPH_HEIGHT_BIT_NV 0x02 -#define GL_GLYPH_HORIZONTAL_BEARING_X_BIT_NV 0x04 -#define GL_GLYPH_HORIZONTAL_BEARING_Y_BIT_NV 0x08 -#define GL_GLYPH_HORIZONTAL_BEARING_ADVANCE_BIT_NV 0x10 -#define GL_GLYPH_VERTICAL_BEARING_X_BIT_NV 0x20 -#define GL_GLYPH_VERTICAL_BEARING_Y_BIT_NV 0x40 -#define GL_GLYPH_VERTICAL_BEARING_ADVANCE_BIT_NV 0x80 -#define GL_GLYPH_HAS_KERNING_BIT_NV 0x100 -#define GL_FONT_X_MIN_BOUNDS_BIT_NV 0x00010000 -#define GL_FONT_Y_MIN_BOUNDS_BIT_NV 0x00020000 -#define GL_FONT_X_MAX_BOUNDS_BIT_NV 0x00040000 -#define GL_FONT_Y_MAX_BOUNDS_BIT_NV 0x00080000 -#define GL_FONT_UNITS_PER_EM_BIT_NV 0x00100000 -#define GL_FONT_ASCENDER_BIT_NV 0x00200000 -#define GL_FONT_DESCENDER_BIT_NV 0x00400000 -#define GL_FONT_HEIGHT_BIT_NV 0x00800000 -#define GL_FONT_MAX_ADVANCE_WIDTH_BIT_NV 0x01000000 -#define GL_FONT_MAX_ADVANCE_HEIGHT_BIT_NV 0x02000000 -#define GL_FONT_UNDERLINE_POSITION_BIT_NV 0x04000000 -#define GL_FONT_UNDERLINE_THICKNESS_BIT_NV 0x08000000 -#define GL_FONT_HAS_KERNING_BIT_NV 0x10000000 -#define GL_ROUNDED_RECT_NV 0xE8 -#define GL_RELATIVE_ROUNDED_RECT_NV 0xE9 -#define GL_ROUNDED_RECT2_NV 0xEA -#define GL_RELATIVE_ROUNDED_RECT2_NV 0xEB -#define GL_ROUNDED_RECT4_NV 0xEC -#define GL_RELATIVE_ROUNDED_RECT4_NV 0xED -#define GL_ROUNDED_RECT8_NV 0xEE -#define GL_RELATIVE_ROUNDED_RECT8_NV 0xEF -#define GL_RELATIVE_RECT_NV 0xF7 -#define GL_FONT_GLYPHS_AVAILABLE_NV 0x9368 -#define GL_FONT_TARGET_UNAVAILABLE_NV 0x9369 -#define GL_FONT_UNAVAILABLE_NV 0x936A -#define GL_FONT_UNINTELLIGIBLE_NV 0x936B -#define GL_CONIC_CURVE_TO_NV 0x1A -#define GL_RELATIVE_CONIC_CURVE_TO_NV 0x1B -#define GL_FONT_NUM_GLYPH_INDICES_BIT_NV 0x20000000 -#define GL_STANDARD_FONT_FORMAT_NV 0x936C -#define GL_PATH_PROJECTION_NV 0x1701 -#define GL_PATH_MODELVIEW_NV 0x1700 -#define GL_PATH_MODELVIEW_STACK_DEPTH_NV 0x0BA3 -#define GL_PATH_MODELVIEW_MATRIX_NV 0x0BA6 -#define GL_PATH_MAX_MODELVIEW_STACK_DEPTH_NV 0x0D36 -#define GL_PATH_TRANSPOSE_MODELVIEW_MATRIX_NV 0x84E3 -#define GL_PATH_PROJECTION_STACK_DEPTH_NV 0x0BA4 -#define GL_PATH_PROJECTION_MATRIX_NV 0x0BA7 -#define GL_PATH_MAX_PROJECTION_STACK_DEPTH_NV 0x0D38 -#define GL_PATH_TRANSPOSE_PROJECTION_MATRIX_NV 0x84E4 -#define GL_FRAGMENT_INPUT_NV 0x936D -typedef GLuint(APIENTRYP PFNGLGENPATHSNVPROC)(GLsizei range); -typedef void(APIENTRYP PFNGLDELETEPATHSNVPROC)(GLuint path, GLsizei range); -typedef GLboolean(APIENTRYP PFNGLISPATHNVPROC)(GLuint path); -typedef void(APIENTRYP PFNGLPATHCOMMANDSNVPROC)(GLuint path, GLsizei numCommands, const GLubyte* commands, - GLsizei numCoords, GLenum coordType, const void* coords); -typedef void(APIENTRYP PFNGLPATHCOORDSNVPROC)(GLuint path, GLsizei numCoords, GLenum coordType, const void* coords); -typedef void(APIENTRYP PFNGLPATHSUBCOMMANDSNVPROC)(GLuint path, GLsizei commandStart, GLsizei commandsToDelete, - GLsizei numCommands, const GLubyte* commands, GLsizei numCoords, - GLenum coordType, const void* coords); -typedef void(APIENTRYP PFNGLPATHSUBCOORDSNVPROC)(GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, - const void* coords); -typedef void(APIENTRYP PFNGLPATHSTRINGNVPROC)(GLuint path, GLenum format, GLsizei length, const void* pathString); -typedef void(APIENTRYP PFNGLPATHGLYPHSNVPROC)(GLuint firstPathName, GLenum fontTarget, const void* fontName, - GLbitfield fontStyle, GLsizei numGlyphs, GLenum type, - const void* charcodes, GLenum handleMissingGlyphs, - GLuint pathParameterTemplate, GLfloat emScale); -typedef void(APIENTRYP PFNGLPATHGLYPHRANGENVPROC)(GLuint firstPathName, GLenum fontTarget, const void* fontName, - GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, - GLenum handleMissingGlyphs, GLuint pathParameterTemplate, - GLfloat emScale); -typedef void(APIENTRYP PFNGLWEIGHTPATHSNVPROC)(GLuint resultPath, GLsizei numPaths, const GLuint* paths, - const GLfloat* weights); -typedef void(APIENTRYP PFNGLCOPYPATHNVPROC)(GLuint resultPath, GLuint srcPath); -typedef void(APIENTRYP PFNGLINTERPOLATEPATHSNVPROC)(GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); -typedef void(APIENTRYP PFNGLTRANSFORMPATHNVPROC)(GLuint resultPath, GLuint srcPath, GLenum transformType, - const GLfloat* transformValues); -typedef void(APIENTRYP PFNGLPATHPARAMETERIVNVPROC)(GLuint path, GLenum pname, const GLint* value); -typedef void(APIENTRYP PFNGLPATHPARAMETERINVPROC)(GLuint path, GLenum pname, GLint value); -typedef void(APIENTRYP PFNGLPATHPARAMETERFVNVPROC)(GLuint path, GLenum pname, const GLfloat* value); -typedef void(APIENTRYP PFNGLPATHPARAMETERFNVPROC)(GLuint path, GLenum pname, GLfloat value); -typedef void(APIENTRYP PFNGLPATHDASHARRAYNVPROC)(GLuint path, GLsizei dashCount, const GLfloat* dashArray); -typedef void(APIENTRYP PFNGLPATHSTENCILFUNCNVPROC)(GLenum func, GLint ref, GLuint mask); -typedef void(APIENTRYP PFNGLPATHSTENCILDEPTHOFFSETNVPROC)(GLfloat factor, GLfloat units); -typedef void(APIENTRYP PFNGLSTENCILFILLPATHNVPROC)(GLuint path, GLenum fillMode, GLuint mask); -typedef void(APIENTRYP PFNGLSTENCILSTROKEPATHNVPROC)(GLuint path, GLint reference, GLuint mask); -typedef void(APIENTRYP PFNGLSTENCILFILLPATHINSTANCEDNVPROC)(GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLenum fillMode, GLuint mask, - GLenum transformType, const GLfloat* transformValues); -typedef void(APIENTRYP PFNGLSTENCILSTROKEPATHINSTANCEDNVPROC)(GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLint reference, GLuint mask, - GLenum transformType, const GLfloat* transformValues); -typedef void(APIENTRYP PFNGLPATHCOVERDEPTHFUNCNVPROC)(GLenum func); -typedef void(APIENTRYP PFNGLCOVERFILLPATHNVPROC)(GLuint path, GLenum coverMode); -typedef void(APIENTRYP PFNGLCOVERSTROKEPATHNVPROC)(GLuint path, GLenum coverMode); -typedef void(APIENTRYP PFNGLCOVERFILLPATHINSTANCEDNVPROC)(GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLenum coverMode, GLenum transformType, - const GLfloat* transformValues); -typedef void(APIENTRYP PFNGLCOVERSTROKEPATHINSTANCEDNVPROC)(GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLenum coverMode, GLenum transformType, - const GLfloat* transformValues); -typedef void(APIENTRYP PFNGLGETPATHPARAMETERIVNVPROC)(GLuint path, GLenum pname, GLint* value); -typedef void(APIENTRYP PFNGLGETPATHPARAMETERFVNVPROC)(GLuint path, GLenum pname, GLfloat* value); -typedef void(APIENTRYP PFNGLGETPATHCOMMANDSNVPROC)(GLuint path, GLubyte* commands); -typedef void(APIENTRYP PFNGLGETPATHCOORDSNVPROC)(GLuint path, GLfloat* coords); -typedef void(APIENTRYP PFNGLGETPATHDASHARRAYNVPROC)(GLuint path, GLfloat* dashArray); -typedef void(APIENTRYP PFNGLGETPATHMETRICSNVPROC)(GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, - const void* paths, GLuint pathBase, GLsizei stride, GLfloat* metrics); -typedef void(APIENTRYP PFNGLGETPATHMETRICRANGENVPROC)(GLbitfield metricQueryMask, GLuint firstPathName, - GLsizei numPaths, GLsizei stride, GLfloat* metrics); -typedef void(APIENTRYP PFNGLGETPATHSPACINGNVPROC)(GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, - const void* paths, GLuint pathBase, GLfloat advanceScale, - GLfloat kerningScale, GLenum transformType, GLfloat* returnedSpacing); -typedef GLboolean(APIENTRYP PFNGLISPOINTINFILLPATHNVPROC)(GLuint path, GLuint mask, GLfloat x, GLfloat y); -typedef GLboolean(APIENTRYP PFNGLISPOINTINSTROKEPATHNVPROC)(GLuint path, GLfloat x, GLfloat y); -typedef GLfloat(APIENTRYP PFNGLGETPATHLENGTHNVPROC)(GLuint path, GLsizei startSegment, GLsizei numSegments); -typedef GLboolean(APIENTRYP PFNGLPOINTALONGPATHNVPROC)(GLuint path, GLsizei startSegment, GLsizei numSegments, - GLfloat distance, GLfloat* x, GLfloat* y, GLfloat* tangentX, - GLfloat* tangentY); -typedef void(APIENTRYP PFNGLMATRIXLOAD3X2FNVPROC)(GLenum matrixMode, const GLfloat* m); -typedef void(APIENTRYP PFNGLMATRIXLOAD3X3FNVPROC)(GLenum matrixMode, const GLfloat* m); -typedef void(APIENTRYP PFNGLMATRIXLOADTRANSPOSE3X3FNVPROC)(GLenum matrixMode, const GLfloat* m); -typedef void(APIENTRYP PFNGLMATRIXMULT3X2FNVPROC)(GLenum matrixMode, const GLfloat* m); -typedef void(APIENTRYP PFNGLMATRIXMULT3X3FNVPROC)(GLenum matrixMode, const GLfloat* m); -typedef void(APIENTRYP PFNGLMATRIXMULTTRANSPOSE3X3FNVPROC)(GLenum matrixMode, const GLfloat* m); -typedef void(APIENTRYP PFNGLSTENCILTHENCOVERFILLPATHNVPROC)(GLuint path, GLenum fillMode, GLuint mask, - GLenum coverMode); -typedef void(APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHNVPROC)(GLuint path, GLint reference, GLuint mask, - GLenum coverMode); -typedef void(APIENTRYP PFNGLSTENCILTHENCOVERFILLPATHINSTANCEDNVPROC)(GLsizei numPaths, GLenum pathNameType, - const void* paths, GLuint pathBase, - GLenum fillMode, GLuint mask, GLenum coverMode, - GLenum transformType, - const GLfloat* transformValues); -typedef void(APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDNVPROC)(GLsizei numPaths, GLenum pathNameType, - const void* paths, GLuint pathBase, - GLint reference, GLuint mask, GLenum coverMode, - GLenum transformType, - const GLfloat* transformValues); -typedef GLenum(APIENTRYP PFNGLPATHGLYPHINDEXRANGENVPROC)(GLenum fontTarget, const void* fontName, GLbitfield fontStyle, - GLuint pathParameterTemplate, GLfloat emScale, - GLuint baseAndCount[2]); -typedef GLenum(APIENTRYP PFNGLPATHGLYPHINDEXARRAYNVPROC)(GLuint firstPathName, GLenum fontTarget, const void* fontName, - GLbitfield fontStyle, GLuint firstGlyphIndex, - GLsizei numGlyphs, GLuint pathParameterTemplate, - GLfloat emScale); -typedef GLenum(APIENTRYP PFNGLPATHMEMORYGLYPHINDEXARRAYNVPROC)(GLuint firstPathName, GLenum fontTarget, - GLsizeiptr fontSize, const void* fontData, - GLsizei faceIndex, GLuint firstGlyphIndex, - GLsizei numGlyphs, GLuint pathParameterTemplate, - GLfloat emScale); -typedef void(APIENTRYP PFNGLPROGRAMPATHFRAGMENTINPUTGENNVPROC)(GLuint program, GLint location, GLenum genMode, - GLint components, const GLfloat* coeffs); -typedef void(APIENTRYP PFNGLGETPROGRAMRESOURCEFVNVPROC)(GLuint program, GLenum programInterface, GLuint index, - GLsizei propCount, const GLenum* props, GLsizei bufSize, - GLsizei* length, GLfloat* params); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI GLuint APIENTRY glGenPathsNV(GLsizei range); -GLAPI void APIENTRY glDeletePathsNV(GLuint path, GLsizei range); -GLAPI GLboolean APIENTRY glIsPathNV(GLuint path); -GLAPI void APIENTRY glPathCommandsNV(GLuint path, GLsizei numCommands, const GLubyte* commands, GLsizei numCoords, - GLenum coordType, const void* coords); -GLAPI void APIENTRY glPathCoordsNV(GLuint path, GLsizei numCoords, GLenum coordType, const void* coords); -GLAPI void APIENTRY glPathSubCommandsNV(GLuint path, GLsizei commandStart, GLsizei commandsToDelete, - GLsizei numCommands, const GLubyte* commands, GLsizei numCoords, - GLenum coordType, const void* coords); -GLAPI void APIENTRY glPathSubCoordsNV(GLuint path, GLsizei coordStart, GLsizei numCoords, GLenum coordType, - const void* coords); -GLAPI void APIENTRY glPathStringNV(GLuint path, GLenum format, GLsizei length, const void* pathString); -GLAPI void APIENTRY glPathGlyphsNV(GLuint firstPathName, GLenum fontTarget, const void* fontName, GLbitfield fontStyle, - GLsizei numGlyphs, GLenum type, const void* charcodes, GLenum handleMissingGlyphs, - GLuint pathParameterTemplate, GLfloat emScale); -GLAPI void APIENTRY glPathGlyphRangeNV(GLuint firstPathName, GLenum fontTarget, const void* fontName, - GLbitfield fontStyle, GLuint firstGlyph, GLsizei numGlyphs, - GLenum handleMissingGlyphs, GLuint pathParameterTemplate, GLfloat emScale); -GLAPI void APIENTRY glWeightPathsNV(GLuint resultPath, GLsizei numPaths, const GLuint* paths, const GLfloat* weights); -GLAPI void APIENTRY glCopyPathNV(GLuint resultPath, GLuint srcPath); -GLAPI void APIENTRY glInterpolatePathsNV(GLuint resultPath, GLuint pathA, GLuint pathB, GLfloat weight); -GLAPI void APIENTRY glTransformPathNV(GLuint resultPath, GLuint srcPath, GLenum transformType, - const GLfloat* transformValues); -GLAPI void APIENTRY glPathParameterivNV(GLuint path, GLenum pname, const GLint* value); -GLAPI void APIENTRY glPathParameteriNV(GLuint path, GLenum pname, GLint value); -GLAPI void APIENTRY glPathParameterfvNV(GLuint path, GLenum pname, const GLfloat* value); -GLAPI void APIENTRY glPathParameterfNV(GLuint path, GLenum pname, GLfloat value); -GLAPI void APIENTRY glPathDashArrayNV(GLuint path, GLsizei dashCount, const GLfloat* dashArray); -GLAPI void APIENTRY glPathStencilFuncNV(GLenum func, GLint ref, GLuint mask); -GLAPI void APIENTRY glPathStencilDepthOffsetNV(GLfloat factor, GLfloat units); -GLAPI void APIENTRY glStencilFillPathNV(GLuint path, GLenum fillMode, GLuint mask); -GLAPI void APIENTRY glStencilStrokePathNV(GLuint path, GLint reference, GLuint mask); -GLAPI void APIENTRY glStencilFillPathInstancedNV(GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLenum fillMode, GLuint mask, GLenum transformType, - const GLfloat* transformValues); -GLAPI void APIENTRY glStencilStrokePathInstancedNV(GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLint reference, GLuint mask, GLenum transformType, - const GLfloat* transformValues); -GLAPI void APIENTRY glPathCoverDepthFuncNV(GLenum func); -GLAPI void APIENTRY glCoverFillPathNV(GLuint path, GLenum coverMode); -GLAPI void APIENTRY glCoverStrokePathNV(GLuint path, GLenum coverMode); -GLAPI void APIENTRY glCoverFillPathInstancedNV(GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLenum coverMode, GLenum transformType, - const GLfloat* transformValues); -GLAPI void APIENTRY glCoverStrokePathInstancedNV(GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLenum coverMode, GLenum transformType, - const GLfloat* transformValues); -GLAPI void APIENTRY glGetPathParameterivNV(GLuint path, GLenum pname, GLint* value); -GLAPI void APIENTRY glGetPathParameterfvNV(GLuint path, GLenum pname, GLfloat* value); -GLAPI void APIENTRY glGetPathCommandsNV(GLuint path, GLubyte* commands); -GLAPI void APIENTRY glGetPathCoordsNV(GLuint path, GLfloat* coords); -GLAPI void APIENTRY glGetPathDashArrayNV(GLuint path, GLfloat* dashArray); -GLAPI void APIENTRY glGetPathMetricsNV(GLbitfield metricQueryMask, GLsizei numPaths, GLenum pathNameType, - const void* paths, GLuint pathBase, GLsizei stride, GLfloat* metrics); -GLAPI void APIENTRY glGetPathMetricRangeNV(GLbitfield metricQueryMask, GLuint firstPathName, GLsizei numPaths, - GLsizei stride, GLfloat* metrics); -GLAPI void APIENTRY glGetPathSpacingNV(GLenum pathListMode, GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLfloat advanceScale, GLfloat kerningScale, - GLenum transformType, GLfloat* returnedSpacing); -GLAPI GLboolean APIENTRY glIsPointInFillPathNV(GLuint path, GLuint mask, GLfloat x, GLfloat y); -GLAPI GLboolean APIENTRY glIsPointInStrokePathNV(GLuint path, GLfloat x, GLfloat y); -GLAPI GLfloat APIENTRY glGetPathLengthNV(GLuint path, GLsizei startSegment, GLsizei numSegments); -GLAPI GLboolean APIENTRY glPointAlongPathNV(GLuint path, GLsizei startSegment, GLsizei numSegments, GLfloat distance, - GLfloat* x, GLfloat* y, GLfloat* tangentX, GLfloat* tangentY); -GLAPI void APIENTRY glMatrixLoad3x2fNV(GLenum matrixMode, const GLfloat* m); -GLAPI void APIENTRY glMatrixLoad3x3fNV(GLenum matrixMode, const GLfloat* m); -GLAPI void APIENTRY glMatrixLoadTranspose3x3fNV(GLenum matrixMode, const GLfloat* m); -GLAPI void APIENTRY glMatrixMult3x2fNV(GLenum matrixMode, const GLfloat* m); -GLAPI void APIENTRY glMatrixMult3x3fNV(GLenum matrixMode, const GLfloat* m); -GLAPI void APIENTRY glMatrixMultTranspose3x3fNV(GLenum matrixMode, const GLfloat* m); -GLAPI void APIENTRY glStencilThenCoverFillPathNV(GLuint path, GLenum fillMode, GLuint mask, GLenum coverMode); -GLAPI void APIENTRY glStencilThenCoverStrokePathNV(GLuint path, GLint reference, GLuint mask, GLenum coverMode); -GLAPI void APIENTRY glStencilThenCoverFillPathInstancedNV(GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLenum fillMode, GLuint mask, - GLenum coverMode, GLenum transformType, - const GLfloat* transformValues); -GLAPI void APIENTRY glStencilThenCoverStrokePathInstancedNV(GLsizei numPaths, GLenum pathNameType, const void* paths, - GLuint pathBase, GLint reference, GLuint mask, - GLenum coverMode, GLenum transformType, - const GLfloat* transformValues); -GLAPI GLenum APIENTRY glPathGlyphIndexRangeNV(GLenum fontTarget, const void* fontName, GLbitfield fontStyle, - GLuint pathParameterTemplate, GLfloat emScale, GLuint baseAndCount[2]); -GLAPI GLenum APIENTRY glPathGlyphIndexArrayNV(GLuint firstPathName, GLenum fontTarget, const void* fontName, - GLbitfield fontStyle, GLuint firstGlyphIndex, GLsizei numGlyphs, - GLuint pathParameterTemplate, GLfloat emScale); -GLAPI GLenum APIENTRY glPathMemoryGlyphIndexArrayNV(GLuint firstPathName, GLenum fontTarget, GLsizeiptr fontSize, - const void* fontData, GLsizei faceIndex, GLuint firstGlyphIndex, - GLsizei numGlyphs, GLuint pathParameterTemplate, GLfloat emScale); -GLAPI void APIENTRY glProgramPathFragmentInputGenNV(GLuint program, GLint location, GLenum genMode, GLint components, - const GLfloat* coeffs); -GLAPI void APIENTRY glGetProgramResourcefvNV(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, - const GLenum* props, GLsizei bufSize, GLsizei* length, GLfloat* params); -#endif -#endif /* GL_NV_path_rendering */ - -#ifndef GL_NV_path_rendering_shared_edge -#define GL_NV_path_rendering_shared_edge 1 -#define GL_SHARED_EDGE_NV 0xC0 -#endif /* GL_NV_path_rendering_shared_edge */ - -#ifndef GL_NV_sample_locations -#define GL_NV_sample_locations 1 -#define GL_SAMPLE_LOCATION_SUBPIXEL_BITS_NV 0x933D -#define GL_SAMPLE_LOCATION_PIXEL_GRID_WIDTH_NV 0x933E -#define GL_SAMPLE_LOCATION_PIXEL_GRID_HEIGHT_NV 0x933F -#define GL_PROGRAMMABLE_SAMPLE_LOCATION_TABLE_SIZE_NV 0x9340 -#define GL_SAMPLE_LOCATION_NV 0x8E50 -#define GL_PROGRAMMABLE_SAMPLE_LOCATION_NV 0x9341 -#define GL_FRAMEBUFFER_PROGRAMMABLE_SAMPLE_LOCATIONS_NV 0x9342 -#define GL_FRAMEBUFFER_SAMPLE_LOCATION_PIXEL_GRID_NV 0x9343 -typedef void(APIENTRYP PFNGLFRAMEBUFFERSAMPLELOCATIONSFVNVPROC)(GLenum target, GLuint start, GLsizei count, - const GLfloat* v); -typedef void(APIENTRYP PFNGLNAMEDFRAMEBUFFERSAMPLELOCATIONSFVNVPROC)(GLuint framebuffer, GLuint start, GLsizei count, - const GLfloat* v); -typedef void(APIENTRYP PFNGLRESOLVEDEPTHVALUESNVPROC)(void); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glFramebufferSampleLocationsfvNV(GLenum target, GLuint start, GLsizei count, const GLfloat* v); -GLAPI void APIENTRY glNamedFramebufferSampleLocationsfvNV(GLuint framebuffer, GLuint start, GLsizei count, - const GLfloat* v); -GLAPI void APIENTRY glResolveDepthValuesNV(void); -#endif -#endif /* GL_NV_sample_locations */ - -#ifndef GL_NV_sample_mask_override_coverage -#define GL_NV_sample_mask_override_coverage 1 -#endif /* GL_NV_sample_mask_override_coverage */ - -#ifndef GL_NV_shader_atomic_counters -#define GL_NV_shader_atomic_counters 1 -#endif /* GL_NV_shader_atomic_counters */ - -#ifndef GL_NV_shader_atomic_float -#define GL_NV_shader_atomic_float 1 -#endif /* GL_NV_shader_atomic_float */ - -#ifndef GL_NV_shader_atomic_float64 -#define GL_NV_shader_atomic_float64 1 -#endif /* GL_NV_shader_atomic_float64 */ - -#ifndef GL_NV_shader_atomic_fp16_vector -#define GL_NV_shader_atomic_fp16_vector 1 -#endif /* GL_NV_shader_atomic_fp16_vector */ - -#ifndef GL_NV_shader_atomic_int64 -#define GL_NV_shader_atomic_int64 1 -#endif /* GL_NV_shader_atomic_int64 */ - -#ifndef GL_NV_shader_buffer_load -#define GL_NV_shader_buffer_load 1 -#define GL_BUFFER_GPU_ADDRESS_NV 0x8F1D -#define GL_GPU_ADDRESS_NV 0x8F34 -#define GL_MAX_SHADER_BUFFER_ADDRESS_NV 0x8F35 -typedef void(APIENTRYP PFNGLMAKEBUFFERRESIDENTNVPROC)(GLenum target, GLenum access); -typedef void(APIENTRYP PFNGLMAKEBUFFERNONRESIDENTNVPROC)(GLenum target); -typedef GLboolean(APIENTRYP PFNGLISBUFFERRESIDENTNVPROC)(GLenum target); -typedef void(APIENTRYP PFNGLMAKENAMEDBUFFERRESIDENTNVPROC)(GLuint buffer, GLenum access); -typedef void(APIENTRYP PFNGLMAKENAMEDBUFFERNONRESIDENTNVPROC)(GLuint buffer); -typedef GLboolean(APIENTRYP PFNGLISNAMEDBUFFERRESIDENTNVPROC)(GLuint buffer); -typedef void(APIENTRYP PFNGLGETBUFFERPARAMETERUI64VNVPROC)(GLenum target, GLenum pname, GLuint64EXT* params); -typedef void(APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERUI64VNVPROC)(GLuint buffer, GLenum pname, GLuint64EXT* params); -typedef void(APIENTRYP PFNGLGETINTEGERUI64VNVPROC)(GLenum value, GLuint64EXT* result); -typedef void(APIENTRYP PFNGLUNIFORMUI64NVPROC)(GLint location, GLuint64EXT value); -typedef void(APIENTRYP PFNGLUNIFORMUI64VNVPROC)(GLint location, GLsizei count, const GLuint64EXT* value); -typedef void(APIENTRYP PFNGLGETUNIFORMUI64VNVPROC)(GLuint program, GLint location, GLuint64EXT* params); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMUI64NVPROC)(GLuint program, GLint location, GLuint64EXT value); -typedef void(APIENTRYP PFNGLPROGRAMUNIFORMUI64VNVPROC)(GLuint program, GLint location, GLsizei count, - const GLuint64EXT* value); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glMakeBufferResidentNV(GLenum target, GLenum access); -GLAPI void APIENTRY glMakeBufferNonResidentNV(GLenum target); -GLAPI GLboolean APIENTRY glIsBufferResidentNV(GLenum target); -GLAPI void APIENTRY glMakeNamedBufferResidentNV(GLuint buffer, GLenum access); -GLAPI void APIENTRY glMakeNamedBufferNonResidentNV(GLuint buffer); -GLAPI GLboolean APIENTRY glIsNamedBufferResidentNV(GLuint buffer); -GLAPI void APIENTRY glGetBufferParameterui64vNV(GLenum target, GLenum pname, GLuint64EXT* params); -GLAPI void APIENTRY glGetNamedBufferParameterui64vNV(GLuint buffer, GLenum pname, GLuint64EXT* params); -GLAPI void APIENTRY glGetIntegerui64vNV(GLenum value, GLuint64EXT* result); -GLAPI void APIENTRY glUniformui64NV(GLint location, GLuint64EXT value); -GLAPI void APIENTRY glUniformui64vNV(GLint location, GLsizei count, const GLuint64EXT* value); -GLAPI void APIENTRY glGetUniformui64vNV(GLuint program, GLint location, GLuint64EXT* params); -GLAPI void APIENTRY glProgramUniformui64NV(GLuint program, GLint location, GLuint64EXT value); -GLAPI void APIENTRY glProgramUniformui64vNV(GLuint program, GLint location, GLsizei count, const GLuint64EXT* value); -#endif -#endif /* GL_NV_shader_buffer_load */ - -#ifndef GL_NV_shader_buffer_store -#define GL_NV_shader_buffer_store 1 -#define GL_SHADER_GLOBAL_ACCESS_BARRIER_BIT_NV 0x00000010 -#endif /* GL_NV_shader_buffer_store */ - -#ifndef GL_NV_shader_thread_group -#define GL_NV_shader_thread_group 1 -#define GL_WARP_SIZE_NV 0x9339 -#define GL_WARPS_PER_SM_NV 0x933A -#define GL_SM_COUNT_NV 0x933B -#endif /* GL_NV_shader_thread_group */ - -#ifndef GL_NV_shader_thread_shuffle -#define GL_NV_shader_thread_shuffle 1 -#endif /* GL_NV_shader_thread_shuffle */ - -#ifndef GL_NV_stereo_view_rendering -#define GL_NV_stereo_view_rendering 1 -#endif /* GL_NV_stereo_view_rendering */ - -#ifndef GL_NV_texture_barrier -#define GL_NV_texture_barrier 1 -typedef void(APIENTRYP PFNGLTEXTUREBARRIERNVPROC)(void); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glTextureBarrierNV(void); -#endif -#endif /* GL_NV_texture_barrier */ - -#ifndef GL_NV_uniform_buffer_unified_memory -#define GL_NV_uniform_buffer_unified_memory 1 -#define GL_UNIFORM_BUFFER_UNIFIED_NV 0x936E -#define GL_UNIFORM_BUFFER_ADDRESS_NV 0x936F -#define GL_UNIFORM_BUFFER_LENGTH_NV 0x9370 -#endif /* GL_NV_uniform_buffer_unified_memory */ - -#ifndef GL_NV_vertex_attrib_integer_64bit -#define GL_NV_vertex_attrib_integer_64bit 1 -typedef void(APIENTRYP PFNGLVERTEXATTRIBL1I64NVPROC)(GLuint index, GLint64EXT x); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL2I64NVPROC)(GLuint index, GLint64EXT x, GLint64EXT y); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL3I64NVPROC)(GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL4I64NVPROC)(GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, - GLint64EXT w); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL1I64VNVPROC)(GLuint index, const GLint64EXT* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL2I64VNVPROC)(GLuint index, const GLint64EXT* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL3I64VNVPROC)(GLuint index, const GLint64EXT* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL4I64VNVPROC)(GLuint index, const GLint64EXT* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL1UI64NVPROC)(GLuint index, GLuint64EXT x); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL2UI64NVPROC)(GLuint index, GLuint64EXT x, GLuint64EXT y); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL3UI64NVPROC)(GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL4UI64NVPROC)(GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, - GLuint64EXT w); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL1UI64VNVPROC)(GLuint index, const GLuint64EXT* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL2UI64VNVPROC)(GLuint index, const GLuint64EXT* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL3UI64VNVPROC)(GLuint index, const GLuint64EXT* v); -typedef void(APIENTRYP PFNGLVERTEXATTRIBL4UI64VNVPROC)(GLuint index, const GLuint64EXT* v); -typedef void(APIENTRYP PFNGLGETVERTEXATTRIBLI64VNVPROC)(GLuint index, GLenum pname, GLint64EXT* params); -typedef void(APIENTRYP PFNGLGETVERTEXATTRIBLUI64VNVPROC)(GLuint index, GLenum pname, GLuint64EXT* params); -typedef void(APIENTRYP PFNGLVERTEXATTRIBLFORMATNVPROC)(GLuint index, GLint size, GLenum type, GLsizei stride); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glVertexAttribL1i64NV(GLuint index, GLint64EXT x); -GLAPI void APIENTRY glVertexAttribL2i64NV(GLuint index, GLint64EXT x, GLint64EXT y); -GLAPI void APIENTRY glVertexAttribL3i64NV(GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z); -GLAPI void APIENTRY glVertexAttribL4i64NV(GLuint index, GLint64EXT x, GLint64EXT y, GLint64EXT z, GLint64EXT w); -GLAPI void APIENTRY glVertexAttribL1i64vNV(GLuint index, const GLint64EXT* v); -GLAPI void APIENTRY glVertexAttribL2i64vNV(GLuint index, const GLint64EXT* v); -GLAPI void APIENTRY glVertexAttribL3i64vNV(GLuint index, const GLint64EXT* v); -GLAPI void APIENTRY glVertexAttribL4i64vNV(GLuint index, const GLint64EXT* v); -GLAPI void APIENTRY glVertexAttribL1ui64NV(GLuint index, GLuint64EXT x); -GLAPI void APIENTRY glVertexAttribL2ui64NV(GLuint index, GLuint64EXT x, GLuint64EXT y); -GLAPI void APIENTRY glVertexAttribL3ui64NV(GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z); -GLAPI void APIENTRY glVertexAttribL4ui64NV(GLuint index, GLuint64EXT x, GLuint64EXT y, GLuint64EXT z, GLuint64EXT w); -GLAPI void APIENTRY glVertexAttribL1ui64vNV(GLuint index, const GLuint64EXT* v); -GLAPI void APIENTRY glVertexAttribL2ui64vNV(GLuint index, const GLuint64EXT* v); -GLAPI void APIENTRY glVertexAttribL3ui64vNV(GLuint index, const GLuint64EXT* v); -GLAPI void APIENTRY glVertexAttribL4ui64vNV(GLuint index, const GLuint64EXT* v); -GLAPI void APIENTRY glGetVertexAttribLi64vNV(GLuint index, GLenum pname, GLint64EXT* params); -GLAPI void APIENTRY glGetVertexAttribLui64vNV(GLuint index, GLenum pname, GLuint64EXT* params); -GLAPI void APIENTRY glVertexAttribLFormatNV(GLuint index, GLint size, GLenum type, GLsizei stride); -#endif -#endif /* GL_NV_vertex_attrib_integer_64bit */ - -#ifndef GL_NV_vertex_buffer_unified_memory -#define GL_NV_vertex_buffer_unified_memory 1 -#define GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV 0x8F1E -#define GL_ELEMENT_ARRAY_UNIFIED_NV 0x8F1F -#define GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV 0x8F20 -#define GL_VERTEX_ARRAY_ADDRESS_NV 0x8F21 -#define GL_NORMAL_ARRAY_ADDRESS_NV 0x8F22 -#define GL_COLOR_ARRAY_ADDRESS_NV 0x8F23 -#define GL_INDEX_ARRAY_ADDRESS_NV 0x8F24 -#define GL_TEXTURE_COORD_ARRAY_ADDRESS_NV 0x8F25 -#define GL_EDGE_FLAG_ARRAY_ADDRESS_NV 0x8F26 -#define GL_SECONDARY_COLOR_ARRAY_ADDRESS_NV 0x8F27 -#define GL_FOG_COORD_ARRAY_ADDRESS_NV 0x8F28 -#define GL_ELEMENT_ARRAY_ADDRESS_NV 0x8F29 -#define GL_VERTEX_ATTRIB_ARRAY_LENGTH_NV 0x8F2A -#define GL_VERTEX_ARRAY_LENGTH_NV 0x8F2B -#define GL_NORMAL_ARRAY_LENGTH_NV 0x8F2C -#define GL_COLOR_ARRAY_LENGTH_NV 0x8F2D -#define GL_INDEX_ARRAY_LENGTH_NV 0x8F2E -#define GL_TEXTURE_COORD_ARRAY_LENGTH_NV 0x8F2F -#define GL_EDGE_FLAG_ARRAY_LENGTH_NV 0x8F30 -#define GL_SECONDARY_COLOR_ARRAY_LENGTH_NV 0x8F31 -#define GL_FOG_COORD_ARRAY_LENGTH_NV 0x8F32 -#define GL_ELEMENT_ARRAY_LENGTH_NV 0x8F33 -#define GL_DRAW_INDIRECT_UNIFIED_NV 0x8F40 -#define GL_DRAW_INDIRECT_ADDRESS_NV 0x8F41 -#define GL_DRAW_INDIRECT_LENGTH_NV 0x8F42 -typedef void(APIENTRYP PFNGLBUFFERADDRESSRANGENVPROC)(GLenum pname, GLuint index, GLuint64EXT address, - GLsizeiptr length); -typedef void(APIENTRYP PFNGLVERTEXFORMATNVPROC)(GLint size, GLenum type, GLsizei stride); -typedef void(APIENTRYP PFNGLNORMALFORMATNVPROC)(GLenum type, GLsizei stride); -typedef void(APIENTRYP PFNGLCOLORFORMATNVPROC)(GLint size, GLenum type, GLsizei stride); -typedef void(APIENTRYP PFNGLINDEXFORMATNVPROC)(GLenum type, GLsizei stride); -typedef void(APIENTRYP PFNGLTEXCOORDFORMATNVPROC)(GLint size, GLenum type, GLsizei stride); -typedef void(APIENTRYP PFNGLEDGEFLAGFORMATNVPROC)(GLsizei stride); -typedef void(APIENTRYP PFNGLSECONDARYCOLORFORMATNVPROC)(GLint size, GLenum type, GLsizei stride); -typedef void(APIENTRYP PFNGLFOGCOORDFORMATNVPROC)(GLenum type, GLsizei stride); -typedef void(APIENTRYP PFNGLVERTEXATTRIBFORMATNVPROC)(GLuint index, GLint size, GLenum type, GLboolean normalized, - GLsizei stride); -typedef void(APIENTRYP PFNGLVERTEXATTRIBIFORMATNVPROC)(GLuint index, GLint size, GLenum type, GLsizei stride); -typedef void(APIENTRYP PFNGLGETINTEGERUI64I_VNVPROC)(GLenum value, GLuint index, GLuint64EXT* result); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glBufferAddressRangeNV(GLenum pname, GLuint index, GLuint64EXT address, GLsizeiptr length); -GLAPI void APIENTRY glVertexFormatNV(GLint size, GLenum type, GLsizei stride); -GLAPI void APIENTRY glNormalFormatNV(GLenum type, GLsizei stride); -GLAPI void APIENTRY glColorFormatNV(GLint size, GLenum type, GLsizei stride); -GLAPI void APIENTRY glIndexFormatNV(GLenum type, GLsizei stride); -GLAPI void APIENTRY glTexCoordFormatNV(GLint size, GLenum type, GLsizei stride); -GLAPI void APIENTRY glEdgeFlagFormatNV(GLsizei stride); -GLAPI void APIENTRY glSecondaryColorFormatNV(GLint size, GLenum type, GLsizei stride); -GLAPI void APIENTRY glFogCoordFormatNV(GLenum type, GLsizei stride); -GLAPI void APIENTRY glVertexAttribFormatNV(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride); -GLAPI void APIENTRY glVertexAttribIFormatNV(GLuint index, GLint size, GLenum type, GLsizei stride); -GLAPI void APIENTRY glGetIntegerui64i_vNV(GLenum value, GLuint index, GLuint64EXT* result); -#endif -#endif /* GL_NV_vertex_buffer_unified_memory */ - -#ifndef GL_NV_viewport_array2 -#define GL_NV_viewport_array2 1 -#endif /* GL_NV_viewport_array2 */ - -#ifndef GL_NV_viewport_swizzle -#define GL_NV_viewport_swizzle 1 -#define GL_VIEWPORT_SWIZZLE_POSITIVE_X_NV 0x9350 -#define GL_VIEWPORT_SWIZZLE_NEGATIVE_X_NV 0x9351 -#define GL_VIEWPORT_SWIZZLE_POSITIVE_Y_NV 0x9352 -#define GL_VIEWPORT_SWIZZLE_NEGATIVE_Y_NV 0x9353 -#define GL_VIEWPORT_SWIZZLE_POSITIVE_Z_NV 0x9354 -#define GL_VIEWPORT_SWIZZLE_NEGATIVE_Z_NV 0x9355 -#define GL_VIEWPORT_SWIZZLE_POSITIVE_W_NV 0x9356 -#define GL_VIEWPORT_SWIZZLE_NEGATIVE_W_NV 0x9357 -#define GL_VIEWPORT_SWIZZLE_X_NV 0x9358 -#define GL_VIEWPORT_SWIZZLE_Y_NV 0x9359 -#define GL_VIEWPORT_SWIZZLE_Z_NV 0x935A -#define GL_VIEWPORT_SWIZZLE_W_NV 0x935B -typedef void(APIENTRYP PFNGLVIEWPORTSWIZZLENVPROC)(GLuint index, GLenum swizzlex, GLenum swizzley, GLenum swizzlez, - GLenum swizzlew); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glViewportSwizzleNV(GLuint index, GLenum swizzlex, GLenum swizzley, GLenum swizzlez, - GLenum swizzlew); -#endif -#endif /* GL_NV_viewport_swizzle */ - -#ifndef GL_OVR_multiview -#define GL_OVR_multiview 1 -#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR 0x9630 -#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR 0x9632 -#define GL_MAX_VIEWS_OVR 0x9631 -#define GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR 0x9633 -typedef void(APIENTRYP PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)(GLenum target, GLenum attachment, GLuint texture, - GLint level, GLint baseViewIndex, GLsizei numViews); -#ifdef GL_GLEXT_PROTOTYPES -GLAPI void APIENTRY glFramebufferTextureMultiviewOVR(GLenum target, GLenum attachment, GLuint texture, GLint level, - GLint baseViewIndex, GLsizei numViews); -#endif -#endif /* GL_OVR_multiview */ - -#ifndef GL_OVR_multiview2 -#define GL_OVR_multiview2 1 -#endif /* GL_OVR_multiview2 */ - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/Framework/src/imgui/LICENSE.dearimgui b/Framework/src/imgui/LICENSE.dearimgui deleted file mode 100644 index 21b6ee7e2a..0000000000 --- a/Framework/src/imgui/LICENSE.dearimgui +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014-2018 Omar Cornut - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Framework/src/imgui/gl3w.c b/Framework/src/imgui/gl3w.c deleted file mode 100644 index 2760ebea17..0000000000 --- a/Framework/src/imgui/gl3w.c +++ /dev/null @@ -1,824 +0,0 @@ -/* - - This file was generated with gl3w_gen.py, part of gl3w - (hosted at https://github.com/skaslev/gl3w) - - This is free and unencumbered software released into the public domain. - - Anyone is free to copy, modify, publish, use, compile, sell, or - distribute this software, either in source code form or as a compiled - binary, for any purpose, commercial or non-commercial, and by any - means. - - In jurisdictions that recognize copyright laws, the author or authors - of this software dedicate any and all copyright interest in the - software to the public domain. We make this dedication for the benefit - of the public at large and to the detriment of our heirs and - successors. We intend this dedication to be an overt act of - relinquishment in perpetuity of all present and future rights to this - software under copyright law. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR - OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "GL/gl3w.h" -#include - -#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) - -#if defined(_WIN32) -#define WIN32_LEAN_AND_MEAN 1 -#include - -static HMODULE libgl; - -static void open_libgl(void) -{ - libgl = LoadLibraryA("opengl32.dll"); -} - -static void close_libgl(void) -{ - FreeLibrary(libgl); -} - -static GL3WglProc get_proc(const char *proc) -{ - GL3WglProc res; - - res = (GL3WglProc)wglGetProcAddress(proc); - if (!res) - res = (GL3WglProc)GetProcAddress(libgl, proc); - return res; -} -#elif defined(__APPLE__) -#include - -static void *libgl; - -static void open_libgl(void) -{ - libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY | RTLD_GLOBAL); -} - -static void close_libgl(void) -{ - dlclose(libgl); -} - -static GL3WglProc get_proc(const char *proc) -{ - GL3WglProc res; - - *(void **)(&res) = dlsym(libgl, proc); - return res; -} -#else -#include -#include - -static void *libgl; -static PFNGLXGETPROCADDRESSPROC glx_get_proc_address; - -static void open_libgl(void) -{ - libgl = dlopen("libGL.so.1", RTLD_LAZY | RTLD_GLOBAL); - *(void **)(&glx_get_proc_address) = dlsym(libgl, "glXGetProcAddressARB"); -} - -static void close_libgl(void) -{ - dlclose(libgl); -} - -static GL3WglProc get_proc(const char *proc) -{ - GL3WglProc res; - - res = glx_get_proc_address((const GLubyte *)proc); - if (!res) - *(void **)(&res) = dlsym(libgl, proc); - return res; -} -#endif - -static struct { - int major, minor; -} version; - -static int parse_version(void) -{ - if (!glGetIntegerv) - return -1; - - glGetIntegerv(GL_MAJOR_VERSION, &version.major); - glGetIntegerv(GL_MINOR_VERSION, &version.minor); - - if (version.major < 3) - return -1; - return 0; -} - -static void load_procs(GL3WGetProcAddressProc proc); - -int gl3wInit(void) -{ - open_libgl(); - atexit(close_libgl); - load_procs(get_proc); - return parse_version(); -} - -int gl3wInit2(GL3WGetProcAddressProc proc) -{ - open_libgl(); - atexit(close_libgl); - load_procs(proc); - return parse_version(); -} - -int gl3wIsSupported(int major, int minor) -{ - if (major < 3) - return 0; - if (version.major == major) - return version.minor >= minor; - return version.major >= major; -} - -GL3WglProc gl3wGetProcAddress(const char *proc) -{ - return get_proc(proc); -} - -static const char *proc_names[] = { - "glActiveShaderProgram", - "glActiveTexture", - "glAttachShader", - "glBeginConditionalRender", - "glBeginQuery", - "glBeginQueryIndexed", - "glBeginTransformFeedback", - "glBindAttribLocation", - "glBindBuffer", - "glBindBufferBase", - "glBindBufferRange", - "glBindBuffersBase", - "glBindBuffersRange", - "glBindFragDataLocation", - "glBindFragDataLocationIndexed", - "glBindFramebuffer", - "glBindImageTexture", - "glBindImageTextures", - "glBindProgramPipeline", - "glBindRenderbuffer", - "glBindSampler", - "glBindSamplers", - "glBindTexture", - "glBindTextureUnit", - "glBindTextures", - "glBindTransformFeedback", - "glBindVertexArray", - "glBindVertexBuffer", - "glBindVertexBuffers", - "glBlendColor", - "glBlendEquation", - "glBlendEquationSeparate", - "glBlendEquationSeparatei", - "glBlendEquationi", - "glBlendFunc", - "glBlendFuncSeparate", - "glBlendFuncSeparatei", - "glBlendFunci", - "glBlitFramebuffer", - "glBlitNamedFramebuffer", - "glBufferData", - "glBufferStorage", - "glBufferSubData", - "glCheckFramebufferStatus", - "glCheckNamedFramebufferStatus", - "glClampColor", - "glClear", - "glClearBufferData", - "glClearBufferSubData", - "glClearBufferfi", - "glClearBufferfv", - "glClearBufferiv", - "glClearBufferuiv", - "glClearColor", - "glClearDepth", - "glClearDepthf", - "glClearNamedBufferData", - "glClearNamedBufferSubData", - "glClearNamedFramebufferfi", - "glClearNamedFramebufferfv", - "glClearNamedFramebufferiv", - "glClearNamedFramebufferuiv", - "glClearStencil", - "glClearTexImage", - "glClearTexSubImage", - "glClientWaitSync", - "glClipControl", - "glColorMask", - "glColorMaski", - "glCompileShader", - "glCompressedTexImage1D", - "glCompressedTexImage2D", - "glCompressedTexImage3D", - "glCompressedTexSubImage1D", - "glCompressedTexSubImage2D", - "glCompressedTexSubImage3D", - "glCompressedTextureSubImage1D", - "glCompressedTextureSubImage2D", - "glCompressedTextureSubImage3D", - "glCopyBufferSubData", - "glCopyImageSubData", - "glCopyNamedBufferSubData", - "glCopyTexImage1D", - "glCopyTexImage2D", - "glCopyTexSubImage1D", - "glCopyTexSubImage2D", - "glCopyTexSubImage3D", - "glCopyTextureSubImage1D", - "glCopyTextureSubImage2D", - "glCopyTextureSubImage3D", - "glCreateBuffers", - "glCreateFramebuffers", - "glCreateProgram", - "glCreateProgramPipelines", - "glCreateQueries", - "glCreateRenderbuffers", - "glCreateSamplers", - "glCreateShader", - "glCreateShaderProgramv", - "glCreateTextures", - "glCreateTransformFeedbacks", - "glCreateVertexArrays", - "glCullFace", - "glDebugMessageCallback", - "glDebugMessageControl", - "glDebugMessageInsert", - "glDeleteBuffers", - "glDeleteFramebuffers", - "glDeleteProgram", - "glDeleteProgramPipelines", - "glDeleteQueries", - "glDeleteRenderbuffers", - "glDeleteSamplers", - "glDeleteShader", - "glDeleteSync", - "glDeleteTextures", - "glDeleteTransformFeedbacks", - "glDeleteVertexArrays", - "glDepthFunc", - "glDepthMask", - "glDepthRange", - "glDepthRangeArrayv", - "glDepthRangeIndexed", - "glDepthRangef", - "glDetachShader", - "glDisable", - "glDisableVertexArrayAttrib", - "glDisableVertexAttribArray", - "glDisablei", - "glDispatchCompute", - "glDispatchComputeIndirect", - "glDrawArrays", - "glDrawArraysIndirect", - "glDrawArraysInstanced", - "glDrawArraysInstancedBaseInstance", - "glDrawBuffer", - "glDrawBuffers", - "glDrawElements", - "glDrawElementsBaseVertex", - "glDrawElementsIndirect", - "glDrawElementsInstanced", - "glDrawElementsInstancedBaseInstance", - "glDrawElementsInstancedBaseVertex", - "glDrawElementsInstancedBaseVertexBaseInstance", - "glDrawRangeElements", - "glDrawRangeElementsBaseVertex", - "glDrawTransformFeedback", - "glDrawTransformFeedbackInstanced", - "glDrawTransformFeedbackStream", - "glDrawTransformFeedbackStreamInstanced", - "glEnable", - "glEnableVertexArrayAttrib", - "glEnableVertexAttribArray", - "glEnablei", - "glEndConditionalRender", - "glEndQuery", - "glEndQueryIndexed", - "glEndTransformFeedback", - "glFenceSync", - "glFinish", - "glFlush", - "glFlushMappedBufferRange", - "glFlushMappedNamedBufferRange", - "glFramebufferParameteri", - "glFramebufferRenderbuffer", - "glFramebufferTexture", - "glFramebufferTexture1D", - "glFramebufferTexture2D", - "glFramebufferTexture3D", - "glFramebufferTextureLayer", - "glFrontFace", - "glGenBuffers", - "glGenFramebuffers", - "glGenProgramPipelines", - "glGenQueries", - "glGenRenderbuffers", - "glGenSamplers", - "glGenTextures", - "glGenTransformFeedbacks", - "glGenVertexArrays", - "glGenerateMipmap", - "glGenerateTextureMipmap", - "glGetActiveAtomicCounterBufferiv", - "glGetActiveAttrib", - "glGetActiveSubroutineName", - "glGetActiveSubroutineUniformName", - "glGetActiveSubroutineUniformiv", - "glGetActiveUniform", - "glGetActiveUniformBlockName", - "glGetActiveUniformBlockiv", - "glGetActiveUniformName", - "glGetActiveUniformsiv", - "glGetAttachedShaders", - "glGetAttribLocation", - "glGetBooleani_v", - "glGetBooleanv", - "glGetBufferParameteri64v", - "glGetBufferParameteriv", - "glGetBufferPointerv", - "glGetBufferSubData", - "glGetCompressedTexImage", - "glGetCompressedTextureImage", - "glGetCompressedTextureSubImage", - "glGetDebugMessageLog", - "glGetDoublei_v", - "glGetDoublev", - "glGetError", - "glGetFloati_v", - "glGetFloatv", - "glGetFragDataIndex", - "glGetFragDataLocation", - "glGetFramebufferAttachmentParameteriv", - "glGetFramebufferParameteriv", - "glGetGraphicsResetStatus", - "glGetInteger64i_v", - "glGetInteger64v", - "glGetIntegeri_v", - "glGetIntegerv", - "glGetInternalformati64v", - "glGetInternalformativ", - "glGetMultisamplefv", - "glGetNamedBufferParameteri64v", - "glGetNamedBufferParameteriv", - "glGetNamedBufferPointerv", - "glGetNamedBufferSubData", - "glGetNamedFramebufferAttachmentParameteriv", - "glGetNamedFramebufferParameteriv", - "glGetNamedRenderbufferParameteriv", - "glGetObjectLabel", - "glGetObjectPtrLabel", - "glGetPointerv", - "glGetProgramBinary", - "glGetProgramInfoLog", - "glGetProgramInterfaceiv", - "glGetProgramPipelineInfoLog", - "glGetProgramPipelineiv", - "glGetProgramResourceIndex", - "glGetProgramResourceLocation", - "glGetProgramResourceLocationIndex", - "glGetProgramResourceName", - "glGetProgramResourceiv", - "glGetProgramStageiv", - "glGetProgramiv", - "glGetQueryBufferObjecti64v", - "glGetQueryBufferObjectiv", - "glGetQueryBufferObjectui64v", - "glGetQueryBufferObjectuiv", - "glGetQueryIndexediv", - "glGetQueryObjecti64v", - "glGetQueryObjectiv", - "glGetQueryObjectui64v", - "glGetQueryObjectuiv", - "glGetQueryiv", - "glGetRenderbufferParameteriv", - "glGetSamplerParameterIiv", - "glGetSamplerParameterIuiv", - "glGetSamplerParameterfv", - "glGetSamplerParameteriv", - "glGetShaderInfoLog", - "glGetShaderPrecisionFormat", - "glGetShaderSource", - "glGetShaderiv", - "glGetString", - "glGetStringi", - "glGetSubroutineIndex", - "glGetSubroutineUniformLocation", - "glGetSynciv", - "glGetTexImage", - "glGetTexLevelParameterfv", - "glGetTexLevelParameteriv", - "glGetTexParameterIiv", - "glGetTexParameterIuiv", - "glGetTexParameterfv", - "glGetTexParameteriv", - "glGetTextureImage", - "glGetTextureLevelParameterfv", - "glGetTextureLevelParameteriv", - "glGetTextureParameterIiv", - "glGetTextureParameterIuiv", - "glGetTextureParameterfv", - "glGetTextureParameteriv", - "glGetTextureSubImage", - "glGetTransformFeedbackVarying", - "glGetTransformFeedbacki64_v", - "glGetTransformFeedbacki_v", - "glGetTransformFeedbackiv", - "glGetUniformBlockIndex", - "glGetUniformIndices", - "glGetUniformLocation", - "glGetUniformSubroutineuiv", - "glGetUniformdv", - "glGetUniformfv", - "glGetUniformiv", - "glGetUniformuiv", - "glGetVertexArrayIndexed64iv", - "glGetVertexArrayIndexediv", - "glGetVertexArrayiv", - "glGetVertexAttribIiv", - "glGetVertexAttribIuiv", - "glGetVertexAttribLdv", - "glGetVertexAttribPointerv", - "glGetVertexAttribdv", - "glGetVertexAttribfv", - "glGetVertexAttribiv", - "glGetnCompressedTexImage", - "glGetnTexImage", - "glGetnUniformdv", - "glGetnUniformfv", - "glGetnUniformiv", - "glGetnUniformuiv", - "glHint", - "glInvalidateBufferData", - "glInvalidateBufferSubData", - "glInvalidateFramebuffer", - "glInvalidateNamedFramebufferData", - "glInvalidateNamedFramebufferSubData", - "glInvalidateSubFramebuffer", - "glInvalidateTexImage", - "glInvalidateTexSubImage", - "glIsBuffer", - "glIsEnabled", - "glIsEnabledi", - "glIsFramebuffer", - "glIsProgram", - "glIsProgramPipeline", - "glIsQuery", - "glIsRenderbuffer", - "glIsSampler", - "glIsShader", - "glIsSync", - "glIsTexture", - "glIsTransformFeedback", - "glIsVertexArray", - "glLineWidth", - "glLinkProgram", - "glLogicOp", - "glMapBuffer", - "glMapBufferRange", - "glMapNamedBuffer", - "glMapNamedBufferRange", - "glMemoryBarrier", - "glMemoryBarrierByRegion", - "glMinSampleShading", - "glMultiDrawArrays", - "glMultiDrawArraysIndirect", - "glMultiDrawElements", - "glMultiDrawElementsBaseVertex", - "glMultiDrawElementsIndirect", - "glNamedBufferData", - "glNamedBufferStorage", - "glNamedBufferSubData", - "glNamedFramebufferDrawBuffer", - "glNamedFramebufferDrawBuffers", - "glNamedFramebufferParameteri", - "glNamedFramebufferReadBuffer", - "glNamedFramebufferRenderbuffer", - "glNamedFramebufferTexture", - "glNamedFramebufferTextureLayer", - "glNamedRenderbufferStorage", - "glNamedRenderbufferStorageMultisample", - "glObjectLabel", - "glObjectPtrLabel", - "glPatchParameterfv", - "glPatchParameteri", - "glPauseTransformFeedback", - "glPixelStoref", - "glPixelStorei", - "glPointParameterf", - "glPointParameterfv", - "glPointParameteri", - "glPointParameteriv", - "glPointSize", - "glPolygonMode", - "glPolygonOffset", - "glPopDebugGroup", - "glPrimitiveRestartIndex", - "glProgramBinary", - "glProgramParameteri", - "glProgramUniform1d", - "glProgramUniform1dv", - "glProgramUniform1f", - "glProgramUniform1fv", - "glProgramUniform1i", - "glProgramUniform1iv", - "glProgramUniform1ui", - "glProgramUniform1uiv", - "glProgramUniform2d", - "glProgramUniform2dv", - "glProgramUniform2f", - "glProgramUniform2fv", - "glProgramUniform2i", - "glProgramUniform2iv", - "glProgramUniform2ui", - "glProgramUniform2uiv", - "glProgramUniform3d", - "glProgramUniform3dv", - "glProgramUniform3f", - "glProgramUniform3fv", - "glProgramUniform3i", - "glProgramUniform3iv", - "glProgramUniform3ui", - "glProgramUniform3uiv", - "glProgramUniform4d", - "glProgramUniform4dv", - "glProgramUniform4f", - "glProgramUniform4fv", - "glProgramUniform4i", - "glProgramUniform4iv", - "glProgramUniform4ui", - "glProgramUniform4uiv", - "glProgramUniformMatrix2dv", - "glProgramUniformMatrix2fv", - "glProgramUniformMatrix2x3dv", - "glProgramUniformMatrix2x3fv", - "glProgramUniformMatrix2x4dv", - "glProgramUniformMatrix2x4fv", - "glProgramUniformMatrix3dv", - "glProgramUniformMatrix3fv", - "glProgramUniformMatrix3x2dv", - "glProgramUniformMatrix3x2fv", - "glProgramUniformMatrix3x4dv", - "glProgramUniformMatrix3x4fv", - "glProgramUniformMatrix4dv", - "glProgramUniformMatrix4fv", - "glProgramUniformMatrix4x2dv", - "glProgramUniformMatrix4x2fv", - "glProgramUniformMatrix4x3dv", - "glProgramUniformMatrix4x3fv", - "glProvokingVertex", - "glPushDebugGroup", - "glQueryCounter", - "glReadBuffer", - "glReadPixels", - "glReadnPixels", - "glReleaseShaderCompiler", - "glRenderbufferStorage", - "glRenderbufferStorageMultisample", - "glResumeTransformFeedback", - "glSampleCoverage", - "glSampleMaski", - "glSamplerParameterIiv", - "glSamplerParameterIuiv", - "glSamplerParameterf", - "glSamplerParameterfv", - "glSamplerParameteri", - "glSamplerParameteriv", - "glScissor", - "glScissorArrayv", - "glScissorIndexed", - "glScissorIndexedv", - "glShaderBinary", - "glShaderSource", - "glShaderStorageBlockBinding", - "glStencilFunc", - "glStencilFuncSeparate", - "glStencilMask", - "glStencilMaskSeparate", - "glStencilOp", - "glStencilOpSeparate", - "glTexBuffer", - "glTexBufferRange", - "glTexImage1D", - "glTexImage2D", - "glTexImage2DMultisample", - "glTexImage3D", - "glTexImage3DMultisample", - "glTexParameterIiv", - "glTexParameterIuiv", - "glTexParameterf", - "glTexParameterfv", - "glTexParameteri", - "glTexParameteriv", - "glTexStorage1D", - "glTexStorage2D", - "glTexStorage2DMultisample", - "glTexStorage3D", - "glTexStorage3DMultisample", - "glTexSubImage1D", - "glTexSubImage2D", - "glTexSubImage3D", - "glTextureBarrier", - "glTextureBuffer", - "glTextureBufferRange", - "glTextureParameterIiv", - "glTextureParameterIuiv", - "glTextureParameterf", - "glTextureParameterfv", - "glTextureParameteri", - "glTextureParameteriv", - "glTextureStorage1D", - "glTextureStorage2D", - "glTextureStorage2DMultisample", - "glTextureStorage3D", - "glTextureStorage3DMultisample", - "glTextureSubImage1D", - "glTextureSubImage2D", - "glTextureSubImage3D", - "glTextureView", - "glTransformFeedbackBufferBase", - "glTransformFeedbackBufferRange", - "glTransformFeedbackVaryings", - "glUniform1d", - "glUniform1dv", - "glUniform1f", - "glUniform1fv", - "glUniform1i", - "glUniform1iv", - "glUniform1ui", - "glUniform1uiv", - "glUniform2d", - "glUniform2dv", - "glUniform2f", - "glUniform2fv", - "glUniform2i", - "glUniform2iv", - "glUniform2ui", - "glUniform2uiv", - "glUniform3d", - "glUniform3dv", - "glUniform3f", - "glUniform3fv", - "glUniform3i", - "glUniform3iv", - "glUniform3ui", - "glUniform3uiv", - "glUniform4d", - "glUniform4dv", - "glUniform4f", - "glUniform4fv", - "glUniform4i", - "glUniform4iv", - "glUniform4ui", - "glUniform4uiv", - "glUniformBlockBinding", - "glUniformMatrix2dv", - "glUniformMatrix2fv", - "glUniformMatrix2x3dv", - "glUniformMatrix2x3fv", - "glUniformMatrix2x4dv", - "glUniformMatrix2x4fv", - "glUniformMatrix3dv", - "glUniformMatrix3fv", - "glUniformMatrix3x2dv", - "glUniformMatrix3x2fv", - "glUniformMatrix3x4dv", - "glUniformMatrix3x4fv", - "glUniformMatrix4dv", - "glUniformMatrix4fv", - "glUniformMatrix4x2dv", - "glUniformMatrix4x2fv", - "glUniformMatrix4x3dv", - "glUniformMatrix4x3fv", - "glUniformSubroutinesuiv", - "glUnmapBuffer", - "glUnmapNamedBuffer", - "glUseProgram", - "glUseProgramStages", - "glValidateProgram", - "glValidateProgramPipeline", - "glVertexArrayAttribBinding", - "glVertexArrayAttribFormat", - "glVertexArrayAttribIFormat", - "glVertexArrayAttribLFormat", - "glVertexArrayBindingDivisor", - "glVertexArrayElementBuffer", - "glVertexArrayVertexBuffer", - "glVertexArrayVertexBuffers", - "glVertexAttrib1d", - "glVertexAttrib1dv", - "glVertexAttrib1f", - "glVertexAttrib1fv", - "glVertexAttrib1s", - "glVertexAttrib1sv", - "glVertexAttrib2d", - "glVertexAttrib2dv", - "glVertexAttrib2f", - "glVertexAttrib2fv", - "glVertexAttrib2s", - "glVertexAttrib2sv", - "glVertexAttrib3d", - "glVertexAttrib3dv", - "glVertexAttrib3f", - "glVertexAttrib3fv", - "glVertexAttrib3s", - "glVertexAttrib3sv", - "glVertexAttrib4Nbv", - "glVertexAttrib4Niv", - "glVertexAttrib4Nsv", - "glVertexAttrib4Nub", - "glVertexAttrib4Nubv", - "glVertexAttrib4Nuiv", - "glVertexAttrib4Nusv", - "glVertexAttrib4bv", - "glVertexAttrib4d", - "glVertexAttrib4dv", - "glVertexAttrib4f", - "glVertexAttrib4fv", - "glVertexAttrib4iv", - "glVertexAttrib4s", - "glVertexAttrib4sv", - "glVertexAttrib4ubv", - "glVertexAttrib4uiv", - "glVertexAttrib4usv", - "glVertexAttribBinding", - "glVertexAttribDivisor", - "glVertexAttribFormat", - "glVertexAttribI1i", - "glVertexAttribI1iv", - "glVertexAttribI1ui", - "glVertexAttribI1uiv", - "glVertexAttribI2i", - "glVertexAttribI2iv", - "glVertexAttribI2ui", - "glVertexAttribI2uiv", - "glVertexAttribI3i", - "glVertexAttribI3iv", - "glVertexAttribI3ui", - "glVertexAttribI3uiv", - "glVertexAttribI4bv", - "glVertexAttribI4i", - "glVertexAttribI4iv", - "glVertexAttribI4sv", - "glVertexAttribI4ubv", - "glVertexAttribI4ui", - "glVertexAttribI4uiv", - "glVertexAttribI4usv", - "glVertexAttribIFormat", - "glVertexAttribIPointer", - "glVertexAttribL1d", - "glVertexAttribL1dv", - "glVertexAttribL2d", - "glVertexAttribL2dv", - "glVertexAttribL3d", - "glVertexAttribL3dv", - "glVertexAttribL4d", - "glVertexAttribL4dv", - "glVertexAttribLFormat", - "glVertexAttribLPointer", - "glVertexAttribP1ui", - "glVertexAttribP1uiv", - "glVertexAttribP2ui", - "glVertexAttribP2uiv", - "glVertexAttribP3ui", - "glVertexAttribP3uiv", - "glVertexAttribP4ui", - "glVertexAttribP4uiv", - "glVertexAttribPointer", - "glVertexBindingDivisor", - "glViewport", - "glViewportArrayv", - "glViewportIndexedf", - "glViewportIndexedfv", - "glWaitSync", -}; - -union GL3WProcs gl3wProcs; - -static void load_procs(GL3WGetProcAddressProc proc) -{ - size_t i; - for (i = 0; i < ARRAY_SIZE(proc_names); i++) - gl3wProcs.ptr[i] = proc(proc_names[i]); -} diff --git a/Framework/src/imgui/imconfig.h b/Framework/src/imgui/imconfig.h deleted file mode 100644 index 7bc1fccb9f..0000000000 --- a/Framework/src/imgui/imconfig.h +++ /dev/null @@ -1,79 +0,0 @@ -//----------------------------------------------------------------------------- -// COMPILE-TIME OPTIONS FOR DEAR IMGUI -// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO -// structure. You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory -// allocation functions. -//----------------------------------------------------------------------------- -// A) You may edit imconfig.h (and not overwrite it when updating imgui, or maintain a patch/branch with your -// modifications to imconfig.h) B) or add configuration directives in your own file and compile with #define -// IMGUI_USER_CONFIG "myfilename.h" If you do so you need to make sure that configuration settings are defined -// consistently _everywhere_ dear imgui is used, which include the imgui*.cpp files but also _any_ of your code that -// uses imgui. This is because some compile-time options have an affect on data structures. Defining those options in -// imconfig.h will ensure every compilation unit gets to see the same data structure layouts. Call IMGUI_CHECKVERSION() -// from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is -// using. -//----------------------------------------------------------------------------- - -#pragma once - -//---- Define assertion handler. Defaults to calling assert(). -//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) -//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts - -//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows. -//#define IMGUI_API __declspec( dllexport ) -//#define IMGUI_API __declspec( dllimport ) - -//---- Don't define obsolete functions/enums names. Consider enabling from time to time after updating to avoid using -//soon-to-be obsolete function/names. #define IMGUI_DISABLE_OBSOLETE_FUNCTIONS - -//---- Don't implement demo windows functionality (ShowDemoWindow()/ShowStyleEditor()/ShowUserGuide() methods will be -//empty) -//---- It is very strongly recommended to NOT disable the demo windows during development. Please read the comments in -//imgui_demo.cpp. #define IMGUI_DISABLE_DEMO_WINDOWS - -//---- Don't implement some functions to reduce linkage requirements. -//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't -//use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. #define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS -//// [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. #define -//IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can -//implement them yourself if you don't want to link with vsnprintf. #define IMGUI_DISABLE_MATH_FUNCTIONS // Don't -//implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 wrapper so you can implement them yourself. Declare -//your prototypes in imconfig.h. #define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default -//allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). - -//---- Include imgui_user.h at the end of imgui.h as a convenience -//#define IMGUI_INCLUDE_IMGUI_USER_H - -//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) -//#define IMGUI_USE_BGRA_PACKED_COLOR - -//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version -// By default the embedded implementations are declared static and not available outside of imgui cpp files. -//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" -//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" -//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION -//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION - -//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. -// This will be inlined as part of ImVec2 and ImVec4 class declarations. -/* -#define IM_VEC2_CLASS_EXTRA \ - ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ - operator MyVec2() const { return MyVec2(x,y); } - -#define IM_VEC4_CLASS_EXTRA \ - ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ - operator MyVec4() const { return MyVec4(x,y,z,w); } -*/ - -//---- Use 32-bit vertex indices (default is 16-bit) to allow meshes with more than 64K vertices. Render function needs -//to support it. #define ImDrawIdx unsigned int - -//---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. -/* -namespace ImGui -{ - void MyFunction(const char* name, const MyMatrix44& v); -} -*/ diff --git a/Framework/src/imgui/imgui.cpp b/Framework/src/imgui/imgui.cpp deleted file mode 100644 index e6a9bc1afe..0000000000 --- a/Framework/src/imgui/imgui.cpp +++ /dev/null @@ -1,8970 +0,0 @@ -// dear imgui, v1.65 -// (main code and documentation) - -// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. -// Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase. -// Get latest version at https://github.com/ocornut/imgui -// Releases change-log at https://github.com/ocornut/imgui/releases -// Technical Support for Getting Started https://discourse.dearimgui.org/c/getting-started -// Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/1269 -// Developed by Omar Cornut and every direct or indirect contributors to the GitHub. -// This library is free but I need your support to sustain development and maintenance. -// If you work for a company, please consider financial support, see README. For individuals: https://www.patreon.com/imgui - -// It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library. -// Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without -// modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't -// come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you -// to a better solution or official support for them. - -/* - -Index of this file: - -DOCUMENTATION - -- MISSION STATEMENT -- END-USER GUIDE -- PROGRAMMER GUIDE (read me!) - - Read first - - How to update to a newer version of Dear ImGui - - Getting started with integrating Dear ImGui in your code/engine - - This is how a simple application may look like (2 variations) - - This is how a simple rendering function may look like - - Using gamepad/keyboard navigation controls -- API BREAKING CHANGES (read me when you update!) -- FREQUENTLY ASKED QUESTIONS (FAQ), TIPS - - How can I tell whether to dispatch mouse/keyboard to imgui or to my application? - - How can I display an image? What is ImTextureID, how does it works? - - How can I have multiple widgets with the same label or without a label? A primer on labels and the ID Stack. - - How can I use my own math types instead of ImVec2/ImVec4? - - How can I load a different font than the default? - - How can I easily use icons in my application? - - How can I load multiple fonts? - - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic? - - How can I use the drawing facilities without an ImGui window? (using ImDrawList API) - - I integrated Dear ImGui in my engine and the text or lines are blurry.. - - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. - - How can I help? - -CODE -(search for "[SECTION]" in the code to find them) - -// [SECTION] FORWARD DECLARATIONS -// [SECTION] CONTEXT AND MEMORY ALLOCATORS -// [SECTION] MAIN USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) -// [SECTION] MISC HELPER/UTILITIES (Maths, String, Format, Hash, File functions) -// [SECTION] MISC HELPER/UTILITIES (ImText* functions) -// [SECTION] MISC HELPER/UTILITIES (Color functions) -// [SECTION] ImGuiStorage -// [SECTION] ImGuiTextFilter -// [SECTION] ImGuiTextBuffer -// [SECTION] ImGuiListClipper -// [SECTION] RENDER HELPERS -// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) -// [SECTION] TOOLTIPS -// [SECTION] POPUPS -// [SECTION] KEYBOARD/GAMEPAD NAVIGATION -// [SECTION] COLUMNS -// [SECTION] DRAG AND DROP -// [SECTION] LOGGING/CAPTURING -// [SECTION] SETTINGS -// [SECTION] PLATFORM DEPENDENT HELPERS -// [SECTION] METRICS/DEBUG WINDOW - -*/ - -//----------------------------------------------------------------------------- -// DOCUMENTATION -//----------------------------------------------------------------------------- - -/* - - MISSION STATEMENT - ================= - - - Easy to use to create code-driven and data-driven tools - - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools - - Easy to hack and improve - - Minimize screen real-estate usage - - Minimize setup and maintenance - - Minimize state storage on user side - - Portable, minimize dependencies, run on target (consoles, phones, etc.) - - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window, - opening a tree node for the first time, etc. but a typical frame should not allocate anything) - - Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes: - - Doesn't look fancy, doesn't animate - - Limited layout features, intricate layouts are typically crafted in code - - - END-USER GUIDE - ============== - - - Double-click on title bar to collapse window. - - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin(). - - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents). - - Click and drag on any empty space to move window. - - TAB/SHIFT+TAB to cycle through keyboard editable fields. - - CTRL+Click on a slider or drag box to input value as text. - - Use mouse wheel to scroll. - - Text editor: - - Hold SHIFT or use mouse to select text. - - CTRL+Left/Right to word jump. - - CTRL+Shift+Left/Right to select words. - - CTRL+A our Double-Click to select all. - - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/ - - CTRL+Z,CTRL+Y to undo/redo. - - ESCAPE to revert text to its original value. - - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!) - - Controls are automatically adjusted for OSX to match standard OSX text editing operations. - - General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard. - - General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. See suggested mappings in imgui.h ImGuiNavInput_ + download PNG/PSD at http://goo.gl/9LgVZW - - - PROGRAMMER GUIDE - ================ - - READ FIRST - - - Read the FAQ below this section! - - Your code creates the UI, if your code doesn't run the UI is gone! The UI can be highly dynamic, there are no construction - or destruction steps, less data retention on your side, less state duplication, less state synchronization, less bugs. - - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features. - - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861 - - HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI - - - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h) - - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes. - If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed - from the public API. If you have a problem with a missing function/symbols, search for its name in the code, there will - likely be a comment about it. Please report any issue to the GitHub page! - - Try to keep your copy of dear imgui reasonably up to date. - - GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE - - - Run and study the examples and demo to get acquainted with the library. - - Add the Dear ImGui source files to your projects or using your preferred build system. - It is recommended you build the .cpp files as part of your project and not as a library. - - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types. - - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/ folder. - - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them. - - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function that you provide. - Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" - phases of your own application. All rendering informatioe are stored into command-lists that you will retrieve after calling ImGui::Render(). - - Refer to the bindings and demo applications in the examples/ folder for instruction on how to setup your code. - - THIS IS HOW A SIMPLE APPLICATION MAY LOOK LIKE - EXHIBIT 1: USING THE EXAMPLE BINDINGS (imgui_impl_XXX.cpp files from the examples/ folder) - - // Application init: create a dear imgui context, setup some options, load fonts - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls - // TODO: Fill optional fields of the io structure later. - // TODO: Load TTF/OTF fonts if you don't want to use the default font. - - // Initialize helper Platform and Renderer bindings (here we are using imgui_impl_win32 and imgui_impl_dx11) - ImGui_ImplWin32_Init(hwnd); - ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext); - - // Application main loop - while (true) - { - // Feed inputs to dear imgui, start new frame - ImGui_ImplDX11_NewFrame(); - ImGui_ImplWin32_NewFrame(); - ImGui::NewFrame(); - - // Any application code here - ImGui::Text("Hello, world!"); - - // Render dear imgui into screen - ImGui::Render(); - ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); - g_pSwapChain->Present(1, 0); - } - - // Shutdown - ImGui_ImplDX11_Shutdown(); - ImGui_ImplWin32_Shutdown(); - ImGui::DestroyContext(); - - THIS IS HOW A SIMPLE APPLICATION MAY LOOK LIKE - EXHIBIT 2: IMPLEMENTING CUSTOM BINDING / CUSTOM ENGINE - - // Application init: create a dear imgui context, setup some options, load fonts - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls - // TODO: Fill optional fields of the io structure later. - // TODO: Load TTF/OTF fonts if you don't want to use the default font. - - // Build and load the texture atlas into a texture - // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer) - int width, height; - unsigned char* pixels = NULL; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - // At this point you've got the texture data and you need to upload that your your graphic system: - // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'. - // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ below for details about ImTextureID. - MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32) - io.Fonts->TexID = (void*)texture; - - // Application main loop - while (true) - { - // Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows message handlers, etc. - // (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo Platform bindings) - io.DeltaTime = 1.0f/60.0f; // set the time elapsed since the previous frame (in seconds) - io.DisplaySize.x = 1920.0f; // set the current display width - io.DisplaySize.y = 1280.0f; // set the current display height here - io.MousePos = my_mouse_pos; // set the mouse position - io.MouseDown[0] = my_mouse_buttons[0]; // set the mouse button states - io.MouseDown[1] = my_mouse_buttons[1]; - - // Call NewFrame(), after this point you can use ImGui::* functions anytime - // (So you want to try calling NewFrame() as early as you can in your mainloop to be able to use imgui everywhere) - ImGui::NewFrame(); - - // Most of your application code here - ImGui::Text("Hello, world!"); - MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); - MyGameRender(); // may use any ImGui functions as well! - - // Render imgui, swap buffers - // (You want to try calling EndFrame/Render as late as you can, to be able to use imgui in your own game rendering code) - ImGui::EndFrame(); - ImGui::Render(); - ImDrawData* draw_data = ImGui::GetDrawData(); - MyImGuiRenderFunction(draw_data); - SwapBuffers(); - } - - // Shutdown - ImGui::DestroyContext(); - - THIS HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE - - void void MyImGuiRenderFunction(ImDrawData* draw_data) - { - // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled - // TODO: Setup viewport using draw_data->DisplaySize - // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize - // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color. - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by ImGui - const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by ImGui - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback) - { - pcmd->UserCallback(cmd_list, pcmd); - } - else - { - // The texture for the draw call is specified by pcmd->TextureId. - // The vast majority of draw calls with use the imgui texture atlas, which value you have set yourself during initialization. - MyEngineBindTexture(pcmd->TextureId); - - // We are using scissoring to clip some objects. All low-level graphics API should supports it. - // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches - // (some elements visible outside their bounds) but you can fix that once everywhere else works! - // - Clipping coordinates are provided in imgui coordinates space (from draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize) - // In a single viewport application, draw_data->DisplayPos will always be (0,0) and draw_data->DisplaySize will always be == io.DisplaySize. - // However, in the interest of supporting multi-viewport applications in the future, always subtract draw_data->DisplayPos from - // clipping bounds to convert them to your viewport space. - // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min) - ImVec2 pos = draw_data->DisplayPos; - MyEngineScissor((int)(pcmd->ClipRect.x - pos.x), (int)(pcmd->ClipRect.y - pos.y), (int)(pcmd->ClipRect.z - pos.x), (int)(pcmd->ClipRect.w - pos.y)); - - // Render 'pcmd->ElemCount/3' indexed triangles. - // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits if your engine doesn't support 16-bits indices. - MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer); - } - idx_buffer += pcmd->ElemCount; - } - } - } - - - The examples/ folders contains many functional implementation of the pseudo-code above. - - When calling NewFrame(), the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags are updated. - They tell you if ImGui intends to use your inputs. When a flag is set you want to hide the corresponding inputs from the rest of your application. - In both cases you need to pass on the inputs to imgui. Read the FAQ below for more information about those flags. - - Please read the FAQ above. Amusingly, it is called a FAQ because people frequently have the same issues! - - USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS - - - The gamepad/keyboard navigation is fairly functional and keeps being improved. - - You can ask questions and report issues at https://github.com/ocornut/imgui/issues/787 - - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable. - - Gamepad: - - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable. - - Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + fill the io.NavInputs[] fields before calling NewFrame(). - Note that io.NavInputs[] is cleared by EndFrame(). - - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values: - 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks. - - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone. - Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.). - - You can download PNG/PSD files depicting the gamepad controls for common controllers at: http://goo.gl/9LgVZW. - - If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo - to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved. - - Keyboard: - - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable. - NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays. - - When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard flag - will be set. For more advanced uses, you may want to read from: - - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set. - - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used). - - or query focus information with e.g. IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc. functions. - Please reach out if you think the game vs navigation input sharing could be improved. - - Mouse: - - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback. - - Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + uSynergy.c (on your console/tablet/phone app) to share your PC mouse/keyboard. - - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiConfigFlags_NavEnableSetMousePos flag. - Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements. - When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved. - When that happens your back-end NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the binding in examples/ do that. - (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, imgui will misbehave as it will see your mouse as moving back and forth!) - (In a setup when you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want - to set a boolean to ignore your other external mouse positions until the external source is moved again.) - - - API BREAKING CHANGES - ==================== - - Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix. - Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code. - When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. - You can read releases logs https://github.com/ocornut/imgui/releases for more details. - - - 2018/09/06 (1.65) - renamed stb_truetype.h to imstb_truetype.h, stb_textedit.h to imstb_textedit.h, and stb_rect_pack.h to imstb_rectpack.h. - If you were conveniently using the imgui copy of those STB headers in your project you will have to update your include paths. - - 2018/09/05 (1.65) - renamed io.OptCursorBlink/io.ConfigCursorBlink to io.ConfigInputTextCursorBlink. (#1427) - - 2018/08/31 (1.64) - added imgui_widgets.cpp file, extracted and moved widgets code out of imgui.cpp into imgui_widgets.cpp. Re-ordered some of the code remaining in imgui.cpp. - NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED. - Because of this, any local modifications to imgui.cpp will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions. - - 2018/08/22 (1.63) - renamed IsItemDeactivatedAfterChange() to IsItemDeactivatedAfterEdit() for consistency with new IsItemEdited() API. Kept redirection function (will obsolete soonish as IsItemDeactivatedAfterChange() is very recent). - - 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to ImGuiInputTextCallbackData for consistency. Kept redirection types (will obsolete). - - 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of (ImGuiInputTextCallbackData::Flags & ImGuiInputTextFlags_ReadOnly). - - 2018/08/01 (1.63) - removed per-window ImGuiWindowFlags_ResizeFromAnySide beta flag in favor of a global io.ConfigResizeWindowsFromEdges to enable the feature. - - 2018/08/01 (1.63) - renamed io.OptCursorBlink to io.ConfigCursorBlink [-> io.ConfigInputTextCursorBlink in 1.65], io.OptMacOSXBehaviors to ConfigMacOSXBehaviors for consistency. - - 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point imprecisions over time. - - 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with other features. Kept redirection enum (will obsolete). - - 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and discourage using the full set. - - 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the ImGuiTreeNodeFlags_NoTreePushOnOpen flag. See Changelog for details. - - 2018/05/03 (1.61) - DragInt(): the default compile-time format string has been changed from "%.0f" to "%d", as we are not using integers internally any more. - If you used DragInt() with custom format strings, make sure you change them to use %d or an integer-compatible format. - To honor backward-compatibility, the DragInt() code will currently parse and modify format strings to replace %*f with %d, giving time to users to upgrade their code. - If you have IMGUI_DISABLE_OBSOLETE_FUNCTIONS enabled, the code will instead assert! You may run a reg-exp search on your codebase for e.g. "DragInt.*%f" to help you find them. - - 2018/04/28 (1.61) - obsoleted InputFloat() functions taking an optional "int decimal_precision" in favor of an equivalent and more flexible "const char* format", - consistent with other functions. Kept redirection functions (will obsolete). - - 2018/04/09 (1.61) - IM_DELETE() helper function added in 1.60 doesn't clear the input _pointer_ reference, more consistent with expectation and allows passing r-value. - - 2018/03/20 (1.60) - renamed io.WantMoveMouse to io.WantSetMousePos for consistency and ease of understanding (was added in 1.52, _not_ used by core and only honored by some binding ahead of merging the Nav branch). - - 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the closing cross uses regular button colors now. - - 2018/03/08 (1.60) - changed ImFont::DisplayOffset.y to default to 0 instead of +1. Fixed rounding of Ascent/Descent to match TrueType renderer. If you were adding or subtracting to ImFont::DisplayOffset check if your fonts are correctly aligned vertically. - - 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other public enums. - - 2018/02/18 (1.60) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is not really usable in many situations at the moment. - - 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function after ImGui::Render(). Use ImGui::GetDrawData() to retrieve the ImDrawData* to display. - - 2018/02/07 (1.60) - reorganized context handling to be more explicit, - - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END. - - removed Shutdown() function, as DestroyContext() serve this purpose. - - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwise CreateContext() will create its own font atlas instance. - - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts. - - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts. - - 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the imgui repo you may need to update your paths. - - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete). - - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete). - - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData. - - 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo code. If you need it it's easy to replicate on your side. - - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete). - - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags - - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame. - - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set. - - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete). - - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete). - - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete). - - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete). - - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete). - - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed. - - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up. - Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions. - - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency. - - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg. - - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding. - - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); - - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency. - - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it. - - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details. - removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting. - - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead! - - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete). - - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete). - - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your binding if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)". - - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)! - - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). - - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). - - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. - - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix. - - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame. - - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely. - - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete). - - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete). - - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton(). - - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change them with right-click context menu. - - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to 'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op (hello dodgy backward compatibility!). - check and run the demo window, under "Color/Picker Widgets", to understand the various new options. - - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0,0))' - - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse - - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset. - - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity. - - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetId() and use it instead of passing string to BeginChild(). - - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it. - - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc. - - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully breakage should be minimal. - - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore. - If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you. - If your TitleBg/TitleBgActive alpha was <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar. - This helper function will convert an old TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color. - ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) - { - float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; - return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); - } - If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create TitleBgActive from a tweaked TitleBg color. - - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext(). - - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection. - - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen). - - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDrawList::PushClipRect(Imvec2 min,ImVec2 max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer. - - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref github issue #337). - - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. (ref github issue #337) - - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete). - - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert. - - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you. - - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis. - - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete. - - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position. - GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may include scrolling, not at +100 from the window left side. - GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a problem, but watch out! - - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize - - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to add to your project. - - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an extra PI for no justifiable reason - - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the ImFontConfig structure. - you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text. - - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost. - this necessary change will break your rendering function! the fix should be very easy. sorry for that :( - - if you are using a vanilla copy of one of the imgui_impl_XXXX.cpp provided in the example, you just need to update your copy and you can ignore the rest. - - the signature of the io.RenderDrawListsFn handler has changed! - old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) - new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data). - argument: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes 'draw_data->CmdListsCount' - ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is new. - ImDrawCmd: 'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes 'UserCallback', 'texture_id' becomes 'TextureId'. - - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render ElemCount/3 triangles using indices from the index buffer. - - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers() method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering! - - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade! - - 2015/07/10 (1.43) - changed SameLine() parameters from int to float. - - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete). - - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. cursor position) are not equivalent to scrolling amount. - - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have transparence - - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely be used. Sorry! - - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete). - - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete). - - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons. - - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if the popup is opened. - - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same). - - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50. - - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API - - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive. - - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead. - - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50. - - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing - - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50. - - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing) - - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50. - - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once. - - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now. - - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior - - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing() - - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused) - - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions. - - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader. - (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels. - font init: const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); <..Upload texture to GPU..> - became: unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); <..Upload texture to GPU>; io.Fonts->TexId = YourTextureIdentifier; - you now more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. - it is now recommended that you sample the font texture with bilinear interpolation. - (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID. - (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix) - (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets - - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver) - - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph) - - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility - - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered() - - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly) - - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity) - - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale() - - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn - - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically) - - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite - - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes - - - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS - ====================================== - - Q: How can I tell whether to dispatch mouse/keyboard to imgui or to my application? - A: You can read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags from the ImGuiIO structure (e.g. if (ImGui::GetIO().WantCaptureMouse) { ... } ) - - When 'io.WantCaptureMouse' is set, imgui wants to use your mouse state, and you may want to discard/hide the inputs from the rest of your application. - - When 'io.WantCaptureKeyboard' is set, imgui wants to use your keyboard state, and you may want to discard/hide the inputs from the rest of your application. - - When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS). - Note: you should always pass your mouse/keyboard inputs to imgui, even when the io.WantCaptureXXX flag are set false. - This is because imgui needs to detect that you clicked in the void to unfocus its windows. - Note: The 'io.WantCaptureMouse' is more accurate that any attempt to "check if the mouse is hovering a window" (don't do that!). - It handle mouse dragging correctly (both dragging that started over your application or over an imgui window) and handle e.g. modal windows blocking inputs. - Those flags are updated by ImGui::NewFrame(). Preferably read the flags after calling NewFrame() if you can afford it, but reading them before is also - perfectly fine, as the bool toggle fairly rarely. If you have on a touch device, you might find use for an early call to UpdateHoveredWindowAndCaptureFlags(). - Note: Text input widget releases focus on "Return KeyDown", so the subsequent "Return KeyUp" event that your application receive will typically - have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs - were targeted for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.) - - Q: How can I display an image? What is ImTextureID, how does it works? - A: Short explanation: - - You may use functions such as ImGui::Image(), ImGui::ImageButton() or lower-level ImDrawList::AddImage() to emit draw calls that will use your own textures. - - Actual textures are identified in a way that is up to the user/engine. - - Loading image files from the disk and turning them into a texture is not within the scope of Dear ImGui (for a good reason). - Please read documentations or tutorials on your graphics API to understand how to display textures on the screen before moving onward. - - Long explanation: - - Dear ImGui's job is to create "meshes", defined in a renderer-agnostic format made of draw commands and vertices. - At the end of the frame those meshes (ImDrawList) will be displayed by your rendering function. They are made up of textured polygons and the code - to render them is generally fairly short (a few dozen lines). In the examples/ folder we provide functions for popular graphics API (OpenGL, DirectX, etc.). - - Each rendering function decides on a data type to represent "textures". The concept of what is a "texture" is entirely tied to your underlying engine/graphics API. - We carry the information to identify a "texture" in the ImTextureID type. - ImTextureID is nothing more that a void*, aka 4/8 bytes worth of data: just enough to store 1 pointer or 1 integer of your choice. - Dear ImGui doesn't know or understand what you are storing in ImTextureID, it merely pass ImTextureID values until they reach your rendering function. - - In the examples/ bindings, for each graphics API binding we decided on a type that is likely to be a good representation for specifying - an image from the end-user perspective. This is what the _examples_ rendering functions are using: - - OpenGL: ImTextureID = GLuint (see ImGui_ImplGlfwGL3_RenderDrawData() function in imgui_impl_glfw_gl3.cpp) - DirectX9: ImTextureID = LPDIRECT3DTEXTURE9 (see ImGui_ImplDX9_RenderDrawData() function in imgui_impl_dx9.cpp) - DirectX11: ImTextureID = ID3D11ShaderResourceView* (see ImGui_ImplDX11_RenderDrawData() function in imgui_impl_dx11.cpp) - DirectX12: ImTextureID = D3D12_GPU_DESCRIPTOR_HANDLE (see ImGui_ImplDX12_RenderDrawData() function in imgui_impl_dx12.cpp) - - For example, in the OpenGL example binding we store raw OpenGL texture identifier (GLuint) inside ImTextureID. - Whereas in the DirectX11 example binding we store a pointer to ID3D11ShaderResourceView inside ImTextureID, which is a higher-level structure - tying together both the texture and information about its format and how to read it. - - If you have a custom engine built over e.g. OpenGL, instead of passing GLuint around you may decide to use a high-level data type to carry information about - the texture as well as how to display it (shaders, etc.). The decision of what to use as ImTextureID can always be made better knowing how your codebase - is designed. If your engine has high-level data types for "textures" and "material" then you may want to use them. - If you are starting with OpenGL or DirectX or Vulkan and haven't built much of a rendering engine over them, keeping the default ImTextureID - representation suggested by the example bindings is probably the best choice. - (Advanced users may also decide to keep a low-level type in ImTextureID, and use ImDrawList callback and pass information to their renderer) - - User code may do: - - // Cast our texture type to ImTextureID / void* - MyTexture* texture = g_CoffeeTableTexture; - ImGui::Image((void*)texture, ImVec2(texture->Width, texture->Height)); - - The renderer function called after ImGui::Render() will receive that same value that the user code passed: - - // Cast ImTextureID / void* stored in the draw command as our texture type - MyTexture* texture = (MyTexture*)pcmd->TextureId; - MyEngineBindTexture2D(texture); - - Once you understand this design you will understand that loading image files and turning them into displayable textures is not within the scope of Dear ImGui. - This is by design and is actually a good thing, because it means your code has full control over your data types and how you display them. - If you want to display an image file (e.g. PNG file) into the screen, please refer to documentation and tutorials for the graphics API you are using. - - Here's a simplified OpenGL example using stb_image.h: - - // Use stb_image.h to load a PNG from disk and turn it into raw RGBA pixel data: - #define STB_IMAGE_IMPLEMENTATION - #include - [...] - int my_image_width, my_image_height; - unsigned char* my_image_data = stbi_load("my_image.png", &my_image_width, &my_image_height, NULL, 4); - - // Turn the RGBA pixel data into an OpenGL texture: - GLuint my_opengl_texture; - glGenTextures(1, &my_opengl_texture); - glBindTexture(GL_TEXTURE_2D, my_opengl_texture); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data); - - // Now that we have an OpenGL texture, assuming our imgui rendering function (imgui_impl_xxx.cpp file) takes GLuint as ImTextureID, we can display it: - ImGui::Image((void*)(intptr_t)my_opengl_texture, ImVec2(my_image_width, my_image_height)); - - C/C++ tip: a void* is pointer-sized storage. You may safely store any pointer or integer into it by casting your value to ImTexture / void*, and vice-versa. - Because both end-points (user code and rendering function) are under your control, you know exactly what is stored inside the ImTexture / void*. - Examples: - - GLuint my_tex = XXX; - void* my_void_ptr; - my_void_ptr = (void*)(intptr_t)my_tex; // cast a GLuint into a void* (we don't take its address! we literally store the value inside the pointer) - my_tex = (GLuint)(intptr_t)my_void_ptr; // cast a void* into a GLuint - - ID3D11ShaderResourceView* my_dx11_srv = XXX; - void* my_void_ptr; - my_void_ptr = (void*)my_dx11_srv; // cast a ID3D11ShaderResourceView* into an opaque void* - my_dx11_srv = (ID3D11ShaderResourceView*)my_void_ptr; // cast a void* into a ID3D11ShaderResourceView* - - Finally, you may call ImGui::ShowMetricsWindow() to explore/visualize/understand how the ImDrawList are generated. - - Q: How can I have multiple widgets with the same label or without a label? - Q: I have multiple widgets with the same label, and only the first one works. Why is that? - A: A primer on labels and the ID Stack... - - Dear ImGui internally need to uniquely identify UI elements. - Elements that are typically not clickable (such as calls to the Text functions) don't need an ID. - Interactive widgets (such as calls to Button buttons) need a unique ID. - Unique ID are used internally to track active widgets and occasionally associate state to widgets. - Unique ID are implicitly built from the hash of multiple elements that identify the "path" to the UI element. - - - Unique ID are often derived from a string label: - - Button("OK"); // Label = "OK", ID = hash of (..., "OK") - Button("Cancel"); // Label = "Cancel", ID = hash of (..., "Cancel") - - - ID are uniquely scoped within windows, tree nodes, etc. which all pushes to the ID stack. Having - two buttons labeled "OK" in different windows or different tree locations is fine. - We used "..." above to signify whatever was already pushed to the ID stack previously: - - Begin("MyWindow"); - Button("OK"); // Label = "OK", ID = hash of ("MyWindow", "OK") - End(); - - - If you have a same ID twice in the same location, you'll have a conflict: - - Button("OK"); - Button("OK"); // ID collision! Interacting with either button will trigger the first one. - - Fear not! this is easy to solve and there are many ways to solve it! - - - Solving ID conflict in a simple/local context: - When passing a label you can optionally specify extra ID information within string itself. - Use "##" to pass a complement to the ID that won't be visible to the end-user. - This helps solving the simple collision cases when you know e.g. at compilation time which items - are going to be created: - - Begin("MyWindow"); - Button("Play"); // Label = "Play", ID = hash of ("MyWindow", "Play") - Button("Play##foo1"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo1") // Different from above - Button("Play##foo2"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo2") // Different from above - End(); - - - If you want to completely hide the label, but still need an ID: - - Checkbox("##On", &b); // Label = "", ID = hash of (..., "##On") // No visible label! - - - Occasionally/rarely you might want change a label while preserving a constant ID. This allows - you to animate labels. For example you may want to include varying information in a window title bar, - but windows are uniquely identified by their ID. Use "###" to pass a label that isn't part of ID: - - Button("Hello###ID"; // Label = "Hello", ID = hash of (..., "ID") - Button("World###ID"; // Label = "World", ID = hash of (..., "ID") // Same as above, even though the label looks different - - sprintf(buf, "My game (%f FPS)###MyGame", fps); - Begin(buf); // Variable label, ID = hash of "MyGame" - - - Solving ID conflict in a more general manner: - Use PushID() / PopID() to create scopes and manipulate the ID stack, as to avoid ID conflicts - within the same window. This is the most convenient way of distinguishing ID when iterating and - creating many UI elements programmatically. - You can push a pointer, a string or an integer value into the ID stack. - Remember that ID are formed from the concatenation of _everything_ in the ID stack! - - Begin("Window"); - for (int i = 0; i < 100; i++) - { - PushID(i); // Push i to the id tack - Button("Click"); // Label = "Click", ID = Hash of ("Window", i, "Click") - PopID(); - } - for (int i = 0; i < 100; i++) - { - MyObject* obj = Objects[i]; - PushID(obj); - Button("Click"); // Label = "Click", ID = Hash of ("Window", obj pointer, "Click") - PopID(); - } - for (int i = 0; i < 100; i++) - { - MyObject* obj = Objects[i]; - PushID(obj->Name); - Button("Click"); // Label = "Click", ID = Hash of ("Window", obj->Name, "Click") - PopID(); - } - End(); - - - More example showing that you can stack multiple prefixes into the ID stack: - - Button("Click"); // Label = "Click", ID = hash of (..., "Click") - PushID("node"); - Button("Click"); // Label = "Click", ID = hash of (..., "node", "Click") - PushID(my_ptr); - Button("Click"); // Label = "Click", ID = hash of (..., "node", my_ptr, "Click") - PopID(); - PopID(); - - - Tree nodes implicitly creates a scope for you by calling PushID(). - - Button("Click"); // Label = "Click", ID = hash of (..., "Click") - if (TreeNode("node")) - { - Button("Click"); // Label = "Click", ID = hash of (..., "node", "Click") - TreePop(); - } - - - When working with trees, ID are used to preserve the open/close state of each tree node. - Depending on your use cases you may want to use strings, indices or pointers as ID. - e.g. when following a single pointer that may change over time, using a static string as ID - will preserve your node open/closed state when the targeted object change. - e.g. when displaying a list of objects, using indices or pointers as ID will preserve the - node open/closed state differently. See what makes more sense in your situation! - - Q: How can I use my own math types instead of ImVec2/ImVec4? - A: You can edit imconfig.h and setup the IM_VEC2_CLASS_EXTRA/IM_VEC4_CLASS_EXTRA macros to add implicit type conversions. - This way you'll be able to use your own types everywhere, e.g. passsing glm::vec2 to ImGui functions instead of ImVec2. - - Q: How can I load a different font than the default? - A: Use the font atlas to load the TTF/OTF file you want: - ImGuiIO& io = ImGui::GetIO(); - io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); - io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() - (default is ProggyClean.ttf, rendered at size 13, embedded in dear imgui's source code) - - New programmers: remember that in C/C++ and most programming languages if you want to use a - backslash \ within a string literal, you need to write it double backslash "\\": - io.Fonts->AddFontFromFileTTF("MyDataFolder\MyFontFile.ttf", size_in_pixels); // WRONG (you are escape the M here!) - io.Fonts->AddFontFromFileTTF("MyDataFolder\\MyFontFile.ttf", size_in_pixels); // CORRECT - io.Fonts->AddFontFromFileTTF("MyDataFolder/MyFontFile.ttf", size_in_pixels); // ALSO CORRECT - - Q: How can I easily use icons in my application? - A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you - main font. Then you can refer to icons within your strings. Read 'How can I load multiple fonts?' - and the file 'misc/fonts/README.txt' for instructions and useful header files. - - Q: How can I load multiple fonts? - A: Use the font atlas to pack them into a single texture: - (Read misc/fonts/README.txt and the code in ImFontAtlas for more details.) - - ImGuiIO& io = ImGui::GetIO(); - ImFont* font0 = io.Fonts->AddFontDefault(); - ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels); - ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels); - io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8() - // the first loaded font gets used by default - // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime - - // Options - ImFontConfig config; - config.OversampleH = 3; - config.OversampleV = 1; - config.GlyphOffset.y -= 2.0f; // Move everything by 2 pixels up - config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters - io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config); - - // Combine multiple fonts into one (e.g. for icon fonts) - ImWchar ranges[] = { 0xf000, 0xf3ff, 0 }; - ImFontConfig config; - config.MergeMode = true; - io.Fonts->AddFontDefault(); - io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font - io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs - - Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? - A: When loading a font, pass custom Unicode ranges to specify the glyphs to load. - - // Add default Japanese ranges - io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese()); - - // Or create your own custom ranges (e.g. for a game you can feed your entire game script and only build the characters the game need) - ImVector ranges; - ImFontAtlas::GlyphRangesBuilder builder; - builder.AddText("Hello world"); // Add a string (here "Hello world" contains 7 unique characters) - builder.AddChar(0x7262); // Add a specific character - builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges - builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted) - io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data); - - All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8 - by using the u8"hello" syntax. Specifying literal in your source code using a local code page - (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work! - Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8. - - Text input: it is up to your application to pass the right character code by calling io.AddInputCharacter(). - The applications in examples/ are doing that. - Windows: you can use the WM_CHAR or WM_UNICHAR or WM_IME_CHAR message (depending if your app is built using Unicode or MultiByte mode). - You may also use MultiByteToWideChar() or ToUnicode() to retrieve Unicode codepoints from MultiByte characters or keyboard state. - Windows: if your language is relying on an Input Method Editor (IME), you copy the HWND of your window to io.ImeWindowHandle in order for - the default implementation of io.ImeSetInputScreenPosFn() to set your Microsoft IME position correctly. - - Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API) - A: - You can create a dummy window. Call SetNextWindowBgAlpha(0.0f), call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flags. - Then you can retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. - - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows. - - You can create your own ImDrawList instance. You'll need to initialize them ImGui::GetDrawListSharedData(), or create your own ImDrawListSharedData. - - Q: I integrated Dear ImGui in my engine and the text or lines are blurry.. - A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f). - Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension. - - Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around.. - A: You are probably mishandling the clipping rectangles in your render function. - Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height). - - Q: How can I help? - A: - If you are experienced with Dear ImGui and C++, look at the github issues, or docs/TODO.txt and see how you want/can help! - - Convince your company to fund development time! Individual users: you can also become a Patron (patreon.com/imgui) or donate on PayPal! See README. - - Disclose your usage of dear imgui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. - You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/1269). Visuals are ideal as they inspire other programmers. - But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions. - - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on github or privately). - - - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window. - this is also useful to set yourself in the context of another window (to get/set other settings) - - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug". - - tip: the ImGuiOnceUponAFrame helper will allow run the block of code only once a frame. You can use it to quickly add custom UI in the middle - of a deep nested inner loop in your code. - - tip: you can call Render() multiple times (e.g for VR renders). - - tip: call and read the ShowDemoWindow() code in imgui_demo.cpp for more example of how to use ImGui! - -*/ - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include "imgui.h" -#ifndef IMGUI_DEFINE_MATH_OPERATORS -#define IMGUI_DEFINE_MATH_OPERATORS -#endif -#include "imgui_internal.h" - -#include // toupper, isprint -#include // vsnprintf, sscanf, printf -#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier -#include // intptr_t -#else -#include // intptr_t -#endif - -#define IMGUI_DEBUG_NAV_SCORING 0 -#define IMGUI_DEBUG_NAV_RECTS 0 - -// Visual Studio warnings -#ifdef _MSC_VER -#pragma warning (disable: 4127) // condition expression is constant -#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen -#endif - -// Clang/GCC warnings with -Weverything -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown warning group '-Wformat-pedantic *' // not all warnings are known by all clang versions.. so ignoring warnings triggers new warnings on some configuration. great! -#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. -#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. -#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. -#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. -#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. -#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // -#pragma clang diagnostic ignored "-Wformat-pedantic" // warning : format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic. -#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' -#elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used -#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size -#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked -#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false -#if __GNUC__ >= 8 -#pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead -#endif -#endif - -// When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch. -static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in -static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear - -//------------------------------------------------------------------------- -// [SECTION] FORWARD DECLARATIONS -//------------------------------------------------------------------------- - -static void SetCurrentWindow(ImGuiWindow* window); -static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond); -static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond); -static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond); -static void FindHoveredWindow(); -static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags); -static void CheckStacksSize(ImGuiWindow* window, bool write); -static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges); - -static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list); -static void AddWindowToDrawData(ImVector* out_list, ImGuiWindow* window); -static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, ImGuiWindow* window); - -static ImRect GetViewportRect(); - -// Settings -static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); -static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line); -static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf); - -// Platform Dependents default implementation for IO functions -static const char* GetClipboardTextFn_DefaultImpl(void* user_data); -static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text); -static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y); - -namespace ImGui -{ -static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags); - -// Navigation -static void NavUpdate(); -static void NavUpdateWindowing(); -static void NavUpdateWindowingList(); -static void NavUpdateMoveResult(); -static float NavUpdatePageUpPageDown(int allowed_dir_flags); -static inline void NavUpdateAnyRequestFlag(); -static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id); -static ImVec2 NavCalcPreferredRefPos(); -static void NavSaveLastChildNavWindow(ImGuiWindow* nav_window); -static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); - -// Misc -static void UpdateMouseInputs(); -static void UpdateMouseWheel(); -static void UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]); -} - -//----------------------------------------------------------------------------- -// [SECTION] CONTEXT AND MEMORY ALLOCATORS -//----------------------------------------------------------------------------- - -// Current context pointer. Implicitly used by all ImGui functions. Always assumed to be != NULL. -// CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext(). -// If you use DLL hotreloading you might need to call SetCurrentContext() after reloading code from this file. -// ImGui functions are not thread-safe because of this pointer. If you want thread-safety to allow N threads to access N different contexts, you can: -// - Change this variable to use thread local storage. You may #define GImGui in imconfig.h for that purpose. Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586 -// - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts) -#ifndef GImGui -ImGuiContext* GImGui = NULL; -#endif - -// Memory Allocator functions. Use SetAllocatorFunctions() to change them. -// If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file. -// Otherwise, you probably don't want to modify them mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction. -#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS -static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; return malloc(size); } -static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; free(ptr); } -#else -static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; (void)size; IM_ASSERT(0); return NULL; } -static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; (void)ptr; IM_ASSERT(0); } -#endif - -static void* (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper; -static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper; -static void* GImAllocatorUserData = NULL; - -//----------------------------------------------------------------------------- -// [SECTION] MAIN USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) -//----------------------------------------------------------------------------- - -ImGuiStyle::ImGuiStyle() -{ - Alpha = 1.0f; // Global alpha applies to everything in ImGui - WindowPadding = ImVec2(8,8); // Padding within a window - WindowRounding = 7.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows - WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. - WindowMinSize = ImVec2(32,32); // Minimum window size - WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text - ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows - ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. - PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows - PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. - FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) - FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). - FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. - ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines - ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) - TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! - IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). - ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns - ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar - ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar - GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar - GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. - ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. - DisplayWindowPadding = ImVec2(20,20); // Window position are clamped to be visible within the display area by at least this amount. Only applies to regular windows. - DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. - MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. - AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU. - AntiAliasedFill = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) - CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - - // Default theme - ImGui::StyleColorsDark(this); -} - -// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. -// Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. -void ImGuiStyle::ScaleAllSizes(float scale_factor) -{ - WindowPadding = ImFloor(WindowPadding * scale_factor); - WindowRounding = ImFloor(WindowRounding * scale_factor); - WindowMinSize = ImFloor(WindowMinSize * scale_factor); - ChildRounding = ImFloor(ChildRounding * scale_factor); - PopupRounding = ImFloor(PopupRounding * scale_factor); - FramePadding = ImFloor(FramePadding * scale_factor); - FrameRounding = ImFloor(FrameRounding * scale_factor); - ItemSpacing = ImFloor(ItemSpacing * scale_factor); - ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); - TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); - IndentSpacing = ImFloor(IndentSpacing * scale_factor); - ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor); - ScrollbarSize = ImFloor(ScrollbarSize * scale_factor); - ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor); - GrabMinSize = ImFloor(GrabMinSize * scale_factor); - GrabRounding = ImFloor(GrabRounding * scale_factor); - DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor); - DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor); - MouseCursorScale = ImFloor(MouseCursorScale * scale_factor); -} - -ImGuiIO::ImGuiIO() -{ - // Most fields are initialized with zero - memset(this, 0, sizeof(*this)); - - // Settings - ConfigFlags = 0x00; - BackendFlags = 0x00; - DisplaySize = ImVec2(-1.0f, -1.0f); - DeltaTime = 1.0f/60.0f; - IniSavingRate = 5.0f; - IniFilename = "imgui.ini"; - LogFilename = "imgui_log.txt"; - MouseDoubleClickTime = 0.30f; - MouseDoubleClickMaxDist = 6.0f; - for (int i = 0; i < ImGuiKey_COUNT; i++) - KeyMap[i] = -1; - KeyRepeatDelay = 0.250f; - KeyRepeatRate = 0.050f; - UserData = NULL; - - Fonts = NULL; - FontGlobalScale = 1.0f; - FontDefault = NULL; - FontAllowUserScaling = false; - DisplayFramebufferScale = ImVec2(1.0f, 1.0f); - DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f); - - // Miscellaneous configuration options -#ifdef __APPLE__ - ConfigMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag -#else - ConfigMacOSXBehaviors = false; -#endif - ConfigInputTextCursorBlink = true; - ConfigResizeWindowsFromEdges = false; - - // Settings (User Functions) - GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations - SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; - ClipboardUserData = NULL; - ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl; - ImeWindowHandle = NULL; - -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - RenderDrawListsFn = NULL; -#endif - - // Input (NB: we already have memset zero the entire structure) - MousePos = ImVec2(-FLT_MAX, -FLT_MAX); - MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX); - MouseDragThreshold = 6.0f; - for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; - for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f; - for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f; -} - -// Pass in translated ASCII characters for text input. -// - with glfw you can get those from the callback set in glfwSetCharCallback() -// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message -void ImGuiIO::AddInputCharacter(ImWchar c) -{ - const int n = ImStrlenW(InputCharacters); - if (n + 1 < IM_ARRAYSIZE(InputCharacters)) - { - InputCharacters[n] = c; - InputCharacters[n+1] = '\0'; - } -} - -void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) -{ - // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more - const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar); - ImWchar wchars[wchars_buf_len]; - ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL); - for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++) - AddInputCharacter(wchars[i]); -} - -//----------------------------------------------------------------------------- -// [SECTION] MISC HELPER/UTILITIES (Maths, String, Format, Hash, File functions) -//----------------------------------------------------------------------------- - -ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) -{ - ImVec2 ap = p - a; - ImVec2 ab_dir = b - a; - float dot = ap.x * ab_dir.x + ap.y * ab_dir.y; - if (dot < 0.0f) - return a; - float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y; - if (dot > ab_len_sqr) - return b; - return a + ab_dir * dot / ab_len_sqr; -} - -bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) -{ - bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f; - bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f; - bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f; - return ((b1 == b2) && (b2 == b3)); -} - -void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w) -{ - ImVec2 v0 = b - a; - ImVec2 v1 = c - a; - ImVec2 v2 = p - a; - const float denom = v0.x * v1.y - v1.x * v0.y; - out_v = (v2.x * v1.y - v1.x * v2.y) / denom; - out_w = (v0.x * v2.y - v2.x * v0.y) / denom; - out_u = 1.0f - out_v - out_w; -} - -ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p) -{ - ImVec2 proj_ab = ImLineClosestPoint(a, b, p); - ImVec2 proj_bc = ImLineClosestPoint(b, c, p); - ImVec2 proj_ca = ImLineClosestPoint(c, a, p); - float dist2_ab = ImLengthSqr(p - proj_ab); - float dist2_bc = ImLengthSqr(p - proj_bc); - float dist2_ca = ImLengthSqr(p - proj_ca); - float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca)); - if (m == dist2_ab) - return proj_ab; - if (m == dist2_bc) - return proj_bc; - return proj_ca; -} - -int ImStricmp(const char* str1, const char* str2) -{ - int d; - while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } - return d; -} - -int ImStrnicmp(const char* str1, const char* str2, size_t count) -{ - int d = 0; - while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; } - return d; -} - -void ImStrncpy(char* dst, const char* src, size_t count) -{ - if (count < 1) return; - strncpy(dst, src, count); - dst[count-1] = 0; -} - -char* ImStrdup(const char *str) -{ - size_t len = strlen(str) + 1; - void* buf = ImGui::MemAlloc(len); - return (char*)memcpy(buf, (const void*)str, len); -} - -const char* ImStrchrRange(const char* str, const char* str_end, char c) -{ - for ( ; str < str_end; str++) - if (*str == c) - return str; - return NULL; -} - -int ImStrlenW(const ImWchar* str) -{ - int n = 0; - while (*str++) n++; - return n; -} - -const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line -{ - while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') - buf_mid_line--; - return buf_mid_line; -} - -const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) -{ - if (!needle_end) - needle_end = needle + strlen(needle); - - const char un0 = (char)toupper(*needle); - while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) - { - if (toupper(*haystack) == un0) - { - const char* b = needle + 1; - for (const char* a = haystack + 1; b < needle_end; a++, b++) - if (toupper(*a) != toupper(*b)) - break; - if (b == needle_end) - return haystack; - } - haystack++; - } - return NULL; -} - -// Trim str by offsetting contents when there's leading data + writing a \0 at the trailing position. We use this in situation where the cost is negligible. -void ImStrTrimBlanks(char* buf) -{ - char* p = buf; - while (p[0] == ' ' || p[0] == '\t') // Leading blanks - p++; - char* p_start = p; - while (*p != 0) // Find end of string - p++; - while (p > p_start && (p[-1] == ' ' || p[-1] == '\t')) // Trailing blanks - p--; - if (p_start != buf) // Copy memory if we had leading blanks - memmove(buf, p_start, p - p_start); - buf[p - p_start] = 0; // Zero terminate -} - -// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). -// Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm. -// B) When buf==NULL vsnprintf() will return the output size. -#ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS - -#if defined(_MSC_VER) && !defined(vsnprintf) -#define vsnprintf _vsnprintf -#endif - -int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - int w = vsnprintf(buf, buf_size, fmt, args); - va_end(args); - if (buf == NULL) - return w; - if (w == -1 || w >= (int)buf_size) - w = (int)buf_size - 1; - buf[w] = 0; - return w; -} - -int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) -{ - int w = vsnprintf(buf, buf_size, fmt, args); - if (buf == NULL) - return w; - if (w == -1 || w >= (int)buf_size) - w = (int)buf_size - 1; - buf[w] = 0; - return w; -} -#endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS - -// Pass data_size==0 for zero-terminated strings -// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. -ImU32 ImHash(const void* data, int data_size, ImU32 seed) -{ - static ImU32 crc32_lut[256] = { 0 }; - if (!crc32_lut[1]) - { - const ImU32 polynomial = 0xEDB88320; - for (ImU32 i = 0; i < 256; i++) - { - ImU32 crc = i; - for (ImU32 j = 0; j < 8; j++) - crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial); - crc32_lut[i] = crc; - } - } - - seed = ~seed; - ImU32 crc = seed; - const unsigned char* current = (const unsigned char*)data; - - if (data_size > 0) - { - // Known size - while (data_size--) - crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++]; - } - else - { - // Zero-terminated string - while (unsigned char c = *current++) - { - // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. - // Because this syntax is rarely used we are optimizing for the common case. - // - If we reach ### in the string we discard the hash so far and reset to the seed. - // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller. - if (c == '#' && current[0] == '#' && current[1] == '#') - crc = seed; - crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; - } - } - return ~crc; -} - -FILE* ImFileOpen(const char* filename, const char* mode) -{ -#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(__GNUC__) - // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. Converting both strings from UTF-8 to wchar format (using a single allocation, because we can) - const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1; - const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1; - ImVector buf; - buf.resize(filename_wsize + mode_wsize); - ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL); - ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL); - return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]); -#else - return fopen(filename, mode); -#endif -} - -// Load file content into memory -// Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree() -void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size, int padding_bytes) -{ - IM_ASSERT(filename && file_open_mode); - if (out_file_size) - *out_file_size = 0; - - FILE* f; - if ((f = ImFileOpen(filename, file_open_mode)) == NULL) - return NULL; - - long file_size_signed; - if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) - { - fclose(f); - return NULL; - } - - size_t file_size = (size_t)file_size_signed; - void* file_data = ImGui::MemAlloc(file_size + padding_bytes); - if (file_data == NULL) - { - fclose(f); - return NULL; - } - if (fread(file_data, 1, file_size, f) != file_size) - { - fclose(f); - ImGui::MemFree(file_data); - return NULL; - } - if (padding_bytes > 0) - memset((void*)(((char*)file_data) + file_size), 0, (size_t)padding_bytes); - - fclose(f); - if (out_file_size) - *out_file_size = file_size; - - return file_data; -} - -//----------------------------------------------------------------------------- -// [SECTION] MISC HELPERS/UTILITIES (ImText* functions) -//----------------------------------------------------------------------------- - -// Convert UTF-8 to 32-bits character, process single character input. -// Based on stb_from_utf8() from github.com/nothings/stb/ -// We handle UTF-8 decoding error by skipping forward. -int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) -{ - unsigned int c = (unsigned int)-1; - const unsigned char* str = (const unsigned char*)in_text; - if (!(*str & 0x80)) - { - c = (unsigned int)(*str++); - *out_char = c; - return 1; - } - if ((*str & 0xe0) == 0xc0) - { - *out_char = 0xFFFD; // will be invalid but not end of string - if (in_text_end && in_text_end - (const char*)str < 2) return 1; - if (*str < 0xc2) return 2; - c = (unsigned int)((*str++ & 0x1f) << 6); - if ((*str & 0xc0) != 0x80) return 2; - c += (*str++ & 0x3f); - *out_char = c; - return 2; - } - if ((*str & 0xf0) == 0xe0) - { - *out_char = 0xFFFD; // will be invalid but not end of string - if (in_text_end && in_text_end - (const char*)str < 3) return 1; - if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3; - if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below - c = (unsigned int)((*str++ & 0x0f) << 12); - if ((*str & 0xc0) != 0x80) return 3; - c += (unsigned int)((*str++ & 0x3f) << 6); - if ((*str & 0xc0) != 0x80) return 3; - c += (*str++ & 0x3f); - *out_char = c; - return 3; - } - if ((*str & 0xf8) == 0xf0) - { - *out_char = 0xFFFD; // will be invalid but not end of string - if (in_text_end && in_text_end - (const char*)str < 4) return 1; - if (*str > 0xf4) return 4; - if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4; - if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below - c = (unsigned int)((*str++ & 0x07) << 18); - if ((*str & 0xc0) != 0x80) return 4; - c += (unsigned int)((*str++ & 0x3f) << 12); - if ((*str & 0xc0) != 0x80) return 4; - c += (unsigned int)((*str++ & 0x3f) << 6); - if ((*str & 0xc0) != 0x80) return 4; - c += (*str++ & 0x3f); - // utf-8 encodings of values used in surrogate pairs are invalid - if ((c & 0xFFFFF800) == 0xD800) return 4; - *out_char = c; - return 4; - } - *out_char = 0; - return 0; -} - -int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) -{ - ImWchar* buf_out = buf; - ImWchar* buf_end = buf + buf_size; - while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) - { - unsigned int c; - in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); - if (c == 0) - break; - if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes - *buf_out++ = (ImWchar)c; - } - *buf_out = 0; - if (in_text_remaining) - *in_text_remaining = in_text; - return (int)(buf_out - buf); -} - -int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end) -{ - int char_count = 0; - while ((!in_text_end || in_text < in_text_end) && *in_text) - { - unsigned int c; - in_text += ImTextCharFromUtf8(&c, in_text, in_text_end); - if (c == 0) - break; - if (c < 0x10000) - char_count++; - } - return char_count; -} - -// Based on stb_to_utf8() from github.com/nothings/stb/ -static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) -{ - if (c < 0x80) - { - buf[0] = (char)c; - return 1; - } - if (c < 0x800) - { - if (buf_size < 2) return 0; - buf[0] = (char)(0xc0 + (c >> 6)); - buf[1] = (char)(0x80 + (c & 0x3f)); - return 2; - } - if (c >= 0xdc00 && c < 0xe000) - { - return 0; - } - if (c >= 0xd800 && c < 0xdc00) - { - if (buf_size < 4) return 0; - buf[0] = (char)(0xf0 + (c >> 18)); - buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); - buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); - buf[3] = (char)(0x80 + ((c ) & 0x3f)); - return 4; - } - //else if (c < 0x10000) - { - if (buf_size < 3) return 0; - buf[0] = (char)(0xe0 + (c >> 12)); - buf[1] = (char)(0x80 + ((c>> 6) & 0x3f)); - buf[2] = (char)(0x80 + ((c ) & 0x3f)); - return 3; - } -} - -// Not optimal but we very rarely use this function. -int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end) -{ - unsigned int dummy = 0; - return ImTextCharFromUtf8(&dummy, in_text, in_text_end); -} - -static inline int ImTextCountUtf8BytesFromChar(unsigned int c) -{ - if (c < 0x80) return 1; - if (c < 0x800) return 2; - if (c >= 0xdc00 && c < 0xe000) return 0; - if (c >= 0xd800 && c < 0xdc00) return 4; - return 3; -} - -int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end) -{ - char* buf_out = buf; - const char* buf_end = buf + buf_size; - while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) - { - unsigned int c = (unsigned int)(*in_text++); - if (c < 0x80) - *buf_out++ = (char)c; - else - buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end-buf_out-1), c); - } - *buf_out = 0; - return (int)(buf_out - buf); -} - -int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end) -{ - int bytes_count = 0; - while ((!in_text_end || in_text < in_text_end) && *in_text) - { - unsigned int c = (unsigned int)(*in_text++); - if (c < 0x80) - bytes_count++; - else - bytes_count += ImTextCountUtf8BytesFromChar(c); - } - return bytes_count; -} - -//----------------------------------------------------------------------------- -// [SECTION] MISC HELPER/UTILTIES (Color functions) -// Note: The Convert functions are early design which are not consistent with other API. -//----------------------------------------------------------------------------- - -ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in) -{ - float s = 1.0f/255.0f; - return ImVec4( - ((in >> IM_COL32_R_SHIFT) & 0xFF) * s, - ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, - ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, - ((in >> IM_COL32_A_SHIFT) & 0xFF) * s); -} - -ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in) -{ - ImU32 out; - out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT; - out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT; - out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT; - out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT; - return out; -} - -// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 -// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv -void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) -{ - float K = 0.f; - if (g < b) - { - ImSwap(g, b); - K = -1.f; - } - if (r < g) - { - ImSwap(r, g); - K = -2.f / 6.f - K; - } - - const float chroma = r - (g < b ? g : b); - out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f)); - out_s = chroma / (r + 1e-20f); - out_v = r; -} - -// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 -// also http://en.wikipedia.org/wiki/HSL_and_HSV -void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) -{ - if (s == 0.0f) - { - // gray - out_r = out_g = out_b = v; - return; - } - - h = ImFmod(h, 1.0f) / (60.0f/360.0f); - int i = (int)h; - float f = h - (float)i; - float p = v * (1.0f - s); - float q = v * (1.0f - s * f); - float t = v * (1.0f - s * (1.0f - f)); - - switch (i) - { - case 0: out_r = v; out_g = t; out_b = p; break; - case 1: out_r = q; out_g = v; out_b = p; break; - case 2: out_r = p; out_g = v; out_b = t; break; - case 3: out_r = p; out_g = q; out_b = v; break; - case 4: out_r = t; out_g = p; out_b = v; break; - case 5: default: out_r = v; out_g = p; out_b = q; break; - } -} - -ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul) -{ - ImGuiStyle& style = GImGui->Style; - ImVec4 c = style.Colors[idx]; - c.w *= style.Alpha * alpha_mul; - return ColorConvertFloat4ToU32(c); -} - -ImU32 ImGui::GetColorU32(const ImVec4& col) -{ - ImGuiStyle& style = GImGui->Style; - ImVec4 c = col; - c.w *= style.Alpha; - return ColorConvertFloat4ToU32(c); -} - -const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx) -{ - ImGuiStyle& style = GImGui->Style; - return style.Colors[idx]; -} - -ImU32 ImGui::GetColorU32(ImU32 col) -{ - float style_alpha = GImGui->Style.Alpha; - if (style_alpha >= 1.0f) - return col; - ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; - a = (ImU32)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range. - return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); -} - -//----------------------------------------------------------------------------- -// [SECTION] ImGuiStorage -// Helper: Key->value storage -//----------------------------------------------------------------------------- - -// std::lower_bound but without the bullshit -static ImVector::iterator LowerBound(ImVector& data, ImGuiID key) -{ - ImVector::iterator first = data.begin(); - ImVector::iterator last = data.end(); - size_t count = (size_t)(last - first); - while (count > 0) - { - size_t count2 = count >> 1; - ImVector::iterator mid = first + count2; - if (mid->key < key) - { - first = ++mid; - count -= count2 + 1; - } - else - { - count = count2; - } - } - return first; -} - -// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once. -void ImGuiStorage::BuildSortByKey() -{ - struct StaticFunc - { - static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs) - { - // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that. - if (((const Pair*)lhs)->key > ((const Pair*)rhs)->key) return +1; - if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1; - return 0; - } - }; - if (Data.Size > 1) - ImQsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID); -} - -int ImGuiStorage::GetInt(ImGuiID key, int default_val) const -{ - ImVector::iterator it = LowerBound(const_cast&>(Data), key); - if (it == Data.end() || it->key != key) - return default_val; - return it->val_i; -} - -bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const -{ - return GetInt(key, default_val ? 1 : 0) != 0; -} - -float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const -{ - ImVector::iterator it = LowerBound(const_cast&>(Data), key); - if (it == Data.end() || it->key != key) - return default_val; - return it->val_f; -} - -void* ImGuiStorage::GetVoidPtr(ImGuiID key) const -{ - ImVector::iterator it = LowerBound(const_cast&>(Data), key); - if (it == Data.end() || it->key != key) - return NULL; - return it->val_p; -} - -// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer. -int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val) -{ - ImVector::iterator it = LowerBound(Data, key); - if (it == Data.end() || it->key != key) - it = Data.insert(it, Pair(key, default_val)); - return &it->val_i; -} - -bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val) -{ - return (bool*)GetIntRef(key, default_val ? 1 : 0); -} - -float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val) -{ - ImVector::iterator it = LowerBound(Data, key); - if (it == Data.end() || it->key != key) - it = Data.insert(it, Pair(key, default_val)); - return &it->val_f; -} - -void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val) -{ - ImVector::iterator it = LowerBound(Data, key); - if (it == Data.end() || it->key != key) - it = Data.insert(it, Pair(key, default_val)); - return &it->val_p; -} - -// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame) -void ImGuiStorage::SetInt(ImGuiID key, int val) -{ - ImVector::iterator it = LowerBound(Data, key); - if (it == Data.end() || it->key != key) - { - Data.insert(it, Pair(key, val)); - return; - } - it->val_i = val; -} - -void ImGuiStorage::SetBool(ImGuiID key, bool val) -{ - SetInt(key, val ? 1 : 0); -} - -void ImGuiStorage::SetFloat(ImGuiID key, float val) -{ - ImVector::iterator it = LowerBound(Data, key); - if (it == Data.end() || it->key != key) - { - Data.insert(it, Pair(key, val)); - return; - } - it->val_f = val; -} - -void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val) -{ - ImVector::iterator it = LowerBound(Data, key); - if (it == Data.end() || it->key != key) - { - Data.insert(it, Pair(key, val)); - return; - } - it->val_p = val; -} - -void ImGuiStorage::SetAllInt(int v) -{ - for (int i = 0; i < Data.Size; i++) - Data[i].val_i = v; -} - -//----------------------------------------------------------------------------- -// [SECTION] ImGuiTextFilter -//----------------------------------------------------------------------------- - -// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" -ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) -{ - if (default_filter) - { - ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf)); - Build(); - } - else - { - InputBuf[0] = 0; - CountGrep = 0; - } -} - -bool ImGuiTextFilter::Draw(const char* label, float width) -{ - if (width != 0.0f) - ImGui::PushItemWidth(width); - bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); - if (width != 0.0f) - ImGui::PopItemWidth(); - if (value_changed) - Build(); - return value_changed; -} - -void ImGuiTextFilter::TextRange::split(char separator, ImVector* out) const -{ - out->resize(0); - const char* wb = b; - const char* we = wb; - while (we < e) - { - if (*we == separator) - { - out->push_back(TextRange(wb, we)); - wb = we + 1; - } - we++; - } - if (wb != we) - out->push_back(TextRange(wb, we)); -} - -void ImGuiTextFilter::Build() -{ - Filters.resize(0); - TextRange input_range(InputBuf, InputBuf+strlen(InputBuf)); - input_range.split(',', &Filters); - - CountGrep = 0; - for (int i = 0; i != Filters.Size; i++) - { - TextRange& f = Filters[i]; - while (f.b < f.e && ImCharIsBlankA(f.b[0])) - f.b++; - while (f.e > f.b && ImCharIsBlankA(f.e[-1])) - f.e--; - if (f.empty()) - continue; - if (Filters[i].b[0] != '-') - CountGrep += 1; - } -} - -bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const -{ - if (Filters.empty()) - return true; - - if (text == NULL) - text = ""; - - for (int i = 0; i != Filters.Size; i++) - { - const TextRange& f = Filters[i]; - if (f.empty()) - continue; - if (f.b[0] == '-') - { - // Subtract - if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL) - return false; - } - else - { - // Grep - if (ImStristr(text, text_end, f.begin(), f.end()) != NULL) - return true; - } - } - - // Implicit * grep - if (CountGrep == 0) - return true; - - return false; -} - -//----------------------------------------------------------------------------- -// [SECTION] ImGuiTextBuffer -//----------------------------------------------------------------------------- - -// On some platform vsnprintf() takes va_list by reference and modifies it. -// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. -#ifndef va_copy -#if defined(__GNUC__) || defined(__clang__) -#define va_copy(dest, src) __builtin_va_copy(dest, src) -#else -#define va_copy(dest, src) (dest = src) -#endif -#endif - -// Helper: Text buffer for logging/accumulating text -void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) -{ - va_list args_copy; - va_copy(args_copy, args); - - int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass. - if (len <= 0) - { - va_end(args_copy); - return; - } - - const int write_off = Buf.Size; - const int needed_sz = write_off + len; - if (write_off + len >= Buf.Capacity) - { - int double_capacity = Buf.Capacity * 2; - Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity); - } - - Buf.resize(needed_sz); - ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy); - va_end(args_copy); -} - -void ImGuiTextBuffer::appendf(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - appendfv(fmt, args); - va_end(args); -} - -//----------------------------------------------------------------------------- -// [SECTION] ImGuiListClipper -// This is currently not as flexible/powerful as it should be, needs some rework (see TODO) -//----------------------------------------------------------------------------- - -static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height) -{ - // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor. - // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. - // The clipper should probably have a 4th step to display the last item in a regular manner. - ImGui::SetCursorPosY(pos_y); - ImGuiWindow* window = ImGui::GetCurrentWindow(); - window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHere() can properly function after the end of our clipper usage. - window->DC.PrevLineSize.y = (line_height - GImGui->Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. - if (window->DC.ColumnsSet) - window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly -} - -// Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1 -// Use case B: Begin() called from constructor with items_height>0 -// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style. -void ImGuiListClipper::Begin(int count, float items_height) -{ - StartPosY = ImGui::GetCursorPosY(); - ItemsHeight = items_height; - ItemsCount = count; - StepNo = 0; - DisplayEnd = DisplayStart = -1; - if (ItemsHeight > 0.0f) - { - ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display - if (DisplayStart > 0) - SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor - StepNo = 2; - } -} - -void ImGuiListClipper::End() -{ - if (ItemsCount < 0) - return; - // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. - if (ItemsCount < INT_MAX) - SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor - ItemsCount = -1; - StepNo = 3; -} - -bool ImGuiListClipper::Step() -{ - if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems) - { - ItemsCount = -1; - return false; - } - if (StepNo == 0) // Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height. - { - DisplayStart = 0; - DisplayEnd = 1; - StartPosY = ImGui::GetCursorPosY(); - StepNo = 1; - return true; - } - if (StepNo == 1) // Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element. - { - if (ItemsCount == 1) { ItemsCount = -1; return false; } - float items_height = ImGui::GetCursorPosY() - StartPosY; - IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically - Begin(ItemsCount-1, items_height); - DisplayStart++; - DisplayEnd++; - StepNo = 3; - return true; - } - if (StepNo == 2) // Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user still call Step(). Does nothing and switch to Step 3. - { - IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0); - StepNo = 3; - return true; - } - if (StepNo == 3) // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop. - End(); - return false; -} - -//----------------------------------------------------------------------------- -// [SECTION] RENDER HELPERS -// Those (internal) functions are currently quite a legacy mess - their signature and behavior will change. -// Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: state. -//----------------------------------------------------------------------------- - -const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end) -{ - const char* text_display_end = text; - if (!text_end) - text_end = (const char*)-1; - - while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#')) - text_display_end++; - return text_display_end; -} - -// Internal ImGui functions to render text -// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText() -void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - // Hide anything after a '##' string - const char* text_display_end; - if (hide_text_after_hash) - { - text_display_end = FindRenderedTextEnd(text, text_end); - } - else - { - if (!text_end) - text_end = text + strlen(text); // FIXME-OPT - text_display_end = text_end; - } - - if (text != text_display_end) - { - window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end); - if (g.LogEnabled) - LogRenderedText(&pos, text, text_display_end); - } -} - -void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - if (!text_end) - text_end = text + strlen(text); // FIXME-OPT - - if (text != text_end) - { - window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); - if (g.LogEnabled) - LogRenderedText(&pos, text, text_end); - } -} - -// Default clip_rect uses (pos_min,pos_max) -// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) -void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) -{ - // Hide anything after a '##' string - const char* text_display_end = FindRenderedTextEnd(text, text_end); - const int text_len = (int)(text_display_end - text); - if (text_len == 0) - return; - - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - // Perform CPU side clipping for single clipped element to avoid using scissor state - ImVec2 pos = pos_min; - const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f); - - const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min; - const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max; - bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); - if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min - need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); - - // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment. - if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x); - if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y); - - // Render - if (need_clipping) - { - ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); - window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); - } - else - { - window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); - } - if (g.LogEnabled) - LogRenderedText(&pos, text, text_display_end); -} - -// Render a rectangle shaped with optional rounding and borders -void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); - const float border_size = g.Style.FrameBorderSize; - if (border && border_size > 0.0f) - { - window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); - window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); - } -} - -void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - const float border_size = g.Style.FrameBorderSize; - if (border_size > 0.0f) - { - window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size); - window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size); - } -} - -// Render an arrow aimed to be aligned with text (p_min is a position in the same space text would be positioned). To e.g. denote expanded/collapsed state -void ImGui::RenderArrow(ImVec2 p_min, ImGuiDir dir, float scale) -{ - ImGuiContext& g = *GImGui; - - const float h = g.FontSize * 1.00f; - float r = h * 0.40f * scale; - ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale); - - ImVec2 a, b, c; - switch (dir) - { - case ImGuiDir_Up: - case ImGuiDir_Down: - if (dir == ImGuiDir_Up) r = -r; - a = ImVec2(+0.000f,+0.750f) * r; - b = ImVec2(-0.866f,-0.750f) * r; - c = ImVec2(+0.866f,-0.750f) * r; - break; - case ImGuiDir_Left: - case ImGuiDir_Right: - if (dir == ImGuiDir_Left) r = -r; - a = ImVec2(+0.750f,+0.000f) * r; - b = ImVec2(-0.750f,+0.866f) * r; - c = ImVec2(-0.750f,-0.866f) * r; - break; - case ImGuiDir_None: - case ImGuiDir_COUNT: - IM_ASSERT(0); - break; - } - - g.CurrentWindow->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text)); -} - -void ImGui::RenderBullet(ImVec2 pos) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - window->DrawList->AddCircleFilled(pos, g.FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8); -} - -void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - float thickness = ImMax(sz / 5.0f, 1.0f); - sz -= thickness*0.5f; - pos += ImVec2(thickness*0.25f, thickness*0.25f); - - float third = sz / 3.0f; - float bx = pos.x + third; - float by = pos.y + sz - third*0.5f; - window->DrawList->PathLineTo(ImVec2(bx - third, by - third)); - window->DrawList->PathLineTo(ImVec2(bx, by)); - window->DrawList->PathLineTo(ImVec2(bx + third*2, by - third*2)); - window->DrawList->PathStroke(col, false, thickness); -} - -void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags) -{ - ImGuiContext& g = *GImGui; - if (id != g.NavId) - return; - if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw)) - return; - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->DC.NavHideHighlightOneFrame) - return; - - float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; - ImRect display_rect = bb; - display_rect.ClipWith(window->ClipRect); - if (flags & ImGuiNavHighlightFlags_TypeDefault) - { - const float THICKNESS = 2.0f; - const float DISTANCE = 3.0f + THICKNESS * 0.5f; - display_rect.Expand(ImVec2(DISTANCE,DISTANCE)); - bool fully_visible = window->ClipRect.Contains(display_rect); - if (!fully_visible) - window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); - window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), display_rect.Max - ImVec2(THICKNESS*0.5f,THICKNESS*0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, ImDrawCornerFlags_All, THICKNESS); - if (!fully_visible) - window->DrawList->PopClipRect(); - } - if (flags & ImGuiNavHighlightFlags_TypeThin) - { - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f); - } -} - -//----------------------------------------------------------------------------- -// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) -//----------------------------------------------------------------------------- - -// ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods -ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) - : DrawListInst(&context->DrawListSharedData) -{ - Name = ImStrdup(name); - ID = ImHash(name, 0); - IDStack.push_back(ID); - Flags = 0; - Pos = ImVec2(0.0f, 0.0f); - Size = SizeFull = ImVec2(0.0f, 0.0f); - SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f); - WindowPadding = ImVec2(0.0f, 0.0f); - WindowRounding = 0.0f; - WindowBorderSize = 0.0f; - MoveId = GetID("#MOVE"); - ChildId = 0; - Scroll = ImVec2(0.0f, 0.0f); - ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); - ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); - ScrollbarSizes = ImVec2(0.0f, 0.0f); - ScrollbarX = ScrollbarY = false; - Active = WasActive = false; - WriteAccessed = false; - Collapsed = false; - WantCollapseToggle = false; - SkipItems = false; - Appearing = false; - Hidden = false; - HasCloseButton = false; - BeginCount = 0; - BeginOrderWithinParent = -1; - BeginOrderWithinContext = -1; - PopupId = 0; - AutoFitFramesX = AutoFitFramesY = -1; - AutoFitOnlyGrows = false; - AutoFitChildAxises = 0x00; - AutoPosLastDirection = ImGuiDir_None; - HiddenFramesRegular = HiddenFramesForResize = 0; - SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; - SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); - - LastFrameActive = -1; - ItemWidthDefault = 0.0f; - FontWindowScale = 1.0f; - SettingsIdx = -1; - - DrawList = &DrawListInst; - DrawList->_OwnerName = Name; - ParentWindow = NULL; - RootWindow = NULL; - RootWindowForTitleBarHighlight = NULL; - RootWindowForNav = NULL; - - NavLastIds[0] = NavLastIds[1] = 0; - NavRectRel[0] = NavRectRel[1] = ImRect(); - NavLastChildNavWindow = NULL; - - FocusIdxAllCounter = FocusIdxTabCounter = -1; - FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX; - FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX; -} - -ImGuiWindow::~ImGuiWindow() -{ - IM_ASSERT(DrawList == &DrawListInst); - IM_DELETE(Name); - for (int i = 0; i != ColumnsStorage.Size; i++) - ColumnsStorage[i].~ImGuiColumnsSet(); -} - -ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) -{ - ImGuiID seed = IDStack.back(); - ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed); - ImGui::KeepAliveID(id); - return id; -} - -ImGuiID ImGuiWindow::GetID(const void* ptr) -{ - ImGuiID seed = IDStack.back(); - ImGuiID id = ImHash(&ptr, sizeof(void*), seed); - ImGui::KeepAliveID(id); - return id; -} - -ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end) -{ - ImGuiID seed = IDStack.back(); - return ImHash(str, str_end ? (int)(str_end - str) : 0, seed); -} - -ImGuiID ImGuiWindow::GetIDNoKeepAlive(const void* ptr) -{ - ImGuiID seed = IDStack.back(); - return ImHash(&ptr, sizeof(void*), seed); -} - -// This is only used in rare/specific situations to manufacture an ID out of nowhere. -ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) -{ - ImGuiID seed = IDStack.back(); - const int r_rel[4] = { (int)(r_abs.Min.x - Pos.x), (int)(r_abs.Min.y - Pos.y), (int)(r_abs.Max.x - Pos.x), (int)(r_abs.Max.y - Pos.y) }; - ImGuiID id = ImHash(&r_rel, sizeof(r_rel), seed); - ImGui::KeepAliveID(id); - return id; -} - -static void SetCurrentWindow(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - g.CurrentWindow = window; - if (window) - g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); -} - -void ImGui::SetNavID(ImGuiID id, int nav_layer) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.NavWindow); - IM_ASSERT(nav_layer == 0 || nav_layer == 1); - g.NavId = id; - g.NavWindow->NavLastIds[nav_layer] = id; -} - -void ImGui::SetNavIDWithRectRel(ImGuiID id, int nav_layer, const ImRect& rect_rel) -{ - ImGuiContext& g = *GImGui; - SetNavID(id, nav_layer); - g.NavWindow->NavRectRel[nav_layer] = rect_rel; - g.NavMousePosDirty = true; - g.NavDisableHighlight = false; - g.NavDisableMouseHover = true; -} - -void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - g.ActiveIdIsJustActivated = (g.ActiveId != id); - if (g.ActiveIdIsJustActivated) - { - g.ActiveIdTimer = 0.0f; - g.ActiveIdHasBeenEdited = false; - if (id != 0) - { - g.LastActiveId = id; - g.LastActiveIdTimer = 0.0f; - } - } - g.ActiveId = id; - g.ActiveIdAllowNavDirFlags = 0; - g.ActiveIdAllowOverlap = false; - g.ActiveIdWindow = window; - if (id) - { - g.ActiveIdIsAlive = id; - g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse; - } -} - -void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(id != 0); - - // Assume that SetFocusID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it. - const int nav_layer = window->DC.NavLayerCurrent; - if (g.NavWindow != window) - g.NavInitRequest = false; - g.NavId = id; - g.NavWindow = window; - g.NavLayer = nav_layer; - window->NavLastIds[nav_layer] = id; - if (window->DC.LastItemId == id) - window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos); - - if (g.ActiveIdSource == ImGuiInputSource_Nav) - g.NavDisableMouseHover = true; - else - g.NavDisableHighlight = true; -} - -void ImGui::ClearActiveID() -{ - SetActiveID(0, NULL); -} - -void ImGui::SetHoveredID(ImGuiID id) -{ - ImGuiContext& g = *GImGui; - g.HoveredId = id; - g.HoveredIdAllowOverlap = false; - if (id != 0 && g.HoveredIdPreviousFrame != id) - g.HoveredIdTimer = 0.0f; -} - -ImGuiID ImGui::GetHoveredID() -{ - ImGuiContext& g = *GImGui; - return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame; -} - -void ImGui::KeepAliveID(ImGuiID id) -{ - ImGuiContext& g = *GImGui; - if (g.ActiveId == id) - g.ActiveIdIsAlive = id; - if (g.ActiveIdPreviousFrame == id) - g.ActiveIdPreviousFrameIsAlive = true; -} - -void ImGui::MarkItemEdited(ImGuiID id) -{ - // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit(). - // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need need to fill the data. - (void)id; // Avoid unused variable warnings when asserts are compiled out. - ImGuiContext& g = *GImGui; - IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive); - //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); - g.ActiveIdHasBeenEdited = true; - g.CurrentWindow->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited; -} - -static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) -{ - // An active popup disable hovering on other windows (apart from its own children) - // FIXME-OPT: This could be cached/stored within the window. - ImGuiContext& g = *GImGui; - if (g.NavWindow) - if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) - if (focused_root_window->WasActive && focused_root_window != window->RootWindow) - { - // For the purpose of those flags we differentiate "standard popup" from "modal popup" - // NB: The order of those two tests is important because Modal windows are also Popups. - if (focused_root_window->Flags & ImGuiWindowFlags_Modal) - return false; - if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) - return false; - } - - return true; -} - -// Advance cursor given item size for layout. -void ImGui::ItemSize(const ImVec2& size, float text_offset_y) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (window->SkipItems) - return; - - // Always align ourselves on pixel boundaries - const float line_height = ImMax(window->DC.CurrentLineSize.y, size.y); - const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y); - //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] - window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y); - window->DC.CursorPos = ImVec2((float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x), (float)(int)(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y)); - window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x); - window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y); - //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] - - window->DC.PrevLineSize.y = line_height; - window->DC.PrevLineTextBaseOffset = text_base_offset; - window->DC.CurrentLineSize.y = window->DC.CurrentLineTextBaseOffset = 0.0f; - - // Horizontal layout mode - if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) - SameLine(); -} - -void ImGui::ItemSize(const ImRect& bb, float text_offset_y) -{ - ItemSize(bb.GetSize(), text_offset_y); -} - -// Declare item bounding box for clipping and interaction. -// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface -// declare their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd(). -bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - if (id != 0) - { - // Navigation processing runs prior to clipping early-out - // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget - // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests unfortunately, but it is still limited to one window. - // it may not scale very well for windows with ten of thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame. - // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick) - window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask; - if (g.NavId == id || g.NavAnyRequest) - if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) - if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) - NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id); - } - - window->DC.LastItemId = id; - window->DC.LastItemRect = bb; - window->DC.LastItemStatusFlags = 0; - - // Clipping test - const bool is_clipped = IsClippedEx(bb, id, false); - if (is_clipped) - return false; - //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] - - // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) - if (IsMouseHoveringRect(bb.Min, bb.Max)) - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect; - return true; -} - -// This is roughly matching the behavior of internal-facing ItemHoverable() -// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered() -// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId -bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (g.NavDisableMouseHover && !g.NavDisableHighlight) - return IsItemFocused(); - - // Test for bounding box overlap, as updated as ItemAdd() - if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) - return false; - IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function - - // Test if we are hovering the right window (our window could be behind another window) - // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself. - // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while. - //if (g.HoveredWindow != window) - // return false; - if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped)) - return false; - - // Test if another item is active (e.g. being dragged) - if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) - return false; - - // Test if interactions on this window are blocked by an active popup or modal - if (!IsWindowContentHoverable(window, flags)) - return false; - - // Test if the item is disabled - if ((window->DC.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) - return false; - - // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect tht case. - if (window->DC.LastItemId == window->MoveId && window->WriteAccessed) - return false; - return true; -} - -// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). -bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) -{ - ImGuiContext& g = *GImGui; - if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) - return false; - - ImGuiWindow* window = g.CurrentWindow; - if (g.HoveredWindow != window) - return false; - if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) - return false; - if (!IsMouseHoveringRect(bb.Min, bb.Max)) - return false; - if (g.NavDisableMouseHover || !IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) - return false; - if (window->DC.ItemFlags & ImGuiItemFlags_Disabled) - return false; - - SetHoveredID(id); - return true; -} - -bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (!bb.Overlaps(window->ClipRect)) - if (id == 0 || id != g.ActiveId) - if (clip_even_when_logged || !g.LogEnabled) - return true; - return false; -} - -bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop) -{ - ImGuiContext& g = *GImGui; - - const bool allow_keyboard_focus = (window->DC.ItemFlags & (ImGuiItemFlags_AllowKeyboardFocus | ImGuiItemFlags_Disabled)) == ImGuiItemFlags_AllowKeyboardFocus; - window->FocusIdxAllCounter++; - if (allow_keyboard_focus) - window->FocusIdxTabCounter++; - - // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item. - // Note that we can always TAB out of a widget that doesn't allow tabbing in. - if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab)) - window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items. - - if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent) - return true; - if (allow_keyboard_focus && window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent) - { - g.NavJustTabbedId = id; - return true; - } - - return false; -} - -void ImGui::FocusableItemUnregister(ImGuiWindow* window) -{ - window->FocusIdxAllCounter--; - window->FocusIdxTabCounter--; -} - -ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y) -{ - ImGuiContext& g = *GImGui; - ImVec2 content_max; - if (size.x < 0.0f || size.y < 0.0f) - content_max = g.CurrentWindow->Pos + GetContentRegionMax(); - if (size.x <= 0.0f) - size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x; - if (size.y <= 0.0f) - size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y; - return size; -} - -float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) -{ - if (wrap_pos_x < 0.0f) - return 0.0f; - - ImGuiWindow* window = GetCurrentWindowRead(); - if (wrap_pos_x == 0.0f) - wrap_pos_x = GetContentRegionMax().x + window->Pos.x; - else if (wrap_pos_x > 0.0f) - wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space - - return ImMax(wrap_pos_x - pos.x, 1.0f); -} - -void* ImGui::MemAlloc(size_t size) -{ - if (ImGuiContext* ctx = GImGui) - ctx->IO.MetricsActiveAllocations++; - return GImAllocatorAllocFunc(size, GImAllocatorUserData); -} - -void ImGui::MemFree(void* ptr) -{ - if (ptr) - if (ImGuiContext* ctx = GImGui) - ctx->IO.MetricsActiveAllocations--; - return GImAllocatorFreeFunc(ptr, GImAllocatorUserData); -} - -const char* ImGui::GetClipboardText() -{ - return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : ""; -} - -void ImGui::SetClipboardText(const char* text) -{ - if (GImGui->IO.SetClipboardTextFn) - GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text); -} - -const char* ImGui::GetVersion() -{ - return IMGUI_VERSION; -} - -// Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself -// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module -ImGuiContext* ImGui::GetCurrentContext() -{ - return GImGui; -} - -void ImGui::SetCurrentContext(ImGuiContext* ctx) -{ -#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC - IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this. -#else - GImGui = ctx; -#endif -} - -// Helper function to verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit -// If the user has inconsistent compilation settings, imgui configuration #define, packing pragma, etc. you may see different structures from what imgui.cpp sees which is highly problematic. -bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_vert) -{ - bool error = false; - if (strcmp(version, IMGUI_VERSION)!=0) { error = true; IM_ASSERT(strcmp(version,IMGUI_VERSION)==0 && "Mismatched version string!"); } - if (sz_io != sizeof(ImGuiIO)) { error = true; IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!"); } - if (sz_style != sizeof(ImGuiStyle)) { error = true; IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!"); } - if (sz_vec2 != sizeof(ImVec2)) { error = true; IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!"); } - if (sz_vec4 != sizeof(ImVec4)) { error = true; IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!"); } - if (sz_vert != sizeof(ImDrawVert)) { error = true; IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!"); } - return !error; -} - -void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data) -{ - GImAllocatorAllocFunc = alloc_func; - GImAllocatorFreeFunc = free_func; - GImAllocatorUserData = user_data; -} - -ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas) -{ - ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas); - if (GImGui == NULL) - SetCurrentContext(ctx); - Initialize(ctx); - return ctx; -} - -void ImGui::DestroyContext(ImGuiContext* ctx) -{ - if (ctx == NULL) - ctx = GImGui; - Shutdown(ctx); - if (GImGui == ctx) - SetCurrentContext(NULL); - IM_DELETE(ctx); -} - -ImGuiIO& ImGui::GetIO() -{ - IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); - return GImGui->IO; -} - -ImGuiStyle& ImGui::GetStyle() -{ - IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); - return GImGui->Style; -} - -// Same value as passed to the old io.RenderDrawListsFn function. Valid after Render() and until the next call to NewFrame() -ImDrawData* ImGui::GetDrawData() -{ - ImGuiContext& g = *GImGui; - return g.DrawData.Valid ? &g.DrawData : NULL; -} - -double ImGui::GetTime() -{ - return GImGui->Time; -} - -int ImGui::GetFrameCount() -{ - return GImGui->FrameCount; -} - -ImDrawList* ImGui::GetOverlayDrawList() -{ - return &GImGui->OverlayDrawList; -} - -ImDrawListSharedData* ImGui::GetDrawListSharedData() -{ - return &GImGui->DrawListSharedData; -} - -void ImGui::StartMouseMovingWindow(ImGuiWindow* window) -{ - // Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows. - ImGuiContext& g = *GImGui; - FocusWindow(window); - SetActiveID(window->MoveId, window); - g.NavDisableHighlight = true; - g.ActiveIdClickOffset = g.IO.MousePos - window->RootWindow->Pos; - if (!(window->Flags & ImGuiWindowFlags_NoMove) && !(window->RootWindow->Flags & ImGuiWindowFlags_NoMove)) - g.MovingWindow = window; -} - -// Handle mouse moving window -// Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing() -void ImGui::UpdateMouseMovingWindow() -{ - ImGuiContext& g = *GImGui; - if (g.MovingWindow != NULL) - { - // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window). - // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency. - KeepAliveID(g.ActiveId); - IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow); - ImGuiWindow* moving_window = g.MovingWindow->RootWindow; - if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos)) - { - ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset; - if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y) - { - MarkIniSettingsDirty(moving_window); - SetWindowPos(moving_window, pos, ImGuiCond_Always); - } - FocusWindow(g.MovingWindow); - } - else - { - ClearActiveID(); - g.MovingWindow = NULL; - } - } - else - { - // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others. - if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId) - { - KeepAliveID(g.ActiveId); - if (!g.IO.MouseDown[0]) - ClearActiveID(); - } - } -} - -static bool IsWindowActiveAndVisible(ImGuiWindow* window) -{ - return (window->Active) && (!window->Hidden); -} - -static void ImGui::UpdateMouseInputs() -{ - ImGuiContext& g = *GImGui; - - // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta - if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev)) - g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; - else - g.IO.MouseDelta = ImVec2(0.0f, 0.0f); - if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f) - g.NavDisableMouseHover = false; - - g.IO.MousePosPrev = g.IO.MousePos; - for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) - { - g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f; - g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f; - g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; - g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f; - g.IO.MouseDoubleClicked[i] = false; - if (g.IO.MouseClicked[i]) - { - if ((float)(g.Time - g.IO.MouseClickedTime[i]) < g.IO.MouseDoubleClickTime) - { - ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); - if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist) - g.IO.MouseDoubleClicked[i] = true; - g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click - } - else - { - g.IO.MouseClickedTime[i] = g.Time; - } - g.IO.MouseClickedPos[i] = g.IO.MousePos; - g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f); - g.IO.MouseDragMaxDistanceSqr[i] = 0.0f; - } - else if (g.IO.MouseDown[i]) - { - // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold - ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f); - g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos)); - g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x); - g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y); - } - if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation - g.NavDisableMouseHover = false; - } -} - -void ImGui::UpdateMouseWheel() -{ - ImGuiContext& g = *GImGui; - if (!g.HoveredWindow || g.HoveredWindow->Collapsed) - return; - if (g.IO.MouseWheel == 0.0f && g.IO.MouseWheelH == 0.0f) - return; - - // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent (unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set). - ImGuiWindow* window = g.HoveredWindow; - ImGuiWindow* scroll_window = window; - while ((scroll_window->Flags & ImGuiWindowFlags_ChildWindow) && (scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoScrollbar) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs) && scroll_window->ParentWindow) - scroll_window = scroll_window->ParentWindow; - const bool scroll_allowed = !(scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs); - - if (g.IO.MouseWheel != 0.0f) - { - if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling) - { - // Zoom / Scale window - const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f); - const float scale = new_font_scale / window->FontWindowScale; - window->FontWindowScale = new_font_scale; - - const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; - window->Pos += offset; - window->Size *= scale; - window->SizeFull *= scale; - } - else if (!g.IO.KeyCtrl && scroll_allowed) - { - // Mouse wheel vertical scrolling - float scroll_amount = 5 * scroll_window->CalcFontSize(); - scroll_amount = (float)(int)ImMin(scroll_amount, (scroll_window->ContentsRegionRect.GetHeight() + scroll_window->WindowPadding.y * 2.0f) * 0.67f); - SetWindowScrollY(scroll_window, scroll_window->Scroll.y - g.IO.MouseWheel * scroll_amount); - } - } - if (g.IO.MouseWheelH != 0.0f && scroll_allowed && !g.IO.KeyCtrl) - { - // Mouse wheel horizontal scrolling (for hardware that supports it) - float scroll_amount = scroll_window->CalcFontSize(); - SetWindowScrollX(scroll_window, scroll_window->Scroll.x - g.IO.MouseWheelH * scroll_amount); - } -} - -// The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app) -void ImGui::UpdateHoveredWindowAndCaptureFlags() -{ - ImGuiContext& g = *GImGui; - - // Find the window hovered by mouse: - // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. - // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point of the frame. - // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. - FindHoveredWindow(); - - // Modal windows prevents cursor from hovering behind them. - ImGuiWindow* modal_window = GetFrontMostPopupModal(); - if (modal_window) - if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window)) - g.HoveredRootWindow = g.HoveredWindow = NULL; - - // Disabled mouse? - if (g.IO.ConfigFlags & ImGuiConfigFlags_NoMouse) - g.HoveredWindow = g.HoveredRootWindow = NULL; - - // We track click ownership. When clicked outside of a window the click is owned by the application and won't report hovering nor request capture even while dragging over our windows afterward. - int mouse_earliest_button_down = -1; - bool mouse_any_down = false; - for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) - { - if (g.IO.MouseClicked[i]) - g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty()); - mouse_any_down |= g.IO.MouseDown[i]; - if (g.IO.MouseDown[i]) - if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down]) - mouse_earliest_button_down = i; - } - const bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down]; - - // If mouse was first clicked outside of ImGui bounds we also cancel out hovering. - // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02) - const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0; - if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload) - g.HoveredWindow = g.HoveredRootWindow = NULL; - - // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to imgui, false = dispatch mouse info to imgui + app) - if (g.WantCaptureMouseNextFrame != -1) - g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0); - else - g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty()); - - // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to imgui, false = dispatch keyboard info to imgui + app) - if (g.WantCaptureKeyboardNextFrame != -1) - g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); - else - g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL); - if (g.IO.NavActive && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard)) - g.IO.WantCaptureKeyboard = true; - - // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a software keyboard if possible - g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; -} - -void ImGui::NewFrame() -{ - IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); - ImGuiContext& g = *GImGui; - - // Check user data - // (We pass an error message in the assert expression to make it visible to programmers who are not using a debugger, as most assert handlers display their argument) - IM_ASSERT(g.Initialized); - IM_ASSERT(g.IO.DeltaTime >= 0.0f && "Need a positive DeltaTime (zero is tolerated but will cause some timing issues)"); - IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value"); - IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?"); - IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?"); - IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting"); - IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting. Alpha cannot be negative (allows us to avoid a few clamps in color computations)"); - IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); - for (int n = 0; n < ImGuiKey_COUNT; n++) - IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) && "io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)"); - - // Perform simple check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was only recently added in 1.60 WIP) - if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) - IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); - - // Perform simple check: the beta io.ConfigResizeWindowsFromEdges option requires back-end to honor mouse cursor changes and set the ImGuiBackendFlags_HasMouseCursors flag accordingly. - if (g.IO.ConfigResizeWindowsFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors)) - g.IO.ConfigResizeWindowsFromEdges = false; - - // Load settings on first frame (if not explicitly loaded manually before) - if (!g.SettingsLoaded) - { - IM_ASSERT(g.SettingsWindows.empty()); - if (g.IO.IniFilename) - LoadIniSettingsFromDisk(g.IO.IniFilename); - g.SettingsLoaded = true; - } - - // Save settings (with a delay after the last modification, so we don't spam disk too much) - if (g.SettingsDirtyTimer > 0.0f) - { - g.SettingsDirtyTimer -= g.IO.DeltaTime; - if (g.SettingsDirtyTimer <= 0.0f) - { - if (g.IO.IniFilename != NULL) - SaveIniSettingsToDisk(g.IO.IniFilename); - else - g.IO.WantSaveIniSettings = true; // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves. - g.SettingsDirtyTimer = 0.0f; - } - } - - g.Time += g.IO.DeltaTime; - g.FrameScopeActive = true; - g.FrameCount += 1; - g.TooltipOverrideCount = 0; - g.WindowsActiveCount = 0; - - // Setup current font and draw list - g.IO.Fonts->Locked = true; - SetCurrentFont(GetDefaultFont()); - IM_ASSERT(g.Font->IsLoaded()); - g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); - g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; - - g.OverlayDrawList.Clear(); - g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID); - g.OverlayDrawList.PushClipRectFullScreen(); - g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); - - // Mark rendering data as invalid to prevent user who may have a handle on it to use it - g.DrawData.Clear(); - - // Drag and drop keep the source ID alive so even if the source disappear our state is consistent - if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId) - KeepAliveID(g.DragDropPayload.SourceId); - - // Clear reference to active widget if the widget isn't alive anymore - if (!g.HoveredIdPreviousFrame) - g.HoveredIdTimer = 0.0f; - if (g.HoveredId) - g.HoveredIdTimer += g.IO.DeltaTime; - g.HoveredIdPreviousFrame = g.HoveredId; - g.HoveredId = 0; - g.HoveredIdAllowOverlap = false; - if (g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0) - ClearActiveID(); - if (g.ActiveId) - g.ActiveIdTimer += g.IO.DeltaTime; - g.LastActiveIdTimer += g.IO.DeltaTime; - g.ActiveIdPreviousFrame = g.ActiveId; - g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow; - g.ActiveIdPreviousFrameHasBeenEdited = g.ActiveIdHasBeenEdited; - g.ActiveIdIsAlive = 0; - g.ActiveIdPreviousFrameIsAlive = false; - g.ActiveIdIsJustActivated = false; - if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId) - g.ScalarAsInputTextId = 0; - - // Drag and drop - g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; - g.DragDropAcceptIdCurr = 0; - g.DragDropAcceptIdCurrRectSurface = FLT_MAX; - g.DragDropWithinSourceOrTarget = false; - - // Update keyboard input state - memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); - for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) - g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f; - - // Update gamepad/keyboard directional navigation - NavUpdate(); - - // Update mouse input state - UpdateMouseInputs(); - - // Calculate frame-rate for the user, as a purely luxurious feature - g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime; - g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); - g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame))) : FLT_MAX; - - // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering) - UpdateMouseMovingWindow(); - UpdateHoveredWindowAndCaptureFlags(); - - // Background darkening/whitening - if (GetFrontMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f)) - g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0f, 1.0f); - else - g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0f, 0.0f); - - g.MouseCursor = ImGuiMouseCursor_Arrow; - g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1; - g.PlatformImePos = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default - - // Mouse wheel scrolling, scale - UpdateMouseWheel(); - - // Pressing TAB activate widget focus - if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, false)) - { - if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX) - g.NavWindow->FocusIdxTabRequestNext = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1); - else - g.NavWindow->FocusIdxTabRequestNext = g.IO.KeyShift ? -1 : 0; - } - g.NavIdTabCounter = INT_MAX; - - // Mark all windows as not visible - for (int i = 0; i != g.Windows.Size; i++) - { - ImGuiWindow* window = g.Windows[i]; - window->WasActive = window->Active; - window->Active = false; - window->WriteAccessed = false; - } - - // Closing the focused window restore focus to the first active root window in descending z-order - if (g.NavWindow && !g.NavWindow->WasActive) - FocusFrontMostActiveWindowIgnoringOne(NULL); - - // No window should be open at the beginning of the frame. - // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. - g.CurrentWindowStack.resize(0); - g.CurrentPopupStack.resize(0); - ClosePopupsOverWindow(g.NavWindow); - - // Create implicit window - we will only render it if the user has added something to it. - // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. - SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver); - Begin("Debug##Default"); -} - -void ImGui::Initialize(ImGuiContext* context) -{ - ImGuiContext& g = *context; - IM_ASSERT(!g.Initialized && !g.SettingsLoaded); - - // Add .ini handle for ImGuiWindow type - ImGuiSettingsHandler ini_handler; - ini_handler.TypeName = "Window"; - ini_handler.TypeHash = ImHash("Window", 0, 0); - ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen; - ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine; - ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll; - g.SettingsHandlers.push_front(ini_handler); - - g.Initialized = true; -} - -// This function is merely here to free heap allocations. -void ImGui::Shutdown(ImGuiContext* context) -{ - // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) - ImGuiContext& g = *context; - if (g.IO.Fonts && g.FontAtlasOwnedByContext) - IM_DELETE(g.IO.Fonts); - g.IO.Fonts = NULL; - - // Cleanup of other data are conditional on actually having initialized ImGui. - if (!g.Initialized) - return; - - // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file) - if (g.SettingsLoaded && g.IO.IniFilename != NULL) - SaveIniSettingsToDisk(g.IO.IniFilename); - - // Clear everything else - for (int i = 0; i < g.Windows.Size; i++) - IM_DELETE(g.Windows[i]); - g.Windows.clear(); - g.WindowsSortBuffer.clear(); - g.CurrentWindow = NULL; - g.CurrentWindowStack.clear(); - g.WindowsById.Clear(); - g.NavWindow = NULL; - g.HoveredWindow = NULL; - g.HoveredRootWindow = NULL; - g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; - g.MovingWindow = NULL; - g.ColorModifiers.clear(); - g.StyleModifiers.clear(); - g.FontStack.clear(); - g.OpenPopupStack.clear(); - g.CurrentPopupStack.clear(); - g.DrawDataBuilder.ClearFreeMemory(); - g.OverlayDrawList.ClearFreeMemory(); - g.PrivateClipboard.clear(); - g.InputTextState.TextW.clear(); - g.InputTextState.InitialText.clear(); - g.InputTextState.TempBuffer.clear(); - - for (int i = 0; i < g.SettingsWindows.Size; i++) - IM_DELETE(g.SettingsWindows[i].Name); - g.SettingsWindows.clear(); - g.SettingsHandlers.clear(); - - if (g.LogFile && g.LogFile != stdout) - { - fclose(g.LogFile); - g.LogFile = NULL; - } - g.LogClipboard.clear(); - - g.Initialized = false; -} - -// FIXME: Add a more explicit sort order in the window structure. -static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) -{ - const ImGuiWindow* const a = *(const ImGuiWindow* const *)lhs; - const ImGuiWindow* const b = *(const ImGuiWindow* const *)rhs; - if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup)) - return d; - if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip)) - return d; - return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); -} - -static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) -{ - out_sorted_windows->push_back(window); - if (window->Active) - { - int count = window->DC.ChildWindows.Size; - if (count > 1) - ImQsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer); - for (int i = 0; i < count; i++) - { - ImGuiWindow* child = window->DC.ChildWindows[i]; - if (child->Active) - AddWindowToSortedBuffer(out_sorted_windows, child); - } - } -} - -static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list) -{ - if (draw_list->CmdBuffer.empty()) - return; - - // Remove trailing command if unused - ImDrawCmd& last_cmd = draw_list->CmdBuffer.back(); - if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL) - { - draw_list->CmdBuffer.pop_back(); - if (draw_list->CmdBuffer.empty()) - return; - } - - // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. May trigger for you if you are using PrimXXX functions incorrectly. - IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); - IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); - IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); - - // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) - // If this assert triggers because you are drawing lots of stuff manually: - // A) Make sure you are coarse clipping, because ImDrawList let all your vertices pass. You can use the Metrics window to inspect draw list contents. - // B) If you need/want meshes with more than 64K vertices, uncomment the '#define ImDrawIdx unsigned int' line in imconfig.h to set the index size to 4 bytes. - // You'll need to handle the 4-bytes indices to your renderer. For example, the OpenGL example code detect index size at compile-time by doing: - // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); - // Your own engine or render API may use different parameters or function calls to specify index sizes. 2 and 4 bytes indices are generally supported by most API. - // C) If for some reason you cannot use 4 bytes indices or don't want to, a workaround is to call BeginChild()/EndChild() before reaching the 64K limit to split your draw commands in multiple draw lists. - if (sizeof(ImDrawIdx) == 2) - IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); - - out_list->push_back(draw_list); -} - -static void AddWindowToDrawData(ImVector* out_render_list, ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - g.IO.MetricsRenderWindows++; - AddDrawListToDrawData(out_render_list, window->DrawList); - for (int i = 0; i < window->DC.ChildWindows.Size; i++) - { - ImGuiWindow* child = window->DC.ChildWindows[i]; - if (IsWindowActiveAndVisible(child)) // clipped children may have been marked not active - AddWindowToDrawData(out_render_list, child); - } -} - -static void AddWindowToDrawDataSelectLayer(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - if (window->Flags & ImGuiWindowFlags_Tooltip) - AddWindowToDrawData(&g.DrawDataBuilder.Layers[1], window); - else - AddWindowToDrawData(&g.DrawDataBuilder.Layers[0], window); -} - -void ImDrawDataBuilder::FlattenIntoSingleLayer() -{ - int n = Layers[0].Size; - int size = n; - for (int i = 1; i < IM_ARRAYSIZE(Layers); i++) - size += Layers[i].Size; - Layers[0].resize(size); - for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++) - { - ImVector& layer = Layers[layer_n]; - if (layer.empty()) - continue; - memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*)); - n += layer.Size; - layer.resize(0); - } -} - -static void SetupDrawData(ImVector* draw_lists, ImDrawData* draw_data) -{ - ImGuiIO& io = ImGui::GetIO(); - draw_data->Valid = true; - draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL; - draw_data->CmdListsCount = draw_lists->Size; - draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0; - draw_data->DisplayPos = ImVec2(0.0f, 0.0f); - draw_data->DisplaySize = io.DisplaySize; - for (int n = 0; n < draw_lists->Size; n++) - { - draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size; - draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size; - } -} - -// When using this function it is sane to ensure that float are perfectly rounded to integer values, to that e.g. (int)(max.x-min.x) in user's render produce correct result. -void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect); - window->ClipRect = window->DrawList->_ClipRectStack.back(); -} - -void ImGui::PopClipRect() -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DrawList->PopClipRect(); - window->ClipRect = window->DrawList->_ClipRectStack.back(); -} - -// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal. -void ImGui::EndFrame() -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.Initialized); - if (g.FrameCountEnded == g.FrameCount) // Don't process EndFrame() multiple times. - return; - IM_ASSERT(g.FrameScopeActive && "Forgot to call ImGui::NewFrame()"); - - // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) - if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.PlatformImeLastPos - g.PlatformImePos) > 0.0001f) - { - g.IO.ImeSetInputScreenPosFn((int)g.PlatformImePos.x, (int)g.PlatformImePos.y); - g.PlatformImeLastPos = g.PlatformImePos; - } - - // Hide implicit "Debug" window if it hasn't been used - IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls, did you forget to call end on g.CurrentWindow->Name? - if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed) - g.CurrentWindow->Active = false; - End(); - - // Show CTRL+TAB list - if (g.NavWindowingTarget) - NavUpdateWindowingList(); - - // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) - if (g.DragDropActive) - { - bool is_delivered = g.DragDropPayload.Delivery; - bool is_elapsed = (g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) && ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceAutoExpirePayload) || !IsMouseDown(g.DragDropMouseButton)); - if (is_delivered || is_elapsed) - ClearDragDrop(); - } - - // Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing. - if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount) - { - g.DragDropWithinSourceOrTarget = true; - SetTooltip("..."); - g.DragDropWithinSourceOrTarget = false; - } - - // Initiate moving window - if (g.ActiveId == 0 && g.HoveredId == 0) - { - if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear - { - // Click to focus window and start moving (after we're done with all our widgets) - if (g.IO.MouseClicked[0]) - { - if (g.HoveredRootWindow != NULL) - StartMouseMovingWindow(g.HoveredWindow); - else if (g.NavWindow != NULL && GetFrontMostPopupModal() == NULL) - FocusWindow(NULL); // Clicking on void disable focus - } - - // With right mouse button we close popups without changing focus - // (The left mouse button path calls FocusWindow which will lead NewFrame->ClosePopupsOverWindow to trigger) - if (g.IO.MouseClicked[1]) - { - // Find the top-most window between HoveredWindow and the front most Modal Window. - // This is where we can trim the popup stack. - ImGuiWindow* modal = GetFrontMostPopupModal(); - bool hovered_window_above_modal = false; - if (modal == NULL) - hovered_window_above_modal = true; - for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--) - { - ImGuiWindow* window = g.Windows[i]; - if (window == modal) - break; - if (window == g.HoveredWindow) - hovered_window_above_modal = true; - } - ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal); - } - } - } - - // Sort the window list so that all child windows are after their parent - // We cannot do that on FocusWindow() because childs may not exist yet - g.WindowsSortBuffer.resize(0); - g.WindowsSortBuffer.reserve(g.Windows.Size); - for (int i = 0; i != g.Windows.Size; i++) - { - ImGuiWindow* window = g.Windows[i]; - if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it - continue; - AddWindowToSortedBuffer(&g.WindowsSortBuffer, window); - } - - IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong - g.Windows.swap(g.WindowsSortBuffer); - g.IO.MetricsActiveWindows = g.WindowsActiveCount; - - // Unlock font atlas - g.IO.Fonts->Locked = false; - - // Clear Input data for next frame - g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f; - memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); - memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs)); - - g.FrameScopeActive = false; - g.FrameCountEnded = g.FrameCount; -} - -void ImGui::Render() -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.Initialized); - - if (g.FrameCountEnded != g.FrameCount) - ImGui::EndFrame(); - g.FrameCountRendered = g.FrameCount; - - // Gather ImDrawList to render (for each active window) - g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsRenderWindows = 0; - g.DrawDataBuilder.Clear(); - ImGuiWindow* windows_to_render_front_most[2]; - windows_to_render_front_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL; - windows_to_render_front_most[1] = g.NavWindowingTarget ? g.NavWindowingList : NULL; - for (int n = 0; n != g.Windows.Size; n++) - { - ImGuiWindow* window = g.Windows[n]; - if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_front_most[0] && window != windows_to_render_front_most[1]) - AddWindowToDrawDataSelectLayer(window); - } - for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_front_most); n++) - if (windows_to_render_front_most[n] && IsWindowActiveAndVisible(windows_to_render_front_most[n])) // NavWindowingTarget is always temporarily displayed as the front-most window - AddWindowToDrawDataSelectLayer(windows_to_render_front_most[n]); - g.DrawDataBuilder.FlattenIntoSingleLayer(); - - // Draw software mouse cursor if requested - if (g.IO.MouseDrawCursor) - RenderMouseCursor(&g.OverlayDrawList, g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor); - - if (!g.OverlayDrawList.VtxBuffer.empty()) - AddDrawListToDrawData(&g.DrawDataBuilder.Layers[0], &g.OverlayDrawList); - - // Setup ImDrawData structure for end-user - SetupDrawData(&g.DrawDataBuilder.Layers[0], &g.DrawData); - g.IO.MetricsRenderVertices = g.DrawData.TotalVtxCount; - g.IO.MetricsRenderIndices = g.DrawData.TotalIdxCount; - - // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData() -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - if (g.DrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL) - g.IO.RenderDrawListsFn(&g.DrawData); -#endif -} - -// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker. -// CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize) -ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width) -{ - ImGuiContext& g = *GImGui; - - const char* text_display_end; - if (hide_text_after_double_hash) - text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string - else - text_display_end = text_end; - - ImFont* font = g.Font; - const float font_size = g.FontSize; - if (text == text_display_end) - return ImVec2(0.0f, font_size); - ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL); - - // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field) - const float font_scale = font_size / font->FontSize; - const float character_spacing_x = 1.0f * font_scale; - if (text_size.x > 0.0f) - text_size.x -= character_spacing_x; - text_size.x = (float)(int)(text_size.x + 0.95f); - - return text_size; -} - -// Helper to calculate coarse clipping of large list of evenly sized items. -// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. -// NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX -void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (g.LogEnabled) - { - // If logging is active, do not perform any clipping - *out_items_display_start = 0; - *out_items_display_end = items_count; - return; - } - if (window->SkipItems) - { - *out_items_display_start = *out_items_display_end = 0; - return; - } - - // We create the union of the ClipRect and the NavScoringRect which at worst should be 1 page away from ClipRect - ImRect unclipped_rect = window->ClipRect; - if (g.NavMoveRequest) - unclipped_rect.Add(g.NavScoringRectScreen); - - const ImVec2 pos = window->DC.CursorPos; - int start = (int)((unclipped_rect.Min.y - pos.y) / items_height); - int end = (int)((unclipped_rect.Max.y - pos.y) / items_height); - - // When performing a navigation request, ensure we have one item extra in the direction we are moving to - if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Up) - start--; - if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Down) - end++; - - start = ImClamp(start, 0, items_count); - end = ImClamp(end + 1, start, items_count); - *out_items_display_start = start; - *out_items_display_end = end; -} - -// Find window given position, search front-to-back -// FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programatically -// with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is -// called, aka before the next Begin(). Moving window isn't affected. -static void FindHoveredWindow() -{ - ImGuiContext& g = *GImGui; - - ImGuiWindow* hovered_window = NULL; - if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs)) - hovered_window = g.MovingWindow; - - for (int i = g.Windows.Size - 1; i >= 0 && hovered_window == NULL; i--) - { - ImGuiWindow* window = g.Windows[i]; - if (!window->Active || window->Hidden) - continue; - if (window->Flags & ImGuiWindowFlags_NoInputs) - continue; - - // Using the clipped AABB, a child window will typically be clipped by its parent (not always) - ImRect bb(window->OuterRectClipped.Min - g.Style.TouchExtraPadding, window->OuterRectClipped.Max + g.Style.TouchExtraPadding); - if (bb.Contains(g.IO.MousePos)) - { - if (hovered_window == NULL) - hovered_window = window; - if (hovered_window) - break; - } - } - - g.HoveredWindow = hovered_window; - g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; - -} - -// Test if mouse cursor is hovering given rectangle -// NB- Rectangle is clipped by our current clip setting -// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding) -bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip) -{ - ImGuiContext& g = *GImGui; - - // Clip - ImRect rect_clipped(r_min, r_max); - if (clip) - rect_clipped.ClipWith(g.CurrentWindow->ClipRect); - - // Expand for touch input - const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); - if (!rect_for_touch.Contains(g.IO.MousePos)) - return false; - return true; -} - -int ImGui::GetKeyIndex(ImGuiKey imgui_key) -{ - IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT); - return GImGui->IO.KeyMap[imgui_key]; -} - -// Note that imgui doesn't know the semantic of each entry of io.KeysDown[]. Use your own indices/enums according to how your back-end/engine stored them into io.KeysDown[]! -bool ImGui::IsKeyDown(int user_key_index) -{ - if (user_key_index < 0) return false; - IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown)); - return GImGui->IO.KeysDown[user_key_index]; -} - -int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate) -{ - if (t == 0.0f) - return 1; - if (t <= repeat_delay || repeat_rate <= 0.0f) - return 0; - const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate); - return (count > 0) ? count : 0; -} - -int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate) -{ - ImGuiContext& g = *GImGui; - if (key_index < 0) return false; - IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); - const float t = g.IO.KeysDownDuration[key_index]; - return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate); -} - -bool ImGui::IsKeyPressed(int user_key_index, bool repeat) -{ - ImGuiContext& g = *GImGui; - if (user_key_index < 0) return false; - IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); - const float t = g.IO.KeysDownDuration[user_key_index]; - if (t == 0.0f) - return true; - if (repeat && t > g.IO.KeyRepeatDelay) - return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; - return false; -} - -bool ImGui::IsKeyReleased(int user_key_index) -{ - ImGuiContext& g = *GImGui; - if (user_key_index < 0) return false; - IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown)); - return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index]; -} - -bool ImGui::IsMouseDown(int button) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseDown[button]; -} - -bool ImGui::IsAnyMouseDown() -{ - ImGuiContext& g = *GImGui; - for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++) - if (g.IO.MouseDown[n]) - return true; - return false; -} - -bool ImGui::IsMouseClicked(int button, bool repeat) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - const float t = g.IO.MouseDownDuration[button]; - if (t == 0.0f) - return true; - - if (repeat && t > g.IO.KeyRepeatDelay) - { - float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate; - if ((ImFmod(t - delay, rate) > rate*0.5f) != (ImFmod(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) - return true; - } - - return false; -} - -bool ImGui::IsMouseReleased(int button) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseReleased[button]; -} - -bool ImGui::IsMouseDoubleClicked(int button) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - return g.IO.MouseDoubleClicked[button]; -} - -bool ImGui::IsMouseDragging(int button, float lock_threshold) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - if (!g.IO.MouseDown[button]) - return false; - if (lock_threshold < 0.0f) - lock_threshold = g.IO.MouseDragThreshold; - return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold; -} - -ImVec2 ImGui::GetMousePos() -{ - return GImGui->IO.MousePos; -} - -// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed! -ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup() -{ - ImGuiContext& g = *GImGui; - if (g.CurrentPopupStack.Size > 0) - return g.OpenPopupStack[g.CurrentPopupStack.Size-1].OpenMousePos; - return g.IO.MousePos; -} - -// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position -bool ImGui::IsMousePosValid(const ImVec2* mouse_pos) -{ - if (mouse_pos == NULL) - mouse_pos = &GImGui->IO.MousePos; - const float MOUSE_INVALID = -256000.0f; - return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID; -} - -// NB: This is only valid if IsMousePosValid(). Back-ends in theory should always keep mouse position valid when dragging even outside the client window. -ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - if (lock_threshold < 0.0f) - lock_threshold = g.IO.MouseDragThreshold; - if (g.IO.MouseDown[button]) - if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold) - return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment). - return ImVec2(0.0f, 0.0f); -} - -void ImGui::ResetMouseDragDelta(int button) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr - g.IO.MouseClickedPos[button] = g.IO.MousePos; -} - -ImGuiMouseCursor ImGui::GetMouseCursor() -{ - return GImGui->MouseCursor; -} - -void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type) -{ - GImGui->MouseCursor = cursor_type; -} - -void ImGui::CaptureKeyboardFromApp(bool capture) -{ - GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0; -} - -void ImGui::CaptureMouseFromApp(bool capture) -{ - GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0; -} - -bool ImGui::IsItemActive() -{ - ImGuiContext& g = *GImGui; - if (g.ActiveId) - { - ImGuiWindow* window = g.CurrentWindow; - return g.ActiveId == window->DC.LastItemId; - } - return false; -} - -bool ImGui::IsItemDeactivated() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - return (g.ActiveIdPreviousFrame == window->DC.LastItemId && g.ActiveIdPreviousFrame != 0 && g.ActiveId != window->DC.LastItemId); -} - -bool ImGui::IsItemDeactivatedAfterEdit() -{ - ImGuiContext& g = *GImGui; - return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEdited || (g.ActiveId == 0 && g.ActiveIdHasBeenEdited)); -} - -bool ImGui::IsItemFocused() -{ - ImGuiContext& g = *GImGui; - return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId; -} - -bool ImGui::IsItemClicked(int mouse_button) -{ - return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None); -} - -bool ImGui::IsAnyItemHovered() -{ - ImGuiContext& g = *GImGui; - return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0; -} - -bool ImGui::IsAnyItemActive() -{ - ImGuiContext& g = *GImGui; - return g.ActiveId != 0; -} - -bool ImGui::IsAnyItemFocused() -{ - ImGuiContext& g = *GImGui; - return g.NavId != 0 && !g.NavDisableHighlight; -} - -bool ImGui::IsItemVisible() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->ClipRect.Overlaps(window->DC.LastItemRect); -} - -bool ImGui::IsItemEdited() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Edited) != 0; -} - -// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. -void ImGui::SetItemAllowOverlap() -{ - ImGuiContext& g = *GImGui; - if (g.HoveredId == g.CurrentWindow->DC.LastItemId) - g.HoveredIdAllowOverlap = true; - if (g.ActiveId == g.CurrentWindow->DC.LastItemId) - g.ActiveIdAllowOverlap = true; -} - -ImVec2 ImGui::GetItemRectMin() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemRect.Min; -} - -ImVec2 ImGui::GetItemRectMax() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemRect.Max; -} - -ImVec2 ImGui::GetItemRectSize() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemRect.GetSize(); -} - -static ImRect GetViewportRect() -{ - ImGuiContext& g = *GImGui; - if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y) - return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax); - return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); -} - -static bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* parent_window = g.CurrentWindow; - - flags |= ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow; - flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag - - // Size - const ImVec2 content_avail = GetContentRegionAvail(); - ImVec2 size = ImFloor(size_arg); - const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00); - if (size.x <= 0.0f) - size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues) - if (size.y <= 0.0f) - size.y = ImMax(content_avail.y + size.y, 4.0f); - SetNextWindowSize(size); - - // Name - char title[256]; - if (name) - ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s", parent_window->Name, name); - else - ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id); - - const float backup_border_size = g.Style.ChildBorderSize; - if (!border) - g.Style.ChildBorderSize = 0.0f; - bool ret = Begin(title, NULL, flags); - g.Style.ChildBorderSize = backup_border_size; - - ImGuiWindow* child_window = g.CurrentWindow; - child_window->ChildId = id; - child_window->AutoFitChildAxises = auto_fit_axises; - - // Process navigation-in immediately so NavInit can run on first frame - if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll)) - { - FocusWindow(child_window); - NavInitWindow(child_window, false); - SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item - g.ActiveIdSource = ImGuiInputSource_Nav; - } - return ret; -} - -bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) -{ - ImGuiWindow* window = GetCurrentWindow(); - return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags); -} - -bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags) -{ - IM_ASSERT(id != 0); - return BeginChildEx(NULL, id, size_arg, border, extra_flags); -} - -void ImGui::EndChild() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss - if (window->BeginCount > 1) - { - End(); - } - else - { - ImVec2 sz = window->Size; - if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f - sz.x = ImMax(4.0f, sz.x); - if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y)) - sz.y = ImMax(4.0f, sz.y); - End(); - - ImGuiWindow* parent_window = g.CurrentWindow; - ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz); - ItemSize(sz); - if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened)) - { - ItemAdd(bb, window->ChildId); - RenderNavHighlight(bb, window->ChildId); - - // When browsing a window that has no activable items (scroll only) we keep a highlight on the child - if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow) - RenderNavHighlight(ImRect(bb.Min - ImVec2(2,2), bb.Max + ImVec2(2,2)), g.NavId, ImGuiNavHighlightFlags_TypeThin); - } - else - { - // Not navigable into - ItemAdd(bb, 0); - } - } -} - -// Helper to create a child window / scrolling region that looks like a normal widget frame. -bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags) -{ - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]); - PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding); - PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize); - PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding); - bool ret = BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags); - PopStyleVar(3); - PopStyleColor(); - return ret; -} - -void ImGui::EndChildFrame() -{ - EndChild(); -} - -// Save and compare stack sizes on Begin()/End() to detect usage errors -static void CheckStacksSize(ImGuiWindow* window, bool write) -{ - // NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin) - ImGuiContext& g = *GImGui; - int* p_backup = &window->DC.StackSizesBackup[0]; - { int current = window->IDStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!"); p_backup++; } // Too few or too many PopID()/TreePop() - { int current = window->DC.GroupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!"); p_backup++; } // Too few or too many EndGroup() - { int current = g.CurrentPopupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch"); p_backup++;}// Too few or too many EndMenu()/EndPopup() - // For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our checks a little to allow them. - { int current = g.ColorModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushStyleColor/PopStyleColor Mismatch!"); p_backup++; } // Too few or too many PopStyleColor() - { int current = g.StyleModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushStyleVar/PopStyleVar Mismatch!"); p_backup++; } // Too few or too many PopStyleVar() - { int current = g.FontStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushFont/PopFont Mismatch!"); p_backup++; } // Too few or too many PopFont() - IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup)); -} - -static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled) -{ - window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags); - window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags); - window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags); -} - -ImGuiWindow* ImGui::FindWindowByName(const char* name) -{ - ImGuiContext& g = *GImGui; - ImGuiID id = ImHash(name, 0); - return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id); -} - -static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags) -{ - ImGuiContext& g = *GImGui; - - // Create window the first time - ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name); - window->Flags = flags; - g.WindowsById.SetVoidPtr(window->ID, window); - - // Default/arbitrary window position. Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. - window->Pos = ImVec2(60, 60); - - // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. - if (!(flags & ImGuiWindowFlags_NoSavedSettings)) - if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID)) - { - // Retrieve settings from .ini file - window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings); - SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); - window->Pos = ImFloor(settings->Pos); - window->Collapsed = settings->Collapsed; - if (ImLengthSqr(settings->Size) > 0.00001f) - size = ImFloor(settings->Size); - } - window->Size = window->SizeFull = window->SizeFullAtLastBegin = size; - window->DC.CursorMaxPos = window->Pos; // So first call to CalcSizeContents() doesn't return crazy values - - if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) - { - window->AutoFitFramesX = window->AutoFitFramesY = 2; - window->AutoFitOnlyGrows = false; - } - else - { - if (window->Size.x <= 0.0f) - window->AutoFitFramesX = 2; - if (window->Size.y <= 0.0f) - window->AutoFitFramesY = 2; - window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); - } - - if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) - g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once - else - g.Windows.push_back(window); - return window; -} - -static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size) -{ - ImGuiContext& g = *GImGui; - if (g.NextWindowData.SizeConstraintCond != 0) - { - // Using -1,-1 on either X/Y axis to preserve the current size. - ImRect cr = g.NextWindowData.SizeConstraintRect; - new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x; - new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y; - if (g.NextWindowData.SizeCallback) - { - ImGuiSizeCallbackData data; - data.UserData = g.NextWindowData.SizeCallbackUserData; - data.Pos = window->Pos; - data.CurrentSize = window->SizeFull; - data.DesiredSize = new_size; - g.NextWindowData.SizeCallback(&data); - new_size = data.DesiredSize; - } - } - - // Minimum size - if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) - { - new_size = ImMax(new_size, g.Style.WindowMinSize); - new_size.y = ImMax(new_size.y, window->TitleBarHeight() + window->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows - } - return new_size; -} - -static ImVec2 CalcSizeContents(ImGuiWindow* window) -{ - ImVec2 sz; - sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x)); - sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y)); - return sz + window->WindowPadding; -} - -static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents) -{ - ImGuiContext& g = *GImGui; - ImGuiStyle& style = g.Style; - if (window->Flags & ImGuiWindowFlags_Tooltip) - { - // Tooltip always resize - return size_contents; - } - else - { - // When the window cannot fit all contents (either because of constraints, either because screen is too small): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding. - const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) != 0; - const bool is_menu = (window->Flags & ImGuiWindowFlags_ChildMenu) != 0; - ImVec2 size_min = style.WindowMinSize; - if (is_popup || is_menu) // Popups and menus bypass style.WindowMinSize by default, but we give then a non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups) - size_min = ImMin(size_min, ImVec2(4.0f, 4.0f)); - ImVec2 size_auto_fit = ImClamp(size_contents, size_min, ImMax(size_min, g.IO.DisplaySize - style.DisplaySafeAreaPadding * 2.0f)); - ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit); - if (size_auto_fit_after_constraint.x < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) - size_auto_fit.y += style.ScrollbarSize; - if (size_auto_fit_after_constraint.y < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) - size_auto_fit.x += style.ScrollbarSize; - return size_auto_fit; - } -} - -ImVec2 ImGui::CalcWindowExpectedSize(ImGuiWindow* window) -{ - ImVec2 size_contents = CalcSizeContents(window); - return CalcSizeAfterConstraint(window, CalcSizeAutoFit(window, size_contents)); -} - -static float GetScrollMaxX(ImGuiWindow* window) -{ - return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x)); -} - -static float GetScrollMaxY(ImGuiWindow* window) -{ - return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y)); -} - -static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges) -{ - ImGuiContext& g = *GImGui; - ImVec2 scroll = window->Scroll; - if (window->ScrollTarget.x < FLT_MAX) - { - float cr_x = window->ScrollTargetCenterRatio.x; - scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x); - } - if (window->ScrollTarget.y < FLT_MAX) - { - // 'snap_on_edges' allows for a discontinuity at the edge of scrolling limits to take account of WindowPadding so that scrolling to make the last item visible scroll far enough to see the padding. - float cr_y = window->ScrollTargetCenterRatio.y; - float target_y = window->ScrollTarget.y; - if (snap_on_edges && cr_y <= 0.0f && target_y <= window->WindowPadding.y) - target_y = 0.0f; - if (snap_on_edges && cr_y >= 1.0f && target_y >= window->SizeContents.y - window->WindowPadding.y + g.Style.ItemSpacing.y) - target_y = window->SizeContents.y; - scroll.y = target_y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y); - } - scroll = ImMax(scroll, ImVec2(0.0f, 0.0f)); - if (!window->Collapsed && !window->SkipItems) - { - scroll.x = ImMin(scroll.x, GetScrollMaxX(window)); - scroll.y = ImMin(scroll.y, GetScrollMaxY(window)); - } - return scroll; -} - -static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags) -{ - if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) - return ImGuiCol_PopupBg; - if (flags & ImGuiWindowFlags_ChildWindow) - return ImGuiCol_ChildBg; - return ImGuiCol_WindowBg; -} - -static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) -{ - ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left - ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right - ImVec2 size_expected = pos_max - pos_min; - ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected); - *out_pos = pos_min; - if (corner_norm.x == 0.0f) - out_pos->x -= (size_constrained.x - size_expected.x); - if (corner_norm.y == 0.0f) - out_pos->y -= (size_constrained.y - size_expected.y); - *out_size = size_constrained; -} - -struct ImGuiResizeGripDef -{ - ImVec2 CornerPos; - ImVec2 InnerDir; - int AngleMin12, AngleMax12; -}; - -const ImGuiResizeGripDef resize_grip_def[4] = -{ - { ImVec2(1,1), ImVec2(-1,-1), 0, 3 }, // Lower right - { ImVec2(0,1), ImVec2(+1,-1), 3, 6 }, // Lower left - { ImVec2(0,0), ImVec2(+1,+1), 6, 9 }, // Upper left - { ImVec2(1,0), ImVec2(-1,+1), 9,12 }, // Upper right -}; - -static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness) -{ - ImRect rect = window->Rect(); - if (thickness == 0.0f) rect.Max -= ImVec2(1,1); - if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y, rect.Max.x - perp_padding, rect.Min.y + thickness); - if (border_n == 1) return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x, rect.Max.y - perp_padding); - if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y); - if (border_n == 3) return ImRect(rect.Min.x, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); - IM_ASSERT(0); - return ImRect(); -} - -// Handle resize for: Resize Grips, Borders, Gamepad -static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]) -{ - ImGuiContext& g = *GImGui; - ImGuiWindowFlags flags = window->Flags; - if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) - return; - - const int resize_border_count = g.IO.ConfigResizeWindowsFromEdges ? 4 : 0; - const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f); - const float grip_hover_size = (float)(int)(grip_draw_size * 0.75f); - - ImVec2 pos_target(FLT_MAX, FLT_MAX); - ImVec2 size_target(FLT_MAX, FLT_MAX); - - // Manual resize grips - PushID("#RESIZE"); - for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) - { - const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; - const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); - - // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window - ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size); - if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(resize_rect.Min.x, resize_rect.Max.x); - if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(resize_rect.Min.y, resize_rect.Max.y); - bool hovered, held; - ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); - if (hovered || held) - g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE; - - if (held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0) - { - // Manual auto-fit when double-clicking - size_target = CalcSizeAfterConstraint(window, size_auto_fit); - ClearActiveID(); - } - else if (held) - { - // Resize from any of the four corners - // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position - ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize() * grip.CornerPos; // Corner of the window corresponding to our corner grip - CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target); - } - if (resize_grip_n == 0 || held || hovered) - resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); - } - for (int border_n = 0; border_n < resize_border_count; border_n++) - { - const float BORDER_SIZE = 5.0f; // FIXME: Only works _inside_ window because of HoveredWindow check. - const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise - bool hovered, held; - ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_size, BORDER_SIZE); - ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n + 4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren); - if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held) - { - g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS; - if (held) *border_held = border_n; - } - if (held) - { - ImVec2 border_target = window->Pos; - ImVec2 border_posn; - if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y); } - if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + BORDER_SIZE); } - if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + BORDER_SIZE); } - if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x); } - CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target); - } - } - PopID(); - - // Navigation resize (keyboard/gamepad) - if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window) - { - ImVec2 nav_resize_delta; - if (g.NavInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift) - nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); - if (g.NavInputSource == ImGuiInputSource_NavGamepad) - nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down); - if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f) - { - const float NAV_RESIZE_SPEED = 600.0f; - nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); - g.NavWindowingToggleLayer = false; - g.NavDisableMouseHover = true; - resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive); - // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck. - size_target = CalcSizeAfterConstraint(window, window->SizeFull + nav_resize_delta); - } - } - - // Apply back modified position/size to window - if (size_target.x != FLT_MAX) - { - window->SizeFull = size_target; - MarkIniSettingsDirty(window); - } - if (pos_target.x != FLT_MAX) - { - window->Pos = ImFloor(pos_target); - MarkIniSettingsDirty(window); - } - - window->Size = window->SizeFull; -} - -void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window) -{ - window->ParentWindow = parent_window; - window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window; - if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) - window->RootWindow = parent_window->RootWindow; - if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) - window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight; - while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened) - window->RootWindowForNav = window->RootWindowForNav->ParentWindow; -} - -// Push a new ImGui window to add widgets to. -// - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. -// - Begin/End can be called multiple times during the frame with the same window name to append content. -// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file). -// You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See documentation at the top of this file. -// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned. -// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed. -bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) -{ - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - IM_ASSERT(name != NULL); // Window name required - IM_ASSERT(g.FrameScopeActive); // Forgot to call ImGui::NewFrame() - IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet - - // Find or create - ImGuiWindow* window = FindWindowByName(name); - const bool window_just_created = (window == NULL); - if (window_just_created) - { - ImVec2 size_on_first_use = (g.NextWindowData.SizeCond != 0) ? g.NextWindowData.SizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here. - window = CreateNewWindow(name, size_on_first_use, flags); - } - - // Automatically disable manual moving/resizing when NoInputs is set - if (flags & ImGuiWindowFlags_NoInputs) - flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; - - if (flags & ImGuiWindowFlags_NavFlattened) - IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow); - - const int current_frame = g.FrameCount; - const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); - if (first_begin_of_the_frame) - window->Flags = (ImGuiWindowFlags)flags; - else - flags = window->Flags; - - // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack - ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back(); - ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; - IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); - window->HasCloseButton = (p_open != NULL); - - // Update the Appearing flag - bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on - const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesForResize > 0); - if (flags & ImGuiWindowFlags_Popup) - { - ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; - window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed - window_just_activated_by_user |= (window != popup_ref.Window); - } - window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize); - if (window->Appearing) - SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); - - // Add to stack - g.CurrentWindowStack.push_back(window); - SetCurrentWindow(window); - CheckStacksSize(window, true); - if (flags & ImGuiWindowFlags_Popup) - { - ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; - popup_ref.Window = window; - g.CurrentPopupStack.push_back(popup_ref); - window->PopupId = popup_ref.PopupId; - } - - if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow)) - window->NavLastIds[0] = 0; - - // Process SetNextWindow***() calls - bool window_pos_set_by_api = false; - bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; - if (g.NextWindowData.PosCond) - { - window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0; - if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f) - { - // May be processed on the next frame if this is our first frame and we are measuring size - // FIXME: Look into removing the branch so everything can go through this same code path for consistency. - window->SetWindowPosVal = g.NextWindowData.PosVal; - window->SetWindowPosPivot = g.NextWindowData.PosPivotVal; - window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); - } - else - { - SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond); - } - } - if (g.NextWindowData.SizeCond) - { - window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f); - window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f); - SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond); - } - if (g.NextWindowData.ContentSizeCond) - { - // Adjust passed "client size" to become a "window size" - window->SizeContentsExplicit = g.NextWindowData.ContentSizeVal; - if (window->SizeContentsExplicit.y != 0.0f) - window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight(); - } - else if (first_begin_of_the_frame) - { - window->SizeContentsExplicit = ImVec2(0.0f, 0.0f); - } - if (g.NextWindowData.CollapsedCond) - SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); - if (g.NextWindowData.FocusCond) - FocusWindow(window); - if (window->Appearing) - SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); - - // When reusing window again multiple times a frame, just append content (don't need to setup again) - if (first_begin_of_the_frame) - { - // Initialize - const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345) - UpdateWindowParentAndRootLinks(window, flags, parent_window); - - window->Active = true; - window->BeginOrderWithinParent = 0; - window->BeginOrderWithinContext = g.WindowsActiveCount++; - window->BeginCount = 0; - window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX); - window->LastFrameActive = current_frame; - window->IDStack.resize(1); - - // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS - - // Update contents size from last frame for auto-fitting (or use explicit size) - window->SizeContents = CalcSizeContents(window); - if (window->HiddenFramesRegular > 0) - window->HiddenFramesRegular--; - if (window->HiddenFramesForResize > 0) - window->HiddenFramesForResize--; - - // Hide new windows for one frame until they calculate their size - if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api)) - window->HiddenFramesForResize = 1; - - // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) - // We reset Size/SizeContents for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size. - if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0) - { - window->HiddenFramesForResize = 1; - if (flags & ImGuiWindowFlags_AlwaysAutoResize) - { - if (!window_size_x_set_by_api) - window->Size.x = window->SizeFull.x = 0.f; - if (!window_size_y_set_by_api) - window->Size.y = window->SizeFull.y = 0.f; - window->SizeContents = ImVec2(0.f, 0.f); - } - } - - SetCurrentWindow(window); - - // Lock border size and padding for the frame (so that altering them doesn't cause inconsistencies) - window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; - window->WindowPadding = style.WindowPadding; - if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) - window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); - window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); - window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; - - // Collapse window by double-clicking on title bar - // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing - if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse)) - { - // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't have items over the title bar. - ImRect title_bar_rect = window->TitleBarRect(); - if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]) - window->WantCollapseToggle = true; - if (window->WantCollapseToggle) - { - window->Collapsed = !window->Collapsed; - MarkIniSettingsDirty(window); - FocusWindow(window); - } - } - else - { - window->Collapsed = false; - } - window->WantCollapseToggle = false; - - // SIZE - - // Calculate auto-fit size, handle automatic resize - const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents); - ImVec2 size_full_modified(FLT_MAX, FLT_MAX); - if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed) - { - // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. - if (!window_size_x_set_by_api) - window->SizeFull.x = size_full_modified.x = size_auto_fit.x; - if (!window_size_y_set_by_api) - window->SizeFull.y = size_full_modified.y = size_auto_fit.y; - } - else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) - { - // Auto-fit may only grow window during the first few frames - // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. - if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) - window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; - if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) - window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; - if (!window->Collapsed) - MarkIniSettingsDirty(window); - } - - // Apply minimum/maximum window size constraints and final size - window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull); - window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull; - - // SCROLLBAR STATUS - - // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size). - if (!window->Collapsed) - { - // When reading the current size we need to read it after size constraints have been applied - float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x; - float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y; - window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); - window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); - if (window->ScrollbarX && !window->ScrollbarY) - window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); - window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); - } - - // POSITION - - // Popup latch its initial position, will position itself when it appears next frame - if (window_just_activated_by_user) - { - window->AutoPosLastDirection = ImGuiDir_None; - if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) - window->Pos = g.CurrentPopupStack.back().OpenPopupPos; - } - - // Position child window - if (flags & ImGuiWindowFlags_ChildWindow) - { - window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size; - parent_window->DC.ChildWindows.push_back(window); - if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip) - window->Pos = parent_window->DC.CursorPos; - } - - const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesForResize == 0); - if (window_pos_with_pivot) - SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); // Position given a pivot (e.g. for centering) - else if ((flags & ImGuiWindowFlags_ChildMenu) != 0) - window->Pos = FindBestWindowPosForPopup(window); - else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) - window->Pos = FindBestWindowPosForPopup(window); - else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) - window->Pos = FindBestWindowPosForPopup(window); - - // Clamp position so it stays visible - if (!(flags & ImGuiWindowFlags_ChildWindow)) - { - if (!window_pos_set_by_api && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. - { - ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); - window->Pos = ImMax(window->Pos + window->Size, padding) - window->Size; - window->Pos = ImMin(window->Pos, g.IO.DisplaySize - padding); - } - } - window->Pos = ImFloor(window->Pos); - - // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) - window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; - - // Prepare for item focus requests - window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); - window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1); - window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1; - window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX; - - // Apply scrolling - window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window, true); - window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); - - // Apply window focus (new and reactivated windows are moved to front) - bool want_focus = false; - if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) - if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) - want_focus = true; - - // Handle manual resize: Resize Grips, Borders, Gamepad - int border_held = -1; - ImU32 resize_grip_col[4] = { 0 }; - const int resize_grip_count = g.IO.ConfigResizeWindowsFromEdges ? 2 : 1; // 4 - const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f); - if (!window->Collapsed) - UpdateManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0]); - - // Default item width. Make it proportional to window size if window manually resizes - if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) - window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f); - else - window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f); - - // DRAWING - - // Setup draw list and outer clipping rectangle - window->DrawList->Clear(); - window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); - window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); - ImRect viewport_rect(GetViewportRect()); - if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) - PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true); - else - PushClipRect(viewport_rect.Min, viewport_rect.Max, true); - - // Draw modal window background (darkens what is behind them, all viewports) - const bool dim_bg_for_modal = (flags & ImGuiWindowFlags_Modal) && window == GetFrontMostPopupModal() && window->HiddenFramesForResize <= 0; - const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && (window == g.NavWindowingTargetAnim->RootWindow); - if (dim_bg_for_modal || dim_bg_for_window_list) - { - const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio); - window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, dim_bg_col); - } - - // Draw navigation selection/windowing rectangle background - if (dim_bg_for_window_list && window == g.NavWindowingTargetAnim) - { - ImRect bb = window->Rect(); - bb.Expand(g.FontSize); - if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway - window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding); - } - - // Draw window + handle manual resize - const float window_rounding = window->WindowRounding; - const float window_border_size = window->WindowBorderSize; - const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow; - const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight); - const ImRect title_bar_rect = window->TitleBarRect(); - if (window->Collapsed) - { - // Title bar only - float backup_border_size = style.FrameBorderSize; - g.Style.FrameBorderSize = window->WindowBorderSize; - ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); - RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); - g.Style.FrameBorderSize = backup_border_size; - } - else - { - // Window background - ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags)); - if (g.NextWindowData.BgAlphaCond != 0) - { - bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(g.NextWindowData.BgAlphaVal) << IM_COL32_A_SHIFT); - g.NextWindowData.BgAlphaCond = 0; - } - window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot); - - // Title bar - ImU32 title_bar_col = GetColorU32(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); - if (!(flags & ImGuiWindowFlags_NoTitleBar)) - window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top); - - // Menu bar - if (flags & ImGuiWindowFlags_MenuBar) - { - ImRect menu_bar_rect = window->MenuBarRect(); - menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. - window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top); - if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) - window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); - } - - // Scrollbars - if (window->ScrollbarX) - Scrollbar(ImGuiLayoutType_Horizontal); - if (window->ScrollbarY) - Scrollbar(ImGuiLayoutType_Vertical); - - // Render resize grips (after their input handling so we don't have a frame of latency) - if (!(flags & ImGuiWindowFlags_NoResize)) - { - for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) - { - const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; - const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); - window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size))); - window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size))); - window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); - window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]); - } - } - - // Borders - if (window_border_size > 0.0f) - window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size); - if (border_held != -1) - { - ImRect border = GetResizeBorderRect(window, border_held, grip_draw_size, 0.0f); - window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size)); - } - if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar)) - window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize, -1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); - } - - // Draw navigation selection/windowing rectangle border - if (g.NavWindowingTargetAnim == window) - { - float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding); - ImRect bb = window->Rect(); - bb.Expand(g.FontSize); - if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward - { - bb.Expand(-g.FontSize - 1.0f); - rounding = window->WindowRounding; - } - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f); - } - - // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars. - window->SizeFullAtLastBegin = window->SizeFull; - - // Update various regions. Variables they depends on are set above in this function. - // FIXME: window->ContentsRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it. - window->ContentsRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x; - window->ContentsRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight(); - window->ContentsRegionRect.Max.x = window->Pos.x - window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x)); - window->ContentsRegionRect.Max.y = window->Pos.y - window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y)); - - // Setup drawing context - // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) - window->DC.Indent.x = 0.0f + window->WindowPadding.x - window->Scroll.x; - window->DC.GroupOffset.x = 0.0f; - window->DC.ColumnsOffset.x = 0.0f; - window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.Indent.x + window->DC.ColumnsOffset.x, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y); - window->DC.CursorPos = window->DC.CursorStartPos; - window->DC.CursorPosPrevLine = window->DC.CursorPos; - window->DC.CursorMaxPos = window->DC.CursorStartPos; - window->DC.CurrentLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f); - window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; - window->DC.NavHideHighlightOneFrame = false; - window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f); - window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext; - window->DC.NavLayerActiveMaskNext = 0x00; - window->DC.MenuBarAppending = false; - window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; - window->DC.ChildWindows.resize(0); - window->DC.LayoutType = ImGuiLayoutType_Vertical; - window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; - window->DC.ItemFlags = parent_window ? parent_window->DC.ItemFlags : ImGuiItemFlags_Default_; - window->DC.ItemWidth = window->ItemWidthDefault; - window->DC.TextWrapPos = -1.0f; // disabled - window->DC.ItemFlagsStack.resize(0); - window->DC.ItemWidthStack.resize(0); - window->DC.TextWrapPosStack.resize(0); - window->DC.ColumnsSet = NULL; - window->DC.TreeDepth = 0; - window->DC.TreeDepthMayJumpToParentOnPop = 0x00; - window->DC.StateStorage = &window->StateStorage; - window->DC.GroupStack.resize(0); - window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); - - if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags)) - { - window->DC.ItemFlags = parent_window->DC.ItemFlags; - window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); - } - - if (window->AutoFitFramesX > 0) - window->AutoFitFramesX--; - if (window->AutoFitFramesY > 0) - window->AutoFitFramesY--; - - // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) - if (want_focus) - { - FocusWindow(window); - NavInitWindow(window, false); - } - - // Title bar - if (!(flags & ImGuiWindowFlags_NoTitleBar)) - { - // Close & collapse button are on layer 1 (same as menus) and don't default focus - const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; - window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; - window->DC.NavLayerCurrent++; - window->DC.NavLayerCurrentMask <<= 1; - - // Collapse button - if (!(flags & ImGuiWindowFlags_NoCollapse)) - if (CollapseButton(window->GetID("#COLLAPSE"), window->Pos)) - window->WantCollapseToggle = true; // Defer collapsing to next frame as we are too far in the Begin() function - - // Close button - if (p_open != NULL) - { - const float pad = style.FramePadding.y; - const float rad = g.FontSize * 0.5f; - if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad + 1)) - *p_open = false; - } - - window->DC.NavLayerCurrent--; - window->DC.NavLayerCurrentMask >>= 1; - window->DC.ItemFlags = item_flags_backup; - - // Title text (FIXME: refactor text alignment facilities along with RenderText helpers, this is too much code for what it does.) - ImVec2 text_size = CalcTextSize(name, NULL, true); - ImRect text_r = title_bar_rect; - float pad_left = (flags & ImGuiWindowFlags_NoCollapse) ? style.FramePadding.x : (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x); - float pad_right = (p_open == NULL) ? style.FramePadding.x : (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x); - if (style.WindowTitleAlign.x > 0.0f) - pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x); - text_r.Min.x += pad_left; - text_r.Max.x -= pad_right; - ImRect clip_rect = text_r; - clip_rect.Max.x = window->Pos.x + window->Size.x - (p_open ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x); // Match the size of CloseButton() - RenderTextClipped(text_r.Min, text_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect); - } - - // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() - window->OuterRectClipped = window->Rect(); - window->OuterRectClipped.ClipWith(window->ClipRect); - - // Pressing CTRL+C while holding on a window copy its content to the clipboard - // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. - // Maybe we can support CTRL+C on every element? - /* - if (g.ActiveId == move_id) - if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) - ImGui::LogToClipboard(); - */ - - // Inner rectangle - // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame - // Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which is the correct behavior. - window->InnerMainRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize; - window->InnerMainRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); - window->InnerMainRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize; - window->InnerMainRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize; - //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE); - - // Inner clipping rectangle - // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. - window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerMainRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize))); - window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerMainRect.Min.y); - window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerMainRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize))); - window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerMainRect.Max.y); - - // After Begin() we fill the last item / hovered data based on title bar data. It is a standard behavior (to allow creation of context menus on title bar only, etc.). - window->DC.LastItemId = window->MoveId; - window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0; - window->DC.LastItemRect = title_bar_rect; - } - - PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); - - // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) - if (first_begin_of_the_frame) - window->WriteAccessed = false; - - window->BeginCount++; - g.NextWindowData.Clear(); - - if (flags & ImGuiWindowFlags_ChildWindow) - { - // Child window can be out of sight and have "negative" clip windows. - // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar). - IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0); - - if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) - if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y) - window->HiddenFramesRegular = 1; - - // Completely hide along with parent or if parent is collapsed - if (parent_window && (parent_window->Collapsed || parent_window->Hidden)) - window->HiddenFramesRegular = 1; - } - - // Don't render if style alpha is 0.0 at the time of Begin(). This is arbitrary and inconsistent but has been there for a long while (may remove at some point) - if (style.Alpha <= 0.0f) - window->HiddenFramesRegular = 1; - - // Update the Hidden flag - window->Hidden = (window->HiddenFramesRegular > 0) || (window->HiddenFramesForResize); - - // Return false if we don't intend to display anything to allow user to perform an early out optimization - window->SkipItems = (window->Collapsed || !window->Active || window->Hidden) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesForResize <= 0; - - return !window->SkipItems; -} - -// Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()/SetNextWindowBgAlpha() + Begin() instead. -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_first_use, float bg_alpha_override, ImGuiWindowFlags flags) -{ - // Old API feature: we could pass the initial window size as a parameter. This was misleading because it only had an effect if the window didn't have data in the .ini file. - if (size_first_use.x != 0.0f || size_first_use.y != 0.0f) - ImGui::SetNextWindowSize(size_first_use, ImGuiCond_FirstUseEver); - - // Old API feature: override the window background alpha with a parameter. - if (bg_alpha_override >= 0.0f) - ImGui::SetNextWindowBgAlpha(bg_alpha_override); - - return ImGui::Begin(name, p_open, flags); -} -#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS - -void ImGui::End() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - if (window->DC.ColumnsSet != NULL) - EndColumns(); - PopClipRect(); // Inner window clip rectangle - - // Stop logging - if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging - LogFinish(); - - // Pop from window stack - g.CurrentWindowStack.pop_back(); - if (window->Flags & ImGuiWindowFlags_Popup) - g.CurrentPopupStack.pop_back(); - CheckStacksSize(window, false); - SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); -} - -void ImGui::BringWindowToFront(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* current_front_window = g.Windows.back(); - if (current_front_window == window || current_front_window->RootWindow == window) - return; - for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the front most window - if (g.Windows[i] == window) - { - g.Windows.erase(g.Windows.Data + i); - g.Windows.push_back(window); - break; - } -} - -void ImGui::BringWindowToBack(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - if (g.Windows[0] == window) - return; - for (int i = 0; i < g.Windows.Size; i++) - if (g.Windows[i] == window) - { - memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); - g.Windows[0] = window; - break; - } -} - -// Moving window to front of display and set focus (which happens to be back of our sorted list) -void ImGui::FocusWindow(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - - if (g.NavWindow != window) - { - g.NavWindow = window; - if (window && g.NavDisableMouseHover) - g.NavMousePosDirty = true; - g.NavInitRequest = false; - g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId - g.NavIdIsAlive = false; - g.NavLayer = 0; - //printf("[%05d] FocusWindow(\"%s\")\n", g.FrameCount, window ? window->Name : NULL); - } - - // Passing NULL allow to disable keyboard focus - if (!window) - return; - - // Move the root window to the top of the pile - if (window->RootWindow) - window = window->RootWindow; - - // Steal focus on active widgets - if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it.. - if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window) - ClearActiveID(); - - // Bring to front - if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) - BringWindowToFront(window); -} - -void ImGui::FocusFrontMostActiveWindowIgnoringOne(ImGuiWindow* ignore_window) -{ - ImGuiContext& g = *GImGui; - for (int i = g.Windows.Size - 1; i >= 0; i--) - if (g.Windows[i] != ignore_window && g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow)) - { - ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(g.Windows[i]); - FocusWindow(focus_window); - return; - } -} - -void ImGui::PushItemWidth(float item_width) -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); - window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); -} - -void ImGui::PushMultiItemsWidths(int components, float w_full) -{ - ImGuiWindow* window = GetCurrentWindow(); - const ImGuiStyle& style = GImGui->Style; - if (w_full <= 0.0f) - w_full = CalcItemWidth(); - const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); - const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); - window->DC.ItemWidthStack.push_back(w_item_last); - for (int i = 0; i < components-1; i++) - window->DC.ItemWidthStack.push_back(w_item_one); - window->DC.ItemWidth = window->DC.ItemWidthStack.back(); -} - -void ImGui::PopItemWidth() -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DC.ItemWidthStack.pop_back(); - window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back(); -} - -float ImGui::CalcItemWidth() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - float w = window->DC.ItemWidth; - if (w < 0.0f) - { - // Align to a right-side limit. We include 1 frame padding in the calculation because this is how the width is always used (we add 2 frame padding to it), but we could move that responsibility to the widget as well. - float width_to_right_edge = GetContentRegionAvail().x; - w = ImMax(1.0f, width_to_right_edge + w); - } - w = (float)(int)w; - return w; -} - -void ImGui::SetCurrentFont(ImFont* font) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? - IM_ASSERT(font->Scale > 0.0f); - g.Font = font; - g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale; - g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; - - ImFontAtlas* atlas = g.Font->ContainerAtlas; - g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; - g.DrawListSharedData.Font = g.Font; - g.DrawListSharedData.FontSize = g.FontSize; -} - -void ImGui::PushFont(ImFont* font) -{ - ImGuiContext& g = *GImGui; - if (!font) - font = GetDefaultFont(); - SetCurrentFont(font); - g.FontStack.push_back(font); - g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID); -} - -void ImGui::PopFont() -{ - ImGuiContext& g = *GImGui; - g.CurrentWindow->DrawList->PopTextureID(); - g.FontStack.pop_back(); - SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back()); -} - -void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (enabled) - window->DC.ItemFlags |= option; - else - window->DC.ItemFlags &= ~option; - window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); -} - -void ImGui::PopItemFlag() -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DC.ItemFlagsStack.pop_back(); - window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back(); -} - -void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus) -{ - PushItemFlag(ImGuiItemFlags_AllowKeyboardFocus, allow_keyboard_focus); -} - -void ImGui::PopAllowKeyboardFocus() -{ - PopItemFlag(); -} - -void ImGui::PushButtonRepeat(bool repeat) -{ - PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); -} - -void ImGui::PopButtonRepeat() -{ - PopItemFlag(); -} - -void ImGui::PushTextWrapPos(float wrap_pos_x) -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DC.TextWrapPos = wrap_pos_x; - window->DC.TextWrapPosStack.push_back(wrap_pos_x); -} - -void ImGui::PopTextWrapPos() -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DC.TextWrapPosStack.pop_back(); - window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back(); -} - -// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32 -void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col) -{ - ImGuiContext& g = *GImGui; - ImGuiColorMod backup; - backup.Col = idx; - backup.BackupValue = g.Style.Colors[idx]; - g.ColorModifiers.push_back(backup); - g.Style.Colors[idx] = ColorConvertU32ToFloat4(col); -} - -void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col) -{ - ImGuiContext& g = *GImGui; - ImGuiColorMod backup; - backup.Col = idx; - backup.BackupValue = g.Style.Colors[idx]; - g.ColorModifiers.push_back(backup); - g.Style.Colors[idx] = col; -} - -void ImGui::PopStyleColor(int count) -{ - ImGuiContext& g = *GImGui; - while (count > 0) - { - ImGuiColorMod& backup = g.ColorModifiers.back(); - g.Style.Colors[backup.Col] = backup.BackupValue; - g.ColorModifiers.pop_back(); - count--; - } -} - -struct ImGuiStyleVarInfo -{ - ImGuiDataType Type; - ImU32 Count; - ImU32 Offset; - void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); } -}; - -static const ImGuiStyleVarInfo GStyleVarInfo[] = -{ - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize - { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding - { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign -}; - -static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx) -{ - IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT); - IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT); - return &GStyleVarInfo[idx]; -} - -void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) -{ - const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) - { - ImGuiContext& g = *GImGui; - float* pvar = (float*)var_info->GetVarPtr(&g.Style); - g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); - *pvar = val; - return; - } - IM_ASSERT(0); // Called function with wrong-type? Variable is not a float. -} - -void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) -{ - const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2) - { - ImGuiContext& g = *GImGui; - ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style); - g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar)); - *pvar = val; - return; - } - IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2. -} - -void ImGui::PopStyleVar(int count) -{ - ImGuiContext& g = *GImGui; - while (count > 0) - { - // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it. - ImGuiStyleMod& backup = g.StyleModifiers.back(); - const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx); - void* data = info->GetVarPtr(&g.Style); - if (info->Type == ImGuiDataType_Float && info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; } - else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; } - g.StyleModifiers.pop_back(); - count--; - } -} - -const char* ImGui::GetStyleColorName(ImGuiCol idx) -{ - // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1"; - switch (idx) - { - case ImGuiCol_Text: return "Text"; - case ImGuiCol_TextDisabled: return "TextDisabled"; - case ImGuiCol_WindowBg: return "WindowBg"; - case ImGuiCol_ChildBg: return "ChildBg"; - case ImGuiCol_PopupBg: return "PopupBg"; - case ImGuiCol_Border: return "Border"; - case ImGuiCol_BorderShadow: return "BorderShadow"; - case ImGuiCol_FrameBg: return "FrameBg"; - case ImGuiCol_FrameBgHovered: return "FrameBgHovered"; - case ImGuiCol_FrameBgActive: return "FrameBgActive"; - case ImGuiCol_TitleBg: return "TitleBg"; - case ImGuiCol_TitleBgActive: return "TitleBgActive"; - case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed"; - case ImGuiCol_MenuBarBg: return "MenuBarBg"; - case ImGuiCol_ScrollbarBg: return "ScrollbarBg"; - case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab"; - case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; - case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; - case ImGuiCol_CheckMark: return "CheckMark"; - case ImGuiCol_SliderGrab: return "SliderGrab"; - case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; - case ImGuiCol_Button: return "Button"; - case ImGuiCol_ButtonHovered: return "ButtonHovered"; - case ImGuiCol_ButtonActive: return "ButtonActive"; - case ImGuiCol_Header: return "Header"; - case ImGuiCol_HeaderHovered: return "HeaderHovered"; - case ImGuiCol_HeaderActive: return "HeaderActive"; - case ImGuiCol_Separator: return "Separator"; - case ImGuiCol_SeparatorHovered: return "SeparatorHovered"; - case ImGuiCol_SeparatorActive: return "SeparatorActive"; - case ImGuiCol_ResizeGrip: return "ResizeGrip"; - case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; - case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; - case ImGuiCol_PlotLines: return "PlotLines"; - case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; - case ImGuiCol_PlotHistogram: return "PlotHistogram"; - case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered"; - case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; - case ImGuiCol_DragDropTarget: return "DragDropTarget"; - case ImGuiCol_NavHighlight: return "NavHighlight"; - case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight"; - case ImGuiCol_NavWindowingDimBg: return "NavWindowingDimBg"; - case ImGuiCol_ModalWindowDimBg: return "ModalWindowDimBg"; - } - IM_ASSERT(0); - return "Unknown"; -} - -bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent) -{ - if (window->RootWindow == potential_parent) - return true; - while (window != NULL) - { - if (window == potential_parent) - return true; - window = window->ParentWindow; - } - return false; -} - -bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) -{ - IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function - ImGuiContext& g = *GImGui; - - if (flags & ImGuiHoveredFlags_AnyWindow) - { - if (g.HoveredWindow == NULL) - return false; - } - else - { - switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) - { - case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows: - if (g.HoveredRootWindow != g.CurrentWindow->RootWindow) - return false; - break; - case ImGuiHoveredFlags_RootWindow: - if (g.HoveredWindow != g.CurrentWindow->RootWindow) - return false; - break; - case ImGuiHoveredFlags_ChildWindows: - if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow)) - return false; - break; - default: - if (g.HoveredWindow != g.CurrentWindow) - return false; - break; - } - } - - if (!IsWindowContentHoverable(g.HoveredRootWindow, flags)) - return false; - if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId) - return false; - return true; -} - -bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End() - - if (flags & ImGuiFocusedFlags_AnyWindow) - return g.NavWindow != NULL; - - switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows)) - { - case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows: - return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow; - case ImGuiFocusedFlags_RootWindow: - return g.NavWindow == g.CurrentWindow->RootWindow; - case ImGuiFocusedFlags_ChildWindows: - return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow); - default: - return g.NavWindow == g.CurrentWindow; - } -} - -// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) -bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) -{ - return window->Active && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); -} - -float ImGui::GetWindowWidth() -{ - ImGuiWindow* window = GImGui->CurrentWindow; - return window->Size.x; -} - -float ImGui::GetWindowHeight() -{ - ImGuiWindow* window = GImGui->CurrentWindow; - return window->Size.y; -} - -ImVec2 ImGui::GetWindowPos() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - return window->Pos; -} - -void ImGui::SetWindowScrollX(ImGuiWindow* window, float new_scroll_x) -{ - window->DC.CursorMaxPos.x += window->Scroll.x; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. - window->Scroll.x = new_scroll_x; - window->DC.CursorMaxPos.x -= window->Scroll.x; -} - -void ImGui::SetWindowScrollY(ImGuiWindow* window, float new_scroll_y) -{ - window->DC.CursorMaxPos.y += window->Scroll.y; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it. - window->Scroll.y = new_scroll_y; - window->DC.CursorMaxPos.y -= window->Scroll.y; -} - -static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond) -{ - // Test condition (NB: bit 0 is always true) and clear flags for next time - if (cond && (window->SetWindowPosAllowFlags & cond) == 0) - return; - - IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); - window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX); - - // Set - const ImVec2 old_pos = window->Pos; - window->Pos = ImFloor(pos); - window->DC.CursorPos += (window->Pos - old_pos); // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor - window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected. -} - -void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - SetWindowPos(window, pos, cond); -} - -void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond) -{ - if (ImGuiWindow* window = FindWindowByName(name)) - SetWindowPos(window, pos, cond); -} - -ImVec2 ImGui::GetWindowSize() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->Size; -} - -static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond) -{ - // Test condition (NB: bit 0 is always true) and clear flags for next time - if (cond && (window->SetWindowSizeAllowFlags & cond) == 0) - return; - - IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); - - // Set - if (size.x > 0.0f) - { - window->AutoFitFramesX = 0; - window->SizeFull.x = size.x; - } - else - { - window->AutoFitFramesX = 2; - window->AutoFitOnlyGrows = false; - } - if (size.y > 0.0f) - { - window->AutoFitFramesY = 0; - window->SizeFull.y = size.y; - } - else - { - window->AutoFitFramesY = 2; - window->AutoFitOnlyGrows = false; - } -} - -void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond) -{ - SetWindowSize(GImGui->CurrentWindow, size, cond); -} - -void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond) -{ - if (ImGuiWindow* window = FindWindowByName(name)) - SetWindowSize(window, size, cond); -} - -static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond) -{ - // Test condition (NB: bit 0 is always true) and clear flags for next time - if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0) - return; - window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); - - // Set - window->Collapsed = collapsed; -} - -void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond) -{ - SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond); -} - -bool ImGui::IsWindowCollapsed() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->Collapsed; -} - -bool ImGui::IsWindowAppearing() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->Appearing; -} - -void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) -{ - if (ImGuiWindow* window = FindWindowByName(name)) - SetWindowCollapsed(window, collapsed, cond); -} - -void ImGui::SetWindowFocus() -{ - FocusWindow(GImGui->CurrentWindow); -} - -void ImGui::SetWindowFocus(const char* name) -{ - if (name) - { - if (ImGuiWindow* window = FindWindowByName(name)) - FocusWindow(window); - } - else - { - FocusWindow(NULL); - } -} - -void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - g.NextWindowData.PosVal = pos; - g.NextWindowData.PosPivotVal = pivot; - g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always; -} - -void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - g.NextWindowData.SizeVal = size; - g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always; -} - -void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data) -{ - ImGuiContext& g = *GImGui; - g.NextWindowData.SizeConstraintCond = ImGuiCond_Always; - g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max); - g.NextWindowData.SizeCallback = custom_callback; - g.NextWindowData.SizeCallbackUserData = custom_callback_user_data; -} - -void ImGui::SetNextWindowContentSize(const ImVec2& size) -{ - ImGuiContext& g = *GImGui; - g.NextWindowData.ContentSizeVal = size; // In Begin() we will add the size of window decorations (title bar, menu etc.) to that to form a SizeContents value. - g.NextWindowData.ContentSizeCond = ImGuiCond_Always; -} - -void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - g.NextWindowData.CollapsedVal = collapsed; - g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always; -} - -void ImGui::SetNextWindowFocus() -{ - ImGuiContext& g = *GImGui; - g.NextWindowData.FocusCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) -} - -void ImGui::SetNextWindowBgAlpha(float alpha) -{ - ImGuiContext& g = *GImGui; - g.NextWindowData.BgAlphaVal = alpha; - g.NextWindowData.BgAlphaCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) -} - -// In window space (not screen space!) -ImVec2 ImGui::GetContentRegionMax() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImVec2 mx = window->ContentsRegionRect.Max - window->Pos; - if (window->DC.ColumnsSet) - mx.x = GetColumnOffset(window->DC.ColumnsSet->Current + 1) - window->WindowPadding.x; - return mx; -} - -ImVec2 ImGui::GetContentRegionAvail() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return GetContentRegionMax() - (window->DC.CursorPos - window->Pos); -} - -float ImGui::GetContentRegionAvailWidth() -{ - return GetContentRegionAvail().x; -} - -// In window space (not screen space!) -ImVec2 ImGui::GetWindowContentRegionMin() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->ContentsRegionRect.Min - window->Pos; -} - -ImVec2 ImGui::GetWindowContentRegionMax() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->ContentsRegionRect.Max - window->Pos; -} - -float ImGui::GetWindowContentRegionWidth() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->ContentsRegionRect.GetWidth(); -} - -float ImGui::GetTextLineHeight() -{ - ImGuiContext& g = *GImGui; - return g.FontSize; -} - -float ImGui::GetTextLineHeightWithSpacing() -{ - ImGuiContext& g = *GImGui; - return g.FontSize + g.Style.ItemSpacing.y; -} - -float ImGui::GetFrameHeight() -{ - ImGuiContext& g = *GImGui; - return g.FontSize + g.Style.FramePadding.y * 2.0f; -} - -float ImGui::GetFrameHeightWithSpacing() -{ - ImGuiContext& g = *GImGui; - return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y; -} - -ImDrawList* ImGui::GetWindowDrawList() -{ - ImGuiWindow* window = GetCurrentWindow(); - return window->DrawList; -} - -ImFont* ImGui::GetFont() -{ - return GImGui->Font; -} - -float ImGui::GetFontSize() -{ - return GImGui->FontSize; -} - -ImVec2 ImGui::GetFontTexUvWhitePixel() -{ - return GImGui->DrawListSharedData.TexUvWhitePixel; -} - -void ImGui::SetWindowFontScale(float scale) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - window->FontWindowScale = scale; - g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); -} - -// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient. -// Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'. -ImVec2 ImGui::GetCursorPos() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.CursorPos - window->Pos + window->Scroll; -} - -float ImGui::GetCursorPosX() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x; -} - -float ImGui::GetCursorPosY() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y; -} - -void ImGui::SetCursorPos(const ImVec2& local_pos) -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DC.CursorPos = window->Pos - window->Scroll + local_pos; - window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); -} - -void ImGui::SetCursorPosX(float x) -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x; - window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x); -} - -void ImGui::SetCursorPosY(float y) -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y; - window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y); -} - -ImVec2 ImGui::GetCursorStartPos() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.CursorStartPos - window->Pos; -} - -ImVec2 ImGui::GetCursorScreenPos() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.CursorPos; -} - -void ImGui::SetCursorScreenPos(const ImVec2& screen_pos) -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DC.CursorPos = screen_pos; - window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); -} - -float ImGui::GetScrollX() -{ - return GImGui->CurrentWindow->Scroll.x; -} - -float ImGui::GetScrollY() -{ - return GImGui->CurrentWindow->Scroll.y; -} - -float ImGui::GetScrollMaxX() -{ - return GetScrollMaxX(GImGui->CurrentWindow); -} - -float ImGui::GetScrollMaxY() -{ - return GetScrollMaxY(GImGui->CurrentWindow); -} - -void ImGui::SetScrollX(float scroll_x) -{ - ImGuiWindow* window = GetCurrentWindow(); - window->ScrollTarget.x = scroll_x; - window->ScrollTargetCenterRatio.x = 0.0f; -} - -void ImGui::SetScrollY(float scroll_y) -{ - ImGuiWindow* window = GetCurrentWindow(); - window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY - window->ScrollTargetCenterRatio.y = 0.0f; -} - -void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio) -{ - // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size - ImGuiWindow* window = GetCurrentWindow(); - IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f); - window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y); - window->ScrollTargetCenterRatio.y = center_y_ratio; -} - -// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. -void ImGui::SetScrollHere(float center_y_ratio) -{ - ImGuiWindow* window = GetCurrentWindow(); - float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space - target_y += (window->DC.PrevLineSize.y * center_y_ratio) + (GImGui->Style.ItemSpacing.y * (center_y_ratio - 0.5f) * 2.0f); // Precisely aim above, in the middle or below the last line. - SetScrollFromPosY(target_y, center_y_ratio); -} - -void ImGui::ActivateItem(ImGuiID id) -{ - ImGuiContext& g = *GImGui; - g.NavNextActivateId = id; -} - -void ImGui::SetKeyboardFocusHere(int offset) -{ - IM_ASSERT(offset >= -1); // -1 is allowed but not below - ImGuiWindow* window = GetCurrentWindow(); - window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset; - window->FocusIdxTabRequestNext = INT_MAX; -} - -void ImGui::SetItemDefaultFocus() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (!window->Appearing) - return; - if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent) - { - g.NavInitRequest = false; - g.NavInitResultId = g.NavWindow->DC.LastItemId; - g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos); - NavUpdateAnyRequestFlag(); - if (!IsItemVisible()) - SetScrollHere(); - } -} - -void ImGui::SetStateStorage(ImGuiStorage* tree) -{ - ImGuiWindow* window = GetCurrentWindow(); - window->DC.StateStorage = tree ? tree : &window->StateStorage; -} - -ImGuiStorage* ImGui::GetStateStorage() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.StateStorage; -} - -void ImGui::PushID(const char* str_id) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - window->IDStack.push_back(window->GetIDNoKeepAlive(str_id)); -} - -void ImGui::PushID(const char* str_id_begin, const char* str_id_end) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - window->IDStack.push_back(window->GetIDNoKeepAlive(str_id_begin, str_id_end)); -} - -void ImGui::PushID(const void* ptr_id) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - window->IDStack.push_back(window->GetIDNoKeepAlive(ptr_id)); -} - -void ImGui::PushID(int int_id) -{ - const void* ptr_id = (void*)(intptr_t)int_id; - ImGuiWindow* window = GetCurrentWindowRead(); - window->IDStack.push_back(window->GetIDNoKeepAlive(ptr_id)); -} - -void ImGui::PopID() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - window->IDStack.pop_back(); -} - -ImGuiID ImGui::GetID(const char* str_id) -{ - return GImGui->CurrentWindow->GetID(str_id); -} - -ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end) -{ - return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end); -} - -ImGuiID ImGui::GetID(const void* ptr_id) -{ - return GImGui->CurrentWindow->GetID(ptr_id); -} - -bool ImGui::IsRectVisible(const ImVec2& size) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); -} - -bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); -} - -// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) -void ImGui::BeginGroup() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - - window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1); - ImGuiGroupData& group_data = window->DC.GroupStack.back(); - group_data.BackupCursorPos = window->DC.CursorPos; - group_data.BackupCursorMaxPos = window->DC.CursorMaxPos; - group_data.BackupIndent = window->DC.Indent; - group_data.BackupGroupOffset = window->DC.GroupOffset; - group_data.BackupCurrentLineSize = window->DC.CurrentLineSize; - group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset; - group_data.BackupLogLinePosY = window->DC.LogLinePosY; - group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; - group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive; - group_data.AdvanceCursor = true; - - window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x; - window->DC.Indent = window->DC.GroupOffset; - window->DC.CursorMaxPos = window->DC.CursorPos; - window->DC.CurrentLineSize = ImVec2(0.0f, 0.0f); - window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return -} - -void ImGui::EndGroup() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls - - ImGuiGroupData& group_data = window->DC.GroupStack.back(); - - ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos); - group_bb.Max = ImMax(group_bb.Min, group_bb.Max); - - window->DC.CursorPos = group_data.BackupCursorPos; - window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos); - window->DC.Indent = group_data.BackupIndent; - window->DC.GroupOffset = group_data.BackupGroupOffset; - window->DC.CurrentLineSize = group_data.BackupCurrentLineSize; - window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset; - window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return - - if (group_data.AdvanceCursor) - { - window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now. - ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset); - ItemAdd(group_bb, 0); - } - - // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group. - // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets. - // (and if you grep for LastItemId you'll notice it is only used in that context. - if ((group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId) // && g.ActiveIdWindow->RootWindow == window->RootWindow) - window->DC.LastItemId = g.ActiveId; - else if (!group_data.BackupActiveIdPreviousFrameIsAlive && g.ActiveIdPreviousFrameIsAlive) // && g.ActiveIdPreviousFrameWindow->RootWindow == window->RootWindow) - window->DC.LastItemId = g.ActiveIdPreviousFrame; - window->DC.LastItemRect = group_bb; - - window->DC.GroupStack.pop_back(); - - //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug] -} - -// Gets back to previous line and continue with horizontal layout -// pos_x == 0 : follow right after previous item -// pos_x != 0 : align to specified x position (relative to window/group left) -// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0 -// spacing_w >= 0 : enforce spacing amount -void ImGui::SameLine(float pos_x, float spacing_w) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext& g = *GImGui; - if (pos_x != 0.0f) - { - if (spacing_w < 0.0f) spacing_w = 0.0f; - window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x; - window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; - } - else - { - if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x; - window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w; - window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y; - } - window->DC.CurrentLineSize = window->DC.PrevLineSize; - window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; -} - -void ImGui::Indent(float indent_w) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; - window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x; -} - -void ImGui::Unindent(float indent_w) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing; - window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x; -} - -//----------------------------------------------------------------------------- -// [SECTION] TOOLTIPS -//----------------------------------------------------------------------------- - -void ImGui::BeginTooltip() -{ - ImGuiContext& g = *GImGui; - if (g.DragDropWithinSourceOrTarget) - { - // The default tooltip position is a little offset to give space to see the context menu (it's also clamped within the current viewport/monitor) - // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor. - // Whatever we do we want to call SetNextWindowPos() to enforce a tooltip position and disable clipping the tooltip without our display area, like regular tooltip do. - //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding; - ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale); - SetNextWindowPos(tooltip_pos); - SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f); - //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :( - BeginTooltipEx(0, true); - } - else - { - BeginTooltipEx(0, false); - } -} - -// Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first. -void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip) -{ - ImGuiContext& g = *GImGui; - char window_name[16]; - ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount); - if (override_previous_tooltip) - if (ImGuiWindow* window = FindWindowByName(window_name)) - if (window->Active) - { - // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. - window->Hidden = true; - window->HiddenFramesRegular = 1; - ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount); - } - ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNav; - Begin(window_name, NULL, flags | extra_flags); -} - -void ImGui::EndTooltip() -{ - IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls - End(); -} - -void ImGui::SetTooltipV(const char* fmt, va_list args) -{ - ImGuiContext& g = *GImGui; - if (g.DragDropWithinSourceOrTarget) - BeginTooltip(); - else - BeginTooltipEx(0, true); - TextV(fmt, args); - EndTooltip(); -} - -void ImGui::SetTooltip(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - SetTooltipV(fmt, args); - va_end(args); -} - -//----------------------------------------------------------------------------- -// [SECTION] POPUPS -//----------------------------------------------------------------------------- - -bool ImGui::IsPopupOpen(ImGuiID id) -{ - ImGuiContext& g = *GImGui; - return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id; -} - -bool ImGui::IsPopupOpen(const char* str_id) -{ - ImGuiContext& g = *GImGui; - return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id); -} - -ImGuiWindow* ImGui::GetFrontMostPopupModal() -{ - ImGuiContext& g = *GImGui; - for (int n = g.OpenPopupStack.Size-1; n >= 0; n--) - if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window) - if (popup->Flags & ImGuiWindowFlags_Modal) - return popup; - return NULL; -} - -void ImGui::OpenPopup(const char* str_id) -{ - ImGuiContext& g = *GImGui; - OpenPopupEx(g.CurrentWindow->GetID(str_id)); -} - -// Mark popup as open (toggle toward open state). -// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. -// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). -// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL) -void ImGui::OpenPopupEx(ImGuiID id) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* parent_window = g.CurrentWindow; - int current_stack_size = g.CurrentPopupStack.Size; - ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. - popup_ref.PopupId = id; - popup_ref.Window = NULL; - popup_ref.ParentWindow = parent_window; - popup_ref.OpenFrameCount = g.FrameCount; - popup_ref.OpenParentId = parent_window->IDStack.back(); - popup_ref.OpenMousePos = g.IO.MousePos; - popup_ref.OpenPopupPos = NavCalcPreferredRefPos(); - - //printf("[%05d] OpenPopupEx(0x%08X)\n", g.FrameCount, id); - if (g.OpenPopupStack.Size < current_stack_size + 1) - { - g.OpenPopupStack.push_back(popup_ref); - } - else - { - // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the ui - // would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing - // situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will be more obvious to understand. - if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) - { - g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount; - } - else - { - // Close child popups if any, then flag popup for open/reopen - g.OpenPopupStack.resize(current_stack_size + 1); - g.OpenPopupStack[current_stack_size] = popup_ref; - } - - // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow(). - // This is equivalent to what ClosePopupToLevel() does. - //if (g.OpenPopupStack[current_stack_size].PopupId == id) - // FocusWindow(parent_window); - } -} - -bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button) -{ - ImGuiWindow* window = GImGui->CurrentWindow; - if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) - { - ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! - IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) - OpenPopupEx(id); - return true; - } - return false; -} - -void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window) -{ - ImGuiContext& g = *GImGui; - if (g.OpenPopupStack.empty()) - return; - - // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. - // Don't close our own child popup windows. - int n = 0; - if (ref_window) - { - for (n = 0; n < g.OpenPopupStack.Size; n++) - { - ImGuiPopupRef& popup = g.OpenPopupStack[n]; - if (!popup.Window) - continue; - IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); - if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) - continue; - - // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow) - bool has_focus = false; - for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++) - has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow); - if (!has_focus) - break; - } - } - if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below - ClosePopupToLevel(n); -} - -void ImGui::ClosePopupToLevel(int remaining) -{ - IM_ASSERT(remaining >= 0); - ImGuiContext& g = *GImGui; - ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining-1].Window : g.OpenPopupStack[0].ParentWindow; - if (g.NavLayer == 0) - focus_window = NavRestoreLastChildNavWindow(focus_window); - FocusWindow(focus_window); - focus_window->DC.NavHideHighlightOneFrame = true; - g.OpenPopupStack.resize(remaining); -} - -void ImGui::ClosePopup(ImGuiID id) -{ - if (!IsPopupOpen(id)) - return; - ImGuiContext& g = *GImGui; - ClosePopupToLevel(g.OpenPopupStack.Size - 1); -} - -// Close the popup we have begin-ed into. -void ImGui::CloseCurrentPopup() -{ - ImGuiContext& g = *GImGui; - int popup_idx = g.CurrentPopupStack.Size - 1; - if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId) - return; - while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu)) - popup_idx--; - ClosePopupToLevel(popup_idx); -} - -bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags) -{ - ImGuiContext& g = *GImGui; - if (!IsPopupOpen(id)) - { - g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values - return false; - } - - char name[20]; - if (extra_flags & ImGuiWindowFlags_ChildMenu) - ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth - else - ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame - - bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup); - if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) - EndPopup(); - - return is_open; -} - -bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) -{ - ImGuiContext& g = *GImGui; - if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance - { - g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values - return false; - } - return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); -} - -bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - const ImGuiID id = window->GetID(name); - if (!IsPopupOpen(id)) - { - g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values - return false; - } - - // Center modal windows by default - // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window. - if (g.NextWindowData.PosCond == 0) - SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - - bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings); - if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) - { - EndPopup(); - if (is_open) - ClosePopup(id); - return false; - } - return is_open; -} - -void ImGui::EndPopup() -{ - ImGuiContext& g = *GImGui; (void)g; - IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls - IM_ASSERT(g.CurrentPopupStack.Size > 0); - - // Make all menus and popups wrap around for now, may need to expose that policy. - NavMoveRequestTryWrapping(g.CurrentWindow, ImGuiNavMoveFlags_LoopY); - - End(); -} - -// This is a helper to handle the simplest case of associating one named popup to one given widget. -// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). -// You can pass a NULL str_id to use the identifier of the last item. -bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button) -{ - ImGuiWindow* window = GImGui->CurrentWindow; - ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! - IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) - if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) - OpenPopupEx(id); - return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); -} - -bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items) -{ - if (!str_id) - str_id = "window_context"; - ImGuiID id = GImGui->CurrentWindow->GetID(str_id); - if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) - if (also_over_items || !IsAnyItemHovered()) - OpenPopupEx(id); - return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); -} - -bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button) -{ - if (!str_id) - str_id = "void_context"; - ImGuiID id = GImGui->CurrentWindow->GetID(str_id); - if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow)) - OpenPopupEx(id); - return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); -} - -ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow*) -{ - ImVec2 padding = GImGui->Style.DisplaySafeAreaPadding; - ImRect r_screen = GetViewportRect(); - r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f)); - return r_screen; -} - -// r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.) -// r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it. -ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy) -{ - ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size); - //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255)); - //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255)); - - // Combo Box policy (we want a connecting edge) - if (policy == ImGuiPopupPositionPolicy_ComboBox) - { - const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up }; - for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) - { - const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; - if (n != -1 && dir == *last_dir) // Already tried this direction? - continue; - ImVec2 pos; - if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default) - if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right - if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left - if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left - if (!r_outer.Contains(ImRect(pos, pos + size))) - continue; - *last_dir = dir; - return pos; - } - } - - // Default popup policy - const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; - for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) - { - const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; - if (n != -1 && dir == *last_dir) // Already tried this direction? - continue; - float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x); - float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y); - if (avail_w < size.x || avail_h < size.y) - continue; - ImVec2 pos; - pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x; - pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y; - *last_dir = dir; - return pos; - } - - // Fallback, try to keep within display - *last_dir = ImGuiDir_None; - ImVec2 pos = ref_pos; - pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); - pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); - return pos; -} - -ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - - ImRect r_outer = GetWindowAllowedExtentRect(window); - if (window->Flags & ImGuiWindowFlags_ChildMenu) - { - // Child menus typically request _any_ position within the parent menu item, and then our FindBestWindowPosForPopup() function will move the new menu outside the parent bounds. - // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. - IM_ASSERT(g.CurrentWindow == window); - ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2]; - float horizontal_overlap = g.Style.ItemSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x). - ImRect r_avoid; - if (parent_window->DC.MenuBarAppending) - r_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight()); - else - r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX); - return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid); - } - if (window->Flags & ImGuiWindowFlags_Popup) - { - ImRect r_avoid = ImRect(window->Pos.x - 1, window->Pos.y - 1, window->Pos.x + 1, window->Pos.y + 1); - return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid); - } - if (window->Flags & ImGuiWindowFlags_Tooltip) - { - // Position tooltip (always follows mouse) - float sc = g.Style.MouseCursorScale; - ImVec2 ref_pos = NavCalcPreferredRefPos(); - ImRect r_avoid; - if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos)) - r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); - else - r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc, ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. - ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid); - if (window->AutoPosLastDirection == ImGuiDir_None) - pos = ref_pos + ImVec2(2, 2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. - return pos; - } - IM_ASSERT(0); - return window->Pos; -} - -//----------------------------------------------------------------------------- -// [SECTION] KEYBOARD/GAMEPAD NAVIGATION -//----------------------------------------------------------------------------- - -ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy) -{ - if (ImFabs(dx) > ImFabs(dy)) - return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left; - return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up; -} - -static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1) -{ - if (a1 < b0) - return a1 - b0; - if (b1 < a0) - return a0 - b1; - return 0.0f; -} - -static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect) -{ - if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) - { - r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y); - r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y); - } - else - { - r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x); - r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x); - } -} - -// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057 -static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (g.NavLayer != window->DC.NavLayerCurrent) - return false; - - const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) - g.NavScoringCount++; - - // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring - if (window->ParentWindow == g.NavWindow) - { - IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened); - if (!window->ClipRect.Contains(cand)) - return false; - cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window - } - - // We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items) - // For example, this ensure that items in one column are not reached when moving vertically from items in another column. - NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect); - - // Compute distance between boxes - // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. - float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); - float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items - if (dby != 0.0f && dbx != 0.0f) - dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); - float dist_box = ImFabs(dbx) + ImFabs(dby); - - // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter) - float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x); - float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y); - float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee) - - // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance - ImGuiDir quadrant; - float dax = 0.0f, day = 0.0f, dist_axial = 0.0f; - if (dbx != 0.0f || dby != 0.0f) - { - // For non-overlapping boxes, use distance between boxes - dax = dbx; - day = dby; - dist_axial = dist_box; - quadrant = ImGetDirQuadrantFromDelta(dbx, dby); - } - else if (dcx != 0.0f || dcy != 0.0f) - { - // For overlapping boxes with different centers, use distance between centers - dax = dcx; - day = dcy; - dist_axial = dist_center; - quadrant = ImGetDirQuadrantFromDelta(dcx, dcy); - } - else - { - // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) - quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; - } - -#if IMGUI_DEBUG_NAV_SCORING - char buf[128]; - if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max)) - { - ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]); - ImDrawList* draw_list = ImGui::GetOverlayDrawList(); - draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100)); - draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200)); - draw_list->AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150)); - draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf); - } - else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate. - { - if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; } - if (quadrant == g.NavMoveDir) - { - ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); - ImDrawList* draw_list = ImGui::GetOverlayDrawList(); - draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200)); - draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf); - } - } - #endif - - // Is it in the quadrant we're interesting in moving to? - bool new_best = false; - if (quadrant == g.NavMoveDir) - { - // Does it beat the current best candidate? - if (dist_box < result->DistBox) - { - result->DistBox = dist_box; - result->DistCenter = dist_center; - return true; - } - if (dist_box == result->DistBox) - { - // Try using distance between center points to break ties - if (dist_center < result->DistCenter) - { - result->DistCenter = dist_center; - new_best = true; - } - else if (dist_center == result->DistCenter) - { - // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items - // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index), - // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis. - if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance - new_best = true; - } - } - } - - // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches - // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness) - // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too. - // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward. - // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option? - if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match - if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) - if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f)) - { - result->DistAxial = dist_axial; - new_best = true; - } - - return new_best; -} - -// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) -static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id) -{ - ImGuiContext& g = *GImGui; - //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. - // return; - - const ImGuiItemFlags item_flags = window->DC.ItemFlags; - const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); - - // Process Init Request - if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) - { - // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback - if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0) - { - g.NavInitResultId = id; - g.NavInitResultRectRel = nav_bb_rel; - } - if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus)) - { - g.NavInitRequest = false; // Found a match, clear request - NavUpdateAnyRequestFlag(); - } - } - - // Process Move Request (scoring for navigation) - // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy) - if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_NoNav)) - { - ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; -#if IMGUI_DEBUG_NAV_SCORING - // [DEBUG] Score all items in NavWindow at all times - if (!g.NavMoveRequest) - g.NavMoveDir = g.NavMoveDirLast; - bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest; -#else - bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb); -#endif - if (new_best) - { - result->ID = id; - result->Window = window; - result->RectRel = nav_bb_rel; - } - - const float VISIBLE_RATIO = 0.70f; - if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) - if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) - if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb)) - { - result = &g.NavMoveResultLocalVisibleSet; - result->ID = id; - result->Window = window; - result->RectRel = nav_bb_rel; - } - } - - // Update window-relative bounding box of navigated item - if (g.NavId == id) - { - g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. - g.NavLayer = window->DC.NavLayerCurrent; - g.NavIdIsAlive = true; - g.NavIdTabCounter = window->FocusIdxTabCounter; - window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) - } -} - -bool ImGui::NavMoveRequestButNoResultYet() -{ - ImGuiContext& g = *GImGui; - return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; -} - -void ImGui::NavMoveRequestCancel() -{ - ImGuiContext& g = *GImGui; - g.NavMoveRequest = false; - NavUpdateAnyRequestFlag(); -} - -void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None); - ImGui::NavMoveRequestCancel(); - g.NavMoveDir = move_dir; - g.NavMoveClipDir = clip_dir; - g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; - g.NavMoveRequestFlags = move_flags; - g.NavWindow->NavRectRel[g.NavLayer] = bb_rel; -} - -void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags) -{ - ImGuiContext& g = *GImGui; - if (g.NavWindow != window || !NavMoveRequestButNoResultYet() || g.NavMoveRequestForward != ImGuiNavForward_None || g.NavLayer != 0) - return; - IM_ASSERT(move_flags != 0); // No points calling this with no wrapping - ImRect bb_rel = window->NavRectRel[0]; - - ImGuiDir clip_dir = g.NavMoveDir; - if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) - { - bb_rel.Min.x = bb_rel.Max.x = ImMax(window->SizeFull.x, window->SizeContents.x) - window->Scroll.x; - if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(-bb_rel.GetHeight()); clip_dir = ImGuiDir_Up; } - NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); - } - if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) - { - bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x; - if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(+bb_rel.GetHeight()); clip_dir = ImGuiDir_Down; } - NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); - } - if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) - { - bb_rel.Min.y = bb_rel.Max.y = ImMax(window->SizeFull.y, window->SizeContents.y) - window->Scroll.y; - if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(-bb_rel.GetWidth()); clip_dir = ImGuiDir_Left; } - NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); - } - if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) - { - bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y; - if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(+bb_rel.GetWidth()); clip_dir = ImGuiDir_Right; } - NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); - } -} - -static void ImGui::NavSaveLastChildNavWindow(ImGuiWindow* nav_window) -{ - ImGuiWindow* parent_window = nav_window; - while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) - parent_window = parent_window->ParentWindow; - if (parent_window && parent_window != nav_window) - parent_window->NavLastChildNavWindow = nav_window; -} - -// Call when we are expected to land on Layer 0 after FocusWindow() -static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window) -{ - return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window; -} - -static void NavRestoreLayer(int layer) -{ - ImGuiContext& g = *GImGui; - g.NavLayer = layer; - if (layer == 0) - g.NavWindow = ImGui::NavRestoreLastChildNavWindow(g.NavWindow); - if (layer == 0 && g.NavWindow->NavLastIds[0] != 0) - ImGui::SetNavIDWithRectRel(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]); - else - ImGui::NavInitWindow(g.NavWindow, true); -} - -static inline void ImGui::NavUpdateAnyRequestFlag() -{ - ImGuiContext& g = *GImGui; - g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL); - if (g.NavAnyRequest) - IM_ASSERT(g.NavWindow != NULL); -} - -// This needs to be called before we submit any widget (aka in or before Begin) -void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(window == g.NavWindow); - bool init_for_nav = false; - if (!(window->Flags & ImGuiWindowFlags_NoNavInputs)) - if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) - init_for_nav = true; - if (init_for_nav) - { - SetNavID(0, g.NavLayer); - g.NavInitRequest = true; - g.NavInitRequestFromMove = false; - g.NavInitResultId = 0; - g.NavInitResultRectRel = ImRect(); - NavUpdateAnyRequestFlag(); - } - else - { - g.NavId = window->NavLastIds[0]; - } -} - -static ImVec2 ImGui::NavCalcPreferredRefPos() -{ - ImGuiContext& g = *GImGui; - if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow) - return ImFloor(g.IO.MousePos); - - // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item - const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer]; - ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x*4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight())); - ImRect visible_rect = GetViewportRect(); - return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max)); // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta. -} - -float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode) -{ - ImGuiContext& g = *GImGui; - if (mode == ImGuiInputReadMode_Down) - return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user) - - const float t = g.IO.NavInputsDownDuration[n]; - if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input. - return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f); - if (t < 0.0f) - return 0.0f; - if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input. - return (t == 0.0f) ? 1.0f : 0.0f; - if (mode == ImGuiInputReadMode_Repeat) - return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f); - if (mode == ImGuiInputReadMode_RepeatSlow) - return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f); - if (mode == ImGuiInputReadMode_RepeatFast) - return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f); - return 0.0f; -} - -ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor) -{ - ImVec2 delta(0.0f, 0.0f); - if (dir_sources & ImGuiNavDirSourceFlags_Keyboard) - delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode)); - if (dir_sources & ImGuiNavDirSourceFlags_PadDPad) - delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode)); - if (dir_sources & ImGuiNavDirSourceFlags_PadLStick) - delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode)); - if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow)) - delta *= slow_factor; - if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast)) - delta *= fast_factor; - return delta; -} - -// Scroll to keep newly navigated item fully into view -// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated. -static void NavScrollToBringItemIntoView(ImGuiWindow* window, const ImRect& item_rect) -{ - ImRect window_rect(window->InnerMainRect.Min - ImVec2(1, 1), window->InnerMainRect.Max + ImVec2(1, 1)); - //g.OverlayDrawList.AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG] - if (window_rect.Contains(item_rect)) - return; - - ImGuiContext& g = *GImGui; - if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x) - { - window->ScrollTarget.x = item_rect.Min.x - window->Pos.x + window->Scroll.x - g.Style.ItemSpacing.x; - window->ScrollTargetCenterRatio.x = 0.0f; - } - else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x) - { - window->ScrollTarget.x = item_rect.Max.x - window->Pos.x + window->Scroll.x + g.Style.ItemSpacing.x; - window->ScrollTargetCenterRatio.x = 1.0f; - } - if (item_rect.Min.y < window_rect.Min.y) - { - window->ScrollTarget.y = item_rect.Min.y - window->Pos.y + window->Scroll.y - g.Style.ItemSpacing.y; - window->ScrollTargetCenterRatio.y = 0.0f; - } - else if (item_rect.Max.y >= window_rect.Max.y) - { - window->ScrollTarget.y = item_rect.Max.y - window->Pos.y + window->Scroll.y + g.Style.ItemSpacing.y; - window->ScrollTargetCenterRatio.y = 1.0f; - } -} - -static void ImGui::NavUpdate() -{ - ImGuiContext& g = *GImGui; - g.IO.WantSetMousePos = false; -#if 0 - if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); -#endif - - // Set input source as Gamepad when buttons are pressed before we map Keyboard (some features differs when used with Gamepad vs Keyboard) - bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; - if (nav_gamepad_active) - if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f) - g.NavInputSource = ImGuiInputSource_NavGamepad; - - // Update Keyboard->Nav inputs mapping - if (nav_keyboard_active) - { - #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; } - NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate ); - NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input ); - NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel ); - NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ ); - NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_); - NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ ); - NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ ); - if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; - if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; - if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f; - #undef NAV_MAP_KEY - } - memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration)); - for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++) - g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f; - - // Process navigation init request (select first/default focus) - if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove)) - { - // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) - IM_ASSERT(g.NavWindow); - if (g.NavInitRequestFromMove) - SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel); - else - SetNavID(g.NavInitResultId, g.NavLayer); - g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel; - } - g.NavInitRequest = false; - g.NavInitRequestFromMove = false; - g.NavInitResultId = 0; - g.NavJustMovedToId = 0; - - // Process navigation move request - if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0)) - NavUpdateMoveResult(); - - // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame - if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive) - { - IM_ASSERT(g.NavMoveRequest); - if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0) - g.NavDisableHighlight = false; - g.NavMoveRequestForward = ImGuiNavForward_None; - } - - // Apply application mouse position movement, after we had a chance to process move request result. - if (g.NavMousePosDirty && g.NavIdIsAlive) - { - // Set mouse position given our knowledge of the navigated item position from last frame - if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (g.IO.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) - { - if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) - { - g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredRefPos(); - g.IO.WantSetMousePos = true; - } - } - g.NavMousePosDirty = false; - } - g.NavIdIsAlive = false; - g.NavJustTabbedId = 0; - IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1); - - // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0 - if (g.NavWindow) - NavSaveLastChildNavWindow(g.NavWindow); - if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0) - g.NavWindow->NavLastChildNavWindow = NULL; - - // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.) - NavUpdateWindowing(); - - // Set output flags for user application - g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); - g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest; - - // Process NavCancel input (to close a popup, get back to parent, clear focus) - if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed)) - { - if (g.ActiveId != 0) - { - ClearActiveID(); - } - else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) - { - // Exit child window - ImGuiWindow* child_window = g.NavWindow; - ImGuiWindow* parent_window = g.NavWindow->ParentWindow; - IM_ASSERT(child_window->ChildId != 0); - FocusWindow(parent_window); - SetNavID(child_window->ChildId, 0); - g.NavIdIsAlive = false; - if (g.NavDisableMouseHover) - g.NavMousePosDirty = true; - } - else if (g.OpenPopupStack.Size > 0) - { - // Close open popup/menu - if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) - ClosePopupToLevel(g.OpenPopupStack.Size - 1); - } - else if (g.NavLayer != 0) - { - // Leave the "menu" layer - NavRestoreLayer(0); - } - else - { - // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were - if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) - g.NavWindow->NavLastIds[0] = 0; - g.NavId = 0; - } - } - - // Process manual activation request - g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0; - if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) - { - bool activate_down = IsNavInputDown(ImGuiNavInput_Activate); - bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed); - if (g.ActiveId == 0 && activate_pressed) - g.NavActivateId = g.NavId; - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down) - g.NavActivateDownId = g.NavId; - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed) - g.NavActivatePressedId = g.NavId; - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed)) - g.NavInputId = g.NavId; - } - if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) - g.NavDisableHighlight = true; - if (g.NavActivateId != 0) - IM_ASSERT(g.NavActivateDownId == g.NavActivateId); - g.NavMoveRequest = false; - - // Process programmatic activation request - if (g.NavNextActivateId != 0) - g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId; - g.NavNextActivateId = 0; - - // Initiate directional inputs request - const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags; - if (g.NavMoveRequestForward == ImGuiNavForward_None) - { - g.NavMoveDir = ImGuiDir_None; - g.NavMoveRequestFlags = 0; - if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) - { - if ((allowed_dir_flags & (1<Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget) - { - // *Fallback* manual-scroll with Nav directional keys when window has no navigable item - ImGuiWindow* window = g.NavWindow; - const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. - if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) - { - if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) - SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); - if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) - SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); - } - - // *Normal* Manual scroll with NavScrollXXX keys - // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds. - ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f); - if (scroll_dir.x != 0.0f && window->ScrollbarX) - { - SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed)); - g.NavMoveFromClampedRefRect = true; - } - if (scroll_dir.y != 0.0f) - { - SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed)); - g.NavMoveFromClampedRefRect = true; - } - } - - // Reset search results - g.NavMoveResultLocal.Clear(); - g.NavMoveResultLocalVisibleSet.Clear(); - g.NavMoveResultOther.Clear(); - - // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items - if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0) - { - ImGuiWindow* window = g.NavWindow; - ImRect window_rect_rel(window->InnerMainRect.Min - window->Pos - ImVec2(1,1), window->InnerMainRect.Max - window->Pos + ImVec2(1,1)); - if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer])) - { - float pad = window->CalcFontSize() * 0.5f; - window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item - window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel); - g.NavId = 0; - } - g.NavMoveFromClampedRefRect = false; - } - - // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) - ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0); - g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect(); - g.NavScoringRectScreen.TranslateY(nav_scoring_rect_offset_y); - g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x); - g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x; - IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem(). - //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG] - g.NavScoringCount = 0; -#if IMGUI_DEBUG_NAV_RECTS - if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) GetOverlayDrawList()->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG] - if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames == 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } -#endif -} - -static void ImGui::NavUpdateMoveResult() -{ - // Select which result to use - ImGuiContext& g = *GImGui; - ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; - - // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page. - if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) - if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId) - result = &g.NavMoveResultLocalVisibleSet; - - // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules. - if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) - if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter)) - result = &g.NavMoveResultOther; - IM_ASSERT(g.NavWindow && result->Window); - - // Scroll to keep newly navigated item fully into view. - if (g.NavLayer == 0) - { - ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos); - NavScrollToBringItemIntoView(result->Window, rect_abs); - - // Estimate upcoming scroll so we can offset our result position so mouse position can be applied immediately after in NavUpdate() - ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(result->Window, false); - ImVec2 delta_scroll = result->Window->Scroll - next_scroll; - result->RectRel.Translate(delta_scroll); - - // Also scroll parent window to keep us into view if necessary (we could/should technically recurse back the whole the parent hierarchy). - if (result->Window->Flags & ImGuiWindowFlags_ChildWindow) - NavScrollToBringItemIntoView(result->Window->ParentWindow, ImRect(rect_abs.Min + delta_scroll, rect_abs.Max + delta_scroll)); - } - - // Apply result from previous frame navigation directional move request - ClearActiveID(); - g.NavWindow = result->Window; - SetNavIDWithRectRel(result->ID, g.NavLayer, result->RectRel); - g.NavJustMovedToId = result->ID; - g.NavMoveFromClampedRefRect = false; -} - -static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags) -{ - ImGuiContext& g = *GImGui; - if (g.NavMoveDir == ImGuiDir_None && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget && g.NavLayer == 0) - { - ImGuiWindow* window = g.NavWindow; - bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up)); - bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down)); - if ((page_up_held && !page_down_held) || (page_down_held && !page_up_held)) - { - if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll) - { - // Fallback manual-scroll when window has no navigable item - if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true)) - SetWindowScrollY(window, window->Scroll.y - window->InnerClipRect.GetHeight()); - else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) - SetWindowScrollY(window, window->Scroll.y + window->InnerClipRect.GetHeight()); - } - else - { - const ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer]; - const float page_offset_y = ImMax(0.0f, window->InnerClipRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight()); - float nav_scoring_rect_offset_y = 0.0f; - if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true)) - { - nav_scoring_rect_offset_y = -page_offset_y; - g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item) - g.NavMoveClipDir = ImGuiDir_Up; - g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; - } - else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) - { - nav_scoring_rect_offset_y = +page_offset_y; - g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item) - g.NavMoveClipDir = ImGuiDir_Down; - g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; - } - return nav_scoring_rect_offset_y; - } - } - } - return 0.0f; -} - -static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N) -{ - ImGuiContext& g = *GImGui; - for (int i = g.Windows.Size-1; i >= 0; i--) - if (g.Windows[i] == window) - return i; - return -1; -} - -static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) -{ - ImGuiContext& g = *GImGui; - for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir) - if (ImGui::IsWindowNavFocusable(g.Windows[i])) - return g.Windows[i]; - return NULL; -} - -static void NavUpdateWindowingHighlightWindow(int focus_change_dir) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.NavWindowingTarget); - if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal) - return; - - const int i_current = FindWindowIndex(g.NavWindowingTarget); - ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir); - if (!window_target) - window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir); - if (window_target) // Don't reset windowing target if there's a single window in the list - g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target; - g.NavWindowingToggleLayer = false; -} - -// Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer) -static void ImGui::NavUpdateWindowing() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* apply_focus_window = NULL; - bool apply_toggle_layer = false; - - ImGuiWindow* modal_window = GetFrontMostPopupModal(); - if (modal_window != NULL) - { - g.NavWindowingTarget = NULL; - return; - } - - // Fade out - if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL) - { - g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f); - if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f) - g.NavWindowingTargetAnim = NULL; - } - - // Start CTRL-TAB or Square+L/R window selection - bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed); - bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard); - if (start_windowing_with_gamepad || start_windowing_with_keyboard) - if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.Windows.Size - 1, -INT_MAX, -1)) - { - g.NavWindowingTarget = g.NavWindowingTargetAnim = window; - g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f; - g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; - g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad; - } - - // Gamepad update - g.NavWindowingTimer += g.IO.DeltaTime; - if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad) - { - // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise - g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); - - // Select window to focus - const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow); - if (focus_change_dir != 0) - { - NavUpdateWindowingHighlightWindow(focus_change_dir); - g.NavWindowingHighlightAlpha = 1.0f; - } - - // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most) - if (!IsNavInputDown(ImGuiNavInput_Menu)) - { - g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore. - if (g.NavWindowingToggleLayer && g.NavWindow) - apply_toggle_layer = true; - else if (!g.NavWindowingToggleLayer) - apply_focus_window = g.NavWindowingTarget; - g.NavWindowingTarget = NULL; - } - } - - // Keyboard: Focus - if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard) - { - // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise - g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f - if (IsKeyPressedMap(ImGuiKey_Tab, true)) - NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1); - if (!g.IO.KeyCtrl) - apply_focus_window = g.NavWindowingTarget; - } - - // Keyboard: Press and Release ALT to toggle menu layer - // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB - if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released)) - if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev)) - apply_toggle_layer = true; - - // Move window - if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) - { - ImVec2 move_delta; - if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift) - move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); - if (g.NavInputSource == ImGuiInputSource_NavGamepad) - move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down); - if (move_delta.x != 0.0f || move_delta.y != 0.0f) - { - const float NAV_MOVE_SPEED = 800.0f; - const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't code variable framerate very well - g.NavWindowingTarget->RootWindow->Pos += move_delta * move_speed; - g.NavDisableMouseHover = true; - MarkIniSettingsDirty(g.NavWindowingTarget); - } - } - - // Apply final focus - if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)) - { - g.NavDisableHighlight = false; - g.NavDisableMouseHover = true; - apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); - ClosePopupsOverWindow(apply_focus_window); - FocusWindow(apply_focus_window); - if (apply_focus_window->NavLastIds[0] == 0) - NavInitWindow(apply_focus_window, false); - - // If the window only has a menu layer, select it directly - if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1)) - g.NavLayer = 1; - } - if (apply_focus_window) - g.NavWindowingTarget = NULL; - - // Apply menu/layer toggle - if (apply_toggle_layer && g.NavWindow) - { - // Move to parent menu if necessary - ImGuiWindow* new_nav_window = g.NavWindow; - while ((new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) - new_nav_window = new_nav_window->ParentWindow; - if (new_nav_window != g.NavWindow) - { - ImGuiWindow* old_nav_window = g.NavWindow; - FocusWindow(new_nav_window); - new_nav_window->NavLastChildNavWindow = old_nav_window; - } - g.NavDisableHighlight = false; - g.NavDisableMouseHover = true; - NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0); - } -} - -// Window has already passed the IsWindowNavFocusable() -static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) -{ - if (window->Flags & ImGuiWindowFlags_Popup) - return "(Popup)"; - if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0) - return "(Main menu bar)"; - return "(Untitled)"; -} - -// Overlay displayed when using CTRL+TAB. Called by EndFrame(). -void ImGui::NavUpdateWindowingList() -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.NavWindowingTarget != NULL); - - if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY) - return; - - if (g.NavWindowingList == NULL) - g.NavWindowingList = FindWindowByName("###NavWindowingList"); - SetNextWindowSizeConstraints(ImVec2(g.IO.DisplaySize.x * 0.20f, g.IO.DisplaySize.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); - SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); - PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); - Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); - for (int n = g.Windows.Size - 1; n >= 0; n--) - { - ImGuiWindow* window = g.Windows[n]; - if (!IsWindowNavFocusable(window)) - continue; - const char* label = window->Name; - if (label == FindRenderedTextEnd(label)) - label = GetFallbackWindowNameForWindowingList(window); - Selectable(label, g.NavWindowingTarget == window); - } - End(); - PopStyleVar(); -} - -//----------------------------------------------------------------------------- -// [SECTION] COLUMNS -// In the current version, Columns are very weak. Needs to be replaced with a more full-featured system. -//----------------------------------------------------------------------------- - -void ImGui::NextColumn() -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems || window->DC.ColumnsSet == NULL) - return; - - ImGuiContext& g = *GImGui; - PopItemWidth(); - PopClipRect(); - - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); - if (++columns->Current < columns->Count) - { - // Columns 1+ cancel out IndentX - window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + g.Style.ItemSpacing.x; - window->DrawList->ChannelsSetCurrent(columns->Current); - } - else - { - window->DC.ColumnsOffset.x = 0.0f; - window->DrawList->ChannelsSetCurrent(0); - columns->Current = 0; - columns->LineMinY = columns->LineMaxY; - } - window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); - window->DC.CursorPos.y = columns->LineMinY; - window->DC.CurrentLineSize = ImVec2(0.0f, 0.0f); - window->DC.CurrentLineTextBaseOffset = 0.0f; - - PushColumnClipRect(); - PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup -} - -int ImGui::GetColumnIndex() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0; -} - -int ImGui::GetColumnsCount() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1; -} - -static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm) -{ - return offset_norm * (columns->MaxX - columns->MinX); -} - -static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset) -{ - return offset / (columns->MaxX - columns->MinX); -} - -static inline float GetColumnsRectHalfWidth() { return 4.0f; } - -static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index) -{ - // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing - // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - IM_ASSERT(column_index > 0); // We are not supposed to drag column 0. - IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); - - float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x; - x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); - if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths)) - x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); - - return x; -} - -float ImGui::GetColumnOffset(int column_index) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - IM_ASSERT(columns != NULL); - - if (column_index < 0) - column_index = columns->Current; - IM_ASSERT(column_index < columns->Columns.Size); - - const float t = columns->Columns[column_index].OffsetNorm; - const float x_offset = ImLerp(columns->MinX, columns->MaxX, t); - return x_offset; -} - -static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false) -{ - if (column_index < 0) - column_index = columns->Current; - - float offset_norm; - if (before_resize) - offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; - else - offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; - return OffsetNormToPixels(columns, offset_norm); -} - -float ImGui::GetColumnWidth(int column_index) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - IM_ASSERT(columns != NULL); - - if (column_index < 0) - column_index = columns->Current; - return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); -} - -void ImGui::SetColumnOffset(int column_index, float offset) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - IM_ASSERT(columns != NULL); - - if (column_index < 0) - column_index = columns->Current; - IM_ASSERT(column_index < columns->Columns.Size); - - const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1); - const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; - - if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) - offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); - columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX); - - if (preserve_width) - SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); -} - -void ImGui::SetColumnWidth(int column_index, float width) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - IM_ASSERT(columns != NULL); - - if (column_index < 0) - column_index = columns->Current; - SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); -} - -void ImGui::PushColumnClipRect(int column_index) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - if (column_index < 0) - column_index = columns->Current; - - PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false); -} - -static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id) -{ - for (int n = 0; n < window->ColumnsStorage.Size; n++) - if (window->ColumnsStorage[n].ID == id) - return &window->ColumnsStorage[n]; - - window->ColumnsStorage.push_back(ImGuiColumnsSet()); - ImGuiColumnsSet* columns = &window->ColumnsStorage.back(); - columns->ID = id; - return columns; -} - -void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - - IM_ASSERT(columns_count > 1); - IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported - - // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. - // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. - PushID(0x11223347 + (str_id ? 0 : columns_count)); - ImGuiID id = window->GetID(str_id ? str_id : "columns"); - PopID(); - - // Acquire storage for the columns set - ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id); - IM_ASSERT(columns->ID == id); - columns->Current = 0; - columns->Count = columns_count; - columns->Flags = flags; - window->DC.ColumnsSet = columns; - - // Set state for first column - const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->InnerClipRect.Max.x - window->Pos.x); - columns->MinX = window->DC.Indent.x - g.Style.ItemSpacing.x; // Lock our horizontal range - columns->MaxX = ImMax(content_region_width - window->Scroll.x, columns->MinX + 1.0f); - columns->StartPosY = window->DC.CursorPos.y; - columns->StartMaxPosX = window->DC.CursorMaxPos.x; - columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y; - window->DC.ColumnsOffset.x = 0.0f; - window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); - - // Clear data if columns count changed - if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) - columns->Columns.resize(0); - - // Initialize defaults - columns->IsFirstFrame = (columns->Columns.Size == 0); - if (columns->Columns.Size == 0) - { - columns->Columns.reserve(columns_count + 1); - for (int n = 0; n < columns_count + 1; n++) - { - ImGuiColumnData column; - column.OffsetNorm = n / (float)columns_count; - columns->Columns.push_back(column); - } - } - - for (int n = 0; n < columns_count; n++) - { - // Compute clipping rectangle - ImGuiColumnData* column = &columns->Columns[n]; - float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f); - float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f); - column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); - column->ClipRect.ClipWith(window->ClipRect); - } - - window->DrawList->ChannelsSplit(columns->Count); - PushColumnClipRect(); - PushItemWidth(GetColumnWidth() * 0.65f); -} - -void ImGui::EndColumns() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - IM_ASSERT(columns != NULL); - - PopItemWidth(); - PopClipRect(); - window->DrawList->ChannelsMerge(); - - columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); - window->DC.CursorPos.y = columns->LineMaxY; - if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize)) - window->DC.CursorMaxPos.x = columns->StartMaxPosX; // Restore cursor max pos, as columns don't grow parent - - // Draw columns borders and handle resize - bool is_being_resized = false; - if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems) - { - const float y1 = columns->StartPosY; - const float y2 = window->DC.CursorPos.y; - int dragging_column = -1; - for (int n = 1; n < columns->Count; n++) - { - float x = window->Pos.x + GetColumnOffset(n); - const ImGuiID column_id = columns->ID + ImGuiID(n); - const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction - const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2)); - KeepAliveID(column_id); - if (IsClippedEx(column_rect, column_id, false)) - continue; - - bool hovered = false, held = false; - if (!(columns->Flags & ImGuiColumnsFlags_NoResize)) - { - ButtonBehavior(column_rect, column_id, &hovered, &held); - if (hovered || held) - g.MouseCursor = ImGuiMouseCursor_ResizeEW; - if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize)) - dragging_column = n; - } - - // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.) - const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); - const float xi = (float)(int)x; - window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col); - } - - // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. - if (dragging_column != -1) - { - if (!columns->IsBeingResized) - for (int n = 0; n < columns->Count + 1; n++) - columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; - columns->IsBeingResized = is_being_resized = true; - float x = GetDraggedColumnOffset(columns, dragging_column); - SetColumnOffset(dragging_column, x); - } - } - columns->IsBeingResized = is_being_resized; - - window->DC.ColumnsSet = NULL; - window->DC.ColumnsOffset.x = 0.0f; - window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); -} - -// [2018-03: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing] -void ImGui::Columns(int columns_count, const char* id, bool border) -{ - ImGuiWindow* window = GetCurrentWindow(); - IM_ASSERT(columns_count >= 1); - - ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder); - //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior - if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count == columns_count && window->DC.ColumnsSet->Flags == flags) - return; - - if (window->DC.ColumnsSet != NULL) - EndColumns(); - - if (columns_count != 1) - BeginColumns(id, columns_count, flags); -} - -//----------------------------------------------------------------------------- -// [SECTION] DRAG AND DROP -//----------------------------------------------------------------------------- - -void ImGui::ClearDragDrop() -{ - ImGuiContext& g = *GImGui; - g.DragDropActive = false; - g.DragDropPayload.Clear(); - g.DragDropAcceptFlags = 0; - g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; - g.DragDropAcceptIdCurrRectSurface = FLT_MAX; - g.DragDropAcceptFrameCount = -1; - - g.DragDropPayloadBufHeap.clear(); - memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); -} - -// Call when current ID is active. -// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() -bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - bool source_drag_active = false; - ImGuiID source_id = 0; - ImGuiID source_parent_id = 0; - int mouse_button = 0; - if (!(flags & ImGuiDragDropFlags_SourceExtern)) - { - source_id = window->DC.LastItemId; - if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case - return false; - if (g.IO.MouseDown[mouse_button] == false) - return false; - - if (source_id == 0) - { - // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: - // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride. - if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) - { - IM_ASSERT(0); - return false; - } - - // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() - // We build a throwaway ID based on current ID stack + relative AABB of items in window. - // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. - // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. - bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0; - if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window)) - return false; - source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); - if (is_hovered) - SetHoveredID(source_id); - if (is_hovered && g.IO.MouseClicked[mouse_button]) - { - SetActiveID(source_id, window); - FocusWindow(window); - } - if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. - g.ActiveIdAllowOverlap = is_hovered; - } - else - { - g.ActiveIdAllowOverlap = false; - } - if (g.ActiveId != source_id) - return false; - source_parent_id = window->IDStack.back(); - source_drag_active = IsMouseDragging(mouse_button); - } - else - { - window = NULL; - source_id = ImHash("#SourceExtern", 0); - source_drag_active = true; - } - - if (source_drag_active) - { - if (!g.DragDropActive) - { - IM_ASSERT(source_id != 0); - ClearDragDrop(); - ImGuiPayload& payload = g.DragDropPayload; - payload.SourceId = source_id; - payload.SourceParentId = source_parent_id; - g.DragDropActive = true; - g.DragDropSourceFlags = flags; - g.DragDropMouseButton = mouse_button; - } - g.DragDropSourceFrameCount = g.FrameCount; - g.DragDropWithinSourceOrTarget = true; - - if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) - { - // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit) - // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents. - BeginTooltip(); - if (g.DragDropActive && g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) - { - ImGuiWindow* tooltip_window = g.CurrentWindow; - tooltip_window->SkipItems = true; - tooltip_window->HiddenFramesRegular = 1; - } - } - - if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) - window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; - - return true; - } - return false; -} - -void ImGui::EndDragDropSource() -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.DragDropActive); - IM_ASSERT(g.DragDropWithinSourceOrTarget && "Not after a BeginDragDropSource()?"); - - if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) - EndTooltip(); - - // Discard the drag if have not called SetDragDropPayload() - if (g.DragDropPayload.DataFrameCount == -1) - ClearDragDrop(); - g.DragDropWithinSourceOrTarget = false; -} - -// Use 'cond' to choose to submit payload on drag start or every frame -bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) -{ - ImGuiContext& g = *GImGui; - ImGuiPayload& payload = g.DragDropPayload; - if (cond == 0) - cond = ImGuiCond_Always; - - IM_ASSERT(type != NULL); - IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long"); - IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); - IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); - IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() - - if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) - { - // Copy payload - ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); - g.DragDropPayloadBufHeap.resize(0); - if (data_size > sizeof(g.DragDropPayloadBufLocal)) - { - // Store in heap - g.DragDropPayloadBufHeap.resize((int)data_size); - payload.Data = g.DragDropPayloadBufHeap.Data; - memcpy(payload.Data, data, data_size); - } - else if (data_size > 0) - { - // Store locally - memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); - payload.Data = g.DragDropPayloadBufLocal; - memcpy(payload.Data, data, data_size); - } - else - { - payload.Data = NULL; - } - payload.DataSize = (int)data_size; - } - payload.DataFrameCount = g.FrameCount; - - return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1); -} - -bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) -{ - ImGuiContext& g = *GImGui; - if (!g.DragDropActive) - return false; - - ImGuiWindow* window = g.CurrentWindow; - if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) - return false; - IM_ASSERT(id != 0); - if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) - return false; - if (window->SkipItems) - return false; - - IM_ASSERT(g.DragDropWithinSourceOrTarget == false); - g.DragDropTargetRect = bb; - g.DragDropTargetId = id; - g.DragDropWithinSourceOrTarget = true; - return true; -} - -// We don't use BeginDragDropTargetCustom() and duplicate its code because: -// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. -// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. -// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) -bool ImGui::BeginDragDropTarget() -{ - ImGuiContext& g = *GImGui; - if (!g.DragDropActive) - return false; - - ImGuiWindow* window = g.CurrentWindow; - if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) - return false; - if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) - return false; - - const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect; - ImGuiID id = window->DC.LastItemId; - if (id == 0) - id = window->GetIDFromRectangle(display_rect); - if (g.DragDropPayload.SourceId == id) - return false; - - IM_ASSERT(g.DragDropWithinSourceOrTarget == false); - g.DragDropTargetRect = display_rect; - g.DragDropTargetId = id; - g.DragDropWithinSourceOrTarget = true; - return true; -} - -bool ImGui::IsDragDropPayloadBeingAccepted() -{ - ImGuiContext& g = *GImGui; - return g.DragDropActive && g.DragDropAcceptIdPrev != 0; -} - -const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImGuiPayload& payload = g.DragDropPayload; - IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? - IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ? - if (type != NULL && !payload.IsDataType(type)) - return NULL; - - // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints. - // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! - const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); - ImRect r = g.DragDropTargetRect; - float r_surface = r.GetWidth() * r.GetHeight(); - if (r_surface < g.DragDropAcceptIdCurrRectSurface) - { - g.DragDropAcceptFlags = flags; - g.DragDropAcceptIdCurr = g.DragDropTargetId; - g.DragDropAcceptIdCurrRectSurface = r_surface; - } - - // Render default drop visuals - payload.Preview = was_accepted_previously; - flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) - if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) - { - // FIXME-DRAG: Settle on a proper default visuals for drop target. - r.Expand(3.5f); - bool push_clip_rect = !window->ClipRect.Contains(r); - if (push_clip_rect) window->DrawList->PushClipRect(r.Min-ImVec2(1,1), r.Max+ImVec2(1,1)); - window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f); - if (push_clip_rect) window->DrawList->PopClipRect(); - } - - g.DragDropAcceptFrameCount = g.FrameCount; - payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() - if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) - return NULL; - - return &payload; -} - -// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. -void ImGui::EndDragDropTarget() -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.DragDropActive); - IM_ASSERT(g.DragDropWithinSourceOrTarget); - g.DragDropWithinSourceOrTarget = false; -} - -//----------------------------------------------------------------------------- -// [SECTION] LOGGING/CAPTURING -//----------------------------------------------------------------------------- - -// Pass text data straight to log (without being displayed) -void ImGui::LogText(const char* fmt, ...) -{ - ImGuiContext& g = *GImGui; - if (!g.LogEnabled) - return; - - va_list args; - va_start(args, fmt); - if (g.LogFile) - vfprintf(g.LogFile, fmt, args); - else - g.LogClipboard.appendfv(fmt, args); - va_end(args); -} - -// Internal version that takes a position to decide on newline placement and pad items according to their depth. -// We split text into individual lines to add current tree level padding -void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - if (!text_end) - text_end = FindRenderedTextEnd(text, text_end); - - const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1); - if (ref_pos) - window->DC.LogLinePosY = ref_pos->y; - - const char* text_remaining = text; - if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth - g.LogStartDepth = window->DC.TreeDepth; - const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth); - for (;;) - { - // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry. - const char* line_end = text_remaining; - while (line_end < text_end) - if (*line_end == '\n') - break; - else - line_end++; - if (line_end >= text_end) - line_end = NULL; - - const bool is_first_line = (text == text_remaining); - bool is_last_line = false; - if (line_end == NULL) - { - is_last_line = true; - line_end = text_end; - } - if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0)) - { - const int char_count = (int)(line_end - text_remaining); - if (log_new_line || !is_first_line) - LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining); - else - LogText(" %.*s", char_count, text_remaining); - } - - if (is_last_line) - break; - text_remaining = line_end + 1; - } -} - -// Start logging ImGui output to TTY -void ImGui::LogToTTY(int max_depth) -{ - ImGuiContext& g = *GImGui; - if (g.LogEnabled) - return; - ImGuiWindow* window = g.CurrentWindow; - - IM_ASSERT(g.LogFile == NULL); - g.LogFile = stdout; - g.LogEnabled = true; - g.LogStartDepth = window->DC.TreeDepth; - if (max_depth >= 0) - g.LogAutoExpandMaxDepth = max_depth; -} - -// Start logging ImGui output to given file -void ImGui::LogToFile(int max_depth, const char* filename) -{ - ImGuiContext& g = *GImGui; - if (g.LogEnabled) - return; - ImGuiWindow* window = g.CurrentWindow; - - if (!filename) - { - filename = g.IO.LogFilename; - if (!filename) - return; - } - - IM_ASSERT(g.LogFile == NULL); - g.LogFile = ImFileOpen(filename, "ab"); - if (!g.LogFile) - { - IM_ASSERT(g.LogFile != NULL); // Consider this an error - return; - } - g.LogEnabled = true; - g.LogStartDepth = window->DC.TreeDepth; - if (max_depth >= 0) - g.LogAutoExpandMaxDepth = max_depth; -} - -// Start logging ImGui output to clipboard -void ImGui::LogToClipboard(int max_depth) -{ - ImGuiContext& g = *GImGui; - if (g.LogEnabled) - return; - ImGuiWindow* window = g.CurrentWindow; - - IM_ASSERT(g.LogFile == NULL); - g.LogFile = NULL; - g.LogEnabled = true; - g.LogStartDepth = window->DC.TreeDepth; - if (max_depth >= 0) - g.LogAutoExpandMaxDepth = max_depth; -} - -void ImGui::LogFinish() -{ - ImGuiContext& g = *GImGui; - if (!g.LogEnabled) - return; - - LogText(IM_NEWLINE); - if (g.LogFile != NULL) - { - if (g.LogFile == stdout) - fflush(g.LogFile); - else - fclose(g.LogFile); - g.LogFile = NULL; - } - if (g.LogClipboard.size() > 1) - { - SetClipboardText(g.LogClipboard.begin()); - g.LogClipboard.clear(); - } - g.LogEnabled = false; -} - -// Helper to display logging buttons -void ImGui::LogButtons() -{ - ImGuiContext& g = *GImGui; - - PushID("LogButtons"); - const bool log_to_tty = Button("Log To TTY"); SameLine(); - const bool log_to_file = Button("Log To File"); SameLine(); - const bool log_to_clipboard = Button("Log To Clipboard"); SameLine(); - PushItemWidth(80.0f); - PushAllowKeyboardFocus(false); - SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL); - PopAllowKeyboardFocus(); - PopItemWidth(); - PopID(); - - // Start logging at the end of the function so that the buttons don't appear in the log - if (log_to_tty) - LogToTTY(g.LogAutoExpandMaxDepth); - if (log_to_file) - LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename); - if (log_to_clipboard) - LogToClipboard(g.LogAutoExpandMaxDepth); -} - -//----------------------------------------------------------------------------- -// [SECTION] SETTINGS -//----------------------------------------------------------------------------- - -void ImGui::MarkIniSettingsDirty() -{ - ImGuiContext& g = *GImGui; - if (g.SettingsDirtyTimer <= 0.0f) - g.SettingsDirtyTimer = g.IO.IniSavingRate; -} - -void ImGui::MarkIniSettingsDirty(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings)) - if (g.SettingsDirtyTimer <= 0.0f) - g.SettingsDirtyTimer = g.IO.IniSavingRate; -} - -static ImGuiWindowSettings* CreateNewWindowSettings(const char* name) -{ - ImGuiContext& g = *GImGui; - g.SettingsWindows.push_back(ImGuiWindowSettings()); - ImGuiWindowSettings* settings = &g.SettingsWindows.back(); - settings->Name = ImStrdup(name); - settings->ID = ImHash(name, 0); - return settings; -} - -ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id) -{ - ImGuiContext& g = *GImGui; - for (int i = 0; i != g.SettingsWindows.Size; i++) - if (g.SettingsWindows[i].ID == id) - return &g.SettingsWindows[i]; - return NULL; -} - -void ImGui::LoadIniSettingsFromDisk(const char* ini_filename) -{ - size_t file_data_size = 0; - char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size); - if (!file_data) - return; - LoadIniSettingsFromMemory(file_data, (size_t)file_data_size); - ImGui::MemFree(file_data); -} - -ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name) -{ - ImGuiContext& g = *GImGui; - const ImGuiID type_hash = ImHash(type_name, 0, 0); - for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) - if (g.SettingsHandlers[handler_n].TypeHash == type_hash) - return &g.SettingsHandlers[handler_n]; - return NULL; -} - -// Zero-tolerance, no error reporting, cheap .ini parsing -void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.Initialized); - IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0); - - // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter). - // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy.. - if (ini_size == 0) - ini_size = strlen(ini_data); - char* buf = (char*)ImGui::MemAlloc(ini_size + 1); - char* buf_end = buf + ini_size; - memcpy(buf, ini_data, ini_size); - buf[ini_size] = 0; - - void* entry_data = NULL; - ImGuiSettingsHandler* entry_handler = NULL; - - char* line_end = NULL; - for (char* line = buf; line < buf_end; line = line_end + 1) - { - // Skip new lines markers, then find end of the line - while (*line == '\n' || *line == '\r') - line++; - line_end = line; - while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') - line_end++; - line_end[0] = 0; - if (line[0] == ';') - continue; - if (line[0] == '[' && line_end > line && line_end[-1] == ']') - { - // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code. - line_end[-1] = 0; - const char* name_end = line_end - 1; - const char* type_start = line + 1; - char* type_end = (char*)(intptr_t)ImStrchrRange(type_start, name_end, ']'); - const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL; - if (!type_end || !name_start) - { - name_start = type_start; // Import legacy entries that have no type - type_start = "Window"; - } - else - { - *type_end = 0; // Overwrite first ']' - name_start++; // Skip second '[' - } - entry_handler = FindSettingsHandler(type_start); - entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL; - } - else if (entry_handler != NULL && entry_data != NULL) - { - // Let type handler parse the line - entry_handler->ReadLineFn(&g, entry_handler, entry_data, line); - } - } - ImGui::MemFree(buf); - g.SettingsLoaded = true; -} - -void ImGui::SaveIniSettingsToDisk(const char* ini_filename) -{ - ImGuiContext& g = *GImGui; - g.SettingsDirtyTimer = 0.0f; - if (!ini_filename) - return; - - size_t ini_data_size = 0; - const char* ini_data = SaveIniSettingsToMemory(&ini_data_size); - FILE* f = ImFileOpen(ini_filename, "wt"); - if (!f) - return; - fwrite(ini_data, sizeof(char), ini_data_size, f); - fclose(f); -} - -// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer -const char* ImGui::SaveIniSettingsToMemory(size_t* out_size) -{ - ImGuiContext& g = *GImGui; - g.SettingsDirtyTimer = 0.0f; - g.SettingsIniData.Buf.resize(0); - g.SettingsIniData.Buf.push_back(0); - for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++) - { - ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n]; - handler->WriteAllFn(&g, handler, &g.SettingsIniData); - } - if (out_size) - *out_size = (size_t)g.SettingsIniData.size(); - return g.SettingsIniData.c_str(); -} - -static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) -{ - ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0)); - if (!settings) - settings = CreateNewWindowSettings(name); - return (void*)settings; -} - -static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line) -{ - ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry; - float x, y; - int i; - if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y); - else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize); - else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0); -} - -static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) -{ - // Gather data from windows that were active during this session - // (if a window wasn't opened in this session we preserve its settings) - ImGuiContext& g = *imgui_ctx; - for (int i = 0; i != g.Windows.Size; i++) - { - ImGuiWindow* window = g.Windows[i]; - if (window->Flags & ImGuiWindowFlags_NoSavedSettings) - continue; - - ImGuiWindowSettings* settings = (window->SettingsIdx != -1) ? &g.SettingsWindows[window->SettingsIdx] : ImGui::FindWindowSettings(window->ID); - if (!settings) - { - settings = CreateNewWindowSettings(window->Name); - window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings); - } - IM_ASSERT(settings->ID == window->ID); - settings->Pos = window->Pos; - settings->Size = window->SizeFull; - settings->Collapsed = window->Collapsed; - } - - // Write to text buffer - buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve - for (int i = 0; i != g.SettingsWindows.Size; i++) - { - const ImGuiWindowSettings* settings = &g.SettingsWindows[i]; - if (settings->Pos.x == FLT_MAX) - continue; - const char* name = settings->Name; - if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() - name = p; - buf->appendf("[%s][%s]\n", handler->TypeName, name); - buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); - buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); - buf->appendf("Collapsed=%d\n", settings->Collapsed); - buf->appendf("\n"); - } -} - -//----------------------------------------------------------------------------- -// [SECTION] PLATFORM DEPENDENT HELPERS -//----------------------------------------------------------------------------- - -#if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#ifndef __MINGW32__ -#include -#else -#include -#endif -#endif - -// Win32 API clipboard implementation -#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) - -#ifdef _MSC_VER -#pragma comment(lib, "user32") -#endif - -static const char* GetClipboardTextFn_DefaultImpl(void*) -{ - static ImVector buf_local; - buf_local.clear(); - if (!::OpenClipboard(NULL)) - return NULL; - HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT); - if (wbuf_handle == NULL) - { - ::CloseClipboard(); - return NULL; - } - if (ImWchar* wbuf_global = (ImWchar*)::GlobalLock(wbuf_handle)) - { - int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1; - buf_local.resize(buf_len); - ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL); - } - ::GlobalUnlock(wbuf_handle); - ::CloseClipboard(); - return buf_local.Data; -} - -static void SetClipboardTextFn_DefaultImpl(void*, const char* text) -{ - if (!::OpenClipboard(NULL)) - return; - const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1; - HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar)); - if (wbuf_handle == NULL) - { - ::CloseClipboard(); - return; - } - ImWchar* wbuf_global = (ImWchar*)::GlobalLock(wbuf_handle); - ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL); - ::GlobalUnlock(wbuf_handle); - ::EmptyClipboard(); - if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL) - ::GlobalFree(wbuf_handle); - ::CloseClipboard(); -} - -#else - -// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers -static const char* GetClipboardTextFn_DefaultImpl(void*) -{ - ImGuiContext& g = *GImGui; - return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin(); -} - -// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers -static void SetClipboardTextFn_DefaultImpl(void*, const char* text) -{ - ImGuiContext& g = *GImGui; - g.PrivateClipboard.clear(); - const char* text_end = text + strlen(text); - g.PrivateClipboard.resize((int)(text_end - text) + 1); - memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text)); - g.PrivateClipboard[(int)(text_end - text)] = 0; -} - -#endif - -// Win32 API IME support (for Asian languages, etc.) -#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) - -#include -#ifdef _MSC_VER -#pragma comment(lib, "imm32") -#endif - -static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y) -{ - // Notify OS Input Method Editor of text input position - if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle) - if (HIMC himc = ::ImmGetContext(hwnd)) - { - COMPOSITIONFORM cf; - cf.ptCurrentPos.x = x; - cf.ptCurrentPos.y = y; - cf.dwStyle = CFS_FORCE_POSITION; - ::ImmSetCompositionWindow(himc, &cf); - ::ImmReleaseContext(hwnd, himc); - } -} - -#else - -static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {} - -#endif - -//----------------------------------------------------------------------------- -// [SECTION] METRICS/DEBUG WINDOW -//----------------------------------------------------------------------------- - -void ImGui::ShowMetricsWindow(bool* p_open) -{ - if (!ImGui::Begin("ImGui Metrics", p_open)) - { - ImGui::End(); - return; - } - static bool show_draw_cmd_clip_rects = true; - static bool show_window_begin_order = false; - ImGuiIO& io = ImGui::GetIO(); - ImGui::Text("Dear ImGui %s", ImGui::GetVersion()); - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); - ImGui::Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3); - ImGui::Text("%d active windows (%d visible)", io.MetricsActiveWindows, io.MetricsRenderWindows); - ImGui::Text("%d allocations", io.MetricsActiveAllocations); - ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_draw_cmd_clip_rects); - ImGui::Checkbox("Ctrl shows window begin order", &show_window_begin_order); - - ImGui::Separator(); - - struct Funcs - { - static void NodeDrawList(ImGuiWindow* window, ImDrawList* draw_list, const char* label) - { - bool node_open = ImGui::TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, draw_list->CmdBuffer.Size); - if (draw_list == ImGui::GetWindowDrawList()) - { - ImGui::SameLine(); - ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) - if (node_open) ImGui::TreePop(); - return; - } - - ImDrawList* overlay_draw_list = GetOverlayDrawList(); // Render additional visuals into the top-most draw list - if (window && IsItemHovered()) - overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); - if (!node_open) - return; - - int elem_offset = 0; - for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) - { - if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0) - continue; - if (pcmd->UserCallback) - { - ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); - continue; - } - ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; - bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %4d %s vtx, tex 0x%p, clip_rect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); - if (show_draw_cmd_clip_rects && ImGui::IsItemHovered()) - { - ImRect clip_rect = pcmd->ClipRect; - ImRect vtxs_rect; - for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++) - vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos); - clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255)); - vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255)); - } - if (!pcmd_node_open) - continue; - - // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. - ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. - while (clipper.Step()) - for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++) - { - char buf[300]; - char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf); - ImVec2 triangles_pos[3]; - for (int n = 0; n < 3; n++, vtx_i++) - { - ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i]; - triangles_pos[n] = v.pos; - buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); - } - ImGui::Selectable(buf, false); - if (ImGui::IsItemHovered()) - { - ImDrawListFlags backup_flags = overlay_draw_list->Flags; - overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles. - overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f); - overlay_draw_list->Flags = backup_flags; - } - } - ImGui::TreePop(); - } - ImGui::TreePop(); - } - - static void NodeWindows(ImVector& windows, const char* label) - { - if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size)) - return; - for (int i = 0; i < windows.Size; i++) - Funcs::NodeWindow(windows[i], "Window"); - ImGui::TreePop(); - } - - static void NodeWindow(ImGuiWindow* window, const char* label) - { - if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window)) - return; - ImGuiWindowFlags flags = window->Flags; - NodeDrawList(window, window->DrawList, "DrawList"); - ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y); - ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s..)", flags, - (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "", - (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "", - (flags & ImGuiWindowFlags_NoInputs) ? "NoInputs":"", (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : ""); - ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetScrollMaxX(window), window->Scroll.y, GetScrollMaxY(window)); - ImGui::BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1); - ImGui::BulletText("Appearing: %d, Hidden: %d (Reg %d Resize %d), SkipItems: %d", window->Appearing, window->Hidden, window->HiddenFramesRegular, window->HiddenFramesForResize, window->SkipItems); - ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask); - ImGui::BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); - if (!window->NavRectRel[0].IsInverted()) - ImGui::BulletText("NavRectRel[0]: (%.1f,%.1f)(%.1f,%.1f)", window->NavRectRel[0].Min.x, window->NavRectRel[0].Min.y, window->NavRectRel[0].Max.x, window->NavRectRel[0].Max.y); - else - ImGui::BulletText("NavRectRel[0]: "); - if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow"); - if (window->ParentWindow != NULL) NodeWindow(window->ParentWindow, "ParentWindow"); - if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows"); - if (window->ColumnsStorage.Size > 0 && ImGui::TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size)) - { - for (int n = 0; n < window->ColumnsStorage.Size; n++) - { - const ImGuiColumnsSet* columns = &window->ColumnsStorage[n]; - if (ImGui::TreeNode((void*)(uintptr_t)columns->ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID, columns->Count, columns->Flags)) - { - ImGui::BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->MaxX - columns->MinX, columns->MinX, columns->MaxX); - for (int column_n = 0; column_n < columns->Columns.Size; column_n++) - ImGui::BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", column_n, columns->Columns[column_n].OffsetNorm, OffsetNormToPixels(columns, columns->Columns[column_n].OffsetNorm)); - ImGui::TreePop(); - } - } - ImGui::TreePop(); - } - ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair)); - ImGui::TreePop(); - } - }; - - // Access private state, we are going to display the draw lists from last frame - ImGuiContext& g = *GImGui; - Funcs::NodeWindows(g.Windows, "Windows"); - if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.DrawDataBuilder.Layers[0].Size)) - { - for (int i = 0; i < g.DrawDataBuilder.Layers[0].Size; i++) - Funcs::NodeDrawList(NULL, g.DrawDataBuilder.Layers[0][i], "DrawList"); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) - { - for (int i = 0; i < g.OpenPopupStack.Size; i++) - { - ImGuiWindow* window = g.OpenPopupStack[i].Window; - ImGui::BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : ""); - } - ImGui::TreePop(); - } - if (ImGui::TreeNode("Internal state")) - { - const char* input_source_names[] = { "None", "Mouse", "Nav", "NavKeyboard", "NavGamepad" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT); - ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); - ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL"); - ImGui::Text("HoveredId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not - ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, input_source_names[g.ActiveIdSource]); - ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); - ImGui::Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL"); - ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL"); - ImGui::Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer); - ImGui::Text("NavInputSource: %s", input_source_names[g.NavInputSource]); - ImGui::Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); - ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId); - ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); - ImGui::Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL"); - ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); - ImGui::TreePop(); - } - - - if (g.IO.KeyCtrl && show_window_begin_order) - { - for (int n = 0; n < g.Windows.Size; n++) - { - ImGuiWindow* window = g.Windows[n]; - if ((window->Flags & ImGuiWindowFlags_ChildWindow) || !window->WasActive) - continue; - char buf[32]; - ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext); - float font_size = ImGui::GetFontSize() * 2; - ImDrawList* overlay_draw_list = GetOverlayDrawList(); - overlay_draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255)); - overlay_draw_list->AddText(NULL, font_size, window->Pos, IM_COL32(255, 255, 255, 255), buf); - } - } - ImGui::End(); -} - -//----------------------------------------------------------------------------- - -// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. -// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from imgui_internal.h add it or request it on the github. -#ifdef IMGUI_INCLUDE_IMGUI_USER_INL -#include "imgui_user.inl" -#endif - -//----------------------------------------------------------------------------- diff --git a/Framework/src/imgui/imgui.h b/Framework/src/imgui/imgui.h deleted file mode 100644 index 8913459ac5..0000000000 --- a/Framework/src/imgui/imgui.h +++ /dev/null @@ -1,2961 +0,0 @@ -// dear imgui, v1.65 -// (headers) - -// See imgui.cpp file for documentation. -// Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. -// Read 'Programmer guide' in imgui.cpp for notes on how to setup ImGui in your codebase. -// Get latest version at https://github.com/ocornut/imgui - -#pragma once - -// Configuration file (edit imconfig.h or define IMGUI_USER_CONFIG to set your own filename) -#ifdef IMGUI_USER_CONFIG -#include IMGUI_USER_CONFIG -#endif -#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || defined(IMGUI_INCLUDE_IMCONFIG_H) -#include "imconfig.h" -#endif - -#include // FLT_MAX -#include // va_list -#include // ptrdiff_t, NULL -#include // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp - -// Version -// (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at -// XYY00 then bounced up to XYY01 when release tagging happens) -#define IMGUI_VERSION "1.65" -#define IMGUI_VERSION_NUM 16501 -#define IMGUI_CHECKVERSION() \ - ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), \ - sizeof(ImVec4), sizeof(ImDrawVert)) - -// Define attributes of all API symbols declarations (e.g. for DLL under Windows) -// IMGUI_API is used for core imgui functions, IMGUI_IMPL_API is used for the default bindings files (imgui_impl_xxx.h) -#ifndef IMGUI_API -#define IMGUI_API -#endif -#ifndef IMGUI_IMPL_API -#define IMGUI_IMPL_API IMGUI_API -#endif - -// Helpers -#ifndef IM_ASSERT -#include -#define IM_ASSERT(_EXPR) assert(_EXPR) // You can override the default assert handler by editing imconfig.h -#endif -#if defined(__clang__) || defined(__GNUC__) -#define IM_FMTARGS(FMT) __attribute__((format(printf, FMT, FMT + 1))) // Apply printf-style warnings to user functions. -#define IM_FMTLIST(FMT) __attribute__((format(printf, FMT, 0))) -#else -#define IM_FMTARGS(FMT) -#define IM_FMTLIST(FMT) -#endif -#define IM_ARRAYSIZE(_ARR) \ - ((int)(sizeof(_ARR) / sizeof(*_ARR))) // Size of a static C-style array. Don't use on pointers! -#define IM_OFFSETOF(_TYPE, _MEMBER) \ - ((size_t) & (((_TYPE*)0)->_MEMBER)) // Offset of _MEMBER within _TYPE. Standardized as offsetof() in modern C++. - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wold-style-cast" -#elif defined(__GNUC__) && __GNUC__ >= 8 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wclass-memaccess" -#endif - -// Forward declarations -struct ImDrawChannel; // Temporary storage for outputting drawing commands out of order, used by - // ImDrawList::ChannelsSplit() -struct ImDrawCmd; // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call) -struct ImDrawData; // All draw command lists required to render the frame -struct ImDrawList; // A single draw command list (generally one per window, conceptually you may see this as a dynamic - // "mesh" builder) -struct ImDrawListSharedData; // Data shared among multiple draw lists (typically owned by parent ImGui context, but you - // may create one yourself) -struct ImDrawVert; // A single vertex (20 bytes by default, override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) -struct ImFont; // Runtime data for a single font within a parent ImFontAtlas -struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader -struct ImFontConfig; // Configuration data when adding a font or merging fonts -struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*obsolete* please - // avoid using) -#ifndef ImTextureID -typedef void* ImTextureID; // User data to identify a texture (this is whatever to you want it to be! read the FAQ about - // ImTextureID in imgui.cpp) -#endif -struct ImGuiContext; // ImGui context (opaque) -struct ImGuiIO; // Main configuration and I/O between your application and ImGui -struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback - // (rare/advanced use) -struct ImGuiListClipper; // Helper to manually clip large list of items -struct ImGuiOnceUponAFrame; // Helper for running a block of code not more than once a frame, used by - // IMGUI_ONCE_UPON_A_FRAME macro -struct ImGuiPayload; // User data payload for drag and drop operations -struct ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use) -struct ImGuiStorage; // Helper for key->value storage -struct ImGuiStyle; // Runtime data for styling/colors -struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbb][,ccccc]") -struct ImGuiTextBuffer; // Helper to hold and append into a text buffer (~string builder) - -// Typedefs and Enums/Flags (declared as int for compatibility with old C++, to allow using as flags and to not pollute -// the top of this file) Use your programming IDE "Go to definition" facility on the names of the center columns to find -// the actual flags/enum lists. -typedef unsigned int ImGuiID; // Unique ID used by widgets (typically hashed from a stack of string) -typedef unsigned short ImWchar; // Character for keyboard input/display -typedef int ImGuiCol; // -> enum ImGuiCol_ // Enum: A color identifier for styling -typedef int ImGuiCond; // -> enum ImGuiCond_ // Enum: A condition for Set*() -typedef int ImGuiDataType; // -> enum ImGuiDataType_ // Enum: A primary data type -typedef int ImGuiDir; // -> enum ImGuiDir_ // Enum: A cardinal direction -typedef int ImGuiKey; // -> enum ImGuiKey_ // Enum: A key identifier (ImGui-side enum) -typedef int ImGuiNavInput; // -> enum ImGuiNavInput_ // Enum: An input identifier for navigation -typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor identifier -typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling -typedef int ImDrawCornerFlags; // -> enum ImDrawCornerFlags_ // Flags: for ImDrawList::AddRect*() etc. -typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList -typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas -typedef int ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags -typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: for ColorEdit*(), ColorPicker*() -typedef int ImGuiColumnsFlags; // -> enum ImGuiColumnsFlags_ // Flags: for Columns(), BeginColumns() -typedef int ImGuiConfigFlags; // -> enum ImGuiConfigFlags_ // Flags: for io.ConfigFlags -typedef int ImGuiComboFlags; // -> enum ImGuiComboFlags_ // Flags: for BeginCombo() -typedef int ImGuiDragDropFlags; // -> enum ImGuiDragDropFlags_ // Flags: for *DragDrop*() -typedef int ImGuiFocusedFlags; // -> enum ImGuiFocusedFlags_ // Flags: for IsWindowFocused() -typedef int ImGuiHoveredFlags; // -> enum ImGuiHoveredFlags_ // Flags: for IsItemHovered(), IsWindowHovered() etc. -typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText*() -typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: for Selectable() -typedef int ImGuiTreeNodeFlags; // -> enum ImGuiTreeNodeFlags_ // Flags: for TreeNode*(),CollapsingHeader() -typedef int ImGuiWindowFlags; // -> enum ImGuiWindowFlags_ // Flags: for Begin*() -typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data); -typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); - -// Scalar data types -typedef signed int ImS32; // 32-bit signed integer == int -typedef unsigned int ImU32; // 32-bit unsigned integer (often used to store packed colors) -#if defined(_MSC_VER) && !defined(__clang__) -typedef signed __int64 ImS64; // 64-bit signed integer (pre and post C++11 with Visual Studio) -typedef unsigned __int64 ImU64; // 64-bit unsigned integer (pre and post C++11 with Visual Studio) -#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100) -#include -typedef int64_t ImS64; // 64-bit signed integer (pre C++11) -typedef uint64_t ImU64; // 64-bit unsigned integer (pre C++11) -#else -typedef signed long long ImS64; // 64-bit signed integer (post C++11) -typedef unsigned long long ImU64; // 64-bit unsigned integer (post C++11) -#endif - -// 2D vector (often used to store positions, sizes, etc.) -struct ImVec2 { - float x, y; - ImVec2() { x = y = 0.0f; } - ImVec2(float _x, float _y) - { - x = _x; - y = _y; - } - float operator[](size_t i) const - { - IM_ASSERT(i <= 1); - return (&x)[i]; - } // We very rarely use this [] operator, the assert overhead is fine. -#ifdef IM_VEC2_CLASS_EXTRA - IM_VEC2_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and - // forth between your math types and ImVec2. -#endif -}; - -// 4D vector (often used to store floating-point colors) -struct ImVec4 { - float x, y, z, w; - ImVec4() { x = y = z = w = 0.0f; } - ImVec4(float _x, float _y, float _z, float _w) - { - x = _x; - y = _y; - z = _z; - w = _w; - } -#ifdef IM_VEC4_CLASS_EXTRA - IM_VEC4_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and - // forth between your math types and ImVec4. -#endif -}; - -// Dear ImGui end-user API -// (In a namespace so you can add extra functions in your own separate file. Please don't modify imgui.cpp/.h!) -namespace ImGui -{ -// Context creation and access -// Each context create its own ImFontAtlas by default. You may instance one yourself and pass it to CreateContext() to -// share a font atlas between imgui contexts. All those functions are not reliant on the current context. -IMGUI_API ImGuiContext* CreateContext(ImFontAtlas* shared_font_atlas = NULL); -IMGUI_API void DestroyContext(ImGuiContext* ctx = NULL); // NULL = destroy current context -IMGUI_API ImGuiContext* GetCurrentContext(); -IMGUI_API void SetCurrentContext(ImGuiContext* ctx); -IMGUI_API bool DebugCheckVersionAndDataLayout(const char* version_str, size_t sz_io, size_t sz_style, size_t sz_vec2, - size_t sz_vec4, size_t sz_drawvert); - -// Main -IMGUI_API ImGuiIO& - GetIO(); // access the IO structure (mouse/keyboard/gamepad inputs, time, various configuration options/flags) -IMGUI_API ImGuiStyle& GetStyle(); // access the Style structure (colors, sizes). Always use PushStyleCol(), - // PushStyleVar() to modify style mid-frame. -IMGUI_API void - NewFrame(); // start a new ImGui frame, you can submit any command from this point until Render()/EndFrame(). -IMGUI_API void EndFrame(); // ends the ImGui frame. automatically called by Render(), you likely don't need to call that - // yourself directly. If you don't need to render data (skipping rendering) you may call - // EndFrame() but you'll have wasted CPU already! If you don't need to render, better to not - // create any imgui windows and not call NewFrame() at all! -IMGUI_API void Render(); // ends the ImGui frame, finalize the draw data. (Obsolete: optionally call - // io.RenderDrawListsFn if set. Nowadays, prefer calling your render function yourself.) -IMGUI_API ImDrawData* - GetDrawData(); // valid after Render() and until the next call to NewFrame(). this is what you have to render. - // (Obsolete: this used to be passed to your io.RenderDrawListsFn() function.) - -// Demo, Debug, Information -IMGUI_API void ShowDemoWindow( - bool* p_open = NULL); // create demo/test window (previously called ShowTestWindow). demonstrate most ImGui features. - // call this to learn about the library! try to make it always available in your application! -IMGUI_API void ShowMetricsWindow( - bool* p_open = NULL); // create metrics window. display ImGui internals: draw commands (with individual draw calls and - // vertices), window list, basic internal state, etc. -IMGUI_API void ShowStyleEditor( - ImGuiStyle* ref = NULL); // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to - // compare to, revert to and save to (else it uses the default style) -IMGUI_API bool ShowStyleSelector( - const char* label); // add style selector block (not a window), essentially a combo listing the default styles. -IMGUI_API void ShowFontSelector( - const char* label); // add font selector block (not a window), essentially a combo listing the loaded fonts. -IMGUI_API void ShowUserGuide(); // add basic help/info block (not a window): how to manipulate ImGui as a end-user - // (mouse/keyboard controls). -IMGUI_API const char* GetVersion(); // get the compiled version string e.g. "1.23" - -// Styles -IMGUI_API void StyleColorsDark(ImGuiStyle* dst = NULL); // new, recommended style (default) -IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL); // classic imgui style -IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL); // best used with borders and a custom, thicker font - -// Windows -// (Begin = push window to the stack and start appending to it. End = pop window from the stack. You may append multiple -// times to the same window during the same frame) Begin()/BeginChild() return false to indicate the window being -// collapsed or fully clipped, so you may early out and omit submitting anything to the window. You need to always call -// a matching End()/EndChild() for a Begin()/BeginChild() call, regardless of its return value (this is due to legacy -// reason and is inconsistent with BeginMenu/EndMenu, BeginPopup/EndPopup and other functions where the End call should -// only be called if the corresponding Begin function returned true.) Passing 'bool* p_open != NULL' shows a close -// widget in the upper-right corner of the window, which when clicking will set the boolean to false. Use child windows -// to introduce independent scrolling/clipping regions within a host window. Child windows can embed their own child. -IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); -IMGUI_API void End(); -IMGUI_API bool BeginChild( - const char* str_id, const ImVec2& size = ImVec2(0, 0), bool border = false, - ImGuiWindowFlags flags = - 0); // Begin a scrolling region. size==0.0f: use remaining window size, size<0.0f: use remaining window size minus - // abs(size). size>0.0f: fixed size. each axis can use a different mode, e.g. ImVec2(0,400). -IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0, 0), bool border = false, - ImGuiWindowFlags flags = 0); -IMGUI_API void EndChild(); - -// Windows Utilities -IMGUI_API bool IsWindowAppearing(); -IMGUI_API bool IsWindowCollapsed(); -IMGUI_API bool IsWindowFocused(ImGuiFocusedFlags flags = 0); // is current window focused? or its root/child, depending - // on flags. see flags for options. -IMGUI_API bool IsWindowHovered( - ImGuiHoveredFlags flags = - 0); // is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. NB: If you - // are trying to check whether your mouse should be dispatched to imgui or to your app, you should use the - // 'io.WantCaptureMouse' boolean for that! Please read the FAQ! -IMGUI_API ImDrawList* - GetWindowDrawList(); // get draw list associated to the window, to append your own drawing primitives -IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (useful if you want to do your own - // drawing via the DrawList API) -IMGUI_API ImVec2 GetWindowSize(); // get current window size -IMGUI_API float GetWindowWidth(); // get current window width (shortcut for GetWindowSize().x) -IMGUI_API float GetWindowHeight(); // get current window height (shortcut for GetWindowSize().y) -IMGUI_API ImVec2 GetContentRegionMax(); // current content boundaries (typically window boundaries including scrolling, - // or current column boundaries), in windows coordinates -IMGUI_API ImVec2 GetContentRegionAvail(); // == GetContentRegionMax() - GetCursorPos() -IMGUI_API float GetContentRegionAvailWidth(); // -IMGUI_API ImVec2 GetWindowContentRegionMin(); // content boundaries min (roughly (0,0)-Scroll), in window coordinates -IMGUI_API ImVec2 GetWindowContentRegionMax(); // content boundaries max (roughly (0,0)+Size-Scroll) where Size can be - // override with SetNextWindowContentSize(), in window coordinates -IMGUI_API float GetWindowContentRegionWidth(); // - -IMGUI_API void SetNextWindowPos( - const ImVec2& pos, ImGuiCond cond = 0, - const ImVec2& pivot = ImVec2( - 0, 0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc. -IMGUI_API void SetNextWindowSize( - const ImVec2& size, - ImGuiCond cond = 0); // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin() -IMGUI_API void SetNextWindowSizeConstraints( - const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback = NULL, - void* custom_callback_data = NULL); // set next window size limits. use -1,-1 on either X/Y axis to preserve the - // current size. Use callback to apply non-trivial programmatic constraints. -IMGUI_API void SetNextWindowContentSize( - const ImVec2& - size); // set next window content size (~ enforce the range of scrollbars). not including window decorations (title - // bar, menu bar, etc.). set an axis to 0.0f to leave it automatic. call before Begin() -IMGUI_API void SetNextWindowCollapsed(bool collapsed, - ImGuiCond cond = 0); // set next window collapsed state. call before Begin() -IMGUI_API void SetNextWindowFocus(); // set next window to be focused / front-most. call before Begin() -IMGUI_API void SetNextWindowBgAlpha( - float alpha); // set next window background color alpha. helper to easily modify ImGuiCol_WindowBg/ChildBg/PopupBg. -IMGUI_API void SetWindowPos( - const ImVec2& pos, - ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using - // SetNextWindowPos(), as this may incur tearing and side-effects. -IMGUI_API void SetWindowSize( - const ImVec2& size, - ImGuiCond cond = - 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0,0) to force an - // auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. -IMGUI_API void SetWindowCollapsed( - bool collapsed, - ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). -IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / front-most. prefer using - // SetNextWindowFocus(). -IMGUI_API void SetWindowFontScale( - float scale); // set font scale. Adjust IO.FontGlobalScale if you want to scale all windows -IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. -IMGUI_API void SetWindowSize( - const char* name, const ImVec2& size, - ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. -IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, - ImGuiCond cond = 0); // set named window collapsed state -IMGUI_API void SetWindowFocus( - const char* name); // set named window to be focused / front-most. use NULL to remove focus. - -// Windows Scrolling -IMGUI_API float GetScrollX(); // get scrolling amount [0..GetScrollMaxX()] -IMGUI_API float GetScrollY(); // get scrolling amount [0..GetScrollMaxY()] -IMGUI_API float GetScrollMaxX(); // get maximum scrolling amount ~~ ContentSize.X - WindowSize.X -IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.Y - WindowSize.Y -IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0..GetScrollMaxX()] -IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()] -IMGUI_API void SetScrollHere( - float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: - // top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, - // consider using SetItemDefaultFocus() instead. -IMGUI_API void SetScrollFromPosY( - float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() - // or GetCursorStartPos()+offset to get valid positions. - -// Parameters stacks (shared) -IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font -IMGUI_API void PopFont(); -IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); -IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); -IMGUI_API void PopStyleColor(int count = 1); -IMGUI_API void PushStyleVar(ImGuiStyleVar idx, float val); -IMGUI_API void PushStyleVar(ImGuiStyleVar idx, const ImVec2& val); -IMGUI_API void PopStyleVar(int count = 1); -IMGUI_API const ImVec4& GetStyleColorVec4( - ImGuiCol idx); // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), - // otherwise use GetColorU32() to get style color with style alpha baked in. -IMGUI_API ImFont* GetFont(); // get current font -IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied -IMGUI_API ImVec2 - GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API -IMGUI_API ImU32 GetColorU32( - ImGuiCol idx, - float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier -IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied -IMGUI_API ImU32 GetColorU32(ImU32 col); // retrieve given color with style alpha applied - -// Parameters stacks (current window) -IMGUI_API void PushItemWidth( - float item_width); // width of items for the common item+label case, pixels. 0.0f = default to ~2/3 of windows width, - // >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -1.0f always align - // width to the right side) -IMGUI_API void PopItemWidth(); -IMGUI_API float CalcItemWidth(); // width of item given pushed settings and current cursor position -IMGUI_API void PushTextWrapPos( - float wrap_pos_x = 0.0f); // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or - // column); > 0.0f: wrap at 'wrap_pos_x' position in window local space -IMGUI_API void PopTextWrapPos(); -IMGUI_API void PushAllowKeyboardFocus(bool allow_keyboard_focus); // allow focusing using TAB/Shift-TAB, enabled by - // default but you can disable it for certain widgets -IMGUI_API void PopAllowKeyboardFocus(); -IMGUI_API void PushButtonRepeat( - bool repeat); // in 'repeat' mode, Button*() functions return repeated true in a typematic manner (using - // io.KeyRepeatDelay/io.KeyRepeatRate setting). Note that you can call IsItemActive() after any Button() - // to tell if the button is held in the current frame. -IMGUI_API void PopButtonRepeat(); - -// Cursor / Layout -IMGUI_API void Separator(); // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this - // becomes a vertical separator. -IMGUI_API void SameLine(float pos_x = 0.0f, - float spacing_w = -1.0f); // call between widgets or groups to layout them horizontally -IMGUI_API void NewLine(); // undo a SameLine() -IMGUI_API void Spacing(); // add vertical spacing -IMGUI_API void Dummy(const ImVec2& size); // add a dummy item of given size -IMGUI_API void Indent( - float indent_w = 0.0f); // move content position toward the right, by style.IndentSpacing or indent_w if != 0 -IMGUI_API void Unindent( - float indent_w = 0.0f); // move content position back to the left, by style.IndentSpacing or indent_w if != 0 -IMGUI_API void BeginGroup(); // lock horizontal starting position + capture group bounding box into one "item" (so you - // can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) -IMGUI_API void EndGroup(); -IMGUI_API ImVec2 GetCursorPos(); // cursor position is relative to window position -IMGUI_API float GetCursorPosX(); // " -IMGUI_API float GetCursorPosY(); // " -IMGUI_API void SetCursorPos(const ImVec2& local_pos); // " -IMGUI_API void SetCursorPosX(float x); // " -IMGUI_API void SetCursorPosY(float y); // " -IMGUI_API ImVec2 GetCursorStartPos(); // initial cursor position -IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute screen coordinates [0..io.DisplaySize] (useful to - // work with ImDrawList API) -IMGUI_API void SetCursorScreenPos( - const ImVec2& screen_pos); // cursor position in absolute screen coordinates [0..io.DisplaySize] -IMGUI_API void - AlignTextToFramePadding(); // vertically align upcoming text baseline to FramePadding.y so that it will align properly - // to regularly framed items (call if you have text on a line before a framed item) -IMGUI_API float GetTextLineHeight(); // ~ FontSize -IMGUI_API float GetTextLineHeightWithSpacing(); // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 - // consecutive lines of text) -IMGUI_API float GetFrameHeight(); // ~ FontSize + style.FramePadding.y * 2 -IMGUI_API float GetFrameHeightWithSpacing(); // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in - // pixels between 2 consecutive lines of framed widgets) - -// ID stack/scopes -// Read the FAQ for more details about how ID are handled in dear imgui. If you are creating widgets in a loop you most -// likely want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them. -// You can also use the "##foobar" syntax within widget label to distinguish them from each others. -// In this header file we use the "label"/"name" terminology to denote a string that will be displayed and used as an -// ID, whereas "str_id" denote a string that is only used as an ID and not aimed to be displayed. -IMGUI_API void PushID(const char* str_id); // push identifier into the ID stack. IDs are hash of the entire stack! -IMGUI_API void PushID(const char* str_id_begin, const char* str_id_end); -IMGUI_API void PushID(const void* ptr_id); -IMGUI_API void PushID(int int_id); -IMGUI_API void PopID(); -IMGUI_API ImGuiID GetID(const char* str_id); // calculate unique ID (hash of whole ID stack + given parameter). e.g. if - // you want to query into ImGuiStorage yourself -IMGUI_API ImGuiID GetID(const char* str_id_begin, const char* str_id_end); -IMGUI_API ImGuiID GetID(const void* ptr_id); - -// Widgets: Text -IMGUI_API void TextUnformatted( - const char* text, - const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't - // require null terminated string if 'text_end' is specified, B) it's faster, no memory - // copy is done, no buffer size limits, recommended for long chunks of text. -IMGUI_API void Text(const char* fmt, ...) IM_FMTARGS(1); // simple formatted text -IMGUI_API void TextV(const char* fmt, va_list args) IM_FMTLIST(1); -IMGUI_API void TextColored(const ImVec4& col, const char* fmt, ...) - IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor(); -IMGUI_API void TextColoredV(const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(2); -IMGUI_API void TextDisabled(const char* fmt, ...) - IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); - // PopStyleColor(); -IMGUI_API void TextDisabledV(const char* fmt, va_list args) IM_FMTLIST(1); -IMGUI_API void TextWrapped(const char* fmt, ...) - IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on - // an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set - // a size using SetNextWindowSize(). -IMGUI_API void TextWrappedV(const char* fmt, va_list args) IM_FMTLIST(1); -IMGUI_API void LabelText(const char* label, const char* fmt, ...) - IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets -IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args) IM_FMTLIST(2); -IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() -IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); - -// Widgets: Main -// Most widgets return true when the value has been changed or when pressed/selected -IMGUI_API bool Button(const char* label, const ImVec2& size = ImVec2(0, 0)); // button -IMGUI_API bool SmallButton(const char* label); // button with FramePadding=(0,0) to easily embed within text -IMGUI_API bool InvisibleButton( - const char* str_id, const ImVec2& size); // button behavior without the visuals, useful to build custom behaviors - // using the public api (along with IsItemActive, IsItemHovered, etc.) -IMGUI_API bool ArrowButton(const char* str_id, ImGuiDir dir); // square button with an arrow shape -IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), - const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& tint_col = ImVec4(1, 1, 1, 1), - const ImVec4& border_col = ImVec4(0, 0, 0, 0)); -IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), - const ImVec2& uv1 = ImVec2(1, 1), int frame_padding = -1, - const ImVec4& bg_col = ImVec4(0, 0, 0, 0), - const ImVec4& tint_col = ImVec4( - 1, 1, 1, 1)); // <0 frame_padding uses default frame padding settings. 0 for no padding -IMGUI_API bool Checkbox(const char* label, bool* v); -IMGUI_API bool CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value); -IMGUI_API bool RadioButton(const char* label, - bool active); // use with e.g. if (RadioButton("one", my_value==1)) { my_value = 1; } -IMGUI_API bool RadioButton(const char* label, int* v, - int v_button); // shortcut to handle the above pattern when value is an integer -IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-1, 0), const char* overlay = NULL); -IMGUI_API void Bullet(); // draw a small circle and keep the cursor on the same line. advance cursor x position by - // GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses - -// Widgets: Combo Box -// The new BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it, by -// creating e.g. Selectable() items. The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept -// available for convenience purpose. -IMGUI_API bool BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0); -IMGUI_API void EndCombo(); // only call EndCombo() if BeginCombo() returns true! -IMGUI_API bool Combo(const char* label, int* current_item, const char* const items[], int items_count, - int popup_max_height_in_items = -1); -IMGUI_API bool Combo(const char* label, int* current_item, const char* items_separated_by_zeros, - int popup_max_height_in_items = - -1); // Separate items with \0 within a string, end item-list with \0\0. e.g. "One\0Two\0Three\0" -IMGUI_API bool Combo(const char* label, int* current_item, - bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, - int popup_max_height_in_items = -1); - -// Widgets: Drags (tip: ctrl+click on a drag box to input with keyboard. manually input values aren't clamped, can go -// off-bounds) For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' -// function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that -// are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x -// Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. -// "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. Speed are per-pixel of mouse -// movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, -// minimum speed is Max(v_speed, minimum_step_at_given_precision). -IMGUI_API bool DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, - const char* format = "%.3f", float power = 1.0f); // If v_min >= v_max we have no bound -IMGUI_API bool DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, - const char* format = "%.3f", float power = 1.0f); -IMGUI_API bool DragFloat3(const char* label, float v[3], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, - const char* format = "%.3f", float power = 1.0f); -IMGUI_API bool DragFloat4(const char* label, float v[4], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, - const char* format = "%.3f", float power = 1.0f); -IMGUI_API bool DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed = 1.0f, - float v_min = 0.0f, float v_max = 0.0f, const char* format = "%.3f", - const char* format_max = NULL, float power = 1.0f); -IMGUI_API bool DragInt(const char* label, int* v, float v_speed = 1.0f, int v_min = 0, int v_max = 0, - const char* format = "%d"); // If v_min >= v_max we have no bound -IMGUI_API bool DragInt2(const char* label, int v[2], float v_speed = 1.0f, int v_min = 0, int v_max = 0, - const char* format = "%d"); -IMGUI_API bool DragInt3(const char* label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, - const char* format = "%d"); -IMGUI_API bool DragInt4(const char* label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, - const char* format = "%d"); -IMGUI_API bool DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, - int v_min = 0, int v_max = 0, const char* format = "%d", const char* format_max = NULL); -IMGUI_API bool DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min = NULL, - const void* v_max = NULL, const char* format = NULL, float power = 1.0f); -IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, - const void* v_min = NULL, const void* v_max = NULL, const char* format = NULL, - float power = 1.0f); - -// Widgets: Sliders (tip: ctrl+click on a slider to input with keyboard. manually input values aren't clamped, can go -// off-bounds) Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display -// precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. -IMGUI_API bool SliderFloat( - const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", - float power = 1.0f); // adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit - // display. Use power!=1.0 for power curve sliders -IMGUI_API bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format = "%.3f", - float power = 1.0f); -IMGUI_API bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format = "%.3f", - float power = 1.0f); -IMGUI_API bool SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format = "%.3f", - float power = 1.0f); -IMGUI_API bool SliderAngle(const char* label, float* v_rad, float v_degrees_min = -360.0f, - float v_degrees_max = +360.0f); -IMGUI_API bool SliderInt(const char* label, int* v, int v_min, int v_max, const char* format = "%d"); -IMGUI_API bool SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format = "%d"); -IMGUI_API bool SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format = "%d"); -IMGUI_API bool SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format = "%d"); -IMGUI_API bool SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, - const char* format = NULL, float power = 1.0f); -IMGUI_API bool SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, - const void* v_max, const char* format = NULL, float power = 1.0f); -IMGUI_API bool VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, - const char* format = "%.3f", float power = 1.0f); -IMGUI_API bool VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, - const char* format = "%d"); -IMGUI_API bool VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, - const void* v_max, const char* format = NULL, float power = 1.0f); - -// Widgets: Input with Keyboard -// If you want to use InputText() with a dynamic string type such as std::string or your own, see misc/stl/imgui_stl.h -IMGUI_API bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, - ImGuiInputTextCallback callback = NULL, void* user_data = NULL); -IMGUI_API bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0, 0), - ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, - void* user_data = NULL); -IMGUI_API bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, - const char* format = "%.3f", ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputFloat2(const char* label, float v[2], const char* format = "%.3f", - ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputFloat3(const char* label, float v[3], const char* format = "%.3f", - ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputFloat4(const char* label, float v[4], const char* format = "%.3f", - ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100, - ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputDouble(const char* label, double* v, double step = 0.0f, double step_fast = 0.0f, - const char* format = "%.6f", ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputScalar(const char* label, ImGuiDataType data_type, void* v, const void* step = NULL, - const void* step_fast = NULL, const char* format = NULL, - ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, - const void* step = NULL, const void* step_fast = NULL, const char* format = NULL, - ImGuiInputTextFlags extra_flags = 0); - -// Widgets: Color Editor/Picker (tip: the ColorEdit* functions have a little colored preview square that can be -// left-clicked to open a picker, and right-clicked to open an option menu.) Note that a 'float v[X]' function argument -// is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be -// accessible. You can the pass the address of a first float element out of a contiguous structure, e.g. &myvector.x -IMGUI_API bool ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); -IMGUI_API bool ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0); -IMGUI_API bool ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0); -IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, - const float* ref_col = NULL); -IMGUI_API bool ColorButton( - const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, - ImVec2 size = ImVec2(0, 0)); // display a colored square/button, hover for details, return true when pressed. -IMGUI_API void SetColorEditOptions( - ImGuiColorEditFlags flags); // initialize current options (generally on application startup) if you want to select a - // default format, picker type, etc. User will be able to change many settings, unless you - // pass the _NoOptions flag to your calls. - -// Widgets: Trees -// TreeNode functions return true when the node is open, in which case you need to also call TreePop() when you are -// finished displaying the tree node contents. -IMGUI_API bool TreeNode(const char* label); -IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) - IM_FMTARGS(2); // helper variation to completely decorelate the id from the displayed string. Read the FAQ about why - // and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). -IMGUI_API bool TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2); // " -IMGUI_API bool TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2); -IMGUI_API bool TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2); -IMGUI_API bool TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0); -IMGUI_API bool TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); -IMGUI_API bool TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3); -IMGUI_API bool TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); -IMGUI_API bool TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3); -IMGUI_API void TreePush(const char* str_id); // ~ Indent()+PushId(). Already called by TreeNode() when returning true, - // but you can call TreePush/TreePop yourself if desired. -IMGUI_API void TreePush(const void* ptr_id = NULL); // " -IMGUI_API void TreePop(); // ~ Unindent()+PopId() -IMGUI_API void TreeAdvanceToLabelPos(); // advance cursor x position by GetTreeNodeToLabelSpacing() -IMGUI_API float GetTreeNodeToLabelSpacing(); // horizontal distance preceding label when using TreeNode*() or Bullet() - // == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode -IMGUI_API void SetNextTreeNodeOpen(bool is_open, ImGuiCond cond = 0); // set next TreeNode/CollapsingHeader open state. -IMGUI_API bool CollapsingHeader( - const char* label, ImGuiTreeNodeFlags flags = 0); // if returning 'true' the header is open. doesn't indent nor push - // on ID stack. user doesn't have to call TreePop(). -IMGUI_API bool CollapsingHeader(const char* label, bool* p_open, - ImGuiTreeNodeFlags flags = 0); // when 'p_open' isn't NULL, display an additional small - // close button on upper right of the header - -// Widgets: Selectables -IMGUI_API bool Selectable( - const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, - const ImVec2& size = - ImVec2(0, 0)); // "bool selected" carry the selection state (read-only). Selectable() is clicked is returns true so - // you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. - // size.y==0.0: use label height, size.y>0.0: specify height -IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, - const ImVec2& size = ImVec2(0, 0)); // "bool* p_selected" point to the selection state - // (read-write), as a convenient helper. - -// Widgets: List Boxes -IMGUI_API bool ListBox(const char* label, int* current_item, const char* const items[], int items_count, - int height_in_items = -1); -IMGUI_API bool ListBox(const char* label, int* current_item, - bool (*items_getter)(void* data, int idx, const char** out_text), void* data, int items_count, - int height_in_items = -1); -IMGUI_API bool ListBoxHeader( - const char* label, const ImVec2& size = ImVec2( - 0, 0)); // use if you want to reimplement ListBox() will custom data or interactions. if the - // function return true, you can output elements then call ListBoxFooter() afterwards. -IMGUI_API bool ListBoxHeader(const char* label, int items_count, int height_in_items = -1); // " -IMGUI_API void - ListBoxFooter(); // terminate the scrolling region. only call ListBoxFooter() if ListBoxHeader() returned true! - -// Widgets: Data Plotting -IMGUI_API void PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, - const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, - ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); -IMGUI_API void PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, - int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, - float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0)); -IMGUI_API void PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, - const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, - ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float)); -IMGUI_API void PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, - int values_count, int values_offset = 0, const char* overlay_text = NULL, - float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0)); - -// Widgets: Value() Helpers. Output single value in "name: value" format (tip: freely declare more in your code to -// handle your types. you can add functions to the ImGui namespace) -IMGUI_API void Value(const char* prefix, bool b); -IMGUI_API void Value(const char* prefix, int v); -IMGUI_API void Value(const char* prefix, unsigned int v); -IMGUI_API void Value(const char* prefix, float v, const char* float_format = NULL); - -// Widgets: Menus -IMGUI_API bool BeginMainMenuBar(); // create and append to a full screen menu-bar. -IMGUI_API void EndMainMenuBar(); // only call EndMainMenuBar() if BeginMainMenuBar() returns true! -IMGUI_API bool - BeginMenuBar(); // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window). -IMGUI_API void EndMenuBar(); // only call EndMenuBar() if BeginMenuBar() returns true! -IMGUI_API bool BeginMenu(const char* label, - bool enabled = true); // create a sub-menu entry. only call EndMenu() if this returns true! -IMGUI_API void EndMenu(); // only call EndMenu() if BeginMenu() returns true! -IMGUI_API bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, - bool enabled = true); // return true when activated. shortcuts are displayed for convenience but - // not processed by ImGui at the moment -IMGUI_API bool MenuItem(const char* label, const char* shortcut, bool* p_selected, - bool enabled = true); // return true when activated + toggle (*p_selected) if p_selected != NULL - -// Tooltips -IMGUI_API void - BeginTooltip(); // begin/append a tooltip window. to create full-featured tooltip (with any kind of items). -IMGUI_API void EndTooltip(); -IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS( - 1); // set a text-only tooltip, typically use with ImGui::IsItemHovered(). overidde any previous call to SetTooltip(). -IMGUI_API void SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1); - -// Popups -IMGUI_API void OpenPopup( - const char* str_id); // call to mark popup as open (don't call every frame!). popups are closed when user click - // outside, or if CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. By - // default, Selectable()/MenuItem() are calling CloseCurrentPopup(). Popup identifiers are - // relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). -IMGUI_API bool BeginPopup(const char* str_id, - ImGuiWindowFlags flags = 0); // return true if the popup is open, and you can start outputting - // to it. only call EndPopup() if BeginPopup() returns true! -IMGUI_API bool BeginPopupContextItem( - const char* str_id = NULL, - int mouse_button = 1); // helper to open and begin popup when clicked on last item. if you can pass a NULL str_id only - // if the previous item had an id. If you want to use that on a non-interactive item such as - // Text() you need to pass in an explicit ID here. read comments in .cpp! -IMGUI_API bool BeginPopupContextWindow( - const char* str_id = NULL, int mouse_button = 1, - bool also_over_items = true); // helper to open and begin popup when clicked on current window. -IMGUI_API bool BeginPopupContextVoid( - const char* str_id = NULL, - int mouse_button = 1); // helper to open and begin popup when clicked in void (where there are no imgui windows). -IMGUI_API bool BeginPopupModal( - const char* name, bool* p_open = NULL, - ImGuiWindowFlags flags = 0); // modal dialog (regular window with title bar, block interactions behind the modal - // window, can't close the modal window by clicking outside) -IMGUI_API void EndPopup(); // only call EndPopup() if BeginPopupXXX() returns true! -IMGUI_API bool OpenPopupOnItemClick( - const char* str_id = NULL, - int mouse_button = 1); // helper to open popup when clicked on last item. return true when just opened. -IMGUI_API bool IsPopupOpen(const char* str_id); // return true if the popup is open -IMGUI_API void CloseCurrentPopup(); // close the popup we have begin-ed into. clicking on a MenuItem or Selectable - // automatically close the current popup. - -// Columns -// You can also use SameLine(pos_x) for simplified columns. The columns API is still work-in-progress and rather -// lacking. -IMGUI_API void Columns(int count = 1, const char* id = NULL, bool border = true); -IMGUI_API void NextColumn(); // next column, defaults to current row or next row if the current row is finished -IMGUI_API int GetColumnIndex(); // get current column index -IMGUI_API float GetColumnWidth(int column_index = -1); // get column width (in pixels). pass -1 to use current column -IMGUI_API void SetColumnWidth(int column_index, - float width); // set column width (in pixels). pass -1 to use current column -IMGUI_API float GetColumnOffset( - int column_index = -1); // get position of column line (in pixels, from the left side of the contents region). pass -1 - // to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f -IMGUI_API void SetColumnOffset(int column_index, - float offset_x); // set position of column line (in pixels, from the left side of the - // contents region). pass -1 to use current column -IMGUI_API int GetColumnsCount(); - -// Logging/Capture: all text output from interface is captured to tty/file/clipboard. By default, tree nodes are -// automatically opened during logging. -IMGUI_API void LogToTTY(int max_depth = -1); // start logging to tty -IMGUI_API void LogToFile(int max_depth = -1, const char* filename = NULL); // start logging to file -IMGUI_API void LogToClipboard(int max_depth = -1); // start logging to OS clipboard -IMGUI_API void LogFinish(); // stop logging (close file, etc.) -IMGUI_API void LogButtons(); // helper to display buttons for logging to tty/file/clipboard -IMGUI_API void LogText(const char* fmt, ...) IM_FMTARGS(1); // pass text data straight to log (without being displayed) - -// Drag and Drop -// [BETA API] Missing Demo code. API may evolve. -IMGUI_API bool BeginDragDropSource( - ImGuiDragDropFlags flags = 0); // call when the current item is active. If this return true, you can call - // SetDragDropPayload() + EndDragDropSource() -IMGUI_API bool SetDragDropPayload( - const char* type, const void* data, size_t size, - ImGuiCond cond = 0); // type is a user defined string of maximum 32 characters. Strings starting with '_' are reserved - // for dear imgui internal types. Data is copied and held by imgui. -IMGUI_API void EndDragDropSource(); // only call EndDragDropSource() if BeginDragDropSource() returns true! -IMGUI_API bool BeginDragDropTarget(); // call after submitting an item that may receive an item. If this returns true, - // you can call AcceptDragDropPayload() + EndDragDropTarget() -IMGUI_API const ImGuiPayload* AcceptDragDropPayload( - const char* type, - ImGuiDragDropFlags flags = 0); // accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set - // you can peek into the payload before the mouse button is released. -IMGUI_API void EndDragDropTarget(); // only call EndDragDropTarget() if BeginDragDropTarget() returns true! - -// Clipping -IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, - bool intersect_with_current_clip_rect); -IMGUI_API void PopClipRect(); - -// Focus, Activation -// (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable, to make your -// code more forward compatible when navigation branch is merged) -IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. Please use instead of "if - // (IsWindowAppearing()) SetScrollHere()" to signify "default item". -IMGUI_API void SetKeyboardFocusHere( - int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple - // component widget. Use -1 to access previous widget. - -// Utilities -// See Demo Window under "Widgets->Querying Status" for an interactive visualization of many of those functions. -IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered? (and usable, aka not blocked by - // a popup, etc.). See ImGuiHoveredFlags for more options. -IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited. This will - // continuously return true while holding mouse button on an item. Items that don't - // interact will always return false) -IMGUI_API bool IsItemFocused(); // is the last item focused for keyboard/gamepad navigation? -IMGUI_API bool IsItemClicked(int mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on) == - // IsMouseClicked(mouse_button) && IsItemHovered() -IMGUI_API bool IsItemVisible(); // is the last item visible? (items may be out of sight because of clipping/scrolling) -IMGUI_API bool IsItemEdited(); // did the last item modify its underlying value this frame? or was pressed? This is - // generally the same as the "bool" return value of many widgets. -IMGUI_API bool IsItemDeactivated(); // was the last item just made inactive (item was previously active). Useful for - // Undo/Redo patterns with widgets that requires continuous editing. -IMGUI_API bool IsItemDeactivatedAfterEdit(); // was the last item just made inactive and made a value change when it was - // active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with - // widgets that requires continuous editing. Note that you may get false - // positives (some widgets such as Combo()/ListBox()/Selectable() will - // return true even when clicking an already selected item). -IMGUI_API bool IsAnyItemHovered(); -IMGUI_API bool IsAnyItemActive(); -IMGUI_API bool IsAnyItemFocused(); -IMGUI_API ImVec2 GetItemRectMin(); // get bounding rectangle of last item, in screen space -IMGUI_API ImVec2 GetItemRectMax(); // " -IMGUI_API ImVec2 GetItemRectSize(); // get size of last item, in screen space -IMGUI_API void SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent item. sometimes useful with - // invisible buttons, selectables, etc. to catch unused area. -IMGUI_API bool IsRectVisible( - const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. -IMGUI_API bool IsRectVisible(const ImVec2& rect_min, - const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. - // to perform coarse clipping on user's side. -IMGUI_API double GetTime(); -IMGUI_API int GetFrameCount(); -IMGUI_API ImDrawList* - GetOverlayDrawList(); // this draw list will be the last rendered one, useful to quickly draw overlays shapes/text -IMGUI_API ImDrawListSharedData* GetDrawListSharedData(); // you may use this when creating your own ImDrawList instances -IMGUI_API const char* GetStyleColorName(ImGuiCol idx); -IMGUI_API void SetStateStorage(ImGuiStorage* storage); // replace current window storage with our own (if you want to - // manipulate it yourself, typically clear subsection of it) -IMGUI_API ImGuiStorage* GetStateStorage(); -IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, - float wrap_width = -1.0f); -IMGUI_API void CalcListClipping( - int items_count, float items_height, int* out_items_display_start, - int* out_items_display_end); // calculate coarse clipping for large list of evenly sized items. Prefer using the - // ImGuiListClipper higher-level helper if you can. - -IMGUI_API bool BeginChildFrame(ImGuiID id, const ImVec2& size, - ImGuiWindowFlags flags = 0); // helper to create a child window / scrolling region that - // looks like a normal widget frame -IMGUI_API void EndChildFrame(); // always call EndChildFrame() regardless of BeginChildFrame() return values (which - // indicates a collapsed/clipped window) - -IMGUI_API ImVec4 ColorConvertU32ToFloat4(ImU32 in); -IMGUI_API ImU32 ColorConvertFloat4ToU32(const ImVec4& in); -IMGUI_API void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v); -IMGUI_API void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b); - -// Inputs -IMGUI_API int GetKeyIndex(ImGuiKey imgui_key); // map ImGuiKey_* values into user's key index. == io.KeyMap[key] -IMGUI_API bool IsKeyDown( - int user_key_index); // is key being held. == io.KeysDown[user_key_index]. note that imgui doesn't know the semantic - // of each entry of io.KeysDown[]. Use your own indices/enums according to how your - // backend/engine stored them into io.KeysDown[]! -IMGUI_API bool IsKeyPressed(int user_key_index, - bool repeat = true); // was key pressed (went from !Down to Down). if repeat=true, uses - // io.KeyRepeatDelay / KeyRepeatRate -IMGUI_API bool IsKeyReleased(int user_key_index); // was key released (went from Down to !Down).. -IMGUI_API int GetKeyPressedAmount(int key_index, float repeat_delay, - float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but - // might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate -IMGUI_API bool IsMouseDown(int button); // is mouse button held (0=left, 1=right, 2=middle) -IMGUI_API bool IsAnyMouseDown(); // is any mouse button held -IMGUI_API bool IsMouseClicked( - int button, bool repeat = false); // did mouse button clicked (went from !Down to Down) (0=left, 1=right, 2=middle) -IMGUI_API bool IsMouseDoubleClicked(int button); // did mouse button double-clicked. a double-click returns false in - // IsMouseClicked(). uses io.MouseDoubleClickTime. -IMGUI_API bool IsMouseReleased(int button); // did mouse button released (went from Down to !Down) -IMGUI_API bool IsMouseDragging( - int button = 0, - float lock_threshold = -1.0f); // is mouse dragging. if lock_threshold < -1.0f uses io.MouseDraggingThreshold -IMGUI_API bool IsMouseHoveringRect( - const ImVec2& r_min, const ImVec2& r_max, - bool clip = true); // is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, - // but disregarding of other consideration of focus/window ordering/popup-block. -IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // -IMGUI_API ImVec2 - GetMousePos(); // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls -IMGUI_API ImVec2 GetMousePosOnOpeningCurrentPopup(); // retrieve backup of mouse position at the time of opening popup - // we have BeginPopup() into -IMGUI_API ImVec2 GetMouseDragDelta( - int button = 0, float lock_threshold = - -1.0f); // dragging amount since clicking. if lock_threshold < -1.0f uses io.MouseDraggingThreshold -IMGUI_API void ResetMouseDragDelta(int button = 0); // -IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired cursor type, reset in ImGui::NewFrame(), this is updated - // during the frame. valid before Render(). If you use software rendering - // by setting io.MouseDrawCursor ImGui will render those for you -IMGUI_API void SetMouseCursor(ImGuiMouseCursor type); // set desired cursor type -IMGUI_API void CaptureKeyboardFromApp( - bool capture = true); // manually override io.WantCaptureKeyboard flag next frame (said flag is entirely left for your - // application to handle). e.g. force capture keyboard when your widget is being hovered. -IMGUI_API void CaptureMouseFromApp(bool capture = true); // manually override io.WantCaptureMouse flag next frame (said - // flag is entirely left for your application to handle). - -// Clipboard Utilities (also see the LogToClipboard() function to capture or output text data to the clipboard) -IMGUI_API const char* GetClipboardText(); -IMGUI_API void SetClipboardText(const char* text); - -// Settings/.Ini Utilities -// The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini"). -// Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving -// manually. -IMGUI_API void LoadIniSettingsFromDisk( - const char* ini_filename); // call after CreateContext() and before the first call to NewFrame(). NewFrame() - // automatically calls LoadIniSettingsFromDisk(io.IniFilename). -IMGUI_API void LoadIniSettingsFromMemory( - const char* ini_data, size_t ini_size = 0); // call after CreateContext() and before the first call to NewFrame() to - // provide .ini data from your own data source. -IMGUI_API void SaveIniSettingsToDisk(const char* ini_filename); -IMGUI_API const char* SaveIniSettingsToMemory( - size_t* out_ini_size = - NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when - // io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings. - -// Memory Utilities -// All those functions are not reliant on the current context. -// If you reload the contents of imgui.cpp at runtime, you may need to call SetCurrentContext() + -// SetAllocatorFunctions() again. -IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), - void (*free_func)(void* ptr, void* user_data), void* user_data = NULL); -IMGUI_API void* MemAlloc(size_t size); -IMGUI_API void MemFree(void* ptr); - -} // namespace ImGui - -// Flags for ImGui::Begin() -enum ImGuiWindowFlags_ { - ImGuiWindowFlags_None = 0, - ImGuiWindowFlags_NoTitleBar = 1 << 0, // Disable title-bar - ImGuiWindowFlags_NoResize = 1 << 1, // Disable user resizing with the lower-right grip - ImGuiWindowFlags_NoMove = 1 << 2, // Disable user moving the window - ImGuiWindowFlags_NoScrollbar = 1 << 3, // Disable scrollbars (window can still scroll with mouse or programatically) - ImGuiWindowFlags_NoScrollWithMouse = - 1 << 4, // Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the - // parent unless NoScrollbar is also set. - ImGuiWindowFlags_NoCollapse = 1 << 5, // Disable user collapsing window by double-clicking on it - ImGuiWindowFlags_AlwaysAutoResize = 1 << 6, // Resize every window to its content every frame - ImGuiWindowFlags_NoSavedSettings = 1 << 8, // Never load/save settings in .ini file - ImGuiWindowFlags_NoInputs = 1 << 9, // Disable catching mouse or keyboard inputs, hovering test with pass through. - ImGuiWindowFlags_MenuBar = 1 << 10, // Has a menu-bar - ImGuiWindowFlags_HorizontalScrollbar = - 1 << 11, // Allow horizontal scrollbar to appear (off by default). You may use - // SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in - // imgui_demo in the "Horizontal Scrolling" section. - ImGuiWindowFlags_NoFocusOnAppearing = 1 << 12, // Disable taking focus when transitioning from hidden to visible state - ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13, // Disable bringing window to front when taking focus (e.g. clicking - // on it or programatically giving it focus) - ImGuiWindowFlags_AlwaysVerticalScrollbar = 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) - ImGuiWindowFlags_AlwaysHorizontalScrollbar = - 1 << 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) - ImGuiWindowFlags_AlwaysUseWindowPadding = - 1 << 16, // Ensure child windows without border uses style.WindowPadding (ignored by default for non-bordered child - // windows, because more convenient) - ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window - ImGuiWindowFlags_NoNavFocus = - 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) - ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, - - // [Internal] - ImGuiWindowFlags_NavFlattened = 1 << 23, // [BETA] Allow gamepad/keyboard navigation to cross over parent border to - // this child (only use on child that have no scrolling!) - ImGuiWindowFlags_ChildWindow = 1 << 24, // Don't use! For internal use by BeginChild() - ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() - ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup() - ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal() - ImGuiWindowFlags_ChildMenu = 1 << 28 // Don't use! For internal use by BeginMenu() - - // [Obsolete] - // ImGuiWindowFlags_ShowBorders = 1 << 7, // --> Set style.FrameBorderSize=1.0f / - // style.WindowBorderSize=1.0f to enable borders around windows and items ImGuiWindowFlags_ResizeFromAnySide = 1 << - // 17, // --> Set io.ConfigResizeWindowsFromEdges and make sure mouse cursors are supported by back-end - // (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) -}; - -// Flags for ImGui::InputText() -enum ImGuiInputTextFlags_ { - ImGuiInputTextFlags_None = 0, - ImGuiInputTextFlags_CharsDecimal = 1 << 0, // Allow 0123456789.+-*/ - ImGuiInputTextFlags_CharsHexadecimal = 1 << 1, // Allow 0123456789ABCDEFabcdef - ImGuiInputTextFlags_CharsUppercase = 1 << 2, // Turn a..z into A..Z - ImGuiInputTextFlags_CharsNoBlank = 1 << 3, // Filter out spaces, tabs - ImGuiInputTextFlags_AutoSelectAll = 1 << 4, // Select entire text when first taking mouse focus - ImGuiInputTextFlags_EnterReturnsTrue = - 1 << 5, // Return 'true' when Enter is pressed (as opposed to when the value was modified) - ImGuiInputTextFlags_CallbackCompletion = 1 << 6, // Call user function on pressing TAB (for completion handling) - ImGuiInputTextFlags_CallbackHistory = 1 << 7, // Call user function on pressing Up/Down arrows (for history handling) - ImGuiInputTextFlags_CallbackAlways = - 1 << 8, // Call user function every time. User code may query cursor position, modify text buffer. - ImGuiInputTextFlags_CallbackCharFilter = 1 - << 9, // Call user function to filter character. Modify data->EventChar to - // replace/filter input, or return 1 in callback to discard character. - ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field - ImGuiInputTextFlags_CtrlEnterForNewLine = - 1 << 11, // In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with - // Ctrl+Enter, add line with Enter). - ImGuiInputTextFlags_NoHorizontalScroll = 1 << 12, // Disable following the cursor horizontally - ImGuiInputTextFlags_AlwaysInsertMode = 1 << 13, // Insert mode - ImGuiInputTextFlags_ReadOnly = 1 << 14, // Read-only mode - ImGuiInputTextFlags_Password = 1 << 15, // Password mode, display all characters as '*' - ImGuiInputTextFlags_NoUndoRedo = - 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own - // undo/redo stack you need e.g. to call ClearActiveID(). - ImGuiInputTextFlags_CharsScientific = 1 << 17, // Allow 0123456789.+-*/eE (Scientific notation input) - ImGuiInputTextFlags_CallbackResize = - 1 << 18, // Allow buffer capacity resize + notify when the string wants to be resized (for string types which hold a - // cache of their Size) (see misc/stl/imgui_stl.h for an example of using this) - // [Internal] - ImGuiInputTextFlags_Multiline = 1 << 20 // For internal use by InputTextMultiline() -}; - -// Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*() -enum ImGuiTreeNodeFlags_ { - ImGuiTreeNodeFlags_None = 0, - ImGuiTreeNodeFlags_Selected = 1 << 0, // Draw as selected - ImGuiTreeNodeFlags_Framed = 1 << 1, // Full colored frame (e.g. for CollapsingHeader) - ImGuiTreeNodeFlags_AllowItemOverlap = 1 << 2, // Hit testing to allow subsequent widgets to overlap this one - ImGuiTreeNodeFlags_NoTreePushOnOpen = - 1 << 3, // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack - ImGuiTreeNodeFlags_NoAutoOpenOnLog = 1 << 4, // Don't automatically and temporarily open node when Logging is active - // (by default logging will automatically open tree nodes) - ImGuiTreeNodeFlags_DefaultOpen = 1 << 5, // Default node to be open - ImGuiTreeNodeFlags_OpenOnDoubleClick = 1 << 6, // Need double-click to open node - ImGuiTreeNodeFlags_OpenOnArrow = - 1 << 7, // Only open when clicking on the arrow part. If ImGuiTreeNodeFlags_OpenOnDoubleClick is also set, - // single-click arrow or double-click all box to open. - ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). - ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow - ImGuiTreeNodeFlags_FramePadding = - 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget - // height. Equivalent to calling AlignTextToFramePadding(). - // ImGuITreeNodeFlags_SpanAllAvailWidth = 1 << 11, // FIXME: TODO: Extend hit box horizontally even if not framed - // ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 12, // FIXME: TODO: Disable automatic scroll on TreePop() if node got - // just open and contents is not visible - ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 13, // (WIP) Nav: left direction may move to this TreeNode() from any - // of its child (items submitted between TreeNode and TreePop) - ImGuiTreeNodeFlags_CollapsingHeader = - ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog - -// Obsolete names (will be removed) -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - , - ImGuiTreeNodeFlags_AllowOverlapMode = ImGuiTreeNodeFlags_AllowItemOverlap -#endif -}; - -// Flags for ImGui::Selectable() -enum ImGuiSelectableFlags_ { - ImGuiSelectableFlags_None = 0, - ImGuiSelectableFlags_DontClosePopups = 1 << 0, // Clicking this don't close parent popup window - ImGuiSelectableFlags_SpanAllColumns = - 1 << 1, // Selectable frame can span all columns (text will still fit in current column) - ImGuiSelectableFlags_AllowDoubleClick = 1 << 2, // Generate press events on double clicks too - ImGuiSelectableFlags_Disabled = 1 << 3 // Cannot be selected, display greyed out text -}; - -// Flags for ImGui::BeginCombo() -enum ImGuiComboFlags_ { - ImGuiComboFlags_None = 0, - ImGuiComboFlags_PopupAlignLeft = 1 << 0, // Align the popup toward the left by default - ImGuiComboFlags_HeightSmall = 1 << 1, // Max ~4 items visible. Tip: If you want your combo popup to be a specific size - // you can use SetNextWindowSizeConstraints() prior to calling BeginCombo() - ImGuiComboFlags_HeightRegular = 1 << 2, // Max ~8 items visible (default) - ImGuiComboFlags_HeightLarge = 1 << 3, // Max ~20 items visible - ImGuiComboFlags_HeightLargest = 1 << 4, // As many fitting items as possible - ImGuiComboFlags_NoArrowButton = 1 << 5, // Display on the preview box without the square arrow button - ImGuiComboFlags_NoPreview = 1 << 6, // Display only a square arrow button - ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | - ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest -}; - -// Flags for ImGui::IsWindowFocused() -enum ImGuiFocusedFlags_ { - ImGuiFocusedFlags_None = 0, - ImGuiFocusedFlags_ChildWindows = 1 << 0, // IsWindowFocused(): Return true if any children of the window is focused - ImGuiFocusedFlags_RootWindow = - 1 << 1, // IsWindowFocused(): Test from root window (top most parent of the current hierarchy) - ImGuiFocusedFlags_AnyWindow = 1 << 2, // IsWindowFocused(): Return true if any window is focused - ImGuiFocusedFlags_RootAndChildWindows = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows -}; - -// Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered() -// Note: if you are trying to check whether your mouse should be dispatched to imgui or to your app, you should use the -// 'io.WantCaptureMouse' boolean for that. Please read the FAQ! Note: windows with the ImGuiWindowFlags_NoInputs flag -// are ignored by IsWindowHovered() calls. -enum ImGuiHoveredFlags_ { - ImGuiHoveredFlags_None = 0, // Return true if directly over the item/window, not obstructed by another window, not - // obstructed by an active popup or modal blocking inputs under them. - ImGuiHoveredFlags_ChildWindows = - 1 << 0, // IsWindowHovered() only: Return true if any children of the window is hovered - ImGuiHoveredFlags_RootWindow = - 1 << 1, // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy) - ImGuiHoveredFlags_AnyWindow = 1 << 2, // IsWindowHovered() only: Return true if any window is hovered - ImGuiHoveredFlags_AllowWhenBlockedByPopup = - 1 << 3, // Return true even if a popup window is normally blocking access to this item/window - // ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 4, // Return true even if a modal popup window is normally - // blocking access to this item/window. FIXME-TODO: Unavailable yet. - ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 5, // Return true even if an active item is blocking access to - // this item/window. Useful for Drag and Drop patterns. - ImGuiHoveredFlags_AllowWhenOverlapped = 1 << 6, // Return true even if the position is overlapped by another window - ImGuiHoveredFlags_AllowWhenDisabled = 1 << 7, // Return true even if the item is disabled - ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | - ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, - ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows -}; - -// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() -enum ImGuiDragDropFlags_ { - ImGuiDragDropFlags_None = 0, - // BeginDragDropSource() flags - ImGuiDragDropFlags_SourceNoPreviewTooltip = - 1 << 0, // By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or - // description of the source contents. This flag disable this behavior. - ImGuiDragDropFlags_SourceNoDisableHover = - 1 << 1, // By default, when dragging we clear data so that IsItemHovered() will return false, to avoid subsequent - // user code submitting tooltips. This flag disable this behavior so you can still call IsItemHovered() on - // the source item. - ImGuiDragDropFlags_SourceNoHoldToOpenOthers = - 1 << 2, // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while - // dragging a source item. - ImGuiDragDropFlags_SourceAllowNullID = - 1 << 3, // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by - // manufacturing a temporary identifier based on their window-relative position. This is extremely unusual - // within the dear imgui ecosystem and so we made it explicit. - ImGuiDragDropFlags_SourceExtern = - 1 << 4, // External source (from outside of imgui), won't attempt to read current item/window info. Will always - // return true. Only one Extern source can be active simultaneously. - ImGuiDragDropFlags_SourceAutoExpirePayload = - 1 << 5, // Automatically expire the payload if the source cease to be submitted (otherwise payloads are persisting - // while being dragged) - // AcceptDragDropPayload() flags - ImGuiDragDropFlags_AcceptBeforeDelivery = - 1 << 10, // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call - // IsDelivery() to test if the payload needs to be delivered. - ImGuiDragDropFlags_AcceptNoDrawDefaultRect = - 1 << 11, // Do not draw the default highlight rectangle when hovering over target. - ImGuiDragDropFlags_AcceptNoPreviewTooltip = - 1 << 12, // Request hiding the BeginDragDropSource tooltip from the BeginDragDropTarget site. - ImGuiDragDropFlags_AcceptPeekOnly = - ImGuiDragDropFlags_AcceptBeforeDelivery | - ImGuiDragDropFlags_AcceptNoDrawDefaultRect // For peeking ahead and inspecting the payload before delivery. -}; - -// Standard Drag and Drop payload types. You can define you own payload types using short strings. Types starting with -// '_' are defined by Dear ImGui. -#define IMGUI_PAYLOAD_TYPE_COLOR_3F \ - "_COL3F" // float[3]: Standard type for colors, without alpha. User code may use this type. -#define IMGUI_PAYLOAD_TYPE_COLOR_4F "_COL4F" // float[4]: Standard type for colors. User code may use this type. - -// A primary data type -enum ImGuiDataType_ { - ImGuiDataType_S32, // int - ImGuiDataType_U32, // unsigned int - ImGuiDataType_S64, // long long, __int64 - ImGuiDataType_U64, // unsigned long long, unsigned __int64 - ImGuiDataType_Float, // float - ImGuiDataType_Double, // double - ImGuiDataType_COUNT -}; - -// A cardinal direction -enum ImGuiDir_ { - ImGuiDir_None = -1, - ImGuiDir_Left = 0, - ImGuiDir_Right = 1, - ImGuiDir_Up = 2, - ImGuiDir_Down = 3, - ImGuiDir_COUNT -}; - -// User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array -enum ImGuiKey_ { - ImGuiKey_Tab, - ImGuiKey_LeftArrow, - ImGuiKey_RightArrow, - ImGuiKey_UpArrow, - ImGuiKey_DownArrow, - ImGuiKey_PageUp, - ImGuiKey_PageDown, - ImGuiKey_Home, - ImGuiKey_End, - ImGuiKey_Insert, - ImGuiKey_Delete, - ImGuiKey_Backspace, - ImGuiKey_Space, - ImGuiKey_Enter, - ImGuiKey_Escape, - ImGuiKey_A, // for text edit CTRL+A: select all - ImGuiKey_C, // for text edit CTRL+C: copy - ImGuiKey_V, // for text edit CTRL+V: paste - ImGuiKey_X, // for text edit CTRL+X: cut - ImGuiKey_Y, // for text edit CTRL+Y: redo - ImGuiKey_Z, // for text edit CTRL+Z: undo - ImGuiKey_COUNT -}; - -// Gamepad/Keyboard directional navigation -// Keyboard: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable. NewFrame() will automatically fill -// io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays. Gamepad: Set io.ConfigFlags |= -// ImGuiConfigFlags_NavEnableGamepad to enable. Back-end: set ImGuiBackendFlags_HasGamepad and fill the io.NavInputs[] -// fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). Read instructions in imgui.cpp -// for more details. Download PNG/PSD at http://goo.gl/9LgVZW. -enum ImGuiNavInput_ { - // Gamepad Mapping - ImGuiNavInput_Activate, // activate / open / toggle / tweak value // e.g. Cross (PS4), A (Xbox), A (Switch), - // Space (Keyboard) - ImGuiNavInput_Cancel, // cancel / close / exit // e.g. Circle (PS4), B (Xbox), B (Switch), - // Escape (Keyboard) - ImGuiNavInput_Input, // text input / on-screen keyboard // e.g. Triang.(PS4), Y (Xbox), X (Switch), - // Return (Keyboard) - ImGuiNavInput_Menu, // tap: toggle menu / hold: focus, move, resize // e.g. Square (PS4), X (Xbox), Y (Switch), Alt - // (Keyboard) - ImGuiNavInput_DpadLeft, // move / tweak / resize window (w/ PadMenu) // e.g. D-pad Left/Right/Up/Down (Gamepads), - // Arrow keys (Keyboard) - ImGuiNavInput_DpadRight, // - ImGuiNavInput_DpadUp, // - ImGuiNavInput_DpadDown, // - ImGuiNavInput_LStickLeft, // scroll / move window (w/ PadMenu) // e.g. Left Analog Stick Left/Right/Up/Down - ImGuiNavInput_LStickRight, // - ImGuiNavInput_LStickUp, // - ImGuiNavInput_LStickDown, // - ImGuiNavInput_FocusPrev, // next window (w/ PadMenu) // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or - // ZL (Switch) - ImGuiNavInput_FocusNext, // prev window (w/ PadMenu) // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or - // ZL (Switch) - ImGuiNavInput_TweakSlow, // slower tweaks // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or - // ZL (Switch) - ImGuiNavInput_TweakFast, // faster tweaks // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or - // ZL (Switch) - - // [Internal] Don't use directly! This is used internally to differentiate keyboard from gamepad inputs for behaviors - // that require to differentiate them. Keyboard behavior that have no corresponding gamepad mapping (e.g. CTRL+TAB) - // will be directly reading from io.KeysDown[] instead of io.NavInputs[]. - ImGuiNavInput_KeyMenu_, // toggle menu // = io.KeyAlt - ImGuiNavInput_KeyLeft_, // move left // = Arrow keys - ImGuiNavInput_KeyRight_, // move right - ImGuiNavInput_KeyUp_, // move up - ImGuiNavInput_KeyDown_, // move down - ImGuiNavInput_COUNT, - ImGuiNavInput_InternalStart_ = ImGuiNavInput_KeyMenu_ -}; - -// Configuration flags stored in io.ConfigFlags. Set by user/application. -enum ImGuiConfigFlags_ { - ImGuiConfigFlags_NavEnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. NewFrame() will automatically - // fill io.NavInputs[] based on io.KeysDown[]. - ImGuiConfigFlags_NavEnableGamepad = - 1 << 1, // Master gamepad navigation enable flag. This is mostly to instruct your imgui back-end to fill - // io.NavInputs[]. Back-end also needs to set ImGuiBackendFlags_HasGamepad. - ImGuiConfigFlags_NavEnableSetMousePos = - 1 << 2, // Instruct navigation to move the mouse cursor. May be useful on TV/console systems where moving a virtual - // mouse is awkward. Will update io.MousePos and set io.WantSetMousePos=true. If enabled you MUST honor - // io.WantSetMousePos requests in your binding, otherwise ImGui will react as if the mouse is jumping around - // back and forth. - ImGuiConfigFlags_NavNoCaptureKeyboard = - 1 << 3, // Instruct navigation to not set the io.WantCaptureKeyboard flag when io.NavActive is set. - ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct imgui to clear mouse position/buttons in NewFrame(). This allows - // ignoring the mouse information set by the back-end. - ImGuiConfigFlags_NoMouseCursorChange = - 1 << 5, // Instruct back-end to not alter mouse cursor shape and visibility. Use if the back-end cursor changes are - // interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to - // honor requests from imgui by reading GetMouseCursor() yourself instead. - - // User storage (to allow your back-end/engine to communicate to code that may be shared between multiple projects. - // Those flags are not used by core ImGui) - ImGuiConfigFlags_IsSRGB = 1 << 20, // Application is SRGB-aware. - ImGuiConfigFlags_IsTouchScreen = 1 << 21 // Application is using a touch screen instead of a mouse. -}; - -// Back-end capabilities flags stored in io.BackendFlags. Set by imgui_impl_xxx or custom back-end. -enum ImGuiBackendFlags_ { - ImGuiBackendFlags_HasGamepad = 1 << 0, // Back-end supports gamepad and currently has one connected. - ImGuiBackendFlags_HasMouseCursors = - 1 << 1, // Back-end supports honoring GetMouseCursor() value to change the OS cursor shape. - ImGuiBackendFlags_HasSetMousePos = 1 << 2 // Back-end supports io.WantSetMousePos requests to reposition the OS mouse - // position (only used if ImGuiConfigFlags_NavEnableSetMousePos is set). -}; - -// Enumeration for PushStyleColor() / PopStyleColor() -enum ImGuiCol_ { - ImGuiCol_Text, - ImGuiCol_TextDisabled, - ImGuiCol_WindowBg, // Background of normal windows - ImGuiCol_ChildBg, // Background of child windows - ImGuiCol_PopupBg, // Background of popups, menus, tooltips windows - ImGuiCol_Border, - ImGuiCol_BorderShadow, - ImGuiCol_FrameBg, // Background of checkbox, radio button, plot, slider, text input - ImGuiCol_FrameBgHovered, - ImGuiCol_FrameBgActive, - ImGuiCol_TitleBg, - ImGuiCol_TitleBgActive, - ImGuiCol_TitleBgCollapsed, - ImGuiCol_MenuBarBg, - ImGuiCol_ScrollbarBg, - ImGuiCol_ScrollbarGrab, - ImGuiCol_ScrollbarGrabHovered, - ImGuiCol_ScrollbarGrabActive, - ImGuiCol_CheckMark, - ImGuiCol_SliderGrab, - ImGuiCol_SliderGrabActive, - ImGuiCol_Button, - ImGuiCol_ButtonHovered, - ImGuiCol_ButtonActive, - ImGuiCol_Header, - ImGuiCol_HeaderHovered, - ImGuiCol_HeaderActive, - ImGuiCol_Separator, - ImGuiCol_SeparatorHovered, - ImGuiCol_SeparatorActive, - ImGuiCol_ResizeGrip, - ImGuiCol_ResizeGripHovered, - ImGuiCol_ResizeGripActive, - ImGuiCol_PlotLines, - ImGuiCol_PlotLinesHovered, - ImGuiCol_PlotHistogram, - ImGuiCol_PlotHistogramHovered, - ImGuiCol_TextSelectedBg, - ImGuiCol_DragDropTarget, - ImGuiCol_NavHighlight, // Gamepad/keyboard: current highlighted item - ImGuiCol_NavWindowingHighlight, // Highlight window when using CTRL+TAB - ImGuiCol_NavWindowingDimBg, // Darken/colorize entire screen behind the CTRL+TAB window list, when active - ImGuiCol_ModalWindowDimBg, // Darken/colorize entire screen behind a modal window, when one is active - ImGuiCol_COUNT - -// Obsolete names (will be removed) -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - , - ImGuiCol_ChildWindowBg = ImGuiCol_ChildBg, - ImGuiCol_Column = ImGuiCol_Separator, - ImGuiCol_ColumnHovered = ImGuiCol_SeparatorHovered, - ImGuiCol_ColumnActive = ImGuiCol_SeparatorActive, - ImGuiCol_ModalWindowDarkening = ImGuiCol_ModalWindowDimBg -// ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered, // [unused since 1.60+] the close -// button now uses regular button colors. ImGuiCol_ComboBg, // [unused since 1.53+] ComboBg has been merged with PopupBg, -// so a redirect isn't accurate. -#endif -}; - -// Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure. -// NB: the enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. During -// initialization, feel free to just poke into ImGuiStyle directly. NB: if changing this enum, you need to update the -// associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type. -enum ImGuiStyleVar_ { - // Enum name ......................// Member in ImGuiStyle structure (see ImGuiStyle for descriptions) - ImGuiStyleVar_Alpha, // float Alpha - ImGuiStyleVar_WindowPadding, // ImVec2 WindowPadding - ImGuiStyleVar_WindowRounding, // float WindowRounding - ImGuiStyleVar_WindowBorderSize, // float WindowBorderSize - ImGuiStyleVar_WindowMinSize, // ImVec2 WindowMinSize - ImGuiStyleVar_WindowTitleAlign, // ImVec2 WindowTitleAlign - ImGuiStyleVar_ChildRounding, // float ChildRounding - ImGuiStyleVar_ChildBorderSize, // float ChildBorderSize - ImGuiStyleVar_PopupRounding, // float PopupRounding - ImGuiStyleVar_PopupBorderSize, // float PopupBorderSize - ImGuiStyleVar_FramePadding, // ImVec2 FramePadding - ImGuiStyleVar_FrameRounding, // float FrameRounding - ImGuiStyleVar_FrameBorderSize, // float FrameBorderSize - ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing - ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing - ImGuiStyleVar_IndentSpacing, // float IndentSpacing - ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize - ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding - ImGuiStyleVar_GrabMinSize, // float GrabMinSize - ImGuiStyleVar_GrabRounding, // float GrabRounding - ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign - ImGuiStyleVar_COUNT - -// Obsolete names (will be removed) -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - , - ImGuiStyleVar_Count_ = ImGuiStyleVar_COUNT, - ImGuiStyleVar_ChildWindowRounding = ImGuiStyleVar_ChildRounding -#endif -}; - -// Enumeration for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() -enum ImGuiColorEditFlags_ { - ImGuiColorEditFlags_None = 0, - ImGuiColorEditFlags_NoAlpha = 1 << 1, // // ColorEdit, ColorPicker, ColorButton: ignore Alpha component - // (read 3 components from the input pointer). - ImGuiColorEditFlags_NoPicker = 1 << 2, // // ColorEdit: disable picker when clicking on colored square. - ImGuiColorEditFlags_NoOptions = - 1 << 3, // // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview. - ImGuiColorEditFlags_NoSmallPreview = 1 << 4, // // ColorEdit, ColorPicker: disable colored square preview - // next to the inputs. (e.g. to show only the inputs) - ImGuiColorEditFlags_NoInputs = 1 << 5, // // ColorEdit, ColorPicker: disable inputs sliders/text widgets - // (e.g. to show only the small preview colored square). - ImGuiColorEditFlags_NoTooltip = - 1 << 6, // // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview. - ImGuiColorEditFlags_NoLabel = 1 << 7, // // ColorEdit, ColorPicker: disable display of inline text label - // (the label is still forwarded to the tooltip and picker). - ImGuiColorEditFlags_NoSidePreview = 1 << 8, // // ColorPicker: disable bigger color preview on right side - // of the picker, use small colored square preview instead. - ImGuiColorEditFlags_NoDragDrop = - 1 << 9, // // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source. - - // User Options (right-click on widget to change some of them). You can set application defaults using - // SetColorEditOptions(). The idea is that you probably don't want to override them in most of your calls, let the - // user choose and/or call SetColorEditOptions() during startup. - ImGuiColorEditFlags_AlphaBar = - 1 << 16, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. - ImGuiColorEditFlags_AlphaPreview = - 1 << 17, // // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a - // checkerboard, instead of opaque. - ImGuiColorEditFlags_AlphaPreviewHalf = 1 << 18, // // ColorEdit, ColorPicker, ColorButton: display half - // opaque / half checkerboard, instead of opaque. - ImGuiColorEditFlags_HDR = - 1 << 19, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you - // probably want to use ImGuiColorEditFlags_Float flag as well). - ImGuiColorEditFlags_RGB = 1 << 20, // [Inputs] // ColorEdit: choose one among RGB/HSV/HEX. ColorPicker: choose any - // combination using RGB/HSV/HEX. - ImGuiColorEditFlags_HSV = 1 << 21, // [Inputs] // " - ImGuiColorEditFlags_HEX = 1 << 22, // [Inputs] // " - ImGuiColorEditFlags_Uint8 = - 1 << 23, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255. - ImGuiColorEditFlags_Float = - 1 << 24, // [DataType] // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats - // instead of 0..255 integers. No round-trip of value via integers. - ImGuiColorEditFlags_PickerHueBar = 1 << 25, // [PickerMode] // ColorPicker: bar for Hue, rectangle for Sat/Value. - ImGuiColorEditFlags_PickerHueWheel = 1 << 26, // [PickerMode] // ColorPicker: wheel for Hue, triangle for Sat/Value. - - // [Internal] Masks - ImGuiColorEditFlags__InputsMask = ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV | ImGuiColorEditFlags_HEX, - ImGuiColorEditFlags__DataTypeMask = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_Float, - ImGuiColorEditFlags__PickerMask = ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_PickerHueBar, - ImGuiColorEditFlags__OptionsDefault = - ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_RGB | - ImGuiColorEditFlags_PickerHueBar // Change application default using SetColorEditOptions() -}; - -// Enumeration for GetMouseCursor() -// User code may request binding to display given cursor by calling SetMouseCursor(), which is why we have some cursors -// that are marked unused here -enum ImGuiMouseCursor_ { - ImGuiMouseCursor_None = -1, - ImGuiMouseCursor_Arrow = 0, - ImGuiMouseCursor_TextInput, // When hovering over InputText, etc. - ImGuiMouseCursor_ResizeAll, // (Unused by imgui functions) - ImGuiMouseCursor_ResizeNS, // When hovering over an horizontal border - ImGuiMouseCursor_ResizeEW, // When hovering over a vertical border or a column - ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window - ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window - ImGuiMouseCursor_Hand, // (Unused by imgui functions. Use for e.g. hyperlinks) - ImGuiMouseCursor_COUNT - -// Obsolete names (will be removed) -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - , - ImGuiMouseCursor_Count_ = ImGuiMouseCursor_COUNT -#endif -}; - -// Condition for ImGui::SetWindow***(), SetNextWindow***(), SetNextTreeNode***() functions -// Important: Treat as a regular enum! Do NOT combine multiple values using binary operators! All the functions above -// treat 0 as a shortcut to ImGuiCond_Always. -enum ImGuiCond_ { - ImGuiCond_Always = 1 << 0, // Set the variable - ImGuiCond_Once = 1 << 1, // Set the variable once per runtime session (only the first call with succeed) - ImGuiCond_FirstUseEver = - 1 << 2, // Set the variable if the object/window has no persistently saved data (no entry in .ini file) - ImGuiCond_Appearing = - 1 << 3 // Set the variable if the object/window is appearing after being hidden/inactive (or the first time) - -// Obsolete names (will be removed) -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - , - ImGuiSetCond_Always = ImGuiCond_Always, - ImGuiSetCond_Once = ImGuiCond_Once, - ImGuiSetCond_FirstUseEver = ImGuiCond_FirstUseEver, - ImGuiSetCond_Appearing = ImGuiCond_Appearing -#endif -}; - -// You may modify the ImGui::GetStyle() main instance during initialization and before NewFrame(). -// During the frame, use ImGui::PushStyleVar(ImGuiStyleVar_XXXX)/PopStyleVar() to alter the main style values, and -// ImGui::PushStyleColor(ImGuiCol_XXX)/PopStyleColor() for colors. -struct ImGuiStyle { - float Alpha; // Global alpha applies to everything in ImGui. - ImVec2 WindowPadding; // Padding within a window. - float WindowRounding; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. - float WindowBorderSize; // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not - // well tested and more CPU/GPU costly). - ImVec2 WindowMinSize; // Minimum window size. This is a global setting. If you want to constraint individual windows, - // use SetNextWindowSizeConstraints(). - ImVec2 - WindowTitleAlign; // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered. - float ChildRounding; // Radius of child window corners rounding. Set to 0.0f to have rectangular windows. - float ChildBorderSize; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. (Other values are - // not well tested and more CPU/GPU costly). - float PopupRounding; // Radius of popup window corners rounding. (Note that tooltip windows use WindowRounding) - float PopupBorderSize; // Thickness of border around popup/tooltip windows. Generally set to 0.0f or 1.0f. (Other - // values are not well tested and more CPU/GPU costly). - ImVec2 FramePadding; // Padding within a framed rectangle (used by most widgets). - float - FrameRounding; // Radius of frame corners rounding. Set to 0.0f to have rectangular frame (used by most widgets). - float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well - // tested and more CPU/GPU costly). - ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. - ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a - // slider and its label). - ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate - // enough. Unfortunately we don't sort widgets so priority on overlap will always be given - // to the first widget. So don't grow this too much! - float - IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). - float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. - float ScrollbarSize; // Width of the vertical scrollbar, Height of the horizontal scrollbar. - float ScrollbarRounding; // Radius of grab corners for scrollbar. - float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. - float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. - ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f,0.5f) for - // horizontally+vertically centered. - ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area by at least this - // amount. Only applies to regular windows. - ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area - // padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring - // your TV sets correctly! - float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed - // later. - bool AntiAliasedLines; // Enable anti-aliasing on lines/borders. Disable if you are really tight on CPU/GPU. - bool AntiAliasedFill; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.) - float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of - // segments. Decrease for highly tessellated curves (higher quality, more polygons), - // increase to reduce quality. - ImVec4 Colors[ImGuiCol_COUNT]; - - IMGUI_API ImGuiStyle(); - IMGUI_API void ScaleAllSizes(float scale_factor); -}; - -// This is where your app communicate with Dear ImGui. Access via ImGui::GetIO(). -// Read 'Programmer guide' section in .cpp file for general usage. -struct ImGuiIO { - //------------------------------------------------------------------ - // Configuration (fill once) // Default value: - //------------------------------------------------------------------ - - ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. - // Gamepad/keyboard navigation options, etc. - ImGuiBackendFlags BackendFlags; // = 0 // Set ImGuiBackendFlags_ enum. Set by imgui_impl_xxx files or - // custom back-end to communicate features supported by the back-end. - ImVec2 DisplaySize; // // Main display size, in pixels. For clamping windows positions. - float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. - float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. - const char* IniFilename; // = "imgui.ini" // Path to .ini file. Set NULL to disable automatic .ini - // loading/saving, if e.g. you want to manually load/save from memory. - const char* LogFilename; // = "imgui_log.txt" // Path to .log file (default parameter to ImGui::LogToFile when no - // file is specified). - float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds. - float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in - // pixels. - float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging. - int KeyMap[ImGuiKey_COUNT]; // // Map of indices into the KeysDown[512] entries array which - // represent your "native" keyboard state. - float KeyRepeatDelay; // = 0.250f // When holding a key/button, time before it starts repeating, in - // seconds (for buttons in Repeat mode, etc.). - float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds. - void* UserData; // = NULL // Store your own data for retrieval by callbacks. - - ImFontAtlas* Fonts; // // Load and assemble one or more fonts into a single tightly packed - // texture. Output to Fonts array. - float FontGlobalScale; // = 1.0f // Global scale all fonts - bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. - ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. - ImVec2 DisplayFramebufferScale; // = (1.0f,1.0f) // For retina display or other situations where window - // coordinates are different from framebuffer coordinates. User storage only, - // presently not used by ImGui. - ImVec2 DisplayVisibleMin; // (0.0f,0.0f) // [obsolete] If you use DisplaySize as a virtual space larger than - // your screen, set DisplayVisibleMin/Max to the visible area. - ImVec2 DisplayVisibleMax; // (0.0f,0.0f) // [obsolete: just use io.DisplaySize] If the values are the same, - // we defaults to Min=(0.0f) and Max=DisplaySize - - // Miscellaneous configuration options - bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform - // without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is - // frequently used by back-end implementations. - bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // OS X style: Text editing cursor movement using Alt instead of - // Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using - // Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole - // text, Multi-selection in lists uses Cmd/Super instead of Ctrl (was called - // io.OptMacOSXBehaviors prior to 1.63) - bool ConfigInputTextCursorBlink; // = true // Set to false to disable blinking cursor, for users who - // consider it distracting. (was called: io.OptCursorBlink prior to 1.63) - bool ConfigResizeWindowsFromEdges; // = false // [BETA] Enable resizing of windows from their edges and from - // the lower-left corner. This requires (io.BackendFlags & - // ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This - // used to be the ImGuiWindowFlags_ResizeFromAnySide flag) - - //------------------------------------------------------------------ - // Settings (User Functions) - //------------------------------------------------------------------ - - // Optional: access OS clipboard - // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS - // clipboard on other architectures) - const char* (*GetClipboardTextFn)(void* user_data); - void (*SetClipboardTextFn)(void* user_data, const char* text); - void* ClipboardUserData; - - // Optional: notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when - // using Japanese/Chinese IME in Windows) (default to use native imm32 api on Windows) - void (*ImeSetInputScreenPosFn)(int x, int y); - void* ImeWindowHandle; // (Windows) Set this to your HWND to get automatic IME cursor positioning. - -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - // [OBSOLETE since 1.60+] Rendering function, will be automatically called in Render(). Please call your rendering - // function yourself now! You can obtain the ImDrawData* by calling ImGui::GetDrawData() after Render(). See example - // applications if you are unsure of how to implement this. - void (*RenderDrawListsFn)(ImDrawData* data); -#else - // This is only here to keep ImGuiIO the same size, so that IMGUI_DISABLE_OBSOLETE_FUNCTIONS can exceptionally be used - // outside of imconfig.h. - void* RenderDrawListsFnUnused; -#endif - - //------------------------------------------------------------------ - // Input - Fill before calling NewFrame() - //------------------------------------------------------------------ - - ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX,-FLT_MAX) if mouse is unavailable (on another - // screen, etc.) - bool MouseDown[5]; // Mouse buttons: 0=left, 1=right, 2=middle + extras. ImGui itself mostly only uses left button - // (BeginPopupContext** are using right button). Others buttons allows us to track if the mouse is - // being used by your application + available to user as a convenience via IsMouse** API. - float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. - float MouseWheelH; // Mouse wheel Horizontal. Most users don't have a mouse with an horizontal wheel, may not be - // filled by all back-ends. - bool KeyCtrl; // Keyboard modifier pressed: Control - bool KeyShift; // Keyboard modifier pressed: Shift - bool KeyAlt; // Keyboard modifier pressed: Alt - bool KeySuper; // Keyboard modifier pressed: Cmd/Super/Windows - bool KeysDown[512]; // Keyboard keys that are pressed (ideally left in the "native" order your engine has access to - // keyboard keys, so you can use your own defines/enums for keys). - ImWchar InputCharacters[16 + 1]; // List of characters input (translated by user from keypress+keyboard state). Fill - // using AddInputCharacter() helper. - float NavInputs[ImGuiNavInput_COUNT]; // Gamepad inputs (keyboard keys will be auto-mapped and be written here by - // ImGui::NewFrame, all values will be cleared back to zero in ImGui::EndFrame) - - // Functions - IMGUI_API void AddInputCharacter(ImWchar c); // Add new character into InputCharacters[] - IMGUI_API void AddInputCharactersUTF8( - const char* utf8_chars); // Add new characters into InputCharacters[] from an UTF-8 string - inline void ClearInputCharacters() { InputCharacters[0] = 0; } // Clear the text input buffer manually - - //------------------------------------------------------------------ - // Output - Retrieve after calling NewFrame() - //------------------------------------------------------------------ - - bool WantCaptureMouse; // When io.WantCaptureMouse is true, imgui will use the mouse inputs, do not dispatch them to - // your main game/application (in both cases, always pass on mouse inputs to imgui). (e.g. - // unclicked mouse is hovering over an imgui window, widget is active, mouse was clicked over - // an imgui window, etc.). - bool WantCaptureKeyboard; // When io.WantCaptureKeyboard is true, imgui will use the keyboard inputs, do not dispatch - // them to your main game/application (in both cases, always pass keyboard inputs to imgui). - // (e.g. InputText active, or an imgui window is focused and navigation is enabled, etc.). - bool - WantTextInput; // Mobile/console: when io.WantTextInput is true, you may display an on-screen keyboard. This is set - // by ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). - bool WantSetMousePos; // MousePos has been altered, back-end should reposition mouse on next frame. Set only when - // ImGuiConfigFlags_NavEnableSetMousePos flag is enabled. - bool WantSaveIniSettings; // When manual .ini load/save is active (io.IniFilename == NULL), this will be set to notify - // your application that you can call SaveIniSettingsToMemory() and save yourself. - // IMPORTANT: You need to clear io.WantSaveIniSettings yourself. - bool NavActive; // Directional navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is - // focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag. - bool NavVisible; // Directional navigation is visible and allowed (will handle ImGuiKey_NavXXX events). - float Framerate; // Application framerate estimation, in frame per second. Solely for convenience. Rolling average - // estimation based on IO.DeltaTime over 120 frames - int MetricsRenderVertices; // Vertices output during last call to Render() - int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 - int MetricsRenderWindows; // Number of visible windows - int MetricsActiveWindows; // Number of active windows - int MetricsActiveAllocations; // Number of active allocations, updated by MemAlloc/MemFree based on current context. - // May be off if you have multiple imgui contexts. - ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid - // (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. - - //------------------------------------------------------------------ - // [Internal] ImGui will maintain those fields. Forward compatibility not guaranteed! - //------------------------------------------------------------------ - - ImVec2 - MousePosPrev; // Previous mouse position temporary storage (nb: not for public use, set to MousePos in NewFrame()) - ImVec2 MouseClickedPos[5]; // Position at time of clicking - double MouseClickedTime[5]; // Time of last click (used to figure out double-click) - bool MouseClicked[5]; // Mouse button went from !Down to Down - bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? - bool MouseReleased[5]; // Mouse button went from Down to !Down - bool MouseDownOwned[5]; // Track if button was clicked inside a window. We don't request mouse capture from the - // application if click started outside ImGui bounds. - float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) - float MouseDownDurationPrev[5]; // Previous time the mouse button has been down - ImVec2 MouseDragMaxDistanceAbs[5]; // Maximum distance, absolute, on each axis, of how much mouse has traveled from - // the clicking point - float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point - float KeysDownDuration[512]; // Duration the keyboard key has been down (0.0f == just pressed) - float KeysDownDurationPrev[512]; // Previous duration the key has been down - float NavInputsDownDuration[ImGuiNavInput_COUNT]; - float NavInputsDownDurationPrev[ImGuiNavInput_COUNT]; - - IMGUI_API ImGuiIO(); -}; - -//----------------------------------------------------------------------------- -// Obsolete functions (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details) -//----------------------------------------------------------------------------- - -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -namespace ImGui -{ -// OBSOLETED in 1.63 (from Aug 2018) -static inline bool IsItemDeactivatedAfterChange() { return IsItemDeactivatedAfterEdit(); } -// OBSOLETED in 1.61 (from Apr 2018) -IMGUI_API bool InputFloat( - const char* label, float* v, float step, float step_fast, int decimal_precision, - ImGuiInputTextFlags extra_flags = 0); // Use the 'const char* format' version instead of 'decimal_precision'! -IMGUI_API bool InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags = 0); -IMGUI_API bool InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags = 0); -// OBSOLETED in 1.60 (from Dec 2017) -static inline bool IsAnyWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_AnyWindow); } -static inline bool IsAnyWindowHovered() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } -static inline ImVec2 CalcItemRectClosestPoint(const ImVec2& pos, bool on_edge = false, float outward = 0.f) -{ - (void)on_edge; - (void)outward; - IM_ASSERT(0); - return pos; -} -// OBSOLETED in 1.53 (between Oct 2017 and Dec 2017) -static inline void ShowTestWindow() { return ShowDemoWindow(); } -static inline bool IsRootWindowFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootWindow); } -static inline bool IsRootWindowOrAnyChildFocused() { return IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); } -static inline void SetNextWindowContentWidth(float w) { SetNextWindowContentSize(ImVec2(w, 0.0f)); } -static inline float GetItemsLineHeightWithSpacing() { return GetFrameHeightWithSpacing(); } -// OBSOLETED in 1.52 (between Aug 2017 and Oct 2017) -IMGUI_API bool Begin( - const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, - ImGuiWindowFlags flags = 0); // Use SetNextWindowSize(size, ImGuiCond_FirstUseEver) + SetNextWindowBgAlpha() instead. -static inline bool IsRootWindowOrAnyChildHovered() { return IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); } -static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } -static inline void SetNextWindowPosCenter(ImGuiCond c = 0) -{ - ImGuiIO& io = GetIO(); - SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), c, ImVec2(0.5f, 0.5f)); -} -// OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) -static inline bool IsItemHoveredRect() { return IsItemHovered(ImGuiHoveredFlags_RectOnly); } -static inline bool IsPosHoveringAnyWindow(const ImVec2&) -{ - IM_ASSERT(0); - return false; -} // This was misleading and partly broken. You probably want to use the ImGui::GetIO().WantCaptureMouse flag instead. -static inline bool IsMouseHoveringAnyWindow() { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); } -static inline bool IsMouseHoveringWindow() -{ - return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); -} -} // namespace ImGui -#endif - -//----------------------------------------------------------------------------- -// Helpers -//----------------------------------------------------------------------------- - -// Helper: Lightweight std::vector<> like class to avoid dragging dependencies (also: Windows implementation of STL with -// debug enabled is absurdly slow, so let's bypass it so our code runs fast in debug). *Important* Our implementation -// does NOT call C++ constructors/destructors. This is intentional, we do not require it but you have to be mindful of -// that. Do _not_ use this class as a std::vector replacement in your code! -template -class ImVector -{ - public: - int Size; - int Capacity; - T* Data; - - typedef T value_type; - typedef value_type* iterator; - typedef const value_type* const_iterator; - - inline ImVector() - { - Size = Capacity = 0; - Data = NULL; - } - inline ~ImVector() - { - if (Data) - ImGui::MemFree(Data); - } - inline ImVector(const ImVector& src) - { - Size = Capacity = 0; - Data = NULL; - operator=(src); - } - inline ImVector& operator=(const ImVector& src) - { - clear(); - resize(src.Size); - memcpy(Data, src.Data, (size_t)Size * sizeof(value_type)); - return *this; - } - - inline bool empty() const { return Size == 0; } - inline int size() const { return Size; } - inline int capacity() const { return Capacity; } - inline value_type& operator[](int i) - { - IM_ASSERT(i < Size); - return Data[i]; - } - inline const value_type& operator[](int i) const - { - IM_ASSERT(i < Size); - return Data[i]; - } - - inline void clear() - { - if (Data) { - Size = Capacity = 0; - ImGui::MemFree(Data); - Data = NULL; - } - } - inline iterator begin() { return Data; } - inline const_iterator begin() const { return Data; } - inline iterator end() { return Data + Size; } - inline const_iterator end() const { return Data + Size; } - inline value_type& front() - { - IM_ASSERT(Size > 0); - return Data[0]; - } - inline const value_type& front() const - { - IM_ASSERT(Size > 0); - return Data[0]; - } - inline value_type& back() - { - IM_ASSERT(Size > 0); - return Data[Size - 1]; - } - inline const value_type& back() const - { - IM_ASSERT(Size > 0); - return Data[Size - 1]; - } - inline void swap(ImVector& rhs) - { - int rhs_size = rhs.Size; - rhs.Size = Size; - Size = rhs_size; - int rhs_cap = rhs.Capacity; - rhs.Capacity = Capacity; - Capacity = rhs_cap; - value_type* rhs_data = rhs.Data; - rhs.Data = Data; - Data = rhs_data; - } - - inline int _grow_capacity(int sz) const - { - int new_capacity = Capacity ? (Capacity + Capacity / 2) : 8; - return new_capacity > sz ? new_capacity : sz; - } - inline void resize(int new_size) - { - if (new_size > Capacity) - reserve(_grow_capacity(new_size)); - Size = new_size; - } - inline void resize(int new_size, const value_type& v) - { - if (new_size > Capacity) - reserve(_grow_capacity(new_size)); - if (new_size > Size) - for (int n = Size; n < new_size; n++) - memcpy(&Data[n], &v, sizeof(v)); - Size = new_size; - } - inline void reserve(int new_capacity) - { - if (new_capacity <= Capacity) - return; - value_type* new_data = (value_type*)ImGui::MemAlloc((size_t)new_capacity * sizeof(value_type)); - if (Data) { - memcpy(new_data, Data, (size_t)Size * sizeof(value_type)); - ImGui::MemFree(Data); - } - Data = new_data; - Capacity = new_capacity; - } - - // NB: It is forbidden to call push_back/push_front/insert with a reference pointing inside the ImVector data itself! - // e.g. v.push_back(v[10]) is forbidden. - inline void push_back(const value_type& v) - { - if (Size == Capacity) - reserve(_grow_capacity(Size + 1)); - memcpy(&Data[Size], &v, sizeof(v)); - Size++; - } - inline void pop_back() - { - IM_ASSERT(Size > 0); - Size--; - } - inline void push_front(const value_type& v) - { - if (Size == 0) - push_back(v); - else - insert(Data, v); - } - inline iterator erase(const_iterator it) - { - IM_ASSERT(it >= Data && it < Data + Size); - const ptrdiff_t off = it - Data; - memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(value_type)); - Size--; - return Data + off; - } - inline iterator erase(const_iterator it, const_iterator it_last) - { - IM_ASSERT(it >= Data && it < Data + Size && it_last > it && it_last <= Data + Size); - const ptrdiff_t count = it_last - it; - const ptrdiff_t off = it - Data; - memmove(Data + off, Data + off + count, ((size_t)Size - (size_t)off - count) * sizeof(value_type)); - Size -= (int)count; - return Data + off; - } - inline iterator erase_unsorted(const_iterator it) - { - IM_ASSERT(it >= Data && it < Data + Size); - const ptrdiff_t off = it - Data; - if (it < Data + Size - 1) - memcpy(Data + off, Data + Size - 1, sizeof(value_type)); - Size--; - return Data + off; - } - inline iterator insert(const_iterator it, const value_type& v) - { - IM_ASSERT(it >= Data && it <= Data + Size); - const ptrdiff_t off = it - Data; - if (Size == Capacity) - reserve(_grow_capacity(Size + 1)); - if (off < (int)Size) - memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(value_type)); - memcpy(&Data[off], &v, sizeof(v)); - Size++; - return Data + off; - } - inline bool contains(const value_type& v) const - { - const T* data = Data; - const T* data_end = Data + Size; - while (data < data_end) - if (*data++ == v) - return true; - return false; - } - inline int index_from_pointer(const_iterator it) const - { - IM_ASSERT(it >= Data && it <= Data + Size); - const ptrdiff_t off = it - Data; - return (int)off; - } -}; - -// Helper: IM_NEW(), IM_PLACEMENT_NEW(), IM_DELETE() macros to call MemAlloc + Placement New, Placement Delete + MemFree -// We call C++ constructor on own allocated memory via the placement "new(ptr) Type()" syntax. -// Defining a custom placement new() with a dummy parameter allows us to bypass including which on some platforms -// complains when user has disabled exceptions. -struct ImNewDummy { -}; -inline void* operator new(size_t, ImNewDummy, void* ptr) { return ptr; } -inline void operator delete(void*, ImNewDummy, void*) {} // This is only required so we can use the symetrical new() -#define IM_PLACEMENT_NEW(_PTR) new (ImNewDummy(), _PTR) -#define IM_NEW(_TYPE) new (ImNewDummy(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE -template -void IM_DELETE(T* p) -{ - if (p) { - p->~T(); - ImGui::MemFree(p); - } -} - -// Helper: Execute a block of code at maximum once a frame. Convenient if you want to quickly create an UI within -// deep-nested code that runs multiple times every frame. Usage: static ImGuiOnceUponAFrame oaf; if (oaf) -// ImGui::Text("This will be called only once per frame"); -struct ImGuiOnceUponAFrame { - ImGuiOnceUponAFrame() { RefFrame = -1; } - mutable int RefFrame; - operator bool() const - { - int current_frame = ImGui::GetFrameCount(); - if (RefFrame == current_frame) - return false; - RefFrame = current_frame; - return true; - } -}; - -// Helper: Macro for ImGuiOnceUponAFrame. Attention: The macro expands into 2 statement so make sure you don't use it -// within e.g. an if() statement without curly braces. -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Will obsolete -#define IMGUI_ONCE_UPON_A_FRAME \ - static ImGuiOnceUponAFrame imgui_oaf; \ - if (imgui_oaf) -#endif - -// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" -struct ImGuiTextFilter { - IMGUI_API ImGuiTextFilter(const char* default_filter = ""); - IMGUI_API bool Draw(const char* label = "Filter (inc,-exc)", float width = 0.0f); // Helper calling InputText+Build - IMGUI_API bool PassFilter(const char* text, const char* text_end = NULL) const; - IMGUI_API void Build(); - void Clear() - { - InputBuf[0] = 0; - Build(); - } - bool IsActive() const { return !Filters.empty(); } - - // [Internal] - struct TextRange { - const char* b; - const char* e; - - TextRange() { b = e = NULL; } - TextRange(const char* _b, const char* _e) - { - b = _b; - e = _e; - } - const char* begin() const { return b; } - const char* end() const { return e; } - bool empty() const { return b == e; } - IMGUI_API void split(char separator, ImVector* out) const; - }; - char InputBuf[256]; - ImVector Filters; - int CountGrep; -}; - -// Helper: Text buffer for logging/accumulating text -struct ImGuiTextBuffer { - ImVector Buf; - - ImGuiTextBuffer() { Buf.push_back(0); } - inline char operator[](int i) { return Buf.Data[i]; } - const char* begin() const { return &Buf.front(); } - const char* end() const { return &Buf.back(); } // Buf is zero-terminated, so end() will point on the zero-terminator - int size() const { return Buf.Size - 1; } - bool empty() { return Buf.Size <= 1; } - void clear() - { - Buf.clear(); - Buf.push_back(0); - } - void reserve(int capacity) { Buf.reserve(capacity); } - const char* c_str() const { return Buf.Data; } - IMGUI_API void appendf(const char* fmt, ...) IM_FMTARGS(2); - IMGUI_API void appendfv(const char* fmt, va_list args) IM_FMTLIST(2); -}; - -// Helper: key->value storage -// Typically you don't have to worry about this since a storage is held within each Window. -// We use it to e.g. store collapse state for a tree (Int 0/1) -// This is optimized for efficient lookup (dichotomy into a contiguous buffer) and rare insertion (typically tied to -// user interactions aka max once a frame) You can use it as custom user storage for temporary values. Declare your own -// storage if, for example: -// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to -// store their state). -// - You want to store custom debug data easily without adding or editing structures in your code (probably not -// efficient, but convenient) Types are NOT stored, so it is up to you to make sure your Key don't collide with -// different types. -struct ImGuiStorage { - struct Pair { - ImGuiID key; - union { - int val_i; - float val_f; - void* val_p; - }; - Pair(ImGuiID _key, int _val_i) - { - key = _key; - val_i = _val_i; - } - Pair(ImGuiID _key, float _val_f) - { - key = _key; - val_f = _val_f; - } - Pair(ImGuiID _key, void* _val_p) - { - key = _key; - val_p = _val_p; - } - }; - ImVector Data; - - // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N) - // - Set***() functions find pair, insertion on demand if missing. - // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new pair. - void Clear() { Data.clear(); } - IMGUI_API int GetInt(ImGuiID key, int default_val = 0) const; - IMGUI_API void SetInt(ImGuiID key, int val); - IMGUI_API bool GetBool(ImGuiID key, bool default_val = false) const; - IMGUI_API void SetBool(ImGuiID key, bool val); - IMGUI_API float GetFloat(ImGuiID key, float default_val = 0.0f) const; - IMGUI_API void SetFloat(ImGuiID key, float val); - IMGUI_API void* GetVoidPtr(ImGuiID key) const; // default_val is NULL - IMGUI_API void SetVoidPtr(ImGuiID key, void* val); - - // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if you intend to do - // Get+Set. - // - References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() - // function invalidates the pointer. - // - A typical use case where this is convenient for quick hacking (e.g. add storage during a live Edit&Continue - // session if you can't modify existing struct) - // float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat("var", pvar, 0, 100.0f); some_var += *pvar; - IMGUI_API int* GetIntRef(ImGuiID key, int default_val = 0); - IMGUI_API bool* GetBoolRef(ImGuiID key, bool default_val = false); - IMGUI_API float* GetFloatRef(ImGuiID key, float default_val = 0.0f); - IMGUI_API void** GetVoidPtrRef(ImGuiID key, void* default_val = NULL); - - // Use on your own storage if you know only integer are being stored (open/close all tree nodes) - IMGUI_API void SetAllInt(int val); - - // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort - // once. - IMGUI_API void BuildSortByKey(); -}; - -// Shared state of InputText(), passed as an argument to your callback when a ImGuiInputTextFlags_Callback* flag is -// used. The callback function should return 0 by default. Special processing: -// - ImGuiInputTextFlags_CallbackCharFilter: return 1 if the character is not allowed. You may also set 'EventChar=0' -// as any character replacement are allowed. -// - ImGuiInputTextFlags_CallbackResize: notified by InputText() when the string is resized. BufTextLen is set to -// the new desired string length so you can update the string size on your side of the fence. You can also replace Buf -// pointer if your underlying data is reallocated. No need to initialize new characters or zero-terminator as InputText -// will do it right after the resize callback. -struct ImGuiInputTextCallbackData { - ImGuiInputTextFlags EventFlag; // One ImGuiInputTextFlags_Callback* // Read-only - ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only - void* UserData; // What user passed to InputText() // Read-only - - // Arguments for the different callback events - // - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() - // will take care of calling the resize callback if necessary. - // - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of - // 'Buf[]' directly. You need to update 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to - // true so InputText can update its internal state. - ImWchar EventChar; // Character input // Read-write // [CharFilter] Replace character or set to - // zero. return 1 is equivalent to setting EventChar=0; - ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only // [Completion,History] - char* Buf; // Text buffer // Read-write // [Resize] Can replace pointer / - // [Completion,History,Always] Only write to pointed data, don't replace the actual pointer! - int BufTextLen; // Text length in bytes // Read-write // [Resize,Completion,History,Always] Exclude - // zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length() - int BufSize; // Buffer capacity in bytes // Read-only // [Resize,Completion,History,Always] Include - // zero-terminator storage. In C land == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1 - bool BufDirty; // Set if you modify Buf/BufTextLen!! // Write // [Completion,History,Always] - int CursorPos; // // Read-write // [Completion,History,Always] - int SelectionStart; // // Read-write // [Completion,History,Always] == to - // SelectionEnd when no selection) - int SelectionEnd; // // Read-write // [Completion,History,Always] - - // Helper functions for text manipulation. - // Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection. - ImGuiInputTextCallbackData(); - IMGUI_API void DeleteChars(int pos, int bytes_count); - IMGUI_API void InsertChars(int pos, const char* text, const char* text_end = NULL); - bool HasSelection() const { return SelectionStart != SelectionEnd; } -}; - -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -typedef ImGuiInputTextCallback ImGuiTextEditCallback; // [OBSOLETE 1.63+] Made the names consistent -typedef ImGuiInputTextCallbackData ImGuiTextEditCallbackData; -#endif - -// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called -// during the next Begin(). NB: For basic min/max size constraint on each axis you don't need to use the callback! The -// SetNextWindowSizeConstraints() parameters are enough. -struct ImGuiSizeCallbackData { - void* UserData; // Read-only. What user passed to SetNextWindowSizeConstraints() - ImVec2 Pos; // Read-only. Window position, for reference. - ImVec2 CurrentSize; // Read-only. Current window size. - ImVec2 - DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. -}; - -// Data payload for Drag and Drop operations -struct ImGuiPayload { - // Members - void* Data; // Data (copied and owned by dear imgui) - int DataSize; // Data size - - // [Internal] - ImGuiID SourceId; // Source item id - ImGuiID SourceParentId; // Source parent id (if available) - int DataFrameCount; // Data timestamp - char DataType[32 + 1]; // Data type tag (short user-supplied string, 32 characters max) - bool Preview; // Set when AcceptDragDropPayload() was called and mouse has been hovering the target item (nb: handle - // overlapping drag targets) - bool Delivery; // Set when AcceptDragDropPayload() was called and mouse button is released over the target item. - - ImGuiPayload() { Clear(); } - void Clear() - { - SourceId = SourceParentId = 0; - Data = NULL; - DataSize = 0; - memset(DataType, 0, sizeof(DataType)); - DataFrameCount = -1; - Preview = Delivery = false; - } - bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; } - bool IsPreview() const { return Preview; } - bool IsDelivery() const { return Delivery; } -}; - -// Helpers macros to generate 32-bits encoded colors -#ifdef IMGUI_USE_BGRA_PACKED_COLOR -#define IM_COL32_R_SHIFT 16 -#define IM_COL32_G_SHIFT 8 -#define IM_COL32_B_SHIFT 0 -#define IM_COL32_A_SHIFT 24 -#define IM_COL32_A_MASK 0xFF000000 -#else -#define IM_COL32_R_SHIFT 0 -#define IM_COL32_G_SHIFT 8 -#define IM_COL32_B_SHIFT 16 -#define IM_COL32_A_SHIFT 24 -#define IM_COL32_A_MASK 0xFF000000 -#endif -#define IM_COL32(R, G, B, A) \ - (((ImU32)(A) << IM_COL32_A_SHIFT) | ((ImU32)(B) << IM_COL32_B_SHIFT) | ((ImU32)(G) << IM_COL32_G_SHIFT) | \ - ((ImU32)(R) << IM_COL32_R_SHIFT)) -#define IM_COL32_WHITE IM_COL32(255, 255, 255, 255) // Opaque white = 0xFFFFFFFF -#define IM_COL32_BLACK IM_COL32(0, 0, 0, 255) // Opaque black -#define IM_COL32_BLACK_TRANS IM_COL32(0, 0, 0, 0) // Transparent black = 0x00000000 - -// Helper: ImColor() implicity converts colors to either ImU32 (packed 4x1 byte) or ImVec4 (4x1 float) -// Prefer using IM_COL32() macros if you want a guaranteed compile-time ImU32 for usage with ImDrawList API. -// **Avoid storing ImColor! Store either u32 of ImVec4. This is not a full-featured color class. MAY OBSOLETE. -// **None of the ImGui API are using ImColor directly but you can use it as a convenience to pass colors in either ImU32 -// or ImVec4 formats. Explicitly cast to ImU32 or ImVec4 if needed. -struct ImColor { - ImVec4 Value; - - ImColor() { Value.x = Value.y = Value.z = Value.w = 0.0f; } - ImColor(int r, int g, int b, int a = 255) - { - float sc = 1.0f / 255.0f; - Value.x = (float)r * sc; - Value.y = (float)g * sc; - Value.z = (float)b * sc; - Value.w = (float)a * sc; - } - ImColor(ImU32 rgba) - { - float sc = 1.0f / 255.0f; - Value.x = (float)((rgba >> IM_COL32_R_SHIFT) & 0xFF) * sc; - Value.y = (float)((rgba >> IM_COL32_G_SHIFT) & 0xFF) * sc; - Value.z = (float)((rgba >> IM_COL32_B_SHIFT) & 0xFF) * sc; - Value.w = (float)((rgba >> IM_COL32_A_SHIFT) & 0xFF) * sc; - } - ImColor(float r, float g, float b, float a = 1.0f) - { - Value.x = r; - Value.y = g; - Value.z = b; - Value.w = a; - } - ImColor(const ImVec4& col) { Value = col; } - inline operator ImU32() const { return ImGui::ColorConvertFloat4ToU32(Value); } - inline operator ImVec4() const { return Value; } - - // FIXME-OBSOLETE: May need to obsolete/cleanup those helpers. - inline void SetHSV(float h, float s, float v, float a = 1.0f) - { - ImGui::ColorConvertHSVtoRGB(h, s, v, Value.x, Value.y, Value.z); - Value.w = a; - } - static ImColor HSV(float h, float s, float v, float a = 1.0f) - { - float r, g, b; - ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); - return ImColor(r, g, b, a); - } -}; - -// Helper: Manually clip large list of items. -// If you are submitting lots of evenly spaced items and you have a random access to the list, you can perform coarse -// clipping based on visibility to save yourself from processing those items at all. The clipper calculates the range of -// visible items and advance the cursor to compensate for the non-visible items we have skipped. ImGui already clip -// items based on their bounds but it needs to measure text size to do so. Coarse clipping before submission makes this -// cost and your own data fetching/submission cost null. Usage: -// ImGuiListClipper clipper(1000); // we have 1000 elements, evenly spaced. -// while (clipper.Step()) -// for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) -// ImGui::Text("line number %d", i); -// - Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the -// element height (step skipped if we passed a known height as second arg to constructor). -// - Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and -// position the cursor before the first element. -// - (Step 2: dummy step only required if an explicit items_height was passed to constructor or Begin() and user call -// Step(). Does nothing and switch to Step 3.) -// - Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), -// advance the cursor to the end of the list and then returns 'false' to end the loop. -struct ImGuiListClipper { - float StartPosY; - float ItemsHeight; - int ItemsCount, StepNo, DisplayStart, DisplayEnd; - - // items_count: Use -1 to ignore (you can call Begin later). Use INT_MAX if you don't know how many items you have - // (in which case the cursor won't be advanced in the final step). items_height: Use -1.0f to be calculated - // automatically on first step. Otherwise pass in the distance between your items, typically - // GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). If you don't specify an items_height, you NEED to - // call Step(). If you specify items_height you may call the old Begin()/End() api directly, but prefer calling - // Step(). - ImGuiListClipper(int items_count = -1, float items_height = -1.0f) - { - Begin(items_count, items_height); - } // NB: Begin() initialize every fields (as we allow user to call Begin/End multiple times on a same instance if they - // want). - ~ImGuiListClipper() { IM_ASSERT(ItemsCount == -1); } // Assert if user forgot to call End() or Step() until false. - - IMGUI_API bool Step(); // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can - // process/draw those items. - IMGUI_API void Begin(int items_count, float items_height = -1.0f); // Automatically called by constructor if you - // passed 'items_count' or by Step() in Step 1. - IMGUI_API void End(); // Automatically called on the last call of Step() that returns false. -}; - -//----------------------------------------------------------------------------- -// Draw List -// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of -// ImDrawList. -//----------------------------------------------------------------------------- - -// Draw callbacks for advanced uses. -// NB- You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering (you -// can poke into the draw list for that) Draw callback may be useful for example, A) Change your GPU render state, B) -// render a complex 3D scene inside a UI element (without an intermediate texture/render target), etc. The expected -// behavior from your rendering function is 'if (cmd.UserCallback != NULL) cmd.UserCallback(parent_list, cmd); else -// RenderTriangles()' -typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd); - -// Typically, 1 command = 1 GPU draw call (unless command is a callback) -struct ImDrawCmd { - unsigned int ElemCount; // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the - // callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. - ImVec4 ClipRect; // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in - // "viewport" coordinates - ImTextureID TextureId; // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to - // Image*() functions. Ignore if never using images or multiple fonts atlas. - ImDrawCallback UserCallback; // If != NULL, call the function instead of rendering the vertices. clip_rect and - // texture_id will be set normally. - void* UserCallbackData; // The draw callback code can access this. - - ImDrawCmd() - { - ElemCount = 0; - ClipRect.x = ClipRect.y = ClipRect.z = ClipRect.w = 0.0f; - TextureId = NULL; - UserCallback = NULL; - UserCallbackData = NULL; - } -}; - -// Vertex index (override with '#define ImDrawIdx unsigned int' inside in imconfig.h) -#ifndef ImDrawIdx -typedef unsigned short ImDrawIdx; -#endif - -// Vertex layout -#ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT -struct ImDrawVert { - ImVec2 pos; - ImVec2 uv; - ImU32 col; -}; -#else -// You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h -// The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add -// other fields as needed to simplify integration in your engine. The type has to be described within the macro (you can -// either declare the struct or use a typedef) NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO -// ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR -// THEM DURING RENDER OR TO IGNORE THEM. -IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; -#endif - -// Draw channels are used by the Columns API to "split" the render list into different channels while building, so items -// of each column can be batched together. You can also use them to simulate drawing layers and submit primitives in a -// different order than how they will be rendered. -struct ImDrawChannel { - ImVector CmdBuffer; - ImVector IdxBuffer; -}; - -enum ImDrawCornerFlags_ { - ImDrawCornerFlags_TopLeft = 1 << 0, // 0x1 - ImDrawCornerFlags_TopRight = 1 << 1, // 0x2 - ImDrawCornerFlags_BotLeft = 1 << 2, // 0x4 - ImDrawCornerFlags_BotRight = 1 << 3, // 0x8 - ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, // 0x3 - ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, // 0xC - ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, // 0x5 - ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, // 0xA - ImDrawCornerFlags_All = - 0xF // In your function calls you may use ~0 (= all bits sets) instead of ImDrawCornerFlags_All, as a convenience -}; - -enum ImDrawListFlags_ { ImDrawListFlags_AntiAliasedLines = 1 << 0, - ImDrawListFlags_AntiAliasedFill = 1 << 1 }; - -// Draw command list -// This is the low-level list of polygons that ImGui functions are filling. At the end of the frame, all command lists -// are passed to your ImGuiIO::RenderDrawListFn function for rendering. Each ImGui window contains its own ImDrawList. -// You can use ImGui::GetWindowDrawList() to access the current window draw list and draw custom primitives. You can -// interleave normal ImGui:: calls and adding primitives to the current draw list. All positions are generally in pixel -// coordinates (top-left at (0,0), bottom-right at io.DisplaySize), but you are totally free to apply whatever -// transformation matrix to want to the data (if you apply such transformation you'll want to apply it to ClipRect as -// well) Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: -// functions), if you use this API a lot consider coarse culling your drawn objects. -struct ImDrawList { - // This is what you have to render - ImVector - CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. - ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those - ImVector VtxBuffer; // Vertex buffer. - ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. - - // [Internal, used while building lists] - const ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get - // the one from current ImGui context) - const char* _OwnerName; // Pointer to owner window's name for debugging - unsigned int _VtxCurrentIdx; // [Internal] == VtxBuffer.Size - ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the - // ImVector<> operators too much) - ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the - // ImVector<> operators too much) - ImVector _ClipRectStack; // [Internal] - ImVector _TextureIdStack; // [Internal] - ImVector _Path; // [Internal] current path building - int _ChannelsCurrent; // [Internal] current channel number (0) - int _ChannelsCount; // [Internal] number of active channels (1+) - ImVector _Channels; // [Internal] draw channels for columns API (not resized down so _ChannelsCount may - // be smaller than _Channels.Size) - - // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own - // ImDrawListSharedData (so you can use ImDrawList without ImGui) - ImDrawList(const ImDrawListSharedData* shared_data) - { - _Data = shared_data; - _OwnerName = NULL; - Clear(); - } - ~ImDrawList() { ClearFreeMemory(); } - IMGUI_API void PushClipRect(ImVec2 clip_rect_min, ImVec2 clip_rect_max, - bool intersect_with_current_clip_rect = - false); // Render-level scissoring. This is passed down to your render function but not - // used for CPU-side coarse clipping. Prefer using higher-level - // ImGui::PushClipRect() to affect logic (hit-testing and widget culling) - IMGUI_API void PushClipRectFullScreen(); - IMGUI_API void PopClipRect(); - IMGUI_API void PushTextureID(ImTextureID texture_id); - IMGUI_API void PopTextureID(); - inline ImVec2 GetClipRectMin() const - { - const ImVec4& cr = _ClipRectStack.back(); - return ImVec2(cr.x, cr.y); - } - inline ImVec2 GetClipRectMax() const - { - const ImVec4& cr = _ClipRectStack.back(); - return ImVec2(cr.z, cr.w); - } - - // Primitives - IMGUI_API void AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness = 1.0f); - IMGUI_API void AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, - int rounding_corners_flags = ImDrawCornerFlags_All, - float thickness = 1.0f); // a: upper-left, b: lower-right, rounding_corners_flags: 4-bits - // corresponding to which corner to round - IMGUI_API void AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding = 0.0f, - int rounding_corners_flags = ImDrawCornerFlags_All); // a: upper-left, b: lower-right - IMGUI_API void AddRectFilledMultiColor(const ImVec2& a, const ImVec2& b, ImU32 col_upr_left, ImU32 col_upr_right, - ImU32 col_bot_right, ImU32 col_bot_left); - IMGUI_API void AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, - float thickness = 1.0f); - IMGUI_API void AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col); - IMGUI_API void AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness = 1.0f); - IMGUI_API void AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col); - IMGUI_API void AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12, - float thickness = 1.0f); - IMGUI_API void AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12); - IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); - IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, - const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); - IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, - const ImVec2& uv_a = ImVec2(0, 0), const ImVec2& uv_b = ImVec2(1, 1), ImU32 col = 0xFFFFFFFF); - IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, - const ImVec2& d, const ImVec2& uv_a = ImVec2(0, 0), const ImVec2& uv_b = ImVec2(1, 0), - const ImVec2& uv_c = ImVec2(1, 1), const ImVec2& uv_d = ImVec2(0, 1), - ImU32 col = 0xFFFFFFFF); - IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, - const ImVec2& uv_b, ImU32 col, float rounding, - int rounding_corners = ImDrawCornerFlags_All); - IMGUI_API void AddPolyline(const ImVec2* points, const int num_points, ImU32 col, bool closed, float thickness); - IMGUI_API void AddConvexPolyFilled(const ImVec2* points, const int num_points, - ImU32 col); // Note: Anti-aliased filling requires points to be in clockwise order. - IMGUI_API void AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, - float thickness, int num_segments = 0); - - // Stateful path API, add points then finish with PathFillConvex() or PathStroke() - inline void PathClear() { _Path.resize(0); } - inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } - inline void PathLineToMergeDuplicate(const ImVec2& pos) - { - if (_Path.Size == 0 || memcmp(&_Path[_Path.Size - 1], &pos, 8) != 0) - _Path.push_back(pos); - } - inline void PathFillConvex(ImU32 col) - { - AddConvexPolyFilled(_Path.Data, _Path.Size, col); - PathClear(); - } // Note: Anti-aliased filling requires points to be in clockwise order. - inline void PathStroke(ImU32 col, bool closed, float thickness = 1.0f) - { - AddPolyline(_Path.Data, _Path.Size, col, closed, thickness); - PathClear(); - } - IMGUI_API void PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments = 10); - IMGUI_API void PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, - int a_max_of_12); // Use precomputed angles for a 12 steps circle - IMGUI_API void PathBezierCurveTo(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, int num_segments = 0); - IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, - int rounding_corners_flags = ImDrawCornerFlags_All); - - // Channels - // - Use to simulate layers. By switching channels to can render out-of-order (e.g. submit foreground primitives - // before background primitives) - // - Use to minimize draw calls (e.g. if going back-and-forth between multiple non-overlapping clipping rectangles, - // prefer to append into separate channels then merge at the end) - IMGUI_API void ChannelsSplit(int channels_count); - IMGUI_API void ChannelsMerge(); - IMGUI_API void ChannelsSetCurrent(int channel_index); - - // Advanced - IMGUI_API void AddCallback(ImDrawCallback callback, - void* callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd - // and call the function instead of rendering triangles. - IMGUI_API void - AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / - // blending). Otherwise primitives are merged into the same draw-call as much as possible - IMGUI_API ImDrawList* CloneOutput() const; // Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer. - - // Internal helpers - // NB: all primitives needs to be reserved via PrimReserve() beforehand! - IMGUI_API void Clear(); - IMGUI_API void ClearFreeMemory(); - IMGUI_API void PrimReserve(int idx_count, int vtx_count); - IMGUI_API void PrimRect(const ImVec2& a, const ImVec2& b, - ImU32 col); // Axis aligned rectangle (composed of two triangles) - IMGUI_API void PrimRectUV(const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col); - IMGUI_API void PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, - const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col); - inline void PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) - { - _VtxWritePtr->pos = pos; - _VtxWritePtr->uv = uv; - _VtxWritePtr->col = col; - _VtxWritePtr++; - _VtxCurrentIdx++; - } - inline void PrimWriteIdx(ImDrawIdx idx) - { - *_IdxWritePtr = idx; - _IdxWritePtr++; - } - inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) - { - PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); - PrimWriteVtx(pos, uv, col); - } - IMGUI_API void UpdateClipRect(); - IMGUI_API void UpdateTextureID(); -}; - -// All draw data to render an ImGui frame -// (NB: the style and the naming convention here is a little inconsistent but we preserve them for backward -// compatibility purpose) -struct ImDrawData { - bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. - ImDrawList** - CmdLists; // Array of ImDrawList* to render. The ImDrawList are owned by ImGuiContext and only pointed to from here. - int CmdListsCount; // Number of ImDrawList* to render - int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size - int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size - ImVec2 DisplayPos; // Upper-left position of the viewport to render (== upper-left of the orthogonal projection matrix - // to use) - ImVec2 DisplaySize; // Size of the viewport to render (== io.DisplaySize for the main viewport) (DisplayPos + - // DisplaySize == lower-right of the orthogonal projection matrix to use) - - // Functions - ImDrawData() - { - Valid = false; - Clear(); - } - ~ImDrawData() { Clear(); } - void Clear() - { - Valid = false; - CmdLists = NULL; - CmdListsCount = TotalVtxCount = TotalIdxCount = 0; - DisplayPos = DisplaySize = ImVec2(0.f, 0.f); - } // The ImDrawList are owned by ImGuiContext! - IMGUI_API void DeIndexAllBuffers(); // Helper to convert all buffers from indexed to non-indexed, in case you cannot - // render indexed. Note: this is slow and most likely a waste of resources. Always - // prefer indexed rendering! - IMGUI_API void ScaleClipRects( - const ImVec2& sc); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a - // different scale than ImGui expects, or if there is a difference between your window resolution - // and framebuffer resolution. -}; - -struct ImFontConfig { - void* FontData; // // TTF/OTF data - int FontDataSize; // // TTF/OTF data size - bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete - // memory itself). - int FontNo; // 0 // Index of font within TTF/OTF file - float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). - int OversampleH; // 3 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel - // positions on the Y axis. - int OversampleV; // 1 // Rasterize at higher quality for sub-pixel positioning. We don't use sub-pixel - // positions on the Y axis. - bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel - // aligned font with the default font. If enabled, you can set OversampleH/V to 1. - ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. - ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. - const ImWchar* - GlyphRanges; // NULL // Pointer to a user-provided list of Unicode range (2 value per range, values are - // inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. - float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to - // enforce mono-space font - float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs - bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont - // (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of - // different heights. - unsigned int RasterizerFlags; // 0x00 // Settings for custom font rasterizer (e.g. ImGuiFreeType). Leave as zero - // if you aren't using one. - float RasterizerMultiply; // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may - // be a good workaround to make them more readable. - - // [Internal] - char Name[40]; // Name (strictly to ease debugging) - ImFont* DstFont; - - IMGUI_API ImFontConfig(); -}; - -struct ImFontGlyph { - ImWchar Codepoint; // 0x0000..0xFFFF - float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) - float X0, Y0, X1, Y1; // Glyph corners - float U0, V0, U1, V1; // Texture coordinates -}; - -enum ImFontAtlasFlags_ { - ImFontAtlasFlags_None = 0, - ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two - ImFontAtlasFlags_NoMouseCursors = 1 << 1 // Don't build software mouse cursors into the atlas -}; - -// Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding: -// - One or more fonts. -// - Custom graphics data needed to render the shapes needed by Dear ImGui. -// - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in -// the font atlas). -// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by -// your graphics api. -// - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code -// will be loaded for you. -// - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. -// - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples) -// - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics -// API. -// This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID -// for more details. -// Common pitfalls: -// - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until -// the -// atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data. -// - Important: By default, AddFontFromMemoryTTF() takes ownership of the data. Even though we are not writing to it, we -// will free the pointer on destruction. -// You can set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed, -// - Even though many functions are suffixed with "TTF", OTF data is supported just as well. -// - This is an old API and it is currently awkward for those and and various other reasons! We will address them in the -// future! -struct ImFontAtlas { - IMGUI_API ImFontAtlas(); - IMGUI_API ~ImFontAtlas(); - IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); - IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); - IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, - const ImWchar* glyph_ranges = NULL); - IMGUI_API ImFont* AddFontFromMemoryTTF( - void* font_data, int font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, - const ImWchar* glyph_ranges = - NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. - // Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. - IMGUI_API ImFont* AddFontFromMemoryCompressedTTF( - const void* compressed_font_data, int compressed_font_size, float size_pixels, const ImFontConfig* font_cfg = NULL, - const ImWchar* glyph_ranges = - NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. - IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF( - const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, - const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with - // binary_to_compressed_c.cpp with -base85 parameter. - IMGUI_API void ClearInputData(); // Clear input data (all ImFontConfig structures including sizes, TTF data, glyph - // ranges, etc.) = all the data used to build the texture and fonts. - IMGUI_API void ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to - // graphics memory. - IMGUI_API void ClearFonts(); // Clear output font data (glyphs storage, UV coordinates). - IMGUI_API void Clear(); // Clear all input and output. - - // Build atlas, retrieve pixel data. - // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store - // your texture handle with SetTexID(). The pitch is always = Width * BytesPerPixels (1 or 4) Building in RGBA32 - // format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color - // data into the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white - // (~75% of memory/bandwidth waste. - IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. - IMGUI_API bool IsBuilt() { return Fonts.Size > 0 && (TexPixelsAlpha8 != NULL || TexPixelsRGBA32 != NULL); } - IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, - int* out_bytes_per_pixel = NULL); // 1 byte per-pixel - IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, - int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - void SetTexID(ImTextureID id) { TexID = id; } - - //------------------------------------------- - // Glyph Ranges - //------------------------------------------- - - // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) - // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string - // literal using the u8"Hello world" syntax. See FAQ for details. NB: Consider using GlyphRangesBuilder to build glyph - // ranges from textual data. - IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin - IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters - IMGUI_API const ImWchar* - GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs - IMGUI_API const ImWchar* GetGlyphRangesChineseFull(); // Default + Half-Width + Japanese Hiragana/Katakana + full set - // of about 21000 CJK Unified Ideographs - IMGUI_API const ImWchar* - GetGlyphRangesChineseSimplifiedCommon(); // Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK - // Unified Ideographs for common simplified Chinese - IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters - IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters - - // Helpers to build glyph ranges from text data. Feed your application strings/characters to it then call - // BuildRanges(). - struct GlyphRangesBuilder { - ImVector UsedChars; // Store 1-bit per Unicode code point (0=unused, 1=used) - GlyphRangesBuilder() - { - UsedChars.resize(0x10000 / 8); - memset(UsedChars.Data, 0, 0x10000 / 8); - } - bool GetBit(int n) const { return (UsedChars[n >> 3] & (1 << (n & 7))) != 0; } - void SetBit(int n) { UsedChars[n >> 3] |= 1 << (n & 7); } // Set bit 'c' in the array - void AddChar(ImWchar c) { SetBit(c); } // Add character - IMGUI_API void AddText(const char* text, - const char* text_end = NULL); // Add string (each character of the UTF-8 string are added) - IMGUI_API void AddRanges( - const ImWchar* ranges); // Add ranges, e.g. builder.AddRanges(ImFontAtlas::GetGlyphRangesDefault()) to force add - // all of ASCII/Latin+Ext - IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges - }; - - //------------------------------------------- - // Custom Rectangles/Glyphs API - //------------------------------------------- - - // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. After calling Build(), you - // can query the rectangle position and render your pixels. You can also request your rectangles to be mapped as font - // glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. - struct CustomRect { - unsigned int ID; // Input // User ID. Use <0x10000 to map into a font glyph, >=0x10000 for other/internal/custom - // texture data. - unsigned short Width, Height; // Input // Desired rectangle dimension - unsigned short X, Y; // Output // Packed position in Atlas - float GlyphAdvanceX; // Input // For custom font glyphs only (ID<0x10000): glyph xadvance - ImVec2 GlyphOffset; // Input // For custom font glyphs only (ID<0x10000): glyph display offset - ImFont* Font; // Input // For custom font glyphs only (ID<0x10000): target font - CustomRect() - { - ID = 0xFFFFFFFF; - Width = Height = 0; - X = Y = 0xFFFF; - GlyphAdvanceX = 0.0f; - GlyphOffset = ImVec2(0, 0); - Font = NULL; - } - bool IsPacked() const { return X != 0xFFFF; } - }; - - IMGUI_API int AddCustomRectRegular( - unsigned int id, int width, - int height); // Id needs to be >= 0x10000. Id >= 0x80000000 are reserved for ImGui and ImDrawList - IMGUI_API int AddCustomRectFontGlyph( - ImFont* font, ImWchar id, int width, int height, float advance_x, - const ImVec2& offset = ImVec2(0, - 0)); // Id needs to be < 0x10000 to register a rectangle to map into a specific font. - const CustomRect* GetCustomRectByIndex(int index) const - { - if (index < 0) - return NULL; - return &CustomRects[index]; - } - - // [Internal] - IMGUI_API void CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max); - IMGUI_API bool GetMouseCursorTexData(ImGuiMouseCursor cursor, ImVec2* out_offset, ImVec2* out_size, - ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); - - //------------------------------------------- - // Members - //------------------------------------------- - - bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. - ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) - ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is - // passed back to you during rendering via the ImDrawCmd structure. - int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your - // graphics API have texture size restrictions you may want to increase texture width to decrease - // height. - int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. - - // [Internal] - // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. - unsigned char* - TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight - unsigned int* - TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 - int TexWidth; // Texture width calculated during Build(). - int TexHeight; // Texture height calculated during Build(). - ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) - ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel - ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling - // ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. - ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. - ImVector ConfigData; // Internal data - int CustomRectIds[1]; // Identifiers of custom texture rectangle used by ImFontAtlas/ImDrawList -}; - -// Font runtime data and rendering -// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or -// GetTexDataAsRGBA32(). -struct ImFont { - // Members: Hot ~62/78 bytes - float FontSize; // // Height of characters, set during loading (don't change after loading) - float Scale; // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with - // SetFontScale() - ImVec2 DisplayOffset; // = (0.f,0.f) // Offset font rendering by xx pixels - ImVector Glyphs; // // All glyphs. - ImVector - IndexAdvanceX; // // Sparse. Glyphs->AdvanceX in a directly indexable way (more cache-friendly, for - // CalcTextSize functions which are often bottleneck in large UI). - ImVector IndexLookup; // // Sparse. Index glyphs by Unicode code-point. - const ImFontGlyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) - float FallbackAdvanceX; // == FallbackGlyph->AdvanceX - ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar() - - // Members: Cold ~18/26 bytes - short ConfigDataCount; // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when - // merging multiple font sources into one ImFont. - ImFontConfig* ConfigData; // // Pointer within ContainerAtlas->ConfigData - ImFontAtlas* ContainerAtlas; // // What we has been loaded into - float Ascent, Descent; // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] - bool DirtyLookupTables; - int MetricsTotalSurface; // // Total surface in pixels to get an idea of the font rasterization/texture - // cost (not exact, we approximate the cost of padding between glyphs) - - // Methods - IMGUI_API ImFont(); - IMGUI_API ~ImFont(); - IMGUI_API void ClearOutputData(); - IMGUI_API void BuildLookupTable(); - IMGUI_API const ImFontGlyph* FindGlyph(ImWchar c) const; - IMGUI_API const ImFontGlyph* FindGlyphNoFallback(ImWchar c) const; - IMGUI_API void SetFallbackChar(ImWchar c); - float GetCharAdvance(ImWchar c) const - { - return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; - } - bool IsLoaded() const { return ContainerAtlas != NULL; } - const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } - - // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. - // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. - IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, - const char* text_end = NULL, const char** remaining = NULL) const; // utf8 - IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, - float wrap_width) const; - IMGUI_API void RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const; - IMGUI_API void RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, - const char* text_begin, const char* text_end, float wrap_width = 0.0f, - bool cpu_fine_clip = false) const; - - // [Internal] - IMGUI_API void GrowIndex(int new_size); - IMGUI_API void AddGlyph(ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, - float advance_x); - IMGUI_API void AddRemapChar( - ImWchar dst, ImWchar src, - bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be - // called AFTER fonts have been built. - -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - typedef ImFontGlyph Glyph; // OBSOLETE 1.52+ -#endif -}; - -#if defined(__clang__) -#pragma clang diagnostic pop -#elif defined(__GNUC__) && __GNUC__ >= 8 -#pragma GCC diagnostic pop -#endif - -// Include imgui_user.h at the end of imgui.h (convenient for user to only explicitly include vanilla imgui.h) -#ifdef IMGUI_INCLUDE_IMGUI_USER_H -#include "imgui_user.h" -#endif diff --git a/Framework/src/imgui/imgui_demo.cpp b/Framework/src/imgui/imgui_demo.cpp deleted file mode 100644 index de891bec19..0000000000 --- a/Framework/src/imgui/imgui_demo.cpp +++ /dev/null @@ -1,3589 +0,0 @@ -// dear imgui, v1.65 -// (demo code) - -// Message to the person tempted to delete this file when integrating ImGui into their code base: -// Don't do it! Do NOT remove this file from your project! It is useful reference code that you and other users will want to refer to. -// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow(). -// During development, you can call ImGui::ShowDemoWindow() in your code to learn about various features of ImGui. Have it wired in a debug menu! -// Removing this file from your project is hindering access to documentation for everyone in your team, likely leading you to poorer usage of the library. -// Note that you can #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h for the same effect. -// If you want to link core ImGui in your final builds but not those demo windows, #define IMGUI_DISABLE_DEMO_WINDOWS in imconfig.h and those functions will be empty. -// In other situation, when you have ImGui available you probably want this to be available for reference and execution. -// Thank you, -// -Your beloved friend, imgui_demo.cpp (that you won't delete) - -// Message to beginner C/C++ programmers about the meaning of the 'static' keyword: in this demo code, we frequently we use 'static' variables inside functions. -// A static variable persist across calls, so it is essentially like a global variable but declared inside the scope of the function. -// We do this as a way to gather code and data in the same place, just to make the demo code faster to read, faster to write, and use less code. -// It also happens to be a convenient way of storing simple UI related information as long as your function doesn't need to be reentrant or used in threads. -// This might be a pattern you occasionally want to use in your code, but most of the real data you would be editing is likely to be stored outside your functions. - -/* - -Index of this file: - -// [SECTION] Forward Declarations, Helpers -// [SECTION] Demo Window / ShowDemoWindow() -// [SECTION] Style Editor / ShowStyleEditor() -// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() -// [SECTION] Example App: Debug Console / ShowExampleAppConsole() -// [SECTION] Example App: Debug Log / ShowExampleAppLog() -// [SECTION] Example App: Simple Layout / ShowExampleAppLayout() -// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() -// [SECTION] Example App: Long Text / ShowExampleAppLongText() -// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize() -// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize() -// [SECTION] Example App: Simple Overlay / ShowExampleAppSimpleOverlay() -// [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles() -// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() - -*/ - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include "imgui.h" -#include // toupper, isprint -#include // INT_MIN, INT_MAX -#include // sqrtf, powf, cosf, sinf, floorf, ceilf -#include // vsnprintf, sscanf, printf -#include // NULL, malloc, free, atoi -#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier -#include // intptr_t -#else -#include // intptr_t -#endif - -#ifdef _MSC_VER -#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen -#define vsnprintf _vsnprintf -#endif -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // warning : 'xx' is deprecated: The POSIX name for this item.. // for strdup used in demo code (so user can copy & paste the code) -#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' -#pragma clang diagnostic ignored "-Wformat-security" // warning : warning: format string is not a string literal -#pragma clang diagnostic ignored "-Wexit-time-destructors" // warning : declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. -#if __has_warning("-Wreserved-id-macro") -#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // -#endif -#elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size -#pragma GCC diagnostic ignored "-Wformat-security" // warning : format string is not a string literal (potentially insecure) -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#if (__GNUC__ >= 6) -#pragma GCC diagnostic ignored "-Wmisleading-indentation" // warning: this 'if' clause does not guard this statement // GCC 6.0+ only. See #883 on GitHub. -#endif -#endif - -// Play it nice with Windows users. Notepad in 2017 still doesn't display text data with Unix-style \n. -#ifdef _WIN32 -#define IM_NEWLINE "\r\n" -#else -#define IM_NEWLINE "\n" -#endif - -#define IM_MAX(_A,_B) (((_A) >= (_B)) ? (_A) : (_B)) - -//----------------------------------------------------------------------------- -// [SECTION] Forward Declarations, Helpers -//----------------------------------------------------------------------------- - -#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && defined(IMGUI_DISABLE_TEST_WINDOWS) && !defined(IMGUI_DISABLE_DEMO_WINDOWS) // Obsolete name since 1.53, TEST->DEMO -#define IMGUI_DISABLE_DEMO_WINDOWS -#endif - -#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) - -// Forward Declarations -static void ShowExampleAppMainMenuBar(); -static void ShowExampleAppConsole(bool* p_open); -static void ShowExampleAppLog(bool* p_open); -static void ShowExampleAppLayout(bool* p_open); -static void ShowExampleAppPropertyEditor(bool* p_open); -static void ShowExampleAppLongText(bool* p_open); -static void ShowExampleAppAutoResize(bool* p_open); -static void ShowExampleAppConstrainedResize(bool* p_open); -static void ShowExampleAppSimpleOverlay(bool* p_open); -static void ShowExampleAppWindowTitles(bool* p_open); -static void ShowExampleAppCustomRendering(bool* p_open); -static void ShowExampleMenuFile(); - -// Helper to display a little (?) mark which shows a tooltip when hovered. -static void ShowHelpMarker(const char* desc) -{ - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextUnformatted(desc); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } -} - -// Helper to display basic user controls. -void ImGui::ShowUserGuide() -{ - ImGui::BulletText("Double-click on title bar to collapse window."); - ImGui::BulletText("Click and drag on lower right corner to resize window\n(double-click to auto fit window to its contents)."); - ImGui::BulletText("Click and drag on any empty space to move window."); - ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields."); - ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text."); - if (ImGui::GetIO().FontAllowUserScaling) - ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents."); - ImGui::BulletText("Mouse Wheel to scroll."); - ImGui::BulletText("While editing text:\n"); - ImGui::Indent(); - ImGui::BulletText("Hold SHIFT or use mouse to select text."); - ImGui::BulletText("CTRL+Left/Right to word jump."); - ImGui::BulletText("CTRL+A or double-click to select all."); - ImGui::BulletText("CTRL+X,CTRL+C,CTRL+V to use clipboard."); - ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo."); - ImGui::BulletText("ESCAPE to revert."); - ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract."); - ImGui::Unindent(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Demo Window / ShowDemoWindow() -//----------------------------------------------------------------------------- - -// Demonstrate most Dear ImGui features (this is big function!) -// You may execute this function to experiment with the UI and understand what it does. You may then search for keywords in the code when you are interested by a specific feature. -void ImGui::ShowDemoWindow(bool* p_open) -{ - // Examples Apps (accessible from the "Examples" menu) - static bool show_app_main_menu_bar = false; - static bool show_app_console = false; - static bool show_app_log = false; - static bool show_app_layout = false; - static bool show_app_property_editor = false; - static bool show_app_long_text = false; - static bool show_app_auto_resize = false; - static bool show_app_constrained_resize = false; - static bool show_app_simple_overlay = false; - static bool show_app_window_titles = false; - static bool show_app_custom_rendering = false; - - if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); - if (show_app_console) ShowExampleAppConsole(&show_app_console); - if (show_app_log) ShowExampleAppLog(&show_app_log); - if (show_app_layout) ShowExampleAppLayout(&show_app_layout); - if (show_app_property_editor) ShowExampleAppPropertyEditor(&show_app_property_editor); - if (show_app_long_text) ShowExampleAppLongText(&show_app_long_text); - if (show_app_auto_resize) ShowExampleAppAutoResize(&show_app_auto_resize); - if (show_app_constrained_resize) ShowExampleAppConstrainedResize(&show_app_constrained_resize); - if (show_app_simple_overlay) ShowExampleAppSimpleOverlay(&show_app_simple_overlay); - if (show_app_window_titles) ShowExampleAppWindowTitles(&show_app_window_titles); - if (show_app_custom_rendering) ShowExampleAppCustomRendering(&show_app_custom_rendering); - - // Dear ImGui Apps (accessible from the "Help" menu) - static bool show_app_metrics = false; - static bool show_app_style_editor = false; - static bool show_app_about = false; - - if (show_app_metrics) { ImGui::ShowMetricsWindow(&show_app_metrics); } - if (show_app_style_editor) { ImGui::Begin("Style Editor", &show_app_style_editor); ImGui::ShowStyleEditor(); ImGui::End(); } - if (show_app_about) - { - ImGui::Begin("About Dear ImGui", &show_app_about, ImGuiWindowFlags_AlwaysAutoResize); - ImGui::Text("Dear ImGui, %s", ImGui::GetVersion()); - ImGui::Separator(); - ImGui::Text("By Omar Cornut and all dear imgui contributors."); - ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); - ImGui::End(); - } - - // Demonstrate the various window flags. Typically you would just use the default! - static bool no_titlebar = false; - static bool no_scrollbar = false; - static bool no_menu = false; - static bool no_move = false; - static bool no_resize = false; - static bool no_collapse = false; - static bool no_close = false; - static bool no_nav = false; - - ImGuiWindowFlags window_flags = 0; - if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; - if (no_scrollbar) window_flags |= ImGuiWindowFlags_NoScrollbar; - if (!no_menu) window_flags |= ImGuiWindowFlags_MenuBar; - if (no_move) window_flags |= ImGuiWindowFlags_NoMove; - if (no_resize) window_flags |= ImGuiWindowFlags_NoResize; - if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; - if (no_nav) window_flags |= ImGuiWindowFlags_NoNav; - if (no_close) p_open = NULL; // Don't pass our bool* to Begin - - // We specify a default position/size in case there's no data in the .ini file. Typically this isn't required! We only do it to make the Demo applications a little more welcoming. - ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver); - - // Main body of the Demo window starts here. - if (!ImGui::Begin("ImGui Demo", p_open, window_flags)) - { - // Early out if the window is collapsed, as an optimization. - ImGui::End(); - return; - } - ImGui::Text("dear imgui says hello. (%s)", IMGUI_VERSION); - - // Most "big" widgets share a common width settings by default. - //ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.65f); // Use 2/3 of the space for widgets and 1/3 for labels (default) - ImGui::PushItemWidth(ImGui::GetFontSize() * -12); // Use fixed width for labels (by passing a negative value), the rest goes to widgets. We choose a width proportional to our font size. - - // Menu - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Menu")) - { - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Examples")) - { - ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar); - ImGui::MenuItem("Console", NULL, &show_app_console); - ImGui::MenuItem("Log", NULL, &show_app_log); - ImGui::MenuItem("Simple layout", NULL, &show_app_layout); - ImGui::MenuItem("Property editor", NULL, &show_app_property_editor); - ImGui::MenuItem("Long text display", NULL, &show_app_long_text); - ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize); - ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize); - ImGui::MenuItem("Simple overlay", NULL, &show_app_simple_overlay); - ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); - ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Help")) - { - ImGui::MenuItem("Metrics", NULL, &show_app_metrics); - ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor); - ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - - ImGui::Spacing(); - if (ImGui::CollapsingHeader("Help")) - { - ImGui::Text("PROGRAMMER GUIDE:"); - ImGui::BulletText("Please see the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!"); - ImGui::BulletText("Please see the comments in imgui.cpp."); - ImGui::BulletText("Please see the examples/ in application."); - ImGui::BulletText("Enable 'io.ConfigFlags |= NavEnableKeyboard' for keyboard controls."); - ImGui::BulletText("Enable 'io.ConfigFlags |= NavEnableGamepad' for gamepad controls."); - ImGui::Separator(); - - ImGui::Text("USER GUIDE:"); - ImGui::ShowUserGuide(); - } - - if (ImGui::CollapsingHeader("Configuration")) - { - ImGuiIO& io = ImGui::GetIO(); - - if (ImGui::TreeNode("Configuration##2")) - { - ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard); - ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad); - ImGui::SameLine(); ShowHelpMarker("Required back-end to feed in gamepad inputs in io.NavInputs[] and set io.BackendFlags |= ImGuiBackendFlags_HasGamepad.\n\nRead instructions in imgui.cpp for details."); - ImGui::CheckboxFlags("io.ConfigFlags: NavEnableSetMousePos", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NavEnableSetMousePos); - ImGui::SameLine(); ShowHelpMarker("Instruct navigation to move the mouse cursor. See comment for ImGuiConfigFlags_NavEnableSetMousePos."); - ImGui::CheckboxFlags("io.ConfigFlags: NoMouse", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NoMouse); - if (io.ConfigFlags & ImGuiConfigFlags_NoMouse) // Create a way to restore this flag otherwise we could be stuck completely! - { - if (fmodf((float)ImGui::GetTime(), 0.40f) < 0.20f) - { - ImGui::SameLine(); - ImGui::Text("<>"); - } - if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Space))) - io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse; - } - ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); - ImGui::SameLine(); ShowHelpMarker("Instruct back-end to not alter mouse cursor shape and visibility."); - ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink); - ImGui::SameLine(); ShowHelpMarker("Set to false to disable blinking cursor, for users who consider it distracting"); - ImGui::Checkbox("io.ConfigResizeWindowsFromEdges [beta]", &io.ConfigResizeWindowsFromEdges); - ImGui::SameLine(); ShowHelpMarker("Enable resizing of windows from their edges and from the lower-left corner.\nThis requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback."); - ImGui::Checkbox("io.MouseDrawCursor", &io.MouseDrawCursor); - ImGui::SameLine(); ShowHelpMarker("Instruct Dear ImGui to render a mouse cursor for you. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\n\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something)."); - ImGui::TreePop(); - ImGui::Separator(); - } - - if (ImGui::TreeNode("Backend Flags")) - { - ImGuiBackendFlags backend_flags = io.BackendFlags; // Make a local copy to avoid modifying the back-end flags. - ImGui::CheckboxFlags("io.BackendFlags: HasGamepad", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasGamepad); - ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasMouseCursors); - ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasSetMousePos); - ImGui::TreePop(); - ImGui::Separator(); - } - - if (ImGui::TreeNode("Style")) - { - ImGui::ShowStyleEditor(); - ImGui::TreePop(); - ImGui::Separator(); - } - - if (ImGui::TreeNode("Capture/Logging")) - { - ImGui::TextWrapped("The logging API redirects all text output so you can easily capture the content of a window or a block. Tree nodes can be automatically expanded."); - ShowHelpMarker("Try opening any of the contents below in this window and then click one of the \"Log To\" button."); - ImGui::LogButtons(); - ImGui::TextWrapped("You can also call ImGui::LogText() to output directly to the log without a visual output."); - if (ImGui::Button("Copy \"Hello, world!\" to clipboard")) - { - ImGui::LogToClipboard(); - ImGui::LogText("Hello, world!"); - ImGui::LogFinish(); - } - ImGui::TreePop(); - } - } - - if (ImGui::CollapsingHeader("Window options")) - { - ImGui::Checkbox("No titlebar", &no_titlebar); ImGui::SameLine(150); - ImGui::Checkbox("No scrollbar", &no_scrollbar); ImGui::SameLine(300); - ImGui::Checkbox("No menu", &no_menu); - ImGui::Checkbox("No move", &no_move); ImGui::SameLine(150); - ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300); - ImGui::Checkbox("No collapse", &no_collapse); - ImGui::Checkbox("No close", &no_close); ImGui::SameLine(150); - ImGui::Checkbox("No nav", &no_nav); - } - - if (ImGui::CollapsingHeader("Widgets")) - { - if (ImGui::TreeNode("Basic")) - { - static int clicked = 0; - if (ImGui::Button("Button")) - clicked++; - if (clicked & 1) - { - ImGui::SameLine(); - ImGui::Text("Thanks for clicking me!"); - } - - static bool check = true; - ImGui::Checkbox("checkbox", &check); - - static int e = 0; - ImGui::RadioButton("radio a", &e, 0); ImGui::SameLine(); - ImGui::RadioButton("radio b", &e, 1); ImGui::SameLine(); - ImGui::RadioButton("radio c", &e, 2); - - // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. - for (int i = 0; i < 7; i++) - { - if (i > 0) ImGui::SameLine(); - ImGui::PushID(i); - ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.6f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i/7.0f, 0.8f, 0.8f)); - ImGui::Button("Click"); - ImGui::PopStyleColor(3); - ImGui::PopID(); - } - - // Arrow buttons - static int counter = 0; - float spacing = ImGui::GetStyle().ItemInnerSpacing.x; - ImGui::PushButtonRepeat(true); - if (ImGui::ArrowButton("##left", ImGuiDir_Left)) { counter--; } - ImGui::SameLine(0.0f, spacing); - if (ImGui::ArrowButton("##right", ImGuiDir_Right)) { counter++; } - ImGui::PopButtonRepeat(); - ImGui::SameLine(); - ImGui::Text("%d", counter); - - ImGui::Text("Hover over me"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("I am a tooltip"); - - ImGui::SameLine(); - ImGui::Text("- or me"); - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::Text("I am a fancy tooltip"); - static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; - ImGui::PlotLines("Curve", arr, IM_ARRAYSIZE(arr)); - ImGui::EndTooltip(); - } - - ImGui::Separator(); - - ImGui::LabelText("label", "Value"); - - { - // Using the _simplified_ one-liner Combo() api here - // See "Combo" section for examples of how to use the more complete BeginCombo()/EndCombo() api. - const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; - static int item_current = 0; - ImGui::Combo("combo", &item_current, items, IM_ARRAYSIZE(items)); - ImGui::SameLine(); ShowHelpMarker("Refer to the \"Combo\" section below for an explanation of the full BeginCombo/EndCombo API, and demonstration of various flags.\n"); - } - - { - static char str0[128] = "Hello, world!"; - static int i0 = 123; - ImGui::InputText("input text", str0, IM_ARRAYSIZE(str0)); - ImGui::SameLine(); ShowHelpMarker("USER:\nHold SHIFT or use mouse to select text.\n" "CTRL+Left/Right to word jump.\n" "CTRL+A or double-click to select all.\n" "CTRL+X,CTRL+C,CTRL+V clipboard.\n" "CTRL+Z,CTRL+Y undo/redo.\n" "ESCAPE to revert.\n\nPROGRAMMER:\nYou can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputText() to a dynamic string type. See misc/stl/imgui_stl.h for an example (this is not demonstrated in imgui_demo.cpp)."); - - ImGui::InputInt("input int", &i0); - ImGui::SameLine(); ShowHelpMarker("You can apply arithmetic operators +,*,/ on numerical values.\n e.g. [ 100 ], input \'*2\', result becomes [ 200 ]\nUse +- to subtract.\n"); - - static float f0 = 0.001f; - ImGui::InputFloat("input float", &f0, 0.01f, 1.0f); - - static double d0 = 999999.00000001; - ImGui::InputDouble("input double", &d0, 0.01f, 1.0f, "%.8f"); - - static float f1 = 1.e10f; - ImGui::InputFloat("input scientific", &f1, 0.0f, 0.0f, "%e"); - ImGui::SameLine(); ShowHelpMarker("You can input value using the scientific notation,\n e.g. \"1e+8\" becomes \"100000000\".\n"); - - static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; - ImGui::InputFloat3("input float3", vec4a); - } - - { - static int i1 = 50, i2 = 42; - ImGui::DragInt("drag int", &i1, 1); - ImGui::SameLine(); ShowHelpMarker("Click and drag to edit value.\nHold SHIFT/ALT for faster/slower edit.\nDouble-click or CTRL+click to input value."); - - ImGui::DragInt("drag int 0..100", &i2, 1, 0, 100, "%d%%"); - - static float f1=1.00f, f2=0.0067f; - ImGui::DragFloat("drag float", &f1, 0.005f); - ImGui::DragFloat("drag small float", &f2, 0.0001f, 0.0f, 0.0f, "%.06f ns"); - } - - { - static int i1=0; - ImGui::SliderInt("slider int", &i1, -1, 3); - ImGui::SameLine(); ShowHelpMarker("CTRL+click to input value."); - - static float f1=0.123f, f2=0.0f; - ImGui::SliderFloat("slider float", &f1, 0.0f, 1.0f, "ratio = %.3f"); - ImGui::SliderFloat("slider float (curve)", &f2, -10.0f, 10.0f, "%.4f", 2.0f); - static float angle = 0.0f; - ImGui::SliderAngle("slider angle", &angle); - } - - { - static float col1[3] = { 1.0f,0.0f,0.2f }; - static float col2[4] = { 0.4f,0.7f,0.0f,0.5f }; - ImGui::ColorEdit3("color 1", col1); - ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nClick and hold to use drag and drop.\nRight-click on the colored square to show options.\nCTRL+click on individual component to input value.\n"); - - ImGui::ColorEdit4("color 2", col2); - } - - { - // List box - const char* listbox_items[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" }; - static int listbox_item_current = 1; - ImGui::ListBox("listbox\n(single select)", &listbox_item_current, listbox_items, IM_ARRAYSIZE(listbox_items), 4); - - //static int listbox_item_current2 = 2; - //ImGui::PushItemWidth(-1); - //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4); - //ImGui::PopItemWidth(); - } - - ImGui::TreePop(); - } - - // Testing ImGuiOnceUponAFrame helper. - //static ImGuiOnceUponAFrame once; - //for (int i = 0; i < 5; i++) - // if (once) - // ImGui::Text("This will be displayed only once."); - - if (ImGui::TreeNode("Trees")) - { - if (ImGui::TreeNode("Basic trees")) - { - for (int i = 0; i < 5; i++) - if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i)) - { - ImGui::Text("blah blah"); - ImGui::SameLine(); - if (ImGui::SmallButton("button")) { }; - ImGui::TreePop(); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Advanced, with Selectable nodes")) - { - ShowHelpMarker("This is a more standard looking tree with selectable nodes.\nClick to select, CTRL+Click to toggle, click on arrows or double-click to open."); - static bool align_label_with_current_x_position = false; - ImGui::Checkbox("Align label with current X position)", &align_label_with_current_x_position); - ImGui::Text("Hello!"); - if (align_label_with_current_x_position) - ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); - - static int selection_mask = (1 << 2); // Dumb representation of what may be user-side selection state. You may carry selection state inside or outside your objects in whatever format you see fit. - int node_clicked = -1; // Temporary storage of what node we have clicked to process selection at the end of the loop. May be a pointer to your own node type, etc. - ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, ImGui::GetFontSize()*3); // Increase spacing to differentiate leaves from expanded contents. - for (int i = 0; i < 6; i++) - { - // Disable the default open on single-click behavior and pass in Selected flag according to our selection state. - ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ((selection_mask & (1 << i)) ? ImGuiTreeNodeFlags_Selected : 0); - if (i < 3) - { - // Node - bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Node %d", i); - if (ImGui::IsItemClicked()) - node_clicked = i; - if (node_open) - { - ImGui::Text("Blah blah\nBlah Blah"); - ImGui::TreePop(); - } - } - else - { - // Leaf: The only reason we have a TreeNode at all is to allow selection of the leaf. Otherwise we can use BulletText() or TreeAdvanceToLabelPos()+Text(). - node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet - ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, "Selectable Leaf %d", i); - if (ImGui::IsItemClicked()) - node_clicked = i; - } - } - if (node_clicked != -1) - { - // Update selection state. Process outside of tree loop to avoid visual inconsistencies during the clicking-frame. - if (ImGui::GetIO().KeyCtrl) - selection_mask ^= (1 << node_clicked); // CTRL+click to toggle - else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, this commented bit preserve selection when clicking on item that is part of the selection - selection_mask = (1 << node_clicked); // Click to single-select - } - ImGui::PopStyleVar(); - if (align_label_with_current_x_position) - ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); - ImGui::TreePop(); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Collapsing Headers")) - { - static bool closable_group = true; - ImGui::Checkbox("Enable extra group", &closable_group); - if (ImGui::CollapsingHeader("Header")) - { - ImGui::Text("IsItemHovered: %d", IsItemHovered()); - for (int i = 0; i < 5; i++) - ImGui::Text("Some content %d", i); - } - if (ImGui::CollapsingHeader("Header with a close button", &closable_group)) - { - ImGui::Text("IsItemHovered: %d", IsItemHovered()); - for (int i = 0; i < 5; i++) - ImGui::Text("More content %d", i); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Bullets")) - { - ImGui::BulletText("Bullet point 1"); - ImGui::BulletText("Bullet point 2\nOn multiple lines"); - ImGui::Bullet(); ImGui::Text("Bullet point 3 (two calls)"); - ImGui::Bullet(); ImGui::SmallButton("Button"); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Text")) - { - if (ImGui::TreeNode("Colored Text")) - { - // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility. - ImGui::TextColored(ImVec4(1.0f,0.0f,1.0f,1.0f), "Pink"); - ImGui::TextColored(ImVec4(1.0f,1.0f,0.0f,1.0f), "Yellow"); - ImGui::TextDisabled("Disabled"); - ImGui::SameLine(); ShowHelpMarker("The TextDisabled color is stored in ImGuiStyle."); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Word Wrapping")) - { - // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility. - ImGui::TextWrapped("This text should automatically wrap on the edge of the window. The current implementation for text wrapping follows simple rules suitable for English and possibly other languages."); - ImGui::Spacing(); - - static float wrap_width = 200.0f; - ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f"); - - ImGui::Text("Test paragraph 1:"); - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); - ImGui::Text("The lazy dog is a good dog. This paragraph is made to fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width); - ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); - ImGui::PopTextWrapPos(); - - ImGui::Text("Test paragraph 2:"); - pos = ImGui::GetCursorScreenPos(); - ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + wrap_width, pos.y), ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight()), IM_COL32(255,0,255,255)); - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width); - ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh"); - ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255,255,0,255)); - ImGui::PopTextWrapPos(); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("UTF-8 Text")) - { - // UTF-8 test with Japanese characters - // (Needs a suitable font, try Noto, or Arial Unicode, or M+ fonts. Read misc/fonts/README.txt for details.) - // - From C++11 you can use the u8"my text" syntax to encode literal strings as UTF-8 - // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. Visual Studio save your file as 'UTF-8 without signature') - // - FOR THIS DEMO FILE ONLY, BECAUSE WE WANT TO SUPPORT OLD COMPILERS, WE ARE *NOT* INCLUDING RAW UTF-8 CHARACTERS IN THIS SOURCE FILE. - // Instead we are encoding a few strings with hexadecimal constants. Don't do this in your application! - // Please use u8"text in any language" in your application! - // Note that characters values are preserved even by InputText() if the font cannot be displayed, so you can safely copy & paste garbled characters into another application. - ImGui::TextWrapped("CJK text will only appears if the font was loaded with the appropriate CJK character ranges. Call io.Font->LoadFromFileTTF() manually to load extra character ranges. Read misc/fonts/README.txt for details."); - ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)"); // Normally we would use u8"blah blah" with the proper characters directly in the string. - ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)"); - static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; - //static char buf[32] = u8"NIHONGO"; // <- this is how you would write it with C++11, using real kanjis - ImGui::InputText("UTF-8 input", buf, IM_ARRAYSIZE(buf)); - ImGui::TreePop(); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Images")) - { - ImGuiIO& io = ImGui::GetIO(); - ImGui::TextWrapped("Below we are displaying the font texture (which is the only texture we have access to in this demo). Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. Hover the texture for a zoomed view!"); - - // Here we are grabbing the font texture because that's the only one we have access to inside the demo code. - // Remember that ImTextureID is just storage for whatever you want it to be, it is essentially a value that will be passed to the render function inside the ImDrawCmd structure. - // If you use one of the default imgui_impl_XXXX.cpp renderer, they all have comments at the top of their file to specify what they expect to be stored in ImTextureID. - // (for example, the imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer. The imgui_impl_glfw_gl3.cpp renderer expect a GLuint OpenGL texture identifier etc.) - // If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers to ImGui::Image(), and gather width/height through your own functions, etc. - // Using ShowMetricsWindow() as a "debugger" to inspect the draw data that are being passed to your render will help you debug issues if you are confused about this. - // Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage(). - ImTextureID my_tex_id = io.Fonts->TexID; - float my_tex_w = (float)io.Fonts->TexWidth; - float my_tex_h = (float)io.Fonts->TexHeight; - - ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h); - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - float region_sz = 32.0f; - float region_x = io.MousePos.x - pos.x - region_sz * 0.5f; if (region_x < 0.0f) region_x = 0.0f; else if (region_x > my_tex_w - region_sz) region_x = my_tex_w - region_sz; - float region_y = io.MousePos.y - pos.y - region_sz * 0.5f; if (region_y < 0.0f) region_y = 0.0f; else if (region_y > my_tex_h - region_sz) region_y = my_tex_h - region_sz; - float zoom = 4.0f; - ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y); - ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz); - ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h); - ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h); - ImGui::Image(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImColor(255,255,255,255), ImColor(255,255,255,128)); - ImGui::EndTooltip(); - } - ImGui::TextWrapped("And now some textured buttons.."); - static int pressed_count = 0; - for (int i = 0; i < 8; i++) - { - ImGui::PushID(i); - int frame_padding = -1 + i; // -1 = uses default padding - if (ImGui::ImageButton(my_tex_id, ImVec2(32,32), ImVec2(0,0), ImVec2(32.0f/my_tex_w,32/my_tex_h), frame_padding, ImColor(0,0,0,255))) - pressed_count += 1; - ImGui::PopID(); - ImGui::SameLine(); - } - ImGui::NewLine(); - ImGui::Text("Pressed %d times.", pressed_count); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Combo")) - { - // Expose flags as checkbox for the demo - static ImGuiComboFlags flags = 0; - ImGui::CheckboxFlags("ImGuiComboFlags_PopupAlignLeft", (unsigned int*)&flags, ImGuiComboFlags_PopupAlignLeft); - if (ImGui::CheckboxFlags("ImGuiComboFlags_NoArrowButton", (unsigned int*)&flags, ImGuiComboFlags_NoArrowButton)) - flags &= ~ImGuiComboFlags_NoPreview; // Clear the other flag, as we cannot combine both - if (ImGui::CheckboxFlags("ImGuiComboFlags_NoPreview", (unsigned int*)&flags, ImGuiComboFlags_NoPreview)) - flags &= ~ImGuiComboFlags_NoArrowButton; // Clear the other flag, as we cannot combine both - - // General BeginCombo() API, you have full control over your selection data and display type. - // (your selection data could be an index, a pointer to the object, an id for the object, a flag stored in the object itself, etc.) - const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" }; - static const char* item_current = items[0]; // Here our selection is a single pointer stored outside the object. - if (ImGui::BeginCombo("combo 1", item_current, flags)) // The second parameter is the label previewed before opening the combo. - { - for (int n = 0; n < IM_ARRAYSIZE(items); n++) - { - bool is_selected = (item_current == items[n]); - if (ImGui::Selectable(items[n], is_selected)) - item_current = items[n]; - if (is_selected) - ImGui::SetItemDefaultFocus(); // Set the initial focus when opening the combo (scrolling + for keyboard navigation support in the upcoming navigation branch) - } - ImGui::EndCombo(); - } - - // Simplified one-liner Combo() API, using values packed in a single constant string - static int item_current_2 = 0; - ImGui::Combo("combo 2 (one-liner)", &item_current_2, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); - - // Simplified one-liner Combo() using an array of const char* - static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview - ImGui::Combo("combo 3 (array)", &item_current_3, items, IM_ARRAYSIZE(items)); - - // Simplified one-liner Combo() using an accessor function - struct FuncHolder { static bool ItemGetter(void* data, int idx, const char** out_str) { *out_str = ((const char**)data)[idx]; return true; } }; - static int item_current_4 = 0; - ImGui::Combo("combo 4 (function)", &item_current_4, &FuncHolder::ItemGetter, items, IM_ARRAYSIZE(items)); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Selectables")) - { - // Selectable() has 2 overloads: - // - The one taking "bool selected" as a read-only selection information. When Selectable() has been clicked is returns true and you can alter selection state accordingly. - // - The one taking "bool* p_selected" as a read-write selection information (convenient in some cases) - // The earlier is more flexible, as in real application your selection may be stored in a different manner (in flags within objects, as an external list, etc). - if (ImGui::TreeNode("Basic")) - { - static bool selection[5] = { false, true, false, false, false }; - ImGui::Selectable("1. I am selectable", &selection[0]); - ImGui::Selectable("2. I am selectable", &selection[1]); - ImGui::Text("3. I am not selectable"); - ImGui::Selectable("4. I am selectable", &selection[3]); - if (ImGui::Selectable("5. I am double clickable", selection[4], ImGuiSelectableFlags_AllowDoubleClick)) - if (ImGui::IsMouseDoubleClicked(0)) - selection[4] = !selection[4]; - ImGui::TreePop(); - } - if (ImGui::TreeNode("Selection State: Single Selection")) - { - static int selected = -1; - for (int n = 0; n < 5; n++) - { - char buf[32]; - sprintf(buf, "Object %d", n); - if (ImGui::Selectable(buf, selected == n)) - selected = n; - } - ImGui::TreePop(); - } - if (ImGui::TreeNode("Selection State: Multiple Selection")) - { - ShowHelpMarker("Hold CTRL and click to select multiple items."); - static bool selection[5] = { false, false, false, false, false }; - for (int n = 0; n < 5; n++) - { - char buf[32]; - sprintf(buf, "Object %d", n); - if (ImGui::Selectable(buf, selection[n])) - { - if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held - memset(selection, 0, sizeof(selection)); - selection[n] ^= 1; - } - } - ImGui::TreePop(); - } - if (ImGui::TreeNode("Rendering more text into the same line")) - { - // Using the Selectable() override that takes "bool* p_selected" parameter and toggle your booleans automatically. - static bool selected[3] = { false, false, false }; - ImGui::Selectable("main.c", &selected[0]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); - ImGui::Selectable("Hello.cpp", &selected[1]); ImGui::SameLine(300); ImGui::Text("12,345 bytes"); - ImGui::Selectable("Hello.h", &selected[2]); ImGui::SameLine(300); ImGui::Text(" 2,345 bytes"); - ImGui::TreePop(); - } - if (ImGui::TreeNode("In columns")) - { - ImGui::Columns(3, NULL, false); - static bool selected[16] = { 0 }; - for (int i = 0; i < 16; i++) - { - char label[32]; sprintf(label, "Item %d", i); - if (ImGui::Selectable(label, &selected[i])) {} - ImGui::NextColumn(); - } - ImGui::Columns(1); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Grid")) - { - static bool selected[16] = { true, false, false, false, false, true, false, false, false, false, true, false, false, false, false, true }; - for (int i = 0; i < 16; i++) - { - ImGui::PushID(i); - if (ImGui::Selectable("Sailor", &selected[i], 0, ImVec2(50,50))) - { - int x = i % 4, y = i / 4; - if (x > 0) selected[i - 1] ^= 1; - if (x < 3) selected[i + 1] ^= 1; - if (y > 0) selected[i - 4] ^= 1; - if (y < 3) selected[i + 4] ^= 1; - } - if ((i % 4) < 3) ImGui::SameLine(); - ImGui::PopID(); - } - ImGui::TreePop(); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Filtered Text Input")) - { - static char buf1[64] = ""; ImGui::InputText("default", buf1, 64); - static char buf2[64] = ""; ImGui::InputText("decimal", buf2, 64, ImGuiInputTextFlags_CharsDecimal); - static char buf3[64] = ""; ImGui::InputText("hexadecimal", buf3, 64, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); - static char buf4[64] = ""; ImGui::InputText("uppercase", buf4, 64, ImGuiInputTextFlags_CharsUppercase); - static char buf5[64] = ""; ImGui::InputText("no blank", buf5, 64, ImGuiInputTextFlags_CharsNoBlank); - struct TextFilters { static int FilterImGuiLetters(ImGuiInputTextCallbackData* data) { if (data->EventChar < 256 && strchr("imgui", (char)data->EventChar)) return 0; return 1; } }; - static char buf6[64] = ""; ImGui::InputText("\"imgui\" letters", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); - - ImGui::Text("Password input"); - static char bufpass[64] = "password123"; - ImGui::InputText("password", bufpass, 64, ImGuiInputTextFlags_Password | ImGuiInputTextFlags_CharsNoBlank); - ImGui::SameLine(); ShowHelpMarker("Display all characters as '*'.\nDisable clipboard cut and copy.\nDisable logging.\n"); - ImGui::InputText("password (clear)", bufpass, 64, ImGuiInputTextFlags_CharsNoBlank); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Multi-line Text Input")) - { - static bool read_only = false; - static char text[1024*16] = - "/*\n" - " The Pentium F00F bug, shorthand for F0 0F C7 C8,\n" - " the hexadecimal encoding of one offending instruction,\n" - " more formally, the invalid operand with locked CMPXCHG8B\n" - " instruction bug, is a design flaw in the majority of\n" - " Intel Pentium, Pentium MMX, and Pentium OverDrive\n" - " processors (all in the P5 microarchitecture).\n" - "*/\n\n" - "label:\n" - "\tlock cmpxchg8b eax\n"; - - ShowHelpMarker("You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputTextMultiline() to a dynamic string type. See misc/stl/imgui_stl.h for an example. (This is not demonstrated in imgui_demo.cpp)"); - ImGui::Checkbox("Read-only", &read_only); - ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput | (read_only ? ImGuiInputTextFlags_ReadOnly : 0); - ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(-1.0f, ImGui::GetTextLineHeight() * 16), flags); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Plots Widgets")) - { - static bool animate = true; - ImGui::Checkbox("Animate", &animate); - - static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; - ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); - - // Create a dummy array of contiguous float values to plot - // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float and the sizeof() of your structure in the Stride parameter. - static float values[90] = { 0 }; - static int values_offset = 0; - static double refresh_time = 0.0; - if (!animate || refresh_time == 0.0f) - refresh_time = ImGui::GetTime(); - while (refresh_time < ImGui::GetTime()) // Create dummy data at fixed 60 hz rate for the demo - { - static float phase = 0.0f; - values[values_offset] = cosf(phase); - values_offset = (values_offset+1) % IM_ARRAYSIZE(values); - phase += 0.10f*values_offset; - refresh_time += 1.0f/60.0f; - } - ImGui::PlotLines("Lines", values, IM_ARRAYSIZE(values), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,80)); - ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,80)); - - // Use functions to generate output - // FIXME: This is rather awkward because current plot API only pass in indices. We probably want an API passing floats and user provide sample rate/count. - struct Funcs - { - static float Sin(void*, int i) { return sinf(i * 0.1f); } - static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; } - }; - static int func_type = 0, display_count = 70; - ImGui::Separator(); - ImGui::PushItemWidth(100); ImGui::Combo("func", &func_type, "Sin\0Saw\0"); ImGui::PopItemWidth(); - ImGui::SameLine(); - ImGui::SliderInt("Sample count", &display_count, 1, 400); - float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw; - ImGui::PlotLines("Lines", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); - ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0,80)); - ImGui::Separator(); - - // Animate a simple progress bar - static float progress = 0.0f, progress_dir = 1.0f; - if (animate) - { - progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; - if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } - if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } - } - - // Typically we would use ImVec2(-1.0f,0.0f) to use all available width, or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. - ImGui::ProgressBar(progress, ImVec2(0.0f,0.0f)); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::Text("Progress Bar"); - - float progress_saturated = (progress < 0.0f) ? 0.0f : (progress > 1.0f) ? 1.0f : progress; - char buf[32]; - sprintf(buf, "%d/%d", (int)(progress_saturated*1753), 1753); - ImGui::ProgressBar(progress, ImVec2(0.f,0.f), buf); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Color/Picker Widgets")) - { - static ImVec4 color = ImColor(114, 144, 154, 200); - - static bool alpha_preview = true; - static bool alpha_half_preview = false; - static bool drag_and_drop = true; - static bool options_menu = true; - static bool hdr = false; - ImGui::Checkbox("With Alpha Preview", &alpha_preview); - ImGui::Checkbox("With Half Alpha Preview", &alpha_half_preview); - ImGui::Checkbox("With Drag and Drop", &drag_and_drop); - ImGui::Checkbox("With Options Menu", &options_menu); ImGui::SameLine(); ShowHelpMarker("Right-click on the individual color widget to show options."); - ImGui::Checkbox("With HDR", &hdr); ImGui::SameLine(); ShowHelpMarker("Currently all this does is to lift the 0..1 limits on dragging widgets."); - int misc_flags = (hdr ? ImGuiColorEditFlags_HDR : 0) | (drag_and_drop ? 0 : ImGuiColorEditFlags_NoDragDrop) | (alpha_half_preview ? ImGuiColorEditFlags_AlphaPreviewHalf : (alpha_preview ? ImGuiColorEditFlags_AlphaPreview : 0)) | (options_menu ? 0 : ImGuiColorEditFlags_NoOptions); - - ImGui::Text("Color widget:"); - ImGui::SameLine(); ShowHelpMarker("Click on the colored square to open a color picker.\nCTRL+click on individual component to input value.\n"); - ImGui::ColorEdit3("MyColor##1", (float*)&color, misc_flags); - - ImGui::Text("Color widget HSV with Alpha:"); - ImGui::ColorEdit4("MyColor##2", (float*)&color, ImGuiColorEditFlags_HSV | misc_flags); - - ImGui::Text("Color widget with Float Display:"); - ImGui::ColorEdit4("MyColor##2f", (float*)&color, ImGuiColorEditFlags_Float | misc_flags); - - ImGui::Text("Color button with Picker:"); - ImGui::SameLine(); ShowHelpMarker("With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\nWith the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only be used for the tooltip and picker popup."); - ImGui::ColorEdit4("MyColor##3", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | misc_flags); - - ImGui::Text("Color button with Custom Picker Popup:"); - - // Generate a dummy palette - static bool saved_palette_inited = false; - static ImVec4 saved_palette[32]; - if (!saved_palette_inited) - for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) - { - ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f, saved_palette[n].x, saved_palette[n].y, saved_palette[n].z); - saved_palette[n].w = 1.0f; // Alpha - } - saved_palette_inited = true; - - static ImVec4 backup_color; - bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags); - ImGui::SameLine(); - open_popup |= ImGui::Button("Palette"); - if (open_popup) - { - ImGui::OpenPopup("mypicker"); - backup_color = color; - } - if (ImGui::BeginPopup("mypicker")) - { - // FIXME: Adding a drag and drop example here would be perfect! - ImGui::Text("MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!"); - ImGui::Separator(); - ImGui::ColorPicker4("##picker", (float*)&color, misc_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview); - ImGui::SameLine(); - ImGui::BeginGroup(); - ImGui::Text("Current"); - ImGui::ColorButton("##current", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40)); - ImGui::Text("Previous"); - if (ImGui::ColorButton("##previous", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60,40))) - color = backup_color; - ImGui::Separator(); - ImGui::Text("Palette"); - for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++) - { - ImGui::PushID(n); - if ((n % 8) != 0) - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y); - if (ImGui::ColorButton("##palette", saved_palette[n], ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip, ImVec2(20,20))) - color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha! - - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) - memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3); - if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) - memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4); - EndDragDropTarget(); - } - - ImGui::PopID(); - } - ImGui::EndGroup(); - ImGui::EndPopup(); - } - - ImGui::Text("Color button only:"); - ImGui::ColorButton("MyColor##3c", *(ImVec4*)&color, misc_flags, ImVec2(80,80)); - - ImGui::Text("Color picker:"); - static bool alpha = true; - static bool alpha_bar = true; - static bool side_preview = true; - static bool ref_color = false; - static ImVec4 ref_color_v(1.0f,0.0f,1.0f,0.5f); - static int inputs_mode = 2; - static int picker_mode = 0; - ImGui::Checkbox("With Alpha", &alpha); - ImGui::Checkbox("With Alpha Bar", &alpha_bar); - ImGui::Checkbox("With Side Preview", &side_preview); - if (side_preview) - { - ImGui::SameLine(); - ImGui::Checkbox("With Ref Color", &ref_color); - if (ref_color) - { - ImGui::SameLine(); - ImGui::ColorEdit4("##RefColor", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | misc_flags); - } - } - ImGui::Combo("Inputs Mode", &inputs_mode, "All Inputs\0No Inputs\0RGB Input\0HSV Input\0HEX Input\0"); - ImGui::Combo("Picker Mode", &picker_mode, "Auto/Current\0Hue bar + SV rect\0Hue wheel + SV triangle\0"); - ImGui::SameLine(); ShowHelpMarker("User can right-click the picker to change mode."); - ImGuiColorEditFlags flags = misc_flags; - if (!alpha) flags |= ImGuiColorEditFlags_NoAlpha; // This is by default if you call ColorPicker3() instead of ColorPicker4() - if (alpha_bar) flags |= ImGuiColorEditFlags_AlphaBar; - if (!side_preview) flags |= ImGuiColorEditFlags_NoSidePreview; - if (picker_mode == 1) flags |= ImGuiColorEditFlags_PickerHueBar; - if (picker_mode == 2) flags |= ImGuiColorEditFlags_PickerHueWheel; - if (inputs_mode == 1) flags |= ImGuiColorEditFlags_NoInputs; - if (inputs_mode == 2) flags |= ImGuiColorEditFlags_RGB; - if (inputs_mode == 3) flags |= ImGuiColorEditFlags_HSV; - if (inputs_mode == 4) flags |= ImGuiColorEditFlags_HEX; - ImGui::ColorPicker4("MyColor##4", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL); - - ImGui::Text("Programmatically set defaults:"); - ImGui::SameLine(); ShowHelpMarker("SetColorEditOptions() is designed to allow you to set boot-time default.\nWe don't have Push/Pop functions because you can force options on a per-widget basis if needed, and the user can change non-forced ones with the options menu.\nWe don't have a getter to avoid encouraging you to persistently save values that aren't forward-compatible."); - if (ImGui::Button("Default: Uint8 + HSV + Hue Bar")) - ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_HSV | ImGuiColorEditFlags_PickerHueBar); - if (ImGui::Button("Default: Float + HDR + Hue Wheel")) - ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Range Widgets")) - { - static float begin = 10, end = 90; - static int begin_i = 100, end_i = 1000; - ImGui::DragFloatRange2("range", &begin, &end, 0.25f, 0.0f, 100.0f, "Min: %.1f %%", "Max: %.1f %%"); - ImGui::DragIntRange2("range int (no bounds)", &begin_i, &end_i, 5, 0, 0, "Min: %d units", "Max: %d units"); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Data Types")) - { - // The DragScalar/InputScalar/SliderScalar functions allow various data types: signed/unsigned int/long long and float/double - // To avoid polluting the public API with all possible combinations, we use the ImGuiDataType enum to pass the type, - // and passing all arguments by address. - // This is the reason the test code below creates local variables to hold "zero" "one" etc. for each types. - // In practice, if you frequently use a given type that is not covered by the normal API entry points, you can wrap it - // yourself inside a 1 line function which can take typed argument as value instead of void*, and then pass their address - // to the generic function. For example: - // bool MySliderU64(const char *label, u64* value, u64 min = 0, u64 max = 0, const char* format = "%lld") - // { - // return SliderScalar(label, ImGuiDataType_U64, value, &min, &max, format); - // } - - // Limits (as helper variables that we can take the address of) - // Note that the SliderScalar function has a maximum usable range of half the natural type maximum, hence the /2 below. - #ifndef LLONG_MIN - ImS64 LLONG_MIN = -9223372036854775807LL - 1; - ImS64 LLONG_MAX = 9223372036854775807LL; - ImU64 ULLONG_MAX = (2ULL * 9223372036854775807LL + 1); - #endif - const ImS32 s32_zero = 0, s32_one = 1, s32_fifty = 50, s32_min = INT_MIN/2, s32_max = INT_MAX/2, s32_hi_a = INT_MAX/2 - 100, s32_hi_b = INT_MAX/2; - const ImU32 u32_zero = 0, u32_one = 1, u32_fifty = 50, u32_min = 0, u32_max = UINT_MAX/2, u32_hi_a = UINT_MAX/2 - 100, u32_hi_b = UINT_MAX/2; - const ImS64 s64_zero = 0, s64_one = 1, s64_fifty = 50, s64_min = LLONG_MIN/2, s64_max = LLONG_MAX/2, s64_hi_a = LLONG_MAX/2 - 100, s64_hi_b = LLONG_MAX/2; - const ImU64 u64_zero = 0, u64_one = 1, u64_fifty = 50, u64_min = 0, u64_max = ULLONG_MAX/2, u64_hi_a = ULLONG_MAX/2 - 100, u64_hi_b = ULLONG_MAX/2; - const float f32_zero = 0.f, f32_one = 1.f, f32_lo_a = -10000000000.0f, f32_hi_a = +10000000000.0f; - const double f64_zero = 0., f64_one = 1., f64_lo_a = -1000000000000000.0, f64_hi_a = +1000000000000000.0; - - // State - static ImS32 s32_v = -1; - static ImU32 u32_v = (ImU32)-1; - static ImS64 s64_v = -1; - static ImU64 u64_v = (ImU64)-1; - static float f32_v = 0.123f; - static double f64_v = 90000.01234567890123456789; - - const float drag_speed = 0.2f; - static bool drag_clamp = false; - ImGui::Text("Drags:"); - ImGui::Checkbox("Clamp integers to 0..50", &drag_clamp); ImGui::SameLine(); ShowHelpMarker("As with every widgets in dear imgui, we never modify values unless there is a user interaction.\nYou can override the clamping limits by using CTRL+Click to input a value."); - ImGui::DragScalar("drag s32", ImGuiDataType_S32, &s32_v, drag_speed, drag_clamp ? &s32_zero : NULL, drag_clamp ? &s32_fifty : NULL); - ImGui::DragScalar("drag u32", ImGuiDataType_U32, &u32_v, drag_speed, drag_clamp ? &u32_zero : NULL, drag_clamp ? &u32_fifty : NULL, "%u ms"); - ImGui::DragScalar("drag s64", ImGuiDataType_S64, &s64_v, drag_speed, drag_clamp ? &s64_zero : NULL, drag_clamp ? &s64_fifty : NULL); - ImGui::DragScalar("drag u64", ImGuiDataType_U64, &u64_v, drag_speed, drag_clamp ? &u64_zero : NULL, drag_clamp ? &u64_fifty : NULL); - ImGui::DragScalar("drag float", ImGuiDataType_Float, &f32_v, 0.005f, &f32_zero, &f32_one, "%f", 1.0f); - ImGui::DragScalar("drag float ^2", ImGuiDataType_Float, &f32_v, 0.005f, &f32_zero, &f32_one, "%f", 2.0f); ImGui::SameLine(); ShowHelpMarker("You can use the 'power' parameter to increase tweaking precision on one side of the range."); - ImGui::DragScalar("drag double", ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, NULL, "%.10f grams", 1.0f); - ImGui::DragScalar("drag double ^2", ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, &f64_one, "0 < %.10f < 1", 2.0f); - - ImGui::Text("Sliders"); - ImGui::SliderScalar("slider s32 low", ImGuiDataType_S32, &s32_v, &s32_zero, &s32_fifty,"%d"); - ImGui::SliderScalar("slider s32 high", ImGuiDataType_S32, &s32_v, &s32_hi_a, &s32_hi_b, "%d"); - ImGui::SliderScalar("slider s32 full", ImGuiDataType_S32, &s32_v, &s32_min, &s32_max, "%d"); - ImGui::SliderScalar("slider u32 low", ImGuiDataType_U32, &u32_v, &u32_zero, &u32_fifty,"%u"); - ImGui::SliderScalar("slider u32 high", ImGuiDataType_U32, &u32_v, &u32_hi_a, &u32_hi_b, "%u"); - ImGui::SliderScalar("slider u32 full", ImGuiDataType_U32, &u32_v, &u32_min, &u32_max, "%u"); - ImGui::SliderScalar("slider s64 low", ImGuiDataType_S64, &s64_v, &s64_zero, &s64_fifty,"%I64d"); - ImGui::SliderScalar("slider s64 high", ImGuiDataType_S64, &s64_v, &s64_hi_a, &s64_hi_b, "%I64d"); - ImGui::SliderScalar("slider s64 full", ImGuiDataType_S64, &s64_v, &s64_min, &s64_max, "%I64d"); - ImGui::SliderScalar("slider u64 low", ImGuiDataType_U64, &u64_v, &u64_zero, &u64_fifty,"%I64u ms"); - ImGui::SliderScalar("slider u64 high", ImGuiDataType_U64, &u64_v, &u64_hi_a, &u64_hi_b, "%I64u ms"); - ImGui::SliderScalar("slider u64 full", ImGuiDataType_U64, &u64_v, &u64_min, &u64_max, "%I64u ms"); - ImGui::SliderScalar("slider float low", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one); - ImGui::SliderScalar("slider float low^2", ImGuiDataType_Float, &f32_v, &f32_zero, &f32_one, "%.10f", 2.0f); - ImGui::SliderScalar("slider float high", ImGuiDataType_Float, &f32_v, &f32_lo_a, &f32_hi_a, "%e"); - ImGui::SliderScalar("slider double low", ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f grams", 1.0f); - ImGui::SliderScalar("slider double low^2",ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one, "%.10f", 2.0f); - ImGui::SliderScalar("slider double high", ImGuiDataType_Double, &f64_v, &f64_lo_a, &f64_hi_a, "%e grams", 1.0f); - - static bool inputs_step = true; - ImGui::Text("Inputs"); - ImGui::Checkbox("Show step buttons", &inputs_step); - ImGui::InputScalar("input s32", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%d"); - ImGui::InputScalar("input s32 hex", ImGuiDataType_S32, &s32_v, inputs_step ? &s32_one : NULL, NULL, "%08X", ImGuiInputTextFlags_CharsHexadecimal); - ImGui::InputScalar("input u32", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%u"); - ImGui::InputScalar("input u32 hex", ImGuiDataType_U32, &u32_v, inputs_step ? &u32_one : NULL, NULL, "%08X", ImGuiInputTextFlags_CharsHexadecimal); - ImGui::InputScalar("input s64", ImGuiDataType_S64, &s64_v, inputs_step ? &s64_one : NULL); - ImGui::InputScalar("input u64", ImGuiDataType_U64, &u64_v, inputs_step ? &u64_one : NULL); - ImGui::InputScalar("input float", ImGuiDataType_Float, &f32_v, inputs_step ? &f32_one : NULL); - ImGui::InputScalar("input double", ImGuiDataType_Double, &f64_v, inputs_step ? &f64_one : NULL); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Multi-component Widgets")) - { - static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f }; - static int vec4i[4] = { 1, 5, 100, 255 }; - - ImGui::InputFloat2("input float2", vec4f); - ImGui::DragFloat2("drag float2", vec4f, 0.01f, 0.0f, 1.0f); - ImGui::SliderFloat2("slider float2", vec4f, 0.0f, 1.0f); - ImGui::InputInt2("input int2", vec4i); - ImGui::DragInt2("drag int2", vec4i, 1, 0, 255); - ImGui::SliderInt2("slider int2", vec4i, 0, 255); - ImGui::Spacing(); - - ImGui::InputFloat3("input float3", vec4f); - ImGui::DragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f); - ImGui::SliderFloat3("slider float3", vec4f, 0.0f, 1.0f); - ImGui::InputInt3("input int3", vec4i); - ImGui::DragInt3("drag int3", vec4i, 1, 0, 255); - ImGui::SliderInt3("slider int3", vec4i, 0, 255); - ImGui::Spacing(); - - ImGui::InputFloat4("input float4", vec4f); - ImGui::DragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f); - ImGui::SliderFloat4("slider float4", vec4f, 0.0f, 1.0f); - ImGui::InputInt4("input int4", vec4i); - ImGui::DragInt4("drag int4", vec4i, 1, 0, 255); - ImGui::SliderInt4("slider int4", vec4i, 0, 255); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Vertical Sliders")) - { - const float spacing = 4; - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)); - - static int int_value = 0; - ImGui::VSliderInt("##int", ImVec2(18,160), &int_value, 0, 5); - ImGui::SameLine(); - - static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f }; - ImGui::PushID("set1"); - for (int i = 0; i < 7; i++) - { - if (i > 0) ImGui::SameLine(); - ImGui::PushID(i); - ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i/7.0f, 0.5f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i/7.0f, 0.9f, 0.9f)); - ImGui::VSliderFloat("##v", ImVec2(18,160), &values[i], 0.0f, 1.0f, ""); - if (ImGui::IsItemActive() || ImGui::IsItemHovered()) - ImGui::SetTooltip("%.3f", values[i]); - ImGui::PopStyleColor(4); - ImGui::PopID(); - } - ImGui::PopID(); - - ImGui::SameLine(); - ImGui::PushID("set2"); - static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f }; - const int rows = 3; - const ImVec2 small_slider_size(18, (160.0f-(rows-1)*spacing)/rows); - for (int nx = 0; nx < 4; nx++) - { - if (nx > 0) ImGui::SameLine(); - ImGui::BeginGroup(); - for (int ny = 0; ny < rows; ny++) - { - ImGui::PushID(nx*rows+ny); - ImGui::VSliderFloat("##v", small_slider_size, &values2[nx], 0.0f, 1.0f, ""); - if (ImGui::IsItemActive() || ImGui::IsItemHovered()) - ImGui::SetTooltip("%.3f", values2[nx]); - ImGui::PopID(); - } - ImGui::EndGroup(); - } - ImGui::PopID(); - - ImGui::SameLine(); - ImGui::PushID("set3"); - for (int i = 0; i < 4; i++) - { - if (i > 0) ImGui::SameLine(); - ImGui::PushID(i); - ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40); - ImGui::VSliderFloat("##v", ImVec2(40,160), &values[i], 0.0f, 1.0f, "%.2f\nsec"); - ImGui::PopStyleVar(); - ImGui::PopID(); - } - ImGui::PopID(); - ImGui::PopStyleVar(); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Drag and Drop")) - { - { - // ColorEdit widgets automatically act as drag source and drag target. - // They are using standardized payload strings IMGUI_PAYLOAD_TYPE_COLOR_3F and IMGUI_PAYLOAD_TYPE_COLOR_4F to allow your own widgets - // to use colors in their drag and drop interaction. Also see the demo in Color Picker -> Palette demo. - ImGui::BulletText("Drag and drop in standard widgets"); - ImGui::Indent(); - static float col1[3] = { 1.0f,0.0f,0.2f }; - static float col2[4] = { 0.4f,0.7f,0.0f,0.5f }; - ImGui::ColorEdit3("color 1", col1); - ImGui::ColorEdit4("color 2", col2); - ImGui::Unindent(); - } - - { - ImGui::BulletText("Drag and drop to copy/swap items"); - ImGui::Indent(); - enum Mode - { - Mode_Copy, - Mode_Move, - Mode_Swap - }; - static int mode = 0; - if (ImGui::RadioButton("Copy", mode == Mode_Copy)) { mode = Mode_Copy; } ImGui::SameLine(); - if (ImGui::RadioButton("Move", mode == Mode_Move)) { mode = Mode_Move; } ImGui::SameLine(); - if (ImGui::RadioButton("Swap", mode == Mode_Swap)) { mode = Mode_Swap; } - static const char* names[9] = { "Bobby", "Beatrice", "Betty", "Brianna", "Barry", "Bernard", "Bibi", "Blaine", "Bryn" }; - for (int n = 0; n < IM_ARRAYSIZE(names); n++) - { - ImGui::PushID(n); - if ((n % 3) != 0) - ImGui::SameLine(); - ImGui::Button(names[n], ImVec2(60,60)); - - // Our buttons are both drag sources and drag targets here! - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - ImGui::SetDragDropPayload("DND_DEMO_CELL", &n, sizeof(int)); // Set payload to carry the index of our item (could be anything) - if (mode == Mode_Copy) { ImGui::Text("Copy %s", names[n]); } // Display preview (could be anything, e.g. when dragging an image we could decide to display the filename and a small preview of the image, etc.) - if (mode == Mode_Move) { ImGui::Text("Move %s", names[n]); } - if (mode == Mode_Swap) { ImGui::Text("Swap %s", names[n]); } - ImGui::EndDragDropSource(); - } - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_DEMO_CELL")) - { - IM_ASSERT(payload->DataSize == sizeof(int)); - int payload_n = *(const int*)payload->Data; - if (mode == Mode_Copy) - { - names[n] = names[payload_n]; - } - if (mode == Mode_Move) - { - names[n] = names[payload_n]; - names[payload_n] = ""; - } - if (mode == Mode_Swap) - { - const char* tmp = names[n]; - names[n] = names[payload_n]; - names[payload_n] = tmp; - } - } - ImGui::EndDragDropTarget(); - } - ImGui::PopID(); - } - ImGui::Unindent(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Querying Status (Active/Focused/Hovered etc.)")) - { - // Display the value of IsItemHovered() and other common item state functions. Note that the flags can be combined. - // (because BulletText is an item itself and that would affect the output of IsItemHovered() we pass all state in a single call to simplify the code). - static int item_type = 1; - static bool b = false; - static float col4f[4] = { 1.0f, 0.5, 0.0f, 1.0f }; - ImGui::RadioButton("Text", &item_type, 0); - ImGui::RadioButton("Button", &item_type, 1); - ImGui::RadioButton("CheckBox", &item_type, 2); - ImGui::RadioButton("SliderFloat", &item_type, 3); - ImGui::RadioButton("ColorEdit4", &item_type, 4); - ImGui::RadioButton("ListBox", &item_type, 5); - ImGui::Separator(); - bool ret = false; - if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction - if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button - if (item_type == 2) { ret = ImGui::Checkbox("ITEM: CheckBox", &b); } // Testing checkbox - if (item_type == 3) { ret = ImGui::SliderFloat("ITEM: SliderFloat", &col4f[0], 0.0f, 1.0f); } // Testing basic item - if (item_type == 4) { ret = ImGui::ColorEdit4("ITEM: ColorEdit4", col4f); } // Testing multi-component items (IsItemXXX flags are reported merged) - if (item_type == 5) { const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } - ImGui::BulletText( - "Return value = %d\n" - "IsItemFocused() = %d\n" - "IsItemHovered() = %d\n" - "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" - "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" - "IsItemHovered(_AllowWhenOverlapped) = %d\n" - "IsItemHovered(_RectOnly) = %d\n" - "IsItemActive() = %d\n" - "IsItemEdited() = %d\n" - "IsItemDeactivated() = %d\n" - "IsItemDeactivatedEdit() = %d\n" - "IsItemVisible() = %d\n" - "GetItemRectMin() = (%.1f, %.1f)\n" - "GetItemRectMax() = (%.1f, %.1f)\n" - "GetItemRectSize() = (%.1f, %.1f)", - ret, - ImGui::IsItemFocused(), - ImGui::IsItemHovered(), - ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), - ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), - ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), - ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly), - ImGui::IsItemActive(), - ImGui::IsItemEdited(), - ImGui::IsItemDeactivated(), - ImGui::IsItemDeactivatedAfterEdit(), - ImGui::IsItemVisible(), - ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y, - ImGui::GetItemRectMax().x, ImGui::GetItemRectMax().y, - ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y - ); - - static bool embed_all_inside_a_child_window = false; - ImGui::Checkbox("Embed everything inside a child window (for additional testing)", &embed_all_inside_a_child_window); - if (embed_all_inside_a_child_window) - ImGui::BeginChild("outer_child", ImVec2(0, ImGui::GetFontSize() * 20), true); - - // Testing IsWindowFocused() function with its various flags. Note that the flags can be combined. - ImGui::BulletText( - "IsWindowFocused() = %d\n" - "IsWindowFocused(_ChildWindows) = %d\n" - "IsWindowFocused(_ChildWindows|_RootWindow) = %d\n" - "IsWindowFocused(_RootWindow) = %d\n" - "IsWindowFocused(_AnyWindow) = %d\n", - ImGui::IsWindowFocused(), - ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows), - ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow), - ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow), - ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)); - - // Testing IsWindowHovered() function with its various flags. Note that the flags can be combined. - ImGui::BulletText( - "IsWindowHovered() = %d\n" - "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n" - "IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n" - "IsWindowHovered(_ChildWindows) = %d\n" - "IsWindowHovered(_ChildWindows|_RootWindow) = %d\n" - "IsWindowHovered(_RootWindow) = %d\n" - "IsWindowHovered(_AnyWindow) = %d\n", - ImGui::IsWindowHovered(), - ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), - ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), - ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows), - ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow), - ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow), - ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)); - - ImGui::BeginChild("child", ImVec2(0, 50), true); - ImGui::Text("This is another child window for testing with the _ChildWindows flag."); - ImGui::EndChild(); - if (embed_all_inside_a_child_window) - EndChild(); - - // Calling IsItemHovered() after begin returns the hovered status of the title bar. - // This is useful in particular if you want to create a context menu (with BeginPopupContextItem) associated to the title bar of a window. - static bool test_window = false; - ImGui::Checkbox("Hovered/Active tests after Begin() for title bar testing", &test_window); - if (test_window) - { - ImGui::Begin("Title bar Hovered/Active tests", &test_window); - if (ImGui::BeginPopupContextItem()) // <-- This is using IsItemHovered() - { - if (ImGui::MenuItem("Close")) { test_window = false; } - ImGui::EndPopup(); - } - ImGui::Text( - "IsItemHovered() after begin = %d (== is title bar hovered)\n" - "IsItemActive() after begin = %d (== is window being clicked/moved)\n", - ImGui::IsItemHovered(), ImGui::IsItemActive()); - ImGui::End(); - } - - ImGui::TreePop(); - } - } - - if (ImGui::CollapsingHeader("Layout")) - { - if (ImGui::TreeNode("Child regions")) - { - static bool disable_mouse_wheel = false; - static bool disable_menu = false; - ImGui::Checkbox("Disable Mouse Wheel", &disable_mouse_wheel); - ImGui::Checkbox("Disable Menu", &disable_menu); - - static int line = 50; - bool goto_line = ImGui::Button("Goto"); - ImGui::SameLine(); - ImGui::PushItemWidth(100); - goto_line |= ImGui::InputInt("##Line", &line, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue); - ImGui::PopItemWidth(); - - // Child 1: no border, enable horizontal scrollbar - { - ImGui::BeginChild("Child1", ImVec2(ImGui::GetWindowContentRegionWidth() * 0.5f, 300), false, ImGuiWindowFlags_HorizontalScrollbar | (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0)); - for (int i = 0; i < 100; i++) - { - ImGui::Text("%04d: scrollable region", i); - if (goto_line && line == i) - ImGui::SetScrollHere(); - } - if (goto_line && line >= 100) - ImGui::SetScrollHere(); - ImGui::EndChild(); - } - - ImGui::SameLine(); - - // Child 2: rounded border - { - ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); - ImGui::BeginChild("Child2", ImVec2(0,300), true, (disable_mouse_wheel ? ImGuiWindowFlags_NoScrollWithMouse : 0) | (disable_menu ? 0 : ImGuiWindowFlags_MenuBar)); - if (!disable_menu && ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Menu")) - { - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - ImGui::Columns(2); - for (int i = 0; i < 100; i++) - { - char buf[32]; - sprintf(buf, "%03d", i); - ImGui::Button(buf, ImVec2(-1.0f, 0.0f)); - ImGui::NextColumn(); - } - ImGui::EndChild(); - ImGui::PopStyleVar(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Widgets Width")) - { - static float f = 0.0f; - ImGui::Text("PushItemWidth(100)"); - ImGui::SameLine(); ShowHelpMarker("Fixed width."); - ImGui::PushItemWidth(100); - ImGui::DragFloat("float##1", &f); - ImGui::PopItemWidth(); - - ImGui::Text("PushItemWidth(GetWindowWidth() * 0.5f)"); - ImGui::SameLine(); ShowHelpMarker("Half of window width."); - ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); - ImGui::DragFloat("float##2", &f); - ImGui::PopItemWidth(); - - ImGui::Text("PushItemWidth(GetContentRegionAvailWidth() * 0.5f)"); - ImGui::SameLine(); ShowHelpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)"); - ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() * 0.5f); - ImGui::DragFloat("float##3", &f); - ImGui::PopItemWidth(); - - ImGui::Text("PushItemWidth(-100)"); - ImGui::SameLine(); ShowHelpMarker("Align to right edge minus 100"); - ImGui::PushItemWidth(-100); - ImGui::DragFloat("float##4", &f); - ImGui::PopItemWidth(); - - ImGui::Text("PushItemWidth(-1)"); - ImGui::SameLine(); ShowHelpMarker("Align to right edge"); - ImGui::PushItemWidth(-1); - ImGui::DragFloat("float##5", &f); - ImGui::PopItemWidth(); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Basic Horizontal Layout")) - { - ImGui::TextWrapped("(Use ImGui::SameLine() to keep adding items to the right of the preceding item)"); - - // Text - ImGui::Text("Two items: Hello"); ImGui::SameLine(); - ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); - - // Adjust spacing - ImGui::Text("More spacing: Hello"); ImGui::SameLine(0, 20); - ImGui::TextColored(ImVec4(1,1,0,1), "Sailor"); - - // Button - ImGui::AlignTextToFramePadding(); - ImGui::Text("Normal buttons"); ImGui::SameLine(); - ImGui::Button("Banana"); ImGui::SameLine(); - ImGui::Button("Apple"); ImGui::SameLine(); - ImGui::Button("Corniflower"); - - // Button - ImGui::Text("Small buttons"); ImGui::SameLine(); - ImGui::SmallButton("Like this one"); ImGui::SameLine(); - ImGui::Text("can fit within a text block."); - - // Aligned to arbitrary position. Easy/cheap column. - ImGui::Text("Aligned"); - ImGui::SameLine(150); ImGui::Text("x=150"); - ImGui::SameLine(300); ImGui::Text("x=300"); - ImGui::Text("Aligned"); - ImGui::SameLine(150); ImGui::SmallButton("x=150"); - ImGui::SameLine(300); ImGui::SmallButton("x=300"); - - // Checkbox - static bool c1=false,c2=false,c3=false,c4=false; - ImGui::Checkbox("My", &c1); ImGui::SameLine(); - ImGui::Checkbox("Tailor", &c2); ImGui::SameLine(); - ImGui::Checkbox("Is", &c3); ImGui::SameLine(); - ImGui::Checkbox("Rich", &c4); - - // Various - static float f0=1.0f, f1=2.0f, f2=3.0f; - ImGui::PushItemWidth(80); - const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD" }; - static int item = -1; - ImGui::Combo("Combo", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine(); - ImGui::SliderFloat("X", &f0, 0.0f,5.0f); ImGui::SameLine(); - ImGui::SliderFloat("Y", &f1, 0.0f,5.0f); ImGui::SameLine(); - ImGui::SliderFloat("Z", &f2, 0.0f,5.0f); - ImGui::PopItemWidth(); - - ImGui::PushItemWidth(80); - ImGui::Text("Lists:"); - static int selection[4] = { 0, 1, 2, 3 }; - for (int i = 0; i < 4; i++) - { - if (i > 0) ImGui::SameLine(); - ImGui::PushID(i); - ImGui::ListBox("", &selection[i], items, IM_ARRAYSIZE(items)); - ImGui::PopID(); - //if (ImGui::IsItemHovered()) ImGui::SetTooltip("ListBox %d hovered", i); - } - ImGui::PopItemWidth(); - - // Dummy - ImVec2 button_sz(40,40); - ImGui::Button("A", button_sz); ImGui::SameLine(); - ImGui::Dummy(button_sz); ImGui::SameLine(); - ImGui::Button("B", button_sz); - - // Manually wrapping (we should eventually provide this as an automatic layout feature, but for now you can do it manually) - ImGui::Text("Manually wrapping:"); - ImGuiStyle& style = ImGui::GetStyle(); - int buttons_count = 20; - float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x; - for (int n = 0; n < buttons_count; n++) - { - ImGui::PushID(n); - ImGui::Button("Box", button_sz); - float last_button_x2 = ImGui::GetItemRectMax().x; - float next_button_x2 = last_button_x2 + style.ItemSpacing.x + button_sz.x; // Expected position if next button was on same line - if (n + 1 < buttons_count && next_button_x2 < window_visible_x2) - ImGui::SameLine(); - ImGui::PopID(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Groups")) - { - ImGui::TextWrapped("(Using ImGui::BeginGroup()/EndGroup() to layout items. BeginGroup() basically locks the horizontal position. EndGroup() bundles the whole group so that you can use functions such as IsItemHovered() on it.)"); - ImGui::BeginGroup(); - { - ImGui::BeginGroup(); - ImGui::Button("AAA"); - ImGui::SameLine(); - ImGui::Button("BBB"); - ImGui::SameLine(); - ImGui::BeginGroup(); - ImGui::Button("CCC"); - ImGui::Button("DDD"); - ImGui::EndGroup(); - ImGui::SameLine(); - ImGui::Button("EEE"); - ImGui::EndGroup(); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("First group hovered"); - } - // Capture the group size and create widgets using the same size - ImVec2 size = ImGui::GetItemRectSize(); - const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f }; - ImGui::PlotHistogram("##values", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size); - - ImGui::Button("ACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y)); - ImGui::SameLine(); - ImGui::Button("REACTION", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x)*0.5f,size.y)); - ImGui::EndGroup(); - ImGui::SameLine(); - - ImGui::Button("LEVERAGE\nBUZZWORD", size); - ImGui::SameLine(); - - if (ImGui::ListBoxHeader("List", size)) - { - ImGui::Selectable("Selected", true); - ImGui::Selectable("Not Selected", false); - ImGui::ListBoxFooter(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Text Baseline Alignment")) - { - ImGui::TextWrapped("(This is testing the vertical alignment that occurs on text to keep it at the same baseline as widgets. Lines only composed of text or \"small\" widgets fit in less vertical spaces than lines with normal widgets)"); - - ImGui::Text("One\nTwo\nThree"); ImGui::SameLine(); - ImGui::Text("Hello\nWorld"); ImGui::SameLine(); - ImGui::Text("Banana"); - - ImGui::Text("Banana"); ImGui::SameLine(); - ImGui::Text("Hello\nWorld"); ImGui::SameLine(); - ImGui::Text("One\nTwo\nThree"); - - ImGui::Button("HOP##1"); ImGui::SameLine(); - ImGui::Text("Banana"); ImGui::SameLine(); - ImGui::Text("Hello\nWorld"); ImGui::SameLine(); - ImGui::Text("Banana"); - - ImGui::Button("HOP##2"); ImGui::SameLine(); - ImGui::Text("Hello\nWorld"); ImGui::SameLine(); - ImGui::Text("Banana"); - - ImGui::Button("TEST##1"); ImGui::SameLine(); - ImGui::Text("TEST"); ImGui::SameLine(); - ImGui::SmallButton("TEST##2"); - - ImGui::AlignTextToFramePadding(); // If your line starts with text, call this to align it to upcoming widgets. - ImGui::Text("Text aligned to Widget"); ImGui::SameLine(); - ImGui::Button("Widget##1"); ImGui::SameLine(); - ImGui::Text("Widget"); ImGui::SameLine(); - ImGui::SmallButton("Widget##2"); ImGui::SameLine(); - ImGui::Button("Widget##3"); - - // Tree - const float spacing = ImGui::GetStyle().ItemInnerSpacing.x; - ImGui::Button("Button##1"); - ImGui::SameLine(0.0f, spacing); - if (ImGui::TreeNode("Node##1")) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data - - ImGui::AlignTextToFramePadding(); // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget. Otherwise you can use SmallButton (smaller fit). - bool node_open = ImGui::TreeNode("Node##2"); // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add child content. - ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##2"); - if (node_open) { for (int i = 0; i < 6; i++) ImGui::BulletText("Item %d..", i); ImGui::TreePop(); } // Dummy tree data - - // Bullet - ImGui::Button("Button##3"); - ImGui::SameLine(0.0f, spacing); - ImGui::BulletText("Bullet text"); - - ImGui::AlignTextToFramePadding(); - ImGui::BulletText("Node"); - ImGui::SameLine(0.0f, spacing); ImGui::Button("Button##4"); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Scrolling")) - { - ImGui::TextWrapped("(Use SetScrollHere() or SetScrollFromPosY() to scroll to a given position.)"); - static bool track = true; - static int track_line = 50, scroll_to_px = 200; - ImGui::Checkbox("Track", &track); - ImGui::PushItemWidth(100); - ImGui::SameLine(130); track |= ImGui::DragInt("##line", &track_line, 0.25f, 0, 99, "Line = %d"); - bool scroll_to = ImGui::Button("Scroll To Pos"); - ImGui::SameLine(130); scroll_to |= ImGui::DragInt("##pos_y", &scroll_to_px, 1.00f, 0, 9999, "Y = %d px"); - ImGui::PopItemWidth(); - if (scroll_to) track = false; - - for (int i = 0; i < 5; i++) - { - if (i > 0) ImGui::SameLine(); - ImGui::BeginGroup(); - ImGui::Text("%s", i == 0 ? "Top" : i == 1 ? "25%" : i == 2 ? "Center" : i == 3 ? "75%" : "Bottom"); - ImGui::BeginChild(ImGui::GetID((void*)(intptr_t)i), ImVec2(ImGui::GetWindowWidth() * 0.17f, 200.0f), true); - if (scroll_to) - ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_px, i * 0.25f); - for (int line = 0; line < 100; line++) - { - if (track && line == track_line) - { - ImGui::TextColored(ImColor(255,255,0), "Line %d", line); - ImGui::SetScrollHere(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom - } - else - { - ImGui::Text("Line %d", line); - } - } - float scroll_y = ImGui::GetScrollY(), scroll_max_y = ImGui::GetScrollMaxY(); - ImGui::EndChild(); - ImGui::Text("%.0f/%0.f", scroll_y, scroll_max_y); - ImGui::EndGroup(); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Horizontal Scrolling")) - { - ImGui::Bullet(); ImGui::TextWrapped("Horizontal scrolling for a window has to be enabled explicitly via the ImGuiWindowFlags_HorizontalScrollbar flag."); - ImGui::Bullet(); ImGui::TextWrapped("You may want to explicitly specify content width by calling SetNextWindowContentWidth() before Begin()."); - static int lines = 7; - ImGui::SliderInt("Lines", &lines, 1, 15); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f)); - ImGui::BeginChild("scrolling", ImVec2(0, ImGui::GetFrameHeightWithSpacing()*7 + 30), true, ImGuiWindowFlags_HorizontalScrollbar); - for (int line = 0; line < lines; line++) - { - // Display random stuff (for the sake of this trivial demo we are using basic Button+SameLine. If you want to create your own time line for a real application you may be better off - // manipulating the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets yourself. You may also want to use the lower-level ImDrawList API) - int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3); - for (int n = 0; n < num_buttons; n++) - { - if (n > 0) ImGui::SameLine(); - ImGui::PushID(n + line * 1000); - char num_buf[16]; - sprintf(num_buf, "%d", n); - const char* label = (!(n%15)) ? "FizzBuzz" : (!(n%3)) ? "Fizz" : (!(n%5)) ? "Buzz" : num_buf; - float hue = n*0.05f; - ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f)); - ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f)); - ImGui::PopStyleColor(3); - ImGui::PopID(); - } - } - float scroll_x = ImGui::GetScrollX(), scroll_max_x = ImGui::GetScrollMaxX(); - ImGui::EndChild(); - ImGui::PopStyleVar(2); - float scroll_x_delta = 0.0f; - ImGui::SmallButton("<<"); if (ImGui::IsItemActive()) scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); - ImGui::Text("Scroll from code"); ImGui::SameLine(); - ImGui::SmallButton(">>"); if (ImGui::IsItemActive()) scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f; ImGui::SameLine(); - ImGui::Text("%.0f/%.0f", scroll_x, scroll_max_x); - if (scroll_x_delta != 0.0f) - { - ImGui::BeginChild("scrolling"); // Demonstrate a trick: you can use Begin to set yourself in the context of another window (here we are already out of your child window) - ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta); - ImGui::End(); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Clipping")) - { - static ImVec2 size(100, 100), offset(50, 20); - ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text CPU-side if it won't fit in its frame. Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost."); - ImGui::DragFloat2("size", (float*)&size, 0.5f, 0.0f, 200.0f, "%.0f"); - ImGui::TextWrapped("(Click and drag)"); - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImVec4 clip_rect(pos.x, pos.y, pos.x+size.x, pos.y+size.y); - ImGui::InvisibleButton("##dummy", size); - if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) { offset.x += ImGui::GetIO().MouseDelta.x; offset.y += ImGui::GetIO().MouseDelta.y; } - ImGui::GetWindowDrawList()->AddRectFilled(pos, ImVec2(pos.x+size.x,pos.y+size.y), IM_COL32(90,90,120,255)); - ImGui::GetWindowDrawList()->AddText(ImGui::GetFont(), ImGui::GetFontSize()*2.0f, ImVec2(pos.x+offset.x,pos.y+offset.y), IM_COL32(255,255,255,255), "Line 1 hello\nLine 2 clip me!", NULL, 0.0f, &clip_rect); - ImGui::TreePop(); - } - } - - if (ImGui::CollapsingHeader("Popups & Modal windows")) - { - if (ImGui::TreeNode("Popups")) - { - ImGui::TextWrapped("When a popup is active, it inhibits interacting with windows that are behind the popup. Clicking outside the popup closes it."); - - static int selected_fish = -1; - const char* names[] = { "Bream", "Haddock", "Mackerel", "Pollock", "Tilefish" }; - static bool toggles[] = { true, false, false, false, false }; - - // Simple selection popup - // (If you want to show the current selection inside the Button itself, you may want to build a string using the "###" operator to preserve a constant ID with a variable label) - if (ImGui::Button("Select..")) - ImGui::OpenPopup("select"); - ImGui::SameLine(); - ImGui::TextUnformatted(selected_fish == -1 ? "" : names[selected_fish]); - if (ImGui::BeginPopup("select")) - { - ImGui::Text("Aquarium"); - ImGui::Separator(); - for (int i = 0; i < IM_ARRAYSIZE(names); i++) - if (ImGui::Selectable(names[i])) - selected_fish = i; - ImGui::EndPopup(); - } - - // Showing a menu with toggles - if (ImGui::Button("Toggle..")) - ImGui::OpenPopup("toggle"); - if (ImGui::BeginPopup("toggle")) - { - for (int i = 0; i < IM_ARRAYSIZE(names); i++) - ImGui::MenuItem(names[i], "", &toggles[i]); - if (ImGui::BeginMenu("Sub-menu")) - { - ImGui::MenuItem("Click me"); - ImGui::EndMenu(); - } - - ImGui::Separator(); - ImGui::Text("Tooltip here"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("I am a tooltip over a popup"); - - if (ImGui::Button("Stacked Popup")) - ImGui::OpenPopup("another popup"); - if (ImGui::BeginPopup("another popup")) - { - for (int i = 0; i < IM_ARRAYSIZE(names); i++) - ImGui::MenuItem(names[i], "", &toggles[i]); - if (ImGui::BeginMenu("Sub-menu")) - { - ImGui::MenuItem("Click me"); - ImGui::EndMenu(); - } - ImGui::EndPopup(); - } - ImGui::EndPopup(); - } - - if (ImGui::Button("Popup Menu..")) - ImGui::OpenPopup("FilePopup"); - if (ImGui::BeginPopup("FilePopup")) - { - ShowExampleMenuFile(); - ImGui::EndPopup(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Context menus")) - { - // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing: - // if (IsItemHovered() && IsMouseClicked(0)) - // OpenPopup(id); - // return BeginPopup(id); - // For more advanced uses you may want to replicate and cuztomize this code. This the comments inside BeginPopupContextItem() implementation. - static float value = 0.5f; - ImGui::Text("Value = %.3f (<-- right-click here)", value); - if (ImGui::BeginPopupContextItem("item context menu")) - { - if (ImGui::Selectable("Set to zero")) value = 0.0f; - if (ImGui::Selectable("Set to PI")) value = 3.1415f; - ImGui::PushItemWidth(-1); - ImGui::DragFloat("##Value", &value, 0.1f, 0.0f, 0.0f); - ImGui::PopItemWidth(); - ImGui::EndPopup(); - } - - static char name[32] = "Label1"; - char buf[64]; sprintf(buf, "Button: %s###Button", name); // ### operator override ID ignoring the preceding label - ImGui::Button(buf); - if (ImGui::BeginPopupContextItem()) // When used after an item that has an ID (here the Button), we can skip providing an ID to BeginPopupContextItem(). - { - ImGui::Text("Edit name:"); - ImGui::InputText("##edit", name, IM_ARRAYSIZE(name)); - if (ImGui::Button("Close")) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - ImGui::SameLine(); ImGui::Text("(<-- right-click here)"); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Modals")) - { - ImGui::TextWrapped("Modal windows are like popups but the user cannot close them by clicking outside the window."); - - if (ImGui::Button("Delete..")) - ImGui::OpenPopup("Delete?"); - if (ImGui::BeginPopupModal("Delete?", NULL, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::Text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); - ImGui::Separator(); - - //static int dummy_i = 0; - //ImGui::Combo("Combo", &dummy_i, "Delete\0Delete harder\0"); - - static bool dont_ask_me_next_time = false; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); - ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time); - ImGui::PopStyleVar(); - - if (ImGui::Button("OK", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); } - ImGui::SetItemDefaultFocus(); - ImGui::SameLine(); - if (ImGui::Button("Cancel", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); } - ImGui::EndPopup(); - } - - if (ImGui::Button("Stacked modals..")) - ImGui::OpenPopup("Stacked 1"); - if (ImGui::BeginPopupModal("Stacked 1")) - { - ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDimBg] behind it."); - static int item = 1; - ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); - static float color[4] = { 0.4f,0.7f,0.0f,0.5f }; - ImGui::ColorEdit4("color", color); // This is to test behavior of stacked regular popups over a modal - - if (ImGui::Button("Add another modal..")) - ImGui::OpenPopup("Stacked 2"); - if (ImGui::BeginPopupModal("Stacked 2")) - { - ImGui::Text("Hello from Stacked The Second!"); - if (ImGui::Button("Close")) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - - if (ImGui::Button("Close")) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Menus inside a regular window")) - { - ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); - ImGui::Separator(); - // NB: As a quirk in this very specific example, we want to differentiate the parent of this menu from the parent of the various popup menus above. - // To do so we are encloding the items in a PushID()/PopID() block to make them two different menusets. If we don't, opening any popup above and hovering our menu here - // would open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, which is the desired behavior for regular menus. - ImGui::PushID("foo"); - ImGui::MenuItem("Menu item", "CTRL+M"); - if (ImGui::BeginMenu("Menu inside a regular window")) - { - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - ImGui::PopID(); - ImGui::Separator(); - ImGui::TreePop(); - } - } - - if (ImGui::CollapsingHeader("Columns")) - { - ImGui::PushID("Columns"); - - // Basic columns - if (ImGui::TreeNode("Basic")) - { - ImGui::Text("Without border:"); - ImGui::Columns(3, "mycolumns3", false); // 3-ways, no border - ImGui::Separator(); - for (int n = 0; n < 14; n++) - { - char label[32]; - sprintf(label, "Item %d", n); - if (ImGui::Selectable(label)) {} - //if (ImGui::Button(label, ImVec2(-1,0))) {} - ImGui::NextColumn(); - } - ImGui::Columns(1); - ImGui::Separator(); - - ImGui::Text("With border:"); - ImGui::Columns(4, "mycolumns"); // 4-ways, with border - ImGui::Separator(); - ImGui::Text("ID"); ImGui::NextColumn(); - ImGui::Text("Name"); ImGui::NextColumn(); - ImGui::Text("Path"); ImGui::NextColumn(); - ImGui::Text("Hovered"); ImGui::NextColumn(); - ImGui::Separator(); - const char* names[3] = { "One", "Two", "Three" }; - const char* paths[3] = { "/path/one", "/path/two", "/path/three" }; - static int selected = -1; - for (int i = 0; i < 3; i++) - { - char label[32]; - sprintf(label, "%04d", i); - if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns)) - selected = i; - bool hovered = ImGui::IsItemHovered(); - ImGui::NextColumn(); - ImGui::Text(names[i]); ImGui::NextColumn(); - ImGui::Text(paths[i]); ImGui::NextColumn(); - ImGui::Text("%d", hovered); ImGui::NextColumn(); - } - ImGui::Columns(1); - ImGui::Separator(); - ImGui::TreePop(); - } - - // Create multiple items in a same cell before switching to next column - if (ImGui::TreeNode("Mixed items")) - { - ImGui::Columns(3, "mixed"); - ImGui::Separator(); - - ImGui::Text("Hello"); - ImGui::Button("Banana"); - ImGui::NextColumn(); - - ImGui::Text("ImGui"); - ImGui::Button("Apple"); - static float foo = 1.0f; - ImGui::InputFloat("red", &foo, 0.05f, 0, "%.3f"); - ImGui::Text("An extra line here."); - ImGui::NextColumn(); - - ImGui::Text("Sailor"); - ImGui::Button("Corniflower"); - static float bar = 1.0f; - ImGui::InputFloat("blue", &bar, 0.05f, 0, "%.3f"); - ImGui::NextColumn(); - - if (ImGui::CollapsingHeader("Category A")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); - if (ImGui::CollapsingHeader("Category B")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); - if (ImGui::CollapsingHeader("Category C")) { ImGui::Text("Blah blah blah"); } ImGui::NextColumn(); - ImGui::Columns(1); - ImGui::Separator(); - ImGui::TreePop(); - } - - // Word wrapping - if (ImGui::TreeNode("Word-wrapping")) - { - ImGui::Columns(2, "word-wrapping"); - ImGui::Separator(); - ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); - ImGui::TextWrapped("Hello Left"); - ImGui::NextColumn(); - ImGui::TextWrapped("The quick brown fox jumps over the lazy dog."); - ImGui::TextWrapped("Hello Right"); - ImGui::Columns(1); - ImGui::Separator(); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Borders")) - { - // NB: Future columns API should allow automatic horizontal borders. - static bool h_borders = true; - static bool v_borders = true; - ImGui::Checkbox("horizontal", &h_borders); - ImGui::SameLine(); - ImGui::Checkbox("vertical", &v_borders); - ImGui::Columns(4, NULL, v_borders); - for (int i = 0; i < 4*3; i++) - { - if (h_borders && ImGui::GetColumnIndex() == 0) - ImGui::Separator(); - ImGui::Text("%c%c%c", 'a'+i, 'a'+i, 'a'+i); - ImGui::Text("Width %.2f\nOffset %.2f", ImGui::GetColumnWidth(), ImGui::GetColumnOffset()); - ImGui::NextColumn(); - } - ImGui::Columns(1); - if (h_borders) - ImGui::Separator(); - ImGui::TreePop(); - } - - // Scrolling columns - /* - if (ImGui::TreeNode("Vertical Scrolling")) - { - ImGui::BeginChild("##header", ImVec2(0, ImGui::GetTextLineHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)); - ImGui::Columns(3); - ImGui::Text("ID"); ImGui::NextColumn(); - ImGui::Text("Name"); ImGui::NextColumn(); - ImGui::Text("Path"); ImGui::NextColumn(); - ImGui::Columns(1); - ImGui::Separator(); - ImGui::EndChild(); - ImGui::BeginChild("##scrollingregion", ImVec2(0, 60)); - ImGui::Columns(3); - for (int i = 0; i < 10; i++) - { - ImGui::Text("%04d", i); ImGui::NextColumn(); - ImGui::Text("Foobar"); ImGui::NextColumn(); - ImGui::Text("/path/foobar/%04d/", i); ImGui::NextColumn(); - } - ImGui::Columns(1); - ImGui::EndChild(); - ImGui::TreePop(); - } - */ - - if (ImGui::TreeNode("Horizontal Scrolling")) - { - ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f)); - ImGui::BeginChild("##ScrollingRegion", ImVec2(0, ImGui::GetFontSize() * 20), false, ImGuiWindowFlags_HorizontalScrollbar); - ImGui::Columns(10); - int ITEMS_COUNT = 2000; - ImGuiListClipper clipper(ITEMS_COUNT); // Also demonstrate using the clipper for large list - while (clipper.Step()) - { - for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - for (int j = 0; j < 10; j++) - { - ImGui::Text("Line %d Column %d...", i, j); - ImGui::NextColumn(); - } - } - ImGui::Columns(1); - ImGui::EndChild(); - ImGui::TreePop(); - } - - bool node_open = ImGui::TreeNode("Tree within single cell"); - ImGui::SameLine(); ShowHelpMarker("NB: Tree node must be poped before ending the cell. There's no storage of state per-cell."); - if (node_open) - { - ImGui::Columns(2, "tree items"); - ImGui::Separator(); - if (ImGui::TreeNode("Hello")) { ImGui::BulletText("Sailor"); ImGui::TreePop(); } ImGui::NextColumn(); - if (ImGui::TreeNode("Bonjour")) { ImGui::BulletText("Marin"); ImGui::TreePop(); } ImGui::NextColumn(); - ImGui::Columns(1); - ImGui::Separator(); - ImGui::TreePop(); - } - ImGui::PopID(); - } - - if (ImGui::CollapsingHeader("Filtering")) - { - static ImGuiTextFilter filter; - ImGui::Text("Filter usage:\n" - " \"\" display all lines\n" - " \"xxx\" display lines containing \"xxx\"\n" - " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" - " \"-xxx\" hide lines containing \"xxx\""); - filter.Draw(); - const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; - for (int i = 0; i < IM_ARRAYSIZE(lines); i++) - if (filter.PassFilter(lines[i])) - ImGui::BulletText("%s", lines[i]); - } - - if (ImGui::CollapsingHeader("Inputs, Navigation & Focus")) - { - ImGuiIO& io = ImGui::GetIO(); - - ImGui::Text("WantCaptureMouse: %d", io.WantCaptureMouse); - ImGui::Text("WantCaptureKeyboard: %d", io.WantCaptureKeyboard); - ImGui::Text("WantTextInput: %d", io.WantTextInput); - ImGui::Text("WantSetMousePos: %d", io.WantSetMousePos); - ImGui::Text("NavActive: %d, NavVisible: %d", io.NavActive, io.NavVisible); - - if (ImGui::TreeNode("Keyboard, Mouse & Navigation State")) - { - if (ImGui::IsMousePosValid()) - ImGui::Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y); - else - ImGui::Text("Mouse pos: "); - ImGui::Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y); - ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]); } - ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse dbl-clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDoubleClicked(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("b%d", i); } - ImGui::Text("Mouse wheel: %.1f", io.MouseWheel); - - ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (io.KeysDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("%d (%.02f secs)", i, io.KeysDownDuration[i]); } - ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } - ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } - ImGui::Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); - - ImGui::Text("NavInputs down:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputs[i] > 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputs[i]); } - ImGui::Text("NavInputs pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] == 0.0f) { ImGui::SameLine(); ImGui::Text("[%d]", i); } - ImGui::Text("NavInputs duration:"); for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++) if (io.NavInputsDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("[%d] %.2f", i, io.NavInputsDownDuration[i]); } - - ImGui::Button("Hovering me sets the\nkeyboard capture flag"); - if (ImGui::IsItemHovered()) - ImGui::CaptureKeyboardFromApp(true); - ImGui::SameLine(); - ImGui::Button("Holding me clears the\nthe keyboard capture flag"); - if (ImGui::IsItemActive()) - ImGui::CaptureKeyboardFromApp(false); - - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Tabbing")) - { - ImGui::Text("Use TAB/SHIFT+TAB to cycle through keyboard editable fields."); - static char buf[32] = "dummy"; - ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); - ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); - ImGui::InputText("3", buf, IM_ARRAYSIZE(buf)); - ImGui::PushAllowKeyboardFocus(false); - ImGui::InputText("4 (tab skip)", buf, IM_ARRAYSIZE(buf)); - //ImGui::SameLine(); ShowHelperMarker("Use ImGui::PushAllowKeyboardFocus(bool)\nto disable tabbing through certain widgets."); - ImGui::PopAllowKeyboardFocus(); - ImGui::InputText("5", buf, IM_ARRAYSIZE(buf)); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Focus from code")) - { - bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine(); - bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine(); - bool focus_3 = ImGui::Button("Focus on 3"); - int has_focus = 0; - static char buf[128] = "click on a button to set focus"; - - if (focus_1) ImGui::SetKeyboardFocusHere(); - ImGui::InputText("1", buf, IM_ARRAYSIZE(buf)); - if (ImGui::IsItemActive()) has_focus = 1; - - if (focus_2) ImGui::SetKeyboardFocusHere(); - ImGui::InputText("2", buf, IM_ARRAYSIZE(buf)); - if (ImGui::IsItemActive()) has_focus = 2; - - ImGui::PushAllowKeyboardFocus(false); - if (focus_3) ImGui::SetKeyboardFocusHere(); - ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf)); - if (ImGui::IsItemActive()) has_focus = 3; - ImGui::PopAllowKeyboardFocus(); - - if (has_focus) - ImGui::Text("Item with focus: %d", has_focus); - else - ImGui::Text("Item with focus: "); - - // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item - static float f3[3] = { 0.0f, 0.0f, 0.0f }; - int focus_ahead = -1; - if (ImGui::Button("Focus on X")) focus_ahead = 0; ImGui::SameLine(); - if (ImGui::Button("Focus on Y")) focus_ahead = 1; ImGui::SameLine(); - if (ImGui::Button("Focus on Z")) focus_ahead = 2; - if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead); - ImGui::SliderFloat3("Float3", &f3[0], 0.0f, 1.0f); - - ImGui::TextWrapped("NB: Cursor & selection are preserved when refocusing last used item in code."); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Dragging")) - { - ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget."); - for (int button = 0; button < 3; button++) - ImGui::Text("IsMouseDragging(%d):\n w/ default threshold: %d,\n w/ zero threshold: %d\n w/ large threshold: %d", - button, ImGui::IsMouseDragging(button), ImGui::IsMouseDragging(button, 0.0f), ImGui::IsMouseDragging(button, 20.0f)); - ImGui::Button("Drag Me"); - if (ImGui::IsItemActive()) - { - // Draw a line between the button and the mouse cursor - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->PushClipRectFullScreen(); - draw_list->AddLine(io.MouseClickedPos[0], io.MousePos, ImGui::GetColorU32(ImGuiCol_Button), 4.0f); - draw_list->PopClipRect(); - - // Drag operations gets "unlocked" when the mouse has moved past a certain threshold (the default threshold is stored in io.MouseDragThreshold) - // You can request a lower or higher threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta() - ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f); - ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0); - ImVec2 mouse_delta = io.MouseDelta; - ImGui::SameLine(); ImGui::Text("Raw (%.1f, %.1f), WithLockThresold (%.1f, %.1f), MouseDelta (%.1f, %.1f)", value_raw.x, value_raw.y, value_with_lock_threshold.x, value_with_lock_threshold.y, mouse_delta.x, mouse_delta.y); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Mouse cursors")) - { - const char* mouse_cursors_names[] = { "Arrow", "TextInput", "Move", "ResizeNS", "ResizeEW", "ResizeNESW", "ResizeNWSE", "Hand" }; - IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT); - - ImGui::Text("Current mouse cursor = %d: %s", ImGui::GetMouseCursor(), mouse_cursors_names[ImGui::GetMouseCursor()]); - ImGui::Text("Hover to see mouse cursors:"); - ImGui::SameLine(); ShowHelpMarker("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, otherwise your backend needs to handle it."); - for (int i = 0; i < ImGuiMouseCursor_COUNT; i++) - { - char label[32]; - sprintf(label, "Mouse cursor %d: %s", i, mouse_cursors_names[i]); - ImGui::Bullet(); ImGui::Selectable(label, false); - if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) - ImGui::SetMouseCursor(i); - } - ImGui::TreePop(); - } - } - - // End of ShowDemoWindow() - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Style Editor / ShowStyleEditor() -//----------------------------------------------------------------------------- - -// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options. -// Here we use the simplified Combo() api that packs items into a single literal string. Useful for quick combo boxes where the choices are known locally. -bool ImGui::ShowStyleSelector(const char* label) -{ - static int style_idx = -1; - if (ImGui::Combo(label, &style_idx, "Classic\0Dark\0Light\0")) - { - switch (style_idx) - { - case 0: ImGui::StyleColorsClassic(); break; - case 1: ImGui::StyleColorsDark(); break; - case 2: ImGui::StyleColorsLight(); break; - } - return true; - } - return false; -} - -// Demo helper function to select among loaded fonts. -// Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one. -void ImGui::ShowFontSelector(const char* label) -{ - ImGuiIO& io = ImGui::GetIO(); - ImFont* font_current = ImGui::GetFont(); - if (ImGui::BeginCombo(label, font_current->GetDebugName())) - { - for (int n = 0; n < io.Fonts->Fonts.Size; n++) - if (ImGui::Selectable(io.Fonts->Fonts[n]->GetDebugName(), io.Fonts->Fonts[n] == font_current)) - io.FontDefault = io.Fonts->Fonts[n]; - ImGui::EndCombo(); - } - ImGui::SameLine(); - ShowHelpMarker( - "- Load additional fonts with io.Fonts->AddFontFromFileTTF().\n" - "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" - "- Read FAQ and documentation in misc/fonts/ for more details.\n" - "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); -} - -void ImGui::ShowStyleEditor(ImGuiStyle* ref) -{ - // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it compares to an internally stored reference) - ImGuiStyle& style = ImGui::GetStyle(); - static ImGuiStyle ref_saved_style; - - // Default to using internal storage as reference - static bool init = true; - if (init && ref == NULL) - ref_saved_style = style; - init = false; - if (ref == NULL) - ref = &ref_saved_style; - - ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); - - if (ImGui::ShowStyleSelector("Colors##Selector")) - ref_saved_style = style; - ImGui::ShowFontSelector("Fonts##Selector"); - - // Simplified Settings - if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) - style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding - { bool window_border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &window_border)) style.WindowBorderSize = window_border ? 1.0f : 0.0f; } - ImGui::SameLine(); - { bool frame_border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &frame_border)) style.FrameBorderSize = frame_border ? 1.0f : 0.0f; } - ImGui::SameLine(); - { bool popup_border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &popup_border)) style.PopupBorderSize = popup_border ? 1.0f : 0.0f; } - - // Save/Revert button - if (ImGui::Button("Save Ref")) - *ref = ref_saved_style = style; - ImGui::SameLine(); - if (ImGui::Button("Revert Ref")) - style = *ref; - ImGui::SameLine(); - ShowHelpMarker("Save/Revert in local non-persistent storage. Default Colors definition are not affected. Use \"Export Colors\" below to save them somewhere."); - - if (ImGui::TreeNode("Rendering")) - { - ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); ShowHelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); - ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); - ImGui::PushItemWidth(100); - ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, FLT_MAX, NULL, 2.0f); - if (style.CurveTessellationTol < 0.0f) style.CurveTessellationTol = 0.10f; - ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. - ImGui::PopItemWidth(); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Settings")) - { - ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 16.0f, "%.0f"); - ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); - ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); - ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); - ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); - ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); - ImGui::Text("BorderSize"); - ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::Text("Rounding"); - ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 14.0f, "%.0f"); - ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 16.0f, "%.0f"); - ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); - ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); - ImGui::Text("Alignment"); - ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); - ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ShowHelpMarker("Alignment applies when a button is larger than its text content."); - ImGui::Text("Safe Area Padding"); ImGui::SameLine(); ShowHelpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); - ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); - ImGui::TreePop(); - } - - if (ImGui::TreeNode("Colors")) - { - static int output_dest = 0; - static bool output_only_modified = true; - if (ImGui::Button("Export Unsaved")) - { - if (output_dest == 0) - ImGui::LogToClipboard(); - else - ImGui::LogToTTY(); - ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); - for (int i = 0; i < ImGuiCol_COUNT; i++) - { - const ImVec4& col = style.Colors[i]; - const char* name = ImGui::GetStyleColorName(i); - if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) - ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23-(int)strlen(name), "", col.x, col.y, col.z, col.w); - } - ImGui::LogFinish(); - } - ImGui::SameLine(); ImGui::PushItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); ImGui::PopItemWidth(); - ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); - - ImGui::Text("Tip: Left-click on colored square to open color picker,\nRight-click to open edit options menu."); - - static ImGuiTextFilter filter; - filter.Draw("Filter colors", 200); - - static ImGuiColorEditFlags alpha_flags = 0; - ImGui::RadioButton("Opaque", &alpha_flags, 0); ImGui::SameLine(); - ImGui::RadioButton("Alpha", &alpha_flags, ImGuiColorEditFlags_AlphaPreview); ImGui::SameLine(); - ImGui::RadioButton("Both", &alpha_flags, ImGuiColorEditFlags_AlphaPreviewHalf); - - ImGui::BeginChild("#colors", ImVec2(0, 300), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); - ImGui::PushItemWidth(-160); - for (int i = 0; i < ImGuiCol_COUNT; i++) - { - const char* name = ImGui::GetStyleColorName(i); - if (!filter.PassFilter(name)) - continue; - ImGui::PushID(i); - ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); - if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) - { - // Tips: in a real user application, you may want to merge and use an icon font into the main font, so instead of "Save"/"Revert" you'd use icons. - // Read the FAQ and misc/fonts/README.txt about using icon fonts. It's really easy and super convenient! - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) ref->Colors[i] = style.Colors[i]; - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) style.Colors[i] = ref->Colors[i]; - } - ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); - ImGui::TextUnformatted(name); - ImGui::PopID(); - } - ImGui::PopItemWidth(); - ImGui::EndChild(); - - ImGui::TreePop(); - } - - bool fonts_opened = ImGui::TreeNode("Fonts", "Fonts (%d)", ImGui::GetIO().Fonts->Fonts.Size); - if (fonts_opened) - { - ImFontAtlas* atlas = ImGui::GetIO().Fonts; - if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) - { - ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0,0), ImVec2(1,1), ImColor(255,255,255,255), ImColor(255,255,255,128)); - ImGui::TreePop(); - } - ImGui::PushItemWidth(100); - for (int i = 0; i < atlas->Fonts.Size; i++) - { - ImFont* font = atlas->Fonts[i]; - ImGui::PushID(font); - bool font_details_opened = ImGui::TreeNode(font, "Font %d: \'%s\', %.2f px, %d glyphs", i, font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size); - ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) ImGui::GetIO().FontDefault = font; - if (font_details_opened) - { - ImGui::PushFont(font); - ImGui::Text("The quick brown fox jumps over the lazy dog"); - ImGui::PopFont(); - ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // Scale only this font - ImGui::SameLine(); ShowHelpMarker("Note than the default embedded font is NOT meant to be scaled.\n\nFont are currently rendered into bitmaps at a given size at the time of building the atlas. You may oversample them to get some flexibility with scaling. You can also render at multiple sizes and select which one to use at runtime.\n\n(Glimmer of hope: the atlas system should hopefully be rewritten in the future to make scaling more natural and automatic.)"); - ImGui::InputFloat("Font offset", &font->DisplayOffset.y, 1, 1, "%.0f"); - ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); - ImGui::Text("Fallback character: '%c' (%d)", font->FallbackChar, font->FallbackChar); - ImGui::Text("Texture surface: %d pixels (approx) ~ %dx%d", font->MetricsTotalSurface, (int)sqrtf((float)font->MetricsTotalSurface), (int)sqrtf((float)font->MetricsTotalSurface)); - for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) - if (ImFontConfig* cfg = &font->ConfigData[config_i]) - ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d", config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH); - if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) - { - // Display all glyphs of the fonts in separate pages of 256 characters - for (int base = 0; base < 0x10000; base += 256) - { - int count = 0; - for (int n = 0; n < 256; n++) - count += font->FindGlyphNoFallback((ImWchar)(base + n)) ? 1 : 0; - if (count > 0 && ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base+255, count, count > 1 ? "glyphs" : "glyph")) - { - float cell_size = font->FontSize * 1; - float cell_spacing = style.ItemSpacing.y; - ImVec2 base_pos = ImGui::GetCursorScreenPos(); - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - for (int n = 0; n < 256; n++) - { - ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); - ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base+n)); - draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255,255,255,100) : IM_COL32(255,255,255,50)); - if (glyph) - font->RenderChar(draw_list, cell_size, cell_p1, ImGui::GetColorU32(ImGuiCol_Text), (ImWchar)(base+n)); // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions available to generate a string. - if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) - { - ImGui::BeginTooltip(); - ImGui::Text("Codepoint: U+%04X", base+n); - ImGui::Separator(); - ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); - ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); - ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); - ImGui::EndTooltip(); - } - } - ImGui::Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); - ImGui::TreePop(); - } - } - ImGui::TreePop(); - } - ImGui::TreePop(); - } - ImGui::PopID(); - } - static float window_scale = 1.0f; - ImGui::DragFloat("this window scale", &window_scale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale only this window - ImGui::DragFloat("global scale", &ImGui::GetIO().FontGlobalScale, 0.005f, 0.3f, 2.0f, "%.1f"); // scale everything - ImGui::PopItemWidth(); - ImGui::SetWindowFontScale(window_scale); - ImGui::TreePop(); - } - - ImGui::PopItemWidth(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar() -//----------------------------------------------------------------------------- - -// Demonstrate creating a fullscreen menu bar and populating it. -static void ShowExampleAppMainMenuBar() -{ - if (ImGui::BeginMainMenuBar()) - { - if (ImGui::BeginMenu("File")) - { - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Edit")) - { - if (ImGui::MenuItem("Undo", "CTRL+Z")) {} - if (ImGui::MenuItem("Redo", "CTRL+Y", false, false)) {} // Disabled item - ImGui::Separator(); - if (ImGui::MenuItem("Cut", "CTRL+X")) {} - if (ImGui::MenuItem("Copy", "CTRL+C")) {} - if (ImGui::MenuItem("Paste", "CTRL+V")) {} - ImGui::EndMenu(); - } - ImGui::EndMainMenuBar(); - } -} - -static void ShowExampleMenuFile() -{ - ImGui::MenuItem("(dummy menu)", NULL, false, false); - if (ImGui::MenuItem("New")) {} - if (ImGui::MenuItem("Open", "Ctrl+O")) {} - if (ImGui::BeginMenu("Open Recent")) - { - ImGui::MenuItem("fish_hat.c"); - ImGui::MenuItem("fish_hat.inl"); - ImGui::MenuItem("fish_hat.h"); - if (ImGui::BeginMenu("More..")) - { - ImGui::MenuItem("Hello"); - ImGui::MenuItem("Sailor"); - if (ImGui::BeginMenu("Recurse..")) - { - ShowExampleMenuFile(); - ImGui::EndMenu(); - } - ImGui::EndMenu(); - } - ImGui::EndMenu(); - } - if (ImGui::MenuItem("Save", "Ctrl+S")) {} - if (ImGui::MenuItem("Save As..")) {} - ImGui::Separator(); - if (ImGui::BeginMenu("Options")) - { - static bool enabled = true; - ImGui::MenuItem("Enabled", "", &enabled); - ImGui::BeginChild("child", ImVec2(0, 60), true); - for (int i = 0; i < 10; i++) - ImGui::Text("Scrolling Text %d", i); - ImGui::EndChild(); - static float f = 0.5f; - static int n = 0; - static bool b = true; - ImGui::SliderFloat("Value", &f, 0.0f, 1.0f); - ImGui::InputFloat("Input", &f, 0.1f); - ImGui::Combo("Combo", &n, "Yes\0No\0Maybe\0\0"); - ImGui::Checkbox("Check", &b); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Colors")) - { - float sz = ImGui::GetTextLineHeight(); - for (int i = 0; i < ImGuiCol_COUNT; i++) - { - const char* name = ImGui::GetStyleColorName((ImGuiCol)i); - ImVec2 p = ImGui::GetCursorScreenPos(); - ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x+sz, p.y+sz), ImGui::GetColorU32((ImGuiCol)i)); - ImGui::Dummy(ImVec2(sz, sz)); - ImGui::SameLine(); - ImGui::MenuItem(name); - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Disabled", false)) // Disabled - { - IM_ASSERT(0); - } - if (ImGui::MenuItem("Checked", NULL, true)) {} - if (ImGui::MenuItem("Quit", "Alt+F4")) {} -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Debug Console / ShowExampleAppConsole() -//----------------------------------------------------------------------------- - -// Demonstrate creating a simple console window, with scrolling, filtering, completion and history. -// For the console example, here we are using a more C++ like approach of declaring a class to hold the data and the functions. -struct ExampleAppConsole -{ - char InputBuf[256]; - ImVector Items; - bool ScrollToBottom; - ImVector History; - int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. - ImVector Commands; - - ExampleAppConsole() - { - ClearLog(); - memset(InputBuf, 0, sizeof(InputBuf)); - HistoryPos = -1; - Commands.push_back("HELP"); - Commands.push_back("HISTORY"); - Commands.push_back("CLEAR"); - Commands.push_back("CLASSIFY"); // "classify" is only here to provide an example of "C"+[tab] completing to "CL" and displaying matches. - AddLog("Welcome to Dear ImGui!"); - } - ~ExampleAppConsole() - { - ClearLog(); - for (int i = 0; i < History.Size; i++) - free(History[i]); - } - - // Portable helpers - static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; } - static int Strnicmp(const char* str1, const char* str2, int n) { int d = 0; while (n > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; n--; } return d; } - static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buff = malloc(len); return (char*)memcpy(buff, (const void*)str, len); } - static void Strtrim(char* str) { char* str_end = str + strlen(str); while (str_end > str && str_end[-1] == ' ') str_end--; *str_end = 0; } - - void ClearLog() - { - for (int i = 0; i < Items.Size; i++) - free(Items[i]); - Items.clear(); - ScrollToBottom = true; - } - - void AddLog(const char* fmt, ...) IM_FMTARGS(2) - { - // FIXME-OPT - char buf[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); - buf[IM_ARRAYSIZE(buf)-1] = 0; - va_end(args); - Items.push_back(Strdup(buf)); - ScrollToBottom = true; - } - - void Draw(const char* title, bool* p_open) - { - ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin(title, p_open)) - { - ImGui::End(); - return; - } - - // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar. So e.g. IsItemHovered() will return true when hovering the title bar. - // Here we create a context menu only available from the title bar. - if (ImGui::BeginPopupContextItem()) - { - if (ImGui::MenuItem("Close Console")) - *p_open = false; - ImGui::EndPopup(); - } - - ImGui::TextWrapped("This example implements a console with basic coloring, completion and history. A more elaborate implementation may want to store entries along with extra data such as timestamp, emitter, etc."); - ImGui::TextWrapped("Enter 'HELP' for help, press TAB to use text completion."); - - // TODO: display items starting from the bottom - - if (ImGui::SmallButton("Add Dummy Text")) { AddLog("%d some text", Items.Size); AddLog("some more text"); AddLog("display very important message here!"); } ImGui::SameLine(); - if (ImGui::SmallButton("Add Dummy Error")) { AddLog("[error] something went wrong"); } ImGui::SameLine(); - if (ImGui::SmallButton("Clear")) { ClearLog(); } ImGui::SameLine(); - bool copy_to_clipboard = ImGui::SmallButton("Copy"); ImGui::SameLine(); - if (ImGui::SmallButton("Scroll to bottom")) ScrollToBottom = true; - //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog("Spam %f", t); } - - ImGui::Separator(); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0,0)); - static ImGuiTextFilter filter; - filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180); - ImGui::PopStyleVar(); - ImGui::Separator(); - - const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text - ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText - if (ImGui::BeginPopupContextWindow()) - { - if (ImGui::Selectable("Clear")) ClearLog(); - ImGui::EndPopup(); - } - - // Display every line as a separate entry so we can change their color or add custom widgets. If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); - // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping to only process visible items. - // You can seek and display only the lines that are visible using the ImGuiListClipper helper, if your elements are evenly spaced and you have cheap random access to the elements. - // To use the clipper we could replace the 'for (int i = 0; i < Items.Size; i++)' loop with: - // ImGuiListClipper clipper(Items.Size); - // while (clipper.Step()) - // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - // However, note that you can not use this code as is if a filter is active because it breaks the 'cheap random-access' property. We would need random-access on the post-filtered list. - // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices that passed the filtering test, recomputing this array when user changes the filter, - // and appending newly elements as they are inserted. This is left as a task to the user until we can manage to improve this example code! - // If your items are of variable size you may want to implement code similar to what ImGuiListClipper does. Or split your data into fixed height items to allow random-seeking into your list. - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing - if (copy_to_clipboard) - ImGui::LogToClipboard(); - ImVec4 col_default_text = ImGui::GetStyleColorVec4(ImGuiCol_Text); - for (int i = 0; i < Items.Size; i++) - { - const char* item = Items[i]; - if (!filter.PassFilter(item)) - continue; - ImVec4 col = col_default_text; - if (strstr(item, "[error]")) col = ImColor(1.0f,0.4f,0.4f,1.0f); - else if (strncmp(item, "# ", 2) == 0) col = ImColor(1.0f,0.78f,0.58f,1.0f); - ImGui::PushStyleColor(ImGuiCol_Text, col); - ImGui::TextUnformatted(item); - ImGui::PopStyleColor(); - } - if (copy_to_clipboard) - ImGui::LogFinish(); - if (ScrollToBottom) - ImGui::SetScrollHere(1.0f); - ScrollToBottom = false; - ImGui::PopStyleVar(); - ImGui::EndChild(); - ImGui::Separator(); - - // Command-line - bool reclaim_focus = false; - if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) - { - char* s = InputBuf; - Strtrim(s); - if (s[0]) - ExecCommand(s); - strcpy(s, ""); - reclaim_focus = true; - } - - // Auto-focus on window apparition - ImGui::SetItemDefaultFocus(); - if (reclaim_focus) - ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget - - ImGui::End(); - } - - void ExecCommand(const char* command_line) - { - AddLog("# %s\n", command_line); - - // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal. - HistoryPos = -1; - for (int i = History.Size-1; i >= 0; i--) - if (Stricmp(History[i], command_line) == 0) - { - free(History[i]); - History.erase(History.begin() + i); - break; - } - History.push_back(Strdup(command_line)); - - // Process command - if (Stricmp(command_line, "CLEAR") == 0) - { - ClearLog(); - } - else if (Stricmp(command_line, "HELP") == 0) - { - AddLog("Commands:"); - for (int i = 0; i < Commands.Size; i++) - AddLog("- %s", Commands[i]); - } - else if (Stricmp(command_line, "HISTORY") == 0) - { - int first = History.Size - 10; - for (int i = first > 0 ? first : 0; i < History.Size; i++) - AddLog("%3d: %s\n", i, History[i]); - } - else - { - AddLog("Unknown command: '%s'\n", command_line); - } - } - - static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks - { - ExampleAppConsole* console = (ExampleAppConsole*)data->UserData; - return console->TextEditCallback(data); - } - - int TextEditCallback(ImGuiInputTextCallbackData* data) - { - //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd); - switch (data->EventFlag) - { - case ImGuiInputTextFlags_CallbackCompletion: - { - // Example of TEXT COMPLETION - - // Locate beginning of current word - const char* word_end = data->Buf + data->CursorPos; - const char* word_start = word_end; - while (word_start > data->Buf) - { - const char c = word_start[-1]; - if (c == ' ' || c == '\t' || c == ',' || c == ';') - break; - word_start--; - } - - // Build a list of candidates - ImVector candidates; - for (int i = 0; i < Commands.Size; i++) - if (Strnicmp(Commands[i], word_start, (int)(word_end-word_start)) == 0) - candidates.push_back(Commands[i]); - - if (candidates.Size == 0) - { - // No match - AddLog("No match for \"%.*s\"!\n", (int)(word_end-word_start), word_start); - } - else if (candidates.Size == 1) - { - // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing - data->DeleteChars((int)(word_start-data->Buf), (int)(word_end-word_start)); - data->InsertChars(data->CursorPos, candidates[0]); - data->InsertChars(data->CursorPos, " "); - } - else - { - // Multiple matches. Complete as much as we can, so inputing "C" will complete to "CL" and display "CLEAR" and "CLASSIFY" - int match_len = (int)(word_end - word_start); - for (;;) - { - int c = 0; - bool all_candidates_matches = true; - for (int i = 0; i < candidates.Size && all_candidates_matches; i++) - if (i == 0) - c = toupper(candidates[i][match_len]); - else if (c == 0 || c != toupper(candidates[i][match_len])) - all_candidates_matches = false; - if (!all_candidates_matches) - break; - match_len++; - } - - if (match_len > 0) - { - data->DeleteChars((int)(word_start - data->Buf), (int)(word_end-word_start)); - data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len); - } - - // List matches - AddLog("Possible matches:\n"); - for (int i = 0; i < candidates.Size; i++) - AddLog("- %s\n", candidates[i]); - } - - break; - } - case ImGuiInputTextFlags_CallbackHistory: - { - // Example of HISTORY - const int prev_history_pos = HistoryPos; - if (data->EventKey == ImGuiKey_UpArrow) - { - if (HistoryPos == -1) - HistoryPos = History.Size - 1; - else if (HistoryPos > 0) - HistoryPos--; - } - else if (data->EventKey == ImGuiKey_DownArrow) - { - if (HistoryPos != -1) - if (++HistoryPos >= History.Size) - HistoryPos = -1; - } - - // A better implementation would preserve the data on the current input line along with cursor position. - if (prev_history_pos != HistoryPos) - { - const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : ""; - data->DeleteChars(0, data->BufTextLen); - data->InsertChars(0, history_str); - } - } - } - return 0; - } -}; - -static void ShowExampleAppConsole(bool* p_open) -{ - static ExampleAppConsole console; - console.Draw("Example: Console", p_open); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Debug Log / ShowExampleAppLog() -//----------------------------------------------------------------------------- - -// Usage: -// static ExampleAppLog my_log; -// my_log.AddLog("Hello %d world\n", 123); -// my_log.Draw("title"); -struct ExampleAppLog -{ - ImGuiTextBuffer Buf; - ImGuiTextFilter Filter; - ImVector LineOffsets; // Index to lines offset - bool ScrollToBottom; - - void Clear() { Buf.clear(); LineOffsets.clear(); } - - void AddLog(const char* fmt, ...) IM_FMTARGS(2) - { - int old_size = Buf.size(); - va_list args; - va_start(args, fmt); - Buf.appendfv(fmt, args); - va_end(args); - for (int new_size = Buf.size(); old_size < new_size; old_size++) - if (Buf[old_size] == '\n') - LineOffsets.push_back(old_size); - ScrollToBottom = true; - } - - void Draw(const char* title, bool* p_open = NULL) - { - ImGui::SetNextWindowSize(ImVec2(500,400), ImGuiCond_FirstUseEver); - if (!ImGui::Begin(title, p_open)) - { - ImGui::End(); - return; - } - if (ImGui::Button("Clear")) Clear(); - ImGui::SameLine(); - bool copy = ImGui::Button("Copy"); - ImGui::SameLine(); - Filter.Draw("Filter", -100.0f); - ImGui::Separator(); - ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_HorizontalScrollbar); - if (copy) ImGui::LogToClipboard(); - - if (Filter.IsActive()) - { - const char* buf_begin = Buf.begin(); - const char* line = buf_begin; - for (int line_no = 0; line != NULL; line_no++) - { - const char* line_end = (line_no < LineOffsets.Size) ? buf_begin + LineOffsets[line_no] : NULL; - if (Filter.PassFilter(line, line_end)) - ImGui::TextUnformatted(line, line_end); - line = line_end && line_end[1] ? line_end + 1 : NULL; - } - } - else - { - ImGui::TextUnformatted(Buf.begin()); - } - - if (ScrollToBottom) - ImGui::SetScrollHere(1.0f); - ScrollToBottom = false; - ImGui::EndChild(); - ImGui::End(); - } -}; - -// Demonstrate creating a simple log window with basic filtering. -static void ShowExampleAppLog(bool* p_open) -{ - static ExampleAppLog log; - - // Demo: add random items (unless Ctrl is held) - static double last_time = -1.0; - double time = ImGui::GetTime(); - if (time - last_time >= 0.20f && !ImGui::GetIO().KeyCtrl) - { - const char* random_words[] = { "system", "info", "warning", "error", "fatal", "notice", "log" }; - log.AddLog("[%s] Hello, time is %.1f, frame count is %d\n", random_words[rand() % IM_ARRAYSIZE(random_words)], time, ImGui::GetFrameCount()); - last_time = time; - } - - log.Draw("Example: Log", p_open); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Simple Layout / ShowExampleAppLayout() -//----------------------------------------------------------------------------- - -// Demonstrate create a window with multiple child windows. -static void ShowExampleAppLayout(bool* p_open) -{ - ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver); - if (ImGui::Begin("Example: Layout", p_open, ImGuiWindowFlags_MenuBar)) - { - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("File")) - { - if (ImGui::MenuItem("Close")) *p_open = false; - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - - // left - static int selected = 0; - ImGui::BeginChild("left pane", ImVec2(150, 0), true); - for (int i = 0; i < 100; i++) - { - char label[128]; - sprintf(label, "MyObject %d", i); - if (ImGui::Selectable(label, selected == i)) - selected = i; - } - ImGui::EndChild(); - ImGui::SameLine(); - - // right - ImGui::BeginGroup(); - ImGui::BeginChild("item view", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us - ImGui::Text("MyObject: %d", selected); - ImGui::Separator(); - ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. "); - ImGui::EndChild(); - if (ImGui::Button("Revert")) {} - ImGui::SameLine(); - if (ImGui::Button("Save")) {} - ImGui::EndGroup(); - } - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor() -//----------------------------------------------------------------------------- - -// Demonstrate create a simple property editor. -static void ShowExampleAppPropertyEditor(bool* p_open) -{ - ImGui::SetNextWindowSize(ImVec2(430,450), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Example: Property editor", p_open)) - { - ImGui::End(); - return; - } - - ShowHelpMarker("This example shows how you may implement a property editor using two columns.\nAll objects/fields data are dummies here.\nRemember that in many simple cases, you can use ImGui::SameLine(xxx) to position\nyour cursor horizontally instead of using the Columns() API."); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2,2)); - ImGui::Columns(2); - ImGui::Separator(); - - struct funcs - { - static void ShowDummyObject(const char* prefix, int uid) - { - ImGui::PushID(uid); // Use object uid as identifier. Most commonly you could also use the object pointer as a base ID. - ImGui::AlignTextToFramePadding(); // Text and Tree nodes are less high than regular widgets, here we add vertical spacing to make the tree lines equal high. - bool node_open = ImGui::TreeNode("Object", "%s_%u", prefix, uid); - ImGui::NextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::Text("my sailor is rich"); - ImGui::NextColumn(); - if (node_open) - { - static float dummy_members[8] = { 0.0f,0.0f,1.0f,3.1416f,100.0f,999.0f }; - for (int i = 0; i < 8; i++) - { - ImGui::PushID(i); // Use field index as identifier. - if (i < 2) - { - ShowDummyObject("Child", 424242); - } - else - { - // Here we use a TreeNode to highlight on hover (we could use e.g. Selectable as well) - ImGui::AlignTextToFramePadding(); - ImGui::TreeNodeEx("Field", ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Bullet, "Field_%d", i); - ImGui::NextColumn(); - ImGui::PushItemWidth(-1); - if (i >= 5) - ImGui::InputFloat("##value", &dummy_members[i], 1.0f); - else - ImGui::DragFloat("##value", &dummy_members[i], 0.01f); - ImGui::PopItemWidth(); - ImGui::NextColumn(); - } - ImGui::PopID(); - } - ImGui::TreePop(); - } - ImGui::PopID(); - } - }; - - // Iterate dummy objects with dummy members (all the same data) - for (int obj_i = 0; obj_i < 3; obj_i++) - funcs::ShowDummyObject("Object", obj_i); - - ImGui::Columns(1); - ImGui::Separator(); - ImGui::PopStyleVar(); - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Long Text / ShowExampleAppLongText() -//----------------------------------------------------------------------------- - -// Demonstrate/test rendering huge amount of text, and the incidence of clipping. -static void ShowExampleAppLongText(bool* p_open) -{ - ImGui::SetNextWindowSize(ImVec2(520,600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Example: Long text display", p_open)) - { - ImGui::End(); - return; - } - - static int test_type = 0; - static ImGuiTextBuffer log; - static int lines = 0; - ImGui::Text("Printing unusually long amount of text."); - ImGui::Combo("Test type", &test_type, "Single call to TextUnformatted()\0Multiple calls to Text(), clipped manually\0Multiple calls to Text(), not clipped (slow)\0"); - ImGui::Text("Buffer contents: %d lines, %d bytes", lines, log.size()); - if (ImGui::Button("Clear")) { log.clear(); lines = 0; } - ImGui::SameLine(); - if (ImGui::Button("Add 1000 lines")) - { - for (int i = 0; i < 1000; i++) - log.appendf("%i The quick brown fox jumps over the lazy dog\n", lines+i); - lines += 1000; - } - ImGui::BeginChild("Log"); - switch (test_type) - { - case 0: - // Single call to TextUnformatted() with a big buffer - ImGui::TextUnformatted(log.begin(), log.end()); - break; - case 1: - { - // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper. - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); - ImGuiListClipper clipper(lines); - while (clipper.Step()) - for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); - ImGui::PopStyleVar(); - break; - } - case 2: - // Multiple calls to Text(), not clipped (slow) - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); - for (int i = 0; i < lines; i++) - ImGui::Text("%i The quick brown fox jumps over the lazy dog", i); - ImGui::PopStyleVar(); - break; - } - ImGui::EndChild(); - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize() -//----------------------------------------------------------------------------- - -// Demonstrate creating a window which gets auto-resized according to its content. -static void ShowExampleAppAutoResize(bool* p_open) -{ - if (!ImGui::Begin("Example: Auto-resizing window", p_open, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::End(); - return; - } - - static int lines = 10; - ImGui::Text("Window will resize every-frame to the size of its content.\nNote that you probably don't want to query the window size to\noutput your content because that would create a feedback loop."); - ImGui::SliderInt("Number of lines", &lines, 1, 20); - for (int i = 0; i < lines; i++) - ImGui::Text("%*sThis is line %d", i * 4, "", i); // Pad with space to extend size horizontally - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize() -//----------------------------------------------------------------------------- - -// Demonstrate creating a window with custom resize constraints. -static void ShowExampleAppConstrainedResize(bool* p_open) -{ - struct CustomConstraints // Helper functions to demonstrate programmatic constraints - { - static void Square(ImGuiSizeCallbackData* data) { data->DesiredSize = ImVec2(IM_MAX(data->DesiredSize.x, data->DesiredSize.y), IM_MAX(data->DesiredSize.x, data->DesiredSize.y)); } - static void Step(ImGuiSizeCallbackData* data) { float step = (float)(int)(intptr_t)data->UserData; data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step); } - }; - - static bool auto_resize = false; - static int type = 0; - static int display_lines = 10; - if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Vertical only - if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Horizontal only - if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100 - if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width 400-500 - if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, 500)); // Height 400-500 - if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square - if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)100);// Fixed Step - - ImGuiWindowFlags flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0; - if (ImGui::Begin("Example: Constrained Resize", p_open, flags)) - { - const char* desc[] = - { - "Resize vertical only", - "Resize horizontal only", - "Width > 100, Height > 100", - "Width 400-500", - "Height 400-500", - "Custom: Always Square", - "Custom: Fixed Steps (100)", - }; - if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); - if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); - if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } - ImGui::PushItemWidth(200); - ImGui::Combo("Constraint", &type, desc, IM_ARRAYSIZE(desc)); - ImGui::DragInt("Lines", &display_lines, 0.2f, 1, 100); - ImGui::PopItemWidth(); - ImGui::Checkbox("Auto-resize", &auto_resize); - for (int i = 0; i < display_lines; i++) - ImGui::Text("%*sHello, sailor! Making this line long enough for the example.", i * 4, ""); - } - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Simple Overlay / ShowExampleAppSimpleOverlay() -//----------------------------------------------------------------------------- - -// Demonstrate creating a simple static window with no decoration + a context-menu to choose which corner of the screen to use. -static void ShowExampleAppSimpleOverlay(bool* p_open) -{ - const float DISTANCE = 10.0f; - static int corner = 0; - ImVec2 window_pos = ImVec2((corner & 1) ? ImGui::GetIO().DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? ImGui::GetIO().DisplaySize.y - DISTANCE : DISTANCE); - ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); - if (corner != -1) - ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); - ImGui::SetNextWindowBgAlpha(0.3f); // Transparent background - if (ImGui::Begin("Example: Simple Overlay", p_open, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) - { - ImGui::Text("Simple overlay\n" "in the corner of the screen.\n" "(right-click to change position)"); - ImGui::Separator(); - if (ImGui::IsMousePosValid()) - ImGui::Text("Mouse Position: (%.1f,%.1f)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); - else - ImGui::Text("Mouse Position: "); - if (ImGui::BeginPopupContextWindow()) - { - if (ImGui::MenuItem("Custom", NULL, corner == -1)) corner = -1; - if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; - if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; - if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; - if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; - if (p_open && ImGui::MenuItem("Close")) *p_open = false; - ImGui::EndPopup(); - } - } - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles() -//----------------------------------------------------------------------------- - -// Demonstrate using "##" and "###" in identifiers to manipulate ID generation. -// This apply to all regular items as well. Read FAQ section "How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on the purpose of labels/IDs." for details. -static void ShowExampleAppWindowTitles(bool*) -{ - // By default, Windows are uniquely identified by their title. - // You can use the "##" and "###" markers to manipulate the display/ID. - - // Using "##" to display same title but have unique identifier. - ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver); - ImGui::Begin("Same title as another window##1"); - ImGui::Text("This is window 1.\nMy title is the same as window 2, but my identifier is unique."); - ImGui::End(); - - ImGui::SetNextWindowPos(ImVec2(100, 200), ImGuiCond_FirstUseEver); - ImGui::Begin("Same title as another window##2"); - ImGui::Text("This is window 2.\nMy title is the same as window 1, but my identifier is unique."); - ImGui::End(); - - // Using "###" to display a changing title but keep a static identifier "AnimatedTitle" - char buf[128]; - sprintf(buf, "Animated title %c %d###AnimatedTitle", "|/-\\"[(int)(ImGui::GetTime() / 0.25f) & 3], ImGui::GetFrameCount()); - ImGui::SetNextWindowPos(ImVec2(100, 300), ImGuiCond_FirstUseEver); - ImGui::Begin(buf); - ImGui::Text("This window has a changing title."); - ImGui::End(); -} - -//----------------------------------------------------------------------------- -// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() -//----------------------------------------------------------------------------- - -// Demonstrate using the low-level ImDrawList to draw custom shapes. -static void ShowExampleAppCustomRendering(bool* p_open) -{ - ImGui::SetNextWindowSize(ImVec2(350, 560), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Example: Custom rendering", p_open)) - { - ImGui::End(); - return; - } - - // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of overloaded operators, etc. - // Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your types and ImVec2/ImVec4. - // ImGui defines overloaded operators but they are internal to imgui.cpp and not exposed outside (to avoid messing with your types) - // In this example we are not using the maths operators! - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - // Primitives - ImGui::Text("Primitives"); - static float sz = 36.0f; - static float thickness = 4.0f; - static ImVec4 col = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); - ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 72.0f, "%.0f"); - ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f"); - ImGui::ColorEdit3("Color", &col.x); - { - const ImVec2 p = ImGui::GetCursorScreenPos(); - const ImU32 col32 = ImColor(col); - float x = p.x + 4.0f, y = p.y + 4.0f, spacing = 8.0f; - for (int n = 0; n < 2; n++) - { - float curr_thickness = (n == 0) ? 1.0f : thickness; - draw_list->AddCircle(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 20, curr_thickness); x += sz+spacing; - draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 0.0f, ImDrawCornerFlags_All, curr_thickness); x += sz+spacing; - draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_All, curr_thickness); x += sz+spacing; - draw_list->AddRect(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight, curr_thickness); x += sz+spacing; - draw_list->AddTriangle(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32, curr_thickness); x += sz+spacing; - draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y ), col32, curr_thickness); x += sz+spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine(ImVec2(x, y), ImVec2(x, y+sz), col32, curr_thickness); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, curr_thickness); x += sz+spacing; // Diagonal line - draw_list->AddBezierCurve(ImVec2(x, y), ImVec2(x+sz*1.3f,y+sz*0.3f), ImVec2(x+sz-sz*1.3f,y+sz-sz*0.3f), ImVec2(x+sz, y+sz), col32, curr_thickness); - x = p.x + 4; - y += sz+spacing; - } - draw_list->AddCircleFilled(ImVec2(x+sz*0.5f, y+sz*0.5f), sz*0.5f, col32, 32); x += sz+spacing; - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32); x += sz+spacing; - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f); x += sz+spacing; - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+sz), col32, 10.0f, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotRight); x += sz+spacing; - draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f, y), ImVec2(x+sz,y+sz-0.5f), ImVec2(x,y+sz-0.5f), col32); x += sz+spacing; - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+sz, y+thickness), col32); x += sz+spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+thickness, y+sz), col32); x += spacing+spacing; // Vertical line (faster than AddLine, but only handle integer thickness) - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x+1, y+1), col32); x += sz; // Pixel (faster than AddLine) - draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x+sz, y+sz), IM_COL32(0,0,0,255), IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255)); - ImGui::Dummy(ImVec2((sz+spacing)*8, (sz+spacing)*3)); - } - ImGui::Separator(); - { - static ImVector points; - static bool adding_line = false; - ImGui::Text("Canvas example"); - if (ImGui::Button("Clear")) points.clear(); - if (points.Size >= 2) { ImGui::SameLine(); if (ImGui::Button("Undo")) { points.pop_back(); points.pop_back(); } } - ImGui::Text("Left-click and drag to add lines,\nRight-click to undo"); - - // Here we are using InvisibleButton() as a convenience to 1) advance the cursor and 2) allows us to use IsItemHovered() - // But you can also draw directly and poll mouse/keyboard by yourself. You can manipulate the cursor using GetCursorPos() and SetCursorPos(). - // If you only use the ImDrawList API, you can notify the owner window of its extends by using SetCursorPos(max). - ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates! - ImVec2 canvas_size = ImGui::GetContentRegionAvail(); // Resize canvas to what's available - if (canvas_size.x < 50.0f) canvas_size.x = 50.0f; - if (canvas_size.y < 50.0f) canvas_size.y = 50.0f; - draw_list->AddRectFilledMultiColor(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(50, 50, 50, 255), IM_COL32(50, 50, 60, 255), IM_COL32(60, 60, 70, 255), IM_COL32(50, 50, 60, 255)); - draw_list->AddRect(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), IM_COL32(255, 255, 255, 255)); - - bool adding_preview = false; - ImGui::InvisibleButton("canvas", canvas_size); - ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y); - if (adding_line) - { - adding_preview = true; - points.push_back(mouse_pos_in_canvas); - if (!ImGui::IsMouseDown(0)) - adding_line = adding_preview = false; - } - if (ImGui::IsItemHovered()) - { - if (!adding_line && ImGui::IsMouseClicked(0)) - { - points.push_back(mouse_pos_in_canvas); - adding_line = true; - } - if (ImGui::IsMouseClicked(1) && !points.empty()) - { - adding_line = adding_preview = false; - points.pop_back(); - points.pop_back(); - } - } - draw_list->PushClipRect(canvas_pos, ImVec2(canvas_pos.x + canvas_size.x, canvas_pos.y + canvas_size.y), true); // clip lines within the canvas (if we resize it, etc.) - for (int i = 0; i < points.Size - 1; i += 2) - draw_list->AddLine(ImVec2(canvas_pos.x + points[i].x, canvas_pos.y + points[i].y), ImVec2(canvas_pos.x + points[i + 1].x, canvas_pos.y + points[i + 1].y), IM_COL32(255, 255, 0, 255), 2.0f); - draw_list->PopClipRect(); - if (adding_preview) - points.pop_back(); - } - ImGui::End(); -} - -// End of Demo code -#else - -void ImGui::ShowDemoWindow(bool*) {} -void ImGui::ShowUserGuide() {} -void ImGui::ShowStyleEditor(ImGuiStyle*) {} - -#endif diff --git a/Framework/src/imgui/imgui_draw.cpp b/Framework/src/imgui/imgui_draw.cpp deleted file mode 100644 index 3afcac4e90..0000000000 --- a/Framework/src/imgui/imgui_draw.cpp +++ /dev/null @@ -1,3153 +0,0 @@ -// dear imgui, v1.65 -// (drawing and font code) - -/* - -Index of this file: - -// [SECTION] STB libraries implementation -// [SECTION] Style functions -// [SECTION] ImDrawList -// [SECTION] ImDrawData -// [SECTION] Helpers ShadeVertsXXX functions -// [SECTION] ImFontConfig -// [SECTION] ImFontAtlas -// [SECTION] ImFontAtlas glyph ranges helpers + GlyphRangesBuilder -// [SECTION] ImFont -// [SECTION] Internal Render Helpers -// [SECTION] Decompression code -// [SECTION] Default font data (ProggyClean.ttf) - -*/ - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include "imgui.h" -#ifndef IMGUI_DEFINE_MATH_OPERATORS -#define IMGUI_DEFINE_MATH_OPERATORS -#endif -#include "imgui_internal.h" - -#include // vsnprintf, sscanf, printf -#if !defined(alloca) -#if defined(__GLIBC__) || defined(__sun) || defined(__CYGWIN__) -#include // alloca (glibc uses . Note that Cygwin may have _WIN32 defined, so the order matters here) -#elif defined(_WIN32) -#include // alloca -#if !defined(alloca) -#define alloca _alloca // for clang with MS Codegen -#endif -#else -#include // alloca -#endif -#endif - -// Visual Studio warnings -#ifdef _MSC_VER -#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) -#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen -#endif - -// Clang/GCC warnings with -Weverything -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse. -#pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants ok. -#pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it. -#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // -#if __has_warning("-Wcomma") -#pragma clang diagnostic ignored "-Wcomma" // warning : possible misuse of comma operator here // -#endif -#if __has_warning("-Wreserved-id-macro") -#pragma clang diagnostic ignored "-Wreserved-id-macro" // warning : macro name is a reserved identifier // -#endif -#if __has_warning("-Wdouble-promotion") -#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#endif -#elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#if __GNUC__ >= 8 -#pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead -#endif -#endif - -//------------------------------------------------------------------------- -// [SECTION] STB libraries implementation -//------------------------------------------------------------------------- - -// Compile time options: -//#define IMGUI_STB_NAMESPACE ImGuiStb -//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" -//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" -//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION -//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION - -#ifdef IMGUI_STB_NAMESPACE -namespace IMGUI_STB_NAMESPACE -{ -#endif - -#ifdef _MSC_VER -#pragma warning (push) -#pragma warning (disable: 4456) // declaration of 'xx' hides previous local declaration -#endif - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-function" -#pragma clang diagnostic ignored "-Wmissing-prototypes" -#pragma clang diagnostic ignored "-Wimplicit-fallthrough" -#pragma clang diagnostic ignored "-Wcast-qual" // warning : cast from 'const xxxx *' to 'xxx *' drops const qualifier // -#endif - -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wtype-limits" // warning: comparison is always true due to limited range of data type [-Wtype-limits] -#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers -#endif - -#ifndef STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) -#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION -#define STBRP_STATIC -#define STBRP_ASSERT(x) IM_ASSERT(x) -#define STBRP_SORT ImQsort -#define STB_RECT_PACK_IMPLEMENTATION -#endif -#ifdef IMGUI_STB_RECT_PACK_FILENAME -#include IMGUI_STB_RECT_PACK_FILENAME -#else -#include "imstb_rectpack.h" -#endif -#endif - -#ifndef STB_TRUETYPE_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) -#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION -#define STBTT_malloc(x,u) ((void)(u), ImGui::MemAlloc(x)) -#define STBTT_free(x,u) ((void)(u), ImGui::MemFree(x)) -#define STBTT_assert(x) IM_ASSERT(x) -#define STBTT_fmod(x,y) ImFmod(x,y) -#define STBTT_sqrt(x) ImSqrt(x) -#define STBTT_pow(x,y) ImPow(x,y) -#define STBTT_fabs(x) ImFabs(x) -#define STBTT_ifloor(x) ((int)ImFloorStd(x)) -#define STBTT_iceil(x) ((int)ImCeil(x)) -#define STBTT_STATIC -#define STB_TRUETYPE_IMPLEMENTATION -#else -#define STBTT_DEF extern -#endif -#ifdef IMGUI_STB_TRUETYPE_FILENAME -#include IMGUI_STB_TRUETYPE_FILENAME -#else -#include "imstb_truetype.h" -#endif -#endif - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -#ifdef _MSC_VER -#pragma warning (pop) -#endif - -#ifdef IMGUI_STB_NAMESPACE -} // namespace ImGuiStb -using namespace IMGUI_STB_NAMESPACE; -#endif - -//----------------------------------------------------------------------------- -// [SECTION] Style functions -//----------------------------------------------------------------------------- - -void ImGui::StyleColorsDark(ImGuiStyle* dst) -{ - ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); - ImVec4* colors = style->Colors; - - colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); - colors[ImGuiCol_ChildBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); - colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); - colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.29f, 0.48f, 0.54f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); - colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.29f, 0.48f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); - colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); - colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); - colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f); - colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); - colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); - colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); - colors[ImGuiCol_Separator] = colors[ImGuiCol_Border]; - colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); - colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); - colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); - colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); - colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); - colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); - colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); - colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); - colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); - colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); - colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); - colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); - colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); - colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); -} - -void ImGui::StyleColorsClassic(ImGuiStyle* dst) -{ - ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); - ImVec4* colors = style->Colors; - - colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f); - colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f); - colors[ImGuiCol_Border] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f); - colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_FrameBg] = ImVec4(0.43f, 0.43f, 0.43f, 0.39f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.47f, 0.47f, 0.69f, 0.40f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.41f, 0.64f, 0.69f); - colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.27f, 0.54f, 0.83f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.32f, 0.63f, 0.87f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.40f, 0.40f, 0.80f, 0.20f); - colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.40f, 0.55f, 0.80f); - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.20f, 0.25f, 0.30f, 0.60f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.40f, 0.40f, 0.80f, 0.30f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); - colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); - colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); - colors[ImGuiCol_SliderGrabActive] = ImVec4(0.41f, 0.39f, 0.80f, 0.60f); - colors[ImGuiCol_Button] = ImVec4(0.35f, 0.40f, 0.61f, 0.62f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.48f, 0.71f, 0.79f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.46f, 0.54f, 0.80f, 1.00f); - colors[ImGuiCol_Header] = ImVec4(0.40f, 0.40f, 0.90f, 0.45f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.45f, 0.90f, 0.80f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.53f, 0.87f, 0.80f); - colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); - colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.60f, 0.70f, 1.00f); - colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.70f, 0.90f, 1.00f); - colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.16f); - colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); - colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); - colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); - colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); - colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); - colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); - colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); - colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; - colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); - colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); -} - -// Those light colors are better suited with a thicker font than the default one + FrameBorder -void ImGui::StyleColorsLight(ImGuiStyle* dst) -{ - ImGuiStyle* style = dst ? dst : &ImGui::GetStyle(); - ImVec4* colors = style->Colors; - - colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_PopupBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f); - colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f); - colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); - colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f); - colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.98f, 0.98f, 0.98f, 0.53f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.69f, 0.69f, 0.69f, 0.80f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.49f, 0.49f, 0.49f, 0.80f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.49f, 0.49f, 0.49f, 1.00f); - colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); - colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.59f, 0.98f, 0.78f); - colors[ImGuiCol_SliderGrabActive] = ImVec4(0.46f, 0.54f, 0.80f, 0.60f); - colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); - colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); - colors[ImGuiCol_Separator] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); - colors[ImGuiCol_SeparatorHovered] = ImVec4(0.14f, 0.44f, 0.80f, 0.78f); - colors[ImGuiCol_SeparatorActive] = ImVec4(0.14f, 0.44f, 0.80f, 1.00f); - colors[ImGuiCol_ResizeGrip] = ImVec4(0.80f, 0.80f, 0.80f, 0.56f); - colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); - colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); - colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); - colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); - colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); - colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.45f, 0.00f, 1.00f); - colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); - colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); - colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; - colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.70f, 0.70f, 0.70f, 0.70f); - colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.20f); - colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); -} - -//----------------------------------------------------------------------------- -// ImDrawList -//----------------------------------------------------------------------------- - -ImDrawListSharedData::ImDrawListSharedData() -{ - Font = NULL; - FontSize = 0.0f; - CurveTessellationTol = 0.0f; - ClipRectFullscreen = ImVec4(-8192.0f, -8192.0f, +8192.0f, +8192.0f); - - // Const data - for (int i = 0; i < IM_ARRAYSIZE(CircleVtx12); i++) - { - const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(CircleVtx12); - CircleVtx12[i] = ImVec2(ImCos(a), ImSin(a)); - } -} - -void ImDrawList::Clear() -{ - CmdBuffer.resize(0); - IdxBuffer.resize(0); - VtxBuffer.resize(0); - Flags = ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill; - _VtxCurrentIdx = 0; - _VtxWritePtr = NULL; - _IdxWritePtr = NULL; - _ClipRectStack.resize(0); - _TextureIdStack.resize(0); - _Path.resize(0); - _ChannelsCurrent = 0; - _ChannelsCount = 1; - // NB: Do not clear channels so our allocations are re-used after the first frame. -} - -void ImDrawList::ClearFreeMemory() -{ - CmdBuffer.clear(); - IdxBuffer.clear(); - VtxBuffer.clear(); - _VtxCurrentIdx = 0; - _VtxWritePtr = NULL; - _IdxWritePtr = NULL; - _ClipRectStack.clear(); - _TextureIdStack.clear(); - _Path.clear(); - _ChannelsCurrent = 0; - _ChannelsCount = 1; - for (int i = 0; i < _Channels.Size; i++) - { - if (i == 0) memset(&_Channels[0], 0, sizeof(_Channels[0])); // channel 0 is a copy of CmdBuffer/IdxBuffer, don't destruct again - _Channels[i].CmdBuffer.clear(); - _Channels[i].IdxBuffer.clear(); - } - _Channels.clear(); -} - -ImDrawList* ImDrawList::CloneOutput() const -{ - ImDrawList* dst = IM_NEW(ImDrawList(NULL)); - dst->CmdBuffer = CmdBuffer; - dst->IdxBuffer = IdxBuffer; - dst->VtxBuffer = VtxBuffer; - dst->Flags = Flags; - return dst; -} - -// Using macros because C++ is a terrible language, we want guaranteed inline, no code in header, and no overhead in Debug builds -#define GetCurrentClipRect() (_ClipRectStack.Size ? _ClipRectStack.Data[_ClipRectStack.Size-1] : _Data->ClipRectFullscreen) -#define GetCurrentTextureId() (_TextureIdStack.Size ? _TextureIdStack.Data[_TextureIdStack.Size-1] : NULL) - -void ImDrawList::AddDrawCmd() -{ - ImDrawCmd draw_cmd; - draw_cmd.ClipRect = GetCurrentClipRect(); - draw_cmd.TextureId = GetCurrentTextureId(); - - IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z && draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w); - CmdBuffer.push_back(draw_cmd); -} - -void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) -{ - ImDrawCmd* current_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; - if (!current_cmd || current_cmd->ElemCount != 0 || current_cmd->UserCallback != NULL) - { - AddDrawCmd(); - current_cmd = &CmdBuffer.back(); - } - current_cmd->UserCallback = callback; - current_cmd->UserCallbackData = callback_data; - - AddDrawCmd(); // Force a new command after us (see comment below) -} - -// Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack. -// The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only. -void ImDrawList::UpdateClipRect() -{ - // If current command is used with different settings we need to add a new command - const ImVec4 curr_clip_rect = GetCurrentClipRect(); - ImDrawCmd* curr_cmd = CmdBuffer.Size > 0 ? &CmdBuffer.Data[CmdBuffer.Size-1] : NULL; - if (!curr_cmd || (curr_cmd->ElemCount != 0 && memcmp(&curr_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) != 0) || curr_cmd->UserCallback != NULL) - { - AddDrawCmd(); - return; - } - - // Try to merge with previous command if it matches, else use current command - ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; - if (curr_cmd->ElemCount == 0 && prev_cmd && memcmp(&prev_cmd->ClipRect, &curr_clip_rect, sizeof(ImVec4)) == 0 && prev_cmd->TextureId == GetCurrentTextureId() && prev_cmd->UserCallback == NULL) - CmdBuffer.pop_back(); - else - curr_cmd->ClipRect = curr_clip_rect; -} - -void ImDrawList::UpdateTextureID() -{ - // If current command is used with different settings we need to add a new command - const ImTextureID curr_texture_id = GetCurrentTextureId(); - ImDrawCmd* curr_cmd = CmdBuffer.Size ? &CmdBuffer.back() : NULL; - if (!curr_cmd || (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != curr_texture_id) || curr_cmd->UserCallback != NULL) - { - AddDrawCmd(); - return; - } - - // Try to merge with previous command if it matches, else use current command - ImDrawCmd* prev_cmd = CmdBuffer.Size > 1 ? curr_cmd - 1 : NULL; - if (curr_cmd->ElemCount == 0 && prev_cmd && prev_cmd->TextureId == curr_texture_id && memcmp(&prev_cmd->ClipRect, &GetCurrentClipRect(), sizeof(ImVec4)) == 0 && prev_cmd->UserCallback == NULL) - CmdBuffer.pop_back(); - else - curr_cmd->TextureId = curr_texture_id; -} - -#undef GetCurrentClipRect -#undef GetCurrentTextureId - -// Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) -void ImDrawList::PushClipRect(ImVec2 cr_min, ImVec2 cr_max, bool intersect_with_current_clip_rect) -{ - ImVec4 cr(cr_min.x, cr_min.y, cr_max.x, cr_max.y); - if (intersect_with_current_clip_rect && _ClipRectStack.Size) - { - ImVec4 current = _ClipRectStack.Data[_ClipRectStack.Size-1]; - if (cr.x < current.x) cr.x = current.x; - if (cr.y < current.y) cr.y = current.y; - if (cr.z > current.z) cr.z = current.z; - if (cr.w > current.w) cr.w = current.w; - } - cr.z = ImMax(cr.x, cr.z); - cr.w = ImMax(cr.y, cr.w); - - _ClipRectStack.push_back(cr); - UpdateClipRect(); -} - -void ImDrawList::PushClipRectFullScreen() -{ - PushClipRect(ImVec2(_Data->ClipRectFullscreen.x, _Data->ClipRectFullscreen.y), ImVec2(_Data->ClipRectFullscreen.z, _Data->ClipRectFullscreen.w)); -} - -void ImDrawList::PopClipRect() -{ - IM_ASSERT(_ClipRectStack.Size > 0); - _ClipRectStack.pop_back(); - UpdateClipRect(); -} - -void ImDrawList::PushTextureID(ImTextureID texture_id) -{ - _TextureIdStack.push_back(texture_id); - UpdateTextureID(); -} - -void ImDrawList::PopTextureID() -{ - IM_ASSERT(_TextureIdStack.Size > 0); - _TextureIdStack.pop_back(); - UpdateTextureID(); -} - -void ImDrawList::ChannelsSplit(int channels_count) -{ - IM_ASSERT(_ChannelsCurrent == 0 && _ChannelsCount == 1); - int old_channels_count = _Channels.Size; - if (old_channels_count < channels_count) - _Channels.resize(channels_count); - _ChannelsCount = channels_count; - - // _Channels[] (24/32 bytes each) hold storage that we'll swap with this->_CmdBuffer/_IdxBuffer - // The content of _Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to. - // When we switch to the next channel, we'll copy _CmdBuffer/_IdxBuffer into _Channels[0] and then _Channels[1] into _CmdBuffer/_IdxBuffer - memset(&_Channels[0], 0, sizeof(ImDrawChannel)); - for (int i = 1; i < channels_count; i++) - { - if (i >= old_channels_count) - { - IM_PLACEMENT_NEW(&_Channels[i]) ImDrawChannel(); - } - else - { - _Channels[i].CmdBuffer.resize(0); - _Channels[i].IdxBuffer.resize(0); - } - if (_Channels[i].CmdBuffer.Size == 0) - { - ImDrawCmd draw_cmd; - draw_cmd.ClipRect = _ClipRectStack.back(); - draw_cmd.TextureId = _TextureIdStack.back(); - _Channels[i].CmdBuffer.push_back(draw_cmd); - } - } -} - -void ImDrawList::ChannelsMerge() -{ - // Note that we never use or rely on channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use. - if (_ChannelsCount <= 1) - return; - - ChannelsSetCurrent(0); - if (CmdBuffer.Size && CmdBuffer.back().ElemCount == 0) - CmdBuffer.pop_back(); - - int new_cmd_buffer_count = 0, new_idx_buffer_count = 0; - for (int i = 1; i < _ChannelsCount; i++) - { - ImDrawChannel& ch = _Channels[i]; - if (ch.CmdBuffer.Size && ch.CmdBuffer.back().ElemCount == 0) - ch.CmdBuffer.pop_back(); - new_cmd_buffer_count += ch.CmdBuffer.Size; - new_idx_buffer_count += ch.IdxBuffer.Size; - } - CmdBuffer.resize(CmdBuffer.Size + new_cmd_buffer_count); - IdxBuffer.resize(IdxBuffer.Size + new_idx_buffer_count); - - ImDrawCmd* cmd_write = CmdBuffer.Data + CmdBuffer.Size - new_cmd_buffer_count; - _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size - new_idx_buffer_count; - for (int i = 1; i < _ChannelsCount; i++) - { - ImDrawChannel& ch = _Channels[i]; - if (int sz = ch.CmdBuffer.Size) { memcpy(cmd_write, ch.CmdBuffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; } - if (int sz = ch.IdxBuffer.Size) { memcpy(_IdxWritePtr, ch.IdxBuffer.Data, sz * sizeof(ImDrawIdx)); _IdxWritePtr += sz; } - } - UpdateClipRect(); // We call this instead of AddDrawCmd(), so that empty channels won't produce an extra draw call. - _ChannelsCount = 1; -} - -void ImDrawList::ChannelsSetCurrent(int idx) -{ - IM_ASSERT(idx < _ChannelsCount); - if (_ChannelsCurrent == idx) return; - memcpy(&_Channels.Data[_ChannelsCurrent].CmdBuffer, &CmdBuffer, sizeof(CmdBuffer)); // copy 12 bytes, four times - memcpy(&_Channels.Data[_ChannelsCurrent].IdxBuffer, &IdxBuffer, sizeof(IdxBuffer)); - _ChannelsCurrent = idx; - memcpy(&CmdBuffer, &_Channels.Data[_ChannelsCurrent].CmdBuffer, sizeof(CmdBuffer)); - memcpy(&IdxBuffer, &_Channels.Data[_ChannelsCurrent].IdxBuffer, sizeof(IdxBuffer)); - _IdxWritePtr = IdxBuffer.Data + IdxBuffer.Size; -} - -// NB: this can be called with negative count for removing primitives (as long as the result does not underflow) -void ImDrawList::PrimReserve(int idx_count, int vtx_count) -{ - ImDrawCmd& draw_cmd = CmdBuffer.Data[CmdBuffer.Size-1]; - draw_cmd.ElemCount += idx_count; - - int vtx_buffer_old_size = VtxBuffer.Size; - VtxBuffer.resize(vtx_buffer_old_size + vtx_count); - _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size; - - int idx_buffer_old_size = IdxBuffer.Size; - IdxBuffer.resize(idx_buffer_old_size + idx_count); - _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size; -} - -// Fully unrolled with inline call to keep our debug builds decently fast. -void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) -{ - ImVec2 b(c.x, a.y), d(a.x, c.y), uv(_Data->TexUvWhitePixel); - ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); - _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); - _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; - _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; - _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; - _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; - _VtxWritePtr += 4; - _VtxCurrentIdx += 4; - _IdxWritePtr += 6; -} - -void ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a, const ImVec2& uv_c, ImU32 col) -{ - ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y); - ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); - _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); - _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; - _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; - _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; - _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; - _VtxWritePtr += 4; - _VtxCurrentIdx += 4; - _IdxWritePtr += 6; -} - -void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) -{ - ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2); - _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3); - _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col; - _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col; - _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col; - _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col; - _VtxWritePtr += 4; - _VtxCurrentIdx += 4; - _IdxWritePtr += 6; -} - -// TODO: Thickness anti-aliased lines cap are missing their AA fringe. -void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, bool closed, float thickness) -{ - if (points_count < 2) - return; - - const ImVec2 uv = _Data->TexUvWhitePixel; - - int count = points_count; - if (!closed) - count = points_count-1; - - const bool thick_line = thickness > 1.0f; - if (Flags & ImDrawListFlags_AntiAliasedLines) - { - // Anti-aliased stroke - const float AA_SIZE = 1.0f; - const ImU32 col_trans = col & ~IM_COL32_A_MASK; - - const int idx_count = thick_line ? count*18 : count*12; - const int vtx_count = thick_line ? points_count*4 : points_count*3; - PrimReserve(idx_count, vtx_count); - - // Temporary buffer - ImVec2* temp_normals = (ImVec2*)alloca(points_count * (thick_line ? 5 : 3) * sizeof(ImVec2)); - ImVec2* temp_points = temp_normals + points_count; - - for (int i1 = 0; i1 < count; i1++) - { - const int i2 = (i1+1) == points_count ? 0 : i1+1; - ImVec2 diff = points[i2] - points[i1]; - diff *= ImInvLength(diff, 1.0f); - temp_normals[i1].x = diff.y; - temp_normals[i1].y = -diff.x; - } - if (!closed) - temp_normals[points_count-1] = temp_normals[points_count-2]; - - if (!thick_line) - { - if (!closed) - { - temp_points[0] = points[0] + temp_normals[0] * AA_SIZE; - temp_points[1] = points[0] - temp_normals[0] * AA_SIZE; - temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * AA_SIZE; - temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * AA_SIZE; - } - - // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. - unsigned int idx1 = _VtxCurrentIdx; - for (int i1 = 0; i1 < count; i1++) - { - const int i2 = (i1+1) == points_count ? 0 : i1+1; - unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+3; - - // Average normals - ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; - float dmr2 = dm.x*dm.x + dm.y*dm.y; - if (dmr2 > 0.000001f) - { - float scale = 1.0f / dmr2; - if (scale > 100.0f) scale = 100.0f; - dm *= scale; - } - dm *= AA_SIZE; - temp_points[i2*2+0] = points[i2] + dm; - temp_points[i2*2+1] = points[i2] - dm; - - // Add indexes - _IdxWritePtr[0] = (ImDrawIdx)(idx2+0); _IdxWritePtr[1] = (ImDrawIdx)(idx1+0); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); - _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+0); - _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); - _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10]= (ImDrawIdx)(idx2+0); _IdxWritePtr[11]= (ImDrawIdx)(idx2+1); - _IdxWritePtr += 12; - - idx1 = idx2; - } - - // Add vertexes - for (int i = 0; i < points_count; i++) - { - _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; - _VtxWritePtr[1].pos = temp_points[i*2+0]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; - _VtxWritePtr[2].pos = temp_points[i*2+1]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col_trans; - _VtxWritePtr += 3; - } - } - else - { - const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; - if (!closed) - { - temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE); - temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness); - temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness); - temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE); - temp_points[(points_count-1)*4+0] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); - temp_points[(points_count-1)*4+1] = points[points_count-1] + temp_normals[points_count-1] * (half_inner_thickness); - temp_points[(points_count-1)*4+2] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness); - temp_points[(points_count-1)*4+3] = points[points_count-1] - temp_normals[points_count-1] * (half_inner_thickness + AA_SIZE); - } - - // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. - unsigned int idx1 = _VtxCurrentIdx; - for (int i1 = 0; i1 < count; i1++) - { - const int i2 = (i1+1) == points_count ? 0 : i1+1; - unsigned int idx2 = (i1+1) == points_count ? _VtxCurrentIdx : idx1+4; - - // Average normals - ImVec2 dm = (temp_normals[i1] + temp_normals[i2]) * 0.5f; - float dmr2 = dm.x*dm.x + dm.y*dm.y; - if (dmr2 > 0.000001f) - { - float scale = 1.0f / dmr2; - if (scale > 100.0f) scale = 100.0f; - dm *= scale; - } - ImVec2 dm_out = dm * (half_inner_thickness + AA_SIZE); - ImVec2 dm_in = dm * half_inner_thickness; - temp_points[i2*4+0] = points[i2] + dm_out; - temp_points[i2*4+1] = points[i2] + dm_in; - temp_points[i2*4+2] = points[i2] - dm_in; - temp_points[i2*4+3] = points[i2] - dm_out; - - // Add indexes - _IdxWritePtr[0] = (ImDrawIdx)(idx2+1); _IdxWritePtr[1] = (ImDrawIdx)(idx1+1); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); - _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+1); - _IdxWritePtr[6] = (ImDrawIdx)(idx2+1); _IdxWritePtr[7] = (ImDrawIdx)(idx1+1); _IdxWritePtr[8] = (ImDrawIdx)(idx1+0); - _IdxWritePtr[9] = (ImDrawIdx)(idx1+0); _IdxWritePtr[10] = (ImDrawIdx)(idx2+0); _IdxWritePtr[11] = (ImDrawIdx)(idx2+1); - _IdxWritePtr[12] = (ImDrawIdx)(idx2+2); _IdxWritePtr[13] = (ImDrawIdx)(idx1+2); _IdxWritePtr[14] = (ImDrawIdx)(idx1+3); - _IdxWritePtr[15] = (ImDrawIdx)(idx1+3); _IdxWritePtr[16] = (ImDrawIdx)(idx2+3); _IdxWritePtr[17] = (ImDrawIdx)(idx2+2); - _IdxWritePtr += 18; - - idx1 = idx2; - } - - // Add vertexes - for (int i = 0; i < points_count; i++) - { - _VtxWritePtr[0].pos = temp_points[i*4+0]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col_trans; - _VtxWritePtr[1].pos = temp_points[i*4+1]; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; - _VtxWritePtr[2].pos = temp_points[i*4+2]; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; - _VtxWritePtr[3].pos = temp_points[i*4+3]; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col_trans; - _VtxWritePtr += 4; - } - } - _VtxCurrentIdx += (ImDrawIdx)vtx_count; - } - else - { - // Non Anti-aliased Stroke - const int idx_count = count*6; - const int vtx_count = count*4; // FIXME-OPT: Not sharing edges - PrimReserve(idx_count, vtx_count); - - for (int i1 = 0; i1 < count; i1++) - { - const int i2 = (i1+1) == points_count ? 0 : i1+1; - const ImVec2& p1 = points[i1]; - const ImVec2& p2 = points[i2]; - ImVec2 diff = p2 - p1; - diff *= ImInvLength(diff, 1.0f); - - const float dx = diff.x * (thickness * 0.5f); - const float dy = diff.y * (thickness * 0.5f); - _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; - _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col; - _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col; - _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col; - _VtxWritePtr += 4; - - _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+2); - _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx+2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx+3); - _IdxWritePtr += 6; - _VtxCurrentIdx += 4; - } - } -} - -void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) -{ - const ImVec2 uv = _Data->TexUvWhitePixel; - - if (Flags & ImDrawListFlags_AntiAliasedFill) - { - // Anti-aliased Fill - const float AA_SIZE = 1.0f; - const ImU32 col_trans = col & ~IM_COL32_A_MASK; - const int idx_count = (points_count-2)*3 + points_count*6; - const int vtx_count = (points_count*2); - PrimReserve(idx_count, vtx_count); - - // Add indexes for fill - unsigned int vtx_inner_idx = _VtxCurrentIdx; - unsigned int vtx_outer_idx = _VtxCurrentIdx+1; - for (int i = 2; i < points_count; i++) - { - _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+((i-1)<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx+(i<<1)); - _IdxWritePtr += 3; - } - - // Compute normals - ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); - for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) - { - const ImVec2& p0 = points[i0]; - const ImVec2& p1 = points[i1]; - ImVec2 diff = p1 - p0; - diff *= ImInvLength(diff, 1.0f); - temp_normals[i0].x = diff.y; - temp_normals[i0].y = -diff.x; - } - - for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) - { - // Average normals - const ImVec2& n0 = temp_normals[i0]; - const ImVec2& n1 = temp_normals[i1]; - ImVec2 dm = (n0 + n1) * 0.5f; - float dmr2 = dm.x*dm.x + dm.y*dm.y; - if (dmr2 > 0.000001f) - { - float scale = 1.0f / dmr2; - if (scale > 100.0f) scale = 100.0f; - dm *= scale; - } - dm *= AA_SIZE * 0.5f; - - // Add vertices - _VtxWritePtr[0].pos = (points[i1] - dm); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner - _VtxWritePtr[1].pos = (points[i1] + dm); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer - _VtxWritePtr += 2; - - // Add indexes for fringes - _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+(i0<<1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); - _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx+(i1<<1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); - _IdxWritePtr += 6; - } - _VtxCurrentIdx += (ImDrawIdx)vtx_count; - } - else - { - // Non Anti-aliased Fill - const int idx_count = (points_count-2)*3; - const int vtx_count = points_count; - PrimReserve(idx_count, vtx_count); - for (int i = 0; i < vtx_count; i++) - { - _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; - _VtxWritePtr++; - } - for (int i = 2; i < points_count; i++) - { - _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx+i-1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx+i); - _IdxWritePtr += 3; - } - _VtxCurrentIdx += (ImDrawIdx)vtx_count; - } -} - -void ImDrawList::PathArcToFast(const ImVec2& centre, float radius, int a_min_of_12, int a_max_of_12) -{ - if (radius == 0.0f || a_min_of_12 > a_max_of_12) - { - _Path.push_back(centre); - return; - } - _Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1)); - for (int a = a_min_of_12; a <= a_max_of_12; a++) - { - const ImVec2& c = _Data->CircleVtx12[a % IM_ARRAYSIZE(_Data->CircleVtx12)]; - _Path.push_back(ImVec2(centre.x + c.x * radius, centre.y + c.y * radius)); - } -} - -void ImDrawList::PathArcTo(const ImVec2& centre, float radius, float a_min, float a_max, int num_segments) -{ - if (radius == 0.0f) - { - _Path.push_back(centre); - return; - } - _Path.reserve(_Path.Size + (num_segments + 1)); - for (int i = 0; i <= num_segments; i++) - { - const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); - _Path.push_back(ImVec2(centre.x + ImCos(a) * radius, centre.y + ImSin(a) * radius)); - } -} - -static void PathBezierToCasteljau(ImVector* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level) -{ - float dx = x4 - x1; - float dy = y4 - y1; - float d2 = ((x2 - x4) * dy - (y2 - y4) * dx); - float d3 = ((x3 - x4) * dy - (y3 - y4) * dx); - d2 = (d2 >= 0) ? d2 : -d2; - d3 = (d3 >= 0) ? d3 : -d3; - if ((d2+d3) * (d2+d3) < tess_tol * (dx*dx + dy*dy)) - { - path->push_back(ImVec2(x4, y4)); - } - else if (level < 10) - { - float x12 = (x1+x2)*0.5f, y12 = (y1+y2)*0.5f; - float x23 = (x2+x3)*0.5f, y23 = (y2+y3)*0.5f; - float x34 = (x3+x4)*0.5f, y34 = (y3+y4)*0.5f; - float x123 = (x12+x23)*0.5f, y123 = (y12+y23)*0.5f; - float x234 = (x23+x34)*0.5f, y234 = (y23+y34)*0.5f; - float x1234 = (x123+x234)*0.5f, y1234 = (y123+y234)*0.5f; - - PathBezierToCasteljau(path, x1,y1, x12,y12, x123,y123, x1234,y1234, tess_tol, level+1); - PathBezierToCasteljau(path, x1234,y1234, x234,y234, x34,y34, x4,y4, tess_tol, level+1); - } -} - -void ImDrawList::PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments) -{ - ImVec2 p1 = _Path.back(); - if (num_segments == 0) - { - // Auto-tessellated - PathBezierToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); - } - else - { - float t_step = 1.0f / (float)num_segments; - for (int i_step = 1; i_step <= num_segments; i_step++) - { - float t = t_step * i_step; - float u = 1.0f - t; - float w1 = u*u*u; - float w2 = 3*u*u*t; - float w3 = 3*u*t*t; - float w4 = t*t*t; - _Path.push_back(ImVec2(w1*p1.x + w2*p2.x + w3*p3.x + w4*p4.x, w1*p1.y + w2*p2.y + w3*p3.y + w4*p4.y)); - } - } -} - -void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, int rounding_corners) -{ - rounding = ImMin(rounding, ImFabs(b.x - a.x) * ( ((rounding_corners & ImDrawCornerFlags_Top) == ImDrawCornerFlags_Top) || ((rounding_corners & ImDrawCornerFlags_Bot) == ImDrawCornerFlags_Bot) ? 0.5f : 1.0f ) - 1.0f); - rounding = ImMin(rounding, ImFabs(b.y - a.y) * ( ((rounding_corners & ImDrawCornerFlags_Left) == ImDrawCornerFlags_Left) || ((rounding_corners & ImDrawCornerFlags_Right) == ImDrawCornerFlags_Right) ? 0.5f : 1.0f ) - 1.0f); - - if (rounding <= 0.0f || rounding_corners == 0) - { - PathLineTo(a); - PathLineTo(ImVec2(b.x, a.y)); - PathLineTo(b); - PathLineTo(ImVec2(a.x, b.y)); - } - else - { - const float rounding_tl = (rounding_corners & ImDrawCornerFlags_TopLeft) ? rounding : 0.0f; - const float rounding_tr = (rounding_corners & ImDrawCornerFlags_TopRight) ? rounding : 0.0f; - const float rounding_br = (rounding_corners & ImDrawCornerFlags_BotRight) ? rounding : 0.0f; - const float rounding_bl = (rounding_corners & ImDrawCornerFlags_BotLeft) ? rounding : 0.0f; - PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); - PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); - PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); - PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6); - } -} - -void ImDrawList::AddLine(const ImVec2& a, const ImVec2& b, ImU32 col, float thickness) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - PathLineTo(a + ImVec2(0.5f,0.5f)); - PathLineTo(b + ImVec2(0.5f,0.5f)); - PathStroke(col, false, thickness); -} - -// a: upper-left, b: lower-right. we don't render 1 px sized rectangles properly. -void ImDrawList::AddRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags, float thickness) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - if (Flags & ImDrawListFlags_AntiAliasedLines) - PathRect(a + ImVec2(0.5f,0.5f), b - ImVec2(0.50f,0.50f), rounding, rounding_corners_flags); - else - PathRect(a + ImVec2(0.5f,0.5f), b - ImVec2(0.49f,0.49f), rounding, rounding_corners_flags); // Better looking lower-right corner and rounded non-AA shapes. - PathStroke(col, true, thickness); -} - -void ImDrawList::AddRectFilled(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, int rounding_corners_flags) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - if (rounding > 0.0f) - { - PathRect(a, b, rounding, rounding_corners_flags); - PathFillConvex(col); - } - else - { - PrimReserve(6, 4); - PrimRect(a, b, col); - } -} - -void ImDrawList::AddRectFilledMultiColor(const ImVec2& a, const ImVec2& c, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) -{ - if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) - return; - - const ImVec2 uv = _Data->TexUvWhitePixel; - PrimReserve(6, 4); - PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); - PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+2)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx+3)); - PrimWriteVtx(a, uv, col_upr_left); - PrimWriteVtx(ImVec2(c.x, a.y), uv, col_upr_right); - PrimWriteVtx(c, uv, col_bot_right); - PrimWriteVtx(ImVec2(a.x, c.y), uv, col_bot_left); -} - -void ImDrawList::AddQuad(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col, float thickness) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - PathLineTo(a); - PathLineTo(b); - PathLineTo(c); - PathLineTo(d); - PathStroke(col, true, thickness); -} - -void ImDrawList::AddQuadFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, ImU32 col) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - PathLineTo(a); - PathLineTo(b); - PathLineTo(c); - PathLineTo(d); - PathFillConvex(col); -} - -void ImDrawList::AddTriangle(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col, float thickness) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - PathLineTo(a); - PathLineTo(b); - PathLineTo(c); - PathStroke(col, true, thickness); -} - -void ImDrawList::AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - PathLineTo(a); - PathLineTo(b); - PathLineTo(c); - PathFillConvex(col); -} - -void ImDrawList::AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments, float thickness) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; - PathArcTo(centre, radius-0.5f, 0.0f, a_max, num_segments); - PathStroke(col, true, thickness); -} - -void ImDrawList::AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - const float a_max = IM_PI*2.0f * ((float)num_segments - 1.0f) / (float)num_segments; - PathArcTo(centre, radius, 0.0f, a_max, num_segments); - PathFillConvex(col); -} - -void ImDrawList::AddBezierCurve(const ImVec2& pos0, const ImVec2& cp0, const ImVec2& cp1, const ImVec2& pos1, ImU32 col, float thickness, int num_segments) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - PathLineTo(pos0); - PathBezierCurveTo(cp0, cp1, pos1, num_segments); - PathStroke(col, false, thickness); -} - -void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - if (text_end == NULL) - text_end = text_begin + strlen(text_begin); - if (text_begin == text_end) - return; - - // Pull default font/size from the shared ImDrawListSharedData instance - if (font == NULL) - font = _Data->Font; - if (font_size == 0.0f) - font_size = _Data->FontSize; - - IM_ASSERT(font->ContainerAtlas->TexID == _TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. - - ImVec4 clip_rect = _ClipRectStack.back(); - if (cpu_fine_clip_rect) - { - clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); - clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); - clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); - clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); - } - font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); -} - -void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) -{ - AddText(NULL, 0.0f, pos, col, text_begin, text_end); -} - -void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); - if (push_texture_id) - PushTextureID(user_texture_id); - - PrimReserve(6, 4); - PrimRectUV(a, b, uv_a, uv_b, col); - - if (push_texture_id) - PopTextureID(); -} - -void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); - if (push_texture_id) - PushTextureID(user_texture_id); - - PrimReserve(6, 4); - PrimQuadUV(a, b, c, d, uv_a, uv_b, uv_c, uv_d, col); - - if (push_texture_id) - PopTextureID(); -} - -void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col, float rounding, int rounding_corners) -{ - if ((col & IM_COL32_A_MASK) == 0) - return; - - if (rounding <= 0.0f || (rounding_corners & ImDrawCornerFlags_All) == 0) - { - AddImage(user_texture_id, a, b, uv_a, uv_b, col); - return; - } - - const bool push_texture_id = _TextureIdStack.empty() || user_texture_id != _TextureIdStack.back(); - if (push_texture_id) - PushTextureID(user_texture_id); - - int vert_start_idx = VtxBuffer.Size; - PathRect(a, b, rounding, rounding_corners); - PathFillConvex(col); - int vert_end_idx = VtxBuffer.Size; - ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, a, b, uv_a, uv_b, true); - - if (push_texture_id) - PopTextureID(); -} - -//----------------------------------------------------------------------------- -// [SECTION] ImDrawData -//----------------------------------------------------------------------------- - -// For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! -void ImDrawData::DeIndexAllBuffers() -{ - ImVector new_vtx_buffer; - TotalVtxCount = TotalIdxCount = 0; - for (int i = 0; i < CmdListsCount; i++) - { - ImDrawList* cmd_list = CmdLists[i]; - if (cmd_list->IdxBuffer.empty()) - continue; - new_vtx_buffer.resize(cmd_list->IdxBuffer.Size); - for (int j = 0; j < cmd_list->IdxBuffer.Size; j++) - new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]]; - cmd_list->VtxBuffer.swap(new_vtx_buffer); - cmd_list->IdxBuffer.resize(0); - TotalVtxCount += cmd_list->VtxBuffer.Size; - } -} - -// Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. -void ImDrawData::ScaleClipRects(const ImVec2& scale) -{ - for (int i = 0; i < CmdListsCount; i++) - { - ImDrawList* cmd_list = CmdLists[i]; - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - ImDrawCmd* cmd = &cmd_list->CmdBuffer[cmd_i]; - cmd->ClipRect = ImVec4(cmd->ClipRect.x * scale.x, cmd->ClipRect.y * scale.y, cmd->ClipRect.z * scale.x, cmd->ClipRect.w * scale.y); - } - } -} - -//----------------------------------------------------------------------------- -// [SECTION] Helpers ShadeVertsXXX functions -//----------------------------------------------------------------------------- - -// Generic linear color gradient, write to RGB fields, leave A untouched. -void ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1) -{ - ImVec2 gradient_extent = gradient_p1 - gradient_p0; - float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent); - ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx; - ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx; - for (ImDrawVert* vert = vert_start; vert < vert_end; vert++) - { - float d = ImDot(vert->pos - gradient_p0, gradient_extent); - float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f); - int r = ImLerp((int)(col0 >> IM_COL32_R_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_R_SHIFT) & 0xFF, t); - int g = ImLerp((int)(col0 >> IM_COL32_G_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_G_SHIFT) & 0xFF, t); - int b = ImLerp((int)(col0 >> IM_COL32_B_SHIFT) & 0xFF, (int)(col1 >> IM_COL32_B_SHIFT) & 0xFF, t); - vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK); - } -} - -// Distribute UV over (a, b) rectangle -void ImGui::ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp) -{ - const ImVec2 size = b - a; - const ImVec2 uv_size = uv_b - uv_a; - const ImVec2 scale = ImVec2( - size.x != 0.0f ? (uv_size.x / size.x) : 0.0f, - size.y != 0.0f ? (uv_size.y / size.y) : 0.0f); - - ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx; - ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx; - if (clamp) - { - const ImVec2 min = ImMin(uv_a, uv_b); - const ImVec2 max = ImMax(uv_a, uv_b); - for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) - vertex->uv = ImClamp(uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale), min, max); - } - else - { - for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex) - vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale); - } -} - -//----------------------------------------------------------------------------- -// [SECTION] ImFontConfig -//----------------------------------------------------------------------------- - -ImFontConfig::ImFontConfig() -{ - FontData = NULL; - FontDataSize = 0; - FontDataOwnedByAtlas = true; - FontNo = 0; - SizePixels = 0.0f; - OversampleH = 3; - OversampleV = 1; - PixelSnapH = false; - GlyphExtraSpacing = ImVec2(0.0f, 0.0f); - GlyphOffset = ImVec2(0.0f, 0.0f); - GlyphRanges = NULL; - GlyphMinAdvanceX = 0.0f; - GlyphMaxAdvanceX = FLT_MAX; - MergeMode = false; - RasterizerFlags = 0x00; - RasterizerMultiply = 1.0f; - memset(Name, 0, sizeof(Name)); - DstFont = NULL; -} - -//----------------------------------------------------------------------------- -// [SECTION] ImFontAtlas -//----------------------------------------------------------------------------- - -// A work of art lies ahead! (. = white layer, X = black layer, others are blank) -// The white texels on the top left are the ones we'll use everywhere in ImGui to render filled shapes. -const int FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF = 108; -const int FONT_ATLAS_DEFAULT_TEX_DATA_H = 27; -const unsigned int FONT_ATLAS_DEFAULT_TEX_DATA_ID = 0x80000000; -static const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] = -{ - "..- -XXXXXXX- X - X -XXXXXXX - XXXXXXX- XX " - "..- -X.....X- X.X - X.X -X.....X - X.....X- X..X " - "--- -XXX.XXX- X...X - X...X -X....X - X....X- X..X " - "X - X.X - X.....X - X.....X -X...X - X...X- X..X " - "XX - X.X -X.......X- X.......X -X..X.X - X.X..X- X..X " - "X.X - X.X -XXXX.XXXX- XXXX.XXXX -X.X X.X - X.X X.X- X..XXX " - "X..X - X.X - X.X - X.X -XX X.X - X.X XX- X..X..XXX " - "X...X - X.X - X.X - XX X.X XX - X.X - X.X - X..X..X..XX " - "X....X - X.X - X.X - X.X X.X X.X - X.X - X.X - X..X..X..X.X " - "X.....X - X.X - X.X - X..X X.X X..X - X.X - X.X -XXX X..X..X..X..X" - "X......X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X XX-XX X.X -X..XX........X..X" - "X.......X - X.X - X.X -X.....................X- X.X X.X-X.X X.X -X...X...........X" - "X........X - X.X - X.X - X...XXXXXX.XXXXXX...X - X.X..X-X..X.X - X..............X" - "X.........X -XXX.XXX- X.X - X..X X.X X..X - X...X-X...X - X.............X" - "X..........X-X.....X- X.X - X.X X.X X.X - X....X-X....X - X.............X" - "X......XXXXX-XXXXXXX- X.X - XX X.X XX - X.....X-X.....X - X............X" - "X...X..X --------- X.X - X.X - XXXXXXX-XXXXXXX - X...........X " - "X..X X..X - -XXXX.XXXX- XXXX.XXXX ------------------------------------- X..........X " - "X.X X..X - -X.......X- X.......X - XX XX - - X..........X " - "XX X..X - - X.....X - X.....X - X.X X.X - - X........X " - " X..X - X...X - X...X - X..X X..X - - X........X " - " XX - X.X - X.X - X...XXXXXXXXXXXXX...X - - XXXXXXXXXX " - "------------ - X - X -X.....................X- ------------------" - " ----------------------------------- X...XXXXXXXXXXXXX...X - " - " - X..X X..X - " - " - X.X X.X - " - " - XX XX - " -}; - -static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3] = -{ - // Pos ........ Size ......... Offset ...... - { ImVec2( 0,3), ImVec2(12,19), ImVec2( 0, 0) }, // ImGuiMouseCursor_Arrow - { ImVec2(13,0), ImVec2( 7,16), ImVec2( 1, 8) }, // ImGuiMouseCursor_TextInput - { ImVec2(31,0), ImVec2(23,23), ImVec2(11,11) }, // ImGuiMouseCursor_ResizeAll - { ImVec2(21,0), ImVec2( 9,23), ImVec2( 4,11) }, // ImGuiMouseCursor_ResizeNS - { ImVec2(55,18),ImVec2(23, 9), ImVec2(11, 4) }, // ImGuiMouseCursor_ResizeEW - { ImVec2(73,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNESW - { ImVec2(55,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNWSE - { ImVec2(91,0), ImVec2(17,22), ImVec2( 5, 0) }, // ImGuiMouseCursor_Hand -}; - -ImFontAtlas::ImFontAtlas() -{ - Locked = false; - Flags = ImFontAtlasFlags_None; - TexID = NULL; - TexDesiredWidth = 0; - TexGlyphPadding = 1; - - TexPixelsAlpha8 = NULL; - TexPixelsRGBA32 = NULL; - TexWidth = TexHeight = 0; - TexUvScale = ImVec2(0.0f, 0.0f); - TexUvWhitePixel = ImVec2(0.0f, 0.0f); - for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) - CustomRectIds[n] = -1; -} - -ImFontAtlas::~ImFontAtlas() -{ - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - Clear(); -} - -void ImFontAtlas::ClearInputData() -{ - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - for (int i = 0; i < ConfigData.Size; i++) - if (ConfigData[i].FontData && ConfigData[i].FontDataOwnedByAtlas) - { - ImGui::MemFree(ConfigData[i].FontData); - ConfigData[i].FontData = NULL; - } - - // When clearing this we lose access to the font name and other information used to build the font. - for (int i = 0; i < Fonts.Size; i++) - if (Fonts[i]->ConfigData >= ConfigData.Data && Fonts[i]->ConfigData < ConfigData.Data + ConfigData.Size) - { - Fonts[i]->ConfigData = NULL; - Fonts[i]->ConfigDataCount = 0; - } - ConfigData.clear(); - CustomRects.clear(); - for (int n = 0; n < IM_ARRAYSIZE(CustomRectIds); n++) - CustomRectIds[n] = -1; -} - -void ImFontAtlas::ClearTexData() -{ - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - if (TexPixelsAlpha8) - ImGui::MemFree(TexPixelsAlpha8); - if (TexPixelsRGBA32) - ImGui::MemFree(TexPixelsRGBA32); - TexPixelsAlpha8 = NULL; - TexPixelsRGBA32 = NULL; -} - -void ImFontAtlas::ClearFonts() -{ - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - for (int i = 0; i < Fonts.Size; i++) - IM_DELETE(Fonts[i]); - Fonts.clear(); -} - -void ImFontAtlas::Clear() -{ - ClearInputData(); - ClearTexData(); - ClearFonts(); -} - -void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) -{ - // Build atlas on demand - if (TexPixelsAlpha8 == NULL) - { - if (ConfigData.empty()) - AddFontDefault(); - Build(); - } - - *out_pixels = TexPixelsAlpha8; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; -} - -void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) -{ - // Convert to RGBA32 format on demand - // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp - if (!TexPixelsRGBA32) - { - unsigned char* pixels = NULL; - GetTexDataAsAlpha8(&pixels, NULL, NULL); - if (pixels) - { - TexPixelsRGBA32 = (unsigned int*)ImGui::MemAlloc((size_t)(TexWidth * TexHeight * 4)); - const unsigned char* src = pixels; - unsigned int* dst = TexPixelsRGBA32; - for (int n = TexWidth * TexHeight; n > 0; n--) - *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); - } - } - - *out_pixels = (unsigned char*)TexPixelsRGBA32; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; -} - -ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) -{ - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); - IM_ASSERT(font_cfg->SizePixels > 0.0f); - - // Create new font - if (!font_cfg->MergeMode) - Fonts.push_back(IM_NEW(ImFont)); - else - IM_ASSERT(!Fonts.empty()); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. - - ConfigData.push_back(*font_cfg); - ImFontConfig& new_font_cfg = ConfigData.back(); - if (!new_font_cfg.DstFont) - new_font_cfg.DstFont = Fonts.back(); - if (!new_font_cfg.FontDataOwnedByAtlas) - { - new_font_cfg.FontData = ImGui::MemAlloc(new_font_cfg.FontDataSize); - new_font_cfg.FontDataOwnedByAtlas = true; - memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); - } - - // Invalidate texture - ClearTexData(); - return new_font_cfg.DstFont; -} - -// Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) -static unsigned int stb_decompress_length(const unsigned char *input); -static unsigned int stb_decompress(unsigned char *output, const unsigned char *input, unsigned int length); -static const char* GetDefaultCompressedFontDataTTFBase85(); -static unsigned int Decode85Byte(char c) { return c >= '\\' ? c-36 : c-35; } -static void Decode85(const unsigned char* src, unsigned char* dst) -{ - while (*src) - { - unsigned int tmp = Decode85Byte(src[0]) + 85*(Decode85Byte(src[1]) + 85*(Decode85Byte(src[2]) + 85*(Decode85Byte(src[3]) + 85*Decode85Byte(src[4])))); - dst[0] = ((tmp >> 0) & 0xFF); dst[1] = ((tmp >> 8) & 0xFF); dst[2] = ((tmp >> 16) & 0xFF); dst[3] = ((tmp >> 24) & 0xFF); // We can't assume little-endianness. - src += 5; - dst += 4; - } -} - -// Load embedded ProggyClean.ttf at size 13, disable oversampling -ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) -{ - ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); - if (!font_cfg_template) - { - font_cfg.OversampleH = font_cfg.OversampleV = 1; - font_cfg.PixelSnapH = true; - } - if (font_cfg.Name[0] == '\0') strcpy(font_cfg.Name, "ProggyClean.ttf, 13px"); - if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f; - - const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); - const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault(); - ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges); - font->DisplayOffset.y = 1.0f; - return font; -} - -ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) -{ - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - size_t data_size = 0; - void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); - if (!data) - { - IM_ASSERT(0); // Could not load file. - return NULL; - } - ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); - if (font_cfg.Name[0] == '\0') - { - // Store a short copy of filename into into the font name for convenience - const char* p; - for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); - } - return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges); -} - -// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). -ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* ttf_data, int ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) -{ - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); - IM_ASSERT(font_cfg.FontData == NULL); - font_cfg.FontData = ttf_data; - font_cfg.FontDataSize = ttf_size; - font_cfg.SizePixels = size_pixels; - if (glyph_ranges) - font_cfg.GlyphRanges = glyph_ranges; - return AddFont(&font_cfg); -} - -ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) -{ - const unsigned int buf_decompressed_size = stb_decompress_length((const unsigned char*)compressed_ttf_data); - unsigned char* buf_decompressed_data = (unsigned char *)ImGui::MemAlloc(buf_decompressed_size); - stb_decompress(buf_decompressed_data, (const unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size); - - ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); - IM_ASSERT(font_cfg.FontData == NULL); - font_cfg.FontDataOwnedByAtlas = true; - return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, &font_cfg, glyph_ranges); -} - -ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) -{ - int compressed_ttf_size = (((int)strlen(compressed_ttf_data_base85) + 4) / 5) * 4; - void* compressed_ttf = ImGui::MemAlloc((size_t)compressed_ttf_size); - Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf); - ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges); - ImGui::MemFree(compressed_ttf); - return font; -} - -int ImFontAtlas::AddCustomRectRegular(unsigned int id, int width, int height) -{ - IM_ASSERT(id >= 0x10000); - IM_ASSERT(width > 0 && width <= 0xFFFF); - IM_ASSERT(height > 0 && height <= 0xFFFF); - CustomRect r; - r.ID = id; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - CustomRects.push_back(r); - return CustomRects.Size - 1; // Return index -} - -int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) -{ - IM_ASSERT(font != NULL); - IM_ASSERT(width > 0 && width <= 0xFFFF); - IM_ASSERT(height > 0 && height <= 0xFFFF); - CustomRect r; - r.ID = id; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - r.GlyphAdvanceX = advance_x; - r.GlyphOffset = offset; - r.Font = font; - CustomRects.push_back(r); - return CustomRects.Size - 1; // Return index -} - -void ImFontAtlas::CalcCustomRectUV(const CustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) -{ - IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates - IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed - *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); - *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); -} - -bool ImFontAtlas::GetMouseCursorTexData(ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) -{ - if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_COUNT) - return false; - if (Flags & ImFontAtlasFlags_NoMouseCursors) - return false; - - IM_ASSERT(CustomRectIds[0] != -1); - ImFontAtlas::CustomRect& r = CustomRects[CustomRectIds[0]]; - IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); - ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r.X, (float)r.Y); - ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; - *out_size = size; - *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; - out_uv_border[0] = (pos) * TexUvScale; - out_uv_border[1] = (pos + size) * TexUvScale; - pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; - out_uv_fill[0] = (pos) * TexUvScale; - out_uv_fill[1] = (pos + size) * TexUvScale; - return true; -} - -bool ImFontAtlas::Build() -{ - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - return ImFontAtlasBuildWithStbTruetype(this); -} - -void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) -{ - for (unsigned int i = 0; i < 256; i++) - { - unsigned int value = (unsigned int)(i * in_brighten_factor); - out_table[i] = value > 255 ? 255 : (value & 0xFF); - } -} - -void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) -{ - unsigned char* data = pixels + x + y * stride; - for (int j = h; j > 0; j--, data += stride) - for (int i = 0; i < w; i++) - data[i] = table[data[i]]; -} - -bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) -{ - IM_ASSERT(atlas->ConfigData.Size > 0); - - ImFontAtlasBuildRegisterDefaultCustomRects(atlas); - - atlas->TexID = NULL; - atlas->TexWidth = atlas->TexHeight = 0; - atlas->TexUvScale = ImVec2(0.0f, 0.0f); - atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); - atlas->ClearTexData(); - - // Count glyphs/ranges - int total_glyphs_count = 0; - int total_ranges_count = 0; - for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) - { - ImFontConfig& cfg = atlas->ConfigData[input_i]; - if (!cfg.GlyphRanges) - cfg.GlyphRanges = atlas->GetGlyphRangesDefault(); - for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, total_ranges_count++) - total_glyphs_count += (in_range[1] - in_range[0]) + 1; - } - - // We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish. - // Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. - atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512; - atlas->TexHeight = 0; - - // Start packing - const int max_tex_height = 1024*32; - stbtt_pack_context spc = {}; - if (!stbtt_PackBegin(&spc, NULL, atlas->TexWidth, max_tex_height, 0, atlas->TexGlyphPadding, NULL)) - return false; - stbtt_PackSetOversampling(&spc, 1, 1); - - // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). - ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); - - // Initialize font information (so we can error without any cleanup) - struct ImFontTempBuildData - { - stbtt_fontinfo FontInfo; - stbrp_rect* Rects; - int RectsCount; - stbtt_pack_range* Ranges; - int RangesCount; - }; - ImFontTempBuildData* tmp_array = (ImFontTempBuildData*)ImGui::MemAlloc((size_t)atlas->ConfigData.Size * sizeof(ImFontTempBuildData)); - for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) - { - ImFontConfig& cfg = atlas->ConfigData[input_i]; - ImFontTempBuildData& tmp = tmp_array[input_i]; - IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); - - const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo); - IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); - if (!stbtt_InitFont(&tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset)) - { - atlas->TexWidth = atlas->TexHeight = 0; // Reset output on failure - ImGui::MemFree(tmp_array); - return false; - } - } - - // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) - int buf_packedchars_n = 0, buf_rects_n = 0, buf_ranges_n = 0; - stbtt_packedchar* buf_packedchars = (stbtt_packedchar*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbtt_packedchar)); - stbrp_rect* buf_rects = (stbrp_rect*)ImGui::MemAlloc(total_glyphs_count * sizeof(stbrp_rect)); - stbtt_pack_range* buf_ranges = (stbtt_pack_range*)ImGui::MemAlloc(total_ranges_count * sizeof(stbtt_pack_range)); - memset(buf_packedchars, 0, total_glyphs_count * sizeof(stbtt_packedchar)); - memset(buf_rects, 0, total_glyphs_count * sizeof(stbrp_rect)); // Unnecessary but let's clear this for the sake of sanity. - memset(buf_ranges, 0, total_ranges_count * sizeof(stbtt_pack_range)); - - // First font pass: pack all glyphs (no rendering at this point, we are working with rectangles in an infinitely tall texture at this point) - for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) - { - ImFontConfig& cfg = atlas->ConfigData[input_i]; - ImFontTempBuildData& tmp = tmp_array[input_i]; - - // Setup ranges - int font_glyphs_count = 0; - int font_ranges_count = 0; - for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2, font_ranges_count++) - font_glyphs_count += (in_range[1] - in_range[0]) + 1; - tmp.Ranges = buf_ranges + buf_ranges_n; - tmp.RangesCount = font_ranges_count; - buf_ranges_n += font_ranges_count; - for (int i = 0; i < font_ranges_count; i++) - { - const ImWchar* in_range = &cfg.GlyphRanges[i * 2]; - stbtt_pack_range& range = tmp.Ranges[i]; - range.font_size = cfg.SizePixels; - range.first_unicode_codepoint_in_range = in_range[0]; - range.num_chars = (in_range[1] - in_range[0]) + 1; - range.chardata_for_range = buf_packedchars + buf_packedchars_n; - buf_packedchars_n += range.num_chars; - } - - // Gather the sizes of all rectangle we need - tmp.Rects = buf_rects + buf_rects_n; - tmp.RectsCount = font_glyphs_count; - buf_rects_n += font_glyphs_count; - stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); - int n = stbtt_PackFontRangesGatherRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); - IM_ASSERT(n == font_glyphs_count); - - // Detect missing glyphs and replace them with a zero-sized box instead of relying on the default glyphs - // This allows us merging overlapping icon fonts more easily. - int rect_i = 0; - for (int range_i = 0; range_i < tmp.RangesCount; range_i++) - for (int char_i = 0; char_i < tmp.Ranges[range_i].num_chars; char_i++, rect_i++) - if (stbtt_FindGlyphIndex(&tmp.FontInfo, tmp.Ranges[range_i].first_unicode_codepoint_in_range + char_i) == 0) - tmp.Rects[rect_i].w = tmp.Rects[rect_i].h = 0; - - // Pack - stbrp_pack_rects((stbrp_context*)spc.pack_info, tmp.Rects, n); - - // Extend texture height - // Also mark missing glyphs as non-packed so we don't attempt to render into them - for (int i = 0; i < n; i++) - { - if (tmp.Rects[i].w == 0 && tmp.Rects[i].h == 0) - tmp.Rects[i].was_packed = 0; - if (tmp.Rects[i].was_packed) - atlas->TexHeight = ImMax(atlas->TexHeight, tmp.Rects[i].y + tmp.Rects[i].h); - } - } - IM_ASSERT(buf_rects_n == total_glyphs_count); - IM_ASSERT(buf_packedchars_n == total_glyphs_count); - IM_ASSERT(buf_ranges_n == total_ranges_count); - - // Create texture - atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); - atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); - atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight); - memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); - spc.pixels = atlas->TexPixelsAlpha8; - spc.height = atlas->TexHeight; - - // Second pass: render font characters - for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) - { - ImFontConfig& cfg = atlas->ConfigData[input_i]; - ImFontTempBuildData& tmp = tmp_array[input_i]; - stbtt_PackSetOversampling(&spc, cfg.OversampleH, cfg.OversampleV); - stbtt_PackFontRangesRenderIntoRects(&spc, &tmp.FontInfo, tmp.Ranges, tmp.RangesCount, tmp.Rects); - if (cfg.RasterizerMultiply != 1.0f) - { - unsigned char multiply_table[256]; - ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); - for (const stbrp_rect* r = tmp.Rects; r != tmp.Rects + tmp.RectsCount; r++) - if (r->was_packed) - ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, spc.pixels, r->x, r->y, r->w, r->h, spc.stride_in_bytes); - } - tmp.Rects = NULL; - } - - // End packing - stbtt_PackEnd(&spc); - ImGui::MemFree(buf_rects); - buf_rects = NULL; - - // Third pass: setup ImFont and glyphs for runtime - for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) - { - ImFontConfig& cfg = atlas->ConfigData[input_i]; - ImFontTempBuildData& tmp = tmp_array[input_i]; - ImFont* dst_font = cfg.DstFont; // We can have multiple input fonts writing into a same destination font (when using MergeMode=true) - if (cfg.MergeMode) - dst_font->BuildLookupTable(); - - const float font_scale = stbtt_ScaleForPixelHeight(&tmp.FontInfo, cfg.SizePixels); - int unscaled_ascent, unscaled_descent, unscaled_line_gap; - stbtt_GetFontVMetrics(&tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); - - const float ascent = ImFloor(unscaled_ascent * font_scale + ((unscaled_ascent > 0.0f) ? +1 : -1)); - const float descent = ImFloor(unscaled_descent * font_scale + ((unscaled_descent > 0.0f) ? +1 : -1)); - ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); - const float font_off_x = cfg.GlyphOffset.x; - const float font_off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); - - for (int i = 0; i < tmp.RangesCount; i++) - { - stbtt_pack_range& range = tmp.Ranges[i]; - for (int char_idx = 0; char_idx < range.num_chars; char_idx += 1) - { - const stbtt_packedchar& pc = range.chardata_for_range[char_idx]; - if (!pc.x0 && !pc.x1 && !pc.y0 && !pc.y1) - continue; - - const int codepoint = range.first_unicode_codepoint_in_range + char_idx; - if (cfg.MergeMode && dst_font->FindGlyphNoFallback((unsigned short)codepoint)) - continue; - - float char_advance_x_org = pc.xadvance; - float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX); - float char_off_x = font_off_x; - if (char_advance_x_org != char_advance_x_mod) - char_off_x += cfg.PixelSnapH ? (float)(int)((char_advance_x_mod - char_advance_x_org) * 0.5f) : (char_advance_x_mod - char_advance_x_org) * 0.5f; - - stbtt_aligned_quad q; - float dummy_x = 0.0f, dummy_y = 0.0f; - stbtt_GetPackedQuad(range.chardata_for_range, atlas->TexWidth, atlas->TexHeight, char_idx, &dummy_x, &dummy_y, &q, 0); - dst_font->AddGlyph((ImWchar)codepoint, q.x0 + char_off_x, q.y0 + font_off_y, q.x1 + char_off_x, q.y1 + font_off_y, q.s0, q.t0, q.s1, q.t1, char_advance_x_mod); - } - } - } - - // Cleanup temporaries - ImGui::MemFree(buf_packedchars); - ImGui::MemFree(buf_ranges); - ImGui::MemFree(tmp_array); - - ImFontAtlasBuildFinish(atlas); - - return true; -} - -void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas) -{ - if (atlas->CustomRectIds[0] >= 0) - return; - if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) - atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF*2+1, FONT_ATLAS_DEFAULT_TEX_DATA_H); - else - atlas->CustomRectIds[0] = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_ID, 2, 2); -} - -void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) -{ - if (!font_config->MergeMode) - { - font->ClearOutputData(); - font->FontSize = font_config->SizePixels; - font->ConfigData = font_config; - font->ContainerAtlas = atlas; - font->Ascent = ascent; - font->Descent = descent; - } - font->ConfigDataCount++; -} - -void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* pack_context_opaque) -{ - stbrp_context* pack_context = (stbrp_context*)pack_context_opaque; - - ImVector& user_rects = atlas->CustomRects; - IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. - - ImVector pack_rects; - pack_rects.resize(user_rects.Size); - memset(pack_rects.Data, 0, sizeof(stbrp_rect) * user_rects.Size); - for (int i = 0; i < user_rects.Size; i++) - { - pack_rects[i].w = user_rects[i].Width; - pack_rects[i].h = user_rects[i].Height; - } - stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); - for (int i = 0; i < pack_rects.Size; i++) - if (pack_rects[i].was_packed) - { - user_rects[i].X = pack_rects[i].x; - user_rects[i].Y = pack_rects[i].y; - IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); - atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); - } -} - -static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) -{ - IM_ASSERT(atlas->CustomRectIds[0] >= 0); - IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); - ImFontAtlas::CustomRect& r = atlas->CustomRects[atlas->CustomRectIds[0]]; - IM_ASSERT(r.ID == FONT_ATLAS_DEFAULT_TEX_DATA_ID); - IM_ASSERT(r.IsPacked()); - - const int w = atlas->TexWidth; - if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) - { - // Render/copy pixels - IM_ASSERT(r.Width == FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF * 2 + 1 && r.Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); - for (int y = 0, n = 0; y < FONT_ATLAS_DEFAULT_TEX_DATA_H; y++) - for (int x = 0; x < FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF; x++, n++) - { - const int offset0 = (int)(r.X + x) + (int)(r.Y + y) * w; - const int offset1 = offset0 + FONT_ATLAS_DEFAULT_TEX_DATA_W_HALF + 1; - atlas->TexPixelsAlpha8[offset0] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == '.' ? 0xFF : 0x00; - atlas->TexPixelsAlpha8[offset1] = FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[n] == 'X' ? 0xFF : 0x00; - } - } - else - { - IM_ASSERT(r.Width == 2 && r.Height == 2); - const int offset = (int)(r.X) + (int)(r.Y) * w; - atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; - } - atlas->TexUvWhitePixel = ImVec2((r.X + 0.5f) * atlas->TexUvScale.x, (r.Y + 0.5f) * atlas->TexUvScale.y); -} - -void ImFontAtlasBuildFinish(ImFontAtlas* atlas) -{ - // Render into our custom data block - ImFontAtlasBuildRenderDefaultTexData(atlas); - - // Register custom rectangle glyphs - for (int i = 0; i < atlas->CustomRects.Size; i++) - { - const ImFontAtlas::CustomRect& r = atlas->CustomRects[i]; - if (r.Font == NULL || r.ID > 0x10000) - continue; - - IM_ASSERT(r.Font->ContainerAtlas == atlas); - ImVec2 uv0, uv1; - atlas->CalcCustomRectUV(&r, &uv0, &uv1); - r.Font->AddGlyph((ImWchar)r.ID, r.GlyphOffset.x, r.GlyphOffset.y, r.GlyphOffset.x + r.Width, r.GlyphOffset.y + r.Height, uv0.x, uv0.y, uv1.x, uv1.y, r.GlyphAdvanceX); - } - - // Build all fonts lookup tables - for (int i = 0; i < atlas->Fonts.Size; i++) - if (atlas->Fonts[i]->DirtyLookupTables) - atlas->Fonts[i]->BuildLookupTable(); -} - -// Retrieve list of range (2 int per range, values are inclusive) -const ImWchar* ImFontAtlas::GetGlyphRangesDefault() -{ - static const ImWchar ranges[] = - { - 0x0020, 0x00FF, // Basic Latin + Latin Supplement - 0, - }; - return &ranges[0]; -} - -const ImWchar* ImFontAtlas::GetGlyphRangesKorean() -{ - static const ImWchar ranges[] = - { - 0x0020, 0x00FF, // Basic Latin + Latin Supplement - 0x3131, 0x3163, // Korean alphabets - 0xAC00, 0xD79D, // Korean characters - 0, - }; - return &ranges[0]; -} - -const ImWchar* ImFontAtlas::GetGlyphRangesChineseFull() -{ - static const ImWchar ranges[] = - { - 0x0020, 0x00FF, // Basic Latin + Latin Supplement - 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana - 0x31F0, 0x31FF, // Katakana Phonetic Extensions - 0xFF00, 0xFFEF, // Half-width characters - 0x4e00, 0x9FAF, // CJK Ideograms - 0, - }; - return &ranges[0]; -} - -static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* accumulative_offsets, int accumulative_offsets_count, ImWchar* out_ranges) -{ - for (int n = 0; n < accumulative_offsets_count; n++, out_ranges += 2) - { - out_ranges[0] = out_ranges[1] = (ImWchar)(base_codepoint + accumulative_offsets[n]); - base_codepoint += accumulative_offsets[n]; - } - out_ranges[0] = 0; -} - -//------------------------------------------------------------------------- -// [SECTION] ImFontAtlas glyph ranges helpers + GlyphRangesBuilder -//------------------------------------------------------------------------- - -const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() -{ - // Store 2500 regularly used characters for Simplified Chinese. - // Sourced from https://zh.wiktionary.org/wiki/%E9%99%84%E5%BD%95:%E7%8E%B0%E4%BB%A3%E6%B1%89%E8%AF%AD%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8 - // This table covers 97.97% of all characters used during the month in July, 1987. - // You can use ImFontAtlas::GlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters. - // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.) - static const short accumulative_offsets_from_0x4E00[] = - { - 0,1,2,4,1,1,1,1,2,1,3,2,1,2,2,1,1,1,1,1,5,2,1,2,3,3,3,2,2,4,1,1,1,2,1,5,2,3,1,2,1,2,1,1,2,1,1,2,2,1,4,1,1,1,1,5,10,1,2,19,2,1,2,1,2,1,2,1,2, - 1,5,1,6,3,2,1,2,2,1,1,1,4,8,5,1,1,4,1,1,3,1,2,1,5,1,2,1,1,1,10,1,1,5,2,4,6,1,4,2,2,2,12,2,1,1,6,1,1,1,4,1,1,4,6,5,1,4,2,2,4,10,7,1,1,4,2,4, - 2,1,4,3,6,10,12,5,7,2,14,2,9,1,1,6,7,10,4,7,13,1,5,4,8,4,1,1,2,28,5,6,1,1,5,2,5,20,2,2,9,8,11,2,9,17,1,8,6,8,27,4,6,9,20,11,27,6,68,2,2,1,1, - 1,2,1,2,2,7,6,11,3,3,1,1,3,1,2,1,1,1,1,1,3,1,1,8,3,4,1,5,7,2,1,4,4,8,4,2,1,2,1,1,4,5,6,3,6,2,12,3,1,3,9,2,4,3,4,1,5,3,3,1,3,7,1,5,1,1,1,1,2, - 3,4,5,2,3,2,6,1,1,2,1,7,1,7,3,4,5,15,2,2,1,5,3,22,19,2,1,1,1,1,2,5,1,1,1,6,1,1,12,8,2,9,18,22,4,1,1,5,1,16,1,2,7,10,15,1,1,6,2,4,1,2,4,1,6, - 1,1,3,2,4,1,6,4,5,1,2,1,1,2,1,10,3,1,3,2,1,9,3,2,5,7,2,19,4,3,6,1,1,1,1,1,4,3,2,1,1,1,2,5,3,1,1,1,2,2,1,1,2,1,1,2,1,3,1,1,1,3,7,1,4,1,1,2,1, - 1,2,1,2,4,4,3,8,1,1,1,2,1,3,5,1,3,1,3,4,6,2,2,14,4,6,6,11,9,1,15,3,1,28,5,2,5,5,3,1,3,4,5,4,6,14,3,2,3,5,21,2,7,20,10,1,2,19,2,4,28,28,2,3, - 2,1,14,4,1,26,28,42,12,40,3,52,79,5,14,17,3,2,2,11,3,4,6,3,1,8,2,23,4,5,8,10,4,2,7,3,5,1,1,6,3,1,2,2,2,5,28,1,1,7,7,20,5,3,29,3,17,26,1,8,4, - 27,3,6,11,23,5,3,4,6,13,24,16,6,5,10,25,35,7,3,2,3,3,14,3,6,2,6,1,4,2,3,8,2,1,1,3,3,3,4,1,1,13,2,2,4,5,2,1,14,14,1,2,2,1,4,5,2,3,1,14,3,12, - 3,17,2,16,5,1,2,1,8,9,3,19,4,2,2,4,17,25,21,20,28,75,1,10,29,103,4,1,2,1,1,4,2,4,1,2,3,24,2,2,2,1,1,2,1,3,8,1,1,1,2,1,1,3,1,1,1,6,1,5,3,1,1, - 1,3,4,1,1,5,2,1,5,6,13,9,16,1,1,1,1,3,2,3,2,4,5,2,5,2,2,3,7,13,7,2,2,1,1,1,1,2,3,3,2,1,6,4,9,2,1,14,2,14,2,1,18,3,4,14,4,11,41,15,23,15,23, - 176,1,3,4,1,1,1,1,5,3,1,2,3,7,3,1,1,2,1,2,4,4,6,2,4,1,9,7,1,10,5,8,16,29,1,1,2,2,3,1,3,5,2,4,5,4,1,1,2,2,3,3,7,1,6,10,1,17,1,44,4,6,2,1,1,6, - 5,4,2,10,1,6,9,2,8,1,24,1,2,13,7,8,8,2,1,4,1,3,1,3,3,5,2,5,10,9,4,9,12,2,1,6,1,10,1,1,7,7,4,10,8,3,1,13,4,3,1,6,1,3,5,2,1,2,17,16,5,2,16,6, - 1,4,2,1,3,3,6,8,5,11,11,1,3,3,2,4,6,10,9,5,7,4,7,4,7,1,1,4,2,1,3,6,8,7,1,6,11,5,5,3,24,9,4,2,7,13,5,1,8,82,16,61,1,1,1,4,2,2,16,10,3,8,1,1, - 6,4,2,1,3,1,1,1,4,3,8,4,2,2,1,1,1,1,1,6,3,5,1,1,4,6,9,2,1,1,1,2,1,7,2,1,6,1,5,4,4,3,1,8,1,3,3,1,3,2,2,2,2,3,1,6,1,2,1,2,1,3,7,1,8,2,1,2,1,5, - 2,5,3,5,10,1,2,1,1,3,2,5,11,3,9,3,5,1,1,5,9,1,2,1,5,7,9,9,8,1,3,3,3,6,8,2,3,2,1,1,32,6,1,2,15,9,3,7,13,1,3,10,13,2,14,1,13,10,2,1,3,10,4,15, - 2,15,15,10,1,3,9,6,9,32,25,26,47,7,3,2,3,1,6,3,4,3,2,8,5,4,1,9,4,2,2,19,10,6,2,3,8,1,2,2,4,2,1,9,4,4,4,6,4,8,9,2,3,1,1,1,1,3,5,5,1,3,8,4,6, - 2,1,4,12,1,5,3,7,13,2,5,8,1,6,1,2,5,14,6,1,5,2,4,8,15,5,1,23,6,62,2,10,1,1,8,1,2,2,10,4,2,2,9,2,1,1,3,2,3,1,5,3,3,2,1,3,8,1,1,1,11,3,1,1,4, - 3,7,1,14,1,2,3,12,5,2,5,1,6,7,5,7,14,11,1,3,1,8,9,12,2,1,11,8,4,4,2,6,10,9,13,1,1,3,1,5,1,3,2,4,4,1,18,2,3,14,11,4,29,4,2,7,1,3,13,9,2,2,5, - 3,5,20,7,16,8,5,72,34,6,4,22,12,12,28,45,36,9,7,39,9,191,1,1,1,4,11,8,4,9,2,3,22,1,1,1,1,4,17,1,7,7,1,11,31,10,2,4,8,2,3,2,1,4,2,16,4,32,2, - 3,19,13,4,9,1,5,2,14,8,1,1,3,6,19,6,5,1,16,6,2,10,8,5,1,2,3,1,5,5,1,11,6,6,1,3,3,2,6,3,8,1,1,4,10,7,5,7,7,5,8,9,2,1,3,4,1,1,3,1,3,3,2,6,16, - 1,4,6,3,1,10,6,1,3,15,2,9,2,10,25,13,9,16,6,2,2,10,11,4,3,9,1,2,6,6,5,4,30,40,1,10,7,12,14,33,6,3,6,7,3,1,3,1,11,14,4,9,5,12,11,49,18,51,31, - 140,31,2,2,1,5,1,8,1,10,1,4,4,3,24,1,10,1,3,6,6,16,3,4,5,2,1,4,2,57,10,6,22,2,22,3,7,22,6,10,11,36,18,16,33,36,2,5,5,1,1,1,4,10,1,4,13,2,7, - 5,2,9,3,4,1,7,43,3,7,3,9,14,7,9,1,11,1,1,3,7,4,18,13,1,14,1,3,6,10,73,2,2,30,6,1,11,18,19,13,22,3,46,42,37,89,7,3,16,34,2,2,3,9,1,7,1,1,1,2, - 2,4,10,7,3,10,3,9,5,28,9,2,6,13,7,3,1,3,10,2,7,2,11,3,6,21,54,85,2,1,4,2,2,1,39,3,21,2,2,5,1,1,1,4,1,1,3,4,15,1,3,2,4,4,2,3,8,2,20,1,8,7,13, - 4,1,26,6,2,9,34,4,21,52,10,4,4,1,5,12,2,11,1,7,2,30,12,44,2,30,1,1,3,6,16,9,17,39,82,2,2,24,7,1,7,3,16,9,14,44,2,1,2,1,2,3,5,2,4,1,6,7,5,3, - 2,6,1,11,5,11,2,1,18,19,8,1,3,24,29,2,1,3,5,2,2,1,13,6,5,1,46,11,3,5,1,1,5,8,2,10,6,12,6,3,7,11,2,4,16,13,2,5,1,1,2,2,5,2,28,5,2,23,10,8,4, - 4,22,39,95,38,8,14,9,5,1,13,5,4,3,13,12,11,1,9,1,27,37,2,5,4,4,63,211,95,2,2,2,1,3,5,2,1,1,2,2,1,1,1,3,2,4,1,2,1,1,5,2,2,1,1,2,3,1,3,1,1,1, - 3,1,4,2,1,3,6,1,1,3,7,15,5,3,2,5,3,9,11,4,2,22,1,6,3,8,7,1,4,28,4,16,3,3,25,4,4,27,27,1,4,1,2,2,7,1,3,5,2,28,8,2,14,1,8,6,16,25,3,3,3,14,3, - 3,1,1,2,1,4,6,3,8,4,1,1,1,2,3,6,10,6,2,3,18,3,2,5,5,4,3,1,5,2,5,4,23,7,6,12,6,4,17,11,9,5,1,1,10,5,12,1,1,11,26,33,7,3,6,1,17,7,1,5,12,1,11, - 2,4,1,8,14,17,23,1,2,1,7,8,16,11,9,6,5,2,6,4,16,2,8,14,1,11,8,9,1,1,1,9,25,4,11,19,7,2,15,2,12,8,52,7,5,19,2,16,4,36,8,1,16,8,24,26,4,6,2,9, - 5,4,36,3,28,12,25,15,37,27,17,12,59,38,5,32,127,1,2,9,17,14,4,1,2,1,1,8,11,50,4,14,2,19,16,4,17,5,4,5,26,12,45,2,23,45,104,30,12,8,3,10,2,2, - 3,3,1,4,20,7,2,9,6,15,2,20,1,3,16,4,11,15,6,134,2,5,59,1,2,2,2,1,9,17,3,26,137,10,211,59,1,2,4,1,4,1,1,1,2,6,2,3,1,1,2,3,2,3,1,3,4,4,2,3,3, - 1,4,3,1,7,2,2,3,1,2,1,3,3,3,2,2,3,2,1,3,14,6,1,3,2,9,6,15,27,9,34,145,1,1,2,1,1,1,1,2,1,1,1,1,2,2,2,3,1,2,1,1,1,2,3,5,8,3,5,2,4,1,3,2,2,2,12, - 4,1,1,1,10,4,5,1,20,4,16,1,15,9,5,12,2,9,2,5,4,2,26,19,7,1,26,4,30,12,15,42,1,6,8,172,1,1,4,2,1,1,11,2,2,4,2,1,2,1,10,8,1,2,1,4,5,1,2,5,1,8, - 4,1,3,4,2,1,6,2,1,3,4,1,2,1,1,1,1,12,5,7,2,4,3,1,1,1,3,3,6,1,2,2,3,3,3,2,1,2,12,14,11,6,6,4,12,2,8,1,7,10,1,35,7,4,13,15,4,3,23,21,28,52,5, - 26,5,6,1,7,10,2,7,53,3,2,1,1,1,2,163,532,1,10,11,1,3,3,4,8,2,8,6,2,2,23,22,4,2,2,4,2,1,3,1,3,3,5,9,8,2,1,2,8,1,10,2,12,21,20,15,105,2,3,1,1, - 3,2,3,1,1,2,5,1,4,15,11,19,1,1,1,1,5,4,5,1,1,2,5,3,5,12,1,2,5,1,11,1,1,15,9,1,4,5,3,26,8,2,1,3,1,1,15,19,2,12,1,2,5,2,7,2,19,2,20,6,26,7,5, - 2,2,7,34,21,13,70,2,128,1,1,2,1,1,2,1,1,3,2,2,2,15,1,4,1,3,4,42,10,6,1,49,85,8,1,2,1,1,4,4,2,3,6,1,5,7,4,3,211,4,1,2,1,2,5,1,2,4,2,2,6,5,6, - 10,3,4,48,100,6,2,16,296,5,27,387,2,2,3,7,16,8,5,38,15,39,21,9,10,3,7,59,13,27,21,47,5,21,6 - }; - static ImWchar base_ranges[] = // not zero-terminated - { - 0x0020, 0x00FF, // Basic Latin + Latin Supplement - 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana - 0x31F0, 0x31FF, // Katakana Phonetic Extensions - 0xFF00, 0xFFEF, // Half-width characters - }; - static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00) * 2 + 1] = { 0 }; - if (!full_ranges[0]) - { - memcpy(full_ranges, base_ranges, sizeof(base_ranges)); - UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges)); - } - return &full_ranges[0]; -} - -const ImWchar* ImFontAtlas::GetGlyphRangesJapanese() -{ - // 1946 common ideograms code points for Japanese - // Sourced from http://theinstructionlimit.com/common-kanji-character-ranges-for-xna-spritefont-rendering - // FIXME: Source a list of the revised 2136 Joyo Kanji list from 2010 and rebuild this. - // You can use ImFontAtlas::GlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters. - // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.) - static const short accumulative_offsets_from_0x4E00[] = - { - 0,1,2,4,1,1,1,1,2,1,6,2,2,1,8,5,7,11,1,2,10,10,8,2,4,20,2,11,8,2,1,2,1,6,2,1,7,5,3,7,1,1,13,7,9,1,4,6,1,2,1,10,1,1,9,2,2,4,5,6,14,1,1,9,3,18, - 5,4,2,2,10,7,1,1,1,3,2,4,3,23,2,10,12,2,14,2,4,13,1,6,10,3,1,7,13,6,4,13,5,2,3,17,2,2,5,7,6,4,1,7,14,16,6,13,9,15,1,1,7,16,4,7,1,19,9,2,7,15, - 2,6,5,13,25,4,14,13,11,25,1,1,1,2,1,2,2,3,10,11,3,3,1,1,4,4,2,1,4,9,1,4,3,5,5,2,7,12,11,15,7,16,4,5,16,2,1,1,6,3,3,1,1,2,7,6,6,7,1,4,7,6,1,1, - 2,1,12,3,3,9,5,8,1,11,1,2,3,18,20,4,1,3,6,1,7,3,5,5,7,2,2,12,3,1,4,2,3,2,3,11,8,7,4,17,1,9,25,1,1,4,2,2,4,1,2,7,1,1,1,3,1,2,6,16,1,2,1,1,3,12, - 20,2,5,20,8,7,6,2,1,1,1,1,6,2,1,2,10,1,1,6,1,3,1,2,1,4,1,12,4,1,3,1,1,1,1,1,10,4,7,5,13,1,15,1,1,30,11,9,1,15,38,14,1,32,17,20,1,9,31,2,21,9, - 4,49,22,2,1,13,1,11,45,35,43,55,12,19,83,1,3,2,3,13,2,1,7,3,18,3,13,8,1,8,18,5,3,7,25,24,9,24,40,3,17,24,2,1,6,2,3,16,15,6,7,3,12,1,9,7,3,3, - 3,15,21,5,16,4,5,12,11,11,3,6,3,2,31,3,2,1,1,23,6,6,1,4,2,6,5,2,1,1,3,3,22,2,6,2,3,17,3,2,4,5,1,9,5,1,1,6,15,12,3,17,2,14,2,8,1,23,16,4,2,23, - 8,15,23,20,12,25,19,47,11,21,65,46,4,3,1,5,6,1,2,5,26,2,1,1,3,11,1,1,1,2,1,2,3,1,1,10,2,3,1,1,1,3,6,3,2,2,6,6,9,2,2,2,6,2,5,10,2,4,1,2,1,2,2, - 3,1,1,3,1,2,9,23,9,2,1,1,1,1,5,3,2,1,10,9,6,1,10,2,31,25,3,7,5,40,1,15,6,17,7,27,180,1,3,2,2,1,1,1,6,3,10,7,1,3,6,17,8,6,2,2,1,3,5,5,8,16,14, - 15,1,1,4,1,2,1,1,1,3,2,7,5,6,2,5,10,1,4,2,9,1,1,11,6,1,44,1,3,7,9,5,1,3,1,1,10,7,1,10,4,2,7,21,15,7,2,5,1,8,3,4,1,3,1,6,1,4,2,1,4,10,8,1,4,5, - 1,5,10,2,7,1,10,1,1,3,4,11,10,29,4,7,3,5,2,3,33,5,2,19,3,1,4,2,6,31,11,1,3,3,3,1,8,10,9,12,11,12,8,3,14,8,6,11,1,4,41,3,1,2,7,13,1,5,6,2,6,12, - 12,22,5,9,4,8,9,9,34,6,24,1,1,20,9,9,3,4,1,7,2,2,2,6,2,28,5,3,6,1,4,6,7,4,2,1,4,2,13,6,4,4,3,1,8,8,3,2,1,5,1,2,2,3,1,11,11,7,3,6,10,8,6,16,16, - 22,7,12,6,21,5,4,6,6,3,6,1,3,2,1,2,8,29,1,10,1,6,13,6,6,19,31,1,13,4,4,22,17,26,33,10,4,15,12,25,6,67,10,2,3,1,6,10,2,6,2,9,1,9,4,4,1,2,16,2, - 5,9,2,3,8,1,8,3,9,4,8,6,4,8,11,3,2,1,1,3,26,1,7,5,1,11,1,5,3,5,2,13,6,39,5,1,5,2,11,6,10,5,1,15,5,3,6,19,21,22,2,4,1,6,1,8,1,4,8,2,4,2,2,9,2, - 1,1,1,4,3,6,3,12,7,1,14,2,4,10,2,13,1,17,7,3,2,1,3,2,13,7,14,12,3,1,29,2,8,9,15,14,9,14,1,3,1,6,5,9,11,3,38,43,20,7,7,8,5,15,12,19,15,81,8,7, - 1,5,73,13,37,28,8,8,1,15,18,20,165,28,1,6,11,8,4,14,7,15,1,3,3,6,4,1,7,14,1,1,11,30,1,5,1,4,14,1,4,2,7,52,2,6,29,3,1,9,1,21,3,5,1,26,3,11,14, - 11,1,17,5,1,2,1,3,2,8,1,2,9,12,1,1,2,3,8,3,24,12,7,7,5,17,3,3,3,1,23,10,4,4,6,3,1,16,17,22,3,10,21,16,16,6,4,10,2,1,1,2,8,8,6,5,3,3,3,39,25, - 15,1,1,16,6,7,25,15,6,6,12,1,22,13,1,4,9,5,12,2,9,1,12,28,8,3,5,10,22,60,1,2,40,4,61,63,4,1,13,12,1,4,31,12,1,14,89,5,16,6,29,14,2,5,49,18,18, - 5,29,33,47,1,17,1,19,12,2,9,7,39,12,3,7,12,39,3,1,46,4,12,3,8,9,5,31,15,18,3,2,2,66,19,13,17,5,3,46,124,13,57,34,2,5,4,5,8,1,1,1,4,3,1,17,5, - 3,5,3,1,8,5,6,3,27,3,26,7,12,7,2,17,3,7,18,78,16,4,36,1,2,1,6,2,1,39,17,7,4,13,4,4,4,1,10,4,2,4,6,3,10,1,19,1,26,2,4,33,2,73,47,7,3,8,2,4,15, - 18,1,29,2,41,14,1,21,16,41,7,39,25,13,44,2,2,10,1,13,7,1,7,3,5,20,4,8,2,49,1,10,6,1,6,7,10,7,11,16,3,12,20,4,10,3,1,2,11,2,28,9,2,4,7,2,15,1, - 27,1,28,17,4,5,10,7,3,24,10,11,6,26,3,2,7,2,2,49,16,10,16,15,4,5,27,61,30,14,38,22,2,7,5,1,3,12,23,24,17,17,3,3,2,4,1,6,2,7,5,1,1,5,1,1,9,4, - 1,3,6,1,8,2,8,4,14,3,5,11,4,1,3,32,1,19,4,1,13,11,5,2,1,8,6,8,1,6,5,13,3,23,11,5,3,16,3,9,10,1,24,3,198,52,4,2,2,5,14,5,4,22,5,20,4,11,6,41, - 1,5,2,2,11,5,2,28,35,8,22,3,18,3,10,7,5,3,4,1,5,3,8,9,3,6,2,16,22,4,5,5,3,3,18,23,2,6,23,5,27,8,1,33,2,12,43,16,5,2,3,6,1,20,4,2,9,7,1,11,2, - 10,3,14,31,9,3,25,18,20,2,5,5,26,14,1,11,17,12,40,19,9,6,31,83,2,7,9,19,78,12,14,21,76,12,113,79,34,4,1,1,61,18,85,10,2,2,13,31,11,50,6,33,159, - 179,6,6,7,4,4,2,4,2,5,8,7,20,32,22,1,3,10,6,7,28,5,10,9,2,77,19,13,2,5,1,4,4,7,4,13,3,9,31,17,3,26,2,6,6,5,4,1,7,11,3,4,2,1,6,2,20,4,1,9,2,6, - 3,7,1,1,1,20,2,3,1,6,2,3,6,2,4,8,1,5,13,8,4,11,23,1,10,6,2,1,3,21,2,2,4,24,31,4,10,10,2,5,192,15,4,16,7,9,51,1,2,1,1,5,1,1,2,1,3,5,3,1,3,4,1, - 3,1,3,3,9,8,1,2,2,2,4,4,18,12,92,2,10,4,3,14,5,25,16,42,4,14,4,2,21,5,126,30,31,2,1,5,13,3,22,5,6,6,20,12,1,14,12,87,3,19,1,8,2,9,9,3,3,23,2, - 3,7,6,3,1,2,3,9,1,3,1,6,3,2,1,3,11,3,1,6,10,3,2,3,1,2,1,5,1,1,11,3,6,4,1,7,2,1,2,5,5,34,4,14,18,4,19,7,5,8,2,6,79,1,5,2,14,8,2,9,2,1,36,28,16, - 4,1,1,1,2,12,6,42,39,16,23,7,15,15,3,2,12,7,21,64,6,9,28,8,12,3,3,41,59,24,51,55,57,294,9,9,2,6,2,15,1,2,13,38,90,9,9,9,3,11,7,1,1,1,5,6,3,2, - 1,2,2,3,8,1,4,4,1,5,7,1,4,3,20,4,9,1,1,1,5,5,17,1,5,2,6,2,4,1,4,5,7,3,18,11,11,32,7,5,4,7,11,127,8,4,3,3,1,10,1,1,6,21,14,1,16,1,7,1,3,6,9,65, - 51,4,3,13,3,10,1,1,12,9,21,110,3,19,24,1,1,10,62,4,1,29,42,78,28,20,18,82,6,3,15,6,84,58,253,15,155,264,15,21,9,14,7,58,40,39, - }; - static ImWchar base_ranges[] = // not zero-terminated - { - 0x0020, 0x00FF, // Basic Latin + Latin Supplement - 0x3000, 0x30FF, // Punctuations, Hiragana, Katakana - 0x31F0, 0x31FF, // Katakana Phonetic Extensions - 0xFF00, 0xFFEF, // Half-width characters - }; - static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00)*2 + 1] = { 0 }; - if (!full_ranges[0]) - { - memcpy(full_ranges, base_ranges, sizeof(base_ranges)); - UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges)); - } - return &full_ranges[0]; -} - -const ImWchar* ImFontAtlas::GetGlyphRangesCyrillic() -{ - static const ImWchar ranges[] = - { - 0x0020, 0x00FF, // Basic Latin + Latin Supplement - 0x0400, 0x052F, // Cyrillic + Cyrillic Supplement - 0x2DE0, 0x2DFF, // Cyrillic Extended-A - 0xA640, 0xA69F, // Cyrillic Extended-B - 0, - }; - return &ranges[0]; -} - -const ImWchar* ImFontAtlas::GetGlyphRangesThai() -{ - static const ImWchar ranges[] = - { - 0x0020, 0x00FF, // Basic Latin - 0x2010, 0x205E, // Punctuations - 0x0E00, 0x0E7F, // Thai - 0, - }; - return &ranges[0]; -} - -void ImFontAtlas::GlyphRangesBuilder::AddText(const char* text, const char* text_end) -{ - while (text_end ? (text < text_end) : *text) - { - unsigned int c = 0; - int c_len = ImTextCharFromUtf8(&c, text, text_end); - text += c_len; - if (c_len == 0) - break; - if (c < 0x10000) - AddChar((ImWchar)c); - } -} - -void ImFontAtlas::GlyphRangesBuilder::AddRanges(const ImWchar* ranges) -{ - for (; ranges[0]; ranges += 2) - for (ImWchar c = ranges[0]; c <= ranges[1]; c++) - AddChar(c); -} - -void ImFontAtlas::GlyphRangesBuilder::BuildRanges(ImVector* out_ranges) -{ - for (int n = 0; n < 0x10000; n++) - if (GetBit(n)) - { - out_ranges->push_back((ImWchar)n); - while (n < 0x10000 && GetBit(n + 1)) - n++; - out_ranges->push_back((ImWchar)n); - } - out_ranges->push_back(0); -} - -//----------------------------------------------------------------------------- -// [SECTION] ImFont -//----------------------------------------------------------------------------- - -ImFont::ImFont() -{ - Scale = 1.0f; - FallbackChar = (ImWchar)'?'; - DisplayOffset = ImVec2(0.0f, 0.0f); - ClearOutputData(); -} - -ImFont::~ImFont() -{ - // Invalidate active font so that the user gets a clear crash instead of a dangling pointer. - // If you want to delete fonts you need to do it between Render() and NewFrame(). - // FIXME-CLEANUP - /* - ImGuiContext& g = *GImGui; - if (g.Font == this) - g.Font = NULL; - */ - ClearOutputData(); -} - -void ImFont::ClearOutputData() -{ - FontSize = 0.0f; - Glyphs.clear(); - IndexAdvanceX.clear(); - IndexLookup.clear(); - FallbackGlyph = NULL; - FallbackAdvanceX = 0.0f; - ConfigDataCount = 0; - ConfigData = NULL; - ContainerAtlas = NULL; - Ascent = Descent = 0.0f; - DirtyLookupTables = true; - MetricsTotalSurface = 0; -} - -void ImFont::BuildLookupTable() -{ - int max_codepoint = 0; - for (int i = 0; i != Glyphs.Size; i++) - max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); - - IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved - IndexAdvanceX.clear(); - IndexLookup.clear(); - DirtyLookupTables = false; - GrowIndex(max_codepoint + 1); - for (int i = 0; i < Glyphs.Size; i++) - { - int codepoint = (int)Glyphs[i].Codepoint; - IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; - IndexLookup[codepoint] = (unsigned short)i; - } - - // Create a glyph to handle TAB - // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) - if (FindGlyph((unsigned short)' ')) - { - if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& tab_glyph = Glyphs.back(); - tab_glyph = *FindGlyph((unsigned short)' '); - tab_glyph.Codepoint = '\t'; - tab_glyph.AdvanceX *= 4; - IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; - IndexLookup[(int)tab_glyph.Codepoint] = (unsigned short)(Glyphs.Size-1); - } - - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - FallbackAdvanceX = FallbackGlyph ? FallbackGlyph->AdvanceX : 0.0f; - for (int i = 0; i < max_codepoint + 1; i++) - if (IndexAdvanceX[i] < 0.0f) - IndexAdvanceX[i] = FallbackAdvanceX; -} - -void ImFont::SetFallbackChar(ImWchar c) -{ - FallbackChar = c; - BuildLookupTable(); -} - -void ImFont::GrowIndex(int new_size) -{ - IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); - if (new_size <= IndexLookup.Size) - return; - IndexAdvanceX.resize(new_size, -1.0f); - IndexLookup.resize(new_size, (unsigned short)-1); -} - -// x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. -// Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). -void ImFont::AddGlyph(ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) -{ - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& glyph = Glyphs.back(); - glyph.Codepoint = (ImWchar)codepoint; - glyph.X0 = x0; - glyph.Y0 = y0; - glyph.X1 = x1; - glyph.Y1 = y1; - glyph.U0 = u0; - glyph.V0 = v0; - glyph.U1 = u1; - glyph.V1 = v1; - glyph.AdvanceX = advance_x + ConfigData->GlyphExtraSpacing.x; // Bake spacing into AdvanceX - - if (ConfigData->PixelSnapH) - glyph.AdvanceX = (float)(int)(glyph.AdvanceX + 0.5f); - - // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) - DirtyLookupTables = true; - MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + 1.99f) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + 1.99f); -} - -void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) -{ - IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. - int index_size = IndexLookup.Size; - - if (dst < index_size && IndexLookup.Data[dst] == (unsigned short)-1 && !overwrite_dst) // 'dst' already exists - return; - if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op - return; - - GrowIndex(dst + 1); - IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (unsigned short)-1; - IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; -} - -const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const -{ - if (c >= IndexLookup.Size) - return FallbackGlyph; - const unsigned short i = IndexLookup[c]; - if (i == (unsigned short)-1) - return FallbackGlyph; - return &Glyphs.Data[i]; -} - -const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const -{ - if (c >= IndexLookup.Size) - return NULL; - const unsigned short i = IndexLookup[c]; - if (i == (unsigned short)-1) - return NULL; - return &Glyphs.Data[i]; -} - -const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const -{ - // Simple word-wrapping for English, not full-featured. Please submit failing cases! - // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) - - // For references, possible wrap point marked with ^ - // "aaa bbb, ccc,ddd. eee fff. ggg!" - // ^ ^ ^ ^ ^__ ^ ^ - - // List of hardcoded separators: .,;!?'" - - // Skip extra blanks after a line returns (that includes not counting them in width computation) - // e.g. "Hello world" --> "Hello" "World" - - // Cut words that cannot possibly fit within one line. - // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" - - float line_width = 0.0f; - float word_width = 0.0f; - float blank_width = 0.0f; - wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters - - const char* word_end = text; - const char* prev_word_end = NULL; - bool inside_word = true; - - const char* s = text; - while (s < text_end) - { - unsigned int c = (unsigned int)*s; - const char* next_s; - if (c < 0x80) - next_s = s + 1; - else - next_s = s + ImTextCharFromUtf8(&c, s, text_end); - if (c == 0) - break; - - if (c < 32) - { - if (c == '\n') - { - line_width = word_width = blank_width = 0.0f; - inside_word = true; - s = next_s; - continue; - } - if (c == '\r') - { - s = next_s; - continue; - } - } - - const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX); - if (ImCharIsBlankW(c)) - { - if (inside_word) - { - line_width += blank_width; - blank_width = 0.0f; - word_end = s; - } - blank_width += char_width; - inside_word = false; - } - else - { - word_width += char_width; - if (inside_word) - { - word_end = next_s; - } - else - { - prev_word_end = word_end; - line_width += word_width + blank_width; - word_width = blank_width = 0.0f; - } - - // Allow wrapping after punctuation. - inside_word = !(c == '.' || c == ',' || c == ';' || c == '!' || c == '?' || c == '\"'); - } - - // We ignore blank width at the end of the line (they can be skipped) - if (line_width + word_width >= wrap_width) - { - // Words that cannot possibly fit within an entire line will be cut anywhere. - if (word_width < wrap_width) - s = prev_word_end ? prev_word_end : word_end; - break; - } - - s = next_s; - } - - return s; -} - -ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const -{ - if (!text_end) - text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. - - const float line_height = size; - const float scale = size / FontSize; - - ImVec2 text_size = ImVec2(0,0); - float line_width = 0.0f; - - const bool word_wrap_enabled = (wrap_width > 0.0f); - const char* word_wrap_eol = NULL; - - const char* s = text_begin; - while (s < text_end) - { - if (word_wrap_enabled) - { - // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. - if (!word_wrap_eol) - { - word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); - if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. - word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below - } - - if (s >= word_wrap_eol) - { - if (text_size.x < line_width) - text_size.x = line_width; - text_size.y += line_height; - line_width = 0.0f; - word_wrap_eol = NULL; - - // Wrapping skips upcoming blanks - while (s < text_end) - { - const char c = *s; - if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } - } - continue; - } - } - - // Decode and advance source - const char* prev_s = s; - unsigned int c = (unsigned int)*s; - if (c < 0x80) - { - s += 1; - } - else - { - s += ImTextCharFromUtf8(&c, s, text_end); - if (c == 0) // Malformed UTF-8? - break; - } - - if (c < 32) - { - if (c == '\n') - { - text_size.x = ImMax(text_size.x, line_width); - text_size.y += line_height; - line_width = 0.0f; - continue; - } - if (c == '\r') - continue; - } - - const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX[(int)c] : FallbackAdvanceX) * scale; - if (line_width + char_width >= max_width) - { - s = prev_s; - break; - } - - line_width += char_width; - } - - if (text_size.x < line_width) - text_size.x = line_width; - - if (line_width > 0 || text_size.y == 0.0f) - text_size.y += line_height; - - if (remaining) - *remaining = s; - - return text_size; -} - -void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const -{ - if (c == ' ' || c == '\t' || c == '\n' || c == '\r') // Match behavior of RenderText(), those 4 codepoints are hard-coded. - return; - if (const ImFontGlyph* glyph = FindGlyph(c)) - { - float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; - pos.x = (float)(int)pos.x + DisplayOffset.x; - pos.y = (float)(int)pos.y + DisplayOffset.y; - draw_list->PrimReserve(6, 4); - draw_list->PrimRectUV(ImVec2(pos.x + glyph->X0 * scale, pos.y + glyph->Y0 * scale), ImVec2(pos.x + glyph->X1 * scale, pos.y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); - } -} - -void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const -{ - if (!text_end) - text_end = text_begin + strlen(text_begin); // ImGui functions generally already provides a valid text_end, so this is merely to handle direct calls. - - // Align to be pixel perfect - pos.x = (float)(int)pos.x + DisplayOffset.x; - pos.y = (float)(int)pos.y + DisplayOffset.y; - float x = pos.x; - float y = pos.y; - if (y > clip_rect.w) - return; - - const float scale = size / FontSize; - const float line_height = FontSize * scale; - const bool word_wrap_enabled = (wrap_width > 0.0f); - const char* word_wrap_eol = NULL; - - // Fast-forward to first visible line - const char* s = text_begin; - if (y + line_height < clip_rect.y && !word_wrap_enabled) - while (y + line_height < clip_rect.y) - { - while (s < text_end) - if (*s++ == '\n') - break; - y += line_height; - } - - // For large text, scan for the last visible line in order to avoid over-reserving in the call to PrimReserve() - // Note that very large horizontal line will still be affected by the issue (e.g. a one megabyte string buffer without a newline will likely crash atm) - if (text_end - s > 10000 && !word_wrap_enabled) - { - const char* s_end = s; - float y_end = y; - while (y_end < clip_rect.w) - { - while (s_end < text_end) - if (*s_end++ == '\n') - break; - y_end += line_height; - } - text_end = s_end; - } - - // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) - const int vtx_count_max = (int)(text_end - s) * 4; - const int idx_count_max = (int)(text_end - s) * 6; - const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; - draw_list->PrimReserve(idx_count_max, vtx_count_max); - - ImDrawVert* vtx_write = draw_list->_VtxWritePtr; - ImDrawIdx* idx_write = draw_list->_IdxWritePtr; - unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; - - while (s < text_end) - { - if (word_wrap_enabled) - { - // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. - if (!word_wrap_eol) - { - word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - pos.x)); - if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. - word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below - } - - if (s >= word_wrap_eol) - { - x = pos.x; - y += line_height; - word_wrap_eol = NULL; - - // Wrapping skips upcoming blanks - while (s < text_end) - { - const char c = *s; - if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } - } - continue; - } - } - - // Decode and advance source - unsigned int c = (unsigned int)*s; - if (c < 0x80) - { - s += 1; - } - else - { - s += ImTextCharFromUtf8(&c, s, text_end); - if (c == 0) // Malformed UTF-8? - break; - } - - if (c < 32) - { - if (c == '\n') - { - x = pos.x; - y += line_height; - if (y > clip_rect.w) - break; // break out of main loop - continue; - } - if (c == '\r') - continue; - } - - float char_width = 0.0f; - if (const ImFontGlyph* glyph = FindGlyph((unsigned short)c)) - { - char_width = glyph->AdvanceX * scale; - - // Arbitrarily assume that both space and tabs are empty glyphs as an optimization - if (c != ' ' && c != '\t') - { - // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w - float x1 = x + glyph->X0 * scale; - float x2 = x + glyph->X1 * scale; - float y1 = y + glyph->Y0 * scale; - float y2 = y + glyph->Y1 * scale; - if (x1 <= clip_rect.z && x2 >= clip_rect.x) - { - // Render a character - float u1 = glyph->U0; - float v1 = glyph->V0; - float u2 = glyph->U1; - float v2 = glyph->V1; - - // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. - if (cpu_fine_clip) - { - if (x1 < clip_rect.x) - { - u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); - x1 = clip_rect.x; - } - if (y1 < clip_rect.y) - { - v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); - y1 = clip_rect.y; - } - if (x2 > clip_rect.z) - { - u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); - x2 = clip_rect.z; - } - if (y2 > clip_rect.w) - { - v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); - y2 = clip_rect.w; - } - if (y1 >= y2) - { - x += char_width; - continue; - } - } - - // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here: - { - idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); - idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); - vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1; - vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1; - vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2; - vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2; - vtx_write += 4; - vtx_current_idx += 4; - idx_write += 6; - } - } - } - } - - x += char_width; - } - - // Give back unused vertices - draw_list->VtxBuffer.resize((int)(vtx_write - draw_list->VtxBuffer.Data)); - draw_list->IdxBuffer.resize((int)(idx_write - draw_list->IdxBuffer.Data)); - draw_list->CmdBuffer[draw_list->CmdBuffer.Size-1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); - draw_list->_VtxWritePtr = vtx_write; - draw_list->_IdxWritePtr = idx_write; - draw_list->_VtxCurrentIdx = (unsigned int)draw_list->VtxBuffer.Size; -} - -//----------------------------------------------------------------------------- -// [SECTION] Internal Render Helpers -// (progressively moved from imgui.cpp to here when they are redesigned to stop accessing ImGui global state) -//----------------------------------------------------------------------------- -// - RenderMouseCursor() -// - RenderArrowPointingAt() -// - RenderRectFilledRangeH() -//----------------------------------------------------------------------------- - -void ImGui::RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor) -{ - if (mouse_cursor == ImGuiMouseCursor_None) - return; - IM_ASSERT(mouse_cursor > ImGuiMouseCursor_None && mouse_cursor < ImGuiMouseCursor_COUNT); - - const ImU32 col_shadow = IM_COL32(0, 0, 0, 48); - const ImU32 col_border = IM_COL32(0, 0, 0, 255); // Black - const ImU32 col_fill = IM_COL32(255, 255, 255, 255); // White - - ImFontAtlas* font_atlas = draw_list->_Data->Font->ContainerAtlas; - ImVec2 offset, size, uv[4]; - if (font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2])) - { - pos -= offset; - const ImTextureID tex_id = font_atlas->TexID; - draw_list->PushTextureID(tex_id); - draw_list->AddImage(tex_id, pos + ImVec2(1,0)*scale, pos + ImVec2(1,0)*scale + size*scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos + ImVec2(2,0)*scale, pos + ImVec2(2,0)*scale + size*scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos, pos + size*scale, uv[2], uv[3], col_border); - draw_list->AddImage(tex_id, pos, pos + size*scale, uv[0], uv[1], col_fill); - draw_list->PopTextureID(); - } -} - -// Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. -void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col) -{ - switch (direction) - { - case ImGuiDir_Left: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return; - case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return; - case ImGuiDir_Up: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return; - case ImGuiDir_Down: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return; - case ImGuiDir_None: case ImGuiDir_COUNT: break; // Fix warnings - } -} - -static inline float ImAcos01(float x) -{ - if (x <= 0.0f) return IM_PI * 0.5f; - if (x >= 1.0f) return 0.0f; - return ImAcos(x); - //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do. -} - -// FIXME: Cleanup and move code to ImDrawList. -void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding) -{ - if (x_end_norm == x_start_norm) - return; - if (x_start_norm > x_end_norm) - ImSwap(x_start_norm, x_end_norm); - - ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y); - ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y); - if (rounding == 0.0f) - { - draw_list->AddRectFilled(p0, p1, col, 0.0f); - return; - } - - rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding); - const float inv_rounding = 1.0f / rounding; - const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding); - const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding); - const float x0 = ImMax(p0.x, rect.Min.x + rounding); - if (arc0_b == arc0_e) - { - draw_list->PathLineTo(ImVec2(x0, p1.y)); - draw_list->PathLineTo(ImVec2(x0, p0.y)); - } - else if (arc0_b == 0.0f && arc0_e == IM_PI*0.5f) - { - draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL - draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR - } - else - { - draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL - draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR - } - if (p1.x > rect.Min.x + rounding) - { - const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding); - const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding); - const float x1 = ImMin(p1.x, rect.Max.x - rounding); - if (arc1_b == arc1_e) - { - draw_list->PathLineTo(ImVec2(x1, p0.y)); - draw_list->PathLineTo(ImVec2(x1, p1.y)); - } - else if (arc1_b == 0.0f && arc1_e == IM_PI*0.5f) - { - draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR - draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3); // BR - } - else - { - draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR - draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR - } - } - draw_list->PathFillConvex(col); -} - - -//----------------------------------------------------------------------------- -// [SECTION] Decompression code -//----------------------------------------------------------------------------- -// Compressed with stb_compress() then converted to a C array and encoded as base85. -// Use the program in misc/fonts/binary_to_compressed_c.cpp to create the array from a TTF file. -// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. -// Decompression from stb.h (public domain) by Sean Barrett https://github.com/nothings/stb/blob/master/stb.h -//----------------------------------------------------------------------------- - -static unsigned int stb_decompress_length(const unsigned char *input) -{ - return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11]; -} - -static unsigned char *stb__barrier_out_e, *stb__barrier_out_b; -static const unsigned char *stb__barrier_in_b; -static unsigned char *stb__dout; -static void stb__match(const unsigned char *data, unsigned int length) -{ - // INVERSE of memmove... write each byte before copying the next... - IM_ASSERT(stb__dout + length <= stb__barrier_out_e); - if (stb__dout + length > stb__barrier_out_e) { stb__dout += length; return; } - if (data < stb__barrier_out_b) { stb__dout = stb__barrier_out_e+1; return; } - while (length--) *stb__dout++ = *data++; -} - -static void stb__lit(const unsigned char *data, unsigned int length) -{ - IM_ASSERT(stb__dout + length <= stb__barrier_out_e); - if (stb__dout + length > stb__barrier_out_e) { stb__dout += length; return; } - if (data < stb__barrier_in_b) { stb__dout = stb__barrier_out_e+1; return; } - memcpy(stb__dout, data, length); - stb__dout += length; -} - -#define stb__in2(x) ((i[x] << 8) + i[(x)+1]) -#define stb__in3(x) ((i[x] << 16) + stb__in2((x)+1)) -#define stb__in4(x) ((i[x] << 24) + stb__in3((x)+1)) - -static const unsigned char *stb_decompress_token(const unsigned char *i) -{ - if (*i >= 0x20) { // use fewer if's for cases that expand small - if (*i >= 0x80) stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2; - else if (*i >= 0x40) stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3; - else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1); - } else { // more ifs for cases that expand large, since overhead is amortized - if (*i >= 0x18) stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4; - else if (*i >= 0x10) stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5; - else if (*i >= 0x08) stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1); - else if (*i == 0x07) stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1); - else if (*i == 0x06) stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5; - else if (*i == 0x04) stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6; - } - return i; -} - -static unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen) -{ - const unsigned long ADLER_MOD = 65521; - unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16; - unsigned long blocklen, i; - - blocklen = buflen % 5552; - while (buflen) { - for (i=0; i + 7 < blocklen; i += 8) { - s1 += buffer[0], s2 += s1; - s1 += buffer[1], s2 += s1; - s1 += buffer[2], s2 += s1; - s1 += buffer[3], s2 += s1; - s1 += buffer[4], s2 += s1; - s1 += buffer[5], s2 += s1; - s1 += buffer[6], s2 += s1; - s1 += buffer[7], s2 += s1; - - buffer += 8; - } - - for (; i < blocklen; ++i) - s1 += *buffer++, s2 += s1; - - s1 %= ADLER_MOD, s2 %= ADLER_MOD; - buflen -= blocklen; - blocklen = 5552; - } - return (unsigned int)(s2 << 16) + (unsigned int)s1; -} - -static unsigned int stb_decompress(unsigned char *output, const unsigned char *i, unsigned int /*length*/) -{ - unsigned int olen; - if (stb__in4(0) != 0x57bC0000) return 0; - if (stb__in4(4) != 0) return 0; // error! stream is > 4GB - olen = stb_decompress_length(i); - stb__barrier_in_b = i; - stb__barrier_out_e = output + olen; - stb__barrier_out_b = output; - i += 16; - - stb__dout = output; - for (;;) { - const unsigned char *old_i = i; - i = stb_decompress_token(i); - if (i == old_i) { - if (*i == 0x05 && i[1] == 0xfa) { - IM_ASSERT(stb__dout == output + olen); - if (stb__dout != output + olen) return 0; - if (stb_adler32(1, output, olen) != (unsigned int) stb__in4(2)) - return 0; - return olen; - } else { - IM_ASSERT(0); /* NOTREACHED */ - return 0; - } - } - IM_ASSERT(stb__dout <= output + olen); - if (stb__dout > output + olen) - return 0; - } -} - -//----------------------------------------------------------------------------- -// [SECTION] Default font data (ProggyClean.ttf) -//----------------------------------------------------------------------------- -// ProggyClean.ttf -// Copyright (c) 2004, 2005 Tristan Grimmer -// MIT license (see License.txt in http://www.upperbounds.net/download/ProggyClean.ttf.zip) -// Download and more information at http://upperbounds.net -//----------------------------------------------------------------------------- -// File: 'ProggyClean.ttf' (41208 bytes) -// Exported using misc/fonts/binary_to_compressed_c.cpp (with compression + base85 string encoding). -// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. -//----------------------------------------------------------------------------- -static const char proggy_clean_ttf_compressed_data_base85[11980+1] = - "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" - "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" - "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." - "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" - "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" - "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" - "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" - "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" - "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" - "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" - "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" - "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" - "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" - "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" - "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" - "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" - "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" - "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" - "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" - "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" - "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" - ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" - "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" - "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" - "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" - "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" - "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" - "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" - "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" - "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" - "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" - "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" - "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" - "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" - "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" - "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" - "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" - ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" - "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" - "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" - "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" - "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" - "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" - "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" - ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" - "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" - "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" - "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" - "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" - "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; - -static const char* GetDefaultCompressedFontDataTTFBase85() -{ - return proggy_clean_ttf_compressed_data_base85; -} diff --git a/Framework/src/imgui/imgui_impl_glfw_gl3.cpp b/Framework/src/imgui/imgui_impl_glfw_gl3.cpp deleted file mode 100644 index d46adc0f0a..0000000000 --- a/Framework/src/imgui/imgui_impl_glfw_gl3.cpp +++ /dev/null @@ -1,405 +0,0 @@ -// ImGui GLFW binding with OpenGL3 + shaders -// In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ about ImTextureID in imgui.cpp. - -// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. -// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). -// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. -// https://github.com/ocornut/imgui - -#include "imgui.h" -#include "imgui_impl_glfw_gl3.h" - -// GL3W/GLFW -#include "GL/gl3w.h" // This example is using gl3w to access OpenGL functions (because it is small). You may use glew/glad/glLoadGen/etc. whatever already works for you. -#include -#ifdef _WIN32 -#undef APIENTRY -#define GLFW_EXPOSE_NATIVE_WIN32 -#define GLFW_EXPOSE_NATIVE_WGL -#include -#endif - -// Data -static GLFWwindow* g_Window = NULL; -static double g_Time = 0.0f; -static bool g_MousePressed[3] = { false, false, false }; -static float g_MouseWheel = 0.0f; -static GLuint g_FontTexture = 0; -static int g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; -static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; -static int g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0; -static unsigned int g_VboHandle = 0, g_VaoHandle = 0, g_ElementsHandle = 0; - -// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure) -// If text or lines are blurry when integrating ImGui in your engine: -// - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f) -void ImGui_ImplGlfwGL3_RenderDrawLists(ImDrawData* draw_data) -{ - // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) - ImGuiIO& io = ImGui::GetIO(); - int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); - int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); - if (fb_width == 0 || fb_height == 0) - return; - draw_data->ScaleClipRects(io.DisplayFramebufferScale); - - // Backup GL state - GLint last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, &last_active_texture); - glActiveTexture(GL_TEXTURE0); - GLint last_program; glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); - GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); - GLint last_array_buffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); - GLint last_element_array_buffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_element_array_buffer); - GLint last_vertex_array; glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); - GLint last_blend_src_rgb; glGetIntegerv(GL_BLEND_SRC_RGB, &last_blend_src_rgb); - GLint last_blend_dst_rgb; glGetIntegerv(GL_BLEND_DST_RGB, &last_blend_dst_rgb); - GLint last_blend_src_alpha; glGetIntegerv(GL_BLEND_SRC_ALPHA, &last_blend_src_alpha); - GLint last_blend_dst_alpha; glGetIntegerv(GL_BLEND_DST_ALPHA, &last_blend_dst_alpha); - GLint last_blend_equation_rgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, &last_blend_equation_rgb); - GLint last_blend_equation_alpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &last_blend_equation_alpha); - GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); - GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); - GLboolean last_enable_blend = glIsEnabled(GL_BLEND); - GLboolean last_enable_cull_face = glIsEnabled(GL_CULL_FACE); - GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); - GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); - - // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled - glEnable(GL_BLEND); - glBlendEquation(GL_FUNC_ADD); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glEnable(GL_SCISSOR_TEST); - - // Setup viewport, orthographic projection matrix - glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); - const float ortho_projection[4][4] = - { - { 2.0f/io.DisplaySize.x, 0.0f, 0.0f, 0.0f }, - { 0.0f, 2.0f/-io.DisplaySize.y, 0.0f, 0.0f }, - { 0.0f, 0.0f, -1.0f, 0.0f }, - {-1.0f, 1.0f, 0.0f, 1.0f }, - }; - glUseProgram(g_ShaderHandle); - glUniform1i(g_AttribLocationTex, 0); - glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); - glBindVertexArray(g_VaoHandle); - - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - const ImDrawIdx* idx_buffer_offset = 0; - - glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); - glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmd_list->VtxBuffer.Size * sizeof(ImDrawVert), (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx), (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); - - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback) - { - pcmd->UserCallback(cmd_list, pcmd); - } - else - { - glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); - glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); - glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset); - } - idx_buffer_offset += pcmd->ElemCount; - } - } - - // Restore modified GL state - glUseProgram(last_program); - glBindTexture(GL_TEXTURE_2D, last_texture); - glActiveTexture(last_active_texture); - glBindVertexArray(last_vertex_array); - glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer); - glBlendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); - glBlendFuncSeparate(last_blend_src_rgb, last_blend_dst_rgb, last_blend_src_alpha, last_blend_dst_alpha); - if (last_enable_blend) glEnable(GL_BLEND); else glDisable(GL_BLEND); - if (last_enable_cull_face) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); - if (last_enable_depth_test) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); - if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); - glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); - glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); -} - -static const char* ImGui_ImplGlfwGL3_GetClipboardText(void* user_data) -{ - return glfwGetClipboardString((GLFWwindow*)user_data); -} - -static void ImGui_ImplGlfwGL3_SetClipboardText(void* user_data, const char* text) -{ - glfwSetClipboardString((GLFWwindow*)user_data, text); -} - -void ImGui_ImplGlfwGL3_MouseButtonCallback(GLFWwindow*, int button, int action, int /*mods*/) -{ - if (action == GLFW_PRESS && button >= 0 && button < 3) - g_MousePressed[button] = true; -} - -void ImGui_ImplGlfwGL3_ScrollCallback(GLFWwindow*, double /*xoffset*/, double yoffset) -{ - g_MouseWheel += (float)yoffset; // Use fractional mouse wheel, 1.0 unit 5 lines. -} - -void ImGui_ImplGlfwGL3_KeyCallback(GLFWwindow*, int key, int, int action, int mods) -{ - ImGuiIO& io = ImGui::GetIO(); - if (action == GLFW_PRESS) - io.KeysDown[key] = true; - if (action == GLFW_RELEASE) - io.KeysDown[key] = false; - - (void)mods; // Modifiers are not reliable across systems - io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; - io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; - io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; - io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; -} - -void ImGui_ImplGlfwGL3_CharCallback(GLFWwindow*, unsigned int c) -{ - ImGuiIO& io = ImGui::GetIO(); - if (c > 0 && c < 0x10000) - io.AddInputCharacter((unsigned short)c); -} - -bool ImGui_ImplGlfwGL3_CreateFontsTexture() -{ - // Build texture atlas - ImGuiIO& io = ImGui::GetIO(); - unsigned char* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. - - // Upload texture to graphics system - GLint last_texture; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); - glGenTextures(1, &g_FontTexture); - glBindTexture(GL_TEXTURE_2D, g_FontTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - // Store our identifier - io.Fonts->TexID = (void *)(intptr_t)g_FontTexture; - - // Restore state - glBindTexture(GL_TEXTURE_2D, last_texture); - - return true; -} - -bool ImGui_ImplGlfwGL3_CreateDeviceObjects() -{ - // Backup GL state - GLint last_texture, last_array_buffer, last_vertex_array; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); - glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer); - glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vertex_array); - - const GLchar *vertex_shader = - "#version 330\n" - "uniform mat4 ProjMtx;\n" - "in vec2 Position;\n" - "in vec2 UV;\n" - "in vec4 Color;\n" - "out vec2 Frag_UV;\n" - "out vec4 Frag_Color;\n" - "void main()\n" - "{\n" - " Frag_UV = UV;\n" - " Frag_Color = Color;\n" - " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" - "}\n"; - - const GLchar* fragment_shader = - "#version 330\n" - "uniform sampler2D Texture;\n" - "in vec2 Frag_UV;\n" - "in vec4 Frag_Color;\n" - "out vec4 Out_Color;\n" - "void main()\n" - "{\n" - " Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n" - "}\n"; - - g_ShaderHandle = glCreateProgram(); - g_VertHandle = glCreateShader(GL_VERTEX_SHADER); - g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(g_VertHandle, 1, &vertex_shader, 0); - glShaderSource(g_FragHandle, 1, &fragment_shader, 0); - glCompileShader(g_VertHandle); - glCompileShader(g_FragHandle); - glAttachShader(g_ShaderHandle, g_VertHandle); - glAttachShader(g_ShaderHandle, g_FragHandle); - glLinkProgram(g_ShaderHandle); - - g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); - g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); - g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position"); - g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV"); - g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color"); - - glGenBuffers(1, &g_VboHandle); - glGenBuffers(1, &g_ElementsHandle); - - glGenVertexArrays(1, &g_VaoHandle); - glBindVertexArray(g_VaoHandle); - glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); - glEnableVertexAttribArray(g_AttribLocationPosition); - glEnableVertexAttribArray(g_AttribLocationUV); - glEnableVertexAttribArray(g_AttribLocationColor); - -#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)) - glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, pos)); - glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, uv)); - glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, col)); -#undef OFFSETOF - - ImGui_ImplGlfwGL3_CreateFontsTexture(); - - // Restore modified GL state - glBindTexture(GL_TEXTURE_2D, last_texture); - glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); - glBindVertexArray(last_vertex_array); - - return true; -} - -void ImGui_ImplGlfwGL3_InvalidateDeviceObjects() -{ - if (g_VaoHandle) glDeleteVertexArrays(1, &g_VaoHandle); - if (g_VboHandle) glDeleteBuffers(1, &g_VboHandle); - if (g_ElementsHandle) glDeleteBuffers(1, &g_ElementsHandle); - g_VaoHandle = g_VboHandle = g_ElementsHandle = 0; - - if (g_ShaderHandle && g_VertHandle) glDetachShader(g_ShaderHandle, g_VertHandle); - if (g_VertHandle) glDeleteShader(g_VertHandle); - g_VertHandle = 0; - - if (g_ShaderHandle && g_FragHandle) glDetachShader(g_ShaderHandle, g_FragHandle); - if (g_FragHandle) glDeleteShader(g_FragHandle); - g_FragHandle = 0; - - if (g_ShaderHandle) glDeleteProgram(g_ShaderHandle); - g_ShaderHandle = 0; - - if (g_FontTexture) - { - glDeleteTextures(1, &g_FontTexture); - ImGui::GetIO().Fonts->TexID = 0; - g_FontTexture = 0; - } -} - -bool ImGui_ImplGlfwGL3_Init(GLFWwindow* window, bool install_callbacks) -{ - ImGui::CreateContext(); - g_Window = window; - - ImGuiIO& io = ImGui::GetIO(); - ImGui::StyleColorsDark(); - io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. - io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; - io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; - io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; - io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; - io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; - io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; - io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; - io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; - io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; - io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; - io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; - io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; - io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; - io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; - io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; - io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; - io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; - io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; - - io.RenderDrawListsFn = ImGui_ImplGlfwGL3_RenderDrawLists; // Alternatively you can set this to NULL and call ImGui::GetDrawData() after ImGui::Render() to get the same ImDrawData pointer. - io.SetClipboardTextFn = ImGui_ImplGlfwGL3_SetClipboardText; - io.GetClipboardTextFn = ImGui_ImplGlfwGL3_GetClipboardText; - io.ClipboardUserData = g_Window; -#ifdef _WIN32 - io.ImeWindowHandle = glfwGetWin32Window(g_Window); -#endif - - if (install_callbacks) - { - glfwSetMouseButtonCallback(window, ImGui_ImplGlfwGL3_MouseButtonCallback); - glfwSetScrollCallback(window, ImGui_ImplGlfwGL3_ScrollCallback); - glfwSetKeyCallback(window, ImGui_ImplGlfwGL3_KeyCallback); - glfwSetCharCallback(window, ImGui_ImplGlfwGL3_CharCallback); - } - - return true; -} - -void ImGui_ImplGlfwGL3_Shutdown() -{ - ImGui_ImplGlfwGL3_InvalidateDeviceObjects(); - ImGui::DestroyContext(); -} - -void ImGui_ImplGlfwGL3_NewFrame() -{ - if (!g_FontTexture) - ImGui_ImplGlfwGL3_CreateDeviceObjects(); - - ImGuiIO& io = ImGui::GetIO(); - - // Setup display size (every frame to accommodate for window resizing) - int w, h; - int display_w, display_h; - glfwGetWindowSize(g_Window, &w, &h); - glfwGetFramebufferSize(g_Window, &display_w, &display_h); - io.DisplaySize = ImVec2((float)w, (float)h); - io.DisplayFramebufferScale = ImVec2(w > 0 ? ((float)display_w / w) : 0, h > 0 ? ((float)display_h / h) : 0); - - // Setup time step - double current_time = glfwGetTime(); - io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f/60.0f); - g_Time = current_time; - - // Setup inputs - // (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents()) - if (glfwGetWindowAttrib(g_Window, GLFW_FOCUSED)) - { - double mouse_x, mouse_y; - glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); - io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); // Mouse position in screen coordinates (set to -1,-1 if no mouse / on another screen, etc.) - } - else - { - io.MousePos = ImVec2(-1,-1); - } - - for (int i = 0; i < 3; i++) - { - io.MouseDown[i] = g_MousePressed[i] || glfwGetMouseButton(g_Window, i) != 0; // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. - g_MousePressed[i] = false; - } - - io.MouseWheel = g_MouseWheel; - g_MouseWheel = 0.0f; - - // Hide OS mouse cursor if ImGui is drawing it - glfwSetInputMode(g_Window, GLFW_CURSOR, io.MouseDrawCursor ? GLFW_CURSOR_HIDDEN : GLFW_CURSOR_NORMAL); - - // Start the frame - ImGui::NewFrame(); -} diff --git a/Framework/src/imgui/imgui_impl_glfw_gl3.h b/Framework/src/imgui/imgui_impl_glfw_gl3.h deleted file mode 100644 index 1e2341038d..0000000000 --- a/Framework/src/imgui/imgui_impl_glfw_gl3.h +++ /dev/null @@ -1,26 +0,0 @@ -// ImGui GLFW binding with OpenGL3 + shaders -// In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ about ImTextureID -// in imgui.cpp. - -// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. -// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), -// ImGui::Render() and ImGui_ImplXXXX_Shutdown(). If you are new to ImGui, see examples/README.txt and documentation at -// the top of imgui.cpp. https://github.com/ocornut/imgui - -struct GLFWwindow; - -IMGUI_API bool ImGui_ImplGlfwGL3_Init(GLFWwindow* window, bool install_callbacks); -IMGUI_API void ImGui_ImplGlfwGL3_Shutdown(); -IMGUI_API void ImGui_ImplGlfwGL3_NewFrame(); - -// Use if you want to reset your rendering device without losing ImGui state. -IMGUI_API void ImGui_ImplGlfwGL3_InvalidateDeviceObjects(); -IMGUI_API bool ImGui_ImplGlfwGL3_CreateDeviceObjects(); - -// GLFW callbacks (installed by default if you enable 'install_callbacks' during initialization) -// Provided here if you want to chain callbacks. -// You can also handle inputs yourself and use those as a reference. -IMGUI_API void ImGui_ImplGlfwGL3_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); -IMGUI_API void ImGui_ImplGlfwGL3_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); -IMGUI_API void ImGui_ImplGlfwGL3_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); -IMGUI_API void ImGui_ImplGlfwGL3_CharCallback(GLFWwindow* window, unsigned int c); diff --git a/Framework/src/imgui/imgui_internal.h b/Framework/src/imgui/imgui_internal.h deleted file mode 100644 index 128017f677..0000000000 --- a/Framework/src/imgui/imgui_internal.h +++ /dev/null @@ -1,1674 +0,0 @@ -// dear imgui, v1.65 -// (internal structures/api) - -// You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward -// compatibility! Set: -// #define IMGUI_DEFINE_MATH_OPERATORS -// To implement maths operators for ImVec2 (disabled by default to not collide with using IM_VEC2_CLASS_EXTRA along with -// your own math types+operators) - -#pragma once - -#ifndef IMGUI_VERSION -#error Must include imgui.h before imgui_internal.h -#endif - -#include // INT_MIN, INT_MAX -#include // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf -#include // FILE* -#include // NULL, malloc, free, qsort, atoi, atof - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when \ - // IMGUI_API is set to__declspec(dllexport) -#endif - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h -#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h -#pragma clang diagnostic ignored "-Wold-style-cast" -#endif - -//----------------------------------------------------------------------------- -// Forward Declarations -//----------------------------------------------------------------------------- - -struct ImRect; // An axis-aligned rectangle (2 points) -struct ImDrawDataBuilder; // Helper to build a ImDrawData instance -struct ImDrawListSharedData; // Data shared between all ImDrawList instances -struct ImGuiColorMod; // Stacked color modifier, backup of modified data so we can restore it -struct ImGuiColumnData; // Storage data for a single column -struct ImGuiColumnsSet; // Storage data for a columns set -struct ImGuiContext; // Main imgui context -struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() -struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box -struct ImGuiItemHoveredDataBackup; // Backup and restore IsItemHovered() internal data -struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only -struct ImGuiNavMoveResult; // Result of a directional navigation move query result -struct ImGuiNextWindowData; // Storage for SetNexWindow** functions -struct ImGuiPopupRef; // Storage for current popup stack -struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file -struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it -struct ImGuiWindow; // Storage for one window -struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the - // end of the frame) -struct ImGuiWindowSettings; // Storage for window settings stored in .ini file (we keep one of those even if the actual - // window wasn't instanced during this session) - -// Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum -// lists. -typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical -typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for ButtonEx(), ButtonBehavior() -typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag() -typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for DC.LastItemStatusFlags -typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() -typedef int ImGuiNavDirSourceFlags; // -> enum ImGuiNavDirSourceFlags_ // Flags: for GetNavInputAmount2d() -typedef int ImGuiNavMoveFlags; // -> enum ImGuiNavMoveFlags_ // Flags: for navigation requests -typedef int ImGuiSeparatorFlags; // -> enum ImGuiSeparatorFlags_ // Flags: for Separator() - internal -typedef int ImGuiSliderFlags; // -> enum ImGuiSliderFlags_ // Flags: for SliderBehavior() - -//------------------------------------------------------------------------- -// STB libraries -//------------------------------------------------------------------------- - -namespace ImGuiStb -{ - -#undef STB_TEXTEDIT_STRING -#undef STB_TEXTEDIT_CHARTYPE -#define STB_TEXTEDIT_STRING ImGuiInputTextState -#define STB_TEXTEDIT_CHARTYPE ImWchar -#define STB_TEXTEDIT_GETWIDTH_NEWLINE -1.0f -#include "imstb_textedit.h" - -} // namespace ImGuiStb - -//----------------------------------------------------------------------------- -// Context -//----------------------------------------------------------------------------- - -#ifndef GImGui -extern IMGUI_API ImGuiContext* GImGui; // Current implicit ImGui context pointer -#endif - -//----------------------------------------------------------------------------- -// Helpers -//----------------------------------------------------------------------------- - -#define IM_PI 3.14159265358979323846f -#ifdef _WIN32 -#define IM_NEWLINE \ - "\r\n" // Play it nice with Windows users (2018/05 news: Microsoft announced that Notepad will finally display \ - // Unix-style carriage returns!) -#else -#define IM_NEWLINE "\n" -#endif -#define IM_STATIC_ASSERT(_COND) typedef char static_assertion_##__line__[(_COND) ? 1 : -1] -#define IM_F32_TO_INT8_UNBOUND(_VAL) \ - ((int)((_VAL)*255.0f + ((_VAL) >= 0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose -#define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 - -// Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed -// the default to e.g. __vectorcall -#ifdef _MSC_VER -#define IMGUI_CDECL __cdecl -#else -#define IMGUI_CDECL -#endif - -// Helpers: UTF-8 <> wchar -IMGUI_API int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, - const ImWchar* in_text_end); // return output UTF-8 bytes count -IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, - const char* in_text_end); // read one character. return input UTF-8 bytes count -IMGUI_API int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, - const char** in_remaining = NULL); // return input UTF-8 bytes count -IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, - const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) -IMGUI_API int ImTextCountUtf8BytesFromChar( - const char* in_text, const char* in_text_end); // return number of bytes to express one char in UTF-8 -IMGUI_API int ImTextCountUtf8BytesFromStr( - const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string in UTF-8 - -// Helpers: Misc -IMGUI_API ImU32 ImHash(const void* data, int data_size, - ImU32 seed = 0); // Pass data_size==0 for zero-terminated strings -IMGUI_API void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size = NULL, - int padding_bytes = 0); -IMGUI_API FILE* ImFileOpen(const char* filename, const char* file_open_mode); -static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } -static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } -static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } -static inline int ImUpperPowerOfTwo(int v) -{ - v--; - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - v++; - return v; -} -#define ImQsort qsort - -// Helpers: Geometry -IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p); -IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); -IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); -IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, - float& out_u, float& out_v, float& out_w); -IMGUI_API ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy); - -// Helpers: String -IMGUI_API int ImStricmp(const char* str1, const char* str2); -IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); -IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); -IMGUI_API char* ImStrdup(const char* str); -IMGUI_API const char* ImStrchrRange(const char* str_begin, const char* str_end, char c); -IMGUI_API int ImStrlenW(const ImWchar* str); -IMGUI_API const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line -IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, - const char* needle_end); -IMGUI_API void ImStrTrimBlanks(char* str); -IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); -IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); -IMGUI_API const char* ImParseFormatFindStart(const char* format); -IMGUI_API const char* ImParseFormatFindEnd(const char* format); -IMGUI_API const char* ImParseFormatTrimDecorations(const char* format, char* buf, int buf_size); -IMGUI_API int ImParseFormatPrecision(const char* format, int default_value); - -// Helpers: ImVec2/ImVec4 operators -// We are keeping those disabled by default so they don't leak in user space, to allow user enabling implicit cast -// operators between ImVec2 and their own types (using IM_VEC2_CLASS_EXTRA etc.) We unfortunately don't have a unary- -// operator for ImVec2 because this would needs to be defined inside the class itself. -#ifdef IMGUI_DEFINE_MATH_OPERATORS -static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) -{ - return ImVec2(lhs.x * rhs, lhs.y * rhs); -} -static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } -static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } -static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } -static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } -static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } -static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) -{ - lhs.x += rhs.x; - lhs.y += rhs.y; - return lhs; -} -static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) -{ - lhs.x -= rhs.x; - lhs.y -= rhs.y; - return lhs; -} -static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) -{ - lhs.x *= rhs; - lhs.y *= rhs; - return lhs; -} -static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) -{ - lhs.x /= rhs; - lhs.y /= rhs; - return lhs; -} -static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) -{ - return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); -} -static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) -{ - return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); -} -static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) -{ - return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); -} -#endif - -// Helpers: Maths -// - Wrapper for standard libs functions. (Note that imgui_demo.cpp does _not_ use them to keep the code easy to copy) -#ifndef IMGUI_DISABLE_MATH_FUNCTIONS -static inline float ImFabs(float x) -{ - return fabsf(x); -} -static inline float ImSqrt(float x) { return sqrtf(x); } -static inline float ImPow(float x, float y) { return powf(x, y); } -static inline double ImPow(double x, double y) { return pow(x, y); } -static inline float ImFmod(float x, float y) { return fmodf(x, y); } -static inline double ImFmod(double x, double y) { return fmod(x, y); } -static inline float ImCos(float x) { return cosf(x); } -static inline float ImSin(float x) { return sinf(x); } -static inline float ImAcos(float x) { return acosf(x); } -static inline float ImAtan2(float y, float x) { return atan2f(y, x); } -static inline double ImAtof(const char* s) { return atof(s); } -static inline float ImFloorStd(float x) -{ - return floorf(x); -} // we already uses our own ImFloor() { return (float)(int)v } internally so the standard one wrapper is named -// differently (it's used by stb_truetype) -static inline float ImCeil(float x) { return ceilf(x); } -#endif -// - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support for variety of types: signed/unsigned int/long -// long float/double, using templates here but we could also redefine them 6 times -template -static inline T ImMin(T lhs, T rhs) -{ - return lhs < rhs ? lhs : rhs; -} -template -static inline T ImMax(T lhs, T rhs) -{ - return lhs >= rhs ? lhs : rhs; -} -template -static inline T ImClamp(T v, T mn, T mx) -{ - return (v < mn) ? mn : (v > mx) ? mx : v; -} -template -static inline T ImLerp(T a, T b, float t) -{ - return (T)(a + (b - a) * t); -} -template -static inline void ImSwap(T& a, T& b) -{ - T tmp = a; - a = b; - b = tmp; -} -// - Misc maths helpers -static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) -{ - return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); -} -static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) -{ - return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); -} -static inline ImVec2 ImClamp(const ImVec2& v, const ImVec2& mn, ImVec2 mx) -{ - return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); -} -static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) -{ - return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); -} -static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) -{ - return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); -} -static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) -{ - return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); -} -static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } -static inline float ImLengthSqr(const ImVec2& lhs) { return lhs.x * lhs.x + lhs.y * lhs.y; } -static inline float ImLengthSqr(const ImVec4& lhs) -{ - return lhs.x * lhs.x + lhs.y * lhs.y + lhs.z * lhs.z + lhs.w * lhs.w; -} -static inline float ImInvLength(const ImVec2& lhs, float fail_value) -{ - float d = lhs.x * lhs.x + lhs.y * lhs.y; - if (d > 0.0f) - return 1.0f / ImSqrt(d); - return fail_value; -} -static inline float ImFloor(float f) { return (float)(int)f; } -static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2((float)(int)v.x, (float)(int)v.y); } -static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } -static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) -{ - return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); -} -static inline float ImLinearSweep(float current, float target, float speed) -{ - if (current < target) - return ImMin(current + speed, target); - if (current > target) - return ImMax(current - speed, target); - return current; -} -static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } - -//----------------------------------------------------------------------------- -// Types -//----------------------------------------------------------------------------- - -// 1D vector (this odd construct is used to facilitate the transition between 1D and 2D and maintenance of some patches) -struct ImVec1 { - float x; - ImVec1() { x = 0.0f; } - ImVec1(float _x) { x = _x; } -}; - -enum ImGuiButtonFlags_ { - ImGuiButtonFlags_None = 0, - ImGuiButtonFlags_Repeat = 1 << 0, // hold to repeat - ImGuiButtonFlags_PressedOnClickRelease = - 1 << 1, // return true on click + release on same item [DEFAULT if no PressedOn* flag is set] - ImGuiButtonFlags_PressedOnClick = 1 << 2, // return true on click (default requires click+release) - ImGuiButtonFlags_PressedOnRelease = 1 << 3, // return true on release (default requires click+release) - ImGuiButtonFlags_PressedOnDoubleClick = 1 << 4, // return true on double-click (default requires click+release) - ImGuiButtonFlags_FlattenChildren = 1 << 5, // allow interactions even if a child window is overlapping - ImGuiButtonFlags_AllowItemOverlap = 1 << 6, // require previous frame HoveredId to either match id or be null before - // being usable, use along with SetItemAllowOverlap() - ImGuiButtonFlags_DontClosePopups = 1 << 7, // disable automatically closing parent popup on press // [UNUSED] - ImGuiButtonFlags_Disabled = 1 << 8, // disable interactions - ImGuiButtonFlags_AlignTextBaseLine = - 1 << 9, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled - // by SmallButton(), not possible currently because of DC.CursorPosPrevLine - ImGuiButtonFlags_NoKeyModifiers = 1 << 10, // disable interaction if a key modifier is held - ImGuiButtonFlags_NoHoldingActiveID = - 1 << 11, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) - ImGuiButtonFlags_PressedOnDragDropHold = 1 << 12, // press when held into while we are drag and dropping another item - // (used by e.g. tree nodes, collapsing headers) - ImGuiButtonFlags_NoNavFocus = 1 << 13 // don't override navigation focus when activated -}; - -enum ImGuiSliderFlags_ { ImGuiSliderFlags_None = 0, - ImGuiSliderFlags_Vertical = 1 << 0 }; - -enum ImGuiColumnsFlags_ { - // Default: 0 - ImGuiColumnsFlags_None = 0, - ImGuiColumnsFlags_NoBorder = 1 << 0, // Disable column dividers - ImGuiColumnsFlags_NoResize = 1 << 1, // Disable resizing columns when clicking on the dividers - ImGuiColumnsFlags_NoPreserveWidths = 1 << 2, // Disable column width preservation when adjusting columns - ImGuiColumnsFlags_NoForceWithinWindow = 1 << 3, // Disable forcing columns to fit within window - ImGuiColumnsFlags_GrowParentContentsSize = - 1 << 4 // (WIP) Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the - // columns width at all_. Will eventually remove. -}; - -enum ImGuiSelectableFlagsPrivate_ { - // NB: need to be in sync with last value of ImGuiSelectableFlags_ - ImGuiSelectableFlags_NoHoldingActiveID = 1 << 10, - ImGuiSelectableFlags_PressedOnClick = 1 << 11, - ImGuiSelectableFlags_PressedOnRelease = 1 << 12, - ImGuiSelectableFlags_DrawFillAvailWidth = 1 << 13 -}; - -enum ImGuiSeparatorFlags_ { - ImGuiSeparatorFlags_None = 0, - ImGuiSeparatorFlags_Horizontal = - 1 << 0, // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar - ImGuiSeparatorFlags_Vertical = 1 << 1 -}; - -// Storage for LastItem data -enum ImGuiItemStatusFlags_ { - ImGuiItemStatusFlags_None = 0, - ImGuiItemStatusFlags_HoveredRect = 1 << 0, - ImGuiItemStatusFlags_HasDisplayRect = 1 << 1, - ImGuiItemStatusFlags_Edited = - 1 << 2 // Value exposed by item was edited in the current frame (should match the bool return value of most widgets) -}; - -// FIXME: this is in development, not exposed/functional as a generic feature yet. -enum ImGuiLayoutType_ { ImGuiLayoutType_Vertical, - ImGuiLayoutType_Horizontal }; - -enum ImGuiAxis { ImGuiAxis_None = -1, - ImGuiAxis_X = 0, - ImGuiAxis_Y = 1 }; - -enum ImGuiPlotType { ImGuiPlotType_Lines, - ImGuiPlotType_Histogram }; - -enum ImGuiInputSource { - ImGuiInputSource_None = 0, - ImGuiInputSource_Mouse, - ImGuiInputSource_Nav, - ImGuiInputSource_NavKeyboard, // Only used occasionally for storage, not tested/handled by most code - ImGuiInputSource_NavGamepad, // " - ImGuiInputSource_COUNT -}; - -// FIXME-NAV: Clarify/expose various repeat delay/rate -enum ImGuiInputReadMode { - ImGuiInputReadMode_Down, - ImGuiInputReadMode_Pressed, - ImGuiInputReadMode_Released, - ImGuiInputReadMode_Repeat, - ImGuiInputReadMode_RepeatSlow, - ImGuiInputReadMode_RepeatFast -}; - -enum ImGuiNavHighlightFlags_ { - ImGuiNavHighlightFlags_None = 0, - ImGuiNavHighlightFlags_TypeDefault = 1 << 0, - ImGuiNavHighlightFlags_TypeThin = 1 << 1, - ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2, - ImGuiNavHighlightFlags_NoRounding = 1 << 3 -}; - -enum ImGuiNavDirSourceFlags_ { - ImGuiNavDirSourceFlags_None = 0, - ImGuiNavDirSourceFlags_Keyboard = 1 << 0, - ImGuiNavDirSourceFlags_PadDPad = 1 << 1, - ImGuiNavDirSourceFlags_PadLStick = 1 << 2 -}; - -enum ImGuiNavMoveFlags_ { - ImGuiNavMoveFlags_None = 0, - ImGuiNavMoveFlags_LoopX = 1 << 0, // On failed request, restart from opposite side - ImGuiNavMoveFlags_LoopY = 1 << 1, - ImGuiNavMoveFlags_WrapX = 1 << 2, // On failed request, request from opposite side one line down (when NavDir==right) - // or one line up (when NavDir==left) - ImGuiNavMoveFlags_WrapY = 1 << 3, // This is not super useful for provided for completeness - ImGuiNavMoveFlags_AllowCurrentNavId = - 1 << 4, // Allow scoring and considering the current NavId as a move target candidate. This is used when the move - // source is offset (e.g. pressing PageDown actually needs to send a Up move request, if we are pressing - // PageDown from the bottom-most item we need to stay in place) - ImGuiNavMoveFlags_AlsoScoreVisibleSet = 1 << 5 // Store alternate result in NavMoveResultLocalVisibleSet that only - // comprise elements that are already fully visible. -}; - -enum ImGuiNavForward { ImGuiNavForward_None, - ImGuiNavForward_ForwardQueued, - ImGuiNavForward_ForwardActive }; - -enum ImGuiPopupPositionPolicy { ImGuiPopupPositionPolicy_Default, - ImGuiPopupPositionPolicy_ComboBox }; - -// 2D axis aligned bounding-box -// NB: we can't rely on ImVec2 math operators being available here -struct IMGUI_API ImRect { - ImVec2 Min; // Upper-left - ImVec2 Max; // Lower-right - - ImRect() : Min(FLT_MAX, FLT_MAX), Max(-FLT_MAX, -FLT_MAX) {} - ImRect(const ImVec2& min, const ImVec2& max) : Min(min), Max(max) {} - ImRect(const ImVec4& v) : Min(v.x, v.y), Max(v.z, v.w) {} - ImRect(float x1, float y1, float x2, float y2) : Min(x1, y1), Max(x2, y2) {} - - ImVec2 GetCenter() const { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); } - ImVec2 GetSize() const { return ImVec2(Max.x - Min.x, Max.y - Min.y); } - float GetWidth() const { return Max.x - Min.x; } - float GetHeight() const { return Max.y - Min.y; } - ImVec2 GetTL() const { return Min; } // Top-left - ImVec2 GetTR() const { return ImVec2(Max.x, Min.y); } // Top-right - ImVec2 GetBL() const { return ImVec2(Min.x, Max.y); } // Bottom-left - ImVec2 GetBR() const { return Max; } // Bottom-right - bool Contains(const ImVec2& p) const { return p.x >= Min.x && p.y >= Min.y && p.x < Max.x && p.y < Max.y; } - bool Contains(const ImRect& r) const - { - return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x <= Max.x && r.Max.y <= Max.y; - } - bool Overlaps(const ImRect& r) const - { - return r.Min.y < Max.y && r.Max.y > Min.y && r.Min.x < Max.x && r.Max.x > Min.x; - } - void Add(const ImVec2& p) - { - if (Min.x > p.x) - Min.x = p.x; - if (Min.y > p.y) - Min.y = p.y; - if (Max.x < p.x) - Max.x = p.x; - if (Max.y < p.y) - Max.y = p.y; - } - void Add(const ImRect& r) - { - if (Min.x > r.Min.x) - Min.x = r.Min.x; - if (Min.y > r.Min.y) - Min.y = r.Min.y; - if (Max.x < r.Max.x) - Max.x = r.Max.x; - if (Max.y < r.Max.y) - Max.y = r.Max.y; - } - void Expand(const float amount) - { - Min.x -= amount; - Min.y -= amount; - Max.x += amount; - Max.y += amount; - } - void Expand(const ImVec2& amount) - { - Min.x -= amount.x; - Min.y -= amount.y; - Max.x += amount.x; - Max.y += amount.y; - } - void Translate(const ImVec2& d) - { - Min.x += d.x; - Min.y += d.y; - Max.x += d.x; - Max.y += d.y; - } - void TranslateX(float dx) - { - Min.x += dx; - Max.x += dx; - } - void TranslateY(float dy) - { - Min.y += dy; - Max.y += dy; - } - void ClipWith(const ImRect& r) - { - Min = ImMax(Min, r.Min); - Max = ImMin(Max, r.Max); - } // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display. - void ClipWithFull(const ImRect& r) - { - Min = ImClamp(Min, r.Min, r.Max); - Max = ImClamp(Max, r.Min, r.Max); - } // Full version, ensure both points are fully clipped. - void Floor() - { - Min.x = (float)(int)Min.x; - Min.y = (float)(int)Min.y; - Max.x = (float)(int)Max.x; - Max.y = (float)(int)Max.y; - } - bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } -}; - -// Stacked color modifier, backup of modified data so we can restore it -struct ImGuiColorMod { - ImGuiCol Col; - ImVec4 BackupValue; -}; - -// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable. -struct ImGuiStyleMod { - ImGuiStyleVar VarIdx; - union { - int BackupInt[2]; - float BackupFloat[2]; - }; - ImGuiStyleMod(ImGuiStyleVar idx, int v) - { - VarIdx = idx; - BackupInt[0] = v; - } - ImGuiStyleMod(ImGuiStyleVar idx, float v) - { - VarIdx = idx; - BackupFloat[0] = v; - } - ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) - { - VarIdx = idx; - BackupFloat[0] = v.x; - BackupFloat[1] = v.y; - } -}; - -// Stacked storage data for BeginGroup()/EndGroup() -struct ImGuiGroupData { - ImVec2 BackupCursorPos; - ImVec2 BackupCursorMaxPos; - ImVec1 BackupIndent; - ImVec1 BackupGroupOffset; - ImVec2 BackupCurrentLineSize; - float BackupCurrentLineTextBaseOffset; - float BackupLogLinePosY; - ImGuiID BackupActiveIdIsAlive; - bool BackupActiveIdPreviousFrameIsAlive; - bool AdvanceCursor; -}; - -// Simple column measurement, currently used for MenuItem() only.. This is very short-sighted/throw-away code and NOT a -// generic helper. -struct IMGUI_API ImGuiMenuColumns { - int Count; - float Spacing; - float Width, NextWidth; - float Pos[4], NextWidths[4]; - - ImGuiMenuColumns(); - void Update(int count, float spacing, bool clear); - float DeclColumns(float w0, float w1, float w2); - float CalcExtraSpace(float avail_w); -}; - -// Internal state of the currently focused/edited text input box -struct IMGUI_API ImGuiInputTextState { - ImGuiID ID; // widget id owning the text state - ImVector TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided - // buffer. so we copy into own buffer. - ImVector InitialText; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) - ImVector TempBuffer; // temporary buffer for callback and other other operations. size=capacity. - int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. - int BufCapacityA; // end-user buffer capacity - float ScrollX; - ImGuiStb::STB_TexteditState StbState; - float CursorAnim; - bool CursorFollow; - bool SelectedAllMouseLock; - - // Temporarily set when active - ImGuiInputTextFlags UserFlags; - ImGuiInputTextCallback UserCallback; - void* UserCallbackData; - - ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } - void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking - void CursorClamp() - { - StbState.cursor = ImMin(StbState.cursor, CurLenW); - StbState.select_start = ImMin(StbState.select_start, CurLenW); - StbState.select_end = ImMin(StbState.select_end, CurLenW); - } - bool HasSelection() const { return StbState.select_start != StbState.select_end; } - void ClearSelection() { StbState.select_start = StbState.select_end = StbState.cursor; } - void SelectAll() - { - StbState.select_start = 0; - StbState.cursor = StbState.select_end = CurLenW; - StbState.has_preferred_x = false; - } - void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation -}; - -// Windows data saved in imgui.ini file -struct ImGuiWindowSettings { - char* Name; - ImGuiID ID; - ImVec2 Pos; - ImVec2 Size; - bool Collapsed; - - ImGuiWindowSettings() - { - Name = NULL; - ID = 0; - Pos = Size = ImVec2(0, 0); - Collapsed = false; - } -}; - -struct ImGuiSettingsHandler { - const char* TypeName; // Short description stored in .ini file. Disallowed characters: '[' ']' - ImGuiID TypeHash; // == ImHash(TypeName, 0, 0) - void* (*ReadOpenFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, - const char* name); // Read: Called when entering into a new ini entry e.g. "[Window][Name]" - void (*ReadLineFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, - const char* line); // Read: Called for every line of text within an ini entry - void (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, - ImGuiTextBuffer* out_buf); // Write: Output every entries into 'out_buf' - void* UserData; - - ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); } -}; - -// Storage for current popup stack -struct ImGuiPopupRef { - ImGuiID PopupId; // Set on OpenPopup() - ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() - ImGuiWindow* ParentWindow; // Set on OpenPopup() - int OpenFrameCount; // Set on OpenPopup() - ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differenciate multiple menu sets from each others (e.g. - // inside menu bar vs loose menu items) - ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) - ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup -}; - -struct ImGuiColumnData { - float OffsetNorm; // Column start offset, normalized 0.0 (far left) -> 1.0 (far right) - float OffsetNormBeforeResize; - ImGuiColumnsFlags Flags; // Not exposed - ImRect ClipRect; - - ImGuiColumnData() - { - OffsetNorm = OffsetNormBeforeResize = 0.0f; - Flags = 0; - } -}; - -struct ImGuiColumnsSet { - ImGuiID ID; - ImGuiColumnsFlags Flags; - bool IsFirstFrame; - bool IsBeingResized; - int Current; - int Count; - float MinX, MaxX; - float LineMinY, LineMaxY; - float StartPosY; // Copy of CursorPos - float StartMaxPosX; // Copy of CursorMaxPos - ImVector Columns; - - ImGuiColumnsSet() { Clear(); } - void Clear() - { - ID = 0; - Flags = 0; - IsFirstFrame = false; - IsBeingResized = false; - Current = 0; - Count = 1; - MinX = MaxX = 0.0f; - LineMinY = LineMaxY = 0.0f; - StartPosY = 0.0f; - StartMaxPosX = 0.0f; - Columns.clear(); - } -}; - -// Data shared between all ImDrawList instances -struct IMGUI_API ImDrawListSharedData { - ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas - ImFont* Font; // Current/default font (optional, for simplified AddText overload) - float FontSize; // Current/default font size (optional, for simplified AddText overload) - float CurveTessellationTol; - ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() - - // Const data - // FIXME: Bake rounded corners fill/borders in atlas - ImVec2 CircleVtx12[12]; - - ImDrawListSharedData(); -}; - -struct ImDrawDataBuilder { - ImVector Layers[2]; // Global layers for: regular, tooltip - - void Clear() - { - for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) - Layers[n].resize(0); - } - void ClearFreeMemory() - { - for (int n = 0; n < IM_ARRAYSIZE(Layers); n++) - Layers[n].clear(); - } - IMGUI_API void FlattenIntoSingleLayer(); -}; - -struct ImGuiNavMoveResult { - ImGuiID ID; // Best candidate - ImGuiWindow* Window; // Best candidate window - float DistBox; // Best candidate box distance to current NavId - float DistCenter; // Best candidate center distance to current NavId - float DistAxial; - ImRect RectRel; // Best candidate bounding box in window relative space - - ImGuiNavMoveResult() { Clear(); } - void Clear() - { - ID = 0; - Window = NULL; - DistBox = DistCenter = DistAxial = FLT_MAX; - RectRel = ImRect(); - } -}; - -// Storage for SetNexWindow** functions -struct ImGuiNextWindowData { - ImGuiCond PosCond; - ImGuiCond SizeCond; - ImGuiCond ContentSizeCond; - ImGuiCond CollapsedCond; - ImGuiCond SizeConstraintCond; - ImGuiCond FocusCond; - ImGuiCond BgAlphaCond; - ImVec2 PosVal; - ImVec2 PosPivotVal; - ImVec2 SizeVal; - ImVec2 ContentSizeVal; - bool CollapsedVal; - ImRect SizeConstraintRect; - ImGuiSizeCallback SizeCallback; - void* SizeCallbackUserData; - float BgAlphaVal; - ImVec2 MenuBarOffsetMinVal; // This is not exposed publicly, so we don't clear it. - - ImGuiNextWindowData() - { - PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; - PosVal = PosPivotVal = SizeVal = ImVec2(0.0f, 0.0f); - ContentSizeVal = ImVec2(0.0f, 0.0f); - CollapsedVal = false; - SizeConstraintRect = ImRect(); - SizeCallback = NULL; - SizeCallbackUserData = NULL; - BgAlphaVal = FLT_MAX; - MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); - } - - void Clear() - { - PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; - } -}; - -// Main imgui context -struct ImGuiContext { - bool Initialized; - bool FrameScopeActive; // Set by NewFrame(), cleared by EndFrame()/Render() - bool FontAtlasOwnedByContext; // Io.Fonts-> is owned by the ImGuiContext and will be destructed along with it. - ImGuiIO IO; - ImGuiStyle Style; - ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() - float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height - // for current window. - float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. - ImDrawListSharedData DrawListSharedData; - - double Time; - int FrameCount; - int FrameCountEnded; - int FrameCountRendered; - ImVector Windows; - ImVector WindowsSortBuffer; - ImVector CurrentWindowStack; - ImGuiStorage WindowsById; - int WindowsActiveCount; - ImGuiWindow* CurrentWindow; // Being drawn into - ImGuiWindow* HoveredWindow; // Will catch mouse inputs - ImGuiWindow* HoveredRootWindow; // Will catch mouse inputs (for focus/move only) - ImGuiID HoveredId; // Hovered widget - bool HoveredIdAllowOverlap; - ImGuiID HoveredIdPreviousFrame; - float HoveredIdTimer; - ImGuiID ActiveId; // Active widget - ImGuiID ActiveIdPreviousFrame; - ImGuiID ActiveIdIsAlive; // Active widget has been seen this frame (we can't use a bool as the ActiveId may change - // within the frame) - float ActiveIdTimer; - bool ActiveIdIsJustActivated; // Set at the time of activation for one frame - bool ActiveIdAllowOverlap; // Active widget allows another widget to steal active id (generally for overlapping - // widgets, but not always) - bool ActiveIdHasBeenEdited; // Was the value associated to the widget Edited over the course of the Active state. - bool ActiveIdPreviousFrameIsAlive; - bool ActiveIdPreviousFrameHasBeenEdited; - int ActiveIdAllowNavDirFlags; // Active widget allows using directional navigation (e.g. can activate a button and - // move away from it) - ImVec2 - ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) - ImGuiWindow* ActiveIdWindow; - ImGuiWindow* ActiveIdPreviousFrameWindow; - ImGuiInputSource ActiveIdSource; // Activating with mouse or nav (gamepad/keyboard) - ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. - float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for - // animation. - ImGuiWindow* MovingWindow; // Track the window we clicked on (in order to preserve focus). The actually window that is - // moved is generally MovingWindow->RootWindow. - ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() - ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() - ImVector FontStack; // Stack for PushFont()/PopFont() - ImVector OpenPopupStack; // Which popups are open (persistent) - ImVector CurrentPopupStack; // Which level of BeginPopup() we are in (reset every frame) - ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions - bool NextTreeNodeOpenVal; // Storage for SetNextTreeNode** functions - ImGuiCond NextTreeNodeOpenCond; - - // Navigation data (for gamepad/keyboard) - ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusWindow' - ImGuiID NavId; // Focused item for navigation - ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0, also set when - // calling ActivateItem() - ImGuiID NavActivateDownId; // ~~ IsNavInputDown(ImGuiNavInput_Activate) ? NavId : 0 - ImGuiID NavActivatePressedId; // ~~ IsNavInputPressed(ImGuiNavInput_Activate) ? NavId : 0 - ImGuiID NavInputId; // ~~ IsNavInputPressed(ImGuiNavInput_Input) ? NavId : 0 - ImGuiID NavJustTabbedId; // Just tabbed to this id. - ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest) - ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame - ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? - ImRect NavScoringRectScreen; // Rectangle used for scoring, in screen space. Based of window->DC.NavRefRectRel[], - // modified for directional navigation scoring. - int NavScoringCount; // Metrics for debugging - ImGuiWindow* NavWindowingTarget; // When selecting a window (holding Menu+FocusPrev/Next, or equivalent of CTRL-TAB) - // this window is temporarily displayed front-most. - ImGuiWindow* NavWindowingTargetAnim; // Record of last valid NavWindowingTarget until DimBgRatio and - // NavWindowingHighlightAlpha becomes 0.0f - ImGuiWindow* NavWindowingList; - float NavWindowingTimer; - float NavWindowingHighlightAlpha; - bool NavWindowingToggleLayer; - int NavLayer; // Layer we are navigating on. For now the system is hard-coded for 0=main contents and 1=menu/title - // bar, may expose layers later. - int NavIdTabCounter; // == NavWindow->DC.FocusIdxTabCounter at time of NavId processing - bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRefRectRel is valid - bool NavMousePosDirty; // When set we will update mouse position if (io.ConfigFlags & - // ImGuiConfigFlags_NavEnableSetMousePos) if set (NB: this not enabled by default) - bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (NB: but they are still - // available, which is why NavDisableHighlight isn't always != NavDisableMouseHover) - bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is - // touched again. - bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest - bool NavInitRequest; // Init request for appearing window to select first item - bool NavInitRequestFromMove; - ImGuiID NavInitResultId; - ImRect NavInitResultRectRel; - bool NavMoveFromClampedRefRect; // Set by manual scrolling, if we scroll to a point where NavId isn't visible we reset - // navigation from visible items - bool NavMoveRequest; // Move request for this frame - ImGuiNavMoveFlags NavMoveRequestFlags; - ImGuiNavForward NavMoveRequestForward; // None / ForwardQueued / ForwardActive (this is used to navigate sibling - // parent menus from a child menu) - ImGuiDir NavMoveDir, - NavMoveDirLast; // Direction of the move request (left/right/up/down), direction of the previous move request - ImGuiDir NavMoveClipDir; - ImGuiNavMoveResult NavMoveResultLocal; // Best move request candidate within NavWindow - ImGuiNavMoveResult NavMoveResultLocalVisibleSet; // Best move request candidate within NavWindow that are mostly - // visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag) - ImGuiNavMoveResult NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when - // using ImGuiWindowFlags_NavFlattened flag) - - // Render - ImDrawData DrawData; // Main ImDrawData instance to pass render information to the user - ImDrawDataBuilder DrawDataBuilder; - float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list) - ImDrawList - OverlayDrawList; // Optional software render of mouse cursors, if io.MouseDrawCursor is set + a few debug overlays - ImGuiMouseCursor MouseCursor; - - // Drag and Drop - bool DragDropActive; - bool DragDropWithinSourceOrTarget; - ImGuiDragDropFlags DragDropSourceFlags; - int DragDropSourceFrameCount; - int DragDropMouseButton; - ImGuiPayload DragDropPayload; - ImRect DragDropTargetRect; - ImGuiID DragDropTargetId; - ImGuiDragDropFlags DragDropAcceptFlags; - float DragDropAcceptIdCurrRectSurface; // Target item surface (we resolve overlapping targets by prioritizing the - // smaller surface) - ImGuiID DragDropAcceptIdCurr; // Target item id (set at the time of accepting the payload) - ImGuiID DragDropAcceptIdPrev; // Target item id from previous frame (we need to store this to allow for overlapping - // drag and drop targets) - int DragDropAcceptFrameCount; // Last time a target expressed a desire to accept the source - ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly - unsigned char DragDropPayloadBufLocal[8]; // Local buffer for small payloads - - // Widget state - ImGuiInputTextState InputTextState; - ImFont InputTextPasswordFont; - ImGuiID ScalarAsInputTextId; // Temporary text input when CTRL+clicking on a slider, etc. - ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets - ImVec4 ColorPickerRef; - bool DragCurrentAccumDirty; - float DragCurrentAccum; // Accumulator for dragging modification. Always high-precision, not rounded by end-user - // precision settings - float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio - ImVec2 ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. - // Use storage? - int TooltipOverrideCount; - ImVector PrivateClipboard; // If no custom clipboard handler is defined - ImVec2 PlatformImePos, PlatformImeLastPos; // Cursor position request & last passed to the OS Input Method Editor - - // Settings - bool SettingsLoaded; - float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero - ImGuiTextBuffer SettingsIniData; // In memory .ini settings - ImVector SettingsHandlers; // List of .ini settings handlers - ImVector SettingsWindows; // ImGuiWindow .ini settings entries (parsed from the last loaded .ini - // file and maintained on saving) - - // Logging - bool LogEnabled; - FILE* LogFile; // If != NULL log to stdout/ file - ImGuiTextBuffer LogClipboard; // Accumulation buffer when log to clipboard. This is pointer so our GImGui static - // constructor doesn't call heap allocators. - int LogStartDepth; - int LogAutoExpandMaxDepth; - - // Misc - float FramerateSecPerFrame[120]; // Calculate estimate of framerate for user over the last 2 seconds. - int FramerateSecPerFrameIdx; - float FramerateSecPerFrameAccum; - int WantCaptureMouseNextFrame; // Explicit capture via CaptureKeyboardFromApp()/CaptureMouseFromApp() sets those flags - int WantCaptureKeyboardNextFrame; - int WantTextInputNextFrame; - char TempBuffer[1024 * 3 + 1]; // Temporary text buffer - - ImGuiContext(ImFontAtlas* shared_font_atlas) : OverlayDrawList(NULL) - { - Initialized = false; - FrameScopeActive = false; - Font = NULL; - FontSize = FontBaseSize = 0.0f; - FontAtlasOwnedByContext = shared_font_atlas ? false : true; - IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); - - Time = 0.0f; - FrameCount = 0; - FrameCountEnded = FrameCountRendered = -1; - WindowsActiveCount = 0; - CurrentWindow = NULL; - HoveredWindow = NULL; - HoveredRootWindow = NULL; - HoveredId = 0; - HoveredIdAllowOverlap = false; - HoveredIdPreviousFrame = 0; - HoveredIdTimer = 0.0f; - ActiveId = 0; - ActiveIdPreviousFrame = 0; - ActiveIdIsAlive = 0; - ActiveIdTimer = 0.0f; - ActiveIdIsJustActivated = false; - ActiveIdAllowOverlap = false; - ActiveIdHasBeenEdited = false; - ActiveIdPreviousFrameIsAlive = false; - ActiveIdPreviousFrameHasBeenEdited = false; - ActiveIdAllowNavDirFlags = 0; - ActiveIdClickOffset = ImVec2(-1, -1); - ActiveIdWindow = ActiveIdPreviousFrameWindow = NULL; - ActiveIdSource = ImGuiInputSource_None; - LastActiveId = 0; - LastActiveIdTimer = 0.0f; - MovingWindow = NULL; - NextTreeNodeOpenVal = false; - NextTreeNodeOpenCond = 0; - - NavWindow = NULL; - NavId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0; - NavJustTabbedId = NavJustMovedToId = NavNextActivateId = 0; - NavInputSource = ImGuiInputSource_None; - NavScoringRectScreen = ImRect(); - NavScoringCount = 0; - NavWindowingTarget = NavWindowingTargetAnim = NavWindowingList = NULL; - NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; - NavWindowingToggleLayer = false; - NavLayer = 0; - NavIdTabCounter = INT_MAX; - NavIdIsAlive = false; - NavMousePosDirty = false; - NavDisableHighlight = true; - NavDisableMouseHover = false; - NavAnyRequest = false; - NavInitRequest = false; - NavInitRequestFromMove = false; - NavInitResultId = 0; - NavMoveFromClampedRefRect = false; - NavMoveRequest = false; - NavMoveRequestFlags = 0; - NavMoveRequestForward = ImGuiNavForward_None; - NavMoveDir = NavMoveDirLast = NavMoveClipDir = ImGuiDir_None; - - DimBgRatio = 0.0f; - OverlayDrawList._Data = &DrawListSharedData; - OverlayDrawList._OwnerName = "##Overlay"; // Give it a name for debugging - MouseCursor = ImGuiMouseCursor_Arrow; - - DragDropActive = DragDropWithinSourceOrTarget = false; - DragDropSourceFlags = 0; - DragDropSourceFrameCount = -1; - DragDropMouseButton = -1; - DragDropTargetId = 0; - DragDropAcceptFlags = 0; - DragDropAcceptIdCurrRectSurface = 0.0f; - DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; - DragDropAcceptFrameCount = -1; - memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal)); - - ScalarAsInputTextId = 0; - ColorEditOptions = ImGuiColorEditFlags__OptionsDefault; - DragCurrentAccumDirty = false; - DragCurrentAccum = 0.0f; - DragSpeedDefaultRatio = 1.0f / 100.0f; - ScrollbarClickDeltaToGrabCenter = ImVec2(0.0f, 0.0f); - TooltipOverrideCount = 0; - PlatformImePos = PlatformImeLastPos = ImVec2(FLT_MAX, FLT_MAX); - - SettingsLoaded = false; - SettingsDirtyTimer = 0.0f; - - LogEnabled = false; - LogFile = NULL; - LogStartDepth = 0; - LogAutoExpandMaxDepth = 2; - - memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame)); - FramerateSecPerFrameIdx = 0; - FramerateSecPerFrameAccum = 0.0f; - WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; - memset(TempBuffer, 0, sizeof(TempBuffer)); - } -}; - -// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first -// Begin(). This is going to be exposed in imgui.h when stabilized enough. -enum ImGuiItemFlags_ { - ImGuiItemFlags_AllowKeyboardFocus = 1 << 0, // true - ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on - // io.KeyRepeatDelay and io.KeyRepeatRate settings. - ImGuiItemFlags_Disabled = 1 << 2, // false // [BETA] Disable interactions but doesn't affect visuals yet. See - // github.com/ocornut/imgui/issues/211 - ImGuiItemFlags_NoNav = 1 << 3, // false - ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false - ImGuiItemFlags_SelectableDontClosePopup = - 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window - ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus -}; - -// Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC -// variable name in ImGuiWindow. -// FIXME: That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and -// could be reconsidered. -struct IMGUI_API ImGuiWindowTempData { - ImVec2 CursorPos; - ImVec2 CursorPosPrevLine; - ImVec2 CursorStartPos; // Initial position in client area with padding - ImVec2 CursorMaxPos; // Used to implicitly calculate the size of our contents, always growing during the frame. Turned - // into window->SizeContents at the beginning of next frame - ImVec2 CurrentLineSize; - float CurrentLineTextBaseOffset; - ImVec2 PrevLineSize; - float PrevLineTextBaseOffset; - float LogLinePosY; - int TreeDepth; - ImU32 TreeDepthMayJumpToParentOnPop; // Store a copy of !g.NavIdIsAlive for TreeDepth 0..31 - ImGuiID LastItemId; - ImGuiItemStatusFlags LastItemStatusFlags; - ImRect LastItemRect; // Interaction rect - ImRect LastItemDisplayRect; // End-user display rect (only valid if LastItemStatusFlags & - // ImGuiItemStatusFlags_HasDisplayRect) - bool NavHideHighlightOneFrame; - bool NavHasScroll; // Set when scrolling can be used (ScrollMax > 0.0f) - int NavLayerCurrent; // Current layer, 0..31 (we currently only use 0..1) - int NavLayerCurrentMask; // = (1 << NavLayerCurrent) used by ItemAdd prior to clipping. - int NavLayerActiveMask; // Which layer have been written to (result from previous frame) - int NavLayerActiveMaskNext; // Which layer have been written to (buffer for current frame) - bool MenuBarAppending; // FIXME: Remove this - ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch - // to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > - // FramePadding.y), often used on TVs. - ImVector ChildWindows; - ImGuiStorage* StateStorage; - ImGuiLayoutType LayoutType; - ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() - - // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors - // are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those - // settings. - ImGuiItemFlags ItemFlags; // == ItemFlagsStack.back() [empty == ImGuiItemFlags_Default] - float ItemWidth; // == ItemWidthStack.back(). 0.0: default, >0.0: width in pixels, <0.0: align xx pixels to the right - // of window - float TextWrapPos; // == TextWrapPosStack.back() [empty == -1.0f] - ImVector ItemFlagsStack; - ImVector ItemWidthStack; - ImVector TextWrapPosStack; - ImVector GroupStack; - int StackSizesBackup[6]; // Store size of various stacks for asserting - - ImVec1 Indent; // Indentation / start position from left of window (increased by TreePush/TreePop, etc.) - ImVec1 GroupOffset; - ImVec1 ColumnsOffset; // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a - // stack to allow use cases like Tree->Column->Tree. Need revamp columns API. - ImGuiColumnsSet* ColumnsSet; // Current columns set - - ImGuiWindowTempData() - { - CursorPos = CursorPosPrevLine = CursorStartPos = CursorMaxPos = ImVec2(0.0f, 0.0f); - CurrentLineSize = PrevLineSize = ImVec2(0.0f, 0.0f); - CurrentLineTextBaseOffset = PrevLineTextBaseOffset = 0.0f; - LogLinePosY = -1.0f; - TreeDepth = 0; - TreeDepthMayJumpToParentOnPop = 0x00; - LastItemId = 0; - LastItemStatusFlags = 0; - LastItemRect = LastItemDisplayRect = ImRect(); - NavHideHighlightOneFrame = false; - NavHasScroll = false; - NavLayerActiveMask = NavLayerActiveMaskNext = 0x00; - NavLayerCurrent = 0; - NavLayerCurrentMask = 1 << 0; - MenuBarAppending = false; - MenuBarOffset = ImVec2(0.0f, 0.0f); - StateStorage = NULL; - LayoutType = ParentLayoutType = ImGuiLayoutType_Vertical; - ItemWidth = 0.0f; - ItemFlags = ImGuiItemFlags_Default_; - TextWrapPos = -1.0f; - memset(StackSizesBackup, 0, sizeof(StackSizesBackup)); - - Indent = ImVec1(0.0f); - GroupOffset = ImVec1(0.0f); - ColumnsOffset = ImVec1(0.0f); - ColumnsSet = NULL; - } -}; - -// Storage for one window -struct IMGUI_API ImGuiWindow { - char* Name; - ImGuiID ID; // == ImHash(Name) - ImGuiWindowFlags Flags; // See enum ImGuiWindowFlags_ - ImVec2 Pos; // Position (always rounded-up to nearest pixel) - ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) - ImVec2 SizeFull; // Size when non collapsed - ImVec2 SizeFullAtLastBegin; // Copy of SizeFull at the end of Begin. This is the reference value we'll use on the next - // frame to decide if we need scrollbars. - ImVec2 SizeContents; // Size of contents (== extents reach of the drawing cursor) from previous frame. Include - // decoration, window title, border, menu, etc. - ImVec2 SizeContentsExplicit; // Size of contents explicitly set by the user via SetNextWindowContentSize() - ImVec2 WindowPadding; // Window padding at the time of begin. - float WindowRounding; // Window rounding at the time of begin. - float WindowBorderSize; // Window border size at the time of begin. - ImGuiID MoveId; // == window->GetID("#MOVE") - ImGuiID - ChildId; // ID of corresponding item in parent window (for navigation to return from child window to parent window) - ImVec2 Scroll; - ImVec2 ScrollTarget; // target scroll position. stored as cursor position with scrolling canceled out, so the highest - // point is always 0.0f. (FLT_MAX for no change) - ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target - // position is centered - ImVec2 ScrollbarSizes; // Size taken by scrollbars on each axis - bool ScrollbarX, ScrollbarY; - bool Active; // Set to true on Begin(), unless Collapsed - bool WasActive; - bool WriteAccessed; // Set to true when any widget access the current window - bool Collapsed; // Set when collapsing window to become only title-bar - bool WantCollapseToggle; - bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) - bool Appearing; // Set during the frame where the window is appearing (or re-appearing) - bool Hidden; // Do not display (== (HiddenFramesForResize > 0) || - bool HasCloseButton; // Set when the window has a close button (p_open != NULL) - int BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple - // Begin/End pairs) - int BeginOrderWithinParent; // Order within immediate parent window, if we are a child window. Otherwise 0. - int BeginOrderWithinContext; // Order within entire imgui context. This is mostly used for debugging submission order - // related issues. - ImGuiID PopupId; // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for - // recycling) - int AutoFitFramesX, AutoFitFramesY; - bool AutoFitOnlyGrows; - int AutoFitChildAxises; - ImGuiDir AutoPosLastDirection; - int HiddenFramesRegular; // Hide the window for N frames - int HiddenFramesForResize; // Hide the window for N frames while allowing items to be submitted so we can measure - // their size - ImGuiCond SetWindowPosAllowFlags; // store acceptable condition flags for SetNextWindowPos() use. - ImGuiCond SetWindowSizeAllowFlags; // store acceptable condition flags for SetNextWindowSize() use. - ImGuiCond SetWindowCollapsedAllowFlags; // store acceptable condition flags for SetNextWindowCollapsed() use. - ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when - // we know the window size) - ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; - // ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right. - - ImGuiWindowTempData DC; // Temporary per-window data, reset at the beginning of the frame. This used to be called - // ImGuiDrawContext, hence the "DC" variable name. - ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack - ImRect ClipRect; // Current clipping rectangle. = DrawList->clip_rect_stack.back(). Scissoring / clipping rectangle. - // x1, y1, x2, y2. - ImRect OuterRectClipped; // = WindowRect just after setup in Begin(). == window->Rect() for root window. - ImRect InnerMainRect, InnerClipRect; - ImRect ContentsRegionRect; // FIXME: This is currently confusing/misleading. Maximum visible content position ~~ Pos + - // (SizeContentsExplicit ? SizeContentsExplicit : Size - ScrollbarSizes) - CursorStartPos, - // per axis - int LastFrameActive; // Last frame number the window was Active. - float ItemWidthDefault; - ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items - ImGuiStorage StateStorage; - ImVector ColumnsStorage; - float FontWindowScale; // User scale multiplier per-window - int SettingsIdx; // Index into SettingsWindow[] (indices are always valid as we only grow the array from the back) - - ImDrawList* DrawList; // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep - // this a pointer) - ImDrawList DrawListInst; - ImGuiWindow* ParentWindow; // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL. - ImGuiWindow* RootWindow; // Point to ourself or first ancestor that is not a child window. - ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive - // color when this window is active. - ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. - - ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This - // could probably be made implicit if we kept g.Windows sorted by last focused - // including child window.) - ImGuiID NavLastIds[2]; // Last known NavId for this window, per layer (0/1) - ImRect NavRectRel[2]; // Reference rectangle, in window relative space - - // Navigation / Focus - // FIXME-NAV: Merge all this with the new Nav system, at least the request variables should be moved to ImGuiContext - int FocusIdxAllCounter; // Start at -1 and increase as assigned via FocusItemRegister() - int FocusIdxTabCounter; // (same, but only count widgets which you can Tab through) - int FocusIdxAllRequestCurrent; // Item being requested for focus - int FocusIdxTabRequestCurrent; // Tab-able item being requested for focus - int FocusIdxAllRequestNext; // Item being requested for focus, for next update (relies on layout to be stable between - // the frame pressing TAB and the next frame) - int FocusIdxTabRequestNext; // " - - public: - ImGuiWindow(ImGuiContext* context, const char* name); - ~ImGuiWindow(); - - ImGuiID GetID(const char* str, const char* str_end = NULL); - ImGuiID GetID(const void* ptr); - ImGuiID GetIDNoKeepAlive(const char* str, const char* str_end = NULL); - ImGuiID GetIDNoKeepAlive(const void* ptr); - ImGuiID GetIDFromRectangle(const ImRect& r_abs); - - // We don't use g.FontSize because the window may be != g.CurrentWidow. - ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } - float CalcFontSize() const { return GImGui->FontBaseSize * FontWindowScale; } - float TitleBarHeight() const - { - return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f; - } - ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); } - float MenuBarHeight() const - { - return (Flags & ImGuiWindowFlags_MenuBar) - ? DC.MenuBarOffset.y + CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f - : 0.0f; - } - ImRect MenuBarRect() const - { - float y1 = Pos.y + TitleBarHeight(); - return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight()); - } -}; - -// Backup and restore just enough data to be able to use IsItemHovered() on item A after another B in the same window -// has overwritten the data. -struct ImGuiItemHoveredDataBackup { - ImGuiID LastItemId; - ImGuiItemStatusFlags LastItemStatusFlags; - ImRect LastItemRect; - ImRect LastItemDisplayRect; - - ImGuiItemHoveredDataBackup() { Backup(); } - void Backup() - { - ImGuiWindow* window = GImGui->CurrentWindow; - LastItemId = window->DC.LastItemId; - LastItemStatusFlags = window->DC.LastItemStatusFlags; - LastItemRect = window->DC.LastItemRect; - LastItemDisplayRect = window->DC.LastItemDisplayRect; - } - void Restore() const - { - ImGuiWindow* window = GImGui->CurrentWindow; - window->DC.LastItemId = LastItemId; - window->DC.LastItemStatusFlags = LastItemStatusFlags; - window->DC.LastItemRect = LastItemRect; - window->DC.LastItemDisplayRect = LastItemDisplayRect; - } -}; - -//----------------------------------------------------------------------------- -// Internal API -// No guarantee of forward compatibility here. -//----------------------------------------------------------------------------- - -namespace ImGui -{ -// We should always have a CurrentWindow in the stack (there is an implicit "Debug" window) -// If this ever crash because g.CurrentWindow is NULL it means that either -// - ImGui::NewFrame() has never been called, which is illegal. -// - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), -// which is also illegal. -inline ImGuiWindow* GetCurrentWindowRead() -{ - ImGuiContext& g = *GImGui; - return g.CurrentWindow; -} -inline ImGuiWindow* GetCurrentWindow() -{ - ImGuiContext& g = *GImGui; - g.CurrentWindow->WriteAccessed = true; - return g.CurrentWindow; -} -IMGUI_API ImGuiWindow* FindWindowByName(const char* name); -IMGUI_API void FocusWindow(ImGuiWindow* window); -IMGUI_API void FocusFrontMostActiveWindowIgnoringOne(ImGuiWindow* ignore_window); -IMGUI_API void BringWindowToFront(ImGuiWindow* window); -IMGUI_API void BringWindowToBack(ImGuiWindow* window); -IMGUI_API void UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window); -IMGUI_API ImVec2 CalcWindowExpectedSize(ImGuiWindow* window); -IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent); -IMGUI_API bool IsWindowNavFocusable(ImGuiWindow* window); -IMGUI_API void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x); -IMGUI_API void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y); -IMGUI_API ImRect GetWindowAllowedExtentRect(ImGuiWindow* window); -IMGUI_API void SetCurrentFont(ImFont* font); -inline ImFont* GetDefaultFont() -{ - ImGuiContext& g = *GImGui; - return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; -} - -// Init -IMGUI_API void Initialize(ImGuiContext* context); -IMGUI_API void Shutdown(ImGuiContext* context); // Since 1.60 this is a _private_ function. You can call - // DestroyContext() to destroy the context created by CreateContext(). - -// NewFrame -IMGUI_API void UpdateHoveredWindowAndCaptureFlags(); -IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); -IMGUI_API void UpdateMouseMovingWindow(); - -// Settings -IMGUI_API void MarkIniSettingsDirty(); -IMGUI_API void MarkIniSettingsDirty(ImGuiWindow* window); -IMGUI_API ImGuiWindowSettings* CreateNewWindowSettings(const char* name); -IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id); -IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); - -// Basic Accessors -inline ImGuiID GetItemID() -{ - ImGuiContext& g = *GImGui; - return g.CurrentWindow->DC.LastItemId; -} -inline ImGuiID GetActiveID() -{ - ImGuiContext& g = *GImGui; - return g.ActiveId; -} -inline ImGuiID GetFocusID() -{ - ImGuiContext& g = *GImGui; - return g.NavId; -} -IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); -IMGUI_API void SetFocusID(ImGuiID id, ImGuiWindow* window); -IMGUI_API void ClearActiveID(); -IMGUI_API ImGuiID GetHoveredID(); -IMGUI_API void SetHoveredID(ImGuiID id); -IMGUI_API void KeepAliveID(ImGuiID id); -IMGUI_API void MarkItemEdited(ImGuiID id); - -// Basic Helpers for widget code -IMGUI_API void ItemSize(const ImVec2& size, float text_offset_y = 0.0f); -IMGUI_API void ItemSize(const ImRect& bb, float text_offset_y = 0.0f); -IMGUI_API bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL); -IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id); -IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged); -IMGUI_API bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id, - bool tab_stop = true); // Return true if focus is requested -IMGUI_API void FocusableItemUnregister(ImGuiWindow* window); -IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_x, float default_y); -IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); -IMGUI_API void PushMultiItemsWidths(int components, float width_full = 0.0f); -IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); -IMGUI_API void PopItemFlag(); - -// Popups, Modals, Tooltips -IMGUI_API void OpenPopupEx(ImGuiID id); -IMGUI_API void ClosePopup(ImGuiID id); -IMGUI_API void ClosePopupToLevel(int remaining); -IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window); -IMGUI_API bool IsPopupOpen(ImGuiID id); -IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags); -IMGUI_API void BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip = true); -IMGUI_API ImGuiWindow* GetFrontMostPopupModal(); -IMGUI_API ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window); -IMGUI_API ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, - const ImRect& r_outer, const ImRect& r_avoid, - ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default); - -// Navigation -IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); -IMGUI_API bool NavMoveRequestButNoResultYet(); -IMGUI_API void NavMoveRequestCancel(); -IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, - ImGuiNavMoveFlags move_flags); -IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); -IMGUI_API float GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode); -IMGUI_API ImVec2 GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, - float slow_factor = 0.0f, float fast_factor = 0.0f); -IMGUI_API int CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate); -IMGUI_API void ActivateItem( - ImGuiID id); // Remotely activate a button, checkbox, tree node etc. given its unique ID. activation is queued and - // processed on the next frame when the item is encountered again. -IMGUI_API void SetNavID(ImGuiID id, int nav_layer); -IMGUI_API void SetNavIDWithRectRel(ImGuiID id, int nav_layer, const ImRect& rect_rel); - -// Inputs -inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) -{ - const int key_index = GImGui->IO.KeyMap[key]; - return (key_index >= 0) ? IsKeyPressed(key_index, repeat) : false; -} -inline bool IsNavInputDown(ImGuiNavInput n) { return GImGui->IO.NavInputs[n] > 0.0f; } -inline bool IsNavInputPressed(ImGuiNavInput n, ImGuiInputReadMode mode) { return GetNavInputAmount(n, mode) > 0.0f; } -inline bool IsNavInputPressedAnyOfTwo(ImGuiNavInput n1, ImGuiNavInput n2, ImGuiInputReadMode mode) -{ - return (GetNavInputAmount(n1, mode) + GetNavInputAmount(n2, mode)) > 0.0f; -} - -// Drag and Drop -IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); -IMGUI_API void ClearDragDrop(); -IMGUI_API bool IsDragDropPayloadBeingAccepted(); - -// New Columns API (FIXME-WIP) -IMGUI_API void BeginColumns(const char* str_id, int count, - ImGuiColumnsFlags flags = 0); // setup number of columns. use an identifier to distinguish - // multiple column sets. close with EndColumns(). -IMGUI_API void EndColumns(); // close columns -IMGUI_API void PushColumnClipRect(int column_index = -1); - -// Render helpers -// AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND -// BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. NB: All position are in absolute pixels -// coordinates (we are never using window coordinates internally) -IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); -IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); -IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, - const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), - const ImRect* clip_rect = NULL); -IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); -IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); -IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, - ImVec2 grid_off, float rounding = 0.0f, - int rounding_corners_flags = ~0); -IMGUI_API void RenderArrow(ImVec2 pos, ImGuiDir dir, float scale = 1.0f); -IMGUI_API void RenderBullet(ImVec2 pos); -IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col, float sz); -IMGUI_API void RenderNavHighlight( - const ImRect& bb, ImGuiID id, - ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight -IMGUI_API const char* FindRenderedTextEnd( - const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. -IMGUI_API void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL); - -// Render helpers (those functions don't access any ImGui state!) -IMGUI_API void RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, - ImGuiMouseCursor mouse_cursor = ImGuiMouseCursor_Arrow); -IMGUI_API void RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col); -IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, - float x_end_norm, float rounding); - -// Widgets -IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); -IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos, float radius); -IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos); -IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags); -IMGUI_API void Scrollbar(ImGuiLayoutType direction); -IMGUI_API void VerticalSeparator(); // Vertical separator, for menu bars (use current line height). Not exposed because - // it is misleading and it doesn't have an effect on regular layout. - -// Widgets low-level behaviors -IMGUI_API bool ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, - ImGuiButtonFlags flags = 0); -IMGUI_API bool DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, - const void* v_max, const char* format, float power); -IMGUI_API bool SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, - const void* v_max, const char* format, float power, ImGuiSliderFlags flags, - ImRect* out_grab_bb); -IMGUI_API bool SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, - float min_size1, float min_size2, float hover_extend = 0.0f, - float hover_visibility_delay = 0.0f); -IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); -IMGUI_API bool TreeNodeBehaviorIsOpen( - ImGuiID id, - ImGuiTreeNodeFlags flags = 0); // Consume previous SetNextTreeNodeOpened() data, if any. May return true when logging -IMGUI_API void TreePushRawID(ImGuiID id); - -// Template functions are instantiated in imgui_widgets.cpp for a finite number of types. -// To use them externally (for custom widget) you may need an "extern template" statement in your code in order to link -// to existing instances and silence Clang warnings (see #2036). e.g. " extern template IMGUI_API float -// RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, float v); " -template -IMGUI_API bool DragBehaviorT(ImGuiDataType data_type, T* v, float v_speed, const T v_min, const T v_max, - const char* format, float power); -template -IMGUI_API bool SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, T* v, const T v_min, - const T v_max, const char* format, float power, ImGuiSliderFlags flags, - ImRect* out_grab_bb); -template -IMGUI_API float SliderCalcRatioFromValueT(ImGuiDataType data_type, T v, T v_min, T v_max, float power, - float linear_zero_pos); -template -IMGUI_API T RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, T v); - -// InputText -IMGUI_API bool InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, - ImGuiInputTextFlags flags, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); -IMGUI_API bool InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, - void* data_ptr, const char* format); - -// Color -IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); -IMGUI_API void ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags); -IMGUI_API void ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags); - -// Plot -IMGUI_API void PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), - void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, - float scale_max, ImVec2 graph_size); - -// Shade functions (write over already created vertices) -IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, - ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); -IMGUI_API void ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, - const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp); - -} // namespace ImGui - -// ImFontAtlas internals -IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildRegisterDefaultCustomRects(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, - float descent); -IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* spc); -IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); -IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, - int w, int h, int stride); - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -#ifdef _MSC_VER -#pragma warning(pop) -#endif diff --git a/Framework/src/imgui/imgui_stl.cpp b/Framework/src/imgui/imgui_stl.cpp deleted file mode 100644 index 014f0c1eeb..0000000000 --- a/Framework/src/imgui/imgui_stl.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// imgui_stl.cpp -// Wrappers for C++ standard library (STL) types (std::string, etc.) -// This is also an example of how you may wrap your own similar types. - -// Compatibility: -// - std::string support is only guaranteed to work from C++11. -// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture) - -#include "imgui.h" -#include "imgui_stl.h" - -struct InputTextCallback_UserData -{ - std::string* Str; - ImGuiInputTextCallback ChainCallback; - void* ChainCallbackUserData; -}; - -static int InputTextCallback(ImGuiInputTextCallbackData* data) -{ - InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData; - if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) - { - // Resize string callback - std::string* str = user_data->Str; - IM_ASSERT(data->Buf == str->c_str()); - str->resize(data->BufTextLen); - data->Buf = (char*)str->c_str(); - } - else if (user_data->ChainCallback) - { - // Forward to user callback, if any - data->UserData = user_data->ChainCallbackUserData; - return user_data->ChainCallback(data); - } - return 0; -} - -bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) -{ - IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); - flags |= ImGuiInputTextFlags_CallbackResize; - - InputTextCallback_UserData cb_user_data; - cb_user_data.Str = str; - cb_user_data.ChainCallback = callback; - cb_user_data.ChainCallbackUserData = user_data; - return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data); -} - -bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) -{ - IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); - flags |= ImGuiInputTextFlags_CallbackResize; - - InputTextCallback_UserData cb_user_data; - cb_user_data.Str = str; - cb_user_data.ChainCallback = callback; - cb_user_data.ChainCallbackUserData = user_data; - return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data); -} diff --git a/Framework/src/imgui/imgui_stl.h b/Framework/src/imgui/imgui_stl.h deleted file mode 100644 index 33ac953534..0000000000 --- a/Framework/src/imgui/imgui_stl.h +++ /dev/null @@ -1,25 +0,0 @@ -// imgui_stl.h -// Wrappers for C++ standard library (STL) types (std::string, etc.) -// This is also an example of how you may wrap your own similar types. - -// Compatibility: -// - std::string support is only guaranteed to work from C++11. -// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture) - -// Changelog: -// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string - -#pragma once - -#include - -namespace ImGui -{ -// ImGui::InputText() with std::string -// Because text input needs dynamic resizing, we need to setup a callback to grow the capacity -IMGUI_API bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, - ImGuiInputTextCallback callback = NULL, void* user_data = NULL); -IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), - ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, - void* user_data = NULL); -} // namespace ImGui diff --git a/Framework/src/imgui/imgui_widgets.cpp b/Framework/src/imgui/imgui_widgets.cpp deleted file mode 100644 index 110c4f8646..0000000000 --- a/Framework/src/imgui/imgui_widgets.cpp +++ /dev/null @@ -1,5725 +0,0 @@ -// dear imgui, v1.65 -// (widgets code) - -/* - -Index of this file: - -// [SECTION] Forward Declarations -// [SECTION] Widgets: Text, etc. -// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.) -// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.) -// [SECTION] Widgets: ComboBox -// [SECTION] Data Type and Data Formatting Helpers -// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. -// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. -// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. -// [SECTION] Widgets: InputText, InputTextMultiline -// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. -// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. -// [SECTION] Widgets: Selectable -// [SECTION] Widgets: ListBox -// [SECTION] Widgets: PlotLines, PlotHistogram -// [SECTION] Widgets: Value helpers -// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc. - -*/ - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include "imgui.h" -#ifndef IMGUI_DEFINE_MATH_OPERATORS -#define IMGUI_DEFINE_MATH_OPERATORS -#endif -#include "imgui_internal.h" - -#include // toupper, isprint -#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier -#include // intptr_t -#else -#include // intptr_t -#endif - -// Visual Studio warnings -#ifdef _MSC_VER -#pragma warning (disable: 4127) // condition expression is constant -#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen -#endif - -// Clang/GCC warnings with -Weverything -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. -#pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness // -#elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked -#if __GNUC__ >= 8 -#pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead -#endif -#endif - -//------------------------------------------------------------------------- -// Data -//------------------------------------------------------------------------- - -// Those MIN/MAX values are not define because we need to point to them -static const ImS32 IM_S32_MIN = INT_MIN; // (-2147483647 - 1), (0x80000000); -static const ImS32 IM_S32_MAX = INT_MAX; // (2147483647), (0x7FFFFFFF) -static const ImU32 IM_U32_MIN = 0; -static const ImU32 IM_U32_MAX = UINT_MAX; // (0xFFFFFFFF) -#ifdef LLONG_MIN -static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll); -static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll); -#else -static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1; -static const ImS64 IM_S64_MAX = 9223372036854775807LL; -#endif -static const ImU64 IM_U64_MIN = 0; -#ifdef ULLONG_MAX -static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull); -#else -static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); -#endif - -//------------------------------------------------------------------------- -// [SECTION] Forward Declarations -//------------------------------------------------------------------------- - -// Data Type helpers -static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format); -static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2); -static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format); - -// For InputTextEx() -static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data); -static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); -static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); - -//------------------------------------------------------------------------- -// [SECTION] Widgets: Text, etc. -//------------------------------------------------------------------------- -// - TextUnformatted() -// - Text() -// - TextV() -// - TextColored() -// - TextColoredV() -// - TextDisabled() -// - TextDisabledV() -// - TextWrapped() -// - TextWrappedV() -// - LabelText() -// - LabelTextV() -// - BulletText() -// - BulletTextV() -//------------------------------------------------------------------------- - -void ImGui::TextUnformatted(const char* text, const char* text_end) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext& g = *GImGui; - IM_ASSERT(text != NULL); - const char* text_begin = text; - if (text_end == NULL) - text_end = text + strlen(text); // FIXME-OPT - - const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset); - const float wrap_pos_x = window->DC.TextWrapPos; - const bool wrap_enabled = wrap_pos_x >= 0.0f; - if (text_end - text > 2000 && !wrap_enabled) - { - // Long text! - // Perform manual coarse clipping to optimize for long multi-line text - // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. - // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line. - const char* line = text; - const float line_height = GetTextLineHeight(); - const ImRect clip_rect = window->ClipRect; - ImVec2 text_size(0,0); - - if (text_pos.y <= clip_rect.Max.y) - { - ImVec2 pos = text_pos; - - // Lines to skip (can't skip when logging text) - if (!g.LogEnabled) - { - int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height); - if (lines_skippable > 0) - { - int lines_skipped = 0; - while (line < text_end && lines_skipped < lines_skippable) - { - const char* line_end = strchr(line, '\n'); - if (!line_end) - line_end = text_end; - line = line_end + 1; - lines_skipped++; - } - pos.y += lines_skipped * line_height; - } - } - - // Lines to render - if (line < text_end) - { - ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height)); - while (line < text_end) - { - const char* line_end = strchr(line, '\n'); - if (IsClippedEx(line_rect, 0, false)) - break; - - const ImVec2 line_size = CalcTextSize(line, line_end, false); - text_size.x = ImMax(text_size.x, line_size.x); - RenderText(pos, line, line_end, false); - if (!line_end) - line_end = text_end; - line = line_end + 1; - line_rect.Min.y += line_height; - line_rect.Max.y += line_height; - pos.y += line_height; - } - - // Count remaining lines - int lines_skipped = 0; - while (line < text_end) - { - const char* line_end = strchr(line, '\n'); - if (!line_end) - line_end = text_end; - line = line_end + 1; - lines_skipped++; - } - pos.y += lines_skipped * line_height; - } - - text_size.y += (pos - text_pos).y; - } - - ImRect bb(text_pos, text_pos + text_size); - ItemSize(bb); - ItemAdd(bb, 0); - } - else - { - const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; - const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); - - // Account of baseline offset - ImRect bb(text_pos, text_pos + text_size); - ItemSize(text_size); - if (!ItemAdd(bb, 0)) - return; - - // Render (we don't hide text after ## in this end-user function) - RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); - } -} - -void ImGui::Text(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - TextV(fmt, args); - va_end(args); -} - -void ImGui::TextV(const char* fmt, va_list args) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext& g = *GImGui; - const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - TextUnformatted(g.TempBuffer, text_end); -} - -void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - TextColoredV(col, fmt, args); - va_end(args); -} - -void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args) -{ - PushStyleColor(ImGuiCol_Text, col); - TextV(fmt, args); - PopStyleColor(); -} - -void ImGui::TextDisabled(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - TextDisabledV(fmt, args); - va_end(args); -} - -void ImGui::TextDisabledV(const char* fmt, va_list args) -{ - PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]); - TextV(fmt, args); - PopStyleColor(); -} - -void ImGui::TextWrapped(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - TextWrappedV(fmt, args); - va_end(args); -} - -void ImGui::TextWrappedV(const char* fmt, va_list args) -{ - bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set - if (need_wrap) PushTextWrapPos(0.0f); - TextV(fmt, args); - if (need_wrap) PopTextWrapPos(); -} - -void ImGui::LabelText(const char* label, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - LabelTextV(label, fmt, args); - va_end(args); -} - -// Add a label+text combo aligned to other label+value widgets -void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const float w = CalcItemWidth(); - - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2)); - const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size); - ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, 0)) - return; - - // Render - const char* value_text_begin = &g.TempBuffer[0]; - const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f)); - if (label_size.x > 0.0f) - RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); -} - -void ImGui::BulletText(const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - BulletTextV(fmt, args); - va_end(args); -} - -// Text with a little bullet aligned to the typical tree node. -void ImGui::BulletTextV(const char* fmt, va_list args) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - - const char* text_begin = g.TempBuffer; - const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); - const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it - const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding - ItemSize(bb); - if (!ItemAdd(bb, 0)) - return; - - // Render - RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); - RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false); -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: Main -//------------------------------------------------------------------------- -// - ButtonBehavior() [Internal] -// - Button() -// - SmallButton() -// - InvisibleButton() -// - ArrowButton() -// - CloseButton() [Internal] -// - CollapseButton() [Internal] -// - Scrollbar() [Internal] -// - Image() -// - ImageButton() -// - Checkbox() -// - CheckboxFlags() -// - RadioButton() -// - ProgressBar() -// - Bullet() -//------------------------------------------------------------------------- - -bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - - if (flags & ImGuiButtonFlags_Disabled) - { - if (out_hovered) *out_hovered = false; - if (out_held) *out_held = false; - if (g.ActiveId == id) ClearActiveID(); - return false; - } - - // Default behavior requires click+release on same spot - if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0) - flags |= ImGuiButtonFlags_PressedOnClickRelease; - - ImGuiWindow* backup_hovered_window = g.HoveredWindow; - if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) - g.HoveredWindow = window; - - bool pressed = false; - bool hovered = ItemHoverable(bb, id); - - // Drag source doesn't report as hovered - if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover)) - hovered = false; - - // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button - if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) - if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - { - hovered = true; - SetHoveredID(id); - if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy - { - pressed = true; - FocusWindow(window); - } - } - - if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window) - g.HoveredWindow = backup_hovered_window; - - // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one. - if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0)) - hovered = false; - - // Mouse - if (hovered) - { - if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) - { - // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat - // PressedOnClickRelease | * | .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds - // PressedOnClick | | .. - // PressedOnRelease | | .. (NOT on release) - // PressedOnDoubleClick | | .. - // FIXME-NAV: We don't honor those different behaviors. - if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0]) - { - SetActiveID(id, window); - if (!(flags & ImGuiButtonFlags_NoNavFocus)) - SetFocusID(id, window); - FocusWindow(window); - } - if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0])) - { - pressed = true; - if (flags & ImGuiButtonFlags_NoHoldingActiveID) - ClearActiveID(); - else - SetActiveID(id, window); // Hold on ID - FocusWindow(window); - } - if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0]) - { - if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps - pressed = true; - ClearActiveID(); - } - - // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above). - // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings. - if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true)) - pressed = true; - } - - if (pressed) - g.NavDisableHighlight = true; - } - - // Gamepad/Keyboard navigation - // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. - if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) - hovered = true; - - if (g.NavActivateDownId == id) - { - bool nav_activated_by_code = (g.NavActivateId == id); - bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed); - if (nav_activated_by_code || nav_activated_by_inputs) - pressed = true; - if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id) - { - // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. - g.NavActivateId = id; // This is so SetActiveId assign a Nav source - SetActiveID(id, window); - if (!(flags & ImGuiButtonFlags_NoNavFocus)) - SetFocusID(id, window); - g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); - } - } - - bool held = false; - if (g.ActiveId == id) - { - if (g.ActiveIdSource == ImGuiInputSource_Mouse) - { - if (g.ActiveIdIsJustActivated) - g.ActiveIdClickOffset = g.IO.MousePos - bb.Min; - if (g.IO.MouseDown[0]) - { - held = true; - } - else - { - if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease)) - if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps - if (!g.DragDropActive) - pressed = true; - ClearActiveID(); - } - if (!(flags & ImGuiButtonFlags_NoNavFocus)) - g.NavDisableHighlight = true; - } - else if (g.ActiveIdSource == ImGuiInputSource_Nav) - { - if (g.NavActivateDownId != id) - ClearActiveID(); - } - } - - if (out_hovered) *out_hovered = hovered; - if (out_held) *out_held = held; - - return pressed; -} - -bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - - ImVec2 pos = window->DC.CursorPos; - if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) - pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; - ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f); - - const ImRect bb(pos, pos + size); - ItemSize(bb, style.FramePadding.y); - if (!ItemAdd(bb, id)) - return false; - - if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) - flags |= ImGuiButtonFlags_Repeat; - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); - if (pressed) - MarkItemEdited(id); - - // Render - const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - RenderNavHighlight(bb, id); - RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); - RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); - - // Automatically close popups - //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) - // CloseCurrentPopup(); - - return pressed; -} - -bool ImGui::Button(const char* label, const ImVec2& size_arg) -{ - return ButtonEx(label, size_arg, 0); -} - -// Small buttons fits within text without additional vertical spacing. -bool ImGui::SmallButton(const char* label) -{ - ImGuiContext& g = *GImGui; - float backup_padding_y = g.Style.FramePadding.y; - g.Style.FramePadding.y = 0.0f; - bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine); - g.Style.FramePadding.y = backup_padding_y; - return pressed; -} - -// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack. -// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id) -bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size. - IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f); - - const ImGuiID id = window->GetID(str_id); - ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); - ItemSize(bb); - if (!ItemAdd(bb, id)) - return false; - - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held); - - return pressed; -} - -bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiID id = window->GetID(str_id); - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); - const float default_size = GetFrameHeight(); - ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); - if (!ItemAdd(bb, id)) - return false; - - if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) - flags |= ImGuiButtonFlags_Repeat; - - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); - - // Render - const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - RenderNavHighlight(bb, id); - RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding); - RenderArrow(bb.Min + ImVec2(ImMax(0.0f, size.x - g.FontSize - g.Style.FramePadding.x), ImMax(0.0f, size.y - g.FontSize - g.Style.FramePadding.y)), dir); - - return pressed; -} - -bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) -{ - float sz = GetFrameHeight(); - return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0); -} - -// Button to close a window -bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window. - // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible). - const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius)); - bool is_clipped = !ItemAdd(bb, id); - - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held); - if (is_clipped) - return pressed; - - // Render - ImVec2 center = bb.GetCenter(); - if (hovered) - window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9); - - float cross_extent = (radius * 0.7071f) - 1.0f; - ImU32 cross_col = GetColorU32(ImGuiCol_Text); - center -= ImVec2(0.5f, 0.5f); - window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f); - window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f); - - return pressed; -} - -bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f); - ItemAdd(bb, id); - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); - - ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - if (hovered || held) - window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9); - RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); - - // Switch to moving the window after mouse is moved beyond the initial drag threshold - if (IsItemActive() && IsMouseDragging()) - StartMouseMovingWindow(window); - - return pressed; -} - -// Vertical/Horizontal scrollbar -// The entire piece of code below is rather confusing because: -// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) -// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar -// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. -void ImGui::Scrollbar(ImGuiLayoutType direction) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - const bool horizontal = (direction == ImGuiLayoutType_Horizontal); - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY"); - - // Render background - bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX); - float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f; - const ImRect window_rect = window->Rect(); - const float border_size = window->WindowBorderSize; - ImRect bb = horizontal - ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size) - : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size); - if (!horizontal) - bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f); - if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f) - return; - - int window_rounding_corners; - if (horizontal) - window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); - else - window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight); - window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners); - bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f))); - - // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) - float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight(); - float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y; - float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w; - float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y; - - // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) - // But we maintain a minimum size in pixel to allow for the user to still aim inside. - IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. - const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f); - const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v); - const float grab_h_norm = grab_h_pixels / scrollbar_size_v; - - // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). - bool held = false; - bool hovered = false; - const bool previously_held = (g.ActiveId == id); - ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus); - - float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v); - float scroll_ratio = ImSaturate(scroll_v / scroll_max); - float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; - if (held && grab_h_norm < 1.0f) - { - float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y; - float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; - float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y; - - // Click position in scrollbar normalized space (0.0f->1.0f) - const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); - SetHoveredID(id); - - bool seek_absolute = false; - if (!previously_held) - { - // On initial click calculate the distance between mouse and the center of the grab - if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm) - { - *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; - } - else - { - seek_absolute = true; - *click_delta_to_grab_center_v = 0.0f; - } - } - - // Apply scroll - // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position - const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm)); - scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v)); - if (horizontal) - window->Scroll.x = scroll_v; - else - window->Scroll.y = scroll_v; - - // Update values for rendering - scroll_ratio = ImSaturate(scroll_v / scroll_max); - grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; - - // Update distance to grab now that we have seeked and saturated - if (seek_absolute) - *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f; - } - - // Render - const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab); - ImRect grab_rect; - if (horizontal) - grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y); - else - grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y)); - window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding); -} - -void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); - if (border_col.w > 0.0f) - bb.Max += ImVec2(2, 2); - ItemSize(bb); - if (!ItemAdd(bb, 0)) - return; - - if (border_col.w > 0.0f) - { - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f); - window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col)); - } - else - { - window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col)); - } -} - -// frame_padding < 0: uses FramePadding from style (default) -// frame_padding = 0: no framing -// frame_padding > 0: set framing size -// The color used are the button colors. -bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - - // Default to using texture ID as ID. User can still push string/integer prefixes. - // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. - PushID((void*)user_texture_id); - const ImGuiID id = window->GetID("#image"); - PopID(); - - const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding; - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); - const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size); - ItemSize(bb); - if (!ItemAdd(bb, id)) - return false; - - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held); - - // Render - const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - RenderNavHighlight(bb, id); - RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding)); - if (bg_col.w > 0.0f) - window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col)); - window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col)); - - return pressed; -} - -bool ImGui::Checkbox(const char* label, bool* v) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - - const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice - ItemSize(check_bb, style.FramePadding.y); - - ImRect total_bb = check_bb; - if (label_size.x > 0) - SameLine(0, style.ItemInnerSpacing.x); - const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size); - if (label_size.x > 0) - { - ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); - total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); - } - - if (!ItemAdd(total_bb, id)) - return false; - - bool hovered, held; - bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); - if (pressed) - { - *v = !(*v); - MarkItemEdited(id); - } - - RenderNavHighlight(total_bb, id); - RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); - if (*v) - { - const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); - const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); - RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f); - } - - if (g.LogEnabled) - LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]"); - if (label_size.x > 0.0f) - RenderText(text_bb.Min, label); - - return pressed; -} - -bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value) -{ - bool v = ((*flags & flags_value) == flags_value); - bool pressed = Checkbox(label, &v); - if (pressed) - { - if (v) - *flags |= flags_value; - else - *flags &= ~flags_value; - } - - return pressed; -} - -bool ImGui::RadioButton(const char* label, bool active) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - - const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1)); - ItemSize(check_bb, style.FramePadding.y); - - ImRect total_bb = check_bb; - if (label_size.x > 0) - SameLine(0, style.ItemInnerSpacing.x); - const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size); - if (label_size.x > 0) - { - ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y); - total_bb.Add(text_bb); - } - - if (!ItemAdd(total_bb, id)) - return false; - - ImVec2 center = check_bb.GetCenter(); - center.x = (float)(int)center.x + 0.5f; - center.y = (float)(int)center.y + 0.5f; - const float radius = check_bb.GetHeight() * 0.5f; - - bool hovered, held; - bool pressed = ButtonBehavior(total_bb, id, &hovered, &held); - if (pressed) - MarkItemEdited(id); - - RenderNavHighlight(total_bb, id); - window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16); - if (active) - { - const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight()); - const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f)); - window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16); - } - - if (style.FrameBorderSize > 0.0f) - { - window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize); - window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize); - } - - if (g.LogEnabled) - LogRenderedText(&text_bb.Min, active ? "(x)" : "( )"); - if (label_size.x > 0.0f) - RenderText(text_bb.Min, label); - - return pressed; -} - -bool ImGui::RadioButton(const char* label, int* v, int v_button) -{ - const bool pressed = RadioButton(label, *v == v_button); - if (pressed) - *v = v_button; - return pressed; -} - -// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size -void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - - ImVec2 pos = window->DC.CursorPos; - ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f)); - ItemSize(bb, style.FramePadding.y); - if (!ItemAdd(bb, 0)) - return; - - // Render - fraction = ImSaturate(fraction); - RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); - bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); - const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); - RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); - - // Default displaying the fraction as percentage string, but user can override it - char overlay_buf[32]; - if (!overlay) - { - ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f); - overlay = overlay_buf; - } - - ImVec2 overlay_size = CalcTextSize(overlay, NULL); - if (overlay_size.x > 0.0f) - RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb); -} - -void ImGui::Bullet() -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const float line_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y*2), g.FontSize); - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height)); - ItemSize(bb); - if (!ItemAdd(bb, 0)) - { - SameLine(0, style.FramePadding.x*2); - return; - } - - // Render and stay on same line - RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f)); - SameLine(0, style.FramePadding.x*2); -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: Low-level Layout helpers -//------------------------------------------------------------------------- -// - Spacing() -// - Dummy() -// - NewLine() -// - AlignTextToFramePadding() -// - Separator() -// - VerticalSeparator() [Internal] -// - SplitterBehavior() [Internal] -//------------------------------------------------------------------------- - -void ImGui::Spacing() -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - ItemSize(ImVec2(0,0)); -} - -void ImGui::Dummy(const ImVec2& size) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); - ItemSize(bb); - ItemAdd(bb, 0); -} - -void ImGui::NewLine() -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext& g = *GImGui; - const ImGuiLayoutType backup_layout_type = window->DC.LayoutType; - window->DC.LayoutType = ImGuiLayoutType_Vertical; - if (window->DC.CurrentLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height. - ItemSize(ImVec2(0,0)); - else - ItemSize(ImVec2(0.0f, g.FontSize)); - window->DC.LayoutType = backup_layout_type; -} - -void ImGui::AlignTextToFramePadding() -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext& g = *GImGui; - window->DC.CurrentLineSize.y = ImMax(window->DC.CurrentLineSize.y, g.FontSize + g.Style.FramePadding.y * 2); - window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y); -} - -// Horizontal/vertical separating line -void ImGui::Separator() -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - ImGuiContext& g = *GImGui; - - // Those flags should eventually be overridable by the user - ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal; - IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected - if (flags & ImGuiSeparatorFlags_Vertical) - { - VerticalSeparator(); - return; - } - - // Horizontal Separator - if (window->DC.ColumnsSet) - PopClipRect(); - - float x1 = window->Pos.x; - float x2 = window->Pos.x + window->Size.x; - if (!window->DC.GroupStack.empty()) - x1 += window->DC.Indent.x; - - const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f)); - ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout. - if (!ItemAdd(bb, 0)) - { - if (window->DC.ColumnsSet) - PushColumnClipRect(); - return; - } - - window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator)); - - if (g.LogEnabled) - LogRenderedText(NULL, IM_NEWLINE "--------------------------------"); - - if (window->DC.ColumnsSet) - { - PushColumnClipRect(); - window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y; - } -} - -void ImGui::VerticalSeparator() -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - ImGuiContext& g = *GImGui; - - float y1 = window->DC.CursorPos.y; - float y2 = window->DC.CursorPos.y + window->DC.CurrentLineSize.y; - const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2)); - ItemSize(ImVec2(bb.GetWidth(), 0.0f)); - if (!ItemAdd(bb, 0)) - return; - - window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator)); - if (g.LogEnabled) - LogText(" |"); -} - -// Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise. -bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; - window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus; - bool item_add = ItemAdd(bb, id); - window->DC.ItemFlags = item_flags_backup; - if (!item_add) - return false; - - bool hovered, held; - ImRect bb_interact = bb; - bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f)); - ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap); - if (g.ActiveId != id) - SetItemAllowOverlap(); - - if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay)) - SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW); - - ImRect bb_render = bb; - if (held) - { - ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min; - float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x; - - // Minimum pane size - float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1); - float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2); - if (mouse_delta < -size_1_maximum_delta) - mouse_delta = -size_1_maximum_delta; - if (mouse_delta > size_2_maximum_delta) - mouse_delta = size_2_maximum_delta; - - // Apply resize - if (mouse_delta != 0.0f) - { - if (mouse_delta < 0.0f) - IM_ASSERT(*size1 + mouse_delta >= min_size1); - if (mouse_delta > 0.0f) - IM_ASSERT(*size2 - mouse_delta >= min_size2); - *size1 += mouse_delta; - *size2 -= mouse_delta; - bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta)); - MarkItemEdited(id); - } - } - - // Render - const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); - window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding); - - return held; -} - - -//------------------------------------------------------------------------- -// [SECTION] Widgets: Combo Box -//------------------------------------------------------------------------- -// - BeginCombo() -// - EndCombo() -// - Combo() -//------------------------------------------------------------------------- - -static float CalcMaxPopupHeightFromItemCount(int items_count) -{ - ImGuiContext& g = *GImGui; - if (items_count <= 0) - return FLT_MAX; - return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2); -} - -bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags) -{ - // Always consume the SetNextWindowSizeConstraint() call in our early return paths - ImGuiContext& g = *GImGui; - ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond; - g.NextWindowData.SizeConstraintCond = 0; - - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together - - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - - const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth(); - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); - const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); - ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb)) - return false; - - bool hovered, held; - bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held); - bool popup_open = IsPopupOpen(id); - - const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f)); - const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - RenderNavHighlight(frame_bb, id); - if (!(flags & ImGuiComboFlags_NoPreview)) - window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left); - if (!(flags & ImGuiComboFlags_NoArrowButton)) - { - window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right); - RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down); - } - RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding); - if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) - RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f)); - if (label_size.x > 0) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - - if ((pressed || g.NavActivateId == id) && !popup_open) - { - if (window->DC.NavLayerCurrent == 0) - window->NavLastIds[0] = id; - OpenPopupEx(id); - popup_open = true; - } - - if (!popup_open) - return false; - - if (backup_next_window_size_constraint) - { - g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint; - g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); - } - else - { - if ((flags & ImGuiComboFlags_HeightMask_) == 0) - flags |= ImGuiComboFlags_HeightRegular; - IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one - int popup_max_height_in_items = -1; - if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8; - else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; - else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; - SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); - } - - char name[16]; - ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth - - // Peak into expected window size so we can position it - if (ImGuiWindow* popup_window = FindWindowByName(name)) - if (popup_window->WasActive) - { - ImVec2 size_expected = CalcWindowExpectedSize(popup_window); - if (flags & ImGuiComboFlags_PopupAlignLeft) - popup_window->AutoPosLastDirection = ImGuiDir_Left; - ImRect r_outer = GetWindowAllowedExtentRect(popup_window); - ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox); - SetNextWindowPos(pos); - } - - // Horizontally align ourselves with the framed text - ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; - PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y)); - bool ret = Begin(name, NULL, window_flags); - PopStyleVar(); - if (!ret) - { - EndPopup(); - IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above - return false; - } - return true; -} - -void ImGui::EndCombo() -{ - EndPopup(); -} - -// Getter for the old Combo() API: const char*[] -static bool Items_ArrayGetter(void* data, int idx, const char** out_text) -{ - const char* const* items = (const char* const*)data; - if (out_text) - *out_text = items[idx]; - return true; -} - -// Getter for the old Combo() API: "item1\0item2\0item3\0" -static bool Items_SingleStringGetter(void* data, int idx, const char** out_text) -{ - // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited. - const char* items_separated_by_zeros = (const char*)data; - int items_count = 0; - const char* p = items_separated_by_zeros; - while (*p) - { - if (idx == items_count) - break; - p += strlen(p) + 1; - items_count++; - } - if (!*p) - return false; - if (out_text) - *out_text = p; - return true; -} - -// Old API, prefer using BeginCombo() nowadays if you can. -bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items) -{ - ImGuiContext& g = *GImGui; - - // Call the getter to obtain the preview string which is a parameter to BeginCombo() - const char* preview_value = NULL; - if (*current_item >= 0 && *current_item < items_count) - items_getter(data, *current_item, &preview_value); - - // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. - if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond) - SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); - - if (!BeginCombo(label, preview_value, ImGuiComboFlags_None)) - return false; - - // Display items - // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed) - bool value_changed = false; - for (int i = 0; i < items_count; i++) - { - PushID((void*)(intptr_t)i); - const bool item_selected = (i == *current_item); - const char* item_text; - if (!items_getter(data, i, &item_text)) - item_text = "*Unknown item*"; - if (Selectable(item_text, item_selected)) - { - value_changed = true; - *current_item = i; - } - if (item_selected) - SetItemDefaultFocus(); - PopID(); - } - - EndCombo(); - return value_changed; -} - -// Combo box helper allowing to pass an array of strings. -bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items) -{ - const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items); - return value_changed; -} - -// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0" -bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items) -{ - int items_count = 0; - const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open - while (*p) - { - p += strlen(p) + 1; - items_count++; - } - bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); - return value_changed; -} - -//------------------------------------------------------------------------- -// [SECTION] Data Type and Data Formatting Helpers [Internal] -//------------------------------------------------------------------------- -// - PatchFormatStringFloatToInt() -// - DataTypeFormatString() -// - DataTypeApplyOp() -// - DataTypeApplyOpFromText() -// - GetMinimumStepAtDecimalPrecision -// - RoundScalarWithFormat<>() -//------------------------------------------------------------------------- - -struct ImGuiDataTypeInfo -{ - size_t Size; - const char* PrintFmt; // Unused - const char* ScanFmt; -}; - -static const ImGuiDataTypeInfo GDataTypeInfo[] = -{ - { sizeof(int), "%d", "%d" }, - { sizeof(unsigned int), "%u", "%u" }, -#ifdef _MSC_VER - { sizeof(ImS64), "%I64d","%I64d" }, - { sizeof(ImU64), "%I64u","%I64u" }, -#else - { sizeof(ImS64), "%lld", "%lld" }, - { sizeof(ImU64), "%llu", "%llu" }, -#endif - { sizeof(float), "%f", "%f" }, // float are promoted to double in va_arg - { sizeof(double), "%f", "%lf" }, -}; -IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); - -// FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f". -// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls. -// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?! -static const char* PatchFormatStringFloatToInt(const char* fmt) -{ - if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case. - return "%d"; - const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%) - const char* fmt_end = ImParseFormatFindEnd(fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user). - if (fmt_end > fmt_start && fmt_end[-1] == 'f') - { -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - if (fmt_start == fmt && fmt_end[0] == 0) - return "%d"; - ImGuiContext& g = *GImGui; - ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision. - return g.TempBuffer; -#else - IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d" -#endif - } - return fmt; -} - -static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format) -{ - if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) // Signedness doesn't matter when pushing the argument - return ImFormatString(buf, buf_size, format, *(const ImU32*)data_ptr); - if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) // Signedness doesn't matter when pushing the argument - return ImFormatString(buf, buf_size, format, *(const ImU64*)data_ptr); - if (data_type == ImGuiDataType_Float) - return ImFormatString(buf, buf_size, format, *(const float*)data_ptr); - if (data_type == ImGuiDataType_Double) - return ImFormatString(buf, buf_size, format, *(const double*)data_ptr); - IM_ASSERT(0); - return 0; -} - -// FIXME: Adding support for clamping on boundaries of the data type would be nice. -static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2) -{ - IM_ASSERT(op == '+' || op == '-'); - switch (data_type) - { - case ImGuiDataType_S32: - if (op == '+') *(int*)output = *(const int*)arg1 + *(const int*)arg2; - else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2; - return; - case ImGuiDataType_U32: - if (op == '+') *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2; - else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2; - return; - case ImGuiDataType_S64: - if (op == '+') *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2; - else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2; - return; - case ImGuiDataType_U64: - if (op == '+') *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2; - else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2; - return; - case ImGuiDataType_Float: - if (op == '+') *(float*)output = *(const float*)arg1 + *(const float*)arg2; - else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2; - return; - case ImGuiDataType_Double: - if (op == '+') *(double*)output = *(const double*)arg1 + *(const double*)arg2; - else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2; - return; - case ImGuiDataType_COUNT: break; - } - IM_ASSERT(0); -} - -// User can input math operators (e.g. +100) to edit a numerical values. -// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess.. -static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format) -{ - while (ImCharIsBlankA(*buf)) - buf++; - - // We don't support '-' op because it would conflict with inputing negative value. - // Instead you can use +-100 to subtract from an existing value - char op = buf[0]; - if (op == '+' || op == '*' || op == '/') - { - buf++; - while (ImCharIsBlankA(*buf)) - buf++; - } - else - { - op = 0; - } - if (!buf[0]) - return false; - - // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all. - IM_ASSERT(data_type < ImGuiDataType_COUNT); - int data_backup[2]; - IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup)); - memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size); - - if (format == NULL) - format = GDataTypeInfo[data_type].ScanFmt; - - int arg1i = 0; - if (data_type == ImGuiDataType_S32) - { - int* v = (int*)data_ptr; - int arg0i = *v; - float arg1f = 0.0f; - if (op && sscanf(initial_value_buf, format, &arg0i) < 1) - return false; - // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision - if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract) - else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply - else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide - else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } // Assign constant - } - else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) - { - // Assign constant - // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future. - sscanf(buf, format, data_ptr); - } - else if (data_type == ImGuiDataType_Float) - { - // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in - format = "%f"; - float* v = (float*)data_ptr; - float arg0f = *v, arg1f = 0.0f; - if (op && sscanf(initial_value_buf, format, &arg0f) < 1) - return false; - if (sscanf(buf, format, &arg1f) < 1) - return false; - if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) - else if (op == '*') { *v = arg0f * arg1f; } // Multiply - else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide - else { *v = arg1f; } // Assign constant - } - else if (data_type == ImGuiDataType_Double) - { - format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis - double* v = (double*)data_ptr; - double arg0f = *v, arg1f = 0.0; - if (op && sscanf(initial_value_buf, format, &arg0f) < 1) - return false; - if (sscanf(buf, format, &arg1f) < 1) - return false; - if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract) - else if (op == '*') { *v = arg0f * arg1f; } // Multiply - else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide - else { *v = arg1f; } // Assign constant - } - return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0; -} - -static float GetMinimumStepAtDecimalPrecision(int decimal_precision) -{ - static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; - if (decimal_precision < 0) - return FLT_MIN; - return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision); -} - -template -static const char* ImAtoi(const char* src, TYPE* output) -{ - int negative = 0; - if (*src == '-') { negative = 1; src++; } - if (*src == '+') { src++; } - TYPE v = 0; - while (*src >= '0' && *src <= '9') - v = (v * 10) + (*src++ - '0'); - *output = negative ? -v : v; - return src; -} - -template -TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v) -{ - const char* fmt_start = ImParseFormatFindStart(format); - if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string - return v; - char v_str[64]; - ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v); - const char* p = v_str; - while (*p == ' ') - p++; - if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) - v = (TYPE)ImAtof(p); - else - ImAtoi(p, (SIGNEDTYPE*)&v); - return v; -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc. -//------------------------------------------------------------------------- -// - DragBehaviorT<>() [Internal] -// - DragBehavior() [Internal] -// - DragScalar() -// - DragScalarN() -// - DragFloat() -// - DragFloat2() -// - DragFloat3() -// - DragFloat4() -// - DragFloatRange2() -// - DragInt() -// - DragInt2() -// - DragInt3() -// - DragInt4() -// - DragIntRange2() -//------------------------------------------------------------------------- - -// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) -template -bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power) -{ - ImGuiContext& g = *GImGui; - - // Default tweak speed - bool has_min_max = (v_min != v_max) && (v_max - v_max < FLT_MAX); - if (v_speed == 0.0f && has_min_max) - v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); - - // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings - float adjust_delta = 0.0f; - if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f) - { - adjust_delta = g.IO.MouseDelta.x; - if (g.IO.KeyAlt) - adjust_delta *= 1.0f/100.0f; - if (g.IO.KeyShift) - adjust_delta *= 10.0f; - } - else if (g.ActiveIdSource == ImGuiInputSource_Nav) - { - int decimal_precision = (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImParseFormatPrecision(format, 3) : 0; - adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x; - v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); - } - adjust_delta *= v_speed; - - // Clear current value on activation - // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. - bool is_just_activated = g.ActiveIdIsJustActivated; - bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f)); - if (is_just_activated || is_already_past_limits_and_pushing_outward) - { - g.DragCurrentAccum = 0.0f; - g.DragCurrentAccumDirty = false; - } - else if (adjust_delta != 0.0f) - { - g.DragCurrentAccum += adjust_delta; - g.DragCurrentAccumDirty = true; - } - - if (!g.DragCurrentAccumDirty) - return false; - - TYPE v_cur = *v; - FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f; - - const bool is_power = (power != 1.0f && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) && has_min_max); - if (is_power) - { - // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range - FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power); - FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min)); - v_cur = v_min + (TYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min); - v_old_ref_for_accum_remainder = v_old_norm_curved; - } - else - { - v_cur += (TYPE)g.DragCurrentAccum; - } - - // Round to user desired precision based on format string - v_cur = RoundScalarWithFormatT(format, data_type, v_cur); - - // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. - g.DragCurrentAccumDirty = false; - if (is_power) - { - FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power); - g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder); - } - else - { - g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v); - } - - // Lose zero sign for float/double - if (v_cur == (TYPE)-0) - v_cur = (TYPE)0; - - // Clamp values (handle overflow/wrap-around) - if (*v != v_cur && has_min_max) - { - if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f)) - v_cur = v_min; - if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f)) - v_cur = v_max; - } - - // Apply result - if (*v == v_cur) - return false; - *v = v_cur; - return true; -} - -bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power) -{ - ImGuiContext& g = *GImGui; - if (g.ActiveId == id) - { - if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0]) - ClearActiveID(); - else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) - ClearActiveID(); - } - if (g.ActiveId != id) - return false; - - switch (data_type) - { - case ImGuiDataType_S32: return DragBehaviorT(data_type, (ImS32*)v, v_speed, v_min ? *(const ImS32* )v_min : IM_S32_MIN, v_max ? *(const ImS32* )v_max : IM_S32_MAX, format, power); - case ImGuiDataType_U32: return DragBehaviorT(data_type, (ImU32*)v, v_speed, v_min ? *(const ImU32* )v_min : IM_U32_MIN, v_max ? *(const ImU32* )v_max : IM_U32_MAX, format, power); - case ImGuiDataType_S64: return DragBehaviorT(data_type, (ImS64*)v, v_speed, v_min ? *(const ImS64* )v_min : IM_S64_MIN, v_max ? *(const ImS64* )v_max : IM_S64_MAX, format, power); - case ImGuiDataType_U64: return DragBehaviorT(data_type, (ImU64*)v, v_speed, v_min ? *(const ImU64* )v_min : IM_U64_MIN, v_max ? *(const ImU64* )v_max : IM_U64_MAX, format, power); - case ImGuiDataType_Float: return DragBehaviorT(data_type, (float*)v, v_speed, v_min ? *(const float* )v_min : -FLT_MAX, v_max ? *(const float* )v_max : FLT_MAX, format, power); - case ImGuiDataType_Double: return DragBehaviorT(data_type, (double*)v, v_speed, v_min ? *(const double*)v_min : -DBL_MAX, v_max ? *(const double*)v_max : DBL_MAX, format, power); - case ImGuiDataType_COUNT: break; - } - IM_ASSERT(0); - return false; -} - -bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - if (power != 1.0f) - IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const float w = CalcItemWidth(); - - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); - const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); - const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); - - // NB- we don't call ItemSize() yet because we may turn into a text edit box below - if (!ItemAdd(total_bb, id, &frame_bb)) - { - ItemSize(total_bb, style.FramePadding.y); - return false; - } - const bool hovered = ItemHoverable(frame_bb, id); - - // Default format string when passing NULL - // Patch old "%.0f" format string to use "%d", read function comments for more details. - IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); - if (format == NULL) - format = GDataTypeInfo[data_type].PrintFmt; - else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) - format = PatchFormatStringFloatToInt(format); - - // Tabbing or CTRL-clicking on Drag turns it into an input box - bool start_text_input = false; - const bool tab_focus_requested = FocusableItemRegister(window, id); - if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) - { - SetActiveID(id, window); - SetFocusID(id, window); - FocusWindow(window); - g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); - if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id) - { - start_text_input = true; - g.ScalarAsInputTextId = 0; - } - } - if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) - return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format); - - // Actual drag behavior - ItemSize(total_bb, style.FramePadding.y); - const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power); - if (value_changed) - MarkItemEdited(id); - - // Draw frame - const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - RenderNavHighlight(frame_bb, id); - RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); - - // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. - char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format); - RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); - - if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); - - return value_changed; -} - -bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min, const void* v_max, const char* format, float power) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - bool value_changed = false; - BeginGroup(); - PushID(label); - PushMultiItemsWidths(components); - size_t type_size = GDataTypeInfo[data_type].Size; - for (int i = 0; i < components; i++) - { - PushID(i); - value_changed |= DragScalar("##v", data_type, v, v_speed, v_min, v_max, format, power); - SameLine(0, g.Style.ItemInnerSpacing.x); - PopID(); - PopItemWidth(); - v = (void*)((char*)v + type_size); - } - PopID(); - - TextUnformatted(label, FindRenderedTextEnd(label)); - EndGroup(); - return value_changed; -} - -bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power) -{ - return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power); -} - -bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power) -{ - return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power); -} - -bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power) -{ - return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power); -} - -bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power) -{ - return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power); -} - -bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, float power) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - PushID(label); - BeginGroup(); - PushMultiItemsWidths(2); - - bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format, power); - PopItemWidth(); - SameLine(0, g.Style.ItemInnerSpacing.x); - value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, format_max ? format_max : format, power); - PopItemWidth(); - SameLine(0, g.Style.ItemInnerSpacing.x); - - TextUnformatted(label, FindRenderedTextEnd(label)); - EndGroup(); - PopID(); - return value_changed; -} - -// NB: v_speed is float to allow adjusting the drag speed with more precision -bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format) -{ - return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format); -} - -bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format) -{ - return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format); -} - -bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format) -{ - return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format); -} - -bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format) -{ - return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format); -} - -bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - PushID(label); - BeginGroup(); - PushMultiItemsWidths(2); - - bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format); - PopItemWidth(); - SameLine(0, g.Style.ItemInnerSpacing.x); - value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, format_max ? format_max : format); - PopItemWidth(); - SameLine(0, g.Style.ItemInnerSpacing.x); - - TextUnformatted(label, FindRenderedTextEnd(label)); - EndGroup(); - PopID(); - - return value_changed; -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc. -//------------------------------------------------------------------------- -// - SliderBehaviorT<>() [Internal] -// - SliderBehavior() [Internal] -// - SliderScalar() -// - SliderScalarN() -// - SliderFloat() -// - SliderFloat2() -// - SliderFloat3() -// - SliderFloat4() -// - SliderAngle() -// - SliderInt() -// - SliderInt2() -// - SliderInt3() -// - SliderInt4() -// - VSliderScalar() -// - VSliderFloat() -// - VSliderInt() -//------------------------------------------------------------------------- - -template -float ImGui::SliderCalcRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos) -{ - if (v_min == v_max) - return 0.0f; - - const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double); - const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min); - if (is_power) - { - if (v_clamped < 0.0f) - { - const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min)); - return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos; - } - else - { - const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min))); - return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos); - } - } - - // Linear slider - return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min)); -} - -// FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc. -template -bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb) -{ - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - - const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0; - const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); - const bool is_power = (power != 1.0f) && is_decimal; - - const float grab_padding = 2.0f; - const float slider_sz = is_horizontal ? (bb.GetWidth() - grab_padding * 2.0f) : (bb.GetHeight() - grab_padding * 2.0f); - float grab_sz = style.GrabMinSize; - SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max); - if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows - grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit - grab_sz = ImMin(grab_sz, slider_sz); - const float slider_usable_sz = slider_sz - grab_sz; - const float slider_usable_pos_min = (is_horizontal ? bb.Min.x : bb.Min.y) + grab_padding + grab_sz*0.5f; - const float slider_usable_pos_max = (is_horizontal ? bb.Max.x : bb.Max.y) - grab_padding - grab_sz*0.5f; - - // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f - float linear_zero_pos; // 0.0->1.0f - if (is_power && v_min * v_max < 0.0f) - { - // Different sign - const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power); - const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power); - linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0)); - } - else - { - // Same sign - linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f; - } - - // Process interacting with the slider - bool value_changed = false; - if (g.ActiveId == id) - { - bool set_new_value = false; - float clicked_t = 0.0f; - if (g.ActiveIdSource == ImGuiInputSource_Mouse) - { - if (!g.IO.MouseDown[0]) - { - ClearActiveID(); - } - else - { - const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y; - clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f; - if (!is_horizontal) - clicked_t = 1.0f - clicked_t; - set_new_value = true; - } - } - else if (g.ActiveIdSource == ImGuiInputSource_Nav) - { - const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f); - float delta = is_horizontal ? delta2.x : -delta2.y; - if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated) - { - ClearActiveID(); - } - else if (delta != 0.0f) - { - clicked_t = SliderCalcRatioFromValueT(data_type, *v, v_min, v_max, power, linear_zero_pos); - const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0; - if ((decimal_precision > 0) || is_power) - { - delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds - if (IsNavInputDown(ImGuiNavInput_TweakSlow)) - delta /= 10.0f; - } - else - { - if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow)) - delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps - else - delta /= 100.0f; - } - if (IsNavInputDown(ImGuiNavInput_TweakFast)) - delta *= 10.0f; - set_new_value = true; - if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits - set_new_value = false; - else - clicked_t = ImSaturate(clicked_t + delta); - } - } - - if (set_new_value) - { - TYPE v_new; - if (is_power) - { - // Account for power curve scale on both sides of the zero - if (clicked_t < linear_zero_pos) - { - // Negative: rescale to the negative range before powering - float a = 1.0f - (clicked_t / linear_zero_pos); - a = ImPow(a, power); - v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a); - } - else - { - // Positive: rescale to the positive range before powering - float a; - if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f) - a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos); - else - a = clicked_t; - a = ImPow(a, power); - v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a); - } - } - else - { - // Linear slider - if (is_decimal) - { - v_new = ImLerp(v_min, v_max, clicked_t); - } - else - { - // For integer values we want the clicking position to match the grab box so we round above - // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. - FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t; - TYPE v_new_off_floor = (TYPE)(v_new_off_f); - TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5); - if (!is_decimal && v_new_off_floor < v_new_off_round) - v_new = v_min + v_new_off_round; - else - v_new = v_min + v_new_off_floor; - } - } - - // Round to user desired precision based on format string - v_new = RoundScalarWithFormatT(format, data_type, v_new); - - // Apply result - if (*v != v_new) - { - *v = v_new; - value_changed = true; - } - } - } - - // Output grab position so it can be displayed by the caller - float grab_t = SliderCalcRatioFromValueT(data_type, *v, v_min, v_max, power, linear_zero_pos); - if (!is_horizontal) - grab_t = 1.0f - grab_t; - const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t); - if (is_horizontal) - *out_grab_bb = ImRect(grab_pos - grab_sz*0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz*0.5f, bb.Max.y - grab_padding); - else - *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f); - - return value_changed; -} - -// For 32-bits and larger types, slider bounds are limited to half the natural type range. -// So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok. -// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders. -bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags, ImRect* out_grab_bb) -{ - switch (data_type) - { - case ImGuiDataType_S32: - IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2); - return SliderBehaviorT(bb, id, data_type, (ImS32*)v, *(const ImS32*)v_min, *(const ImS32*)v_max, format, power, flags, out_grab_bb); - case ImGuiDataType_U32: - IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2); - return SliderBehaviorT(bb, id, data_type, (ImU32*)v, *(const ImU32*)v_min, *(const ImU32*)v_max, format, power, flags, out_grab_bb); - case ImGuiDataType_S64: - IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2); - return SliderBehaviorT(bb, id, data_type, (ImS64*)v, *(const ImS64*)v_min, *(const ImS64*)v_max, format, power, flags, out_grab_bb); - case ImGuiDataType_U64: - IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2); - return SliderBehaviorT(bb, id, data_type, (ImU64*)v, *(const ImU64*)v_min, *(const ImU64*)v_max, format, power, flags, out_grab_bb); - case ImGuiDataType_Float: - IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f); - return SliderBehaviorT(bb, id, data_type, (float*)v, *(const float*)v_min, *(const float*)v_max, format, power, flags, out_grab_bb); - case ImGuiDataType_Double: - IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f); - return SliderBehaviorT(bb, id, data_type, (double*)v, *(const double*)v_min, *(const double*)v_max, format, power, flags, out_grab_bb); - case ImGuiDataType_COUNT: break; - } - IM_ASSERT(0); - return false; -} - -bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - const float w = CalcItemWidth(); - - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f)); - const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); - - // NB- we don't call ItemSize() yet because we may turn into a text edit box below - if (!ItemAdd(total_bb, id, &frame_bb)) - { - ItemSize(total_bb, style.FramePadding.y); - return false; - } - - // Default format string when passing NULL - // Patch old "%.0f" format string to use "%d", read function comments for more details. - IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); - if (format == NULL) - format = GDataTypeInfo[data_type].PrintFmt; - else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) - format = PatchFormatStringFloatToInt(format); - - // Tabbing or CTRL-clicking on Slider turns it into an input box - bool start_text_input = false; - const bool tab_focus_requested = FocusableItemRegister(window, id); - const bool hovered = ItemHoverable(frame_bb, id); - if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id)) - { - SetActiveID(id, window); - SetFocusID(id, window); - FocusWindow(window); - g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); - if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id) - { - start_text_input = true; - g.ScalarAsInputTextId = 0; - } - } - if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id)) - return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format); - - ItemSize(total_bb, style.FramePadding.y); - - // Draw frame - const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - RenderNavHighlight(frame_bb, id); - RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); - - // Slider behavior - ImRect grab_bb; - const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_None, &grab_bb); - if (value_changed) - MarkItemEdited(id); - - // Render grab - window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); - - // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. - char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format); - RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f)); - - if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - - return value_changed; -} - -// Add multiple sliders on 1 line for compact edition of multiple components -bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - bool value_changed = false; - BeginGroup(); - PushID(label); - PushMultiItemsWidths(components); - size_t type_size = GDataTypeInfo[data_type].Size; - for (int i = 0; i < components; i++) - { - PushID(i); - value_changed |= SliderScalar("##v", data_type, v, v_min, v_max, format, power); - SameLine(0, g.Style.ItemInnerSpacing.x); - PopID(); - PopItemWidth(); - v = (void*)((char*)v + type_size); - } - PopID(); - - TextUnformatted(label, FindRenderedTextEnd(label)); - EndGroup(); - return value_changed; -} - -bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power) -{ - return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); -} - -bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power) -{ - return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power); -} - -bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power) -{ - return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); -} - -bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power) -{ - return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power); -} - -bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max) -{ - float v_deg = (*v_rad) * 360.0f / (2*IM_PI); - bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f); - *v_rad = v_deg * (2*IM_PI) / 360.0f; - return value_changed; -} - -bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format) -{ - return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format); -} - -bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format) -{ - return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format); -} - -bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format) -{ - return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format); -} - -bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format) -{ - return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format); -} - -bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); - const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); - - ItemSize(bb, style.FramePadding.y); - if (!ItemAdd(frame_bb, id)) - return false; - - // Default format string when passing NULL - // Patch old "%.0f" format string to use "%d", read function comments for more details. - IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); - if (format == NULL) - format = GDataTypeInfo[data_type].PrintFmt; - else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) - format = PatchFormatStringFloatToInt(format); - - const bool hovered = ItemHoverable(frame_bb, id); - if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id) - { - SetActiveID(id, window); - SetFocusID(id, window); - FocusWindow(window); - g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); - } - - // Draw frame - const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - RenderNavHighlight(frame_bb, id); - RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); - - // Slider behavior - ImRect grab_bb; - const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb); - if (value_changed) - MarkItemEdited(id); - - // Render grab - window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); - - // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. - // For the vertical slider we allow centered text to overlap the frame padding - char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format); - RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f)); - if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - - return value_changed; -} - -bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power) -{ - return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power); -} - -bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format) -{ - return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format); -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc. -//------------------------------------------------------------------------- -// - ImParseFormatFindStart() [Internal] -// - ImParseFormatFindEnd() [Internal] -// - ImParseFormatTrimDecorations() [Internal] -// - ImParseFormatPrecision() [Internal] -// - InputScalarAsWidgetReplacement() [Internal] -// - InputScalar() -// - InputScalarN() -// - InputFloat() -// - InputFloat2() -// - InputFloat3() -// - InputFloat4() -// - InputInt() -// - InputInt2() -// - InputInt3() -// - InputInt4() -// - InputDouble() -//------------------------------------------------------------------------- - -// We don't use strchr() because our strings are usually very short and often start with '%' -const char* ImParseFormatFindStart(const char* fmt) -{ - while (char c = fmt[0]) - { - if (c == '%' && fmt[1] != '%') - return fmt; - else if (c == '%') - fmt++; - fmt++; - } - return fmt; -} - -const char* ImParseFormatFindEnd(const char* fmt) -{ - // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format. - if (fmt[0] != '%') - return fmt; - const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A')); - const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a')); - for (char c; (c = *fmt) != 0; fmt++) - { - if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0) - return fmt + 1; - if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0) - return fmt + 1; - } - return fmt; -} - -// Extract the format out of a format string with leading or trailing decorations -// fmt = "blah blah" -> return fmt -// fmt = "%.3f" -> return fmt -// fmt = "hello %.3f" -> return fmt + 6 -// fmt = "%.3f hello" -> return buf written with "%.3f" -const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, int buf_size) -{ - const char* fmt_start = ImParseFormatFindStart(fmt); - if (fmt_start[0] != '%') - return fmt; - const char* fmt_end = ImParseFormatFindEnd(fmt_start); - if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data. - return fmt_start; - ImStrncpy(buf, fmt_start, ImMin((int)(fmt_end + 1 - fmt_start), buf_size)); - return buf; -} - -// Parse display precision back from the display format string -// FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed. -int ImParseFormatPrecision(const char* fmt, int default_precision) -{ - fmt = ImParseFormatFindStart(fmt); - if (fmt[0] != '%') - return default_precision; - fmt++; - while (*fmt >= '0' && *fmt <= '9') - fmt++; - int precision = INT_MAX; - if (*fmt == '.') - { - fmt = ImAtoi(fmt + 1, &precision); - if (precision < 0 || precision > 99) - precision = default_precision; - } - if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation - precision = -1; - if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX) - precision = -1; - return (precision == INT_MAX) ? default_precision : precision; -} - -// Create text input in place of a slider (when CTRL+Clicking on slider) -// FIXME: Logic is messy and confusing. -bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - - // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen) - // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id - SetActiveID(g.ScalarAsInputTextId, window); - g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); - SetHoveredID(0); - FocusableItemUnregister(window); - - char fmt_buf[32]; - char data_buf[32]; - format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf)); - DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format); - ImStrTrimBlanks(data_buf); - ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal); - bool value_changed = InputTextEx(label, data_buf, IM_ARRAYSIZE(data_buf), bb.GetSize(), flags); - if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget - { - IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID - g.ScalarAsInputTextId = g.ActiveId; - SetHoveredID(id); - } - if (value_changed) - return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL); - return false; -} - -// NB: format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "format" argument) -bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - - IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); - if (format == NULL) - format = GDataTypeInfo[data_type].PrintFmt; - - char buf[64]; - DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format); - - bool value_changed = false; - if ((extra_flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0) - extra_flags |= ImGuiInputTextFlags_CharsDecimal; - extra_flags |= ImGuiInputTextFlags_AutoSelectAll; - - if (step != NULL) - { - const float button_size = GetFrameHeight(); - - BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive() - PushID(label); - PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); - if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view - value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format); - PopItemWidth(); - - // Step buttons - SameLine(0, style.ItemInnerSpacing.x); - if (ButtonEx("-", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) - { - DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step); - value_changed = true; - } - SameLine(0, style.ItemInnerSpacing.x); - if (ButtonEx("+", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) - { - DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step); - value_changed = true; - } - SameLine(0, style.ItemInnerSpacing.x); - TextUnformatted(label, FindRenderedTextEnd(label)); - - PopID(); - EndGroup(); - } - else - { - if (InputText(label, buf, IM_ARRAYSIZE(buf), extra_flags)) - value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format); - } - - return value_changed; -} - -bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - bool value_changed = false; - BeginGroup(); - PushID(label); - PushMultiItemsWidths(components); - size_t type_size = GDataTypeInfo[data_type].Size; - for (int i = 0; i < components; i++) - { - PushID(i); - value_changed |= InputScalar("##v", data_type, v, step, step_fast, format, extra_flags); - SameLine(0, g.Style.ItemInnerSpacing.x); - PopID(); - PopItemWidth(); - v = (void*)((char*)v + type_size); - } - PopID(); - - TextUnformatted(label, FindRenderedTextEnd(label)); - EndGroup(); - return value_changed; -} - -bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags extra_flags) -{ - extra_flags |= ImGuiInputTextFlags_CharsScientific; - return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), format, extra_flags); -} - -bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags extra_flags) -{ - return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags); -} - -bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags extra_flags) -{ - return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags); -} - -bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags extra_flags) -{ - return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags); -} - -// Prefer using "const char* format" directly, which is more flexible and consistent with other API. -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags) -{ - char format[16] = "%f"; - if (decimal_precision >= 0) - ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision); - return InputFloat(label, v, step, step_fast, format, extra_flags); -} - -bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags) -{ - char format[16] = "%f"; - if (decimal_precision >= 0) - ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision); - return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags); -} - -bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags) -{ - char format[16] = "%f"; - if (decimal_precision >= 0) - ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision); - return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags); -} - -bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags) -{ - char format[16] = "%f"; - if (decimal_precision >= 0) - ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision); - return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags); -} -#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS - -bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags) -{ - // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes. - const char* format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d"; - return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), format, extra_flags); -} - -bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags) -{ - return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", extra_flags); -} - -bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags) -{ - return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", extra_flags); -} - -bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags) -{ - return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", extra_flags); -} - -bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags extra_flags) -{ - extra_flags |= ImGuiInputTextFlags_CharsScientific; - return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), format, extra_flags); -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: InputText, InputTextMultiline -//------------------------------------------------------------------------- -// - InputText() -// - InputTextMultiline() -// - InputTextEx() [Internal] -//------------------------------------------------------------------------- - -bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) -{ - IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() - return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data); -} - -bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) -{ - return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); -} - -static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) -{ - int line_count = 0; - const char* s = text_begin; - while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding - if (c == '\n') - line_count++; - s--; - if (s[0] != '\n' && s[0] != '\r') - line_count++; - *out_text_end = s; - return line_count; -} - -static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line) -{ - ImFont* font = GImGui->Font; - const float line_height = GImGui->FontSize; - const float scale = line_height / font->FontSize; - - ImVec2 text_size = ImVec2(0,0); - float line_width = 0.0f; - - const ImWchar* s = text_begin; - while (s < text_end) - { - unsigned int c = (unsigned int)(*s++); - if (c == '\n') - { - text_size.x = ImMax(text_size.x, line_width); - text_size.y += line_height; - line_width = 0.0f; - if (stop_on_new_line) - break; - continue; - } - if (c == '\r') - continue; - - const float char_width = font->GetCharAdvance((unsigned short)c) * scale; - line_width += char_width; - } - - if (text_size.x < line_width) - text_size.x = line_width; - - if (out_offset) - *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n - - if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n - text_size.y += line_height; - - if (remaining) - *remaining = s; - - return text_size; -} - -// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) -namespace ImGuiStb -{ - -static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } -static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->TextW[idx]; } -static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); } -static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } -static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; -static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) -{ - const ImWchar* text = obj->TextW.Data; - const ImWchar* text_remaining = NULL; - const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); - r->x0 = 0.0f; - r->x1 = size.x; - r->baseline_y_delta = size.y; - r->ymin = 0.0f; - r->ymax = size.y; - r->num_chars = (int)(text_remaining - (text + line_start_idx)); -} - -static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } -static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->TextW[idx-1] ) && !is_separator( obj->TextW[idx] ) ) : 1; } -static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } -#ifdef __APPLE__ // FIXME: Move setting to IO structure -static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->TextW[idx-1] ) && is_separator( obj->TextW[idx] ) ) : 1; } -static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } -#else -static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } -#endif -#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h -#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL - -static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) -{ - ImWchar* dst = obj->TextW.Data + pos; - - // We maintain our buffer length in both UTF-8 and wchar formats - obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); - obj->CurLenW -= n; - - // Offset remaining text - const ImWchar* src = obj->TextW.Data + pos + n; - while (ImWchar c = *src++) - *dst++ = c; - *dst = '\0'; -} - -static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len) -{ - const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0; - const int text_len = obj->CurLenW; - IM_ASSERT(pos <= text_len); - - const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); - if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA)) - return false; - - // Grow internal buffer if needed - if (new_text_len + text_len + 1 > obj->TextW.Size) - { - if (!is_resizable) - return false; - IM_ASSERT(text_len < obj->TextW.Size); - obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1); - } - - ImWchar* text = obj->TextW.Data; - if (pos != text_len) - memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar)); - memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); - - obj->CurLenW += new_text_len; - obj->CurLenA += new_text_len_utf8; - obj->TextW[obj->CurLenW] = '\0'; - - return true; -} - -// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) -#define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left -#define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right -#define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up -#define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down -#define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line -#define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line -#define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text -#define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text -#define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor -#define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor -#define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo -#define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo -#define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word -#define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word -#define STB_TEXTEDIT_K_SHIFT 0x20000 - -#define STB_TEXTEDIT_IMPLEMENTATION -#include "imstb_textedit.h" - -} - -void ImGuiInputTextState::OnKeyPressed(int key) -{ - stb_textedit_key(this, &StbState, key); - CursorFollow = true; - CursorAnimReset(); -} - -ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() -{ - memset(this, 0, sizeof(*this)); -} - -// Public API to manipulate UTF-8 text -// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) -// FIXME: The existence of this rarely exercised code path is a bit of a nuisance. -void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count) -{ - IM_ASSERT(pos + bytes_count <= BufTextLen); - char* dst = Buf + pos; - const char* src = Buf + pos + bytes_count; - while (char c = *src++) - *dst++ = c; - *dst = '\0'; - - if (CursorPos + bytes_count >= pos) - CursorPos -= bytes_count; - else if (CursorPos >= pos) - CursorPos = pos; - SelectionStart = SelectionEnd = CursorPos; - BufDirty = true; - BufTextLen -= bytes_count; -} - -void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end) -{ - const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; - const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); - if (new_text_len + BufTextLen >= BufSize) - { - if (!is_resizable) - return; - - // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!) - ImGuiContext& g = *GImGui; - ImGuiInputTextState* edit_state = &g.InputTextState; - IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); - IM_ASSERT(Buf == edit_state->TempBuffer.Data); - int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; - edit_state->TempBuffer.reserve(new_buf_size + 1); - Buf = edit_state->TempBuffer.Data; - BufSize = edit_state->BufCapacityA = new_buf_size; - } - - if (BufTextLen != pos) - memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos)); - memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); - Buf[BufTextLen + new_text_len] = '\0'; - - if (CursorPos >= pos) - CursorPos += new_text_len; - SelectionStart = SelectionEnd = CursorPos; - BufDirty = true; - BufTextLen += new_text_len; -} - -// Return false to discard a character. -static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) -{ - unsigned int c = *p_char; - - if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) - { - bool pass = false; - pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); - pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); - if (!pass) - return false; - } - - if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys. - return false; - - if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific)) - { - if (flags & ImGuiInputTextFlags_CharsDecimal) - if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/')) - return false; - - if (flags & ImGuiInputTextFlags_CharsScientific) - if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E')) - return false; - - if (flags & ImGuiInputTextFlags_CharsHexadecimal) - if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) - return false; - - if (flags & ImGuiInputTextFlags_CharsUppercase) - if (c >= 'a' && c <= 'z') - *p_char = (c += (unsigned int)('A'-'a')); - - if (flags & ImGuiInputTextFlags_CharsNoBlank) - if (ImCharIsBlankW(c)) - return false; - } - - if (flags & ImGuiInputTextFlags_CallbackCharFilter) - { - ImGuiInputTextCallbackData callback_data; - memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData)); - callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; - callback_data.EventChar = (ImWchar)c; - callback_data.Flags = flags; - callback_data.UserData = user_data; - if (callback(&callback_data) != 0) - return false; - *p_char = callback_data.EventChar; - if (!callback_data.EventChar) - return false; - } - - return true; -} - -// Edit a string of text -// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!". -// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match -// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator. -// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect. -// - If you want to use ImGui::InputText() with std::string, see misc/stl/imgui_stl.h -// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188) -bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) - IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) - - ImGuiContext& g = *GImGui; - const ImGuiIO& io = g.IO; - const ImGuiStyle& style = g.Style; - - const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; - const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0; - const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; - const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; - const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; - if (is_resizable) - IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! - - if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope, - BeginGroup(); - const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); - const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); - - ImGuiWindow* draw_window = window; - if (is_multiline) - { - ItemAdd(total_bb, id, &frame_bb); - if (!BeginChildFrame(id, frame_bb.GetSize())) - { - EndChildFrame(); - EndGroup(); - return false; - } - draw_window = GetCurrentWindow(); - draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight - size.x -= draw_window->ScrollbarSizes.x; - } - else - { - ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, id, &frame_bb)) - return false; - } - const bool hovered = ItemHoverable(frame_bb, id); - if (hovered) - g.MouseCursor = ImGuiMouseCursor_TextInput; - - // Password pushes a temporary font with only a fallback glyph - if (is_password) - { - const ImFontGlyph* glyph = g.Font->FindGlyph('*'); - ImFont* password_font = &g.InputTextPasswordFont; - password_font->FontSize = g.Font->FontSize; - password_font->Scale = g.Font->Scale; - password_font->DisplayOffset = g.Font->DisplayOffset; - password_font->Ascent = g.Font->Ascent; - password_font->Descent = g.Font->Descent; - password_font->ContainerAtlas = g.Font->ContainerAtlas; - password_font->FallbackGlyph = glyph; - password_font->FallbackAdvanceX = glyph->AdvanceX; - IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); - PushFont(password_font); - } - - // NB: we are only allowed to access 'edit_state' if we are the active widget. - ImGuiInputTextState& edit_state = g.InputTextState; - - const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing - const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent); - const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; - - const bool user_clicked = hovered && io.MouseClicked[0]; - const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY"); - const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard)); - - bool clear_active_id = false; - - bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline); - if (focus_requested || user_clicked || user_scrolled || user_nav_input_start) - { - if (g.ActiveId != id) - { - // Start edition - // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) - // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) - const int prev_len_w = edit_state.CurLenW; - const int init_buf_len = (int)strlen(buf); - edit_state.TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash. - edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash. - memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1); - const char* buf_end = NULL; - edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end); - edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. - edit_state.CursorAnimReset(); - - // Preserve cursor position and undo/redo stack if we come back to same widget - // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar). - const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW); - if (recycle_state) - { - // Recycle existing cursor/selection/undo stack but clamp position - // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. - edit_state.CursorClamp(); - } - else - { - edit_state.ID = id; - edit_state.ScrollX = 0.0f; - stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); - if (!is_multiline && focus_requested_by_code) - select_all = true; - } - if (flags & ImGuiInputTextFlags_AlwaysInsertMode) - edit_state.StbState.insert_mode = true; - if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) - select_all = true; - } - SetActiveID(id, window); - SetFocusID(id, window); - FocusWindow(window); - if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory)) - g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down)); - } - else if (io.MouseClicked[0]) - { - // Release focus when we click outside - clear_active_id = true; - } - - bool value_changed = false; - bool enter_pressed = false; - int backup_current_text_length = 0; - - if (g.ActiveId == id) - { - if (!is_editable && !g.ActiveIdIsJustActivated) - { - // When read-only we always use the live data passed to the function - edit_state.TextW.resize(buf_size+1); - const char* buf_end = NULL; - edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end); - edit_state.CurLenA = (int)(buf_end - buf); - edit_state.CursorClamp(); - } - - backup_current_text_length = edit_state.CurLenA; - edit_state.BufCapacityA = buf_size; - edit_state.UserFlags = flags; - edit_state.UserCallback = callback; - edit_state.UserCallbackData = callback_user_data; - - // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. - // Down the line we should have a cleaner library-wide concept of Selected vs Active. - g.ActiveIdAllowOverlap = !io.MouseDown[0]; - g.WantTextInputNextFrame = 1; - - // Edit in progress - const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; - const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); - - const bool is_osx = io.ConfigMacOSXBehaviors; - if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0])) - { - edit_state.SelectAll(); - edit_state.SelectedAllMouseLock = true; - } - else if (hovered && is_osx && io.MouseDoubleClicked[0]) - { - // Double-click select a word only, OS X style (by simulating keystrokes) - edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); - edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); - } - else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) - { - if (hovered) - { - stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y); - edit_state.CursorAnimReset(); - } - } - else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) - { - stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y); - edit_state.CursorAnimReset(); - edit_state.CursorFollow = true; - } - if (edit_state.SelectedAllMouseLock && !io.MouseDown[0]) - edit_state.SelectedAllMouseLock = false; - - if (io.InputCharacters[0]) - { - // Process text input (before we check for Return because using some IME will effectively send a Return?) - // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. - bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper); - if (!ignore_inputs && is_editable && !user_nav_input_start) - for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++) - { - // Insert character if they pass filtering - unsigned int c = (unsigned int)io.InputCharacters[n]; - if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) - edit_state.OnKeyPressed((int)c); - } - - // Consume characters - memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters)); - } - } - - bool cancel_edit = false; - if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) - { - // Handle key-presses - const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); - const bool is_osx = io.ConfigMacOSXBehaviors; - const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl - const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt; - const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl - const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End - const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper; - const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper; - - const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection()); - const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection()); - const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable; - const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable); - const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable; - - if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) - { - if (!edit_state.HasSelection()) - { - if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT); - else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT); - } - edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); - } - else if (IsKeyPressedMap(ImGuiKey_Enter)) - { - bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; - if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) - { - enter_pressed = clear_active_id = true; - } - else if (is_editable) - { - unsigned int c = '\n'; // Insert new line - if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) - edit_state.OnKeyPressed((int)c); - } - } - else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) - { - unsigned int c = '\t'; // Insert TAB - if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) - edit_state.OnKeyPressed((int)c); - } - else if (IsKeyPressedMap(ImGuiKey_Escape)) - { - clear_active_id = cancel_edit = true; - } - else if (is_undo || is_redo) - { - edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO); - edit_state.ClearSelection(); - } - else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A)) - { - edit_state.SelectAll(); - edit_state.CursorFollow = true; - } - else if (is_cut || is_copy) - { - // Cut, Copy - if (io.SetClipboardTextFn) - { - const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0; - const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW; - edit_state.TempBuffer.resize((ie-ib) * 4 + 1); - ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie); - SetClipboardText(edit_state.TempBuffer.Data); - } - if (is_cut) - { - if (!edit_state.HasSelection()) - edit_state.SelectAll(); - edit_state.CursorFollow = true; - stb_textedit_cut(&edit_state, &edit_state.StbState); - } - } - else if (is_paste) - { - if (const char* clipboard = GetClipboardText()) - { - // Filter pasted buffer - const int clipboard_len = (int)strlen(clipboard); - ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar)); - int clipboard_filtered_len = 0; - for (const char* s = clipboard; *s; ) - { - unsigned int c; - s += ImTextCharFromUtf8(&c, s, NULL); - if (c == 0) - break; - if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data)) - continue; - clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; - } - clipboard_filtered[clipboard_filtered_len] = 0; - if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation - { - stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len); - edit_state.CursorFollow = true; - } - ImGui::MemFree(clipboard_filtered); - } - } - } - - if (g.ActiveId == id) - { - const char* apply_new_text = NULL; - int apply_new_text_length = 0; - if (cancel_edit) - { - // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. - if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0) - { - apply_new_text = edit_state.InitialText.Data; - apply_new_text_length = edit_state.InitialText.Size - 1; - } - } - - // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. - // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage. - bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); - if (apply_edit_back_to_user_buffer) - { - // Apply new value immediately - copy modified buffer back - // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer - // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. - // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. - if (is_editable) - { - edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1); - ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL); - } - - // User callback - if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0) - { - IM_ASSERT(callback != NULL); - - // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. - ImGuiInputTextFlags event_flag = 0; - ImGuiKey event_key = ImGuiKey_COUNT; - if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab)) - { - event_flag = ImGuiInputTextFlags_CallbackCompletion; - event_key = ImGuiKey_Tab; - } - else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow)) - { - event_flag = ImGuiInputTextFlags_CallbackHistory; - event_key = ImGuiKey_UpArrow; - } - else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow)) - { - event_flag = ImGuiInputTextFlags_CallbackHistory; - event_key = ImGuiKey_DownArrow; - } - else if (flags & ImGuiInputTextFlags_CallbackAlways) - event_flag = ImGuiInputTextFlags_CallbackAlways; - - if (event_flag) - { - ImGuiInputTextCallbackData callback_data; - memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData)); - callback_data.EventFlag = event_flag; - callback_data.Flags = flags; - callback_data.UserData = callback_user_data; - - callback_data.EventKey = event_key; - callback_data.Buf = edit_state.TempBuffer.Data; - callback_data.BufTextLen = edit_state.CurLenA; - callback_data.BufSize = edit_state.BufCapacityA; - callback_data.BufDirty = false; - - // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) - ImWchar* text = edit_state.TextW.Data; - const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor); - const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start); - const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end); - - // Call user code - callback(&callback_data); - - // Read back what user may have modified - IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data); // Invalid to modify those fields - IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA); - IM_ASSERT(callback_data.Flags == flags); - if (callback_data.CursorPos != utf8_cursor_pos) { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; } - if (callback_data.SelectionStart != utf8_selection_start) { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } - if (callback_data.SelectionEnd != utf8_selection_end) { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } - if (callback_data.BufDirty) - { - IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! - if (callback_data.BufTextLen > backup_current_text_length && is_resizable) - edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); - edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL); - edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() - edit_state.CursorAnimReset(); - } - } - } - - // Will copy result string if modified - if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0) - { - apply_new_text = edit_state.TempBuffer.Data; - apply_new_text_length = edit_state.CurLenA; - } - } - - // Copy result to user buffer - if (apply_new_text) - { - IM_ASSERT(apply_new_text_length >= 0); - if (backup_current_text_length != apply_new_text_length && is_resizable) - { - ImGuiInputTextCallbackData callback_data; - callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; - callback_data.Flags = flags; - callback_data.Buf = buf; - callback_data.BufTextLen = apply_new_text_length; - callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1); - callback_data.UserData = callback_user_data; - callback(&callback_data); - buf = callback_data.Buf; - buf_size = callback_data.BufSize; - apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1); - IM_ASSERT(apply_new_text_length <= buf_size); - } - - // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size. - ImStrncpy(buf, edit_state.TempBuffer.Data, ImMin(apply_new_text_length + 1, buf_size)); - value_changed = true; - } - - // Clear temporary user storage - edit_state.UserFlags = 0; - edit_state.UserCallback = NULL; - edit_state.UserCallbackData = NULL; - } - - // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) - if (clear_active_id && g.ActiveId == id) - ClearActiveID(); - - // Render - // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on. - const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL; - - // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line - // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether. - // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash. - const int buf_display_max_length = 2 * 1024 * 1024; - - if (!is_multiline) - { - RenderNavHighlight(frame_bb, id); - RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); - } - - const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size - ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; - ImVec2 text_size(0.f, 0.f); - const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY")); - if (g.ActiveId == id || is_currently_scrolling) - { - edit_state.CursorAnim += io.DeltaTime; - - // This is going to be messy. We need to: - // - Display the text (this alone can be more easily clipped) - // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) - // - Measure text height (for scrollbar) - // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) - // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. - const ImWchar* text_begin = edit_state.TextW.Data; - ImVec2 cursor_offset, select_start_offset; - - { - // Count lines + find lines numbers straddling 'cursor' and 'select_start' position. - const ImWchar* searches_input_ptr[2]; - searches_input_ptr[0] = text_begin + edit_state.StbState.cursor; - searches_input_ptr[1] = NULL; - int searches_remaining = 1; - int searches_result_line_number[2] = { -1, -999 }; - if (edit_state.StbState.select_start != edit_state.StbState.select_end) - { - searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); - searches_result_line_number[1] = -1; - searches_remaining++; - } - - // Iterate all lines to find our line numbers - // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. - searches_remaining += is_multiline ? 1 : 0; - int line_count = 0; - for (const ImWchar* s = text_begin; *s != 0; s++) - if (*s == '\n') - { - line_count++; - if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; } - if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; } - } - line_count++; - if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count; - if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count; - - // Calculate 2d position by finding the beginning of the line and measuring distance - cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; - cursor_offset.y = searches_result_line_number[0] * g.FontSize; - if (searches_result_line_number[1] >= 0) - { - select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; - select_start_offset.y = searches_result_line_number[1] * g.FontSize; - } - - // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) - if (is_multiline) - text_size = ImVec2(size.x, line_count * g.FontSize); - } - - // Scroll - if (edit_state.CursorFollow) - { - // Horizontal scroll in chunks of quarter width - if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) - { - const float scroll_increment_x = size.x * 0.25f; - if (cursor_offset.x < edit_state.ScrollX) - edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x); - else if (cursor_offset.x - size.x >= edit_state.ScrollX) - edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x); - } - else - { - edit_state.ScrollX = 0.0f; - } - - // Vertical scroll - if (is_multiline) - { - float scroll_y = draw_window->Scroll.y; - if (cursor_offset.y - g.FontSize < scroll_y) - scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); - else if (cursor_offset.y - size.y >= scroll_y) - scroll_y = cursor_offset.y - size.y; - draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag - draw_window->Scroll.y = scroll_y; - render_pos.y = draw_window->DC.CursorPos.y; - } - } - edit_state.CursorFollow = false; - const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f); - - // Draw selection - if (edit_state.StbState.select_start != edit_state.StbState.select_end) - { - const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); - const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end); - - float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. - float bg_offy_dn = is_multiline ? 0.0f : 2.0f; - ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg); - ImVec2 rect_pos = render_pos + select_start_offset - render_scroll; - for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) - { - if (rect_pos.y > clip_rect.w + g.FontSize) - break; - if (rect_pos.y < clip_rect.y) - { - while (p < text_selected_end) - if (*p++ == '\n') - break; - } - else - { - ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); - if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines - ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn)); - rect.ClipWith(clip_rect); - if (rect.Overlaps(clip_rect)) - draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); - } - rect_pos.x = render_pos.x - render_scroll.x; - rect_pos.y += g.FontSize; - } - } - - const int buf_display_len = edit_state.CurLenA; - if (is_multiline || buf_display_len < buf_display_max_length) - draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect); - - // Draw blinking cursor - bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f; - ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll; - ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f); - if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) - draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); - - // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) - if (is_editable) - g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize); - } - else - { - // Render text only - const char* buf_end = NULL; - if (is_multiline) - text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width - else - buf_end = buf_display + strlen(buf_display); - if (is_multiline || (buf_end - buf_display) < buf_display_max_length) - draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect); - } - - if (is_multiline) - { - Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line - EndChildFrame(); - EndGroup(); - } - - if (is_password) - PopFont(); - - // Log as text - if (g.LogEnabled && !is_password) - LogRenderedText(&render_pos, buf_display, NULL); - - if (label_size.x > 0) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - - if (value_changed) - MarkItemEdited(id); - - if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0) - return enter_pressed; - else - return value_changed; -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. -//------------------------------------------------------------------------- -// - ColorEdit3() -// - ColorEdit4() -// - ColorPicker3() -// - RenderColorRectWithAlphaCheckerboard() [Internal] -// - ColorPicker4() -// - ColorButton() -// - SetColorEditOptions() -// - ColorTooltip() [Internal] -// - ColorEditOptionsPopup() [Internal] -// - ColorPickerOptionsPopup() [Internal] -//------------------------------------------------------------------------- - -bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags) -{ - return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha); -} - -// Edit colors components (each component in 0.0f..1.0f range). -// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. -// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item. -bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const float square_sz = GetFrameHeight(); - const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x); - const float w_items_all = CalcItemWidth() - w_extra; - const char* label_display_end = FindRenderedTextEnd(label); - - BeginGroup(); - PushID(label); - - // If we're not showing any slider there's no point in doing any HSV conversions - const ImGuiColorEditFlags flags_untouched = flags; - if (flags & ImGuiColorEditFlags_NoInputs) - flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions; - - // Context menu: display and modify options (before defaults are applied) - if (!(flags & ImGuiColorEditFlags_NoOptions)) - ColorEditOptionsPopup(col, flags); - - // Read stored options - if (!(flags & ImGuiColorEditFlags__InputsMask)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask); - if (!(flags & ImGuiColorEditFlags__DataTypeMask)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask); - if (!(flags & ImGuiColorEditFlags__PickerMask)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask); - flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask)); - - const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0; - const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0; - const int components = alpha ? 4 : 3; - - // Convert to the formats we need - float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f }; - if (flags & ImGuiColorEditFlags_HSV) - ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]); - int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) }; - - bool value_changed = false; - bool value_changed_as_float = false; - - if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) - { - // RGB/HSV 0..255 Sliders - const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components)); - const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1))); - - const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x); - const char* ids[4] = { "##X", "##Y", "##Z", "##W" }; - const char* fmt_table_int[3][4] = - { - { "%3d", "%3d", "%3d", "%3d" }, // Short display - { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA - { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA - }; - const char* fmt_table_float[3][4] = - { - { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display - { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA - { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA - }; - const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1; - - PushItemWidth(w_item_one); - for (int n = 0; n < components; n++) - { - if (n > 0) - SameLine(0, style.ItemInnerSpacing.x); - if (n + 1 == components) - PushItemWidth(w_item_last); - if (flags & ImGuiColorEditFlags_Float) - value_changed = value_changed_as_float = value_changed | DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); - else - value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); - if (!(flags & ImGuiColorEditFlags_NoOptions)) - OpenPopupOnItemClick("context"); - } - PopItemWidth(); - PopItemWidth(); - } - else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0) - { - // RGB Hexadecimal Input - char buf[64]; - if (alpha) - ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255)); - else - ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255)); - PushItemWidth(w_items_all); - if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase)) - { - value_changed = true; - char* p = buf; - while (*p == '#' || ImCharIsBlankA(*p)) - p++; - i[0] = i[1] = i[2] = i[3] = 0; - if (alpha) - sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned) - else - sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); - } - if (!(flags & ImGuiColorEditFlags_NoOptions)) - OpenPopupOnItemClick("context"); - PopItemWidth(); - } - - ImGuiWindow* picker_active_window = NULL; - if (!(flags & ImGuiColorEditFlags_NoSmallPreview)) - { - if (!(flags & ImGuiColorEditFlags_NoInputs)) - SameLine(0, style.ItemInnerSpacing.x); - - const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f); - if (ColorButton("##ColorButton", col_v4, flags)) - { - if (!(flags & ImGuiColorEditFlags_NoPicker)) - { - // Store current color and open a picker - g.ColorPickerRef = col_v4; - OpenPopup("picker"); - SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y)); - } - } - if (!(flags & ImGuiColorEditFlags_NoOptions)) - OpenPopupOnItemClick("context"); - - if (BeginPopup("picker")) - { - picker_active_window = g.CurrentWindow; - if (label != label_display_end) - { - TextUnformatted(label, label_display_end); - Separator(); - } - ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; - ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; - PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes? - value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x); - PopItemWidth(); - EndPopup(); - } - } - - if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel)) - { - SameLine(0, style.ItemInnerSpacing.x); - TextUnformatted(label, label_display_end); - } - - // Convert back - if (picker_active_window == NULL) - { - if (!value_changed_as_float) - for (int n = 0; n < 4; n++) - f[n] = i[n] / 255.0f; - if (flags & ImGuiColorEditFlags_HSV) - ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]); - if (value_changed) - { - col[0] = f[0]; - col[1] = f[1]; - col[2] = f[2]; - if (alpha) - col[3] = f[3]; - } - } - - PopID(); - EndGroup(); - - // Drag and Drop Target - // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. - if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) - { - memcpy((float*)col, payload->Data, sizeof(float) * 3); - value_changed = true; - } - if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F)) - { - memcpy((float*)col, payload->Data, sizeof(float) * components); - value_changed = true; - } - EndDragDropTarget(); - } - - // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). - if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window) - window->DC.LastItemId = g.ActiveId; - - if (value_changed) - MarkItemEdited(window->DC.LastItemId); - - return value_changed; -} - -bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags) -{ - float col4[4] = { col[0], col[1], col[2], 1.0f }; - if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha)) - return false; - col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2]; - return true; -} - -static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b) -{ - float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f; - int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t); - int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t); - int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t); - return IM_COL32(r, g, b, 0xFF); -} - -// Helper for ColorPicker4() -// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that. -// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether. -void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) - { - ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col)); - ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col)); - window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags); - - int yi = 0; - for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++) - { - float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y); - if (y2 <= y1) - continue; - for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f) - { - float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x); - if (x2 <= x1) - continue; - int rounding_corners_flags_cell = 0; - if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; } - if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; } - rounding_corners_flags_cell &= rounding_corners_flags; - window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell); - } - } - } - else - { - window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags); - } -} - -// Helper for ColorPicker4() -static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w) -{ - ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK); - ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE); - ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32_BLACK); - ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE); -} - -// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. -// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..) -bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - ImDrawList* draw_list = window->DrawList; - - ImGuiStyle& style = g.Style; - ImGuiIO& io = g.IO; - - PushID(label); - BeginGroup(); - - if (!(flags & ImGuiColorEditFlags_NoSidePreview)) - flags |= ImGuiColorEditFlags_NoSmallPreview; - - // Context menu: display and store options. - if (!(flags & ImGuiColorEditFlags_NoOptions)) - ColorPickerOptionsPopup(col, flags); - - // Read stored options - if (!(flags & ImGuiColorEditFlags__PickerMask)) - flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask; - IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected - if (!(flags & ImGuiColorEditFlags_NoOptions)) - flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar); - - // Setup - int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4; - bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha); - ImVec2 picker_pos = window->DC.CursorPos; - float square_sz = GetFrameHeight(); - float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars - float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box - float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x; - float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x; - float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f); - - float backup_initial_col[4]; - memcpy(backup_initial_col, col, components * sizeof(float)); - - float wheel_thickness = sv_picker_size * 0.08f; - float wheel_r_outer = sv_picker_size * 0.50f; - float wheel_r_inner = wheel_r_outer - wheel_thickness; - ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f); - - // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic. - float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f); - ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point. - ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point. - ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point. - - float H,S,V; - ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V); - - bool value_changed = false, value_changed_h = false, value_changed_sv = false; - - PushItemFlag(ImGuiItemFlags_NoNav, true); - if (flags & ImGuiColorEditFlags_PickerHueWheel) - { - // Hue wheel + SV triangle logic - InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size)); - if (IsItemActive()) - { - ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center; - ImVec2 current_off = g.IO.MousePos - wheel_center; - float initial_dist2 = ImLengthSqr(initial_off); - if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1)) - { - // Interactive with Hue wheel - H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f; - if (H < 0.0f) - H += 1.0f; - value_changed = value_changed_h = true; - } - float cos_hue_angle = ImCos(-H * 2.0f * IM_PI); - float sin_hue_angle = ImSin(-H * 2.0f * IM_PI); - if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle))) - { - // Interacting with SV triangle - ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle); - if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated)) - current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated); - float uu, vv, ww; - ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww); - V = ImClamp(1.0f - vv, 0.0001f, 1.0f); - S = ImClamp(uu / V, 0.0001f, 1.0f); - value_changed = value_changed_sv = true; - } - } - if (!(flags & ImGuiColorEditFlags_NoOptions)) - OpenPopupOnItemClick("context"); - } - else if (flags & ImGuiColorEditFlags_PickerHueBar) - { - // SV rectangle logic - InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size)); - if (IsItemActive()) - { - S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1)); - V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); - value_changed = value_changed_sv = true; - } - if (!(flags & ImGuiColorEditFlags_NoOptions)) - OpenPopupOnItemClick("context"); - - // Hue bar logic - SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); - InvisibleButton("hue", ImVec2(bars_width, sv_picker_size)); - if (IsItemActive()) - { - H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); - value_changed = value_changed_h = true; - } - } - - // Alpha bar logic - if (alpha_bar) - { - SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y)); - InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size)); - if (IsItemActive()) - { - col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); - value_changed = true; - } - } - PopItemFlag(); // ImGuiItemFlags_NoNav - - if (!(flags & ImGuiColorEditFlags_NoSidePreview)) - { - SameLine(0, style.ItemInnerSpacing.x); - BeginGroup(); - } - - if (!(flags & ImGuiColorEditFlags_NoLabel)) - { - const char* label_display_end = FindRenderedTextEnd(label); - if (label != label_display_end) - { - if ((flags & ImGuiColorEditFlags_NoSidePreview)) - SameLine(0, style.ItemInnerSpacing.x); - TextUnformatted(label, label_display_end); - } - } - - if (!(flags & ImGuiColorEditFlags_NoSidePreview)) - { - PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true); - ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); - if ((flags & ImGuiColorEditFlags_NoLabel)) - Text("Current"); - ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)); - if (ref_col != NULL) - { - Text("Original"); - ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]); - if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2))) - { - memcpy(col, ref_col, components * sizeof(float)); - value_changed = true; - } - } - PopItemFlag(); - EndGroup(); - } - - // Convert back color to RGB - if (value_changed_h || value_changed_sv) - ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]); - - // R,G,B and H,S,V slider color editor - bool value_changed_fix_hue_wrap = false; - if ((flags & ImGuiColorEditFlags_NoInputs) == 0) - { - PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); - ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; - ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; - if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0) - if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB)) - { - // FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget. - // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050) - value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap); - value_changed = true; - } - if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0) - value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV); - if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0) - value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX); - PopItemWidth(); - } - - // Try to cancel hue wrap (after ColorEdit4 call), if any - if (value_changed_fix_hue_wrap) - { - float new_H, new_S, new_V; - ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V); - if (new_H <= 0 && H > 0) - { - if (new_V <= 0 && V != new_V) - ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]); - else if (new_S <= 0) - ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]); - } - } - - ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z); - ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f); - ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f)); - - const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) }; - ImVec2 sv_cursor_pos; - - if (flags & ImGuiColorEditFlags_PickerHueWheel) - { - // Render Hue Wheel - const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out). - const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12); - for (int n = 0; n < 6; n++) - { - const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps; - const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps; - const int vert_start_idx = draw_list->VtxBuffer.Size; - draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc); - draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness); - const int vert_end_idx = draw_list->VtxBuffer.Size; - - // Paint colors over existing vertices - ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner); - ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner); - ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]); - } - - // Render Cursor + preview on Hue Wheel - float cos_hue_angle = ImCos(H * 2.0f * IM_PI); - float sin_hue_angle = ImSin(H * 2.0f * IM_PI); - ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f); - float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f; - int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32); - draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments); - draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments); - draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments); - - // Render SV triangle (rotated according to hue) - ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle); - ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle); - ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle); - ImVec2 uv_white = GetFontTexUvWhitePixel(); - draw_list->PrimReserve(6, 6); - draw_list->PrimVtx(tra, uv_white, hue_color32); - draw_list->PrimVtx(trb, uv_white, hue_color32); - draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE); - draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS); - draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK); - draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS); - draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f); - sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V)); - } - else if (flags & ImGuiColorEditFlags_PickerHueBar) - { - // Render SV Square - draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE); - draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK); - RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f); - sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S) * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much - sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2); - - // Render Hue Bar - for (int i = 0; i < 6; ++i) - draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]); - float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f); - RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f); - RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); - } - - // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) - float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f; - draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12); - draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12); - draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12); - - // Render alpha bar - if (alpha_bar) - { - float alpha = ImSaturate(col[3]); - ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size); - RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f)); - draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK); - float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f); - RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f); - RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f); - } - - EndGroup(); - - if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0) - value_changed = false; - if (value_changed) - MarkItemEdited(window->DC.LastItemId); - - PopID(); - - return value_changed; -} - -// A little colored square. Return true when clicked. -// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. -// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. -bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiID id = window->GetID(desc_id); - float default_size = GetFrameHeight(); - if (size.x == 0.0f) - size.x = default_size; - if (size.y == 0.0f) - size.y = default_size; - const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); - ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f); - if (!ItemAdd(bb, id)) - return false; - - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held); - - if (flags & ImGuiColorEditFlags_NoAlpha) - flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); - - ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f); - float grid_step = ImMin(size.x, size.y) / 2.99f; - float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f); - ImRect bb_inner = bb; - float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. - bb_inner.Expand(off); - if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f) - { - float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f); - RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight); - window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft); - } - else - { - // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha - ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha; - if (col_source.w < 1.0f) - RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); - else - window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All); - } - RenderNavHighlight(bb, id); - if (g.Style.FrameBorderSize > 0.0f) - RenderFrameBorder(bb.Min, bb.Max, rounding); - else - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border - - // Drag and Drop Source - // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test. - if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource()) - { - if (flags & ImGuiColorEditFlags_NoAlpha) - SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once); - else - SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once); - ColorButton(desc_id, col, flags); - SameLine(); - TextUnformatted("Color"); - EndDragDropSource(); - } - - // Tooltip - if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered) - ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); - - if (pressed) - MarkItemEdited(id); - - return pressed; -} - -void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags) -{ - ImGuiContext& g = *GImGui; - if ((flags & ImGuiColorEditFlags__InputsMask) == 0) - flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask; - if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0) - flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask; - if ((flags & ImGuiColorEditFlags__PickerMask) == 0) - flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask; - IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected - IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected - g.ColorEditOptions = flags; -} - -// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. -void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags) -{ - ImGuiContext& g = *GImGui; - - int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); - BeginTooltipEx(0, true); - - const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text; - if (text_end > text) - { - TextUnformatted(text, text_end); - Separator(); - } - - ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); - ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); - SameLine(); - if (flags & ImGuiColorEditFlags_NoAlpha) - Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]); - else - Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]); - EndTooltip(); -} - -void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) -{ - bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask); - bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask); - if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) - return; - ImGuiContext& g = *GImGui; - ImGuiColorEditFlags opts = g.ColorEditOptions; - if (allow_opt_inputs) - { - if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB; - if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV; - if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX; - } - if (allow_opt_datatype) - { - if (allow_opt_inputs) Separator(); - if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8; - if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float; - } - - if (allow_opt_inputs || allow_opt_datatype) - Separator(); - if (Button("Copy as..", ImVec2(-1,0))) - OpenPopup("Copy"); - if (BeginPopup("Copy")) - { - int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); - char buf[64]; - ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); - if (Selectable(buf)) - SetClipboardText(buf); - ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca); - if (Selectable(buf)) - SetClipboardText(buf); - if (flags & ImGuiColorEditFlags_NoAlpha) - ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb); - else - ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca); - if (Selectable(buf)) - SetClipboardText(buf); - EndPopup(); - } - - g.ColorEditOptions = opts; - EndPopup(); -} - -void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags) -{ - bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask); - bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); - if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context")) - return; - ImGuiContext& g = *GImGui; - if (allow_opt_picker) - { - ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function - ImGui::PushItemWidth(picker_size.x); - for (int picker_type = 0; picker_type < 2; picker_type++) - { - // Draw small/thumbnail version of each picker type (over an invisible button for selection) - if (picker_type > 0) ImGui::Separator(); - ImGui::PushID(picker_type); - ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha); - if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar; - if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel; - ImVec2 backup_pos = ImGui::GetCursorScreenPos(); - if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup - g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask); - ImGui::SetCursorScreenPos(backup_pos); - ImVec4 dummy_ref_col; - memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4)); - ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags); - ImGui::PopID(); - } - ImGui::PopItemWidth(); - } - if (allow_opt_alpha_bar) - { - if (allow_opt_picker) ImGui::Separator(); - ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); - } - ImGui::EndPopup(); -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: TreeNode, CollapsingHeader, etc. -//------------------------------------------------------------------------- -// - TreeNode() -// - TreeNodeV() -// - TreeNodeEx() -// - TreeNodeExV() -// - TreeNodeBehavior() [Internal] -// - TreePush() -// - TreePop() -// - TreeAdvanceToLabelPos() -// - GetTreeNodeToLabelSpacing() -// - SetNextTreeNodeOpen() -// - CollapsingHeader() -//------------------------------------------------------------------------- - -bool ImGui::TreeNode(const char* str_id, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool is_open = TreeNodeExV(str_id, 0, fmt, args); - va_end(args); - return is_open; -} - -bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool is_open = TreeNodeExV(ptr_id, 0, fmt, args); - va_end(args); - return is_open; -} - -bool ImGui::TreeNode(const char* label) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - return TreeNodeBehavior(window->GetID(label), 0, label, NULL); -} - -bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args) -{ - return TreeNodeExV(str_id, 0, fmt, args); -} - -bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args) -{ - return TreeNodeExV(ptr_id, 0, fmt, args); -} - -bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - return TreeNodeBehavior(window->GetID(label), flags, label, NULL); -} - -bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool is_open = TreeNodeExV(str_id, flags, fmt, args); - va_end(args); - return is_open; -} - -bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool is_open = TreeNodeExV(ptr_id, flags, fmt, args); - va_end(args); - return is_open; -} - -bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end); -} - -bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end); -} - -bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) -{ - if (flags & ImGuiTreeNodeFlags_Leaf) - return true; - - // We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions) - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImGuiStorage* storage = window->DC.StateStorage; - - bool is_open; - if (g.NextTreeNodeOpenCond != 0) - { - if (g.NextTreeNodeOpenCond & ImGuiCond_Always) - { - is_open = g.NextTreeNodeOpenVal; - storage->SetInt(id, is_open); - } - else - { - // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently. - const int stored_value = storage->GetInt(id, -1); - if (stored_value == -1) - { - is_open = g.NextTreeNodeOpenVal; - storage->SetInt(id, is_open); - } - else - { - is_open = stored_value != 0; - } - } - g.NextTreeNodeOpenCond = 0; - } - else - { - is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0; - } - - // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). - // NB- If we are above max depth we still allow manually opened nodes to be logged. - if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth) - is_open = true; - - return is_open; -} - -bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0; - const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f); - - if (!label_end) - label_end = FindRenderedTextEnd(label); - const ImVec2 label_size = CalcTextSize(label, label_end, false); - - // We vertically grow up to current line height up the typical widget height. - const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it - const float frame_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2); - ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height)); - if (display_frame) - { - // Framed header expand a little outside the default padding - frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1; - frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1; - } - - const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing - const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser - ItemSize(ImVec2(text_width, frame_height), text_base_offset_y); - - // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing - // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not) - const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y); - bool is_open = TreeNodeBehaviorIsOpen(id, flags); - - // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child. - // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). - // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero. - if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) - window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth); - - bool item_add = ItemAdd(interact_bb, id); - window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; - window->DC.LastItemDisplayRect = frame_bb; - - if (!item_add) - { - if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) - TreePushRawID(id); - return is_open; - } - - // Flags that affects opening behavior: - // - 0(default) ..................... single-click anywhere to open - // - OpenOnDoubleClick .............. double-click anywhere to open - // - OpenOnArrow .................... single-click on arrow to open - // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open - ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0); - if (!(flags & ImGuiTreeNodeFlags_Leaf)) - button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; - if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) - button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0); - - bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags); - if (!(flags & ImGuiTreeNodeFlags_Leaf)) - { - bool toggled = false; - if (pressed) - { - toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id); - if (flags & ImGuiTreeNodeFlags_OpenOnArrow) - toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover); - if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) - toggled |= g.IO.MouseDoubleClicked[0]; - if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again. - toggled = false; - } - - if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open) - { - toggled = true; - NavMoveRequestCancel(); - } - if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority? - { - toggled = true; - NavMoveRequestCancel(); - } - - if (toggled) - { - is_open = !is_open; - window->DC.StateStorage->SetInt(id, is_open); - } - } - if (flags & ImGuiTreeNodeFlags_AllowItemOverlap) - SetItemAllowOverlap(); - - // Render - const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y); - if (display_frame) - { - // Framed type - RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding); - RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin); - RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f); - if (g.LogEnabled) - { - // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here. - const char log_prefix[] = "\n##"; - const char log_suffix[] = "##"; - LogRenderedText(&text_pos, log_prefix, log_prefix+3); - RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); - LogRenderedText(&text_pos, log_suffix+1, log_suffix+3); - } - else - { - RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); - } - } - else - { - // Unframed typed for tree nodes - if (hovered || (flags & ImGuiTreeNodeFlags_Selected)) - { - RenderFrame(frame_bb.Min, frame_bb.Max, col, false); - RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin); - } - - if (flags & ImGuiTreeNodeFlags_Bullet) - RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y)); - else if (!(flags & ImGuiTreeNodeFlags_Leaf)) - RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f); - if (g.LogEnabled) - LogRenderedText(&text_pos, ">"); - RenderText(text_pos, label, label_end, false); - } - - if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) - TreePushRawID(id); - return is_open; -} - -void ImGui::TreePush(const char* str_id) -{ - ImGuiWindow* window = GetCurrentWindow(); - Indent(); - window->DC.TreeDepth++; - PushID(str_id ? str_id : "#TreePush"); -} - -void ImGui::TreePush(const void* ptr_id) -{ - ImGuiWindow* window = GetCurrentWindow(); - Indent(); - window->DC.TreeDepth++; - PushID(ptr_id ? ptr_id : (const void*)"#TreePush"); -} - -void ImGui::TreePushRawID(ImGuiID id) -{ - ImGuiWindow* window = GetCurrentWindow(); - Indent(); - window->DC.TreeDepth++; - window->IDStack.push_back(id); -} - -void ImGui::TreePop() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - Unindent(); - - window->DC.TreeDepth--; - if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) - if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth))) - { - SetNavID(window->IDStack.back(), g.NavLayer); - NavMoveRequestCancel(); - } - window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1; - - IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much. - PopID(); -} - -void ImGui::TreeAdvanceToLabelPos() -{ - ImGuiContext& g = *GImGui; - g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing(); -} - -// Horizontal distance preceding label when using TreeNode() or Bullet() -float ImGui::GetTreeNodeToLabelSpacing() -{ - ImGuiContext& g = *GImGui; - return g.FontSize + (g.Style.FramePadding.x * 2.0f); -} - -void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond) -{ - ImGuiContext& g = *GImGui; - if (g.CurrentWindow->SkipItems) - return; - g.NextTreeNodeOpenVal = is_open; - g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always; -} - -// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag). -// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode(). -bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label); -} - -bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - if (p_open && !*p_open) - return false; - - ImGuiID id = window->GetID(label); - bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label); - if (p_open) - { - // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc. - ImGuiContext& g = *GImGui; - ImGuiItemHoveredDataBackup last_item_backup; - float button_radius = g.FontSize * 0.5f; - ImVec2 button_center = ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_radius, window->DC.LastItemRect.GetCenter().y); - if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), button_center, button_radius)) - *p_open = false; - last_item_backup.Restore(); - } - - return is_open; -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: Selectable -//------------------------------------------------------------------------- -// - Selectable() -//------------------------------------------------------------------------- - -// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image. -// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id. -bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - - if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped. - PopClipRect(); - - ImGuiID id = window->GetID(label); - ImVec2 label_size = CalcTextSize(label, NULL, true); - ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); - ImVec2 pos = window->DC.CursorPos; - pos.y += window->DC.CurrentLineTextBaseOffset; - ImRect bb_inner(pos, pos + size); - ItemSize(bb_inner); - - // Fill horizontal space. - ImVec2 window_padding = window->WindowPadding; - float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x; - float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x); - ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y); - ImRect bb(pos, pos + size_draw); - if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth)) - bb.Max.x += window_padding.x; - - // Selectables are tightly packed together, we extend the box to cover spacing between selectable. - float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f); - float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f); - float spacing_R = style.ItemSpacing.x - spacing_L; - float spacing_D = style.ItemSpacing.y - spacing_U; - bb.Min.x -= spacing_L; - bb.Min.y -= spacing_U; - bb.Max.x += spacing_R; - bb.Max.y += spacing_D; - if (!ItemAdd(bb, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id)) - { - if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) - PushColumnClipRect(); - return false; - } - - // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries - ImGuiButtonFlags button_flags = 0; - if (flags & ImGuiSelectableFlags_NoHoldingActiveID) button_flags |= ImGuiButtonFlags_NoHoldingActiveID; - if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick; - if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease; - if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled; - if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); - if (flags & ImGuiSelectableFlags_Disabled) - selected = false; - - // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets) - if (pressed || hovered) - if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) - { - g.NavDisableHighlight = true; - SetNavID(id, window->DC.NavLayerCurrent); - } - if (pressed) - MarkItemEdited(id); - - // Render - if (hovered || selected) - { - const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(bb.Min, bb.Max, col, false, 0.0f); - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); - } - - if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) - { - PushColumnClipRect(); - bb.Max.x -= (GetContentRegionMax().x - max_x); - } - - if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); - RenderTextClipped(bb_inner.Min, bb.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f)); - if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor(); - - // Automatically close popups - if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) - CloseCurrentPopup(); - return pressed; -} - -bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg) -{ - if (Selectable(label, *p_selected, flags, size_arg)) - { - *p_selected = !*p_selected; - return true; - } - return false; -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: ListBox -//------------------------------------------------------------------------- -// - ListBox() -// - ListBoxHeader() -// - ListBoxFooter() -//------------------------------------------------------------------------- - -// FIXME: Rename to BeginListBox() -// Helper to calculate the size of a listbox and display a label on the right. -// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty" -bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - const ImGuiStyle& style = GetStyle(); - const ImGuiID id = GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - - // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. - ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y); - ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y)); - ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); - ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); - window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy. - - BeginGroup(); - if (label_size.x > 0) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - - BeginChildFrame(id, frame_bb.GetSize()); - return true; -} - -// FIXME: Rename to BeginListBox() -bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items) -{ - // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. - // We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size. - // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution. - if (height_in_items < 0) - height_in_items = ImMin(items_count, 7); - float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f); - - // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild(). - ImVec2 size; - size.x = 0.0f; - size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y; - return ListBoxHeader(label, size); -} - -// FIXME: Rename to EndListBox() -void ImGui::ListBoxFooter() -{ - ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow; - const ImRect bb = parent_window->DC.LastItemRect; - const ImGuiStyle& style = GetStyle(); - - EndChildFrame(); - - // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect) - // We call SameLine() to restore DC.CurrentLine* data - SameLine(); - parent_window->DC.CursorPos = bb.Min; - ItemSize(bb, style.FramePadding.y); - EndGroup(); -} - -bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items) -{ - const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items); - return value_changed; -} - -bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items) -{ - if (!ListBoxHeader(label, items_count, height_in_items)) - return false; - - // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper. - ImGuiContext& g = *GImGui; - bool value_changed = false; - ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. - while (clipper.Step()) - for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - { - const bool item_selected = (i == *current_item); - const char* item_text; - if (!items_getter(data, i, &item_text)) - item_text = "*Unknown item*"; - - PushID(i); - if (Selectable(item_text, item_selected)) - { - *current_item = i; - value_changed = true; - } - if (item_selected) - SetItemDefaultFocus(); - PopID(); - } - ListBoxFooter(); - if (value_changed) - MarkItemEdited(g.CurrentWindow->DC.LastItemId); - - return value_changed; -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: PlotLines, PlotHistogram -//------------------------------------------------------------------------- -// - PlotEx() [Internal] -// - PlotLines() -// - PlotHistogram() -//------------------------------------------------------------------------- - -void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - - const ImVec2 label_size = CalcTextSize(label, NULL, true); - if (graph_size.x == 0.0f) - graph_size.x = CalcItemWidth(); - if (graph_size.y == 0.0f) - graph_size.y = label_size.y + (style.FramePadding.y * 2); - - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y)); - const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding); - const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0)); - ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, 0, &frame_bb)) - return; - const bool hovered = ItemHoverable(inner_bb, 0); - - // Determine scale from values if not specified - if (scale_min == FLT_MAX || scale_max == FLT_MAX) - { - float v_min = FLT_MAX; - float v_max = -FLT_MAX; - for (int i = 0; i < values_count; i++) - { - const float v = values_getter(data, i); - v_min = ImMin(v_min, v); - v_max = ImMax(v_max, v); - } - if (scale_min == FLT_MAX) - scale_min = v_min; - if (scale_max == FLT_MAX) - scale_max = v_max; - } - - RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); - - if (values_count > 0) - { - int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); - int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0); - - // Tooltip on hover - int v_hovered = -1; - if (hovered) - { - const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f); - const int v_idx = (int)(t * item_count); - IM_ASSERT(v_idx >= 0 && v_idx < values_count); - - const float v0 = values_getter(data, (v_idx + values_offset) % values_count); - const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count); - if (plot_type == ImGuiPlotType_Lines) - SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1); - else if (plot_type == ImGuiPlotType_Histogram) - SetTooltip("%d: %8.4g", v_idx, v0); - v_hovered = v_idx; - } - - const float t_step = 1.0f / (float)res_w; - const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min)); - - float v0 = values_getter(data, (0 + values_offset) % values_count); - float t0 = 0.0f; - ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle - float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands - - const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram); - const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered); - - for (int n = 0; n < res_w; n++) - { - const float t1 = t0 + t_step; - const int v1_idx = (int)(t0 * item_count + 0.5f); - IM_ASSERT(v1_idx >= 0 && v1_idx < values_count); - const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count); - const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) ); - - // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. - ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0); - ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t)); - if (plot_type == ImGuiPlotType_Lines) - { - window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); - } - else if (plot_type == ImGuiPlotType_Histogram) - { - if (pos1.x >= pos0.x + 2.0f) - pos1.x -= 1.0f; - window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base); - } - - t0 = t1; - tp0 = tp1; - } - } - - // Text overlay - if (overlay_text) - RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f)); - - if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); -} - -struct ImGuiPlotArrayGetterData -{ - const float* Values; - int Stride; - - ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; } -}; - -static float Plot_ArrayGetter(void* data, int idx) -{ - ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data; - const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride); - return v; -} - -void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) -{ - ImGuiPlotArrayGetterData data(values, stride); - PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); -} - -void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) -{ - PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); -} - -void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride) -{ - ImGuiPlotArrayGetterData data(values, stride); - PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); -} - -void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size) -{ - PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size); -} - -//------------------------------------------------------------------------- -// [SECTION] Widgets: Value helpers -// Those is not very useful, legacy API. -//------------------------------------------------------------------------- -// - Value() -//------------------------------------------------------------------------- - -void ImGui::Value(const char* prefix, bool b) -{ - Text("%s: %s", prefix, (b ? "true" : "false")); -} - -void ImGui::Value(const char* prefix, int v) -{ - Text("%s: %d", prefix, v); -} - -void ImGui::Value(const char* prefix, unsigned int v) -{ - Text("%s: %d", prefix, v); -} - -void ImGui::Value(const char* prefix, float v, const char* float_format) -{ - if (float_format) - { - char fmt[64]; - ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format); - Text(fmt, prefix, v); - } - else - { - Text("%s: %.3f", prefix, v); - } -} - -//------------------------------------------------------------------------- -// [SECTION] MenuItem, BeginMenu, EndMenu, etc. -//------------------------------------------------------------------------- -// - ImGuiMenuColumns [Internal] -// - BeginMainMenuBar() -// - EndMainMenuBar() -// - BeginMenuBar() -// - EndMenuBar() -// - BeginMenu() -// - EndMenu() -// - MenuItem() -//------------------------------------------------------------------------- - -// Helpers for internal use -ImGuiMenuColumns::ImGuiMenuColumns() -{ - Count = 0; - Spacing = Width = NextWidth = 0.0f; - memset(Pos, 0, sizeof(Pos)); - memset(NextWidths, 0, sizeof(NextWidths)); -} - -void ImGuiMenuColumns::Update(int count, float spacing, bool clear) -{ - IM_ASSERT(Count <= IM_ARRAYSIZE(Pos)); - Count = count; - Width = NextWidth = 0.0f; - Spacing = spacing; - if (clear) memset(NextWidths, 0, sizeof(NextWidths)); - for (int i = 0; i < Count; i++) - { - if (i > 0 && NextWidths[i] > 0.0f) - Width += Spacing; - Pos[i] = (float)(int)Width; - Width += NextWidths[i]; - NextWidths[i] = 0.0f; - } -} - -float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double -{ - NextWidth = 0.0f; - NextWidths[0] = ImMax(NextWidths[0], w0); - NextWidths[1] = ImMax(NextWidths[1], w1); - NextWidths[2] = ImMax(NextWidths[2], w2); - for (int i = 0; i < 3; i++) - NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f); - return ImMax(Width, NextWidth); -} - -float ImGuiMenuColumns::CalcExtraSpace(float avail_w) -{ - return ImMax(0.0f, avail_w - Width); -} - -// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set. -bool ImGui::BeginMainMenuBar() -{ - ImGuiContext& g = *GImGui; - g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); - SetNextWindowPos(ImVec2(0.0f, 0.0f)); - SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y)); - PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0)); - ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; - bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar(); - PopStyleVar(2); - g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); - if (!is_open) - { - End(); - return false; - } - return true; -} - -void ImGui::EndMainMenuBar() -{ - EndMenuBar(); - - // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window - ImGuiContext& g = *GImGui; - if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0) - FocusFrontMostActiveWindowIgnoringOne(g.NavWindow); - - End(); -} - -bool ImGui::BeginMenuBar() -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - if (!(window->Flags & ImGuiWindowFlags_MenuBar)) - return false; - - IM_ASSERT(!window->DC.MenuBarAppending); - BeginGroup(); // Backup position on layer 0 - PushID("##menubar"); - - // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect. - // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy. - ImRect bar_rect = window->MenuBarRect(); - ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f)); - clip_rect.ClipWith(window->OuterRectClipped); - PushClipRect(clip_rect.Min, clip_rect.Max, false); - - window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y); - window->DC.LayoutType = ImGuiLayoutType_Horizontal; - window->DC.NavLayerCurrent++; - window->DC.NavLayerCurrentMask <<= 1; - window->DC.MenuBarAppending = true; - AlignTextToFramePadding(); - return true; -} - -void ImGui::EndMenuBar() -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return; - ImGuiContext& g = *GImGui; - - // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. - if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) - { - ImGuiWindow* nav_earliest_child = g.NavWindow; - while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu)) - nav_earliest_child = nav_earliest_child->ParentWindow; - if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None) - { - // To do so we claim focus back, restore NavId and then process the movement request for yet another frame. - // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost) - IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check - FocusWindow(window); - SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]); - g.NavLayer = 1; - g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. - g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; - NavMoveRequestCancel(); - } - } - - IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); - IM_ASSERT(window->DC.MenuBarAppending); - PopClipRect(); - PopID(); - window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. - window->DC.GroupStack.back().AdvanceCursor = false; - EndGroup(); // Restore position on layer 0 - window->DC.LayoutType = ImGuiLayoutType_Vertical; - window->DC.NavLayerCurrent--; - window->DC.NavLayerCurrentMask >>= 1; - window->DC.MenuBarAppending = false; -} - -bool ImGui::BeginMenu(const char* label, bool enabled) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - - ImVec2 label_size = CalcTextSize(label, NULL, true); - - bool pressed; - bool menu_is_open = IsPopupOpen(id); - bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back()); - ImGuiWindow* backed_nav_window = g.NavWindow; - if (menuset_is_open) - g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent) - - // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup). - ImVec2 popup_pos, pos = window->DC.CursorPos; - if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) - { - // Menu inside an horizontal menu bar - // Selectable extend their highlight by half ItemSpacing in each direction. - // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() - popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight()); - window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); - PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); - float w = label_size.x; - pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); - PopStyleVar(); - window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). - } - else - { - // Menu inside a menu - popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); - float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame - float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); - pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f)); - if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); - RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right); - if (!enabled) PopStyleColor(); - } - - const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id); - if (menuset_is_open) - g.NavWindow = backed_nav_window; - - bool want_open = false, want_close = false; - if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) - { - // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive. - bool moving_within_opened_triangle = false; - if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar)) - { - if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window) - { - ImRect next_window_rect = next_window->Rect(); - ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta; - ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR(); - ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR(); - float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack. - ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues - tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale? - tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f); - moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos); - //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug - } - } - - want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle); - want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed); - - if (g.NavActivateId == id) - { - want_close = menu_is_open; - want_open = !menu_is_open; - } - if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open - { - want_open = true; - NavMoveRequestCancel(); - } - } - else - { - // Menu bar - if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it - { - want_close = true; - want_open = menu_is_open = false; - } - else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others - { - want_open = true; - } - else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open - { - want_open = true; - NavMoveRequestCancel(); - } - } - - if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }' - want_close = true; - if (want_close && IsPopupOpen(id)) - ClosePopupToLevel(g.CurrentPopupStack.Size); - - if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size) - { - // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame. - OpenPopup(label); - return false; - } - - menu_is_open |= want_open; - if (want_open) - OpenPopup(label); - - if (menu_is_open) - { - // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) - SetNextWindowPos(popup_pos, ImGuiCond_Always); - ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; - if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) - flags |= ImGuiWindowFlags_ChildWindow; - menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) - } - - return menu_is_open; -} - -void ImGui::EndMenu() -{ - // Nav: When a left move request _within our child menu_ failed, close the menu. - // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs. - // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction. - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical) - { - ClosePopupToLevel(g.OpenPopupStack.Size - 1); - NavMoveRequestCancel(); - } - - EndPopup(); -} - -bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled) -{ - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - ImGuiContext& g = *GImGui; - ImGuiStyle& style = g.Style; - ImVec2 pos = window->DC.CursorPos; - ImVec2 label_size = CalcTextSize(label, NULL, true); - - ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled); - bool pressed; - if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) - { - // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful - // Note that in this situation we render neither the shortcut neither the selected tick mark - float w = label_size.x; - window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); - PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); - pressed = Selectable(label, false, flags, ImVec2(w, 0.0f)); - PopStyleVar(); - window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). - } - else - { - ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f); - float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame - float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w); - pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f)); - if (shortcut_size.x > 0.0f) - { - PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]); - RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false); - PopStyleColor(); - } - if (selected) - RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f); - } - return pressed; -} - -bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled) -{ - if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled)) - { - if (p_selected) - *p_selected = !*p_selected; - return true; - } - return false; -} diff --git a/Framework/src/imgui/imstb_rectpack.h b/Framework/src/imgui/imstb_rectpack.h deleted file mode 100644 index 3660d15fd3..0000000000 --- a/Framework/src/imgui/imstb_rectpack.h +++ /dev/null @@ -1,613 +0,0 @@ -// stb_rect_pack.h - v0.11 - public domain - rectangle packing -// Sean Barrett 2014 -// -// Useful for e.g. packing rectangular textures into an atlas. -// Does not do rotation. -// -// Not necessarily the awesomest packing method, but better than -// the totally naive one in stb_truetype (which is primarily what -// this is meant to replace). -// -// Has only had a few tests run, may have issues. -// -// More docs to come. -// -// No memory allocations; uses qsort() and assert() from stdlib. -// Can override those by defining STBRP_SORT and STBRP_ASSERT. -// -// This library currently uses the Skyline Bottom-Left algorithm. -// -// Please note: better rectangle packers are welcome! Please -// implement them to the same API, but with a different init -// function. -// -// Credits -// -// Library -// Sean Barrett -// Minor features -// Martins Mozeiko -// github:IntellectualKitty -// -// Bugfixes / warning fixes -// Jeremy Jaussaud -// -// Version history: -// -// 0.11 (2017-03-03) return packing success/fail result -// 0.10 (2016-10-25) remove cast-away-const to avoid warnings -// 0.09 (2016-08-27) fix compiler warnings -// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) -// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) -// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort -// 0.05: added STBRP_ASSERT to allow replacing assert -// 0.04: fixed minor bug in STBRP_LARGE_RECTS support -// 0.01: initial release -// -// LICENSE -// -// See end of file for license information. - -////////////////////////////////////////////////////////////////////////////// -// -// INCLUDE SECTION -// - -#ifndef STB_INCLUDE_STB_RECT_PACK_H -#define STB_INCLUDE_STB_RECT_PACK_H - -#define STB_RECT_PACK_VERSION 1 - -#ifdef STBRP_STATIC -#define STBRP_DEF static -#else -#define STBRP_DEF extern -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct stbrp_context stbrp_context; -typedef struct stbrp_node stbrp_node; -typedef struct stbrp_rect stbrp_rect; - -#ifdef STBRP_LARGE_RECTS -typedef int stbrp_coord; -#else -typedef unsigned short stbrp_coord; -#endif - -STBRP_DEF int stbrp_pack_rects(stbrp_context* context, stbrp_rect* rects, int num_rects); -// Assign packed locations to rectangles. The rectangles are of type -// 'stbrp_rect' defined below, stored in the array 'rects', and there -// are 'num_rects' many of them. -// -// Rectangles which are successfully packed have the 'was_packed' flag -// set to a non-zero value and 'x' and 'y' store the minimum location -// on each axis (i.e. bottom-left in cartesian coordinates, top-left -// if you imagine y increasing downwards). Rectangles which do not fit -// have the 'was_packed' flag set to 0. -// -// You should not try to access the 'rects' array from another thread -// while this function is running, as the function temporarily reorders -// the array while it executes. -// -// To pack into another rectangle, you need to call stbrp_init_target -// again. To continue packing into the same rectangle, you can call -// this function again. Calling this multiple times with multiple rect -// arrays will probably produce worse packing results than calling it -// a single time with the full rectangle array, but the option is -// available. -// -// The function returns 1 if all of the rectangles were successfully -// packed and 0 otherwise. - -struct stbrp_rect { - // reserved for your use: - int id; - - // input: - stbrp_coord w, h; - - // output: - stbrp_coord x, y; - int was_packed; // non-zero if valid packing - -}; // 16 bytes, nominally - -STBRP_DEF void stbrp_init_target(stbrp_context* context, int width, int height, stbrp_node* nodes, int num_nodes); -// Initialize a rectangle packer to: -// pack a rectangle that is 'width' by 'height' in dimensions -// using temporary storage provided by the array 'nodes', which is 'num_nodes' long -// -// You must call this function every time you start packing into a new target. -// -// There is no "shutdown" function. The 'nodes' memory must stay valid for -// the following stbrp_pack_rects() call (or calls), but can be freed after -// the call (or calls) finish. -// -// Note: to guarantee best results, either: -// 1. make sure 'num_nodes' >= 'width' -// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' -// -// If you don't do either of the above things, widths will be quantized to multiples -// of small integers to guarantee the algorithm doesn't run out of temporary storage. -// -// If you do #2, then the non-quantized algorithm will be used, but the algorithm -// may run out of temporary storage and be unable to pack some rectangles. - -STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context* context, int allow_out_of_mem); -// Optionally call this function after init but before doing any packing to -// change the handling of the out-of-temp-memory scenario, described above. -// If you call init again, this will be reset to the default (false). - -STBRP_DEF void stbrp_setup_heuristic(stbrp_context* context, int heuristic); -// Optionally select which packing heuristic the library should use. Different -// heuristics will produce better/worse results for different data sets. -// If you call init again, this will be reset to the default. - -enum { - STBRP_HEURISTIC_Skyline_default = 0, - STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, - STBRP_HEURISTIC_Skyline_BF_sortHeight -}; - -////////////////////////////////////////////////////////////////////////////// -// -// the details of the following structures don't matter to you, but they must -// be visible so you can handle the memory allocations for them - -struct stbrp_node { - stbrp_coord x, y; - stbrp_node* next; -}; - -struct stbrp_context { - int width; - int height; - int align; - int init_mode; - int heuristic; - int num_nodes; - stbrp_node* active_head; - stbrp_node* free_head; - stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' -}; - -#ifdef __cplusplus -} -#endif - -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// IMPLEMENTATION SECTION -// - -#ifdef STB_RECT_PACK_IMPLEMENTATION -#ifndef STBRP_SORT -#include -#define STBRP_SORT qsort -#endif - -#ifndef STBRP_ASSERT -#include -#define STBRP_ASSERT assert -#endif - -#ifdef _MSC_VER -#define STBRP__NOTUSED(v) (void)(v) -#define STBRP__CDECL __cdecl -#else -#define STBRP__NOTUSED(v) (void)sizeof(v) -#define STBRP__CDECL -#endif - -enum { STBRP__INIT_skyline = 1 }; - -STBRP_DEF void stbrp_setup_heuristic(stbrp_context* context, int heuristic) -{ - switch (context->init_mode) { - case STBRP__INIT_skyline: - STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || - heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); - context->heuristic = heuristic; - break; - default: - STBRP_ASSERT(0); - } -} - -STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context* context, int allow_out_of_mem) -{ - if (allow_out_of_mem) - // if it's ok to run out of memory, then don't bother aligning them; - // this gives better packing, but may fail due to OOM (even though - // the rectangles easily fit). @TODO a smarter approach would be to only - // quantize once we've hit OOM, then we could get rid of this parameter. - context->align = 1; - else { - // if it's not ok to run out of memory, then quantize the widths - // so that num_nodes is always enough nodes. - // - // I.e. num_nodes * align >= width - // align >= width / num_nodes - // align = ceil(width/num_nodes) - - context->align = (context->width + context->num_nodes - 1) / context->num_nodes; - } -} - -STBRP_DEF void stbrp_init_target(stbrp_context* context, int width, int height, stbrp_node* nodes, int num_nodes) -{ - int i; -#ifndef STBRP_LARGE_RECTS - STBRP_ASSERT(width <= 0xffff && height <= 0xffff); -#endif - - for (i = 0; i < num_nodes - 1; ++i) - nodes[i].next = &nodes[i + 1]; - nodes[i].next = NULL; - context->init_mode = STBRP__INIT_skyline; - context->heuristic = STBRP_HEURISTIC_Skyline_default; - context->free_head = &nodes[0]; - context->active_head = &context->extra[0]; - context->width = width; - context->height = height; - context->num_nodes = num_nodes; - stbrp_setup_allow_out_of_mem(context, 0); - - // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) - context->extra[0].x = 0; - context->extra[0].y = 0; - context->extra[0].next = &context->extra[1]; - context->extra[1].x = (stbrp_coord)width; -#ifdef STBRP_LARGE_RECTS - context->extra[1].y = (1 << 30); -#else - context->extra[1].y = 65535; -#endif - context->extra[1].next = NULL; -} - -// find minimum y position if it starts at x1 -static int stbrp__skyline_find_min_y(stbrp_context* c, stbrp_node* first, int x0, int width, int* pwaste) -{ - stbrp_node* node = first; - int x1 = x0 + width; - int min_y, visited_width, waste_area; - - STBRP__NOTUSED(c); - - STBRP_ASSERT(first->x <= x0); - -#if 0 - // skip in case we're past the node - while (node->next->x <= x0) - ++node; -#else - STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency -#endif - - STBRP_ASSERT(node->x <= x0); - - min_y = 0; - waste_area = 0; - visited_width = 0; - while (node->x < x1) { - if (node->y > min_y) { - // raise min_y higher. - // we've accounted for all waste up to min_y, - // but we'll now add more waste for everything we've visted - waste_area += visited_width * (node->y - min_y); - min_y = node->y; - // the first time through, visited_width might be reduced - if (node->x < x0) - visited_width += node->next->x - x0; - else - visited_width += node->next->x - node->x; - } else { - // add waste area - int under_width = node->next->x - node->x; - if (under_width + visited_width > width) - under_width = width - visited_width; - waste_area += under_width * (min_y - node->y); - visited_width += under_width; - } - node = node->next; - } - - *pwaste = waste_area; - return min_y; -} - -typedef struct { - int x, y; - stbrp_node** prev_link; -} stbrp__findresult; - -static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context* c, int width, int height) -{ - int best_waste = (1 << 30), best_x, best_y = (1 << 30); - stbrp__findresult fr; - stbrp_node **prev, *node, *tail, **best = NULL; - - // align to multiple of c->align - width = (width + c->align - 1); - width -= width % c->align; - STBRP_ASSERT(width % c->align == 0); - - node = c->active_head; - prev = &c->active_head; - while (node->x + width <= c->width) { - int y, waste; - y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); - if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL - // bottom left - if (y < best_y) { - best_y = y; - best = prev; - } - } else { - // best-fit - if (y + height <= c->height) { - // can only use it if it first vertically - if (y < best_y || (y == best_y && waste < best_waste)) { - best_y = y; - best_waste = waste; - best = prev; - } - } - } - prev = &node->next; - node = node->next; - } - - best_x = (best == NULL) ? 0 : (*best)->x; - - // if doing best-fit (BF), we also have to try aligning right edge to each node position - // - // e.g, if fitting - // - // ____________________ - // |____________________| - // - // into - // - // | | - // | ____________| - // |____________| - // - // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned - // - // This makes BF take about 2x the time - - if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { - tail = c->active_head; - node = c->active_head; - prev = &c->active_head; - // find first node that's admissible - while (tail->x < width) - tail = tail->next; - while (tail) { - int xpos = tail->x - width; - int y, waste; - STBRP_ASSERT(xpos >= 0); - // find the left position that matches this - while (node->next->x <= xpos) { - prev = &node->next; - node = node->next; - } - STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); - y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); - if (y + height < c->height) { - if (y <= best_y) { - if (y < best_y || waste < best_waste || (waste == best_waste && xpos < best_x)) { - best_x = xpos; - STBRP_ASSERT(y <= best_y); - best_y = y; - best_waste = waste; - best = prev; - } - } - } - tail = tail->next; - } - } - - fr.prev_link = best; - fr.x = best_x; - fr.y = best_y; - return fr; -} - -static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context* context, int width, int height) -{ - // find best position according to heuristic - stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); - stbrp_node *node, *cur; - - // bail if: - // 1. it failed - // 2. the best node doesn't fit (we don't always check this) - // 3. we're out of memory - if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { - res.prev_link = NULL; - return res; - } - - // on success, create new node - node = context->free_head; - node->x = (stbrp_coord)res.x; - node->y = (stbrp_coord)(res.y + height); - - context->free_head = node->next; - - // insert the new node into the right starting point, and - // let 'cur' point to the remaining nodes needing to be - // stiched back in - - cur = *res.prev_link; - if (cur->x < res.x) { - // preserve the existing one, so start testing with the next one - stbrp_node* next = cur->next; - cur->next = node; - cur = next; - } else { - *res.prev_link = node; - } - - // from here, traverse cur and free the nodes, until we get to one - // that shouldn't be freed - while (cur->next && cur->next->x <= res.x + width) { - stbrp_node* next = cur->next; - // move the current node to the free list - cur->next = context->free_head; - context->free_head = cur; - cur = next; - } - - // stitch the list back in - node->next = cur; - - if (cur->x < res.x + width) - cur->x = (stbrp_coord)(res.x + width); - -#ifdef _DEBUG - cur = context->active_head; - while (cur->x < context->width) { - STBRP_ASSERT(cur->x < cur->next->x); - cur = cur->next; - } - STBRP_ASSERT(cur->next == NULL); - - { - int count = 0; - cur = context->active_head; - while (cur) { - cur = cur->next; - ++count; - } - cur = context->free_head; - while (cur) { - cur = cur->next; - ++count; - } - STBRP_ASSERT(count == context->num_nodes + 2); - } -#endif - - return res; -} - -static int STBRP__CDECL rect_height_compare(const void* a, const void* b) -{ - const stbrp_rect* p = (const stbrp_rect*)a; - const stbrp_rect* q = (const stbrp_rect*)b; - if (p->h > q->h) - return -1; - if (p->h < q->h) - return 1; - return (p->w > q->w) ? -1 : (p->w < q->w); -} - -static int STBRP__CDECL rect_original_order(const void* a, const void* b) -{ - const stbrp_rect* p = (const stbrp_rect*)a; - const stbrp_rect* q = (const stbrp_rect*)b; - return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); -} - -#ifdef STBRP_LARGE_RECTS -#define STBRP__MAXVAL 0xffffffff -#else -#define STBRP__MAXVAL 0xffff -#endif - -STBRP_DEF int stbrp_pack_rects(stbrp_context* context, stbrp_rect* rects, int num_rects) -{ - int i, all_rects_packed = 1; - - // we use the 'was_packed' field internally to allow sorting/unsorting - for (i = 0; i < num_rects; ++i) { - rects[i].was_packed = i; -#ifndef STBRP_LARGE_RECTS - STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); -#endif - } - - // sort according to heuristic - STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); - - for (i = 0; i < num_rects; ++i) { - if (rects[i].w == 0 || rects[i].h == 0) { - rects[i].x = rects[i].y = 0; // empty rect needs no space - } else { - stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); - if (fr.prev_link) { - rects[i].x = (stbrp_coord)fr.x; - rects[i].y = (stbrp_coord)fr.y; - } else { - rects[i].x = rects[i].y = STBRP__MAXVAL; - } - } - } - - // unsort - STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); - - // set was_packed flags and all_rects_packed status - for (i = 0; i < num_rects; ++i) { - rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); - if (!rects[i].was_packed) - all_rects_packed = 0; - } - - // return the all_rects_packed status - return all_rects_packed; -} -#endif - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/Framework/src/imgui/imstb_textedit.h b/Framework/src/imgui/imstb_textedit.h deleted file mode 100644 index 4285da6826..0000000000 --- a/Framework/src/imgui/imstb_textedit.h +++ /dev/null @@ -1,1413 +0,0 @@ -// [ImGui] this is a slightly modified version of stb_textedit.h 1.12. Those changes would need to be pushed into -// nothings/stb [ImGui] - 2018-06: fixed undo/redo after pasting large amount of text (over 32 kb). Redo will still fail -// when undo buffers are exhausted, but text won't be corrupted (see nothings/stb issue #620) [ImGui] - 2018-06: fix in -// stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) [ImGui] - fixed some minor warnings - -// stb_textedit.h - v1.12 - public domain - Sean Barrett -// Development of this library was sponsored by RAD Game Tools -// -// This C header file implements the guts of a multi-line text-editing -// widget; you implement display, word-wrapping, and low-level string -// insertion/deletion, and stb_textedit will map user inputs into -// insertions & deletions, plus updates to the cursor position, -// selection state, and undo state. -// -// It is intended for use in games and other systems that need to build -// their own custom widgets and which do not have heavy text-editing -// requirements (this library is not recommended for use for editing large -// texts, as its performance does not scale and it has limited undo). -// -// Non-trivial behaviors are modelled after Windows text controls. -// -// -// LICENSE -// -// See end of file for license information. -// -// -// DEPENDENCIES -// -// Uses the C runtime function 'memmove', which you can override -// by defining STB_TEXTEDIT_memmove before the implementation. -// Uses no other functions. Performs no runtime allocations. -// -// -// VERSION HISTORY -// -// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash -// 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield -// 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual -// 1.9 (2016-08-27) customizable move-by-word -// 1.8 (2016-04-02) better keyboard handling when mouse button is down -// 1.7 (2015-09-13) change y range handling in case baseline is non-0 -// 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove -// 1.5 (2014-09-10) add support for secondary keys for OS X -// 1.4 (2014-08-17) fix signed/unsigned warnings -// 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary -// 1.2 (2014-05-27) fix some RAD types that had crept into the new code -// 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) -// 1.0 (2012-07-26) improve documentation, initial public release -// 0.3 (2012-02-24) bugfixes, single-line mode; insert mode -// 0.2 (2011-11-28) fixes to undo/redo -// 0.1 (2010-07-08) initial version -// -// ADDITIONAL CONTRIBUTORS -// -// Ulf Winklemann: move-by-word in 1.1 -// Fabian Giesen: secondary key inputs in 1.5 -// Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 -// -// Bugfixes: -// Scott Graham -// Daniel Keller -// Omar Cornut -// Dan Thompson -// -// USAGE -// -// This file behaves differently depending on what symbols you define -// before including it. -// -// -// Header-file mode: -// -// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, -// it will operate in "header file" mode. In this mode, it declares a -// single public symbol, STB_TexteditState, which encapsulates the current -// state of a text widget (except for the string, which you will store -// separately). -// -// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a -// primitive type that defines a single character (e.g. char, wchar_t, etc). -// -// To save space or increase undo-ability, you can optionally define the -// following things that are used by the undo system: -// -// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position -// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow -// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer -// -// If you don't define these, they are set to permissive types and -// moderate sizes. The undo system does no memory allocations, so -// it grows STB_TexteditState by the worst-case storage which is (in bytes): -// -// [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT -// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT -// -// -// Implementation mode: -// -// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it -// will compile the implementation of the text edit widget, depending -// on a large number of symbols which must be defined before the include. -// -// The implementation is defined only as static functions. You will then -// need to provide your own APIs in the same file which will access the -// static functions. -// -// The basic concept is that you provide a "string" object which -// behaves like an array of characters. stb_textedit uses indices to -// refer to positions in the string, implicitly representing positions -// in the displayed textedit. This is true for both plain text and -// rich text; even with rich text stb_truetype interacts with your -// code as if there was an array of all the displayed characters. -// -// Symbols that must be the same in header-file and implementation mode: -// -// STB_TEXTEDIT_CHARTYPE the character type -// STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position -// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow -// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer -// -// Symbols you must define for implementation mode: -// -// STB_TEXTEDIT_STRING the type of object representing a string being edited, -// typically this is a wrapper object with other data you need -// -// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) -// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters -// starting from character #n (see discussion below) -// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character -// to the xpos of the i+1'th char for a line of characters -// starting at character #n (i.e. accounts for kerning -// with previous char) -// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character -// (return type is int, -1 means not valid to insert) -// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based -// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize -// as manually wordwrapping for end-of-line positioning -// -// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i -// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) -// -// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key -// -// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left -// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right -// STB_TEXTEDIT_K_UP keyboard input to move cursor up -// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down -// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME -// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END -// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME -// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END -// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor -// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor -// STB_TEXTEDIT_K_UNDO keyboard input to perform undo -// STB_TEXTEDIT_K_REDO keyboard input to perform redo -// -// Optional: -// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode -// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), -// required for default WORDLEFT/WORDRIGHT handlers -// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to -// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to -// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT -// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT -// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line -// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line -// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text -// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text -// -// Todo: -// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page -// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page -// -// Keyboard input must be encoded as a single integer value; e.g. a character code -// and some bitflags that represent shift states. to simplify the interface, SHIFT must -// be a bitflag, so we can test the shifted state of cursor movements to allow selection, -// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. -// -// You can encode other things, such as CONTROL or ALT, in additional bits, and -// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, -// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN -// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, -// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the -// API below. The control keys will only match WM_KEYDOWN events because of the -// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN -// bit so it only decodes WM_CHAR events. -// -// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed -// row of characters assuming they start on the i'th character--the width and -// the height and the number of characters consumed. This allows this library -// to traverse the entire layout incrementally. You need to compute word-wrapping -// here. -// -// Each textfield keeps its own insert mode state, which is not how normal -// applications work. To keep an app-wide insert mode, update/copy the -// "insert_mode" field of STB_TexteditState before/after calling API functions. -// -// API -// -// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) -// -// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) -// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) -// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) -// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) -// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) -// -// Each of these functions potentially updates the string and updates the -// state. -// -// initialize_state: -// set the textedit state to a known good default state when initially -// constructing the textedit. -// -// click: -// call this with the mouse x,y on a mouse down; it will update the cursor -// and reset the selection start/end to the cursor point. the x,y must -// be relative to the text widget, with (0,0) being the top left. -// -// drag: -// call this with the mouse x,y on a mouse drag/up; it will update the -// cursor and the selection end point -// -// cut: -// call this to delete the current selection; returns true if there was -// one. you should FIRST copy the current selection to the system paste buffer. -// (To copy, just copy the current selection out of the string yourself.) -// -// paste: -// call this to paste text at the current cursor point or over the current -// selection if there is one. -// -// key: -// call this for keyboard inputs sent to the textfield. you can use it -// for "key down" events or for "translated" key events. if you need to -// do both (as in Win32), or distinguish Unicode characters from control -// inputs, set a high bit to distinguish the two; then you can define the -// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit -// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is -// clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to -// anything other type you wante before including. -// -// -// When rendering, you can read the cursor position and selection state from -// the STB_TexteditState. -// -// -// Notes: -// -// This is designed to be usable in IMGUI, so it allows for the possibility of -// running in an IMGUI that has NOT cached the multi-line layout. For this -// reason, it provides an interface that is compatible with computing the -// layout incrementally--we try to make sure we make as few passes through -// as possible. (For example, to locate the mouse pointer in the text, we -// could define functions that return the X and Y positions of characters -// and binary search Y and then X, but if we're doing dynamic layout this -// will run the layout algorithm many times, so instead we manually search -// forward in one pass. Similar logic applies to e.g. up-arrow and -// down-arrow movement.) -// -// If it's run in a widget that *has* cached the layout, then this is less -// efficient, but it's not horrible on modern computers. But you wouldn't -// want to edit million-line files with it. - -//////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////// -//// -//// Header-file mode -//// -//// - -#ifndef INCLUDE_STB_TEXTEDIT_H -#define INCLUDE_STB_TEXTEDIT_H - -//////////////////////////////////////////////////////////////////////// -// -// STB_TexteditState -// -// Definition of STB_TexteditState which you should store -// per-textfield; it includes cursor position, selection state, -// and undo state. -// - -#ifndef STB_TEXTEDIT_UNDOSTATECOUNT -#define STB_TEXTEDIT_UNDOSTATECOUNT 99 -#endif -#ifndef STB_TEXTEDIT_UNDOCHARCOUNT -#define STB_TEXTEDIT_UNDOCHARCOUNT 999 -#endif -#ifndef STB_TEXTEDIT_CHARTYPE -#define STB_TEXTEDIT_CHARTYPE int -#endif -#ifndef STB_TEXTEDIT_POSITIONTYPE -#define STB_TEXTEDIT_POSITIONTYPE int -#endif - -typedef struct { - // private data - STB_TEXTEDIT_POSITIONTYPE where; - STB_TEXTEDIT_POSITIONTYPE insert_length; - STB_TEXTEDIT_POSITIONTYPE delete_length; - int char_storage; -} StbUndoRecord; - -typedef struct { - // private data - StbUndoRecord undo_rec[STB_TEXTEDIT_UNDOSTATECOUNT]; - STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; - short undo_point, redo_point; - int undo_char_point, redo_char_point; -} StbUndoState; - -typedef struct { - ///////////////////// - // - // public data - // - - int cursor; - // position of the text cursor within the string - - int select_start; // selection start point - int select_end; - // selection start and end point in characters; if equal, no selection. - // note that start may be less than or greater than end (e.g. when - // dragging the mouse, start is where the initial click was, and you - // can drag in either direction) - - unsigned char insert_mode; - // each textfield keeps its own insert mode state. to keep an app-wide - // insert mode, copy this value in/out of the app state - - ///////////////////// - // - // private data - // - unsigned char cursor_at_end_of_line; // not implemented yet - unsigned char initialized; - unsigned char has_preferred_x; - unsigned char single_line; - unsigned char padding1, padding2, padding3; - float preferred_x; // this determines where the cursor up/down tries to seek to along x - StbUndoState undostate; -} STB_TexteditState; - -//////////////////////////////////////////////////////////////////////// -// -// StbTexteditRow -// -// Result of layout query, used by stb_textedit to determine where -// the text in each row is. - -// result of layout query -typedef struct { - float x0, x1; // starting x location, end x location (allows for align=right, etc) - float baseline_y_delta; // position of baseline relative to previous row's baseline - float ymin, ymax; // height of row above and below baseline - int num_chars; -} StbTexteditRow; -#endif // INCLUDE_STB_TEXTEDIT_H - -//////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////// -//// -//// Implementation mode -//// -//// - -// implementation isn't include-guarded, since it might have indirectly -// included just the "header" portion -#ifdef STB_TEXTEDIT_IMPLEMENTATION - -#ifndef STB_TEXTEDIT_memmove -#include -#define STB_TEXTEDIT_memmove memmove -#endif - -///////////////////////////////////////////////////////////////////////////// -// -// Mouse input handling -// - -// traverse the layout to locate the nearest character to a display position -static int stb_text_locate_coord(STB_TEXTEDIT_STRING* str, float x, float y) -{ - StbTexteditRow r; - int n = STB_TEXTEDIT_STRINGLEN(str); - float base_y = 0, prev_x; - int i = 0, k; - - r.x0 = r.x1 = 0; - r.ymin = r.ymax = 0; - r.num_chars = 0; - - // search rows to find one that straddles 'y' - while (i < n) { - STB_TEXTEDIT_LAYOUTROW(&r, str, i); - if (r.num_chars <= 0) - return n; - - if (i == 0 && y < base_y + r.ymin) - return 0; - - if (y < base_y + r.ymax) - break; - - i += r.num_chars; - base_y += r.baseline_y_delta; - } - - // below all text, return 'after' last character - if (i >= n) - return n; - - // check if it's before the beginning of the line - if (x < r.x0) - return i; - - // check if it's before the end of the line - if (x < r.x1) { - // search characters in row for one that straddles 'x' - prev_x = r.x0; - for (k = 0; k < r.num_chars; ++k) { - float w = STB_TEXTEDIT_GETWIDTH(str, i, k); - if (x < prev_x + w) { - if (x < prev_x + w / 2) - return k + i; - else - return k + i + 1; - } - prev_x += w; - } - // shouldn't happen, but if it does, fall through to end-of-line case - } - - // if the last character is a newline, return that. otherwise return 'after' the last character - if (STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) == STB_TEXTEDIT_NEWLINE) - return i + r.num_chars - 1; - else - return i + r.num_chars; -} - -// API click: on mouse down, move the cursor to the clicked location, and reset the selection -static void stb_textedit_click(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, float x, float y) -{ - // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse - // goes off the top or bottom of the text - if (state->single_line) { - StbTexteditRow r; - STB_TEXTEDIT_LAYOUTROW(&r, str, 0); - y = r.ymin; - } - - state->cursor = stb_text_locate_coord(str, x, y); - state->select_start = state->cursor; - state->select_end = state->cursor; - state->has_preferred_x = 0; -} - -// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location -static void stb_textedit_drag(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, float x, float y) -{ - int p = 0; - - // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse - // goes off the top or bottom of the text - if (state->single_line) { - StbTexteditRow r; - STB_TEXTEDIT_LAYOUTROW(&r, str, 0); - y = r.ymin; - } - - if (state->select_start == state->select_end) - state->select_start = state->cursor; - - p = stb_text_locate_coord(str, x, y); - state->cursor = state->select_end = p; -} - -///////////////////////////////////////////////////////////////////////////// -// -// Keyboard input handling -// - -// forward declarations -static void stb_text_undo(STB_TEXTEDIT_STRING* str, STB_TexteditState* state); -static void stb_text_redo(STB_TEXTEDIT_STRING* str, STB_TexteditState* state); -static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, int where, int length); -static void stb_text_makeundo_insert(STB_TexteditState* state, int where, int length); -static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, int where, int old_length, - int new_length); - -typedef struct { - float x, y; // position of n'th character - float height; // height of line - int first_char, length; // first char of row, and length - int prev_first; // first char of previous row -} StbFindState; - -// find the x/y location of a character, and remember info about the previous row in -// case we get a move-up event (for page up, we'll have to rescan) -static void stb_textedit_find_charpos(StbFindState* find, STB_TEXTEDIT_STRING* str, int n, int single_line) -{ - StbTexteditRow r; - int prev_start = 0; - int z = STB_TEXTEDIT_STRINGLEN(str); - int i = 0, first; - - if (n == z) { - // if it's at the end, then find the last line -- simpler than trying to - // explicitly handle this case in the regular code - if (single_line) { - STB_TEXTEDIT_LAYOUTROW(&r, str, 0); - find->y = 0; - find->first_char = 0; - find->length = z; - find->height = r.ymax - r.ymin; - find->x = r.x1; - } else { - find->y = 0; - find->x = 0; - find->height = 1; - while (i < z) { - STB_TEXTEDIT_LAYOUTROW(&r, str, i); - prev_start = i; - i += r.num_chars; - } - find->first_char = i; - find->length = 0; - find->prev_first = prev_start; - } - return; - } - - // search rows to find the one that straddles character n - find->y = 0; - - for (;;) { - STB_TEXTEDIT_LAYOUTROW(&r, str, i); - if (n < i + r.num_chars) - break; - prev_start = i; - i += r.num_chars; - find->y += r.baseline_y_delta; - } - - find->first_char = first = i; - find->length = r.num_chars; - find->height = r.ymax - r.ymin; - find->prev_first = prev_start; - - // now scan to find xpos - find->x = r.x0; - i = 0; - for (i = 0; first + i < n; ++i) - find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); -} - -#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) - -// make the selection/cursor state valid if client altered the string -static void stb_textedit_clamp(STB_TEXTEDIT_STRING* str, STB_TexteditState* state) -{ - int n = STB_TEXTEDIT_STRINGLEN(str); - if (STB_TEXT_HAS_SELECTION(state)) { - if (state->select_start > n) - state->select_start = n; - if (state->select_end > n) - state->select_end = n; - // if clamping forced them to be equal, move the cursor to match - if (state->select_start == state->select_end) - state->cursor = state->select_start; - } - if (state->cursor > n) - state->cursor = n; -} - -// delete characters while updating undo -static void stb_textedit_delete(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, int where, int len) -{ - stb_text_makeundo_delete(str, state, where, len); - STB_TEXTEDIT_DELETECHARS(str, where, len); - state->has_preferred_x = 0; -} - -// delete the section -static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING* str, STB_TexteditState* state) -{ - stb_textedit_clamp(str, state); - if (STB_TEXT_HAS_SELECTION(state)) { - if (state->select_start < state->select_end) { - stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); - state->select_end = state->cursor = state->select_start; - } else { - stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); - state->select_start = state->cursor = state->select_end; - } - state->has_preferred_x = 0; - } -} - -// canoncialize the selection so start <= end -static void stb_textedit_sortselection(STB_TexteditState* state) -{ - if (state->select_end < state->select_start) { - int temp = state->select_end; - state->select_end = state->select_start; - state->select_start = temp; - } -} - -// move cursor to first character of selection -static void stb_textedit_move_to_first(STB_TexteditState* state) -{ - if (STB_TEXT_HAS_SELECTION(state)) { - stb_textedit_sortselection(state); - state->cursor = state->select_start; - state->select_end = state->select_start; - state->has_preferred_x = 0; - } -} - -// move cursor to last character of selection -static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING* str, STB_TexteditState* state) -{ - if (STB_TEXT_HAS_SELECTION(state)) { - stb_textedit_sortselection(state); - stb_textedit_clamp(str, state); - state->cursor = state->select_end; - state->select_start = state->select_end; - state->has_preferred_x = 0; - } -} - -#ifdef STB_TEXTEDIT_IS_SPACE -static int is_word_boundary(STB_TEXTEDIT_STRING* str, int idx) -{ - return idx > 0 ? (STB_TEXTEDIT_IS_SPACE(STB_TEXTEDIT_GETCHAR(str, idx - 1)) && - !STB_TEXTEDIT_IS_SPACE(STB_TEXTEDIT_GETCHAR(str, idx))) - : 1; -} - -#ifndef STB_TEXTEDIT_MOVEWORDLEFT -static int stb_textedit_move_to_word_previous(STB_TEXTEDIT_STRING* str, int c) -{ - --c; // always move at least one character - while (c >= 0 && !is_word_boundary(str, c)) - --c; - - if (c < 0) - c = 0; - - return c; -} -#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous -#endif - -#ifndef STB_TEXTEDIT_MOVEWORDRIGHT -static int stb_textedit_move_to_word_next(STB_TEXTEDIT_STRING* str, int c) -{ - const int len = STB_TEXTEDIT_STRINGLEN(str); - ++c; // always move at least one character - while (c < len && !is_word_boundary(str, c)) - ++c; - - if (c > len) - c = len; - - return c; -} -#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next -#endif - -#endif - -// update selection and cursor to match each other -static void stb_textedit_prep_selection_at_cursor(STB_TexteditState* state) -{ - if (!STB_TEXT_HAS_SELECTION(state)) - state->select_start = state->select_end = state->cursor; - else - state->cursor = state->select_end; -} - -// API cut: delete selection -static int stb_textedit_cut(STB_TEXTEDIT_STRING* str, STB_TexteditState* state) -{ - if (STB_TEXT_HAS_SELECTION(state)) { - stb_textedit_delete_selection(str, state); // implicity clamps - state->has_preferred_x = 0; - return 1; - } - return 0; -} - -// API paste: replace existing selection with passed-in text -static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, STB_TEXTEDIT_CHARTYPE* text, - int len) -{ - // if there's a selection, the paste should delete it - stb_textedit_clamp(str, state); - stb_textedit_delete_selection(str, state); - // try to insert the characters - if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { - stb_text_makeundo_insert(state, state->cursor, len); - state->cursor += len; - state->has_preferred_x = 0; - return 1; - } - // remove the undo since we didn't actually insert the characters - if (state->undostate.undo_point) - --state->undostate.undo_point; - return 0; -} - -#ifndef STB_TEXTEDIT_KEYTYPE -#define STB_TEXTEDIT_KEYTYPE int -#endif - -// API key: process a keyboard input -static void stb_textedit_key(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, STB_TEXTEDIT_KEYTYPE key) -{ -retry: - switch (key) { - default: { - int c = STB_TEXTEDIT_KEYTOTEXT(key); - if (c > 0) { - STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE)c; - - // can't add newline in single-line mode - if (c == '\n' && state->single_line) - break; - - if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { - stb_text_makeundo_replace(str, state, state->cursor, 1, 1); - STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); - if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { - ++state->cursor; - state->has_preferred_x = 0; - } - } else { - stb_textedit_delete_selection(str, state); // implicity clamps - if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { - stb_text_makeundo_insert(state, state->cursor, 1); - ++state->cursor; - state->has_preferred_x = 0; - } - } - } - break; - } - -#ifdef STB_TEXTEDIT_K_INSERT - case STB_TEXTEDIT_K_INSERT: - state->insert_mode = !state->insert_mode; - break; -#endif - - case STB_TEXTEDIT_K_UNDO: - stb_text_undo(str, state); - state->has_preferred_x = 0; - break; - - case STB_TEXTEDIT_K_REDO: - stb_text_redo(str, state); - state->has_preferred_x = 0; - break; - - case STB_TEXTEDIT_K_LEFT: - // if currently there's a selection, move cursor to start of selection - if (STB_TEXT_HAS_SELECTION(state)) - stb_textedit_move_to_first(state); - else if (state->cursor > 0) - --state->cursor; - state->has_preferred_x = 0; - break; - - case STB_TEXTEDIT_K_RIGHT: - // if currently there's a selection, move cursor to end of selection - if (STB_TEXT_HAS_SELECTION(state)) - stb_textedit_move_to_last(str, state); - else - ++state->cursor; - stb_textedit_clamp(str, state); - state->has_preferred_x = 0; - break; - - case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: - stb_textedit_clamp(str, state); - stb_textedit_prep_selection_at_cursor(state); - // move selection left - if (state->select_end > 0) - --state->select_end; - state->cursor = state->select_end; - state->has_preferred_x = 0; - break; - -#ifdef STB_TEXTEDIT_MOVEWORDLEFT - case STB_TEXTEDIT_K_WORDLEFT: - if (STB_TEXT_HAS_SELECTION(state)) - stb_textedit_move_to_first(state); - else { - state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); - stb_textedit_clamp(str, state); - } - break; - - case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: - if (!STB_TEXT_HAS_SELECTION(state)) - stb_textedit_prep_selection_at_cursor(state); - - state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); - state->select_end = state->cursor; - - stb_textedit_clamp(str, state); - break; -#endif - -#ifdef STB_TEXTEDIT_MOVEWORDRIGHT - case STB_TEXTEDIT_K_WORDRIGHT: - if (STB_TEXT_HAS_SELECTION(state)) - stb_textedit_move_to_last(str, state); - else { - state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); - stb_textedit_clamp(str, state); - } - break; - - case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: - if (!STB_TEXT_HAS_SELECTION(state)) - stb_textedit_prep_selection_at_cursor(state); - - state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); - state->select_end = state->cursor; - - stb_textedit_clamp(str, state); - break; -#endif - - case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: - stb_textedit_prep_selection_at_cursor(state); - // move selection right - ++state->select_end; - stb_textedit_clamp(str, state); - state->cursor = state->select_end; - state->has_preferred_x = 0; - break; - - case STB_TEXTEDIT_K_DOWN: - case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { - StbFindState find; - StbTexteditRow row; - int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; - - if (state->single_line) { - // on windows, up&down in single-line behave like left&right - key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); - goto retry; - } - - if (sel) - stb_textedit_prep_selection_at_cursor(state); - else if (STB_TEXT_HAS_SELECTION(state)) - stb_textedit_move_to_last(str, state); - - // compute current position of cursor point - stb_textedit_clamp(str, state); - stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); - - // now find character position down a row - if (find.length) { - float goal_x = state->has_preferred_x ? state->preferred_x : find.x; - float x; - int start = find.first_char + find.length; - state->cursor = start; - STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); - x = row.x0; - for (i = 0; i < row.num_chars; ++i) { - float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); -#ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE - if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) - break; -#endif - x += dx; - if (x > goal_x) - break; - ++state->cursor; - } - stb_textedit_clamp(str, state); - - state->has_preferred_x = 1; - state->preferred_x = goal_x; - - if (sel) - state->select_end = state->cursor; - } - break; - } - - case STB_TEXTEDIT_K_UP: - case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { - StbFindState find; - StbTexteditRow row; - int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; - - if (state->single_line) { - // on windows, up&down become left&right - key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); - goto retry; - } - - if (sel) - stb_textedit_prep_selection_at_cursor(state); - else if (STB_TEXT_HAS_SELECTION(state)) - stb_textedit_move_to_first(state); - - // compute current position of cursor point - stb_textedit_clamp(str, state); - stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); - - // can only go up if there's a previous row - if (find.prev_first != find.first_char) { - // now find character position up a row - float goal_x = state->has_preferred_x ? state->preferred_x : find.x; - float x; - state->cursor = find.prev_first; - STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); - x = row.x0; - for (i = 0; i < row.num_chars; ++i) { - float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); -#ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE - if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) - break; -#endif - x += dx; - if (x > goal_x) - break; - ++state->cursor; - } - stb_textedit_clamp(str, state); - - state->has_preferred_x = 1; - state->preferred_x = goal_x; - - if (sel) - state->select_end = state->cursor; - } - break; - } - - case STB_TEXTEDIT_K_DELETE: - case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: - if (STB_TEXT_HAS_SELECTION(state)) - stb_textedit_delete_selection(str, state); - else { - int n = STB_TEXTEDIT_STRINGLEN(str); - if (state->cursor < n) - stb_textedit_delete(str, state, state->cursor, 1); - } - state->has_preferred_x = 0; - break; - - case STB_TEXTEDIT_K_BACKSPACE: - case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: - if (STB_TEXT_HAS_SELECTION(state)) - stb_textedit_delete_selection(str, state); - else { - stb_textedit_clamp(str, state); - if (state->cursor > 0) { - stb_textedit_delete(str, state, state->cursor - 1, 1); - --state->cursor; - } - } - state->has_preferred_x = 0; - break; - -#ifdef STB_TEXTEDIT_K_TEXTSTART2 - case STB_TEXTEDIT_K_TEXTSTART2: -#endif - case STB_TEXTEDIT_K_TEXTSTART: - state->cursor = state->select_start = state->select_end = 0; - state->has_preferred_x = 0; - break; - -#ifdef STB_TEXTEDIT_K_TEXTEND2 - case STB_TEXTEDIT_K_TEXTEND2: -#endif - case STB_TEXTEDIT_K_TEXTEND: - state->cursor = STB_TEXTEDIT_STRINGLEN(str); - state->select_start = state->select_end = 0; - state->has_preferred_x = 0; - break; - -#ifdef STB_TEXTEDIT_K_TEXTSTART2 - case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: -#endif - case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: - stb_textedit_prep_selection_at_cursor(state); - state->cursor = state->select_end = 0; - state->has_preferred_x = 0; - break; - -#ifdef STB_TEXTEDIT_K_TEXTEND2 - case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: -#endif - case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: - stb_textedit_prep_selection_at_cursor(state); - state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); - state->has_preferred_x = 0; - break; - -#ifdef STB_TEXTEDIT_K_LINESTART2 - case STB_TEXTEDIT_K_LINESTART2: -#endif - case STB_TEXTEDIT_K_LINESTART: - stb_textedit_clamp(str, state); - stb_textedit_move_to_first(state); - if (state->single_line) - state->cursor = 0; - else - while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor - 1) != STB_TEXTEDIT_NEWLINE) - --state->cursor; - state->has_preferred_x = 0; - break; - -#ifdef STB_TEXTEDIT_K_LINEEND2 - case STB_TEXTEDIT_K_LINEEND2: -#endif - case STB_TEXTEDIT_K_LINEEND: { - int n = STB_TEXTEDIT_STRINGLEN(str); - stb_textedit_clamp(str, state); - stb_textedit_move_to_first(state); - if (state->single_line) - state->cursor = n; - else - while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) - ++state->cursor; - state->has_preferred_x = 0; - break; - } - -#ifdef STB_TEXTEDIT_K_LINESTART2 - case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: -#endif - case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: - stb_textedit_clamp(str, state); - stb_textedit_prep_selection_at_cursor(state); - if (state->single_line) - state->cursor = 0; - else - while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor - 1) != STB_TEXTEDIT_NEWLINE) - --state->cursor; - state->select_end = state->cursor; - state->has_preferred_x = 0; - break; - -#ifdef STB_TEXTEDIT_K_LINEEND2 - case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: -#endif - case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { - int n = STB_TEXTEDIT_STRINGLEN(str); - stb_textedit_clamp(str, state); - stb_textedit_prep_selection_at_cursor(state); - if (state->single_line) - state->cursor = n; - else - while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) - ++state->cursor; - state->select_end = state->cursor; - state->has_preferred_x = 0; - break; - } - - // @TODO: - // STB_TEXTEDIT_K_PGUP - move cursor up a page - // STB_TEXTEDIT_K_PGDOWN - move cursor down a page - } -} - -///////////////////////////////////////////////////////////////////////////// -// -// Undo processing -// -// @OPTIMIZE: the undo/redo buffer should be circular - -static void stb_textedit_flush_redo(StbUndoState* state) -{ - state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; - state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; -} - -// discard the oldest entry in the undo list -static void stb_textedit_discard_undo(StbUndoState* state) -{ - if (state->undo_point > 0) { - // if the 0th undo state has characters, clean those up - if (state->undo_rec[0].char_storage >= 0) { - int n = state->undo_rec[0].insert_length, i; - // delete n characters from all other records - state->undo_char_point -= n; - STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, - (size_t)(state->undo_char_point * sizeof(STB_TEXTEDIT_CHARTYPE))); - for (i = 0; i < state->undo_point; ++i) - if (state->undo_rec[i].char_storage >= 0) - state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it - } - --state->undo_point; - STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec + 1, - (size_t)(state->undo_point * sizeof(state->undo_rec[0]))); - } -} - -// discard the oldest entry in the redo list--it's bad if this -// ever happens, but because undo & redo have to store the actual -// characters in different cases, the redo character buffer can -// fill up even though the undo buffer didn't -static void stb_textedit_discard_redo(StbUndoState* state) -{ - int k = STB_TEXTEDIT_UNDOSTATECOUNT - 1; - - if (state->redo_point <= k) { - // if the k'th undo state has characters, clean those up - if (state->undo_rec[k].char_storage >= 0) { - int n = state->undo_rec[k].insert_length, i; - // move the remaining redo character data to the end of the buffer - state->redo_char_point += n; - STB_TEXTEDIT_memmove( - state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point - n, - (size_t)((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point) * sizeof(STB_TEXTEDIT_CHARTYPE))); - // adjust the position of all the other records to account for above memmove - for (i = state->redo_point; i < k; ++i) - if (state->undo_rec[i].char_storage >= 0) - state->undo_rec[i].char_storage += n; - } - // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' - STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point + 1, state->undo_rec + state->redo_point, - (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point) * sizeof(state->undo_rec[0]))); - // now move redo_point to point to the new one - ++state->redo_point; - } -} - -static StbUndoRecord* stb_text_create_undo_record(StbUndoState* state, int numchars) -{ - // any time we create a new undo record, we discard redo - stb_textedit_flush_redo(state); - - // if we have no free records, we have to make room, by sliding the - // existing records down - if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) - stb_textedit_discard_undo(state); - - // if the characters to store won't possibly fit in the buffer, we can't undo - if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { - state->undo_point = 0; - state->undo_char_point = 0; - return NULL; - } - - // if we don't have enough free characters in the buffer, we have to make room - while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) - stb_textedit_discard_undo(state); - - return &state->undo_rec[state->undo_point++]; -} - -static STB_TEXTEDIT_CHARTYPE* stb_text_createundo(StbUndoState* state, int pos, int insert_len, int delete_len) -{ - StbUndoRecord* r = stb_text_create_undo_record(state, insert_len); - if (r == NULL) - return NULL; - - r->where = pos; - r->insert_length = (STB_TEXTEDIT_POSITIONTYPE)insert_len; - r->delete_length = (STB_TEXTEDIT_POSITIONTYPE)delete_len; - - if (insert_len == 0) { - r->char_storage = -1; - return NULL; - } else { - r->char_storage = state->undo_char_point; - state->undo_char_point += insert_len; - return &state->undo_char[r->char_storage]; - } -} - -static void stb_text_undo(STB_TEXTEDIT_STRING* str, STB_TexteditState* state) -{ - StbUndoState* s = &state->undostate; - StbUndoRecord u, *r; - if (s->undo_point == 0) - return; - - // we need to do two things: apply the undo record, and create a redo record - u = s->undo_rec[s->undo_point - 1]; - r = &s->undo_rec[s->redo_point - 1]; - r->char_storage = -1; - - r->insert_length = u.delete_length; - r->delete_length = u.insert_length; - r->where = u.where; - - if (u.delete_length) { - // if the undo record says to delete characters, then the redo record will - // need to re-insert the characters that get deleted, so we need to store - // them. - - // there are three cases: - // there's enough room to store the characters - // characters stored for *redoing* don't leave room for redo - // characters stored for *undoing* don't leave room for redo - // if the last is true, we have to bail - - if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { - // the undo records take up too much character space; there's no space to store the redo characters - r->insert_length = 0; - } else { - int i; - - // there's definitely room to store the characters eventually - while (s->undo_char_point + u.delete_length > s->redo_char_point) { - // should never happen: - if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) - return; - // there's currently not enough room, so discard a redo record - stb_textedit_discard_redo(s); - } - r = &s->undo_rec[s->redo_point - 1]; - - r->char_storage = s->redo_char_point - u.delete_length; - s->redo_char_point = s->redo_char_point - u.delete_length; - - // now save the characters - for (i = 0; i < u.delete_length; ++i) - s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); - } - - // now we can carry out the deletion - STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); - } - - // check type of recorded action: - if (u.insert_length) { - // easy case: was a deletion, so we need to insert n characters - STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); - s->undo_char_point -= u.insert_length; - } - - state->cursor = u.where + u.insert_length; - - s->undo_point--; - s->redo_point--; -} - -static void stb_text_redo(STB_TEXTEDIT_STRING* str, STB_TexteditState* state) -{ - StbUndoState* s = &state->undostate; - StbUndoRecord *u, r; - if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) - return; - - // we need to do two things: apply the redo record, and create an undo record - u = &s->undo_rec[s->undo_point]; - r = s->undo_rec[s->redo_point]; - - // we KNOW there must be room for the undo record, because the redo record - // was derived from an undo record - - u->delete_length = r.insert_length; - u->insert_length = r.delete_length; - u->where = r.where; - u->char_storage = -1; - - if (r.delete_length) { - // the redo record requires us to delete characters, so the undo record - // needs to store the characters - - if (s->undo_char_point + u->insert_length > s->redo_char_point) { - u->insert_length = 0; - u->delete_length = 0; - } else { - int i; - u->char_storage = s->undo_char_point; - s->undo_char_point = s->undo_char_point + u->insert_length; - - // now save the characters - for (i = 0; i < u->insert_length; ++i) - s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); - } - - STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); - } - - if (r.insert_length) { - // easy case: need to insert n characters - STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); - s->redo_char_point += r.insert_length; - } - - state->cursor = r.where + r.insert_length; - - s->undo_point++; - s->redo_point++; -} - -static void stb_text_makeundo_insert(STB_TexteditState* state, int where, int length) -{ - stb_text_createundo(&state->undostate, where, 0, length); -} - -static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, int where, int length) -{ - int i; - STB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->undostate, where, length, 0); - if (p) { - for (i = 0; i < length; ++i) - p[i] = STB_TEXTEDIT_GETCHAR(str, where + i); - } -} - -static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, int where, int old_length, - int new_length) -{ - int i; - STB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->undostate, where, old_length, new_length); - if (p) { - for (i = 0; i < old_length; ++i) - p[i] = STB_TEXTEDIT_GETCHAR(str, where + i); - } -} - -// reset the state to default -static void stb_textedit_clear_state(STB_TexteditState* state, int is_single_line) -{ - state->undostate.undo_point = 0; - state->undostate.undo_char_point = 0; - state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; - state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; - state->select_end = state->select_start = 0; - state->cursor = 0; - state->has_preferred_x = 0; - state->preferred_x = 0; - state->cursor_at_end_of_line = 0; - state->initialized = 1; - state->single_line = (unsigned char)is_single_line; - state->insert_mode = 0; -} - -// API initialize -static void stb_textedit_initialize_state(STB_TexteditState* state, int is_single_line) -{ - stb_textedit_clear_state(state, is_single_line); -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - -static int stb_textedit_paste(STB_TEXTEDIT_STRING* str, STB_TexteditState* state, STB_TEXTEDIT_CHARTYPE const* ctext, - int len) -{ - return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE*)ctext, len); -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop -#endif - -#endif // STB_TEXTEDIT_IMPLEMENTATION - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/Framework/src/imgui/imstb_truetype.h b/Framework/src/imgui/imstb_truetype.h deleted file mode 100644 index 89501a8639..0000000000 --- a/Framework/src/imgui/imstb_truetype.h +++ /dev/null @@ -1,5087 +0,0 @@ -// stb_truetype.h - v1.19 - public domain -// authored from 2009-2016 by Sean Barrett / RAD Game Tools -// -// This library processes TrueType files: -// parse files -// extract glyph metrics -// extract glyph shapes -// render glyphs to one-channel bitmaps with antialiasing (box filter) -// render glyphs to one-channel SDF bitmaps (signed-distance field/function) -// -// Todo: -// non-MS cmaps -// crashproof on bad data -// hinting? (no longer patented) -// cleartype-style AA? -// optimize: use simple memory allocator for intermediates -// optimize: build edge-list directly from curves -// optimize: rasterize directly from curves? -// -// ADDITIONAL CONTRIBUTORS -// -// Mikko Mononen: compound shape support, more cmap formats -// Tor Andersson: kerning, subpixel rendering -// Dougall Johnson: OpenType / Type 2 font handling -// Daniel Ribeiro Maciel: basic GPOS-based kerning -// -// Misc other: -// Ryan Gordon -// Simon Glass -// github:IntellectualKitty -// Imanol Celaya -// Daniel Ribeiro Maciel -// -// Bug/warning reports/fixes: -// "Zer" on mollyrocket Fabian "ryg" Giesen -// Cass Everitt Martins Mozeiko -// stoiko (Haemimont Games) Cap Petschulat -// Brian Hook Omar Cornut -// Walter van Niftrik github:aloucks -// David Gow Peter LaValle -// David Given Sergey Popov -// Ivan-Assen Ivanov Giumo X. Clanjor -// Anthony Pesch Higor Euripedes -// Johan Duparc Thomas Fields -// Hou Qiming Derek Vinyard -// Rob Loach Cort Stratton -// Kenney Phillis Jr. github:oyvindjam -// Brian Costabile github:vassvik -// -// VERSION HISTORY -// -// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// variant PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// -// Full history can be found at the end of this file. -// -// LICENSE -// -// See end of file for license information. -// -// USAGE -// -// Include this file in whatever places neeed to refer to it. In ONE C/C++ -// file, write: -// #define STB_TRUETYPE_IMPLEMENTATION -// before the #include of this file. This expands out the actual -// implementation into that C/C++ file. -// -// To make the implementation private to the file that generates the implementation, -// #define STBTT_STATIC -// -// Simple 3D API (don't ship this, but it's fine for tools and quick start) -// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture -// stbtt_GetBakedQuad() -- compute quad to draw for a given char -// -// Improved 3D API (more shippable): -// #include "stb_rect_pack.h" -- optional, but you really want it -// stbtt_PackBegin() -// stbtt_PackSetOversampling() -- for improved quality on small fonts -// stbtt_PackFontRanges() -- pack and renders -// stbtt_PackEnd() -// stbtt_GetPackedQuad() -// -// "Load" a font file from a memory buffer (you have to keep the buffer loaded) -// stbtt_InitFont() -// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections -// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections -// -// Render a unicode codepoint to a bitmap -// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap -// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide -// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be -// -// Character advance/positioning -// stbtt_GetCodepointHMetrics() -// stbtt_GetFontVMetrics() -// stbtt_GetFontVMetricsOS2() -// stbtt_GetCodepointKernAdvance() -// -// Starting with version 1.06, the rasterizer was replaced with a new, -// faster and generally-more-precise rasterizer. The new rasterizer more -// accurately measures pixel coverage for anti-aliasing, except in the case -// where multiple shapes overlap, in which case it overestimates the AA pixel -// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If -// this turns out to be a problem, you can re-enable the old rasterizer with -// #define STBTT_RASTERIZER_VERSION 1 -// which will incur about a 15% speed hit. -// -// ADDITIONAL DOCUMENTATION -// -// Immediately after this block comment are a series of sample programs. -// -// After the sample programs is the "header file" section. This section -// includes documentation for each API function. -// -// Some important concepts to understand to use this library: -// -// Codepoint -// Characters are defined by unicode codepoints, e.g. 65 is -// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is -// the hiragana for "ma". -// -// Glyph -// A visual character shape (every codepoint is rendered as -// some glyph) -// -// Glyph index -// A font-specific integer ID representing a glyph -// -// Baseline -// Glyph shapes are defined relative to a baseline, which is the -// bottom of uppercase characters. Characters extend both above -// and below the baseline. -// -// Current Point -// As you draw text to the screen, you keep track of a "current point" -// which is the origin of each character. The current point's vertical -// position is the baseline. Even "baked fonts" use this model. -// -// Vertical Font Metrics -// The vertical qualities of the font, used to vertically position -// and space the characters. See docs for stbtt_GetFontVMetrics. -// -// Font Size in Pixels or Points -// The preferred interface for specifying font sizes in stb_truetype -// is to specify how tall the font's vertical extent should be in pixels. -// If that sounds good enough, skip the next paragraph. -// -// Most font APIs instead use "points", which are a common typographic -// measurement for describing font size, defined as 72 points per inch. -// stb_truetype provides a point API for compatibility. However, true -// "per inch" conventions don't make much sense on computer displays -// since different monitors have different number of pixels per -// inch. For example, Windows traditionally uses a convention that -// there are 96 pixels per inch, thus making 'inch' measurements have -// nothing to do with inches, and thus effectively defining a point to -// be 1.333 pixels. Additionally, the TrueType font data provides -// an explicit scale factor to scale a given font's glyphs to points, -// but the author has observed that this scale factor is often wrong -// for non-commercial fonts, thus making fonts scaled in points -// according to the TrueType spec incoherently sized in practice. -// -// DETAILED USAGE: -// -// Scale: -// Select how high you want the font to be, in points or pixels. -// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute -// a scale factor SF that will be used by all other functions. -// -// Baseline: -// You need to select a y-coordinate that is the baseline of where -// your text will appear. Call GetFontBoundingBox to get the baseline-relative -// bounding box for all characters. SF*-y0 will be the distance in pixels -// that the worst-case character could extend above the baseline, so if -// you want the top edge of characters to appear at the top of the -// screen where y=0, then you would set the baseline to SF*-y0. -// -// Current point: -// Set the current point where the first character will appear. The -// first character could extend left of the current point; this is font -// dependent. You can either choose a current point that is the leftmost -// point and hope, or add some padding, or check the bounding box or -// left-side-bearing of the first character to be displayed and set -// the current point based on that. -// -// Displaying a character: -// Compute the bounding box of the character. It will contain signed values -// relative to . I.e. if it returns x0,y0,x1,y1, -// then the character should be displayed in the rectangle from -// to = 32 && *text < 128) { - stbtt_aligned_quad q; - stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 - glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); - glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); - glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); - glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); - } - ++text; - } - glEnd(); -} -#endif -// -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program (this compiles): get a single bitmap, print as ASCII art -// -#if 0 -#include -#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation -#include "stb_truetype.h" - -char ttf_buffer[1<<25]; - -int main(int argc, char **argv) -{ - stbtt_fontinfo font; - unsigned char *bitmap; - int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); - - fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); - - stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); - bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); - - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) - putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); - putchar('\n'); - } - return 0; -} -#endif -// -// Output: -// -// .ii. -// @@@@@@. -// V@Mio@@o -// :i. V@V -// :oM@@M -// :@@@MM@M -// @@o o@M -// :@@. M@M -// @@@o@@@@ -// :M@@V:@@. -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program: print "Hello World!" banner, with bugs -// -#if 0 -char buffer[24<<20]; -unsigned char screen[20][79]; - -int main(int arg, char **argv) -{ - stbtt_fontinfo font; - int i,j,ascent,baseline,ch=0; - float scale, xpos=2; // leave a little padding in case the character extends left - char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness - - fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); - stbtt_InitFont(&font, buffer, 0); - - scale = stbtt_ScaleForPixelHeight(&font, 15); - stbtt_GetFontVMetrics(&font, &ascent,0,0); - baseline = (int) (ascent*scale); - - while (text[ch]) { - int advance,lsb,x0,y0,x1,y1; - float x_shift = xpos - (float) floor(xpos); - stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); - stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); - stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); - // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong - // because this API is really for baking character bitmaps into textures. if you want to render - // a sequence of characters, you really need to render each bitmap to a temp buffer, then - // "alpha blend" that into the working buffer - xpos += (advance * scale); - if (text[ch+1]) - xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); - ++ch; - } - - for (j=0; j < 20; ++j) { - for (i=0; i < 78; ++i) - putchar(" .:ioVM@"[screen[j][i]>>5]); - putchar('\n'); - } - - return 0; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// -//// -//// INTEGRATION WITH YOUR CODEBASE -//// -//// The following sections allow you to supply alternate definitions -//// of C library functions used by stb_truetype, e.g. if you don't -//// link with the C runtime library. - -#ifdef STB_TRUETYPE_IMPLEMENTATION -// #define your own (u)stbtt_int8/16/32 before including to override this -#ifndef stbtt_uint8 -typedef unsigned char stbtt_uint8; -typedef signed char stbtt_int8; -typedef unsigned short stbtt_uint16; -typedef signed short stbtt_int16; -typedef unsigned int stbtt_uint32; -typedef signed int stbtt_int32; -#endif - -typedef char stbtt__check_size32[sizeof(stbtt_int32) == 4 ? 1 : -1]; -typedef char stbtt__check_size16[sizeof(stbtt_int16) == 2 ? 1 : -1]; - -// e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h -#ifndef STBTT_ifloor -#include -#define STBTT_ifloor(x) ((int)floor(x)) -#define STBTT_iceil(x) ((int)ceil(x)) -#endif - -#ifndef STBTT_sqrt -#include -#define STBTT_sqrt(x) sqrt(x) -#define STBTT_pow(x, y) pow(x, y) -#endif - -#ifndef STBTT_fmod -#include -#define STBTT_fmod(x, y) fmod(x, y) -#endif - -#ifndef STBTT_cos -#include -#define STBTT_cos(x) cos(x) -#define STBTT_acos(x) acos(x) -#endif - -#ifndef STBTT_fabs -#include -#define STBTT_fabs(x) fabs(x) -#endif - -// #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h -#ifndef STBTT_malloc -#include -#define STBTT_malloc(x, u) ((void)(u), malloc(x)) -#define STBTT_free(x, u) ((void)(u), free(x)) -#endif - -#ifndef STBTT_assert -#include -#define STBTT_assert(x) assert(x) -#endif - -#ifndef STBTT_strlen -#include -#define STBTT_strlen(x) strlen(x) -#endif - -#ifndef STBTT_memcpy -#include -#define STBTT_memcpy memcpy -#define STBTT_memset memset -#endif -#endif - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// INTERFACE -//// -//// - -#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ -#define __STB_INCLUDE_STB_TRUETYPE_H__ - -#ifdef STBTT_STATIC -#define STBTT_DEF static -#else -#define STBTT_DEF extern -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -// private structure -typedef struct { - unsigned char* data; - int cursor; - int size; -} stbtt__buf; - -////////////////////////////////////////////////////////////////////////////// -// -// TEXTURE BAKING API -// -// If you use this API, you only have to call two functions ever. -// - -typedef struct { - unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap - float xoff, yoff, xadvance; -} stbtt_bakedchar; - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char* data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char* pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar* chardata); // you allocate this, it's num_chars long -// if return is positive, the first unused row of the bitmap -// if return is negative, returns the negative of the number of characters that fit -// if return is 0, no characters fit and no rows were used -// This uses a very crappy packing. - -typedef struct { - float x0, y0, s0, t0; // top-left - float x1, y1, s1, t1; // bottom-right -} stbtt_aligned_quad; - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar* chardata, int pw, int ph, // same data as above - int char_index, // character to display - float* xpos, float* ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad* q, // output: quad to draw - int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier -// Call GetBakedQuad with char_index = 'character - first_char', and it -// creates the quad you need to draw and advances the current position. -// -// The coordinate system used assumes y increases downwards. -// -// Characters will extend both above and below the current position; -// see discussion of "BASELINE" above. -// -// It's inefficient; you might want to c&p it and optimize it. - -////////////////////////////////////////////////////////////////////////////// -// -// NEW TEXTURE BAKING API -// -// This provides options for packing multiple fonts into one atlas, not -// perfectly but better than nothing. - -typedef struct { - unsigned short x0, y0, x1, y1; // coordinates of bbox in bitmap - float xoff, yoff, xadvance; - float xoff2, yoff2; -} stbtt_packedchar; - -typedef struct stbtt_pack_context stbtt_pack_context; -typedef struct stbtt_fontinfo stbtt_fontinfo; -#ifndef STB_RECT_PACK_VERSION -typedef struct stbrp_rect stbrp_rect; -#endif - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context* spc, unsigned char* pixels, int width, int height, - int stride_in_bytes, int padding, void* alloc_context); -// Initializes a packing context stored in the passed-in stbtt_pack_context. -// Future calls using this context will pack characters into the bitmap passed -// in here: a 1-channel bitmap that is width * height. stride_in_bytes is -// the distance from one row to the next (or 0 to mean they are packed tightly -// together). "padding" is the amount of padding to leave between each -// character (normally you want '1' for bitmaps you'll use as textures with -// bilinear filtering). -// -// Returns 0 on failure, 1 on success. - -STBTT_DEF void stbtt_PackEnd(stbtt_pack_context* spc); -// Cleans up the packing context and frees all memory. - -#define STBTT_POINT_SIZE(x) (-(x)) - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context* spc, const unsigned char* fontdata, int font_index, - float font_size, int first_unicode_char_in_range, int num_chars_in_range, - stbtt_packedchar* chardata_for_range); -// Creates character bitmaps from the font_index'th font found in fontdata (use -// font_index=0 if you don't know what that is). It creates num_chars_in_range -// bitmaps for characters with unicode values starting at first_unicode_char_in_range -// and increasing. Data for how to render them is stored in chardata_for_range; -// pass these to stbtt_GetPackedQuad to get back renderable quads. -// -// font_size is the full height of the character from ascender to descender, -// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed -// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() -// and pass that result as 'font_size': -// ..., 20 , ... // font max minus min y is 20 pixels tall -// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall - -typedef struct { - float font_size; - int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint - int* array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints - int num_chars; - stbtt_packedchar* chardata_for_range; // output - unsigned char h_oversample, v_oversample; // don't set these, they're used internally -} stbtt_pack_range; - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context* spc, const unsigned char* fontdata, int font_index, - stbtt_pack_range* ranges, int num_ranges); -// Creates character bitmaps from multiple ranges of characters stored in -// ranges. This will usually create a better-packed bitmap than multiple -// calls to stbtt_PackFontRange. Note that you can call this multiple -// times within a single PackBegin/PackEnd. - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context* spc, unsigned int h_oversample, unsigned int v_oversample); -// Oversampling a font increases the quality by allowing higher-quality subpixel -// positioning, and is especially valuable at smaller text sizes. -// -// This function sets the amount of oversampling for all following calls to -// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given -// pack context. The default (no oversampling) is achieved by h_oversample=1 -// and v_oversample=1. The total number of pixels required is -// h_oversample*v_oversample larger than the default; for example, 2x2 -// oversampling requires 4x the storage of 1x1. For best results, render -// oversampled textures with bilinear filtering. Look at the readme in -// stb/tests/oversample for information about oversampled fonts -// -// To use with PackFontRangesGather etc., you must set it before calls -// call to PackFontRangesGatherRects. - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar* chardata, int pw, int ph, // same data as above - int char_index, // character to display - float* xpos, float* ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad* q, // output: quad to draw - int align_to_integer); - -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context* spc, const stbtt_fontinfo* info, - stbtt_pack_range* ranges, int num_ranges, stbrp_rect* rects); -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context* spc, stbrp_rect* rects, int num_rects); -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context* spc, const stbtt_fontinfo* info, - stbtt_pack_range* ranges, int num_ranges, stbrp_rect* rects); -// Calling these functions in sequence is roughly equivalent to calling -// stbtt_PackFontRanges(). If you more control over the packing of multiple -// fonts, or if you want to pack custom data into a font texture, take a look -// at the source to of stbtt_PackFontRanges() and create a custom version -// using these functions, e.g. call GatherRects multiple times, -// building up a single array of rects, then call PackRects once, -// then call RenderIntoRects repeatedly. This may result in a -// better packing than calling PackFontRanges multiple times -// (or it may not). - -// this is an opaque structure that you shouldn't mess with which holds -// all the context needed from PackBegin to PackEnd. -struct stbtt_pack_context { - void* user_allocator_context; - void* pack_info; - int width; - int height; - int stride_in_bytes; - int padding; - unsigned int h_oversample, v_oversample; - unsigned char* pixels; - void* nodes; -}; - -////////////////////////////////////////////////////////////////////////////// -// -// FONT LOADING -// -// - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char* data); -// This function will determine the number of fonts in a font file. TrueType -// collection (.ttc) files may contain multiple fonts, while TrueType font -// (.ttf) files only contain one font. The number of fonts can be used for -// indexing with the previous function where the index is between zero and one -// less than the total fonts. If an error occurs, -1 is returned. - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char* data, int index); -// Each .ttf/.ttc file may have more than one font. Each font has a sequential -// index number starting from 0. Call this function to get the font offset for -// a given index; it returns -1 if the index is out of range. A regular .ttf -// file will only define one font and it always be at offset 0, so it will -// return '0' for index 0, and -1 for all other indices. - -// The following structure is defined publically so you can declare one on -// the stack or as a global or etc, but you should treat it as opaque. -struct stbtt_fontinfo { - void* userdata; - unsigned char* data; // pointer to .ttf file - int fontstart; // offset of start of font - - int numGlyphs; // number of glyphs, needed for range checking - - int loca, head, glyf, hhea, hmtx, kern, gpos; // table locations as offset from start of .ttf - int index_map; // a cmap mapping for our chosen character encoding - int indexToLocFormat; // format needed to map from glyph index to glyph - - stbtt__buf cff; // cff font data - stbtt__buf charstrings; // the charstring index - stbtt__buf gsubrs; // global charstring subroutines index - stbtt__buf subrs; // private charstring subroutines index - stbtt__buf fontdicts; // array of font dicts - stbtt__buf fdselect; // map from glyph to fontdict -}; - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo* info, const unsigned char* data, int offset); -// Given an offset into the file that defines a font, this function builds -// the necessary cached info for the rest of the system. You must allocate -// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't -// need to do anything special to free it, because the contents are pure -// value data with no additional data structures. Returns 0 on failure. - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER TO GLYPH-INDEX CONVERSIOn - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo* info, int unicode_codepoint); -// If you're going to perform multiple operations on the same character -// and you want a speed-up, call this function with the character you're -// going to process, then use glyph-based functions instead of the -// codepoint-based functions. - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER PROPERTIES -// - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo* info, float pixels); -// computes a scale factor to produce a font whose "height" is 'pixels' tall. -// Height is measured as the distance from the highest ascender to the lowest -// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics -// and computing: -// scale = pixels / (ascent - descent) -// so if you prefer to measure height by the ascent only, use a similar calculation. - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo* info, float pixels); -// computes a scale factor to produce a font whose EM size is mapped to -// 'pixels' tall. This is probably what traditional APIs compute, but -// I'm not positive. - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo* info, int* ascent, int* descent, int* lineGap); -// ascent is the coordinate above the baseline the font extends; descent -// is the coordinate below the baseline the font extends (i.e. it is typically negative) -// lineGap is the spacing between one row's descent and the next row's ascent... -// so you should advance the vertical position by "*ascent - *descent + *lineGap" -// these are expressed in unscaled coordinates, so you must multiply by -// the scale factor for a given size - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo* info, int* typoAscent, int* typoDescent, int* typoLineGap); -// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 -// table (specific to MS/Windows TTF files). -// -// Returns 1 on success (table present), 0 on failure. - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo* info, int* x0, int* y0, int* x1, int* y1); -// the bounding box around all possible characters - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo* info, int codepoint, int* advanceWidth, - int* leftSideBearing); -// leftSideBearing is the offset from the current horizontal position to the left edge of the character -// advanceWidth is the offset from the current horizontal position to the next horizontal position -// these are expressed in unscaled coordinates - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo* info, int ch1, int ch2); -// an additional amount to add to the 'advance' value between ch1 and ch2 - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo* info, int codepoint, int* x0, int* y0, int* x1, int* y1); -// Gets the bounding box of the visible part of the glyph, in unscaled coordinates - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo* info, int glyph_index, int* advanceWidth, - int* leftSideBearing); -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo* info, int glyph1, int glyph2); -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo* info, int glyph_index, int* x0, int* y0, int* x1, int* y1); -// as above, but takes one or more glyph indices for greater efficiency - -////////////////////////////////////////////////////////////////////////////// -// -// GLYPH SHAPES (you probably don't need these, but they have to go before -// the bitmaps for C declaration-order reasons) -// - -#ifndef STBTT_vmove // you can predefine these to use different values (but why?) -enum { STBTT_vmove = 1, - STBTT_vline, - STBTT_vcurve, - STBTT_vcubic }; -#endif - -#ifndef stbtt_vertex // you can predefine this to use different values \ - // (we share this with other code at RAD) -#define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file -typedef struct { - stbtt_vertex_type x, y, cx, cy, cx1, cy1; - unsigned char type, padding; -} stbtt_vertex; -#endif - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo* info, int glyph_index); -// returns non-zero if nothing is drawn for this glyph - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo* info, int unicode_codepoint, stbtt_vertex** vertices); -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo* info, int glyph_index, stbtt_vertex** vertices); -// returns # of vertices and fills *vertices with the pointer to them -// these are expressed in "unscaled" coordinates -// -// The shape is a series of countours. Each one starts with -// a STBTT_moveto, then consists of a series of mixed -// STBTT_lineto and STBTT_curveto segments. A lineto -// draws a line from previous endpoint to its x,y; a curveto -// draws a quadratic bezier from previous endpoint to -// its x,y, using cx,cy as the bezier control point. - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo* info, stbtt_vertex* vertices); -// frees the data allocated above - -////////////////////////////////////////////////////////////////////////////// -// -// BITMAP RENDERING -// - -STBTT_DEF void stbtt_FreeBitmap(unsigned char* bitmap, void* userdata); -// frees the bitmap allocated below - -STBTT_DEF unsigned char* stbtt_GetCodepointBitmap(const stbtt_fontinfo* info, float scale_x, float scale_y, - int codepoint, int* width, int* height, int* xoff, int* yoff); -// allocates a large-enough single-channel 8bpp bitmap and renders the -// specified character/glyph at the specified scale into it, with -// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). -// *width & *height are filled out with the width & height of the bitmap, -// which is stored left-to-right, top-to-bottom. -// -// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap - -STBTT_DEF unsigned char* stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo* info, float scale_x, float scale_y, - float shift_x, float shift_y, int codepoint, int* width, - int* height, int* xoff, int* yoff); -// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo* info, unsigned char* output, int out_w, int out_h, - int out_stride, float scale_x, float scale_y, int codepoint); -// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap -// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap -// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the -// width and height and positioning info for it first. - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo* info, unsigned char* output, int out_w, - int out_h, int out_stride, float scale_x, float scale_y, float shift_x, - float shift_y, int codepoint); -// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo* info, unsigned char* output, int out_w, - int out_h, int out_stride, float scale_x, float scale_y, - float shift_x, float shift_y, int oversample_x, - int oversample_y, float* sub_x, float* sub_y, int codepoint); -// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering -// is performed (see stbtt_PackSetOversampling) - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo* font, int codepoint, float scale_x, float scale_y, - int* ix0, int* iy0, int* ix1, int* iy1); -// get the bbox of the bitmap centered around the glyph origin; so the -// bitmap width is ix1-ix0, height is iy1-iy0, and location to place -// the bitmap top left is (leftSideBearing*scale,iy0). -// (Note that the bitmap uses y-increases-down, but the shape uses -// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo* font, int codepoint, float scale_x, - float scale_y, float shift_x, float shift_y, int* ix0, int* iy0, - int* ix1, int* iy1); -// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel -// shift for the character - -// the following functions are equivalent to the above functions, but operate -// on glyph indices instead of Unicode codepoints (for efficiency) -STBTT_DEF unsigned char* stbtt_GetGlyphBitmap(const stbtt_fontinfo* info, float scale_x, float scale_y, int glyph, - int* width, int* height, int* xoff, int* yoff); -STBTT_DEF unsigned char* stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo* info, float scale_x, float scale_y, - float shift_x, float shift_y, int glyph, int* width, int* height, - int* xoff, int* yoff); -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo* info, unsigned char* output, int out_w, int out_h, - int out_stride, float scale_x, float scale_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo* info, unsigned char* output, int out_w, int out_h, - int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, - int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo* info, unsigned char* output, int out_w, - int out_h, int out_stride, float scale_x, float scale_y, - float shift_x, float shift_y, int oversample_x, int oversample_y, - float* sub_x, float* sub_y, int glyph); -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo* font, int glyph, float scale_x, float scale_y, int* ix0, - int* iy0, int* ix1, int* iy1); -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo* font, int glyph, float scale_x, float scale_y, - float shift_x, float shift_y, int* ix0, int* iy0, int* ix1, int* iy1); - -// @TODO: don't expose this structure -typedef struct { - int w, h, stride; - unsigned char* pixels; -} stbtt__bitmap; - -// rasterize a shape with quadratic beziers into a bitmap -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap* result, // 1-channel bitmap to draw into - float flatness_in_pixels, // allowable error of curve in pixels - stbtt_vertex* vertices, // array of vertices defining shape - int num_verts, // number of vertices in above array - float scale_x, float scale_y, // scale applied to input vertices - float shift_x, float shift_y, // translation applied to input vertices - int x_off, int y_off, // another translation applied to input - int invert, // if non-zero, vertically flip shape - void* userdata); // context for to STBTT_MALLOC - -////////////////////////////////////////////////////////////////////////////// -// -// Signed Distance Function (or Field) rendering - -STBTT_DEF void stbtt_FreeSDF(unsigned char* bitmap, void* userdata); -// frees the SDF bitmap allocated below - -STBTT_DEF unsigned char* stbtt_GetGlyphSDF(const stbtt_fontinfo* info, float scale, int glyph, int padding, - unsigned char onedge_value, float pixel_dist_scale, int* width, int* height, - int* xoff, int* yoff); -STBTT_DEF unsigned char* stbtt_GetCodepointSDF(const stbtt_fontinfo* info, float scale, int codepoint, int padding, - unsigned char onedge_value, float pixel_dist_scale, int* width, - int* height, int* xoff, int* yoff); -// These functions compute a discretized SDF field for a single character, suitable for storing -// in a single-channel texture, sampling with bilinear filtering, and testing against -// larger than some threshhold to produce scalable fonts. -// info -- the font -// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular -// bitmap glyph/codepoint -- the character to generate the SDF for padding -- extra "pixels" around -// the character which are filled with the distance to the character (not 0), -// which allows effects like bit outlines -// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of -// the character) pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away -// from the edge (on the 0..255 scale) -// if positive, > onedge_value is inside; if negative, < onedge_value is inside -// width,height -- output height & width of the SDF bitmap (including padding) -// xoff,yoff -- output origin of the character -// return value -- a 2D array of bytes 0..255, width*height in size -// -// pixel_dist_scale & onedge_value are a scale & bias that allows you to make -// optimal use of the limited 0..255 for your application, trading off precision -// and special effects. SDF values outside the range 0..255 are clamped to 0..255. -// -// Example: -// scale = stbtt_ScaleForPixelHeight(22) -// padding = 5 -// onedge_value = 180 -// pixel_dist_scale = 180/5.0 = 36.0 -// -// This will create an SDF bitmap in which the character is about 22 pixels -// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled -// shape, sample the SDF at each pixel and fill the pixel if the SDF value -// is greater than or equal to 180/255. (You'll actually want to antialias, -// which is beyond the scope of this example.) Additionally, you can compute -// offset outlines (e.g. to stroke the character border inside & outside, -// or only outside). For example, to fill outside the character up to 3 SDF -// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above -// choice of variables maps a range from 5 pixels outside the shape to -// 2 pixels inside the shape to 0..255; this is intended primarily for apply -// outside effects only (the interior range is needed to allow proper -// antialiasing of the font at *smaller* sizes) -// -// The function computes the SDF analytically at each SDF pixel, not by e.g. -// building a higher-res bitmap and approximating it. In theory the quality -// should be as high as possible for an SDF of this size & representation, but -// unclear if this is true in practice (perhaps building a higher-res bitmap -// and computing from that can allow drop-out prevention). -// -// The algorithm has not been optimized at all, so expect it to be slow -// if computing lots of characters or very large sizes. - -////////////////////////////////////////////////////////////////////////////// -// -// Finding the right font... -// -// You should really just solve this offline, keep your own tables -// of what font is what, and don't try to get it out of the .ttf file. -// That's because getting it out of the .ttf file is really hard, because -// the names in the file can appear in many possible encodings, in many -// possible languages, and e.g. if you need a case-insensitive comparison, -// the details of that depend on the encoding & language in a complex way -// (actually underspecified in truetype, but also gigantic). -// -// But you can use the provided functions in two possible ways: -// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on -// unicode-encoded names to try to find the font you want; -// you can run this before calling stbtt_InitFont() -// -// stbtt_GetFontNameString() lets you get any of the various strings -// from the file yourself and do your own comparisons on them. -// You have to have called stbtt_InitFont() first. - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char* fontdata, const char* name, int flags); -// returns the offset (not index) of the font that matches, or -1 if none -// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". -// if you use any other flag, use a font name like "Arial"; this checks -// the 'macStyle' header field; i don't know if fonts set this consistently -#define STBTT_MACSTYLE_DONTCARE 0 -#define STBTT_MACSTYLE_BOLD 1 -#define STBTT_MACSTYLE_ITALIC 2 -#define STBTT_MACSTYLE_UNDERSCORE 4 -#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char* s1, int len1, const char* s2, int len2); -// returns 1/0 whether the first string interpreted as utf8 is identical to -// the second string interpreted as big-endian utf16... useful for strings from next func - -STBTT_DEF const char* stbtt_GetFontNameString(const stbtt_fontinfo* font, int* length, int platformID, int encodingID, - int languageID, int nameID); -// returns the string (which may be big-endian double byte, e.g. for unicode) -// and puts the length in bytes in *length. -// -// some of the values for the IDs are below; for more see the truetype spec: -// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html -// http://www.microsoft.com/typography/otspec/name.htm - -enum { // platformID - STBTT_PLATFORM_ID_UNICODE = 0, - STBTT_PLATFORM_ID_MAC = 1, - STBTT_PLATFORM_ID_ISO = 2, - STBTT_PLATFORM_ID_MICROSOFT = 3 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_UNICODE - STBTT_UNICODE_EID_UNICODE_1_0 = 0, - STBTT_UNICODE_EID_UNICODE_1_1 = 1, - STBTT_UNICODE_EID_ISO_10646 = 2, - STBTT_UNICODE_EID_UNICODE_2_0_BMP = 3, - STBTT_UNICODE_EID_UNICODE_2_0_FULL = 4 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT - STBTT_MS_EID_SYMBOL = 0, - STBTT_MS_EID_UNICODE_BMP = 1, - STBTT_MS_EID_SHIFTJIS = 2, - STBTT_MS_EID_UNICODE_FULL = 10 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes - STBTT_MAC_EID_ROMAN = 0, - STBTT_MAC_EID_ARABIC = 4, - STBTT_MAC_EID_JAPANESE = 1, - STBTT_MAC_EID_HEBREW = 5, - STBTT_MAC_EID_CHINESE_TRAD = 2, - STBTT_MAC_EID_GREEK = 6, - STBTT_MAC_EID_KOREAN = 3, - STBTT_MAC_EID_RUSSIAN = 7 -}; - -enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... - // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs - STBTT_MS_LANG_ENGLISH = 0x0409, - STBTT_MS_LANG_ITALIAN = 0x0410, - STBTT_MS_LANG_CHINESE = 0x0804, - STBTT_MS_LANG_JAPANESE = 0x0411, - STBTT_MS_LANG_DUTCH = 0x0413, - STBTT_MS_LANG_KOREAN = 0x0412, - STBTT_MS_LANG_FRENCH = 0x040c, - STBTT_MS_LANG_RUSSIAN = 0x0419, - STBTT_MS_LANG_GERMAN = 0x0407, - STBTT_MS_LANG_SPANISH = 0x0409, - STBTT_MS_LANG_HEBREW = 0x040d, - STBTT_MS_LANG_SWEDISH = 0x041D -}; - -enum { // languageID for STBTT_PLATFORM_ID_MAC - STBTT_MAC_LANG_ENGLISH = 0, - STBTT_MAC_LANG_JAPANESE = 11, - STBTT_MAC_LANG_ARABIC = 12, - STBTT_MAC_LANG_KOREAN = 23, - STBTT_MAC_LANG_DUTCH = 4, - STBTT_MAC_LANG_RUSSIAN = 32, - STBTT_MAC_LANG_FRENCH = 1, - STBTT_MAC_LANG_SPANISH = 6, - STBTT_MAC_LANG_GERMAN = 2, - STBTT_MAC_LANG_SWEDISH = 5, - STBTT_MAC_LANG_HEBREW = 10, - STBTT_MAC_LANG_CHINESE_SIMPLIFIED = 33, - STBTT_MAC_LANG_ITALIAN = 3, - STBTT_MAC_LANG_CHINESE_TRAD = 19 -}; - -#ifdef __cplusplus -} -#endif - -#endif // __STB_INCLUDE_STB_TRUETYPE_H__ - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// IMPLEMENTATION -//// -//// - -#ifdef STB_TRUETYPE_IMPLEMENTATION - -#ifndef STBTT_MAX_OVERSAMPLE -#define STBTT_MAX_OVERSAMPLE 8 -#endif - -#if STBTT_MAX_OVERSAMPLE > 255 -#error "STBTT_MAX_OVERSAMPLE cannot be > 255" -#endif - -typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE - 1)) == 0 ? 1 : -1]; - -#ifndef STBTT_RASTERIZER_VERSION -#define STBTT_RASTERIZER_VERSION 2 -#endif - -#ifdef _MSC_VER -#define STBTT__NOTUSED(v) (void)(v) -#else -#define STBTT__NOTUSED(v) (void)sizeof(v) -#endif - -////////////////////////////////////////////////////////////////////////// -// -// stbtt__buf helpers to parse data from file -// - -static stbtt_uint8 stbtt__buf_get8(stbtt__buf* b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor++]; -} - -static stbtt_uint8 stbtt__buf_peek8(stbtt__buf* b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor]; -} - -static void stbtt__buf_seek(stbtt__buf* b, int o) -{ - STBTT_assert(!(o > b->size || o < 0)); - b->cursor = (o > b->size || o < 0) ? b->size : o; -} - -static void stbtt__buf_skip(stbtt__buf* b, int o) { stbtt__buf_seek(b, b->cursor + o); } - -static stbtt_uint32 stbtt__buf_get(stbtt__buf* b, int n) -{ - stbtt_uint32 v = 0; - int i; - STBTT_assert(n >= 1 && n <= 4); - for (i = 0; i < n; i++) - v = (v << 8) | stbtt__buf_get8(b); - return v; -} - -static stbtt__buf stbtt__new_buf(const void* p, size_t size) -{ - stbtt__buf r; - STBTT_assert(size < 0x40000000); - r.data = (stbtt_uint8*)p; - r.size = (int)size; - r.cursor = 0; - return r; -} - -#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) -#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) - -static stbtt__buf stbtt__buf_range(const stbtt__buf* b, int o, int s) -{ - stbtt__buf r = stbtt__new_buf(NULL, 0); - if (o < 0 || s < 0 || o > b->size || s > b->size - o) - return r; - r.data = b->data + o; - r.size = s; - return r; -} - -static stbtt__buf stbtt__cff_get_index(stbtt__buf* b) -{ - int count, start, offsize; - start = b->cursor; - count = stbtt__buf_get16(b); - if (count) { - offsize = stbtt__buf_get8(b); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(b, offsize * count); - stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); - } - return stbtt__buf_range(b, start, b->cursor - start); -} - -static stbtt_uint32 stbtt__cff_int(stbtt__buf* b) -{ - int b0 = stbtt__buf_get8(b); - if (b0 >= 32 && b0 <= 246) - return b0 - 139; - else if (b0 >= 247 && b0 <= 250) - return (b0 - 247) * 256 + stbtt__buf_get8(b) + 108; - else if (b0 >= 251 && b0 <= 254) - return -(b0 - 251) * 256 - stbtt__buf_get8(b) - 108; - else if (b0 == 28) - return stbtt__buf_get16(b); - else if (b0 == 29) - return stbtt__buf_get32(b); - STBTT_assert(0); - return 0; -} - -static void stbtt__cff_skip_operand(stbtt__buf* b) -{ - int v, b0 = stbtt__buf_peek8(b); - STBTT_assert(b0 >= 28); - if (b0 == 30) { - stbtt__buf_skip(b, 1); - while (b->cursor < b->size) { - v = stbtt__buf_get8(b); - if ((v & 0xF) == 0xF || (v >> 4) == 0xF) - break; - } - } else { - stbtt__cff_int(b); - } -} - -static stbtt__buf stbtt__dict_get(stbtt__buf* b, int key) -{ - stbtt__buf_seek(b, 0); - while (b->cursor < b->size) { - int start = b->cursor, end, op; - while (stbtt__buf_peek8(b) >= 28) - stbtt__cff_skip_operand(b); - end = b->cursor; - op = stbtt__buf_get8(b); - if (op == 12) - op = stbtt__buf_get8(b) | 0x100; - if (op == key) - return stbtt__buf_range(b, start, end - start); - } - return stbtt__buf_range(b, 0, 0); -} - -static void stbtt__dict_get_ints(stbtt__buf* b, int key, int outcount, stbtt_uint32* out) -{ - int i; - stbtt__buf operands = stbtt__dict_get(b, key); - for (i = 0; i < outcount && operands.cursor < operands.size; i++) - out[i] = stbtt__cff_int(&operands); -} - -static int stbtt__cff_index_count(stbtt__buf* b) -{ - stbtt__buf_seek(b, 0); - return stbtt__buf_get16(b); -} - -static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) -{ - int count, offsize, start, end; - stbtt__buf_seek(&b, 0); - count = stbtt__buf_get16(&b); - offsize = stbtt__buf_get8(&b); - STBTT_assert(i >= 0 && i < count); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(&b, i * offsize); - start = stbtt__buf_get(&b, offsize); - end = stbtt__buf_get(&b, offsize); - return stbtt__buf_range(&b, 2 + (count + 1) * offsize + start, end - start); -} - -////////////////////////////////////////////////////////////////////////// -// -// accessors to parse data from file -// - -// on platforms that don't allow misaligned reads, if we want to allow -// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE - -#define ttBYTE(p) (*(stbtt_uint8*)(p)) -#define ttCHAR(p) (*(stbtt_int8*)(p)) -#define ttFixed(p) ttLONG(p) - -static stbtt_uint16 ttUSHORT(stbtt_uint8* p) -{ - return p[0] * 256 + p[1]; -} -static stbtt_int16 ttSHORT(stbtt_uint8* p) { return p[0] * 256 + p[1]; } -static stbtt_uint32 ttULONG(stbtt_uint8* p) { return (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]; } -static stbtt_int32 ttLONG(stbtt_uint8* p) { return (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]; } - -#define stbtt_tag4(p, c0, c1, c2, c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) -#define stbtt_tag(p, str) stbtt_tag4(p, str[0], str[1], str[2], str[3]) - -static int stbtt__isfont(stbtt_uint8* font) -{ - // check the version number - if (stbtt_tag4(font, '1', 0, 0, 0)) - return 1; // TrueType 1 - if (stbtt_tag(font, "typ1")) - return 1; // TrueType with type 1 font -- we don't support this! - if (stbtt_tag(font, "OTTO")) - return 1; // OpenType with CFF - if (stbtt_tag4(font, 0, 1, 0, 0)) - return 1; // OpenType 1.0 - if (stbtt_tag(font, "true")) - return 1; // Apple specification for TrueType fonts - return 0; -} - -// @OPTIMIZE: binary search -static stbtt_uint32 stbtt__find_table(stbtt_uint8* data, stbtt_uint32 fontstart, const char* tag) -{ - stbtt_int32 num_tables = ttUSHORT(data + fontstart + 4); - stbtt_uint32 tabledir = fontstart + 12; - stbtt_int32 i; - for (i = 0; i < num_tables; ++i) { - stbtt_uint32 loc = tabledir + 16 * i; - if (stbtt_tag(data + loc + 0, tag)) - return ttULONG(data + loc + 8); - } - return 0; -} - -static int stbtt_GetFontOffsetForIndex_internal(unsigned char* font_collection, int index) -{ - // if it's just a font, there's only one valid index - if (stbtt__isfont(font_collection)) - return index == 0 ? 0 : -1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection + 4) == 0x00010000 || ttULONG(font_collection + 4) == 0x00020000) { - stbtt_int32 n = ttLONG(font_collection + 8); - if (index >= n) - return -1; - return ttULONG(font_collection + 12 + index * 4); - } - } - return -1; -} - -static int stbtt_GetNumberOfFonts_internal(unsigned char* font_collection) -{ - // if it's just a font, there's only one valid font - if (stbtt__isfont(font_collection)) - return 1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection + 4) == 0x00010000 || ttULONG(font_collection + 4) == 0x00020000) { - return ttLONG(font_collection + 8); - } - } - return 0; -} - -static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) -{ - stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; - stbtt__buf pdict; - stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); - if (!private_loc[1] || !private_loc[0]) - return stbtt__new_buf(NULL, 0); - pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); - stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); - if (!subrsoff) - return stbtt__new_buf(NULL, 0); - stbtt__buf_seek(&cff, private_loc[1] + subrsoff); - return stbtt__cff_get_index(&cff); -} - -static int stbtt_InitFont_internal(stbtt_fontinfo* info, unsigned char* data, int fontstart) -{ - stbtt_uint32 cmap, t; - stbtt_int32 i, numTables; - - info->data = data; - info->fontstart = fontstart; - info->cff = stbtt__new_buf(NULL, 0); - - cmap = stbtt__find_table(data, fontstart, "cmap"); // required - info->loca = stbtt__find_table(data, fontstart, "loca"); // required - info->head = stbtt__find_table(data, fontstart, "head"); // required - info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required - info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required - info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required - info->kern = stbtt__find_table(data, fontstart, "kern"); // not required - info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required - - if (!cmap || !info->head || !info->hhea || !info->hmtx) - return 0; - if (info->glyf) { - // required for truetype - if (!info->loca) - return 0; - } else { - // initialization for CFF / Type2 fonts (OTF) - stbtt__buf b, topdict, topdictidx; - stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; - stbtt_uint32 cff; - - cff = stbtt__find_table(data, fontstart, "CFF "); - if (!cff) - return 0; - - info->fontdicts = stbtt__new_buf(NULL, 0); - info->fdselect = stbtt__new_buf(NULL, 0); - - // @TODO this should use size from table (not 512MB) - info->cff = stbtt__new_buf(data + cff, 512 * 1024 * 1024); - b = info->cff; - - // read the header - stbtt__buf_skip(&b, 2); - stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize - - // @TODO the name INDEX could list multiple fonts, - // but we just use the first one. - stbtt__cff_get_index(&b); // name INDEX - topdictidx = stbtt__cff_get_index(&b); - topdict = stbtt__cff_index_get(topdictidx, 0); - stbtt__cff_get_index(&b); // string INDEX - info->gsubrs = stbtt__cff_get_index(&b); - - stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); - stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); - stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); - stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); - info->subrs = stbtt__get_subrs(b, topdict); - - // we only support Type 2 charstrings - if (cstype != 2) - return 0; - if (charstrings == 0) - return 0; - - if (fdarrayoff) { - // looks like a CID font - if (!fdselectoff) - return 0; - stbtt__buf_seek(&b, fdarrayoff); - info->fontdicts = stbtt__cff_get_index(&b); - info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size - fdselectoff); - } - - stbtt__buf_seek(&b, charstrings); - info->charstrings = stbtt__cff_get_index(&b); - } - - t = stbtt__find_table(data, fontstart, "maxp"); - if (t) - info->numGlyphs = ttUSHORT(data + t + 4); - else - info->numGlyphs = 0xffff; - - // find a cmap encoding table we understand *now* to avoid searching - // later. (todo: could make this installable) - // the same regardless of glyph. - numTables = ttUSHORT(data + cmap + 2); - info->index_map = 0; - for (i = 0; i < numTables; ++i) { - stbtt_uint32 encoding_record = cmap + 4 + 8 * i; - // find an encoding we understand: - switch (ttUSHORT(data + encoding_record)) { - case STBTT_PLATFORM_ID_MICROSOFT: - switch (ttUSHORT(data + encoding_record + 2)) { - case STBTT_MS_EID_UNICODE_BMP: - case STBTT_MS_EID_UNICODE_FULL: - // MS/Unicode - info->index_map = cmap + ttULONG(data + encoding_record + 4); - break; - } - break; - case STBTT_PLATFORM_ID_UNICODE: - // Mac/iOS has these - // all the encodingIDs are unicode, so we don't bother to check it - info->index_map = cmap + ttULONG(data + encoding_record + 4); - break; - } - } - if (info->index_map == 0) - return 0; - - info->indexToLocFormat = ttUSHORT(data + info->head + 50); - return 1; -} - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo* info, int unicode_codepoint) -{ - stbtt_uint8* data = info->data; - stbtt_uint32 index_map = info->index_map; - - stbtt_uint16 format = ttUSHORT(data + index_map + 0); - if (format == 0) { // apple byte encoding - stbtt_int32 bytes = ttUSHORT(data + index_map + 2); - if (unicode_codepoint < bytes - 6) - return ttBYTE(data + index_map + 6 + unicode_codepoint); - return 0; - } else if (format == 6) { - stbtt_uint32 first = ttUSHORT(data + index_map + 6); - stbtt_uint32 count = ttUSHORT(data + index_map + 8); - if ((stbtt_uint32)unicode_codepoint >= first && (stbtt_uint32)unicode_codepoint < first + count) - return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first) * 2); - return 0; - } else if (format == 2) { - STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean - return 0; - } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges - stbtt_uint16 segcount = ttUSHORT(data + index_map + 6) >> 1; - stbtt_uint16 searchRange = ttUSHORT(data + index_map + 8) >> 1; - stbtt_uint16 entrySelector = ttUSHORT(data + index_map + 10); - stbtt_uint16 rangeShift = ttUSHORT(data + index_map + 12) >> 1; - - // do a binary search of the segments - stbtt_uint32 endCount = index_map + 14; - stbtt_uint32 search = endCount; - - if (unicode_codepoint > 0xffff) - return 0; - - // they lie from endCount .. endCount + segCount - // but searchRange is the nearest power of two, so... - if (unicode_codepoint >= ttUSHORT(data + search + rangeShift * 2)) - search += rangeShift * 2; - - // now decrement to bias correctly to find smallest - search -= 2; - while (entrySelector) { - stbtt_uint16 end; - searchRange >>= 1; - end = ttUSHORT(data + search + searchRange * 2); - if (unicode_codepoint > end) - search += searchRange * 2; - --entrySelector; - } - search += 2; - - { - stbtt_uint16 offset, start; - stbtt_uint16 item = (stbtt_uint16)((search - endCount) >> 1); - - STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2 * item)); - start = ttUSHORT(data + index_map + 14 + segcount * 2 + 2 + 2 * item); - if (unicode_codepoint < start) - return 0; - - offset = ttUSHORT(data + index_map + 14 + segcount * 6 + 2 + 2 * item); - if (offset == 0) - return (stbtt_uint16)(unicode_codepoint + ttSHORT(data + index_map + 14 + segcount * 4 + 2 + 2 * item)); - - return ttUSHORT(data + offset + (unicode_codepoint - start) * 2 + index_map + 14 + segcount * 6 + 2 + 2 * item); - } - } else if (format == 12 || format == 13) { - stbtt_uint32 ngroups = ttULONG(data + index_map + 12); - stbtt_int32 low, high; - low = 0; - high = (stbtt_int32)ngroups; - // Binary search the right group. - while (low < high) { - stbtt_int32 mid = low + ((high - low) >> 1); // rounds down, so low <= mid < high - stbtt_uint32 start_char = ttULONG(data + index_map + 16 + mid * 12); - stbtt_uint32 end_char = ttULONG(data + index_map + 16 + mid * 12 + 4); - if ((stbtt_uint32)unicode_codepoint < start_char) - high = mid; - else if ((stbtt_uint32)unicode_codepoint > end_char) - low = mid + 1; - else { - stbtt_uint32 start_glyph = ttULONG(data + index_map + 16 + mid * 12 + 8); - if (format == 12) - return start_glyph + unicode_codepoint - start_char; - else // format == 13 - return start_glyph; - } - } - return 0; // not found - } - // @TODO - STBTT_assert(0); - return 0; -} - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo* info, int unicode_codepoint, stbtt_vertex** vertices) -{ - return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); -} - -static void stbtt_setvertex(stbtt_vertex* v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, - stbtt_int32 cy) -{ - v->type = type; - v->x = (stbtt_int16)x; - v->y = (stbtt_int16)y; - v->cx = (stbtt_int16)cx; - v->cy = (stbtt_int16)cy; -} - -static int stbtt__GetGlyfOffset(const stbtt_fontinfo* info, int glyph_index) -{ - int g1, g2; - - STBTT_assert(!info->cff.size); - - if (glyph_index >= info->numGlyphs) - return -1; // glyph index out of range - if (info->indexToLocFormat >= 2) - return -1; // unknown index->glyph map format - - if (info->indexToLocFormat == 0) { - g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; - g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; - } else { - g1 = info->glyf + ttULONG(info->data + info->loca + glyph_index * 4); - g2 = info->glyf + ttULONG(info->data + info->loca + glyph_index * 4 + 4); - } - - return g1 == g2 ? -1 : g1; // if length is 0, return -1 -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo* info, int glyph_index, int* x0, int* y0, int* x1, int* y1); - -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo* info, int glyph_index, int* x0, int* y0, int* x1, int* y1) -{ - if (info->cff.size) { - stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); - } else { - int g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) - return 0; - - if (x0) - *x0 = ttSHORT(info->data + g + 2); - if (y0) - *y0 = ttSHORT(info->data + g + 4); - if (x1) - *x1 = ttSHORT(info->data + g + 6); - if (y1) - *y1 = ttSHORT(info->data + g + 8); - } - return 1; -} - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo* info, int codepoint, int* x0, int* y0, int* x1, int* y1) -{ - return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info, codepoint), x0, y0, x1, y1); -} - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo* info, int glyph_index) -{ - stbtt_int16 numberOfContours; - int g; - if (info->cff.size) - return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; - g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) - return 1; - numberOfContours = ttSHORT(info->data + g); - return numberOfContours == 0; -} - -static int stbtt__close_shape(stbtt_vertex* vertices, int num_vertices, int was_off, int start_off, stbtt_int32 sx, - stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) -{ - if (start_off) { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx + scx) >> 1, (cy + scy) >> 1, cx, cy); - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx, sy, scx, scy); - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx, sy, cx, cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, sx, sy, 0, 0); - } - return num_vertices; -} - -static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo* info, int glyph_index, stbtt_vertex** pvertices) -{ - stbtt_int16 numberOfContours; - stbtt_uint8* endPtsOfContours; - stbtt_uint8* data = info->data; - stbtt_vertex* vertices = 0; - int num_vertices = 0; - int g = stbtt__GetGlyfOffset(info, glyph_index); - - *pvertices = NULL; - - if (g < 0) - return 0; - - numberOfContours = ttSHORT(data + g); - - if (numberOfContours > 0) { - stbtt_uint8 flags = 0, flagcount; - stbtt_int32 ins, i, j = 0, m, n, next_move, was_off = 0, off, start_off = 0; - stbtt_int32 x, y, cx, cy, sx, sy, scx, scy; - stbtt_uint8* points; - endPtsOfContours = (data + g + 10); - ins = ttUSHORT(data + g + 10 + numberOfContours * 2); - points = data + g + 10 + numberOfContours * 2 + 2 + ins; - - n = 1 + ttUSHORT(endPtsOfContours + numberOfContours * 2 - 2); - - m = n + 2 * numberOfContours; // a loose bound on how many vertices we might need - vertices = (stbtt_vertex*)STBTT_malloc(m * sizeof(vertices[0]), info->userdata); - if (vertices == 0) - return 0; - - next_move = 0; - flagcount = 0; - - // in first pass, we load uninterpreted data into the allocated array - // above, shifted to the end of the array so we won't overwrite it when - // we create our final data starting from the front - - off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated - - // first load flags - - for (i = 0; i < n; ++i) { - if (flagcount == 0) { - flags = *points++; - if (flags & 8) - flagcount = *points++; - } else - --flagcount; - vertices[off + i].type = flags; - } - - // now load x coordinates - x = 0; - for (i = 0; i < n; ++i) { - flags = vertices[off + i].type; - if (flags & 2) { - stbtt_int16 dx = *points++; - x += (flags & 16) ? dx : -dx; // ??? - } else { - if (!(flags & 16)) { - x = x + (stbtt_int16)(points[0] * 256 + points[1]); - points += 2; - } - } - vertices[off + i].x = (stbtt_int16)x; - } - - // now load y coordinates - y = 0; - for (i = 0; i < n; ++i) { - flags = vertices[off + i].type; - if (flags & 4) { - stbtt_int16 dy = *points++; - y += (flags & 32) ? dy : -dy; // ??? - } else { - if (!(flags & 32)) { - y = y + (stbtt_int16)(points[0] * 256 + points[1]); - points += 2; - } - } - vertices[off + i].y = (stbtt_int16)y; - } - - // now convert them to our format - num_vertices = 0; - sx = sy = cx = cy = scx = scy = 0; - for (i = 0; i < n; ++i) { - flags = vertices[off + i].type; - x = (stbtt_int16)vertices[off + i].x; - y = (stbtt_int16)vertices[off + i].y; - - if (next_move == i) { - if (i != 0) - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx, sy, scx, scy, cx, cy); - - // now start the new one - start_off = !(flags & 1); - if (start_off) { - // if we start off with an off-curve point, then when we need to find a point on the curve - // where we can start, and we need to save some state for when we wraparound. - scx = x; - scy = y; - if (!(vertices[off + i + 1].type & 1)) { - // next point is also a curve point, so interpolate an on-point curve - sx = (x + (stbtt_int32)vertices[off + i + 1].x) >> 1; - sy = (y + (stbtt_int32)vertices[off + i + 1].y) >> 1; - } else { - // otherwise just use the next point as our start point - sx = (stbtt_int32)vertices[off + i + 1].x; - sy = (stbtt_int32)vertices[off + i + 1].y; - ++i; // we're using point i+1 as the starting point, so skip it - } - } else { - sx = x; - sy = y; - } - stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove, sx, sy, 0, 0); - was_off = 0; - next_move = 1 + ttUSHORT(endPtsOfContours + j * 2); - ++j; - } else { - if (!(flags & 1)) { // if it's a curve - if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx + x) >> 1, (cy + y) >> 1, cx, cy); - cx = x; - cy = y; - was_off = 1; - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x, y, cx, cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x, y, 0, 0); - was_off = 0; - } - } - } - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx, sy, scx, scy, cx, cy); - } else if (numberOfContours == -1) { - // Compound shapes. - int more = 1; - stbtt_uint8* comp = data + g + 10; - num_vertices = 0; - vertices = 0; - while (more) { - stbtt_uint16 flags, gidx; - int comp_num_verts = 0, i; - stbtt_vertex *comp_verts = 0, *tmp = 0; - float mtx[6] = { 1, 0, 0, 1, 0, 0 }, m, n; - - flags = ttSHORT(comp); - comp += 2; - gidx = ttSHORT(comp); - comp += 2; - - if (flags & 2) { // XY values - if (flags & 1) { // shorts - mtx[4] = ttSHORT(comp); - comp += 2; - mtx[5] = ttSHORT(comp); - comp += 2; - } else { - mtx[4] = ttCHAR(comp); - comp += 1; - mtx[5] = ttCHAR(comp); - comp += 1; - } - } else { - // @TODO handle matching point - STBTT_assert(0); - } - if (flags & (1 << 3)) { // WE_HAVE_A_SCALE - mtx[0] = mtx[3] = ttSHORT(comp) / 16384.0f; - comp += 2; - mtx[1] = mtx[2] = 0; - } else if (flags & (1 << 6)) { // WE_HAVE_AN_X_AND_YSCALE - mtx[0] = ttSHORT(comp) / 16384.0f; - comp += 2; - mtx[1] = mtx[2] = 0; - mtx[3] = ttSHORT(comp) / 16384.0f; - comp += 2; - } else if (flags & (1 << 7)) { // WE_HAVE_A_TWO_BY_TWO - mtx[0] = ttSHORT(comp) / 16384.0f; - comp += 2; - mtx[1] = ttSHORT(comp) / 16384.0f; - comp += 2; - mtx[2] = ttSHORT(comp) / 16384.0f; - comp += 2; - mtx[3] = ttSHORT(comp) / 16384.0f; - comp += 2; - } - - // Find transformation scales. - m = (float)STBTT_sqrt(mtx[0] * mtx[0] + mtx[1] * mtx[1]); - n = (float)STBTT_sqrt(mtx[2] * mtx[2] + mtx[3] * mtx[3]); - - // Get indexed glyph. - comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); - if (comp_num_verts > 0) { - // Transform vertices. - for (i = 0; i < comp_num_verts; ++i) { - stbtt_vertex* v = &comp_verts[i]; - stbtt_vertex_type x, y; - x = v->x; - y = v->y; - v->x = (stbtt_vertex_type)(m * (mtx[0] * x + mtx[2] * y + mtx[4])); - v->y = (stbtt_vertex_type)(n * (mtx[1] * x + mtx[3] * y + mtx[5])); - x = v->cx; - y = v->cy; - v->cx = (stbtt_vertex_type)(m * (mtx[0] * x + mtx[2] * y + mtx[4])); - v->cy = (stbtt_vertex_type)(n * (mtx[1] * x + mtx[3] * y + mtx[5])); - } - // Append vertices. - tmp = (stbtt_vertex*)STBTT_malloc((num_vertices + comp_num_verts) * sizeof(stbtt_vertex), info->userdata); - if (!tmp) { - if (vertices) - STBTT_free(vertices, info->userdata); - if (comp_verts) - STBTT_free(comp_verts, info->userdata); - return 0; - } - if (num_vertices > 0) - STBTT_memcpy(tmp, vertices, num_vertices * sizeof(stbtt_vertex)); - STBTT_memcpy(tmp + num_vertices, comp_verts, comp_num_verts * sizeof(stbtt_vertex)); - if (vertices) - STBTT_free(vertices, info->userdata); - vertices = tmp; - STBTT_free(comp_verts, info->userdata); - num_vertices += comp_num_verts; - } - // More components ? - more = flags & (1 << 5); - } - } else if (numberOfContours < 0) { - // @TODO other compound variations? - STBTT_assert(0); - } else { - // numberOfCounters == 0, do nothing - } - - *pvertices = vertices; - return num_vertices; -} - -typedef struct { - int bounds; - int started; - float first_x, first_y; - float x, y; - stbtt_int32 min_x, max_x, min_y, max_y; - - stbtt_vertex* pvertices; - int num_vertices; -} stbtt__csctx; - -#define STBTT__CSCTX_INIT(bounds) \ - { \ - bounds, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, 0 \ - } - -static void stbtt__track_vertex(stbtt__csctx* c, stbtt_int32 x, stbtt_int32 y) -{ - if (x > c->max_x || !c->started) - c->max_x = x; - if (y > c->max_y || !c->started) - c->max_y = y; - if (x < c->min_x || !c->started) - c->min_x = x; - if (y < c->min_y || !c->started) - c->min_y = y; - c->started = 1; -} - -static void stbtt__csctx_v(stbtt__csctx* c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, - stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) -{ - if (c->bounds) { - stbtt__track_vertex(c, x, y); - if (type == STBTT_vcubic) { - stbtt__track_vertex(c, cx, cy); - stbtt__track_vertex(c, cx1, cy1); - } - } else { - stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); - c->pvertices[c->num_vertices].cx1 = (stbtt_int16)cx1; - c->pvertices[c->num_vertices].cy1 = (stbtt_int16)cy1; - } - c->num_vertices++; -} - -static void stbtt__csctx_close_shape(stbtt__csctx* ctx) -{ - if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rmove_to(stbtt__csctx* ctx, float dx, float dy) -{ - stbtt__csctx_close_shape(ctx); - ctx->first_x = ctx->x = ctx->x + dx; - ctx->first_y = ctx->y = ctx->y + dy; - stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rline_to(stbtt__csctx* ctx, float dx, float dy) -{ - ctx->x += dx; - ctx->y += dy; - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rccurve_to(stbtt__csctx* ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) -{ - float cx1 = ctx->x + dx1; - float cy1 = ctx->y + dy1; - float cx2 = cx1 + dx2; - float cy2 = cy1 + dy2; - ctx->x = cx2 + dx3; - ctx->y = cy2 + dy3; - stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); -} - -static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) -{ - int count = stbtt__cff_index_count(&idx); - int bias = 107; - if (count >= 33900) - bias = 32768; - else if (count >= 1240) - bias = 1131; - n += bias; - if (n < 0 || n >= count) - return stbtt__new_buf(NULL, 0); - return stbtt__cff_index_get(idx, n); -} - -static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo* info, int glyph_index) -{ - stbtt__buf fdselect = info->fdselect; - int nranges, start, end, v, fmt, fdselector = -1, i; - - stbtt__buf_seek(&fdselect, 0); - fmt = stbtt__buf_get8(&fdselect); - if (fmt == 0) { - // untested - stbtt__buf_skip(&fdselect, glyph_index); - fdselector = stbtt__buf_get8(&fdselect); - } else if (fmt == 3) { - nranges = stbtt__buf_get16(&fdselect); - start = stbtt__buf_get16(&fdselect); - for (i = 0; i < nranges; i++) { - v = stbtt__buf_get8(&fdselect); - end = stbtt__buf_get16(&fdselect); - if (glyph_index >= start && glyph_index < end) { - fdselector = v; - break; - } - start = end; - } - } - if (fdselector == -1) - stbtt__new_buf(NULL, 0); - return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); -} - -static int stbtt__run_charstring(const stbtt_fontinfo* info, int glyph_index, stbtt__csctx* c) -{ - int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; - int has_subrs = 0, clear_stack; - float s[48]; - stbtt__buf subr_stack[10], subrs = info->subrs, b; - float f; - -#define STBTT__CSERR(s) (0) - - // this currently ignores the initial width value, which isn't needed if we have hmtx - b = stbtt__cff_index_get(info->charstrings, glyph_index); - while (b.cursor < b.size) { - i = 0; - clear_stack = 1; - b0 = stbtt__buf_get8(&b); - switch (b0) { - // @TODO implement hinting - case 0x13: // hintmask - case 0x14: // cntrmask - if (in_header) - maskbits += (sp / 2); // implicit "vstem" - in_header = 0; - stbtt__buf_skip(&b, (maskbits + 7) / 8); - break; - - case 0x01: // hstem - case 0x03: // vstem - case 0x12: // hstemhm - case 0x17: // vstemhm - maskbits += (sp / 2); - break; - - case 0x15: // rmoveto - in_header = 0; - if (sp < 2) - return STBTT__CSERR("rmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp - 2], s[sp - 1]); - break; - case 0x04: // vmoveto - in_header = 0; - if (sp < 1) - return STBTT__CSERR("vmoveto stack"); - stbtt__csctx_rmove_to(c, 0, s[sp - 1]); - break; - case 0x16: // hmoveto - in_header = 0; - if (sp < 1) - return STBTT__CSERR("hmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp - 1], 0); - break; - - case 0x05: // rlineto - if (sp < 2) - return STBTT__CSERR("rlineto stack"); - for (; i + 1 < sp; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i + 1]); - break; - - // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical - // starting from a different place. - - case 0x07: // vlineto - if (sp < 1) - return STBTT__CSERR("vlineto stack"); - goto vlineto; - case 0x06: // hlineto - if (sp < 1) - return STBTT__CSERR("hlineto stack"); - for (;;) { - if (i >= sp) - break; - stbtt__csctx_rline_to(c, s[i], 0); - i++; - vlineto: - if (i >= sp) - break; - stbtt__csctx_rline_to(c, 0, s[i]); - i++; - } - break; - - case 0x1F: // hvcurveto - if (sp < 4) - return STBTT__CSERR("hvcurveto stack"); - goto hvcurveto; - case 0x1E: // vhcurveto - if (sp < 4) - return STBTT__CSERR("vhcurveto stack"); - for (;;) { - if (i + 3 >= sp) - break; - stbtt__csctx_rccurve_to(c, 0, s[i], s[i + 1], s[i + 2], s[i + 3], (sp - i == 5) ? s[i + 4] : 0.0f); - i += 4; - hvcurveto: - if (i + 3 >= sp) - break; - stbtt__csctx_rccurve_to(c, s[i], 0, s[i + 1], s[i + 2], (sp - i == 5) ? s[i + 4] : 0.0f, s[i + 3]); - i += 4; - } - break; - - case 0x08: // rrcurveto - if (sp < 6) - return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i + 1], s[i + 2], s[i + 3], s[i + 4], s[i + 5]); - break; - - case 0x18: // rcurveline - if (sp < 8) - return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp - 2; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i + 1], s[i + 2], s[i + 3], s[i + 4], s[i + 5]); - if (i + 1 >= sp) - return STBTT__CSERR("rcurveline stack"); - stbtt__csctx_rline_to(c, s[i], s[i + 1]); - break; - - case 0x19: // rlinecurve - if (sp < 8) - return STBTT__CSERR("rlinecurve stack"); - for (; i + 1 < sp - 6; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i + 1]); - if (i + 5 >= sp) - return STBTT__CSERR("rlinecurve stack"); - stbtt__csctx_rccurve_to(c, s[i], s[i + 1], s[i + 2], s[i + 3], s[i + 4], s[i + 5]); - break; - - case 0x1A: // vvcurveto - case 0x1B: // hhcurveto - if (sp < 4) - return STBTT__CSERR("(vv|hh)curveto stack"); - f = 0.0; - if (sp & 1) { - f = s[i]; - i++; - } - for (; i + 3 < sp; i += 4) { - if (b0 == 0x1B) - stbtt__csctx_rccurve_to(c, s[i], f, s[i + 1], s[i + 2], s[i + 3], 0.0); - else - stbtt__csctx_rccurve_to(c, f, s[i], s[i + 1], s[i + 2], 0.0, s[i + 3]); - f = 0.0; - } - break; - - case 0x0A: // callsubr - if (!has_subrs) { - if (info->fdselect.size) - subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); - has_subrs = 1; - } - // fallthrough - case 0x1D: // callgsubr - if (sp < 1) - return STBTT__CSERR("call(g|)subr stack"); - v = (int)s[--sp]; - if (subr_stack_height >= 10) - return STBTT__CSERR("recursion limit"); - subr_stack[subr_stack_height++] = b; - b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); - if (b.size == 0) - return STBTT__CSERR("subr not found"); - b.cursor = 0; - clear_stack = 0; - break; - - case 0x0B: // return - if (subr_stack_height <= 0) - return STBTT__CSERR("return outside subr"); - b = subr_stack[--subr_stack_height]; - clear_stack = 0; - break; - - case 0x0E: // endchar - stbtt__csctx_close_shape(c); - return 1; - - case 0x0C: { // two-byte escape - float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; - float dx, dy; - int b1 = stbtt__buf_get8(&b); - switch (b1) { - // @TODO These "flex" implementations ignore the flex-depth and resolution, - // and always draw beziers. - case 0x22: // hflex - if (sp < 7) - return STBTT__CSERR("hflex stack"); - dx1 = s[0]; - dx2 = s[1]; - dy2 = s[2]; - dx3 = s[3]; - dx4 = s[4]; - dx5 = s[5]; - dx6 = s[6]; - stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); - break; - - case 0x23: // flex - if (sp < 13) - return STBTT__CSERR("flex stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = s[10]; - dy6 = s[11]; - // fd is s[12] - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - case 0x24: // hflex1 - if (sp < 9) - return STBTT__CSERR("hflex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dx4 = s[5]; - dx5 = s[6]; - dy5 = s[7]; - dx6 = s[8]; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1 + dy2 + dy5)); - break; - - case 0x25: // flex1 - if (sp < 11) - return STBTT__CSERR("flex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = dy6 = s[10]; - dx = dx1 + dx2 + dx3 + dx4 + dx5; - dy = dy1 + dy2 + dy3 + dy4 + dy5; - if (STBTT_fabs(dx) > STBTT_fabs(dy)) - dy6 = -dy; - else - dx6 = -dx; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - default: - return STBTT__CSERR("unimplemented"); - } - } break; - - default: - if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) - return STBTT__CSERR("reserved operator"); - - // push immediate - if (b0 == 255) { - f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; - } else { - stbtt__buf_skip(&b, -1); - f = (float)(stbtt_int16)stbtt__cff_int(&b); - } - if (sp >= 48) - return STBTT__CSERR("push stack overflow"); - s[sp++] = f; - clear_stack = 0; - break; - } - if (clear_stack) - sp = 0; - } - return STBTT__CSERR("no endchar"); - -#undef STBTT__CSERR -} - -static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo* info, int glyph_index, stbtt_vertex** pvertices) -{ - // runs the charstring twice, once to count and once to output (to avoid realloc) - stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); - stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); - if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { - *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices * sizeof(stbtt_vertex), info->userdata); - output_ctx.pvertices = *pvertices; - if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { - STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); - return output_ctx.num_vertices; - } - } - *pvertices = NULL; - return 0; -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo* info, int glyph_index, int* x0, int* y0, int* x1, int* y1) -{ - stbtt__csctx c = STBTT__CSCTX_INIT(1); - int r = stbtt__run_charstring(info, glyph_index, &c); - if (x0) - *x0 = r ? c.min_x : 0; - if (y0) - *y0 = r ? c.min_y : 0; - if (x1) - *x1 = r ? c.max_x : 0; - if (y1) - *y1 = r ? c.max_y : 0; - return r ? c.num_vertices : 0; -} - -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo* info, int glyph_index, stbtt_vertex** pvertices) -{ - if (!info->cff.size) - return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); - else - return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); -} - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo* info, int glyph_index, int* advanceWidth, - int* leftSideBearing) -{ - stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data + info->hhea + 34); - if (glyph_index < numOfLongHorMetrics) { - if (advanceWidth) - *advanceWidth = ttSHORT(info->data + info->hmtx + 4 * glyph_index); - if (leftSideBearing) - *leftSideBearing = ttSHORT(info->data + info->hmtx + 4 * glyph_index + 2); - } else { - if (advanceWidth) - *advanceWidth = ttSHORT(info->data + info->hmtx + 4 * (numOfLongHorMetrics - 1)); - if (leftSideBearing) - *leftSideBearing = - ttSHORT(info->data + info->hmtx + 4 * numOfLongHorMetrics + 2 * (glyph_index - numOfLongHorMetrics)); - } -} - -static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo* info, int glyph1, int glyph2) -{ - stbtt_uint8* data = info->data + info->kern; - stbtt_uint32 needle, straw; - int l, r, m; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data + 2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data + 8) != 1) // horizontal flag must be set in format - return 0; - - l = 0; - r = ttUSHORT(data + 10) - 1; - needle = glyph1 << 16 | glyph2; - while (l <= r) { - m = (l + r) >> 1; - straw = ttULONG(data + 18 + (m * 6)); // note: unaligned read - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else - return ttSHORT(data + 22 + (m * 6)); - } - return 0; -} - -static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8* coverageTable, int glyph) -{ - stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); - switch (coverageFormat) { - case 1: { - stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); - - // Binary search. - stbtt_int32 l = 0, r = glyphCount - 1, m; - int straw, needle = glyph; - while (l <= r) { - stbtt_uint8* glyphArray = coverageTable + 4; - stbtt_uint16 glyphID; - m = (l + r) >> 1; - glyphID = ttUSHORT(glyphArray + 2 * m); - straw = glyphID; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - return m; - } - } - } break; - - case 2: { - stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); - stbtt_uint8* rangeArray = coverageTable + 4; - - // Binary search. - stbtt_int32 l = 0, r = rangeCount - 1, m; - int strawStart, strawEnd, needle = glyph; - while (l <= r) { - stbtt_uint8* rangeRecord; - m = (l + r) >> 1; - rangeRecord = rangeArray + 6 * m; - strawStart = ttUSHORT(rangeRecord); - strawEnd = ttUSHORT(rangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else { - stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); - return startCoverageIndex + glyph - strawStart; - } - } - } break; - - default: { - // There are no other cases. - STBTT_assert(0); - } break; - } - - return -1; -} - -static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8* classDefTable, int glyph) -{ - stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); - switch (classDefFormat) { - case 1: { - stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); - stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); - stbtt_uint8* classDef1ValueArray = classDefTable + 6; - - if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) - return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); - - classDefTable = classDef1ValueArray + 2 * glyphCount; - } break; - - case 2: { - stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); - stbtt_uint8* classRangeRecords = classDefTable + 4; - - // Binary search. - stbtt_int32 l = 0, r = classRangeCount - 1, m; - int strawStart, strawEnd, needle = glyph; - while (l <= r) { - stbtt_uint8* classRangeRecord; - m = (l + r) >> 1; - classRangeRecord = classRangeRecords + 6 * m; - strawStart = ttUSHORT(classRangeRecord); - strawEnd = ttUSHORT(classRangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else - return (stbtt_int32)ttUSHORT(classRangeRecord + 4); - } - - classDefTable = classRangeRecords + 6 * classRangeCount; - } break; - - default: { - // There are no other cases. - STBTT_assert(0); - } break; - } - - return -1; -} - -// Define to STBTT_assert(x) if you want to break on unimplemented formats. -#define STBTT_GPOS_TODO_assert(x) - -static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo* info, int glyph1, int glyph2) -{ - stbtt_uint16 lookupListOffset; - stbtt_uint8* lookupList; - stbtt_uint16 lookupCount; - stbtt_uint8* data; - stbtt_int32 i; - - if (!info->gpos) - return 0; - - data = info->data + info->gpos; - - if (ttUSHORT(data + 0) != 1) - return 0; // Major version 1 - if (ttUSHORT(data + 2) != 0) - return 0; // Minor version 0 - - lookupListOffset = ttUSHORT(data + 8); - lookupList = data + lookupListOffset; - lookupCount = ttUSHORT(lookupList); - - for (i = 0; i < lookupCount; ++i) { - stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i); - stbtt_uint8* lookupTable = lookupList + lookupOffset; - - stbtt_uint16 lookupType = ttUSHORT(lookupTable); - stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4); - stbtt_uint8* subTableOffsets = lookupTable + 6; - switch (lookupType) { - case 2: { // Pair Adjustment Positioning Subtable - stbtt_int32 sti; - for (sti = 0; sti < subTableCount; sti++) { - stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti); - stbtt_uint8* table = lookupTable + subtableOffset; - stbtt_uint16 posFormat = ttUSHORT(table); - stbtt_uint16 coverageOffset = ttUSHORT(table + 2); - stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(table + coverageOffset, glyph1); - if (coverageIndex == -1) - continue; - - switch (posFormat) { - case 1: { - stbtt_int32 l, r, m; - int straw, needle; - stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); - stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); - stbtt_int32 valueRecordPairSizeInBytes = 2; - stbtt_uint16 pairSetCount = ttUSHORT(table + 8); - stbtt_uint16 pairPosOffset = ttUSHORT(table + 10 + 2 * coverageIndex); - stbtt_uint8* pairValueTable = table + pairPosOffset; - stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable); - stbtt_uint8* pairValueArray = pairValueTable + 2; - // TODO: Support more formats. - STBTT_GPOS_TODO_assert(valueFormat1 == 4); - if (valueFormat1 != 4) - return 0; - STBTT_GPOS_TODO_assert(valueFormat2 == 0); - if (valueFormat2 != 0) - return 0; - - STBTT_assert(coverageIndex < pairSetCount); - STBTT__NOTUSED(pairSetCount); - - needle = glyph2; - r = pairValueCount - 1; - l = 0; - - // Binary search. - while (l <= r) { - stbtt_uint16 secondGlyph; - stbtt_uint8* pairValue; - m = (l + r) >> 1; - pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; - secondGlyph = ttUSHORT(pairValue); - straw = secondGlyph; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - stbtt_int16 xAdvance = ttSHORT(pairValue + 2); - return xAdvance; - } - } - } break; - - case 2: { - stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); - stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); - - stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); - stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); - int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); - int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); - - stbtt_uint16 class1Count = ttUSHORT(table + 12); - stbtt_uint16 class2Count = ttUSHORT(table + 14); - STBTT_assert(glyph1class < class1Count); - STBTT_assert(glyph2class < class2Count); - - // TODO: Support more formats. - STBTT_GPOS_TODO_assert(valueFormat1 == 4); - if (valueFormat1 != 4) - return 0; - STBTT_GPOS_TODO_assert(valueFormat2 == 0); - if (valueFormat2 != 0) - return 0; - - if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { - stbtt_uint8* class1Records = table + 16; - stbtt_uint8* class2Records = class1Records + 2 * (glyph1class * class2Count); - stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); - return xAdvance; - } - } break; - - default: { - // There are no other cases. - STBTT_assert(0); - break; - }; - } - } - break; - }; - - default: - // TODO: Implement other stuff. - break; - } - } - - return 0; -} - -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo* info, int g1, int g2) -{ - int xAdvance = 0; - - if (info->gpos) - xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); - - if (info->kern) - xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); - - return xAdvance; -} - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo* info, int ch1, int ch2) -{ - if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs - return 0; - return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info, ch1), stbtt_FindGlyphIndex(info, ch2)); -} - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo* info, int codepoint, int* advanceWidth, - int* leftSideBearing) -{ - stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info, codepoint), advanceWidth, leftSideBearing); -} - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo* info, int* ascent, int* descent, int* lineGap) -{ - if (ascent) - *ascent = ttSHORT(info->data + info->hhea + 4); - if (descent) - *descent = ttSHORT(info->data + info->hhea + 6); - if (lineGap) - *lineGap = ttSHORT(info->data + info->hhea + 8); -} - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo* info, int* typoAscent, int* typoDescent, int* typoLineGap) -{ - int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); - if (!tab) - return 0; - if (typoAscent) - *typoAscent = ttSHORT(info->data + tab + 68); - if (typoDescent) - *typoDescent = ttSHORT(info->data + tab + 70); - if (typoLineGap) - *typoLineGap = ttSHORT(info->data + tab + 72); - return 1; -} - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo* info, int* x0, int* y0, int* x1, int* y1) -{ - *x0 = ttSHORT(info->data + info->head + 36); - *y0 = ttSHORT(info->data + info->head + 38); - *x1 = ttSHORT(info->data + info->head + 40); - *y1 = ttSHORT(info->data + info->head + 42); -} - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo* info, float height) -{ - int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); - return (float)height / fheight; -} - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo* info, float pixels) -{ - int unitsPerEm = ttUSHORT(info->data + info->head + 18); - return pixels / unitsPerEm; -} - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo* info, stbtt_vertex* v) { STBTT_free(v, info->userdata); } - -////////////////////////////////////////////////////////////////////////////// -// -// antialiasing software rasterizer -// - -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo* font, int glyph, float scale_x, float scale_y, - float shift_x, float shift_y, int* ix0, int* iy0, int* ix1, int* iy1) -{ - int x0 = 0, y0 = 0, x1, y1; // =0 suppresses compiler warning - if (!stbtt_GetGlyphBox(font, glyph, &x0, &y0, &x1, &y1)) { - // e.g. space character - if (ix0) - *ix0 = 0; - if (iy0) - *iy0 = 0; - if (ix1) - *ix1 = 0; - if (iy1) - *iy1 = 0; - } else { - // move to integral bboxes (treating pixels as little squares, what pixels get touched)? - if (ix0) - *ix0 = STBTT_ifloor(x0 * scale_x + shift_x); - if (iy0) - *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); - if (ix1) - *ix1 = STBTT_iceil(x1 * scale_x + shift_x); - if (iy1) - *iy1 = STBTT_iceil(-y0 * scale_y + shift_y); - } -} - -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo* font, int glyph, float scale_x, float scale_y, int* ix0, - int* iy0, int* ix1, int* iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y, 0.0f, 0.0f, ix0, iy0, ix1, iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo* font, int codepoint, float scale_x, - float scale_y, float shift_x, float shift_y, int* ix0, int* iy0, - int* ix1, int* iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font, codepoint), scale_x, scale_y, shift_x, shift_y, ix0, - iy0, ix1, iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo* font, int codepoint, float scale_x, float scale_y, - int* ix0, int* iy0, int* ix1, int* iy1) -{ - stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y, 0.0f, 0.0f, ix0, iy0, ix1, iy1); -} - -////////////////////////////////////////////////////////////////////////////// -// -// Rasterizer - -typedef struct stbtt__hheap_chunk { - struct stbtt__hheap_chunk* next; -} stbtt__hheap_chunk; - -typedef struct stbtt__hheap { - struct stbtt__hheap_chunk* head; - void* first_free; - int num_remaining_in_head_chunk; -} stbtt__hheap; - -static void* stbtt__hheap_alloc(stbtt__hheap* hh, size_t size, void* userdata) -{ - if (hh->first_free) { - void* p = hh->first_free; - hh->first_free = *(void**)p; - return p; - } else { - if (hh->num_remaining_in_head_chunk == 0) { - int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); - stbtt__hheap_chunk* c = (stbtt__hheap_chunk*)STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); - if (c == NULL) - return NULL; - c->next = hh->head; - hh->head = c; - hh->num_remaining_in_head_chunk = count; - } - --hh->num_remaining_in_head_chunk; - return (char*)(hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; - } -} - -static void stbtt__hheap_free(stbtt__hheap* hh, void* p) -{ - *(void**)p = hh->first_free; - hh->first_free = p; -} - -static void stbtt__hheap_cleanup(stbtt__hheap* hh, void* userdata) -{ - stbtt__hheap_chunk* c = hh->head; - while (c) { - stbtt__hheap_chunk* n = c->next; - STBTT_free(c, userdata); - c = n; - } -} - -typedef struct stbtt__edge { - float x0, y0, x1, y1; - int invert; -} stbtt__edge; - -typedef struct stbtt__active_edge { - struct stbtt__active_edge* next; -#if STBTT_RASTERIZER_VERSION == 1 - int x, dx; - float ey; - int direction; -#elif STBTT_RASTERIZER_VERSION == 2 - float fx, fdx, fdy; - float direction; - float sy; - float ey; -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif -} stbtt__active_edge; - -#if STBTT_RASTERIZER_VERSION == 1 -#define STBTT_FIXSHIFT 10 -#define STBTT_FIX (1 << STBTT_FIXSHIFT) -#define STBTT_FIXMASK (STBTT_FIX - 1) - -static stbtt__active_edge* stbtt__new_active(stbtt__hheap* hh, stbtt__edge* e, int off_x, float start_point, - void* userdata) -{ - stbtt__active_edge* z = (stbtt__active_edge*)stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - if (!z) - return z; - - // round dx down to avoid overshooting - if (dxdy < 0) - z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); - else - z->dx = STBTT_ifloor(STBTT_FIX * dxdy); - - z->x = STBTT_ifloor(STBTT_FIX * e->x0 + - z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount - z->x -= off_x * STBTT_FIX; - - z->ey = e->y1; - z->next = 0; - z->direction = e->invert ? 1 : -1; - return z; -} -#elif STBTT_RASTERIZER_VERSION == 2 -static stbtt__active_edge* stbtt__new_active(stbtt__hheap* hh, stbtt__edge* e, int off_x, float start_point, - void* userdata) -{ - stbtt__active_edge* z = (stbtt__active_edge*)stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - // STBTT_assert(e->y0 <= start_point); - if (!z) - return z; - z->fdx = dxdy; - z->fdy = dxdy != 0.0f ? (1.0f / dxdy) : 0.0f; - z->fx = e->x0 + dxdy * (start_point - e->y0); - z->fx -= off_x; - z->direction = e->invert ? 1.0f : -1.0f; - z->sy = e->y0; - z->ey = e->y1; - z->next = 0; - return z; -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#if STBTT_RASTERIZER_VERSION == 1 -// note: this routine clips fills that extend off the edges... ideally this -// wouldn't happen, but it could happen if the truetype glyph bounding boxes -// are wrong, or if the user supplies a too-small bitmap -static void stbtt__fill_active_edges(unsigned char* scanline, int len, stbtt__active_edge* e, int max_weight) -{ - // non-zero winding fill - int x0 = 0, w = 0; - - while (e) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; - w += e->direction; - } else { - int x1 = e->x; - w += e->direction; - // if we went to zero, we need to draw - if (w == 0) { - int i = x0 >> STBTT_FIXSHIFT; - int j = x1 >> STBTT_FIXSHIFT; - - if (i < len && j >= 0) { - if (i == j) { - // x0,x1 are the same pixel, so compute combined coverage - scanline[i] = scanline[i] + (stbtt_uint8)((x1 - x0) * max_weight >> STBTT_FIXSHIFT); - } else { - if (i >= 0) // add antialiasing for x0 - scanline[i] = - scanline[i] + (stbtt_uint8)(((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); - else - i = -1; // clip - - if (j < len) // add antialiasing for x1 - scanline[j] = scanline[j] + (stbtt_uint8)(((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); - else - j = len; // clip - - for (++i; i < j; ++i) // fill pixels between x0 and x1 - scanline[i] = scanline[i] + (stbtt_uint8)max_weight; - } - } - } - } - - e = e->next; - } -} - -static void stbtt__rasterize_sorted_edges(stbtt__bitmap* result, stbtt__edge* e, int n, int vsubsample, int off_x, - int off_y, void* userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge* active = NULL; - int y, j = 0; - int max_weight = (255 / vsubsample); // weight per vertical scanline - int s; // vertical subsample index - unsigned char scanline_data[512], *scanline; - - if (result->w > 512) - scanline = (unsigned char*)STBTT_malloc(result->w, userdata); - else - scanline = scanline_data; - - y = off_y * vsubsample; - e[n].y0 = (off_y + result->h) * (float)vsubsample + 1; - - while (j < result->h) { - STBTT_memset(scanline, 0, result->w); - for (s = 0; s < vsubsample; ++s) { - // find center of pixel for this scanline - float scan_y = y + 0.5f; - stbtt__active_edge** step = &active; - - // update all active edges; - // remove all active edges that terminate before the center of this scanline - while (*step) { - stbtt__active_edge* z = *step; - if (z->ey <= scan_y) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - z->x += z->dx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - } - - // resort the list if needed - for (;;) { - int changed = 0; - step = &active; - while (*step && (*step)->next) { - if ((*step)->x > (*step)->next->x) { - stbtt__active_edge* t = *step; - stbtt__active_edge* q = t->next; - - t->next = q->next; - q->next = t; - *step = q; - changed = 1; - } - step = &(*step)->next; - } - if (!changed) - break; - } - - // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline - while (e->y0 <= scan_y) { - if (e->y1 > scan_y) { - stbtt__active_edge* z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); - if (z != NULL) { - // find insertion point - if (active == NULL) - active = z; - else if (z->x < active->x) { - // insert at front - z->next = active; - active = z; - } else { - // find thing to insert AFTER - stbtt__active_edge* p = active; - while (p->next && p->next->x < z->x) - p = p->next; - // at this point, p->next->x is NOT < z->x - z->next = p->next; - p->next = z; - } - } - } - ++e; - } - - // now process all active edges in XOR fashion - if (active) - stbtt__fill_active_edges(scanline, result->w, active, max_weight); - - ++y; - } - STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} - -#elif STBTT_RASTERIZER_VERSION == 2 - -// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 -// (i.e. it has already been clipped to those) -static void stbtt__handle_clipped_edge(float* scanline, int x, stbtt__active_edge* e, float x0, float y0, float x1, - float y1) -{ - if (y0 == y1) - return; - STBTT_assert(y0 < y1); - STBTT_assert(e->sy <= e->ey); - if (y0 > e->ey) - return; - if (y1 < e->sy) - return; - if (y0 < e->sy) { - x0 += (x1 - x0) * (e->sy - y0) / (y1 - y0); - y0 = e->sy; - } - if (y1 > e->ey) { - x1 += (x1 - x0) * (e->ey - y1) / (y1 - y0); - y1 = e->ey; - } - - if (x0 == x) - STBTT_assert(x1 <= x + 1); - else if (x0 == x + 1) - STBTT_assert(x1 >= x); - else if (x0 <= x) - STBTT_assert(x1 <= x); - else if (x0 >= x + 1) - STBTT_assert(x1 >= x + 1); - else - STBTT_assert(x1 >= x && x1 <= x + 1); - - if (x0 <= x && x1 <= x) - scanline[x] += e->direction * (y1 - y0); - else if (x0 >= x + 1 && x1 >= x + 1) - ; - else { - STBTT_assert(x0 >= x && x0 <= x + 1 && x1 >= x && x1 <= x + 1); - scanline[x] += e->direction * (y1 - y0) * (1 - ((x0 - x) + (x1 - x)) / 2); // coverage = 1 - average x position - } -} - -static void stbtt__fill_active_edges_new(float* scanline, float* scanline_fill, int len, stbtt__active_edge* e, - float y_top) -{ - float y_bottom = y_top + 1; - - while (e) { - // brute force every pixel - - // compute intersection points with top & bottom - STBTT_assert(e->ey >= y_top); - - if (e->fdx == 0) { - float x0 = e->fx; - if (x0 < len) { - if (x0 >= 0) { - stbtt__handle_clipped_edge(scanline, (int)x0, e, x0, y_top, x0, y_bottom); - stbtt__handle_clipped_edge(scanline_fill - 1, (int)x0 + 1, e, x0, y_top, x0, y_bottom); - } else { - stbtt__handle_clipped_edge(scanline_fill - 1, 0, e, x0, y_top, x0, y_bottom); - } - } - } else { - float x0 = e->fx; - float dx = e->fdx; - float xb = x0 + dx; - float x_top, x_bottom; - float sy0, sy1; - float dy = e->fdy; - STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); - - // compute endpoints of line segment clipped to this scanline (if the - // line segment starts on this scanline. x0 is the intersection of the - // line with y_top, but that may be off the line segment. - if (e->sy > y_top) { - x_top = x0 + dx * (e->sy - y_top); - sy0 = e->sy; - } else { - x_top = x0; - sy0 = y_top; - } - if (e->ey < y_bottom) { - x_bottom = x0 + dx * (e->ey - y_top); - sy1 = e->ey; - } else { - x_bottom = xb; - sy1 = y_bottom; - } - - if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { - // from here on, we don't have to range check x values - - if ((int)x_top == (int)x_bottom) { - float height; - // simple case, only spans one pixel - int x = (int)x_top; - height = sy1 - sy0; - STBTT_assert(x >= 0 && x < len); - scanline[x] += e->direction * (1 - ((x_top - x) + (x_bottom - x)) / 2) * height; - scanline_fill[x] += e->direction * height; // everything right of this pixel is filled - } else { - int x, x1, x2; - float y_crossing, step, sign, area; - // covers 2+ pixels - if (x_top > x_bottom) { - // flip scanline vertically; signed area is the same - float t; - sy0 = y_bottom - (sy0 - y_top); - sy1 = y_bottom - (sy1 - y_top); - t = sy0, sy0 = sy1, sy1 = t; - t = x_bottom, x_bottom = x_top, x_top = t; - dx = -dx; - dy = -dy; - t = x0, x0 = xb, xb = t; - } - - x1 = (int)x_top; - x2 = (int)x_bottom; - // compute intersection with y axis at x1+1 - y_crossing = (x1 + 1 - x0) * dy + y_top; - - sign = e->direction; - // area of the rectangle covered from y0..y_crossing - area = sign * (y_crossing - sy0); - // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) - scanline[x1] += area * (1 - ((x_top - x1) + (x1 + 1 - x1)) / 2); - - step = sign * dy; - for (x = x1 + 1; x < x2; ++x) { - scanline[x] += area + step / 2; - area += step; - } - y_crossing += dy * (x2 - (x1 + 1)); - - STBTT_assert(STBTT_fabs(area) <= 1.01f); - - scanline[x2] += area + sign * (1 - ((x2 - x2) + (x_bottom - x2)) / 2) * (sy1 - y_crossing); - - scanline_fill[x2] += sign * (sy1 - sy0); - } - } else { - // if edge goes outside of box we're drawing, we require - // clipping logic. since this does not match the intended use - // of this library, we use a different, very slow brute - // force implementation - int x; - for (x = 0; x < len; ++x) { - // cases: - // - // there can be up to two intersections with the pixel. any intersection - // with left or right edges can be handled by splitting into two (or three) - // regions. intersections with top & bottom do not necessitate case-wise logic. - // - // the old way of doing this found the intersections with the left & right edges, - // then used some simple logic to produce up to three segments in sorted order - // from top-to-bottom. however, this had a problem: if an x edge was epsilon - // across the x border, then the corresponding y position might not be distinct - // from the other y segment, and it might ignored as an empty segment. to avoid - // that, we need to explicitly produce segments based on x positions. - - // rename variables to clearly-defined pairs - float y0 = y_top; - float x1 = (float)(x); - float x2 = (float)(x + 1); - float x3 = xb; - float y3 = y_bottom; - - // x = e->x + e->dx * (y-y_top) - // (y-y_top) = (x - e->x) / e->dx - // y = (x - e->x) / e->dx + y_top - float y1 = (x - x0) / dx + y_top; - float y2 = (x + 1 - x0) / dx + y_top; - - if (x0 < x1 && x3 > x2) { // three segments descending down-right - stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x1, y1); - stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x2, y2); - stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x3, y3); - } else if (x3 < x1 && x0 > x2) { // three segments descending down-left - stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x2, y2); - stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x1, y1); - stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x3, y3); - } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right - stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x1, y1); - stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x3, y3); - } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left - stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x1, y1); - stbtt__handle_clipped_edge(scanline, x, e, x1, y1, x3, y3); - } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right - stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x2, y2); - stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x3, y3); - } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left - stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x2, y2); - stbtt__handle_clipped_edge(scanline, x, e, x2, y2, x3, y3); - } else { // one segment - stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x3, y3); - } - } - } - } - e = e->next; - } -} - -// directly AA rasterize edges w/o supersampling -static void stbtt__rasterize_sorted_edges(stbtt__bitmap* result, stbtt__edge* e, int n, int vsubsample, int off_x, - int off_y, void* userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge* active = NULL; - int y, j = 0, i; - float scanline_data[129], *scanline, *scanline2; - - STBTT__NOTUSED(vsubsample); - - if (result->w > 64) - scanline = (float*)STBTT_malloc((result->w * 2 + 1) * sizeof(float), userdata); - else - scanline = scanline_data; - - scanline2 = scanline + result->w; - - y = off_y; - e[n].y0 = (float)(off_y + result->h) + 1; - - while (j < result->h) { - // find center of pixel for this scanline - float scan_y_top = y + 0.0f; - float scan_y_bottom = y + 1.0f; - stbtt__active_edge** step = &active; - - STBTT_memset(scanline, 0, result->w * sizeof(scanline[0])); - STBTT_memset(scanline2, 0, (result->w + 1) * sizeof(scanline[0])); - - // update all active edges; - // remove all active edges that terminate before the top of this scanline - while (*step) { - stbtt__active_edge* z = *step; - if (z->ey <= scan_y_top) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - step = &((*step)->next); // advance through list - } - } - - // insert all edges that start before the bottom of this scanline - while (e->y0 <= scan_y_bottom) { - if (e->y0 != e->y1) { - stbtt__active_edge* z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); - if (z != NULL) { - STBTT_assert(z->ey >= scan_y_top); - // insert at front - z->next = active; - active = z; - } - } - ++e; - } - - // now process all active edges - if (active) - stbtt__fill_active_edges_new(scanline, scanline2 + 1, result->w, active, scan_y_top); - - { - float sum = 0; - for (i = 0; i < result->w; ++i) { - float k; - int m; - sum += scanline2[i]; - k = scanline[i] + sum; - k = (float)STBTT_fabs(k) * 255 + 0.5f; - m = (int)k; - if (m > 255) - m = 255; - result->pixels[j * result->stride + i] = (unsigned char)m; - } - } - // advance all the edges - step = &active; - while (*step) { - stbtt__active_edge* z = *step; - z->fx += z->fdx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - - ++y; - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#define STBTT__COMPARE(a, b) ((a)->y0 < (b)->y0) - -static void stbtt__sort_edges_ins_sort(stbtt__edge* p, int n) -{ - int i, j; - for (i = 1; i < n; ++i) { - stbtt__edge t = p[i], *a = &t; - j = i; - while (j > 0) { - stbtt__edge* b = &p[j - 1]; - int c = STBTT__COMPARE(a, b); - if (!c) - break; - p[j] = p[j - 1]; - --j; - } - if (i != j) - p[j] = t; - } -} - -static void stbtt__sort_edges_quicksort(stbtt__edge* p, int n) -{ - /* threshhold for transitioning to insertion sort */ - while (n > 12) { - stbtt__edge t; - int c01, c12, c, m, i, j; - - /* compute median of three */ - m = n >> 1; - c01 = STBTT__COMPARE(&p[0], &p[m]); - c12 = STBTT__COMPARE(&p[m], &p[n - 1]); - /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ - if (c01 != c12) { - /* otherwise, we'll need to swap something else to middle */ - int z; - c = STBTT__COMPARE(&p[0], &p[n - 1]); - /* 0>mid && midn => n; 0 0 */ - /* 0n: 0>n => 0; 0 n */ - z = (c == c12) ? 0 : n - 1; - t = p[z]; - p[z] = p[m]; - p[m] = t; - } - /* now p[m] is the median-of-three */ - /* swap it to the beginning so it won't move around */ - t = p[0]; - p[0] = p[m]; - p[m] = t; - - /* partition loop */ - i = 1; - j = n - 1; - for (;;) { - /* handling of equality is crucial here */ - /* for sentinels & efficiency with duplicates */ - for (;; ++i) { - if (!STBTT__COMPARE(&p[i], &p[0])) - break; - } - for (;; --j) { - if (!STBTT__COMPARE(&p[0], &p[j])) - break; - } - /* make sure we haven't crossed */ - if (i >= j) - break; - t = p[i]; - p[i] = p[j]; - p[j] = t; - - ++i; - --j; - } - /* recurse on smaller side, iterate on larger */ - if (j < (n - i)) { - stbtt__sort_edges_quicksort(p, j); - p = p + i; - n = n - i; - } else { - stbtt__sort_edges_quicksort(p + i, n - i); - n = j; - } - } -} - -static void stbtt__sort_edges(stbtt__edge* p, int n) -{ - stbtt__sort_edges_quicksort(p, n); - stbtt__sort_edges_ins_sort(p, n); -} - -typedef struct { - float x, y; -} stbtt__point; - -static void stbtt__rasterize(stbtt__bitmap* result, stbtt__point* pts, int* wcount, int windings, float scale_x, - float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, - void* userdata) -{ - float y_scale_inv = invert ? -scale_y : scale_y; - stbtt__edge* e; - int n, i, j, k, m; -#if STBTT_RASTERIZER_VERSION == 1 - int vsubsample = result->h < 8 ? 15 : 5; -#elif STBTT_RASTERIZER_VERSION == 2 - int vsubsample = 1; -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - // vsubsample should divide 255 evenly; otherwise we won't reach full opacity - - // now we have to blow out the windings into explicit edge lists - n = 0; - for (i = 0; i < windings; ++i) - n += wcount[i]; - - e = (stbtt__edge*)STBTT_malloc(sizeof(*e) * (n + 1), userdata); // add an extra one as a sentinel - if (e == 0) - return; - n = 0; - - m = 0; - for (i = 0; i < windings; ++i) { - stbtt__point* p = pts + m; - m += wcount[i]; - j = wcount[i] - 1; - for (k = 0; k < wcount[i]; j = k++) { - int a = k, b = j; - // skip the edge if horizontal - if (p[j].y == p[k].y) - continue; - // add edge from j to k to the list - e[n].invert = 0; - if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { - e[n].invert = 1; - a = j, b = k; - } - e[n].x0 = p[a].x * scale_x + shift_x; - e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; - e[n].x1 = p[b].x * scale_x + shift_x; - e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; - ++n; - } - } - - // now sort the edges by their highest point (should snap to integer, and then by x) - // STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); - stbtt__sort_edges(e, n); - - // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule - stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); - - STBTT_free(e, userdata); -} - -static void stbtt__add_point(stbtt__point* points, int n, float x, float y) -{ - if (!points) - return; // during first pass, it's unallocated - points[n].x = x; - points[n].y = y; -} - -// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching -static int stbtt__tesselate_curve(stbtt__point* points, int* num_points, float x0, float y0, float x1, float y1, - float x2, float y2, float objspace_flatness_squared, int n) -{ - // midpoint - float mx = (x0 + 2 * x1 + x2) / 4; - float my = (y0 + 2 * y1 + y2) / 4; - // versus directly drawn line - float dx = (x0 + x2) / 2 - mx; - float dy = (y0 + y2) / 2 - my; - if (n > 16) // 65536 segments on one curve better be enough! - return 1; - if (dx * dx + dy * dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA - stbtt__tesselate_curve(points, num_points, x0, y0, (x0 + x1) / 2.0f, (y0 + y1) / 2.0f, mx, my, - objspace_flatness_squared, n + 1); - stbtt__tesselate_curve(points, num_points, mx, my, (x1 + x2) / 2.0f, (y1 + y2) / 2.0f, x2, y2, - objspace_flatness_squared, n + 1); - } else { - stbtt__add_point(points, *num_points, x2, y2); - *num_points = *num_points + 1; - } - return 1; -} - -static void stbtt__tesselate_cubic(stbtt__point* points, int* num_points, float x0, float y0, float x1, float y1, - float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) -{ - // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough - float dx0 = x1 - x0; - float dy0 = y1 - y0; - float dx1 = x2 - x1; - float dy1 = y2 - y1; - float dx2 = x3 - x2; - float dy2 = y3 - y2; - float dx = x3 - x0; - float dy = y3 - y0; - float longlen = - (float)(STBTT_sqrt(dx0 * dx0 + dy0 * dy0) + STBTT_sqrt(dx1 * dx1 + dy1 * dy1) + STBTT_sqrt(dx2 * dx2 + dy2 * dy2)); - float shortlen = (float)STBTT_sqrt(dx * dx + dy * dy); - float flatness_squared = longlen * longlen - shortlen * shortlen; - - if (n > 16) // 65536 segments on one curve better be enough! - return; - - if (flatness_squared > objspace_flatness_squared) { - float x01 = (x0 + x1) / 2; - float y01 = (y0 + y1) / 2; - float x12 = (x1 + x2) / 2; - float y12 = (y1 + y2) / 2; - float x23 = (x2 + x3) / 2; - float y23 = (y2 + y3) / 2; - - float xa = (x01 + x12) / 2; - float ya = (y01 + y12) / 2; - float xb = (x12 + x23) / 2; - float yb = (y12 + y23) / 2; - - float mx = (xa + xb) / 2; - float my = (ya + yb) / 2; - - stbtt__tesselate_cubic(points, num_points, x0, y0, x01, y01, xa, ya, mx, my, objspace_flatness_squared, n + 1); - stbtt__tesselate_cubic(points, num_points, mx, my, xb, yb, x23, y23, x3, y3, objspace_flatness_squared, n + 1); - } else { - stbtt__add_point(points, *num_points, x3, y3); - *num_points = *num_points + 1; - } -} - -// returns number of contours -static stbtt__point* stbtt_FlattenCurves(stbtt_vertex* vertices, int num_verts, float objspace_flatness, - int** contour_lengths, int* num_contours, void* userdata) -{ - stbtt__point* points = 0; - int num_points = 0; - - float objspace_flatness_squared = objspace_flatness * objspace_flatness; - int i, n = 0, start = 0, pass; - - // count how many "moves" there are to get the contour count - for (i = 0; i < num_verts; ++i) - if (vertices[i].type == STBTT_vmove) - ++n; - - *num_contours = n; - if (n == 0) - return 0; - - *contour_lengths = (int*)STBTT_malloc(sizeof(**contour_lengths) * n, userdata); - - if (*contour_lengths == 0) { - *num_contours = 0; - return 0; - } - - // make two passes through the points so we don't need to realloc - for (pass = 0; pass < 2; ++pass) { - float x = 0, y = 0; - if (pass == 1) { - points = (stbtt__point*)STBTT_malloc(num_points * sizeof(points[0]), userdata); - if (points == NULL) - goto error; - } - num_points = 0; - n = -1; - for (i = 0; i < num_verts; ++i) { - switch (vertices[i].type) { - case STBTT_vmove: - // start the next contour - if (n >= 0) - (*contour_lengths)[n] = num_points - start; - ++n; - start = num_points; - - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x, y); - break; - case STBTT_vline: - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x, y); - break; - case STBTT_vcurve: - stbtt__tesselate_curve(points, &num_points, x, y, vertices[i].cx, vertices[i].cy, vertices[i].x, - vertices[i].y, objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - case STBTT_vcubic: - stbtt__tesselate_cubic(points, &num_points, x, y, vertices[i].cx, vertices[i].cy, vertices[i].cx1, - vertices[i].cy1, vertices[i].x, vertices[i].y, objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - } - } - (*contour_lengths)[n] = num_points - start; - } - - return points; -error: - STBTT_free(points, userdata); - STBTT_free(*contour_lengths, userdata); - *contour_lengths = 0; - *num_contours = 0; - return NULL; -} - -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap* result, float flatness_in_pixels, stbtt_vertex* vertices, int num_verts, - float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, - int invert, void* userdata) -{ - float scale = scale_x > scale_y ? scale_y : scale_x; - int winding_count = 0; - int* winding_lengths = NULL; - stbtt__point* windings = - stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); - if (windings) { - stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, - invert, userdata); - STBTT_free(winding_lengths, userdata); - STBTT_free(windings, userdata); - } -} - -STBTT_DEF void stbtt_FreeBitmap(unsigned char* bitmap, void* userdata) { STBTT_free(bitmap, userdata); } - -STBTT_DEF unsigned char* stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo* info, float scale_x, float scale_y, - float shift_x, float shift_y, int glyph, int* width, int* height, - int* xoff, int* yoff) -{ - int ix0, iy0, ix1, iy1; - stbtt__bitmap gbm; - stbtt_vertex* vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - - if (scale_x == 0) - scale_x = scale_y; - if (scale_y == 0) { - if (scale_x == 0) { - STBTT_free(vertices, info->userdata); - return NULL; - } - scale_y = scale_x; - } - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0, &iy0, &ix1, &iy1); - - // now we get the size - gbm.w = (ix1 - ix0); - gbm.h = (iy1 - iy0); - gbm.pixels = NULL; // in case we error - - if (width) - *width = gbm.w; - if (height) - *height = gbm.h; - if (xoff) - *xoff = ix0; - if (yoff) - *yoff = iy0; - - if (gbm.w && gbm.h) { - gbm.pixels = (unsigned char*)STBTT_malloc(gbm.w * gbm.h, info->userdata); - if (gbm.pixels) { - gbm.stride = gbm.w; - - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, - info->userdata); - } - } - STBTT_free(vertices, info->userdata); - return gbm.pixels; -} - -STBTT_DEF unsigned char* stbtt_GetGlyphBitmap(const stbtt_fontinfo* info, float scale_x, float scale_y, int glyph, - int* width, int* height, int* xoff, int* yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo* info, unsigned char* output, int out_w, int out_h, - int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, - int glyph) -{ - int ix0, iy0; - stbtt_vertex* vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - stbtt__bitmap gbm; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0, &iy0, 0, 0); - gbm.pixels = output; - gbm.w = out_w; - gbm.h = out_h; - gbm.stride = out_stride; - - if (gbm.w && gbm.h) - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); - - STBTT_free(vertices, info->userdata); -} - -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo* info, unsigned char* output, int out_w, int out_h, - int out_stride, float scale_x, float scale_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f, 0.0f, glyph); -} - -STBTT_DEF unsigned char* stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo* info, float scale_x, float scale_y, - float shift_x, float shift_y, int codepoint, int* width, - int* height, int* xoff, int* yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info, codepoint), - width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo* info, unsigned char* output, int out_w, - int out_h, int out_stride, float scale_x, float scale_y, - float shift_x, float shift_y, int oversample_x, - int oversample_y, float* sub_x, float* sub_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, - oversample_x, oversample_y, sub_x, sub_y, - stbtt_FindGlyphIndex(info, codepoint)); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo* info, unsigned char* output, int out_w, - int out_h, int out_stride, float scale_x, float scale_y, float shift_x, - float shift_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, - stbtt_FindGlyphIndex(info, codepoint)); -} - -STBTT_DEF unsigned char* stbtt_GetCodepointBitmap(const stbtt_fontinfo* info, float scale_x, float scale_y, - int codepoint, int* width, int* height, int* xoff, int* yoff) -{ - return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, codepoint, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo* info, unsigned char* output, int out_w, int out_h, - int out_stride, float scale_x, float scale_y, int codepoint) -{ - stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f, 0.0f, codepoint); -} - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-CRAPPY packing to keep source code small - -static int stbtt_BakeFontBitmap_internal(unsigned char* data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char* pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar* chardata) -{ - float scale; - int x, y, bottom_y, i; - stbtt_fontinfo f; - f.userdata = NULL; - if (!stbtt_InitFont(&f, data, offset)) - return -1; - STBTT_memset(pixels, 0, pw * ph); // background of 0 around pixels - x = y = 1; - bottom_y = 1; - - scale = stbtt_ScaleForPixelHeight(&f, pixel_height); - - for (i = 0; i < num_chars; ++i) { - int advance, lsb, x0, y0, x1, y1, gw, gh; - int g = stbtt_FindGlyphIndex(&f, first_char + i); - stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); - stbtt_GetGlyphBitmapBox(&f, g, scale, scale, &x0, &y0, &x1, &y1); - gw = x1 - x0; - gh = y1 - y0; - if (x + gw + 1 >= pw) - y = bottom_y, x = 1; // advance to next row - if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row - return -i; - STBTT_assert(x + gw < pw); - STBTT_assert(y + gh < ph); - stbtt_MakeGlyphBitmap(&f, pixels + x + y * pw, gw, gh, pw, scale, scale, g); - chardata[i].x0 = (stbtt_int16)x; - chardata[i].y0 = (stbtt_int16)y; - chardata[i].x1 = (stbtt_int16)(x + gw); - chardata[i].y1 = (stbtt_int16)(y + gh); - chardata[i].xadvance = scale * advance; - chardata[i].xoff = (float)x0; - chardata[i].yoff = (float)y0; - x = x + gw + 1; - if (y + gh + 1 > bottom_y) - bottom_y = y + gh + 1; - } - return bottom_y; -} - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar* chardata, int pw, int ph, int char_index, float* xpos, - float* ypos, stbtt_aligned_quad* q, int opengl_fillrule) -{ - float d3d_bias = opengl_fillrule ? 0 : -0.5f; - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_bakedchar* b = chardata + char_index; - int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); - int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); - - q->x0 = round_x + d3d_bias; - q->y0 = round_y + d3d_bias; - q->x1 = round_x + b->x1 - b->x0 + d3d_bias; - q->y1 = round_y + b->y1 - b->y0 + d3d_bias; - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// rectangle packing replacement routines if you don't have stb_rect_pack.h -// - -#ifndef STB_RECT_PACK_VERSION - -typedef int stbrp_coord; - -//////////////////////////////////////////////////////////////////////////////////// -// // -// // -// COMPILER WARNING ?!?!? // -// // -// // -// if you get a compile warning due to these symbols being defined more than // -// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // -// // -//////////////////////////////////////////////////////////////////////////////////// - -typedef struct { - int width, height; - int x, y, bottom_y; -} stbrp_context; - -typedef struct { - unsigned char x; -} stbrp_node; - -struct stbrp_rect { - stbrp_coord x, y; - int id, w, h, was_packed; -}; - -static void stbrp_init_target(stbrp_context* con, int pw, int ph, stbrp_node* nodes, int num_nodes) -{ - con->width = pw; - con->height = ph; - con->x = 0; - con->y = 0; - con->bottom_y = 0; - STBTT__NOTUSED(nodes); - STBTT__NOTUSED(num_nodes); -} - -static void stbrp_pack_rects(stbrp_context* con, stbrp_rect* rects, int num_rects) -{ - int i; - for (i = 0; i < num_rects; ++i) { - if (con->x + rects[i].w > con->width) { - con->x = 0; - con->y = con->bottom_y; - } - if (con->y + rects[i].h > con->height) - break; - rects[i].x = con->x; - rects[i].y = con->y; - rects[i].was_packed = 1; - con->x += rects[i].w; - if (con->y + rects[i].h > con->bottom_y) - con->bottom_y = con->y + rects[i].h; - } - for (; i < num_rects; ++i) - rects[i].was_packed = 0; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If -// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context* spc, unsigned char* pixels, int pw, int ph, int stride_in_bytes, - int padding, void* alloc_context) -{ - stbrp_context* context = (stbrp_context*)STBTT_malloc(sizeof(*context), alloc_context); - int num_nodes = pw - padding; - stbrp_node* nodes = (stbrp_node*)STBTT_malloc(sizeof(*nodes) * num_nodes, alloc_context); - - if (context == NULL || nodes == NULL) { - if (context != NULL) - STBTT_free(context, alloc_context); - if (nodes != NULL) - STBTT_free(nodes, alloc_context); - return 0; - } - - spc->user_allocator_context = alloc_context; - spc->width = pw; - spc->height = ph; - spc->pixels = pixels; - spc->pack_info = context; - spc->nodes = nodes; - spc->padding = padding; - spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; - spc->h_oversample = 1; - spc->v_oversample = 1; - - stbrp_init_target(context, pw - padding, ph - padding, nodes, num_nodes); - - if (pixels) - STBTT_memset(pixels, 0, pw * ph); // background of 0 around pixels - - return 1; -} - -STBTT_DEF void stbtt_PackEnd(stbtt_pack_context* spc) -{ - STBTT_free(spc->nodes, spc->user_allocator_context); - STBTT_free(spc->pack_info, spc->user_allocator_context); -} - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context* spc, unsigned int h_oversample, unsigned int v_oversample) -{ - STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); - STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); - if (h_oversample <= STBTT_MAX_OVERSAMPLE) - spc->h_oversample = h_oversample; - if (v_oversample <= STBTT_MAX_OVERSAMPLE) - spc->v_oversample = v_oversample; -} - -#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE - 1) - -static void stbtt__h_prefilter(unsigned char* pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_w = w - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j = 0; j < h; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i = 0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char)(total / 2); - } - break; - case 3: - for (i = 0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char)(total / 3); - } - break; - case 4: - for (i = 0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char)(total / 4); - } - break; - case 5: - for (i = 0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char)(total / 5); - } - break; - default: - for (i = 0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char)(total / kernel_width); - } - break; - } - - for (; i < w; ++i) { - STBTT_assert(pixels[i] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i] = (unsigned char)(total / kernel_width); - } - - pixels += stride_in_bytes; - } -} - -static void stbtt__v_prefilter(unsigned char* pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_h = h - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j = 0; j < w; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i = 0; i <= safe_h; ++i) { - total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i * stride_in_bytes]; - pixels[i * stride_in_bytes] = (unsigned char)(total / 2); - } - break; - case 3: - for (i = 0; i <= safe_h; ++i) { - total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i * stride_in_bytes]; - pixels[i * stride_in_bytes] = (unsigned char)(total / 3); - } - break; - case 4: - for (i = 0; i <= safe_h; ++i) { - total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i * stride_in_bytes]; - pixels[i * stride_in_bytes] = (unsigned char)(total / 4); - } - break; - case 5: - for (i = 0; i <= safe_h; ++i) { - total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i * stride_in_bytes]; - pixels[i * stride_in_bytes] = (unsigned char)(total / 5); - } - break; - default: - for (i = 0; i <= safe_h; ++i) { - total += pixels[i * stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i + kernel_width) & STBTT__OVER_MASK] = pixels[i * stride_in_bytes]; - pixels[i * stride_in_bytes] = (unsigned char)(total / kernel_width); - } - break; - } - - for (; i < h; ++i) { - STBTT_assert(pixels[i * stride_in_bytes] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i * stride_in_bytes] = (unsigned char)(total / kernel_width); - } - - pixels += 1; - } -} - -static float stbtt__oversample_shift(int oversample) -{ - if (!oversample) - return 0.0f; - - // The prefilter is a box filter of width "oversample", - // which shifts phase by (oversample - 1)/2 pixels in - // oversampled space. We want to shift in the opposite - // direction to counter this. - return (float)-(oversample - 1) / (2.0f * (float)oversample); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context* spc, const stbtt_fontinfo* info, - stbtt_pack_range* ranges, int num_ranges, stbrp_rect* rects) -{ - int i, j, k; - - k = 0; - for (i = 0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - ranges[i].h_oversample = (unsigned char)spc->h_oversample; - ranges[i].v_oversample = (unsigned char)spc->v_oversample; - for (j = 0; j < ranges[i].num_chars; ++j) { - int x0, y0, x1, y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j - : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale * spc->h_oversample, scale * spc->v_oversample, 0, 0, &x0, &y0, - &x1, &y1); - rects[k].w = (stbrp_coord)(x1 - x0 + spc->padding + spc->h_oversample - 1); - rects[k].h = (stbrp_coord)(y1 - y0 + spc->padding + spc->v_oversample - 1); - ++k; - } - } - - return k; -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo* info, unsigned char* output, int out_w, - int out_h, int out_stride, float scale_x, float scale_y, - float shift_x, float shift_y, int prefilter_x, int prefilter_y, - float* sub_x, float* sub_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w - (prefilter_x - 1), out_h - (prefilter_y - 1), out_stride, scale_x, - scale_y, shift_x, shift_y, glyph); - - if (prefilter_x > 1) - stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); - - if (prefilter_y > 1) - stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); - - *sub_x = stbtt__oversample_shift(prefilter_x); - *sub_y = stbtt__oversample_shift(prefilter_y); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context* spc, const stbtt_fontinfo* info, - stbtt_pack_range* ranges, int num_ranges, stbrp_rect* rects) -{ - int i, j, k, return_value = 1; - - // save current values - int old_h_over = spc->h_oversample; - int old_v_over = spc->v_oversample; - - k = 0; - for (i = 0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - float recip_h, recip_v, sub_x, sub_y; - spc->h_oversample = ranges[i].h_oversample; - spc->v_oversample = ranges[i].v_oversample; - recip_h = 1.0f / spc->h_oversample; - recip_v = 1.0f / spc->v_oversample; - sub_x = stbtt__oversample_shift(spc->h_oversample); - sub_y = stbtt__oversample_shift(spc->v_oversample); - for (j = 0; j < ranges[i].num_chars; ++j) { - stbrp_rect* r = &rects[k]; - if (r->was_packed) { - stbtt_packedchar* bc = &ranges[i].chardata_for_range[j]; - int advance, lsb, x0, y0, x1, y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j - : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - stbrp_coord pad = (stbrp_coord)spc->padding; - - // pad on left and top - r->x += pad; - r->y += pad; - r->w -= pad; - r->h -= pad; - stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); - stbtt_GetGlyphBitmapBox(info, glyph, scale * spc->h_oversample, scale * spc->v_oversample, &x0, &y0, &x1, &y1); - stbtt_MakeGlyphBitmapSubpixel(info, spc->pixels + r->x + r->y * spc->stride_in_bytes, - r->w - spc->h_oversample + 1, r->h - spc->v_oversample + 1, spc->stride_in_bytes, - scale * spc->h_oversample, scale * spc->v_oversample, 0, 0, glyph); - - if (spc->h_oversample > 1) - stbtt__h_prefilter(spc->pixels + r->x + r->y * spc->stride_in_bytes, r->w, r->h, spc->stride_in_bytes, - spc->h_oversample); - - if (spc->v_oversample > 1) - stbtt__v_prefilter(spc->pixels + r->x + r->y * spc->stride_in_bytes, r->w, r->h, spc->stride_in_bytes, - spc->v_oversample); - - bc->x0 = (stbtt_int16)r->x; - bc->y0 = (stbtt_int16)r->y; - bc->x1 = (stbtt_int16)(r->x + r->w); - bc->y1 = (stbtt_int16)(r->y + r->h); - bc->xadvance = scale * advance; - bc->xoff = (float)x0 * recip_h + sub_x; - bc->yoff = (float)y0 * recip_v + sub_y; - bc->xoff2 = (x0 + r->w) * recip_h + sub_x; - bc->yoff2 = (y0 + r->h) * recip_v + sub_y; - } else { - return_value = 0; // if any fail, report failure - } - - ++k; - } - } - - // restore original values - spc->h_oversample = old_h_over; - spc->v_oversample = old_v_over; - - return return_value; -} - -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context* spc, stbrp_rect* rects, int num_rects) -{ - stbrp_pack_rects((stbrp_context*)spc->pack_info, rects, num_rects); -} - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context* spc, const unsigned char* fontdata, int font_index, - stbtt_pack_range* ranges, int num_ranges) -{ - stbtt_fontinfo info; - int i, j, n, return_value = 1; - // stbrp_context *context = (stbrp_context *) spc->pack_info; - stbrp_rect* rects; - - // flag all characters as NOT packed - for (i = 0; i < num_ranges; ++i) - for (j = 0; j < ranges[i].num_chars; ++j) - ranges[i].chardata_for_range[j].x0 = ranges[i].chardata_for_range[j].y0 = ranges[i].chardata_for_range[j].x1 = - ranges[i].chardata_for_range[j].y1 = 0; - - n = 0; - for (i = 0; i < num_ranges; ++i) - n += ranges[i].num_chars; - - rects = (stbrp_rect*)STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); - if (rects == NULL) - return 0; - - info.userdata = spc->user_allocator_context; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, font_index)); - - n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); - - stbtt_PackFontRangesPackRects(spc, rects, n); - - return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); - - STBTT_free(rects, spc->user_allocator_context); - return return_value; -} - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context* spc, const unsigned char* fontdata, int font_index, - float font_size, int first_unicode_codepoint_in_range, int num_chars_in_range, - stbtt_packedchar* chardata_for_range) -{ - stbtt_pack_range range; - range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; - range.array_of_unicode_codepoints = NULL; - range.num_chars = num_chars_in_range; - range.chardata_for_range = chardata_for_range; - range.font_size = font_size; - return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); -} - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar* chardata, int pw, int ph, int char_index, float* xpos, - float* ypos, stbtt_aligned_quad* q, int align_to_integer) -{ - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_packedchar* b = chardata + char_index; - - if (align_to_integer) { - float x = (float)STBTT_ifloor((*xpos + b->xoff) + 0.5f); - float y = (float)STBTT_ifloor((*ypos + b->yoff) + 0.5f); - q->x0 = x; - q->y0 = y; - q->x1 = x + b->xoff2 - b->xoff; - q->y1 = y + b->yoff2 - b->yoff; - } else { - q->x0 = *xpos + b->xoff; - q->y0 = *ypos + b->yoff; - q->x1 = *xpos + b->xoff2; - q->y1 = *ypos + b->yoff2; - } - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// sdf computation -// - -#define STBTT_min(a, b) ((a) < (b) ? (a) : (b)) -#define STBTT_max(a, b) ((a) < (b) ? (b) : (a)) - -static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], - float hits[2][2]) -{ - float q0perp = q0[1] * ray[0] - q0[0] * ray[1]; - float q1perp = q1[1] * ray[0] - q1[0] * ray[1]; - float q2perp = q2[1] * ray[0] - q2[0] * ray[1]; - float roperp = orig[1] * ray[0] - orig[0] * ray[1]; - - float a = q0perp - 2 * q1perp + q2perp; - float b = q1perp - q0perp; - float c = q0perp - roperp; - - float s0 = 0., s1 = 0.; - int num_s = 0; - - if (a != 0.0) { - float discr = b * b - a * c; - if (discr > 0.0) { - float rcpna = -1 / a; - float d = (float)STBTT_sqrt(discr); - s0 = (b + d) * rcpna; - s1 = (b - d) * rcpna; - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { - if (num_s == 0) - s0 = s1; - ++num_s; - } - } - } else { - // 2*b*s + c = 0 - // s = -c / (2*b) - s0 = c / (-2 * b); - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - } - - if (num_s == 0) - return 0; - else { - float rcp_len2 = 1 / (ray[0] * ray[0] + ray[1] * ray[1]); - float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; - - float q0d = q0[0] * rayn_x + q0[1] * rayn_y; - float q1d = q1[0] * rayn_x + q1[1] * rayn_y; - float q2d = q2[0] * rayn_x + q2[1] * rayn_y; - float rod = orig[0] * rayn_x + orig[1] * rayn_y; - - float q10d = q1d - q0d; - float q20d = q2d - q0d; - float q0rd = q0d - rod; - - hits[0][0] = q0rd + s0 * (2.0f - 2.0f * s0) * q10d + s0 * s0 * q20d; - hits[0][1] = a * s0 + b; - - if (num_s > 1) { - hits[1][0] = q0rd + s1 * (2.0f - 2.0f * s1) * q10d + s1 * s1 * q20d; - hits[1][1] = a * s1 + b; - return 2; - } else { - return 1; - } - } -} - -static int equal(float* a, float* b) { return (a[0] == b[0] && a[1] == b[1]); } - -static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex* verts) -{ - int i; - float orig[2], ray[2] = { 1, 0 }; - float y_frac; - int winding = 0; - - orig[0] = x; - orig[1] = y; - - // make sure y never passes through a vertex of the shape - y_frac = (float)STBTT_fmod(y, 1.0f); - if (y_frac < 0.01f) - y += 0.01f; - else if (y_frac > 0.99f) - y -= 0.01f; - orig[1] = y; - - // test a ray from (-infinity,y) to (x,y) - for (i = 0; i < nverts; ++i) { - if (verts[i].type == STBTT_vline) { - int x0 = (int)verts[i - 1].x, y0 = (int)verts[i - 1].y; - int x1 = (int)verts[i].x, y1 = (int)verts[i].y; - if (y > STBTT_min(y0, y1) && y < STBTT_max(y0, y1) && x > STBTT_min(x0, x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1 - x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } - if (verts[i].type == STBTT_vcurve) { - int x0 = (int)verts[i - 1].x, y0 = (int)verts[i - 1].y; - int x1 = (int)verts[i].cx, y1 = (int)verts[i].cy; - int x2 = (int)verts[i].x, y2 = (int)verts[i].y; - int ax = STBTT_min(x0, STBTT_min(x1, x2)), ay = STBTT_min(y0, STBTT_min(y1, y2)); - int by = STBTT_max(y0, STBTT_max(y1, y2)); - if (y > ay && y < by && x > ax) { - float q0[2], q1[2], q2[2]; - float hits[2][2]; - q0[0] = (float)x0; - q0[1] = (float)y0; - q1[0] = (float)x1; - q1[1] = (float)y1; - q2[0] = (float)x2; - q2[1] = (float)y2; - if (equal(q0, q1) || equal(q1, q2)) { - x0 = (int)verts[i - 1].x; - y0 = (int)verts[i - 1].y; - x1 = (int)verts[i].x; - y1 = (int)verts[i].y; - if (y > STBTT_min(y0, y1) && y < STBTT_max(y0, y1) && x > STBTT_min(x0, x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1 - x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } else { - int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); - if (num_hits >= 1) - if (hits[0][0] < 0) - winding += (hits[0][1] < 0 ? -1 : 1); - if (num_hits >= 2) - if (hits[1][0] < 0) - winding += (hits[1][1] < 0 ? -1 : 1); - } - } - } - } - return winding; -} - -static float stbtt__cuberoot(float x) -{ - if (x < 0) - return -(float)STBTT_pow(-x, 1.0f / 3.0f); - else - return (float)STBTT_pow(x, 1.0f / 3.0f); -} - -// x^3 + c*x^2 + b*x + a = 0 -static int stbtt__solve_cubic(float a, float b, float c, float* r) -{ - float s = -a / 3; - float p = b - a * a / 3; - float q = a * (2 * a * a - 9 * b) / 27 + c; - float p3 = p * p * p; - float d = q * q + 4 * p3 / 27; - if (d >= 0) { - float z = (float)STBTT_sqrt(d); - float u = (-q + z) / 2; - float v = (-q - z) / 2; - u = stbtt__cuberoot(u); - v = stbtt__cuberoot(v); - r[0] = s + u + v; - return 1; - } else { - float u = (float)STBTT_sqrt(-p / 3); - float v = (float)STBTT_acos(-STBTT_sqrt(-27 / p3) * q / 2) / 3; // p3 must be negative, since d is negative - float m = (float)STBTT_cos(v); - float n = (float)STBTT_cos(v - 3.141592 / 2) * 1.732050808f; - r[0] = s + u * 2 * m; - r[1] = s - u * (m + n); - r[2] = s - u * (m - n); - - // STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, - // though they're in bezier t parameter units so maybe? STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); - // STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); - return 3; - } -} - -STBTT_DEF unsigned char* stbtt_GetGlyphSDF(const stbtt_fontinfo* info, float scale, int glyph, int padding, - unsigned char onedge_value, float pixel_dist_scale, int* width, int* height, - int* xoff, int* yoff) -{ - float scale_x = scale, scale_y = scale; - int ix0, iy0, ix1, iy1; - int w, h; - unsigned char* data; - - // if one scale is 0, use same scale for both - if (scale_x == 0) - scale_x = scale_y; - if (scale_y == 0) { - if (scale_x == 0) - return NULL; // if both scales are 0, return NULL - scale_y = scale_x; - } - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f, 0.0f, &ix0, &iy0, &ix1, &iy1); - - // if empty, return NULL - if (ix0 == ix1 || iy0 == iy1) - return NULL; - - ix0 -= padding; - iy0 -= padding; - ix1 += padding; - iy1 += padding; - - w = (ix1 - ix0); - h = (iy1 - iy0); - - if (width) - *width = w; - if (height) - *height = h; - if (xoff) - *xoff = ix0; - if (yoff) - *yoff = iy0; - - // invert for y-downwards bitmaps - scale_y = -scale_y; - - { - int x, y, i, j; - float* precompute; - stbtt_vertex* verts; - int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); - data = (unsigned char*)STBTT_malloc(w * h, info->userdata); - precompute = (float*)STBTT_malloc(num_verts * sizeof(float), info->userdata); - - for (i = 0, j = num_verts - 1; i < num_verts; j = i++) { - if (verts[i].type == STBTT_vline) { - float x0 = verts[i].x * scale_x, y0 = verts[i].y * scale_y; - float x1 = verts[j].x * scale_x, y1 = verts[j].y * scale_y; - float dist = (float)STBTT_sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); - precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[j].x * scale_x, y2 = verts[j].y * scale_y; - float x1 = verts[i].cx * scale_x, y1 = verts[i].cy * scale_y; - float x0 = verts[i].x * scale_x, y0 = verts[i].y * scale_y; - float bx = x0 - 2 * x1 + x2, by = y0 - 2 * y1 + y2; - float len2 = bx * bx + by * by; - if (len2 != 0.0f) - precompute[i] = 1.0f / (bx * bx + by * by); - else - precompute[i] = 0.0f; - } else - precompute[i] = 0.0f; - } - - for (y = iy0; y < iy1; ++y) { - for (x = ix0; x < ix1; ++x) { - float val; - float min_dist = 999999.0f; - float sx = (float)x + 0.5f; - float sy = (float)y + 0.5f; - float x_gspace = (sx / scale_x); - float y_gspace = (sy / scale_y); - - int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, - verts); // @OPTIMIZE: this could just be a rasterization, but needs to - // be line vs. non-tesselated curves so a new path - - for (i = 0; i < num_verts; ++i) { - float x0 = verts[i].x * scale_x, y0 = verts[i].y * scale_y; - - // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' - // in a row produce a garbage point, and given culling, probably more efficient to do within line/curve - float dist2 = (x0 - sx) * (x0 - sx) + (y0 - sy) * (y0 - sy); - if (dist2 < min_dist * min_dist) - min_dist = (float)STBTT_sqrt(dist2); - - if (verts[i].type == STBTT_vline) { - float x1 = verts[i - 1].x * scale_x, y1 = verts[i - 1].y * scale_y; - - // coarse culling against bbox - // if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && - // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) - float dist = (float)STBTT_fabs((x1 - x0) * (y0 - sy) - (y1 - y0) * (x0 - sx)) * precompute[i]; - STBTT_assert(i != 0); - if (dist < min_dist) { - // check position along line - // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) - // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) - float dx = x1 - x0, dy = y1 - y0; - float px = x0 - sx, py = y0 - sy; - // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy - // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve - float t = -(px * dx + py * dy) / (dx * dx + dy * dy); - if (t >= 0.0f && t <= 1.0f) - min_dist = dist; - } - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[i - 1].x * scale_x, y2 = verts[i - 1].y * scale_y; - float x1 = verts[i].cx * scale_x, y1 = verts[i].cy * scale_y; - float box_x0 = STBTT_min(STBTT_min(x0, x1), x2); - float box_y0 = STBTT_min(STBTT_min(y0, y1), y2); - float box_x1 = STBTT_max(STBTT_max(x0, x1), x2); - float box_y1 = STBTT_max(STBTT_max(y0, y1), y2); - // coarse culling against bbox to avoid computing cubic unnecessarily - if (sx > box_x0 - min_dist && sx < box_x1 + min_dist && sy > box_y0 - min_dist && sy < box_y1 + min_dist) { - int num = 0; - float ax = x1 - x0, ay = y1 - y0; - float bx = x0 - 2 * x1 + x2, by = y0 - 2 * y1 + y2; - float mx = x0 - sx, my = y0 - sy; - float res[3], px, py, t, it; - float a_inv = precompute[i]; - if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula - float a = 3 * (ax * bx + ay * by); - float b = 2 * (ax * ax + ay * ay) + (mx * bx + my * by); - float c = mx * ax + my * ay; - if (a == 0.0) { // if a is 0, it's linear - if (b != 0.0) { - res[num++] = -c / b; - } - } else { - float discriminant = b * b - 4 * a * c; - if (discriminant < 0) - num = 0; - else { - float root = (float)STBTT_sqrt(discriminant); - res[0] = (-b - root) / (2 * a); - res[1] = (-b + root) / (2 * a); - num = 2; // don't bother distinguishing 1-solution case, as code below will still work - } - } - } else { - float b = 3 * (ax * bx + ay * by) * a_inv; // could precompute this as it doesn't depend on sample point - float c = (2 * (ax * ax + ay * ay) + (mx * bx + my * by)) * a_inv; - float d = (mx * ax + my * ay) * a_inv; - num = stbtt__solve_cubic(b, c, d, res); - } - if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { - t = res[0], it = 1.0f - t; - px = it * it * x0 + 2 * t * it * x1 + t * t * x2; - py = it * it * y0 + 2 * t * it * y1 + t * t * y2; - dist2 = (px - sx) * (px - sx) + (py - sy) * (py - sy); - if (dist2 < min_dist * min_dist) - min_dist = (float)STBTT_sqrt(dist2); - } - if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { - t = res[1], it = 1.0f - t; - px = it * it * x0 + 2 * t * it * x1 + t * t * x2; - py = it * it * y0 + 2 * t * it * y1 + t * t * y2; - dist2 = (px - sx) * (px - sx) + (py - sy) * (py - sy); - if (dist2 < min_dist * min_dist) - min_dist = (float)STBTT_sqrt(dist2); - } - if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { - t = res[2], it = 1.0f - t; - px = it * it * x0 + 2 * t * it * x1 + t * t * x2; - py = it * it * y0 + 2 * t * it * y1 + t * t * y2; - dist2 = (px - sx) * (px - sx) + (py - sy) * (py - sy); - if (dist2 < min_dist * min_dist) - min_dist = (float)STBTT_sqrt(dist2); - } - } - } - } - if (winding == 0) - min_dist = -min_dist; // if outside the shape, value is negative - val = onedge_value + pixel_dist_scale * min_dist; - if (val < 0) - val = 0; - else if (val > 255) - val = 255; - data[(y - iy0) * w + (x - ix0)] = (unsigned char)val; - } - } - STBTT_free(precompute, info->userdata); - STBTT_free(verts, info->userdata); - } - return data; -} - -STBTT_DEF unsigned char* stbtt_GetCodepointSDF(const stbtt_fontinfo* info, float scale, int codepoint, int padding, - unsigned char onedge_value, float pixel_dist_scale, int* width, - int* height, int* xoff, int* yoff) -{ - return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, - width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_FreeSDF(unsigned char* bitmap, void* userdata) { STBTT_free(bitmap, userdata); } - -////////////////////////////////////////////////////////////////////////////// -// -// font name matching -- recommended not to use this -// - -// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string -static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8* s1, stbtt_int32 len1, stbtt_uint8* s2, - stbtt_int32 len2) -{ - stbtt_int32 i = 0; - - // convert utf16 to utf8 and compare the results while converting - while (len2) { - stbtt_uint16 ch = s2[0] * 256 + s2[1]; - if (ch < 0x80) { - if (i >= len1) - return -1; - if (s1[i++] != ch) - return -1; - } else if (ch < 0x800) { - if (i + 1 >= len1) - return -1; - if (s1[i++] != 0xc0 + (ch >> 6)) - return -1; - if (s1[i++] != 0x80 + (ch & 0x3f)) - return -1; - } else if (ch >= 0xd800 && ch < 0xdc00) { - stbtt_uint32 c; - stbtt_uint16 ch2 = s2[2] * 256 + s2[3]; - if (i + 3 >= len1) - return -1; - c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; - if (s1[i++] != 0xf0 + (c >> 18)) - return -1; - if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) - return -1; - if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) - return -1; - if (s1[i++] != 0x80 + ((c)&0x3f)) - return -1; - s2 += 2; // plus another 2 below - len2 -= 2; - } else if (ch >= 0xdc00 && ch < 0xe000) { - return -1; - } else { - if (i + 2 >= len1) - return -1; - if (s1[i++] != 0xe0 + (ch >> 12)) - return -1; - if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) - return -1; - if (s1[i++] != 0x80 + ((ch)&0x3f)) - return -1; - } - s2 += 2; - len2 -= 2; - } - return i; -} - -static int stbtt_CompareUTF8toUTF16_bigendian_internal(char* s1, int len1, char* s2, int len2) -{ - return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*)s1, len1, (stbtt_uint8*)s2, len2); -} - -// returns results in whatever encoding you request... but note that 2-byte encodings -// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare -STBTT_DEF const char* stbtt_GetFontNameString(const stbtt_fontinfo* font, int* length, int platformID, int encodingID, - int languageID, int nameID) -{ - stbtt_int32 i, count, stringOffset; - stbtt_uint8* fc = font->data; - stbtt_uint32 offset = font->fontstart; - stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); - if (!nm) - return NULL; - - count = ttUSHORT(fc + nm + 2); - stringOffset = nm + ttUSHORT(fc + nm + 4); - for (i = 0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - if (platformID == ttUSHORT(fc + loc + 0) && encodingID == ttUSHORT(fc + loc + 2) && - languageID == ttUSHORT(fc + loc + 4) && nameID == ttUSHORT(fc + loc + 6)) { - *length = ttUSHORT(fc + loc + 8); - return (const char*)(fc + stringOffset + ttUSHORT(fc + loc + 10)); - } - } - return NULL; -} - -static int stbtt__matchpair(stbtt_uint8* fc, stbtt_uint32 nm, stbtt_uint8* name, stbtt_int32 nlen, - stbtt_int32 target_id, stbtt_int32 next_id) -{ - stbtt_int32 i; - stbtt_int32 count = ttUSHORT(fc + nm + 2); - stbtt_int32 stringOffset = nm + ttUSHORT(fc + nm + 4); - - for (i = 0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - stbtt_int32 id = ttUSHORT(fc + loc + 6); - if (id == target_id) { - // find the encoding - stbtt_int32 platform = ttUSHORT(fc + loc + 0), encoding = ttUSHORT(fc + loc + 2), - language = ttUSHORT(fc + loc + 4); - - // is this a Unicode encoding? - if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { - stbtt_int32 slen = ttUSHORT(fc + loc + 8); - stbtt_int32 off = ttUSHORT(fc + loc + 10); - - // check if there's a prefix match - stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc + stringOffset + off, slen); - if (matchlen >= 0) { - // check for target_id+1 immediately following, with same encoding & language - if (i + 1 < count && ttUSHORT(fc + loc + 12 + 6) == next_id && ttUSHORT(fc + loc + 12) == platform && - ttUSHORT(fc + loc + 12 + 2) == encoding && ttUSHORT(fc + loc + 12 + 4) == language) { - slen = ttUSHORT(fc + loc + 12 + 8); - off = ttUSHORT(fc + loc + 12 + 10); - if (slen == 0) { - if (matchlen == nlen) - return 1; - } else if (matchlen < nlen && name[matchlen] == ' ') { - ++matchlen; - if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*)(name + matchlen), nlen - matchlen, - (char*)(fc + stringOffset + off), slen)) - return 1; - } - } else { - // if nothing immediately following - if (matchlen == nlen) - return 1; - } - } - } - - // @TODO handle other encodings - } - } - return 0; -} - -static int stbtt__matches(stbtt_uint8* fc, stbtt_uint32 offset, stbtt_uint8* name, stbtt_int32 flags) -{ - stbtt_int32 nlen = (stbtt_int32)STBTT_strlen((char*)name); - stbtt_uint32 nm, hd; - if (!stbtt__isfont(fc + offset)) - return 0; - - // check italics/bold/underline flags in macStyle... - if (flags) { - hd = stbtt__find_table(fc, offset, "head"); - if ((ttUSHORT(fc + hd + 44) & 7) != (flags & 7)) - return 0; - } - - nm = stbtt__find_table(fc, offset, "name"); - if (!nm) - return 0; - - if (flags) { - // if we checked the macStyle flags, then just check the family and ignore the subfamily - if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) - return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) - return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) - return 1; - } else { - if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) - return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) - return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) - return 1; - } - - return 0; -} - -static int stbtt_FindMatchingFont_internal(unsigned char* font_collection, char* name_utf8, stbtt_int32 flags) -{ - stbtt_int32 i; - for (i = 0;; ++i) { - stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); - if (off < 0) - return off; - if (stbtt__matches((stbtt_uint8*)font_collection, off, (stbtt_uint8*)name_utf8, flags)) - return off; - } -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char* data, int offset, float pixel_height, unsigned char* pixels, - int pw, int ph, int first_char, int num_chars, stbtt_bakedchar* chardata) -{ - return stbtt_BakeFontBitmap_internal((unsigned char*)data, offset, pixel_height, pixels, pw, ph, first_char, - num_chars, chardata); -} - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char* data, int index) -{ - return stbtt_GetFontOffsetForIndex_internal((unsigned char*)data, index); -} - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char* data) -{ - return stbtt_GetNumberOfFonts_internal((unsigned char*)data); -} - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo* info, const unsigned char* data, int offset) -{ - return stbtt_InitFont_internal(info, (unsigned char*)data, offset); -} - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char* fontdata, const char* name, int flags) -{ - return stbtt_FindMatchingFont_internal((unsigned char*)fontdata, (char*)name, flags); -} - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char* s1, int len1, const char* s2, int len2) -{ - return stbtt_CompareUTF8toUTF16_bigendian_internal((char*)s1, len1, (char*)s2, len2); -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop -#endif - -#endif // STB_TRUETYPE_IMPLEMENTATION - -// FULL VERSION HISTORY -// -// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) allow user-defined fabs() replacement -// fix memory leak if fontsize=0.0 -// fix warning from duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// allow PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) -// also more precise AA rasterizer, except if shapes overlap -// remove need for STBTT_sort -// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC -// 1.04 (2015-04-15) typo in example -// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes -// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ -// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match -// non-oversampled; STBTT_POINT_SIZE for packed case only -// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling -// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) -// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID -// 0.8b (2014-07-07) fix a warning -// 0.8 (2014-05-25) fix a few more warnings -// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back -// 0.6c (2012-07-24) improve documentation -// 0.6b (2012-07-20) fix a few more warnings -// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, -// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty -// 0.5 (2011-12-09) bugfixes: -// subpixel glyph renderer computed wrong bounding box -// first vertex of shape can be off-curve (FreeSans) -// 0.4b (2011-12-03) fixed an error in the font baking example -// 0.4 (2011-12-01) kerning, subpixel rendering (tor) -// bugfixes for: -// codepoint-to-glyph conversion using table fmt=12 -// codepoint-to-glyph conversion using table fmt=4 -// stbtt_GetBakedQuad with non-square texture (Zer) -// updated Hello World! sample to use kerning and subpixel -// fixed some warnings -// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) -// userdata, malloc-from-userdata, non-zero fill (stb) -// 0.2 (2009-03-11) Fix unsigned/signed char warnings -// 0.1 (2009-03-09) First public release -// - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/Framework/src/runAdvanced.cxx b/Framework/src/runAdvanced.cxx index 43f07ff3d2..4e6769cf48 100644 --- a/Framework/src/runAdvanced.cxx +++ b/Framework/src/runAdvanced.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -11,49 +12,48 @@ /// /// \file runAdvanced.cxx /// \author Piotr Konopka +/// \author Barthelemy von Haller /// /// \brief This is an executable showing a more complicated QC topology. /// -/// This is an executable showing a more complicated QC topology. It pretends to spawn 4 separate topologies - 3 of them -/// consist of some dummy processing chain, a dispatcher and a local QC task. The last one represents the remote -/// QC servers topology, which has a merger (joining the results from local QC tasks), a checker (checks the result of -/// the previous) and a different, remote QC task with associated checker. Here they are joined into one big topology -/// just to present the concept. +/// This is an executable showing a more complicated QC topology. It spawns 3 separate dummy processing chains, +/// a Dispatcher, two QC Tasks which require different data and CheckRunners which run Checks on MonitorObjects +/// produced by these QC Tasks. /// \image html qcRunAdvanced.png /// /// To launch it, build the project, load the environment and run the executable: /// \code{.sh} /// > aliBuild build QualityControl --defaults o2 /// > alienv enter QualityControl/latest -/// > qcRunAdvanced +/// > o2-qc-run-advanced /// \endcode /// If you have glfw installed, you should see a window with the workflow visualization and sub-windows for each Data /// Processor where their logs can be seen. The processing will continue until the main window it is closed. Regardless /// of glfw being installed or not, in the terminal all the logs will be shown as well. +/// +/// In case you want to run only the processing part, use the option `--no-qc`. +/// In such case, the workflow can be piped to the QC or another workflow: +/// \code{.sh} +/// > o2-qc-run-advanced --no-qc | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/advanced.json +/// \endcode #include -#include -#include +#include +#include "QualityControl/InfrastructureGenerator.h" +using namespace o2; using namespace o2::framework; +using namespace o2::utilities; // Additional configuration of the topology, which is done by implementing `customize` functions and placing them // before `runDataProcessing.h` header. In this case, both Dispatcher and Merger are configured to accept incoming -// messages without waiting for the rest of inputs. +// messages without waiting for the rest of inputs. The `customize` functions have to be above +// `#include "Framework/runDataProcessing.h"` - that header checks if these functions are defined by user and if so, it +// invokes them. It uses a trick with SFINAE expressions to do that. void customize(std::vector& policies) { DataSampling::CustomizeInfrastructure(policies); - - CompletionPolicy mergerConsumesASAP{ - "mergers-always-consume", - [](DeviceSpec const& device) { - return device.name.find("merger") != std::string::npos; - }, - [](gsl::span const& inputs) { - return CompletionPolicy::CompletionOp::Consume; - } - }; - policies.push_back(mergerConsumesASAP); + quality_control::customizeInfrastructure(policies); } void customize(std::vector& policies) @@ -61,94 +61,56 @@ void customize(std::vector& policies) DataSampling::CustomizeInfrastructure(policies); } -#include "QualityControl/InfrastructureGenerator.h" +void customize(std::vector& workflowOptions) +{ + workflowOptions.push_back( + ConfigParamSpec{ "no-qc", VariantType::Bool, false, { "Disable the QC part of this advanced workflow." } }); + workflowOptions.push_back( + ConfigParamSpec{ "no-debug-output", VariantType::Bool, false, { "Disable the Debug output." } }); +} + #include -#include +#include +#include +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/AdvancedWorkflow.h" using namespace o2; using namespace o2::header; +using namespace o2::quality_control::core; using SubSpecificationType = o2::header::DataHeader::SubSpecificationType; +using namespace o2::configuration; -// clang-format off -WorkflowSpec processingTopology(SubSpecificationType subspec) +WorkflowSpec defineDataProcessing(ConfigContext const& config) { - DataProcessorSpec source{ - "source-" + std::to_string(subspec), - Inputs{}, - Outputs{{ "TST", "DATA", subspec }, - { "TST", "PARAM", subspec }}, - AlgorithmSpec{ - (AlgorithmSpec::ProcessCallback) - [generator = std::default_random_engine{ static_cast(time(nullptr)) }, subspec](ProcessingContext & ctx) mutable { - usleep(200000); - auto data = ctx.outputs().make(Output{ "TST", "DATA", subspec }, generator() % 10000); - for (auto&& item : data) { - item = static_cast(generator()); - } - ctx.outputs().make(Output{ "TST", "PARAM", subspec }, 1)[0] = 1 / static_cast(1 + generator()); - } - } - }; - - DataProcessorSpec step{ - "step-" + std::to_string(subspec), - Inputs{{ "data", "TST", "DATA", subspec }}, - Outputs{{ "TST", "SUM", subspec }}, - AlgorithmSpec{ - (AlgorithmSpec::ProcessCallback)[subspec](ProcessingContext & ctx) { - const auto* header = get(ctx.inputs().get("data").header); - auto data = DataRefUtils::as(ctx.inputs().get("data")); - long long sum = 0; - for (auto d : data) { sum += d; } - ctx.outputs().snapshot(Output{ "TST", "SUM", subspec }, sum); - } - } - }; + QcInfoLogger::setFacility("runAdvanced"); - DataProcessorSpec sink{ - "sink-" + std::to_string(subspec), - Inputs{{ "sum", "TST", "SUM", subspec }, - { "param", "TST", "PARAM", subspec }}, - Outputs{}, - AlgorithmSpec{ - (AlgorithmSpec::ProcessCallback)[](ProcessingContext & ctx) { - LOG(INFO) << "Sum is: " << DataRefUtils::as(ctx.inputs().get("sum"))[0]; - LOG(INFO) << "Param is: " << DataRefUtils::as(ctx.inputs().get("param"))[0]; - } - } - }; - - return { source, step, sink }; -} -// clang-format on - -WorkflowSpec defineDataProcessing(ConfigContext const&) -{ + bool noQC = config.options().get("no-qc"); + bool noDebug = config.options().get("no-debug-output"); const std::string qcConfigurationSource = std::string("json://") + getenv("QUALITYCONTROL_ROOT") + "/etc/advanced.json"; - LOG(INFO) << "Using config file '" << qcConfigurationSource << "'"; - - WorkflowSpec specs; - // here we pretend to spawn topologies on three processing machines - for (int i = 1; i < 4; i++) { - auto localTopology = processingTopology(i); - - DataSampling::GenerateInfrastructure(localTopology, qcConfigurationSource); - // a fix to make the topologies work when merged together - localTopology.back().name += std::to_string(i); - - std::string host = "o2flptst" + std::to_string(i); - quality_control::generateLocalInfrastructure(localTopology, qcConfigurationSource, host); - // a fix to make the topologies work when merged together - localTopology.back().name += std::to_string(i); - // temporary fix, which shouldn't be necessary when data sampling uses matchers - DataSpecUtils::updateMatchingSubspec(localTopology.back().inputs[0], i); - - specs.insert(std::end(specs), std::begin(localTopology), std::end(localTopology)); + ILOG(Info, Support) << "Using config file '" << qcConfigurationSource << "'"; + + // we set the infologger levels as soon as possible to avoid spamming + auto configTree = ConfigurationFactory::getConfiguration(qcConfigurationSource)->getRecursive(); + auto infologgerFilterDiscardDebug = configTree.get("qc.config.infologger.filterDiscardDebug", true); + auto infologgerDiscardLevel = configTree.get("qc.config.infologger.filterDiscardLevel", 21); + auto infologgerDiscardFile = configTree.get("qc.config.infologger.filterDiscardFile", ""); + ILOG_INST.filterDiscardDebug(infologgerFilterDiscardDebug); + ILOG_INST.filterDiscardLevel(infologgerDiscardLevel); + ILOG_INST.filterDiscardSetFile(infologgerDiscardFile.c_str(), 0, 0, 0, true /*Do not store Debug messages in file*/); + QcInfoLogger::setFacility("runAdvanced"); + + // Full processing topology. + // We pretend to spawn topologies on three processing machines + WorkflowSpec specs = getFullProcessingTopology(); + + if (!noQC) { + auto configInterface = ConfigurationFactory::getConfiguration(qcConfigurationSource); + auto dataSamplingTree = configInterface->getRecursive("dataSamplingPolicies"); + DataSampling::GenerateInfrastructure(specs, dataSamplingTree); + // Generation of the remote QC topology (for the QC servers) + quality_control::generateStandaloneInfrastructure(specs, configInterface->getRecursive()); } - - // Generation of the remote QC topology (for the QC servers) - quality_control::generateRemoteInfrastructure(specs, qcConfigurationSource); - return specs; -} \ No newline at end of file +} diff --git a/Framework/src/runBasic.cxx b/Framework/src/runBasic.cxx index a2520da24e..c194d4c9fe 100644 --- a/Framework/src/runBasic.cxx +++ b/Framework/src/runBasic.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -16,29 +17,41 @@ /// /// This is an executable showing QC Task's usage in Data Processing Layer. The workflow consists of data producer, /// which generates arrays of random size and content. Its output is dispatched to QC task using Data Sampling -/// infrastructure. QC Task runs exemplary user code located in SkeletonDPL. The checker performes a simple check of +/// infrastructure. QC Task runs exemplary user code located in SkeletonDPL. The checker performs a simple check of /// the histogram shape and colorizes it. The resulting histogram contents are shown in logs by printer. /// -/// QC task and Checker are instantiated by respectively TaskFactory and CheckerFactory, +/// QC task and CheckRunner are instantiated by respectively TaskFactory and CheckRunnerFactory, /// which use preinstalled config file, that can be found in -/// ${QUALITYCONTROL_ROOT}/etc/qcTaskDplConfig.json or Framework/qcTaskDplConfig.json (original one). +/// ${QUALITYCONTROL_ROOT}/etc/basic.json or Framework/basic.json (original one). /// /// To launch it, build the project, load the environment and run the executable: /// \code{.sh} /// > aliBuild build QualityControl --defaults o2 /// > alienv enter QualityControl/latest -/// > runTaskDPL +/// > o2-qc-run-basic /// \endcode /// If you have glfw installed, you should see a window with the workflow visualization and sub-windows for each Data /// Processor where their logs can be seen. The processing will continue until the main window it is closed. Regardless /// of glfw being installed or not, in the terminal all the logs will be shown as well. -#include "Framework/DataSampling.h" +#include +#include "QualityControl/InfrastructureGenerator.h" +#include "QualityControl/UserInputOutput.h" +#include "Common/Exceptions.h" + +using namespace o2; using namespace o2::framework; +using namespace o2::utilities; +using namespace AliceO2::Common; + +// The customize() functions are used to declare the executable arguments and to specify custom completion and channel +// configuration policies. They have to be above `#include "Framework/runDataProcessing.h"` - that header checks if +// these functions are defined by user and if so, it invokes them. It uses a trick with SFINAE expressions to do that. void customize(std::vector& policies) { DataSampling::CustomizeInfrastructure(policies); + quality_control::customizeInfrastructure(policies); } void customize(std::vector& policies) @@ -48,76 +61,104 @@ void customize(std::vector& policies) void customize(std::vector& workflowOptions) { + workflowOptions.push_back( + ConfigParamSpec{ "config-path", VariantType::String, "", { "Absolute path to the config file. Overwrite the default paths. Do not use with no-data-sampling." } }); workflowOptions.push_back( ConfigParamSpec{ "no-data-sampling", VariantType::Bool, false, { "Skips data sampling, connects directly the task to the producer." } }); } -#include -#include -#include -#include +#include -#include "Framework/runDataProcessing.h" +#include +#include +#include -#include "QualityControl/Checker.h" +#include "QualityControl/CheckRunner.h" #include "QualityControl/InfrastructureGenerator.h" -#include "runnerUtils.h" -#include "ExamplePrinterSpec.h" +#include "QualityControl/runnerUtils.h" +#include "QualityControl/ExamplePrinterSpec.h" +#include "QualityControl/DataProducer.h" +#include "QualityControl/TaskRunner.h" + +std::string getConfigPath(const ConfigContext& config); using namespace o2; using namespace o2::framework; +using namespace o2::configuration; using namespace o2::quality_control::checker; using namespace std::chrono; WorkflowSpec defineDataProcessing(const ConfigContext& config) { WorkflowSpec specs; - bool noDS = config.options().get("no-data-sampling"); + std::string qcConfigurationSource = getConfigPath(config); - // The producer to generate some data in the workflow - DataProcessorSpec producer{ - "producer", - Inputs{}, - Outputs{ - { "ITS", "RAWDATA", 0, Lifetime::Timeframe } - }, - AlgorithmSpec{ - (AlgorithmSpec::InitCallback) [](InitContext&) { - std::default_random_engine generator(11); - return (AlgorithmSpec::ProcessCallback) [generator](ProcessingContext& processingContext) mutable { - usleep(100000); - size_t length = generator() % 10000; - auto data = processingContext.outputs().make(Output{ "ITS", "RAWDATA", 0, Lifetime::Timeframe }, - length); - for (auto&& item : data) { - item = static_cast(generator()); - } - }; - } - } - }; + auto configTree = ConfigurationFactory::getConfiguration(qcConfigurationSource)->getRecursive(); + auto infologgerFilterDiscardDebug = configTree.get("qc.config.infologger.filterDiscardDebug", false); + auto infologgerDiscardLevel = configTree.get("qc.config.infologger.filterDiscardLevel", 21); + auto infologgerDiscardFile = configTree.get("qc.config.infologger.filterDiscardFile", ""); + ILOG_INST.filterDiscardDebug(infologgerFilterDiscardDebug); + ILOG_INST.filterDiscardLevel(infologgerDiscardLevel); + ILOG_INST.filterDiscardSetFile(infologgerDiscardFile.c_str(), 0, 0, 0, true /*Do not store Debug messages in file*/); + QcInfoLogger::setFacility("runBasic"); + // The producer to generate some data in the workflow + DataProcessorSpec producer = getDataProducerSpec(1, 10000, 10); specs.push_back(producer); - std::string filename = !noDS ? "basic.json" : "basic-no-sampling.json"; - const std::string qcConfigurationSource = std::string("json://") + getenv("QUALITYCONTROL_ROOT") + "/etc/" + filename; - LOG(INFO) << "Using config file '" << qcConfigurationSource << "'"; + // Path to the config file + ILOG(Info, Support) << "Using config file '" << qcConfigurationSource << "'" << ENDM; // Generation of Data Sampling infrastructure - DataSampling::GenerateInfrastructure(specs, qcConfigurationSource); + auto configInterface = ConfigurationFactory::getConfiguration(qcConfigurationSource); + auto dataSamplingTree = configInterface->getRecursive("dataSamplingPolicies"); + DataSampling::GenerateInfrastructure(specs, dataSamplingTree); // Generation of the QC topology (one task, one checker in this case) - quality_control::generateRemoteInfrastructure(specs, qcConfigurationSource); + quality_control::generateStandaloneInfrastructure(specs, configInterface->getRecursive()); // Finally the printer - DataProcessorSpec printer{ - "printer", - Inputs{ - { "checked-mo", "QC", Checker::createCheckerDataDescription(getFirstTaskName(qcConfigurationSource)), 0 } }, - Outputs{}, - adaptFromTask() - }; - specs.push_back(printer); + if (hasChecks(qcConfigurationSource)) { + DataProcessorSpec printer{ + .name = "printer", + .inputs = Inputs{ + o2::quality_control::core::createUserInputSpec(o2::quality_control::core::DataSourceType::Check, "TST", + getFirstCheckName(qcConfigurationSource), 0, "checked-mo") }, + .algorithm = adaptFromTask(), + .labels = { { "resilient" } } + }; + specs.push_back(printer); + } else { + DataProcessorSpec printer{ + .name = "printer", + .inputs = Inputs{ + o2::quality_control::core::createUserInputSpec(o2::quality_control::core::DataSourceType::Task, "TST", + getFirstTaskName(qcConfigurationSource), 0, "checked-mo") }, + .algorithm = adaptFromTask(), + .labels = { { "resilient" } } + + }; + specs.push_back(printer); + } return specs; } + +// TODO merge this with the one from runReadout.cxx +std::string getConfigPath(const ConfigContext& config) +{ + // Determine the default config file path and name (based on option no-data-sampling and the QC_ROOT path) + bool noDS = config.options().get("no-data-sampling"); + std::string filename = !noDS ? "basic.json" : "basic-no-sampling.json"; + char* qcPath = getenv("QUALITYCONTROL_ROOT"); + // if the var is not set, we just bail because it is most probably not reasonable to guess. + if (qcPath == nullptr) { + BOOST_THROW_EXCEPTION(FatalException() << errinfo_details("Env var QUALITYCONTROL_ROOT not set. We cannot continue.")); + } + std::string defaultConfigPath = std::string(getenv("QUALITYCONTROL_ROOT")) + "/etc/" + filename; + // The optional one by the user + auto userConfigPath = config.options().get("config-path"); + // Finally build the config path based on the default or the user-base one + std::string path = std::string("json://") + (userConfigPath.empty() ? defaultConfigPath : userConfigPath); + return path; +} diff --git a/Framework/src/runBookkeepingBenchmark.cxx b/Framework/src/runBookkeepingBenchmark.cxx new file mode 100644 index 0000000000..a89f547dab --- /dev/null +++ b/Framework/src/runBookkeepingBenchmark.cxx @@ -0,0 +1,87 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "QualityControl/Bookkeeping.h" +#include +#include +#include "BookkeepingApi/BkpClientFactory.h" +#include "QualityControl/Activity.h" +#include +#include "QualityControl/QcInfoLogger.h" + +using namespace std; +namespace bpo = boost::program_options; +using namespace o2::bkp::api; +using namespace o2::quality_control::core; + +/** + * A small utility to stress test the bookkeeping api. + */ + +int main(int argc, const char* argv[]) +{ + bpo::options_description desc{ "Options" }; + desc.add_options()("help,h", "Help screen")("url,u", bpo::value()->required(), "URL to the Bookkeeping")("run,r", bpo::value())("max,m", bpo::value()->default_value(10000), "Max number of executions, default: 10000")("printCycles,p", bpo::value()->default_value(1000), "We print every X cycles, default: 1000")("printActivity", bpo::value()->default_value(false), "just to check that we get something in the activity.")("delay,d", bpo::value()->default_value(0), "Minimum delay between calls in ms, default 0"); + + bpo::variables_map vm; + store(parse_command_line(argc, argv, desc), vm); + + if (vm.count("help")) { + std::cout << desc << std::endl; + return 0; + } + notify(vm); + + const auto url = vm["url"].as(); + cout << "url : " << url << endl; + const auto run = vm["run"].as(); + cout << "run : " << run << endl; + const auto max = vm["max"].as(); + cout << "max : " << max << endl; + const auto printCycles = vm["printCycles"].as(); + cout << "printCycles : " << printCycles << endl; + const auto printActivity = vm["printActivity"].as(); + cout << "printActivity : " << printActivity << endl; + const auto minDelay = vm["delay"].as(); + cout << "minDelay : " << minDelay << endl; + + ILOG_INST.filterDiscardDebug(true); + ILOG_INST.filterDiscardLevel(11); + + Bookkeeping::getInstance().init(url); + + AliceO2::Common::Timer timer; + AliceO2::Common::Timer triggerTimer; + triggerTimer.reset(); + double totalDuration = 0; + double cycleDuration = 0; + int numberOfExecutionsInCycle = 0; + int totalNumberOfExecutions = 0; + + while (totalNumberOfExecutions < max) { + if (triggerTimer.isTimeout()) { + numberOfExecutionsInCycle++; + totalNumberOfExecutions++; + triggerTimer.reset(minDelay * 1000); + timer.reset(); + Bookkeeping::getInstance().registerProcess(123, "asdf", "ITS", o2::bkp::DplProcessType::_NULL, ""); + auto duration = timer.getTime(); + totalDuration += duration; + cycleDuration += duration; + if (totalNumberOfExecutions % printCycles == 0) { + cout << "average duration last " << printCycles << " calls in [ms]: " << cycleDuration / numberOfExecutionsInCycle * 1000 << endl; + numberOfExecutionsInCycle = 0; + cycleDuration = 0; + } + } + } + cout << "average duration overall in ms : " << totalDuration / totalNumberOfExecutions * 1000 << endl; +} \ No newline at end of file diff --git a/Framework/src/runDataDump.cxx b/Framework/src/runDataDump.cxx index d1812628ce..3fd59471d7 100644 --- a/Framework/src/runDataDump.cxx +++ b/Framework/src/runDataDump.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2022 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -15,10 +16,10 @@ /// #include "QualityControl/DataDumpGui.h" -#include "runFairMQDevice.h" +#include namespace bpo = boost::program_options; -void addCustomOptions(bpo::options_description& options) {} +void addCustomOptions(bpo::options_description& /*options*/) {} -FairMQDevicePtr getDevice(const FairMQProgOptions& /*config*/) { return new o2::quality_control::core::DataDumpGui(); } +std::unique_ptr getDevice(fair::mq::ProgOptions&) { return std::make_unqiue(); } diff --git a/Framework/src/runDataProducer.cxx b/Framework/src/runDataProducer.cxx new file mode 100644 index 0000000000..aaa4f297fb --- /dev/null +++ b/Framework/src/runDataProducer.cxx @@ -0,0 +1,76 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runDataProducer.cxx +/// \author Piotr Konopka +/// +/// \brief This is an executable with a basic data producer in Data Processing Layer. +/// +/// This is an executable with a basic random data producer in Data Processing Layer. It does not serve a real purpose +/// on its own, but it can be used as a data source for QC development. For example, one can do: +/// \code{.sh} +/// o2-qc-run-producer | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/basic.json +/// \endcode +/// Check out the help message to see how to configure data rate and message size. +/// +/// If you have glfw installed, you should see a window with the workflow visualization and sub-windows for each Data +/// Processor where their logs can be seen. The processing will continue until the main window it is closed. Regardless +/// of glfw being installed or not, in the terminal all the logs will be shown as well. + +#include +#include + +using namespace o2; +using namespace o2::framework; + +void customize(std::vector& workflowOptions) +{ + workflowOptions.push_back( + ConfigParamSpec{ "min-size", VariantType::Int, 1, { "Minimum size in bytes of produced messages." } }); + workflowOptions.push_back( + ConfigParamSpec{ "max-size", VariantType::Int, 10000, { "Maximum size in bytes of produced messages." } }); + workflowOptions.push_back( + ConfigParamSpec{ "empty", VariantType::Bool, false, { "Don't fill messages with random data." } }); + workflowOptions.push_back( + ConfigParamSpec{ "message-rate", VariantType::Double, 10.0, { "Rate of messages per second." } }); + workflowOptions.push_back( + ConfigParamSpec{ "message-amount", VariantType::Int, 0, { "Amount of messages to be produced in total (0 for inf)." } }); + workflowOptions.push_back( + ConfigParamSpec{ "producers", VariantType::Int, 1, { "Number of producers. Each will have unique SubSpec, counting from 0." } }); + workflowOptions.push_back( + ConfigParamSpec{ "timepipeline", VariantType::Int, 1, { "Timepipeline parameter, i.e. how many copies of each producer. See the DPL documentation for explanation." } }); + workflowOptions.push_back( + ConfigParamSpec{ "monitoring-url", VariantType::String, "", { "URL of the Monitoring backend." } }); +} + +#include +#include "QualityControl/DataProducer.h" + +using namespace o2::quality_control::core; + +WorkflowSpec defineDataProcessing(const ConfigContext& config) +{ + size_t minSize = config.options().get("min-size"); + size_t maxSize = config.options().get("max-size"); + bool fill = !config.options().get("empty"); + double rate = config.options().get("message-rate"); + uint64_t amount = config.options().get("message-amount"); + size_t producers = config.options().get("producers"); + size_t timepipeline = config.options().get("timepipeline"); + std::string monitoringUrl = config.options().get("monitoring-url"); + + WorkflowSpec specs; + for (size_t i = 0; i < producers; i++) { + specs.push_back(getDataProducerSpec(minSize, maxSize, rate, amount, i, monitoringUrl, fill, timepipeline)); + } + return specs; +} \ No newline at end of file diff --git a/Framework/src/runDataProducerExample.cxx b/Framework/src/runDataProducerExample.cxx new file mode 100644 index 0000000000..af9f9752b8 --- /dev/null +++ b/Framework/src/runDataProducerExample.cxx @@ -0,0 +1,46 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runDataProducerExample.cxx +/// \author Barthelemy von Haller +/// +/// \brief This is just an example of very basic data producer in Data Processing Layer. +/// It produces a fixed number on TST/RAWDATA/0 +/// + +#include +#include + +using namespace o2; +using namespace o2::framework; + +// We add custom arguments to the executable by implementing a customize function. +void customize(std::vector& workflowOptions) +{ + workflowOptions.push_back( + ConfigParamSpec{ "my-param", VariantType::Int, 1, { "Example parameter." } }); +} + +#include +#include "QualityControl/DataProducerExample.h" + +using namespace o2::quality_control::core; + +// Here we define all Data Processors which should be run by DPL (only the data producer in this case). +WorkflowSpec defineDataProcessing(const ConfigContext& config) +{ + size_t myParam = config.options().get("my-param"); + + WorkflowSpec specs; + specs.push_back(getDataProducerExampleSpec(myParam)); + return specs; +} diff --git a/Framework/src/runFileMerger.cxx b/Framework/src/runFileMerger.cxx new file mode 100644 index 0000000000..4a366887ad --- /dev/null +++ b/Framework/src/runFileMerger.cxx @@ -0,0 +1,280 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runFileMerger.cxx +/// \author Piotr Konopka +/// +/// \brief This is an executable which reads MonitorObjectCollections from files and creates a file with the merged result. + +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObjectCollection.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace bpo = boost::program_options; +using namespace o2::quality_control::core; + +template +struct overloaded : Ts... { + using Ts::operator()...; +}; +template +overloaded(Ts...) -> overloaded; + +// TODO: this structures and Nodes in RootFileStorage could be merged as a refactoring effort +struct Node { + std::string pathTo{}; + std::string name{}; + std::map> children = {}; + + std::string getFullPath() const { return pathTo + std::filesystem::path::preferred_separator + name; } +}; + +int main(int argc, const char* argv[]) +{ + size_t filesRead = 0; + try { + bpo::options_description desc{ "Options" }; + desc.add_options() // + ("help,h", "Help message") // + ("enable-alien", bpo::bool_switch()->default_value(false), "Connect to alien before accessing input files.") // + ("exit-on-error", bpo::bool_switch()->default_value(false), "Makes the executable exit if any of the input files could not be read.") // + ("output-file", bpo::value()->default_value("merged.root"), "File path to store the merged results, if the file exists, it will be merged with new files.") // + ("input-files-list", bpo::value()->default_value(""), "Path to a file containing a list of input files (row by row)") // + ("input-files", bpo::value>()->multitoken(), "Space-separated file paths which should be merged.") // + ("exclude-directories", bpo::value>()->multitoken(), "Space-separated directories which should be excluded when merging files."); + + bpo::variables_map vm; + store(bpo::command_line_parser(argc, argv).options(desc).run(), vm); + notify(vm); + + QcInfoLogger::setFacility("runFileMerger"); + + if (vm.count("help")) { + ILOG(Info, Support) << desc << ENDM; + return 0; + } else if (vm.count("input-files") > 0 && !vm["input-files-list"].as().empty()) { + ILOG(Error, Support) << "One should use either --input-files-list or --input-files, but not both." << ENDM; + return 1; + } else if (vm.count("input-files") == 0 && vm["input-files-list"].as().empty()) { + ILOG(Error, Support) << "No input files were provided. Use either --input-files-list or --input-files." << ENDM; + return 1; + } + + if (vm.count("help")) { + // no infologger here, because the message is too long. + std::cout << desc << std::endl; + return 0; + } + + std::vector inputFilePaths; + if (vm.count("input-files") > 0) { + inputFilePaths = vm["input-files"].as>(); + } else if (auto inputFileList = vm["input-files-list"].as(); !inputFileList.empty()) { + std::ifstream file(inputFileList); + if (!file.is_open()) { + ILOG(Error, Support) << "Could not open the file with input list: " << inputFileList << ENDM; + return 1; + } + for (std::string line; std::getline(file, line);) { + inputFilePaths.push_back(line); + } + } + + if (vm["enable-alien"].as()) { + ILOG(Info, Support) << "Connecting to alien" << ENDM; + TGrid::Connect("alien:"); + } + + auto excludedDirectories = vm.count("exclude-directories") > 0 ? vm["exclude-directories"].as>() : std::vector(); + if (!excludedDirectories.empty()) { + ILOG(Info, Support) << "Will skip the following directories inside input files:"; + for (const auto& dir : excludedDirectories) { + ILOG(Info, Support) << " " << dir; + } + ILOG(Info, Support) << ENDM; + } + + auto handleError = vm["exit-on-error"].as() + ? [](const std::string& message) { throw std::runtime_error(message); } + : [](const std::string& message) { ILOG(Error, Support) << message << ENDM; }; + + auto outputFilePath = vm["output-file"].as(); + auto outputFile = new TFile(outputFilePath.c_str(), "UPDATE"); + if (outputFile->IsZombie()) { + throw std::runtime_error("File '" + outputFilePath + "' is zombie."); + } + if (!outputFile->IsOpen()) { + throw std::runtime_error("Failed to open the file: " + outputFilePath); + } + if (!outputFile->IsWritable()) { + throw std::runtime_error("File '" + outputFilePath + "' is not writable."); + } + ILOG(Debug) << "Output file '" << outputFilePath << "' successfully open." << ENDM; + + // Unlike in RootFileSink and RootFileSource, where we assume that the latter only supports the output of the first, + // here we have more relaxed assumptions and try to recursively merge everything, regardless of the directory structure. + // This is because we might have to change the structure again when we support moving windows, so we might save some work + // in the future. + // We choose to keep the merged file structure in memory and merge everything we can before storing in a file. + // If this becomes too memory-hungry, it could be rewritten to store anything merge immediately at the cost of + // more I/O operations. + Node mergedTree; + for (const auto& inputFilePath : inputFilePaths) { + auto* file = TFile::Open(inputFilePath.c_str(), "READ"); + if (file == nullptr) { + handleError("File handler for '" + inputFilePath + "' is nullptr."); + continue; + } + if (file->IsZombie()) { + handleError("File '" + inputFilePath + "' is zombie."); + continue; + } + if (!file->IsOpen()) { + handleError("Failed to open the file: " + inputFilePath); + continue; + } + ILOG(Debug) << "Input file '" << inputFilePath << "' successfully open." << ENDM; + + std::function&)> mergeRecursively; + mergeRecursively = [&](TDirectory* fileNode, Node& memoryNode, const std::vector& excludedDirectories = {}) { + if (fileNode == nullptr) { + ILOG(Error) << "Provided parentNode pointer is null, skipping." << ENDM; + return; + } + TIter next(fileNode->GetListOfKeys()); + TKey* key; + while ((key = (TKey*)next())) { + // we look for exact matches here. we skip if there are no subdirectories + if (std::find(excludedDirectories.begin(), excludedDirectories.end(), key->GetName()) != excludedDirectories.end()) { + ILOG(Info, Support) << "Skipping '" << key->GetName() << "' as requested in the input arguments" << ENDM; + continue; + } + // we check if we have to skip any subdirectories wrt where we are + std::vector excludedSubdirectories; + for (const auto& excludedDirectory : excludedDirectories) { + auto match = std::string(key->GetName()) + '/'; + if (excludedDirectory.find(match) == 0) { + if (excludedDirectory.size() < match.size()) { + ILOG(Warning, Support) << "Invalid exclusion path '" << excludedDirectory << "'" << ENDM; + continue; + } + excludedSubdirectories.push_back(excludedDirectory.substr(match.size())); + } + } + + ILOG(Debug, Devel) << "Getting the value for key '" << key->GetName() << "'" << ENDM; + auto* value = fileNode->Get(key->GetName()); + if (value == nullptr) { + ILOG(Error) << "Could not get the value '" << key->GetName() << "', skipping." << ENDM; + continue; + } + if (auto inputMOC = dynamic_cast(value)) { + inputMOC->postDeserialization(); + + if (memoryNode.children.count(inputMOC->GetName()) == 0) { + std::string mocPath = memoryNode.getFullPath() + std::filesystem::path::preferred_separator + key->GetName(); + + auto mergedTObj = outputFile->Get(mocPath.c_str()); + if (mergedTObj != nullptr) { + auto mergedMOC = dynamic_cast(mergedTObj); + if (mergedMOC == nullptr) { + handleError("Could not cast the merged object to MonitorObjectCollection, skipping."); + delete mergedMOC; + continue; + } + mergedMOC->postDeserialization(); + memoryNode.children[mergedMOC->GetName()] = mergedMOC; + ILOG(Info) << "Read merged object '" << mergedMOC->GetName() << "'" << ENDM; + } + } + + if (memoryNode.children.count(inputMOC->GetName())) { + try { + std::get(memoryNode.children[inputMOC->GetName()])->merge(inputMOC); + } catch (...) { + handleError("Failed to merge the Monitor Object Collection. Exception caught: " + boost::current_exception_diagnostic_information(true)); + } + delete inputMOC; + } else { + memoryNode.children[inputMOC->GetName()] = inputMOC; + } + } else if (auto dir = dynamic_cast(value)) { + auto name = dir->GetName(); + if (memoryNode.children.count(name) == 0) { + memoryNode.children[name] = Node{ memoryNode.getFullPath(), name }; + } + mergeRecursively(dir, std::get(memoryNode.children[name]), excludedSubdirectories); + } else { + handleError("Could not cast the node to MonitorObjectCollection nor TDirectory."); + delete value; + continue; + } + } + }; + + mergeRecursively(file, mergedTree, excludedDirectories); + file->Close(); + delete file; + filesRead++; + } + + std::function storeRecursively; + storeRecursively = [&](TDirectory* fout, const Node& memoryNode) { + for (const auto& [name, value] : memoryNode.children) { + std::visit(overloaded{ + [&](const Node& node) { + auto* dir = fout->GetDirectory(node.name.c_str()); + if (dir == nullptr) { + fout->mkdir(node.name.c_str()); + } + dir = fout->GetDirectory(node.name.c_str()); + if (dir == nullptr) { + handleError("Could not create directory '" + node.name + "' in path '" + node.pathTo + "'"); + } else { + storeRecursively(dir, node); + } + }, + [&](const MonitorObjectCollection* moc) { + fout->WriteObject(moc, moc->GetName(), "Overwrite"); + delete moc; + } }, + value); + } + }; + storeRecursively(outputFile, mergedTree); + outputFile->Close(); + + } catch (const bpo::error& ex) { + ILOG(Error, Ops) << "Exception caught: " << ex.what() << ENDM; + return 1; + } catch (const boost::exception& ex) { + ILOG(Error, Ops) << "Exception caught: " << boost::current_exception_diagnostic_information(true) << ENDM; + return 1; + } + + if (filesRead > 0) { + ILOG(Info, Support) << "Successfully merged " << filesRead << " files into one." << ENDM; + } else { + ILOG(Info, Support) << "No files were merged." << ENDM; + } + return 0; +} \ No newline at end of file diff --git a/Framework/src/runHistoProducer.cxx b/Framework/src/runHistoProducer.cxx new file mode 100644 index 0000000000..d8770ce846 --- /dev/null +++ b/Framework/src/runHistoProducer.cxx @@ -0,0 +1,79 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runHistoProducer.cxx +/// \author Barthélémy von Haller +/// +/// \brief This is an executable with a basic histogram producer in the Data Processing Layer. +/// +/// This is an executable with a histogram producer in the Data Processing Layer. It does not serve a real purpose +/// on its own, but it can be used as an external data (TObjArray of histograms) source for QC development. For example, one can do: +/// \code{.sh} +/// o2-qc-run-histo-producer | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/basic-external-histo.json +/// \endcode +/// Histograms have 100 bins between -3 and 3 and filled+published randomly (incremental) every 2 seconds. +/// They are encapsulated in a TObjArray and named `histo_`. +/// In case, there is no encapsulation, the histogram is named `histo`. +/// +/// The option `producers` specifies how many producers to spawn. +/// The option `histograms` specifies how many histograms to publish in each producer. +/// The option `no-tobjarray` is only valid if histograms=1 and will prevent the producer from embedding the histogram in a TObjArray. +/// The option `printer` adds a printer attached to the first producer. +/// + +#include +#include +#include + +using namespace o2; +using namespace o2::framework; + +void customize(std::vector& workflowOptions) +{ + workflowOptions.push_back( + ConfigParamSpec{ "producers", VariantType::Int, 1, { "Number of histograms producers. Each will have unique SubSpec, counting from 0." } }); + workflowOptions.push_back( + ConfigParamSpec{ "printer", VariantType::Bool, false, { "Add a printer to output the histograms content." } }); + workflowOptions.push_back( + ConfigParamSpec{ "histograms", VariantType::Int, 1, { "Number of histograms each producer should produce." } }); + workflowOptions.push_back( + ConfigParamSpec{ "no-tobjarray", VariantType::Bool, false, { "In case option `histograms=1` do not embed the histogram in a TObjArray." } }); +} + +#include +#include "QualityControl/HistoProducer.h" + +using namespace o2::quality_control::core; + +WorkflowSpec defineDataProcessing(const ConfigContext& config) +{ + size_t histoProducers = config.options().get("producers"); + size_t histograms = config.options().get("histograms"); + bool printer = config.options().get("printer"); + bool noTobjArray = config.options().get("no-tobjarray"); + + if (noTobjArray && histograms > 1) { + std::cerr << "Option no-tobjarray is only valid if histograms=1." << std::endl; + exit(1); + } + + WorkflowSpec specs; + for (size_t i = 0; i < histoProducers; i++) { + specs.push_back(getHistoProducerSpec(i, histograms, noTobjArray)); + } + + if (printer) { + specs.push_back(getHistoPrinterSpec(0)); + } + + return specs; +} \ No newline at end of file diff --git a/Framework/src/runInformationService.cxx b/Framework/src/runInformationService.cxx deleted file mode 100644 index 9f77fc776c..0000000000 --- a/Framework/src/runInformationService.cxx +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -// - -/// -/// \author Barthelemy von Haller -/// \file runInformationService.cxx -/// - -#include "InformationService.h" -#include "runFairMQDevice.h" - -namespace bpo = boost::program_options; - -void addCustomOptions(bpo::options_description& options) -{ - options.add_options()( - "fake-data-file", bpo::value()->default_value(""), - "File containing JSON to use as input (useful for tests if no tasks is running). It is used to reply to requests. " - "It is reloaded every 10 seconds and if it changed it is published to the clients."); -} - -FairMQDevicePtr getDevice(const FairMQProgOptions& /*config*/) -{ - InformationService* is = new InformationService(); - - return is; -} diff --git a/Framework/src/runInformationServiceDump.cxx b/Framework/src/runInformationServiceDump.cxx deleted file mode 100644 index 4ead2b3ff7..0000000000 --- a/Framework/src/runInformationServiceDump.cxx +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -// - -/// -/// \author Barthelemy von Haller -/// \file runInformationServiceDump.cxx -/// - -#include "InformationServiceDump.h" -#include "runFairMQDevice.h" - -namespace bpo = boost::program_options; - -void addCustomOptions(bpo::options_description& options) -{ - options.add_options()("request-task", bpo::value()->default_value("all"), - "The name of the task it will request (default: all)"); -} - -FairMQDevicePtr getDevice(const FairMQProgOptions& /*config*/) { return new InformationServiceDump(); } diff --git a/Framework/src/runMergerTest.cxx b/Framework/src/runMergerTest.cxx deleted file mode 100644 index 256423ecfd..0000000000 --- a/Framework/src/runMergerTest.cxx +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -/// -/// \file runMergerTest.cxx -/// \author Piotr Konopka -/// -/// \brief This is DPL workflow to see HistoMerger in action - -#include -#include -#include -#include -#include -#include -using namespace o2::framework; - -void customize(std::vector& policies) -{ - CompletionPolicy mergerConsumesASAP = - CompletionPolicyHelpers::defineByName("merger", CompletionPolicy::CompletionOp::Consume); - policies.push_back(mergerConsumesASAP); -} - -#include "Framework/runDataProcessing.h" - -#include "QualityControl/Checker.h" -#include "QualityControl/CheckerFactory.h" -#include "QualityControl/HistoMerger.h" -#include "QualityControl/TaskRunnerFactory.h" - -using namespace o2::quality_control::core; -using namespace o2::quality_control::checker; -using namespace std::chrono; - -WorkflowSpec defineDataProcessing(ConfigContext const&) -{ - WorkflowSpec specs; - - size_t producersAmount = 10; - for(size_t p = 0; p < producersAmount; p++) { - DataProcessorSpec producer{ - "producer" + std::to_string(p), - Inputs{}, - Outputs{ - { {"mo"}, "TST", "HISTO", p + 1, Lifetime::Timeframe } - }, - AlgorithmSpec{ - (AlgorithmSpec::ProcessCallback) [p, producersAmount](ProcessingContext& processingContext) mutable { - - usleep(100000); - - TH1F* histo = new TH1F("gauss", "gauss", producersAmount, 0, 1); - histo->Fill(p/(double)producersAmount); - - MonitorObject* mo = new MonitorObject(histo, "histo-task"); - mo->setIsOwner(true); - - TObjArray* array = new TObjArray; - array->SetOwner(true); - array->Add(mo); - - processingContext.outputs().adopt(Output{"TST", "HISTO", p + 1}, array); - } - } - }; - specs.push_back(producer); - } - - HistoMerger merger("merger", 1); - merger.configureInputsOutputs("TST", "HISTO", { 1, producersAmount }); - DataProcessorSpec mergerSpec{ - merger.getName(), - merger.getInputSpecs(), - Outputs{merger.getOutputSpec()}, - adaptFromTask(std::move(merger)), - }; - specs.push_back(mergerSpec); - - - DataProcessorSpec printer{ - "printer", - Inputs{ - { "moarray", "TST", "HISTO", 0 } - }, - Outputs{}, - AlgorithmSpec{ - (AlgorithmSpec::InitCallback) [](InitContext&) { - return (AlgorithmSpec::ProcessCallback) [](ProcessingContext& processingContext) mutable { - LOG(INFO) << "printer invoked"; - auto moArray = processingContext.inputs().get("moarray"); - auto mo = dynamic_cast(moArray->First()); - - if (mo->getName() == "gauss") { - auto* g = dynamic_cast(mo->getObject()); - std::string bins = "BINS:"; - for (int i = 0; i <= g->GetNbinsX(); i++) { - bins += " " + std::to_string((int) g->GetBinContent(i)); - } - LOG(INFO) << bins; - } - }; - } - } - }; - specs.push_back(printer); - - return specs; -} diff --git a/Framework/src/runMetadataUpdater.cxx b/Framework/src/runMetadataUpdater.cxx new file mode 100644 index 0000000000..60d66add90 --- /dev/null +++ b/Framework/src/runMetadataUpdater.cxx @@ -0,0 +1,94 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runMetadataUpdater.cxx +/// \author Barthelemy von Haller +/// +/// \brief Easily update the metadata of an object in the QCDB or add new metadata if it does not exist yet. +/// +/// Example: o2-qc-metadata-updater --url ccdb-test.cern.ch:8080 --path Test/pid61065/Test --pair something,else --id 8b9728fe-486b-11ec-afda-2001171b226b --pair key1,value1 +/// +/// Note: commas can be escaped if they must be part of the key: "my,key" --> "my\\,key". Note that it needs double escaping. +/// commas don't have to escaped in the value. + +#include +#include +#include +#include +#include +#include +#include + +namespace bpo = boost::program_options; +using namespace std; + +int main(int argc, const char* argv[]) +{ + try { + bpo::options_description desc{ "Options" }; + desc.add_options()("help,h", "Help screen")("url,u", bpo::value()->required(), "URL to the QCDB")("path,p", bpo::value()->required(), "Path to the object to update")("timestamp,t", bpo::value()->default_value(o2::ccdb::getCurrentTimestamp()), "Timestamp to select the object")("id", bpo::value()->default_value(""), "Id of the object to select")("pair", bpo::value>()->required(), "Key-value pair to update the metadata (e.g. --pair \"1,oil\", can be added multiple times)"); + + bpo::variables_map vm; + store(parse_command_line(argc, argv, desc), vm); + + if (vm.count("help")) { + std::cout << desc << std::endl; + return 0; + } + notify(vm); + + const auto url = vm["url"].as(); + const auto path = vm["path"].as(); + const auto timestamp = vm["timestamp"].as(); + const auto id = vm["id"].as(); + const auto pairs = vm["pair"].as>(); + + // prepare the key value map, take into account escaped commas + map metadata; + for (auto p : pairs) { + size_t hit = -1; // on purpose ... don't worry + do { // make sure we ignore the escaped commas + hit = p.find(',', hit + 1); + } while (hit != string::npos && hit > 0 && p.at(hit - 1) == '\\'); + if (hit == string::npos) { + continue; + } + metadata[p.substr(0, hit)] = p.substr(hit + 1); + } + if (metadata.empty()) { + cout << "No proper pairs found, aborting." << endl; + return -1; + } + + std::cout << "PARAMETERS" << std::endl; + std::cout << "url................" << url << std::endl; + std::cout << "path,.............." << path << std::endl; + std::cout << "timestamp.........." << timestamp << std::endl; + std::cout << "id................." << id << std::endl; + std::cout << "pairs" << std::endl; + for (auto m : metadata) { + std::cout << " |........" << m.first << " -> " << m.second << endl; + } + std::cout << std::endl; + + o2::ccdb::CcdbApi api; + api.init(url); + api.updateMetadata(path, metadata, timestamp, id); + + return 0; + } catch (const bpo::error& ex) { + std::cerr << "Exception caught: " << ex.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/Framework/src/runPostProcessing.cxx b/Framework/src/runPostProcessing.cxx new file mode 100644 index 0000000000..05521ae147 --- /dev/null +++ b/Framework/src/runPostProcessing.cxx @@ -0,0 +1,110 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runPostProcessing.cxx +/// \author Piotr Konopka +/// +/// \brief This is a standalone executable to run postprocessing + +#include "QualityControl/PostProcessingRunner.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/runnerUtils.h" +#include "Framework/ServiceRegistryRef.h" +#include "QualityControl/WorkflowType.h" + +#include +#include +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace AliceO2::Common; +using namespace o2::configuration; +using o2::framework::ServiceRegistry; +namespace bpo = boost::program_options; + +int main(int argc, const char* argv[]) +{ + try { + bpo::options_description desc{ "Options" }; + desc.add_options() // + ("help,h", "Help screen") // + ("config", bpo::value(), "Absolute path to a configuration file, preceded with backend.") // + ("id", bpo::value(), "ID of a post processing task to run") // + ("override-values", bpo::value(), // + "QC configuration file key/value pairs which should be overwritten. " // + "The format is \"full.path.to.key=value[;full.path.to.key=value]\".") // + ("timestamps,t", bpo::value>()->composing(), + "Space-separated timestamps (ms since epoch) which should be given to the post processing task." + " Effectively, it ignores triggers declared in the configuration file and replaces them with" + " TriggerType::Manual with given timestamps. The first value is used for initalization trigger, the last for" + " finalization, so at least two are required."); + + bpo::positional_options_description positionalArgs; + positionalArgs.add("timestamps", -1); + + bpo::variables_map vm; + store(bpo::command_line_parser(argc, argv).options(desc).positional(positionalArgs).run(), vm); + notify(vm); + + QcInfoLogger::setFacility("runPostProcessing"); + + if (vm.count("help")) { + ILOG(Info, Support) << desc << ENDM; + return 0; + } else if (vm.count("id") == 0 && vm.count("config") == 0) { + ILOG(Error, Support) << "No id and/or config parameters provided" << ENDM; + return 1; + } + + auto taskID = vm["id"].as(); + auto configPath = vm["config"].as(); + auto config = ConfigurationFactory::getConfiguration(configPath); + auto configTree = config->getRecursive(); + if (vm.count("override-values")) { + auto keyValuesToOverride = parseOverrideValues(vm["override-values"].as()); + overrideValues(configTree, keyValuesToOverride); + } + int periodUs = static_cast(1000000 * configTree.get("qc.config.postprocessing.periodSeconds", 10.0)); + + PostProcessingRunner runner(taskID); + runner.init(configTree, WorkflowType::Standalone); + ServiceRegistry registry; + + if (vm.count("timestamps")) { + // running the PP task on a set of timestamps + runner.runOverTimestamps(vm["timestamps"].as>()); + } else { + // running the PP task with an event loop + runner.start({ registry }); + + Timer timer; + timer.reset(periodUs); + while (runner.run()) { + while (timer.getRemainingTime() < 0) { + timer.increment(); + } + usleep(1000000.0 * timer.getRemainingTime()); + } + } + runner.stop({ registry }); + return 0; + } catch (const bpo::error& ex) { + ILOG(Error, Ops) << "Exception caught: " << ex.what() << ENDM; + return 1; + } catch (const boost::exception& ex) { + ILOG(Error, Ops) << "Exception caught: " << boost::current_exception_diagnostic_information(true) << ENDM; + return 1; + } + + return 0; +} diff --git a/Framework/src/runPostProcessingOCC.cxx b/Framework/src/runPostProcessingOCC.cxx new file mode 100644 index 0000000000..44b520ab75 --- /dev/null +++ b/Framework/src/runPostProcessingOCC.cxx @@ -0,0 +1,232 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runPostProcessingOCC.cxx +/// \author Piotr Konopka +/// +/// \brief This is an OCC executable to run postprocessing + +#include "QualityControl/PostProcessingRunner.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/QcInfoLogger.h" +#include "Framework/ServiceRegistry.h" +#include "QualityControl/WorkflowType.h" + +#include +#include +#include +#include +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace AliceO2::Common; +namespace bpo = boost::program_options; + +const std::string qcConfigurationKey = "qcConfiguration"; + +class PostProcessingOCCStateMachine : public RuntimeControlledObject +{ + public: + PostProcessingOCCStateMachine(std::string id, double period = 1.0) + : RuntimeControlledObject("Post-processing task runner"), mID(id), mPeriod(period) + { + mRunner = std::make_unique(id); + } + + // In all of the methods below we handle exceptions, so we can go into error state - OCC won't do that for us. + int executeConfigure(const boost::property_tree::ptree& properties) final + { + if (mRunner == nullptr) { + return -1; + } + + bool success = true; + try { + auto config = properties.count(qcConfigurationKey) > 0 ? properties.get_child(qcConfigurationKey) : properties; + mRunner->init(config, WorkflowType::Standalone); + } catch (const boost::exception& ex) { + ILOG(Error, Support) << "Exception caught: " << boost::current_exception_diagnostic_information(true) << ENDM; + success = false; + } catch (const std::exception& ex) { + ILOG(Error, Support) << "Exception caught: " << ex.what() << ENDM; + success = false; + } catch (...) { + ILOG(Error, Support) << "Unknown exception"; + success = false; + } + return !success; + } + + int executeReset() final + { + if (mRunner == nullptr) { + return -1; + } + + bool success = true; + try { + mRunner->reset(); + } catch (const std::exception& ex) { + ILOG(Error, Support) << "Exception caught: " << ex.what() << ENDM; + success = false; + } catch (...) { + ILOG(Error, Support) << "Unknown exception"; + success = false; + } + return !success; + } + + int executeRecover() final + { + mRunner = std::make_unique(mID); + return mRunner == nullptr; + } + + int executeStart() final + { + bool success = true; + try { + o2::framework::ServiceRegistry registry; + mRunner->start(registry); + } catch (const std::exception& ex) { + ILOG(Error, Support) << "Exception caught: " << ex.what() << ENDM; + success = false; + } catch (...) { + ILOG(Error, Support) << "Unknown exception"; + success = false; + } + + mRateLimiter.reset(static_cast(mPeriod * 1000000)); + return !success; + } + + int executeStop() final + { + if (mRunner == nullptr) { + return -1; + } + + bool success = true; + try { + o2::framework::ServiceRegistry registry; + mRunner->stop(registry); + } catch (const std::exception& ex) { + ILOG(Error, Support) << "Exception caught: " << ex.what() << ENDM; + success = false; + } catch (...) { + ILOG(Error, Support) << "Unknown exception"; + success = false; + } + return !success; + } + + int executePause() final + { + return mRunner == nullptr; + } + + int executeResume() final + { + mRateLimiter.reset(static_cast(mPeriod * 1000000)); + return mRunner == nullptr; + } + + int executeExit() final + { + ILOG(Info, Support) << "executeExit" << ENDM; + mRunner = nullptr; + return 0; + } + + int iterateRunning() final + { + if (mRunner == nullptr) { + return -1; + } + + bool success = true; + bool continueRunning = false; + try { + continueRunning = mRunner->run(); + } catch (const std::exception& ex) { + ILOG(Error, Support) << "Exception caught: " << ex.what() << ENDM; + success = false; + } catch (...) { + ILOG(Error, Support) << "Unknown exception"; + success = false; + } + + while (mRateLimiter.getRemainingTime() < 0) { + mRateLimiter.increment(); + } + if (double sleepForUs = 1000000.0 * mRateLimiter.getRemainingTime(); sleepForUs > 0) { + usleep(sleepForUs); + } + + return success ? !continueRunning : -1; + } + + int iterateCheck() final + { + if (mRunner == nullptr) { + return 0; + } + return 0; + } + + private: + std::unique_ptr mRunner = nullptr; + std::string mID = ""; + double mPeriod = 1.0; + Timer mRateLimiter; +}; + +int main(int argc, const char* argv[]) +{ + try { + bpo::options_description desc{ "Options" }; + desc.add_options() // + ("help,h", "Help screen") // + ("id", bpo::value(), "ID of a post processing task to run") // + ("period", bpo::value()->default_value(1.0), "Cycle period of checking triggers in seconds") // + ("control-port", bpo::value()->default_value(0), "Control port"); + + bpo::variables_map vm; + store(parse_command_line(argc, argv, desc), vm); + notify(vm); + + QcInfoLogger::setFacility("runPostProcessingOCC"); + + if (vm.count("help")) { + ILOG(Info, Support) << desc << ENDM; + return 0; + } else if (vm.count("id") == 0) { + ILOG(Error, Support) << "No 'name' parameter provided" << ENDM; + return 1; + } + + PostProcessingOCCStateMachine stateMachine(vm["id"].as(), vm["period"].as()); + OccInstance occ(&stateMachine, vm["control-port"].as()); + occ.wait(); + return 0; + + } catch (const bpo::error& ex) { + ILOG(Error, Support) << ex.what() << ENDM; + return 1; + } catch (const boost::exception& ex) { + ILOG(Error, Support) << "Exception caught: " << boost::current_exception_diagnostic_information(true) << ENDM; + return 1; + } + + return 0; +} diff --git a/Framework/src/runQC.cxx b/Framework/src/runQC.cxx new file mode 100644 index 0000000000..3461d4c80f --- /dev/null +++ b/Framework/src/runQC.cxx @@ -0,0 +1,266 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runQC.cxx +/// \author Piotr Konopka +/// +/// \brief This is an executable which generates a QC topology given a configuration file. +/// +/// This is an executable which generates a QC topology given a configuration file. It can be attached to any other +/// topology which can provide data to Data Sampling and QC. This also means that cannot work on its own, as it would +/// lack input data. A typical usage would be: +/// \code{.sh} +/// o2-qc-run-producer | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/basic.json +/// \endcode +/// Please refer to Framework/example-default.json and Framework/basic.json to see how to configure a QC topology. +/// To generate only the local part of the topology (which would run on main processing servers) use the '--local' flag. +/// Similarly, to generate only the remote part (running on QC servers) add '--remote'. By default, the executable +/// generates both local and remote topologies, as it is the usual use-case for local development. + +#include +#include +#include +#include +#include +#include +#include +#include "QualityControl/runnerUtils.h" +#include "QualityControl/InfrastructureGenerator.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/ConfigParamGlo.h" +#include "QualityControl/WorkflowType.h" + +using namespace o2; +using namespace o2::framework; +using namespace o2::utilities; +using namespace o2::configuration; + +// The customize() functions are used to declare the executable arguments and to specify custom completion and channel +// configuration policies. They have to be above `#include "Framework/runDataProcessing.h"` - that header checks if +// these functions are defined by user and if so, it invokes them. It uses a trick with SFINAE expressions to do that. + +void customize(std::vector& workflowOptions) +{ + workflowOptions.push_back( + ConfigParamSpec{ "config", VariantType::String, "", { "Absolute path to QC and Data Sampling configuration file." } }); + + workflowOptions.push_back( + ConfigParamSpec{ "local", VariantType::Bool, false, { "Runs only the local part of the QC workflow." } }); + workflowOptions.push_back( + ConfigParamSpec{ "host", VariantType::String, "", { "Name of the host of the local part of the QC workflow. " + "Necessary to specify when creating workflows on multiple" + " machines. If not specified, hostname of the current machine" + " will be used" } }); + workflowOptions.push_back( + ConfigParamSpec{ "remote", VariantType::Bool, false, { "Runs only the remote part of the QC workflow." } }); + workflowOptions.push_back( + ConfigParamSpec{ "full-chain", VariantType::Bool, false, { "Runs the full QC chain except proxies." } }); + workflowOptions.push_back( + ConfigParamSpec{ "no-data-sampling", VariantType::Bool, false, { "Do not add Data Sampling infrastructure." } }); + + workflowOptions.push_back( + ConfigParamSpec{ "local-batch", VariantType::String, "", { "Runs the local part of the QC workflow and dumps results to a file. " + "Takes the file path as argument. If it exists, the results are merged. " + "Do not run many QC workflows on the same file at the same time." } }); + workflowOptions.push_back( + ConfigParamSpec{ "remote-batch", VariantType::String, "", { "Runs the remote part of the QC workflow reading the inputs from a file (files). " + "Takes the file path as argument." } }); + + workflowOptions.push_back( + ConfigParamSpec{ "override-values", VariantType::String, "", { "QC configuration file key/value pairs which should be overwritten. " + "The format is \"full.path.to.key=value[;full.path.to.key=value]\"." } }); + workflowOptions.push_back( + ConfigParamSpec{ "configKeyValues", VariantType::String, "", { "Semicolon separated key=value strings (e.g.: 'TPCHwClusterer.peakChargeThreshold=4;...')" } }); + + workflowOptions.push_back( + ConfigParamSpec{ "no-infologger", VariantType::Bool, false, { "Disable entirely the infologger." } }); +} + +void customize(std::vector& policies) +{ + DataSampling::CustomizeInfrastructure(policies); + quality_control::customizeInfrastructure(policies); +} + +void customize(std::vector& policies) +{ + DataSampling::CustomizeInfrastructure(policies); +} + +#include + +using namespace std::chrono; +using WorkflowType = o2::quality_control::core::WorkflowType; + +bool validateArguments(const ConfigContext& config) +{ + const std::string qcConfigurationSource = config.options().get("config"); + std::regex configBackend(".+://.+$"); + if (qcConfigurationSource.empty()) { + ILOG(Warning, Support) << "No configuration path specified, returning an empty workflow." << ENDM; + return false; + } else if (!std::regex_match(qcConfigurationSource, configBackend)) { + ILOG(Error, Ops) << "The --config option expects a backend name (e.g. json:// or consul-json://) preceding the path. User specified: " << qcConfigurationSource << ENDM; + return false; + } + + size_t exclusiveOptions = 0; + exclusiveOptions += config.options().get("local"); + exclusiveOptions += config.options().get("remote"); + exclusiveOptions += config.options().get("full-chain"); + exclusiveOptions += !config.options().get("local-batch").empty(); + exclusiveOptions += !config.options().get("remote-batch").empty(); + if (exclusiveOptions > 1) { + ILOG(Error, Ops) << "More than one of the following options was specified: --local, --remote, --local-batch, --remote-batch, --full-chain. This is not allowed, returning an empty workflow." << ENDM; + return false; + } + + return true; +} + +void setupInfologger(const ConfigContext& config, boost::property_tree::ptree configTree) +{ + if (config.options().get("no-infologger")) { + quality_control::core::QcInfoLogger::disable(); + } else { + auto infologgerFilterDiscardDebug = configTree.get("qc.config.infologger.filterDiscardDebug", true); + auto infologgerDiscardLevel = configTree.get("qc.config.infologger.filterDiscardLevel", 21); + ILOG_INST.filterDiscardDebug(infologgerFilterDiscardDebug); + ILOG_INST.filterDiscardLevel(infologgerDiscardLevel); + } + auto infologgerDiscardFile = configTree.get("qc.config.infologger.filterDiscardFile", ""); + auto rotateMaxBytes = configTree.get("qc.config.infologger.filterRotateMaxBytes", 0); + auto rotateMaxFiles = configTree.get("qc.config.infologger.filterRotateMaxFiles", 0); + std::string debugInDiscardFile = configTree.get("qc.config.infologger.debugInDiscardFile", "false"); + auto debugInDiscardFileBool = debugInDiscardFile == "true"; + ILOG_INST.filterDiscardSetFile(infologgerDiscardFile.c_str(), rotateMaxBytes, rotateMaxFiles, 0, !debugInDiscardFileBool /*Do not store Debug messages in file*/); + + std::string id = "runQC"; + for (size_t i = 0; i < config.argc(); i++) { + if (std::strcmp(config.argv()[i], "--id") == 0 && i + 1 < config.argc()) { + id = config.argv()[i + 1]; + break; + } + } + o2::quality_control::core::QcInfoLogger::setFacility(id); +} + +WorkflowSpec defineDataProcessing(const ConfigContext& config) +{ + WorkflowSpec specs; + + if (!validateArguments(config)) { + return {}; + } + quality_control::ConfigParamGlo::keyValues = config.options().get("configKeyValues"); + + auto qcConfigurationSource = config.options().get("config"); + try { + // The online QC infrastructure is divided into two parts: + // - local - QC tasks which are on the same machines as the main processing. We also put Data Sampling there. + // - remote - QC tasks, mergers and checkers that reside on QC servers + // + // The user can specify to create either one of these parts by selecting corresponding option. + // No flags will effect in generating the minimal complete workflow (data sampling, tasks, checks, aggregators, pp) + // Using '--full-chain' will cause in generating a complete workflow with merger between local tasks and checks. + // + // For file-based processing, there are also: + // - local-batch - QC tasks are run, the results are stored in the specified file. If the file exists, + // QC objects are merged. Multiple local-batch workflows should not run at the same time, + // as they would modify the same file. + // - remote-batch - Checks and Aggregators are run on the QC objects inside a file created by a local-batch workflow. + // The results are stored in the database specified in the config file. + + // we set the infologger levels as soon as possible to avoid spamming + auto configTree = ConfigurationFactory::getConfiguration(qcConfigurationSource)->getRecursive(); + + setupInfologger(config, configTree); + + ILOG(Info, Devel) << "Using config file '" << qcConfigurationSource << "'" << ENDM; + auto keyValuesToOverride = quality_control::core::parseOverrideValues(config.options().get("override-values")); + quality_control::core::overrideValues(configTree, keyValuesToOverride); + + auto workflowType = quality_control::core::workflow_type_helpers::getWorkflowType(config.options()); + switch (workflowType) { + case WorkflowType::Standalone: { + ILOG(Debug, Devel) << "Creating a standalone QC workflow." << ENDM; + + if (!config.options().get("no-data-sampling") && configTree.count("dataSamplingPolicies") > 0) { + ILOG(Debug, Devel) << "Generating Data Sampling" << ENDM; + DataSampling::GenerateInfrastructure(specs, configTree.get_child("dataSamplingPolicies")); + } else { + ILOG(Debug, Devel) << "Omitting Data Sampling" << ENDM; + } + quality_control::generateStandaloneInfrastructure(specs, configTree); + break; + } + case WorkflowType::Local: { + ILOG(Debug, Devel) << "Creating a local QC topology." << ENDM; + auto host = config.options().get("host").empty() + ? boost::asio::ip::host_name() + : config.options().get("host"); + + if (!config.options().get("no-data-sampling") && configTree.count("dataSamplingPolicies") > 0) { + ILOG(Debug, Devel) << "Generating Data Sampling" << ENDM; + DataSampling::GenerateInfrastructure(specs, configTree.get_child("dataSamplingPolicies"), 1, host); + } else { + ILOG(Debug, Devel) << "Omitting Data Sampling" << ENDM; + } + + // Generation of the local QC topology (local QC tasks and their output proxies) + quality_control::generateLocalInfrastructure(specs, configTree, host); + break; + } + case WorkflowType::Remote: { + ILOG(Debug, Devel) << "Creating a remote QC workflow." << ENDM; + + // Generation of the remote QC topology (task for QC servers, input proxies, mergers, all check runners, postprocessing) + quality_control::generateRemoteInfrastructure(specs, configTree); + break; + } + case WorkflowType::FullChain: { + ILOG(Debug, Devel) << "Creating a full QC chain workflow." << ENDM; + if (!config.options().get("no-data-sampling") && configTree.count("dataSamplingPolicies") > 0) { + DataSampling::GenerateInfrastructure(specs, configTree.get_child("dataSamplingPolicies")); + } + // Generates a full QC chain (data sampling, tasks, mergers, checks, aggregators, postprocessing) + quality_control::generateFullChainInfrastructure(specs, configTree); + break; + } + case WorkflowType::LocalBatch: { + ILOG(Debug, Devel) << "Creating a local batch QC workflow." << ENDM; + if (!config.options().get("no-data-sampling") && configTree.count("dataSamplingPolicies") > 0) { + ILOG(Debug, Devel) << "Generating Data Sampling" << ENDM; + DataSampling::GenerateInfrastructure(specs, configTree.get_child("dataSamplingPolicies")); + } else { + ILOG(Debug, Devel) << "Omitting Data Sampling" << ENDM; + } + + auto localBatchFilePath = config.options().get("local-batch"); + // Generation of the local batch QC workflow (QC tasks and file sink) + quality_control::generateLocalBatchInfrastructure(specs, configTree, localBatchFilePath); + break; + } + case WorkflowType::RemoteBatch: { + auto remoteBatchFilePath = config.options().get("remote-batch"); + // Creating the remote batch QC topology (file reader, check runners, aggregator runners, postprocessing) + quality_control::generateRemoteBatchInfrastructure(specs, configTree, remoteBatchFilePath); + break; + } + } + } catch (const std::runtime_error& re) { + ILOG(Fatal, Ops) << "Failed to build the workflow: " << re.what() << ENDM; + throw; + } + + return specs; +} diff --git a/Framework/src/runReadout.cxx b/Framework/src/runReadout.cxx index c2c007e8b5..c8a064a5f7 100644 --- a/Framework/src/runReadout.cxx +++ b/Framework/src/runReadout.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -12,110 +13,36 @@ /// \file runReadout.cxx /// \author Piotr Konopka /// -/// \brief This is an executable showing QC Task's usage in Data Processing Layer with Readout as external data source. +/// \brief This is an executable showing how to connect to Readout as external data source. /// -/// This is an executable showing QC Task's usage with Readout as a data producer. To get the Readout data, a proxy is -/// used. Its output is dispatched to QC task using Data Sampling infrastructure. QC Task runs exemplary user code -/// located in SkeletonDPL. The resulting histogram contents are printed by a fake checker. -/// QC task is instantiated by TaskDataProcessorFactory with preinstalled config file, which can be found in -/// ${QUALITYCONTROL_ROOT}/etc/readout.json or Framework/readout.json (original one). +/// This is an executable showing how to connect to Readout as external data source. It consists only of a proxy, +/// which can inject the Readout data into DPL. This workflow is intended to be merged with the QC workflow by doing: +/// \code{.sh} +/// o2-qc-run-readout | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/readout.json +/// \endcode +/// If you do not need to sample data, use the readout-no-sampling.json file instead. /// -/// To launch it, build the project, load the environment and run the executable: -/// \code{.sh} -/// > aliBuild build QualityControl --defaults o2 -/// > alienv enter QualityControl/latest -/// > runReadoutChainTemplate -/// \endcode /// If you have glfw installed, you should see a window with the workflow visualization and sub-windows for each Data /// Processor where their logs can be seen. The processing will continue until the main window it is closed. Regardless /// of glfw being installed or not, in the terminal all the logs will be shown as well. -#include "Framework/DataSampling.h" - -using namespace o2::framework; - -void customize(std::vector& policies) -{ - DataSampling::CustomizeInfrastructure(policies); -} - -void customize(std::vector& policies) -{ - DataSampling::CustomizeInfrastructure(policies); -} - -void customize(std::vector& workflowOptions) -{ - workflowOptions.push_back( - ConfigParamSpec{ "config-path", VariantType::String, "", { "Path to the config file. Overwrite the default paths. Do not use with no-data-sampling." } }); - workflowOptions.push_back( - ConfigParamSpec{ "no-data-sampling", VariantType::Bool, false, { "Skips data sampling, connects directly the task to the producer." } }); -} - -#include - -#include "Framework/DataSamplingReadoutAdapter.h" -#include "Framework/runDataProcessing.h" -#include "QualityControl/InfrastructureGenerator.h" - -#include "QualityControl/Checker.h" -#include "QualityControl/CheckerFactory.h" -#include "QualityControl/TaskRunnerFactory.h" -#include "QualityControl/TaskRunner.h" -#include "runnerUtils.h" -#include "ExamplePrinterSpec.h" - -#include -#include - -std::string getConfigPath(const ConfigContext& config); +#include +#include using namespace o2; -using namespace o2::quality_control::checker; +using namespace o2::framework; +using namespace o2::utilities; -WorkflowSpec defineDataProcessing(ConfigContext const& config) +WorkflowSpec defineDataProcessing(ConfigContext const&) { // Creating the Readout proxy WorkflowSpec specs{ specifyExternalFairMQDeviceProxy( "readout-proxy", - Outputs{{"ITS", "RAWDATA"}}, + Outputs{ { { "readout" }, { "ROUT", "RAWDATA" } } }, "type=sub,method=connect,address=ipc:///tmp/readout-pipe-1,rateLogging=1", - dataSamplingReadoutAdapter({"ITS", "RAWDATA"})) - }; - - // Path to the config file - std::string qcConfigurationSource = getConfigPath(config); - LOG(INFO) << "Using config file '" << qcConfigurationSource << "'"; - - // Generation of Data Sampling infrastructure - DataSampling::GenerateInfrastructure(specs, qcConfigurationSource); - - // Generation of the QC topology (one task, one checker in this case) - quality_control::generateRemoteInfrastructure(specs, qcConfigurationSource); - - // Finally the printer - DataProcessorSpec printer{ - "printer", - Inputs{ - { "checked-mo", "QC", Checker::createCheckerDataDescription(getFirstTaskName(qcConfigurationSource)), 0 } }, - Outputs{}, - adaptFromTask() + dataSamplingReadoutAdapter({ { "readout" }, { "ROUT", "RAWDATA" } })) }; - specs.push_back(printer); return specs; } - -std::string getConfigPath(const ConfigContext& config) -{ - // Determine the default config file path and name (based on option no-data-sampling and the QC_ROOT path) - bool noDS = config.options().get("no-data-sampling"); - std::string filename = !noDS ? "readout.json" : "readout-no-sampling.json"; - std::string defaultConfigPath = getenv("QUALITYCONTROL_ROOT") != nullptr ? std::string(getenv("QUALITYCONTROL_ROOT")) + "/etc/" + filename : "$QUALITYCONTROL_ROOT undefined"; - // The the optional one by the user - auto userConfigPath = config.options().get("config-path"); - // Finally build the config path based on the default or the user-base one - std::string path = std::string("json:/") + (userConfigPath.empty() ? defaultConfigPath : userConfigPath); - return path; -} diff --git a/Framework/src/runReadoutForDataDump.cxx b/Framework/src/runReadoutForDataDump.cxx deleted file mode 100644 index 87468764af..0000000000 --- a/Framework/src/runReadoutForDataDump.cxx +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -// - -/// \file runReadoutForDataDump.cxx -/// \author Piotr Konopka, Barthelemy von Haller -/// -/// \brief This is a simplistic executable to be able to sample Readout data towards a non-DPL FairMQ device. -/// -/// It uses a config file located at -/// ${QUALITYCONTROL_ROOT}/etc/readoutForDataDump.json or Framework/readoutForDataDump.json (original one). -/// The only thing that might have to be changed is the port (default : 26525) on which the data is sent. -/// \code{.json} -/// > "channelConfig": "name=fairReadoutRawOut,type=pub,method=bind,address=tcp://127.0.0.1:26525,rateLogging=1" -/// \endcode -/// -/// To launch it, build the project, load the environment and run the executable: -/// \code{.sh} -/// > aliBuild build QualityControl --defaults o2 -/// > alienv enter QualityControl/latest -/// > runReadoutDataSampling -/// \endcode -/// -/// If you have glfw installed, you should see a window with the workflow visualization and sub-windows for each Data -/// Processor where their logs can be seen. The processing will continue until the main window is closed. Regardless -/// of glfw being installed or not, in the terminal all the logs will be shown as well. -/// - -#include "Framework/DataSampling.h" - -using namespace o2::framework; -void customize(std::vector& policies) -{ - DataSampling::CustomizeInfrastructure(policies); -} -void customize(std::vector& policies) -{ - DataSampling::CustomizeInfrastructure(policies); -} - -#include "Framework/DataSamplingReadoutAdapter.h" -#include "Framework/runDataProcessing.h" - -using namespace o2::framework; - -WorkflowSpec defineDataProcessing(ConfigContext const&) -{ - WorkflowSpec specs{ - specifyExternalFairMQDeviceProxy( - "readout-proxy", - Outputs{ { "R/O", "RAWDATA" } }, - "type=sub,method=connect,address=ipc:///tmp/readout-pipe-1,rateLogging=1", - dataSamplingReadoutAdapter({ "R/O", "RAWDATA" })) - }; - - const std::string qcConfigurationSource = - std::string("json://") + getenv("QUALITYCONTROL_ROOT") + "/etc/readoutForDataDump.json"; - LOG(INFO) << "Using config file '" << qcConfigurationSource << "'"; - - o2::framework::DataSampling::GenerateInfrastructure(specs, qcConfigurationSource); - - return specs; -} diff --git a/Framework/src/runRepositoryBenchmark.cxx b/Framework/src/runRepositoryBenchmark.cxx index b3ca02dc8f..821784360e 100644 --- a/Framework/src/runRepositoryBenchmark.cxx +++ b/Framework/src/runRepositoryBenchmark.cxx @@ -1,8 +1,9 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". +// Copyright 2019-2022 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. // -// See http://alice-o2.web.cern.ch/license for full licensing information. +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". // // In applying this license CERN does not waive the privileges and immunities // granted to it by virtue of its status as an Intergovernmental Organization @@ -14,7 +15,7 @@ /// #include "RepositoryBenchmark.h" -#include "runFairMQDevice.h" +#include namespace bpo = boost::program_options; @@ -48,7 +49,7 @@ void addCustomOptions(bpo::options_description& options) "The URL to the monitoring system (default : \"infologger://\")"); } -FairMQDevicePtr getDevice(const FairMQProgOptions& /*config*/) +std::unique_ptr getDevice(fair::mq::ProgOptions& /*config*/) { - return new o2::quality_control::core::RepositoryBenchmark(); -} \ No newline at end of file + return std::make_unique(); +} diff --git a/Framework/src/runUploadRootObjects.cxx b/Framework/src/runUploadRootObjects.cxx new file mode 100644 index 0000000000..4963e3d8df --- /dev/null +++ b/Framework/src/runUploadRootObjects.cxx @@ -0,0 +1,159 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runUploadRootObjects.cxx +/// \author Piotr Konopka +/// +/// \brief This is an executable which reads ROOT files with objects and puts them to QCDB. +/// +/// This is an executable which reads QAResults.root generated by DPL analysis tasks and puts them to QCDB. +/// It will ignore the directory structure and put all objects in under the task name specified as the argument. +/// By default the current date and time will be used as the start of validity, and the object will be valid for 10 years. + +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/CcdbDatabase.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/RepoPathUtils.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace bpo = boost::program_options; +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; + +int main(int argc, const char* argv[]) +{ + size_t objectsUploaded = 0; + + try { + bpo::options_description desc{ "Options" }; + desc.add_options() // + ("help,h", "Help screen") // + ("input-file", bpo::value()->default_value("./QAResults.root"), "Path to the ROOT file with objects to insert.") // + ("qcdb-url", bpo::value()->default_value("ccdb-test.cern.ch:8080"), "URL to the QCDB.") // + ("task-name", bpo::value(), "Name of the task to which the objects belong. Use / to make directories") // + ("detector-code", bpo::value()->default_value("TST"), "3-letter detector code. Put AOD for analysis tasks") // + ("validity-start", bpo::value()->default_value(0), "Start of objects validity in ms since epoch") // + ("validity-end", bpo::value()->default_value(0), "End of objects validity in ms since epoch") // + ("run-number", bpo::value(), "Run number of objects (put 0 for many runs)") // + ("period-name", bpo::value()->default_value("unknown"), "Period name of the objects") // todo one could ask logbook + ("pass-name", bpo::value()->default_value("unknown"), "Calib/reco/sim pass name") // + ("provenance", bpo::value()->default_value("qc"), "Object path prefix used to mark if data comes from detector (use qc) or simulation (use qc_mc)") // + ("preserve-directories", bpo::bool_switch()->default_value(false), "If present, the directory structure of the input file will be preserved in QCDB"); + + bpo::variables_map vm; + store(parse_command_line(argc, argv, desc), vm); + notify(vm); + + if (vm.count("help")) { + // no infologger here, because the message is too long. + std::cout << desc << std::endl; + return 0; + } + + /// Read and validate arguments + auto inputFilePath = vm["input-file"].as(); + auto qcdbUrl = vm["qcdb-url"].as(); + auto taskName = vm["task-name"].as(); + auto detectorCode = vm["detector-code"].as(); + auto validityStart = vm["validity-start"].as(); + auto validityEnd = vm["validity-end"].as(); + auto runNumber = vm["run-number"].as(); + auto periodName = vm["period-name"].as(); + auto passName = vm["pass-name"].as(); + auto provenance = vm["provenance"].as(); + auto preserveDirectories = vm["preserve-directories"].as(); + + if (validityStart == 0) { + validityStart = CcdbDatabase::getCurrentTimestamp(); + } + if (validityEnd == 0) { + validityEnd = validityStart + 1000ull * 60 * 60 * 24 * 365 * 10; + } + if (validityStart > validityEnd) { + throw std::runtime_error("Validity start (" + std::to_string(validityStart) + ") is further in the future than validity end (" + std::to_string(validityEnd) + ")"); + } + if (!RepoPathUtils::isProvenanceAllowed(provenance)) { + throw std::runtime_error(std::string(RepoPathUtils::allowedProvenancesMessage) + " '" + provenance + "' was given."); + } + + /// Open ROOT file + auto* file = new TFile(inputFilePath.c_str(), "READ"); + if (file->IsZombie()) { + throw std::runtime_error("File '" + inputFilePath + "' is zombie."); + } + if (!file->IsOpen()) { + throw std::runtime_error("Failed to open the file: " + inputFilePath); + } + ILOG(Info) << "Input file '" << inputFilePath << "' successfully open." << ENDM; + + /// Open CCDB interface + CcdbDatabase database; + database.connect(qcdbUrl, "", "", ""); + + /// Upload the objects + std::function browseFileAndUpload = [&](TDirectoryFile* directory, const std::string& path) { + TIter next(directory->GetListOfKeys()); + TKey* key; + while ((key = (TKey*)next())) { + auto storedTObj = directory->Get(key->GetName()); + if (storedTObj != nullptr) { + if (storedTObj->InheritsFrom("TDirectoryFile")) { + browseFileAndUpload(dynamic_cast(storedTObj), path + std::string(key->GetName()) + std::filesystem::path::preferred_separator); + } else { + std::shared_ptr mo = nullptr; + if (preserveDirectories) { + // one cannot change a name of a TObject, we have to create a new one... + auto clonedTObj = storedTObj->Clone((path + storedTObj->GetName()).c_str()); + mo = std::make_shared(clonedTObj, taskName, "unknown", detectorCode, runNumber, periodName, passName, provenance); + mo->setIsOwner(true); + } else { + mo = std::make_shared(storedTObj, taskName, "unknown", detectorCode, runNumber, periodName, passName, provenance); + mo->setIsOwner(false); + } + mo->setValidity({ validityStart, validityEnd }); + database.storeMO(mo); + objectsUploaded++; + } + } + delete storedTObj; + } + }; + + browseFileAndUpload(file, ""); + + file->Close(); + delete file; + + database.disconnect(); + + } catch (const bpo::error& ex) { + ILOG(Error, Ops) << "Exception caught: " << ex.what() << ENDM; + return 1; + } catch (const boost::exception& ex) { + ILOG(Error, Ops) << "Exception caught: " << boost::current_exception_diagnostic_information(true) << ENDM; + return 1; + } + + if (objectsUploaded > 0) { + ILOG(Info, Support) << "Successfully uploaded " << objectsUploaded << " objects to the QCDB." << ENDM; + } else { + ILOG(Info, Support) << "No objects were uploaded to the QCDB. Maybe the file is empty?" << ENDM; + } + return 0; +} \ No newline at end of file diff --git a/Framework/src/runnerUtils.cxx b/Framework/src/runnerUtils.cxx new file mode 100644 index 0000000000..9270839f4c --- /dev/null +++ b/Framework/src/runnerUtils.cxx @@ -0,0 +1,209 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runnerUtils.cxx +/// \author Barthelemy von Haller +/// \author Piotr Konopka +/// + +#include "QualityControl/runnerUtils.h" +#include "QualityControl/stringUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace o2::quality_control::core +{ + +std::string getFirstTaskName(const std::string& configurationSource) +{ + auto config = o2::configuration::ConfigurationFactory::getConfiguration(configurationSource); + + for (const auto& task : config->getRecursive("qc.tasks")) { + return task.first; // task name; + } + + throw; +} + +std::string getFirstCheckName(const std::string& configurationSource) +{ + auto config = o2::configuration::ConfigurationFactory::getConfiguration(configurationSource); + + if (config->getRecursive("qc").count("checks")) { + for (const auto& check : config->getRecursive("qc.checks")) { + return check.first; // check name; + } + } + + BOOST_THROW_EXCEPTION(AliceO2::Common::ObjectNotFoundError() << AliceO2::Common::errinfo_details("No checks defined")); +} + +bool hasChecks(const std::string& configSource) +{ + auto config = o2::configuration::ConfigurationFactory::getConfiguration(configSource); + return config->getRecursive("qc").count("checks") > 0; +} + +/** + * If the runType is of legacy type, i.e. an integer, the corresponding string representation is returned. + * In case we cannot find a string representation we use "NONE". + * @param runType + */ +std::string_view translateIntegerRunType(const std::string& runType) +{ + // runType used to be an integer. If we find an integer in a config file, the risk is that it is translated directly to a string (2->"2"). + // We must rather translate the integer into the corresponding run type string if at all possible. + if (isUnsignedInteger(runType)) { + try { + ILOG(Warning, Ops) << "Activity type was provided as an integer. A matching activity type could be found: " << parameters::GRPECS::RunTypeNames.at(std::stoi(runType)) << ". Consider using the string representation of the run type." << ENDM; + return parameters::GRPECS::RunTypeNames.at(std::stoi(runType)); + } catch (std::out_of_range& exc) { + ILOG(Warning, Ops) << "Activity type was provided as an integer. No matching activity type could be find. Using 'NONE'." << ENDM; + return "NONE"; + } + } + return runType; +} + +std::string computeStringActivityField(framework::ServiceRegistryRef services, const std::string& name, const std::string& fallBack) +{ + auto property = services.get().device()->fConfig->GetProperty(name, fallBack); + ILOG(Info, Devel) << "Got this property '" << name << "' from RawDeviceService (fallback was " << fallBack << ") : '" << property << "'" << ENDM; + return property; +} + +Activity computeActivity(framework::ServiceRegistryRef services, const Activity& fallbackActivity) +{ + // for a complete list of the properties provided by ECS, see here: https://github.com/AliceO2Group/Control/blob/master/docs/handbook/configuration.md#variables-pushed-to-controlled-tasks + auto runNumber = computeNumericalActivityField(services, "runNumber", fallbackActivity.mId); + std::string runType = computeStringActivityField(services, "run_type", fallbackActivity.mType); + runType = translateIntegerRunType(runType); + auto runStartTimeMs = computeNumericalActivityField(services, "run_start_time_ms", fallbackActivity.mValidity.getMin()); + auto runEndTimeMs = computeNumericalActivityField(services, "run_end_time_ms", fallbackActivity.mValidity.getMax()); + auto partitionName = computeStringActivityField(services, "environment_id", fallbackActivity.mPartitionName); + auto periodName = computeStringActivityField(services, "lhc_period", fallbackActivity.mPeriodName); + auto fillNumber = computeNumericalActivityField(services, "fill_info_fill_number", fallbackActivity.mFillNumber); + auto beam_type = computeStringActivityField(services, "pdp_beam_type", fallbackActivity.mBeamType); + auto originalRunNumber = computeNumericalActivityField(services, "original_run_number", fallbackActivity.mOriginalId); + + Activity activity( + runNumber, + runType, + periodName, + fallbackActivity.mPassName, + fallbackActivity.mProvenance, + { runStartTimeMs, runEndTimeMs }, + beam_type, + partitionName, + fillNumber, + originalRunNumber); + + return activity; +} + +std::string indentTree(int level) +{ + return std::string(level * 2, ' '); +} + +void printTree(const boost::property_tree::ptree& pt, int level) +{ + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, pt); + for (std::string line; std::getline(ss, line, '\n');) + ILOG(Debug, Trace) << line << ENDM; +} + +std::vector> parseOverrideValues(const std::string& input) +{ + std::vector> keyValuePairs; + for (const auto& keyValueToken : utils::Str::tokenize(input, ';', true)) { + auto keyValue = utils::Str::tokenize(keyValueToken, '=', true); + if (keyValue.size() == 1) { + keyValuePairs.emplace_back(keyValue[0], ""); + } else if (keyValue.size() == 2) { + keyValuePairs.emplace_back(keyValue[0], keyValue[1]); + } else { + throw std::runtime_error("Token '" + keyValueToken + "' in the --override-values argument is malformed, use key=value."); + } + } + return keyValuePairs; +} + +void overrideValues(boost::property_tree::ptree& tree, const std::vector>& keyValues) +{ + for (const auto& [key, value] : keyValues) { + tree.put(key, value); + } +} + +/** + * template the param infologgerDiscardFile (_ID_->[device-id]) + * @param originalFile + * @param iCtx + * @return + */ +std::string templateILDiscardFile(std::string& originalFile, framework::InitContext& iCtx) +{ + try { + auto& deviceSpec = iCtx.services().get(); + return std::regex_replace(originalFile, std::regex("_ID_"), deviceSpec.id); + } catch (...) { + ILOG(Error, Devel) << "exception caught and swallowed in templateILDiscardFile : " << boost::current_exception_diagnostic_information() << ENDM; + } + return originalFile; +} + +uint64_t getCurrentTimestamp() +{ + auto now = std::chrono::system_clock::now(); + auto now_ms = std::chrono::time_point_cast(now); + auto epoch = now_ms.time_since_epoch(); + auto value = std::chrono::duration_cast(epoch); + return value.count(); +} + +void initInfologger(framework::InitContext& iCtx, core::LogDiscardParameters infologgerDiscardParameters, std::string facility, std::string detectorName) +{ + AliceO2::InfoLogger::InfoLoggerContext* ilContext = nullptr; + AliceO2::InfoLogger::InfoLogger* il = nullptr; + try { + ilContext = &iCtx.services().get(); + il = &iCtx.services().get(); + } catch (const framework::RuntimeErrorRef& err) { + ILOG(Error, Devel) << "Could not find the DPL InfoLogger" << ENDM; + } + + infologgerDiscardParameters.file = templateILDiscardFile(infologgerDiscardParameters.file, iCtx); + QcInfoLogger::init(facility, + infologgerDiscardParameters, + il, + ilContext); + if (!detectorName.empty()) { + QcInfoLogger::setDetector(detectorName); + } +} + +} // namespace o2::quality_control::core diff --git a/Framework/src/runnerUtils.h b/Framework/src/runnerUtils.h deleted file mode 100644 index 335cd42aee..0000000000 --- a/Framework/src/runnerUtils.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright CERN and copyright holders of ALICE O2. This software is -// distributed under the terms of the GNU General Public License v3 (GPL -// Version 3), copied verbatim in the file "COPYING". -// -// See http://alice-o2.web.cern.ch/license for full licensing information. -// -// In applying this license CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. - -/// -/// \file runnerUtils.h -/// \author Barthelemy von Haller -/// - -#ifndef QUALITYCONTROL_RUNNERUTILS_H -#define QUALITYCONTROL_RUNNERUTILS_H - -#include - -namespace o2::quality_control::core -{ - -/** - * Returns the name of the first task encountered in the config file. - * Ad-hoc solution to avoid hard-coding the task when we create the printer (he needs it to know the data description - * of the data coming out of the checker). - * @param config - * @return The name of the first task in the config file. - */ -std::string getFirstTaskName(std::string configurationSource) -{ - auto config = o2::configuration::ConfigurationFactory::getConfiguration(configurationSource); - - for (const auto&[taskName, taskConfig] : config->getRecursive("qc.tasks")) { - return taskName; - } - - throw; -} - -} // o2::quality_control::core - -#endif //QUALITYCONTROL_RUNNERUTILS_H diff --git a/Framework/src/stringUtils.cxx b/Framework/src/stringUtils.cxx new file mode 100644 index 0000000000..e6509a5c93 --- /dev/null +++ b/Framework/src/stringUtils.cxx @@ -0,0 +1,89 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file stringUtils.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/stringUtils.h" + +#include "QualityControl/CustomParameters.h" +#include +#include +#include +#include +#include + +namespace o2::quality_control::core +{ + +std::vector getBinRepresentation(unsigned char* data, size_t size) +{ + std::stringstream ss; + std::vector result; + result.reserve(size); + + for (size_t i = 0; i < size; i++) { + std::bitset<16> x(data[i]); + ss << x << " "; + result.push_back(ss.str()); + ss.str(std::string()); + } + return result; +} + +std::vector getHexRepresentation(unsigned char* data, size_t size) +{ + std::stringstream ss; + std::vector result; + result.reserve(size); + ss << std::hex << std::setfill('0'); + + for (size_t i = 0; i < size; i++) { + ss << std::setw(2) << static_cast(data[i]) << " "; + result.push_back(ss.str()); + ss.str(std::string()); + } + return result; +} + +/// \brief Decode key of a configurable parameter as boolean +/// \param value Value to be decoded (true or false, case-insensitive) +/// \return Boolean representation of the value +/// \throw std::runtime_error in case value is not a boolean value +bool decodeBool(const std::string& value) +{ + if (value == "true" || value == "True" || value == "TRUE" || value == "1") { + return true; + } + if (value == "false" || value == "False" || value == "FALSE" || value == "0") { + return false; + } + throw std::runtime_error(fmt::format("Value {} not a boolean", value.data())); +} + +bool parseBoolParam(const CustomParameters& customParameters, const std::string& name, const std::string& runType, const std::string& beamType) +{ + try { + return decodeBool(customParameters.at(name, runType, beamType)); + } catch (std::out_of_range& exception) { + BOOST_THROW_EXCEPTION(AliceO2::Common::ObjectNotFoundError() << AliceO2::Common::errinfo_object_name(runType + "/" + beamType + "/" + name)); + } +} + +bool isUnsignedInteger(const std::string& s) +{ + return !s.empty() && std::find_if(s.begin(), + s.end(), [](unsigned char c) { return !std::isdigit(c); }) == s.end(); +} + +} // namespace o2::quality_control::core \ No newline at end of file diff --git a/Framework/test/testActivity.cxx b/Framework/test/testActivity.cxx new file mode 100644 index 0000000000..f0f0f0f0d8 --- /dev/null +++ b/Framework/test/testActivity.cxx @@ -0,0 +1,78 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testActivity.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/Activity.h" +#include + +using namespace o2::quality_control::core; + +TEST_CASE("test_matching") +{ + { + // the default Activity has the widest match (provenance always has to match) + Activity matcher{}; + + CHECK(matcher.matches({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "pp" })); + CHECK(matcher.matches({ 0, "NONE", "", "", "qc", { 1, 10 }, "" })); + CHECK(!matcher.matches({ 0, "NONE", "", "", "qc_mc", { 1, 10 }, "" })); + CHECK(matcher.matches({})); + CHECK(Activity().matches(matcher)); + } + { + // the most concrete matcher + // it also should not match any less concrete Activity + Activity matcher{ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "pp" }; + + // should match only the same but with equal or contained validity + CHECK(matcher.matches({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "pp" })); + CHECK(matcher.matches({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 5, 7 }, "pp" })); + CHECK(matcher.matches({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 5, 15 }, "pp" })); // we support this until we indicate correct validity of our objects + CHECK(!matcher.matches({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 15, 25 }, "pp" })); + + // should not match if any other parameter is different + CHECK(!matcher.matches({ 300001, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "pp" })); + CHECK(!matcher.matches({ 300000, "TECHNICAL", "LHC22a", "spass", "qc", { 1, 10 }, "pp" })); + CHECK(!matcher.matches({ 300000, "PHYSICS", "LHC22b", "apass", "qc", { 1, 10 }, "pp" })); + CHECK(!matcher.matches({ 300000, "PHYSICS", "LHC22a", "spass", "qc_mc", { 1, 10 }, "pp" })); + CHECK(!matcher.matches({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "PbPb" })); + + // should not match any less concrete field + CHECK(!matcher.matches({ 0, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 } })); + CHECK(!matcher.matches({ 300000, "NONE", "LHC22a", "spass", "qc", { 1, 10 } })); + CHECK(!matcher.matches({ 300000, "PHYSICS", "", "spass", "qc", { 1, 10 } })); + CHECK(!matcher.matches({ 300000, "PHYSICS", "LHC22a", "", "qc", { 1, 10 } })); + CHECK(!matcher.matches({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 0, 1000000 } })); + CHECK(!matcher.matches({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "" })); + } +} + +TEST_CASE("test_same") +{ + { + // Activity::same should return true if the other one is has the same field, but the validity can be different + Activity activity{ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 } }; + + CHECK(activity.same({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 } })); + CHECK(activity.same({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 2, 5 } })); + CHECK(activity.same({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 432, 54334 } })); + + CHECK(!activity.same({ 300001, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 } })); + CHECK(!activity.same({ 300000, "TECHNICAL", "LHC22a", "spass", "qc", { 1, 10 } })); + CHECK(!activity.same({ 300000, "PHYSICS", "LHC22b", "spass", "qc", { 1, 10 } })); + CHECK(!activity.same({ 300000, "PHYSICS", "LHC22a", "apass", "qc", { 1, 10 } })); + CHECK(!activity.same({ 300000, "PHYSICS", "LHC22a", "spass", "qc_mc", { 1, 10 } })); + } +} \ No newline at end of file diff --git a/Framework/test/testActivityHelpers.cxx b/Framework/test/testActivityHelpers.cxx new file mode 100644 index 0000000000..31b1f8f883 --- /dev/null +++ b/Framework/test/testActivityHelpers.cxx @@ -0,0 +1,271 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testActivityHelpers.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/ActivityHelpers.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; + +// The code below includes parts generated by AI +TEST_CASE("activity_helpers_asDatabaseMetadata") +{ + + SECTION("Default values") + { + Activity defaultActivity; + + auto metadata = activity_helpers::asDatabaseMetadata(defaultActivity, true); + REQUIRE(metadata[metadata_keys::runType] == "NONE"); + REQUIRE(metadata[metadata_keys::runNumber] == "0"); + REQUIRE(metadata[metadata_keys::passName].empty()); + REQUIRE(metadata[metadata_keys::periodName].empty()); + } + + SECTION("Non-default values") + { + Activity customActivity; + customActivity.mType = "TECHNICAL"; + customActivity.mId = 3; + customActivity.mPassName = "pass"; + customActivity.mPeriodName = "period"; + + auto metadata = activity_helpers::asDatabaseMetadata(customActivity, false); + CHECK(metadata[metadata_keys::runType] == "TECHNICAL"); + CHECK(metadata[metadata_keys::runNumber] == "3"); + CHECK(metadata[metadata_keys::passName] == "pass"); + CHECK(metadata[metadata_keys::periodName] == "period"); + } + + SECTION("Default values not stored when putDefault is false") + { + Activity defaultActivity; + + auto metadata = activity_helpers::asDatabaseMetadata(defaultActivity, false); + REQUIRE(metadata.empty()); + } +} + +TEST_CASE("activity_helpers_asActivity") +{ + + SECTION("Empty metadata map") + { + std::map emptyMetadata; + + Activity activity = activity_helpers::asActivity(emptyMetadata, "test_provenance"); + + CHECK(activity.mType == "NONE"); + CHECK(activity.mId == 0); + CHECK(activity.mPassName.empty()); + CHECK(activity.mPeriodName.empty()); + CHECK(activity.mProvenance == "test_provenance"); + } + + SECTION("Non-empty metadata map") + { + std::map metadata = { + { metadata_keys::runType, "TECHNICAL" }, + { metadata_keys::runNumber, "3" }, + { metadata_keys::passName, "pass" }, + { metadata_keys::periodName, "period" }, + { metadata_keys::validFrom, "1000000" }, + { metadata_keys::validUntil, "2000000" } + }; + + Activity activity = activity_helpers::asActivity(metadata, "test_provenance"); + + CHECK(activity.mType == "TECHNICAL"); + CHECK(activity.mId == 3); + CHECK(activity.mPassName == "pass"); + CHECK(activity.mPeriodName == "period"); + CHECK(activity.mValidity.getMin() == 1000000); + CHECK(activity.mValidity.getMax() == 2000000); + CHECK(activity.mProvenance == "test_provenance"); + } +} + +TEST_CASE("activity_helpers_asActivityPtree") +{ + + SECTION("Empty property tree") + { + boost::property_tree::ptree emptyTree; + + Activity activity = activity_helpers::asActivity(emptyTree, "test_provenance"); + + CHECK(activity.mType == "NONE"); + CHECK(activity.mId == 0); + CHECK(activity.mPassName.empty()); + CHECK(activity.mPeriodName.empty()); + CHECK(activity.mProvenance == "test_provenance"); + } + + SECTION("Non-empty property tree") + { + boost::property_tree::ptree tree; + tree.put(metadata_keys::runType, "TECHNICAL"); + tree.put(metadata_keys::runNumber, 3); + tree.put(metadata_keys::passName, "pass"); + tree.put(metadata_keys::periodName, "period"); + tree.put(metadata_keys::validFrom, 1000000); + tree.put(metadata_keys::validUntil, 2000000); + + Activity activity = activity_helpers::asActivity(tree, "test_provenance"); + + CHECK(activity.mType == "TECHNICAL"); + CHECK(activity.mId == 3); + CHECK(activity.mPassName == "pass"); + CHECK(activity.mPeriodName == "period"); + CHECK(activity.mValidity.getMin() == 1000000); + CHECK(activity.mValidity.getMax() == 2000000); + CHECK(activity.mProvenance == "test_provenance"); + } +} + +TEST_CASE("test_strictestMatchingActivity") +{ + { + // transforming the range + everything being the same except the validity + std::map m{ + { 1, { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "pp" } }, + { 2, { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 10, 20 }, "pp" } }, + { 4, { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 20, 30 }, "pp" } }, + { 3, { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 30, 40 }, "pp" } } + }; + auto result = activity_helpers::strictestMatchingActivity(m | std::views::transform([](const auto& item) { return item.second; })); + Activity expectation{ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 40 }, "pp" }; + CHECK(result == expectation); + } + { + // providing a vector (no transformation) + different run numbers and validities + std::vector m{ + { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "pp" }, + { 300001, "PHYSICS", "LHC22a", "spass", "qc", { 20, 30 }, "pp" } + }; + auto result = activity_helpers::strictestMatchingActivity(m); + Activity expectation{ 0, "PHYSICS", "LHC22a", "spass", "qc", { 1, 30 }, "pp" }; + CHECK(result == expectation); + } + { + // providing a vector (naive view transformation) + different everything + std::vector m{ + { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "pp" }, + { 300001, "TECHNICAL", "LHC22b", "apass2", "qc_mc", { 20, 30 }, "PbPb" } + }; + auto result = activity_helpers::strictestMatchingActivity(m | std::views::transform([](const auto& a) { return a; })); + Activity expectation{ 0, "NONE", "", "", "qc", { 1, 30 }, "" }; + CHECK(result == expectation); + } + { + // just one + std::vector m{ + { 300000, "TECHNICAL", "LHC22a", "spass", "qc", { 1, 10 }, "pp" } + }; + auto result = activity_helpers::strictestMatchingActivity(m); + Activity expectation{ 300000, "TECHNICAL", "LHC22a", "spass", "qc", { 1, 10 }, "pp" }; + CHECK(result == expectation); + } + { + // none + std::vector m{}; + auto result = activity_helpers::strictestMatchingActivity(m); + Activity expectation{}; + CHECK(result == expectation); + } +} + +TEST_CASE("test_overlappingActivity") +{ + // it's like strictestMatchingActivity, but validity is a union + { + // transforming the range + everything being the same except the validity + std::map m{ + { 1, { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 40 }, "pp" } }, + { 2, { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 10, 20 }, "pp" } }, + { 4, { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 15, 30 }, "pp" } }, + { 3, { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 17, 40 }, "pp" } } + }; + auto result = activity_helpers::overlappingActivity(m | std::views::transform([](const auto& item) { return item.second; })); + Activity expectation{ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 17, 20 }, "pp" }; + CHECK(result == expectation); + } + { + // providing a vector (no transformation) + different run numbers and validities + std::vector m{ + { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "pp" }, + { 300001, "PHYSICS", "LHC22a", "spass", "qc", { 10, 30 }, "pp" } + }; + auto result = activity_helpers::overlappingActivity(m); + Activity expectation{ 0, "PHYSICS", "LHC22a", "spass", "qc", { 10, 10 }, "pp" }; + CHECK(result == expectation); + } + { + // providing a vector (naive view transformation) + different everything + std::vector m{ + { 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "pp" }, + { 300001, "TECHNICAL", "LHC22b", "apass2", "qc_mc", { 20, 30 }, "PbPb" } + }; + auto result = activity_helpers::overlappingActivity(m | std::views::transform([](const auto& a) { return a; })); + Activity expectation{ 0, "NONE", "", "", "qc", { 20, 10 }, "" }; // invalid validity + CHECK(result == expectation); + } + { + // just one + std::vector m{ + { 300000, "TECHNICAL", "LHC22a", "spass", "qc", { 1, 10 }, "pp" } + }; + auto result = activity_helpers::overlappingActivity(m); + Activity expectation{ 300000, "TECHNICAL", "LHC22a", "spass", "qc", { 1, 10 }, "pp" }; + CHECK(result == expectation); + } + { + // none + std::vector m{}; + auto result = activity_helpers::overlappingActivity(m); + Activity expectation{}; + CHECK(result == expectation); + } +} + +TEST_CASE("test_backward_compatible_activity_type") +{ + SECTION("metadata map") + { + std::map metadata = { + { metadata_keys::runType, "2" }, // technical + }; + + Activity activity = activity_helpers::asActivity(metadata, "test_provenance"); + + CHECK(activity.mType == "TECHNICAL"); + std::cout << "type " << activity << std::endl; + } + + SECTION("property tree") + { + boost::property_tree::ptree tree; + tree.put(metadata_keys::runType, "2"); + + Activity activity = activity_helpers::asActivity(tree, "test_provenance"); + + CHECK(activity.mType == "TECHNICAL"); + } +} diff --git a/Framework/test/testAggregatorInterface.cxx b/Framework/test/testAggregatorInterface.cxx new file mode 100644 index 0000000000..e71cc57f57 --- /dev/null +++ b/Framework/test/testAggregatorInterface.cxx @@ -0,0 +1,105 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testAggregatorInterface.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/AggregatorInterface.h" +#include +#include +#include + +using namespace o2::quality_control; +using namespace std; + +namespace o2::quality_control +{ + +using namespace core; + +namespace test +{ + +class SimpleTestAggregator : public checker::AggregatorInterface +{ + public: + /// Default constructor + SimpleTestAggregator() = default; + /// Destructor + ~SimpleTestAggregator() override = default; + + // Override interface + void configure() override + { + mValidString = "name"; + } + + // Returns a quality matching the number of quality objects passed as argument (1 good, 2 medium, 3 bad, otherwise null) + std::map aggregate(std::map>& qoMap) override + { + std::map result; + Quality q; + switch (qoMap.size()) { + case 1: + q = Quality::Good; + break; + case 2: + q = Quality::Medium; + break; + case 3: + q = Quality::Bad; + break; + default: + q = Quality::Null; + break; + } + result["asdf"] = q; + return result; + } + + string mValidString; +}; + +} /* namespace test */ +} /* namespace o2::quality_control */ + +TEST_CASE("test_invoke_all_methods") +{ + test::SimpleTestAggregator testAggregator; + + // prepare data + std::shared_ptr qo_null = make_shared(Quality::Null, "testCheckNull", "TST"); + std::shared_ptr qo_good = make_shared(Quality::Good, "testCheckGood", "TST"); + std::shared_ptr qo_medium = make_shared(Quality::Medium, "testCheckMedium", "TST"); + std::shared_ptr qo_bad = make_shared(Quality::Bad, "testCheckBad", "TST"); + QualityObjectsMapType input; + + std::map result1 = testAggregator.aggregate(input); + CHECK(result1.size() == 1); + CHECK(result1["asdf"] == Quality::Null); // because empty vector passed + + input[qo_good->getName()] = qo_good; + std::map result2 = testAggregator.aggregate(input); + CHECK(result2.size() == 1); + CHECK(result2["asdf"] == Quality::Good); + + input[qo_medium->getName()] = qo_medium; + std::map result3 = testAggregator.aggregate(input); + CHECK(result3.size() == 1); + CHECK(result3["asdf"] == Quality::Medium); + + input[qo_bad->getName()] = qo_bad; + std::map result4 = testAggregator.aggregate(input); + CHECK(result4.size() == 1); + CHECK(result4["asdf"] == Quality::Bad); +} diff --git a/Framework/test/testAggregatorRunner.cxx b/Framework/test/testAggregatorRunner.cxx new file mode 100644 index 0000000000..91a5a62948 --- /dev/null +++ b/Framework/test/testAggregatorRunner.cxx @@ -0,0 +1,221 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testAggregatorRunner.cxx +/// \author Barthelemy von Haller +/// + +#include "getTestDataDirectory.h" +#include "QualityControl/AggregatorRunnerFactory.h" +#include "QualityControl/AggregatorRunner.h" +#include "QualityControl/AggregatorRunnerConfig.h" +#include "QualityControl/AggregatorConfig.h" +#include "QualityControl/Aggregator.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/InfrastructureSpecReader.h" +#include +#include +#include +#include +#include + +using namespace o2::quality_control::checker; +using namespace std; +using namespace o2::framework; +using namespace o2::configuration; +using namespace o2::quality_control::core; + +std::pair> getAggregatorConfigs(const std::string& configFilePath) +{ + auto config = ConfigurationFactory::getConfiguration(configFilePath); + auto infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(config->getRecursive(), WorkflowType::Standalone); + std::vector aggregatorConfigs; + for (const auto& aggregatorSpec : infrastructureSpec.aggregators) { + if (aggregatorSpec.active) { + aggregatorConfigs.emplace_back(Aggregator::extractConfig(infrastructureSpec.common, aggregatorSpec)); + } + } + auto aggregatorRunnerConfig = AggregatorRunnerFactory::extractRunnerConfig(infrastructureSpec.common); + + return { aggregatorRunnerConfig, aggregatorConfigs }; +} + +TEST_CASE("test_aggregator_runner") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto [aggregatorRunnerConfig, aggregatorConfigs] = getAggregatorConfigs(configFilePath); + AggregatorRunner aggregatorRunner{ aggregatorRunnerConfig, aggregatorConfigs }; + + Options options{ + { "runNumber", VariantType::String, { "Run number" } }, + { "qcConfiguration", VariantType::Dict, emptyDict(), { "Some dictionary configuration" } } + }; + std::vector> retr; + std::unique_ptr store = make_unique(std::move(options), std::move(retr)); + ConfigParamRegistry cfReg(std::move(store)); + ServiceRegistry sReg; + InitContext initContext{ cfReg, sReg }; + aggregatorRunner.init(initContext); + + CHECK(aggregatorRunner.getDeviceName() == "qc-aggregator"); + + // check the reordering + const std::vector> aggregators = aggregatorRunner.getAggregators(); + REQUIRE(aggregators.size() == 4); + CHECK((aggregators.at(0)->getName() == "MyAggregatorC" || aggregators.at(0)->getName() == "MyAggregatorB")); + CHECK((aggregators.at(1)->getName() == "MyAggregatorC" || aggregators.at(1)->getName() == "MyAggregatorB")); + CHECK(aggregators.at(2)->getName() == "MyAggregatorA"); + CHECK(aggregators.at(3)->getName() == "MyAggregatorD"); +} + +Quality getQualityForCheck(QualityObjectsType qos, string checkName) +{ + auto it = find_if(qos.begin(), qos.end(), [&checkName](const shared_ptr obj) { return obj->getCheckName() == checkName; }); + if (it != qos.end()) { + return (*it)->getQuality(); + } else { + return Quality::Null; + } +} + +TEST_CASE("test_aggregator_onAnyNonZero") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto [aggregatorRunnerConfig, aggregatorConfigs] = getAggregatorConfigs(configFilePath); + auto MyAggregatorBConfig = std::find_if(aggregatorConfigs.begin(), aggregatorConfigs.end(), [](const auto& cfg) { return cfg.name == "MyAggregatorB"; }); + REQUIRE(MyAggregatorBConfig != aggregatorConfigs.end()); + auto aggregator = make_shared(*MyAggregatorBConfig); + aggregator->init(); + + QualityObjectsMapType qoMap; + qoMap["checkAll"] = make_shared(Quality::Good, "checkAll"); + QualityObjectsType result = aggregator->aggregate(qoMap); + CHECK(getQualityForCheck(result, "MyAggregatorB/newQuality") == Quality::Good); + + qoMap["dataSizeCheck2/skeletonTask/example"] = make_shared(Quality::Bad, "dataSizeCheck2/skeletonTask/example"); + result = aggregator->aggregate(qoMap); + CHECK(getQualityForCheck(result, "MyAggregatorB/newQuality") == Quality::Bad); +} + +TEST_CASE("test_aggregator_quality_filter") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto [aggregatorRunnerConfig, aggregatorConfigs] = getAggregatorConfigs(configFilePath); + auto myAggregatorBConfig = std::find_if(aggregatorConfigs.begin(), aggregatorConfigs.end(), [](const auto& cfg) { return cfg.name == "MyAggregatorB"; }); + REQUIRE(myAggregatorBConfig != aggregatorConfigs.end()); + auto aggregator = make_shared(*myAggregatorBConfig); + aggregator->init(); + + // empty list -> Good + QualityObjectsMapType qoMap; + QualityObjectsType result = aggregator->aggregate(qoMap); + CHECK(getQualityForCheck(result, "MyAggregatorB/newQuality") == Quality::Good); + + // Add dataSizeCheck1/q1=good and checkall -> return medium + qoMap["dataSizeCheck1/q1"] = make_shared(Quality::Good, "dataSizeCheck1/q1"); + qoMap["checkAll/"] = make_shared(Quality::Medium, "checkAll/"); + result = aggregator->aggregate(qoMap); + CHECK(getQualityForCheck(result, "MyAggregatorB/newQuality") == Quality::Medium); + + // Add whatever/q1=bad -> return medium because it is filtered out (not in config file) + qoMap["whatever/q1"] = make_shared(Quality::Bad, "whatever/q1"); + result = aggregator->aggregate(qoMap); + CHECK(getQualityForCheck(result, "MyAggregatorB/newQuality") == Quality::Medium); + + // Add someNumbersCheck/example=bad return bad + qoMap["dataSizeCheck2/skeletonTask/example"] = make_shared(Quality::Bad, "dataSizeCheck2/skeletonTask/example"); + result = aggregator->aggregate(qoMap); + CHECK(getQualityForCheck(result, "MyAggregatorB/newQuality") == Quality::Bad); + + // reset and add dataSizeCheck/q1=good and dataSizeCheck/q2=medium and someNumbersCheck/example=medium and someNumbersCheck/whatever=bad + // Return medium because someNumbersTask/whatever is filtered out + qoMap.clear(); + qoMap["dataSizeCheck1/q1"] = make_shared(Quality::Good, "dataSizeCheck1/q1"); + qoMap["dataSizeCheck1/q2"] = make_shared(Quality::Medium, "dataSizeCheck1/q2"); + qoMap["dataSizeCheck2/skeletonTask/example"] = make_shared(Quality::Medium, "dataSizeCheck2/skeletonTask/example"); + qoMap["dataSizeCheck2/skeletonTask/example2"] = make_shared(Quality::Bad, "dataSizeCheck2/skeletonTask/example2"); + result = aggregator->aggregate(qoMap); + CHECK(getQualityForCheck(result, "MyAggregatorB/newQuality") == Quality::Medium); +} + +TEST_CASE("test_getDetector") +{ + AggregatorConfig config; + config.detectorName = "TST"; + + std::vector> aggregators; + CHECK(AggregatorRunner::getDetectorName(aggregators) == ""); + auto checkTST = std::make_shared(config); + aggregators.push_back(checkTST); + CHECK(AggregatorRunner::getDetectorName(aggregators) == "TST"); + auto checkTST2 = std::make_shared(config); + aggregators.push_back(checkTST2); + CHECK(AggregatorRunner::getDetectorName(aggregators) == "TST"); + config.detectorName = "EMC"; + auto checkEMC = std::make_shared(config); + aggregators.push_back(checkEMC); + CHECK(AggregatorRunner::getDetectorName(aggregators) == "MANY"); +} + +TEST_CASE("test_aggregator_activity_propagation") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto [aggregatorRunnerConfig, aggregatorConfigs] = getAggregatorConfigs(configFilePath); + auto myAggregatorCConfig = std::find_if(aggregatorConfigs.begin(), aggregatorConfigs.end(), [](const auto& cfg) { return cfg.name == "MyAggregatorC"; }); + REQUIRE(myAggregatorCConfig != aggregatorConfigs.end()); + auto aggregator = make_shared(*myAggregatorCConfig); + aggregator->init(); + Activity defaultActivity{ 123, "PHYSICS", "LHC34b", "apass4", "qc", { 34, 54 }, "proton - mouton" }; + + // empty list -> Good + QualityObjectsMapType qoMap; + QualityObjectsType result = aggregator->aggregate(qoMap, defaultActivity); + REQUIRE(result.size() == 2); + CHECK(result[0]->getActivity() == defaultActivity); + CHECK(result[1]->getActivity() == defaultActivity); + + // Add dataSizeCheck1/q1=good and dataSizeCheck1/q2=medium -> return medium + auto qo1 = make_shared(Quality::Good, "dataSizeCheck"); + qo1->setActivity({ 123, "PHYSICS", "LHC34b", "apass4", "qc", { 100, 200 }, "proton - mouton" }); + auto qo2 = make_shared(Quality::Medium, "someNumbersCheck"); + qo2->setActivity({ 123, "PHYSICS", "LHC34b", "apass4", "qc", { 125, 175 }, "proton - mouton" }); + qoMap["dataSizeCheck"] = qo1; + qoMap["someNumbersCheck"] = qo2; + + result = aggregator->aggregate(qoMap); + REQUIRE(result.size() == 2); + CHECK(result[0]->getActivity() == Activity{ 123, "PHYSICS", "LHC34b", "apass4", "qc", { 125, 175 }, "proton - mouton" }); + CHECK(result[1]->getActivity() == Activity{ 123, "PHYSICS", "LHC34b", "apass4", "qc", { 125, 175 }, "proton - mouton" }); +} + +TEST_CASE("test_aggregator_cycle") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto [aggregatorRunnerConfig, aggregatorConfigs] = getAggregatorConfigs(configFilePath); + auto MyAggregatorBConfig = std::find_if(aggregatorConfigs.begin(), aggregatorConfigs.end(), [](const auto& cfg) { return cfg.name == "MyAggregatorB"; }); + REQUIRE(MyAggregatorBConfig != aggregatorConfigs.end()); + auto aggregator = make_shared(*MyAggregatorBConfig); + aggregator->init(); + + QualityObjectsMapType qoMap; + auto qo1 = make_shared(Quality::Good, "checkAll"); + qoMap["checkAll"] = qo1; + qo1->addMetadata(o2::quality_control::repository::metadata_keys::cycleNumber, "1"); + auto qo2 = make_shared(Quality::Bad, "dataSizeCheck2/skeletonTask/example"); + qo2->addMetadata(o2::quality_control::repository::metadata_keys::cycleNumber, "2"); + qoMap["dataSizeCheck2/skeletonTask/example"] = qo2; + auto results = aggregator->aggregate(qoMap); + for (auto r : results) { + REQUIRE_NOTHROW(r->getMetadata(o2::quality_control::repository::metadata_keys::cycleNumber)); + CHECK(r->getMetadata(o2::quality_control::repository::metadata_keys::cycleNumber) == "2"); + } +} diff --git a/Framework/test/testBookkeepingQualitySink.cxx b/Framework/test/testBookkeepingQualitySink.cxx new file mode 100644 index 0000000000..2862d6627d --- /dev/null +++ b/Framework/test/testBookkeepingQualitySink.cxx @@ -0,0 +1,114 @@ +// Copyright 2024 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testBookkeepingQualitySink.cxx +/// \author Michal Tichak +/// + +#include +#include "QualityControl/BookkeepingQualitySink.h" +#include "QualityControl/InfrastructureGenerator.h" + +using namespace o2; +using namespace o2::framework; + +void customize(std::vector& policies) +{ + quality_control::customizeInfrastructure(policies); +} + +#include +#include +#include "QualityControl/QualityObject.h" +#include "QualityControl/Quality.h" + +void compareFatal(const quality_control::QualityControlFlag& got, const quality_control::QualityControlFlag& expected) +{ + if (got != expected) { + LOG(fatal) << "flags in test do not match. expected:\n" + << expected << "\nreceived\n" + << got; + } +} + +WorkflowSpec defineDataProcessing(ConfigContext const&) +{ + using namespace quality_control; + + WorkflowSpec specs; + + DataProcessorSpec writer{ + "writer", + Inputs{}, + Outputs{ { { "tst-qo" }, "TST", "DATA" } }, + AlgorithmSpec{ [](ProcessingContext& ctx) { + auto obj = std::make_unique(core::Quality::Null, "testCheckNull", "TST"); + obj->getActivity().mValidity = core::ValidityInterval{ 10, 500 }; + obj->addFlag(FlagTypeFactory::Good(), "I am comment"); + ctx.outputs().snapshot(Output{ "TST", "DATA", 0 }, *obj); + ctx.outputs().make(Output{ "TST", "DATA", 0 }, 1); + ctx.services().get().endOfStream(); + } } + + }; + + specs.push_back(writer); + + DataProcessorSpec reader{ + "bookkeepingSink", + Inputs{ { { "tst-qo" }, "TST", "DATA" } }, + Outputs{}, + adaptFromTask( + "grpcUri", core::Provenance::SyncQC, + [](const std::string&, const core::BookkeepingQualitySink::FlagsMap& flagsMap, core::Provenance) { + if (!flagsMap.contains("TST")) { + LOG(fatal) << "no flag collections for detector TST"; + return; + } + const auto& flagsCollectionsTST = flagsMap.at("TST"); + if (!flagsCollectionsTST.contains("testCheckNull")) { + LOG(fatal) << "no flag collections for QO testCheckNull"; + return; + } + const auto& flagConverter = flagsCollectionsTST.at("testCheckNull"); + if (flagConverter == nullptr) { + LOG(fatal) << "nullptr flag collection for QO testCheckNull"; + return; + } + const auto flagsCollection = flagConverter->getResult(); + if (flagsCollection == nullptr) { + LOG(fatal) << "nullptr flag collection for QO testCheckNull"; + return; + } + + for (size_t i = 0; const auto& flag : *flagsCollection) { + switch (i) { + case 0: + compareFatal(flag, QualityControlFlag{ core::gFullValidityInterval.getMin(), 10, FlagTypeFactory::UnknownQuality(), "Did not receive a Quality Object which covers this period", "qc/TST/QO/testCheckNull" }); + break; + case 1: + compareFatal(flag, QualityControlFlag{ 10, 500, FlagTypeFactory::Good(), "I am comment", "qc/TST/QO/testCheckNull" }); + break; + case 2: + compareFatal(flag, QualityControlFlag{ 500, core::gFullValidityInterval.getMax(), FlagTypeFactory::UnknownQuality(), "Did not receive a Quality Object which covers this period", "qc/TST/QO/testCheckNull" }); + break; + default: + LOG(fatal) << "More Flags received than expected"; + } + ++i; + } + }) + }; + + specs.push_back(reader); + return specs; +} diff --git a/Framework/test/testCcdbDatabase.cxx b/Framework/test/testCcdbDatabase.cxx new file mode 100644 index 0000000000..707e5ab301 --- /dev/null +++ b/Framework/test/testCcdbDatabase.cxx @@ -0,0 +1,463 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCcdbDatabase.cxx +/// \author Adam Wegrzynek +/// \author Barthelemy von Haller +/// + +#include "QualityControl/Activity.h" +#include "QualityControl/CcdbDatabase.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Version.h" + +#define BOOST_TEST_MODULE CcdbDatabase test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include +#include +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/ActivityHelpers.h" +#include +#include +#include +#include + +namespace utf = boost::unit_test; + +namespace o2::quality_control::core +{ + +namespace +{ + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; +using namespace std; + +const std::string CCDB_ENDPOINT = "ccdb-test.cern.ch:8080"; + +/** + * Fixture for the tests, i.e. code is ran in every test that uses it, i.e. it is like a setup and teardown for tests. + */ +struct test_fixture { + test_fixture() + { + backend = std::make_unique(); + backend->connect(CCDB_ENDPOINT, "", "", ""); + pid = std::to_string(getpid()); + taskName = "Test/pid" + pid; + ILOG(Info, Support) << "*** " << boost::unit_test::framework::current_test_case().p_name << " (" << pid + << ") ***" << ENDM; + } + + ~test_fixture() = default; + + // shorthands to get the paths to the objects and their containing folder + std::string getMoPath(const string& objectName, const string& provenance = "qc", bool includeProvenance = true) const + { + return RepoPathUtils::getMoPath(detector, taskName, objectName, provenance, includeProvenance); + } + std::string getQoPath(const string& checkName, const string& provenance = "qc", bool includeProvenance = true) const + { + return RepoPathUtils::getQoPath(detector, taskName + "/" + checkName, "", {}, provenance, includeProvenance); + } + std::string getMoFolder(const string& objectName) const + { + // we extract the path after the provenance and before the object name + string fullMoPath = getMoPath(objectName, "", false); + return fullMoPath.substr(0, fullMoPath.find_last_of('/')); + } + std::string getQoFolder(const string& checkName) const + { + // we extract the path after the provenance and before the object name + string fullQoPath = getQoPath(checkName, "", false); + return fullQoPath.substr(0, fullQoPath.find_last_of('/')); + } + std::unique_ptr backend; + map metadata; + std::string pid; + const std::string detector = "TST"; + std::string taskName; +}; + +struct MyGlobalFixture { + void teardown() + { + std::unique_ptr backend = std::make_unique(); + backend->connect(CCDB_ENDPOINT, "", "", ""); + // cannot use the test_fixture because we are tearing down + backend->truncate("qc/TST/MO/Test/pid" + std::to_string(getpid()), "*"); + backend->truncate("qc/TST/QO/Test/pid" + std::to_string(getpid()), "*"); + backend->truncate("qc/TST/QCFC/Test_pid" + std::to_string(getpid()), "*"); + backend->truncate("qc_hello/TST/MO/Test/pid" + std::to_string(getpid()), "*"); + backend->truncate("qc_hello/TST/QO/Test/pid" + std::to_string(getpid()), "*"); + } +}; +BOOST_TEST_GLOBAL_FIXTURE(MyGlobalFixture); + +long oldTimestamp; + +BOOST_AUTO_TEST_CASE(ccdb_store) +{ + test_fixture f; + + TH1F* h1 = new TH1F("quarantine", "asdf", 100, 0, 99); + h1->FillRandom("gaus", 10000); + shared_ptr mo1 = make_shared(h1, f.taskName, "TestClass", "TST"); + mo1->updateActivity(1234, "LHC66", "passName1", "qc"); + + TH1F* h2 = new TH1F("metadata", "asdf", 100, 0, 99); + shared_ptr mo2 = make_shared(h2, f.taskName, "TestClass", "TST"); + mo2->addMetadata("my_meta", "is_good"); + + TH1F* h3 = new TH1F("short", "asdf", 100, 0, 99); + shared_ptr mo3 = make_shared(h3, f.taskName, "TestClass", "TST"); + + TH1F* h4 = new TH1F("provenance", "asdf", 100, 0, 99); + shared_ptr mo4 = make_shared(h4, f.taskName, "TestClass", "TST"); + mo4->updateActivity(1234, "LHC66", "passName1", "qc_hello"); + + TH1F* h5 = new TH1F("cycle", "asdf", 100, 0, 99); + shared_ptr mo5 = make_shared(h5, f.taskName, "TestClass", "TST"); + mo5->addMetadata(metadata_keys::cycleNumber, "1"); + mo5->setValidity({ 10000, 20000 }); + mo5->updateActivity(1234, "LHC66", "passName1", "qc"); + + TH1F* h6 = new TH1F("cycle", "asdf", 100, 0, 99); + shared_ptr mo6 = make_shared(h6, f.taskName, "TestClass", "TST"); + mo6->addMetadata(metadata_keys::cycleNumber, "2"); + mo6->setValidity({ 10000, 20000 }); + mo6->updateActivity(1234, "LHC66", "passName1", "qc"); + + shared_ptr qo1 = make_shared(Quality::Bad, f.taskName + "/test-ccdb-check", "TST", "OnAll", vector{ string("input1"), string("input2") }); + qo1->updateActivity(1234, "LHC66", "passName1", "qc"); + shared_ptr qo2 = make_shared(Quality::Null, f.taskName + "/metadata", "TST", "OnAll", vector{ string("input1") }); + qo2->addMetadata("my_meta", "is_good"); + shared_ptr qo3 = make_shared(Quality::Good, f.taskName + "/short", "TST", "OnAll", vector{ string("input1") }); + shared_ptr qo4 = make_shared(Quality::Good, f.taskName + "/provenance", "TST", "OnAll", vector{ string("input1") }); + qo4->updateActivity(0, "", "", "qc_hello"); + + oldTimestamp = CcdbDatabase::getCurrentTimestamp(); + f.backend->storeMO(mo1); + f.backend->storeMO(mo2); + f.backend->storeMO(mo4); + f.backend->storeMO(mo5); + f.backend->storeMO(mo6); + + f.backend->storeQO(qo1); + f.backend->storeQO(qo2); + f.backend->storeQO(qo4); + + // with timestamps + mo3->setValidity({ 10000, 20000 }); + qo3->setValidity({ 10000, 20000 }); + f.backend->storeMO(mo3); + f.backend->storeQO(qo3); + + // test the max size + f.backend->setMaxObjectSize(1); + f.backend->storeMO(mo3); // should fail +} + +BOOST_AUTO_TEST_CASE(ccdb_store_for_future_tests) +{ + // this test is storing a version of the objects in a different directory. + // The goal is to keep old versions of the objects, in old formats, for future backward compatibility testing. + test_fixture f; + + TH1F* h1 = new TH1F("to_be_kept", "asdf", 100, 0, 99); + h1->FillRandom("gaus", 12345); + shared_ptr mo1 = make_shared(h1, "TestClass", "task", "TST_KEEP"); + mo1->addMetadata(metadata_keys::runNumber, o2::quality_control::core::Version::GetQcVersion().getString()); + shared_ptr qo1 = make_shared(Quality::Bad, "check", "TST_KEEP", "OnAll", vector{ string("input1"), string("input2") }); + qo1->addMetadata(metadata_keys::runNumber, o2::quality_control::core::Version::GetQcVersion().getString()); + + f.backend->storeMO(mo1); + f.backend->storeQO(qo1); +} + +BOOST_AUTO_TEST_CASE(ccdb_retrieve_mo, *utf::depends_on("ccdb_store")) +{ + test_fixture f; + std::shared_ptr mo = f.backend->retrieveMO(f.getMoFolder("quarantine"), "quarantine"); + BOOST_REQUIRE_NE(mo, nullptr); + BOOST_CHECK_EQUAL(mo->getName(), "quarantine"); + BOOST_CHECK_EQUAL(mo->getActivity().mId, 1234); + BOOST_CHECK_EQUAL(mo->getActivity().mPeriodName, "LHC66"); + BOOST_CHECK_EQUAL(mo->getActivity().mPassName, "passName1"); + BOOST_CHECK_EQUAL(mo->getActivity().mProvenance, "qc"); +} + +BOOST_AUTO_TEST_CASE(ccdb_retrieve_timestamps, *utf::depends_on("ccdb_store")) +{ + test_fixture f; + + std::shared_ptr mo = f.backend->retrieveMO(f.getMoFolder("short"), "short", 15000); + BOOST_REQUIRE_NE(mo, nullptr); + BOOST_CHECK_EQUAL(mo->getName(), "short"); + + std::shared_ptr qo = f.backend->retrieveQO(f.getQoPath("short", "", false), 15000); + BOOST_REQUIRE_NE(qo, nullptr); + BOOST_CHECK_EQUAL(qo->getName(), f.taskName + "/short"); + + auto qoValidity = f.backend->getLatestObjectValidity(f.getQoPath("short", "qc", true), {}); + ValidityInterval expectedValidity{ 10000, 20000 }; + BOOST_CHECK(qoValidity == expectedValidity); +} + +BOOST_AUTO_TEST_CASE(ccdb_retrieve_inexisting_mo) +{ + test_fixture f; + + std::shared_ptr mo = f.backend->retrieveMO("non/existing", "object"); + BOOST_CHECK(mo == nullptr); +} + +BOOST_AUTO_TEST_CASE(ccdb_retrieve_mo_with_cycle, *utf::depends_on("ccdb_store")) +{ + test_fixture f; + std::shared_ptr mo{}; + mo = f.backend->retrieveMO(f.getMoFolder("cycle"), "cycle", + 15000, Activity{}, { { metadata_keys::cycleNumber, "1" } }); + BOOST_REQUIRE(mo.get() != nullptr); + BOOST_REQUIRE_NO_THROW(mo->getMetadata(metadata_keys::cycleNumber)); + BOOST_REQUIRE(mo->getMetadata(metadata_keys::cycleNumber) == "1"); + + mo = f.backend->retrieveMO(f.getMoFolder("cycle"), "cycle", + 15000, Activity{}, { { metadata_keys::cycleNumber, "2" } }); + BOOST_REQUIRE(mo.get() != nullptr); + BOOST_REQUIRE_NO_THROW(mo->getMetadata(metadata_keys::cycleNumber)); + BOOST_REQUIRE(mo->getMetadata(metadata_keys::cycleNumber) == "2"); +} + +BOOST_AUTO_TEST_CASE(ccdb_retrieve_qo, *utf::depends_on("ccdb_store")) +{ + test_fixture f; + std::shared_ptr qo = f.backend->retrieveQO(RepoPathUtils::getQoPath("TST", f.taskName + "/test-ccdb-check", "", {}, "", false)); + BOOST_REQUIRE_NE(qo, nullptr); + Quality q = qo->getQuality(); + BOOST_CHECK_EQUAL(q.getLevel(), 3); + BOOST_CHECK_EQUAL(qo->getActivity().mId, 1234); + BOOST_CHECK_EQUAL(qo->getActivity().mPeriodName, "LHC66"); + BOOST_CHECK_EQUAL(qo->getActivity().mPassName, "passName1"); + BOOST_CHECK_EQUAL(qo->getActivity().mProvenance, "qc"); + + qo = f.backend->retrieveQO(RepoPathUtils::getQoPath("TST", f.taskName + "/metadata", "", {}, "", false), repository::CcdbDatabase::Timestamp::Current, {}, { { "my_meta", "is_good" } }); + BOOST_REQUIRE_NE(qo, nullptr); + BOOST_REQUIRE_NO_THROW(qo->getMetadata("my_meta")); + BOOST_CHECK_EQUAL(qo->getMetadata("my_meta"), "is_good"); + + qo = f.backend->retrieveQO(RepoPathUtils::getQoPath("TST", f.taskName + "/metadata", "", {}, "", false), repository::CcdbDatabase::Timestamp::Current, {}, { { "my_meta", "nonexistent" } }); + BOOST_REQUIRE_EQUAL(qo, nullptr); +} + +BOOST_AUTO_TEST_CASE(ccdb_provenance, *utf::depends_on("ccdb_store")) +{ + test_fixture f; + std::shared_ptr qo = f.backend->retrieveQO(RepoPathUtils::getQoPath("TST", f.taskName + "/provenance", "", {}, "", false), -1, { 0, "NONE", "", "", "qc_hello" }); + BOOST_REQUIRE_NE(qo, nullptr); + BOOST_CHECK_EQUAL(qo->getActivity().mProvenance, "qc_hello"); + + std::shared_ptr mo = f.backend->retrieveMO(f.getMoFolder("provenance"), "provenance", -1, { 0, "NONE", "", "", "qc_hello" }); + BOOST_REQUIRE_NE(mo, nullptr); + BOOST_CHECK_EQUAL(mo->getActivity().mProvenance, "qc_hello"); +} + +unique_ptr backendGlobal = std::make_unique(); + +void askObject(std::string objectPath) +{ + cout << "in askObject" << endl; + map metadata; + map headers; + auto json = backendGlobal->retrieveJson(objectPath, -1, metadata); + cout << "std::string::max_size(): " << std::string().max_size() << endl; + cout << "json string size: " << json.size() << endl; + cout << "object " << json.substr(10) << endl; + BOOST_CHECK(!json.empty()); + cout << "finished " << endl; +} + +BOOST_AUTO_TEST_CASE(ccdb_test_thread, *utf::depends_on("ccdb_store")) +{ + ROOT::EnableThreadSafety(); + string pid = std::to_string(getpid()); + string taskName = "Test/pid" + pid; + string objectPath = RepoPathUtils::getMoPath("TST", taskName, "quarantine"); + backendGlobal->connect(CCDB_ENDPOINT, "", "", ""); + int iterations = 10; + vector threads; + + for (int i = 0; i < iterations; i++) { + cout << "Asking for object, iteration " << i << endl; + threads.emplace_back(askObject, objectPath); + } + + for (std::thread& t : threads) { + t.join(); + } + threads.clear(); +} + +unique_ptr apiGlobal = std::make_unique(); + +void askObjectApi(std::string objectPath) +{ + cout << "in askObject" << endl; + map metadata; + map headers; + + auto* object = apiGlobal->retrieveFromTFileAny(objectPath, metadata, -1, &headers); + BOOST_CHECK(object != nullptr); + cout << "finished " << endl; +} + +BOOST_AUTO_TEST_CASE(ccdb_test_thread_api, *utf::depends_on("ccdb_store")) +{ + ROOT::EnableThreadSafety(); + string pid = std::to_string(getpid()); + string taskName = "Test/pid" + pid; + string objectPath = RepoPathUtils::getMoPath("TST", taskName, "quarantine"); + cout << "objectPath: " << objectPath << endl; + apiGlobal->init(CCDB_ENDPOINT); + int iterations = 10; + vector threads; + + for (int i = 0; i < iterations; i++) { + cout << "Asking for object, iteration " << i << endl; + threads.emplace_back(askObjectApi, objectPath); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + for (std::thread& t : threads) { + t.join(); + } + threads.clear(); +} + +BOOST_AUTO_TEST_CASE(ccdb_test_no_thread_api) +{ + unique_ptr api = std::make_unique(); + string ccdbUrl = "http://ccdb-test.cern.ch:8080"; + api->init(ccdbUrl); + cout << "ccdb url: " << ccdbUrl << endl; + bool hostReachable = api->isHostReachable(); + cout << "Is host reachable ? --> " << hostReachable << endl; + string objectPath = "qc/DAQ/MO/daqTask/UNKNOWN/sumRdhSizesPerInputRecord"; + int iterations = 3; + map metadata; + map headers; + + for (int i = 0; i < iterations; i++) { + cout << "Asking for object, iteration " << i << endl; + auto* object = api->retrieveFromTFileAny(objectPath, metadata); + cout << "object : " << object << endl; + } +} + +BOOST_AUTO_TEST_CASE(ccdb_metadata, *utf::depends_on("ccdb_store")) +{ + test_fixture f; + + std::string pathQuarantine = f.getMoPath("quarantine"); + std::string pathMetadata = f.getMoPath("metadata"); + std::string pathQuality = f.getQoPath("test-ccdb-check"); + std::string pathQualityMetadata = f.getQoPath("metadata"); + + std::map headers1; + std::map headers2; + TObject* obj1 = f.backend->retrieveTObject(pathQuarantine, f.metadata, -1, &headers1); + TObject* obj2 = f.backend->retrieveTObject(pathMetadata, f.metadata, -1, &headers2); + BOOST_CHECK_NE(obj1, nullptr); + BOOST_CHECK_NE(obj2, nullptr); + BOOST_CHECK(headers1.size() > 0); + BOOST_CHECK(headers2.size() > 1); + BOOST_CHECK_EQUAL(headers1.count("my_meta"), 0); + BOOST_CHECK_EQUAL(headers2.count("my_meta"), 1); + BOOST_CHECK_EQUAL(headers2.at("my_meta"), "is_good"); + + // get the path without the objectName because of the interface retrieveMO + auto obj1a = f.backend->retrieveMO(f.getMoFolder("quarantine"), "quarantine"); + auto obj2a = f.backend->retrieveMO(f.getQoFolder(""), "metadata"); + BOOST_REQUIRE_NE(obj1a, nullptr); + BOOST_REQUIRE_NE(obj2a, nullptr); + BOOST_CHECK(obj1a->getMetadataMap().size() > 0); + BOOST_CHECK(obj2a->getMetadataMap().size() > 1); + BOOST_CHECK_EQUAL(obj1a->getMetadataMap().count("my_meta"), 0); + BOOST_CHECK_EQUAL(obj2a->getMetadataMap().count("my_meta"), 1); + BOOST_CHECK_EQUAL(obj2a->getMetadataMap().at("my_meta"), "is_good"); + + auto obj3 = f.backend->retrieveQO(pathQuality.substr(3)); + auto obj4 = f.backend->retrieveQO(pathQualityMetadata.substr(3)); + BOOST_REQUIRE_NE(obj3, nullptr); + BOOST_REQUIRE_NE(obj4, nullptr); + BOOST_CHECK(obj3->getMetadataMap().size() > 0); + BOOST_CHECK(obj4->getMetadataMap().size() > 1); + BOOST_CHECK_EQUAL(obj3->getMetadataMap().count("my_meta"), 0); + BOOST_CHECK_EQUAL(obj4->getMetadataMap().count("my_meta"), 1); + BOOST_CHECK_EQUAL(obj4->getMetadataMap().at("my_meta"), "is_good"); +} + +BOOST_AUTO_TEST_CASE(ccdb_store_retrieve_any) +{ + test_fixture f; + + std::map meta; + TH1F* h1 = new TH1F("quarantine", "asdf", 100, 0, 99); + h1->FillRandom("gaus", 10000); + + f.backend->storeAny(h1, typeid(TH1F), f.getMoPath("storeAny"), meta, "TST", "testStoreAny"); + + meta.clear(); + void* rawResult = f.backend->retrieveAny(typeid(TH1F), f.getMoPath("storeAny"), meta); + auto h1_back = static_cast(rawResult); + BOOST_CHECK(rawResult != nullptr); + BOOST_CHECK(h1_back != nullptr); + BOOST_CHECK(h1_back->GetNbinsX() == 100); + BOOST_CHECK(h1_back->GetEntries() > 0); +} + +BOOST_AUTO_TEST_CASE(ccdb_store_retrieve_latest) +{ + test_fixture f; + + TH1F* h1 = new TH1F("latest_test", "latest_test", 100, 0, 99); + h1->FillRandom("gaus", 10000); + shared_ptr mo1 = make_shared(h1, f.taskName, "TestClass", "TST"); + mo1->updateActivity(1234, "LHC66", "passName1", "qc"); + mo1->setValidity({ 30, 50 }); + f.backend->storeMO(mo1); + + h1->FillRandom("gaus", 10000); + mo1->updateActivity(1234, "LHC66", "passName1", "qc"); + mo1->setValidity({ 10, 30 }); + f.backend->storeMO(mo1); // this is going to be the latest version matching the provided Activity + + h1->FillRandom("gaus", 10000); + mo1->updateActivity(1234, "LHC66", "differentPassName", "qc"); + mo1->setValidity({ 10, 30 }); + f.backend->storeMO(mo1); + + std::shared_ptr moBack = f.backend->retrieveMO(f.getMoFolder("latest_test"), "latest_test", DatabaseInterface::Timestamp::Latest, { 1234, "NONE", "LHC66", "passName1", "qc" }); + BOOST_REQUIRE(moBack != nullptr); + auto h1Back = dynamic_cast(moBack->getObject()); + BOOST_REQUIRE(h1Back != nullptr); + BOOST_CHECK_EQUAL(h1Back->GetEntries(), 20000); +} + +} // namespace +} // namespace o2::quality_control::core diff --git a/Framework/test/testCcdbDatabaseExtra.cxx b/Framework/test/testCcdbDatabaseExtra.cxx new file mode 100644 index 0000000000..25839cb79c --- /dev/null +++ b/Framework/test/testCcdbDatabaseExtra.cxx @@ -0,0 +1,139 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCcdbDatabaseExtra.cxx +/// \author Adam Wegrzynek +/// \author Barthelemy von Haller +/// + +#include "QualityControl/DatabaseFactory.h" +#include +#include "QualityControl/CcdbDatabase.h" +#include "QualityControl/QcInfoLogger.h" + +#define BOOST_TEST_MODULE CcdbDatabaseExtra test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include + +namespace utf = boost::unit_test; + +namespace o2::quality_control::core +{ + +namespace +{ + +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; +using namespace std; + +const std::string CCDB_ENDPOINT = "ccdb-test.cern.ch:8080"; +std::unordered_map Objects; + +/** + * Fixture for the tests, i.e. code is ran in every test that uses it, i.e. it is like a setup and teardown for tests. + */ +struct test_fixture { + test_fixture() + { + backend = DatabaseFactory::create("CCDB"); + backend->connect(CCDB_ENDPOINT, "", "", ""); + ILOG(Info, Support) << "*** " << boost::unit_test::framework::current_test_case().p_name << " ***" << ENDM; + } + + ~test_fixture() = default; + + std::unique_ptr backend; + map metadata; +}; + +// These tests should not be executed automatically, too much error prone. +// It depends on what is in the database. + +BOOST_AUTO_TEST_CASE(ccdb_retrieve_all) +{ + test_fixture f; + for (auto const& [task, object] : Objects) { + ILOG(Info, Support) << "[RETRIEVE]: " << task << object << ENDM; + auto mo = f.backend->retrieveMO(task, object); + if (mo == nullptr) { + ILOG(Info, Support) << "No object found (" << task << object << ")" << ENDM; + continue; + } + ILOG(Info, Support) << "name of encapsulated object : " << mo->getObject()->GetName() << ENDM; // just to test it + } +} + +long oldTimestamp; + +BOOST_AUTO_TEST_CASE(ccdb_store) +{ + test_fixture f; + + TH1F* h1 = new TH1F("asdf/asdf", "asdf", 100, 0, 99); + h1->FillRandom("gaus", 10000); + shared_ptr mo1 = make_shared(h1, "my/task", "TestClass", "TST"); + oldTimestamp = CcdbDatabase::getCurrentTimestamp(); + f.backend->storeMO(mo1); + + shared_ptr qo = make_shared(Quality::Bad, "checkName", "TST", "OnAll"); + f.backend->storeQO(qo); +} + +BOOST_AUTO_TEST_CASE(ccdb_retrieve_former_versions, *utf::depends_on("ccdb_store")) +{ + // store a new object + test_fixture f; + TH1F* h1 = new TH1F("asdf/asdf", "asdf", 100, 0, 99); + h1->FillRandom("gaus", 10001); + shared_ptr mo1 = make_shared(h1, "my/task", "TestClass", "TST"); + f.backend->storeMO(mo1); + + // Retrieve old object stored at timestampStorage + std::shared_ptr mo = f.backend->retrieveMO("TST/MO/my/task", "asdf/asdf", oldTimestamp); + BOOST_CHECK(mo); + TH1F* old = dynamic_cast(mo->getObject()); + BOOST_CHECK_NE(old, nullptr); + BOOST_CHECK_EQUAL(old->GetEntries(), 10000); + + // Retrieve latest object with timestamp + std::shared_ptr mo2 = f.backend->retrieveMO("TST/MO/my/task", "asdf/asdf", CcdbDatabase::getCurrentTimestamp()); + BOOST_CHECK(mo2); + TH1F* latest = dynamic_cast(mo2->getObject()); + BOOST_CHECK_NE(latest, nullptr); + BOOST_CHECK_EQUAL(latest->GetEntries(), 10001); + + // Retrieve latest object without timetsamp + std::shared_ptr mo3 = f.backend->retrieveMO("TST/MO/my/task", "asdf/asdf"); + BOOST_CHECK(mo3); + TH1F* latest2 = dynamic_cast(mo3->getObject()); + BOOST_CHECK_NE(latest2, nullptr); + BOOST_CHECK_EQUAL(latest2->GetEntries(), 10001); +} + +BOOST_AUTO_TEST_CASE(ccdb_getobjects_name) +{ + test_fixture f; + + CcdbDatabase* ccdb = static_cast(f.backend.get()); + ILOG(Info, Support) << "getListing()" << ENDM; + auto tasks = ccdb->getListing("/qc"); + BOOST_CHECK_GT(tasks.size(), 0); // we know that there are a few + // print but only for TST + auto objects = f.backend->getPublishedObjectNames("/qc/TST"); + BOOST_CHECK_GT(objects.size(), 0); +} +} // namespace +} // namespace o2::quality_control::core diff --git a/Framework/test/testCheck.cxx b/Framework/test/testCheck.cxx new file mode 100644 index 0000000000..fc68d5236c --- /dev/null +++ b/Framework/test/testCheck.cxx @@ -0,0 +1,175 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCheck.cxx +/// \author Rafal Pacholek +/// + +#include "QualityControl/CheckInterface.h" +#include "QualityControl/CheckRunnerFactory.h" +#include "QualityControl/CommonSpec.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/InfrastructureSpecReader.h" +#include "getTestDataDirectory.h" +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control::checker; +using namespace o2::quality_control::core; +using namespace std; +using namespace o2::framework; +using namespace o2::utilities; +using namespace o2::header; +using namespace o2::configuration; +using namespace AliceO2::Common; + +CheckConfig getCheckConfig(const std::string& configFilePath, const std::string& checkName) +{ + auto config = ConfigurationFactory::getConfiguration(configFilePath); + auto infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(config->getRecursive(), WorkflowType::Standalone); + + auto checkSpec = std::find_if(infrastructureSpec.checks.begin(), infrastructureSpec.checks.end(), + [&checkName](const auto& checkSpec) { + return checkSpec.checkName == checkName; + }); + if (checkSpec != infrastructureSpec.checks.end()) { + return Check::extractConfig(infrastructureSpec.common, *checkSpec); + } else { + throw std::runtime_error("check " + checkName + " not found in the config file"); + } +} + +TEST_CASE("test_check_specs") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + + Check check(getCheckConfig(configFilePath, "singleCheck")); + + REQUIRE(check.getInputs().size() == 1); + CHECK(check.getInputs()[0] == (InputSpec{ { "mo" }, "QTST", "skeletonTask", 0, Lifetime::Sporadic })); + + CHECK(check.getOutputSpec() == (OutputSpec{ "CTST", "singleCheck", 0, Lifetime::Sporadic })); +} + +TEST_CASE("test_check_long_description") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + + Check check(getCheckConfig(configFilePath, "singleCheckLongDescription")); + + REQUIRE(check.getInputs().size() == 1); + CHECK(check.getInputs()[0] == (InputSpec{ { "mo" }, "QTST", "skeletonTask", 0, Lifetime::Sporadic })); + + CHECK(check.getOutputSpec() == (OutputSpec{ "CTST", "singleCheckL9fdb", 0, Lifetime::Sporadic })); +} + +std::shared_ptr dummyMO(const std::string& objName) +{ + auto obj = std::make_shared(new TH1F(objName.c_str(), objName.c_str(), 100, 0, 10), "test", "test", "TST"); + obj->setIsOwner(true); + return obj; +} + +TEST_CASE("test_check_empty_mo") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + + Check check(getCheckConfig(configFilePath, "singleCheck")); + check.init(); + check.startOfActivity(Activity()); + + { + std::map> moMap{ + { "skeletonTask/example", nullptr } + }; + + auto qos = check.check(moMap); + CHECK(qos.size() == 0); + } + + { + std::map> moMap{ + { "skeletonTask/example", std::make_shared() } + }; + + auto qos = check.check(moMap); + CHECK(qos.size() == 0); + } +} + +TEST_CASE("test_check_invoke_check") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + + Check check(getCheckConfig(configFilePath, "singleCheck")); + check.init(); + check.startOfActivity(Activity()); + + std::map> moMap{ + { "skeletonTask/example", dummyMO("example") } + }; + + auto qos = check.check(moMap); + CHECK(qos.size() == 1); +} + +TEST_CASE("test_check_postprocessing") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + + Check check(getCheckConfig(configFilePath, "checkAnyPP")); + check.init(); + check.startOfActivity(Activity()); + + std::map> moMap{ + { "SkeletonPostProcessing/example", dummyMO("example") } + }; + + auto qos = check.check(moMap); + CHECK(qos.size() == 1); +} + +TEST_CASE("test_check_activity") +{ + Check check({ "test", + "QcSkeleton", + "o2::quality_control_modules::skeleton::SkeletonCheck", + "TST", + "", + {}, + "something", + { { "implementation", "CCDB" }, { "host", "something" } }, + {}, + UpdatePolicyType::OnAny, + {}, + true }); + + std::map> moMap{ + { "abcTask/test1", dummyMO("test1") }, + { "abcTask/test2", dummyMO("test2") } + }; + + moMap["abcTask/test1"]->setActivity({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 1, 10 }, "pp" }); + moMap["abcTask/test2"]->setActivity({ 300000, "PHYSICS", "LHC22a", "spass", "qc", { 5, 15 }, "pp" }); + + check.init(); + check.startOfActivity(Activity()); + auto qos = check.check(moMap); + + REQUIRE(qos.size() == 1); + ValidityInterval correctValidity{ 1, 15 }; + CHECK(qos[0]->getActivity().mValidity == correctValidity); +} \ No newline at end of file diff --git a/Framework/test/testCheckInterface.cxx b/Framework/test/testCheckInterface.cxx new file mode 100644 index 0000000000..901e074423 --- /dev/null +++ b/Framework/test/testCheckInterface.cxx @@ -0,0 +1,88 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCheckInterface.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/CheckInterface.h" +#include +#include "QualityControl/MonitorObject.h" +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace std; + +namespace o2::quality_control +{ + +using namespace core; + +namespace test +{ + +class TestCheck : public checker::CheckInterface +{ + public: + /// Default constructor + TestCheck() = default; + /// Destructor + ~TestCheck() override = default; + + // Override interface + void configure() override + { + } + + Quality check(std::map>* moMap) override + { + auto mo = moMap->begin()->second; + if (mValidString.empty()) { + return Quality::Null; + } + + auto* str = reinterpret_cast(mo->getObject()); + return mValidString == str->String() ? Quality::Good : Quality::Bad; + } + + void beautify(std::shared_ptr mo, Quality = Quality::Null) override + { + auto* str = reinterpret_cast(mo->getObject()); + str->String().Append(" is beautiful now"); + } + + string mValidString; +}; + +} /* namespace test */ +} /* namespace o2::quality_control */ + +TEST_CASE("test_invoke_all_interface_methods") +{ + test::TestCheck testCheck; + + std::shared_ptr mo(new MonitorObject(new TObjString("A string"), "str", "class", "DET")); + std::map> moMap = { { "test", mo } }; + + CHECK(testCheck.check(&moMap) == Quality::Null); + + testCheck.mValidString = "A different string"; + CHECK(testCheck.check(&moMap) == Quality::Bad); + + testCheck.mValidString = "A string"; + CHECK(testCheck.check(&moMap) == Quality::Good); + + testCheck.beautify(mo); + CHECK(reinterpret_cast(mo->getObject())->String() == "A string is beautiful now"); +} diff --git a/Framework/test/testCheckRunner.cxx b/Framework/test/testCheckRunner.cxx new file mode 100644 index 0000000000..b7b3857fa7 --- /dev/null +++ b/Framework/test/testCheckRunner.cxx @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCheckRunner.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/CheckRunnerFactory.h" +#include "QualityControl/CheckRunner.h" +#include "QualityControl/CommonSpec.h" +#include + +using namespace o2::quality_control::checker; +using namespace std; +using namespace o2::framework; +using namespace o2::header; + +TEST_CASE("test_check_runner_static") +{ + // facility name + CHECK(CheckRunner::createCheckRunnerFacility(CheckRunner::createCheckRunnerIdString() + "-test") == "check/test"); + CHECK(CheckRunner::createCheckRunnerFacility(CheckRunner::createCheckRunnerIdString() + "-abcdefghijklmnopqrstuvwxyz") == "check/abcdefghijklmnopqrstuvwxyz"); + CHECK(CheckRunner::createCheckRunnerFacility(CheckRunner::createCheckRunnerIdString() + "-abcdefghijklmnopqrstuvwxyz123456789") == "check/abcdefghijklmnopqrstuvwxyz"); +} + +TEST_CASE("test_checkRunner_getDetector") +{ + CheckConfig config; + config.detectorName = "TST"; + + vector checks; + CHECK(CheckRunner::getDetectorName(checks) == ""); + checks.push_back(config); + CHECK(CheckRunner::getDetectorName(checks) == "TST"); + checks.push_back(config); + CHECK(CheckRunner::getDetectorName(checks) == "TST"); + config.detectorName = "EMC"; + checks.push_back(config); + CHECK(CheckRunner::getDetectorName(checks) == "MANY"); +} diff --git a/Framework/test/testCheckWorkflow.cxx b/Framework/test/testCheckWorkflow.cxx new file mode 100644 index 0000000000..2bb84037b4 --- /dev/null +++ b/Framework/test/testCheckWorkflow.cxx @@ -0,0 +1,162 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCheckWorkflow.cxx +/// \author Rafal Pacholek +/// + +#include +#include +#include +#include "QualityControl/InfrastructureGenerator.h" +#include "QualityControl/UserInputOutput.h" + +using namespace o2; +using namespace o2::framework; +using namespace o2::utilities; +using namespace o2::quality_control::core; + +const std::string receiverName = "Receiver"; + +void customize(std::vector& policies) +{ + DataSampling::CustomizeInfrastructure(policies); + quality_control::customizeInfrastructure(policies); + + auto matcher = [](auto const& device) { + return device.name.find(receiverName) != std::string::npos; + }; + + policies.emplace_back(CompletionPolicyHelpers::consumeWhenAny("receiverCompletionPolicy", matcher)); +} + +#include "getTestDataDirectory.h" +#include "QualityControl/CheckRunner.h" +#include "QualityControl/QualityObject.h" +#include "QualityControl/runnerUtils.h" +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control::checker; +using namespace o2::configuration; + +/** + * Test description + * + * Test a complex configuration with 3 tasks and 4 checks. + * Checks sources contain several tasks with different policies. + * + * The goal is to check whether all checks are triggered and generate Quality Objects. + * It is expected to terminate as soon as all task publish for the first time. + */ + +class Receiver : public o2::framework::Task +{ + public: + Receiver(std::string configurationSource) + { + auto config = o2::configuration::ConfigurationFactory::getConfiguration(configurationSource); + + for (const auto& task : config->getRecursive("qc.checks")) { + mNames.insert(task.first); // check name; + } + } + + /// Destructor + ~Receiver() override{}; + + /// \brief Receiver process callback + void run(o2::framework::ProcessingContext& pctx) override + { + std::vector namesToErase; + + for (const auto& checkName : mNames) { + if (pctx.inputs().isValid(checkName)) { + auto qo = pctx.inputs().get(checkName); + if (!qo) { + ILOG(Error, Devel) << qo->getName() << " - quality is NULL" << ENDM; + pctx.services().get().readyToQuit(QuitRequest::All); + } else { + ILOG(Debug, Devel) << qo->getName() << " - quality: " << qo->getQuality() << ENDM; + namesToErase.emplace_back(checkName); + } + } + } + + for (const auto& nameToErase : namesToErase) { + mNames.erase(nameToErase); + } + + if (mNames.empty()) { + // We ask to shut the topology down, returning 0 if there were no ERROR logs. + pctx.services().get().readyToQuit(QuitRequest::All); + } + ILOG(Debug, Devel) << "Requires " << mNames.size() << " quality objects" << ENDM; + } + + Inputs getInputs() + { + Inputs inputs; + for (auto& checkName : mNames) { + inputs.push_back({ checkName, createUserDataMatcher(DataSourceType::Check, "TST", checkName), Lifetime::Sporadic }); + } + return inputs; + } + + private: + std::set mNames; +}; + +WorkflowSpec defineDataProcessing(ConfigContext const&) +{ + WorkflowSpec specs; + + // The producer to generate some data in the workflow + DataProcessorSpec producer{ + "producer", + Inputs{}, + Outputs{ + { { "tst-data" }, "TST", "DATA" } }, + AlgorithmSpec{ + [](ProcessingContext& pctx) { + usleep(100000); + pctx.outputs().make(OutputRef{ "tst-data" }, 1); + } } + }; + specs.push_back(producer); + + const std::string qcConfigurationSource = std::string("json://") + getTestDataDirectory() + "testCheckWorkflow.json"; + + ILOG(Info) << "Using config file '" << qcConfigurationSource << "'" << ENDM; + + // Generation of Data Sampling infrastructure + auto configInterface = ConfigurationFactory::getConfiguration(qcConfigurationSource); + auto dataSamplingTree = configInterface->getRecursive("dataSamplingPolicies"); + DataSampling::GenerateInfrastructure(specs, dataSamplingTree); + + // Generation of the QC topology (one task, one checker in this case) + quality_control::generateStandaloneInfrastructure(specs, configInterface->getRecursive()); + + Receiver receiver(qcConfigurationSource); + // Finally the receiver + DataProcessorSpec receiverSpec{ receiverName, receiver.getInputs(), {}, {} }; + // We move the task at the end, so receiver.getInputs() is not called first. + receiverSpec.algorithm = adaptFromTask(std::move(receiver)); + specs.push_back(receiverSpec); + + return specs; +} diff --git a/Framework/test/testCheckWorkflow.json b/Framework/test/testCheckWorkflow.json new file mode 100644 index 0000000000..6c9640bb93 --- /dev/null +++ b/Framework/test/testCheckWorkflow.json @@ -0,0 +1,142 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "skeletonTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "taskName": "newName", + "moduleName": "QcSkeleton", + "detectorName" : "TST", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "test-policy" + }, + "taskParameters": {}, + "location": "remote", + "localMachines": [] + }, + "QYZTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName" : "TST", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "test-policy" + }, + "taskParameters": {}, + "location": "remote", + "localMachines": [] + }, + "XYZTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName" : "TST", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "test-policy" + }, + "taskParameters": {}, + "location": "remote", + "localMachines": [] + } + }, + "checks": { + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "policy": "OnAll", + "dataSource": [{ + "type": "Task", + "name": "skeletonTask", + "MOs": ["example"] + }, + { + "type": "Task", + "name": "QYZTask", + "MOs": ["example"] + }] + }, + "XYZCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "policy": "OnAny", + "dataSource": [{ + "type": "Task", + "name": "skeletonTask", + "MOs": ["example"] + }, + { + "type": "Task", + "name": "QYZTask", + "MOs": ["example"] + }, + { + "type": "Task", + "name": "XYZTask", + "MOs": ["example", "example2"] + }] + }, + "ABCCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "skeletonTask", + "MOs": ["example", "example2"] + }, + { + "type": "Task", + "name": "QYZTask", + "MOs": ["example", "example3"] + }] + }, + "CheckSeparately": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnEachSeparately", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "skeletonTask", + "MOs": ["example", "example2"] + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "test-policy", + "active": "true", + "machines": [], + "query": "data:TST/DATA/0", + "samplingConditions": [], + "blocking": "false" + } + ] +} diff --git a/Framework/test/testCustomParameters.cxx b/Framework/test/testCustomParameters.cxx new file mode 100644 index 0000000000..8389047d9d --- /dev/null +++ b/Framework/test/testCustomParameters.cxx @@ -0,0 +1,374 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCustomParameters.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/CustomParameters.h" +#include +#include +#include "getTestDataDirectory.h" +#include "QualityControl/InfrastructureGenerator.h" + +#include +#include +#include +#include + +using namespace o2::quality_control::core; +using namespace std; + +TEST_CASE("test_cp_basic") +{ + CustomParameters cp; + + cp.set("key", "value"); + CHECK(cp.at("key") == "value"); + CHECK(cp.at("key", "default") == "value"); + CHECK(cp.at("key", "default", "default") == "value"); + + cp.set("key", "value_run1", "run1"); + CHECK(cp.at("key") == "value"); + CHECK(cp.at("key", "run1") == "value_run1"); + CHECK(cp.at("key", "run1", "default") == "value_run1"); + + cp.set("key", "value_beam1", "default", "beam1"); + CHECK(cp.at("key") == "value"); + CHECK(cp.at("key", "default") == "value"); + CHECK(cp.at("key", "default", "beam1") == "value_beam1"); + + cp.set("key", "value_run1_beam1", "run1", "beam1"); + CHECK(cp.at("key") == "value"); + CHECK(cp.at("key", "run1", "beam1") == "value_run1_beam1"); + + CHECK_NOTHROW(cout << cp << endl); +} + +TEST_CASE("test_cp_iterators") +{ + CustomParameters cp; + cp.set("key1", "value1", "run1", "beam1"); + auto param = cp.find("key1", "run1", "beam1"); + CHECK(param != cp.end()); + if (param != cp.end()) { + CHECK(param->second == "value1"); + } + + cp.set("key2", "value2"); + param = cp.find("key2"); + CHECK(param != cp.end()); + if (param != cp.end()) { + CHECK(param->second == "value2"); + } + + param = cp.find("not_found"); + CHECK(param == cp.end()); + + param = cp.find("not_found", "run1"); + CHECK(param == cp.end()); + + param = cp.find("not_found", "run1", "beam1"); + CHECK(param == cp.end()); +} + +TEST_CASE("test_cp_misc") +{ + CustomParameters cp; + cp.set("aaa", "AAA"); + cp.set("bbb", "BBB"); + cp.set("aaa", "AAA", "runX"); + cp.set("aaa", "AAA", "runX", "beamB"); + cp.set("ccc", "CCC"); + cp.set("bbb", "BBB", "runX"); + cp.set("ccc", "CCC", "runY"); + + CHECK(cp.count("aaa") == 1); + CHECK(cp.count("bbb") == 1); + CHECK(cp.count("aaa") == 1); + CHECK(cp.count("aaa") == 1); + CHECK(cp.at("aaa") == "AAA"); + CHECK(cp.at("bbb") == "BBB"); + CHECK(cp.at("aaa", "runX") == "AAA"); + + CHECK_THROWS_AS(cp.at("not_found"), std::out_of_range); + CHECK(cp.atOrDefaultValue("not_found", "default_value") == "default_value"); + CHECK(cp.atOrDefaultValue("not_found") == ""); + CHECK(cp.atOrDefaultValue("not_found", "default_value2", "asdf", "adsf") == "default_value2"); + + CHECK(cp["aaa"] == "AAA"); + CHECK(cp["ccc"] == "CCC"); + + auto all = cp.getAllForRunBeam("runX", "default"); + auto another = cp.getAllForRunBeam("default", "default"); + auto same = cp.getAllDefaults(); + CHECK(another.size() == same.size()); + CHECK(all.size() == 2); + CHECK(another.size() == 3); + + CHECK_NOTHROW(cp["not_found"]); // this won't throw, it will create an empty value at "not_found" + CHECK(cp.count("not_found") == 1); + CHECK(cp.at("not_found") == ""); + + // test bracket assignemnt + cp["something"] = "else"; + CHECK(cp.at("something") == "else"); + cp["something"] = "asdf"; + CHECK(cp.at("something") == "asdf"); +} + +TEST_CASE("test_at_optional") +{ + CustomParameters cp; + cp.set("aaa", "AAA"); + cp.set("bbb", "BBB"); + cp.set("aaa", "AAA", "runX"); + cp.set("aaa", "AAA", "runX", "beamB"); + + CHECK(cp.atOptional("aaa").value() == "AAA"); + CHECK(cp.atOptional("abc").has_value() == false); + CHECK(cp.atOptional("abc").value_or("bla") == "bla"); +} + +TEST_CASE("test_at_optional_activity") +{ + Activity activity; + activity.mBeamType = "pp"; + activity.mType = "PHYSICS"; + + CustomParameters cp; + cp.set("aaa", "AAA"); + cp.set("bbb", "BBB"); + cp.set("aaa", "asdf", "PHYSICS"); + cp.set("aaa", "CCC", "PHYSICS", "pp"); + cp.set("aaa", "DDD", "PHYSICS", "PbPb"); + cp.set("aaa", "AAA", "TECHNICAL", "pp"); + + CHECK(cp.atOptional("aaa", activity).value() == "CCC"); + CHECK(cp.atOptional("abc", activity).has_value() == false); + CHECK(cp.atOptional("abc", activity).value_or("bla") == "bla"); + + Activity activity2; + activity.mBeamType = "PbPb"; + activity.mType = "PHYSICS"; + CHECK(cp.atOptional("aaa", activity).value() == "DDD"); +} + +TEST_CASE("test_cp_new_access_pattern") +{ + CustomParameters cp; + cp.set("aaa", "AAA"); + cp.set("bbb", "BBB"); + cp.set("aaa", "AAA", "runX"); + cp.set("aaa", "AAA", "runX", "beamB"); + cp.set("ccc", "1"); + cp.set("bbb", "BBB", "runX"); + cp.set("ccc", "CCC", "runY"); + + // if we have a default value + std::string param = cp.atOrDefaultValue("myOwnKey", "1"); + CHECK(std::stoi(param) == 1); + param = cp.atOrDefaultValue("aaa", "1"); + CHECK(param == "AAA"); + + // if we don't have a default value and only want to do something if there is a value: + if (auto param2 = cp.find("ccc"); param2 != cp.end()) { + CHECK(std::stoi(param2->second) == 1); + } +} + +TEST_CASE("test_load_from_ptree") +{ + boost::property_tree::ptree jsontree; + std::string configFilePath = std::string(getTestDataDirectory()) + "testWorkflow.json"; + + boost::property_tree::read_json(configFilePath, jsontree); + + string v0 = jsontree.get("qc.tasks.skeletonTask.extendedTaskParameters.default.default.myOwnKey"); + + boost::property_tree::ptree params = jsontree.get_child("qc.tasks.skeletonTask.extendedTaskParameters"); + + CustomParameters cp; + cp.populateCustomParameters(params); + + CHECK_NOTHROW(cout << cp << endl); + + CHECK(cp.at("myOwnKey") == "myOwnValue"); + CHECK(cp.at("myOwnKey1", "PHYSICS") == "myOwnValue1b"); + CHECK(cp.atOptional("asdf").has_value() == false); + + auto value = cp.getOptionalPtree("myOwnKey3"); + CHECK(value.has_value() == true); + + // Check that it's an array with 1 element + std::size_t arraySize = std::distance(value->begin(), value->end()); + CHECK(arraySize == 1); + + // Get the first (and only) element of the array + auto firstElement = value->begin()->second; + + // Check the top-level properties + CHECK(firstElement.get("name") == "mean_of_histogram"); + CHECK(firstElement.get("title") == "Mean trend of the example histogram"); + CHECK(firstElement.get("graphAxisLabel") == "Mean X:time"); + CHECK(firstElement.get("graphYRange") == "0:10000"); + + // Check the graphs array + auto graphs = firstElement.get_child("graphs"); + std::size_t graphsSize = std::distance(graphs.begin(), graphs.end()); + CHECK(graphsSize == 1); + + // Check the first graph properties + auto firstGraph = graphs.begin()->second; + CHECK(firstGraph.get("name") == "mean_trend"); + CHECK(firstGraph.get("title") == "mean trend"); + CHECK(firstGraph.get("varexp") == "example.mean:time"); + CHECK(firstGraph.get("selection") == ""); + CHECK(firstGraph.get("option") == "*L PLC PMC"); +} + +TEST_CASE("test_default_if_not_found_at_optional") +{ + CustomParameters cp; + + // no default values are in the CP, we get an empty result + CHECK(cp.atOptional("key", "PHYSICS", "proton-proton").has_value() == false); + CHECK(cp.atOptional("key", "TECHNICAL", "proton-proton").has_value() == false); + + // prepare the CP + cp.set("key", "valueDefaultDefault", "default", "default"); + cp.set("key", "valuePhysicsDefault", "PHYSICS", "default"); + cp.set("key", "valuePhysicsPbPb", "PHYSICS", "PbPb"); + cp.set("key", "valueCosmicsDefault", "COSMICS", "default"); + cp.set("key", "valueCosmicsDefault", "default", "pp"); + + // check the data + CHECK(cp.atOptional("key").value() == "valueDefaultDefault"); + CHECK(cp.atOptional("key", "PHYSICS").value() == "valuePhysicsDefault"); + CHECK(cp.atOptional("key", "PHYSICS", "PbPb").value() == "valuePhysicsPbPb"); + CHECK(cp.atOptional("key", "COSMICS", "default").value() == "valueCosmicsDefault"); + CHECK(cp.atOptional("key", "default", "pp").value() == "valueCosmicsDefault"); + + // check when something is missing + CHECK(cp.atOptional("key", "PHYSICS", "pp").value() == "valuePhysicsDefault"); // key is not defined for pp + CHECK(cp.atOptional("key", "TECHNICAL", "STRANGE").value() == "valueDefaultDefault"); // key is not defined for run nor beam + CHECK(cp.atOptional("key", "TECHNICAL", "pp").value() == "valueCosmicsDefault"); // key is not defined for technical +} + +TEST_CASE("test_default_if_not_found_at") +{ + CustomParameters cp; + + // no default values are in the CP, we get an empty result + CHECK_THROWS_AS(cp.at("key", "PHYSICS", "proton-proton"), std::out_of_range); + CHECK_THROWS_AS(cp.at("key", "TECHNICAL", "proton-proton"), std::out_of_range); + + // prepare the CP + cp.set("key", "valueDefaultDefault", "default", "default"); + cp.set("key", "valuePhysicsDefault", "PHYSICS", "default"); + cp.set("key", "valuePhysicsPbPb", "PHYSICS", "PbPb"); + cp.set("key", "valueCosmicsDefault", "COSMICS", "default"); + cp.set("key", "valueCosmicsDefault", "default", "pp"); + + // check the data + CHECK(cp.at("key") == "valueDefaultDefault"); + CHECK(cp.at("key", "PHYSICS") == "valuePhysicsDefault"); + CHECK(cp.at("key", "PHYSICS", "PbPb") == "valuePhysicsPbPb"); + CHECK(cp.at("key", "COSMICS", "default") == "valueCosmicsDefault"); + CHECK(cp.at("key", "default", "pp") == "valueCosmicsDefault"); + + // check when something is missing + CHECK(cp.at("key", "PHYSICS", "pp") == "valuePhysicsDefault"); // key is not defined for pp + CHECK(cp.at("key", "TECHNICAL", "STRANGE") == "valueDefaultDefault"); // key is not defined for run nor beam + CHECK(cp.at("key", "TECHNICAL", "pp") == "valueCosmicsDefault"); // key is not defined for technical +} + +TEST_CASE("test_getAllDefaults") +{ + CustomParameters cp; + auto result = cp.getAllDefaults(); + CHECK(result.size() == 0); +} + +TEST_CASE("test_getOptionalPtree") +{ + CustomParameters cp; + std::string content = R""""( +[ + { + "name": "mean_of_histogram", + "title": "Mean trend of the example histogram", + "graphAxisLabel": "Mean X:time", + "graphYRange": "0:10000", + "graphs" : [ + { + "name": "mean_trend", + "title": "mean trend", + "varexp": "example.mean:time", + "selection": "", + "option": "*L PLC PMC" + }, { + "name": "mean_trend_1000", + "title": "mean trend + 1000", + "varexp": "example.mean + 1000:time", + "selection": "", + "option": "* PMC", + "graphErrors": "1:200" + } + ] + }, + { + "name": "histogram_of_means", + "title": "Distribution of mean values in the example histogram", + "graphs" : [{ + "varexp": "example.mean", + "selection": "", + "option": "" + }] + }, + { + "name": "example_quality", + "title": "Trend of the example histogram's quality", + "graphs" : [{ + "varexp": "QcCheck.name:time", + "selection": "", + "option": "*" + }] + } + ] + )""""; + cp.set("key", content); + auto pt = cp.getOptionalPtree("key"); + CHECK(pt.has_value()); + + std::size_t number_plots = std::distance(pt->begin(), pt->end()); + CHECK(number_plots == 3); + + auto first_plot = pt->begin()->second; + CHECK(first_plot.get("name") == "mean_of_histogram"); + auto graphs = first_plot.get_child("graphs"); + CHECK(graphs.size() == 2); + + auto last_plot = std::prev(pt->end())->second; + CHECK(last_plot.get("name") == "example_quality"); + + // test for failure + CustomParameters cp2; + cp2.set("key", "blabla"); + auto pt2 = cp2.getOptionalPtree("key"); + CHECK(!pt2.has_value()); + + // try to get it as text + auto text = cp.atOptional("key"); + CHECK(text.has_value()); + CHECK(text == content); +} \ No newline at end of file diff --git a/Framework/test/testDataHeaderHelpers.cxx b/Framework/test/testDataHeaderHelpers.cxx new file mode 100644 index 0000000000..27e2bc8d65 --- /dev/null +++ b/Framework/test/testDataHeaderHelpers.cxx @@ -0,0 +1,50 @@ +// Copyright 2019-2024 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testDataHeaderHelpers.h +/// \author Piotr Konopka +/// + +#include + +#include + +#include "QualityControl/DataHeaderHelpers.h" +#include "QualityControl/DataSourceType.h" + +using namespace o2::quality_control::core; +using namespace o2::header; + +TEST_CASE("DataOrigin") +{ + CHECK_THROWS(createDataOrigin(DataSourceType::Direct, "TST")); // non-QC data source + CHECK_THROWS(createDataOrigin(DataSourceType::Task, "")); // empty detector is wrong + + CHECK(createDataOrigin(DataSourceType::Task, "TST") == DataOrigin{ "QTST" }); + CHECK(createDataOrigin(DataSourceType::TaskMovingWindow, "TST") == DataOrigin{ "WTST" }); + + CHECK(createDataOrigin(DataSourceType::Task, "TOO_LONG") == DataOrigin{ "QTOO" }); + CHECK(createDataOrigin(DataSourceType::Task, "X") == DataOrigin{ "QX" }); +} + +TEST_CASE("DataDescription") +{ + CHECK(createDataDescription("", 10) == DataDescription("")); + CHECK(createDataDescription("ABC", 10) == DataDescription("ABC")); + CHECK(createDataDescription("ABCDEABCDEABCDEA", 10) == DataDescription("ABCDEABCDEABCDEA")); + CHECK(createDataDescription("LOOOOOOOOOOOOOOONG", 4) != DataDescription("LOOOOOOOOOOOOOON")); + + CHECK_THROWS(createDataDescription("LOOOOOOOOOOOOOOONG", DataDescription::size + 50)); + + CHECK(createDataDescription("LOOOOOOOOOOOOOOONG", DataSourceType::Task) != DataDescription("LOOOOOOOOOOOOOON")); + CHECK_THROWS(createDataDescription("WHATEVER", DataSourceType::ExternalTask)); +} \ No newline at end of file diff --git a/Framework/test/testDbFactory.cxx b/Framework/test/testDbFactory.cxx index b5f83f489e..8e0f0533ac 100644 --- a/Framework/test/testDbFactory.cxx +++ b/Framework/test/testDbFactory.cxx @@ -1,29 +1,38 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file testDbFactory.cxx /// \author Barthelemy von Haller /// -#include "../include/QualityControl/DatabaseFactory.h" +#include "QualityControl/DatabaseFactory.h" +#include "QualityControl/QcInfoLogger.h" #ifdef _WITH_MYSQL - #include "QualityControl/MySqlDatabase.h" - #endif -#define BOOST_TEST_MODULE Quality test + +#define BOOST_TEST_MODULE DbFactory test #define BOOST_TEST_MAIN #define BOOST_TEST_DYN_LINK #include -#include -#include +#include #include #include +#include +#include #include -#include -#include -#include using namespace std; using namespace o2::quality_control::core; @@ -31,8 +40,6 @@ using namespace o2::quality_control::core; namespace o2::quality_control::repository { -bool do_nothing(AliceO2::Common::FatalException const& ex) { return true; } - BOOST_AUTO_TEST_CASE(db_factory_test) { #ifdef _WITH_MYSQL @@ -42,12 +49,16 @@ BOOST_AUTO_TEST_CASE(db_factory_test) #endif std::unique_ptr database2 = nullptr; - BOOST_CHECK_EXCEPTION(database2 = DatabaseFactory::create("asf"), AliceO2::Common::FatalException, do_nothing); + BOOST_CHECK_EXCEPTION(database2 = DatabaseFactory::create("asf"), AliceO2::Common::FatalException, o2::quality_control::test::do_nothing); BOOST_CHECK(!database2); std::unique_ptr database3 = DatabaseFactory::create("CCDB"); BOOST_CHECK(database3); BOOST_CHECK(dynamic_cast(database3.get())); + + std::unique_ptr database4 = DatabaseFactory::create("Dummy"); + BOOST_CHECK(database4); + BOOST_CHECK(dynamic_cast(database4.get())); } BOOST_AUTO_TEST_CASE(db_ccdb_listing) @@ -60,557 +71,37 @@ BOOST_AUTO_TEST_CASE(db_ccdb_listing) ccdb->connect("ccdb-test.cern.ch:8080", "", "", ""); // prepare stuff in the db - ccdb->truncate("functional_test", "object1"); - ccdb->truncate("functional_test", "object2"); - ccdb->truncate("functional_test", "path/to/object3"); + string prefixPath = "qc/TST/MO/"; + ccdb->truncate(prefixPath + "functional_test", "object1"); + ccdb->truncate(prefixPath + "functional_test", "object2"); + ccdb->truncate(prefixPath + "functional_test", "path/to/object3"); auto* h1 = new TH1F("object1", "object1", 100, 0, 99); auto* h2 = new TH1F("object2", "object2", 100, 0, 99); auto* h3 = new TH1F("path/to/object3", "object3", 100, 0, 99); - shared_ptr mo1 = make_shared(h1, "functional_test"); - shared_ptr mo2 = make_shared(h2, "functional_test"); - shared_ptr mo3 = make_shared(h3, "functional_test"); - ccdb->store(mo1); - ccdb->store(mo2); - ccdb->store(mo3); + shared_ptr mo1 = make_shared(h1, "functional_test", "testClass", "TST"); + shared_ptr mo2 = make_shared(h2, "functional_test", "testClass", "TST"); + shared_ptr mo3 = make_shared(h3, "functional_test", "testClass", "TST"); + ccdb->storeMO(mo1); + ccdb->storeMO(mo2); + ccdb->storeMO(mo3); // test getting list of tasks - std::vector list = ccdb->getListOfTasksWithPublications(); - // for(const auto &item : list) { - // cout << "task : " << item << endl; - // } - BOOST_CHECK(std::find(list.begin(), list.end(), "functional_test") != list.end()); + std::vector list = ccdb->getListing(prefixPath + "/functional_test"); + cout << prefixPath + "/functional_test" << endl; + for (const auto& item : list) { + ILOG(Info, Support) << "task : " << item << ENDM; + } + BOOST_CHECK(std::find(list.begin(), list.end(), RepoPathUtils::getMoPath(mo1.get())) != list.end()); // test getting objects list from task - auto objectNames = ccdb->getPublishedObjectNames("functional_test"); - // cout << "objects in task functional_test" << endl; + auto objectNames = ccdb->getPublishedObjectNames(prefixPath + "functional_test"); + // ILOG(Info, Support) << "objects in task functional_test" << ENDM; // for (auto name : objectNames) { - // cout << " - object : " << name << endl; + // ILOG(Info, Support) << " - object : " << name << ENDM; // } - BOOST_CHECK(std::find(objectNames.begin(), objectNames.end(), "object1") != objectNames.end()); - BOOST_CHECK(std::find(objectNames.begin(), objectNames.end(), "object2") != objectNames.end()); - BOOST_CHECK(std::find(objectNames.begin(), objectNames.end(), "path/to/object3") != objectNames.end()); - - // test retrieve object - MonitorObject* mo1_retrieved = ccdb->retrieve("functional_test", "object1"); - BOOST_CHECK(mo1_retrieved != nullptr); + BOOST_CHECK(std::find(objectNames.begin(), objectNames.end(), "/object1") != objectNames.end()); + BOOST_CHECK(std::find(objectNames.begin(), objectNames.end(), "/object2") != objectNames.end()); + BOOST_CHECK(std::find(objectNames.begin(), objectNames.end(), "/path/to/object3") != objectNames.end()); } -/* -BOOST_AUTO_TEST_CASE(test_libcurl) -{ - CURL* easyhandle= curl_easy_init(); - -// TMessage message(kMESS_OBJECT); -// message.Reset(); -// message.WriteObjectAny(mo, mo->IsA()); - string url = "localhost:8080"; - char local_buffer[1024]="data to send"; - string fullUrl = url + "/taskname/moname/1/100000"; - //"/quality=" + to_string(mo->getQuality().getLevel()); - cout << "fullUrl : " << fullUrl << endl; - curl_easy_setopt(easyhandle, CURLOPT_URL, - fullUrl.c_str()); - -// curl_easy_setopt(easyhandle, CURLOPT_READFUNCTION, read_function); -// curl_easy_setopt(easyhandle, CURLOPT_READDATA, &filedata); - curl_easy_setopt(easyhandle, CURLOPT_UPLOAD, 1L); -// curl_easy_setopt(easyhandle, CURLOPT_INFILESIZE_LARGE, message.Length()); - // size of the POST data // -// curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDSIZE, message.Length()); - curl_easy_setopt(easyhandle, CURLOPT_POSTFIELDSIZE, 12L); - // pass in a pointer to the data - libcurl will copy // - curl_easy_setopt(easyhandle, CURLOPT_COPYPOSTFIELDS, local_buffer); - // message.Buffer()); // we copy for sake of simplicity, would be better not to - cout << "perform" << endl; - CURLcode success = curl_easy_perform(easyhandle); - cout << "success : " << success << endl; - // Check for errors // - if(success != CURLE_OK) { - cerr << "curl_easy_perform() failed: " << curl_easy_strerror(success) << endl; - } else { - cout << "curl worked" << endl; - } -}*/ - -/* -BOOST_AUTO_TEST_CASE(test_libcurlsimple) -{ - CURL *curl; - CURLcode res; - - curl = curl_easy_init(); - if(curl) { - curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080"); - */ -/* example.com is redirected, so we tell libcurl to follow redirection */ /* -curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - -*/ -/* Perform the request, res will get the return code */ /* - res = curl_easy_perform(curl); - */ -/* Check for errors */ /* - if(res != CURLE_OK) - fprintf(stderr, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); - - */ -/* always cleanup */ /* - curl_easy_cleanup(curl); - } - }*/ -/* -BOOST_AUTO_TEST_CASE(test_libculrsimplepost) -{ - CURL *curl; - CURLcode res; - - static const char *postthis = "moo mooo moo moo"; - string fullUrl = "localhost:8080/taskname/moname/1/100000"; - - curl = curl_easy_init(); - if(curl) { - curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postthis); - - */ -/* if we don't provide POSTFIELDSIZE, libcurl will strlen() by - itself */ -/* -curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(postthis)); - -*/ -/* Perform the request, res will get the return code */ /* -res = curl_easy_perform(curl); -*/ -/* Check for errors */ /* - if(res != CURLE_OK) { - fprintf(stderr, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); - } - - */ -/* always cleanup */ /* - curl_easy_cleanup(curl); - } - }*/ -// -// BOOST_AUTO_TEST_CASE(test_upload) -//{ -// CURL *curl; -// CURLcode res; -// struct stat file_info; -// double speed_upload, total_time; -// FILE *fd; -// string fullUrl = "localhost:8080/taskname/moname/1/100000"; -// -// fd = fopen("/tmp/test.txt", "rb"); /* open file to upload */ -// if(!fd) { -// BOOST_CHECK(fd); -// return; -// } -// -// /* to get the file size */ -// if(fstat(fileno(fd), &file_info) != 0) { -// BOOST_CHECK(false); /* can't continue */ -// return; -// } -// -// curl = curl_easy_init(); -// if(curl) { -// /* upload to this place */ -// curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str()); -//// curl_easy_setopt(curl, CURLOPT_URL, "file:///tmp/test2.txt"); -// -// /* tell it to "upload" to the URL */ -// curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); -// -// /* set where to read from (on Windows you need to use READFUNCTION too) */ -// curl_easy_setopt(curl, CURLOPT_READDATA, fd); -// -// /* and give the size of the upload (optional) */ -// curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, -// (curl_off_t)file_info.st_size); -// -// /* enable verbose for easier tracing */ -// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); -// -// res = curl_easy_perform(curl); -// /* Check for errors */ -// if(res != CURLE_OK) { -// fprintf(stderr, "curl_easy_perform() failed: %s\n", -// curl_easy_strerror(res)); -// -// } -// else { -// /* now extract transfer info */ -// curl_easy_getinfo(curl, CURLINFO_SPEED_UPLOAD, &speed_upload); -// curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &total_time); -// -// fprintf(stderr, "Speed: %.3f bytes/sec during %.3f seconds\n", -// speed_upload, total_time); -// -// } -// /* always cleanup */ -// curl_easy_cleanup(curl); -// } -// fclose(fd); -//} - -// -///* silly test data to POST */ -// static const char data[]="Lorem ipsum dolor sit amet, consectetur adipiscing " -// "elit. Sed vel urna neque. Ut quis leo metus. Quisque eleifend, ex at " -// "laoreet rhoncus, odio ipsum semper metus, at tempus ante urna in mauris. " -// "Suspendisse ornare tempor venenatis. Ut dui neque, pellentesque a varius " -// "eget, mattis vitae ligula. Fusce ut pharetra est. Ut ullamcorper mi ac " -// "sollicitudin semper. Praesent sit amet tellus varius, posuere nulla non, " -// "rhoncus ipsum."; -// -// struct WriteThis { -// const char *readptr; -// size_t sizeleft; -//}; -// -// static size_t read_callback(void *dest, size_t size, size_t nmemb, void *userp) -//{ -// struct WriteThis *wt = (struct WriteThis *)userp; -// size_t buffer_size = size*nmemb; -// -// if(wt->sizeleft) { -// /* copy as much as possible from the source to the destination */ -// size_t copy_this_much = wt->sizeleft; -// if(copy_this_much > buffer_size) -// copy_this_much = buffer_size; -// memcpy(dest, wt->readptr, copy_this_much); -// -// wt->readptr += copy_this_much; -// wt->sizeleft -= copy_this_much; -// return copy_this_much; /* we copied this many bytes */ -// } -// -// return 0; /* no more data left to deliver */ -//} -// -// BOOST_AUTO_TEST_CASE(test_curl_case){ -// CURL *curl; -// CURLcode res; -// -// struct WriteThis wt; -// -// wt.readptr = data; -// wt.sizeleft = strlen(data); -// -// /* get a curl handle */ -// curl = curl_easy_init(); -// if (curl) { -// /* First set the URL that is about to receive our POST. */ -// string fullUrl = "localhost:8080/taskname/moname/1/100000"; -// curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str());//"https://example.com/index.cgi"); -// -// /* Now specify we want to POST data */ -// curl_easy_setopt(curl, CURLOPT_POST, 1L); -// -// /* we want to use our own read function */ -// curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); -// -// /* pointer to pass to our read function */ -// curl_easy_setopt(curl, CURLOPT_READDATA, &wt); -// -// /* get verbose debug output please */ -// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); -// -// /* Set the expected POST size. If you want to POST large amounts of data, -// consider CURLOPT_POSTFIELDSIZE_LARGE */ -// curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long) wt.sizeleft); -// -// /* Perform the request, res will get the return code */ -// res = curl_easy_perform(curl); -// /* Check for errors */ -// if (res != CURLE_OK) -// fprintf(stderr, "curl_easy_perform() failed: %s\n", -// curl_easy_strerror(res)); -// -// /* always cleanup */ -// curl_easy_cleanup(curl); -// } -// curl_global_cleanup(); -//} -// -// static const char data[]="Lorem ipsum dolor sit amet, consectetur adipiscing " -// "elit. Sed vel urna neque. Ut quis leo metus. Quisque eleifend, ex at " -// "laoreet rhoncus, odio ipsum semper metus, at tempus ante urna in mauris. " -// "Suspendisse ornare tempor venenatis. Ut dui neque, pellentesque a varius " -// "eget, mattis vitae ligula. Fusce ut pharetra est. Ut ullamcorper mi ac " -// "sollicitudin semper. Praesent sit amet tellus varius, posuere nulla non, " -// "rhoncus ipsum."; -// -// BOOST_AUTO_TEST_CASE(test_curl_multiform) -//{ -// -// CURL *curl; -// -// CURLM *multi_handle; -// int still_running; -// -// struct curl_httppost *formpost = NULL; -// struct curl_httppost *lastptr = NULL; -// struct curl_slist *headerlist = NULL; -// static const char buf[] = "Expect:"; -// string fullUrl = "localhost:8080/taskname/moname/1/100000"; -// -// /* Fill in the file upload field. This makes libcurl load data from -// the given file name when curl_easy_perform() is called. */ -//// curl_formadd(&formpost, -//// &lastptr, -//// CURLFORM_COPYNAME, "sendfile", -//// CURLFORM_FILE, "/tmp/test.txt", -//// CURLFORM_END); -//// -//// /* Fill in the filename field */ -//// curl_formadd(&formpost, -//// &lastptr, -//// CURLFORM_COPYNAME, "filename", -//// CURLFORM_COPYCONTENTS, "test.txt", -//// CURLFORM_END); -//// -//// /* Fill in the submit field too, even if this is rarely needed */ -//// curl_formadd(&formpost, -//// &lastptr, -//// CURLFORM_COPYNAME, "submit", -//// CURLFORM_COPYCONTENTS, "send", -//// CURLFORM_END); -// -// curl_formadd(&formpost, -// &lastptr, -// CURLFORM_COPYNAME, "send", -// CURLFORM_BUFFER, "test.txt", -// CURLFORM_BUFFERPTR, data, -// CURLFORM_BUFFERLENGTH, strlen(data), -// CURLFORM_END); -// -// curl = curl_easy_init(); -// multi_handle = curl_multi_init(); -// -// /* initialize custom header list (stating that Expect: 100-continue is not -// wanted */ -// headerlist = curl_slist_append(headerlist, buf); -// if (curl && multi_handle) { -// -// /* what URL that receives this POST */ -// curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str()); -// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); -// -// curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); -// curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); -// -// curl_multi_add_handle(multi_handle, curl); -// -// curl_multi_perform(multi_handle, &still_running); -// -// do { -// struct timeval timeout; -// int rc; /* select() return code */ -// CURLMcode mc; /* curl_multi_fdset() return code */ -// -// fd_set fdread; -// fd_set fdwrite; -// fd_set fdexcep; -// int maxfd = -1; -// -// long curl_timeo = -1; -// -// FD_ZERO(&fdread); -// FD_ZERO(&fdwrite); -// FD_ZERO(&fdexcep); -// -// /* set a suitable timeout to play around with */ -// timeout.tv_sec = 1; -// timeout.tv_usec = 0; -// -// curl_multi_timeout(multi_handle, &curl_timeo); -// if (curl_timeo >= 0) { -// timeout.tv_sec = curl_timeo / 1000; -// if (timeout.tv_sec > 1) { -// timeout.tv_sec = 1; -// } else { -// timeout.tv_usec = (curl_timeo % 1000) * 1000; -// } -// } -// -// /* get file descriptors from the transfers */ -// mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd); -// -// if (mc != CURLM_OK) { -// fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc); -// break; -// } -// -// /* On success the value of maxfd is guaranteed to be >= -1. We call -// select(maxfd + 1, ...); specially in case of (maxfd == -1) there are -// no fds ready yet so we call select(0, ...) --or Sleep() on Windows-- -// to sleep 100ms, which is the minimum suggested value in the -// curl_multi_fdset() doc. */ -// -// if (maxfd == -1) { -// /* Portable sleep for platforms other than Windows. */ -// struct timeval wait = {0, 100 * 1000}; /* 100ms */ -// rc = select(0, NULL, NULL, NULL, &wait); -// } else { -// /* Note that on some platforms 'timeout' may be modified by select(). -// If you need access to the original value save a copy beforehand. */ -// rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); -// } -// -// switch (rc) { -// case -1: -// /* select error */ -// break; -// case 0: -// default: -// /* timeout or readable/writable sockets */ -// printf("perform!\n"); -// curl_multi_perform(multi_handle, &still_running); -// printf("running: %d!\n", still_running); -// break; -// } -// } -// while (still_running); -// -// curl_multi_cleanup(multi_handle); -// -// /* always cleanup */ -// curl_easy_cleanup(curl); -// -// /* then cleanup the formpost chain */ -// curl_formfree(formpost); -// -// /* free slist */ -// curl_slist_free_all(headerlist); -// } -//} -// -// struct MemoryStruct { -// char *memory; -// size_t size; -//}; -// -// static size_t -// WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) -//{ -// size_t realsize = size * nmemb; -// struct MemoryStruct *mem = (struct MemoryStruct *)userp; -// -// mem->memory = (char*)realloc(mem->memory, mem->size + realsize + 1); -// if(mem->memory == NULL) { -// /* out of memory! */ -// printf("not enough memory (realloc returned NULL)\n"); -// return 0; -// } -// -// memcpy(&(mem->memory[mem->size]), contents, realsize); -// mem->size += realsize; -// mem->memory[mem->size] = 0; -// -// return realsize; -//} -// -// BOOST_AUTO_TEST_CASE(curl_test_get) -//{ -// CURL *curl_handle; -// CURLcode res; -// -// struct MemoryStruct chunk; -// -// chunk.memory = (char*)malloc(1); /* will be grown as needed by the realloc above */ -// chunk.size = 0; /* no data at this point */ -// -// curl_global_init(CURL_GLOBAL_ALL); -// -// /* init the curl session */ -// curl_handle = curl_easy_init(); -// -// /* specify URL to get */ -// curl_easy_setopt(curl_handle, CURLOPT_URL, "http://www.example.com/"); -// -// /* send all data to this function */ -// curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); -// -// /* we pass our 'chunk' struct to the callback function */ -// curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk); -// -// /* some servers don't like requests that are made without a user-agent -// field, so we provide one */ -// curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); -// -// /* get it! */ -// res = curl_easy_perform(curl_handle); -// -// /* check for errors */ -// if(res != CURLE_OK) { -// fprintf(stderr, "curl_easy_perform() failed: %s\n", -// curl_easy_strerror(res)); -// } -// else { -// /* -// * Now, our chunk.memory points to a memory block that is chunk.size -// * bytes big and contains the remote file. -// * -// * Do something nice with it! -// */ -// -// printf("%lu bytes retrieved\n", (long)chunk.size); -// } -// -// /* cleanup curl stuff */ -// curl_easy_cleanup(curl_handle); -// -// free(chunk.memory); -// -// /* we're done with libcurl, so clean it up */ -// curl_global_cleanup(); -//} -// -// BOOST_AUTO_TEST_CASE(test_curl_get_reloc) -//{ -// CURL *curl; -// CURLcode res; -// char *location; -// long response_code; -// -// curl = curl_easy_init(); -// if(curl) { -// curl_easy_setopt(curl, CURLOPT_URL, "localhost:8080/daqTask/IDs/1"); -// -// /* example.com is redirected, figure out the redirection! */ -// -// /* Perform the request, res will get the return code */ -// res = curl_easy_perform(curl); -// /* Check for errors */ -// if(res != CURLE_OK) -// fprintf(stderr, "curl_easy_perform() failed: %s\n", -// curl_easy_strerror(res)); -// else { -// res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); -// if((res == CURLE_OK) && -// ((response_code / 100) != 3)) { -// /* a redirect implies a 3xx response code */ -// fprintf(stderr, "Not a redirect.\n"); -// } -// else { -// res = curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &location); -// -// if((res == CURLE_OK) && location) { -// /* This is the new absolute URL that you could redirect to, even if -// * the Location: response header may have been a relative URL. */ -// printf("Redirected to: %s\n", location); -// } -// } -// } -// -// /* always cleanup */ -// curl_easy_cleanup(curl); -// } -//} - } // namespace o2::quality_control::repository diff --git a/Framework/test/testEmptyConfig.json b/Framework/test/testEmptyConfig.json new file mode 100644 index 0000000000..cec38fe4c4 --- /dev/null +++ b/Framework/test/testEmptyConfig.json @@ -0,0 +1,3 @@ +{ + "qc": {} +} diff --git a/Framework/test/testFlagHelpers.cxx b/Framework/test/testFlagHelpers.cxx new file mode 100644 index 0000000000..4c5991d3b7 --- /dev/null +++ b/Framework/test/testFlagHelpers.cxx @@ -0,0 +1,304 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testFlagHelpers.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/FlagHelpers.h" +#include "QualityControl/ValidityInterval.h" + +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::core::flag_helpers; + +TEST_CASE("intervalsConnect") +{ + SECTION("Valid intervals that are adjacent") + { + ValidityInterval interval1{ 1, 10 }; + ValidityInterval interval2{ 10, 20 }; + REQUIRE(intervalsConnect(interval1, interval2)); + REQUIRE(intervalsConnect(interval2, interval1)); + } + + SECTION("Valid intervals that are not adjacent") + { + ValidityInterval interval1{ 1, 10 }; + ValidityInterval interval2{ 11, 20 }; + REQUIRE_FALSE(intervalsConnect(interval1, interval2)); + REQUIRE_FALSE(intervalsConnect(interval2, interval1)); + } + + SECTION("Overlapping intervals") + { + ValidityInterval interval1{ 1, 15 }; + ValidityInterval interval2{ 10, 20 }; + REQUIRE(intervalsConnect(interval1, interval2)); + REQUIRE(intervalsConnect(interval2, interval1)); + } + + SECTION("Invalid intervals") + { + ValidityInterval invalidInterval{ 10, 5 }; // max < min + ValidityInterval validInterval{ 1, 10 }; + REQUIRE_FALSE(intervalsConnect(invalidInterval, validInterval)); + REQUIRE_FALSE(intervalsConnect(validInterval, invalidInterval)); + } + + SECTION("Intervals with same start and end") + { + ValidityInterval interval{ 10, 10 }; + REQUIRE(intervalsConnect(interval, interval)); + } +} + +TEST_CASE("intervalsOverlap") +{ + SECTION("Valid intervals that are adjacent") + { + ValidityInterval interval1{ 1, 10 }; + ValidityInterval interval2{ 10, 20 }; + REQUIRE_FALSE(intervalsOverlap(interval1, interval2)); + REQUIRE_FALSE(intervalsOverlap(interval2, interval1)); + } + + SECTION("Overlapping intervals") + { + ValidityInterval interval1{ 1, 15 }; + ValidityInterval interval2{ 10, 20 }; + REQUIRE(intervalsOverlap(interval1, interval2)); + REQUIRE(intervalsOverlap(interval2, interval1)); + } + + SECTION("Invalid intervals") + { + ValidityInterval invalidInterval{ 10, 5 }; // max < min + ValidityInterval validInterval{ 1, 10 }; + REQUIRE_FALSE(intervalsOverlap(invalidInterval, validInterval)); + REQUIRE_FALSE(intervalsOverlap(validInterval, invalidInterval)); + } + + // fixme: This now returns false due to < and > comparators being used. + // However, the true reason for returning false should be the invalidity of the two intervals, + // which are now considered valid, since we use o2::math_utils::detail::Bracket::isValid(). + // This could be refactored, so an interval [x, x) is consistently treated as invalid. + SECTION("Intervals with same start and end") + { + ValidityInterval interval{ 10, 10 }; + REQUIRE_FALSE(intervalsOverlap(interval, interval)); + } +} + +TEST_CASE("excludeInterval") +{ + SECTION("Empty vector when interval fully covers the flag's interval") + { + QualityControlFlag qcFlag{ 10, 20, FlagTypeFactory::BadTracking() }; + ValidityInterval interval{ 5, 25 }; + + auto result = excludeInterval(qcFlag, interval); + + REQUIRE(result.empty()); + } + + SECTION("One flag when interval covers the start of the flag's interval") + { + QualityControlFlag qcFlag{ 10, 20, FlagTypeFactory::BadTracking() }; + ValidityInterval interval{ 5, 15 }; + + auto result = excludeInterval(qcFlag, interval); + + REQUIRE(result.size() == 1); + REQUIRE(result[0].getStart() == 15); + REQUIRE(result[0].getEnd() == 20); + REQUIRE(result[0].getFlag() == qcFlag.getFlag()); + REQUIRE(result[0].getComment() == qcFlag.getComment()); + REQUIRE(result[0].getSource() == qcFlag.getSource()); + } + + SECTION("One flag when interval covers the end of the flag's interval") + { + QualityControlFlag qcFlag{ 10, 20, FlagTypeFactory::BadTracking() }; + ValidityInterval interval{ 15, 25 }; + + auto result = excludeInterval(qcFlag, interval); + + REQUIRE(result.size() == 1); + REQUIRE(result[0].getStart() == 10); + REQUIRE(result[0].getEnd() == 15); + REQUIRE(result[0].getFlag() == qcFlag.getFlag()); + REQUIRE(result[0].getComment() == qcFlag.getComment()); + REQUIRE(result[0].getSource() == qcFlag.getSource()); + } + + SECTION("Two flags when interval is fully contained within the flag's interval") + { + QualityControlFlag qcFlag{ 10, 30, FlagTypeFactory::BadTracking() }; + ValidityInterval interval{ 15, 25 }; + + auto result = excludeInterval(qcFlag, interval); + + REQUIRE(result.size() == 2); + REQUIRE(result[0].getStart() == 10); + REQUIRE(result[0].getEnd() == 15); + REQUIRE(result[0].getFlag() == qcFlag.getFlag()); + REQUIRE(result[0].getComment() == qcFlag.getComment()); + REQUIRE(result[0].getSource() == qcFlag.getSource()); + REQUIRE(result[1].getStart() == 25); + REQUIRE(result[1].getEnd() == 30); + REQUIRE(result[1].getFlag() == qcFlag.getFlag()); + REQUIRE(result[1].getComment() == qcFlag.getComment()); + REQUIRE(result[1].getSource() == qcFlag.getSource()); + } + + SECTION("No exclusion when interval is before the flag's interval") + { + QualityControlFlag qcFlag{ 10, 20, FlagTypeFactory::BadTracking() }; + ValidityInterval interval{ 0, 5 }; + + auto result = excludeInterval(qcFlag, interval); + + REQUIRE(result.size() == 1); + REQUIRE(result[0].getStart() == 10); + REQUIRE(result[0].getEnd() == 20); + REQUIRE(result[0].getFlag() == qcFlag.getFlag()); + REQUIRE(result[0].getComment() == qcFlag.getComment()); + REQUIRE(result[0].getSource() == qcFlag.getSource()); + } + + SECTION("No exclusion when interval is after the flag's interval") + { + QualityControlFlag qcFlag{ 10, 20, FlagTypeFactory::BadTracking() }; + ValidityInterval interval{ 25, 30 }; + + auto result = excludeInterval(qcFlag, interval); + + REQUIRE(result.size() == 1); + REQUIRE(result[0].getStart() == 10); + REQUIRE(result[0].getEnd() == 20); + REQUIRE(result[0].getFlag() == qcFlag.getFlag()); + REQUIRE(result[0].getComment() == qcFlag.getComment()); + REQUIRE(result[0].getSource() == qcFlag.getSource()); + } + + SECTION("Invalid flag interval returns the same flag") + { + QualityControlFlag qcFlag{ 10, 10, FlagTypeFactory::BadTracking() }; // Zero-length interval + ValidityInterval interval{ 5, 15 }; + + auto result = excludeInterval(qcFlag, interval); + + REQUIRE(result.size() == 1); + REQUIRE(result[0].getStart() == 10); + REQUIRE(result[0].getEnd() == 10); + REQUIRE(result[0].getFlag() == qcFlag.getFlag()); + REQUIRE(result[0].getComment() == qcFlag.getComment()); + REQUIRE(result[0].getSource() == qcFlag.getSource()); + } + + SECTION("Zero-length overlap returns original flag") + { + QualityControlFlag qcFlag{ 10, 20, FlagTypeFactory::BadTracking() }; + ValidityInterval interval{ 15, 15 }; // Zero-length interval + + auto result = excludeInterval(qcFlag, interval); + + REQUIRE(result.size() == 1); + REQUIRE(result[0].getStart() == 10); + REQUIRE(result[0].getEnd() == 20); + REQUIRE(result[0].getFlag() == qcFlag.getFlag()); + REQUIRE(result[0].getComment() == qcFlag.getComment()); + REQUIRE(result[0].getSource() == qcFlag.getSource()); + } +} + +TEST_CASE("intersection") +{ + SECTION("Returns original flag when interval is invalid") + { + QualityControlFlag qcFlag{ 10, 20, FlagTypeFactory::BadTracking() }; + ValidityInterval interval{ 10, 5 }; + + auto result = intersection(qcFlag, interval); + + REQUIRE(result.has_value()); + REQUIRE(result->getStart() == 10); + REQUIRE(result->getEnd() == 20); + REQUIRE(result->getFlag() == qcFlag.getFlag()); + REQUIRE(result->getComment() == qcFlag.getComment()); + REQUIRE(result->getSource() == qcFlag.getSource()); + } + + SECTION("Returns nullopt when there is no overlap") + { + QualityControlFlag qcFlag{ 10, 20, FlagTypeFactory::BadTracking() }; + ValidityInterval interval{ 25, 30 }; + + auto result = intersection(qcFlag, interval); + + REQUIRE(!result.has_value()); + } + + SECTION("Returns intersected flag when intervals partially overlap") + { + QualityControlFlag qcFlag{ 10, 20, FlagTypeFactory::BadTracking(), "comment", "source" }; + ValidityInterval interval{ 15, 25 }; + + auto result = intersection(qcFlag, interval); + + REQUIRE(result.has_value()); + REQUIRE(result->getStart() == 15); + REQUIRE(result->getEnd() == 20); + REQUIRE(result->getFlag() == qcFlag.getFlag()); + REQUIRE(result->getComment() == qcFlag.getComment()); + REQUIRE(result->getSource() == qcFlag.getSource()); + } + + SECTION("Returns intersected flag when intervals fully overlap") + { + QualityControlFlag qcFlag{ 10, 30, FlagTypeFactory::BadTracking(), "comment", "source" }; + ValidityInterval interval{ 15, 25 }; + + auto result = intersection(qcFlag, interval); + + REQUIRE(result.has_value()); + REQUIRE(result->getStart() == 15); + REQUIRE(result->getEnd() == 25); + REQUIRE(result->getFlag() == qcFlag.getFlag()); + REQUIRE(result->getComment() == qcFlag.getComment()); + REQUIRE(result->getSource() == qcFlag.getSource()); + } + + SECTION("Returns intersected flag when flag's interval is fully within the given interval") + { + QualityControlFlag qcFlag{ 15, 25, FlagTypeFactory::BadTracking(), "comment", "source" }; + ValidityInterval interval{ 10, 30 }; + + auto result = intersection(qcFlag, interval); + + REQUIRE(result.has_value()); + REQUIRE(result->getStart() == 15); + REQUIRE(result->getEnd() == 25); + REQUIRE(result->getFlag() == qcFlag.getFlag()); + REQUIRE(result->getComment() == qcFlag.getComment()); + REQUIRE(result->getSource() == qcFlag.getSource()); + } + SECTION("Returns nullopt if the flag and the interval are adjacent") + { + QualityControlFlag qcFlag{ 15, 25, FlagTypeFactory::BadTracking(), "comment", "source" }; + REQUIRE_FALSE(intersection(qcFlag, { 10, 15 }).has_value()); + REQUIRE_FALSE(intersection(qcFlag, { 25, 30 }).has_value()); + } +} diff --git a/Framework/test/testInfrastructureGenerator.cxx b/Framework/test/testInfrastructureGenerator.cxx index 5f3fe7e088..4879302bde 100644 --- a/Framework/test/testInfrastructureGenerator.cxx +++ b/Framework/test/testInfrastructureGenerator.cxx @@ -1,99 +1,466 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// -/// \file testQCFactory.cxx +/// \file testInfrastructureGenerator.cxx /// \author Piotr Konopka /// -#define BOOST_TEST_MODULE QCFactory test -#define BOOST_TEST_MAIN -#define BOOST_TEST_DYN_LINK - -#include -#include +#include +#include #include "QualityControl/InfrastructureGenerator.h" +#include "getTestDataDirectory.h" #include +#include +#include "QualityControl/InfrastructureSpecReader.h" using namespace o2::quality_control::core; using namespace o2::framework; +using namespace o2::configuration; -BOOST_AUTO_TEST_CASE(qc_factory_local_test) +TEST_CASE("qc_factory_local_test") { - std::string configFilePath = std::string("json:/") + getenv("QUALITYCONTROL_ROOT") + "/test/testQCFactory.json"; - + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + std::cout << configFilePath << std::endl; + auto configInterface = ConfigurationFactory::getConfiguration(configFilePath); + auto configTree = configInterface->getRecursive(); { - auto workflow = InfrastructureGenerator::generateLocalInfrastructure(configFilePath, "o2flp1"); + auto workflow = InfrastructureGenerator::generateLocalInfrastructure(configTree, "o2flp1"); + + REQUIRE(workflow.size() == 5); + + CHECK(workflow[0].name == "qc-task-TST-skeletonTask"); + CHECK(workflow[0].inputs.size() == 2); + CHECK(workflow[0].outputs.size() == 1); + CHECK(DataSpecUtils::getOptionalSubSpec(workflow[0].outputs[0]).value_or(-1) == 1); - BOOST_REQUIRE_EQUAL(workflow.size(), 1); + CHECK(workflow[1].name == "TST-skeletonTask-proxy"); + CHECK(workflow[1].inputs.size() == 1); + CHECK(DataSpecUtils::getOptionalSubSpec(workflow[1].inputs[0]).value_or(-1) == 1); + CHECK(workflow[1].outputs.size() == 0); - BOOST_CHECK_EQUAL(workflow[0].name, "skeletonTask"); - BOOST_CHECK_EQUAL(workflow[0].inputs.size(), 1); - BOOST_CHECK_EQUAL(workflow[0].outputs.size(), 1); - BOOST_CHECK_EQUAL(workflow[0].outputs[0].subSpec, 1); + CHECK(workflow[2].name == "qc-task-GLO-recoTask"); + CHECK(workflow[2].inputs.size() == 13); + CHECK(workflow[2].outputs.size() == 1); + + CHECK(workflow[3].name == "GLO-recoTask-proxy"); + CHECK(workflow[3].inputs.size() == 1); + CHECK(workflow[3].outputs.size() == 0); + + CHECK(workflow[4].name == "tpcclust-proxy"); + CHECK(workflow[4].inputs.size() == 1); + CHECK(workflow[4].outputs.size() == 0); } { - auto workflow = InfrastructureGenerator::generateLocalInfrastructure(configFilePath, "o2flp2"); + auto workflow = InfrastructureGenerator::generateLocalInfrastructure(configTree, "o2flp2"); + + REQUIRE(workflow.size() == 2); - BOOST_REQUIRE_EQUAL(workflow.size(), 1); + CHECK(workflow[0].name == "qc-task-TST-skeletonTask"); + CHECK(workflow[0].inputs.size() == 2); + CHECK(workflow[0].outputs.size() == 1); + CHECK(DataSpecUtils::getOptionalSubSpec(workflow[0].outputs[0]).value_or(-1) == 2); - BOOST_CHECK_EQUAL(workflow[0].name, "skeletonTask"); - BOOST_CHECK_EQUAL(workflow[0].inputs.size(), 1); - BOOST_CHECK_EQUAL(workflow[0].outputs.size(), 1); - BOOST_CHECK_EQUAL(workflow[0].outputs[0].subSpec, 2); + CHECK(workflow[1].name == "TST-skeletonTask-proxy"); + CHECK(workflow[1].inputs.size() == 1); + CHECK(DataSpecUtils::getOptionalSubSpec(workflow[1].inputs[0]).value_or(-1) == 2); + CHECK(workflow[1].outputs.size() == 0); } { - auto workflow = InfrastructureGenerator::generateLocalInfrastructure(configFilePath, "o2flp3"); + auto workflow = InfrastructureGenerator::generateLocalInfrastructure(configTree, "o2flp3"); - BOOST_REQUIRE_EQUAL(workflow.size(), 0); + REQUIRE(workflow.size() == 0); } } -BOOST_AUTO_TEST_CASE(qc_factory_remote_test) +TEST_CASE("throwIfAggNamesClashCheckNames") { - std::string configFilePath = std::string("json:/") + getenv("QUALITYCONTROL_ROOT") + "/test/testQCFactory.json"; - auto workflow = InfrastructureGenerator::generateRemoteInfrastructure(configFilePath); + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto configInterface = ConfigurationFactory::getConfiguration(configFilePath); + auto configTree = configInterface->getRecursive(); + auto infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(configTree, WorkflowType::Standalone); + CHECK_NOTHROW(InfrastructureGenerator::throwIfAggNamesClashCheckNames(infrastructureSpec)); // should not throw + + configFilePath = std::string("json://") + getTestDataDirectory() + "testThrowNameClash.json"; + configInterface = ConfigurationFactory::getConfiguration(configFilePath); + configTree = configInterface->getRecursive(); + infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(configTree, WorkflowType::Standalone); + CHECK_THROWS(InfrastructureGenerator::throwIfAggNamesClashCheckNames(infrastructureSpec)); // should throw +} + +TEST_CASE("qc_factory_remote_test") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto configInterface = ConfigurationFactory::getConfiguration(configFilePath); + auto configTree = configInterface->getRecursive(); + auto workflow = InfrastructureGenerator::generateRemoteInfrastructure(configTree); + + // the infrastructure should consist of two proxies, mergers and checkers for 'skeletonTask' and 'recoTask' + // (their taskRunner are declared to be local) and also taskRunner and checker for the 'abcTask' and 'xyzTask'. + // Post processing adds one process for the task and one for checks. + // BookkeepingSink is added with 2 inputs (one from Check and one from Aggregator) + REQUIRE(workflow.size() == 16); + + auto tcpclustProxy = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "tpcclust" && + d.inputs.size() == 0 && + d.outputs.size() == 1; + }); + CHECK(tcpclustProxy != workflow.end()); + + auto skeletonTaskProxy = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "TST-skeletonTask-proxy" && + d.inputs.size() == 0 && + d.outputs.size() == 2; + }); + CHECK(skeletonTaskProxy != workflow.end()); - // the infrastructure should consist of a merger and checker for the 'skeletonTask' (its taskRunner is declared to be - // local) and also taskRunner and checker for the 'abcTask'. - BOOST_REQUIRE_EQUAL(workflow.size(), 4); + auto recoTaskProxy = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "GLO-recoTask-proxy" && + d.inputs.size() == 0 && + d.outputs.size() == 1; + }); + CHECK(skeletonTaskProxy != workflow.end()); auto mergerSkeletonTask = std::find_if( workflow.begin(), workflow.end(), [](const DataProcessorSpec& d) { - auto concreteInput0 = DataSpecUtils::asConcreteDataMatcher(d.inputs[0]); - auto concreteInput1 = DataSpecUtils::asConcreteDataMatcher(d.inputs[1]); - return d.name == "skeletonTask-merger" && - d.inputs.size() == 2 && concreteInput0.subSpec == 1 && concreteInput1.subSpec == 2 && - d.outputs.size() == 1 && d.outputs[0].subSpec == 0; + return d.name.find("TST-MERGER-skeletonTask") != std::string::npos && + d.inputs.size() == 3 && + d.outputs.size() == 2 && DataSpecUtils::getOptionalSubSpec(d.outputs[0]).value_or(-1) == 0; }); - BOOST_CHECK(mergerSkeletonTask != workflow.end()); + CHECK(mergerSkeletonTask != workflow.end()); - auto checkerSkeletonTask = std::find_if( + auto mergerRecoTask = std::find_if( workflow.begin(), workflow.end(), [](const DataProcessorSpec& d) { - return d.name == "skeletonTask-checker" && + return d.name.find("GLO-MERGER-recoTask") != std::string::npos && + d.inputs.size() == 2 && + d.outputs.size() == 1 && DataSpecUtils::getOptionalSubSpec(d.outputs[0]).value_or(-1) == 0; + }); + CHECK(mergerRecoTask != workflow.end()); + + auto taskRunnerAbcTask = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-task-MISC-abcTask" && + d.inputs.size() == 2 && + d.outputs.size() == 1; + }); + CHECK(taskRunnerAbcTask != workflow.end()); + + auto taskRunnerXyzTask = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-task-ITS-xyzTask" && + d.inputs.size() == 2 && + d.outputs.size() == 1; + }); + CHECK(taskRunnerXyzTask != workflow.end()); + + // This task shouldn't be generated here - it is local + auto taskRunnerSkeletonTask = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-task-TST-skeletonTask"; + }); + CHECK(taskRunnerSkeletonTask == workflow.end()); + + auto checkRunnerCount = std::count_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name.find("qc-check") != std::string::npos && + d.inputs.size() == 1; + }); + REQUIRE(checkRunnerCount == 4); + + auto checkSinkCount = std::count_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name.find("qc-sink") != std::string::npos && + d.inputs.size() == 1; + }); + REQUIRE(checkSinkCount == 2); + + auto postprocessingTask = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-pp-TST-SkeletonPostProcessing" && d.inputs.size() == 1 && d.outputs.size() == 1; }); - BOOST_CHECK(checkerSkeletonTask != workflow.end()); + CHECK(postprocessingTask != workflow.end()); + + auto aggregator = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-aggregator" && + d.inputs.size() == 4 && + d.outputs.size() == 4; + }); + CHECK(aggregator != workflow.end()); + + auto bookkeepingSink = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "BookkeepingSink" && + d.inputs.size() == 2 && + d.outputs.size() == 0; + }); + CHECK(bookkeepingSink != workflow.end()); +} + +TEST_CASE("qc_factory_standalone_test") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto configInterface = ConfigurationFactory::getConfiguration(configFilePath); + auto configTree = configInterface->getRecursive(); + auto workflow = InfrastructureGenerator::generateStandaloneInfrastructure(configTree); + + // the infrastructure should consist of 4 TaskRunners, 1 PostProcessingRunner, 5 CheckRunners (including one for PP), 1 AggregatorRunner, 1 BookkeepingSink + REQUIRE(workflow.size() == 12); + + auto taskRunnerSkeleton = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-task-TST-skeletonTask" && + d.inputs.size() == 2 && + d.outputs.size() == 1; + }); + CHECK(taskRunnerSkeleton != workflow.end()); auto taskRunnerAbcTask = std::find_if( workflow.begin(), workflow.end(), [](const DataProcessorSpec& d) { - return d.name == "abcTask" && + return d.name == "qc-task-MISC-abcTask" && + d.inputs.size() == 2 && + d.outputs.size() == 1; + }); + CHECK(taskRunnerAbcTask != workflow.end()); + + auto taskRunnerXyzTask = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-task-ITS-xyzTask" && + d.inputs.size() == 2 && + d.outputs.size() == 1; + }); + CHECK(taskRunnerXyzTask != workflow.end()); + + auto taskRunnerRecoTask = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-task-GLO-recoTask" && + d.inputs.size() == 13 && + d.outputs.size() == 1; + }); + CHECK(taskRunnerRecoTask != workflow.end()); + + auto checkRunnerCount = std::count_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name.find("qc-check") != std::string::npos && + d.inputs.size() == 1; + }); + REQUIRE(checkRunnerCount == 4); + + auto checkSinkCount = std::count_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name.find("qc-sink") != std::string::npos && + d.inputs.size() == 1; + }); + REQUIRE(checkSinkCount == 1); + + auto postprocessingTask = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-pp-TST-SkeletonPostProcessing" && d.inputs.size() == 1 && d.outputs.size() == 1; }); - BOOST_CHECK(taskRunnerAbcTask != workflow.end()); + CHECK(postprocessingTask != workflow.end()); + + auto aggregator = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-aggregator" && + d.inputs.size() == 4 && + d.outputs.size() == 4; + }); + CHECK(aggregator != workflow.end()); - auto checkerAbcTask = std::find_if( + auto bookkeepingSink = std::find_if( workflow.begin(), workflow.end(), [](const DataProcessorSpec& d) { - return d.name == "abcTask-checker" && + return d.name == "BookkeepingSink" && + d.inputs.size() == 2 && + d.outputs.size() == 0; + }); + CHECK(bookkeepingSink != workflow.end()); +} + +TEST_CASE("qc_factory_empty_config") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testEmptyConfig.json"; + auto configInterface = ConfigurationFactory::getConfiguration(configFilePath); + auto configTree = configInterface->getRecursive(); + { + WorkflowSpec workflow; + REQUIRE_NOTHROW(InfrastructureGenerator::generateStandaloneInfrastructure(workflow, configTree)); + CHECK(workflow.size() == 0); + } + { + WorkflowSpec workflow; + REQUIRE_NOTHROW(InfrastructureGenerator::generateLocalInfrastructure(workflow, configTree, "asdf")); + CHECK(workflow.size() == 0); + } + { + WorkflowSpec workflow; + REQUIRE_NOTHROW(InfrastructureGenerator::generateRemoteInfrastructure(workflow, configTree)); + CHECK(workflow.size() == 0); + } + { + WorkflowSpec workflow; + REQUIRE_NOTHROW(InfrastructureGenerator::generateLocalBatchInfrastructure(workflow, configTree, "file.root")); + CHECK(workflow.size() == 0); + } + { + WorkflowSpec workflow; + REQUIRE_NOTHROW(InfrastructureGenerator::generateRemoteBatchInfrastructure(workflow, configTree, "file.root")); + CHECK(workflow.size() == 0); + } +} + +TEST_CASE("qc_infrastructure_local_batch_test") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + std::cout << configFilePath << std::endl; + auto configInterface = ConfigurationFactory::getConfiguration(configFilePath); + auto configTree = configInterface->getRecursive(); + { + auto workflow = InfrastructureGenerator::generateLocalBatchInfrastructure(configTree, "file.root"); + + REQUIRE(workflow.size() == 5); + + auto taskRunnerSkeleton = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-task-TST-skeletonTask" && + d.inputs.size() == 2 && + d.outputs.size() == 1; + }); + CHECK(taskRunnerSkeleton != workflow.end()); + + auto taskRunnerReco = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-task-GLO-recoTask" && + d.inputs.size() == 13 && + d.outputs.size() == 1; + }); + CHECK(taskRunnerReco != workflow.end()); + + auto taskRunnerAbcTask = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-task-MISC-abcTask" && + d.inputs.size() == 2 && + d.outputs.size() == 1; + }); + CHECK(taskRunnerAbcTask != workflow.end()); + + auto taskRunnerXyzTask = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-task-ITS-xyzTask" && + d.inputs.size() == 2 && + d.outputs.size() == 1; + }); + CHECK(taskRunnerXyzTask != workflow.end()); + + CHECK(workflow[4].name == "qc-root-file-sink"); + CHECK(workflow[4].inputs.size() == 4); + CHECK(workflow[4].outputs.size() == 0); + } +} + +TEST_CASE("qc_infrastructure_remote_batch_test") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto configInterface = ConfigurationFactory::getConfiguration(configFilePath); + auto configTree = configInterface->getRecursive(); + auto workflow = InfrastructureGenerator::generateRemoteBatchInfrastructure(configTree, "file.root"); + + REQUIRE(workflow.size() == 10); + + auto fileReader = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-root-file-source" && + d.inputs.size() == 0 && + d.outputs.size() == 5; + }); + CHECK(fileReader != workflow.end()); + + auto checkRunnerCount = std::count_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name.find("qc-check") != std::string::npos && + d.inputs.size() == 1; + }); + REQUIRE(checkRunnerCount == 4); + + auto checkSinkCount = std::count_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name.find("qc-sink") != std::string::npos && + d.inputs.size() == 1; + }); + REQUIRE(checkSinkCount == 2); + + auto postprocessingTask = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-pp-TST-SkeletonPostProcessing" && d.inputs.size() == 1 && d.outputs.size() == 1; }); - BOOST_CHECK(checkerAbcTask != workflow.end()); -} \ No newline at end of file + CHECK(postprocessingTask != workflow.end()); + + auto aggregator = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "qc-aggregator" && + d.inputs.size() == 4 && + d.outputs.size() == 4; + }); + CHECK(aggregator != workflow.end()); + + auto bookkeepingSink = std::find_if( + workflow.begin(), workflow.end(), + [](const DataProcessorSpec& d) { + return d.name == "BookkeepingSink" && + d.inputs.size() == 2 && + d.outputs.size() == 0; + }); + CHECK(bookkeepingSink != workflow.end()); +} diff --git a/Framework/test/testKafkaTests.cxx b/Framework/test/testKafkaTests.cxx new file mode 100644 index 0000000000..87b4560083 --- /dev/null +++ b/Framework/test/testKafkaTests.cxx @@ -0,0 +1,460 @@ +// Copyright 2024 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testKafkaTests.cxx +/// \author Michal Tichak +/// \brief Tests for kafkaPoller class and StartOfRun and EndOfRun triggers +/// +/// Detailed description: +/// We are testing SOR and EOR triggers here so we have all tests that require +/// Kafka in one place. Tests in this file can be run manually only right now, +/// as there is no kafka cluster setup as a part of CI/CD for QC. +/// In order to run these tests you need to setup TEST_KAFKA_CLUSTER environment +/// variable and to run these tests call from command line: +/// `o2-qc-test-core [.manual_kafka]` +/// +/// NOTE: it might be necessary to recreate or purge topic when you are doing +/// a lot of consecutive tests. + +#include +#include +#include +#include +#include +#include +#include "kafka/ProducerRecord.h" +#include "kafka/Types.h" + +#include +#include +#include +#include + +// get test kafka cluster id from Environment variable TEST_KAFKA_CLUSTER +auto getClusterURLFromEnv() -> std::string +{ + const auto* val = std::getenv("TEST_KAFKA_CLUSTER"); + if (val == nullptr) { + throw std::runtime_error("TEST_KAFKA_CLUSTER env variable was not set"); + } + return std::string{ val }; +} + +constexpr auto testTopic = "qc_test_topic"; + +constexpr auto globalRunNumber = 123; +constexpr auto timestamp = 1234; +constexpr auto globalEnvironmentId = "envID"; + +auto createSoSorProtoMessage(const std::string& environmentId = globalEnvironmentId, int runNumber = globalRunNumber) -> events::Event +{ + events::Event event; + event.set_timestamp(1234); + + auto* runEvent = event.mutable_runevent(); + runEvent->set_transition("START_ACTIVITY"); + runEvent->set_state("CONFIGURED"); + runEvent->set_transitionstatus(events::OpStatus::STARTED); + runEvent->set_environmentid(environmentId); + runEvent->set_runnumber(runNumber); + + return event; +}; + +auto createEoSorProtoMessage(const std::string& environmentId = globalEnvironmentId, int runNumber = globalRunNumber) -> events::Event +{ + events::Event event; + event.set_timestamp(1234); + + auto* runEvent = event.mutable_runevent(); + runEvent->set_transition("START_ACTIVITY"); + runEvent->set_state("CONFIGURED"); + runEvent->set_transitionstatus(events::OpStatus::DONE_OK); + runEvent->set_environmentid(environmentId); + runEvent->set_runnumber(runNumber); + + return event; +}; + +auto createSoEorProtoMessage(const std::string& environmentId = globalEnvironmentId, int runNumber = globalRunNumber) -> events::Event +{ + events::Event event; + event.set_timestamp(1234); + + auto* runEvent = event.mutable_runevent(); + runEvent->set_transition("STOP_ACTIVITY"); + runEvent->set_state("RUNNING"); + runEvent->set_transitionstatus(events::OpStatus::STARTED); + runEvent->set_environmentid(environmentId); + runEvent->set_runnumber(runNumber); + + return event; +} + +auto createEoEorProtoMessage(const std::string& environmentId = globalEnvironmentId, int runNumber = globalRunNumber) -> events::Event +{ + events::Event event; + event.set_timestamp(1234); + + auto* runEvent = event.mutable_runevent(); + runEvent->set_transition("STOP_ACTIVITY"); + runEvent->set_state("RUNNING"); + runEvent->set_transitionstatus(events::OpStatus::DONE_OK); + runEvent->set_environmentid(environmentId); + runEvent->set_runnumber(runNumber); + + return event; +} + +auto createEorTeardownProtoMessage() -> events::Event +{ + events::Event event; + event.set_timestamp(1234); + + auto* runEvent = event.mutable_runevent(); + runEvent->set_transition("TEARDOWN"); + runEvent->set_state("RUNNING"); + runEvent->set_transitionstatus(events::OpStatus::STARTED); + runEvent->set_environmentid(globalEnvironmentId); + runEvent->set_runnumber(globalRunNumber); + + return event; +} + +void sendMessage(kafka::clients::producer::KafkaProducer& producer, events::Event&& event, const std::string& topic = testTopic) +{ + std::binary_semaphore semaphore{ 0 }; + auto deliveryCb = [&semaphore](const kafka::clients::producer::RecordMetadata& metadata, const kafka::Error& error) { + semaphore.release(); + }; + + std::string buffer{}; + REQUIRE(event.SerializeToString(&buffer)); + kafka::clients::producer::ProducerRecord record(topic, kafka::NullKey, kafka::Value(buffer.data(), buffer.size())); + + producer.send(record, deliveryCb, kafka::clients::producer::KafkaProducer::SendOption::ToCopyRecordValue); + semaphore.acquire(); +} + +void sendSorAndEor() +{ + const auto kafkaCluster = getClusterURLFromEnv(); + kafka::Properties props{ { { "bootstrap.servers", { kafkaCluster } } } }; + kafka::clients::producer::KafkaProducer producer(props); + + sendMessage(producer, createSoSorProtoMessage()); + sendMessage(producer, createSoEorProtoMessage()); +} + +void sendSorAndTeardown() +{ + kafka::Properties props{ { + { "bootstrap.servers", { getClusterURLFromEnv() } }, + } }; + kafka::clients::producer::KafkaProducer producer(props); + + sendMessage(producer, createSoSorProtoMessage()); + sendMessage(producer, createEorTeardownProtoMessage()); +} + +TEST_CASE("test_kafka_empty_brokers", "[.manual_kafka]") +{ + using namespace o2::quality_control::core; + REQUIRE_THROWS_AS(KafkaPoller("", ""), std::invalid_argument); +} + +TEST_CASE("test_kafka_poller_soreor_empty_brokers", "[.manual_kafka]") +{ + using namespace o2::quality_control; + core::Activity activityMatching{}; + REQUIRE_THROWS_AS(postprocessing::triggers::StartOfRun("", testTopic, "TST", "sor_test_matching", activityMatching), std::invalid_argument); + REQUIRE_THROWS_AS(postprocessing::triggers::StartOfRun("emptystring", "", "TST", "sor_test_matching", activityMatching), std::invalid_argument); + REQUIRE_THROWS_AS(postprocessing::triggers::EndOfRun("", testTopic, "TST", "sor_test_matching", activityMatching), std::invalid_argument); + REQUIRE_THROWS_AS(postprocessing::triggers::EndOfRun("emptystring", "", "TST", "sor_test_matching", activityMatching), std::invalid_argument); +} + +TEST_CASE("test_kafka_poller_soreor", "[.manual_kafka]") +{ + const auto kafkaCluster = getClusterURLFromEnv(); + using namespace o2::quality_control::core; + KafkaPoller kafkaPoller(kafkaCluster, "unitTestID"); + REQUIRE_NOTHROW(kafkaPoller.subscribe(testTopic)); + // this timeout help to keep order of subscribing and consuming + std::this_thread::sleep_for(std::chrono::milliseconds{ 500 }); + + sendSorAndEor(); + + bool receivedSOR = false; + bool receivedEOR = false; + + while (!receivedSOR || !receivedEOR) { + std::this_thread::sleep_for(std::chrono::seconds{ 1 }); + const auto records = kafkaPoller.poll(); + + for (const auto& record : records) { + const auto event = proto::recordToEvent(record.value()); + // we just need values that are different from global ones + if (proto::start_of_run::isValid(event.value(), "randomEnvID", 1)) { + receivedSOR = true; + } + if (proto::end_of_run::isValid(event.value(), "randomEnvID", 1)) { + receivedEOR = true; + } + } + + if (receivedSOR && receivedEOR) { + break; + } + } +} + +TEST_CASE("test_kafka_poller_sorteardown", "[.manual_kafka]") +{ + using namespace o2::quality_control::core; + const auto kafkaCluster = getClusterURLFromEnv(); + KafkaPoller kafkaPoller(kafkaCluster, "unitTestID"); + REQUIRE_NOTHROW(kafkaPoller.subscribe(testTopic)); + + // this timeout help to keep order of subscribing and consuming + std::this_thread::sleep_for(std::chrono::milliseconds{ 500 }); + + sendSorAndEor(); + + bool receivedSOR = false; + bool receivedTeardown = false; + + while (!receivedSOR || !receivedTeardown) { + std::this_thread::sleep_for(std::chrono::seconds{ 1 }); + const auto records = kafkaPoller.poll(); + + for (const auto& record : records) { + const auto event = proto::recordToEvent(record.value()); + // we just need values that are different from global ones + if (proto::start_of_run::isValid(event.value(), "randomEnvID", 1)) { + receivedSOR = true; + } + if (proto::end_of_run::isValid(event.value(), "randomEnvID", 1)) { + receivedTeardown = true; + } + } + + if (receivedSOR && receivedTeardown) { + break; + } + } +} + +TEST_CASE("test_SOR_trigger", "[.manual_kafka]") +{ + using namespace o2::quality_control; + + const auto kafkaCluster = getClusterURLFromEnv(); + constexpr auto differentEnvId = "differentEnvId"; + constexpr int differentRunNumber = 42; + + core::Activity activityMatching; + activityMatching.mId = differentRunNumber; + activityMatching.mPartitionName = differentEnvId; + auto sorTriggerMatchingFn = postprocessing::triggers::StartOfRun(kafkaCluster, testTopic, "TST", "sor_test_matching", activityMatching); + // NOTE: calls for triggers in the beginning of function are meant to get rid of any offset lags on cluster + sorTriggerMatchingFn(); + + core::Activity activityNotMatching; + activityNotMatching.mId = globalRunNumber; + activityNotMatching.mPartitionName = globalEnvironmentId; + auto sorTriggerNotMatchingFn = postprocessing::triggers::StartOfRun(kafkaCluster, testTopic, "TST", "sor_test_notmatching", activityNotMatching); + sorTriggerNotMatchingFn(); + + core::Activity activityMatchingDiffRunNumber; + activityMatchingDiffRunNumber.mId = differentRunNumber; + activityMatchingDiffRunNumber.mPartitionName = globalEnvironmentId; + auto sorTriggerNotMatchingDiffRunNumber = postprocessing::triggers::StartOfRun(kafkaCluster, testTopic, "TST", "sor_test_not_matching_diff_runnumber", activityMatchingDiffRunNumber); + sorTriggerNotMatchingDiffRunNumber(); + + core::Activity activityMatchingDiffEnvId; + activityMatchingDiffEnvId.mId = globalRunNumber; + activityMatchingDiffEnvId.mPartitionName = differentEnvId; + auto sorTriggerNotMatchingDiffEnvId = postprocessing::triggers::StartOfRun(kafkaCluster, testTopic, "TST", "sor_test_not_matching_diff_envid", activityMatchingDiffEnvId); + sorTriggerNotMatchingDiffEnvId(); + + { + auto trigger = sorTriggerMatchingFn(); + REQUIRE(trigger.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTrigger = sorTriggerNotMatchingFn(); + REQUIRE(notMatchingTrigger.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTriggerDiffRunNumber = sorTriggerNotMatchingDiffRunNumber(); + REQUIRE(notMatchingTriggerDiffRunNumber.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTriggerDiffEnvId = sorTriggerNotMatchingDiffEnvId(); + REQUIRE(notMatchingTriggerDiffEnvId.triggerType == postprocessing::TriggerType::No); + } + + kafka::Properties props{ { { "bootstrap.servers", { kafkaCluster } } } }; + kafka::clients::producer::KafkaProducer producer(props); + + sendMessage(producer, createEoSorProtoMessage()); + + // kafka consumer inside trigger is using default poll timeout (10ms), so we wait here + // for synchronisation purposes + std::this_thread::sleep_for(std::chrono::milliseconds{ 500 }); + + { + auto trigger = sorTriggerMatchingFn(); + REQUIRE(trigger.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTrigger = sorTriggerNotMatchingFn(); + REQUIRE(notMatchingTrigger.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTriggerDiffRunNumber = sorTriggerNotMatchingDiffRunNumber(); + REQUIRE(notMatchingTriggerDiffRunNumber.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTriggerDiffEnvId = sorTriggerNotMatchingDiffEnvId(); + REQUIRE(notMatchingTriggerDiffEnvId.triggerType == postprocessing::TriggerType::No); + } + + sendMessage(producer, createSoSorProtoMessage()); + + // kafka consumer inside trigger is using default poll timeout (10ms), so we wait here + // for synchronisation purposes + std::this_thread::sleep_for(std::chrono::milliseconds{ 500 }); + + { + auto trigger = sorTriggerMatchingFn(); + auto& filledActivity = trigger.activity; + REQUIRE(trigger.triggerType == postprocessing::TriggerType::StartOfRun); + REQUIRE(filledActivity.mId == globalRunNumber); // was set as differentRunNumber + REQUIRE(filledActivity.mPartitionName == globalEnvironmentId); // was set as differentEnvId + REQUIRE(filledActivity.mValidity.getMin() == timestamp); + + auto notmatchingTrigger = sorTriggerNotMatchingFn(); + REQUIRE(notmatchingTrigger.triggerType == postprocessing::TriggerType::No); // was set as globalEnvironmentId and globalRunNumber + + auto notMatchingTriggerDiffRunNumber = sorTriggerNotMatchingDiffRunNumber(); + auto& notMatchingActivityDiffRunNumber = trigger.activity; + REQUIRE(notMatchingTriggerDiffRunNumber.triggerType == postprocessing::TriggerType::StartOfRun); + REQUIRE(notMatchingActivityDiffRunNumber.mId == globalRunNumber); // was set as globalRunNumber + REQUIRE(notMatchingActivityDiffRunNumber.mPartitionName == globalEnvironmentId); // was set as differentEnvId + REQUIRE(notMatchingActivityDiffRunNumber.mValidity.getMin() == timestamp); + + auto notMatchingTriggerDiffEnvId = sorTriggerNotMatchingDiffEnvId(); + auto& notMatchingActivityDiffEnvId = trigger.activity; + REQUIRE(notMatchingTriggerDiffEnvId.triggerType == postprocessing::TriggerType::StartOfRun); + REQUIRE(notMatchingActivityDiffRunNumber.mId == globalRunNumber); // was set as differentRunNumber + REQUIRE(notMatchingActivityDiffRunNumber.mPartitionName == globalEnvironmentId); // was set as globalEnvironmentId + REQUIRE(notMatchingActivityDiffEnvId.mValidity.getMin() == timestamp); + } +} + +TEST_CASE("test_EOR_trigger", "[.manual_kafka]") +{ + using namespace o2::quality_control; + + const auto kafkaCluster = getClusterURLFromEnv(); + constexpr auto differentEnvId = "differentEnvId"; + constexpr int differentRunNumber = 42; + + core::Activity activityMatching; + activityMatching.mId = differentRunNumber; + activityMatching.mPartitionName = differentEnvId; + auto eorTriggerMatchingFn = postprocessing::triggers::EndOfRun(kafkaCluster, testTopic, "TST", "eor_test_matching", activityMatching); + // NOTE: calls for triggers in the beginning of function are meant to get rid of any offset lags on cluster + eorTriggerMatchingFn(); + + core::Activity activityNotMatching; + activityNotMatching.mId = globalRunNumber; + activityNotMatching.mPartitionName = globalEnvironmentId; + auto eorTriggerNotMatchingFn = postprocessing::triggers::EndOfRun(kafkaCluster, testTopic, "TST", "eor_test_notmatching", activityNotMatching); + eorTriggerNotMatchingFn(); + + core::Activity activityMatchingDiffRunNumber; + activityMatchingDiffRunNumber.mId = differentRunNumber; + activityMatchingDiffRunNumber.mPartitionName = globalEnvironmentId; + auto eorTriggerNotMatchingDiffRunNumber = postprocessing::triggers::EndOfRun(kafkaCluster, testTopic, "TST", "eor_test_not_matching_diff_runnumber", activityMatchingDiffRunNumber); + eorTriggerNotMatchingDiffRunNumber(); + + core::Activity activityMatchingDiffEnvId; + activityMatchingDiffEnvId.mId = globalRunNumber; + activityMatchingDiffEnvId.mPartitionName = differentEnvId; + auto eorTriggerNotMatchingDiffEnvId = postprocessing::triggers::EndOfRun(kafkaCluster, testTopic, "TST", "eor_test_not_matching_diff_envid", activityMatchingDiffEnvId); + eorTriggerNotMatchingDiffEnvId(); + + { + auto trigger = eorTriggerMatchingFn(); + REQUIRE(trigger.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTrigger = eorTriggerNotMatchingFn(); + REQUIRE(notMatchingTrigger.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTriggerDiffRunNumber = eorTriggerNotMatchingDiffRunNumber(); + REQUIRE(notMatchingTriggerDiffRunNumber.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTriggerDiffEnvId = eorTriggerNotMatchingDiffEnvId(); + REQUIRE(notMatchingTriggerDiffEnvId.triggerType == postprocessing::TriggerType::No); + } + + kafka::Properties props{ { { "bootstrap.servers", { kafkaCluster } } } }; + kafka::clients::producer::KafkaProducer producer(props); + + sendMessage(producer, createEoEorProtoMessage()); + + // kafka consumer inside trigger is using default poll timeout (10ms), so we wait here + // for synchronisation purposes + std::this_thread::sleep_for(std::chrono::seconds{ 1 }); + + { + auto trigger = eorTriggerMatchingFn(); + REQUIRE(trigger.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTrigger = eorTriggerNotMatchingFn(); + REQUIRE(notMatchingTrigger.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTriggerDiffRunNumber = eorTriggerNotMatchingDiffRunNumber(); + REQUIRE(notMatchingTriggerDiffRunNumber.triggerType == postprocessing::TriggerType::No); + + auto notMatchingTriggerDiffEnvId = eorTriggerNotMatchingDiffEnvId(); + REQUIRE(notMatchingTriggerDiffEnvId.triggerType == postprocessing::TriggerType::No); + } + + sendMessage(producer, createSoEorProtoMessage()); + + // kafka consumer inside trigger is using default poll timeout (10ms), so we wait here + // for synchronisation purposes + std::this_thread::sleep_for(std::chrono::seconds{ 1 }); + + { + auto trigger = eorTriggerMatchingFn(); + auto& filledActivity = trigger.activity; + REQUIRE(trigger.triggerType == postprocessing::TriggerType::EndOfRun); + REQUIRE(filledActivity.mId == globalRunNumber); // was set as differentRunNumber + REQUIRE(filledActivity.mPartitionName == globalEnvironmentId); // was set as differentEnvId + REQUIRE(filledActivity.mValidity.getMax() == timestamp); + + auto notmatchingTrigger = eorTriggerNotMatchingFn(); + REQUIRE(notmatchingTrigger.triggerType == postprocessing::TriggerType::No); // was set as globalEnvironmentId and globalRunNumber + + auto notMatchingTriggerDiffRunNumber = eorTriggerNotMatchingDiffRunNumber(); + auto& notMatchingActivityDiffRunNumber = trigger.activity; + REQUIRE(notMatchingTriggerDiffRunNumber.triggerType == postprocessing::TriggerType::EndOfRun); + REQUIRE(notMatchingActivityDiffRunNumber.mId == globalRunNumber); // was set as globalRunNumber + REQUIRE(notMatchingActivityDiffRunNumber.mPartitionName == globalEnvironmentId); // was set as differentEnvId + REQUIRE(notMatchingActivityDiffRunNumber.mValidity.getMax() == timestamp); + + auto notMatchingTriggerDiffEnvId = eorTriggerNotMatchingDiffEnvId(); + auto& notMatchingActivityDiffEnvId = trigger.activity; + REQUIRE(notMatchingTriggerDiffEnvId.triggerType == postprocessing::TriggerType::EndOfRun); + REQUIRE(notMatchingActivityDiffRunNumber.mId == globalRunNumber); // was set as differentRunNumber + REQUIRE(notMatchingActivityDiffRunNumber.mPartitionName == globalEnvironmentId); // was set as globalEnvironmentId + REQUIRE(notMatchingActivityDiffEnvId.mValidity.getMax() == timestamp); + } +} diff --git a/Framework/test/testMonitorObject.cxx b/Framework/test/testMonitorObject.cxx index ec027a37b3..b35d3e96cc 100644 --- a/Framework/test/testMonitorObject.cxx +++ b/Framework/test/testMonitorObject.cxx @@ -1,54 +1,345 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// -/// \file Publisher_test.cpp +/// \file testMonitorObject.cxx /// \author Barthelemy von Haller /// -#include "../include/QualityControl/MonitorObject.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include +#include +#include +#include +#include -#define BOOST_TEST_MODULE MO test -#define BOOST_TEST_MAIN -#define BOOST_TEST_DYN_LINK -#include -#include +using namespace std; namespace o2::quality_control::core { -BOOST_AUTO_TEST_CASE(mo) +TEST_CASE("mo") { o2::quality_control::core::MonitorObject obj; - BOOST_CHECK_EQUAL(obj.getName(), ""); - - obj.addCheck("first", "class1", "lib1"); - obj.addCheck("second", "class1", "lib1"); - obj.addCheck("third", "class2", "lib1"); - obj.addCheck("first", "class2", "lib1"); - auto checkers1 = obj.getChecks(); - BOOST_CHECK_EQUAL(checkers1["first"].name, "first"); - BOOST_CHECK_EQUAL(checkers1["first"].className, "class2"); - BOOST_CHECK_EQUAL(checkers1["first"].libraryName, "lib1"); - BOOST_CHECK_EQUAL(obj.getCheck("second").name, "second"); - BOOST_CHECK_EQUAL(obj.getCheck("second").className, "class1"); - BOOST_CHECK_EQUAL(obj.getCheck("second").libraryName, "lib1"); - - BOOST_CHECK_EQUAL(obj.getQuality(), Quality::Null); - obj.setQualityForCheck("first", Quality::Good); - BOOST_CHECK_EQUAL(obj.getQuality(), Quality::Good); - obj.setQualityForCheck("second", Quality::Bad); - BOOST_CHECK_EQUAL(obj.getQuality(), Quality::Bad); - obj.setQualityForCheck("second", Quality::Medium); - BOOST_CHECK_EQUAL(obj.getQuality(), Quality::Medium); + CHECK(obj.getName() == ""); + CHECK(std::string(obj.GetName()) == ""); +} + +TEST_CASE("mo_save") +{ + string objectName = "asdf"; + TH1F h(objectName.data(), objectName.data(), 100, 0, 99); + o2::quality_control::core::MonitorObject obj(&h, "task", "class", "DET"); + ILOG(Info, Support) << "getName : '" << obj.getName() << "'" << ENDM; + ILOG(Info, Support) << "GetName : '" << obj.GetName() << "'" << ENDM; + ILOG(Info, Support) << "title : '" << obj.GetTitle() << "'" << ENDM; + CHECK(obj.getName() == "asdf"); + CHECK(std::string(obj.GetName()) == "asdf"); + CHECK(std::string(obj.GetTitle()) == ""); + obj.setIsOwner(false); + string libName = "libraryName"; + string libName2 = "libraryName2"; + std::chrono::nanoseconds ns = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + std::string filename = string("/tmp/test") + std::to_string(ns.count()) + ".root"; + TFile file(filename.data(), "RECREATE"); + obj.Write(obj.getName().data()); + file.Close(); + + ILOG(Info, Support) << "***" << ENDM; + TFile file2(filename.data()); + auto* mo = dynamic_cast(file2.Get(objectName.data())); + CHECK(mo != nullptr); + ILOG(Info, Support) << "mo : " << mo << ENDM; + CHECK(mo->GetName() == objectName); + CHECK(mo->getName() == objectName); + ILOG(Info, Support) << "name : " << mo->GetName() << ENDM; + ILOG(Info, Support) << "name : " << mo->getName() << ENDM; + gSystem->Unlink(filename.data()); +} + +TEST_CASE("mo_clone") +{ + auto th1 = std::make_unique("name", "title"); + TObject* obj = th1.get(); + auto* cloned = obj->Clone(); + auto* secondCloned = cloned->Clone(); + delete cloned; + delete secondCloned; +} + +TEST_CASE("mo_copy") +{ + auto compareWithoutObject = [](const MonitorObject& lhs, const MonitorObject& rhs) { + REQUIRE(lhs.getName() == rhs.getName()); + REQUIRE(lhs.getTaskName() == rhs.getTaskName()); + REQUIRE(lhs.getDetectorName() == rhs.getDetectorName()); + REQUIRE(lhs.getTaskClass() == rhs.getTaskClass()); + REQUIRE(lhs.isIsOwner() == rhs.isIsOwner()); + REQUIRE(lhs.getActivity() == rhs.getActivity()); + REQUIRE(lhs.getCreateMovingWindow() == rhs.getCreateMovingWindow()); + REQUIRE(lhs.getDescription() == rhs.getDescription()); + REQUIRE(lhs.getMetadataMap() == rhs.getMetadataMap()); + }; + + SECTION("Empty orignal MO") + { + MonitorObject original{}; + + SECTION("copy of non owning object returns same pointer") + { + auto compareShallowNonOwning = [&compareWithoutObject](const MonitorObject& lhs, const MonitorObject& rhs) { + compareWithoutObject(lhs, rhs); + // we expect shallow rhs when lhs does not own the underlying object + REQUIRE(lhs.getObject() == nullptr); + REQUIRE(rhs.getObject() == nullptr); + }; + + SECTION("copy constructor") + { + MonitorObject copy{ original }; + compareShallowNonOwning(original, copy); + } + + SECTION("copy assignment operator") + { + MonitorObject copy{}; + copy = original; + compareShallowNonOwning(original, copy); + } + + SECTION("Copy method") + { + MonitorObject copy{}; + original.Copy(copy); + compareShallowNonOwning(original, copy); + } + } + + SECTION("copy of object owned by MO returns deep copy, non nullptr init") + { + auto compareTNamed = [](const MonitorObject& lhs, const MonitorObject& rhs) { + REQUIRE(lhs.getObject() == nullptr); + REQUIRE(rhs.getObject() == nullptr); + }; + + SECTION("copy owning object before") + { + MonitorObject copy{}; + copy.setObject(new TNamed("copy named", "title copy")); + copy.setIsOwner(true); + + SECTION("copy assignment operator") + { + copy = original; + compareTNamed(original, copy); + } + + SECTION("Copy method") + { + original.Copy(copy); + compareTNamed(original, copy); + } + } + } + } + + SECTION("original MO with data") + { + + MonitorObject original{}; + + original.setTaskName("taskName"); + original.setTaskClass("taskClass"); + original.setDescription("description"); + original.setDetectorName("TST"); + original.setActivity({ 123, "type", "periodName", "passName", "provenance", gFullValidityInterval, "beamType", "partitionName", 2 }); + original.setCreateMovingWindow(true); + + SECTION("copy of non owning object returns same pointer") + { + auto compareShallowNonOwning = [&compareWithoutObject](const MonitorObject& lhs, const MonitorObject& rhs) { + compareWithoutObject(lhs, rhs); + // we expect shallow rhs when lhs does not own the underlying object + REQUIRE((lhs.getObject() != nullptr && lhs.getObject() == rhs.getObject())); + }; + + auto th1 = TH1I("name", "title", 10, 0, 10); + th1.Fill(8); + original.setObject(&th1); + original.setIsOwner(false); + + SECTION("copy constructor") + { + MonitorObject copy{ original }; + compareShallowNonOwning(original, copy); + } + + SECTION("copy assignment operator") + { + MonitorObject copy{}; + copy = original; + compareShallowNonOwning(original, copy); + } + + SECTION("Copy method") + { + MonitorObject copy{}; + original.Copy(copy); + compareShallowNonOwning(original, copy); + } + } + + SECTION("copy of object owned by MO returns deep copy, init from nullptr") + { + auto compareTNamed = [](const MonitorObject& lhs, const MonitorObject& rhs) { + auto* namedoriginal = static_cast(lhs.getObject()); + auto* namedcopy = static_cast(rhs.getObject()); + REQUIRE(std::string(namedoriginal->GetName()) == std::string(namedcopy->GetName())); + REQUIRE(std::string(namedoriginal->GetTitle()) == std::string(namedcopy->GetTitle())); + }; + + auto* named = new TNamed("named", "title"); + original.setObject(named); + original.setIsOwner(true); + + SECTION("copy constructor") + { + MonitorObject copy{ original }; + compareTNamed(original, copy); + } + + SECTION("copy assignment operator") + { + MonitorObject copy{}; + copy = original; + compareTNamed(original, copy); + } + + SECTION("Copy method") + { + MonitorObject copy{}; + original.Copy(copy); + compareTNamed(original, copy); + } + } + + SECTION("copy of object owned by MO returns deep copy, non nullptr init") + { + auto compareTNamed = [](const MonitorObject& lhs, const MonitorObject& rhs) { + auto* namedoriginal = static_cast(lhs.getObject()); + auto* namedcopy = static_cast(rhs.getObject()); + REQUIRE(std::string(namedoriginal->GetName()) == std::string(namedcopy->GetName())); + REQUIRE(std::string(namedoriginal->GetTitle()) == std::string(namedcopy->GetTitle())); + }; + + auto* named = new TNamed("named", "title"); + original.setObject(named); + original.setIsOwner(true); + + SECTION("copy owning object before") + { + MonitorObject copy{}; + copy.setObject(new TNamed("copy named", "title copy")); + copy.setIsOwner(true); + + SECTION("copy assignment operator") + { + copy = original; + compareTNamed(original, copy); + } + + SECTION("Copy method") + { + original.Copy(copy); + compareTNamed(original, copy); + } + } + } + } +} + +TEST_CASE("metadata") +{ + string objectName = "asdf"; + TH1F h(objectName.data(), objectName.data(), 100, 0, 99); + + // no metadata at creation + o2::quality_control::core::MonitorObject obj(&h, "task", "class", "DET"); + obj.setIsOwner(false); + CHECK(obj.getMetadataMap().size() == 0); + + // add metadata with key value, check it is there + obj.addMetadata("key1", "value1"); + CHECK(obj.getMetadataMap().size() == 1); + CHECK(obj.getMetadataMap().at("key1") == "value1"); + + // add same key again -> ignore + obj.addMetadata("key1", "value1"); + CHECK(obj.getMetadataMap().size() == 1); + auto test = obj.getMetadataMap(); + CHECK(obj.getMetadataMap().at("key1") == "value1"); + + // add map + map another = { { "key2", "value2" }, { "key3", "value3" } }; + obj.addMetadata(another); + CHECK(obj.getMetadataMap().size() == 3); + CHECK(obj.getMetadataMap().at("key1") == "value1"); + CHECK(obj.getMetadataMap().at("key2") == "value2"); + CHECK(obj.getMetadataMap().at("key3") == "value3"); + + // add map sharing some keys -> those are ignored not the others + map another2 = { { "key2", "value2a" }, { "key4", "value4" } }; + obj.addMetadata(another2); + CHECK(obj.getMetadataMap().size() == 4); + CHECK(obj.getMetadataMap().at("key1") == "value1"); + CHECK(obj.getMetadataMap().at("key2") == "value2"); + CHECK(obj.getMetadataMap().at("key3") == "value3"); + CHECK(obj.getMetadataMap().at("key4") == "value4"); + + // update value of existing key + obj.updateMetadata("key1", "value11"); + CHECK(obj.getMetadataMap().size() == 4); + CHECK(obj.getMetadataMap().at("key1") == "value11"); + + // update value of non-existing key -> ignore + obj.updateMetadata("asdf", "asdf"); + CHECK(obj.getMetadataMap().size() == 4); } -BOOST_AUTO_TEST_CASE(mo_check) + +TEST_CASE("path") +{ + string objectName = "asdf"; + TH1F h(objectName.data(), objectName.data(), 100, 0, 99); + o2::quality_control::core::MonitorObject obj(&h, "task", "class", "DET"); + obj.setIsOwner(false); + string path = obj.getPath(); + CHECK(path == "qc/DET/MO/task/asdf"); +} + +TEST_CASE("validity") { o2::quality_control::core::MonitorObject obj; - BOOST_CHECK_EQUAL(obj.getQuality(), Quality::Null); - CheckDefinition check; - check.name = "test"; - check.libraryName = "test"; - check.className = "test"; - obj.addOrReplaceCheck("test", check); - BOOST_CHECK_EQUAL(obj.getQuality(), Quality::Null); + + CHECK(obj.getValidity() == gInvalidValidityInterval); + + obj.updateValidity(1234); + CHECK(obj.getValidity() == o2::quality_control::core::ValidityInterval(1234, 1234)); + obj.updateValidity(9000); + CHECK(obj.getValidity() == o2::quality_control::core::ValidityInterval(1234, 9000)); + + obj.setValidity({ 3, 4 }); + CHECK(obj.getValidity() == o2::quality_control::core::ValidityInterval(3, 4)); } } // namespace o2::quality_control::core diff --git a/Framework/test/testMonitorObjectCollection.cxx b/Framework/test/testMonitorObjectCollection.cxx new file mode 100644 index 0000000000..0b77f2e39e --- /dev/null +++ b/Framework/test/testMonitorObjectCollection.cxx @@ -0,0 +1,277 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testMonitorObjectCollection.cxx +/// \author Piotr Konopka +/// + +#include "Framework/include/QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/MonitorObjectCollection.h" +#include "QualityControl/MonitorObject.h" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace o2::mergers; + +namespace o2::quality_control::core +{ + +TEST_CASE("monitor_object_collection_merge") +{ + const size_t bins = 10; + const size_t min = 0; + const size_t max = 10; + + // Setting up the target. Histo 1D + MonitorObjectCollection* target = new MonitorObjectCollection(); + target->SetOwner(true); + + TH1I* targetTH1I = new TH1I("histo 1d", "histo 1d", bins, min, max); + targetTH1I->Fill(5); + MonitorObject* targetMoTH1I = new MonitorObject(targetTH1I, "histo 1d", "class", "DET"); + targetMoTH1I->setActivity({ 300000, "PHYSICS", "LHC32x", "apass2", "qc_async", gInvalidValidityInterval }); + targetMoTH1I->setIsOwner(true); + target->Add(targetMoTH1I); + + // Setting up the other. Histo 1D + Histo 2D + MonitorObjectCollection* other = new MonitorObjectCollection(); + other->SetOwner(true); + + TH1I* otherTH1I = new TH1I("histo 1d", "histo 1d", bins, min, max); + otherTH1I->Fill(5); + MonitorObject* otherMoTH1I = new MonitorObject(otherTH1I, "histo 1d", "class", "DET"); + otherMoTH1I->setActivity({ 300000, "PHYSICS", "LHC32x", "apass2", "qc_async", { 43, 60 } }); + otherMoTH1I->setIsOwner(true); + other->Add(otherMoTH1I); + + TH2I* otherTH2I = new TH2I("histo 2d", "histo 2d", bins, min, max, bins, min, max); + otherTH2I->Fill(5, 5); + MonitorObject* otherMoTH2I = new MonitorObject(otherTH2I, "histo 2d", "class", "DET"); + other->Add(otherMoTH2I); + + // Merge 1st time + CHECK_NOTHROW(algorithm::merge(target, other)); + + // Merge 2nd time to check stability and correct bevahiour with objects of wrong validity + otherMoTH1I->setValidity(gInvalidValidityInterval); + otherMoTH2I->setValidity(gInvalidValidityInterval); + otherTH1I->Reset(); + otherTH2I->Reset(); + CHECK_NOTHROW(algorithm::merge(target, other)); + + // Make sure that deleting the object present only in `other` doesn't delete it in the `target` + delete other; + + // Checks + REQUIRE(target->GetEntries() == 2); + + MonitorObject* resultMoTH1I = dynamic_cast(target->FindObject("histo 1d")); + REQUIRE(resultMoTH1I != nullptr); + TH1I* resultTH1I = dynamic_cast(resultMoTH1I->getObject()); + REQUIRE(resultTH1I != nullptr); + CHECK(resultTH1I->GetBinContent(resultTH1I->FindBin(5)) == 2); + CHECK(resultMoTH1I->getValidity() == ValidityInterval{ 43, 60 }); + + MonitorObject* resultMoTH2I = dynamic_cast(target->FindObject("histo 2d")); + REQUIRE(resultMoTH2I != nullptr); + TH2I* resultTH2I = dynamic_cast(resultMoTH2I->getObject()); + REQUIRE(resultTH2I != nullptr); + CHECK(resultTH2I->GetBinContent(resultTH2I->FindBin(5, 5)) == 1); + + delete target; +} + +TEST_CASE("monitor_object_collection_merge_different_id") +{ + const auto toHisto = [](std::unique_ptr& collection) -> TH1I* { + return dynamic_cast(dynamic_cast(collection->At(0))->getObject()); + }; + + constexpr size_t bins = 10; + constexpr size_t min = 0; + constexpr size_t max = 10; + + SECTION("other has higher run number than target") + { + auto target = std::make_unique(); + + auto* targetTH1I = new TH1I("histo 1d", "original", bins, min, max); + targetTH1I->Fill(5); + auto* targetMoTH1I = new MonitorObject(targetTH1I, "histo 1d", "class", "DET"); + targetMoTH1I->setActivity({ 123, "PHYSICS", "LHC32x", "apass2", "qc_async", { 10, 20 } }); + targetMoTH1I->setIsOwner(true); + target->Add(targetMoTH1I); + + auto other = std::make_unique(); + other->SetOwner(true); + + auto* otherTH1I = new TH1I("histo 1d", "input", bins, min, max); + otherTH1I->Fill(2); + auto* otherMoTH1I = new MonitorObject(otherTH1I, "histo 1d", "class", "DET"); + otherMoTH1I->setActivity({ 1234, "PHYSICS", "LHC32x", "apass2", "qc_async", { 43, 60 } }); + otherMoTH1I->setIsOwner(true); + other->Add(otherMoTH1I); + + CHECK_NOTHROW(algorithm::merge(target.get(), other.get())); + auto* h1orig = toHisto(target); + auto* h1other = toHisto(other); + REQUIRE(h1orig->GetAt(3) == 1); + for (size_t i = 0; i != h1orig->GetSize(); ++i) { + REQUIRE(h1orig->GetAt(i) == h1other->GetAt(i)); + } + } + + SECTION("other has lower run number than target") + { + auto target = std::make_unique(); + + auto* targetTH1I = new TH1I("histo 1d", "original", bins, min, max); + targetTH1I->Fill(5); + auto* targetMoTH1I = new MonitorObject(targetTH1I, "histo 1d", "class", "DET"); + targetMoTH1I->setActivity({ 1234, "PHYSICS", "LHC32x", "apass2", "qc_async", { 10, 20 } }); + targetMoTH1I->setIsOwner(true); + target->Add(targetMoTH1I); + + auto other = std::make_unique(); + other->SetOwner(true); + + auto* otherTH1I = new TH1I("histo 1d", "input", bins, min, max); + otherTH1I->Fill(2); + auto* otherMoTH1I = new MonitorObject(otherTH1I, "histo 1d", "class", "DET"); + otherMoTH1I->setActivity({ 123, "PHYSICS", "LHC32x", "apass2", "qc_async", { 43, 60 } }); + otherMoTH1I->setIsOwner(true); + other->Add(otherMoTH1I); + + CHECK_NOTHROW(algorithm::merge(target.get(), other.get())); + auto* h1orig = toHisto(target); + auto* h1other = toHisto(other); + REQUIRE(h1orig->At(h1orig->FindBin(5)) == 1); + REQUIRE(h1other->At(h1other->FindBin(5)) == 0); + REQUIRE(h1orig->At(h1orig->FindBin(2)) == 0); + REQUIRE(h1other->At(h1other->FindBin(2)) == 1); + } +} + +TEST_CASE("monitor_object_collection_post_deserialization") +{ + const size_t bins = 10; + const size_t min = 0; + const size_t max = 10; + + // Setting up the moc. Histo 1D + MonitorObjectCollection* moc = new MonitorObjectCollection(); + moc->SetOwner(false); + + TH1I* objTH1I = new TH1I("histo 1d", "histo 1d", bins, min, max); + objTH1I->Fill(5); + MonitorObject* moTH1I = new MonitorObject(objTH1I, "histo 1d", "class", "DET"); + moTH1I->setIsOwner(false); + moc->Add(moTH1I); + + moc->postDeserialization(); + + CHECK(moc->IsOwner() == true); + CHECK(moTH1I->isIsOwner() == true); + + delete moc; +} + +TEST_CASE("monitor_object_collection_clone_mw") +{ + const size_t bins = 10; + const size_t min = 0; + const size_t max = 10; + + // Setting up the moc. Histo 1D + MonitorObjectCollection* moc = new MonitorObjectCollection(); + moc->SetOwner(false); + + TH1I* objTH1I = new TH1I("histo 1d", "histo 1d", bins, min, max); + objTH1I->Fill(5); + MonitorObject* moTH1I = new MonitorObject(objTH1I, "histo 1d", "class", "DET"); + moTH1I->setIsOwner(false); + moTH1I->setCreateMovingWindow(true); + moTH1I->setValidity({ 10, 432000 }); + moc->Add(moTH1I); + + TH2I* objTH2I = new TH2I("histo 2d", "histo 2d", bins, min, max, bins, min, max); + MonitorObject* moTH2I = new MonitorObject(objTH2I, "histo 2d", "class", "DET"); + moTH2I->setCreateMovingWindow(false); + moc->Add(moTH2I); + + auto mwMergeInterface = moc->cloneMovingWindow(); + + REQUIRE(mwMergeInterface != nullptr); + auto mwMOC = dynamic_cast(mwMergeInterface); + REQUIRE(mwMOC != nullptr); + REQUIRE(mwMOC->GetEntries() == 1); + CHECK(mwMOC->IsOwner() == true); + + MonitorObject* mwMoTH1I = dynamic_cast(mwMOC->FindObject("histo 1d")); + REQUIRE(mwMoTH1I != nullptr); + CHECK(mwMoTH1I->isIsOwner() == true); + TH1I* mwTH1I = dynamic_cast(mwMoTH1I->getObject()); + REQUIRE(mwTH1I != nullptr); + CHECK(mwTH1I->GetBinContent(mwTH1I->FindBin(5)) == 1); + CHECK(std::strcmp(mwTH1I->GetTitle(), "histo 1d (7m11s window)") == 0); + + moTH1I->setValidity(gInvalidValidityInterval); + auto mwMergeInterface2 = moc->cloneMovingWindow(); + REQUIRE(mwMergeInterface2 != nullptr); + auto mwMOC2 = dynamic_cast(mwMergeInterface2); + REQUIRE(mwMOC2 != nullptr); + REQUIRE(mwMOC2->GetEntries() == 0); + + delete moc; + delete mwMOC; + delete mwMOC2; +} + +TEST_CASE("monitor_object_collection_merge_cycle") +{ + MonitorObjectCollection target; + MonitorObjectCollection other; + constexpr size_t bins = 10; + constexpr size_t min = 0; + constexpr size_t max = 10; + + target.SetOwner(true); + + TH1I* targetTH1I = new TH1I("histo 1d", "histo 1d", bins, min, max); + MonitorObject* targetMoTH1I = new MonitorObject(targetTH1I, "histo 1d", "class", "DET"); + targetMoTH1I->setIsOwner(true); + target.Add(targetMoTH1I); + targetMoTH1I->addOrUpdateMetadata(repository::metadata_keys::cycleNumber, "1"); + + other.SetOwner(true); + + TH1I* otherTH1I = new TH1I("histo 1d", "histo 1d", bins, min, max); + MonitorObject* otherMoTH1I = new MonitorObject(otherTH1I, "histo 1d", "class", "DET"); + otherMoTH1I->setIsOwner(true); + other.Add(otherMoTH1I); + otherMoTH1I->addOrUpdateMetadata(repository::metadata_keys::cycleNumber, "2"); + + algorithm::merge(&target, &other); + + const auto mergedCycle = targetMoTH1I->getMetadata(repository::metadata_keys::cycleNumber); + REQUIRE(mergedCycle.has_value()); + REQUIRE(mergedCycle.value() == "2"); +} + +} // namespace o2::quality_control::core diff --git a/Framework/test/testObjectsManager.cxx b/Framework/test/testObjectsManager.cxx new file mode 100644 index 0000000000..17dfec2080 --- /dev/null +++ b/Framework/test/testObjectsManager.cxx @@ -0,0 +1,220 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Publisher_test.cpp +/// \author Barthelemy von Haller +/// + +#include "QualityControl/ObjectsManager.h" + +#define BOOST_TEST_MODULE ObjectManager test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control::core; +using namespace AliceO2::Common; + +namespace o2::quality_control::core +{ + +struct Config { + std::string taskName = "test"; + std::string detectorName = "TST"; + std::string consulUrl = "invalid"; + std::string taskClass = "TestClass"; +}; + +BOOST_AUTO_TEST_CASE(invalid_url_test) +{ + Config config; + config.taskName = "test"; + config.consulUrl = "bad-url:1234"; + ObjectsManager objectsManager(config.taskName, config.taskClass, config.detectorName, 0); +} + +BOOST_AUTO_TEST_CASE(duplicate_object_test) +{ + Config config; + config.taskName = "test"; + config.consulUrl = ""; + ObjectsManager objectsManager(config.taskName, config.taskClass, config.detectorName, 0); + TObjString s("content"); + objectsManager.startPublishing(&s, PublicationPolicy::Forever); + BOOST_CHECK_NO_THROW(objectsManager.startPublishing(&s, PublicationPolicy::Forever)); + BOOST_REQUIRE(objectsManager.getMonitorObject("content") != nullptr); + + TObjString s2("content"); + BOOST_CHECK_NO_THROW(objectsManager.startPublishing(&s2, PublicationPolicy::Forever)); + auto mo2 = objectsManager.getMonitorObject("content"); + BOOST_REQUIRE(mo2 != nullptr); + BOOST_REQUIRE(mo2->getObject() != &s); + BOOST_REQUIRE(mo2->getObject() == &s2); +} + +BOOST_AUTO_TEST_CASE(is_being_published_test) +{ + Config config; + config.taskName = "test"; + config.consulUrl = ""; + ObjectsManager objectsManager(config.taskName, config.taskClass, config.detectorName, 0); + TObjString s("content"); + BOOST_CHECK(!objectsManager.isBeingPublished("content")); + objectsManager.startPublishing(&s, PublicationPolicy::Forever); + BOOST_CHECK_NO_THROW(objectsManager.startPublishing(&s, PublicationPolicy::Forever)); + BOOST_CHECK(objectsManager.isBeingPublished("content")); +} + +BOOST_AUTO_TEST_CASE(unpublish_test) +{ + Config config; + config.taskName = "test"; + ObjectsManager objectsManager(config.taskName, config.taskClass, config.detectorName, 0); + TObjString s("content"); + objectsManager.startPublishing(&s, PublicationPolicy::Forever); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 1); + objectsManager.stopPublishing(&s); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 0); + objectsManager.startPublishing(&s, PublicationPolicy::Forever); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 1); + objectsManager.stopPublishing("content"); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 0); + BOOST_CHECK_THROW(objectsManager.stopPublishing("content"), ObjectNotFoundError); + BOOST_CHECK_THROW(objectsManager.stopPublishing("asdf"), ObjectNotFoundError); + + // unpublish all + objectsManager.startPublishing(&s, PublicationPolicy::Forever); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 1); + objectsManager.stopPublishingAll(); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 0); + BOOST_CHECK_NO_THROW(objectsManager.stopPublishingAll()); + + // unpublish after deletion + auto s2 = new TObjString("content"); + objectsManager.startPublishing(s2, PublicationPolicy::Forever); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 1); + delete s2; + objectsManager.stopPublishing(s2); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 0); + + // unpublish for publication policy + auto s3 = new TObjString("content3"); + objectsManager.startPublishing(s3, PublicationPolicy::Once); + auto s4 = new TObjString("content4"); + objectsManager.startPublishing(s4, PublicationPolicy::Once); + auto s5 = new TObjString("content5"); + objectsManager.startPublishing(s5, PublicationPolicy::ThroughStop); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 3); + objectsManager.stopPublishing(PublicationPolicy::Once); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 1); + objectsManager.stopPublishing(PublicationPolicy::ThroughStop); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 0); + + objectsManager.startPublishing(s3, PublicationPolicy::Once); + objectsManager.stopPublishing(s3); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 0); + BOOST_CHECK_NO_THROW(objectsManager.stopPublishing(PublicationPolicy::Once)); + BOOST_CHECK_EQUAL(objectsManager.getNumberPublishedObjects(), 0); + BOOST_CHECK_NO_THROW(objectsManager.stopPublishing(s3)); + + delete s3; + delete s4; + delete s5; +} + +BOOST_AUTO_TEST_CASE(getters_test) +{ + Config config; + config.taskName = "test"; + config.consulUrl = ""; + ObjectsManager objectsManager(config.taskName, config.taskClass, config.detectorName, 0); + + TObjString s("content"); + TH1F h("histo", "h", 100, 0, 99); + + objectsManager.startPublishing(&s, PublicationPolicy::Forever); + objectsManager.startPublishing(&h, PublicationPolicy::Forever); + + // basic gets + BOOST_CHECK_NO_THROW(objectsManager.getMonitorObject("content")); + BOOST_CHECK_NO_THROW(objectsManager.getMonitorObject("histo")); + BOOST_CHECK_THROW(objectsManager.getMonitorObject("unexisting object"), ObjectNotFoundError); + + // non owning array + TObjArray* array = objectsManager.getNonOwningArray(); + BOOST_CHECK_EQUAL(array->GetEntries(), 2); + BOOST_CHECK(array->FindObject("content") != nullptr); + BOOST_CHECK(array->FindObject("histo") != nullptr); + + // we confirm that deleting the array does not delete objects + delete array; + BOOST_CHECK_NO_THROW(objectsManager.getMonitorObject("content")); + BOOST_CHECK_NO_THROW(objectsManager.getMonitorObject("histo")); +} + +BOOST_AUTO_TEST_CASE(metadata_test) +{ + Config config; + config.taskName = "test"; + config.consulUrl = ""; + ObjectsManager objectsManager(config.taskName, config.taskClass, config.detectorName, 0); + + TObjString s("content"); + TH1F h("histo", "h", 100, 0, 99); + objectsManager.startPublishing(&s, PublicationPolicy::Forever); + objectsManager.startPublishing(&h, PublicationPolicy::Forever); + + objectsManager.addMetadata("content", "aaa", "bbb"); + BOOST_CHECK_EQUAL(objectsManager.getMonitorObject("content")->getMetadataMap().at("aaa"), "bbb"); +} + +BOOST_AUTO_TEST_CASE(drawOptions_test) +{ + Config config; + config.taskName = "test"; + config.consulUrl = ""; + ObjectsManager objectsManager(config.taskName, config.taskClass, config.detectorName, 0); + + TH1F h("histo", "h", 100, 0, 99); + objectsManager.startPublishing(&h, PublicationPolicy::Forever); + + BOOST_CHECK_THROW(objectsManager.getMonitorObject("histo")->getMetadataMap().at(ObjectsManager::gDrawOptionsKey), out_of_range); + objectsManager.setDefaultDrawOptions(&h, "colz"); + BOOST_CHECK_EQUAL(objectsManager.getMonitorObject("histo")->getMetadataMap().at(ObjectsManager::gDrawOptionsKey), "colz"); + objectsManager.setDefaultDrawOptions("histo", "alp lego1"); + BOOST_CHECK_EQUAL(objectsManager.getMonitorObject("histo")->getMetadataMap().at(ObjectsManager::gDrawOptionsKey), "alp lego1"); + + BOOST_CHECK_THROW(objectsManager.getMonitorObject("histo")->getMetadataMap().at(ObjectsManager::gDisplayHintsKey), out_of_range); + objectsManager.setDisplayHint(&h, "logx"); + BOOST_CHECK_EQUAL(objectsManager.getMonitorObject("histo")->getMetadataMap().at(ObjectsManager::gDisplayHintsKey), "logx"); + objectsManager.setDisplayHint("histo", "gridy logy"); + BOOST_CHECK_EQUAL(objectsManager.getMonitorObject("histo")->getMetadataMap().at(ObjectsManager::gDisplayHintsKey), "gridy logy"); +} + +BOOST_AUTO_TEST_CASE(feed_with_nullptr) +{ + Config config; + config.taskName = "test"; + config.consulUrl = ""; + ObjectsManager objectsManager(config.taskName, config.taskClass, config.detectorName, 0); + + BOOST_CHECK_NO_THROW(objectsManager.startPublishing(nullptr, PublicationPolicy::Forever)); + BOOST_CHECK_NO_THROW(objectsManager.setDefaultDrawOptions(nullptr, "")); + BOOST_CHECK_NO_THROW(objectsManager.setDisplayHint(nullptr, "")); + BOOST_CHECK_NO_THROW(objectsManager.stopPublishing(nullptr)); +} + +} // namespace o2::quality_control::core diff --git a/Framework/test/testPolicyManager.cxx b/Framework/test/testPolicyManager.cxx new file mode 100644 index 0000000000..f78ed047c5 --- /dev/null +++ b/Framework/test/testPolicyManager.cxx @@ -0,0 +1,358 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testPolicyManager.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/UpdatePolicyManager.h" +#include +#include +#include +#include + +using namespace o2::quality_control::checker; +using namespace std; +using namespace o2::framework; +using namespace o2::header; +using namespace AliceO2::Common; + +TEST_CASE("test_basic_isready") +{ + UpdatePolicyManager updatePolicyManager; + + // init + updatePolicyManager.addPolicy("actor1", UpdatePolicyType::OnAny, { "object1" }, false, false); + + // this is like 1 iteration of the run() : + // get new data + updatePolicyManager.updateObjectRevision("object1"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + // update + updatePolicyManager.updateActorRevision("actor1"); + // policy must be false now because we have already processed this actor + CHECK(updatePolicyManager.isReady("actor1") == false); + + updatePolicyManager.updateGlobalRevision(); + CHECK(updatePolicyManager.isReady("actor1") == false); + + // get new data + updatePolicyManager.updateObjectRevision("object1"); + CHECK(updatePolicyManager.isReady("actor1") == true); +} + +TEST_CASE("test_basic_isready2") +{ + UpdatePolicyManager updatePolicyManager; + + // init + updatePolicyManager.addPolicy("actor1", UpdatePolicyType::OnAny, { "object1", "object2" }, false, false); + updatePolicyManager.addPolicy("actor2", UpdatePolicyType::OnAny, { "object2", "object3" }, false, false); + updatePolicyManager.addPolicy("actor3", UpdatePolicyType::OnAny, {}, false, false); // no objects listed + updatePolicyManager.addPolicy("actor4", UpdatePolicyType::OnAny, {}, true, false); // allMOs set + + // this is like 1 iteration of the run() : + // get new data + updatePolicyManager.updateObjectRevision("object1"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == false); + CHECK(updatePolicyManager.isReady("actor3") == false); + CHECK(updatePolicyManager.isReady("actor4") == false); + // update + updatePolicyManager.updateActorRevision("actor1"); + updatePolicyManager.updateActorRevision("actor3"); + updatePolicyManager.updateActorRevision("actor4"); + // policy must be false now because we have already processed this actor + CHECK(updatePolicyManager.isReady("actor1") == false); + updatePolicyManager.updateGlobalRevision(); + + // this is like 1 iteration of the run() : + // get new data + updatePolicyManager.updateObjectRevision("object2"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor1"); + updatePolicyManager.updateActorRevision("actor2"); + // policy must be false now because we have already processed this actor + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // this is like 1 iteration of the run() : + // get new data + updatePolicyManager.updateObjectRevision("object3"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); +} + +TEST_CASE("test_check_policy_OnAll") +{ + UpdatePolicyManager updatePolicyManager; + + // init + updatePolicyManager.addPolicy("actor1", UpdatePolicyType::OnAll, { "object1", "object2" }, false, false); + updatePolicyManager.addPolicy("actor2", UpdatePolicyType::OnAll, { "object2", "object3" }, false, false); + // updatePolicyManager.addPolicy("actor3", UpdatePolicyType::OnAll, { }, false, false); + // updatePolicyManager.addPolicy("actor4", UpdatePolicyType::OnAll, { }, true, false); + + // iteration 1 of run() : get object1 + // get new data + updatePolicyManager.updateObjectRevision("object1"); + // check whether data is ready + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + // CHECK(updatePolicyManager.isReady("actor3") == true); + // CHECK(updatePolicyManager.isReady("actor4") == true); + updatePolicyManager.updateGlobalRevision(); + + // iteration 2 of run() : get object2 + // get new data + updatePolicyManager.updateObjectRevision("object2"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == false); + // CHECK(updatePolicyManager.isReady("actor3") == true); + // CHECK(updatePolicyManager.isReady("actor4") == true); + // update + updatePolicyManager.updateActorRevision("actor1"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + // CHECK(updatePolicyManager.isReady("actor3") == true); + // CHECK(updatePolicyManager.isReady("actor4") == true); + updatePolicyManager.updateGlobalRevision(); + + // iteration 3 of run() : get object3 + // get new data + updatePolicyManager.updateObjectRevision("object3"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // iteration 4 of run() : get object1, 2, 3 + // get new data + updatePolicyManager.updateObjectRevision("object1"); + updatePolicyManager.updateObjectRevision("object2"); + updatePolicyManager.updateObjectRevision("object3"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor1"); + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); +} + +TEST_CASE("test_check_policy_OnAny") +{ + UpdatePolicyManager updatePolicyManager; + + // init + updatePolicyManager.addPolicy("actor1", UpdatePolicyType::OnAny, { "object1", "object2" }, false, false); + updatePolicyManager.addPolicy("actor2", UpdatePolicyType::OnAny, { "object2", "object3" }, false, false); + + // iteration 1 of run() : get object1 + // get new data + updatePolicyManager.updateObjectRevision("object1"); + // check whether data is ready + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // iteration 2 of run() : get object2 + // get new data + updatePolicyManager.updateObjectRevision("object2"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor1"); + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // iteration 3 of run() : get object3 + // get new data + updatePolicyManager.updateObjectRevision("object3"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // iteration 4 of run() : get object1, 2, 3 + // get new data + updatePolicyManager.updateObjectRevision("object1"); + updatePolicyManager.updateObjectRevision("object2"); + updatePolicyManager.updateObjectRevision("object3"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor1"); + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); +} + +TEST_CASE("test_check_policy_OnAnyNonZero") +{ + UpdatePolicyManager updatePolicyManager; + + // init + updatePolicyManager.addPolicy("actor1", UpdatePolicyType::OnAnyNonZero, { "object1", "object2" }, false, false); + updatePolicyManager.addPolicy("actor2", UpdatePolicyType::OnAnyNonZero, { "object2", "object3" }, false, false); + + // iteration 1 of run() : get object1 + // get new data + updatePolicyManager.updateObjectRevision("object1"); + // check whether data is ready + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // iteration 2 of run() : get object2 + // get new data + updatePolicyManager.updateObjectRevision("object2"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == false); + // update + updatePolicyManager.updateActorRevision("actor1"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // iteration 3 of run() : get object3 + // get new data + updatePolicyManager.updateObjectRevision("object3"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // iteration 4 of run() : get object1, 2, 3 + // get new data + updatePolicyManager.updateObjectRevision("object1"); + updatePolicyManager.updateObjectRevision("object2"); + updatePolicyManager.updateObjectRevision("object3"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor1"); + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); +} + +TEST_CASE("test_check_policy_OnEachSeparately") +{ + UpdatePolicyManager updatePolicyManager; + + // init + updatePolicyManager.addPolicy("actor1", UpdatePolicyType::OnEachSeparately, { "object1", "object2" }, false, false); + updatePolicyManager.addPolicy("actor2", UpdatePolicyType::OnEachSeparately, { "object2", "object3" }, false, false); + + // iteration 1 of run() : get object1 + // get new data + updatePolicyManager.updateObjectRevision("object1"); + // check whether data is ready + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // iteration 2 of run() : get object2 + // get new data + updatePolicyManager.updateObjectRevision("object2"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor1"); + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // iteration 3 of run() : get object3 + // get new data + updatePolicyManager.updateObjectRevision("object3"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); + + // iteration 4 of run() : get object1, 2, 3 + // get new data + updatePolicyManager.updateObjectRevision("object1"); + updatePolicyManager.updateObjectRevision("object2"); + updatePolicyManager.updateObjectRevision("object3"); + // check the policy + CHECK(updatePolicyManager.isReady("actor1") == true); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + updatePolicyManager.updateActorRevision("actor1"); + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor1") == false); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); +} + +TEST_CASE("test_errors") +{ + UpdatePolicyManager updatePolicyManager; + + // init + updatePolicyManager.addPolicy("actor2", UpdatePolicyType::OnEachSeparately, { "object2", "object3" }, false, false); + + // get new data + updatePolicyManager.updateObjectRevision("object3"); + // check the policy + CHECK_THROWS_AS(updatePolicyManager.isReady("actor1"), ObjectNotFoundError); + CHECK(updatePolicyManager.isReady("actor2") == true); + // update + CHECK_THROWS_AS(updatePolicyManager.updateActorRevision("actor1"), ObjectNotFoundError); + updatePolicyManager.updateActorRevision("actor2"); + CHECK(updatePolicyManager.isReady("actor2") == false); + updatePolicyManager.updateGlobalRevision(); +} diff --git a/Framework/test/testPostProcessingConfig.cxx b/Framework/test/testPostProcessingConfig.cxx new file mode 100644 index 0000000000..0d622d3c39 --- /dev/null +++ b/Framework/test/testPostProcessingConfig.cxx @@ -0,0 +1,55 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testPostProcessingConfig.cxx +/// \author Piotr Konopka +/// + +#include "getTestDataDirectory.h" +#include "QualityControl/PostProcessingConfig.h" +#include "Configuration/ConfigurationFactory.h" + +#define BOOST_TEST_MODULE PostProcessingConfig test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +using namespace o2::quality_control::postprocessing; +using namespace o2::configuration; + +BOOST_AUTO_TEST_CASE(test_configuration_read) +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + + auto configFile = ConfigurationFactory::getConfiguration(configFilePath); + PostProcessingConfig ppconfig("SkeletonPostProcessing", configFile->getRecursive()); + + BOOST_CHECK_EQUAL(ppconfig.taskName, "SkeletonPostProcessing"); + BOOST_CHECK_EQUAL(ppconfig.detectorName, "TST"); + BOOST_CHECK_EQUAL(ppconfig.moduleName, "QcSkeleton"); + BOOST_CHECK_EQUAL(ppconfig.className, "o2::quality_control_modules::skeleton::SkeletonPostProcessing"); + + BOOST_REQUIRE_EQUAL(ppconfig.initTriggers.size(), 3); + BOOST_CHECK_EQUAL(ppconfig.initTriggers[0], "SOR"); + BOOST_CHECK_EQUAL(ppconfig.initTriggers[1], "EOR"); + BOOST_CHECK_EQUAL(ppconfig.initTriggers[2], "once"); + + BOOST_REQUIRE_EQUAL(ppconfig.updateTriggers.size(), 1); + BOOST_CHECK_EQUAL(ppconfig.updateTriggers[0], "once"); + + BOOST_REQUIRE_EQUAL(ppconfig.stopTriggers.size(), 1); + BOOST_CHECK_EQUAL(ppconfig.stopTriggers[0], "once"); + + BOOST_CHECK_EQUAL(ppconfig.customParameters.size(), 4); + + BOOST_CHECK_EQUAL(ppconfig.ccdbUrl, "ccdb-test.cern.ch:8080"); +} \ No newline at end of file diff --git a/Framework/test/testPostProcessingInterface.cxx b/Framework/test/testPostProcessingInterface.cxx new file mode 100644 index 0000000000..4123f0917a --- /dev/null +++ b/Framework/test/testPostProcessingInterface.cxx @@ -0,0 +1,89 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testPostProcessingInterface.cxx +/// \author Piotr Konopka +/// + +#include "getTestDataDirectory.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/PostProcessingFactory.h" +#include +#include + +#define BOOST_TEST_MODULE PostProcessingRunner test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::test +{ + +class TestTask : public PostProcessingInterface +{ + public: + TestTask() : test(0){}; + ~TestTask() override = default; + + void configure(const boost::property_tree::ptree&) override + { + test = 1; + } + + // user gets to know what triggered the init + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override + { + test = 2; + } + // user gets to know what triggered the processing + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override + { + test = 3; + } + // user gets to know what triggered the end + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override + { + test = 4; + } + + int test; +}; + +} /* namespace o2::quality_control_modules::test */ + +using namespace o2::configuration; + +BOOST_AUTO_TEST_CASE(test_factory) +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto configFile = ConfigurationFactory::getConfiguration(configFilePath); + + o2::quality_control_modules::test::TestTask task; + BOOST_CHECK_EQUAL(task.test, 0); + + task.setName("asfd"); + BOOST_CHECK_EQUAL(task.getName(), "asfd"); + + task.configure(configFile->getRecursive()); + BOOST_CHECK_EQUAL(task.test, 1); + + o2::framework::ServiceRegistry services; + + task.initialize({ TriggerType::No }, services); + BOOST_CHECK_EQUAL(task.test, 2); + task.update({ TriggerType::No }, services); + BOOST_CHECK_EQUAL(task.test, 3); + task.finalize({ TriggerType::No }, services); + BOOST_CHECK_EQUAL(task.test, 4); +} \ No newline at end of file diff --git a/Framework/test/testPostProcessingRunner.cxx b/Framework/test/testPostProcessingRunner.cxx new file mode 100644 index 0000000000..b3595da100 --- /dev/null +++ b/Framework/test/testPostProcessingRunner.cxx @@ -0,0 +1,37 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testPostProcessingRunner.cxx +/// \author Piotr Konopka +/// + +#include "getTestDataDirectory.h" +#include "QualityControl/PostProcessingRunner.h" +#include "QualityControl/WorkflowType.h" +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; +using namespace o2::configuration; + +TEST_CASE("test_configurationfactory") +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + auto config = ConfigurationFactory::getConfiguration(configFilePath); + + PostProcessingRunner runner("SkeletonPostProcessing"); + + // todo: this initializes database. should we have an option not to do it, so we don't fail test randomly? + CHECK_NOTHROW(runner.init(config->getRecursive(), WorkflowType::Standalone)); + CHECK_NOTHROW(runner.run()); +} diff --git a/Framework/test/testPublisher.cxx b/Framework/test/testPublisher.cxx index 91e5716137..697ad01202 100644 --- a/Framework/test/testPublisher.cxx +++ b/Framework/test/testPublisher.cxx @@ -1,5 +1,16 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// -/// \file Publisher_test.cpp +/// \file testPublisher.cxx /// \author Barthelemy von Haller /// @@ -8,10 +19,9 @@ #define BOOST_TEST_MODULE Publisher test #define BOOST_TEST_MAIN #define BOOST_TEST_DYN_LINK -#include "../include/Common/Exceptions.h" + #include #include -#include using namespace std; using namespace o2::quality_control::core; @@ -20,36 +30,22 @@ using namespace AliceO2::Common; namespace o2::quality_control::core { +// fixme: unify with testObjectManager? BOOST_AUTO_TEST_CASE(publisher_test) { - TaskConfig config; - config.taskName = "test"; - ObjectsManager objectsManager(config); + std::string taskName = "test"; + std::string detectorName = "TST"; + std::string consulUrl = "invalid"; + ObjectsManager objectsManager(taskName, "taskClass", detectorName, 0); TObjString s("content"); - objectsManager.startPublishing(&s, "test"); - TObjString* s2 = (TObjString*)(objectsManager.getObject("test")); + objectsManager.startPublishing(&s, PublicationPolicy::Forever); + + TObjString* s2 = (TObjString*)(objectsManager.getMonitorObject("content")->getObject()); BOOST_CHECK_EQUAL(s.GetString(), s2->GetString()); - BOOST_CHECK_EQUAL(Quality::Null, objectsManager.getQuality("test")); - auto mo = objectsManager.getMonitorObject("test"); - BOOST_CHECK_THROW(mo->setQualityForCheck("test", Quality::Medium), AliceO2::Common::ObjectNotFoundError); - CheckDefinition check; - check.name = "test"; - check.libraryName = "test"; - check.className = "test"; - check.result = Quality::Medium; - mo->addOrReplaceCheck("test", check); - BOOST_CHECK_EQUAL(Quality::Medium, objectsManager.getQuality("test")); - BOOST_CHECK_THROW(objectsManager.getQuality("test2"), ObjectNotFoundError); - - // that is just for me to see how it looks like - try { - objectsManager.getQuality("test2"); - } catch (ObjectNotFoundError& e) { - std::cout << e.what() << std::endl; - if (std::string const* extra = boost::get_error_info(e)) { - std::cout << "object name : " << *extra << std::endl; - } - } + MonitorObject* mo = nullptr; + BOOST_CHECK_THROW(mo = objectsManager.getMonitorObject("test"), AliceO2::Common::ObjectNotFoundError); + BOOST_CHECK_EQUAL(mo, nullptr); + mo = objectsManager.getMonitorObject("content"); } } // namespace o2::quality_control::core diff --git a/Framework/test/testQCFactory.json b/Framework/test/testQCFactory.json deleted file mode 100644 index 6a5ea1c4f5..0000000000 --- a/Framework/test/testQCFactory.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "qc": { - "config": { - "database": { - "username": "qc_user", - "password": "qc_user", - "name": "quality_control", - "implementation": "MySql", - "host": "localhost:3306" - }, - "Activity": { - "number": "42", - "type": "2" - } - }, - "tasks": { - "skeletonTask": { - "active": true, - "className": "o2::quality_control_modules::skeleton::SkeletonTask", - "moduleName": "QcSkeleton", - "dataSamplingPolicy": "tpcclust", - "cycleDurationSeconds": "10", - "maxNumberCycles": "-1", - "taskParameters": { - "parameter1": 100001, - "parameter2": "qu'est-ce que c'est que ce truc la" - }, - "location": "local", - "machines": [ - "o2flp1", - "o2flp2" - ] - }, - "abcTask": { - "active": true, - "className": "o2::quality_control_modules::skeleton::SkeletonTask", - "moduleName": "QcSkeleton", - "dataSamplingPolicy": "tpcclust", - "cycleDurationSeconds": "10", - "maxNumberCycles": "-1", - "taskParameters": { - "parameter1": 100002, - "parameter2": "c'est quoi" - }, - "location": "remote" - }, - "defTask": { - "active": false - } - } - }, - "dataSamplingPolicies": [ - { - "id": "tpcclust", - "active": "true", - "machines": [], - "dataHeaders": [ - { - "binding": "clusters", - "dataOrigin": "TPC", - "dataDescription": "CLUSTERS" - } - ], - "subSpec": "0", - "samplingConditions": [ - { - "condition": "random", - "fraction": "0.1", - "seed": "1234" - } - ], - "blocking": "false" - } - ] -} \ No newline at end of file diff --git a/Framework/test/testQCInputs.cxx b/Framework/test/testQCInputs.cxx new file mode 100644 index 0000000000..0c54494ea5 --- /dev/null +++ b/Framework/test/testQCInputs.cxx @@ -0,0 +1,205 @@ +// Copyright 2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testQCInputs.cxx +/// \author Michal Tichak +/// + +#include +#include +#include +#include "Framework/include/QualityControl/MonitorObject.h" +#include "QualityControl/QCInputs.h" +#include "QualityControl/QCInputsAdapters.h" +#include +#include +#include + +using namespace o2::quality_control::core; + +struct nonexistent { +}; + +TEST_CASE("Data - constructor", "[Data]") +{ + REQUIRE_NOTHROW([]() { QCInputs data{}; }); +} + +TEST_CASE("Data insert and get", "[Data]") +{ + QCInputs data; + data.insert("test", 1); + auto valueStr = data.get("test"); + REQUIRE(!valueStr.has_value()); + auto valueInt = data.get("test"); + REQUIRE(valueInt.has_value()); + REQUIRE(valueInt == 1); +} + +TEST_CASE("Data - iterateByType", "[Data]") +{ + QCInputs data; + data.insert("testint1", 1); + data.insert("teststr1", std::string{ "1" }); + REQUIRE(data.size() == 2); + + SECTION("iterate by int") + { + size_t count{}; + for (auto& v : data.iterateByType()) { + REQUIRE(v == 1); + count++; + } + REQUIRE(count == 1); + } + + SECTION("iterate by nonexistent") + { + size_t count{}; + REQUIRE(data.iterateByType().empty()); + } +} + +TEST_CASE("Data - iterateByTypeAndFilter", "[Data]") +{ + QCInputs data; + data.insert("1", 1); + data.insert("2", 2); + data.insert("str", "str"); + REQUIRE(data.size() == 3); + + size_t count{}; + for (const auto& v : data.iterateByTypeAndFilter([](const auto& pair) -> bool { return *pair.second == 2; })) { + ++count; + REQUIRE(v == 2); + } + REQUIRE(count == 1); +} + +TEST_CASE("Data - iterateByTypeFilterAndTransform", "[Data]") +{ + + auto* h1 = new TH1F("th11", "th11", 100, 0, 99); + std::shared_ptr mo1 = std::make_shared(h1, "taskname", "class1", "TST"); + + auto h2 = new TH1F("th12", "th12", 100, 0, 99); + std::shared_ptr mo2 = std::make_shared(h2, "taskname", "class2", "TST"); + + QCInputs data; + data.insert("1", mo1); + data.insert("2", mo2); + data.insert("str", "str"); + REQUIRE(data.size() == 3); + auto filtered = data.iterateByTypeFilterAndTransform( + [](const auto& pair) -> bool { return std::string_view{ pair.second->GetName() } == "th11"; }, + [](const MonitorObject* ptr) -> const TH1F* { return dynamic_cast(ptr->getObject()); }); + + REQUIRE(!filtered.empty()); + size_t count{}; + for (const auto& th1 : filtered) { + REQUIRE(std::string_view{ th1.GetName() } == "th11"); + ++count; + } + REQUIRE(count == 1); +} + +TEST_CASE("Data - raw pointers", "[Data]") +{ + QCInputs data; + int a = 1; + int b = 2; + data.insert("1", &a); + data.insert("2", &b); + + auto ints = data.iterateByType(); + REQUIRE(!ints.empty()); + + size_t count{}; + for (const auto& v : ints) { + REQUIRE((v == 1 || v == 2)); + ++count; + } + REQUIRE(count == 2); +} + +TEST_CASE("Data adapters - helper functions", "[Data]") +{ + + QCInputs data; + { + for (size_t i{}; i != 10; ++i) { + const auto iStr = std::to_string(i); + const auto thName = std::string("TH1F_") + iStr; + const auto moName = "testMO_" + iStr; + auto* h = new TH1F(thName.c_str(), thName.c_str(), 100, 0, 99); + data.insert(moName, std::make_shared(h, "taskname_" + iStr, "class1", "TST")); + } + + auto* h = new TH1F("TH1F_duplicate", "TH1F_duplicate", 100, 0, 99); + data.insert("testMO_duplicate", std::make_shared(h, "taskname_8", "class1", "TST")); + + data.insert("testQO_1", std::make_shared(Quality::Good, "QO_1")); + data.insert("testQO_2", std::make_shared(Quality::Good, "QO_2")); + } + + REQUIRE(data.size() == 13); + + SECTION("getMonitorObject") + { + const auto moOpt = getMonitorObject(data, "TH1F_1"); + REQUIRE(moOpt.has_value()); + REQUIRE(std::string_view(moOpt.value().get().GetName()) == "TH1F_1"); + const auto th1Opt = getMonitorObject(data, "TH1F_8"); + REQUIRE(th1Opt.has_value()); + REQUIRE(std::string_view(th1Opt.value().get().GetName()) == "TH1F_8"); + + const auto moSpecificOpt = getMonitorObject(data, "TH1F_duplicate", "taskname_8"); + REQUIRE(moSpecificOpt.has_value()); + REQUIRE(moSpecificOpt.value().get().GetName() == std::string_view{ "TH1F_duplicate" }); + REQUIRE(moSpecificOpt.value().get().getTaskName() == std::string_view{ "taskname_8" }); + const auto th1SpecificOpt = getMonitorObject(data, "TH1F_duplicate", "taskname_8"); + REQUIRE(th1SpecificOpt.has_value()); + REQUIRE(th1SpecificOpt.value().get().GetName() == std::string_view{ "TH1F_duplicate" }); + REQUIRE(!getMonitorObject(data, "TH1F_duplicate", "taskname_8").has_value()); + } + + SECTION("iterateMonitorObjects") + { + size_t count{}; + for (auto& mo : iterateMonitorObjects(data)) { + ++count; + } + REQUIRE(count == 11); + + count = 0; + for (auto& mo : iterateMonitorObjects(data, "taskname_8")) { + ++count; + } + REQUIRE(count == 2); + } + + SECTION("getQualityObject") + { + const auto qoOpt = getQualityObject(data, "QO_1"); + REQUIRE(qoOpt.has_value()); + REQUIRE(std::string_view{ qoOpt.value().get().GetName() } == "QO_1"); + } + + SECTION("iterateQualityObjects") + { + size_t count{}; + for (const auto& qo : iterateQualityObjects(data)) { + ++count; + } + REQUIRE(count == 2); + } +} diff --git a/Framework/test/testQCTask.cxx b/Framework/test/testQCTask.cxx deleted file mode 100644 index 048e4bbd42..0000000000 --- a/Framework/test/testQCTask.cxx +++ /dev/null @@ -1,93 +0,0 @@ -/// -/// \file TestQCTask.cxx -/// \author Barthelemy von Haller -/// - -#include "../include/QualityControl/TaskFactory.h" -#include "../include/QualityControl/TaskInterface.h" - -#define BOOST_TEST_MODULE QC test -#define BOOST_TEST_MAIN -#define BOOST_TEST_DYN_LINK - -#include -#include -#include -#include - -//#include "Framework/DataRefUtils.h" -//#include "Framework/AlgorithmSpec.h" -//#include "Framework/ServiceRegistry.h" -//#include "Framework/runDataProcessing.h" -//#include -//#include "FairMQLogger.h" - -using boost::test_tools::output_test_stream; - -using namespace o2::quality_control; -using namespace std; -using namespace o2::framework; - -namespace o2::quality_control -{ - -using namespace core; - -namespace Test -{ -class TestTask : public TaskInterface -{ - public: - TestTask(ObjectsManager* objectsManager) : TaskInterface(objectsManager) { test = 0; } - - ~TestTask() override {} - - // Definition of the methods for the template method pattern - void initialize(o2::framework::InitContext& ctx) override - { - cout << "initialize" << endl; - test = 1; - } - - void startOfActivity(Activity& activity) override - { - cout << "startOfActivity" << endl; - test = 2; - } - - void startOfCycle() override { cout << "startOfCycle" << endl; } - - virtual void monitorData(o2::framework::ProcessingContext& ctx) { cout << "monitorData" << endl; } - - void endOfCycle() override { cout << "endOfCycle" << endl; } - - void endOfActivity(Activity& activity) override { cout << "endOfActivity" << endl; } - - void reset() override { cout << "reset" << endl; } - - int test; -}; - -} /* namespace Test */ -} /* namespace o2::quality_control */ - - BOOST_AUTO_TEST_CASE(TestInstantiate) -{ -//// o2::framework::InitContext; -// TaskConfig taskConfig; -// ObjectsManager objectsManager(taskConfig); -// Test::TestTask tt(&objectsManager); -// BOOST_CHECK_EQUAL(tt.test, 0); -// -// // TODO is there a way to test a DPL task ?? -// std::unique_ptr retriever; -// ConfigParamRegistry options(move(retriever)); -// ServiceRegistry services; -// InitContext ctx(options, services); -// tt.initialize(ctx); // tt is the TestTask -// BOOST_CHECK_EQUAL(tt.test, 1); -// Activity act; -// tt.startOfActivity(act); -// BOOST_CHECK_EQUAL(tt.test, 2); -//} -} \ No newline at end of file diff --git a/Framework/test/testQcInfoLogger.cxx b/Framework/test/testQcInfoLogger.cxx index 46abdbd101..fe77027b2e 100644 --- a/Framework/test/testQcInfoLogger.cxx +++ b/Framework/test/testQcInfoLogger.cxx @@ -1,28 +1,99 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// -/// \file Publisher_test.cpp +/// \file testQcInfoLogger.cxx /// \author Barthelemy von Haller /// -#include "../include/QualityControl/QcInfoLogger.h" +#include "QualityControl/QcInfoLogger.h" -#define BOOST_TEST_MODULE Quality test +#define BOOST_TEST_MODULE InfoLogger test #define BOOST_TEST_MAIN #define BOOST_TEST_DYN_LINK #include -#include -#include +#include +#include +#include using namespace std; +using namespace AliceO2::InfoLogger; namespace o2::quality_control::core { BOOST_AUTO_TEST_CASE(qc_info_logger) { - QcInfoLogger& qc1 = QcInfoLogger::GetInstance(); - QcInfoLogger& qc2 = QcInfoLogger::GetInstance(); + AliceO2::InfoLogger::InfoLogger& qc1 = QcInfoLogger::GetInfoLogger(); + AliceO2::InfoLogger::InfoLogger& qc2 = QcInfoLogger::GetInfoLogger(); BOOST_CHECK_EQUAL(&qc1, &qc2); qc1 << "test" << AliceO2::InfoLogger::InfoLogger::endm; } +BOOST_AUTO_TEST_CASE(qc_info_logger_2) +{ + // Decreasing verbosity of the code + QcInfoLogger::GetInfoLogger() << "1. info message" << AliceO2::InfoLogger::InfoLogger::endm; + QcInfoLogger::GetInfoLogger() << "2. info message" << InfoLogger::endm; + ILOG(Info, Support) << "3. info message for support" << InfoLogger::endm; + ILOG(Info, Devel) << "4. info message for devel" << ENDM; + ILOG(Info) << "4b. info MEssage for default level" << ENDM; + + // Complexification of the messages + ILOG(Error) << "5. error message" << ENDM; + ILOG(Error) << "6. error message" << LogInfoSupport << " - 7. info message" << ENDM; + ILOG_INST << InfoLogger::InfoLoggerMessageOption{ InfoLogger::Fatal, 1, 1, "asdf", 3 } + << "8. fatal message with extra fields" << ENDM; + + // Different syntax + ILOGE << "9a. error message" << ENDM; + ILOGF << "9b. fatal message" << ENDM; + ILOGW << "9c. warning message" << ENDM; + ILOGI << "9d. info message" << ENDM; + + // Using the normal functions + ILOG_INST.logInfo("a. info message"); + ILOG_INST.logError("b. error message"); + ILOG_INST.log("c. info message"); + + // Using fairlogger + LOG(info) << "fair message in infologger"; + + // using different levels + ILOG(Debug, Devel) << "LogDebugDevel" << ENDM; + ILOG(Warning, Ops) << "LogWarningOps" << ENDM; + ILOG(Error, Support) << "LogErrorSupport" << ENDM; + ILOG(Info, Trace) << "LogInfoTrace" << ENDM; +} + +BOOST_AUTO_TEST_CASE(qc_info_logger_fields) +{ + ILOG(Info, Support) << "No fields set, facility=QC, system=QC, detector=" << ENDM; + QcInfoLogger::setDetector("ITS"); + ILOG(Info, Support) << "Detector ITS set, facility=QC, system=QC, detector=ITS" << ENDM; + QcInfoLogger::setFacility("Test"); + ILOG(Info, Support) << "Facility Test set, facility=Test, system=QC, detector=ITS" << ENDM; + QcInfoLogger::setRun(12345); + ILOG(Info, Support) << "Run set to 12345, facility=Test, system=QC, detector=ITS" << ENDM; + QcInfoLogger::setPartition("physics_1"); + ILOG(Info, Support) << "Partition set to physics_1, facility=Test, system=QC, detector=ITS" << ENDM; +} + +BOOST_AUTO_TEST_CASE(qc_info_logger_dplil) +{ + AliceO2::InfoLogger::InfoLogger dplInfoLogger; + auto dplContext = new AliceO2::InfoLogger::InfoLoggerContext(); + dplContext->setField(infoContext::FieldName::Facility, "dplfacility"); + dplContext->setField(infoContext::FieldName::System, "dplsystem"); + QcInfoLogger::init("facility", { false, 21, "" }, &dplInfoLogger, dplContext); +} + } // namespace o2::quality_control::core diff --git a/Framework/test/testQualitiesToFlagCollectionConverter.cxx b/Framework/test/testQualitiesToFlagCollectionConverter.cxx new file mode 100644 index 0000000000..62cf883f12 --- /dev/null +++ b/Framework/test/testQualitiesToFlagCollectionConverter.cxx @@ -0,0 +1,597 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testQualitiesToFlagCollectionConverter.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/QualitiesToFlagCollectionConverter.h" +#include "QualityControl/QualityObject.h" +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; + +TEST_CASE("Default QO conversions", "[QualitiesToFlagCollectionConverter]") +{ + SECTION("Good QO with no Flags is not converted to any Flag") + { + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + + QualityObject qo{ Quality::Good, "xyzCheck", "DET" }; + qo.setValidity({ 5, 100 }); + converter(qo); + + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 0); + } + SECTION("Bad and Medium QOs with no Flags are converted to Unknown Flag") + { + std::vector qos{ + { Quality::Medium, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" } + }; + + qos[0].setValidity({ 5, 150 }); + qos[1].setValidity({ 10, 100 }); + + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + for (const auto& qo : qos) { + converter(qo); + } + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 2); + + auto& flag1 = *qcfc->begin(); + CHECK(flag1.getStart() == 5); + CHECK(flag1.getEnd() == 100); + CHECK(flag1.getFlag() == FlagTypeFactory::Unknown()); + CHECK(flag1.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag2 = *(++qcfc->begin()); + CHECK(flag2.getStart() == 10); + CHECK(flag2.getEnd() == 100); + CHECK(flag2.getFlag() == FlagTypeFactory::Unknown()); + CHECK(flag2.getSource() == "qc/DET/QO/xyzCheck"); + } + + SECTION("Null QO with no Flags is converted to UnknownQuality Flag") + { + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + + QualityObject qo{ Quality::Null, "xyzCheck", "DET" }; + qo.setValidity({ 5, 100 }); + converter(qo); + + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 1); + auto& flag = *qcfc->begin(); + CHECK(flag.getStart() == 5); + CHECK(flag.getEnd() == 100); + CHECK(flag.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag.getSource() == "qc/DET/QO/xyzCheck"); + } +} + +TEST_CASE("Filling empty intervals with UnknownQuality", "[QualitiesToFlagCollectionConverter]") +{ + SECTION("test_NoQOs") + { + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 1); + auto& flag = *qcfc->begin(); + CHECK(flag.getStart() == 5); + CHECK(flag.getEnd() == 100); + CHECK(flag.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag.getSource() == "qc/DET/QO/xyzCheck"); + } + + SECTION("test_NoBeginning") + { + std::vector qos{ + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Good, "xyzCheck", "DET" } + }; + + qos[0].setValidity({ 10, 50 }); + qos[1].setValidity({ 50, 120 }); + + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + for (const auto& qo : qos) { + converter(qo); + } + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 2); + + auto& flag1 = *qcfc->begin(); + CHECK(flag1.getStart() == 5); + CHECK(flag1.getEnd() == 10); + CHECK(flag1.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag1.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag2 = *(++qcfc->begin()); + CHECK(flag2.getStart() == 10); + CHECK(flag2.getEnd() == 50); + CHECK(flag2.getFlag() == FlagTypeFactory::Unknown()); + CHECK(flag2.getSource() == "qc/DET/QO/xyzCheck"); + } + + SECTION("test_NoEnd") + { + std::vector qos{ + { Quality::Good, "xyzCheck", "DET" } + }; + + qos[0].setValidity({ 5, 80 }); + + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + for (const auto& qo : qos) { + converter(qo); + } + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 1); + auto& flag = *qcfc->begin(); + CHECK(flag.getStart() == 80); + CHECK(flag.getEnd() == 100); + CHECK(flag.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag.getSource() == "qc/DET/QO/xyzCheck"); + } +} + +TEST_CASE("UnknownQuality is overwritten by other Flags", "[QualitiesToFlagCollectionConverter]") +{ + // Automatically generated UnknownQuality is overwritten by anything + // User-provided UnknownQuality and default Flag for Null are overwritten by other user-provided flags which are not UnknownQuality + + std::vector qos{ + { Quality::Null, "xyzCheck", "DET" }, // Null with default UnknownQuality Flag, to be trimmed + { Quality::Null, "xyzCheck", "DET" }, // Null with default UnknownQuality Flag, to be removed + { Quality::Null, "xyzCheck", "DET" }, // Null with a user-provided UnknownQuality Flag, to be trimmed + { Quality::Null, "xyzCheck", "DET" }, // Null with a user-provided UnknownQuality Flag, to be removed + { Quality::Good, "xyzCheck", "DET" } // "known" Flag which should trim/remove all of the above + }; + + qos[0].setValidity({ 5, 30 }); + qos[1].setValidity({ 40, 50 }); + qos[2].setValidity({ 50, 100 }); + qos[2].addFlag(FlagTypeFactory::UnknownQuality(), "custom comment 1"); + qos[3].setValidity({ 50, 60 }); + qos[3].addFlag(FlagTypeFactory::UnknownQuality(), "custom comment 2"); + qos[4].setValidity({ 20, 60 }); + + auto check = [](const QualityControlFlagCollection& qcfc) { + REQUIRE(qcfc.size() == 2); + + auto it = qcfc.begin(); + auto& flag1 = *it; + CHECK(flag1.getStart() == 5); + CHECK(flag1.getEnd() == 20); + CHECK(flag1.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag1.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag2 = *(++it); + CHECK(flag2.getStart() == 60); + CHECK(flag2.getEnd() == 100); + CHECK(flag2.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag2.getComment() == "custom comment 1"); + CHECK(flag2.getSource() == "qc/DET/QO/xyzCheck"); + }; + + // we perform the same test twice by feeding the converter in the normal and reverse order + // to make sure it does not affect the result + SECTION("flags provided from the first to last in the vector") + { + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + for (const auto& qo : qos) { + converter(qo); + } + qcfc = converter.getResult(); + check(*qcfc); + } + SECTION("flags provided in the reverse order") + { + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + for (const auto& qo : qos | std::views::reverse) { + converter(qo); + } + qcfc = converter.getResult(); + check(*qcfc); + } +} + +TEST_CASE("All QOs with Flags are converted to Flags, while Quality is ignored", "[QualitiesToFlagCollectionConverter]") +{ + std::vector qos{ + { Quality::Null, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Medium, "xyzCheck", "DET" }, + { Quality::Good, "xyzCheck", "DET" }, + }; + + qos[0].setValidity({ 5, 20 }); + qos[0].addFlag(FlagTypeFactory::Good(), "null"); + + qos[1].setValidity({ 20, 40 }); + qos[1].addFlag(FlagTypeFactory::Good(), "bad"); + + qos[2].setValidity({ 40, 60 }); + qos[2].addFlag(FlagTypeFactory::Good(), "medium"); + + qos[3].setValidity({ 60, 100 }); + qos[3].addFlag(FlagTypeFactory::Unknown(), "good"); + + std::unique_ptr qcfc{ + new QualityControlFlagCollection("test1", "DET", { 5, 100 }) + }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + for (const auto& qo : qos) { + converter(qo); + } + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 4); + + auto it = qcfc->begin(); + auto& flag1 = *it; + CHECK(flag1.getStart() == 5); + CHECK(flag1.getEnd() == 20); + CHECK(flag1.getFlag() == FlagTypeFactory::Good()); + CHECK(flag1.getComment() == "null"); + CHECK(flag1.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag2 = *(++it); + CHECK(flag2.getStart() == 20); + CHECK(flag2.getEnd() == 40); + CHECK(flag2.getFlag() == FlagTypeFactory::Good()); + CHECK(flag2.getComment() == "bad"); + CHECK(flag2.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag3 = *(++it); + CHECK(flag3.getStart() == 40); + CHECK(flag3.getEnd() == 60); + CHECK(flag3.getFlag() == FlagTypeFactory::Good()); + CHECK(flag3.getComment() == "medium"); + CHECK(flag3.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag4 = *(++it); + CHECK(flag4.getStart() == 60); + CHECK(flag4.getEnd() == 100); + CHECK(flag4.getFlag() == FlagTypeFactory::Unknown()); + CHECK(flag4.getComment() == "good"); + CHECK(flag4.getSource() == "qc/DET/QO/xyzCheck"); +} + +TEST_CASE("Input parameter validation", "[QualitiesToFlagCollectionConverter]") +{ + std::vector qos{ + { Quality::Bad, "xyzCheck", "TPC" }, + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" } + }; + + qos[0].setValidity({ 10, 120 }); + qos[1].setValidity({ 1000, 10000 }); + qos[2].setValidity({ 40, 30 }); + + SECTION("Different detector") + { + // different detector + std::unique_ptr qcfc{ + new QualityControlFlagCollection("test1", "DET", { 5, 100 }) + }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + CHECK_THROWS_AS(converter(qos[0]), std::runtime_error); + } + SECTION("QO start after the QCFC end limit") + { + std::unique_ptr qcfc{ + new QualityControlFlagCollection("test1", "DET", { 5, 100 }) + }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + converter(qos[1]); + + qcfc = converter.getResult(); + REQUIRE(qcfc->size() == 1); + auto& flag = *qcfc->begin(); + CHECK(flag.getStart() == 5); + CHECK(flag.getEnd() == 100); + CHECK(flag.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag.getSource() == "qc/DET/QO/xyzCheck"); + } + SECTION("QO validity starts after it finishes") + { + std::unique_ptr qcfc{ + new QualityControlFlagCollection("test1", "DET", { 5, 100 }) + }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + + qcfc = converter.getResult(); + REQUIRE(qcfc->size() == 1); + auto& flag = *qcfc->begin(); + CHECK(flag.getStart() == 5); + CHECK(flag.getEnd() == 100); + CHECK(flag.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag.getSource() == "qc/DET/QO/xyzCheck"); + } +} + +TEST_CASE("Merging Flags", "[QualitiesToFlagCollectionConverter]") +{ + SECTION("test_OverlappingQOs") + { + std::vector qos{ + { Quality::Good, "xyzCheck", "DET" }, + { Quality::Good, "xyzCheck", "DET" }, + { Quality::Good, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" } + }; + + qos[0].setValidity({ 5, 50 }); + qos[1].setValidity({ 10, 50 }); + qos[2].setValidity({ 15, 60 }); + qos[3].setValidity({ 55, 120 }); + qos[4].setValidity({ 60, 120 }); + qos[5].setValidity({ 70, 120 }); + + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + for (const auto& qo : qos) { + converter(qo); + } + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 1); + + auto& flag1 = *qcfc->begin(); + CHECK(flag1.getStart() == 55); + CHECK(flag1.getEnd() == 100); + CHECK(flag1.getFlag() == FlagTypeFactory::Unknown()); + CHECK(flag1.getSource() == "qc/DET/QO/xyzCheck"); + } + + SECTION("test_AdjacentQOs") + { + std::vector qos{ + { Quality::Good, "xyzCheck", "DET" }, + { Quality::Good, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" } + }; + + qos[0].setValidity({ 5, 10 }); + qos[1].setValidity({ 10, 50 }); + qos[2].setValidity({ 50, 80 }); + qos[3].setValidity({ 80, 120 }); + + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + + for (const auto& qo : qos) { + converter(qo); + } + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 1); + + auto& flag1 = *qcfc->begin(); + CHECK(flag1.getStart() == 50); + CHECK(flag1.getEnd() == 100); + CHECK(flag1.getFlag() == FlagTypeFactory::Unknown()); + CHECK(flag1.getSource() == "qc/DET/QO/xyzCheck"); + } + + SECTION("test_NonDefaultFlags") + { + std::vector qos{ + { Quality::Good, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" } + }; + + qos[0].setValidity({ 5, 10 }); + qos[1].setValidity({ 10, 40 }); + qos[2].setValidity({ 30, 80 }); + qos[3].setValidity({ 50, 100 }); + + qos[1].addFlag(FlagTypeFactory::BadTracking(), "Bug in reco"); + qos[2].addFlag(FlagTypeFactory::BadTracking(), "Bug in reco"); + qos[2].addFlag(FlagTypeFactory::BadHadronPID(), "evil CERN scientists changed the proton mass"); + qos[3].addFlag(FlagTypeFactory::BadTracking(), "Bug in reco"); + + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + for (const auto& qo : qos) { + converter(qo); + } + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 2); + + auto& flag1 = *qcfc->begin(); + CHECK(flag1.getStart() == 10); + CHECK(flag1.getEnd() == 100); + CHECK(flag1.getFlag() == FlagTypeFactory::BadTracking()); + CHECK(flag1.getComment() == "Bug in reco"); + CHECK(flag1.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag2 = *(++qcfc->begin()); + CHECK(flag2.getStart() == 30); + CHECK(flag2.getEnd() == 80); + CHECK(flag2.getFlag() == FlagTypeFactory::BadHadronPID()); + CHECK(flag2.getComment() == "evil CERN scientists changed the proton mass"); + CHECK(flag2.getSource() == "qc/DET/QO/xyzCheck"); + } + + SECTION("test_TheSameFlagsButSeparated") + { + std::vector qos{ + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Good, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" } + }; + + qos[0].setValidity({ 5, 25 }); + qos[1].setValidity({ 10, 40 }); + qos[2].setValidity({ 30, 50 }); + qos[3].setValidity({ 80, 100 }); + + qos[0].addFlag(FlagTypeFactory::BadTracking(), "Bug in reco"); + + qos[2].addFlag(FlagTypeFactory::BadTracking(), "Bug in reco"); + qos[3].addFlag(FlagTypeFactory::BadTracking(), "Bug in reco"); + + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + + for (const auto& qo : qos) { + converter(qo); + } + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 4); + + auto it = qcfc->begin(); + auto& flag1 = *it; + CHECK(flag1.getStart() == 5); + CHECK(flag1.getEnd() == 25); + CHECK(flag1.getFlag() == FlagTypeFactory::BadTracking()); + CHECK(flag1.getComment() == "Bug in reco"); + CHECK(flag1.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag2 = *(++it); + CHECK(flag2.getStart() == 30); + CHECK(flag2.getEnd() == 50); + CHECK(flag2.getFlag() == FlagTypeFactory::BadTracking()); + CHECK(flag2.getComment() == "Bug in reco"); + CHECK(flag2.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag3 = *(++it); + CHECK(flag3.getStart() == 50); + CHECK(flag3.getEnd() == 80); + CHECK(flag3.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag3.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag4 = *(++it); + CHECK(flag4.getStart() == 80); + CHECK(flag4.getEnd() == 100); + CHECK(flag4.getFlag() == FlagTypeFactory::BadTracking()); + CHECK(flag4.getComment() == "Bug in reco"); + CHECK(flag4.getSource() == "qc/DET/QO/xyzCheck"); + } +} + +TEST_CASE("Trimming the validity interval", "[QualitiesToFlagCollectionConverter]") +{ + std::vector qos{ + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Good, "xyzCheck", "DET" }, + { Quality::Bad, "xyzCheck", "DET" } + }; + + qos[0].setValidity({ 5, 100 }); + qos[1].setValidity({ 50, 100 }); + qos[1].addFlag(FlagTypeFactory::Good(), "hello"); + qos[2].setValidity({ 30, 70 }); + qos[2].addFlag(FlagTypeFactory::BadTracking(), "comment"); + + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + converter(qos[0]); + converter(qos[1]); + converter.updateValidityInterval({ 10, 40 }); + converter(qos[2]); + qcfc = converter.getResult(); + + REQUIRE(qcfc->size() == 2); + + auto& flag1 = *qcfc->begin(); + CHECK(flag1.getStart() == 10); + CHECK(flag1.getEnd() == 40); + CHECK(flag1.getFlag() == FlagTypeFactory::Unknown()); + CHECK(flag1.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag2 = *(++qcfc->begin()); + CHECK(flag2.getStart() == 30); + CHECK(flag2.getEnd() == 40); + CHECK(flag2.getFlag() == FlagTypeFactory::BadTracking()); + CHECK(flag2.getComment() == "comment"); +} + +TEST_CASE("Extending the validity interval", "[QualitiesToFlagCollectionConverter]") +{ + std::vector qos{ + { Quality::Bad, "xyzCheck", "DET" }, + { Quality::Good, "xyzCheck", "DET" } + }; + + qos[0].setValidity({ 5, 100 }); + qos[1].setValidity({ 50, 100 }); + qos[1].addFlag(FlagTypeFactory::Good(), "hello"); + + std::unique_ptr qcfc{ new QualityControlFlagCollection("test1", "DET", { 5, 100 }) }; + QualitiesToFlagCollectionConverter converter(std::move(qcfc), "qc/DET/QO/xyzCheck"); + for (const auto& qo : qos) { + converter(qo); + } + converter.updateValidityInterval({ 1, 120 }); + qcfc = converter.getResult(); + + CHECK(qcfc->size() == 4); + + auto it = qcfc->begin(); + auto& flag1 = *it; + CHECK(flag1.getStart() == 1); + CHECK(flag1.getEnd() == 5); + CHECK(flag1.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag1.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag2 = *(++it); + CHECK(flag2.getStart() == 5); + CHECK(flag2.getEnd() == 100); + CHECK(flag2.getFlag() == FlagTypeFactory::Unknown()); + CHECK(flag2.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag3 = *(++it); + CHECK(flag3.getStart() == 50); + CHECK(flag3.getEnd() == 100); + CHECK(flag3.getFlag() == FlagTypeFactory::Good()); + CHECK(flag3.getComment() == "hello"); + CHECK(flag3.getSource() == "qc/DET/QO/xyzCheck"); + + auto& flag4 = *(++it); + CHECK(flag4.getStart() == 100); + CHECK(flag4.getEnd() == 120); + CHECK(flag4.getFlag() == FlagTypeFactory::UnknownQuality()); + CHECK(flag4.getSource() == "qc/DET/QO/xyzCheck"); +} \ No newline at end of file diff --git a/Framework/test/testQuality.cxx b/Framework/test/testQuality.cxx index 58a5ec5f6a..327223e63d 100644 --- a/Framework/test/testQuality.cxx +++ b/Framework/test/testQuality.cxx @@ -1,49 +1,81 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// -/// \file Publisher_test.cpp +/// \file testQuality.cxx /// \author Barthelemy von Haller /// -#include "../include/QualityControl/Quality.h" - -#define BOOST_TEST_MODULE Quality test -#define BOOST_TEST_MAIN -#define BOOST_TEST_DYN_LINK -#include -#include -#include +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include using namespace std; namespace o2::quality_control::core { -BOOST_AUTO_TEST_CASE(quality_test) +TEST_CASE("quality_test") { Quality asdf(123, "asdf"); Quality myQuality = Quality::Bad; - BOOST_CHECK_EQUAL(myQuality.getLevel(), 3); - BOOST_CHECK_EQUAL(myQuality.getName(), "Bad"); + CHECK(myQuality.getLevel() == 3); + CHECK(myQuality.getName() == "Bad"); myQuality = Quality::Good; - BOOST_CHECK_EQUAL(myQuality.getLevel(), 1); - BOOST_CHECK_EQUAL(myQuality.getName(), "Good"); + CHECK(myQuality.getLevel() == 1); + CHECK(myQuality.getName() == "Good"); myQuality = Quality::Medium; - BOOST_CHECK_EQUAL(myQuality.getLevel(), 2); - BOOST_CHECK_EQUAL(myQuality.getName(), "Medium"); + CHECK(myQuality.getLevel() == 2); + CHECK(myQuality.getName() == "Medium"); myQuality = Quality::Null; - BOOST_CHECK_EQUAL(myQuality.getLevel(), Quality::NullLevel); - BOOST_CHECK_EQUAL(myQuality.getName(), "Null"); + CHECK(myQuality.getLevel() == Quality::NullLevel); + CHECK(myQuality.getName() == "Null"); + + ILOG(Info, Support) << "test quality output : " << myQuality << ENDM; + + CHECK(Quality::Bad.isWorseThan(Quality::Medium)); + CHECK(Quality::Bad.isWorseThan(Quality::Good)); + CHECK(!Quality::Bad.isWorseThan(Quality::Null)); + CHECK(!Quality::Bad.isWorseThan(Quality::Bad)); - cout << "test quality output : " << myQuality << endl; + CHECK(Quality::Good.isBetterThan(Quality::Medium)); + CHECK(Quality::Good.isBetterThan(Quality::Bad)); + CHECK(Quality::Good.isBetterThan(Quality::Null)); + CHECK(!Quality::Good.isBetterThan(Quality::Good)); +} + +TEST_CASE("quality_flags") +{ + Quality myQuality = Quality::Bad; + myQuality.addFlag(FlagTypeFactory::BadTracking(), "exception in x"); + myQuality.addFlag(FlagTypeFactory::BadTracking(), "exception in y"); + myQuality.addFlag(FlagTypeFactory::BadPID(), "Bethe and Bloch had a bad day"); - BOOST_CHECK(Quality::Bad.isWorstThan(Quality::Medium)); - BOOST_CHECK(Quality::Bad.isWorstThan(Quality::Good)); - BOOST_CHECK(!Quality::Bad.isWorstThan(Quality::Null)); - BOOST_CHECK(!Quality::Bad.isWorstThan(Quality::Bad)); + auto myFlags = myQuality.getFlags(); + CHECK(myFlags[0].first == FlagTypeFactory::BadTracking()); + CHECK(myFlags[0].second == "exception in x"); + CHECK(myFlags[1].first == FlagTypeFactory::BadTracking()); + CHECK(myFlags[1].second == "exception in y"); + CHECK(myFlags[2].first == FlagTypeFactory::BadPID()); + CHECK(myFlags[2].second == "Bethe and Bloch had a bad day"); - BOOST_CHECK(Quality::Good.isBetterThan(Quality::Medium)); - BOOST_CHECK(Quality::Good.isBetterThan(Quality::Bad)); - BOOST_CHECK(Quality::Good.isBetterThan(Quality::Null)); - BOOST_CHECK(!Quality::Good.isBetterThan(Quality::Good)); + auto copyQuality = myQuality; + auto copyFlags = copyQuality.getFlags(); + CHECK(copyFlags[0].first == FlagTypeFactory::BadTracking()); + CHECK(copyFlags[0].second == "exception in x"); + CHECK(copyFlags[1].first == FlagTypeFactory::BadTracking()); + CHECK(copyFlags[1].second == "exception in y"); + CHECK(copyFlags[2].first == FlagTypeFactory::BadPID()); + CHECK(copyFlags[2].second == "Bethe and Bloch had a bad day"); } } // namespace o2::quality_control::core diff --git a/Framework/test/testQualityObject.cxx b/Framework/test/testQualityObject.cxx new file mode 100644 index 0000000000..aa53f8f5aa --- /dev/null +++ b/Framework/test/testQualityObject.cxx @@ -0,0 +1,153 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testQualityObject.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/QualityObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/testUtils.h" + +#include +#include + +#include + +using namespace std; +using namespace o2::quality_control::test; +using namespace o2::quality_control::core; +using namespace o2::quality_control; + +TEST_CASE("quality_object_test_constructors") +{ + QualityObject qo(Quality::Medium, + "xyzCheck", + "TST", + "", + { "qc/TST/testTask/mo1", "qc/TST/testTask/mo2" }, + {}, + { { "probability", "0.45" }, { "threshold_medium", "0.42" } }); + + CHECK(qo.getName() == "xyzCheck"); + CHECK(strcmp(qo.GetName(), "xyzCheck") == 0); + CHECK(qo.getDetectorName() == "TST"); + CHECK(qo.getQuality() == Quality::Medium); + REQUIRE(qo.getInputs().size() == 2); + CHECK(qo.getInputs()[0] == "qc/TST/testTask/mo1"); + CHECK(qo.getInputs()[1] == "qc/TST/testTask/mo2"); + REQUIRE(qo.getMetadataMap().count("probability") == 1); + CHECK(qo.getMetadataMap().at("probability") == "0.45"); + REQUIRE(qo.getMetadataMap().count("threshold_medium") == 1); + CHECK(qo.getMetadataMap().at("threshold_medium") == "0.42"); + + auto qo2 = qo; + + CHECK(qo2.getName() == "xyzCheck"); + CHECK(strcmp(qo2.GetName(), "xyzCheck") == 0); + CHECK(qo2.getDetectorName() == "TST"); + CHECK(qo2.getQuality() == Quality::Medium); + REQUIRE(qo2.getInputs().size() == 2); + CHECK(qo2.getInputs()[0] == "qc/TST/testTask/mo1"); + CHECK(qo2.getInputs()[1] == "qc/TST/testTask/mo2"); + REQUIRE(qo2.getMetadataMap().count("probability") == 1); + CHECK(qo2.getMetadataMap().at("probability") == "0.45"); + REQUIRE(qo2.getMetadataMap().count("threshold_medium") == 1); + CHECK(qo2.getMetadataMap().at("threshold_medium") == "0.42"); + + Quality q(123, "defCheck"); + q.addMetadata("mykey", "myvalue"); + QualityObject qo3{ q, "defCheck" }; + REQUIRE(qo3.getMetadataMap().count("mykey") == 1); + CHECK(qo3.getMetadataMap().at("mykey") == "myvalue"); +} + +TEST_CASE("quality_object_test_setters") +{ + QualityObject qo(Quality::Null, "xyzCheck"); + qo.setDetectorName("INVALID"); + qo.setDetectorName("TST"); + qo.setQuality(Quality::Null); + qo.setQuality(Quality::Medium); + qo.setInputs({ "that should be overwritten" }); + qo.setInputs({ "qc/TST/testTask/mo1", "qc/TST/testTask/mo2" }); + qo.addMetadata("probability", "0.45"); + qo.addMetadata("threshold_medium", "0.42"); + + CHECK(qo.getName() == "xyzCheck"); + CHECK(strcmp(qo.GetName(), "xyzCheck") == 0); + CHECK(qo.getDetectorName() == "TST"); + CHECK(qo.getQuality() == Quality::Medium); + REQUIRE(qo.getInputs().size() == 2); + CHECK(qo.getInputs()[0] == "qc/TST/testTask/mo1"); + CHECK(qo.getInputs()[1] == "qc/TST/testTask/mo2"); + REQUIRE(qo.getMetadataMap().count("probability") == 1); + CHECK(qo.getMetadataMap().at("probability") == "0.45"); + REQUIRE(qo.getMetadataMap().count("threshold_medium") == 1); + CHECK(qo.getMetadataMap().at("threshold_medium") == "0.42"); +} + +TEST_CASE("qopath") +{ + // no policy + QualityObject qo(Quality::Null, "xyzCheck", "DET"); + string path = qo.getPath(); + CHECK(path == "qc/DET/QO/xyzCheck"); + + // a policy which is not OnEachSeparately + QualityObject qo2(Quality::Null, "xyzCheck", "DET", "OnAnyNonZero"); + string path2 = qo2.getPath(); + CHECK(path2 == "qc/DET/QO/xyzCheck"); + + // policy is OnEachSeparately + QualityObject qo3(Quality::Null, "xyzCheck", "DET", "OnEachSeparately", {}, { "objectABC" }); + string path3 = qo3.getPath(); + CHECK(path3 == "qc/DET/QO/xyzCheck/objectABC"); + + // policy is OnEachSeparately and the vector is empty + QualityObject qo4(Quality::Null, "xyzCheck", "DET", "OnEachSeparately", {}, {}); + CHECK_THROWS_AS(qo4.getPath(), AliceO2::Common::FatalException); +} + +TEST_CASE("qo_flags") +{ + QualityObject qo1(Quality::Bad, "xyzCheck", "DET"); + qo1.addFlag(FlagTypeFactory::BadTracking(), "exception in x"); + qo1.addFlag(FlagTypeFactory::BadTracking(), "exception in y"); + qo1.addFlag(FlagTypeFactory::BadPID(), "wrong time of flight due to the summer time change"); + + auto flags1 = qo1.getFlags(); + CHECK(flags1[0].first == FlagTypeFactory::BadTracking()); + CHECK(flags1[0].second == "exception in x"); + CHECK(flags1[1].first == FlagTypeFactory::BadTracking()); + CHECK(flags1[1].second == "exception in y"); + CHECK(flags1[2].first == FlagTypeFactory::BadPID()); + CHECK(flags1[2].second == "wrong time of flight due to the summer time change"); + + auto qo2 = qo1; + auto flags2 = qo2.getFlags(); + CHECK(flags2[0].first == FlagTypeFactory::BadTracking()); + CHECK(flags2[0].second == "exception in x"); + CHECK(flags2[1].first == FlagTypeFactory::BadTracking()); + CHECK(flags2[1].second == "exception in y"); + CHECK(flags2[2].first == FlagTypeFactory::BadPID()); + CHECK(flags2[2].second == "wrong time of flight due to the summer time change"); + + auto quality = qo1.getQuality(); + auto flags3 = quality.getFlags(); + CHECK(flags3[0].first == FlagTypeFactory::BadTracking()); + CHECK(flags3[0].second == "exception in x"); + CHECK(flags3[1].first == FlagTypeFactory::BadTracking()); + CHECK(flags3[1].second == "exception in y"); + CHECK(flags3[2].first == FlagTypeFactory::BadPID()); + CHECK(flags3[2].second == "wrong time of flight due to the summer time change"); +} diff --git a/Framework/test/testReductor.cxx b/Framework/test/testReductor.cxx new file mode 100644 index 0000000000..f525b76ccb --- /dev/null +++ b/Framework/test/testReductor.cxx @@ -0,0 +1,202 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testReductors.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/CustomParameters.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/ReductorConditionAny.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/CcdbDatabase.h" +#include "QualityControl/ConditionAccess.h" +#include +#include + +#define BOOST_TEST_MODULE Reductor test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +const std::string CCDB_ENDPOINT = "ccdb-test.cern.ch:8080"; + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::repository; + +class MyReductor : public ReductorTObject +{ + public: + MyReductor() = default; + ~MyReductor() = default; + + void* getBranchAddress() override + { + return &mStats; + } + + const char* getBranchLeafList() override + { + return "integral/D"; + } + + void update(TObject* obj) override + { + TH1I* histo = dynamic_cast(obj); + mStats.integral = histo->Integral(); + } + + private: + struct { + Double_t integral; + } mStats; +}; + +BOOST_AUTO_TEST_CASE(test_ReductorTObjectInterface) +{ + auto histo = std::make_unique("test", "test", 10, 0, 1000); + auto reductor = std::make_unique(); + reductor->update(histo.get()); + + auto tree = std::make_unique(); + tree->Branch("histo", reductor->getBranchAddress(), reductor->getBranchLeafList()); + + tree->Fill(); + histo->Fill(5); + reductor->update(histo.get()); + tree->Fill(); + histo->Fill(1); + reductor->update(histo.get()); + tree->Fill(); + histo->Fill(6); + histo->Fill(66); + histo->Fill(666); + reductor->update(histo.get()); + tree->Fill(); + + BOOST_REQUIRE_EQUAL(tree->GetEntries(), 4); + tree->Draw("histo.integral", "", "goff"); + Double_t* integrals = tree->GetVal(0); + + BOOST_CHECK_EQUAL(integrals[0], 0); + BOOST_CHECK_EQUAL(integrals[1], 1); + BOOST_CHECK_EQUAL(integrals[2], 2); + BOOST_CHECK_EQUAL(integrals[3], 5); +} + +class MyReductorAny : public ReductorConditionAny +{ + public: + MyReductorAny() = default; + ~MyReductorAny() = default; + + void* getBranchAddress() override + { + return &mStats; + } + + const char* getBranchLeafList() override + { + return "a/I"; + } + + bool update(ConditionRetriever& retriever) override + { + auto obj = retriever.retrieve(); + if (obj != nullptr) { + mStats.a = obj->Length(); + return true; + } + std::cerr << "could not get the object from db" << std::endl; + return false; + } + + private: + struct { + Int_t a; + } mStats; +}; + +std::string pathToTestObject() +{ + return "qc/TST/MO/TestReductor/pid" + std::to_string(getpid()); +} + +std::string fullTestObjectPath() +{ + return pathToTestObject() + "/string"; +} + +struct MyGlobalFixture { + void teardown() + { + std::unique_ptr backend = std::make_unique(); + backend->connect(CCDB_ENDPOINT, "", "", ""); + // cannot use the test_fixture because we are tearing down + backend->truncate(pathToTestObject(), "*"); + } +}; +BOOST_TEST_GLOBAL_FIXTURE(MyGlobalFixture); + +BOOST_AUTO_TEST_CASE(test_ReductorAnyInterface) +{ + std::unique_ptr backend = std::make_unique(); + backend->connect(CCDB_ENDPOINT, "", "", ""); + backend->truncate(pathToTestObject(), "*"); + TString secret = "1234567890"; + backend->storeAny(&secret, typeid(TString), fullTestObjectPath(), {}, "TST", "TestReductor", 1, 10); + + ConditionAccess conditionAccess; + conditionAccess.setCcdbUrl(CCDB_ENDPOINT); + + std::unique_ptr reductor = std::make_unique(); + auto tree = std::make_unique(); + tree->Branch("numbers", reductor->getBranchAddress(), reductor->getBranchLeafList()); + reductor->update(conditionAccess, 5, fullTestObjectPath()); + tree->Fill(); + BOOST_REQUIRE_EQUAL(tree->GetEntries(), 1); + tree->Draw("numbers.a", "", "goff"); + Double_t* integrals = tree->GetVal(0); + + BOOST_CHECK_EQUAL(integrals[0], secret.Length()); +} + +BOOST_AUTO_TEST_CASE(test_ReductorConfigurable) +{ + class ReductorTest : public Reductor + { + public: + std::string value{}; + + virtual void* getBranchAddress() + { + return nullptr; + } + virtual const char* getBranchLeafList() + { + value = mCustomParameters.at("key"); + return value.c_str(); + } + }; + + ReductorTest reductor; + CustomParameters params; + params.set("key", "value"); + Reductor& r = reductor; + r.setCustomConfig(params); + BOOST_REQUIRE_EQUAL(r.getBranchLeafList(), "value"); +} diff --git a/Framework/test/testRepoPathUtils.cxx b/Framework/test/testRepoPathUtils.cxx new file mode 100644 index 0000000000..0a30699952 --- /dev/null +++ b/Framework/test/testRepoPathUtils.cxx @@ -0,0 +1,81 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testRepoPathUtils.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QualityObject.h" +#include "QualityControl/testUtils.h" +#include + +#define BOOST_TEST_MODULE RepoPathUtils test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include + +using namespace std; +using namespace o2::quality_control::test; +using namespace o2::quality_control::core; + +BOOST_AUTO_TEST_CASE(qopath) +{ + // no policy + QualityObject qo(Quality::Null, "xyzCheck", "DET"); + string path = RepoPathUtils::getQoPath(&qo); + BOOST_CHECK_EQUAL(path, "qc/DET/QO/xyzCheck"); + path = RepoPathUtils::getQoPath("DET", "xyzCheck"); + BOOST_CHECK_EQUAL(path, "qc/DET/QO/xyzCheck"); + + // provenance + qo.getActivity().mProvenance = "qc_mc"; + path = RepoPathUtils::getQoPath(&qo); + BOOST_CHECK_EQUAL(path, "qc_mc/DET/QO/xyzCheck"); + + // a policy which is not OnEachSeparately + QualityObject qo2(Quality::Null, "xyzCheck", "DET", "OnAnyNonZero"); + string path2 = RepoPathUtils::getQoPath(&qo2); + BOOST_CHECK_EQUAL(path2, "qc/DET/QO/xyzCheck"); + path2 = RepoPathUtils::getQoPath("DET", "xyzCheck", "OnAnyNonZero"); + BOOST_CHECK_EQUAL(path2, "qc/DET/QO/xyzCheck"); + + // policy is OnEachSeparately + QualityObject qo3(Quality::Null, "xyzCheck", "DET", "OnEachSeparately", {}, { "objectABC" }); + string path3 = RepoPathUtils::getQoPath(&qo3); + BOOST_CHECK_EQUAL(path3, "qc/DET/QO/xyzCheck/objectABC"); + path2 = RepoPathUtils::getQoPath("DET", "xyzCheck", "OnEachSeparately", { "objectABC" }); + BOOST_CHECK_EQUAL(path3, "qc/DET/QO/xyzCheck/objectABC"); + + // policy is OnEachSeparately and the vector is empty + QualityObject qo4(Quality::Null, "xyzCheck", "DET", "OnEachSeparately", {}, {}); + BOOST_CHECK_EXCEPTION(RepoPathUtils::getQoPath(&qo4), AliceO2::Common::FatalException, do_nothing); + BOOST_CHECK_EXCEPTION(RepoPathUtils::getQoPath("DET", "xyzCheck", "OnEachSeparately", {}), AliceO2::Common::FatalException, do_nothing); +} + +BOOST_AUTO_TEST_CASE(mopath) +{ + string objectName = "asdf"; + TH1F h(objectName.data(), objectName.data(), 100, 0, 99); + o2::quality_control::core::MonitorObject obj(&h, "task", "class", "DET"); + obj.setIsOwner(false); + string path = RepoPathUtils::getMoPath(&obj); + BOOST_CHECK_EQUAL(path, "qc/DET/MO/task/asdf"); + path = RepoPathUtils::getMoPath("DET", "task", "asdf"); + BOOST_CHECK_EQUAL(path, "qc/DET/MO/task/asdf"); + + // provenance + obj.getActivity().mProvenance = "qc_mc"; + path = RepoPathUtils::getMoPath(&obj); + BOOST_CHECK_EQUAL(path, "qc_mc/DET/MO/task/asdf"); +} diff --git a/Framework/test/testRootFileStorage.cxx b/Framework/test/testRootFileStorage.cxx new file mode 100644 index 0000000000..d44d9462fb --- /dev/null +++ b/Framework/test/testRootFileStorage.cxx @@ -0,0 +1,405 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testRootFileStorage.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/RootFileStorage.h" +#include "QualityControl/MonitorObjectCollection.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include +#include + +using namespace o2::quality_control::core; + +struct TestFileFixture { + TestFileFixture(const std::string& testCase) + { + filePath = "/tmp/qc_test_root_file_storage_" + testCase + "_" + std::to_string(getpid()) + ".root"; + if (!filePath.empty()) { + std::filesystem::remove(filePath); + } + } + + ~TestFileFixture() + { + if (!filePath.empty()) { + std::filesystem::remove(filePath); + } + } + + std::string filePath; +}; + +const size_t bins = 10; +const size_t min = 0; +const size_t max = 10; + +TEST_CASE("int_write_read") +{ + // the fixture will do the cleanup when being destroyed only after any file readers are destroyed earlier + TestFileFixture fixture("int_write_read"); + + MonitorObjectCollection* mocBefore = new MonitorObjectCollection(); + mocBefore->SetOwner(true); + + TH1I* histoBefore = new TH1I("histo 1d", "histo 1d", bins, min, max); + histoBefore->Fill(5); + MonitorObject* moHistoBefore = new MonitorObject(histoBefore, "histo 1d", "class", "DET"); + moHistoBefore->setActivity({ 300000, "PHYSICS", "LHC32x", "apass2", "qc_async", { 100, 300 } }); + moHistoBefore->setIsOwner(true); + mocBefore->Add(moHistoBefore); + { + RootFileStorage storage(fixture.filePath, RootFileStorage::ReadMode::Update); + REQUIRE(storage.readStructure(false).children.empty()); + + // store and read back, check results + { + REQUIRE_NOTHROW(storage.storeIntegralMOC(mocBefore)); + auto mocAfter = storage.readMonitorObjectCollection("int/TST/Test"); + REQUIRE(mocAfter != nullptr); + + REQUIRE(mocBefore->GetEntries() == mocAfter->GetEntries()); + auto moHistoAfter = dynamic_cast(mocAfter->At(0)); + REQUIRE(moHistoAfter != nullptr); + + CHECK(moHistoAfter->getActivity() == moHistoBefore->getActivity()); + REQUIRE(moHistoAfter->getObject() != nullptr); + auto histoAfter = dynamic_cast(moHistoAfter->getObject()); + CHECK(histoAfter->GetBinContent(histoAfter->FindBin(5)) == 1); + CHECK(histoAfter->GetSum() == 1); + } + // merge mocBefore into the existing file, check results + { + REQUIRE_NOTHROW(storage.storeIntegralMOC(mocBefore)); + auto mocAfter = storage.readMonitorObjectCollection("int/TST/Test"); + REQUIRE(mocAfter != nullptr); + + REQUIRE(mocBefore->GetEntries() == mocAfter->GetEntries()); + auto moHistoAfter = dynamic_cast(mocAfter->At(0)); + REQUIRE(moHistoAfter != nullptr); + + CHECK(moHistoAfter->getActivity() == moHistoBefore->getActivity()); + REQUIRE(moHistoAfter->getObject() != nullptr); + auto histoAfter = dynamic_cast(moHistoAfter->getObject()); + CHECK(histoAfter->GetBinContent(histoAfter->FindBin(5)) == 2); + CHECK(histoAfter->GetSum() == 2); + } + } + + // close and reopen the file, then merge again, check results + { + RootFileStorage storage(fixture.filePath, RootFileStorage::ReadMode::Update); + REQUIRE_NOTHROW(storage.storeIntegralMOC(mocBefore)); + auto mocAfter = storage.readMonitorObjectCollection("int/TST/Test"); + REQUIRE(mocAfter != nullptr); + + REQUIRE(mocBefore->GetEntries() == mocAfter->GetEntries()); + auto moHistoAfter = dynamic_cast(mocAfter->At(0)); + REQUIRE(moHistoAfter != nullptr); + + CHECK(moHistoAfter->getActivity() == moHistoBefore->getActivity()); + REQUIRE(moHistoAfter->getObject() != nullptr); + auto histoAfter = dynamic_cast(moHistoAfter->getObject()); + CHECK(histoAfter->GetBinContent(histoAfter->FindBin(5)) == 3.0); + CHECK(histoAfter->GetSum() == 3.0); + } +} + +TEST_CASE("mw_write_read") +{ + // the fixture will do the cleanup when being destroyed only after any file readers are destroyed earlier + TestFileFixture fixture("mw_write_read"); + + MonitorObjectCollection* mocBefore = new MonitorObjectCollection(); + mocBefore->SetOwner(true); + + TH1I* histoBefore = new TH1I("histo 1d", "histo 1d", bins, min, max); + histoBefore->Fill(5); + MonitorObject* moHistoBefore = new MonitorObject(histoBefore, "histo 1d", "class", "DET"); + moHistoBefore->setActivity({ 300000, "PHYSICS", "LHC32x", "apass2", "qc_async", { 100, 300 } }); + moHistoBefore->setIsOwner(true); + mocBefore->Add(moHistoBefore); + { + RootFileStorage storage(fixture.filePath, RootFileStorage::ReadMode::Update); + REQUIRE(storage.readStructure(false).children.empty()); + + // store and read back, check results + { + REQUIRE_NOTHROW(storage.storeMovingWindowMOC(mocBefore)); + auto mocAfter = storage.readMonitorObjectCollection("mw/TST/Test/100"); + REQUIRE(mocAfter != nullptr); + + REQUIRE(mocBefore->GetEntries() == mocAfter->GetEntries()); + auto moHistoAfter = dynamic_cast(mocAfter->At(0)); + REQUIRE(moHistoAfter != nullptr); + + CHECK(moHistoAfter->getActivity() == moHistoBefore->getActivity()); + REQUIRE(moHistoAfter->getObject() != nullptr); + auto histoAfter = dynamic_cast(moHistoAfter->getObject()); + CHECK(histoAfter->GetBinContent(histoAfter->FindBin(5)) == 1); + CHECK(histoAfter->GetSum() == 1); + } + // merge mocBefore into the existing file, check results + { + // extend the validity forward, start stays the same. + moHistoBefore->setValidity({ 100, 500 }); + REQUIRE_NOTHROW(storage.storeMovingWindowMOC(mocBefore)); + auto mocAfter = storage.readMonitorObjectCollection("mw/TST/Test/100"); + REQUIRE(mocAfter != nullptr); + + REQUIRE(mocBefore->GetEntries() == mocAfter->GetEntries()); + auto moHistoAfter = dynamic_cast(mocAfter->At(0)); + REQUIRE(moHistoAfter != nullptr); + + CHECK(moHistoAfter->getActivity() == moHistoBefore->getActivity()); + REQUIRE(moHistoAfter->getObject() != nullptr); + auto histoAfter = dynamic_cast(moHistoAfter->getObject()); + CHECK(histoAfter->GetBinContent(histoAfter->FindBin(5)) == 2); + CHECK(histoAfter->GetSum() == 2); + } + } + + // move the validity to the future, a new object should be stored in the file + moHistoBefore->setValidity({ 300, 500 }); + // close and reopen the file, then merge again, check results + { + RootFileStorage storage(fixture.filePath, RootFileStorage::ReadMode::Update); + REQUIRE_NOTHROW(storage.storeMovingWindowMOC(mocBefore)); + auto mocAfter = storage.readMonitorObjectCollection("mw/TST/Test/300"); + REQUIRE(mocAfter != nullptr); + + REQUIRE(mocBefore->GetEntries() == mocAfter->GetEntries()); + auto moHistoAfter = dynamic_cast(mocAfter->At(0)); + REQUIRE(moHistoAfter != nullptr); + + CHECK(moHistoAfter->getActivity() == moHistoBefore->getActivity()); + REQUIRE(moHistoAfter->getObject() != nullptr); + auto histoAfter = dynamic_cast(moHistoAfter->getObject()); + CHECK(histoAfter->GetBinContent(histoAfter->FindBin(5)) == 1.0); + CHECK(histoAfter->GetSum() == 1.0); + } +} + +TEST_CASE("read_structure") +{ + // the fixture will do the cleanup when being destroyed only after any file readers are destroyed earlier + TestFileFixture fixture("read_structure"); + + MonitorObjectCollection* moc = new MonitorObjectCollection(); + moc->SetOwner(true); + moc->setDetector("TST"); + + TH1I* histo1 = new TH1I("histo 1", "histo 1", bins, min, max); + histo1->Fill(5); + MonitorObject* moHisto1 = new MonitorObject(histo1, "histo 1", "class", "DET"); + moHisto1->setActivity({ 300000, "PHYSICS", "LHC32x", "apass2", "qc_async", { 100, 300 } }); + moHisto1->setIsOwner(true); + moc->Add(moHisto1); + + TH1I* histo2 = new TH1I("histo 2", "histo 2", bins, min, max); + histo1->Fill(5); + MonitorObject* moHisto2 = new MonitorObject(histo2, "histo 2", "class", "DET"); + moHisto2->setActivity({ 300000, "PHYSICS", "LHC32x", "apass2", "qc_async", { 100, 300 } }); + moHisto2->setIsOwner(true); + moc->Add(moHisto2); + + RootFileStorage storage(fixture.filePath, RootFileStorage::ReadMode::Update); + REQUIRE(storage.readStructure(false).children.empty()); + + storage.storeIntegralMOC(moc); + storage.storeMovingWindowMOC(moc); + dynamic_cast(moc->At(0))->setValidity({ 300, 500 }); + dynamic_cast(moc->At(1))->setValidity({ 300, 500 }); + storage.storeMovingWindowMOC(moc); + + auto structure = storage.readStructure(false); + CHECK(structure.children.size() == 2); + { + REQUIRE(structure.children.find("int") != structure.children.end()); + REQUIRE(std::holds_alternative(structure.children.at("int"))); + auto intDir = std::get(structure.children.at("int")); + CHECK(intDir.name == "int"); + CHECK(intDir.fullPath == "int"); + REQUIRE(intDir.children.size() == 1); + REQUIRE(intDir.children.find("TST") != intDir.children.end()); + REQUIRE(std::holds_alternative(intDir.children.at("TST"))); + { + auto intTstDir = std::get(intDir.children.at("TST")); + CHECK(intTstDir.name == "TST"); + CHECK(intTstDir.fullPath == "int/TST"); + REQUIRE(intTstDir.children.size() == 1); + REQUIRE(intTstDir.children.find("Test") != intTstDir.children.end()); + REQUIRE(std::holds_alternative(intTstDir.children.at("Test"))); + { + auto intTstTestMoc = std::get(intTstDir.children.at("Test")); + CHECK(intTstTestMoc.name == "Test"); + CHECK(intTstTestMoc.fullPath == "int/TST/Test"); + REQUIRE(intTstTestMoc.moc == nullptr); + } + } + } + { + REQUIRE(structure.children.find("mw") != structure.children.end()); + REQUIRE(std::holds_alternative(structure.children.at("mw"))); + auto mwDir = std::get(structure.children.at("mw")); + CHECK(mwDir.name == "mw"); + CHECK(mwDir.fullPath == "mw"); + REQUIRE(mwDir.children.size() == 1); + REQUIRE(mwDir.children.find("TST") != mwDir.children.end()); + REQUIRE(std::holds_alternative(mwDir.children.at("TST"))); + { + auto mwTstDir = std::get(mwDir.children.at("TST")); + CHECK(mwTstDir.name == "TST"); + CHECK(mwTstDir.fullPath == "mw/TST"); + REQUIRE(mwTstDir.children.size() == 1); + REQUIRE(mwTstDir.children.find("Test") != mwTstDir.children.end()); + REQUIRE(std::holds_alternative(mwTstDir.children.at("Test"))); + { + auto mwTstTestDir = std::get(mwTstDir.children.at("Test")); + CHECK(mwTstTestDir.name == "Test"); + CHECK(mwTstTestDir.fullPath == "mw/TST/Test"); + CHECK(mwTstTestDir.children.size() == 2); + REQUIRE(mwTstTestDir.children.find("100") != mwTstTestDir.children.end()); + REQUIRE(std::holds_alternative(mwTstTestDir.children.at("100"))); + { + auto mwTstTestMoc100 = std::get(mwTstTestDir.children.at("100")); + CHECK(mwTstTestMoc100.name == "100"); + CHECK(mwTstTestMoc100.fullPath == "mw/TST/Test/100"); + REQUIRE(mwTstTestMoc100.moc == nullptr); + } + REQUIRE(mwTstTestDir.children.find("300") != mwTstTestDir.children.end()); + REQUIRE(std::holds_alternative(mwTstTestDir.children.at("300"))); + { + auto mwTstTestMoc300 = std::get(mwTstTestDir.children.at("300")); + CHECK(mwTstTestMoc300.name == "300"); + CHECK(mwTstTestMoc300.fullPath == "mw/TST/Test/300"); + REQUIRE(mwTstTestMoc300.moc == nullptr); + } + } + } + } + + // now we read MonitorObjectCollections and delete them + structure = storage.readStructure(true); + auto intTstTest = std::get( + std::get( + std::get( + structure.children.at("int")) + .children.at("TST")) + .children.at("Test")); + CHECK(intTstTest.moc != nullptr); + delete intTstTest.moc; + + auto mwTstTest100 = std::get( + std::get( + std::get( + std::get( + structure.children.at("mw")) + .children.at("TST")) + .children.at("Test")) + .children.at("100")); + CHECK(mwTstTest100.moc != nullptr); + delete mwTstTest100.moc; + + auto mwTstTest300 = std::get( + std::get( + std::get( + std::get( + structure.children.at("mw")) + .children.at("TST")) + .children.at("Test")) + .children.at("300")); + CHECK(mwTstTest300.moc != nullptr); + delete mwTstTest300.moc; +} + +TEST_CASE("walking") +{ + // the fixture will do the cleanup when being destroyed only after any file readers are destroyed earlier + TestFileFixture fixture("walking"); + + MonitorObjectCollection* moc = new MonitorObjectCollection(); + moc->SetOwner(true); + moc->setDetector("TST"); + + TH1I* histo1 = new TH1I("histo 1", "histo 1", bins, min, max); + histo1->Fill(5); + MonitorObject* moHisto1 = new MonitorObject(histo1, "histo 1", "class", "DET"); + moHisto1->setActivity({ 300000, "PHYSICS", "LHC32x", "apass2", "qc_async", { 100, 300 } }); + moHisto1->setIsOwner(true); + moc->Add(moHisto1); + + TH1I* histo2 = new TH1I("histo 2", "histo 2", bins, min, max); + histo1->Fill(5); + MonitorObject* moHisto2 = new MonitorObject(histo2, "histo 2", "class", "DET"); + moHisto2->setActivity({ 300000, "PHYSICS", "LHC32x", "apass2", "qc_async", { 100, 300 } }); + moHisto2->setIsOwner(true); + moc->Add(moHisto2); + + RootFileStorage storage(fixture.filePath, RootFileStorage::ReadMode::Update); + auto structure = storage.readStructure(false); + REQUIRE(structure.children.empty()); + + // check if walkers do not crash when the file is empty + { + IntegralMocWalker intWalker(structure); + REQUIRE(!intWalker.hasNextPath()); + REQUIRE(intWalker.nextPath() == ""); + } + { + MovingWindowMocWalker mwWalker(structure); + REQUIRE(!mwWalker.hasNextPath()); + REQUIRE(mwWalker.nextPath() == ""); + } + + // now we put some data in the file and validate walkers in a usual scenario + storage.storeIntegralMOC(moc); + storage.storeMovingWindowMOC(moc); + dynamic_cast(moc->At(0))->setValidity({ 300, 500 }); + dynamic_cast(moc->At(1))->setValidity({ 300, 500 }); + storage.storeMovingWindowMOC(moc); + + structure = storage.readStructure(false); + { + IntegralMocWalker intWalker(structure); + REQUIRE(intWalker.hasNextPath()); + auto path = intWalker.nextPath(); + REQUIRE(path == "int/TST/Test"); + auto* readMoc = storage.readMonitorObjectCollection(path); + CHECK(readMoc != nullptr); + delete readMoc; + REQUIRE(!intWalker.hasNextPath()); + CHECK(intWalker.nextPath().empty()); + } + { + MovingWindowMocWalker mwWalker(structure); + REQUIRE(mwWalker.hasNextPath()); + auto path = mwWalker.nextPath(); + REQUIRE(path == "mw/TST/Test/100"); + auto* readMoc = storage.readMonitorObjectCollection(path); + CHECK(readMoc != nullptr); + delete readMoc; + REQUIRE(mwWalker.hasNextPath()); + path = mwWalker.nextPath(); + REQUIRE(path == "mw/TST/Test/300"); + readMoc = storage.readMonitorObjectCollection(path); + CHECK(readMoc != nullptr); + delete readMoc; + REQUIRE(!mwWalker.hasNextPath()); + CHECK(mwWalker.nextPath().empty()); + } +} \ No newline at end of file diff --git a/Framework/test/testRunnerUtils.cxx b/Framework/test/testRunnerUtils.cxx new file mode 100644 index 0000000000..e9e15393fa --- /dev/null +++ b/Framework/test/testRunnerUtils.cxx @@ -0,0 +1,40 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testRunnerUtils.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/runnerUtils.h" + +#define BOOST_TEST_MODULE RunnerUtils test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include +#include + +using namespace o2::quality_control::core; +using namespace std; + +BOOST_AUTO_TEST_CASE(test_computeActivity) +{ + string runType; + BOOST_CHECK_EQUAL(translateIntegerRunType("0"), "NONE"); + BOOST_CHECK_EQUAL(translateIntegerRunType("1"), "PHYSICS"); + BOOST_CHECK_EQUAL(translateIntegerRunType("18"), "CALIBRATION_VRESETD"); + BOOST_CHECK_EQUAL(translateIntegerRunType("34"), "NONE"); // unknown + BOOST_CHECK_EQUAL(translateIntegerRunType("-134"), "-134"); // not a uint + BOOST_CHECK_EQUAL(translateIntegerRunType("NONE"), "NONE"); + BOOST_CHECK_EQUAL(translateIntegerRunType("bla"), "bla"); +} \ No newline at end of file diff --git a/Framework/test/testSharedConfig.json b/Framework/test/testSharedConfig.json new file mode 100644 index 0000000000..818227cc96 --- /dev/null +++ b/Framework/test/testSharedConfig.json @@ -0,0 +1,389 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "skeletonTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "cycleDurationSeconds": "10", + "detectorName": "TST", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tpcclust" + }, + "taskParameters": { + "parameter1": "100001", + "parameter2": "qu'est-ce que c'est que ce truc la" + }, + "movingWindows": [ "example" ], + "location": "local", + "localMachines": [ + "o2flp1", + "o2flp2" + ], + "remoteMachine": "o2qc01", + "remotePort": "30123", + "mergingMode": "delta" + }, + "abcTask": { + "active": true, + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "XXXXXXXXX", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tpcclust" + }, + "cycleDurationSeconds": "10", + "taskParameters": { + "parameter1": "100002", + "parameter2": "c'est quoi" + }, + "location": "remote" + }, + "xyzTaskID": { + "active": true, + "taskName": "xyzTask", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "ITS", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tpcclust" + }, + "cycleDurationSeconds": "10", + "location": "remote" + }, + "defTask": { + "active": "false", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "ITS", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tpcclust" + }, + "cycleDurationSeconds": "10" + }, + "recoTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "GLO", + "dataSource": { + "type": "direct", + "query": "" + }, + "cycleDurationSeconds": "10", + "grpGeomRequest" : { + "geomRequest": "Aligned", + "askGRPECS": "true", + "askGRPLHCIF": "true", + "askGRPMagField": "true", + "askMatLUT": "true", + "askTime": "true", + "askOnceAllButField": "true", + "needPropagatorD": "true" + }, + "globalTrackingDataRequest": { + "canProcessTracks" : "ITS,ITS-TPC", + "requestTracks" : "ITS,TPC-TRD", + "canProcessClusters" : "TPC", + "requestClusters" : "TPC", + "mc" : "false" + }, + "location": "local", + "localMachines": [ + "o2flp1" + ], + "remoteMachine": "o2qc01", + "remotePort": "30333", + "mergingMode": "delta" + } + }, + "checks": { + "singleCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "skeletonTask", + "MOs": [ + "example" + ] + }] + }, + "singleCheckLongDescription": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "skeletonTask", + "MOs": [ + "example" + ] + }] + },"checkAll": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "policy": "OnAll", + "dataSource": [{ + "type": "Task", + "name": "abcTask", + "MOs": [ + "test1", + "test2" + ] + }] + }, + "checkAny": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "policy": "OnAny", + "dataSource": [{ + "type": "Task", + "name": "abcTask", + "MOs": [ + "test1", + "test2" + ] + }] + }, + "checkAnyNonZero": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "policy": "OnAnyNonZero", + "dataSource": [{ + "type": "Task", + "name": "abcTask", + "MOs": [ + "test1", + "test2" + ] + }] + }, + "checkGlobalAny": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "xyzTaskID" + }] + }, + "checkOnEachSeparately": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "policy": "OnEachSeparately", + "dataSource": [{ + "type": "Task", + "name": "abcTask", + "MOs": [ + "test1", + "test2" + ] + }] + }, + "checkAnyPP": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "policy": "OnAny", + "dataSource": [{ + "type": "PostProcessing", + "name": "SkeletonPostProcessing", + "MOs": [ + "example" + ] + }] + }, + "dataSizeCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "skeletonTask", + "MOs": ["example"] + }] + }, + "dataSizeCheck2": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "skeletonTask", + "MOs": ["example"] + }] + }, + "someNumbersCheck": { + "active": "true", + "exportToBookkeeping": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "skeletonTask", + "MOs": ["example"] + }] + } + }, + "postprocessing": { + "SkeletonPostProcessing": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonPostProcessing", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "initTrigger": [ + "SOR", + "EOR", + "once" + ], + "updateTrigger": [ + "once" + ], + "stopTrigger": [ + "once" + ], + "extendedTaskParameters": { + "default": { + "default": { + "myOwnKey": "myOwnValue", + "myOwnKey2": "myOwnValue2" + } + }, + "PHYSICS": { + "default": { + "myOwnKey1": "myOwnValue1b", + "myOwnKey2": "myOwnValue2b" + } + } + } + } + }, + "aggregators": { + "MyAggregatorA": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Aggregator", + "name": "MyAggregatorB", "": "no QOs parameter -> all QOs of this aggregator", + "QOs": ["newQuality"], "": "list all objects we are interested in" + }, { + "type": "Aggregator", + "name": "MyAggregatorC", + "QOs": ["newQuality", "another"], "": "if we omitted the QOs for a data source we would default to OnAny" + }] + }, + "MyAggregatorD": { + "active": "true", + "exportToBookkeeping": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Aggregator", + "name": "MyAggregatorA", "": "no QOs parameter -> all QOs of this aggregator", + "QOs": ["newQuality"], "": "list all objects we are interested in" + }, { + "type": "Aggregator", + "name": "MyAggregatorC", + "QOs": ["newQuality", "another"], "": "if we omitted the QOs for a data source we would default to OnAny" + }] + }, + "MyAggregatorC": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", + "name": "dataSizeCheck" + }, { + "type": "Check", + "name": "someNumbersCheck" + }] + }, + "MyAggregatorB": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAnyNonZero", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", "": "this one sends a single object", + "name": "checkAll", + "QOs": "", "": "with OnAnyNonZero we must provide a list of QOs, thus the empty string" + }, { + "type": "Check", "": "this one is using onEachSeparately and thus it sends under a full path", + "name": "dataSizeCheck2", + "QOs": ["skeletonTask/example"], "": "also possible to ignore it altogether, meaning we take all objects" + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tpcclust", + "active": "true", + "machines": [ + "o2flp1" + ], + "query": "data:TPC/CLUSTERS/0", + "port": "30303", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Framework/test/testStringUtils.cxx b/Framework/test/testStringUtils.cxx new file mode 100644 index 0000000000..7d320fb37c --- /dev/null +++ b/Framework/test/testStringUtils.cxx @@ -0,0 +1,37 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testStringUtils.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/stringUtils.h" + +#define BOOST_TEST_MODULE Triggers test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +using namespace o2::quality_control::core; + +BOOST_AUTO_TEST_CASE(test_is_number) +{ + BOOST_CHECK_EQUAL(isUnsignedInteger("1"), true); + BOOST_CHECK_EQUAL(isUnsignedInteger("-1"), false); + BOOST_CHECK_EQUAL(isUnsignedInteger("1000000"), true); + BOOST_CHECK_EQUAL(isUnsignedInteger("0.1"), false); + BOOST_CHECK_EQUAL(isUnsignedInteger(".2"), false); + BOOST_CHECK_EQUAL(isUnsignedInteger("x"), false); + BOOST_CHECK_EQUAL(isUnsignedInteger("1x"), false); + BOOST_CHECK_EQUAL(isUnsignedInteger("......"), false); +} \ No newline at end of file diff --git a/Framework/test/testTaskInterface.cxx b/Framework/test/testTaskInterface.cxx new file mode 100644 index 0000000000..c44351aedb --- /dev/null +++ b/Framework/test/testTaskInterface.cxx @@ -0,0 +1,215 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testTaskInterface.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/TaskFactory.h" +#include "QualityControl/TaskInterface.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include "EMCALCalib/BadChannelMap.h" +#include "CCDB/CcdbApi.h" +#include + +#if (__has_include()) +#include +o2::framework::ConfigParamRegistry createDummyRegistry() +{ + using namespace o2::framework; + std::vector specs; + std::vector> retrievers; + + auto store = std::make_unique(specs, std::move(retrievers)); + store->preload(); + store->activate(); + ConfigParamRegistry registry(std::move(store)); + + return registry; +} +#else +o2::framework::ConfigParamRegistry createDummyRegistry() +{ + using namespace o2::framework; + std::unique_ptr retriever; + ConfigParamRegistry registry(std::move(retriever)); + + return registry; +} +#endif + +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace std; +using namespace o2::framework; + +namespace o2::quality_control +{ + +using namespace core; + +namespace test +{ +class TestTask : public TaskInterface +{ + public: + TestTask(ObjectsManager* objectsManager) : TaskInterface(objectsManager) { test = 0; } + + ~TestTask() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& /*ctx*/) override + { + ILOG(Info, Support) << "initialize" << ENDM; + test = 1; + } + + void startOfActivity(const Activity& /*activity*/) override + { + ILOG(Info, Support) << "startOfActivity" << ENDM; + test = 2; + } + + void startOfCycle() override + { + ILOG(Info, Support) << "startOfCycle" << ENDM; + test = 3; + } + + void monitorData(o2::framework::ProcessingContext& /*ctx*/) override + { + ILOG(Info, Support) << "monitorData" << ENDM; + test = 4; + } + + void endOfCycle() override + { + ILOG(Info, Support) << "endOfCycle" << ENDM; + test = 5; + } + + void endOfActivity(const Activity& /*activity*/) override + { + ILOG(Info, Support) << "endOfActivity" << ENDM; + test = 6; + } + + void reset() override + { + ILOG(Info, Support) << "reset" << ENDM; + test = 7; + } + + o2::emcal::BadChannelMap* testRetrieveCondition() + { + ILOG(Info, Support) << "testRetrieveCondition" << ENDM; + test = 8; + + return retrieveConditionAny("qc/TST/conditions"); + } + + int test; +}; + +} /* namespace test */ +} /* namespace o2::quality_control */ + +TEST_CASE("test_invoke_all_TaskRunnerConfig_methods") +{ + // This is maximum that we can do until we are able to test the DPL algorithms in isolation. + TaskRunnerConfig taskConfig; + auto* objectsManager = new ObjectsManager(taskConfig.name, taskConfig.className, taskConfig.detectorName, 0); + + test::TestTask testTask(objectsManager); + CHECK(testTask.test == 0); + + auto options = createDummyRegistry(); + ServiceRegistry services; + InitContext ctx(options, services); + testTask.initialize(ctx); + CHECK(testTask.test == 1); + + Activity act; + testTask.startOfActivity(act); + CHECK(testTask.test == 2); + + testTask.startOfCycle(); + CHECK(testTask.test == 3); + + // creating a valid ProcessingContext is almost impossible outside of the framework + // testTask.monitorData(pctx); + // CHECK(testTask.test == 4); + + testTask.endOfCycle(); + CHECK(testTask.test == 5); + + testTask.endOfActivity(act); + CHECK(testTask.test == 6); + + testTask.reset(); + CHECK(testTask.test == 7); +} + +TEST_CASE("test_task_factory") +{ + TaskRunnerConfig config{ + "skeletonTask", + "QcSkeleton", + "o2::quality_control_modules::skeleton::SkeletonTask", + "TST", + "", + {}, + "something", + {}, + {}, + "SkeletonTaskRunner", + { { 10, 1 } }, + -1, + true, + "" + }; + + auto objectsManager = make_shared(config.name, config.className, config.detectorName, 0); + + TaskFactory taskFactory; + auto task = taskFactory.create(config, objectsManager); + + REQUIRE(task != nullptr); + delete task; +} + +TEST_CASE("retrieveCondition") +{ + // first store a condition + o2::emcal::BadChannelMap bad; + bad.addBadChannel(1, o2::emcal::BadChannelMap::MaskType_t::GOOD_CELL); + bad.addBadChannel(2, o2::emcal::BadChannelMap::MaskType_t::BAD_CELL); + bad.addBadChannel(3, o2::emcal::BadChannelMap::MaskType_t::DEAD_CELL); + std::map meta; + o2::ccdb::CcdbApi api; + api.init("ccdb-test.cern.ch:8080"); + api.storeAsTFileAny(&bad, "qc/TST/conditions", meta); + + // retrieve it + TaskRunnerConfig taskConfig; + auto* objectsManager = new ObjectsManager(taskConfig.name, taskConfig.className, taskConfig.detectorName, 0); + test::TestTask testTask(objectsManager); + testTask.setCcdbUrl("ccdb-test.cern.ch:8080"); + o2::emcal::BadChannelMap* bcm = testTask.testRetrieveCondition(); + CHECK(bcm->getChannelStatus(1) == o2::emcal::BadChannelMap::MaskType_t::GOOD_CELL); + CHECK(bcm->getChannelStatus(3) == o2::emcal::BadChannelMap::MaskType_t::DEAD_CELL); +} diff --git a/Framework/test/testTaskRunner.cxx b/Framework/test/testTaskRunner.cxx new file mode 100644 index 0000000000..7c8d8d5e56 --- /dev/null +++ b/Framework/test/testTaskRunner.cxx @@ -0,0 +1,134 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testTaskRunner.cxx +/// \author Piotr Konopka +/// + +#include "getTestDataDirectory.h" +#include "QualityControl/TaskRunnerFactory.h" +#include "QualityControl/TaskRunner.h" +#include "QualityControl/UserInputOutput.h" +#include +#include +#include "QualityControl/InfrastructureSpecReader.h" +#include "Configuration/ConfigurationFactory.h" +#include "Configuration/ConfigurationInterface.h" +#include +#include +#include +#include + +#define BOOST_TEST_MODULE TaskRunner test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +using namespace o2::quality_control::core; +using namespace std; +using namespace o2::framework; +using namespace o2::header; +using namespace o2::utilities; +using namespace o2::configuration; + +TaskRunnerConfig getTaskConfig(const std::string& configFilePath, const std::string& taskName, size_t id) +{ + auto config = ConfigurationFactory::getConfiguration(configFilePath); + auto infrastructureSpec = InfrastructureSpecReader::readInfrastructureSpec(config->getRecursive(), WorkflowType::Standalone); + + auto taskSpec = std::find_if(infrastructureSpec.tasks.begin(), infrastructureSpec.tasks.end(), [&taskName](const auto& taskSpec) { + return taskSpec.taskName == taskName; + }); + if (taskSpec != infrastructureSpec.tasks.end()) { + return TaskRunnerFactory::extractConfig(infrastructureSpec.common, *taskSpec, id); + } else { + throw std::runtime_error("task " + taskName + " not found in the config file"); + } +} + +BOOST_AUTO_TEST_CASE(test_factory) +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + + DataProcessorSpec taskRunner = TaskRunnerFactory::create(getTaskConfig(configFilePath, "abcTask", 123)); + + BOOST_CHECK_EQUAL(taskRunner.name, "qc-task-MISC-abcTask"); + + auto dataSamplingTree = ConfigurationFactory::getConfiguration(configFilePath)->getRecursive("dataSamplingPolicies"); + BOOST_REQUIRE_EQUAL(taskRunner.inputs.size(), 2); + BOOST_CHECK_EQUAL(taskRunner.inputs[0], DataSampling::InputSpecsForPolicy(dataSamplingTree, "tpcclust").at(0)); + BOOST_CHECK(taskRunner.inputs[1].lifetime == Lifetime::Timer); + + BOOST_REQUIRE_EQUAL(taskRunner.outputs.size(), 1); + BOOST_CHECK_EQUAL(taskRunner.outputs[0], createUserOutputSpec(DataSourceType::Task, "XXX", "abcTask", 123)); + + BOOST_CHECK(taskRunner.algorithm.onInit != nullptr); + + BOOST_REQUIRE_EQUAL(taskRunner.options.size(), 3); + BOOST_CHECK_EQUAL(taskRunner.options[0].name, "period-timer-cycle"); +} + +BOOST_AUTO_TEST_CASE(test_task_runner_static) +{ + BOOST_CHECK_EQUAL(TaskRunner::createTaskRunnerIdString(), "qc-task"); +} + +BOOST_AUTO_TEST_CASE(test_task_runner) +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + TaskRunner qcTask{ getTaskConfig(configFilePath, "abcTask", 0) }; + + BOOST_CHECK_EQUAL(qcTask.getDeviceName(), "qc-task-MISC-abcTask"); + + auto dataSamplingTree = ConfigurationFactory::getConfiguration(configFilePath)->getRecursive("dataSamplingPolicies"); + BOOST_REQUIRE_EQUAL(qcTask.getInputsSpecs().size(), 2); + BOOST_CHECK_EQUAL(qcTask.getInputsSpecs()[0], DataSampling::InputSpecsForPolicy(dataSamplingTree, "tpcclust").at(0)); + BOOST_CHECK(qcTask.getInputsSpecs()[1].lifetime == Lifetime::Timer); + + BOOST_CHECK_EQUAL(qcTask.getOutputSpec(), createUserOutputSpec(DataSourceType::Task, "XXX", "abcTask", 0)); + + BOOST_REQUIRE_EQUAL(qcTask.getOptions().size(), 3); + BOOST_CHECK_EQUAL(qcTask.getOptions()[0].name, "period-timer-cycle"); + + // This is maximum that we can do until we are able to test the DPL algorithms in isolation. + // TODO: When it is possible, we should try calling run() and init() + + // Attempt for init: + Options options{ + { "runNumber", VariantType::String, { "Run number" } }, + { "qcConfiguration", VariantType::Dict, emptyDict(), { "Some dictionary configuration" } } + }; + std::vector> retr; + std::unique_ptr store = make_unique(std::move(options), std::move(retr)); + ConfigParamRegistry cfReg(std::move(store)); + ServiceRegistry sReg; + InitContext initContext{ cfReg, sReg }; + qcTask.init(initContext); +} + +BOOST_AUTO_TEST_CASE(test_task_wrong_detector_name) +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + + DataProcessorSpec taskRunner = TaskRunnerFactory::create(getTaskConfig(configFilePath, "abcTask", 0)); + // cout << "It should print an error message" << endl; +} + +BOOST_AUTO_TEST_CASE(test_task_good_detector_name) +{ + std::string configFilePath = std::string("json://") + getTestDataDirectory() + "testSharedConfig.json"; + + DataProcessorSpec taskRunner = TaskRunnerFactory::create(getTaskConfig(configFilePath, "xyzTask", 0)); + + // cout << "no error message" << endl; +} diff --git a/Framework/test/testThrowNameClash.json b/Framework/test/testThrowNameClash.json new file mode 100644 index 0000000000..71c638ccba --- /dev/null +++ b/Framework/test/testThrowNameClash.json @@ -0,0 +1,54 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + } + }, + "tasks": { + "QcTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query" : "data:TST/RAWDATA/0" + }, + "location": "remote" + } + }, + "checks": { + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnEachSeparately", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "QcTask", + "MOs": ["example"] + }] + } + }, + "aggregators": { + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", "": "for a check which produces a single result do it like that", + "name": "QcCheck", "": "no need to specify each object because there is only 1" + }] + } + } + } +} diff --git a/Framework/test/testTimekeeper.cxx b/Framework/test/testTimekeeper.cxx new file mode 100644 index 0000000000..7d8bfc066a --- /dev/null +++ b/Framework/test/testTimekeeper.cxx @@ -0,0 +1,337 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testTimekeeper.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/Timekeeper.h" +#include "QualityControl/TimekeeperSynchronous.h" +#include "QualityControl/TimekeeperAsynchronous.h" +#include "QualityControl/ActivityHelpers.h" +#include +#include + +#include + +using namespace o2::quality_control::core; +using namespace o2::framework; + +TEST_CASE("timekeeper_synchronous") +{ + SECTION("defaults") + { + auto tk = std::make_shared(); + CHECK(tk->getValidity() == gInvalidValidityInterval); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + + tk->reset(); + CHECK(tk->getValidity() == gInvalidValidityInterval); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + } + + SECTION("one_data_point_no_timer") + { + auto tk = std::make_shared(); + tk->updateByTimeFrameID(5); + + CHECK(tk->getValidity() == gInvalidValidityInterval); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 5, 5 }); + + tk->reset(); + CHECK(tk->getValidity() == gInvalidValidityInterval); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + } + + SECTION("no_data_one_timer") + { + auto tk = std::make_shared(); + tk->updateByCurrentTimestamp(1653000000000); + + CHECK(tk->getValidity() == ValidityInterval{ 1653000000000, 1653000000000 }); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + + tk->reset(); + CHECK(tk->getValidity() == ValidityInterval{ 1653000000000, 1653000000000 }); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + } + + SECTION("one_data_point_sor_timer") + { + auto tk = std::make_shared(); + tk->setActivityDuration(ValidityInterval{ 1653000000000, 1653000000000 }); + tk->updateByTimeFrameID(5); + + CHECK(tk->getValidity() == gInvalidValidityInterval); + // we need at least one update with timestamp for a valid validity + tk->updateByCurrentTimestamp(1653000000000); + CHECK(tk->getValidity() == ValidityInterval{ 1653000000000, 1653000000000 }); + CHECK(tk->getSampleTimespan() == ValidityInterval{ 1653000000011, 1653000000013 }); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 5, 5 }); + } + + SECTION("one_data_point_one_timer") + { + auto tk = std::make_shared(); + tk->updateByCurrentTimestamp(1653000000000); + tk->updateByTimeFrameID(5); + + CHECK(tk->getValidity() == ValidityInterval{ 1653000000000, 1653000000000 }); + CHECK(tk->getSampleTimespan() == ValidityInterval{ 1653000000011, 1653000000013 }); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 5, 5 }); + + tk->reset(); + CHECK(tk->getValidity() == ValidityInterval{ 1653000000000, 1653000000000 }); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + } + + SECTION("no_data_many_timers") + { + auto tk = std::make_shared(); + tk->updateByCurrentTimestamp(1655000000000); + tk->updateByCurrentTimestamp(1656000000000); + tk->updateByCurrentTimestamp(1654000000000); // a timer from the past is rather unexpected, but it should not break anything + + CHECK(tk->getValidity() == ValidityInterval{ 1654000000000, 1656000000000 }); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + + tk->reset(); + tk->updateByCurrentTimestamp(1655000000000); // again, we try with a timestamp which is before the beginning of this window + CHECK(tk->getValidity() == ValidityInterval{ 1655000000000, 1656000000000 }); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + } + + SECTION("many_data_points_many_timers") + { + auto tk = std::make_shared(); + tk->updateByCurrentTimestamp(1653000000000); + tk->updateByTimeFrameID(5); + tk->updateByTimeFrameID(7); + tk->updateByTimeFrameID(3); + tk->updateByTimeFrameID(10); + tk->updateByCurrentTimestamp(1653500000000); + + CHECK(tk->getValidity() == ValidityInterval{ 1653000000000, 1653500000000 }); + CHECK(tk->getSampleTimespan() == ValidityInterval{ 1653000000005, 1653000000027 }); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 3, 10 }); + + tk->reset(); + CHECK(tk->getValidity() == ValidityInterval{ 1653500000000, 1653500000000 }); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + + tk->updateByTimeFrameID(12); + tk->updateByTimeFrameID(54); + tk->updateByCurrentTimestamp(1653600000000); + + CHECK(tk->getValidity() == ValidityInterval{ 1653500000000, 1653600000000 }); + CHECK(tk->getSampleTimespan() == ValidityInterval{ 1653000000031, 1653000000152 }); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 12, 54 }); + } + + SECTION("boundary_selection") + { + auto tk = std::make_shared(); + + // ECS first + tk->setStartOfActivity(1, 2, 3); + tk->setEndOfActivity(4, 5, 6); + CHECK(tk->getActivityDuration().getMin() == 1); + CHECK(tk->getActivityDuration().getMax() == 4); + + // config second + tk->setStartOfActivity(0, 2, 3); + tk->setEndOfActivity(0, 5, 6); + CHECK(tk->getActivityDuration().getMin() == 2); + CHECK(tk->getActivityDuration().getMax() == 5); + tk->setStartOfActivity(-1, 2, 3); + tk->setEndOfActivity(-1, 5, 6); + CHECK(tk->getActivityDuration().getMin() == 2); + CHECK(tk->getActivityDuration().getMax() == 5); + + // current timestamp as the last resort + tk->setStartOfActivity(0, 0, 2); + tk->setEndOfActivity(0, 0, 5); + CHECK(tk->getActivityDuration().getMin() == 2); + CHECK(tk->getActivityDuration().getMax() == 5); + tk->setStartOfActivity(-1, -1, 2); + tk->setEndOfActivity(-1, -1, 5); + CHECK(tk->getActivityDuration().getMin() == 2); + CHECK(tk->getActivityDuration().getMax() == 5); + } + + SECTION("finish_cycle") + { + auto tk = std::make_shared(); + CHECK(tk->shouldFinishCycle(TimingInfo{ 1653500000000000 })); + CHECK(tk->shouldFinishCycle(TimingInfo{ 1653500010000000 })); + CHECK(!tk->shouldFinishCycle(TimingInfo{ 54000 })); + } +} + +TEST_CASE("timekeeper_asynchronous") +{ + SECTION("defaults") + { + auto tk = std::make_shared(); + tk->setCCDBOrbitsPerTFAccessor([]() { return 32; }); + CHECK(tk->getValidity() == gInvalidValidityInterval); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + + tk->reset(); + CHECK(tk->getValidity() == gInvalidValidityInterval); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + } + + SECTION("timers_have_no_effect") + { + auto tk = std::make_shared(); + tk->setCCDBOrbitsPerTFAccessor([]() { return 32; }); + tk->setActivityDuration(ValidityInterval{ 1653000000000, 1655000000000 }); + CHECK(tk->getValidity() == gInvalidValidityInterval); + tk->updateByCurrentTimestamp(1654000000000); + CHECK(tk->getValidity() == gInvalidValidityInterval); + } + + SECTION("sor_eor_not_set") + { + auto tk = std::make_shared(); + tk->setCCDBOrbitsPerTFAccessor([]() { return 32; }); + // duration not set + tk->updateByTimeFrameID(1234); + CHECK(tk->getValidity() == gInvalidValidityInterval); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + + // sor set, not eor - not enough + tk->setActivityDuration(ValidityInterval{ 1653000000000, 0 }); + tk->updateByTimeFrameID(1234); + CHECK(tk->getValidity() == gInvalidValidityInterval); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + + // make sure nothing weird happens after reset + tk->reset(); + CHECK(tk->getValidity() == gInvalidValidityInterval); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + } + + SECTION("data_no_moving_window") + { + auto tk = std::make_shared(); + tk->setCCDBOrbitsPerTFAccessor([]() { return 32; }); + tk->setActivityDuration(ValidityInterval{ 1653000000000, 1655000000000 }); + + CHECK(!tk->shouldFinishCycle(TimingInfo{ 3 })); // no, because validity is invalid and there is no moving window + tk->updateByTimeFrameID(3); + tk->updateByTimeFrameID(10); + CHECK(tk->getValidity() == ValidityInterval{ 1653000000000, 1655000000000 }); + CHECK(tk->getSampleTimespan() == ValidityInterval{ 1653000000005, 1653000000027 }); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 3, 10 }); + CHECK(!tk->shouldFinishCycle(TimingInfo{ 4 })); // no, because validity is invalid and there is no moving window + + tk->reset(); + CHECK(tk->getValidity() == gInvalidValidityInterval); + CHECK(tk->getSampleTimespan() == gInvalidValidityInterval); + CHECK(tk->getTimerangeIdRange() == gInvalidTimeframeIdRange); + + tk->updateByTimeFrameID(12); + tk->updateByTimeFrameID(54); + CHECK(tk->getValidity() == ValidityInterval{ 1653000000000, 1655000000000 }); + CHECK(tk->getSampleTimespan() == ValidityInterval{ 1653000000031, 1653000000152 }); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 12, 54 }); + } + + SECTION("data_moving_window") + { + // for "simplicity" assuming TF length of 11246 orbits, which gives us 1.0005 second TF duration + const auto nOrbitPerTF = 11246; + auto tk = std::make_shared(30 * 1000); + tk->setActivityDuration(ValidityInterval{ 1653000000000, 1653000095000 }); // 95 seconds: 0-30, 30-60, 60-95 + tk->setCCDBOrbitsPerTFAccessor([nOrbitPerTF]() { return nOrbitPerTF; }); + CHECK(!tk->shouldFinishCycle(TimingInfo{ 3, 0, 3 })); // no, because validity is invalid + + // hitting only the 1st window + tk->updateByTimeFrameID(1); + CHECK(!tk->shouldFinishCycle(TimingInfo{ 10, 0, 10 })); // no, because TFID 10 is within the 1st window + CHECK(!tk->shouldFinishCycle(TimingInfo{ 1653500000000000 })); // no, because this is a timer input + tk->updateByTimeFrameID(10); + CHECK(tk->getValidity() == ValidityInterval{ 1653000000000, 1653000030000 }); + CHECK(tk->getSampleTimespan() == ValidityInterval{ 1653000000000, 1653000009999 }); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 1, 10 }); + + // hitting the 1st and 2nd window + tk->reset(); + tk->updateByTimeFrameID(1); + CHECK(tk->shouldFinishCycle(TimingInfo{ 55, 0, 55 })); // yes, because TFID 55 is not within the 1st window + tk->updateByTimeFrameID(55); + CHECK(tk->getValidity() == ValidityInterval{ 1653000000000, 1653000060000 }); + CHECK(tk->getSampleTimespan() == ValidityInterval{ 1653000000000, 1653000055001 }); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 1, 55 }); + + // hitting the 3rd, extended window in the main part. + // there is no 4th window, since we merge the last two to avoid having the last one with too little statistics + tk->reset(); + tk->updateByTimeFrameID(80); + CHECK(tk->getValidity() == ValidityInterval{ 1653000060000, 1653000095000 }); + CHECK(tk->getSampleTimespan() == ValidityInterval{ 1653000079003, 1653000080002 }); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 80, 80 }); + + // hitting the 3rd window with a sample which is in the extended part. + tk->reset(); + tk->updateByTimeFrameID(93); + CHECK(tk->getValidity() == ValidityInterval{ 1653000060000, 1653000095000 }); + CHECK(tk->getSampleTimespan() == ValidityInterval{ 1653000092004, 1653000093003 }); + CHECK(tk->getTimerangeIdRange() == TimeframeIdRange{ 93, 93 }); + } + + SECTION("boundary_selection") + { + auto tk = std::make_shared(); + + o2::conf::ConfigurableParam::updateFromString("NameConf.mCCDBServer=http://ccdb-test.cern.ch:8080"); + + // CCDB RCT first + tk->setStartOfActivity(1, 2, 3, activity_helpers::getCcdbSorTimeAccessor(300000)); + tk->setEndOfActivity(4, 5, 6, activity_helpers::getCcdbEorTimeAccessor(300000)); + CHECK(tk->getActivityDuration().getMin() > 100); + CHECK(tk->getActivityDuration().getMax() > 100); + + // ECS second + tk->setStartOfActivity(1, 2, 3); + tk->setEndOfActivity(4, 5, 6); + CHECK(tk->getActivityDuration().getMin() == 1); + CHECK(tk->getActivityDuration().getMax() == 4); + + // config as the last resort + tk->setStartOfActivity(0, 2, 0); + tk->setEndOfActivity(0, 5, 0); + CHECK(tk->getActivityDuration().getMin() == 2); + CHECK(tk->getActivityDuration().getMax() == 5); + tk->setStartOfActivity(-1, 2, 0); + tk->setEndOfActivity(-1, 5, 0); + CHECK(tk->getActivityDuration().getMin() == 2); + CHECK(tk->getActivityDuration().getMax() == 5); + } +} \ No newline at end of file diff --git a/Framework/test/testTrendingTask.cxx b/Framework/test/testTrendingTask.cxx new file mode 100644 index 0000000000..e2b32216c3 --- /dev/null +++ b/Framework/test/testTrendingTask.cxx @@ -0,0 +1,294 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testTrendingTask.cxx +/// \author Piotr Konopka +/// + +#include "Framework/include/QualityControl/Reductor.h" +#include "QualityControl/TrendingTask.h" +#include "QualityControl/DatabaseFactory.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Triggers.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::repository; +using namespace o2::framework; + +const std::string CCDB_ENDPOINT = "ccdb-test.cern.ch:8080"; + +struct CleanupAtDestruction { + public: + CleanupAtDestruction(std::function callback) : mCallback(std::move(callback)) {} + ~CleanupAtDestruction() + { + if (mCallback) { + mCallback(); + } + } + + private: + std::function mCallback = nullptr; +}; + +// https://stackoverflow.com/questions/424104/can-i-access-private-members-from-outside-the-class-without-using-friends +template +struct DeclareGlobalGet { + friend typename Accessor::type get(Accessor) { return Member; } +}; + +struct TrendingTaskReductorAccessor { + using type = std::unordered_map> TrendingTask::*; + friend type get(TrendingTaskReductorAccessor); +}; + +struct ReductorConfigAccessor { + using type = CustomParameters Reductor::*; + friend type get(ReductorConfigAccessor); +}; + +template struct DeclareGlobalGet; +template struct DeclareGlobalGet; + +TEST_CASE("test_trending_task") +{ + const std::string pid = std::to_string(getpid()); + const std::string trendingTaskName = "TestTrendingTask"; + const std::string trendingTaskID = "TSTTrendingTask"; + const std::string taskName = "TrendingTaskTest" + pid; // this is the QC task we want to trend + const std::string checkName = "TrendingTaskTest" + pid; // this is the QC check we want to trend + const size_t trendTimes = 5; + + std::stringstream ss; + ss << R"json({ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080" + }, + "Activity": {}, + "monitoring": { + "url": "infologger:///debug?qc" + } + }, + "postprocessing": { + "TSTTrendingTask": { + "active": "true", + "taskName": "TestTrendingTask", + "className": "o2::quality_control::postprocessing::TrendingTask", + "trendIfAllInputs": true, + "moduleName": "QualityControl", + "detectorName": "TST", + "dataSources": [ + { + "type": "repository", + "path": "TST/MO/)json" + + taskName + R"json(", + "name": "testHistoTrending", + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "reductorParameters": { + "default": { + "default": { + "key":"value" + } + } + }, + "moduleName": "QcCommon" + }, + { + "type": "repository-quality", + "path": "TST/QO", + "names": [ ")json" + + checkName + R"json(" ], + "reductorName": "o2::quality_control_modules::common::QualityReductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_histogram", + "title": "Mean trend of the testHistoTrending histogram", + "graphs": [{ + "varexp": "testHistoTrending.mean:time", + "selection": "", + "option": "*L" + }] + }, + { + "name": "quality_histogram", + "title": "Histogram of qualities", + "varexp": ")json" + + checkName + R"json(.level", + "selection": "", + "option": "" + } + ], + "initTrigger": [], + "updateTrigger": [], + "stopTrigger": [] + } + } + } +})json"; + boost::property_tree::ptree config; + boost::property_tree::read_json(ss, config); + + // clean + std::shared_ptr repository = DatabaseFactory::create("CCDB"); + repository->connect(CCDB_ENDPOINT, "", "", ""); + repository->truncate("qc/TST/MO/" + taskName, "*"); + repository->truncate("qc/TST/QO", checkName); + + // Test "trendIfAllInputs". There should not be anything in DB so we don't have any input sources available + { + auto objectManager = std::make_shared(taskName, "o2::quality_control::postprocessing::TrendingTask", "TST", 0); + ServiceRegistry services; + services.registerService(repository.get()); + + TrendingTask task; + task.setName(trendingTaskName); + task.setID(trendingTaskID); + task.setObjectsManager(objectManager); + REQUIRE_NOTHROW(task.configure(config)); + + { + auto& reductors = task.*get(TrendingTaskReductorAccessor()); + size_t foundCount{}; + for (const auto& reductor : reductors) { + auto& config = (*reductor.second.get()).*get(ReductorConfigAccessor()); + if (auto found = config.find("key"); found != config.end()) { + if (found->second == "value") { + foundCount++; + } + } + } + REQUIRE(foundCount == 1); + } + + // test initialize() + REQUIRE_NOTHROW(task.initialize({ TriggerType::UserOrControl, true, { 0, "NONE", "", "", "qc" }, 1 }, services)); + REQUIRE(objectManager->getNumberPublishedObjects() == 1); + auto treeMO = objectManager->getMonitorObject(trendingTaskName); + REQUIRE(treeMO != nullptr); + TTree* tree = dynamic_cast(treeMO->getObject()); + REQUIRE(tree != nullptr); + REQUIRE(tree->GetEntries() == 0); + + // test update() + task.update({ TriggerType::NewObject, false, { 0, "NONE", "", "", "qc", { 2, 100000 } }, 100000 - 1 }, services); + objectManager->stopPublishing(PublicationPolicy::Once); + task.update({ TriggerType::NewObject, false, { 0, "NONE", "", "", "qc", { 100000, 200000 } }, 200000 - 1 }, services); + REQUIRE(objectManager->getNumberPublishedObjects() == 1); + REQUIRE(tree->GetEntries() == 0); + } + + // Putting the objects to trend into the database + { + TH1I* histo = new TH1I("testHistoTrending", "testHistoTrending", 10, 0, 10.0); + histo->Fill(4); + histo->Fill(5); + histo->Fill(6); + std::shared_ptr mo = std::make_shared(histo, taskName, "TestClass", "TST"); + mo->setValidity({ 2, 100000 }); + repository->storeMO(mo); + + histo->Fill(5); + mo->setValidity({ 100001, 200000 }); + repository->storeMO(mo); + + std::shared_ptr qo = std::make_shared(Quality::Null, checkName, "TST"); + qo->updateQuality(Quality::Bad); + qo->setValidity({ 2, 100000 }); + repository->storeQO(qo); + qo->updateQuality(Quality::Good); + qo->setValidity({ 100001, 200000 }); + repository->storeQO(qo); + } + + // from here on, we clean up the database if we exit + CleanupAtDestruction cleanTestObjects([repository, taskName, checkName]() { + if (repository) { + repository->truncate("qc/TST/MO/" + taskName, "*"); + repository->truncate("qc/TST/QO", checkName); + } + }); + + // Running the task + ServiceRegistry services; + services.registerService(repository.get()); + auto objectManager = std::make_shared(taskName, "o2::quality_control::postprocessing::TrendingTask", "TST"); + + TrendingTask task; + task.setName(trendingTaskName); + task.setID(trendingTaskID); + task.setObjectsManager(objectManager); + REQUIRE_NOTHROW(task.configure(config)); + + // test initialize() + REQUIRE_NOTHROW(task.initialize({ TriggerType::UserOrControl, true, { 0, "NONE", "", "", "qc" }, 1 }, services)); + REQUIRE(objectManager->getNumberPublishedObjects() == 1); + auto treeMO = objectManager->getMonitorObject(trendingTaskName); + REQUIRE(treeMO != nullptr); + TTree* tree = dynamic_cast(treeMO->getObject()); + REQUIRE(tree != nullptr); + REQUIRE(tree->GetEntries() == 0); + + // test update() + task.update({ TriggerType::NewObject, false, { 0, "NONE", "", "", "qc", { 2, 100000 } }, 100000 - 1 }, services); + objectManager->stopPublishing(PublicationPolicy::Once); + task.update({ TriggerType::NewObject, false, { 0, "NONE", "", "", "qc", { 100000, 200000 } }, 200000 - 1 }, services); + REQUIRE(objectManager->getNumberPublishedObjects() == 3); + REQUIRE(tree->GetEntries() == 2); + auto varexp = "testHistoTrending.mean:testHistoTrending.entries:" + checkName + ".level"; + tree->Draw(varexp.c_str(), "", "goff"); + Double_t* means = tree->GetVal(0); + Double_t* entries = tree->GetVal(1); + Double_t* qualityLevels = tree->GetVal(2); + CHECK_THAT(means[0], Catch::Matchers::WithinAbs(5, 0.01)); + CHECK_THAT(entries[0], Catch::Matchers::WithinAbs(3, 0.01)); + CHECK_THAT(qualityLevels[0], Catch::Matchers::WithinAbs(3, 0.01)); + CHECK_THAT(means[1], Catch::Matchers::WithinAbs(5, 0.01)); + CHECK_THAT(entries[1], Catch::Matchers::WithinAbs(4, 0.01)); + CHECK_THAT(qualityLevels[1], Catch::Matchers::WithinAbs(1, 0.01)); + + auto histo1MO = objectManager->getMonitorObject("mean_of_histogram"); + REQUIRE(histo1MO != nullptr); + auto histo1 = dynamic_cast(histo1MO->getObject()); + REQUIRE(histo1 != nullptr); + CHECK(std::strcmp(histo1->GetName(), "mean_of_histogram") == 0); + auto histo2MO = objectManager->getMonitorObject("quality_histogram"); + REQUIRE(histo2MO != nullptr); + auto histo2 = dynamic_cast(histo2MO->getObject()); + REQUIRE(histo2 != nullptr); + CHECK(std::strcmp(histo2->GetName(), "quality_histogram") == 0); + objectManager->stopPublishing(PublicationPolicy::Once); + + // test finalize() + REQUIRE_NOTHROW(task.finalize({ TriggerType::UserOrControl, true }, services)); + REQUIRE(objectManager->getNumberPublishedObjects() == 3); + REQUIRE(tree->GetEntries() == 2); + objectManager->stopPublishing(PublicationPolicy::Once); + objectManager->stopPublishing(PublicationPolicy::ThroughStop); +} diff --git a/Framework/test/testTriggerHelpers.cxx b/Framework/test/testTriggerHelpers.cxx new file mode 100644 index 0000000000..67f3c7a906 --- /dev/null +++ b/Framework/test/testTriggerHelpers.cxx @@ -0,0 +1,152 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testTriggerHelpers.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/TriggerHelpers.h" +#include "QualityControl/PostProcessingConfig.h" +#include + +using namespace o2::quality_control::postprocessing; +const std::string CCDB_ENDPOINT = "ccdb-test.cern.ch:8080"; + +TEST_CASE("test_factory") +{ + PostProcessingConfig dummyConfig; + // check if it ignores letter case + CHECK_NOTHROW(trigger_helpers::triggerFactory("once", dummyConfig)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("Once", dummyConfig)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("ONCE", dummyConfig)); + + // check if it creates correct triggers + auto once = trigger_helpers::triggerFactory("once", dummyConfig); + CHECK(once() == TriggerType::Once); + CHECK(once() == TriggerType::No); + CHECK(once() == TriggerType::No); + CHECK(once() == TriggerType::No); + CHECK(once() == TriggerType::No); + auto always = trigger_helpers::triggerFactory("always", dummyConfig); + CHECK(always() == TriggerType::Always); + CHECK(always() == TriggerType::Always); + CHECK(always() == TriggerType::Always); + CHECK(always() == TriggerType::Always); + CHECK(always() == TriggerType::Always); + + // unknown trigger + CHECK_THROWS_AS(trigger_helpers::triggerFactory("adsfzxcvadsf", dummyConfig), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("", dummyConfig), std::invalid_argument); + + // generating periodic trigger + CHECK_NOTHROW(trigger_helpers::triggerFactory("1sec", dummyConfig)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("1.23sec", dummyConfig)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("123 seconds", dummyConfig)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("2min", dummyConfig)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("2mins", dummyConfig)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("2mins", dummyConfig)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("2minutes", dummyConfig)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("3hour", dummyConfig)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("3hours", dummyConfig)); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("-1sec", dummyConfig), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("sec", dummyConfig), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("asec", dummyConfig), std::invalid_argument); + + // generating new object trigger + PostProcessingConfig configWithDBs; + configWithDBs.repository["host"] = CCDB_ENDPOINT; + configWithDBs.ccdbUrl = CCDB_ENDPOINT; + CHECK_NOTHROW(trigger_helpers::triggerFactory("newobject:qcdb:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("newobject:ccdb:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("newobject:QCDB:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("newobject:CCDB:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("NewObject:QcDb:qc/asdf/vcxz", configWithDBs)); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("newobject", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("newobject:", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("newobject::", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("newobject::qc/no/db/specified", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("newobject:nodb:qc/incorrect/db/speficied", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("newobject:ccdb:qc/too:many tokens", configWithDBs), std::invalid_argument); + + CHECK_NOTHROW(trigger_helpers::triggerFactory("foreachobject:qcdb:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("foreachobject:ccdb:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("foreachobject:QCDB:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("foreachobject:CCDB:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("ForEachObject:QcDb:qc/asdf/vcxz", configWithDBs)); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachobject", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachobject:", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachobject::", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachobject::qc/no/db/specified", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachobject:nodb:qc/incorrect/db/speficied", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachobject:ccdb:qc/too:many tokens", configWithDBs), std::invalid_argument); + + CHECK_NOTHROW(trigger_helpers::triggerFactory("foreachlatest:qcdb:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("foreachlatest:ccdb:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("foreachlatest:QCDB:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("foreachlatest:CCDB:qc/asdf/vcxz", configWithDBs)); + CHECK_NOTHROW(trigger_helpers::triggerFactory("ForEachLatest:QcDb:qc/asdf/vcxz", configWithDBs)); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachlatest", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachlatest:", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachlatest::", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachlatest::qc/no/db/specified", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachlatest:nodb:qc/incorrect/db/speficied", configWithDBs), std::invalid_argument); + CHECK_THROWS_AS(trigger_helpers::triggerFactory("foreachlatest:ccdb:qc/too:many tokens", configWithDBs), std::invalid_argument); + + // check if config string is propagated + CHECK(trigger_helpers::triggerFactory("10sec", dummyConfig)().config == "10sec"); + CHECK(trigger_helpers::triggerFactory("newobject:qcdb:qc/asdf/vcxz", configWithDBs)().config == "newobject:qcdb:qc/asdf/vcxz"); + CHECK(trigger_helpers::triggerFactory("foreachobject:qcdb:qc/asdf/vcxz", configWithDBs)().config == "foreachobject:qcdb:qc/asdf/vcxz"); + CHECK(trigger_helpers::triggerFactory("foreachlatest:qcdb:qc/asdf/vcxz", configWithDBs)().config == "foreachlatest:qcdb:qc/asdf/vcxz"); + + // fixme: this is treated as "123 seconds", do we want to be so defensive? + CHECK_NOTHROW(trigger_helpers::triggerFactory("123 secure code", dummyConfig)); +} + +TEST_CASE("test_create_trigger") +{ + PostProcessingConfig dummyConfig; + + CHECK(trigger_helpers::createTriggers({}, dummyConfig).size() == 0); + CHECK(trigger_helpers::createTriggers({ "once", "always" }, dummyConfig).size() == 2); +} + +TEST_CASE("test_try_triggers") +{ + PostProcessingConfig dummyConfig; + { + auto triggers = trigger_helpers::createTriggers({}, dummyConfig); + CHECK(!trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + } + + { + auto triggers = trigger_helpers::createTriggers({ "once" }, dummyConfig); + CHECK(trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + } + + { + auto triggers = trigger_helpers::createTriggers({ "once", "once", "once" }, dummyConfig); + CHECK(trigger_helpers::tryTrigger(triggers)); + CHECK(trigger_helpers::tryTrigger(triggers)); + CHECK(trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + CHECK(!trigger_helpers::tryTrigger(triggers)); + } +} diff --git a/Framework/test/testTriggers.cxx b/Framework/test/testTriggers.cxx new file mode 100644 index 0000000000..91e5410b88 --- /dev/null +++ b/Framework/test/testTriggers.cxx @@ -0,0 +1,294 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testTriggers.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/Triggers.h" + +#define BOOST_TEST_MODULE Triggers test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include "QualityControl/MonitorObject.h" +#include "QualityControl/DatabaseFactory.h" +#include "QualityControl/CcdbDatabase.h" +#include "QualityControl/RepoPathUtils.h" + +#include +#include +#include +#include +#include +using namespace std::chrono; + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; + +const std::string CCDB_ENDPOINT = "ccdb-test.cern.ch:8080"; + +BOOST_AUTO_TEST_CASE(test_casting_triggers) +{ + auto once = triggers::Once(); + + // confirm that enum values works + BOOST_CHECK_EQUAL(once(), TriggerType::Once); + BOOST_CHECK_EQUAL(once(), TriggerType::No); + + // confirm that casting to booleans works + BOOST_CHECK_EQUAL(once(), false); + BOOST_CHECK(!once()); + once = triggers::Once(); + BOOST_CHECK_EQUAL(once(), true); + once = triggers::Once(); + BOOST_CHECK(once()); +} + +BOOST_AUTO_TEST_CASE(test_timestamps_triggers) +{ + Trigger t1(TriggerType::Once, false, 123); + BOOST_CHECK_EQUAL(t1.triggerType, TriggerType::Once); + BOOST_CHECK_EQUAL(t1.timestamp, 123); + + auto nowMs = duration_cast(system_clock::now().time_since_epoch()).count(); + Trigger t2(TriggerType::Once); + BOOST_CHECK_EQUAL(t2.triggerType, TriggerType::Once); + BOOST_CHECK(std::abs(static_cast(t2.timestamp - nowMs)) < 100000); // 100 seconds of max. difference should be fine. +} + +BOOST_AUTO_TEST_CASE(test_trigger_once) +{ + auto once = triggers::Once(); + + BOOST_CHECK_EQUAL(once(), TriggerType::Once); + BOOST_CHECK_EQUAL(once(), TriggerType::No); + BOOST_CHECK_EQUAL(once(), TriggerType::No); + BOOST_CHECK_EQUAL(once(), TriggerType::No); + BOOST_CHECK_EQUAL(once(), TriggerType::No); +} + +BOOST_AUTO_TEST_CASE(test_trigger_new_object) +{ + // Setup and initialise objects + const std::string pid = std::to_string(getpid()); + const std::string detectorCode = "TST"; + const std::string taskName = "testTriggersNewObject"; + const std::string objectName = "test_object" + pid; + + TH1I* obj = new TH1I(objectName.c_str(), objectName.c_str(), 10, 0, 10.0); + obj->Fill(4); + std::shared_ptr mo = std::make_shared(obj, taskName, "TestClass", detectorCode); + + const std::string fullObjectPath = RepoPathUtils::getMoPath(mo.get(), true); + const std::string objectPath = RepoPathUtils::getMoPath(mo.get(), false); + auto newObjectTrigger = triggers::NewObject(CCDB_ENDPOINT, "qcdb", objectPath); + + // Clean up existing objects + auto directDBAPI = std::make_shared(); + directDBAPI->init(CCDB_ENDPOINT); + BOOST_REQUIRE(directDBAPI->isHostReachable()); + directDBAPI->truncate(fullObjectPath); + + // Check before update - no objects expected + BOOST_CHECK_EQUAL(newObjectTrigger(), TriggerType::No); + BOOST_CHECK_EQUAL(newObjectTrigger(), TriggerType::No); + BOOST_CHECK_EQUAL(newObjectTrigger(), TriggerType::No); + + // Send the object + std::shared_ptr repository = DatabaseFactory::create("CCDB"); + repository->connect(CCDB_ENDPOINT, "", "", ""); + validity_time_t currentTimestamp = CcdbDatabase::getCurrentTimestamp(); + mo->setValidity({ currentTimestamp, gInvalidValidityInterval.getMax() }); + repository->storeMO(mo); + + // Check after sending + BOOST_CHECK_EQUAL(newObjectTrigger(), Trigger(TriggerType::NewObject, currentTimestamp)); + BOOST_CHECK_EQUAL(newObjectTrigger(), TriggerType::No); + BOOST_CHECK_EQUAL(newObjectTrigger(), TriggerType::No); + + // Update the object + obj->Fill(10); + currentTimestamp = CcdbDatabase::getCurrentTimestamp(); + mo->setValidity({ currentTimestamp, gInvalidValidityInterval.getMax() }); + repository->storeMO(mo); + + // Check after the update + BOOST_CHECK_EQUAL(newObjectTrigger(), Trigger(TriggerType::NewObject, currentTimestamp)); + BOOST_CHECK_EQUAL(newObjectTrigger(), TriggerType::No); + BOOST_CHECK_EQUAL(newObjectTrigger(), TriggerType::No); + + // Clean up remaining objects + directDBAPI->truncate(fullObjectPath); +} + +BOOST_AUTO_TEST_CASE(test_trigger_for_each_object) +{ + // Setup and initialise objects + const std::string pid = std::to_string(getpid()); + const std::string detectorCode = "TST"; + const std::string taskName = "testTriggersForEachObject"; + const std::string objectName = "test_object" + pid; + + TH1I* obj = new TH1I(objectName.c_str(), objectName.c_str(), 10, 0, 10.0); + obj->Fill(4); + std::shared_ptr mo = std::make_shared(obj, taskName, "TestClass", detectorCode); + const std::string fullObjectPath = RepoPathUtils::getMoPath(mo.get(), true); + const std::string objectPath = RepoPathUtils::getMoPath(mo.get(), false); + + // Clean up existing objects + auto directDBAPI = std::make_shared(); + directDBAPI->init(CCDB_ENDPOINT); + BOOST_REQUIRE(directDBAPI->isHostReachable()); + directDBAPI->truncate(fullObjectPath); + + // Send three objects with different metadata + std::shared_ptr repository = DatabaseFactory::create("CCDB"); + repository->connect(CCDB_ENDPOINT, "", "", ""); + validity_time_t currentTimestamp = CcdbDatabase::getCurrentTimestamp(); + mo->setActivity({ 100, "TECHNICAL", "FCC42x", "tpass1", "qc", { currentTimestamp, gInvalidValidityInterval.getMax() } }); + repository->storeMO(mo); + mo->setActivity({ 101, "TECHNICAL", "FCC42x", "tpass1", "qc", { currentTimestamp + 1000, gInvalidValidityInterval.getMax() } }); + repository->storeMO(mo); + mo->setActivity({ 100, "TECHNICAL", "FCC42x", "tpass2", "qc", { currentTimestamp + 2000, gInvalidValidityInterval.getMax() } }); + mo->addOrUpdateMetadata(metadata_keys::cycleNumber, "42"); + repository->storeMO(mo); + + { + const Activity activityAllRunsPass1{ 0, "TECHNICAL", "FCC42x", "tpass1", "qc" }; + auto forEachObjectTrigger = triggers::ForEachObject(CCDB_ENDPOINT, "qcdb", objectPath, activityAllRunsPass1); + + auto trigger = forEachObjectTrigger(); + BOOST_CHECK_EQUAL(trigger, TriggerType::ForEachObject); + BOOST_CHECK_EQUAL(trigger.metadata.size(), 0); + + trigger = forEachObjectTrigger(); + BOOST_CHECK_EQUAL(trigger, TriggerType::ForEachObject); + BOOST_CHECK_EQUAL(trigger.metadata.size(), 0); + + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::No); + } + + { + const Activity activityRun100AllPasses{ 100, "TECHNICAL", "FCC42x", "", "qc" }; + auto forEachObjectTrigger = triggers::ForEachObject(CCDB_ENDPOINT, "qcdb", objectPath, activityRun100AllPasses); + + auto trigger = forEachObjectTrigger(); + BOOST_CHECK_EQUAL(trigger, TriggerType::ForEachObject); + BOOST_CHECK_EQUAL(trigger.metadata.size(), 0); + + trigger = forEachObjectTrigger(); + BOOST_CHECK_EQUAL(trigger, TriggerType::ForEachObject); + BOOST_CHECK_EQUAL(trigger.metadata.size(), 1); + BOOST_CHECK_EQUAL(trigger.metadata.at(metadata_keys::cycleNumber), "42"); + + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::No); + } + + { + const Activity activityAll{ 0, "NONE", "", "", "qc" }; + auto forEachObjectTrigger = triggers::ForEachObject(CCDB_ENDPOINT, "qcdb", objectPath, activityAll); + + auto trigger = forEachObjectTrigger(); + BOOST_CHECK_EQUAL(trigger, TriggerType::ForEachObject); + BOOST_CHECK_EQUAL(trigger.metadata.size(), 0); + + trigger = forEachObjectTrigger(); + BOOST_CHECK_EQUAL(trigger, TriggerType::ForEachObject); + BOOST_CHECK_EQUAL(trigger.metadata.size(), 0); + + trigger = forEachObjectTrigger(); + BOOST_CHECK_EQUAL(trigger, TriggerType::ForEachObject); + BOOST_CHECK_EQUAL(trigger.metadata.size(), 1); + BOOST_CHECK_EQUAL(trigger.metadata.at(metadata_keys::cycleNumber), "42"); + + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::No); + } + + { + const Activity activityTimeRestriction{ 0, "NONE", "", "", "qc", { static_cast(currentTimestamp), static_cast(currentTimestamp + 5) } }; + auto forEachObjectTrigger = triggers::ForEachObject(CCDB_ENDPOINT, "qcdb", objectPath, activityTimeRestriction); + + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::ForEachObject); + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::No); + } + // Clean up remaining objects + directDBAPI->truncate(fullObjectPath); +} + +BOOST_AUTO_TEST_CASE(test_trigger_for_each_latest) +{ + // Setup and initialise objects + const std::string pid = std::to_string(getpid()); + const std::string detectorCode = "TST"; + const std::string taskName = "testTriggersForEachLatest"; + const std::string objectName = "test_object" + pid; + + TH1I* obj = new TH1I(objectName.c_str(), objectName.c_str(), 10, 0, 10.0); + obj->Fill(4); + std::shared_ptr mo = std::make_shared(obj, taskName, "TestClass", detectorCode); + const std::string fullObjectPath = RepoPathUtils::getMoPath(mo.get(), true); + const std::string objectPath = RepoPathUtils::getMoPath(mo.get(), false); + + // Clean up existing objects + auto directDBAPI = std::make_shared(); + directDBAPI->init(CCDB_ENDPOINT); + BOOST_REQUIRE(directDBAPI->isHostReachable()); + directDBAPI->truncate(fullObjectPath); + + // Send three objects with different metadata + std::shared_ptr repository = DatabaseFactory::create("CCDB"); + repository->connect(CCDB_ENDPOINT, "", "", ""); + mo->setActivity({ 100, "TECHNICAL", "FCC42x", "tpass1", "qc", gInvalidValidityInterval }); + repository->storeMO(mo); + usleep(1000); + repository->storeMO(mo); + mo->setActivity({ 101, "TECHNICAL", "FCC42x", "tpass1", "qc", gInvalidValidityInterval }); + repository->storeMO(mo); + usleep(1000); + repository->storeMO(mo); + mo->setActivity({ 100, "TECHNICAL", "FCC42x", "tpass2", "qc", gInvalidValidityInterval }); + repository->storeMO(mo); + + { + const Activity activityAllRunsPass1{ 0, "TECHNICAL", "FCC42x", "tpass1", "qc" }; + auto forEachObjectTrigger = triggers::ForEachLatest(CCDB_ENDPOINT, "qcdb", objectPath, activityAllRunsPass1); + + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::ForEachLatest); + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::ForEachLatest); + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::No); + } + + { + const Activity activityRun100AllPasses{ 100, "TECHNICAL", "FCC42x", "", "qc" }; + auto forEachObjectTrigger = triggers::ForEachLatest(CCDB_ENDPOINT, "qcdb", objectPath, activityRun100AllPasses); + + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::ForEachLatest); + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::ForEachLatest); + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::No); + } + + { + const Activity activityAll{ 0, "NONE", "", "", "qc" }; + auto forEachObjectTrigger = triggers::ForEachLatest(CCDB_ENDPOINT, "qcdb", objectPath, activityAll); + + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::ForEachLatest); + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::ForEachLatest); + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::ForEachLatest); + BOOST_CHECK_EQUAL(forEachObjectTrigger(), TriggerType::No); + } + + // Clean up remaining objects + directDBAPI->truncate(fullObjectPath); +} diff --git a/Framework/test/testUserCodeInterface.cxx b/Framework/test/testUserCodeInterface.cxx new file mode 100644 index 0000000000..d7f287cefe --- /dev/null +++ b/Framework/test/testUserCodeInterface.cxx @@ -0,0 +1,100 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCommonInterface.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/UserCodeInterface.h" + +#define BOOST_TEST_MODULE CommonInterface test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control::repository; +using namespace o2::quality_control::core; + +namespace o2::quality_control +{ + +namespace test +{ + +class TestInterface : public core::UserCodeInterface +{ + public: + /// Default constructor + TestInterface() = default; + /// Destructor + ~TestInterface() override = default; + + // Override interface + void configure() override + { + configured = true; + } + + string get(std::string key) + { + return mCustomParameters.at(key); + } + + bool configured = false; +}; + +struct MyGlobalFixture { + void teardown() + { + auto backend = std::make_unique(); + backend->connect("ccdb-test.cern.ch:8080", "", "", ""); + backend->truncate("qc/TST/MO/Test/pid" + std::to_string(getpid()), "*"); + } +}; +BOOST_TEST_GLOBAL_FIXTURE(MyGlobalFixture); + +BOOST_AUTO_TEST_CASE(test_invoke_all_methods) +{ + test::TestInterface testInterface; + + BOOST_CHECK_EQUAL(testInterface.configured, false); + + TH1F* h1 = new TH1F("asdf", "asdf", 100, 0, 99); + auto pid = std::to_string(getpid()); + auto taskName = "Test/pid" + pid; + shared_ptr mo1 = make_shared(h1, taskName, "task", "TST"); + auto backend = std::make_unique(); + backend->connect("ccdb-test.cern.ch:8080", "", "", ""); + backend->storeMO(mo1); + + // setting custom parameters should configure + CustomParameters customParameters; + customParameters["test"] = "asdf"; + testInterface.setCustomParameters(customParameters); + BOOST_CHECK_EQUAL(testInterface.configured, true); + BOOST_CHECK_EQUAL(testInterface.get("test"), "asdf"); + + testInterface.setCcdbUrl("ccdb-test.cern.ch:8080"); + auto obj = testInterface.retrieveConditionAny("qc/TST/MO/" + taskName + "/asdf"); + BOOST_CHECK_NE(obj, nullptr); +} +} /* namespace test */ +} /* namespace o2::quality_control */ \ No newline at end of file diff --git a/Framework/test/testUserInputOutput.cxx b/Framework/test/testUserInputOutput.cxx new file mode 100644 index 0000000000..73f0568c60 --- /dev/null +++ b/Framework/test/testUserInputOutput.cxx @@ -0,0 +1,65 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testUserInputOutput.cxx +/// \author Piotr Konopka +/// + +#include + +#include +#include + +#include "QualityControl/UserInputOutput.h" + +using namespace o2::header; + +namespace o2::quality_control::core +{ + +TEST_CASE("ConcreteDataMatcher") +{ + auto dataMatcher = createUserDataMatcher(DataSourceType::Task, "TST", "mytask", 7); + CHECK(dataMatcher.origin == DataOrigin{ "QTST" }); + CHECK(dataMatcher.description == DataDescription{ "mytask" }); + CHECK(dataMatcher.subSpec == 7); +} + +TEST_CASE("InputSpec") +{ + { + auto inputSpec = createUserInputSpec(DataSourceType::Task, "TST", "mytask", 3); + CHECK(inputSpec.binding == "mytask"); + CHECK(inputSpec.lifetime == framework::Lifetime::Sporadic); + CHECK(framework::DataSpecUtils::match(inputSpec, framework::ConcreteDataMatcher{ DataOrigin{ "QTST" }, DataDescription{ "mytask" }, 3 })); + } + { + auto inputSpec = createUserInputSpec(DataSourceType::Task, "TST", "mytask", 3, "custom_binding"); + CHECK(inputSpec.binding == "custom_binding"); + } +} + +TEST_CASE("OutputSpec") +{ + { + auto outputSpec = createUserOutputSpec(DataSourceType::Task, "TST", "mytask", 5); + CHECK(outputSpec.binding.value == "mytask"); + CHECK(outputSpec.lifetime == framework::Lifetime::Sporadic); + CHECK(framework::DataSpecUtils::match(outputSpec, framework::ConcreteDataMatcher{ DataOrigin{ "QTST" }, DataDescription{ "mytask" }, 5 })); + } + { + auto outputSpec = createUserOutputSpec(DataSourceType::Task, "TST", "mytask", 5, { "custom_binding" }); + CHECK(outputSpec.binding.value == "custom_binding"); + } +} + +} // namespace o2::quality_control::core diff --git a/Framework/test/testVersion.cxx b/Framework/test/testVersion.cxx new file mode 100644 index 0000000000..8ae528068f --- /dev/null +++ b/Framework/test/testVersion.cxx @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testQuality.cxx +/// \author Barthelemy von Haller +/// + +#include "QualityControl/Version.h" +#include + +using namespace std; + +namespace o2::quality_control::core +{ + +TEST_CASE("test_int_repr") +{ + Version v1("0.19.2"); + Version v2("1.19.2"); + Version v3("2.0.0"); + CHECK(v1.getIntegerRepresentation() == (19002)); + CHECK(v2.getIntegerRepresentation() == 1019002); + CHECK(v3.getIntegerRepresentation() == 2000000); +} + +TEST_CASE("test_version") +{ + CHECK((Version("3.7.8.0") == Version("3.7.8.0")) == true); + CHECK((Version("3.7.8.0") == Version("3.7.8")) == true); + CHECK((Version("3.7.8.0") < Version("3.7.8")) == false); + CHECK((Version("3.7.9") < Version("3.7.8")) == false); + CHECK((Version("3") < Version("3.7.9")) == true); + CHECK((Version("1.7.9") < Version("3.1")) == true); + CHECK((Version("") == Version("0.0.0")) == true); + CHECK((Version("0") == Version("0.0.0")) == true); + CHECK((Version("") != Version("0.0.1")) == true); + CHECK((Version("2.0.0") < Version("1.19.0")) == false); + + Version v("2.0.0"); + CHECK(v == Version("2.0.0")); + Version qc = Version::GetQcVersion(); + CHECK((qc.getMajor() != 0 || qc.getMinor() != 0 || qc.getPatch() != 0)); + cout << qc << endl; + + Version v2("3.2.1"); + CHECK(v2.getMajor() == 3); + CHECK(v2.getMinor() == 2); + CHECK(v2.getPatch() == 1); + + CHECK(v < Version("2.1.0")); + CHECK(v < Version("2.1")); + CHECK(v < Version("20")); + CHECK(v >= Version("1.19")); + CHECK(v >= Version("1")); + CHECK(v >= Version("1.8.1")); + CHECK(v >= Version("2.0.0")); + CHECK(v >= Version("2.0")); + CHECK(v > Version("1.19")); + CHECK(v > Version("1")); + CHECK(v > Version("1.8.1")); + CHECK(!(v > Version("2.0.0"))); +} + +TEST_CASE("test_output") +{ + Version v("1.2.3"); + std::stringstream output; + output << v; + + CHECK(output.str() == "1.2.3"); + + CHECK(v.getString() == "1.2.3"); +} + +} // namespace o2::quality_control::core diff --git a/Framework/test/testWorkflow.cxx b/Framework/test/testWorkflow.cxx new file mode 100644 index 0000000000..3bf361b9d4 --- /dev/null +++ b/Framework/test/testWorkflow.cxx @@ -0,0 +1,103 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testWorkflow.cxx +/// \author Piotr Konopka +/// + +#include +#include "QualityControl/InfrastructureGenerator.h" +#include "QualityControl/UserInputOutput.h" + +using namespace o2; +using namespace o2::framework; +using namespace o2::utilities; +using namespace o2::quality_control::core; + +void customize(std::vector& policies) +{ + DataSampling::CustomizeInfrastructure(policies); + quality_control::customizeInfrastructure(policies); +} + +#include "getTestDataDirectory.h" +#include "QualityControl/CheckRunner.h" +#include "QualityControl/runnerUtils.h" +#include +#include +#include +#include +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control::checker; +using namespace o2::configuration; + +WorkflowSpec defineDataProcessing(ConfigContext const&) +{ + WorkflowSpec specs; + + // The producer to generate some data in the workflow + DataProcessorSpec producer{ + "producer", + Inputs{}, + Outputs{ + { { "tst-data" }, "TST", "DATA" } }, + AlgorithmSpec{ + [](ProcessingContext& pctx) { + usleep(100000); + pctx.outputs().make(OutputRef{ "tst-data" }, 1); + } } + }; + specs.push_back(producer); + + const std::string qcConfigurationSource = std::string("json://") + getTestDataDirectory() + "testWorkflow.json"; + + ILOG(Info) << "Using config file '" << qcConfigurationSource << "'" << ENDM; + + // Generation of Data Sampling infrastructure + auto configInterface = ConfigurationFactory::getConfiguration(qcConfigurationSource); + auto dataSamplingTree = configInterface->getRecursive("dataSamplingPolicies"); + DataSampling::GenerateInfrastructure(specs, dataSamplingTree); + + // Generation of the QC topology (one task, one checker in this case) + quality_control::generateStandaloneInfrastructure(specs, configInterface->getRecursive()); + + const auto checkName = getFirstCheckName(qcConfigurationSource); + + // Finally the receiver + DataProcessorSpec receiver{ + "receiver", + Inputs{ + createUserInputSpec(DataSourceType::Check, "TST", checkName) }, + Outputs{}, + AlgorithmSpec{ + [checkName](ProcessingContext& pctx) { + // If any message reaches this point, the QC workflow should work at least on a basic level. + + auto qo = pctx.inputs().get(checkName); + if (!qo) { + ILOG(Error, Devel) << "Quality Object is a NULL" << ENDM; + pctx.services().get().readyToQuit(QuitRequest::All); + return; + } + + ILOG(Info) << qo->getName() << " - quality: " << qo->getQuality(); + + // We ask to shut the topology down, returning 0 if there were no ERROR logs. + pctx.services().get().readyToQuit(QuitRequest::All); + } } + }; + specs.push_back(receiver); + + return specs; +} diff --git a/Framework/test/testWorkflow.json b/Framework/test/testWorkflow.json new file mode 100644 index 0000000000..f02db53386 --- /dev/null +++ b/Framework/test/testWorkflow.json @@ -0,0 +1,87 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "skeletonTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "cycleDurationSeconds": "5", + "detectorName": "TST", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "test-policy" + }, + "extendedTaskParameters": { + "default": { + "default": { + "myOwnKey": "myOwnValue", + "myOwnKey2": "myOwnValue2", + "myOwnKey3": [ + { + "name": "mean_of_histogram", + "title": "Mean trend of the example histogram", + "graphAxisLabel": "Mean X:time", + "graphYRange": "0:10000", + "graphs" : [ + { + "name": "mean_trend", + "title": "mean trend", + "varexp": "example.mean:time", + "selection": "", + "option": "*L PLC PMC" + } + ] + } + ] + } + }, + "PHYSICS": { + "default": { + "myOwnKey1": "myOwnValue1b", + "myOwnKey2": "myOwnValue2b" + } + } + }, + "location": "remote", + "localMachines": [] + } + }, + "checks": { + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "skeletonTask", + "MOs": ["example"] + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "test-policy", + "active": "true", + "machines": [], + "query": "data:TST/DATA/0", + "samplingConditions": [], + "blocking": "false" + } + ] +} diff --git a/Modules/Benchmark/CMakeLists.txt b/Modules/Benchmark/CMakeLists.txt new file mode 100644 index 0000000000..aaf0f8d593 --- /dev/null +++ b/Modules/Benchmark/CMakeLists.txt @@ -0,0 +1,48 @@ +# ---- Library ---- + +add_library(O2QcBenchmark) + +target_sources(O2QcBenchmark PRIVATE src/EmptyPPTask.cxx src/AlwaysGoodCheck.cxx src/TH1FTask.cxx) + +target_include_directories( + O2QcBenchmark + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcBenchmark PUBLIC O2QualityControl) + +install(TARGETS O2QcBenchmark + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcBenchmark + HEADERS + include/Benchmark/EmptyPPTask.h + include/Benchmark/AlwaysGoodCheck.h + include/Benchmark/TH1FTask.h + LINKDEF include/Benchmark/LinkDef.h) + +install(DIRECTORY etc DESTINATION Modules/Benchmark) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/Benchmark + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcBenchmark.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcBenchmark Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + diff --git a/Modules/Benchmark/etc/benchmarkCheckTemplate.json b/Modules/Benchmark/etc/benchmarkCheckTemplate.json new file mode 100644 index 0000000000..d3eb227d71 --- /dev/null +++ b/Modules/Benchmark/etc/benchmarkCheckTemplate.json @@ -0,0 +1,49 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "Dummy", + "host" : "not_applicable", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + } + }, + "tasks" : { + "BenchmarkTask" : { + "active" : "true", + "className" : "o2::quality_control_modules::benchmark::TH1FTask", + "moduleName" : "QcBenchmark", + "detectorName" : "TST", + "cycleDurationSeconds" : "__CYCLE_SECONDS__", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "direct", + "query" : "tst-data:TST/RAWDATA" + }, + "taskParameters" : { + "histoNumber" : "__NUMBER_OF_HISTOGRAMS__", + "binsNumber" : "__NUMBER_OF_BINS__" + }, + "location" : "remote" + } + }, + "checks": { + __CHECKS__ + } + }, + "dataSamplingPolicies" : [] +} diff --git a/Modules/Benchmark/etc/benchmarkTaskTemplate.json b/Modules/Benchmark/etc/benchmarkTaskTemplate.json new file mode 100644 index 0000000000..2448afe903 --- /dev/null +++ b/Modules/Benchmark/etc/benchmarkTaskTemplate.json @@ -0,0 +1,59 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "Dummy", + "host" : "not_applicable", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + } + }, + "tasks" : { + "BenchmarkTask" : { + "active" : "true", + "className" : "o2::quality_control_modules::benchmark::TH1FTask", + "moduleName" : "QcBenchmark", + "detectorName" : "TST", + "cycleDurationSeconds" : "__CYCLE_SECONDS__", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "direct", + "query" : "tst-data:TST/RAWDATA" + }, + "taskParameters" : { + "histoNumber" : "__NUMBER_OF_HISTOGRAMS__", + "binsNumber" : "__NUMBER_OF_BINS__" + }, + "location" : "remote" + } + }, + "checks": { + "AlwaysGoodCheck": { + "active": "true", + "className": "o2::quality_control_modules::benchmark::AlwaysGoodCheck", + "moduleName": "QcBenchmark", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "BenchmarkTask" + }] + } + } + }, + "dataSamplingPolicies" : [] +} diff --git a/Modules/Benchmark/include/Benchmark/AlwaysGoodCheck.h b/Modules/Benchmark/include/Benchmark/AlwaysGoodCheck.h new file mode 100644 index 0000000000..82b061533c --- /dev/null +++ b/Modules/Benchmark/include/Benchmark/AlwaysGoodCheck.h @@ -0,0 +1,45 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AlwaysGoodCheck.h +/// \author Piotr Konopka +/// + +#ifndef QC_MODULE_BENCHMARK_BENCHMARKCHECK_H +#define QC_MODULE_BENCHMARK_BENCHMARKCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::benchmark +{ + +/// \brief Give always a good quality +/// +/// \author Piotr Konopka +class AlwaysGoodCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + AlwaysGoodCheck() = default; + /// Destructor + ~AlwaysGoodCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(AlwaysGoodCheck, 2); +}; + +} // namespace o2::quality_control_modules::benchmark + +#endif // QC_MODULE_BENCHMARK_BENCHMARKCHECK_H diff --git a/Modules/Benchmark/include/Benchmark/EmptyPPTask.h b/Modules/Benchmark/include/Benchmark/EmptyPPTask.h new file mode 100644 index 0000000000..281e443255 --- /dev/null +++ b/Modules/Benchmark/include/Benchmark/EmptyPPTask.h @@ -0,0 +1,59 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file EmptyPPTask.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_EMPTYPPTASK_H +#define QUALITYCONTROL_EMPTYPPTASK_H + +#include "QualityControl/PostProcessingInterface.h" + +namespace o2::quality_control_modules::benchmark +{ + +/// \brief Example Quality Control Postprocessing Task +/// \author My Name +class EmptyPPTask final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + EmptyPPTask() = default; + /// \brief Destructor + ~EmptyPPTask() override; + + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: +}; + +} // namespace o2::quality_control_modules::benchmark + +#endif //QUALITYCONTROL_EMPTYPPTASK_H diff --git a/Modules/Benchmark/include/Benchmark/LinkDef.h b/Modules/Benchmark/include/Benchmark/LinkDef.h new file mode 100644 index 0000000000..31a3eb1811 --- /dev/null +++ b/Modules/Benchmark/include/Benchmark/LinkDef.h @@ -0,0 +1,9 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::benchmark::TH1FTask + ; +#pragma link C++ class o2::quality_control_modules::benchmark::AlwaysGoodCheck + ; +#pragma link C++ class o2::quality_control_modules::benchmark::EmptyPPTask + ; +#endif diff --git a/Modules/Benchmark/include/Benchmark/TH1FTask.h b/Modules/Benchmark/include/Benchmark/TH1FTask.h new file mode 100644 index 0000000000..cd534cef32 --- /dev/null +++ b/Modules/Benchmark/include/Benchmark/TH1FTask.h @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1FTask.h +/// \author Piotr Konopka +/// + +#ifndef QC_MODULE_BENCHMARK_BENCHMARKTASK_H +#define QC_MODULE_BENCHMARK_BENCHMARKTASK_H + +#include "QualityControl/TaskInterface.h" + +class TH1F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::benchmark +{ + +/// \brief A benchmark task which produces TH1Fs +/// \author Piotr Konopka +class TH1FTask final : public TaskInterface +{ + public: + /// \brief Constructor + TH1FTask() = default; + /// Destructor + ~TH1FTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + std::vector> mHistograms; +}; + +} // namespace o2::quality_control_modules::benchmark + +#endif // QC_MODULE_BENCHMARK_BENCHMARKTASK_H diff --git a/Modules/Benchmark/script/benchmarkPP.json b/Modules/Benchmark/script/benchmarkPP.json new file mode 100644 index 0000000000..7c497c4426 --- /dev/null +++ b/Modules/Benchmark/script/benchmarkPP.json @@ -0,0 +1,45 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "checks": { + }, + "postprocessing": { + "BenchmarkPostprocessing": { + "active": "true", + "className": "o2::quality_control_modules::benchmark::EmptyPPTask", + "moduleName": "QcBenchmark", + "detectorName": "TST", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "always" + ], + "stopTrigger": [ + "1min" + ] + } + } + } +} diff --git a/Modules/Benchmark/script/o2-qc-benchmark-checks.sh b/Modules/Benchmark/script/o2-qc-benchmark-checks.sh new file mode 100755 index 0000000000..7a46fbaa32 --- /dev/null +++ b/Modules/Benchmark/script/o2-qc-benchmark-checks.sh @@ -0,0 +1,264 @@ +#!/usr/bin/env bash + +#set -e ;# exit on error +set -u ;# exit when using undeclared variable +#set -x ;# debugging + +trap kill_benchmark INT + +function check_installed() { + # very stupid but easy way to check if a package is installed + $1 --version > /dev/null + if [ $? -ne 0 ]; then + echo "Please install the package "$1" before running the benchmark." + exit 1; + fi +} + +echo 'Checking if all necessary packages are installed...' +check_installed tail +check_installed bc +check_installed pkill +check_installed sed +check_installed grep +echo '...all are there.' + +function kill_benchmark() { + echo "Killing any running benchmark workflows..." + pkill -9 -f 'o2-qc ' + pkill -9 -f 'o2-qc-run-producer' + exit 1 +} + +function inplace_sed() { + sed -ibck "$1" $2 && rm $2bck +} + +# Run the benchmark with given parameters +# \param 1 : number of checks +# \param 2 : number of histograms array name +# \param 3 : number of bins array name +# \param 4 : cycle_seconds +# \param 5 : repetitions +# \param 6 : test_duration +# \param 7 : warm up cycles - how many first metrics should be ignored (they are sent each 10s) +# \param 8 : test name +# \param 9: fill - (yes/no) should write zeroes to the produced messages (prevents memory overcommitting) +function benchmark() { + + local number_of_checks_name=$1[@] + local number_of_histograms_name=$2[@] + local number_of_bins_name=$3[@] + + local number_of_checks=("${!number_of_checks_name}") + local number_of_histograms=("${!number_of_histograms_name}") + local number_of_bins=("${!number_of_bins_name}") + + local cycle_seconds=$4 + local repetitions=$5 + local test_duration=$6 + local warm_up_cycles=$7 + local test_name=$8 + local fill=${9} + + # we assume that no QC Task might sustain more than this value of B/s + # and we will trim down the data rates accordingly to be gentle with memory + local max_data_input=500 + + if [ ! -z $TESTS ] && [[ ! " ${TESTS[@]} " =~ " ${test_name} " ]]; then + echo "Test '"$test_name"' ignored" + return + fi + + echo "=======================================================================" + echo "Running the test '"$test_name"'" + +# local test_duration_timeout=$((test_duration + 60)) + local repo_latest_commit=$(git rev-list --format=oneline --max-count=1 HEAD) + local repo_branch=$(git rev-parse --abbrev-ref HEAD) + local results_filename='qc-check-benchmark-'$(date +"%y-%m-%d_%H%M")'-'$test_name + local test_date=$(date +"%y-%m-%d %H:%M:%S") + local config_file_template='../etc/benchmarkCheckTemplate.json' + local config_file_concrete='benchmarkCheck.json' + local run_log='run_log' + local check_config='\"AlwaysGoodCheck__CHECK_NO__\":{\"active\":\"true\",\"className\":\"o2::quality_control_modules::benchmark::AlwaysGoodCheck\",\"moduleName\":\"QcBenchmark\",\"policy\":\"OnAny\",\"detectorName\":\"TST\",\"dataSource\":[{\"type\":\"Task\",\"name\":\"BenchmarkTask\"}]},__CHECKS__' + + printf "QC CHECK RUNNER BENCHMARK RESULTS\n" > $results_filename + printf "Date: %s\n" "$test_date" >> $results_filename + printf "Latest commit: %s\n" "$repo_latest_commit" >> $results_filename + printf "Branch: %s\n" "$repo_branch" >> $results_filename + printf "Repetitions: %s\n" "$repetitions" >> $results_filename + printf "Test duration [s]: %s\n" "$test_duration" >> $results_filename + printf "Warm up cycles: %s\n" "$warm_up_cycles" >> $results_filename + echo "nb_checks , nb_histograms, nb_bins, objs_per_second, checks_per_second" >> $results_filename + local qc_common_args="--run -b -b --resources-monitoring 10 --monitoring-backend stdout:// --shm-segment-size 50000000000 --infologger-severity info --config json:/"`pwd`'/'$config_file_concrete + local producer_common_args="-b" + if [[ $fill == "no" ]]; then + producer_common_args=$producer_common_args' --empty' + fi + + for nb_checks in ${number_of_checks[@]}; do + for nb_histograms in ${number_of_histograms[@]}; do + for nb_bins in ${number_of_bins[@]}; do + echo "************************************************************" + echo "Launching the test for $nb_checks checks per object, $nb_histograms histograms, $nb_bins bins" + + echo "Creating a config file..." + rm -f $config_file_concrete + + sed 's/__CYCLE_SECONDS__/'$cycle_seconds'/; s/__NUMBER_OF_HISTOGRAMS__/'$nb_histograms'/; s/__NUMBER_OF_BINS__/'$nb_bins'/' $config_file_template > $config_file_concrete + + # create a json string with all check configurations + total_checks_config= + for ((check_no=0;check_no> $results_filename + printf "%14s," "$nb_histograms" >> $results_filename + printf "%13s," "$nb_bins" >> $results_filename + + checks_per_second= + state= + while [ "$state" == 'error' ] || [ -z "$state" ]; do + if [ "$state" == 'error' ]; then + echo "Retrying the test because of an error" + mv run_log run_log_error_"$(date +"%y-%m-%d-%H:%M:%S")" + fi + state='ok' + + # Running the benchmark + rm -f $run_log + echo "Used command:" + echo "timeout -k 5s $test_duration $command > $run_log" + timeout -k 5s $test_duration bash -c "$command > $run_log" + + # Cleaning up potentially leftover processes. Notice the space after o2-qc to avoid suicide + pkill -9 -f 'o2-qc ' + pkill -9 -f 'o2-qc-run-producer' + + # Check the log in case of errors, repeat then + if grep 'ERROR\|segmentation violation' $run_log; then + state='error' + echo 'Error in the test:' + echo `grep 'ERROR\|segmentation' $run_log` + continue + fi + + mapfile -t metrics_checks_executed < <(grep -a 'qc_checkrunner_checks_executed' $run_log | sed -r 's/^.*,0 ([0-9]+) .*/\1/' | tail -n +$warm_up_cycles) + mapfile -t metrics_objects_received < <(grep -a 'qc_checkrunner_objects_received' $run_log | sed -r 's/^.*,0 ([0-9]+) .*/\1/' | tail -n +$warm_up_cycles) + mapfile -t metrics_test_duration < <(grep -a 'qc_checkrunner_checks_executed' $run_log | grep -o -e '[0-9]\{1,\} hostname' | sed -e 's/ hostname//' | tail -n +$warm_up_cycles) + + if [ ${#metrics_checks_executed[@]} -ge 2 ] && [ ${#metrics_test_duration[@]} -ge 2 ] && [ ${#metrics_objects_received[@]} -ge 2 ]; then + + (( total_checks_executed = metrics_checks_executed[-1] - metrics_checks_executed[0] )) + (( total_objects_received = metrics_objects_received[-1] - metrics_objects_received[0] )) + (( total_test_duration_ms = metrics_test_duration[-1] - metrics_test_duration[0] )) + + checks_per_second=`echo "scale=3; $total_checks_executed*1000/$total_test_duration_ms" | bc -l` + objects_per_second=`echo "scale=3; $total_objects_received*1000/$total_test_duration_ms" | bc -l` + else + state='error' + fi + done + + printf "%16s," "$objects_per_second" >> $results_filename + printf "%16s" "$checks_per_second" >> $results_filename + printf "\n" >> $results_filename + + done + done + done + done + + # cleanups + rm -f $config_file_concrete + rm -f $run_log +} + +function print_usage() { + echo "Usage: ./o2-qc-benchmark-checks.sh [-f] [-m MEMORY_USAGE] [-t TEST] + +Run Check Runners Benchmark and create report files in the directory. Execute from within the same directory, after +having entered the QualityControl environment. + +Options: + -h Print this message + -t TEST Test name to be run. Use it multiple times to run multiple tests. If not specified, all are run. See + the last part of this script to find the available tests. + -f Fill QC Task input messages. It slows down producers, but prevents from overcommitting memory. +" +} + + +FILL=no +TESTS= + +while getopts 'hfm:t:' option; do + case "${option}" in + \?) + print_usage + exit 1 + ;; + h) + print_usage + exit 0 + ;; + f) + FILL=yes + ;; + m) + MEMORY_USAGE=$OPTARG + ;; + t) + TESTS=("$OPTARG") + printf '%s\n' "${TESTS[@]}" + ;; + esac +done + +# global parameters +REPETITIONS=5; +TEST_DURATION=300; +WARM_UP_CYCLES=5; + +# test-specific parameters +NB_CHECKS=(1); +NB_HISTOGRAMS=(1 4 16 64 256 1024 4096); +NB_BINS=(64000); +CYCLE_SECONDS=1 +TEST_NAME='objects' + +benchmark NB_CHECKS NB_HISTOGRAMS NB_BINS $CYCLE_SECONDS $REPETITIONS $TEST_DURATION $WARM_UP_CYCLES $TEST_NAME $FILL + +NB_CHECKS=(1 4 16 64 256); +NB_HISTOGRAMS=(1 100); +NB_BINS=(64000); +CYCLE_SECONDS=1 +TEST_NAME='checks' + +benchmark NB_CHECKS NB_HISTOGRAMS NB_BINS $CYCLE_SECONDS $REPETITIONS $TEST_DURATION $WARM_UP_CYCLES $TEST_NAME $FILL + +NB_CHECKS=(1 100); +NB_HISTOGRAMS=(1 100); +NB_BINS=(1 10 100 1000 10000 100000 1000000); +CYCLE_SECONDS=1 +TEST_NAME='object-size' + +benchmark NB_CHECKS NB_HISTOGRAMS NB_BINS $CYCLE_SECONDS $REPETITIONS $TEST_DURATION $WARM_UP_CYCLES $TEST_NAME $FILL diff --git a/Modules/Benchmark/script/o2-qc-benchmark-tasks.sh b/Modules/Benchmark/script/o2-qc-benchmark-tasks.sh new file mode 100755 index 0000000000..27a490fe59 --- /dev/null +++ b/Modules/Benchmark/script/o2-qc-benchmark-tasks.sh @@ -0,0 +1,295 @@ +#!/usr/bin/env bash + +#set -e ;# exit on error +set -u ;# exit when using undeclared variable +#set -x ;# debugging + +trap kill_benchmark INT + +function check_installed() { + # very stupid but easy way to check if a package is installed + $1 --version > /dev/null + if [ $? -ne 0 ]; then + echo "Please install the package "$1" before running the benchmark." + exit 1; + fi +} + +echo 'Checking if all necessary packages are installed...' +check_installed tail +check_installed bc +check_installed pkill +check_installed sed +check_installed grep +echo '...all are there.' + +function kill_benchmark() { + echo "Killing any running benchmark workflows..." + pkill -9 -f 'o2-qc ' + pkill -9 -f 'o2-qc-run-producer' + exit 1 +} + +# Run the benchmark with given parameters +# \param 1 : number of producers array name +# \param 2 : payload sizes array name +# \param 3 : number of histograms array name +# \param 4 : number of bins array name +# \param 5 : cycle_seconds +# \param 6 : repetitions +# \param 7 : test_duration +# \param 8 : warm up cycles - how many first metrics should be ignored (they are sent each 10s) +# \param 9 : test name +# \param 10: fill - (yes/no) should write zeroes to the produced messages (prevents memory overcommitment) +# \param 11: max input data throughput +function benchmark() { + + local number_of_producers_name=$1[@] + local payload_sizes_name=$2[@] + local number_of_histograms_name=$3[@] + local number_of_bins_name=$4[@] + + local number_of_producers=("${!number_of_producers_name}") + local payload_sizes=("${!payload_sizes_name}") + local number_of_histograms=("${!number_of_histograms_name}") + local number_of_bins=("${!number_of_bins_name}") + + local cycle_seconds=$5 + local repetitions=$6 + local test_duration=$7 + local warm_up_cycles=$8 + local test_name=$9 + local fill=${10} + # we assume that no QC Task might sustain more than this value of B/s + # and we will trim down the data rates accordingly to be gentle with memory + local max_data_input=${11} + + + if [ ! -z $TESTS ] && [[ ! " ${TESTS[@]} " =~ " ${test_name} " ]]; then + echo "Test '"$test_name"' ignored" + return + fi + + echo "=======================================================================" + echo "Running the test '"$test_name"'" + +# local test_duration_timeout=$((test_duration + 60)) + local repo_latest_commit=$(git rev-list --format=oneline --max-count=1 HEAD) + local repo_branch=$(git rev-parse --abbrev-ref HEAD) + local results_filename='qc-task-benchmark-'$(date +"%y-%m-%d_%H%M")'-'$test_name + local test_date=$(date +"%y-%m-%d %H:%M:%S") + local config_file_template='../etc/benchmarkTaskTemplate.json' + local config_file_concrete='benchmarkTask.json' + local run_log='run_log' + + printf "QC TASK RUNNER BENCHMARK RESULTS\n" > $results_filename + printf "Date: %s\n" "$test_date" >> $results_filename + printf "Latest commit: %s\n" "$repo_latest_commit" >> $results_filename + printf "Branch: %s\n" "$repo_branch" >> $results_filename + printf "Repetitions: %s\n" "$repetitions" >> $results_filename + printf "Test duration [s]: %s\n" "$test_duration" >> $results_filename + printf "Warm up cycles: %s\n" "$warm_up_cycles" >> $results_filename + echo "nb_producers, payload_sizes, nb_histograms, nb_bins, msgs_per_second, data_per_second, objs_published_per_second" >> $results_filename + local qc_common_args="--run -b -b --resources-monitoring 10 --monitoring-backend stdout:// --shm-segment-size 50000000000 --infologger-severity info --config json:/"`pwd`'/'$config_file_concrete + local producer_common_args="-b" + if [[ $fill == "no" ]]; then + producer_common_args=$producer_common_args' --empty' + fi + + for nb_producers in ${number_of_producers[@]}; do + for payload_size in ${payload_sizes[@]}; do + for nb_histograms in ${number_of_histograms[@]}; do + for nb_bins in ${number_of_bins[@]}; do + echo "************************************************************" + echo "Launching the test for payload size $payload_size bytes, $nb_producers producers, $nb_histograms histograms, $nb_bins bins" + + echo "Creating a config file..." + rm -f $config_file_concrete + sed 's/__CYCLE_SECONDS__/'$cycle_seconds'/; s/__NUMBER_OF_HISTOGRAMS__/'$nb_histograms'/; s/__NUMBER_OF_BINS__/'$nb_bins'/' $config_file_template > $config_file_concrete + echo "...created." + + # calculating parameters + (( message_rate = max_data_input / payload_size / nb_producers)) + + # preparing the command + command_producer="o2-qc-run-producer "$producer_common_args" --min-size "$payload_size" --max-size "$payload_size" --timepipeline "$nb_producers" --message-rate "$message_rate + command_qc="o2-qc "$qc_common_args + command=$command_producer" | "$command_qc + + for ((rep=0;rep> $results_filename + printf "%14s," "$payload_size" >> $results_filename + printf "%14s," "$nb_histograms" >> $results_filename + printf "%13s," "$nb_bins" >> $results_filename + + messages_per_second= + state= + while [ "$state" == 'error' ] || [ -z "$state" ]; do + if [ "$state" == 'error' ]; then + echo "Retrying the test because of an error" + mv run_log run_log_error"$(date +"%y-%m-%d-%H:%M:%S")" + fi + state='ok' + + # Running the benchmark + rm -f $run_log + echo "Used command:" + echo "timeout -k 5s $test_duration $command > $run_log" + timeout -k 5s $test_duration bash -c "$command > $run_log" + + # Cleaning up potentially leftover processes. Notice the space after o2-qc to avoid suicide + pkill -9 -f 'o2-qc ' + pkill -9 -f 'o2-qc-run-producer' + + # Check the log in case of errors, repeat then + if grep 'ERROR\|segmentation violation' $run_log; then + state='error' + echo 'Error in the test:' + echo `grep 'ERROR\|segmentation' $run_log` + continue + fi + + mapfile -t metrics_messages_in_cycle < <(grep -a 'qc_data_received' $run_log | grep -o -e 'messages_in_cycle=[0-9]\{1,\}' | sed -e 's/messages_in_cycle=//' | tail -n +$warm_up_cycles) + mapfile -t metrics_data_in_cycle < <(grep -a 'qc_data_received' $run_log | grep -o -e 'data_in_cycle=[0-9]\{1,\}' | sed -e 's/data_in_cycle=//' | tail -n +$warm_up_cycles) + mapfile -t metrics_objects_published_in_cycle < <(grep -a 'qc_objects_published' $run_log | grep -o -e 'in_cycle=[0-9]\{1,\}' | sed -e 's/in_cycle=//' | tail -n +$warm_up_cycles) + mapfile -t metrics_cycle_duration < <(grep -a 'qc_duration' $run_log | grep -o -e 'module_cycle=[0-9]\{1,\}.[0-9]\{1,\}' | sed -e 's/module_cycle=//' | tail -n +$warm_up_cycles) + + if [ ${#metrics_messages_in_cycle[@]} -ge 2 ] && [ ${#metrics_cycle_duration[@]} -ge 2 ] && [ ${#metrics_objects_published_in_cycle[@]} -ge 2 ] && [ ${#metrics_data_in_cycle[@]} -ge 2 ]; then + + total_messages=0 + for ((i = 0; i < ${#metrics_messages_in_cycle[@]}; i++)) + do + (( total_messages+=metrics_messages_in_cycle[i] )) + done + + total_data=0 + for ((i = 0; i < ${#metrics_data_in_cycle[@]}; i++)) + do + (( total_data+=metrics_data_in_cycle[i] )) + done + + total_objects_published=0 + for ((i = 0; i < ${#metrics_objects_published_in_cycle[@]}; i++)) + do + (( total_objects_published+=metrics_objects_published_in_cycle[i] )) + done + + total_time=0.0 + for ((i = 0; i < ${#metrics_cycle_duration[@]}; i++)) + do + total_time=`echo "scale=3; $total_time+${metrics_cycle_duration[i]}" | bc -l` + done + + messages_per_second=`echo "scale=3; $total_messages/$total_time" | bc -l` + data_per_second=`echo "scale=3; $total_data/$total_time" | bc -l` + objects_per_second=`echo "scale=3; $total_objects_published/$total_time" | bc -l` + else + state='error' + fi + done + + printf "%16s," "$messages_per_second" >> $results_filename + printf "%16s," "$data_per_second" >> $results_filename + printf "%16s" "$objects_per_second" >> $results_filename + printf "\n" >> $results_filename + + done + done + done + done + done + + # cleanups + rm -f $config_file_concrete + rm -f $run_log +} + +function print_usage() { + echo "Usage: ./o2-qc-benchmark-tasks.sh [-f] [-m MEMORY_USAGE] [-t TEST] + +Run Task Runners Benchmark and create report files in the directory. Execute from within the same directory, after +having entered the QualityControl environment. + +Options: + -h Print this message + -t TEST Test name to be run. Use it multiple times to run multiple tests. If not specified, all are run. See + the last part of this script to find the available tests. + -f Fill QC Task input messages. It slows down producers, but prevents from overcommitting memory. +" +} + + +FILL=no +TESTS= + +while getopts 'hfm:t:' option; do + case "${option}" in + \?) + print_usage + exit 1 + ;; + h) + print_usage + exit 0 + ;; + f) + FILL=yes + ;; + m) + MEMORY_USAGE=$OPTARG + ;; + t) + TESTS=("$OPTARG") + printf '%s\n' "${TESTS[@]}" + ;; + esac +done + +REPETITIONS=5; +TEST_DURATION=300; +WARM_UP_CYCLES=5; + +NB_PRODUCERS=(1 2 4 8 16); +PAYLOAD_SIZE=(256 2000000); +NB_HISTOGRAMS=(1); +NB_BINS=(100); +CYCLE_SECONDS=10 +MAX_INPUT_DATA_THROUGHPUT=5000000000 +TEST_NAME='producers' + +benchmark NB_PRODUCERS PAYLOAD_SIZE NB_HISTOGRAMS NB_BINS $CYCLE_SECONDS $REPETITIONS $TEST_DURATION $WARM_UP_CYCLES $TEST_NAME $FILL $MAX_INPUT_DATA_THROUGHPUT + +NB_PRODUCERS=(8); +PAYLOAD_SIZE=(1 256 1024 4096 16384 65536 262144 1048576 4194304 16777216 67108864 268435456); +NB_HISTOGRAMS=(1); +NB_BINS=(100); +CYCLE_SECONDS=10; +MAX_INPUT_DATA_THROUGHPUT=5000000000 +TEST_NAME='payload-sizes' + +benchmark NB_PRODUCERS PAYLOAD_SIZE NB_HISTOGRAMS NB_BINS $CYCLE_SECONDS $REPETITIONS $TEST_DURATION $WARM_UP_CYCLES $TEST_NAME $FILL $MAX_INPUT_DATA_THROUGHPUT + +NB_PRODUCERS=(1); +PAYLOAD_SIZE=(256); +NB_HISTOGRAMS=(100); +NB_BINS=(1 10 100 1000 10000 100000 1000000); +CYCLE_SECONDS=1; +MAX_INPUT_DATA_THROUGHPUT=500 +TEST_NAME='obj-size' + +benchmark NB_PRODUCERS PAYLOAD_SIZE NB_HISTOGRAMS NB_BINS $CYCLE_SECONDS $REPETITIONS $TEST_DURATION $WARM_UP_CYCLES $TEST_NAME $FILL $MAX_INPUT_DATA_THROUGHPUT + +NB_PRODUCERS=(1); +PAYLOAD_SIZE=(256); +NB_HISTOGRAMS=(1 4 16 64 256 1024); +NB_BINS=(64000); +CYCLE_SECONDS=1; +MAX_INPUT_DATA_THROUGHPUT=500 +TEST_NAME='obj-amount' + +benchmark NB_PRODUCERS PAYLOAD_SIZE NB_HISTOGRAMS NB_BINS $CYCLE_SECONDS $REPETITIONS $TEST_DURATION $WARM_UP_CYCLES $TEST_NAME $FILL $MAX_INPUT_DATA_THROUGHPUT diff --git a/Modules/Benchmark/src/AlwaysGoodCheck.cxx b/Modules/Benchmark/src/AlwaysGoodCheck.cxx new file mode 100644 index 0000000000..ec70974d48 --- /dev/null +++ b/Modules/Benchmark/src/AlwaysGoodCheck.cxx @@ -0,0 +1,40 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AlwaysGoodCheck.cxx +/// \author Piotr Konopka +/// + +#include "Benchmark/AlwaysGoodCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" + +// ROOT +#include + +using namespace std; + +namespace o2::quality_control_modules::benchmark +{ + +void AlwaysGoodCheck::configure() {} + +Quality AlwaysGoodCheck::check(std::map>*) +{ + return Quality::Good; +} + +void AlwaysGoodCheck::beautify(std::shared_ptr, Quality) +{ +} + +} // namespace o2::quality_control_modules::benchmark diff --git a/Modules/Benchmark/src/EmptyPPTask.cxx b/Modules/Benchmark/src/EmptyPPTask.cxx new file mode 100644 index 0000000000..827abdbc56 --- /dev/null +++ b/Modules/Benchmark/src/EmptyPPTask.cxx @@ -0,0 +1,41 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingInterface.cxx +/// \author My Name +/// + +#include "Benchmark/EmptyPPTask.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::benchmark +{ + +EmptyPPTask::~EmptyPPTask() +{ +} + +void EmptyPPTask::initialize(Trigger, framework::ServiceRegistryRef) +{ +} + +void EmptyPPTask::update(Trigger, framework::ServiceRegistryRef) +{ +} + +void EmptyPPTask::finalize(Trigger, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::benchmark diff --git a/Modules/Benchmark/src/TH1FTask.cxx b/Modules/Benchmark/src/TH1FTask.cxx new file mode 100644 index 0000000000..ac584e7479 --- /dev/null +++ b/Modules/Benchmark/src/TH1FTask.cxx @@ -0,0 +1,109 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1FTask.cxx +/// \author Piotr Konopka +/// + +#include "Benchmark/TH1FTask.h" + +#include +#include +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include + +namespace o2::quality_control_modules::benchmark +{ + +TH1FTask::~TH1FTask() +{ +} + +void TH1FTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TH1FTask" << ENDM; + + int histogramsNumber = 1; + if (mCustomParameters.count("histoNumber") > 0) { + histogramsNumber = stoi(mCustomParameters.at("histoNumber")); + } + + int binsNumber = 20; + if (mCustomParameters.count("binsNumber") > 0) { + binsNumber = stoi(mCustomParameters.at("binsNumber")); + } + + ILOG(Info, Support) << "Will create " << histogramsNumber << " histograms." << ENDM; + ILOG(Info, Support) << "They will have " << binsNumber << " bins each." << ENDM; + ILOG(Info, Support) << "In-memory size of one histogram will be around " << binsNumber * 4 << " bytes." << ENDM; + + for (int i = 0; i < histogramsNumber; i++) { + std::string name = "histo-" + std::to_string(i); + mHistograms.push_back(std::make_shared(name.c_str(), name.c_str(), binsNumber, 0, 30000)); + getObjectsManager()->startPublishing(mHistograms.back().get(), PublicationPolicy::Forever); + } +} + +void TH1FTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + for (auto& histo : mHistograms) { + histo->Reset(); + } +} + +void TH1FTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TH1FTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + // the minimum that we can do, which includes accessing data and creating non-empty histograms + size_t dummySum = 0; + for (auto&& input : o2::framework::InputRecordWalker(ctx.inputs())) { + if (input.header != nullptr && input.payload != nullptr) { + // TODO: better use InputRecord::get to get a span of the raw input data + // and use its size and first byte + const auto* header = o2::framework::DataRefUtils::getHeader(input); + auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(input); + dummySum += payloadSize + input.payload[0]; + } + } + + dummySum %= 30000; + for (auto& histo : mHistograms) { + histo->Fill(dummySum++); + } +} + +void TH1FTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void TH1FTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TH1FTask::reset() +{ + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + for (auto& histo : mHistograms) { + histo->Reset(); + } +} + +} // namespace o2::quality_control_modules::benchmark diff --git a/Modules/Benchmark/test/testQcBenchmark.cxx b/Modules/Benchmark/test/testQcBenchmark.cxx new file mode 100644 index 0000000000..7c8ddd5872 --- /dev/null +++ b/Modules/Benchmark/test/testQcBenchmark.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testBenchmark.cxx +/// \author +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::benchmark +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::benchmark diff --git a/Modules/CMakeLists.txt b/Modules/CMakeLists.txt index 68a6f9888a..ab7a6d46e3 100644 --- a/Modules/CMakeLists.txt +++ b/Modules/CMakeLists.txt @@ -3,4 +3,21 @@ add_subdirectory(Common) add_subdirectory(Daq) add_subdirectory(Example) +add_subdirectory(Benchmark) add_subdirectory(Skeleton) +add_subdirectory(TOF) +add_subdirectory(EMCAL) +add_subdirectory(MUON) +add_subdirectory(TPC) +add_subdirectory(ITS) +add_subdirectory(MFT) +add_subdirectory(PHOS) +add_subdirectory(FIT) +add_subdirectory(TRD) +add_subdirectory(HMPID) +add_subdirectory(CPV) +add_subdirectory(GLO) +add_subdirectory(ZDC) +add_subdirectory(CTP) +add_subdirectory(PID) +add_subdirectory(FOCAL) diff --git a/Modules/CPV/CMakeLists.txt b/Modules/CPV/CMakeLists.txt new file mode 100644 index 0000000000..6d4ea96021 --- /dev/null +++ b/Modules/CPV/CMakeLists.txt @@ -0,0 +1,47 @@ +# ---- Library ---- + +add_library(O2QcCPV) + +target_sources(O2QcCPV PRIVATE src/PhysicsTask.cxx src/PhysicsCheck.cxx src/PedestalCheck.cxx src/PedestalTask.cxx) + +target_include_directories( + O2QcCPV + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcCPV PUBLIC O2QualityControl O2QcPHOS O2::DataFormatsQualityControl O2::CPVBase ROOT::Spectrum) + +install(TARGETS O2QcCPV + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcCPV + HEADERS + include/CPV/PhysicsTask.h + include/CPV/PhysicsCheck.h + include/CPV/PedestalCheck.h + include/CPV/PedestalTask.h + LINKDEF include/CPV/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/CPV + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcCPV.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcCPV Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + diff --git a/Modules/CPV/etc/cpv-physics-qcmn-epn-staging.json b/Modules/CPV/etc/cpv-physics-qcmn-epn-staging.json new file mode 100644 index 0000000000..5e207f86a8 --- /dev/null +++ b/Modules/CPV/etc/cpv-physics-qcmn-epn-staging.json @@ -0,0 +1,540 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "alio2-cr1-hv-mvs00:8083", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "influxdb-unix:///tmp/telegraf.sock" + }, + "consul": { + "url": "http://alio2-cr1-hv-mvs00:8500" + }, + "conditionDB": { + "url": "o2-ccdb.internal" + }, + "bookkeeping": { + "url": "" + } + }, + "tasks": { + "PhysicsOnEPNs": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsTask", + "moduleName": "QcCPV", + "detectorName": "CPV", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "digits:CPV/DIGITS/0;dtrigrec:CPV/DIGITTRIGREC/0;clusters:CPV/CLUSTERS/0;ctrigrec:CPV/CLUSTERTRIGRECS/0;calibdigits:CPV/CALIBDIGITS/0;rawerrors:CPV/RAWHWERRORS/0;peds:CPV/CPV_Pedestals;badmap:CPV/CPV_BadMap;gains:CPV/CPV_Gains" + }, + "taskParameters": { + "ccdbCheckInterval": "1000", + "isAsyncMode": "0" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "alio2-cr1-qc04.cern.ch", + "remotePort": "47768", + "mergingMode": "delta", + "localControl": "odc" + } + }, + "checks": { + "ClustersIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "CPV", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "ClusterMapM2", + "ClusterMapM3", + "ClusterMapM4" + ] + } + ] + }, + "DigitsIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "CPV", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "DigitMapM2", + "DigitMapM3", + "DigitMapM4" + ] + } + ] + }, + "DigitsCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "DigitsInEventM2", + "DigitsInEventM3", + "DigitsInEventM4", + "DigitMapM2", + "DigitMapM3", + "DigitMapM4", + "DigitOccuranceM2", + "DigitOccuranceM3", + "DigitOccuranceM4" + ] + } + ], + "checkParameters": { + "mMinEventsToCheckDigitMap2": "10000", + "mMinEventsToCheckDigitMap3": "10000", + "mMinEventsToCheckDigitMap4": "10000", + "mNCold3GassiplexAllowed2": "30", + "mNCold3GassiplexAllowed3": "10", + "mNCold3GassiplexAllowed4": "17", + "mNHot3GassiplexAllowed2": "10", + "mNHot3GassiplexAllowed3": "10", + "mNHot3GassiplexAllowed4": "14", + "mHot3GassiplexCriterium2": "2.5", + "mHot3GassiplexCriterium3": "2.5", + "mHot3GassiplexCriterium4": "2.5", + "mCold3GassiplexCriterium2": "0.3", + "mCold3GassiplexCriterium3": "0.3", + "mCold3GassiplexCriterium4": "0.3", + "mMinDigitsPerEvent2": "50", + "mMinDigitsPerEvent3": "50", + "mMinDigitsPerEvent4": "50", + "mMaxDigitsPerEvent2": "100", + "mMaxDigitsPerEvent3": "100", + "mMaxDigitsPerEvent4": "100", + "mHot3GassiplexOccurance2": "0.05", + "mHot3GassiplexOccurance3": "0.05", + "mHot3GassiplexOccurance4": "0.05", + "mCold3GassiplexOccurance2": "0.000001", + "mCold3GassiplexOccurance3": "0.000001", + "mCold3GassiplexOccurance4": "0.000001" + } + }, + "ClustersCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "NDigitsInClusterM2", + "NDigitsInClusterM3", + "NDigitsInClusterM4", + "ClusterTotEnergyM2", + "ClusterTotEnergyM3", + "ClusterTotEnergyM4" + ] + } + ], + "checkParameters": { + "mMinEventsToCheckClusters2": "10000", + "mMinEventsToCheckClusters3": "10000", + "mMinEventsToCheckClusters4": "10000", + "mCluEnergyRangeL2": "100", + "mCluEnergyRangeL3": "100", + "mCluEnergyRangeL4": "100", + "mCluEnergyRangeR2": "2000", + "mCluEnergyRangeR3": "2000", + "mCluEnergyRangeR4": "2000", + "mMinCluEnergyMean2": "50", + "mMinCluEnergyMean3": "50", + "mMinCluEnergyMean4": "50", + "mMaxCluEnergyMean2": "400", + "mMaxCluEnergyMean3": "400", + "mMaxCluEnergyMean4": "400", + "mMinClusterSize2": "1.5", + "mMinClusterSize3": "1.7", + "mMinClusterSize4": "1.7", + "mMaxClusterSize2": "2.5", + "mMaxClusterSize3": "2.5", + "mMaxClusterSize4": "2.5" + } + }, + "CalibDigitsCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "CalibDigitEnergyM2", + "CalibDigitEnergyM3", + "CalibDigitEnergyM4" + ] + } + ], + "checkParameters": { + "mAmplitudeRangeL2": "10", + "mAmplitudeRangeL3": "10", + "mAmplitudeRangeL4": "10", + "mAmplitudeRangeR2": "1000", + "mAmplitudeRangeR3": "1000", + "mAmplitudeRangeR4": "1000", + "mMinEventsToCheckAmplitude2": "10000", + "mMinEventsToCheckAmplitude3": "10000", + "mMinEventsToCheckAmplitude4": "10000", + "mMinAmplitudeMean2": "50", + "mMinAmplitudeMean3": "50", + "mMinAmplitudeMean4": "50", + "mMaxAmplitudeMean2": "400", + "mMaxAmplitudeMean3": "400", + "mMaxAmplitudeMean4": "400" + } + }, + "ErrorsCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "ErrorTypeOccurance" + ] + } + ], + "checkParameters": { + "mErrorOccuranceThreshold18": "0.1" + } + } + }, + "postprocessing": { + "PhysicsTrending": { + "active": "true", + "#taskName": "PhysicsOnEPNs", + "": "use the taskName in order to post output to CPV/MO/TaskName", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcCommon", + "detectorName": "CPV", + "resumeTrend": "false", + "producePlotsOnUpdate": "true", + "initTrigger": [ + "newobject:qcdb:CPV/MO/PhysicsOnEPNs/NDigitsInEventM2M3M4" + ], + "updateTrigger": [ + "newobject:qcdb:CPV/MO/PhysicsOnEPNs/NDigitsInEventM2M3M4" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "CPV/MO/PhysicsOnEPNs", + "names": [ + "NDigitsInEventM2M3M4", + "NClustersInEventM2M3M4", + "ClusterTotEnergyM2", + "ClusterTotEnergyM3", + "ClusterTotEnergyM4", + "CalibDigitEnergyM2", + "CalibDigitEnergyM3", + "CalibDigitEnergyM4", + "DigitEnergyM2", + "DigitEnergyM3", + "DigitEnergyM4", + "NDigitsInClusterM2", + "NDigitsInClusterM3", + "NDigitsInClusterM4" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_n_digits", + "title": "Trend of mean number of digits per event", + "varexp": "NDigitsInEventM2M3M4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean number of digits:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_n_clusters", + "title": "Trend of mean number of clusters per event", + "varexp": "NClustersInEventM2M3M4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean number of clusters:time", + "graphYRange": "0:50" + }, + { + "name": "mean_of_cluEnergyM2", + "title": "Trend of mean cluster energy M2", + "varexp": "ClusterTotEnergyM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster energy:time", + "graphYRange": "0:150" + }, + { + "name": "mean_of_cluEnergyM3", + "title": "Trend of mean cluster energy M3", + "varexp": "ClusterTotEnergyM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster energy:time", + "graphYRange": "0:150" + }, + { + "name": "mean_of_cluEnergyM4", + "title": "Trend of mean cluster energy M4", + "varexp": "ClusterTotEnergyM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster energy:time", + "graphYRange": "0:150" + }, + { + "name": "mean_of_calibDigEnergyM2", + "title": "Trend of mean calib digit energy M2", + "varexp": "CalibDigitEnergyM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_calibDigEnergyM3", + "title": "Trend of mean calib digit energy M3", + "varexp": "CalibDigitEnergyM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_calibDigEnergyM4", + "title": "Trend of mean calib digit energy M4", + "varexp": "CalibDigitEnergyM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_digEnergyM2", + "title": "Trend of mean digit energy M2", + "varexp": "DigitEnergyM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:100" + }, + { + "name": "mean_of_digEnergyM3", + "title": "Trend of mean digit energy M3", + "varexp": "DigitEnergyM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:100" + }, + { + "name": "mean_of_digEnergyM4", + "title": "Trend of mean digit energy M4", + "varexp": "DigitEnergyM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:100" + }, + { + "name": "mean_of_cluSizeM2", + "title": "Trend of mean cluster size M2", + "varexp": "NDigitsInClusterM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster size:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluSizeM3", + "title": "Trend of mean cluster size M3", + "varexp": "NDigitsInClusterM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster size:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluSizeM4", + "title": "Trend of mean cluster size M4", + "varexp": "NDigitsInClusterM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster size:time", + "graphYRange": "0:10" + } + ] + }, + "QualityTask": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QcCommon", + "detectorName": "CPV", + "qualityGroups": [ + { + "name": "global", + "title": "GLOBAL CPV QUALITY", + "path": "CPV/QO/GlobalQuality", + "ignoreQualitiesDetails": [ + "Null", + "Good", + "Medium", + "Bad" + ], + "inputObjects": [ + { + "name": "GlobalQuality", + "title": "Global CPV Quality", + "messageBad": "Inform CPV on-call", + "messageMedium": "Not enough statistics in some histograms", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty!!!" + } + ] + }, + { + "name": "details", + "title": "CPV DETAILS", + "path": "CPV/QO", + "ignoreQualitiesDetails": [], + "inputObjects": [ + { + "name": "DigitsIncrease", + "title": "Number of digits increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "ClustersIncrease", + "title": "Number of clusters increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "DigitsCheck", + "title": "Digit occupancy check", + "messageBad": "Inform CPV on-call", + "messageGood": "Ok", + "messageNull": "" + }, + { + "name": "ClustersCheck", + "title": "Cluster size check", + "messageBad": "Inform CPV on-call", + "messageMedium": "Inform CPV on-call", + "messageNull": "" + }, + { + "name": "CalibDigitsCheck", + "title": "CalibDigit amplitude check", + "messageBad": "Inform CPV on-call", + "messageMedium": "Inform CPV on-call", + "messageNull": "" + }, + { + "name": "ErrorsCheck", + "title": "Errors presence check", + "messageBad": "Inform CPV on-call", + "messageMedium": "Inform CPV on-call", + "messageNull": "" + } + ] + } + ], + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:CPV/QO/GlobalQuality/GlobalQuality" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "aggregators": { + "GlobalQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Check", + "name": "DigitsIncrease" + }, + { + "type": "Check", + "name": "ClustersIncrease" + }, + { + "type": "Check", + "name": "DigitsCheck" + }, + { + "type": "Check", + "name": "ClustersCheck" + }, + { + "type": "Check", + "name": "CalibDigitsCheck" + }, + { + "type": "Check", + "name": "ErrorsCheck" + } + ] + } + } + }, + "dataSamplingPolicies": [] +} \ No newline at end of file diff --git a/Modules/CPV/etc/cpv-physics-qcmn-epn.json b/Modules/CPV/etc/cpv-physics-qcmn-epn.json new file mode 100644 index 0000000000..8f9b6a598c --- /dev/null +++ b/Modules/CPV/etc/cpv-physics-qcmn-epn.json @@ -0,0 +1,540 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ali-qcdb.cern.ch:8083", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "influxdb-unix:///tmp/telegraf.sock" + }, + "consul": { + "url": "http://localhost:8500" + }, + "conditionDB": { + "url": "o2-ccdb.internal" + }, + "bookkeeping": { + "url": "ali-bookkeeping:4001" + } + }, + "tasks": { + "PhysicsOnEPNs": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsTask", + "moduleName": "QcCPV", + "detectorName": "CPV", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "digits:CPV/DIGITS/0;dtrigrec:CPV/DIGITTRIGREC/0;clusters:CPV/CLUSTERS/0;ctrigrec:CPV/CLUSTERTRIGRECS/0;calibdigits:CPV/CALIBDIGITS/0;rawerrors:CPV/RAWHWERRORS/0;peds:CPV/CPV_Pedestals;badmap:CPV/CPV_BadMap;gains:CPV/CPV_Gains" + }, + "taskParameters": { + "ccdbCheckInterval": "10", + "isAsyncMode": "0" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "alio2-cr1-qme07.cern.ch", + "remotePort": "47768", + "mergingMode": "delta", + "localControl": "odc" + } + }, + "checks": { + "ClustersIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "CPV", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "ClusterMapM2", + "ClusterMapM3", + "ClusterMapM4" + ] + } + ] + }, + "DigitsIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "CPV", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "DigitMapM2", + "DigitMapM3", + "DigitMapM4" + ] + } + ] + }, + "DigitsCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "DigitsInEventM2", + "DigitsInEventM3", + "DigitsInEventM4", + "DigitMapM2", + "DigitMapM3", + "DigitMapM4", + "DigitOccuranceM2", + "DigitOccuranceM3", + "DigitOccuranceM4" + ] + } + ], + "checkParameters": { + "mMinEventsToCheckDigitMap2": "10000", + "mMinEventsToCheckDigitMap3": "10000", + "mMinEventsToCheckDigitMap4": "10000", + "mNCold3GassiplexAllowed2": "30", + "mNCold3GassiplexAllowed3": "10", + "mNCold3GassiplexAllowed4": "50", + "mNHot3GassiplexAllowed2": "10", + "mNHot3GassiplexAllowed3": "10", + "mNHot3GassiplexAllowed4": "20", + "mHot3GassiplexCriterium2": "2.5", + "mHot3GassiplexCriterium3": "2.5", + "mHot3GassiplexCriterium4": "2.5", + "mCold3GassiplexCriterium2": "0.3", + "mCold3GassiplexCriterium3": "0.3", + "mCold3GassiplexCriterium4": "0.3", + "mMinDigitsPerEvent2": "50", + "mMinDigitsPerEvent3": "50", + "mMinDigitsPerEvent4": "50", + "mMaxDigitsPerEvent2": "100", + "mMaxDigitsPerEvent3": "100", + "mMaxDigitsPerEvent4": "100", + "mHot3GassiplexOccurance2": "0.05", + "mHot3GassiplexOccurance3": "0.05", + "mHot3GassiplexOccurance4": "0.05", + "mCold3GassiplexOccurance2": "0.000001", + "mCold3GassiplexOccurance3": "0.000001", + "mCold3GassiplexOccurance4": "0.000001" + } + }, + "ClustersCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "NDigitsInClusterM2", + "NDigitsInClusterM3", + "NDigitsInClusterM4", + "ClusterTotEnergyM2", + "ClusterTotEnergyM3", + "ClusterTotEnergyM4" + ] + } + ], + "checkParameters": { + "mMinEventsToCheckClusters2": "10000", + "mMinEventsToCheckClusters3": "10000", + "mMinEventsToCheckClusters4": "10000", + "mCluEnergyRangeL2": "100", + "mCluEnergyRangeL3": "100", + "mCluEnergyRangeL4": "100", + "mCluEnergyRangeR2": "2000", + "mCluEnergyRangeR3": "2000", + "mCluEnergyRangeR4": "2000", + "mMinCluEnergyMean2": "50", + "mMinCluEnergyMean3": "50", + "mMinCluEnergyMean4": "50", + "mMaxCluEnergyMean2": "400", + "mMaxCluEnergyMean3": "400", + "mMaxCluEnergyMean4": "400", + "mMinClusterSize2": "1.5", + "mMinClusterSize3": "1.7", + "mMinClusterSize4": "1.5", + "mMaxClusterSize2": "10.", + "mMaxClusterSize3": "10.", + "mMaxClusterSize4": "10." + } + }, + "CalibDigitsCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "CalibDigitEnergyM2", + "CalibDigitEnergyM3", + "CalibDigitEnergyM4" + ] + } + ], + "checkParameters": { + "mAmplitudeRangeL2": "10", + "mAmplitudeRangeL3": "10", + "mAmplitudeRangeL4": "10", + "mAmplitudeRangeR2": "1000", + "mAmplitudeRangeR3": "1000", + "mAmplitudeRangeR4": "1000", + "mMinEventsToCheckAmplitude2": "1000", + "mMinEventsToCheckAmplitude3": "1000", + "mMinEventsToCheckAmplitude4": "1000", + "mMinAmplitudeMean2": "50", + "mMinAmplitudeMean3": "50", + "mMinAmplitudeMean4": "50", + "mMaxAmplitudeMean2": "400", + "mMaxAmplitudeMean3": "400", + "mMaxAmplitudeMean4": "400" + } + }, + "ErrorsCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "ErrorTypeOccurance" + ] + } + ], + "checkParameters": { + "mErrorOccuranceThreshold18": "0.1" + } + } + }, + "postprocessing": { + "PhysicsTrending": { + "active": "true", + "#taskName": "PhysicsOnEPNs", + "": "use the taskName in order to post output to CPV/MO/TaskName", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcCommon", + "detectorName": "CPV", + "resumeTrend": "false", + "producePlotsOnUpdate": "true", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:CPV/MO/PhysicsOnEPNs/NDigitsInEventM2M3M4" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "CPV/MO/PhysicsOnEPNs", + "names": [ + "NDigitsInEventM2M3M4", + "NClustersInEventM2M3M4", + "ClusterTotEnergyM2", + "ClusterTotEnergyM3", + "ClusterTotEnergyM4", + "CalibDigitEnergyM2", + "CalibDigitEnergyM3", + "CalibDigitEnergyM4", + "DigitEnergyM2", + "DigitEnergyM3", + "DigitEnergyM4", + "NDigitsInClusterM2", + "NDigitsInClusterM3", + "NDigitsInClusterM4" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_n_digits", + "title": "Trend of mean number of digits per event", + "varexp": "NDigitsInEventM2M3M4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean number of digits:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_n_clusters", + "title": "Trend of mean number of clusters per event", + "varexp": "NClustersInEventM2M3M4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean number of clusters:time", + "graphYRange": "0:50" + }, + { + "name": "mean_of_cluEnergyM2", + "title": "Trend of mean cluster energy M2", + "varexp": "ClusterTotEnergyM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster energy:time", + "graphYRange": "0:150" + }, + { + "name": "mean_of_cluEnergyM3", + "title": "Trend of mean cluster energy M3", + "varexp": "ClusterTotEnergyM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster energy:time", + "graphYRange": "0:150" + }, + { + "name": "mean_of_cluEnergyM4", + "title": "Trend of mean cluster energy M4", + "varexp": "ClusterTotEnergyM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster energy:time", + "graphYRange": "0:150" + }, + { + "name": "mean_of_calibDigEnergyM2", + "title": "Trend of mean calib digit energy M2", + "varexp": "CalibDigitEnergyM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_calibDigEnergyM3", + "title": "Trend of mean calib digit energy M3", + "varexp": "CalibDigitEnergyM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_calibDigEnergyM4", + "title": "Trend of mean calib digit energy M4", + "varexp": "CalibDigitEnergyM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_digEnergyM2", + "title": "Trend of mean digit energy M2", + "varexp": "DigitEnergyM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:100" + }, + { + "name": "mean_of_digEnergyM3", + "title": "Trend of mean digit energy M3", + "varexp": "DigitEnergyM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:100" + }, + { + "name": "mean_of_digEnergyM4", + "title": "Trend of mean digit energy M4", + "varexp": "DigitEnergyM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:100" + }, + { + "name": "mean_of_cluSizeM2", + "title": "Trend of mean cluster size M2", + "varexp": "NDigitsInClusterM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster size:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluSizeM3", + "title": "Trend of mean cluster size M3", + "varexp": "NDigitsInClusterM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster size:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluSizeM4", + "title": "Trend of mean cluster size M4", + "varexp": "NDigitsInClusterM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster size:time", + "graphYRange": "0:10" + } + ] + }, + "QualityTask": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QcCommon", + "detectorName": "CPV", + "qualityGroups": [ + { + "name": "global", + "title": "GLOBAL CPV QUALITY", + "path": "CPV/QO/GlobalQuality", + "ignoreQualitiesDetails": [ + "Null", + "Good", + "Medium", + "Bad" + ], + "inputObjects": [ + { + "name": "GlobalQuality", + "title": "Global CPV Quality", + "messageBad": "Inform CPV on-call", + "messageMedium": "Not enough statistics in some histograms", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty!!!" + } + ] + }, + { + "name": "details", + "title": "CPV DETAILS", + "path": "CPV/QO", + "ignoreQualitiesDetails": [], + "inputObjects": [ + { + "name": "DigitsIncrease", + "title": "Number of digits increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "ClustersIncrease", + "title": "Number of clusters increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "DigitsCheck", + "title": "Digit occupancy check", + "messageBad": "Inform CPV on-call", + "messageGood": "Ok", + "messageNull": "" + }, + { + "name": "ClustersCheck", + "title": "Cluster size check", + "messageBad": "Inform CPV on-call", + "messageMedium": "Inform CPV on-call", + "messageNull": "" + }, + { + "name": "CalibDigitsCheck", + "title": "CalibDigit amplitude check", + "messageBad": "Inform CPV on-call", + "messageMedium": "Inform CPV on-call", + "messageNull": "" + }, + { + "name": "ErrorsCheck", + "title": "Errors presence check", + "messageBad": "Inform CPV on-call", + "messageMedium": "Inform CPV on-call", + "messageNull": "" + } + ] + } + ], + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:CPV/QO/GlobalQuality/GlobalQuality" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "aggregators": { + "GlobalQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Check", + "name": "DigitsIncrease" + }, + { + "type": "Check", + "name": "ClustersIncrease" + }, + { + "type": "Check", + "name": "DigitsCheck" + }, + { + "type": "Check", + "name": "ClustersCheck" + }, + { + "type": "Check", + "name": "CalibDigitsCheck" + }, + { + "type": "Check", + "name": "ErrorsCheck" + } + ] + } + } + }, + "dataSamplingPolicies": [] +} \ No newline at end of file diff --git a/Modules/CPV/etc/physics-full-no-sampling.json b/Modules/CPV/etc/physics-full-no-sampling.json new file mode 100644 index 0000000000..0e65c36de1 --- /dev/null +++ b/Modules/CPV/etc/physics-full-no-sampling.json @@ -0,0 +1,535 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "PhysicsOnEPNs": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsTask", + "moduleName": "QcCPV", + "detectorName": "CPV", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "digits:CPV/DIGITS/0;dtrigrec:CPV/DIGITTRIGREC/0;clusters:CPV/CLUSTERS/0;ctrigrec:CPV/CLUSTERTRIGRECS/0;calibdigits:CPV/CALIBDIGITS/0;rawerrors:CPV/RAWHWERRORS/0;peds:CPV/CPV_Pedestals;badmap:CPV/CPV_BadMap;gains:CPV/CPV_Gains" + }, + "taskParameters": { + "ccdbCheckInterval": "1000", + "isAsyncMode": "0" + }, + "location": "remote", + "saveObjectsToFile": "MOs.root" + } + }, + "checks": { + "ClustersIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "CPV", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "ClusterMapM2", + "ClusterMapM3", + "ClusterMapM4" + ] + } + ] + }, + "DigitsIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "CPV", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "DigitMapM2", + "DigitMapM3", + "DigitMapM4" + ] + } + ] + }, + "DigitsCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "DigitsInEventM2", + "DigitsInEventM3", + "DigitsInEventM4", + "DigitMapM2", + "DigitMapM3", + "DigitMapM4", + "DigitOccuranceM2", + "DigitOccuranceM3", + "DigitOccuranceM4" + ] + } + ], + "checkParameters": { + "mMinEventsToCheckDigitMap2": "10000", + "mMinEventsToCheckDigitMap3": "10000", + "mMinEventsToCheckDigitMap4": "10000", + "mNCold3GassiplexAllowed2": "30", + "mNCold3GassiplexAllowed3": "10", + "mNCold3GassiplexAllowed4": "17", + "mNHot3GassiplexAllowed2": "10", + "mNHot3GassiplexAllowed3": "10", + "mNHot3GassiplexAllowed4": "14", + "mHot3GassiplexCriterium2": "2.5", + "mHot3GassiplexCriterium3": "2.5", + "mHot3GassiplexCriterium4": "2.5", + "mCold3GassiplexCriterium2": "0.3", + "mCold3GassiplexCriterium3": "0.3", + "mCold3GassiplexCriterium4": "0.3", + "mMinDigitsPerEvent2": "50", + "mMinDigitsPerEvent3": "50", + "mMinDigitsPerEvent4": "50", + "mMaxDigitsPerEvent2": "100", + "mMaxDigitsPerEvent3": "100", + "mMaxDigitsPerEvent4": "100", + "mHot3GassiplexOccurance2": "0.05", + "mHot3GassiplexOccurance3": "0.05", + "mHot3GassiplexOccurance4": "0.05", + "mCold3GassiplexOccurance2": "0.000001", + "mCold3GassiplexOccurance3": "0.000001", + "mCold3GassiplexOccurance4": "0.000001" + } + }, + "ClustersCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "NDigitsInClusterM2", + "NDigitsInClusterM3", + "NDigitsInClusterM4", + "ClusterTotEnergyM2", + "ClusterTotEnergyM3", + "ClusterTotEnergyM4" + ] + } + ], + "checkParameters": { + "mMinEventsToCheckClusters2": "10000", + "mMinEventsToCheckClusters3": "10000", + "mMinEventsToCheckClusters4": "10000", + "mCluEnergyRangeL2": "100", + "mCluEnergyRangeL3": "100", + "mCluEnergyRangeL4": "100", + "mCluEnergyRangeR2": "2000", + "mCluEnergyRangeR3": "2000", + "mCluEnergyRangeR4": "2000", + "mMinCluEnergyMean2": "50", + "mMinCluEnergyMean3": "50", + "mMinCluEnergyMean4": "50", + "mMaxCluEnergyMean2": "400", + "mMaxCluEnergyMean3": "400", + "mMaxCluEnergyMean4": "400", + "mMinClusterSize2": "1.5", + "mMinClusterSize3": "1.7", + "mMinClusterSize4": "1.7", + "mMaxClusterSize2": "2.5", + "mMaxClusterSize3": "2.5", + "mMaxClusterSize4": "2.5" + } + }, + "CalibDigitsCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "CalibDigitEnergyM2", + "CalibDigitEnergyM3", + "CalibDigitEnergyM4" + ] + } + ], + "checkParameters": { + "mAmplitudeRangeL2": "100", + "mAmplitudeRangeL3": "100", + "mAmplitudeRangeL4": "100", + "mAmplitudeRangeR2": "1000", + "mAmplitudeRangeR3": "1000", + "mAmplitudeRangeR4": "1000", + "mMinEventsToCheckAmplitude2": "10000", + "mMinEventsToCheckAmplitude3": "10000", + "mMinEventsToCheckAmplitude4": "10000", + "mMinAmplitudeMean2": "50", + "mMinAmplitudeMean3": "50", + "mMinAmplitudeMean4": "50", + "mMaxAmplitudeMean2": "400", + "mMaxAmplitudeMean3": "400", + "mMaxAmplitudeMean4": "400" + } + }, + "ErrorsCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Task", + "name": "PhysicsOnEPNs", + "MOs": [ + "ErrorTypeOccurance" + ] + } + ], + "checkParameters": { + "mErrorOccuranceThreshold18": "0.1" + } + } + }, + "postprocessing": { + "PhysicsTrending": { + "active": "true", + "#taskName": "PhysicsOnEPNs", + "": "use the taskName in order to post output to CPV/MO/TaskName", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcCommon", + "detectorName": "CPV", + "resumeTrend": "false", + "producePlotsOnUpdate": "true", + "initTrigger": [ + "newobject:qcdb:CPV/MO/PhysicsOnEPNs/NDigitsInEventM2M3M4" + ], + "updateTrigger": [ + "newobject:qcdb:CPV/MO/PhysicsOnEPNs/NDigitsInEventM2M3M4" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "CPV/MO/PhysicsOnEPNs", + "names": [ + "NDigitsInEventM2M3M4", + "NClustersInEventM2M3M4", + "ClusterTotEnergyM2", + "ClusterTotEnergyM3", + "ClusterTotEnergyM4", + "CalibDigitEnergyM2", + "CalibDigitEnergyM3", + "CalibDigitEnergyM4", + "DigitEnergyM2", + "DigitEnergyM3", + "DigitEnergyM4", + "NDigitsInClusterM2", + "NDigitsInClusterM3", + "NDigitsInClusterM4" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_n_digits", + "title": "Trend of mean number of digits per event", + "varexp": "NDigitsInEventM2M3M4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean number of digits:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_n_clusters", + "title": "Trend of mean number of clusters per event", + "varexp": "NClustersInEventM2M3M4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean number of clusters:time", + "graphYRange": "0:50" + }, + { + "name": "mean_of_cluEnergyM2", + "title": "Trend of mean cluster energy M2", + "varexp": "ClusterTotEnergyM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster energy:time", + "graphYRange": "0:150" + }, + { + "name": "mean_of_cluEnergyM3", + "title": "Trend of mean cluster energy M3", + "varexp": "ClusterTotEnergyM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster energy:time", + "graphYRange": "0:150" + }, + { + "name": "mean_of_cluEnergyM4", + "title": "Trend of mean cluster energy M4", + "varexp": "ClusterTotEnergyM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster energy:time", + "graphYRange": "0:150" + }, + { + "name": "mean_of_calibDigEnergyM2", + "title": "Trend of mean calib digit energy M2", + "varexp": "CalibDigitEnergyM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_calibDigEnergyM3", + "title": "Trend of mean calib digit energy M3", + "varexp": "CalibDigitEnergyM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_calibDigEnergyM4", + "title": "Trend of mean calib digit energy M4", + "varexp": "CalibDigitEnergyM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:300" + }, + { + "name": "mean_of_digEnergyM2", + "title": "Trend of mean digit energy M2", + "varexp": "DigitEnergyM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:100" + }, + { + "name": "mean_of_digEnergyM3", + "title": "Trend of mean digit energy M3", + "varexp": "DigitEnergyM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:100" + }, + { + "name": "mean_of_digEnergyM4", + "title": "Trend of mean digit energy M4", + "varexp": "DigitEnergyM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean digit energy:time", + "graphYRange": "0:100" + }, + { + "name": "mean_of_cluSizeM2", + "title": "Trend of mean cluster size M2", + "varexp": "NDigitsInClusterM2.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster size:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluSizeM3", + "title": "Trend of mean cluster size M3", + "varexp": "NDigitsInClusterM3.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster size:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluSizeM4", + "title": "Trend of mean cluster size M4", + "varexp": "NDigitsInClusterM4.mean:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Mean cluster size:time", + "graphYRange": "0:10" + } + ] + }, + "QualityTask": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QcCommon", + "detectorName": "CPV", + "qualityGroups": [ + { + "name": "global", + "title": "GLOBAL CPV QUALITY", + "path": "CPV/QO/GlobalQuality", + "ignoreQualitiesDetails": [ + "Null", + "Good", + "Medium", + "Bad" + ], + "inputObjects": [ + { + "name": "GlobalQuality", + "title": "Global CPV Quality", + "messageBad": "Inform CPV on-call", + "messageMedium": "Not enough statistics in some histograms", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty!!!" + } + ] + }, + { + "name": "details", + "title": "CPV DETAILS", + "path": "CPV/QO", + "ignoreQualitiesDetails": [], + "inputObjects": [ + { + "name": "DigitsIncrease", + "title": "Number of digits increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "ClustersIncrease", + "title": "Number of clusters increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "DigitsCheck", + "title": "Digit occupancy check", + "messageBad": "Inform CPV on-call", + "messageGood": "Ok", + "messageNull": "" + }, + { + "name": "ClustersCheck", + "title": "Cluster size check", + "messageBad": "Inform CPV on-call", + "messageMedium": "Inform CPV on-call", + "messageNull": "" + }, + { + "name": "CalibDigitsCheck", + "title": "CalibDigit amplitude check", + "messageBad": "Inform CPV on-call", + "messageMedium": "Inform CPV on-call", + "messageNull": "" + }, + { + "name": "ErrorsCheck", + "title": "Errors presence check", + "messageBad": "Inform CPV on-call", + "messageMedium": "Inform CPV on-call", + "messageNull": "" + } + ] + } + ], + "initTrigger": [ + "newobject:qcdb:CPV/QO/GlobalQuality/GlobalQuality" + ], + "updateTrigger": [ + "newobject:qcdb:CPV/QO/GlobalQuality/GlobalQuality" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "aggregators": { + "GlobalQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "CPV", + "dataSource": [ + { + "type": "Check", + "name": "DigitsIncrease" + }, + { + "type": "Check", + "name": "ClustersIncrease" + }, + { + "type": "Check", + "name": "DigitsCheck" + }, + { + "type": "Check", + "name": "ClustersCheck" + }, + { + "type": "Check", + "name": "CalibDigitsCheck" + }, + { + "type": "Check", + "name": "ErrorsCheck" + } + ] + } + } + }, + "dataSamplingPolicies": [] +} \ No newline at end of file diff --git a/Modules/CPV/etc/physics-taskAndCheck-no-sampling.json b/Modules/CPV/etc/physics-taskAndCheck-no-sampling.json new file mode 100644 index 0000000000..78b7d0f63d --- /dev/null +++ b/Modules/CPV/etc/physics-taskAndCheck-no-sampling.json @@ -0,0 +1,108 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "CPVPhysicsTask": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsTask", + "moduleName": "QcCPV", + "detectorName": "CPV", + "cycleDurationSeconds": "10", + "maxNumberCycles": "100", + "": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "direct", + "query": "digits:CPV/DIGITS/0;dtrigrec:CPV/DIGITTRIGREC/0;clusters:CPV/CLUSTERS/0;ctrigrec:CPV/CLUSTERTRIGRECS/0;calibdigits:CPV/CALIBDIGITS/0;rawerrors:CPV/RAWHWERRORS/0;peds:CPV/CPV_Pedestals;badmap:CPV/CPV_BadMap;gains:CPV/CPV_Gains" + }, + "taskParameters": { + "ccdbCheckInterval": "1000", + "isAsyncMode": "1" + }, + "location": "remote", + "saveObjectsToFile": "MOs.root", "": "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "checks": { + "PhysicsCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [{ + "type": "Task", + "name": "CPVPhysicsTask", + "MOs": [ ] + }], + "checkParameters": { + "mAmplitudeRangeL2": "20", + "mAmplitudeRangeL3": "20", + "mAmplitudeRangeL4": "20", + "mAmplitudeRangeR2": "1000", + "mAmplitudeRangeR3": "1000", + "mAmplitudeRangeR4": "1000", + "mMinEventsToFit2": "1000", + "mMinEventsToFit3": "1000", + "mMinEventsToFit4": "1000", + "mMinAmplification2": "5", + "mMinAmplification3": "5", + "mMinAmplification4": "5", + "mMaxAmplification2": "500", + "mMaxAmplification3": "500", + "mMaxAmplification4": "500", + "mMinClusterSize2": "2", + "mMinClusterSize3": "2", + "mMinClusterSize4": "2", + "mMaxClusterSize2": "5", + "mMaxClusterSize3": "5", + "mMaxClusterSize4": "5", + "mMinEventsToCheckDigitMap2": "10000", + "mMinEventsToCheckDigitMap3": "10000", + "mMinEventsToCheckDigitMap4": "10000", + "mStripPopulationDeviationAllowed2": "1.2", + "mStripPopulationDeviationAllowed3": "30", + "mStripPopulationDeviationAllowed4": "30", + "mNBadStripsPerQuarterAllowed2": "5", + "mNBadStripsPerQuarterAllowed3": "10", + "mNBadStripsPerQuarterAllowed4": "10", + "mNCold3GassiplexAllowed2": "1", + "mNCold3GassiplexAllowed3": "10", + "mNCold3GassiplexAllowed4": "10", + "mNHot3GassiplexAllowed2": "1", + "mNHot3GassiplexAllowed3": "10", + "mNHot3GassiplexAllowed4": "10", + "mHot3GassiplexCriterium2": "1", + "mHot3GassiplexCriterium3": "10", + "mHot3GassiplexCriterium4": "10", + "mCold3GassiplexCriterium2": "0.9", + "mCold3GassiplexCriterium3": "0.1", + "mCold3GassiplexCriterium4": "0.1" + } + } + } + }, + "dataSamplingPolicies": [ + + ] +} diff --git a/Modules/CPV/etc/read-raw-from-file/CPVraw.cfg b/Modules/CPV/etc/read-raw-from-file/CPVraw.cfg new file mode 100644 index 0000000000..e4fc05678d --- /dev/null +++ b/Modules/CPV/etc/read-raw-from-file/CPVraw.cfg @@ -0,0 +1,10 @@ +#[defaults] +#dataOrigin = CPV +#dataDescription = RAWDATA +#readoutCard = RORC + +[input-CPV-0] +dataOrigin = CPV +dataDescription = RAWDATA +readoutCard = CRU +filePath = /data/cpv-raw-data/data_p2_21_012.raw diff --git a/Modules/CPV/etc/read-raw-from-file/pedestal-task-no-sampling.json b/Modules/CPV/etc/read-raw-from-file/pedestal-task-no-sampling.json new file mode 100644 index 0000000000..5823154989 --- /dev/null +++ b/Modules/CPV/etc/read-raw-from-file/pedestal-task-no-sampling.json @@ -0,0 +1,105 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "CPVPedestalTask": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PedestalTask", + "moduleName": "QcCPV", + "detectorName": "CPV", + "cycleDurationSeconds": "10", + "": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "direct", + "query": "digits:CPV/DIGITS/0;dtrigrec:CPV/DIGITTRIGREC/0" + }, + "taskParameters": { + "cutOnMinAmplitude": "0" + }, + "location": "remote", + "saveObjectsToFile": "MOs.root", "": "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "checks": { + "CPVPedestalCheck": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PedestalCheck", + "moduleName": "QcCPV", + "policy": "OnAny", + "detectorName": "CPV", + "dataSource": [{ + "type": "Task", + "name": "CPVPedestalTask", + "MOs": [ + "PedestalValueM2", + "PedestalValueM3", + "PedestalValueM4", + "PedestalSigmaM2", + "PedestalSigmaM3", + "PedestalSigmaM4", + "PedestalEfficiencyM2", + "PedestalEfficiencyM3", + "PedestalEfficiencyM4" + ] + }], + "checkParameters": { + "mMinGoodPedestalValueM2": "1", + "mMinGoodPedestalValueM3": "1", + "mMinGoodPedestalValueM4": "1", + "": "", + "mMaxGoodPedestalSigmaM2": "2", + "mMaxGoodPedestalSigmaM3": "5", + "mMaxGoodPedestalSigmaM4": "2", + "": "", + "mMinGoodPedestalEfficiencyM2": "0.75", + "mMinGoodPedestalEfficiencyM3": "0.75", + "mMinGoodPedestalEfficiencyM4": "0.75", + "": "", + "mMaxGoodPedestalEfficiencyM2": "1.0", + "mMaxGoodPedestalEfficiencyM3": "1.0", + "mMaxGoodPedestalEfficiencyM4": "1.0", + "": "", + "mToleratedBadPedestalValueChannelsM2": "100", + "mToleratedBadPedestalValueChannelsM3": "200", + "mToleratedBadPedestalValueChannelsM4": "100", + "": "", + "mToleratedBadPedestalSigmaChannelsM2": "300", + "mToleratedBadPedestalSigmaChannelsM3": "400", + "mToleratedBadPedestalSigmaChannelsM4": "500", + "": "", + "mToleratedBadChannelsM2": "200", + "mToleratedBadChannelsM3": "200", + "mToleratedBadChannelsM4": "200", + "": "", + "mToleratedBadPedestalEfficiencyChannelsM2": "100", + "mToleratedBadPedestalEfficiencyChannelsM3": "100", + "mToleratedBadPedestalEfficiencyChannelsM4": "100" + } + } + } + }, + "dataSamplingPolicies": [ + + ] +} diff --git a/Modules/CPV/etc/read-raw-from-file/physics-taskOnly-no-sampling.json b/Modules/CPV/etc/read-raw-from-file/physics-taskOnly-no-sampling.json new file mode 100644 index 0000000000..dab6cb14e4 --- /dev/null +++ b/Modules/CPV/etc/read-raw-from-file/physics-taskOnly-no-sampling.json @@ -0,0 +1,48 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "CPVPhysicsTask": { + "active": "true", + "className": "o2::quality_control_modules::cpv::PhysicsTask", + "moduleName": "QcCPV", + "detectorName": "CPV", + "cycleDurationSeconds": "10000", + "maxNumberCycles": "100", + "": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "direct", + "query": "digits:CPV/DIGITS/0;dtrigrec:CPV/DIGITTRIGREC/0;clusters:CPV/CLUSTERS/0;ctrigrec:CPV/CLUSTERTRIGRECS/0;calibdigits:CPV/CALIBDIGITS/0;rawerrors:CPV/RAWHWERRORS/0;peds:CPV/CPV_Pedestals;badmap:CPV/CPV_BadMap;gains:CPV/CPV_Gains" + }, + "taskParameters": { + "": "" + }, + "location": "remote", + "saveObjectsToFile": "MOs.root", + "": "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "dataSamplingPolicies": [] + } +} \ No newline at end of file diff --git a/Modules/CPV/etc/read-raw-from-file/readme b/Modules/CPV/etc/read-raw-from-file/readme new file mode 100644 index 0000000000..a01d78f219 --- /dev/null +++ b/Modules/CPV/etc/read-raw-from-file/readme @@ -0,0 +1,10 @@ +0) alienv enter qcg/latest + +1) start raw file reader with command + o2-raw-file-reader-workflow --input-conf CPVraw.cfg + +2) raw data stream is produced by previous command. Try to connect to it with raw to digit converter: + o2-raw-file-reader-workflow --input-conf CPVraw.cfg | o2-cpv-reco-workflow --input-type raw --output-type digits --disable-mc --pedestal=on --disable-root-output + +3) connect qc: + o2-raw-file-reader-workflow --input-conf CPVraw.cfg | o2-cpv-reco-workflow --input-type raw --output-type digits --disable-mc --pedestal=on --disable-root-output | o2-qc --config json://pedestal-task-no-sampling.json diff --git a/Modules/CPV/include/CPV/LinkDef.h b/Modules/CPV/include/CPV/LinkDef.h new file mode 100644 index 0000000000..354419cfdf --- /dev/null +++ b/Modules/CPV/include/CPV/LinkDef.h @@ -0,0 +1,12 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::cpv::PedestalTask + ; +#pragma link C++ class o2::quality_control_modules::cpv::PedestalCheck + ; +#pragma link C++ class o2::quality_control_modules::cpv::PhysicsTask + ; +#pragma link C++ class o2::quality_control_modules::cpv::PhysicsCheck + ; +#pragma link C++ class o2::quality_control_modules::cpv::IntensiveTH2F + ; + +#endif diff --git a/Modules/CPV/include/CPV/PedestalCheck.h b/Modules/CPV/include/CPV/PedestalCheck.h new file mode 100644 index 0000000000..fe36aa6111 --- /dev/null +++ b/Modules/CPV/include/CPV/PedestalCheck.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalCheck.h +/// \author Sergey Evdokimov +/// + +#ifndef QC_MODULE_CPV_CPVPEDESTALCHECK_H +#define QC_MODULE_CPV_CPVPEDESTALCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::cpv +{ + +/// \brief CPV PedestalCheck +/// \author Sergey Evdokimov +/// +class PedestalCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + PedestalCheck() = default; + /// Destructor + ~PedestalCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + int getRunNumberFromMO(std::shared_ptr mo); + + // configurable parameters and their default values + // see config example in Modules/CPV/etc/pedestal-task-no-sampling.json + int mMinGoodPedestalValueM[3] = { 1, 1, 1 }; + float mMaxGoodPedestalSigmaM[3] = { 2., 2., 2. }; + float mMinGoodPedestalEfficiencyM[3] = { 0.7, 0.7, 0.7 }; + float mMaxGoodPedestalEfficiencyM[3] = { 1., 1., 1. }; + int mToleratedBadPedestalValueChannelsM[3] = { 10, 10, 10 }; // pedestal value < mMinGoodPedestalValue or > 512 + int mToleratedBadChannelsM[3] = { 20, 20, 20 }; // double peaks or empty or channels + int mToleratedBadPedestalSigmaChannelsM[3] = { 20, 20, 20 }; // pedestal value < mMinGoodPedestalValue or > 512 + int mToleratedBadPedestalEfficiencyChannelsM[3] = { 20, 20, 20 }; // efficiency < min or > max + bool mIsConfigured = false; // had configure() been called already? + + ClassDefOverride(PedestalCheck, 2); +}; + +} // namespace o2::quality_control_modules::cpv + +#endif // QC_MODULE_CPV_CPVPEDESTALCHECK_H diff --git a/Modules/CPV/include/CPV/PedestalTask.h b/Modules/CPV/include/CPV/PedestalTask.h new file mode 100644 index 0000000000..f7c6e45366 --- /dev/null +++ b/Modules/CPV/include/CPV/PedestalTask.h @@ -0,0 +1,139 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalTask.h +/// \author Sergey Evdokimov +/// + +#ifndef QC_MODULE_CPV_CPVPEDESTALTASK_H +#define QC_MODULE_CPV_CPVPEDESTALTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include + +class TH1F; +class TH2F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::cpv +{ + +/// \brief CPV Pedestal Task which processes uncalibrated digits from pedestal runs and produces pedestal monitor objects +/// \author Sergey Evdokimov +class PedestalTask final : public TaskInterface +{ + public: + /// \brief Constructor + PedestalTask(); + /// Destructor + ~PedestalTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void initHistograms(); + void fillDigitsHistograms(); + void resetHistograms(); + + static constexpr short kNHist1D = 27; + enum Histos1D { H1DRawErrors, + H1DInputPayloadSize, + H1DNInputs, + H1DNValidInputs, + H1DNDigitsPerInput, + H1DDigitIds, + H1DPedestalValueM2, + H1DPedestalValueM3, + H1DPedestalValueM4, + H1DPedestalSigmaM2, + H1DPedestalSigmaM3, + H1DPedestalSigmaM4, + H1DPedestalEfficiencyM2, + H1DPedestalEfficiencyM3, + H1DPedestalEfficiencyM4, + H1DPedestalValueInDigitsM2, + H1DPedestalValueInDigitsM3, + H1DPedestalValueInDigitsM4, + H1DPedestalSigmaInDigitsM2, + H1DPedestalSigmaInDigitsM3, + H1DPedestalSigmaInDigitsM4, + H1DPedestalEfficiencyInDigitsM2, + H1DPedestalEfficiencyInDigitsM3, + H1DPedestalEfficiencyInDigitsM4 + }; + + static constexpr short kNHist2D = 34; + enum Histos2D { H2DErrorType, + H2DDigitMapM2, + H2DDigitMapM3, + H2DDigitMapM4, + H2DPedestalValueMapM2, + H2DPedestalValueMapM3, + H2DPedestalValueMapM4, + H2DPedestalSigmaMapM2, + H2DPedestalSigmaMapM3, + H2DPedestalSigmaMapM4, + H2DPedestalEfficiencyMapM2, + H2DPedestalEfficiencyMapM3, + H2DPedestalEfficiencyMapM4, + H2DFEEThresholdsMapM2, + H2DFEEThresholdsMapM3, + H2DFEEThresholdsMapM4, + H2DHighThresholdMapM2, + H2DHighThresholdMapM3, + H2DHighThresholdMapM4, + H2DDeadChanelsMapM2, + H2DDeadChanelsMapM3, + H2DDeadChanelsMapM4, + H2DPedestalNPeaksMapInDigitsM2, + H2DPedestalNPeaksMapInDigitsM3, + H2DPedestalNPeaksMapInDigitsM4, + H2DPedestalValueMapInDigitsM2, + H2DPedestalValueMapInDigitsM3, + H2DPedestalValueMapInDigitsM4, + H2DPedestalSigmaMapInDigitsM2, + H2DPedestalSigmaMapInDigitsM3, + H2DPedestalSigmaMapInDigitsM4, + H2DPedestalEfficiencyMapInDigitsM2, + H2DPedestalEfficiencyMapInDigitsM3, + H2DPedestalEfficiencyMapInDigitsM4 + }; + + int mNEventsTotal = 0; + int mNEventsFromLastFillHistogramsCall; + int mMinNEventsToUpdatePedestals = 1000; ///< min number of events needed to update pedestals + int mRunNumber = 0; ///< Run number of current activity + bool mMonitorPedestalCalibrator = true; ///< monitor results of pedestal calibrator + int mNtimesCCDBPayloadFetched = 0; ///< how many times non-empty CCDB payload fetched + bool mMonitorDigits = false; ///< monitor digits + + std::array mHist1D = { nullptr }; ///< Array of 1D histograms + std::array mHist2D = { nullptr }; ///< Array of 2D histograms + + std::array mHistAmplitudes = { nullptr }; ///< Array of amplitude spectra + std::array mIsUpdatedAmplitude = { false }; ///< Array of isUpdatedAmplitude bools +}; + +} // namespace o2::quality_control_modules::cpv + +#endif // QC_MODULE_CPV_CPVPEDESTALTASK_H diff --git a/Modules/CPV/include/CPV/PhysicsCheck.h b/Modules/CPV/include/CPV/PhysicsCheck.h new file mode 100644 index 0000000000..b40a2bdb29 --- /dev/null +++ b/Modules/CPV/include/CPV/PhysicsCheck.h @@ -0,0 +1,121 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PhysicsCheck.h +/// \author Sergey Evdokimov +/// + +#ifndef QC_MODULE_CPV_CPVPHYSICSCHECK_H +#define QC_MODULE_CPV_CPVPHYSICSCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::cpv +{ + +/// \brief CPV PhysicsCheck +/// \author Sergey Evdokimov +/// +class PhysicsCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + PhysicsCheck() = default; + /// Destructor + ~PhysicsCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + int getRunNumberFromMO(std::shared_ptr mo); + + // amplitude check parameters + float mAmplitudeRangeL[3] = { 20., 20., 20. }; + float mAmplitudeRangeR[3] = { 1000., 1000., 1000. }; + float mMinEventsToCheckAmplitude[3] = { 100, 100, 100 }; + float mMinAmplitudeMean[3] = { 5., 5., 5. }; + float mMaxAmplitudeMean[3] = { 200., 200., 200. }; + // cluster check parameters + float mMinEventsToCheckClusters[3] = { 10., 10., 10. }; + float mMinClusterSize[3] = { 2., 2., 2. }; + float mMaxClusterSize[3] = { 5., 5., 5. }; + float mCluEnergyRangeL[3] = { 20., 20., 20. }; + float mCluEnergyRangeR[3] = { 1000., 1000., 1000. }; + float mMinCluEnergyMean[3] = { 5., 5., 5. }; + float mMaxCluEnergyMean[3] = { 200., 200., 200. }; + // digits check parameters + int mMinEventsToCheckDigitMap[3] = { 10000, 10000, 10000 }; + int mNCold3GassiplexAllowed[3] = { 10, 10, 10 }; + int mNHot3GassiplexAllowed[3] = { 10, 10, 10 }; + float mHot3GassiplexCriterium[3] = { 10., 10., 10. }; + float mCold3GassiplexCriterium[3] = { 0.1, 0.1, 0.1 }; + float mHot3GassiplexOccurance[3] = { 0.1, 0.1, 0.1 }; + float mCold3GassiplexOccurance[3] = { 1.e-6, 1.e-6, 1.e-6 }; + float mMinDigitsPerEvent[3] = { 0., 0., 0 }; + float mMaxDigitsPerEvent[3] = { 300., 300., 300 }; + // errors check parameters + float mErrorOccuranceThreshold[20] = { + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0., + 0. + }; + const char* mErrorLabel[20] = { + "ok", + "no payload", + "rdh decod", + "rdh invalid", + "not cpv rdh", + "no stopbit", + "page not found", + "0 offset to next", + "payload incomplete", + "no cpv header", + "no cpv trailer", + "cpv header invalid", + "cpv trailer invalid", + "segment header err", + "row header error", + "EOE header error", + "pad error", + "unknown word", + "pad address", + "wrong data format" + }; + + bool mIsConfigured = false; // had configure() been called already? + + ClassDefOverride(PhysicsCheck, 2); +}; + +} // namespace o2::quality_control_modules::cpv + +#endif // QC_MODULE_CPV_CPVPHYSICSCHECK_H diff --git a/Modules/CPV/include/CPV/PhysicsTask.h b/Modules/CPV/include/CPV/PhysicsTask.h new file mode 100644 index 0000000000..9d688c97f5 --- /dev/null +++ b/Modules/CPV/include/CPV/PhysicsTask.h @@ -0,0 +1,193 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PhysicsTask.h +/// \author Sergey Evdokimov +/// + +#ifndef QC_MODULE_CPV_CPVPHYSICSTASK_H +#define QC_MODULE_CPV_CPVPHYSICSTASK_H + +#include "QualityControl/TaskInterface.h" +#include "PHOS/TH1Fraction.h" +#include "PHOS/TH2Fraction.h" +#include +#include +#include +#include +#include +#include + +class TH1F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::cpv +{ + +// this is 2D histogram which is not additive: when merge() is being called, it is just updated with new incoming value rather than merging +// update or not is decided by cycle counter: if this->mCycleNumber < incoming.mCycleNumber then *this = incoming +class IntensiveTH2F : public TH2F, public o2::mergers::MergeInterface +{ + public: + /// \brief Constructor. + IntensiveTH2F(const char* name, const char* title, int nbinsx, double xlow, double xup, int nbinsy, double ylow, double yup) + : TH2F(name, title, nbinsx, xlow, xup, nbinsy, ylow, yup) + { + } + IntensiveTH2F() = default; + /// \brief Default destructor + ~IntensiveTH2F() override = default; + + const char* GetName() const override + { + return TH2F::GetName(); + } + + void setCycleNumber(uint32_t cycleNumber) { mCycleNumber = cycleNumber; } + uint32_t getCylceNumber() { return mCycleNumber; } + + void merge(MergeInterface* const other) override + { + if (IntensiveTH2F* cast = dynamic_cast(other); cast != nullptr) { + if (mCycleNumber < cast->mCycleNumber) { + mCycleNumber = cast->mCycleNumber; + for (int iX = 1; iX <= GetNbinsX(); iX++) { + for (int iY = 1; iY <= GetNbinsY(); iY++) { + SetBinContent(iX, iY, cast->GetBinContent(iX, iY)); + } + } + } + } else { + LOG(warn) << "IntensiveTH2F::merge() : problem occured while dynamic_cast(other) for " + << GetName() << ". Result is nullptr. Do nothing this time."; + } + } + + private: + std::string mTreatMeAs = "TH2F"; // the name of the class this object should be considered as when drawing in QCG. + uint32_t mCycleNumber = 0; // cycle number of last udate + + ClassDefOverride(IntensiveTH2F, 1); +}; + +/// \brief Task for CPV Physics monitoring +/// \author Sergey Evdokimov +using Geometry = o2::cpv::Geometry; +using TH1Fraction = o2::quality_control_modules::phos::TH1Fraction; +using TH2Fraction = o2::quality_control_modules::phos::TH2Fraction; +class PhysicsTask final : public TaskInterface +{ + public: + /// \brief Constructor + PhysicsTask() = default; + /// Destructor + ~PhysicsTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void initHistograms(); + void resetHistograms(); + + static constexpr short kNHist1D = 31; + enum Histos1D { H1DBCsFromDigits, + H1DBCsFromClusters, + H1DInputPayloadSize, + H1DNInputs, + H1DNValidInputs, + H1DRawErrors, + H1DNDigitsPerInput, + H1DNClustersPerInput, + H1DNCalibDigitsPerInput, + H1DDigitIds, + H1DCalibDigitIds, + H1DDigitsInEventM2, + H1DDigitsInEventM3, + H1DDigitsInEventM4, + H1DDigitsInEventM2M3M4, + H1DDigitEnergyM2, + H1DDigitEnergyM3, + H1DDigitEnergyM4, + H1DCalibDigitEnergyM2, + H1DCalibDigitEnergyM3, + H1DCalibDigitEnergyM4, + H1DClustersInEventM2, + H1DClustersInEventM3, + H1DClustersInEventM4, + H1DClustersInEventM2M3M4, + H1DClusterTotEnergyM2, + H1DClusterTotEnergyM3, + H1DClusterTotEnergyM4, + H1DNDigitsInClusterM2, + H1DNDigitsInClusterM3, + H1DNDigitsInClusterM4 + }; + + static constexpr short kNHist2D = 9; + enum Histos2D { H2DDigitMapM2, + H2DDigitMapM3, + H2DDigitMapM4, + H2DCalibDigitMapM2, + H2DCalibDigitMapM3, + H2DCalibDigitMapM4, + H2DClusterMapM2, + H2DClusterMapM3, + H2DClusterMapM4 + }; + static constexpr short kNIntensiveHist2D = 12; + enum IntensiveHistos2D { H2DPedestalValueM2, + H2DPedestalValueM3, + H2DPedestalValueM4, + H2DPedestalSigmaM2, + H2DPedestalSigmaM3, + H2DPedestalSigmaM4, + H2DBadChannelMapM2, + H2DBadChannelMapM3, + H2DBadChannelMapM4, + H2DGainsM2, + H2DGainsM3, + H2DGainsM4 + }; + static constexpr short kNfractions1D = 1; + enum fractions1D { F1DErrorTypeOccurance }; + + static constexpr short kNfractions2D = 3; + enum fractions2D { F2DDigitFreqM2, + F2DDigitFreqM3, + F2DDigitFreqM4 + }; + + static constexpr short kNModules = 3; + static constexpr short kNChannels = 23040; + int mNEventsTotal = 0; + int mCcdbCheckIntervalInMinutes = 1; + uint32_t mCycleNumber = 0; + bool mJustWasReset = false; + bool mIsAsyncMode = false; + std::array mHist1D = { nullptr }; ///< Array of 1D histograms + std::array mHist2D = { nullptr }; ///< Array of 2D histograms + std::array mIntensiveHist2D = { nullptr }; ///< Array of IntensiveTH2F histograms + std::array mFractions1D = { nullptr }; ///< Array of TH1Fraction histograms + std::array mFractions2D = { nullptr }; ///< Array of TH1Fraction histograms +}; + +} // namespace o2::quality_control_modules::cpv + +#endif // QC_MODULE_CPV_CPVPHYSICSTASK_H diff --git a/Modules/CPV/src/PedestalCheck.cxx b/Modules/CPV/src/PedestalCheck.cxx new file mode 100644 index 0000000000..6a436013c4 --- /dev/null +++ b/Modules/CPV/src/PedestalCheck.cxx @@ -0,0 +1,338 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalCheck.cxx +/// \author Sergey Evdokimov +/// + +// QC +#include "CPV/PedestalCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/ObjectMetadataKeys.h" +// O2 +#include +#include +// ROOT +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control; +using namespace o2::quality_control::repository; + +namespace o2::quality_control_modules::cpv +{ + +void PedestalCheck::configure() +{ + ILOG(Info, Support) << "PedestalCheck::configure() : I have been called with following custom parameters" << mCustomParameters << ENDM; + + for (int mod = 0; mod < 3; mod++) { + // mMinGoodPedestalValueM + if (auto param = mCustomParameters.find(Form("mMinGoodPedestalValueM%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMinGoodPedestalValueM%d = ", mod + 2) + << param->second << ENDM; + mMinGoodPedestalValueM[mod] = stoi(param->second); + } + ILOG(Debug, Support) << "configure() : I use " + << Form("mMinGoodPedestalValueM%d", mod + 2) + << " = " + << mMinGoodPedestalValueM[mod] << ENDM; + // mMaxGoodPedestalSigmaM + if (auto param = mCustomParameters.find(Form("mMaxGoodPedestalSigmaM%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMaxGoodPedestalSigmaM%d = ", mod + 2) + << param->second << ENDM; + mMaxGoodPedestalSigmaM[mod] = stof(param->second); + } + ILOG(Debug, Support) << "configure() : I use " + << Form("mMaxGoodPedestalSigmaM%d", mod + 2) + << " = " + << mMaxGoodPedestalSigmaM[mod] << ENDM; + // mMinGoodPedestalEfficiencyM + if (auto param = mCustomParameters.find(Form("mMinGoodPedestalEfficiencyM%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMinGoodPedestalEfficiencyM%d = ", mod + 2) + << param->second << ENDM; + mMinGoodPedestalEfficiencyM[mod] = stof(param->second); + } + ILOG(Debug, Support) << "configure() : I use " + << Form("mMinGoodPedestalEfficiencyM%d", mod + 2) + << " = " + << mMinGoodPedestalEfficiencyM[mod] << ENDM; + // mMaxGoodPedestalEfficiencyM + if (auto param = mCustomParameters.find(Form("mMaxGoodPedestalEfficiencyM%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMaxGoodPedestalEfficiencyM%d = ", mod + 2) + << param->second << ENDM; + mMaxGoodPedestalEfficiencyM[mod] = stof(param->second); + } + ILOG(Debug, Support) << "configure() : I use " + << Form("mMaxGoodPedestalEfficiencyM%d", mod + 2) + << " = " + << mMaxGoodPedestalEfficiencyM[mod] << ENDM; + // mToleratedBadPedestalValueChannelsM + if (auto param = mCustomParameters.find(Form("mToleratedBadPedestalValueChannelsM%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mToleratedBadPedestalValueChannelsM%d = ", mod + 2) + << param->second << ENDM; + mToleratedBadPedestalValueChannelsM[mod] = stoi(param->second); + } + ILOG(Debug, Support) << "configure() : I use " + << Form("mToleratedBadPedestalValueChannelsM%d", mod + 2) + << " = " + << mToleratedBadPedestalValueChannelsM[mod] << ENDM; + // mToleratedBadPedestalSigmaChannelsM + if (auto param = mCustomParameters.find(Form("mToleratedBadPedestalSigmaChannelsM%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mToleratedBadPedestalSigmaChannelsM%d = ", mod + 2) + << param->second << ENDM; + mToleratedBadPedestalSigmaChannelsM[mod] = stoi(param->second); + } + ILOG(Debug, Support) << "configure() : I use " + << Form("mToleratedBadPedestalSigmaChannelsM%d", mod + 2) + << " = " + << mToleratedBadPedestalSigmaChannelsM[mod] << ENDM; + + // mToleratedBadChannelsM + if (auto param = mCustomParameters.find(Form("mToleratedBadChannelsM%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mToleratedBadChannelsM%d = ", mod + 2) + << param->second << ENDM; + mToleratedBadChannelsM[mod] = stoi(param->second); + } + ILOG(Debug, Support) << "configure() : I use " + << Form("mToleratedBadChannelsM%d", mod + 2) + << " = " + << mToleratedBadChannelsM[mod] << ENDM; + // mToleratedBadPedestalEfficiencyChannelsM + if (auto param = mCustomParameters.find(Form("mToleratedBadPedestalEfficiencyChannelsM%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mToleratedBadPedestalEfficiencyChannelsM%d = ", mod + 2) + << param->second << ENDM; + mToleratedBadPedestalEfficiencyChannelsM[mod] = stoi(param->second); + } + ILOG(Debug, Support) << "configure() : I use " + << Form("mToleratedBadPedestalEfficiencyChannelsM%d", mod + 2) + << " = " + << mToleratedBadPedestalEfficiencyChannelsM[mod] << ENDM; + } + ILOG(Info, Support) << "PedestalCheck::configure() : configuring is done." << ENDM; + mIsConfigured = true; +} + +Quality PedestalCheck::check(std::map>* moMap) +{ + if (!mIsConfigured) { + ILOG(Info, Support) << "PedestalCheck::check() : I'm about to check already but configure() had not been called yet. So I call it now." << ENDM; + configure(); + } + + // default + Quality result = Quality::Good; + + for (auto& [moName, mo] : *moMap) { + + (void)moName; // trick the compiler about not used variable + for (int iMod = 0; iMod < 3; iMod++) { // loop modules + if (mo->getName() == Form("PedestalValueM%d", iMod + 2)) { + bool isGoodMO = true; + + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F*, skipping" << ENDM; + continue; + } + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + // msg->AddText(Form("Run %d", getRunNumberFromMO(mo))); + // count number of too small pedestals + too big pedestals + int nOfBadPedestalValues = h->Integral(0, mMinGoodPedestalValueM[iMod]) + h->GetBinContent(h->GetNbinsX() + 1); // underflow + small pedestals + overflow + if (nOfBadPedestalValues > mToleratedBadPedestalValueChannelsM[iMod]) { + if (result.isBetterThan(Quality::Bad)) { + result = Quality::Bad; + } + result.addFlag(FlagTypeFactory::Unknown(), Form("bad ped values M%d", iMod)); + msg->AddText(Form("Too many bad ped values: %d", nOfBadPedestalValues)); + msg->AddText(Form("Tolerated bad ped values: %d", mToleratedBadPedestalValueChannelsM[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + isGoodMO = false; + } + // count number of bad pedestals (double peaked and so) + int nOfBadPedestals = 7680 - h->GetEntries(); + if (nOfBadPedestals > mToleratedBadChannelsM[iMod]) { + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("bad pedestals M%d", iMod)); + msg->AddText(Form("Too many bad channels: %d", nOfBadPedestals)); + msg->AddText(Form("Tolerated bad channels: %d", mToleratedBadChannelsM[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + isGoodMO = false; + } + if (isGoodMO) { + msg->AddText("OK"); + msg->SetFillColor(kGreen); + } + break; // exit modules loop for this particular object + } + + if (mo->getName() == Form("PedestalSigmaM%d", iMod + 2)) { + + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F*, skipping" << ENDM; + continue; + } + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + // msg->AddText(Form("Run %d", getRunNumberFromMO(mo))); + // count number of too small pedestals + too big pedestals + float binWidth = h->GetBinWidth(1); + int nOfBadPedestalSigmas = h->Integral(mMaxGoodPedestalSigmaM[iMod] / binWidth + 1, + h->GetNbinsX() + 1); // big sigmas + overflow + + if (nOfBadPedestalSigmas > mToleratedBadPedestalSigmaChannelsM[iMod]) { + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("bad ped sigmas M%d", iMod)); + msg->AddText(Form("Too many bad ped sigmas: %d", nOfBadPedestalSigmas)); + msg->AddText(Form("Tolerated bad ped sigmas: %d", mToleratedBadPedestalSigmaChannelsM[iMod])); + + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + } else { + msg->AddText("OK"); + msg->SetFillColor(kGreen); + } + break; // exit modules loop for this particular object + } + + if (mo->getName() == Form("PedestalEfficiencyM%d", iMod + 2)) { + + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F*, skipping" << ENDM; + continue; + } + result = Quality::Good; // default + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + // msg->AddText(Form("Run %d", getRunNumberFromMO(mo))); + // count number of too small pedestals + too big pedestals + float binWidth = h->GetBinWidth(1); + int nOfBadPedestalEfficiencies = 7680 - + h->Integral(mMinGoodPedestalEfficiencyM[iMod] / binWidth + 1, + mMaxGoodPedestalEfficiencyM[iMod] / binWidth); // big sigmas + overflow + + if (nOfBadPedestalEfficiencies > mToleratedBadPedestalEfficiencyChannelsM[iMod]) { + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("bad ped efficiencies M%d", iMod)); + msg->AddText(Form("Too many bad ped efficiencies: %d", nOfBadPedestalEfficiencies)); + msg->AddText(Form("Tolerated bad ped efficiencies: %d", + mToleratedBadPedestalEfficiencyChannelsM[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + } else { + msg->AddText("OK"); + msg->SetFillColor(kGreen); + } + break; // exit modules loop for this particular object + } + + } // iMod cycle + } // moMap cycle + + return result; +} + +void PedestalCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + return; // do noting for the time being. Maybe in the future we will do something sofisticated + for (int iMod = 0; iMod < 3; iMod++) { // loop over modules + if (mo->getName() == Form("PedestalValueM%d", iMod + 2)) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F*, skipping" << ENDM; + continue; + } + + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + // + } else if (checkResult == Quality::Bad) { + ILOG(Info, Support) << "beautify() : Quality::Bad, setting to red for " + << mo->GetName() << ENDM; + h->SetFillColor(kRed); + // + } else if (checkResult == Quality::Medium) { + ILOG(Error, Support) << "beautify() : unexpected quality for " << mo->GetName() << ENDM; + h->SetFillColor(kOrange); + } + return; // exit when object is processed + } + } +} + +int PedestalCheck::getRunNumberFromMO(std::shared_ptr mo) +{ + int runNumber{ 0 }; + auto metaData = mo->getMetadataMap(); + ILOG(Info, Support) << "PedestalCheck::check() : I have following metadata:" << ENDM; + for (auto [key, value] : metaData) { + ILOG(Info, Support) << "key = " << key << "; value = " << value << ENDM; + } + auto foundRN = metaData.find(metadata_keys::runNumber); + if (foundRN != metaData.end()) { + runNumber = std::stoi(foundRN->second); + ILOG(Info, Support) << "PedestalCheck::check() : I found in metadata RunNumber = " << foundRN->second << ENDM; + } + if (runNumber == 0) { + ILOG(Info, Support) << "PedestalCheck::check() : I haven't found RunNumber in metadata, using from Activity." << ENDM; + runNumber = mo->getActivity().mId; + ILOG(Info, Support) << "PedestalCheck::check() : RunNumber = " << runNumber << ENDM; + } + if (runNumber == 0) { + auto foundRNFT = metaData.find("RunNumberFromTask"); + if (foundRNFT != metaData.end()) { + runNumber = std::stoi(foundRNFT->second); + ILOG(Info, Support) << "PedestalCheck::check() : I found in metadata RunNumberFromTask = " << foundRNFT->second << ENDM; + } + } + return runNumber; +} + +} // namespace o2::quality_control_modules::cpv diff --git a/Modules/CPV/src/PedestalTask.cxx b/Modules/CPV/src/PedestalTask.cxx new file mode 100644 index 0000000000..bf4b714481 --- /dev/null +++ b/Modules/CPV/src/PedestalTask.cxx @@ -0,0 +1,784 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalTask.cxx +/// \author Sergey Evdokimov +/// + +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "CPV/PedestalTask.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::cpv +{ + +PedestalTask::PedestalTask() +{ + for (int i = 0; i < kNHist1D; i++) { + mHist1D[i] = nullptr; + } + for (int i = 0; i < kNHist2D; i++) { + mHist2D[i] = nullptr; + } + for (int i = 0; i < o2::cpv::Geometry::kNCHANNELS; i++) { + mHistAmplitudes[i] = nullptr; + } +} + +PedestalTask::~PedestalTask() +{ + for (int i = 0; i < kNHist1D; i++) { + if (mHist1D[i]) { + mHist1D[i]->Delete(); + mHist1D[i] = nullptr; + } + } + for (int i = 0; i < kNHist2D; i++) { + if (mHist2D[i]) { + mHist2D[i]->Delete(); + mHist2D[i] = nullptr; + } + } + for (int i = 0; i < o2::cpv::Geometry::kNCHANNELS; i++) { + if (mHistAmplitudes[i]) { + mHistAmplitudes[i]->Delete(); + mHistAmplitudes[i] = nullptr; + } + } +} + +void PedestalTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize PedestalTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + if (auto param = mCustomParameters.find("minNEventsToUpdatePedestals"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter : minNEventsToUpdatePedestals " << param->second << ENDM; + mMinNEventsToUpdatePedestals = stoi(param->second); + ILOG(Info, Devel) << "I set minNEventsToUpdatePedestals = " << mMinNEventsToUpdatePedestals << ENDM; + } else { + ILOG(Info, Devel) << "Default parameter : minNEventsToUpdatePedestals = " << mMinNEventsToUpdatePedestals << ENDM; + } + if (auto param = mCustomParameters.find("monitorPedestalCalibrator"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter : monitorPedestalCalibrator " << param->second << ENDM; + mMonitorPedestalCalibrator = stoi(param->second); + ILOG(Info, Devel) << "I set mMonitorPedestalCalibrator = " << mMonitorPedestalCalibrator << ENDM; + } else { + ILOG(Info, Devel) << "Default parameter : monitorPedestalCalibrator = " << mMonitorPedestalCalibrator << ENDM; + } + if (auto param = mCustomParameters.find("monitorDigits"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter : monitorDigits " << param->second << ENDM; + mMonitorDigits = stoi(param->second); + ILOG(Info, Devel) << "I set mMonitorDigits = " << mMonitorDigits << ENDM; + } else { + ILOG(Info, Devel) << "Default parameter : monitorDigits = " << mMonitorDigits << ENDM; + } + + if (mMonitorPedestalCalibrator) { + ILOG(Info, Devel) << "Results of pedestal calibrator sent to CCDB will be monitored" << ENDM; + } + if (mMonitorDigits) { + ILOG(Info, Devel) << "Digits will be monitored. Look at *FromDigits MOs." << ENDM; + } + + initHistograms(); + mNEventsTotal = 0; + mNEventsFromLastFillHistogramsCall = 0; +} + +void PedestalTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity() : Run Number of Activity is " << activity.mId << ENDM; + resetHistograms(); + mNEventsTotal = 0; + mNEventsFromLastFillHistogramsCall = 0; + mRunNumber = activity.mId; + for (int i = 0; i < kNHist1D; i++) { + if (mHist1D[i]) { + getObjectsManager()->addMetadata(mHist1D[i]->GetName(), "RunNumberFromTask", Form("%d", mRunNumber)); + } + } + for (int i = 0; i < kNHist2D; i++) { + if (mHist2D[i]) { + getObjectsManager()->addMetadata(mHist2D[i]->GetName(), "RunNumberFromTask", Form("%d", mRunNumber)); + } + } +} + +void PedestalTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + // at at the startOfCycle all HistAmplitudes are not updated by definition + if (mMonitorDigits) { + for (int i = 0; i < o2::cpv::Geometry::kNCHANNELS; i++) { + mIsUpdatedAmplitude[i] = false; + } + } +} + +void PedestalTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + int nInputs = ctx.inputs().size(); + mHist1D[H1DNInputs]->Fill(nInputs); + + int nValidInputs = ctx.inputs().countValidInputs(); + mHist1D[H1DNValidInputs]->Fill(nValidInputs); + + bool hasRawErrors = false, hasDigits = false; + bool hasPedestalsCLP = false, hasPedEffsCLP = false, hasFEEThrsCLP = false, hasDeadChnlsCLP = false, hasHighThrsCLP = false; + for (auto&& input : o2::framework::InputRecordWalker(ctx.inputs())) { + // get message header + if (input.header != nullptr && input.payload != nullptr) { + auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(input); + // get payload of a specific input, which is a char array. + // const char* payload = input.payload; + mHist1D[H1DInputPayloadSize]->Fill(payloadSize); + const auto* header = o2::framework::DataRefUtils::getHeader(input); + if (payloadSize) { + if ((strcmp(header->dataOrigin.str, "CPV") == 0) && (strcmp(header->dataDescription.str, "DIGITS") == 0)) { + // LOG(info) << "monitorData() : I found digits in inputs"; + hasDigits = true; + } + if ((strcmp(header->dataOrigin.str, "CLP") == 0) && (strcmp(header->dataDescription.str, "CPV_Pedestals") == 0)) { + // LOG(info) << "monitorData() : I found CPV_Pedestals in inputs"; + hasPedestalsCLP = true; + } + if ((strcmp(header->dataOrigin.str, "CLP") == 0) && (strcmp(header->dataDescription.str, "CPV_FEEThrs") == 0)) { + // LOG(info) << "monitorData() : I found CPV_FEEThrs in inputs"; + hasFEEThrsCLP = true; + } + if ((strcmp(header->dataOrigin.str, "CLP") == 0) && (strcmp(header->dataDescription.str, "CPV_DeadChnls") == 0)) { + // LOG(info) << "monitorData() : I found CPV_DeadChnls in inputs"; + hasDeadChnlsCLP = true; + } + if ((strcmp(header->dataOrigin.str, "CLP") == 0) && (strcmp(header->dataDescription.str, "CPV_HighThrs") == 0)) { + // LOG(info) << "monitorData() : I found CPV_HighThrs in inputs"; + hasHighThrsCLP = true; + } + if ((strcmp(header->dataOrigin.str, "CLP") == 0) && (strcmp(header->dataDescription.str, "CPV_PedEffs") == 0)) { + // LOG(info) << "monitorData() : I found CPV_PedEffs in inputs"; + hasPedEffsCLP = true; + } + if ((strcmp(header->dataOrigin.str, "CPV") == 0) && (strcmp(header->dataDescription.str, "RAWHWERRORS") == 0)) { + // LOG(info) << "monitorData() : I found rawhwerrors in inputs"; + hasRawErrors = true; + } + } + } + } + + // raw errors + if (hasRawErrors) { + auto rawErrors = ctx.inputs().get>("rawerrors"); + for (const auto& rawError : rawErrors) { + mHist1D[H1DRawErrors]->Fill(rawError.errortype); + } + } + + // digits monitoring + if (hasDigits && mMonitorDigits) { + auto digits = ctx.inputs().get>("digits"); + mHist1D[H1DNDigitsPerInput]->Fill(digits.size()); + auto digitsTR = ctx.inputs().get>("dtrigrec"); + // mNEventsTotal += digitsTR.size();//number of events in the current input + for (const auto& trigRecord : digitsTR) { + LOG(debug) << " monitorData() : trigger record #" << mNEventsTotal + << " contains " << trigRecord.getNumberOfObjects() << " objects."; + if (trigRecord.getNumberOfObjects() > 0) { // at least 1 digit -> pedestal event + mNEventsTotal++; + mNEventsFromLastFillHistogramsCall++; + for (int iDig = trigRecord.getFirstEntry(); iDig < trigRecord.getFirstEntry() + trigRecord.getNumberOfObjects(); iDig++) { + mHist1D[H1DDigitIds]->Fill(digits[iDig].getAbsId()); + short relId[3]; + if (o2::cpv::Geometry::absToRelNumbering(digits[iDig].getAbsId(), relId)) { + // reminder: relId[3]={Module, phi col, z row} where Module=2..4, phi col=0..127, z row=0..59 + mHist2D[H2DDigitMapM2 + relId[0] - 2]->Fill(relId[1], relId[2]); + mHistAmplitudes[digits[iDig].getAbsId()]->Fill(digits[iDig].getAmplitude()); + mIsUpdatedAmplitude[digits[iDig].getAbsId()] = true; + } + } + } + } + } + + // pedestal calibrator output monitoring + if (mMonitorPedestalCalibrator) { + // o2::cpv::Pedestals object + if (hasPedestalsCLP) { + auto peds = o2::framework::DataRefUtils::as>(ctx.inputs().get("peds")); + if (peds) { + mNtimesCCDBPayloadFetched++; + LOG(info) << "PedestalTask::monitorData() : Extracted o2::cpv::Pedestals from CLP payload"; + short relId[3]; + for (int ch = 0; ch < o2::cpv::Geometry::kNCHANNELS; ch++) { + o2::cpv::Geometry::absToRelNumbering(ch, relId); + mHist2D[H2DPedestalValueMapM2 + relId[0] - 2]->SetBinContent(relId[1] + 1, relId[2] + 1, peds->getPedestal(ch)); + mHist1D[H1DPedestalValueM2 + relId[0] - 2]->Fill(peds->getPedestal(ch)); + mHist2D[H2DPedestalSigmaMapM2 + relId[0] - 2]->SetBinContent(relId[1] + 1, relId[2] + 1, peds->getPedSigma(ch)); + mHist1D[H1DPedestalSigmaM2 + relId[0] - 2]->Fill(peds->getPedSigma(ch)); + } + } + } + // FEE thresolds + if (hasFEEThrsCLP) { + auto feethrs = o2::framework::DataRefUtils::as>>(ctx.inputs().get("feethrs")); + if (feethrs) { + LOG(info) << "PedestalTask::monitorData() : Extracted FEE thresholds std::vector of size " << feethrs->size() << "from CLP payload"; + short relId[3]; + for (int ch = 0; ch < o2::cpv::Geometry::kNCHANNELS; ch++) { + o2::cpv::Geometry::absToRelNumbering(ch, relId); + mHist2D[H2DFEEThresholdsMapM2 + relId[0] - 2]->SetBinContent(relId[1] + 1, relId[2] + 1, float(feethrs->at(ch) & 0xffff)); + } + } + } + // Dead channels + if (hasDeadChnlsCLP) { + auto deadchs = o2::framework::DataRefUtils::as>>(ctx.inputs().get("deadchs")); + if (deadchs) { + LOG(info) << "PedestalTask::monitorData() : Extracted dead channels std::vector of size " << deadchs->size() << "from CLP payload"; + short relId[3]; + for (int ch : *deadchs) { + o2::cpv::Geometry::absToRelNumbering(ch, relId); + mHist2D[H2DDeadChanelsMapM2 + relId[0] - 2]->SetBinContent(relId[1] + 1, relId[2] + 1, 1.); + } + } + } + // High threshold channels + if (hasHighThrsCLP) { + auto highthrs = o2::framework::DataRefUtils::as>>(ctx.inputs().get("highthrs")); + if (highthrs) { + LOG(info) << "PedestalTask::monitorData() : Extracted high threshold channels std::vector of size " << highthrs->size() << "from CLP payload"; + short relId[3]; + for (int ch : *highthrs) { + o2::cpv::Geometry::absToRelNumbering(ch, relId); + } + } + } + // Efficiencies + if (hasPedEffsCLP) { + auto pedeffs = o2::framework::DataRefUtils::as>>(ctx.inputs().get("pedeffs")); + if (pedeffs) { + LOG(info) << "PedestalTask::monitorData() : Extracted pedesta efficiencies std::vector of size " << pedeffs->size() << "from CLP payload"; + short relId[3]; + for (int ch = 0; ch < o2::cpv::Geometry::kNCHANNELS; ch++) { + o2::cpv::Geometry::absToRelNumbering(ch, relId); + mHist2D[H2DPedestalEfficiencyMapM2 + relId[0] - 2]->SetBinContent(relId[1] + 1, relId[2] + 1, pedeffs->at(ch)); + mHist1D[H1DPedestalEfficiencyM2 + relId[0] - 2]->Fill(pedeffs->at(ch)); + } + } + } + LOG(info) << "PedestalTask::monitorData() : I fetched o2::cpv::Pedestals CLP payload " << mNtimesCCDBPayloadFetched << " times."; + } +} + +void PedestalTask::endOfCycle() +{ + ILOG(Info, Devel) << "PedestalTask::endOfCycle()" << ENDM; + if (mMonitorDigits) { + // fit histograms if have sufficient increment of event number + if (mNEventsFromLastFillHistogramsCall >= mMinNEventsToUpdatePedestals) { + ILOG(Info, Devel) << "I call fillDigitsHistograms()" << ENDM; + fillDigitsHistograms(); + mNEventsFromLastFillHistogramsCall = 0; + } else { + ILOG(Info, Devel) << "Not enough events (" << mNEventsFromLastFillHistogramsCall << ") to call fillDigitsHistograms(). Min " + << mMinNEventsToUpdatePedestals << " needed." << ENDM; + } + } +} + +void PedestalTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; + if (!mMonitorDigits) { + // do a final fill of histograms (if needed) + if (mNEventsFromLastFillHistogramsCall) { + ILOG(Info, Devel) << "Final call of fillDigitsHistograms() " << ENDM; + fillDigitsHistograms(); + } + } +} + +void PedestalTask::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting PedestalTask" << ENDM; + resetHistograms(); + mNEventsTotal = 0; + mNEventsFromLastFillHistogramsCall = 0; +} + +void PedestalTask::initHistograms() +{ + ILOG(Info, Devel) << "initing histograms" << ENDM; + + if (!mHist1D[H1DRawErrors]) { + mHist1D[H1DRawErrors] = + new TH1F("RawErrors", "Raw Errors", 20, 0., 20.); + getObjectsManager()->startPublishing(mHist1D[H1DRawErrors]); + } else { + mHist1D[H1DRawErrors]->Reset(); + } + + if (mMonitorDigits) { + // create monitoring histograms (or reset, if they already exist) + for (int i = 0; i < o2::cpv::Geometry::kNCHANNELS; i++) { + if (!mHistAmplitudes[i]) { + mHistAmplitudes[i] = + new TH1F(Form("HistAmplitude%d", i), Form("HistAmplitude%d", i), 4096, 0., 4096.); + // publish some of them + if (i % 1000 == 0) { + getObjectsManager()->startPublishing(mHistAmplitudes[i]); + } + } else { + mHistAmplitudes[i]->Reset(); + } + mIsUpdatedAmplitude[i] = false; + } + } + // 1D Histos + if (!mHist1D[H1DInputPayloadSize]) { + mHist1D[H1DInputPayloadSize] = + new TH1F("InputPayloadSize", "Input Payload Size", 30000, 0, 30000000); + getObjectsManager()->startPublishing(mHist1D[H1DInputPayloadSize]); + } else { + mHist1D[H1DInputPayloadSize]->Reset(); + } + + if (!mHist1D[H1DNInputs]) { + mHist1D[H1DNInputs] = new TH1F("NInputs", "Number of inputs", 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHist1D[H1DNInputs]); + } else { + mHist1D[H1DNInputs]->Reset(); + } + + if (!mHist1D[H1DNValidInputs]) { + mHist1D[H1DNValidInputs] = + new TH1F("NValidInputs", "Number of valid inputs", 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHist1D[H1DNValidInputs]); + } else { + mHist1D[H1DNValidInputs]->Reset(); + } + + if (mMonitorDigits) { + if (!mHist1D[H1DNDigitsPerInput]) { + mHist1D[H1DNDigitsPerInput] = + new TH1F("NDigitsPerInput", "Number of digits per input", 30000, 0, 300000); + if (mMonitorDigits) { + getObjectsManager()->startPublishing(mHist1D[H1DNDigitsPerInput]); + } + } else { + mHist1D[H1DNDigitsPerInput]->Reset(); + } + + if (!mHist1D[H1DDigitIds]) { + mHist1D[H1DDigitIds] = new TH1F("DigitIds", "Digit Ids", 30000, -0.5, 29999.5); + if (mMonitorDigits) { + getObjectsManager()->startPublishing(mHist1D[H1DDigitIds]); + } + } else { + mHist1D[H1DDigitIds]->Reset(); + } + } + + for (int mod = 0; mod < 3; mod++) { + if (mMonitorPedestalCalibrator) { // MOs to appear in pedestal calibrator monitoring mode + if (!mHist1D[H1DPedestalValueM2 + mod]) { + mHist1D[H1DPedestalValueM2 + mod] = + new TH1F( + Form("PedestalValueM%d", mod + 2), + Form("Pedestal value distribution M%d", mod + 2), + 512, 0, 512); + mHist1D[H1DPedestalValueM2 + mod]->GetXaxis()->SetTitle("Pedestal value"); + getObjectsManager()->startPublishing(mHist1D[H1DPedestalValueM2 + mod]); + } else { + mHist1D[H1DPedestalValueM2 + mod]->Reset(); + } + + if (!mHist1D[H1DPedestalSigmaM2 + mod]) { + mHist1D[H1DPedestalSigmaM2 + mod] = + new TH1F( + Form("PedestalSigmaM%d", mod + 2), + Form("Pedestal sigma distribution M%d", mod + 2), + 200, 0, 20); + mHist1D[H1DPedestalSigmaM2 + mod]->GetXaxis()->SetTitle("Pedestal sigma"); + getObjectsManager()->startPublishing(mHist1D[H1DPedestalSigmaM2 + mod]); + } else { + mHist1D[H1DPedestalSigmaM2 + mod]->Reset(); + } + + if (!mHist1D[H1DPedestalEfficiencyM2 + mod]) { + mHist1D[H1DPedestalEfficiencyM2 + mod] = + new TH1F( + Form("PedestalEfficiencyM%d", mod + 2), + Form("Pedestal efficiency distribution M%d", mod + 2), + 500, 0., 5.); + mHist1D[H1DPedestalEfficiencyM2 + mod]->GetXaxis()->SetTitle("Pedestal efficiency"); + getObjectsManager()->startPublishing(mHist1D[H1DPedestalEfficiencyM2 + mod]); + } else { + mHist1D[H1DPedestalEfficiencyM2 + mod]->Reset(); + } + } + if (mMonitorDigits) { // MOs to appear in digits monitoring mode + if (!mHist1D[H1DPedestalValueInDigitsM2 + mod]) { + mHist1D[H1DPedestalValueInDigitsM2 + mod] = + new TH1F( + Form("PedestalValueInDigitsM%d", mod + 2), + Form("Pedestal value distribution M%d", mod + 2), + 512, 0, 512); + mHist1D[H1DPedestalValueInDigitsM2 + mod]->GetXaxis()->SetTitle("Pedestal value"); + getObjectsManager()->startPublishing(mHist1D[H1DPedestalValueInDigitsM2 + mod]); + } else { + mHist1D[H1DPedestalValueInDigitsM2 + mod]->Reset(); + } + + if (!mHist1D[H1DPedestalSigmaInDigitsM2 + mod]) { + mHist1D[H1DPedestalSigmaInDigitsM2 + mod] = + new TH1F( + Form("PedestalSigmaInDigitsM%d", mod + 2), + Form("Pedestal sigma distribution M%d", mod + 2), + 200, 0, 20); + mHist1D[H1DPedestalSigmaInDigitsM2 + mod]->GetXaxis()->SetTitle("Pedestal sigma"); + getObjectsManager()->startPublishing(mHist1D[H1DPedestalSigmaInDigitsM2 + mod]); + } else { + mHist1D[H1DPedestalSigmaInDigitsM2 + mod]->Reset(); + } + + if (!mHist1D[H1DPedestalEfficiencyInDigitsM2 + mod]) { + mHist1D[H1DPedestalEfficiencyInDigitsM2 + mod] = + new TH1F( + Form("PedestalEfficiencyInDigitsM%d", mod + 2), + Form("Pedestal efficiency distribution M%d", mod + 2), + 500, 0., 5.); + mHist1D[H1DPedestalEfficiencyInDigitsM2 + mod]->GetXaxis()->SetTitle("Pedestal efficiency"); + getObjectsManager()->startPublishing(mHist1D[H1DPedestalEfficiencyInDigitsM2 + mod]); + } else { + mHist1D[H1DPedestalEfficiencyInDigitsM2 + mod]->Reset(); + } + } + } + + // 2D Histos + if (!mHist2D[H2DErrorType]) { + mHist2D[H2DErrorType] = new TH2F("ErrorType", "ErrorType", 50, 0, 50, 5, 0, 5); + mHist2D[H2DErrorType]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DErrorType]); + } else { + mHist2D[H2DErrorType]->Reset(); + } + + // per-module histos + int nPadsX = o2::cpv::Geometry::kNumberOfCPVPadsPhi; + int nPadsZ = o2::cpv::Geometry::kNumberOfCPVPadsZ; + + for (int mod = 0; mod < 3; mod++) { + if (mMonitorPedestalCalibrator) { // MOs to appear in pedestal calibrator monitoring mode + if (!mHist2D[H2DPedestalValueMapM2 + mod]) { + mHist2D[H2DPedestalValueMapM2 + mod] = + new TH2F( + Form("PedestalValueMapM%d", 2 + mod), + Form("PedestalValue Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DPedestalValueMapM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DPedestalValueMapM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DPedestalValueMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DPedestalValueMapM2 + mod]); + } else { + mHist2D[H2DPedestalValueMapM2 + mod]->Reset(); + } + + if (!mHist2D[H2DPedestalSigmaMapM2 + mod]) { + mHist2D[H2DPedestalSigmaMapM2 + mod] = + new TH2F( + Form("PedestalSigmaMapM%d", 2 + mod), + Form("PedestalSigma Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DPedestalSigmaMapM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DPedestalSigmaMapM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DPedestalSigmaMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DPedestalSigmaMapM2 + mod]); + } else { + mHist2D[H2DPedestalSigmaMapM2 + mod]->Reset(); + } + + if (!mHist2D[H2DPedestalEfficiencyMapM2 + mod]) { + mHist2D[H2DPedestalEfficiencyMapM2 + mod] = + new TH2F( + Form("PedestalEfficiencyMapM%d", 2 + mod), + Form("Pedestal Efficiency Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DPedestalEfficiencyMapM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DPedestalEfficiencyMapM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DPedestalEfficiencyMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DPedestalEfficiencyMapM2 + mod]); + } else { + mHist2D[H2DPedestalEfficiencyMapM2 + mod]->Reset(); + } + if (!mHist2D[H2DFEEThresholdsMapM2 + mod]) { + mHist2D[H2DFEEThresholdsMapM2 + mod] = + new TH2F( + Form("FEEThresholdsMapM%d", 2 + mod), + Form("FEE Thresholds Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DFEEThresholdsMapM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DFEEThresholdsMapM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DFEEThresholdsMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DFEEThresholdsMapM2 + mod]); + } else { + mHist2D[H2DFEEThresholdsMapM2 + mod]->Reset(); + } + if (!mHist2D[H2DDeadChanelsMapM2 + mod]) { + mHist2D[H2DDeadChanelsMapM2 + mod] = + new TH2F( + Form("DeadChanelsMapM%d", 2 + mod), + Form("Daed channels map Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DDeadChanelsMapM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DDeadChanelsMapM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DDeadChanelsMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DDeadChanelsMapM2 + mod]); + } else { + mHist2D[H2DDeadChanelsMapM2 + mod]->Reset(); + } + if (!mHist2D[H2DHighThresholdMapM2 + mod]) { + mHist2D[H2DHighThresholdMapM2 + mod] = + new TH2F( + Form("HighThresholdMapM%d", 2 + mod), + Form("High threshold Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DHighThresholdMapM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DHighThresholdMapM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DHighThresholdMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DHighThresholdMapM2 + mod]); + } else { + mHist2D[H2DHighThresholdMapM2 + mod]->Reset(); + } + } + + if (mMonitorDigits) { // MOs to appear in digits monitoring mode + if (!mHist2D[H2DDigitMapM2 + mod]) { + mHist2D[H2DDigitMapM2 + mod] = + new TH2F( + Form("DigitMapM%d", 2 + mod), + Form("Digit Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DDigitMapM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DDigitMapM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DDigitMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DDigitMapM2 + mod]); + } else { + mHist2D[H2DDigitMapM2 + mod]->Reset(); + } + if (!mHist2D[H2DPedestalValueMapInDigitsM2 + mod]) { + mHist2D[H2DPedestalValueMapInDigitsM2 + mod] = + new TH2F( + Form("PedestalValueMapInDigitsM%d", 2 + mod), + Form("PedestalValue Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DPedestalValueMapInDigitsM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DPedestalValueMapInDigitsM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DPedestalValueMapInDigitsM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DPedestalValueMapInDigitsM2 + mod]); + } else { + mHist2D[H2DPedestalValueMapInDigitsM2 + mod]->Reset(); + } + + if (!mHist2D[H2DPedestalSigmaMapInDigitsM2 + mod]) { + mHist2D[H2DPedestalSigmaMapInDigitsM2 + mod] = + new TH2F( + Form("PedestalSigmaMapInDigitsM%d", 2 + mod), + Form("PedestalSigma Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DPedestalSigmaMapInDigitsM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DPedestalSigmaMapInDigitsM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DPedestalSigmaMapInDigitsM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DPedestalSigmaMapInDigitsM2 + mod]); + } else { + mHist2D[H2DPedestalSigmaMapInDigitsM2 + mod]->Reset(); + } + + if (!mHist2D[H2DPedestalEfficiencyMapInDigitsM2 + mod]) { + mHist2D[H2DPedestalEfficiencyMapInDigitsM2 + mod] = + new TH2F( + Form("PedestalEfficiencyMapInDigitsM%d", 2 + mod), + Form("Pedestal Efficiency Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DPedestalEfficiencyMapInDigitsM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DPedestalEfficiencyMapInDigitsM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DPedestalEfficiencyMapInDigitsM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DPedestalEfficiencyMapInDigitsM2 + mod]); + } else { + mHist2D[H2DPedestalEfficiencyMapInDigitsM2 + mod]->Reset(); + } + + if (!mHist2D[H2DPedestalNPeaksMapInDigitsM2 + mod]) { + mHist2D[H2DPedestalNPeaksMapInDigitsM2 + mod] = + new TH2F( + Form("PedestalNPeaksMapInDigitsM%d", 2 + mod), + Form("Number of pedestal peaks Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DPedestalNPeaksMapInDigitsM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DPedestalNPeaksMapInDigitsM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DPedestalNPeaksMapInDigitsM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DPedestalNPeaksMapInDigitsM2 + mod]); + } else { + mHist2D[H2DPedestalNPeaksMapInDigitsM2 + mod]->Reset(); + } + } + } +} + +void PedestalTask::fillDigitsHistograms() +{ + LOG(info) << "fillDigitsHistograms()"; + + if (!mMonitorDigits) { + LOG(info) << "fillDigitsHistograms(): monitor digits mode is off. So do nothing."; + return; + } else { + LOG(info) << "fillDigitsHistograms(): starting analyzing digit data "; + } + // count pedestals and update MOs + float pedestalValue, pedestalSigma, pedestalEfficiency; + short relId[3]; + TF1* functionGaus = new TF1("functionGaus", "gaus", 0., 4095.); + TSpectrum* peakSearcher = new TSpectrum(5); // find up to 5 pedestal peaks + int numberOfPeaks; // number of pedestal peaks in channel. Normaly it's 1, otherwise channel is bad + double *xPeaks, yPeaks[5]; // arrays of x-position of the peaks and their heights + + // first, reset pedestal histograms + for (int mod = 0; mod < 3; mod++) { + mHist2D[H2DPedestalNPeaksMapInDigitsM2 + mod]->Reset(); + mHist2D[H2DPedestalValueMapInDigitsM2 + mod]->Reset(); + mHist2D[H2DPedestalSigmaMapInDigitsM2 + mod]->Reset(); + mHist2D[H2DPedestalEfficiencyMapInDigitsM2 + mod]->Reset(); + mHist1D[H1DPedestalValueInDigitsM2 + mod]->Reset(); + mHist1D[H1DPedestalSigmaInDigitsM2 + mod]->Reset(); + mHist1D[H1DPedestalEfficiencyInDigitsM2 + mod]->Reset(); + } + + // then fill them with actual values + for (int channel = 0; channel < o2::cpv::Geometry::kNCHANNELS; channel++) { + if (!mHistAmplitudes[channel]) { + ILOG(Error, Devel) << "fillDigitsHistograms() : histo mHistAmplitudes[" << channel + << "] does not exist! Something is going wrong." << ENDM; + continue; + } + if (!mIsUpdatedAmplitude[channel]) + continue; // no data in channel, skipping it + + if (channel % 1000 == 0) { + ILOG(Info, Devel) << "fillDigitsHistograms(): Start to search peaks in channel " << channel << ENDM; + } + + numberOfPeaks = peakSearcher->Search(mHistAmplitudes[channel], 10., "nobackground", 0.2); + xPeaks = peakSearcher->GetPositionX(); + + if (numberOfPeaks == 1) { // only 1 peak, fit spectrum with gaus + yPeaks[0] = mHistAmplitudes[channel] + ->GetBinContent(mHistAmplitudes[channel]->GetXaxis()->FindBin(xPeaks[0])); + functionGaus->SetParameters(yPeaks[0], xPeaks[0], 2.); + mHistAmplitudes[channel]->Fit(functionGaus, "WWQ", "", xPeaks[0] - 20., xPeaks[0] + 20.); + pedestalValue = functionGaus->GetParameter(1); + pedestalSigma = functionGaus->GetParameter(2); + // stop publish this amplitude as it's OK (that fails due to some unclear reason) + // if (getObjectsManager()->isBeingPublished(mHistAmplitudes[channel]->GetName())){ + // getObjectsManager()->stopPublishing(mHistAmplitudes[channel]->GetName()); + // } + } else { + if (numberOfPeaks > 1) { // >1 peaks, no fit. Just use mean and stddev as ped value & sigma + pedestalValue = mHistAmplitudes[channel]->GetMean(); + if (pedestalValue > 0) + pedestalValue = -pedestalValue; // let it be negative so we can know it's bad later + pedestalSigma = mHistAmplitudes[channel]->GetStdDev(); + // let's publish this bad amplitude + if (!getObjectsManager()->isBeingPublished(mHistAmplitudes[channel]->GetName())) { + getObjectsManager()->startPublishing(mHistAmplitudes[channel]); + } + } else { // numberOfPeaks < 1 - what is it? + // no peaks found((( OK let's show the spectrum to the world... + if (!getObjectsManager()->isBeingPublished(mHistAmplitudes[channel]->GetName())) { + getObjectsManager()->startPublishing(mHistAmplitudes[channel]); + } + continue; + } + } + + pedestalEfficiency = mHistAmplitudes[channel]->GetEntries() / mNEventsTotal; + o2::cpv::Geometry::absToRelNumbering(channel, relId); + mHist2D[H2DPedestalValueMapInDigitsM2 + relId[0] - 2] + ->SetBinContent(relId[1] + 1, relId[2] + 1, pedestalValue); + mHist2D[H2DPedestalSigmaMapInDigitsM2 + relId[0] - 2] + ->SetBinContent(relId[1] + 1, relId[2] + 1, pedestalSigma); + mHist2D[H2DPedestalEfficiencyMapInDigitsM2 + relId[0] - 2] + ->SetBinContent(relId[1] + 1, relId[2] + 1, pedestalEfficiency); + mHist2D[H2DPedestalNPeaksMapInDigitsM2 + relId[0] - 2] + ->SetBinContent(relId[1] + 1, relId[2] + 1, numberOfPeaks); + + mHist1D[H1DPedestalValueInDigitsM2 + relId[0] - 2]->Fill(pedestalValue); + mHist1D[H1DPedestalSigmaInDigitsM2 + relId[0] - 2]->Fill(pedestalSigma); + mHist1D[H1DPedestalEfficiencyInDigitsM2 + relId[0] - 2]->Fill(pedestalEfficiency); + } + + // show some info to developer + ILOG(Info, Devel) << "fillDigitsHistograms() : at this time, N events = " << mNEventsTotal << ENDM; +} + +void PedestalTask::resetHistograms() +{ + // clean all histograms + ILOG(Debug, Devel) << "Resetting amplitude histograms" << ENDM; + for (int i = 0; i < o2::cpv::Geometry::kNCHANNELS; i++) { + if (mHistAmplitudes[i]) { + mHistAmplitudes[i]->Reset(); + } + mIsUpdatedAmplitude[i] = false; + } + + ILOG(Debug, Devel) << "Resetting the 1D Histograms" << ENDM; + for (int iHist1D = 0; iHist1D < kNHist1D; iHist1D++) { + if (mHist1D[iHist1D]) { + mHist1D[iHist1D]->Reset(); + } + } + + ILOG(Debug, Devel) << "Resetting the 2D Histograms" << ENDM; + for (int iHist2D = 0; iHist2D < kNHist2D; iHist2D++) { + if (mHist2D[iHist2D]) { + mHist2D[iHist2D]->Reset(); + } + } +} + +} // namespace o2::quality_control_modules::cpv diff --git a/Modules/CPV/src/PhysicsCheck.cxx b/Modules/CPV/src/PhysicsCheck.cxx new file mode 100644 index 0000000000..5ff1f0f3d5 --- /dev/null +++ b/Modules/CPV/src/PhysicsCheck.cxx @@ -0,0 +1,653 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PhysicsCheck.cxx +/// \author Sergey Evdokimov +/// + +// QC +#include "CPV/PhysicsCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "PHOS/TH1Fraction.h" +#include "PHOS/TH2Fraction.h" +// O2 +#include +#include +// ROOT +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control; +using namespace o2::quality_control::repository; +using TH1Fraction = o2::quality_control_modules::phos::TH1Fraction; +using TH2Fraction = o2::quality_control_modules::phos::TH2Fraction; + +namespace o2::quality_control_modules::cpv +{ + +void PhysicsCheck::configure() +{ + ILOG(Info, Support) << "PhysicsCheck::configure() : I have been called with following custom parameters" << mCustomParameters << ENDM; + + for (int mod = 0; mod < 3; mod++) { + // mAmplitudeRangeL + if (auto param = mCustomParameters.find(Form("mAmplitudeRangeL%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mAmplitudeRangeL%d = ", mod + 2) + << param->second << ENDM; + mAmplitudeRangeL[mod] = stof(param->second); + } + // mAmplitudeRangeR + if (auto param = mCustomParameters.find(Form("mAmplitudeRangeR%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mAmplitudeRangeR%d = ", mod + 2) + << param->second << ENDM; + mAmplitudeRangeR[mod] = stof(param->second); + } + // mMinEventsToChaeckAmplitude + if (auto param = mCustomParameters.find(Form("mMinEventsToCheckAmplitude%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMinEventsToCheckAmplitude%d = ", mod + 2) + << param->second << ENDM; + mMinEventsToCheckAmplitude[mod] = stof(param->second); + } + + // mMinAmplitudeMean + if (auto param = mCustomParameters.find(Form("mMinAmplitudeMean%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMinAmplitudeMean%d = ", mod + 2) + << param->second << ENDM; + mMinAmplitudeMean[mod] = stof(param->second); + } + // mMaxAmplitudeMean + if (auto param = mCustomParameters.find(Form("mMaxAmplitudeMean%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMaxAmplitudeMean%d = ", mod + 2) + << param->second << ENDM; + mMaxAmplitudeMean[mod] = stof(param->second); + } + // mMinEventsToCheckClusters + if (auto param = mCustomParameters.find(Form("mMinEventsToCheckClusters%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMinEventsToCheckClusters%d = ", mod + 2) + << param->second << ENDM; + mMinEventsToCheckClusters[mod] = stoi(param->second); + } + // mCluEnergyRangeL + if (auto param = mCustomParameters.find(Form("mCluEnergyRangeL%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mCluEnergyRangeL%d = ", mod + 2) + << param->second << ENDM; + mCluEnergyRangeL[mod] = stof(param->second); + } + // mCluEnergyRangeR + if (auto param = mCustomParameters.find(Form("mCluEnergyRangeR%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mCluEnergyRangeR%d = ", mod + 2) + << param->second << ENDM; + mCluEnergyRangeR[mod] = stof(param->second); + } + // mMinCluEnergyMean + if (auto param = mCustomParameters.find(Form("mMinCluEnergyMean%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMinCluEnergyMean%d = ", mod + 2) + << param->second << ENDM; + mMinCluEnergyMean[mod] = stof(param->second); + } + // mMaxCluEnergyMean + if (auto param = mCustomParameters.find(Form("mMaxCluEnergyMean%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMaxCluEnergyMean%d = ", mod + 2) + << param->second << ENDM; + mMaxCluEnergyMean[mod] = stof(param->second); + } + + // mMinClusterSize + if (auto param = mCustomParameters.find(Form("mMinClusterSize%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMinClusterSize%d = ", mod + 2) + << param->second << ENDM; + mMinClusterSize[mod] = stof(param->second); + } + // mMaxClusterSize + if (auto param = mCustomParameters.find(Form("mMaxClusterSize%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMaxClusterSize%d = ", mod + 2) + << param->second << ENDM; + mMaxClusterSize[mod] = stof(param->second); + } + // mMinEventsToCheckDigitMap + if (auto param = mCustomParameters.find(Form("mMinEventsToCheckDigitMap%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMinEventsToCheckDigitMap%d = ", mod + 2) + << param->second << ENDM; + mMinEventsToCheckDigitMap[mod] = stoi(param->second); + } + // mNCold3GassiplexAllowed + if (auto param = mCustomParameters.find(Form("mNCold3GassiplexAllowed%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mNCold3GassiplexAllowed%d = ", mod + 2) + << param->second << ENDM; + mNCold3GassiplexAllowed[mod] = stoi(param->second); + } + // mNHot3GassiplexAllowed + if (auto param = mCustomParameters.find(Form("mNHot3GassiplexAllowed%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mNHot3GassiplexAllowed%d = ", mod + 2) + << param->second << ENDM; + mNHot3GassiplexAllowed[mod] = stoi(param->second); + } + // mHot3GassiplexCriterium + if (auto param = mCustomParameters.find(Form("mHot3GassiplexCriterium%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mHot3GassiplexCriterium%d = ", mod + 2) + << param->second << ENDM; + mHot3GassiplexCriterium[mod] = stof(param->second); + } + // mCold3GassilexCriterium + if (auto param = mCustomParameters.find(Form("mCold3GassiplexCriterium%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mCold3GassiplexCriterium%d = ", mod + 2) + << param->second << ENDM; + mCold3GassiplexCriterium[mod] = stof(param->second); + } + // mHot3GassiplexOccurance + if (auto param = mCustomParameters.find(Form("mHot3GassiplexOccurance%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mHot3GassiplexOccurance%d = ", mod + 2) + << param->second << ENDM; + mHot3GassiplexOccurance[mod] = stof(param->second); + } + // mCold3GassilexOccurance + if (auto param = mCustomParameters.find(Form("mCold3GassiplexOccurance%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mCold3GassiplexOccurance%d = ", mod + 2) + << param->second << ENDM; + mCold3GassiplexOccurance[mod] = stof(param->second); + } + // mMinDigitsPerEvent + if (auto param = mCustomParameters.find(Form("mMinDigitsPerEvent%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMinDigitsPerEvent%d = ", mod + 2) + << param->second << ENDM; + mMinDigitsPerEvent[mod] = stof(param->second); + } + // mMaxDigitsPerEvent + if (auto param = mCustomParameters.find(Form("mMaxDigitsPerEvent%d", mod + 2)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMaxDigitsPerEvent%d = ", mod + 2) + << param->second << ENDM; + mMaxDigitsPerEvent[mod] = stof(param->second); + } + } + // error occurance + for (int i = 0; i < 20; i++) { + if (auto param = mCustomParameters.find(Form("mErrorOccuranceThreshold%d", i)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mErrorOccuranceThreshold%d = ", i) + << param->second << "for the " << mErrorLabel[i] << ENDM; + mErrorOccuranceThreshold[i] = stof(param->second); + } + } + + ILOG(Info, Support) << "PhysicsCheck::configure() : configuring is done." << ENDM; + mIsConfigured = true; +} + +Quality PhysicsCheck::check(std::map>* moMap) +{ + if (!mIsConfigured) { + ILOG(Info, Support) << "PhysicsCheck::check() : I'm about to check already but configure() had not been called yet. So I call it now." << ENDM; + configure(); + } + + // default value + Quality result = Quality::Good; + + for (auto& [moName, mo] : *moMap) { + (void)moName; // trick the compiler about not used variable + for (int iMod = 0; iMod < 3; iMod++) { // loop modules + // ========================= CALIBDIGIT ENERGY ========================= + if (mo->getName() == Form("CalibDigitEnergyM%d", iMod + 2)) { + bool isGoodMO = true; + + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F*, skipping" << ENDM; + continue; + } + TPaveText* msg = new TPaveText(0.6, 0.5, 1.0, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + + auto nEvents = h->GetEntries(); + if (nEvents < mMinEventsToCheckAmplitude[iMod]) { + if (result.isBetterThan(Quality::Null)) { + result.set(Quality::Null); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("not enough statistics M%d", iMod + 2)); + msg->AddText("Not enough data to check"); + msg->SetFillColor(kOrange); + isGoodMO = false; + } else { + h->GetXaxis()->SetRangeUser(mAmplitudeRangeL[iMod], mAmplitudeRangeR[iMod]); + auto mean = h->GetMean(); + if (mean < mMinAmplitudeMean[iMod]) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too small mean M%d", iMod + 2)); + msg->AddText(Form("Mean is too small: %f", mean)); + msg->AddText(Form("Min allowed mean: %f", mMinAmplitudeMean[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + isGoodMO = false; + } else if (mean > mMaxAmplitudeMean[iMod]) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too big mean M%d", iMod + 2)); + msg->AddText(Form("Mean is too big: %f", mean)); + msg->AddText(Form("Max allowed mean: %f", mMaxAmplitudeMean[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + isGoodMO = false; + } + } + if (isGoodMO) { + msg->AddText("OK"); + msg->SetFillColor(kGreen); + } + break; // exit modules loop for this particular object + } + // ========================= CLUSTER ENERGY ========================= + if (mo->getName() == Form("ClusterTotEnergyM%d", iMod + 2)) { + bool isGoodMO = true; + + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F*, skipping" << ENDM; + continue; + } + TPaveText* msg = new TPaveText(0.6, 0.5, 1.0, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + + auto nEvents = h->GetEntries(); + if (nEvents < mMinEventsToCheckClusters[iMod]) { + if (result.isBetterThan(Quality::Null)) { + result.set(Quality::Null); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("not enough statistics M%d", iMod + 2)); + msg->AddText("Not enough data to check"); + msg->SetFillColor(kOrange); + isGoodMO = false; + } else { + h->GetXaxis()->SetRangeUser(mCluEnergyRangeL[iMod], mCluEnergyRangeR[iMod]); + auto mean = h->GetMean(); + if (mean < mMinCluEnergyMean[iMod]) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too small mean energy M%d", iMod + 2)); + msg->AddText(Form("Mean is too small: %f", mean)); + msg->AddText(Form("Min allowed mean: %f", mMinCluEnergyMean[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + isGoodMO = false; + } else if (mean > mMaxCluEnergyMean[iMod]) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too big mean energy M%d", iMod + 2)); + msg->AddText(Form("Mean is too big: %f", mean)); + msg->AddText(Form("Max allowed mean: %f", mMaxCluEnergyMean[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + isGoodMO = false; + } + } + if (isGoodMO) { + msg->AddText("OK"); + msg->SetFillColor(kGreen); + } + break; // exit modules loop for this particular object + } + + // ====================== CLUSTER SIZE ============================= + if (mo->getName() == Form("NDigitsInClusterM%d", iMod + 2)) { + + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F*, skipping" << ENDM; + continue; + } + TPaveText* msg = new TPaveText(0.6, 0.5, 1.0, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + if (h->GetEntries() < mMinEventsToCheckClusters[iMod]) { + result.addFlag(FlagTypeFactory::Unknown(), Form("not enough statistics M%d", iMod)); + msg->AddText("Not enough data to check"); + msg->SetFillColor(kOrange); + break; + } + auto meanClusterSize = h->GetMean(); + if (meanClusterSize < mMinClusterSize[iMod]) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too small mean size M%d", iMod + 2)); + msg->AddText(Form("Mean is too small: %f", meanClusterSize)); + msg->AddText(Form("Min allowed mean: %f", mMinClusterSize[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + } else if (meanClusterSize > mMaxClusterSize[iMod]) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too big mean size M%d", iMod + 2)); + msg->AddText(Form("Mean is too big: %f", meanClusterSize)); + msg->AddText(Form("Max allowed mean: %f", mMaxClusterSize[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + } else { + msg->AddText("OK"); + msg->SetFillColor(kGreen); + } + break; // exit modules loop for this particular object + } + + // ======================= DIGIT MAP ========================= + if (mo->getName() == Form("DigitMapM%d", iMod + 2)) { + + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH2F*, skipping" << ENDM; + break; + } + if (h->GetEntries() < mMinEventsToCheckDigitMap[iMod]) { + break; + } + bool isObjectGood = true; + // check Cold and hot 3gassiplex cards + int nHot3Gassiplexes = 0, nCold3Gassiplexes = 0; + TH2* h3GassiplexMap = (TH2*)h->Clone("h3GassiplexMap"); + h3GassiplexMap->Rebin2D(8, 6); + float meanOcc = 0; + for (int iX = 1; iX <= h3GassiplexMap->GetNbinsX(); iX++) { + for (int iY = 1; iY <= h3GassiplexMap->GetNbinsY(); iY++) { + meanOcc += h3GassiplexMap->GetBinContent(iX, iY); + } + } + meanOcc /= 160.; + for (int iX = 1; iX <= h3GassiplexMap->GetNbinsX(); iX++) { + for (int iY = 1; iY <= h3GassiplexMap->GetNbinsY(); iY++) { + if (h3GassiplexMap->GetBinContent(iX, iY) > meanOcc * mHot3GassiplexCriterium[iMod]) { + nHot3Gassiplexes++; + } + if (h3GassiplexMap->GetBinContent(iX, iY) < meanOcc * mCold3GassiplexCriterium[iMod]) { + nCold3Gassiplexes++; + } + } + } + if (nHot3Gassiplexes > mNHot3GassiplexAllowed[iMod]) { + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("many hot cards M%d", iMod + 2)); + TPaveText* msg = new TPaveText(0.0, 0.0, 0.2, 0.1, "NDC"); + msg->SetName(Form("%s_msgHot3G", mo->GetName())); + msg->Clear(); + msg->AddText(Form("hot 3G cards (%d/%d)", nHot3Gassiplexes, mNHot3GassiplexAllowed[iMod])); + msg->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msg); + isObjectGood = false; + } + if (nCold3Gassiplexes > mNCold3GassiplexAllowed[iMod]) { + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("many cold cards M%d", iMod + 2)); + TPaveText* msg = new TPaveText(0.0, 0.9, 0.2, 1.0, "NDC"); + msg->AddText(Form("cold 3G cards (%d/%d)", nCold3Gassiplexes, mNCold3GassiplexAllowed[iMod])); + msg->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msg); + isObjectGood = false; + } + delete h3GassiplexMap; + + // show OK message if everything is OK + if (isObjectGood) { + TPaveText* msgOk = new TPaveText(0.9, 0.9, 1.0, 1.0, "NDC"); + msgOk->SetName(Form("%s_msg", mo->GetName())); + msgOk->Clear(); + msgOk->AddText("OK"); + msgOk->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msgOk); + } + break; // exit modules loop for this particular object + } + // ======================= DIGIT OCCURANCE ========================= + if (mo->getName() == Form("DigitOccuranceM%d", iMod + 2)) { + + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH2D*, skipping" << ENDM; + break; + } + auto* hFraction = dynamic_cast(mo->getObject()); + if (hFraction->getEventCounter() < mMinEventsToCheckDigitMap[iMod]) { + break; + } + bool isObjectGood = true; + // check Cold and hot 3gassiplex cards + int nHot3Gassiplexes = 0, nCold3Gassiplexes = 0; + TH2* h3GassiplexOccurance = (TH2*)h->Clone("h3GassiplexOccurance"); + h3GassiplexOccurance->Rebin2D(8, 6); + h3GassiplexOccurance->Scale(1. / 48.); + float meanOcc = 0; + for (int iX = 1; iX <= h3GassiplexOccurance->GetNbinsX(); iX++) { + for (int iY = 1; iY <= h3GassiplexOccurance->GetNbinsY(); iY++) { + if (h3GassiplexOccurance->GetBinContent(iX, iY) > mHot3GassiplexOccurance[iMod]) { + nHot3Gassiplexes++; + } + if (h3GassiplexOccurance->GetBinContent(iX, iY) < mCold3GassiplexOccurance[iMod]) { + nCold3Gassiplexes++; + } + } + } + if (nHot3Gassiplexes > mNHot3GassiplexAllowed[iMod]) { + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("digit occurance: many hot cards M%d", iMod + 2)); + TPaveText* msg = new TPaveText(0.0, 0.0, 0.2, 0.1, "NDC"); + msg->SetName(Form("%s_msgHot3G", mo->GetName())); + msg->Clear(); + msg->AddText(Form("hot 3G cards (%d/%d)", nHot3Gassiplexes, mNHot3GassiplexAllowed[iMod])); + msg->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msg); + isObjectGood = false; + } + if (nCold3Gassiplexes > mNCold3GassiplexAllowed[iMod]) { + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("digit occurance: many cold cards M%d", iMod + 2)); + TPaveText* msg = new TPaveText(0.0, 0.9, 0.2, 1.0, "NDC"); + msg->AddText(Form("cold 3G cards (%d/%d)", nCold3Gassiplexes, mNCold3GassiplexAllowed[iMod])); + msg->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msg); + isObjectGood = false; + } + delete h3GassiplexOccurance; + + // show OK message if everything is OK + if (isObjectGood) { + TPaveText* msgOk = new TPaveText(0.9, 0.9, 1.0, 1.0, "NDC"); + msgOk->SetName(Form("%s_msg", mo->GetName())); + msgOk->Clear(); + msgOk->AddText("OK"); + msgOk->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msgOk); + } + break; // exit modules loop for this particular object + } + // ======================= PER EVENT COUNTS =========================== + if (mo->getName() == Form("DigitsInEventM%d", iMod + 2)) { + + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F*, skipping" << ENDM; + break; + } + auto mean = h->GetMean(); + if (mean > mMaxDigitsPerEvent[iMod]) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too many digits per event M%d", iMod + 2)); + TPaveText* msg = new TPaveText(0.6, 0.6, 1.0, 0.8, "NDC"); + msg->AddText(Form("Mean is too big: %f", mean)); + msg->AddText(Form("Max allowed mean: %f", mMaxDigitsPerEvent[iMod])); + msg->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msg); + } else if (mean < mMinDigitsPerEvent[iMod]) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too few digits per event M%d", iMod + 2)); + TPaveText* msg = new TPaveText(0.6, 0.6, 1.0, 0.8, "NDC"); + msg->AddText(Form("Mean is too small: %f", mean)); + msg->AddText(Form("Min allowed mean: %f", mMinDigitsPerEvent[iMod])); + msg->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msg); + } else { + TPaveText* msgOk = new TPaveText(0.0, 0.0, 0.1, 0.1, "NDC"); + msgOk->SetName(Form("%s_msg", mo->GetName())); + msgOk->Clear(); + msgOk->AddText("OK"); + msgOk->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msgOk); + } + } + } // iMod cycle + // ======================= HW ERRORS CHECK ============================ + if (mo->getName() == "ErrorTypeOccurance") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1Fraction*, skipping" << ENDM; + break; + } + bool isGoodMO = true; + for (int i = 1; i <= h->GetXaxis()->GetNbins(); i++) { + if (h->GetBinContent(i) > mErrorOccuranceThreshold[i - 1]) { + isGoodMO = false; + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too many %s errors", mErrorLabel[i - 1])); + TLatex* msg = new TLatex(0.12 + (0.2 * (i % 2)), 0.2 + (i / 2) * 0.06, Form("#color[2]{Too many %s errors}", mErrorLabel[i - 1])); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + h->SetFillColor(kOrange); + h->SetOption("hist"); + h->SetDrawOption("hist"); + h->SetMarkerStyle(21); + h->SetLineWidth(2); + } + } + if (isGoodMO) { + TPaveText* msgOk = new TPaveText(0.0, 0.0, 0.1, 0.1, "NDC"); + msgOk->SetName(Form("%s_msg", mo->GetName())); + msgOk->Clear(); + msgOk->AddText("OK"); + msgOk->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msgOk); + h->SetFillColor(kGreen); + h->SetOption("hist"); + h->SetDrawOption("hist"); + h->SetMarkerStyle(21); + h->SetLineWidth(2); + } + } + + } // moMap cycle + + return result; +} + +void PhysicsCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + return; // do noting for the time being. Maybe in the future we will do something sofisticated + for (int iMod = 0; iMod < 3; iMod++) { // loop over modules + if (mo->getName() == Form("PhysicsValueM%d", iMod + 2)) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F*, skipping" << ENDM; + continue; + } + + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + // + } else if (checkResult == Quality::Bad) { + ILOG(Info, Support) << "beautify() : Quality::Bad, setting to red for " + << mo->GetName() << ENDM; + h->SetFillColor(kRed); + // + } else if (checkResult == Quality::Medium) { + ILOG(Error, Support) << "beautify() : unexpected quality for " << mo->GetName() << ENDM; + h->SetFillColor(kOrange); + } + return; // exit when object is processed + } + } +} + +} // namespace o2::quality_control_modules::cpv diff --git a/Modules/CPV/src/PhysicsTask.cxx b/Modules/CPV/src/PhysicsTask.cxx new file mode 100644 index 0000000000..923615d9d9 --- /dev/null +++ b/Modules/CPV/src/PhysicsTask.cxx @@ -0,0 +1,758 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PhysicsTask.cxx +/// \author Sergey Evdokimov +/// + +#include +#include +#include +#include "QualityControl/QcInfoLogger.h" +#include "CPV/PhysicsTask.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::cpv +{ + +PhysicsTask::~PhysicsTask() +{ + for (int i = 0; i < kNHist1D; i++) { + if (mHist1D[i]) { + mHist1D[i]->Delete(); + mHist1D[i] = nullptr; + } + } + for (int i = 0; i < kNHist2D; i++) { + if (mHist2D[i]) { + mHist2D[i]->Delete(); + mHist2D[i] = nullptr; + } + } +} + +void PhysicsTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize PhysicsTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + if (auto param = mCustomParameters.find("ccdbCheckInterval"); param != mCustomParameters.end()) { + mCcdbCheckIntervalInMinutes = stoi(param->second); + ILOG(Debug, Devel) << "Custom parameter - ccdbCheckInterval: " << mCcdbCheckIntervalInMinutes << ENDM; + } else { + ILOG(Info, Devel) << "Default parameter - ccdbCheckInterval: " << mCcdbCheckIntervalInMinutes << ENDM; + } + if (auto param = mCustomParameters.find("isAsyncMode"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter : isAsyncMode " << param->second << ENDM; + mIsAsyncMode = stoi(param->second); + ILOG(Info, Devel) << "I set mIsAsyncMode = " << mIsAsyncMode << ENDM; + } else { + ILOG(Info, Devel) << "Default parameter : isAsyncMode = " << mIsAsyncMode << ENDM; + } + + initHistograms(); + mNEventsTotal = 0; +} + +void PhysicsTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity() : resetting everything" << activity.mId << ENDM; + resetHistograms(); + mNEventsTotal = 0; +} + +void PhysicsTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + mCycleNumber++; +} + +void PhysicsTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + static bool isFirstTime = true; + + int nInputs = ctx.inputs().size(); + mHist1D[H1DNInputs]->Fill(nInputs); + + int nValidInputs = ctx.inputs().countValidInputs(); + mHist1D[H1DNValidInputs]->Fill(nValidInputs); + + bool hasClusters = false, hasDigits = false, hasCalibDigits = false, hasRawErrors = false; + bool hasGains = false, hasPedestals = false, hasBadChannelMap = false; + for (auto&& input : o2::framework::InputRecordWalker(ctx.inputs())) { + // get message header + if (input.header != nullptr && input.payload != nullptr) { + const auto* header = o2::framework::DataRefUtils::getHeader(input); + auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(input); + // LOG(info) << "monitorData() : obtained input " << header->dataOrigin.str << "/" << header->dataDescription.str; + if ((strcmp(header->dataOrigin.str, "CPV") == 0) && (strcmp(header->dataDescription.str, "DIGITS") == 0)) { + // LOG(info) << "monitorData() : I found digits in inputs"; + hasDigits = true; + } + if ((strcmp(header->dataOrigin.str, "CPV") == 0) && (strcmp(header->dataDescription.str, "CLUSTERS") == 0)) { + // LOG(info) << "monitorData() : I found clusters in inputs"; + hasClusters = true; + } + if ((strcmp(header->dataOrigin.str, "CPV") == 0) && (strcmp(header->dataDescription.str, "CALIBDIGITS") == 0)) { + // LOG(info) << "monitorData() : I found calibdigits in inputs"; + hasCalibDigits = true; + } + if ((strcmp(header->dataOrigin.str, "CPV") == 0) && (strcmp(header->dataDescription.str, "RAWHWERRORS") == 0)) { + // LOG(info) << "monitorData() : I found rawhwerrors in inputs"; + hasRawErrors = true; + } + if ((strcmp(header->dataOrigin.str, "CPV") == 0) && (strcmp(header->dataDescription.str, "CPV_Pedestals") == 0)) { + // LOG(info) << "monitorData() : I found peds in inputs"; + hasPedestals = true; + } + if ((strcmp(header->dataOrigin.str, "CPV") == 0) && (strcmp(header->dataDescription.str, "CPV_BadMap") == 0)) { + // LOG(info) << "monitorData() : I found badmap in inputs"; + hasBadChannelMap = true; + } + if ((strcmp(header->dataOrigin.str, "CPV") == 0) && (strcmp(header->dataDescription.str, "CPV_Gains") == 0)) { + // LOG(info) << "monitorData() : I found gains in inputs"; + hasGains = true; + } + + // get payload of a specific input, which is a char array. + // const char* payload = input.payload; + + // for the sake of an example, let's fill the histogram with payload sizes + mHist1D[H1DInputPayloadSize]->Fill(payloadSize); + } + } + + // HW Raw Errors + if (hasRawErrors) { + auto rawErrors = ctx.inputs().get>("rawerrors"); + for (const auto& rawError : rawErrors) { + mHist1D[H1DRawErrors]->Fill(rawError.errortype); + mFractions1D[F1DErrorTypeOccurance]->fillUnderlying(rawError.errortype); + } + } + + // digits + if (hasDigits) { + auto digits = ctx.inputs().get>("digits"); + auto digitsTR = ctx.inputs().get>("dtrigrec"); + mFractions1D[F1DErrorTypeOccurance]->increaseEventCounter(digitsTR.size()); + mHist1D[H1DNDigitsPerInput]->Fill(digits.size()); + unsigned short nDigPerEvent[3]; + for (const auto& trigRecord : digitsTR) { + mHist1D[H1DBCsFromDigits]->Fill(trigRecord.getBCData().bc); + // this file has a mixture of ILOG and LOG, perhaps it could be unified. + LOG(debug) << " monitorData() : digit trigger record #" << mNEventsTotal + << " contains " << trigRecord.getNumberOfObjects() << " objects."; + mNEventsTotal++; + mHist1D[H1DDigitsInEventM2M3M4]->Fill(trigRecord.getNumberOfObjects()); + memset(nDigPerEvent, 0, 3 * sizeof(short)); + if (trigRecord.getNumberOfObjects() > 0) { + for (int iDig = trigRecord.getFirstEntry(); iDig < trigRecord.getFirstEntry() + trigRecord.getNumberOfObjects(); iDig++) { + mHist1D[H1DDigitIds]->Fill(digits[iDig].getAbsId()); + short relId[3]; + if (Geometry::absToRelNumbering(digits[iDig].getAbsId(), relId)) { + // reminder: relId[3]={Module, phi col, z row} where Module=2..4, phi col=0..127, z row=0..59 + mHist2D[H2DDigitMapM2 + relId[0] - 2]->Fill(relId[1], relId[2]); + mHist1D[H1DDigitEnergyM2 + relId[0] - 2]->Fill(digits[iDig].getAmplitude()); + mFractions2D[F2DDigitFreqM2 + relId[0] - 2]->fillUnderlying(relId[1], relId[2]); + nDigPerEvent[relId[0] - 2]++; + } + } + } + for (int i = 0; i < 3; i++) { + mHist1D[H1DDigitsInEventM2 + i]->Fill(nDigPerEvent[i]); + } + } + for (int iMod = 0; iMod < kNModules; iMod++) { + mFractions2D[F2DDigitFreqM2 + iMod]->increaseEventCounter(digitsTR.size()); + } + } + + // clusters + if (hasClusters) { + auto clusters = ctx.inputs().get>("clusters"); + auto clustersTR = ctx.inputs().get>("ctrigrec"); + mHist1D[H1DNClustersPerInput]->Fill(clusters.size()); + unsigned short nCluPerEvent[3]; + for (const auto& trigRecord : clustersTR) { + mHist1D[H1DBCsFromClusters]->Fill(trigRecord.getBCData().bc); + mHist1D[H1DClustersInEventM2M3M4]->Fill(trigRecord.getNumberOfObjects()); + memset(nCluPerEvent, 0, 3 * sizeof(short)); + if (trigRecord.getNumberOfObjects() > 0) { + for (int iClu = trigRecord.getFirstEntry(); iClu < trigRecord.getFirstEntry() + trigRecord.getNumberOfObjects(); iClu++) { + // reminder: relId[3]={Module, phi col, z row} where Module=2..4, phi col=0..127, z row=0..59 + char mod = clusters[iClu].getModule(); + nCluPerEvent[mod - 2]++; + float x, z, totEn; + clusters[iClu].getLocalPosition(x, z); + totEn = clusters[iClu].getEnergy(); + int mult = clusters[iClu].getMultiplicity(); + mHist2D[H2DClusterMapM2 + mod - 2]->Fill(x, z); + mHist1D[H1DClusterTotEnergyM2 + mod - 2]->Fill(totEn); + mHist1D[H1DNDigitsInClusterM2 + mod - 2]->Fill(mult); + } + } + for (unsigned char mod = 0; mod <= 2; mod++) { + mHist1D[H1DClustersInEventM2 + mod]->Fill(nCluPerEvent[mod]); + } + } + } + + // calib digits + if (hasCalibDigits) { + auto calibDigits = ctx.inputs().get>("calibdigits"); + mHist1D[H1DNCalibDigitsPerInput]->Fill(calibDigits.size()); + for (const auto& digit : calibDigits) { + mHist1D[H1DCalibDigitIds]->Fill(digit.getAbsId()); + short relId[3]; + if (Geometry::absToRelNumbering(digit.getAbsId(), relId)) { + // reminder: relId[3]={Module, phi col, z row} where Module=2..4, phi col=0..127, z row=0..59 + mHist2D[H2DCalibDigitMapM2 + relId[0] - 2]->Fill(relId[1], relId[2]); + mHist1D[H1DCalibDigitEnergyM2 + relId[0] - 2]->Fill(digit.getAmplitude()); + } + } + } + + // !!!todo + // we need somehow to extract timestamp from data when there are no ccdb dpl fetcher inputs available + // however most of the time ccdb dpl fetcher is present. take care of it later + + static auto startTime = std::chrono::system_clock::now(); // remember time when we first time checked ccdb + static int minutesPassed = 0; // count how much minutes passed from 1st ccdb check + bool checkCcdbEntries = isFirstTime && (!mIsAsyncMode); // check at first time or when mCcdbCheckIntervalInMinutes passed + auto now = std::chrono::system_clock::now(); + if (((now - startTime) / std::chrono::minutes(1)) > minutesPassed) { + minutesPassed = (now - startTime) / std::chrono::minutes(1); + LOG(info) << "minutes passed since first monitorData() call: " << minutesPassed; + if (minutesPassed % mCcdbCheckIntervalInMinutes == 0) { + checkCcdbEntries = true; + LOG(info) << "checking CCDB objects"; + } + } + + if (checkCcdbEntries) { + // retrieve gains + const o2::cpv::CalibParams* gains = nullptr; + if (hasGains) { + LOG(info) << "Retrieving CPV/Calib/Gains from DPL fetcher (i.e. internal-dpl-ccdb-backend)"; + std::decay_t("gains"))> gainsPtr{}; + gainsPtr = ctx.inputs().get("gains"); + gains = gainsPtr.get(); + } else { + LOG(info) << "Retrieving CPV/Calib/Gains directly from CCDB"; + gains = TaskInterface::retrieveConditionAny("CPV/Calib/Gains"); + } + if (gains) { + LOG(info) << "Retrieved CPV/Calib/Gains"; + short relId[3]; + for (int iMod = 0; iMod < kNModules; iMod++) { + for (int iCh = iMod * kNChannels / kNModules; iCh < (iMod + 1) * kNChannels / kNModules; iCh++) { + if (o2::cpv::Geometry::absToRelNumbering(iCh, relId)) { + mIntensiveHist2D[H2DGainsM2 + iMod]->SetBinContent(relId[1] + 1, relId[2] + 1, gains->getGain(iCh)); + } + } + mIntensiveHist2D[H2DGainsM2 + iMod]->setCycleNumber(mCycleNumber); + } + } else { + LOG(info) << "failed to retrieve CPV/Calib/Gains"; + } + + // retrieve bad channel map + const o2::cpv::BadChannelMap* bcm = nullptr; + if (hasBadChannelMap) { + LOG(info) << "Retrieving CPV/Calib/BadChannelMap from DPL fetcher (i.e. internal-dpl-ccdb-backend)"; + std::decay_t("badmap"))> bcmPtr{}; + bcmPtr = ctx.inputs().get("badmap"); + bcm = bcmPtr.get(); + } else { + LOG(info) << "Retrieving CPV/Calib/BadChannelMap directly from CCDB"; + bcm = TaskInterface::retrieveConditionAny("CPV/Calib/BadChannelMap"); + } + if (bcm) { + LOG(info) << "Retrieved CPV/Calib/BadChannelMap"; + short relId[3]; + for (int iMod = 0; iMod < kNModules; iMod++) { + for (int iCh = iMod * kNChannels / kNModules; iCh < (iMod + 1) * kNChannels / kNModules; iCh++) { + if (o2::cpv::Geometry::absToRelNumbering(iCh, relId)) { + mIntensiveHist2D[H2DBadChannelMapM2 + iMod]->SetBinContent(relId[1] + 1, relId[2] + 1, !bcm->isChannelGood(iCh)); + } + } + mIntensiveHist2D[H2DBadChannelMapM2 + iMod]->setCycleNumber(mCycleNumber); + } + } else { + LOG(info) << "failed to retrieve CPV/Calib/BadChannelMap"; + } + + // retrieve pedestals + const o2::cpv::Pedestals* peds = nullptr; + if (hasPedestals) { + LOG(info) << "Retrieving CPV/Calib/Pedestals from DPL fetcher (i.e. internal-dpl-ccdb-backend)"; + std::decay_t("peds"))> pedsPtr{}; + pedsPtr = ctx.inputs().get("peds"); + peds = pedsPtr.get(); + } else { + LOG(info) << "Retrieving CPV/Calib/Pedestals directly from CCDB"; + peds = TaskInterface::retrieveConditionAny("CPV/Calib/Pedestals"); + } + if (peds) { + LOG(info) << "Retrieved CPV/Calib/Pedestals"; + short relId[3]; + for (int iMod = 0; iMod < kNModules; iMod++) { + for (int iCh = iMod * kNChannels / kNModules; iCh < (iMod + 1) * kNChannels / kNModules; iCh++) { + if (o2::cpv::Geometry::absToRelNumbering(iCh, relId)) { + mIntensiveHist2D[H2DPedestalValueM2 + iMod]->SetBinContent(relId[1] + 1, relId[2] + 1, peds->getPedestal(iCh)); + mIntensiveHist2D[H2DPedestalSigmaM2 + iMod]->SetBinContent(relId[1] + 1, relId[2] + 1, peds->getPedSigma(iCh)); + } + } + mIntensiveHist2D[H2DPedestalValueM2 + iMod]->setCycleNumber(mCycleNumber); + mIntensiveHist2D[H2DPedestalSigmaM2 + iMod]->setCycleNumber(mCycleNumber); + } + } else { + LOG(info) << "failed to retrieve CPV/Calib/Pedestals"; + } + } + + isFirstTime = false; +} + +void PhysicsTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + for (int i = 0; i < kNfractions1D; i++) { + mFractions1D[i]->update(); + } + for (int i = 0; i < kNfractions2D; i++) { + mFractions2D[i]->update(); + } +} + +void PhysicsTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void PhysicsTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "PhysicsTask::reset() : resetting the histograms" << ENDM; + resetHistograms(); + mNEventsTotal = 0; + mJustWasReset = true; +} + +void PhysicsTask::initHistograms() +{ + ILOG(Info, Devel) << "initing histograms" << ENDM; + // 1D Histos + if (!mHist1D[H1DBCsFromDigits]) { + mHist1D[H1DBCsFromDigits] = + new TH1F("BCsFromDigits", "BCs of digit trigger records", 4000, 0, 4000); + getObjectsManager()->startPublishing(mHist1D[H1DBCsFromDigits]); + } else { + mHist1D[H1DBCsFromDigits]->Reset(); + } + + if (!mHist1D[H1DBCsFromClusters]) { + mHist1D[H1DBCsFromClusters] = + new TH1F("BCsFromClusters", "BCs of cluster trigger records", 4000, 0, 4000); + getObjectsManager()->startPublishing(mHist1D[H1DBCsFromClusters]); + } else { + mHist1D[H1DBCsFromClusters]->Reset(); + } + + if (!mHist1D[H1DInputPayloadSize]) { + mHist1D[H1DInputPayloadSize] = + new TH1F("InputPayloadSize", "Input Payload Size", 30000, 0, 30000000); + getObjectsManager()->startPublishing(mHist1D[H1DInputPayloadSize]); + } else { + mHist1D[H1DInputPayloadSize]->Reset(); + } + + if (!mHist1D[H1DNInputs]) { + mHist1D[H1DNInputs] = new TH1F("NInputs", "Number of inputs", 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHist1D[H1DNInputs]); + } else { + mHist1D[H1DNInputs]->Reset(); + } + + if (!mHist1D[H1DNValidInputs]) { + mHist1D[H1DNValidInputs] = + new TH1F("NValidInputs", "Number of valid inputs", 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHist1D[H1DNValidInputs]); + } else { + mHist1D[H1DNValidInputs]->Reset(); + } + + if (!mHist1D[H1DNDigitsPerInput]) { + mHist1D[H1DNDigitsPerInput] = + new TH1F("NDigitsPerInput", "Number of digits per input", 30000, 0, 300000); + getObjectsManager()->startPublishing(mHist1D[H1DNDigitsPerInput]); + } else { + mHist1D[H1DNDigitsPerInput]->Reset(); + } + + if (!mHist1D[H1DNCalibDigitsPerInput]) { + mHist1D[H1DNCalibDigitsPerInput] = + new TH1F("NCalibDigitsPerInput", "Number of CalibDigits per input", 30000, 0, 300000); + getObjectsManager()->startPublishing(mHist1D[H1DNCalibDigitsPerInput]); + } else { + mHist1D[H1DNCalibDigitsPerInput]->Reset(); + } + + if (!mHist1D[H1DNClustersPerInput]) { + mHist1D[H1DNClustersPerInput] = + new TH1F("NClustersPerInput", "Number of clusters per input", 30000, 0, 300000); + getObjectsManager()->startPublishing(mHist1D[H1DNClustersPerInput]); + } else { + mHist1D[H1DNClustersPerInput]->Reset(); + } + + if (!mHist1D[H1DDigitIds]) { + mHist1D[H1DDigitIds] = new TH1F("DigitIds", "Digit Ids", 30000, -0.5, 29999.5); + getObjectsManager()->startPublishing(mHist1D[H1DDigitIds]); + } else { + mHist1D[H1DDigitIds]->Reset(); + } + + if (!mHist1D[H1DCalibDigitIds]) { + mHist1D[H1DCalibDigitIds] = new TH1F("CalibDigitIds", "CalibDigit Ids", 30000, -0.5, 29999.5); + getObjectsManager()->startPublishing(mHist1D[H1DCalibDigitIds]); + } else { + mHist1D[H1DCalibDigitIds]->Reset(); + } + + if (!mHist1D[H1DDigitsInEventM2M3M4]) { + mHist1D[H1DDigitsInEventM2M3M4] = + new TH1F("NDigitsInEventM2M3M4", "Number of Digits per event", 23040, 0., 23040.); + getObjectsManager()->startPublishing(mHist1D[H1DDigitsInEventM2M3M4]); + } else { + mHist1D[H1DDigitsInEventM2M3M4]->Reset(); + } + + if (!mHist1D[H1DClustersInEventM2M3M4]) { + mHist1D[H1DClustersInEventM2M3M4] = + new TH1F("NClustersInEventM2M3M4", "Number of clusters per event", 23040, 0., 23040.); + getObjectsManager()->startPublishing(mHist1D[H1DClustersInEventM2M3M4]); + } else { + mHist1D[H1DClustersInEventM2M3M4]->Reset(); + } + + if (!mHist1D[H1DRawErrors]) { + mHist1D[H1DRawErrors] = + new TH1F("RawErrors", "Raw Errors", 20, 0., 20.); + getObjectsManager()->startPublishing(mHist1D[H1DRawErrors]); + } else { + mHist1D[H1DRawErrors]->Reset(); + } + + if (!mFractions1D[F1DErrorTypeOccurance]) { + mFractions1D[F1DErrorTypeOccurance] = new TH1Fraction("ErrorTypeOccurance", "Errors of differen types per event", 20, 0, 20); + mFractions1D[F1DErrorTypeOccurance]->GetXaxis()->SetTitle("Error Type"); + mFractions1D[F1DErrorTypeOccurance]->SetStats(0); + mFractions1D[F1DErrorTypeOccurance]->GetYaxis()->SetTitle("Occurance (event^{-1})"); + const char* errorLabel[] = { + "ok", + "no payload", + "rdh decod", + "rdh invalid", + "not cpv rdh", + "no stopbit", + "page not found", + "0 offset to next", + "payload incomplete", + "no cpv header", + "no cpv trailer", + "cpv header invalid", + "cpv trailer invalid", + "segment header err", + "row header error", + "EOE header error", + "pad error", + "unknown word", + "pad address", + "wrong data format" + }; + for (int i = 1; i <= 20; i++) { + mFractions1D[F1DErrorTypeOccurance]->GetXaxis()->SetBinLabel(i, errorLabel[i - 1]); + } + getObjectsManager()->startPublishing(mFractions1D[F1DErrorTypeOccurance]); + } else { + mFractions1D[F1DErrorTypeOccurance]->Reset(); + } + + int nPadsX = Geometry::kNumberOfCPVPadsPhi; + int nPadsZ = Geometry::kNumberOfCPVPadsZ; + float rangeX = Geometry::kCPVPadSizePhi / 2. * nPadsX + 10.; + float rangeZ = Geometry::kCPVPadSizeZ / 2. * nPadsZ + 10.; + + for (int mod = 0; mod < kNModules; mod++) { + // 1D + // Digit energy + if (!mHist1D[H1DDigitEnergyM2 + mod]) { + mHist1D[H1DDigitEnergyM2 + mod] = + new TH1F( + Form("DigitEnergyM%d", mod + 2), + Form("Digit energy distribution M%d", mod + 2), + 1000, 0, 1000.); + mHist1D[H1DDigitEnergyM2 + mod]->GetXaxis()->SetTitle("Digit energy"); + getObjectsManager()->startPublishing(mHist1D[H1DDigitEnergyM2 + mod]); + } else { + mHist1D[H1DDigitEnergyM2 + mod]->Reset(); + } + // calib digits + if (!mHist1D[H1DCalibDigitEnergyM2 + mod]) { + mHist1D[H1DCalibDigitEnergyM2 + mod] = + new TH1F( + Form("CalibDigitEnergyM%d", mod + 2), + Form("CalibDigit energy distribution M%d", mod + 2), + 1000, 0, 1000.); + mHist1D[H1DCalibDigitEnergyM2 + mod]->GetXaxis()->SetTitle("CalibDigit energy"); + getObjectsManager()->startPublishing(mHist1D[H1DCalibDigitEnergyM2 + mod]); + } else { + mHist1D[H1DCalibDigitEnergyM2 + mod]->Reset(); + } + + // N Digits per event + if (!mHist1D[H1DDigitsInEventM2 + mod]) { + mHist1D[H1DDigitsInEventM2 + mod] = + new TH1F( + Form("DigitsInEventM%d", mod + 2), + Form("Digits per event in M%d", mod + 2), + Geometry::kNCHANNELS / 3, 0., float(Geometry::kNCHANNELS / 3)); + mHist1D[H1DDigitsInEventM2 + mod]->GetXaxis()->SetTitle("Number of digits"); + getObjectsManager()->startPublishing(mHist1D[H1DDigitsInEventM2 + mod]); + } else { + mHist1D[H1DDigitsInEventM2 + mod]->Reset(); + } + + // Total cluster energy + if (!mHist1D[H1DClusterTotEnergyM2 + mod]) { + mHist1D[H1DClusterTotEnergyM2 + mod] = + new TH1F( + Form("ClusterTotEnergyM%d", mod + 2), + Form("Total cluster energy distribution M%d", mod + 2), + 2000, 0, 2000.); + mHist1D[H1DClusterTotEnergyM2 + mod]->GetXaxis()->SetTitle("cluster energy"); + getObjectsManager()->startPublishing(mHist1D[H1DClusterTotEnergyM2 + mod]); + } else { + mHist1D[H1DClusterTotEnergyM2 + mod]->Reset(); + } + + // Number of digits in cluster + if (!mHist1D[H1DNDigitsInClusterM2 + mod]) { + mHist1D[H1DNDigitsInClusterM2 + mod] = + new TH1F( + Form("NDigitsInClusterM%d", mod + 2), + Form("Multiplicity of digits in cluster M%d", mod + 2), + 50, 0, 50.); + mHist1D[H1DNDigitsInClusterM2 + mod]->GetXaxis()->SetTitle("Number of digits"); + getObjectsManager()->startPublishing(mHist1D[H1DNDigitsInClusterM2 + mod]); + } else { + mHist1D[H1DNDigitsInClusterM2 + mod]->Reset(); + } + + // N clusters per event + if (!mHist1D[H1DClustersInEventM2 + mod]) { + mHist1D[H1DClustersInEventM2 + mod] = + new TH1F( + Form("ClustersInEventM%d", mod + 2), + Form("Clusters per event in M%d", mod + 2), + Geometry::kNCHANNELS / 3, 0., float(Geometry::kNCHANNELS / 3)); + mHist1D[H1DClustersInEventM2 + mod]->GetXaxis()->SetTitle("Number of digits"); + getObjectsManager()->startPublishing(mHist1D[H1DClustersInEventM2 + mod]); + } else { + mHist1D[H1DClustersInEventM2 + mod]->Reset(); + } + + // 2D + // digit map + if (!mHist2D[H2DDigitMapM2 + mod]) { + mHist2D[H2DDigitMapM2 + mod] = + new TH2F( + Form("DigitMapM%d", 2 + mod), + Form("Digit Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DDigitMapM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DDigitMapM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DDigitMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DDigitMapM2 + mod]); + } else { + mHist2D[H2DDigitMapM2 + mod]->Reset(); + } + + // CalibDigit map + if (!mHist2D[H2DCalibDigitMapM2 + mod]) { + mHist2D[H2DCalibDigitMapM2 + mod] = + new TH2F( + Form("CalibDigitMapM%d", 2 + mod), + Form("CalibDigit Map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mHist2D[H2DCalibDigitMapM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mHist2D[H2DCalibDigitMapM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mHist2D[H2DCalibDigitMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DCalibDigitMapM2 + mod]); + } else { + mHist2D[H2DCalibDigitMapM2 + mod]->Reset(); + } + + // cluster map + if (!mHist2D[H2DClusterMapM2 + mod]) { + mHist2D[H2DClusterMapM2 + mod] = + new TH2F( + Form("ClusterMapM%d", 2 + mod), + Form("Cluster Map in M%d", mod + 2), + 200, -rangeX, rangeX, + 200, -rangeZ, rangeZ); + mHist2D[H2DClusterMapM2 + mod]->GetXaxis()->SetTitle("x, cm"); + mHist2D[H2DClusterMapM2 + mod]->GetYaxis()->SetTitle("z, cm"); + mHist2D[H2DClusterMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[H2DClusterMapM2 + mod]); + } else { + mHist2D[H2DClusterMapM2 + mod]->Reset(); + } + + // pedestal values map + if (!mIntensiveHist2D[H2DPedestalValueM2 + mod]) { + mIntensiveHist2D[H2DPedestalValueM2 + mod] = + new IntensiveTH2F( + Form("PedestalValueM%d", 2 + mod), + Form("Pedestal values in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mIntensiveHist2D[H2DPedestalValueM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mIntensiveHist2D[H2DPedestalValueM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mIntensiveHist2D[H2DPedestalValueM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mIntensiveHist2D[H2DPedestalValueM2 + mod]); + } else { + mIntensiveHist2D[H2DPedestalValueM2 + mod]->Reset(); + mIntensiveHist2D[H2DPedestalValueM2 + mod]->setCycleNumber(0); + } + + // pedestal sigma map + if (!mIntensiveHist2D[H2DPedestalSigmaM2 + mod]) { + mIntensiveHist2D[H2DPedestalSigmaM2 + mod] = + new IntensiveTH2F( + Form("PedestalSigmaM%d", 2 + mod), + Form("Pedestal Sigmas in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mIntensiveHist2D[H2DPedestalSigmaM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mIntensiveHist2D[H2DPedestalSigmaM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mIntensiveHist2D[H2DPedestalSigmaM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mIntensiveHist2D[H2DPedestalSigmaM2 + mod]); + } else { + mIntensiveHist2D[H2DPedestalSigmaM2 + mod]->Reset(); + mIntensiveHist2D[H2DPedestalSigmaM2 + mod]->setCycleNumber(0); + } + + // bad channel map + if (!mIntensiveHist2D[H2DBadChannelMapM2 + mod]) { + mIntensiveHist2D[H2DBadChannelMapM2 + mod] = + new IntensiveTH2F( + Form("BadChannelMapM%d", 2 + mod), + Form("Bad channel map in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mIntensiveHist2D[H2DBadChannelMapM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mIntensiveHist2D[H2DBadChannelMapM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mIntensiveHist2D[H2DBadChannelMapM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mIntensiveHist2D[H2DBadChannelMapM2 + mod]); + } else { + mIntensiveHist2D[H2DBadChannelMapM2 + mod]->Reset(); + mIntensiveHist2D[H2DBadChannelMapM2 + mod]->setCycleNumber(0); + } + + // gains map + if (!mIntensiveHist2D[H2DGainsM2 + mod]) { + mIntensiveHist2D[H2DGainsM2 + mod] = + new IntensiveTH2F( + Form("GainsM%d", 2 + mod), + Form("Gains in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mIntensiveHist2D[H2DGainsM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mIntensiveHist2D[H2DGainsM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mIntensiveHist2D[H2DGainsM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mIntensiveHist2D[H2DGainsM2 + mod]); + } else { + mIntensiveHist2D[H2DGainsM2 + mod]->Reset(); + mIntensiveHist2D[H2DGainsM2 + mod]->setCycleNumber(0); + } + + // digit occurance per event + if (!mFractions2D[F2DDigitFreqM2 + mod]) { + mFractions2D[F2DDigitFreqM2 + mod] = + new TH2Fraction( + Form("DigitOccuranceM%d", 2 + mod), + Form("Digit occurance per event in M%d", mod + 2), + nPadsX, -0.5, nPadsX - 0.5, + nPadsZ, -0.5, nPadsZ - 0.5); + mFractions2D[F2DDigitFreqM2 + mod]->GetXaxis()->SetTitle("x, pad"); + mFractions2D[F2DDigitFreqM2 + mod]->GetYaxis()->SetTitle("z, pad"); + mFractions2D[F2DDigitFreqM2 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mFractions2D[F2DDigitFreqM2 + mod]); + } else { + mFractions2D[F2DDigitFreqM2 + mod]->Reset(); + } + } +} + +void PhysicsTask::resetHistograms() +{ + // clean all histograms + ILOG(Debug, Devel) << "Resetting the 1D Histograms" << ENDM; + for (int itHist1D = H1DInputPayloadSize; itHist1D < kNHist1D; itHist1D++) { + if (mHist1D[itHist1D]) { + mHist1D[itHist1D]->Reset(); + } else { + ILOG(Info, Support) << "1D histo " << itHist1D << " is not created yet!"; + } + } + + ILOG(Debug, Devel) << "Resetting the 2D Histograms" << ENDM; + for (int itHist2D = H2DDigitMapM2; itHist2D < kNHist2D; itHist2D++) { + if (mHist2D[itHist2D]) { + mHist2D[itHist2D]->Reset(); + } + } + ILOG(Debug, Devel) << "Resetting the 2D Intensive Histograms" << ENDM; + for (int itIntHist2D = 0; itIntHist2D < kNIntensiveHist2D; itIntHist2D++) { + if (mIntensiveHist2D[itIntHist2D]) { + mIntensiveHist2D[itIntHist2D]->Reset(); + mIntensiveHist2D[itIntHist2D]->setCycleNumber(0); + } + } + for (int i = 0; i < kNfractions1D; i++) { + if (mFractions1D[i]) { + mFractions1D[i]->Reset(); + } + } + for (int i = 0; i < kNfractions2D; i++) { + if (mFractions2D[i]) { + mFractions2D[i]->Reset(); + } + } +} + +} // namespace o2::quality_control_modules::cpv diff --git a/Modules/CPV/test/testQcCPV.cxx b/Modules/CPV/test/testQcCPV.cxx new file mode 100644 index 0000000000..fb53acbc98 --- /dev/null +++ b/Modules/CPV/test/testQcCPV.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCPV.cxx +/// \author My Name +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::cpv +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::cpv diff --git a/Modules/CTP/CMakeLists.txt b/Modules/CTP/CMakeLists.txt new file mode 100644 index 0000000000..02a4dfdc88 --- /dev/null +++ b/Modules/CTP/CMakeLists.txt @@ -0,0 +1,56 @@ +# ---- Library ---- + +add_library(O2QcCTP + src/TrendingConfigCTP.cxx + src/TH1ctpReductor.cxx ) + +target_sources(O2QcCTP PRIVATE + src/RawDataReaderCheck.cxx + src/CountersQcTask.cxx + src/RawDataQcTask.cxx + src/TH1ctpReductor.cxx + src/CTPTrendingTask.cxx ) + +target_include_directories( + O2QcCTP + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcCTP PUBLIC O2QualityControl O2QcCommon O2::DataFormatsCTP O2::CTPReconstruction O2QcCommon) + +install(TARGETS O2QcCTP + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcCTP + HEADERS include/CTP/CountersQcTask.h + include/CTP/RawDataQcTask.h + include/CTP/RawDataReaderCheck.h + include/CTP/TH1ctpReductor.h + include/CTP/CTPTrendingTask.h + include/CTP/TrendingConfigCTP.h + LINKDEF include/CTP/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/CTP + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcCTP.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcCTP Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + +# ---- Extra scripts ---- diff --git a/Modules/CTP/basic-ctp.json b/Modules/CTP/basic-ctp.json new file mode 100644 index 0000000000..6dde42384d --- /dev/null +++ b/Modules/CTP/basic-ctp.json @@ -0,0 +1,60 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "MyRawDataQcTask": { + "active": "true", + "className": "o2::quality_control_modules::ctp::CTPRawDataReaderTask", + "moduleName": "QcCTP", + "detectorName": "CTP", + "cycleDurationSeconds": "10", + "": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tst-raw" + }, + "taskParameters": { + "myOwnKey": "myOwnValue" + }, + "location": "remote" + } + }, + }, + "dataSamplingPolicies": [ + { + "id": "ctp-raw", + "active": "true", + "machines": [], + "query": "random:CTP/RAWDATA/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/CTP/include/CTP/CTPTrendingTask.h b/Modules/CTP/include/CTP/CTPTrendingTask.h new file mode 100644 index 0000000000..56420831f9 --- /dev/null +++ b/Modules/CTP/include/CTP/CTPTrendingTask.h @@ -0,0 +1,89 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CTPTrendingTask.h +/// \author Lucia Anna Tarasovicova +/// + +#ifndef QUALITYCONTROL_CTPTTRENDINGTASK_H +#define QUALITYCONTROL_CTPTTRENDINGTASK_H + +#include "QualityControl/PostProcessingInterface.h" +#include "CTP/TrendingConfigCTP.h" +#include +#include "CTP/TH1ctpReductor.h" + +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} // namespace o2::quality_control::repository + +namespace o2::quality_control::postprocessing +{ +/// \brief A post-processing task for trending ctp input and class rates +/// + +class CTPTrendingTask : public PostProcessingInterface +{ + public: + /// \brief Constructor. + CTPTrendingTask() = default; + /// \brief Destructor. + ~CTPTrendingTask() override = default; + + /// \brief Post-processing methods inherited from 'PostProcessingInterface'. + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + void initCTP(Trigger& t); + + private: + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, quality_control::repository::DatabaseInterface& qcdb); + void generatePlots(); + + TrendingConfigCTP mConfig; + MetaData mMetaData; + UInt_t mTime; // this is not a specific time, used for x axis in trending plots, this name is used in all other modules, please suggest a new name if not appropriate + + std::string mInputNamesDefault[5] = { "MTVX", "MVBA", "0DMC", "0EMC", "0PH0" }; // ctp inputs to be trended by default, unless modified in config.json + std::string mTrendedInputNames[5] = { "inputContentMinBias1:time", "inputContentMinBias2:time", "inputContentDMC:time", "inputContentEMC:time", "inputContentPHO:time" }; + std::string mTrendedClassNames[5] = { "classContentMinBias1:time", "classContentMinBias2:time", "classContentDMC:time", "classContentEMC:time", "classContentPHO:time" }; + std::string mTrendedInputRatioNames[4] = { "inputContentMinBias2/inputs", "inputContentDMC/inputs", "inputContentEMC/inputs", "inputContentPHO/inputs" }; + std::string mTrendedClassRatioNames[4] = { "classContentMinBias2/classes", "classContentDMC/classes", "classContentEMC/classes", "classContentPHO/classes" }; + std::string mClassNamesDefault[5] = { "CMTVX-B-NOPF", "CMVBA-B-NOPF", "CTVXDMC-B-NOPF-EMC", "CTVXEMC-B-NOPF-EMC", "CTVXPH0-B-NOPF-PHSCPV" }; // ctp classes to be trended by default, unless modified in config.json + std::string mClassNames[5] = { "", "", "", "", "" }; // the trended ctp classes will be filled in this array, either default or from config + std::string mInputNames[5] = { "", "", "", "", "" }; // the trended ctp inputs will be filled in this array, either default or from config + std::string mInputParameters[5] = { "minBias1Input", "minBias2Input", "minBisDMCInput", "minBiasEMCInput", "minBiasPHOInput" }; + std::string mClassParameters[5] = { "minBias1Class", "minBias2Class", "minBisDMCclass", "minBiasEMCclass", "minBiasPHOclass" }; + const int mNumberOfClasses = 5; // number of thrended ctp classes + const int mNumberOfInputs = 5; // number of thrended ctp inputs + int mClassIndex[5] = { 65, 65, 65, 65, 65 }; // indices of trended ctp classes, found in CTPconfig + int mInputIndex[5] = { 49, 49, 49, 49, 49 }; // indices of trended ctp inputs, found in CTPconfig + bool mCTPconfigFound = false; // bool telling whether the CTPconfing was already found + + std::map mPlots; + + std::unique_ptr mTrend; + std::unordered_map> mReductors; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_CTPTTRENDINGTASK_H diff --git a/Modules/CTP/include/CTP/CountersQcTask.h b/Modules/CTP/include/CTP/CountersQcTask.h new file mode 100644 index 0000000000..f8ee2306ed --- /dev/null +++ b/Modules/CTP/include/CTP/CountersQcTask.h @@ -0,0 +1,115 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CountersQcTask.h +/// \author Marek Bombara +/// + +#ifndef QC_MODULE_CTP_CTPCOUNTERSQCTASK_H +#define QC_MODULE_CTP_CTPCOUNTERSQCTASK_H + +#include "QualityControl/TaskInterface.h" +#include "DataFormatsCTP/Configuration.h" +#include "TH1.h" + +class TH1F; +class TH1D; +class TCanvas; + +using namespace o2::quality_control::core; +// +// Fake class to compile. If the task CTPCounterTask will beused it will be fixed or the whole task shall be removed. +// +class CTPRunManager +{ +}; +namespace o2::quality_control_modules::ctp +{ + +struct runCTP2QC { + int mRunNumber; + std::vector mRunClasses; + int mPositionInCounters; +}; + +class CTPCountersTask final : public TaskInterface +{ + public: + /// \brief Constructor + CTPCountersTask() = default; + /// Destructor + ~CTPCountersTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + // setters + void SetIsFirstCycle(bool isFirstCycle = true) { mIsFirstCycle = isFirstCycle; } + void SetFirstTimeStamp(double firstTimeStamp = 0) { mFirstTimeStamp = firstTimeStamp; } + void SetPreviousTimeStamp(double previousTimeStamp = 0) { mPreviousTimeStamp = previousTimeStamp; } + void SetRateHisto(TH1D* h, double ofs) + { + h->GetXaxis()->SetTimeDisplay(1); + h->GetXaxis()->SetTimeOffset(ofs); + h->GetXaxis()->SetTimeFormat("%H:%M"); + h->GetXaxis()->SetNdivisions(808); + }; + + // getters + bool IsFirstCycle() { return mIsFirstCycle; } + double GetFirstTimeStamp() { return mFirstTimeStamp; } + double GetPreviousTimeStamp() { return mPreviousTimeStamp; } + + private: + bool mIsFirstCycle = true; + double mFirstTimeStamp = 0; + double mPreviousTimeStamp = 0; + std::vector mTime; + std::vector mPreviousTrgInput; + std::vector mPreviousTrgClass; + std::vector mPreviousRunNumbers; + runCTP2QC mNewRun = { 0 }; + std::vector mTimes[48]; + std::vector mClassTimes[64]; + std::vector mInputRates[48]; + std::vector mClassRates[64]; + TH1D* mInputCountsHist = nullptr; + TH1D* mDummyCountsHist = nullptr; + TH1D* mInputRateHist = nullptr; + TH1D* mClassCountsHist = nullptr; + TCanvas* mTCanvasInputs = nullptr; + std::array mHistInputRate = { nullptr }; + TCanvas* mTCanvasClasses = nullptr; + std::array mHistClassRate = { nullptr }; + TCanvas* mTCanvasTotalCountsClasses = nullptr; + std::array mHistClassTotalCounts = { nullptr }; + std::array mTCanvasClassRates = { nullptr }; +}; + +class CTPQcRunManager : public CTPRunManager +{ + public: + /// \brief Constructor + CTPQcRunManager() = default; + /// Destructor + ~CTPQcRunManager(){}; +}; + +} // namespace o2::quality_control_modules::ctp + +#endif // QC_MODULE_CTP_CTPCOUNTERSQCTASK_H diff --git a/Modules/CTP/include/CTP/LinkDef.h b/Modules/CTP/include/CTP/LinkDef.h new file mode 100644 index 0000000000..1279c5438d --- /dev/null +++ b/Modules/CTP/include/CTP/LinkDef.h @@ -0,0 +1,14 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::ctp::CTPRawDataReaderTask + ; + +#pragma link C++ class o2::quality_control_modules::ctp::CTPCountersTask + ; +#pragma link C++ class o2::quality_control_modules::ctp::RawDataReaderCheck + ; +#pragma link C++ class o2::quality_control_modules::ctp::TH1ctpReductor + ; +#pragma link C++ class o2::quality_control::postprocessing::CTPTrendingTask + ; +#pragma link C++ class o2::quality_control::postprocessing::TrendingConfigCTP + ; + +#endif diff --git a/Modules/CTP/include/CTP/RawDataQcTask.h b/Modules/CTP/include/CTP/RawDataQcTask.h new file mode 100644 index 0000000000..a5f7e3bec2 --- /dev/null +++ b/Modules/CTP/include/CTP/RawDataQcTask.h @@ -0,0 +1,93 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawDataQcTask.h +/// \author Marek Bombara +/// \author Lucia Anna Tarasovicova +/// \author Jan Musinsky +/// \date 2026-02-17 +/// + +#ifndef QC_MODULE_CTP_CTPRAWDATAQCTASK_H +#define QC_MODULE_CTP_CTPRAWDATAQCTASK_H + +#include "QualityControl/TaskInterface.h" +#include "CTPReconstruction/RawDataDecoder.h" +#include "Common/TH1Ratio.h" +#include + +class TH1D; + +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::common; +namespace o2::quality_control_modules::ctp +{ + +/// \brief Task for reading the CTP inputs and CTP classes +class CTPRawDataReaderTask final : public TaskInterface +{ + public: + /// \brief Constructor + CTPRawDataReaderTask() = default; + /// Destructor + ~CTPRawDataReaderTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + void splitSortInputs(); + void readLHCFillingScheme(); + + private: + o2::ctp::RawDataDecoder mDecoder; // ctp raw data decoder + std::unique_ptr mHistoInputs = nullptr; // histogram with ctp inputs + std::unique_ptr mHistoClasses = nullptr; // histogram with ctp classes + std::unique_ptr mHistoInputRatios = nullptr; // histogram with ctp input ratios to MB + std::unique_ptr mHistoClassRatios = nullptr; // histogram with ctp class ratios to MB + std::unique_ptr mHistoBCMinBias1 = nullptr; // histogram of BC positions to check LHC filling scheme + std::unique_ptr mHistoBCMinBias2 = nullptr; // histogram of BC positions to check LHC filling scheme + std::unique_ptr mHistoDecodeError = nullptr; // histogram of erros from decoder + static constexpr int mUsedInputsMax = 18; + std::array mHisInputs = {}; ///< Array of input histograms, all BCs + std::array mHisInputsYesLHC = {}; ///< Array of input histograms, LHC BCs + std::array mHisInputsNotLHC = {}; ///< Array of input histograms, not LHC BCs + std::array shiftBC = {}; ///< Array of shifts for the BCs for each input + int mRunNumber; + int indexMB1 = -1; + int indexMB2 = -1; + int mShiftInput1 = 0; + int mShiftInput2 = 0; + static const int ninps = o2::ctp::CTP_NINPUTS + 1; + static const int nclasses = o2::ctp::CTP_NCLASSES + 1; + double mScaleInput1 = 1; + double mScaleInput2 = 1; + long int mTimestamp; + std::string classNames[nclasses]; + int mIndexMBclass = -1; // index for the MB ctp class, which is used as scaling for the ratios + bool mConsistCheck = 0; + bool mReadCTPconfigInMonitorData = 0; + const o2::ctp::CTPConfiguration* mCTPconfig = nullptr; + std::string mMBclassName; + std::array mClassErrorsA; + bool mPerformConsistencyCheck = false; + std::bitset mLHCBCs; /// LHC filling scheme + bool lhcDataFileFound = true; +}; + +} // namespace o2::quality_control_modules::ctp + +#endif // QC_MODULE_CTP_CTPRAWDATAQCTASK_H diff --git a/Modules/CTP/include/CTP/RawDataReaderCheck.h b/Modules/CTP/include/CTP/RawDataReaderCheck.h new file mode 100644 index 0000000000..bcbf37f985 --- /dev/null +++ b/Modules/CTP/include/CTP/RawDataReaderCheck.h @@ -0,0 +1,83 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawDataReaderCheck.h +/// \author Lucia Anna Tarasovicova +/// + +#ifndef QC_MODULE_CTP_CTPRAWDATAREADERCHECK_H +#define QC_MODULE_CTP_CTPRAWDATAREADERCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "CommonConstants/LHCConstants.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include +#include +class TH1D; + +namespace o2::quality_control_modules::ctp +{ + +/// \brief This class is checking the expected BC filling scheme +/// \brief This class is checking the relative change of ctp input and ctp class rates and ratios to MB +/// \author Lucia Anna Tarasovicova +class RawDataReaderCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + RawDataReaderCheck() = default; + /// Destructor + ~RawDataReaderCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void startOfActivity(const Activity& activity) override; + const double_t nofOrbitsPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + const double_t TimeTF = nofOrbitsPerTF * o2::constants::lhc::LHCOrbitMUS / 1e6; // in seconds + + private: + int getRunNumberFromMO(std::shared_ptr mo); + int checkChange(TH1D* mHist, TH1D* mHistPrev); + int checkChangeOfRatio(TH1D* mHist, TH1D* mHistPrev, TH1D* mHistAbs); + float setTextPosition(float iPos, std::shared_ptr msg, TH1D* h); + Quality setQualityResult(std::vector& vBad, std::vector& vMedium); + void clearIndexVectors(); + long int mTimestamp; + float mThreshold = -1; // threshold for BCs + float mThresholdRateBad = -1; // threshold for the relative change in ctp input and class rates + float mThresholdRateMedium = -1; // threshold for the relative change in ctp input and class rates + float mThresholdRateRatioBad = -1; // threshold for the relative change in ctp input and class ratios + float mThresholdRateRatioMedium = -1; // threshold for the relative change in ctp input and class ratios + float mNSigBC = -1; // n sigma for BC threshold + bool mFlagRatio = false; // flag that a ratio plot is checked + bool mFlagInput = false; // flag that an input plot is checked + TH1D* mHistInputPrevious = nullptr; // histogram storing ctp input rates from previous cycle + TH1D* mHistClassesPrevious = nullptr; // histogram storing ctp class rates from previous cycle + TH1D* mHistInputRatioPrevious = nullptr; // histogram storing ctp input ratios to MB from previous cycle + TH1D* mHistClassRatioPrevious = nullptr; + TH1D* mHistAbsolute = nullptr; // histogram storing ctp class ratios to MB from previous cycle + std::vector mVecGoodBC; // vector of good BC positions + std::vector mVecMediumBC; // vector of medium BC positions, we expect a BC at this position, but inputs are below mThreshold + std::vector mVecBadBC; // vector of bad BC positions, we don't expect a BC at this position, but inputs are abow mThreshold + std::vector mVecIndexBad; // vector of ctp input and class indices, which had a big relative change + std::vector mVecIndexMedium; // vector of ctp input and class indices, which had a relative change + std::bitset mLHCBCs; // LHC filling scheme + bool lhcDataFileFound = true; + + ClassDefOverride(RawDataReaderCheck, 10); +}; + +} // namespace o2::quality_control_modules::ctp + +#endif // QC_MODULE_CTP_CTPRAWDATAREADERCHECK_H diff --git a/Modules/CTP/include/CTP/TH1ctpReductor.h b/Modules/CTP/include/CTP/TH1ctpReductor.h new file mode 100644 index 0000000000..7409ef06f3 --- /dev/null +++ b/Modules/CTP/include/CTP/TH1ctpReductor.h @@ -0,0 +1,135 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1ctpReductor.h +/// \author Lucia Anna Tarasovicova +/// +#ifndef QUALITYCONTROL_TH1CTPREDUCTOR_H +#define QUALITYCONTROL_TH1CTPREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::ctp +{ + +/// \brief A Reductor which obtains the characteristics of TH1 including the bin contents. +/// Based on the example by Piotr Jan Konopka +/// It produces a branch in the format: "mean/D:stddev:entries:inputs[%i]:classes[%i]" +class TH1ctpReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + TH1ctpReductor() = default; + ~TH1ctpReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + void SetClassIndexes(int inMB1, int inMB2, int inDMC, int inEMC, int inPHO) + { + mMinBias1ClassIndex = inMB1; + mMinBias2ClassIndex = inMB2; + mPH0ClassIndex = inPHO; + mDMCClassIndex = inDMC; + mEMCClassIndex = inEMC; + } + void SetInputIndexes(int inMB1, int inMB2, int inDMC, int inEMC, int inPHO) + { + mMinBias1InputIndex = inMB1; + mMinBias2InputIndex = inMB2; + mPH0ClassIndex = inPHO; + mDMCInputIndex = inDMC; + mEMCInputIndex = inEMC; + } + void SetMinBias1ClassIndex(int in) + { + mMinBias1ClassIndex = in; + } + void SetMinBias2ClassIndex(int in) + { + mMinBias2ClassIndex = in; + } + void SetDMCClassIndex(int in) + { + mDMCClassIndex = in; + } + void SetPHOClassIndex(int in) + { + mPH0ClassIndex = in; + } + void SetEMCClassIndex(int in) + { + mEMCClassIndex = in; + } + void SetMinBias1InputIndex(int in) + { + mMinBias1InputIndex = in; + } + void SetMinBias2InputIndex(int in) + { + mMinBias2InputIndex = in; + } + void SetDMCInputIndex(int in) + { + mDMCInputIndex = in; + } + void SetPHOInputIndex(int in) + { + mPHOInputIndex = in; + } + void SetEMCInputIndex(int in) + { + mEMCInputIndex = in; + } + + int GetMinBias1ClassIndex() + { + return mMinBias1ClassIndex; + } + int GetMinBias1InputIndex() + { + return mMinBias1InputIndex; + } + + private: + static constexpr int nInputs = 48; + int mMinBias1ClassIndex = 65; + int mMinBias2ClassIndex = 65; + int mDMCClassIndex = 65; + int mEMCClassIndex = 65; + int mPH0ClassIndex = 65; + int mMinBias1InputIndex = 49; + int mMinBias2InputIndex = 49; + int mDMCInputIndex = 49; + int mEMCInputIndex = 49; + int mPHOInputIndex = 49; + + struct { + Double_t mean; + Double_t stddev; + Double_t entries; + Double_t classContentMinBias1; + Double_t classContentMinBias2; + Double_t classContentDMC; + Double_t classContentEMC; + Double_t classContentPHO; + Double_t inputContentMinBias1; + Double_t inputContentMinBias2; + Double_t inputContentDMC; + Double_t inputContentEMC; + Double_t inputContentPHO; + } mStats; +}; + +} // namespace o2::quality_control_modules::ctp + +#endif // QUALITYCONTROL_TH1CTPREDUCTOR_H diff --git a/Modules/CTP/include/CTP/TrendingConfigCTP.h b/Modules/CTP/include/CTP/TrendingConfigCTP.h new file mode 100644 index 0000000000..0cb8c1e894 --- /dev/null +++ b/Modules/CTP/include/CTP/TrendingConfigCTP.h @@ -0,0 +1,59 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingConfigCTP.h +/// \author Lucia Tarasovicova on the model from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKCONFIGCTP_H +#define QUALITYCONTROL_TRENDINGTASKCONFIGCTP_H + +#include "QualityControl/PostProcessingConfig.h" +#include +#include + +namespace o2::quality_control::postprocessing +{ + +// todo pretty print +/// \brief TrendingTask configuration structure +struct TrendingConfigCTP : PostProcessingConfig { + TrendingConfigCTP() = default; + TrendingConfigCTP(std::string name, const boost::property_tree::ptree& config); + ~TrendingConfigCTP() = default; + + struct Plot { + std::string name; + std::string title; + std::string varexp; + std::string selection; + std::string option; + std::string graphErrors; + std::string graphAxisLabel; + std::string graphYRange; + }; + + struct DataSource { + std::string type; + std::string path; + std::string name; + std::string reductorName; + std::string moduleName; + }; + + std::vector plots; + std::vector dataSources; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRENDINGTASKCONFIG_H diff --git a/Modules/CTP/src/CTPTrendingTask.cxx b/Modules/CTP/src/CTPTrendingTask.cxx new file mode 100644 index 0000000000..8f625e985c --- /dev/null +++ b/Modules/CTP/src/CTPTrendingTask.cxx @@ -0,0 +1,300 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CTPTrendingTask.cxx +/// \author Lucia Anna Tarasovicova +/// + +#include "CTP/CTPTrendingTask.h" +#include "CTP/TH1ctpReductor.h" + +#include +#include +#include +#include +#include +#include +// #include "DataFormatsCTP/RunManager.h" +#include "Common/Utils.h" +#include + +using namespace o2::quality_control_modules::common; + +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::repository; +using namespace o2::ctp; + +void CTPTrendingTask::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingConfigCTP(getID(), config); +} + +/// this function initialize all necessary parameters +void CTPTrendingTask::initCTP(Trigger& t) +{ + std::string run = std::to_string(t.activity.mId); + std::string CCDBHost; + try { + CCDBHost = mCustomParameters.at("ccdbName", "default"); + } catch (const std::exception& e) { + CCDBHost = "https://alice-ccdb.cern.ch"; + } + + /// the reading of the ccdb from trending was already discussed and is related with the fact that CTPconfing may not be ready at the QC starts + // o2::ctp::CTPRunManager::setCCDBHost(CCDBHost); + bool ok; + // o2::ctp::CTPConfiguration CTPconfig = o2::ctp::CTPRunManager::getConfigFromCCDB(t.timestamp, run, ok); + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); + mgr.setURL(CCDBHost); + std::map metadata; // can be empty + metadata["runNumber"] = run; + mgr.setFatalWhenNull(false); + auto ctpconfigdb = mgr.getSpecific(o2::ctp::CCDBPathCTPConfig, t.timestamp, metadata); + if (ctpconfigdb == nullptr) { + LOG(info) << "CTP config not in database, timestamp:" << t.timestamp; + ok = 0; + } else { + // ctpconfigdb->printStream(std::cout); + LOG(info) << "CTP config found. Run:" << run; + ok = 1; + } + if (!ok) { + ILOG(Warning, Support) << "CTP Config not found for run:" << run << " timesamp " << t.timestamp << ENDM; + return; + } else { + mCTPconfigFound = true; + } + + for (int i = 0; i < 5; i++) { + mClassNames[i] = getFromExtendedConfig(t.activity, mCustomParameters, mClassParameters[i], mClassNamesDefault[i]); + if (mClassNames[i] == "") { + mClassNames[i] = mClassNamesDefault[i]; + } + mInputNames[i] = getFromExtendedConfig(t.activity, mCustomParameters, mInputParameters[i], mInputNamesDefault[i]); + if (mInputNames[i] == "") { + mInputNames[i] = mInputNamesDefault[i]; + } + } + + // get the indices of the classes we want to trend + // std::vector ctpcls = CTPconfig.getCTPClasses(); + // std::vector clslist = CTPconfig.getTriggerClassList(); + std::vector ctpcls = ctpconfigdb->getCTPClasses(); + std::vector clslist = ctpconfigdb->getTriggerClassList(); + for (size_t i = 0; i < clslist.size(); i++) { + for (size_t j = 0; j < mNumberOfClasses; j++) { + if (ctpcls[i].name.find(mClassNames[j]) != std::string::npos) { + mClassIndex[j] = ctpcls[i].descriptorIndex + 1; + break; + } + } + } + + for (int i = 0; i < 5; i++) { + mInputIndex[i] = o2::ctp::CTPInputsConfiguration::getInputIndexFromName(mInputNames[i]); + } + + // Preparing data structure of TTree + for (const auto& source : mConfig.dataSources) { + mReductors.emplace(source.name, root_class_factory::create(source.moduleName, source.reductorName)); + } + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("time", &mTime); + for (const auto& [sourceName, reductor] : mReductors) { + reductor->SetClassIndexes(mClassIndex[0], mClassIndex[1], mClassIndex[2], mClassIndex[3], mClassIndex[4]); + reductor->SetInputIndexes(mInputIndex[0], mInputIndex[1], mInputIndex[2], mInputIndex[3], mInputIndex[4]); + mTrend->Branch(sourceName.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + } + + getObjectsManager()->startPublishing(mTrend.get()); +} +void CTPTrendingTask::initialize(Trigger t, framework::ServiceRegistryRef services) +{ + initCTP(t); +} + +void CTPTrendingTask::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + if (!mCTPconfigFound) { + initCTP(t); + } + if (!mCTPconfigFound) { + return; + } + trendValues(t, qcdb); + generatePlots(); +} + +void CTPTrendingTask::finalize(Trigger t, framework::ServiceRegistryRef) +{ + generatePlots(); +} + +void CTPTrendingTask::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + mTime = activity_helpers::isLegacyValidity(t.activity.mValidity) + ? t.timestamp / 1000 + : t.activity.mValidity.getMax() / 1000; // ROOT expects seconds since epoch. + mMetaData.runNumber = t.activity.mId; + + bool inputMissing = false; + + for (auto& dataSource : mConfig.dataSources) { + auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name, t.timestamp, t.activity); + if (!mo) { + ILOG(Info, Support) << "no MO object" << ENDM; + continue; + } + TObject* obj = mo ? mo->getObject() : nullptr; + if (!obj) { + ILOG(Info, Support) << "inputs not found" << ENDM; + inputMissing = true; + return; + } + mReductors[dataSource.name]->update(obj); + } + + if (!inputMissing) { + mTrend->Fill(); + } +} + +void CTPTrendingTask::generatePlots() +{ + if (mTrend == nullptr) { + ILOG(Info, Support) << "The trend object is not there, won't generate any plots." << ENDM; + return; + } + + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, won't generate any plots." << ENDM; + return; + } + + int indexInput = -1; + int indexClass = -1; + int indexInputRatio = -1; + int indexClassRatio = -1; + for (const auto& plot : mConfig.plots) { + + // Before we generate any new plots, we have to delete existing under the same names. + // It seems that ROOT cannot handle an existence of two canvases with a common name in the same process. + if (mPlots.count(plot.name)) { + delete mPlots[plot.name]; + mPlots[plot.name] = nullptr; + } + + indexInput = -1; + indexClass = -1; + indexInputRatio = -1; + indexClassRatio = -1; + for (int i = 0; i < 5; i++) { + if (plot.varexp.find(mTrendedInputNames[i]) != std::string::npos) { + if (mInputIndex[i] == 255) { + ILOG(Info, Support) << "Input " << mInputNames[i] << " is not trended." << ENDM; + indexInput = -10; + } else { + indexInput = i; + } + } + if (plot.varexp.find(mTrendedClassNames[i]) != std::string::npos) { + if (mClassIndex[i] == 65) { + ILOG(Info, Support) << "Class " << mClassNames[i] << " is not trended." << ENDM; + indexClass = -10; + } else { + indexClass = i; + } + } + if (i < 4) { + if (plot.varexp.find(mTrendedInputRatioNames[i]) != std::string::npos) { + if (mInputIndex[i + 1] == 255 || mInputIndex[0] == 255) { + ILOG(Info, Support) << "Input ratio " << mInputNames[i + 1] << " / " << mInputNames[0] << " is not trended." << ENDM; + indexInputRatio = -10; + } else { + indexInputRatio = i; + } + } + if (plot.varexp.find(mTrendedClassRatioNames[i]) != std::string::npos) { + if (mClassIndex[i + 1] == 65) { + ILOG(Info, Support) << "Class ratio " << mClassNames[i + 1] << " / " << mClassNames[0] << " is not trended." << ENDM; + indexClassRatio = -10; + } else { + indexClassRatio = i; + } + } + } + } + + if (indexInput == -10 || indexClass == -10) + continue; + if (indexClassRatio == -10 || indexInputRatio == -10) + continue; + if (indexInput == -1 && indexClass == -1 && indexInputRatio == -1 && indexClassRatio == -1) { + ILOG(Warning, Support) << "Wrongly defined variable: " << plot.varexp << ENDM; + continue; + } + + auto* c = new TCanvas(); + mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), plot.option.c_str()); + + c->SetName(plot.name.c_str()); + + if (auto histo = dynamic_cast(c->GetPrimitive("htemp"))) { + if (indexInput > -1) { + histo->SetTitle(mInputNames[indexInput].c_str()); + } + if (indexClass > -1) { + histo->SetTitle(mClassNames[indexClass].c_str()); + } + if (indexInputRatio > -1) { + histo->SetTitle(Form("%s/%s", mInputNames[indexInputRatio + 1].c_str(), mInputNames[0].c_str())); + } + if (indexClassRatio > -1) { + histo->SetTitle(Form("%s/%s", mClassNames[indexClassRatio + 1].c_str(), mClassNames[0].c_str())); + } + + if (indexInput > -1 || indexClass > -1) { + histo->GetYaxis()->SetTitle("rate [kHz]"); + } + if (indexInputRatio > -1 || indexClassRatio > -1) { + histo->GetYaxis()->SetTitle("rate ratio"); + } + c->Update(); + + if (plot.varexp.find(":time") != std::string::npos) { + histo->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + histo->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + histo->GetXaxis()->SetTimeOffset(0.0); + histo->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } else if (plot.varexp.find(":meta.runNumber") != std::string::npos) { + histo->GetXaxis()->SetNoExponent(true); + } + histo->BufferEmpty(); + + } else { + ILOG(Error, Devel) << "Could not get the htemp histogram of the plot '" << plot.name << "'." << ENDM; + } + + mPlots[plot.name] = c; + getObjectsManager()->startPublishing(c, PublicationPolicy::Once); + } +} diff --git a/Modules/CTP/src/CountersQcTask.cxx b/Modules/CTP/src/CountersQcTask.cxx new file mode 100644 index 0000000000..4e3942146f --- /dev/null +++ b/Modules/CTP/src/CountersQcTask.cxx @@ -0,0 +1,559 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CountersQcTask.cxx +/// \author Marek Bombara +/// + +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" + +#include "CTP/CountersQcTask.h" +#include "DetectorsRaw/RDHUtils.h" +#include "Headers/RAWDataHeader.h" +#include "DPLUtils/DPLRawParser.h" +#include "DataFormatsCTP/Digits.h" +#include "DataFormatsCTP/Configuration.h" +#include +#include +#include +#include "CommonUtils/StringUtils.h" + +namespace o2::quality_control_modules::ctp +{ + +CTPCountersTask::~CTPCountersTask() +{ + delete mDummyCountsHist; + delete mInputCountsHist; +} + +void CTPCountersTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize CountersQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + mInputCountsHist = new TH1D("TriggerInputCounts", "Total Trigger Input Counts", 48, 0, 48); + // gPad->SetLogy(mInputCountsHist->GetEntries()>0); + getObjectsManager()->startPublishing(mInputCountsHist); + + { + mTCanvasInputs = new TCanvas("inputsRates", "inputsRates", 2000, 2500); + mTCanvasInputs->Clear(); + mTCanvasInputs->Divide(6, 8); + + for (size_t i = 0; i < 48; i++) { + auto name = std::string("Rate_of_inp") + std::to_string(i); + mHistInputRate[i] = new TH1D(name.c_str(), name.c_str(), 1, 0, 1); + mHistInputRate[i]->GetXaxis()->SetTitle("Time"); + mHistInputRate[i]->GetYaxis()->SetTitle("Rate[Hz]"); + mTCanvasInputs->cd(i + 1); + mHistInputRate[i]->Draw(); + mHistInputRate[i]->SetBit(TObject::kCanDelete); + } + getObjectsManager()->startPublishing(mTCanvasInputs); + } + + { + mTCanvasClasses = new TCanvas("classesRates", "classesRates", 2500, 2500); + mTCanvasClasses->Clear(); + mTCanvasClasses->Divide(8, 8); + + for (size_t i = 0; i < 64; i++) { + auto name = std::string("Rate_of_class") + std::to_string(i); + mHistClassRate[i] = new TH1D(name.c_str(), name.c_str(), 1, 0, 1); + mHistClassRate[i]->GetXaxis()->SetTitle("Time"); + mHistClassRate[i]->GetYaxis()->SetTitle("Rate[Hz]"); + mTCanvasClasses->cd(i + 1); + mHistClassRate[i]->Draw(); + mHistClassRate[i]->SetBit(TObject::kCanDelete); + } + getObjectsManager()->startPublishing(mTCanvasClasses); + } + + { + for (int j = 0; j < 16; j++) { + // auto name = std::string("Class rates in Run ") + std::to_string(mActiveRun[j]); + auto name = std::string("Class_rates_in_Run_position_in_payload:") + std::to_string(j); + mTCanvasClassRates[j] = new TCanvas(name.c_str(), name.c_str(), 2500, 2500); + mTCanvasClassRates[j]->Clear(); + /*mTCanvasClassRates[j]->Divide(mXPad[j], mXPad[j]); + + for (size_t i = 0; i < mNumberOfClasses[j]; i++) { + int k = mActiveClassInRun[i]; + auto name = std::string("Rate_of_class") + std::to_string(k); + //mHistClassRate[k] = new TH1D(name.c_str(), name.c_str(), 1, 0, 1); + //mHistClassRate[k]->GetXaxis()->SetTitle("Time"); + //mHistClassRate[k]->GetYaxis()->SetTitle("Rate[Hz]"); + mTCanvasClassRates[j]->cd(i + 1); + mHistClassRate[k]->Draw(); + mHistClassRate[k]->SetBit(TObject::kCanDelete); + }*/ + getObjectsManager()->startPublishing(mTCanvasClassRates[j]); + } + } + + { + mTCanvasTotalCountsClasses = new TCanvas("TotalCountsClasses", "Total Counts Classes", 2000, 500); + mTCanvasTotalCountsClasses->Clear(); + mTCanvasTotalCountsClasses->Divide(3, 2); + + for (size_t i = 0; i < 6; i++) { + auto name = std::string("Rate_of_classnew") + std::to_string(i); + if (i == 0) { + name = std::string("Trigger Class LMb Total Time Integrated Counts"); + } + if (i == 1) { + name = std::string("Trigger Class L0b Total Time Integrated Counts"); + } + if (i == 2) { + name = std::string("Trigger Class L1b Total Time Integrated Counts"); + } + if (i == 3) { + name = std::string("Trigger Class LMa Total Time Integrated Counts"); + } + if (i == 4) { + name = std::string("Trigger Class L0a Total Time Integrated Counts"); + } + if (i == 5) { + name = std::string("Trigger Class L1a Total Time Integrated Counts"); + } + mHistClassTotalCounts[i] = new TH1D(name.c_str(), name.c_str(), 64, 0, 64); + mHistClassTotalCounts[i]->GetXaxis()->SetTitle("Class"); + mHistClassTotalCounts[i]->GetYaxis()->SetTitle("Total counts for run"); + mTCanvasTotalCountsClasses->cd(i + 1); + mHistClassTotalCounts[i]->Draw(); + mHistClassTotalCounts[i]->SetBit(TObject::kCanDelete); + } + getObjectsManager()->startPublishing(mTCanvasTotalCountsClasses); + } +} + +void CTPCountersTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "Start of all activitites " << ENDM; + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + mInputCountsHist->Reset(); + // mInputRateHist->Reset(); + // mClassCountsHist->Reset(); + if (mTCanvasInputs) { + for (const auto member : mHistInputRate) { + if (member) { + member->Reset(); + } + } + } + if (mTCanvasClasses) { + for (const auto member : mHistClassRate) { + if (member) { + member->Reset(); + } + } + } + if (mTCanvasTotalCountsClasses) { + for (const auto member : mHistClassTotalCounts) { + if (member) { + member->Reset(); + } + } + } + /* + for (int j = 0; j < 16; j++) { + if (mTCanvasClassRates[j]) { + for (const auto member : mHistClassRate) { + if (member) { + member->Reset(); + } + } + } + } + */ +} + +void CTPCountersTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void CTPCountersTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + // dummy publishing + // mDummyCountsHist = new TH1D("DummyCounts", "Dummy counts", 1, 0, 1); + // getObjectsManager()->startPublishing(mDummyCountsHist); + // get the input + // std::cout << "before data reading "; + o2::framework::InputRecord& inputs = ctx.inputs(); + o2::framework::DataRef ref = inputs.get("readout"); + + const char* pp = ref.payload; + if (!pp) { + LOG(info) << "no payload pointer "; + } + // pyaload has 4 formats: + // ctpconfig with rcfg, sox with counters+rcfg, pcp with counters and eox with counters + std::string spp = std::string(pp); + // LOG(info) << "the payload message: " << spp; + // std::cout << "the cout payload message: " << spp; + std::vector counter; + // fill the tokens with counters or rcfg + std::vector tokens = o2::utils::Str::tokenize(spp, ' '); + + // ctpconfig - add classes to newly loaded run + if (tokens[0] == "ctpconfig") { + LOG(info) << "CTP run configuration:"; + // we have to get a rid of the first substring - ctpconfig to gain rcfg message + std::string subspp = "ctpconfig "; + std::string::size_type posInSpp = spp.find(subspp); + if (posInSpp != std::string::npos) + spp.erase(posInSpp, subspp.length()); + std::string ctpConf = spp; + // LOG(info) << "Rcfg message:"; + LOG(info) << ctpConf; + // get Trigger Class Mask for the run from the CTP configuration + o2::ctp::CTPConfiguration activeConf; + activeConf.loadConfigurationRun3(ctpConf); + activeConf.printStream(std::cout); + uint64_t runClassMask = activeConf.getTriggerClassMask(); + LOG(info) << "Class Mask Qc: " << runClassMask; + std::vector runClassList = activeConf.getTriggerClassList(); + std::cout << "size of runClassList: " << runClassList.size() << std::endl; + for (auto i : runClassList) { + std::cout << " print class list: " << i << " "; + } + uint32_t runNumber = activeConf.getRunNumber(); + // runCTPQC mNewRun; + mNewRun.mRunNumber = runNumber; + mNewRun.mRunClasses = runClassList; + } + + if (tokens[0] == "sox") { + for (int i = 2; i < 1040; i++) { + // check if the received data are numbers + std::string sCheck = tokens[i]; + bool notNumber = false; + for (int j = 0; j < sCheck.length(); j++) { + if (!isdigit(sCheck[j])) + notNumber = true; + } + if (notNumber) + continue; + double tempCounter = std::stod(tokens[i]); + counter.push_back(tempCounter); + } + for (int i = 0; i < 16; i++) { + int isNewRun = counter[i] - mPreviousRunNumbers[i]; + if (isNewRun != 0) { + std::cout << "we have a new run!" << std::endl; + mNewRun.mPositionInCounters = i; + // auto name = std::string("Class Rates For Run ") + std::to_string(mNewRun.mRunNumber); + // mTCanvasClassRates[i] = new TCanvas(name.c_str(), name.c_str(), 2500, 2500); + // mTCanvasClassRates[i]->Clear(); + // mTCanvasClassRates[i]->SetTitle(name.c_str()); + int numberOfClasses = mNewRun.mRunClasses.size(); + double numOfCl = numberOfClasses; + double xpad = std::ceil(sqrt(numOfCl)); + mTCanvasClassRates[i]->Divide(xpad, xpad); + + for (size_t j = 0; j < numberOfClasses; j++) { + int k = mNewRun.mRunClasses[j]; + auto name = std::string("Run ") + std::to_string(mNewRun.mRunNumber) + std::string(" Rate_of_class") + std::to_string(k); + mTCanvasClassRates[i]->cd(j + 1); + mHistClassRate[k]->SetTitle(name.c_str()); + mHistClassRate[k]->Draw(); + mHistClassRate[k]->SetBit(TObject::kCanDelete); + } + // std::cout << "before publishing" << std::endl; + // LOG(info) << "before publishing with LOG"; + // getObjectsManager()->startPublishing(mTCanvasClassRates[i]); + // std::cout << "after publishing" << std::endl; + } + mPreviousRunNumbers.clear(); + mPreviousRunNumbers.push_back(counter[i]); + // LOG(info) << "Recent Runs = " << runNumbers[i] << " "; + } + mPreviousRunNumbers.clear(); + for (int i = 0; i < 16; i++) { + mPreviousRunNumbers.push_back(counter[i]); + // LOG(info) << "Recent Runs = " << runNumbers[i] << " "; + } + } + + if (tokens[0] == "eox") { + for (int i = 0; i < 16; i++) { + for (int i = 2; i < 1040; i++) { + // check if the received data are numbers + std::string sCheck = tokens[i]; + bool notNumber = false; + for (int j = 0; j < sCheck.length(); j++) { + if (!isdigit(sCheck[j])) + notNumber = true; + } + if (notNumber) + continue; + double tempCounter = std::stod(tokens[i]); + counter.push_back(tempCounter); + } + int wasNewRun = counter[i] - mPreviousRunNumbers[i]; + if (wasNewRun != 0) { + // std::cout << "before stopping publishing" << std::endl; + // getObjectsManager()->stopPublishing(mTCanvasClassRates[i]); + // std::cout << "after stopping publishing and before deleting" << std::endl; + // delete mTCanvasClassRates[i]; + // std::cout << "after deleting" << std::endl; + } + } + } + + if (tokens[0] == "pcp") { + // for (int i = 2; i < tokens.size(); i++) { + for (int i = 2; i < 1040; i++) { + // check if the received data are numbers + std::string sCheck = tokens[i]; + bool notNumber = false; + for (int j = 0; j < sCheck.length(); j++) { + if (!isdigit(sCheck[j])) + notNumber = true; + } + if (notNumber) + continue; + double tempCounter = std::stod(tokens[i]); + counter.push_back(tempCounter); + } + + LOG(info) << "The topic is = " << tokens[0]; + + double timeStamp = std::stod(tokens[1]); + // LOG(info) << "Time stamp = " << timeStamp; + for (int i = 0; i < counter.size(); i++) { + // std::cout << "i = " << i << " " << counter.at(i) << std::endl; + } + int RunNumber = counter[0]; + std::vector runNumbers; + mPreviousRunNumbers.clear(); + for (int i = 0; i < 16; i++) { + runNumbers.push_back(counter[i]); + mPreviousRunNumbers.push_back(counter[i]); + // LOG(info) << "Recent Runs = " << runNumbers[i] << " "; + } + + // 48 trigger inputs + std::vector trgInput; + // 64 trigger classes after L1 + std::vector trgClass; + std::vector trgClassMb; + std::vector trgClassMa; + std::vector trgClass0b; + std::vector trgClass0a; + std::vector trgClass1b; + std::vector trgClass1a; + for (int i = 0; i < counter.size(); i++) { + if ((i >= 599) && (i < 647)) + trgInput.push_back(counter[i]); + if ((i >= 967) && (i < 1031)) + trgClass.push_back(counter[i]); + + if ((i >= 647) && (i < 711)) + trgClassMb.push_back(counter[i]); + if ((i >= 711) && (i < 775)) + trgClassMa.push_back(counter[i]); + if ((i >= 775) && (i < 839)) + trgClass0b.push_back(counter[i]); + if ((i >= 839) && (i < 903)) + trgClass0a.push_back(counter[i]); + if ((i >= 903) && (i < 967)) + trgClass1b.push_back(counter[i]); + if ((i >= 967) && (i < 1031)) + trgClass1a.push_back(counter[i]); + // std::cout << counter.at(i) << std::endl; + } + // filling input histograms + for (int i = 0; i < trgInput.size(); i++) { + double trgInputDouble = trgInput[i]; + mInputCountsHist->SetBinContent(i, trgInputDouble); + // std::cout << trgInput[i] << " "; + } + for (int i = 0; i < trgClassMb.size(); i++) { + double trgClassDouble = trgClassMb[i]; + mHistClassTotalCounts[0]->SetBinContent(i, trgClassDouble); + } + for (int i = 0; i < trgClassMa.size(); i++) { + double trgClassDouble = trgClassMa[i]; + mHistClassTotalCounts[3]->SetBinContent(i, trgClassDouble); + } + for (int i = 0; i < trgClass0b.size(); i++) { + double trgClassDouble = trgClass0b[i]; + mHistClassTotalCounts[1]->SetBinContent(i, trgClassDouble); + } + for (int i = 0; i < trgClass0a.size(); i++) { + double trgClassDouble = trgClass0a[i]; + mHistClassTotalCounts[4]->SetBinContent(i, trgClassDouble); + } + for (int i = 0; i < trgClass1b.size(); i++) { + double trgClassDouble = trgClass1b[i]; + mHistClassTotalCounts[2]->SetBinContent(i, trgClassDouble); + } + for (int i = 0; i < trgClass1a.size(); i++) { + double trgClassDouble = trgClass1a[i]; + mHistClassTotalCounts[5]->SetBinContent(i, trgClassDouble); + } + + double recentInput = 0.; + double previousInput = 0.; + double countDiff = 0.; + double recentInputs[48] = { 0. }; + double previousInputs[48] = { 0. }; + double countInputDiffs[48] = { 0. }; + double recentClasses[64] = { 0. }; + double previousClasses[64] = { 0. }; + double countClassDiffs[64] = { 0. }; + // int previousRunNumbers[16] = {0}; + + bool firstCycle = IsFirstCycle(); + if (firstCycle) { + SetIsFirstCycle(false); + SetFirstTimeStamp(timeStamp); + SetPreviousTimeStamp(timeStamp); + // SetPreviousInput(trgInput[1]); + for (int i = 0; i < 48; i++) + mPreviousTrgInput.push_back(trgInput[i]); + // mTime.push_back(0.); + mTime.push_back(timeStamp); + // mInputRate.push_back(0.); + for (int i = 0; i < 48; i++) + mTimes[i].push_back(0.); + for (int i = 0; i < 48; i++) + mInputRates[i].push_back(0.); + // class part + // SetPreviousClass(trgClass[1]); + for (int i = 0; i < 64; i++) + mPreviousTrgClass.push_back(trgClass[i]); + for (int i = 0; i < 64; i++) + mClassRates[i].push_back(0.); + for (int i = 0; i < 16; i++) + mPreviousRunNumbers.push_back(runNumbers[i]); + } else { + + for (int i = 0; i < 48; i++) { + recentInputs[i] = trgInput[i]; + previousInputs[i] = mPreviousTrgInput[i]; + countInputDiffs[i] = recentInputs[i] - previousInputs[i]; // should not be negative - integration values + mInputRates[i].push_back(countInputDiffs[i]); + } + + for (int i = 0; i < 64; i++) { + recentClasses[i] = trgClass[i]; + previousClasses[i] = mPreviousTrgClass[i]; + countClassDiffs[i] = recentClasses[i] - previousClasses[i]; // should not be negative - integration values + mClassRates[i].push_back(countClassDiffs[i]); + } + + if (recentInputs[0] > previousInputs[0]) { + mTime.push_back(timeStamp); + } + } + SetIsFirstCycle(false); + SetPreviousTimeStamp(timeStamp); + mPreviousTrgInput.clear(); + for (int i = 0; i < 48; i++) { + mPreviousTrgInput.push_back(trgInput[i]); + // std::cout << "i = " << i << " trgInput = " << trgInput[i] << " prevrate = " << mPreviousTrgInput[i] << std::endl; + } + mPreviousTrgClass.clear(); + for (int i = 0; i < 64; i++) { + mPreviousTrgClass.push_back(trgClass[i]); + // std::cout << "i = " << i << " trgInput = " << trgInput[i] << " prevrate = " << mPreviousTrgInput[i] << std::endl; + } + mPreviousRunNumbers.clear(); + for (int i = 0; i < 16; i++) { + mPreviousRunNumbers.push_back(runNumbers[i]); + // std::cout << "i = " << i << " trgInput = " << trgInput[i] << " prevrate = " << mPreviousTrgInput[i] << std::endl; + } + if (!firstCycle) { + // time in seconds + int nBinsTime = mTime.size(); + double xMinTime = mTime[0]; + double xMaxTime = mTime[nBinsTime - 1]; + // std::cout << "nb = " << nBinsTime-1 << " xmin =" << xMinTime << " xmax =" << xMaxTime << std::endl; + for (int i = 0; i < 48; i++) { + mHistInputRate[i]->SetBins(nBinsTime - 1, 0, xMaxTime - xMinTime); + for (int j = 1; j < nBinsTime; j++) { + double tempInpRate = mInputRates[i][j]; + // std::cout << "i = " << i << " j = " << j << " rate = " << tempInpRate << " nbins = " << nBinsTime << std::endl; + mHistInputRate[i]->SetBinContent(j, tempInpRate); + SetRateHisto(mHistInputRate[i], xMinTime); + } + } + // std::cout << "class rate histos.." << std::endl; + for (int i = 0; i < 64; i++) { + mHistClassRate[i]->SetBins(nBinsTime - 1, 0, xMaxTime - xMinTime); + for (int j = 1; j < nBinsTime; j++) { + double tempClassRate = mClassRates[i][j]; + // std::cout << "i = " << i << " j = " << j << " rate = " << tempClassRate << std::endl; + mHistClassRate[i]->SetBinContent(j, tempClassRate); + SetRateHisto(mHistClassRate[i], xMinTime); + } + } + } + } +} + +void CTPCountersTask::endOfCycle() +{ + std::cout << "End of Cycle" << std::endl; + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void CTPCountersTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void CTPCountersTask::reset() +{ + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mInputCountsHist->Reset(); + // mDummyCountsHist->Reset(); + // mClassCountsHist->Reset(); + if (mTCanvasInputs) { + for (const auto member : mHistInputRate) { + if (member) { + member->Reset(); + } + } + } + if (mTCanvasClasses) { + for (const auto member : mHistClassRate) { + if (member) { + member->Reset(); + } + } + } + if (mTCanvasTotalCountsClasses) { + for (const auto member : mHistClassTotalCounts) { + if (member) { + member->Reset(); + } + } + } +} + +/*void SetRateHisto(TH1D* h, double timeOffset) +{ + h->GetXaxis()->SetTimeOffset(timeOffset); + h->GetXaxis()->SetTimeDisplay(1); + h->GetXaxis()->SetTimeFormat("%H:%M"); + h->GetXaxis()->SetNdivisions(808); +} +*/ +} // namespace o2::quality_control_modules::ctp diff --git a/Modules/CTP/src/RawDataQcTask.cxx b/Modules/CTP/src/RawDataQcTask.cxx new file mode 100644 index 0000000000..8c582ad6cd --- /dev/null +++ b/Modules/CTP/src/RawDataQcTask.cxx @@ -0,0 +1,505 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawDataQcTask.cxx +/// \author Marek Bombara +/// \author Lucia Anna Tarasovicova +/// \author Jan Musinsky +/// \date 2026-02-17 +/// + +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "CTP/RawDataQcTask.h" +#include +#include +#include "DetectorsRaw/RDHUtils.h" +#include "Headers/RAWDataHeader.h" +#include "DataFormatsCTP/Digits.h" +#include "DataFormatsCTP/Configuration.h" +// #include "DataFormatsCTP/RunManager.h" +#include +#include "Framework/TimingInfo.h" +#include "Common/Utils.h" +#include + +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::ctp +{ + +CTPRawDataReaderTask::~CTPRawDataReaderTask() +{ + for (auto& h : mHisInputs) { + delete h; + } + for (auto& h : mHisInputsNotLHC) { // must be before mHisInputsYesLHC + delete h; + } + for (auto& h : mHisInputsYesLHC) { + delete h; + } +} + +void CTPRawDataReaderTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize CTPRawDataReaderTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + int norbits = o2::constants::lhc::LHCMaxBunches; + mHistoInputs = std::make_unique("inputs", "Input Rates; Input ; Rate [kHz]", ninps, 0, ninps, true); + mHistoClasses = std::make_unique("classes", "Class Rates; Class; Rate [kHz]", nclasses, 0, nclasses, true); + mHistoInputs->SetStats(0); + mHistoClasses->SetStats(0); + mHistoBCMinBias1 = std::make_unique("bcMinBias1", "BC position MB1", norbits, 0, norbits); + mHistoBCMinBias2 = std::make_unique("bcMinBias2", "BC position MB2", norbits, 0, norbits); + mHistoInputRatios = std::make_unique("inputRatio", "Input Ratio to MTVX; Input; Ratio;", ninps, 0, ninps, true); + mHistoClassRatios = std::make_unique("classRatio", "Class Ratio to MB; Class; Ratio", nclasses, 0, nclasses, true); + getObjectsManager()->startPublishing(mHistoInputs.get()); + getObjectsManager()->startPublishing(mHistoClasses.get()); + getObjectsManager()->startPublishing(mHistoClassRatios.get()); + getObjectsManager()->startPublishing(mHistoInputRatios.get()); + getObjectsManager()->startPublishing(mHistoBCMinBias1.get()); + getObjectsManager()->startPublishing(mHistoBCMinBias2.get()); + std::string sTmp1, sTmp2; + for (std::size_t i = 0; i < mHisInputs.size(); i++) { + sTmp1 = std::format("input{:02}", i); + sTmp2 = std::format("input[{:02}] {}", i, o2::ctp::CTPInputsConfiguration::getInputNameFromIndex(i + 1)); + // getInputNameFromIndex in range [1-48] + mHisInputs[i] = new TH1D(sTmp1.c_str(), sTmp2.c_str(), norbits, 0, norbits); + + sTmp1 = std::format("input{:02}_yesLHC", i); + mHisInputsYesLHC[i] = new TH1D(sTmp1.c_str(), sTmp2.c_str(), norbits, 0, norbits); + mHisInputsYesLHC[i]->SetLineColor(kGreen + 2); + mHisInputsYesLHC[i]->SetFillColor(kGreen + 2); + + sTmp1 = std::format("input{:02}_notLHC", i); + mHisInputsNotLHC[i] = new TH1D(sTmp1.c_str(), sTmp2.c_str(), norbits, 0, norbits); + mHisInputsNotLHC[i]->SetLineColor(kRed + 1); + mHisInputsNotLHC[i]->SetFillColor(kRed + 1); + + getObjectsManager()->startPublishing(mHisInputs[i]); + getObjectsManager()->startPublishing(mHisInputsYesLHC[i]); + // getObjectsManager()->startPublishing(mHisInputsNotLHC[i]); + } + + mDecoder.setDoLumi(1); + mDecoder.setDoDigits(1); + for (size_t i = 0; i < nclasses; i++) { + classNames[i] = ""; + } + mHistoClassRatios.get()->GetXaxis()->CenterLabels(true); + mHistoClasses.get()->GetXaxis()->CenterLabels(true); +} + +void CTPRawDataReaderTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + mHistoInputs->Reset(); + mHistoClasses->Reset(); + mHistoClassRatios->Reset(); + mHistoInputRatios->Reset(); + mHistoBCMinBias1->Reset(); + mHistoBCMinBias2->Reset(); + for (auto& h : mHisInputs) { + h->Reset(); + } + for (auto& h : mHisInputsYesLHC) { + h->Reset("ICES"); + } + for (auto& h : mHisInputsNotLHC) { + h->Reset(); + } + + mRunNumber = activity.mId; + mTimestamp = activity.mValidity.getMin(); + readLHCFillingScheme(); // call after mTimestamp is set + + std::string readCTPConfig = getFromExtendedConfig(activity, mCustomParameters, "readCTPconfigInMonitorData", "false"); + if (readCTPConfig == "true") { + mReadCTPconfigInMonitorData = true; + } + + mMBclassName = getFromExtendedConfig(activity, mCustomParameters, "MBclassName", "CMTVX-B-NOPF"); + + std::string run = std::to_string(mRunNumber); + std::string ccdbName = mCustomParameters["ccdbName"]; + if (ccdbName.empty()) { + ccdbName = "https://alice-ccdb.cern.ch"; + } + /// the ccdb reading to be futher discussed + // o2::ctp::CTPRunManager::setCCDBHost(ccdbName); + if (!mReadCTPconfigInMonitorData) { + bool ok; + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); + mgr.setURL(ccdbName); + std::map metadata; // can be empty + metadata["runNumber"] = run; + auto ctpconfigdb = mgr.getSpecific(o2::ctp::CCDBPathCTPConfig, mTimestamp, metadata); + if (ctpconfigdb == nullptr) { + LOG(info) << "CTP config not in database, timestamp:" << mTimestamp; + ok = 0; + } else { + LOG(info) << "CTP config found. Run:" << run; + ok = 1; + } + if (ok) { + // get the index of the MB reference class + ILOG(Info, Support) << "CTP config found, run:" << run << ENDM; + std::vector ctpcls = ctpconfigdb->getCTPClasses(); + for (size_t i = 0; i < ctpcls.size(); i++) { + classNames[i] = ctpcls[i].name.c_str(); + if (ctpcls[i].name.find(mMBclassName) != std::string::npos) { + mIndexMBclass = ctpcls[i].getIndex() + 1; + break; + } + } + mDecoder.setCTPConfig(*ctpconfigdb); + } else { + ILOG(Warning, Support) << "CTP config not found, run:" << run << ENDM; + } + if (mIndexMBclass == -1) { + mMBclassName = "CMBV0 (default)"; + mIndexMBclass = 1; + } + mHistoClassRatios->SetTitle(Form("Class Ratio to %s", mMBclassName.c_str())); + } + + std::string nameInput1 = getFromExtendedConfig(activity, mCustomParameters, "MB1inputName", "MTVX"); + std::string nameInput2 = getFromExtendedConfig(activity, mCustomParameters, "MB2inputName", "MT0A"); + + auto input1Tokens = o2::utils::Str::tokenize(nameInput1, ':', false, true); + if (input1Tokens.size() > 0) { + nameInput1 = input1Tokens[0]; + } + if (input1Tokens.size() > 1) { + mScaleInput1 = std::stod(input1Tokens[1]); + } + if (input1Tokens.size() > 2) { + mShiftInput1 = std::stod(input1Tokens[2]); + } + + auto input2Tokens = o2::utils::Str::tokenize(nameInput2, ':', false, true); + if (input2Tokens.size() > 0) { + nameInput2 = input2Tokens[0]; + } + if (input2Tokens.size() > 1) { + mScaleInput2 = std::stod(input2Tokens[1]); + } + if (input2Tokens.size() > 2) { + mShiftInput2 = std::stod(input2Tokens[2]); + } + + indexMB1 = o2::ctp::CTPInputsConfiguration::getInputIndexFromName(nameInput1); + indexMB2 = o2::ctp::CTPInputsConfiguration::getInputIndexFromName(nameInput2); + if (indexMB1 == -1) { + indexMB1 = 3; // 3 is the MTVX index + nameInput1 = "MTVX (default)"; + } + if (indexMB2 == -1) { + indexMB2 = 5; // 5 is the MTCE index + nameInput2 = "MTCE (default)"; + } + for (int i = 0; i < nclasses; i++) { + if (classNames[i] == "") { + mHistoClasses.get()->GetXaxis()->SetBinLabel(i + 1, Form("%i", i + 1)); + mHistoClassRatios.get()->GetXaxis()->SetBinLabel(i + 1, Form("%i", i + 1)); + } else { + mHistoClasses.get()->GetXaxis()->SetBinLabel(i + 1, Form("%s", classNames[i].c_str())); + mHistoClassRatios.get()->GetXaxis()->SetBinLabel(i + 1, Form("%s", classNames[i].c_str())); + } + } + mHistoClasses.get()->GetXaxis()->SetLabelSize(0.025); + mHistoClasses.get()->GetXaxis()->LabelsOption("v"); + mHistoClassRatios.get()->GetXaxis()->SetLabelSize(0.025); + mHistoClassRatios.get()->GetXaxis()->LabelsOption("v"); + + TString title1 = Form("BC position %s", nameInput1.c_str()); + TString titley1 = Form("Rate"); + TString titleX1 = Form("BC"); + if (mScaleInput1 > 1) { + title1 += Form(", scaled with 1/%g", mScaleInput1); + titley1 += Form(" #times 1/%g", mScaleInput1); + } + if (mShiftInput1 > 0) { + title1 += Form(", shifted by - %d", mShiftInput1); + titleX1 += Form(" - %d", mShiftInput1); + } + mHistoBCMinBias1->SetTitle(Form("%s; %s; %s", title1.Data(), titleX1.Data(), titley1.Data())); + + TString title2 = Form("BC position %s", nameInput2.c_str()); + TString titley2 = Form("Rate"); + TString titleX2 = Form("BC"); + if (mScaleInput2 > 1) { + title2 += Form(", scaled with 1/%g", mScaleInput2); + titley2 += Form(" #times 1/%g", mScaleInput2); + } + if (mShiftInput2 > 0) { + title2 += Form(", shifted by - %d", mShiftInput2); + titleX2 += Form(" - %d", mShiftInput2); + } + mHistoBCMinBias2->SetTitle(Form("%s; %s; %s", title2.Data(), titleX2.Data(), titley2.Data())); + mHistoInputRatios->SetTitle(Form("Input Ratio to %s", nameInput1.c_str())); + + std::string performConsistencyCheck = getFromExtendedConfig(activity, mCustomParameters, "consistencyCheck", "true"); + if (performConsistencyCheck == "true") { + mDecoder.setCheckConsistency(1); + mDecoder.setDecodeInps(1); + mPerformConsistencyCheck = true; + for (std::size_t i = 0; i < shiftBC.size(); i++) { + shiftBC[i] = 0; // no shift + } + } else { + mDecoder.setCheckConsistency(0); + for (std::size_t i = 0; i < shiftBC.size(); i++) { + if (i <= 10) { + shiftBC[i] = 0; // [00-10] without shift + } + if (i >= 11 && i <= 23) { + shiftBC[i] = 15; // [11-23] shift by 15 BCs + } else if (i >= 24 && i <= 47) { + shiftBC[i] = 296; // [24-47] shift by 296 BCs + } + } + } + + if (mPerformConsistencyCheck) { + mHistoDecodeError = std::make_unique("decodeError", "Errors from decoder", nclasses, 0, nclasses); + getObjectsManager()->startPublishing(mHistoDecodeError.get()); + } +} + +void CTPRawDataReaderTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void CTPRawDataReaderTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + static constexpr double sOrbitLengthInMS = o2::constants::lhc::LHCOrbitMUS / 1000; + auto nOrbitsPerTF = 32.; + // get the input + std::vector filter{ o2::framework::InputSpec{ "filter", o2::framework::ConcreteDataTypeMatcher{ "DS", "CTPRAWDATA" }, o2::framework::Lifetime::Timeframe } }; + std::vector lumiPointsHBF1; + std::vector outputDigits; + + if (mReadCTPconfigInMonitorData) { + if (mCTPconfig == nullptr) { + mCTPconfig = ctx.inputs().get("ctp-config").get(); + // mCTPconfig = ctpConfigPtr.get(); + if (mCTPconfig != nullptr) { + ILOG(Info, Support) << "CTP config found" << ENDM; + std::vector ctpcls = mCTPconfig->getCTPClasses(); + for (size_t i = 0; i < ctpcls.size(); i++) { + classNames[i] = ctpcls[i].name.c_str(); + if (ctpcls[i].name.find(mMBclassName) != std::string::npos) { + mIndexMBclass = ctpcls[i].getIndex() + 1; + break; + } + } + mDecoder.setCTPConfig(*mCTPconfig); + } + } + for (int i = 0; i < nclasses; i++) { + if (classNames[i] == "") { + mHistoClasses.get()->GetXaxis()->SetBinLabel(i + 1, Form("%i", i + 1)); + mHistoClassRatios.get()->GetXaxis()->SetBinLabel(i + 1, Form("%i", i + 1)); + } else { + mHistoClasses.get()->GetXaxis()->SetBinLabel(i + 1, Form("%s", classNames[i].c_str())); + mHistoClassRatios.get()->GetXaxis()->SetBinLabel(i + 1, Form("%s", classNames[i].c_str())); + } + } + + if (mIndexMBclass == -1) { + mMBclassName = "CMBV0 (default)"; + mIndexMBclass = 1; + } + mHistoClassRatios->SetTitle(Form("Class Ratio to %s", mMBclassName.c_str())); + } + + o2::framework::InputRecord& inputs = ctx.inputs(); + int ret = mDecoder.decodeRaw(inputs, filter, outputDigits, lumiPointsHBF1); + mClassErrorsA = mDecoder.getClassErrorsA(); + if (mPerformConsistencyCheck) { + for (size_t i = 0; i < o2::ctp::CTP_NCLASSES; i++) { + if (mClassErrorsA[i] > 0) { + mHistoDecodeError->Fill(i, mClassErrorsA[i]); + } + } + } + + // reading the ctp inputs and ctp classes + for (auto const digit : outputDigits) { + uint16_t bcid = digit.intRecord.bc; + if (digit.CTPInputMask.count()) { + for (int i = 0; i < mUsedInputsMax; i++) { + if (digit.CTPInputMask[i]) { + mHistoInputs->getNum()->Fill(i); + mHistoInputRatios->getNum()->Fill(i); + if (i == indexMB1 - 1) { + int bc = bcid - mShiftInput1 >= 0 ? bcid - mShiftInput1 : bcid - mShiftInput1 + 3564; + mHistoBCMinBias1->Fill(bc, 1. / mScaleInput1); + mHistoInputRatios->getDen()->Fill(0., 1); + } + if (i == indexMB2 - 1) { + int bc = bcid - mShiftInput2 >= 0 ? bcid - mShiftInput2 : bcid - mShiftInput2 + 3564; + mHistoBCMinBias2->Fill(bc, 1. / mScaleInput2); + } + // int bc = (bcid - shiftBC[i]) >= 0 ? bcid - shiftBC[i] : bcid - shiftBC[i] + o2::constants::lhc::LHCMaxBunches; + int bc = bcid - shiftBC[i]; + mHisInputs[i]->Fill(bc); + } + } + } + if (digit.CTPClassMask.count()) { + for (int i = 0; i < o2::ctp::CTP_NCLASSES; i++) { + if (digit.CTPClassMask[i]) { + mHistoClasses->getNum()->Fill(i); + mHistoClassRatios->getNum()->Fill(i); + if (i == mIndexMBclass - 1) { + mHistoClassRatios->getDen()->Fill(0., 1); + } + } + } + } + } + mHistoInputs->getNum()->Fill(o2::ctp::CTP_NINPUTS); + mHistoClasses->getNum()->Fill(o2::ctp::CTP_NCLASSES); + + // store total duration (in milliseconds) in denominator + mHistoInputs->getDen()->Fill((double)0, sOrbitLengthInMS * nOrbitsPerTF); + mHistoClasses->getDen()->Fill((double)0, sOrbitLengthInMS * nOrbitsPerTF); +} + +void CTPRawDataReaderTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + mHistoInputs->update(); + mHistoInputRatios->update(); + mHistoClasses->update(); + mHistoClassRatios->update(); + splitSortInputs(); +} + +void CTPRawDataReaderTask::splitSortInputs() +{ + struct BC { // BC (Bunch Crossing) + int id; // id [0-3563] + double entries; // number of entries for id + // TH1::GetBinContent(i) return always double over TH1::RetrieveBinContent(i) implemented in TH1* + }; + + std::vector BCs; // ?! std::array BCs = {} ?! + for (std::size_t ih = 0; ih < mHisInputs.size(); ih++) { + if (mHisInputs[ih]->GetEntries() == 0) { + continue; + } + BCs.clear(); + for (int ib = 1; ib <= mHisInputs[ih]->GetNbinsX(); ib++) { // skip underflow bin + BCs.emplace_back(ib - 1, mHisInputs[ih]->GetBinContent(ib)); + } + + std::sort(BCs.begin(), BCs.end(), + [](const BC& a, const BC& b) { return a.entries > b.entries; }); // sort by BC.entries + // std::cout << "size: " << BCs.size() << std::endl; + // for (const BC& bc : BCs) { + // std::cout << "BC.id: " << std::setw(4) << bc.id << ", BC.entries: " << bc.entries << std::endl; + // } + + mHisInputsYesLHC[ih]->Reset("ICES"); // don't delete mHisInputsNotLHC[ih] object + mHisInputsNotLHC[ih]->Reset(); + for (std::size_t ibc = 0; ibc < BCs.size(); ibc++) { // skip underflow bin (in loop) + if (mLHCBCs.test(BCs[ibc].id)) { + mHisInputsYesLHC[ih]->SetBinContent(ibc + 1, BCs[ibc].entries); + } else { + mHisInputsNotLHC[ih]->SetBinContent(ibc + 1, BCs[ibc].entries); + } + } + + TLine* line = nullptr; + if (!mHisInputsYesLHC[ih]->FindObject(mHisInputsNotLHC[ih])) { // only once + // temporary hack: hisNotLHC->Draw("same") + mHisInputsYesLHC[ih]->GetListOfFunctions()->Add(mHisInputsNotLHC[ih], "same"); + // temporary hack: line->Draw("same") + line = new TLine(mLHCBCs.count(), 0, mLHCBCs.count(), mHisInputsYesLHC[ih]->GetMaximum() * 1.05); + line->SetLineStyle(kDotted); + line->SetLineColor(mHisInputsYesLHC[ih]->GetLineColor()); + mHisInputsYesLHC[ih]->GetListOfFunctions()->Add(line, "same"); + } else { // always set Y2 line maximum (is different for each cycle) + line = dynamic_cast(mHisInputsYesLHC[ih]->FindObject("TLine")); + if (line) { + line->SetY2(mHisInputsYesLHC[ih]->GetMaximum() * 1.05); + } + } + } +} + +void CTPRawDataReaderTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void CTPRawDataReaderTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mHistoInputs->Reset(); + mHistoClasses->Reset(); + mHistoInputRatios->Reset(); + mHistoClassRatios->Reset(); + mHistoBCMinBias1->Reset(); + mHistoBCMinBias2->Reset(); + for (auto& h : mHisInputs) { + h->Reset(); + } + for (auto& h : mHisInputsYesLHC) { + h->Reset("ICES"); + } + for (auto& h : mHisInputsNotLHC) { + h->Reset(); + } + if (mPerformConsistencyCheck) + mHistoDecodeError->Reset(); +} + +void CTPRawDataReaderTask::readLHCFillingScheme() +{ + // mTimestamp = activity.mValidity.getMin(); // set in startOfActivity() + // + // manually added timestamps corresponding to known fills and runs (for testing) + // mTimestamp = 1716040930304 + 1; // fill: 9644, run: 551731 + // mTimestamp = 1754317528872 + 1; // fill: 10911, run: 565118 + // mTimestamp = 1760845636156 + 1; // fill: 11203, run: 567147 + + std::map metadata; // can be empty + auto lhcifdata = UserCodeInterface::retrieveConditionAny("GLO/Config/GRPLHCIF", metadata, mTimestamp); + if (lhcifdata == nullptr) { + ILOG(Info, Support) << "LHC data not found for (task) timestamp:" << mTimestamp << ENDM; + lhcDataFileFound = false; + return; + } else { + ILOG(Info, Support) << "LHC data found for (task) timestamp:" << mTimestamp << ENDM; + lhcDataFileFound = true; + // lhcifdata->print(); + } + auto bfilling = lhcifdata->getBunchFilling(); + std::vector bcs = bfilling.getFilledBCs(); + mLHCBCs.reset(); + for (auto const& bc : bcs) { + mLHCBCs.set(bc, 1); + } + // ?! test on mLHCBCs.size() == or <= o2::constants::lhc::LHCMaxBunches ?! +} + +} // namespace o2::quality_control_modules::ctp diff --git a/Modules/CTP/src/RawDataReaderCheck.cxx b/Modules/CTP/src/RawDataReaderCheck.cxx new file mode 100644 index 0000000000..ea3353dbe4 --- /dev/null +++ b/Modules/CTP/src/RawDataReaderCheck.cxx @@ -0,0 +1,436 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawDataReaderCheck.cxx +/// \author Lucia Anna Tarasovicova +/// + +#include "CTP/RawDataReaderCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// #include "DataFormatsCTP/RunManager.h" +#include +// ROOT +#include +#include +#include +#include +#include +#include +#include "Common/Utils.h" +#include + +using namespace std; +using namespace o2::quality_control; +using namespace o2::ctp; + +namespace o2::quality_control_modules::ctp +{ + +void RawDataReaderCheck::configure() +{ + // reading the parameters from the config.json + // if not available, setting a default value + // the threshold values are nSigma + mThresholdRateBad = common::getFromConfig(mCustomParameters, "thresholdRateBad", 3.); + if (mThresholdRateBad > 4 || mThresholdRateBad < 0) { + mThresholdRateBad = 3; + } + + mThresholdRateMedium = common::getFromConfig(mCustomParameters, "thresholdRateMedium", 2.); + if (mThresholdRateMedium > 4 || mThresholdRateMedium < 0) { + mThresholdRateMedium = 2; + } + + mThresholdRateRatioBad = common::getFromConfig(mCustomParameters, "thresholdRateRatioBad", 3.); + if (mThresholdRateRatioBad > 4 || mThresholdRateRatioBad < 0) { + mThresholdRateRatioBad = 3; + } + + mThresholdRateRatioMedium = common::getFromConfig(mCustomParameters, "thresholdRateRatioMedium", 2.); + if (mThresholdRateRatioMedium > 4 || mThresholdRateRatioMedium < 0) { + mThresholdRateRatioMedium = 2; + } + + mNSigBC = common::getFromConfig(mCustomParameters, "nSigmaBC", 2.); + if (mNSigBC < 0) { + mNSigBC = 2; + } +} + +Quality RawDataReaderCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + clearIndexVectors(); + mFlagRatio = false; + mFlagInput = false; + + for (auto& [moName, mo] : *moMap) { + if (moName.find("Ratio") != std::string::npos) { + mFlagRatio = true; + } + if (moName.find("input") != std::string::npos) { + mFlagInput = true; + } + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + ILOG(Info, Support) << "histogram is not found for check:" << moName << ENDM; + continue; + } + if (mo->getName() == "bcMinBias1" || mo->getName() == "bcMinBias2") { + if (mLHCBCs.count() == 0) { + continue; + } + float average = h->Integral() / mLHCBCs.count(); + mThreshold = average - mNSigBC * sqrt(average); + if (mThreshold < std::sqrt(average)) { + mThreshold = average / 2; + } + for (int i = 0; i < o2::constants::lhc::LHCMaxBunches; i++) { + if (mLHCBCs[i] && h->GetBinContent(i + 1) <= mThreshold) { + mVecMediumBC.push_back(i); // medium BC occures when BC is expected on this possition but there is less inputs than threshold + } else if (!mLHCBCs[i] && h->GetBinContent(i + 1) > mThreshold) { + mVecBadBC.push_back(i); // bad BC occures when BC is not expected on this possition but there is more inputs than threshold + } else if (mLHCBCs[i] && h->GetBinContent(i + 1) > mThreshold) { + mVecGoodBC.push_back(i); + } + } + result.set(setQualityResult(mVecBadBC, mVecMediumBC)); + } else if (mo->getName() == "inputs") { + if (mHistInputPrevious) { + checkChange(h, mHistInputPrevious); + delete mHistInputPrevious; + result.set(setQualityResult(mVecIndexBad, mVecIndexMedium)); + } + mHistAbsolute = (TH1D*)h->Clone(); + mHistInputPrevious = (TH1D*)h->Clone(); + } else if (mo->getName() == "inputRatio") { + if (mHistInputRatioPrevious) { + checkChangeOfRatio(h, mHistInputRatioPrevious, mHistAbsolute); + delete mHistInputRatioPrevious; + result.set(setQualityResult(mVecIndexBad, mVecIndexMedium)); + } + mHistInputRatioPrevious = (TH1D*)h->Clone(); + } else if (mo->getName() == "classes") { + if (mHistClassesPrevious) { + checkChange(h, mHistClassesPrevious); + delete mHistClassesPrevious; + result.set(setQualityResult(mVecIndexBad, mVecIndexMedium)); + } + mHistAbsolute = (TH1D*)h->Clone(); + mHistClassesPrevious = (TH1D*)h->Clone(); + } else if (mo->getName() == "classRatio") { + if (mHistClassRatioPrevious) { + checkChangeOfRatio(h, mHistClassRatioPrevious, mHistAbsolute); + delete mHistClassRatioPrevious; + result.set(setQualityResult(mVecIndexBad, mVecIndexMedium)); + } + mHistClassRatioPrevious = (TH1D*)h->Clone(); + } else if (mo->getName() == "decodeError") { + if (h->GetEntries() > 0) { + result.set(Quality::Bad); + } else { + result.set(Quality::Good); + } + } else { + ILOG(Info, Support) << "Unknown histo:" << moName << ENDM; + } + } + + return result; +} + +int RawDataReaderCheck::checkChange(TH1D* mHist, TH1D* mHistPrev) +{ + /// this function check how much the rates differ from previous cycle + float thrBad = mThresholdRateBad; + float thrMedium = mThresholdRateMedium; + for (size_t i = 1; i < mHist->GetXaxis()->GetNbins() + 1; i++) { // Check how many inputs/classes changed more than a threshold value + double val = mHist->GetBinContent(i); + double valPrev = mHistPrev->GetBinContent(i); + double relDiff = (valPrev != 0 || val != 0) ? (val - valPrev) / TMath::Sqrt(val) : 0; + if (TMath::Abs(relDiff) > thrBad) { + mVecIndexBad.push_back(i - 1); + } else if (TMath::Abs(relDiff) > thrMedium) { + mVecIndexMedium.push_back(i - 1); + } + } + return 0; +} +int RawDataReaderCheck::checkChangeOfRatio(TH1D* mHist, TH1D* mHistPrev, TH1D* mHistAbs) +{ + /// this function check how much the rates differ from previous cycle + float thrBad = mThresholdRateRatioBad; + float thrMedium = mThresholdRateRatioMedium; + int binMB; + for (int i = 1; i < mHist->GetXaxis()->GetNbins() + 1; i++) { + if (mHist->GetBinContent(i) == mHistPrev->GetBinContent(i) && mHist->GetBinContent(i) == 1) { + binMB = i; + break; + } + } + for (size_t i = 1; i < mHist->GetXaxis()->GetNbins() + 1; i++) { // Check how many inputs/classes changed more than a threshold value + double val = mHist->GetBinContent(i); + double valPrev = mHistPrev->GetBinContent(i); + double relDiff = (val != 0 || mHistAbs->GetBinContent(i) != 0) ? (val - valPrev) / (val * TMath::Sqrt(1 / mHistAbs->GetBinContent(binMB) + 1 / mHistAbs->GetBinContent(i))) : 0; + if (TMath::Abs(relDiff) > thrBad) { + mVecIndexBad.push_back(i - 1); + } else if (TMath::Abs(relDiff) > thrMedium) { + mVecIndexMedium.push_back(i - 1); + } + } + return 0; +} +float RawDataReaderCheck::setTextPosition(float iPos, std::shared_ptr msg, TH1D* h) +{ + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + float MessagePos = iPos - 0.04; + return MessagePos; +} + +void RawDataReaderCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + std::shared_ptr msg; + if (mo->getName() == "bcMinBias1" || mo->getName() == "bcMinBias2") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Info, Support) << "null pointer for hist:" << mo->getName() << ENDM; + return; + } + h->SetMarkerStyle(20); + h->SetMarkerSize(0.6); + if (checkResult != Quality::Null) { + msg = std::make_shared(0.2, 0.85, Form("Quality: %s", (checkResult.getName()).c_str())); + if (checkResult == Quality::Bad) { + msg->SetTextColor(kRed); + } else if (checkResult == Quality::Medium) { + msg->SetTextColor(kOrange); + } else if (checkResult == Quality::Good) { + msg->SetTextColor(kGreen + 1); + } + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + } + + if (checkResult == Quality::Null) { + if (lhcDataFileFound) + msg = std::make_shared(0.2, 0.8, Form("Check was not performed, LHC filling scheme empty")); + else + msg = std::make_shared(0.2, 0.8, Form("Check was not performed, LHC information not available")); + msg->SetTextColor(kBlack); + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + } else { + msg = std::make_shared(0.2, 0.8, Form("Number of good BC: %lu", mVecGoodBC.size())); + msg->SetTextColor(kBlack); + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + + float messagePos = 0.75; + float initialMessagePos = 0.75; + if (mVecMediumBC.size() > 0) { + msg = std::make_shared(0.2, initialMessagePos, Form("BC is expected on following possitions, but inputs are below threshold (%g):", round(mThreshold))); + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + for (size_t i = 0; i < mVecMediumBC.size(); i++) { + messagePos = initialMessagePos - (i + 1) * 0.02; + msg = std::make_shared(0.2, messagePos, Form("%d", mVecMediumBC[i])); + msg->SetTextSize(0.02); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + } + } + initialMessagePos = messagePos - 0.04; + if (mVecBadBC.size() > 0) { + msg = std::make_shared(0.2, initialMessagePos, Form("BC is not expected on following possitions, but inputs are above threshold (%g):", round(mThreshold))); + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + for (size_t i = 0; i < mVecBadBC.size(); i++) { + messagePos = initialMessagePos - (i + 1) * 0.02; + msg = std::make_shared(0.2, messagePos, Form("%d", mVecBadBC[i])); + msg->SetTextSize(0.02); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + } + } + } + h->SetStats(kFALSE); + h->GetYaxis()->SetRangeUser(0, h->GetMaximum() * 1.5); + } else if (mo->getName() == "decodeError") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Info, Support) << "null pointer for hist:" << mo->getName() << ENDM; + return; + } + if (checkResult != Quality::Null) { + msg = std::make_shared(0.2, 0.85, Form("Quality: %s", (checkResult.getName()).c_str())); + if (checkResult == Quality::Bad) { + msg->SetTextColor(kRed); + } else if (checkResult == Quality::Good) { + msg->SetTextColor(kGreen + 1); + } + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + } + if (checkResult == Quality::Bad) { + float initialMessagePos = 0.8; + string messages[9] = { "Failed to extract RDD", + "", + "Two CTP IRs with the same ts", + "Two digits with the same ts", + "Two CTP class masks with the same ts", + "Two digits (Class Mask) with the same ts", + "Trigger class without input", + "CTP class mask not compatible with input class mask", + "CTP class not found in the digit" }; + for (int i = 1; i < h->GetXaxis()->GetNbins() + 1; i++) { + if (h->GetBinContent(i) > 0) { + msg = std::make_shared(0.2, initialMessagePos, messages[i - 1].c_str()); + initialMessagePos = setTextPosition(initialMessagePos, msg, h); + } + } + } + } else { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Info, Support) << "null pointer for hist:" << mo->getName() << ENDM; + return; + } + h->SetStats(kFALSE); + msg = std::make_shared(0.45, 0.8, Form("Quality: %s", (checkResult.getName()).c_str())); + std::string groupName = "Input"; + std::string relativeness = "relative"; + if (!mFlagInput) { + groupName = "Class"; + } + if (!mFlagRatio) { + relativeness = "absolute"; + } + if (mFlagInput) { + for (size_t i = 0; i < h->GetXaxis()->GetNbins(); i++) { + std::string label = o2::ctp::CTPInputsConfiguration::getInputNameFromIndex(i + 1); + if (label == "none") { + h->GetXaxis()->SetBinLabel(i + 1, Form("%zu", i + 1)); + } else { + h->GetXaxis()->SetBinLabel(i + 1, label.c_str()); + } + } + h->GetXaxis()->SetLabelSize(0.045); + h->GetXaxis()->LabelsOption("v"); + h->GetXaxis()->CenterLabels(true); + } + if (checkResult == Quality::Bad) { + msg->SetTextColor(kRed); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + msg = std::make_shared(0.45, 0.70, Form("Number of %s with big %s rate change: %lu", groupName.c_str(), relativeness.c_str(), mVecIndexBad.size())); + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + for (size_t i = 0; i < mVecIndexBad.size(); i++) { + if (mFlagInput) { + msg = std::make_shared(0.45, 0.65 - i * 0.05, Form("Check %s %s", o2::ctp::CTPInputsConfiguration::getInputNameFromIndex(mVecIndexBad[i] + 1).c_str(), groupName.c_str())); + } else { + msg = std::make_shared(0.45, 0.65 - i * 0.05, Form("Check %s %s", groupName.c_str(), h->GetXaxis()->GetBinLabel(mVecIndexBad[i] + 1))); + } + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + } + if (mVecIndexMedium.size() > 0) { + msg = std::make_shared(0.45, 0.75, Form("Number of %s with medium %s rate change: %lu", groupName.c_str(), relativeness.c_str(), mVecIndexMedium.size())); + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + } + } else if (checkResult == Quality::Medium) { + msg->SetTextColor(kOrange); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + if (mVecIndexMedium.size() > 0) { + msg = std::make_shared(0.45, 0.75, Form("Number of %s with medium %s rate change: %lu", groupName.c_str(), relativeness.c_str(), mVecIndexMedium.size())); + } + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + for (size_t i = 0; i < mVecIndexMedium.size(); i++) { + msg = std::make_shared(0.45, 0.7 - i * 0.05, Form("%s with Index: %d", groupName.c_str(), mVecIndexMedium[i])); + msg->SetTextSize(0.03); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + } + } else if (checkResult == Quality::Good) { + msg->SetTextColor(kGreen + 1); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + } + h->SetMarkerStyle(20); + } +} + +Quality RawDataReaderCheck::setQualityResult(std::vector& vBad, std::vector& vMedium) +{ + // setting a quality for a given hisogram based on nuber of bad/medium indices + Quality quality = Quality::Null; + if (vBad.size() > 0) { + quality = Quality::Bad; + } else if (vMedium.size() > 0) { + quality = Quality::Medium; + } else { + quality = Quality::Good; + } + + return quality; +} + +void RawDataReaderCheck::clearIndexVectors() +{ + // clearing all vectors with saved indices + mVecBadBC.clear(); + mVecMediumBC.clear(); + mVecGoodBC.clear(); + mVecIndexBad.clear(); + mVecIndexMedium.clear(); +} + +void RawDataReaderCheck::startOfActivity(const core::Activity& activity) +{ + mTimestamp = activity.mValidity.getMin(); + + map metadata; // can be empty + auto lhcifdata = UserCodeInterface::retrieveConditionAny("GLO/Config/GRPLHCIF", metadata, mTimestamp); + if (lhcifdata == nullptr) { + ILOG(Info, Support) << "LHC data not found for timestamp:" << mTimestamp << ENDM; + lhcDataFileFound = false; + return; + } else { + ILOG(Info, Support) << "LHC data found for timestamp:" << mTimestamp << ENDM; + } + auto bfilling = lhcifdata->getBunchFilling(); + std::vector bcs = bfilling.getFilledBCs(); + mLHCBCs.reset(); + for (auto const& bc : bcs) { + mLHCBCs.set(bc, 1); + } +} + +} // namespace o2::quality_control_modules::ctp diff --git a/Modules/CTP/src/TH1ctpReductor.cxx b/Modules/CTP/src/TH1ctpReductor.cxx new file mode 100644 index 0000000000..5fbb1c976d --- /dev/null +++ b/Modules/CTP/src/TH1ctpReductor.cxx @@ -0,0 +1,55 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1ctpReductor.cxx +/// \author Lucia Anna Tarasovicova +/// + +#include +#include "CTP/TH1ctpReductor.h" + +namespace o2::quality_control_modules::ctp +{ + +void* TH1ctpReductor::getBranchAddress() +{ + return &mStats; +} + +const char* TH1ctpReductor::getBranchLeafList() +{ + return "mean/D:stddev:entries:classContentMinBias1:classContentMinBias2:classContentDMC:classContentEMC:classContentPHO:inputContentMinBias1:inputContentMinBias2:inputContentDMC:inputContentEMC:inputContentPHO"; +} + +void TH1ctpReductor::update(TObject* obj) +{ + // todo: use GetStats() instead? + auto histo = dynamic_cast(obj); + if (histo) { + mStats.entries = histo->GetEntries(); + mStats.stddev = histo->GetStdDev(); + mStats.mean = histo->GetMean(); + + mStats.classContentMinBias1 = histo->GetBinContent(mMinBias1ClassIndex); + mStats.classContentMinBias2 = histo->GetBinContent(mMinBias2ClassIndex); + mStats.classContentDMC = histo->GetBinContent(mDMCClassIndex); + mStats.classContentEMC = histo->GetBinContent(mEMCClassIndex); + mStats.classContentPHO = histo->GetBinContent(mPH0ClassIndex); + + mStats.inputContentMinBias1 = histo->GetBinContent(mMinBias1InputIndex); + mStats.inputContentMinBias2 = histo->GetBinContent(mMinBias2InputIndex); + mStats.inputContentDMC = histo->GetBinContent(mDMCInputIndex); + mStats.inputContentEMC = histo->GetBinContent(mEMCInputIndex); + mStats.inputContentPHO = histo->GetBinContent(mPHOInputIndex); + } +} +} // namespace o2::quality_control_modules::ctp diff --git a/Modules/CTP/src/TrendingConfigCTP.cxx b/Modules/CTP/src/TrendingConfigCTP.cxx new file mode 100644 index 0000000000..ff7b2f21a6 --- /dev/null +++ b/Modules/CTP/src/TrendingConfigCTP.cxx @@ -0,0 +1,63 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingConfigCTP.cxx +/// \author Lucia Tarasovicova on the model from Piotr Konopka +/// + +#include "CTP/TrendingConfigCTP.h" +#include + +using boost::property_tree::ptree; + +namespace o2::quality_control::postprocessing +{ + +TrendingConfigCTP::TrendingConfigCTP( + std::string id, const boost::property_tree::ptree& config) + : PostProcessingConfig(id, config) +{ + // producePlotsOnUpdate = config.get("qc.postprocessing." + id + ".producePlotsOnUpdate", true); + // resumeTrend = config.get("qc.postprocessing." + id + ".resumeTrend", false); + for (const auto& plotConfig : config.get_child("qc.postprocessing." + id + ".plots")) { + plots.push_back({ plotConfig.second.get("name"), + plotConfig.second.get("title", ""), + plotConfig.second.get("varexp"), + plotConfig.second.get("selection", ""), + plotConfig.second.get("option", ""), + plotConfig.second.get("graphErrors", ""), + plotConfig.second.get("graphAxisLabel", ""), + plotConfig.second.get("graphYRange", "") }); + } + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + id + ".dataSources")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + sourceName.second.data(), + dataSourceConfig.second.get("reductorName"), + dataSourceConfig.second.get("moduleName") }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name"), + dataSourceConfig.second.get("reductorName"), + dataSourceConfig.second.get("moduleName") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + id + ".dataSources'"); + } + } +} + +} // namespace o2::quality_control::postprocessing diff --git a/Modules/CTP/src/counters.json b/Modules/CTP/src/counters.json new file mode 100644 index 0000000000..3a71acd085 --- /dev/null +++ b/Modules/CTP/src/counters.json @@ -0,0 +1,57 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "MyCountersQcTask": { + "active": "true", + "className": "o2::quality_control_modules::ctp::CTPCountersTask", + "moduleName": "QcCTP", + "detectorName": "CTP", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "readout" + }, + "location": "remote", + "saveObjectsToFile" : "testCTP.root" + } + } + }, + "dataSamplingPolicies": [ + { + "id": "readout", + "active": "true", + "machines": [], + "query" : "readout:CTP/CTP_COUNTERS/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/CTP/src/ctpTrending.json b/Modules/CTP/src/ctpTrending.json new file mode 100644 index 0000000000..d2385ff0f1 --- /dev/null +++ b/Modules/CTP/src/ctpTrending.json @@ -0,0 +1,196 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "postprocessing": { + "periodSeconds": "60" + } + }, + "postprocessing": { + "TrendingCTP": { + "active": "true", + "className": "o2::quality_control::postprocessing::CTPTrendingTask", + "moduleName": "QualityControl", + "detectorName": "CTP", + "dataSources": [ + { + "type": "repository", + "path": "CTP/MO/RawData", + "names": ["inputs","classes"], + "reductorName": "o2::quality_control_modules::ctp::TH1ctpReductor", + "moduleName": "QcCommon" + } + ], + "extendedTaskParameters": { + "default": { + "default": { + "minBias1Class": "CMTVX-B-NOPF", + "minBias2Class": "CMVBA-B-NOPF", + "minBisDMCclass": "CTVXDMC-B-NOPF-EMC", + "minBiasEMCclass": "CTVXEMC-B-NOPF-EMC", + "minBiasPHOclass": "CTVXPH0-B-NOPF-PHSCPV", + "minBias1Input": "TVX", + "minBias2Input": "VBA", + "minBisDMCInput": "DMC", + "minBiasEMCInput": "EMC", + "minBiasPHOInput": "PH0", + "ccdbName": "https://alice-ccdb.cern.ch" + } + } + }, + "plots": [ + { + "name": "MinBias1Input_rate", + "title": "MinBias1Input_rate", + "varexp": "inputs.inputContentMinBias1:time", + "selection": "", + "option": "*L" + }, + { + "name": "MinBias2Input_rate", + "title": "MinBias2Input_rate", + "varexp": "inputs.inputContentMinBias2:time", + "selection": "", + "option": "*L" + }, + { + "name": "DMCInput_rate", + "title": "DMCInput_rate", + "varexp": "inputs.inputContentDMC:time", + "selection": "", + "option": "*L" + }, + { + "name": "EMCInput_rate", + "title": "EMCInput_rate", + "varexp": "inputs.inputContentEMC:time", + "selection": "", + "option": "*L" + }, + { + "name": "PHOInput_rate", + "title": "PHOInput_rate", + "varexp": "inputs.inputContentPHO:time", + "selection": "", + "option": "*L" + }, + { + "name": "Class_MinBias1_rate", + "title": "Class_MinBias1_rate", + "varexp": "classes.classContentMinBias1:time", + "selection": "", + "option": "*L" + }, + { + "name": "Class_MinBias2_rate", + "title": "Class_MinBias2_rate", + "varexp": "classes.classContentMinBias2:time", + "selection": "", + "option": "*L" + }, + { + "name": "Class_DMC_rate", + "title": "Class_DMC_rate", + "varexp": "classes.classContentDMC:time", + "selection": "", + "option": "*L" + }, + { + "name": "Class_EMC_rate", + "title": "Class_EMC_rate", + "varexp": "classes.classContentEMC:time", + "selection": "", + "option": "*L" + }, + { + "name": "Class_PHO_rate", + "title": "Class_PHO_rate", + "varexp": "classes.classContentPHO:time", + "selection": "", + "option": "*L" + }, + { + "name": "Input_MinBias2_rate_ratio", + "title": "Input_MinBias2_rate_ratio", + "varexp": "inputs.inputContentMinBias2/inputs.inputContentMinBias1:time", + "selection": "", + "option": "*L" + }, + { + "name": "Input_DMC_rate_ratio", + "title": "Input_DMC_rate_ratio", + "varexp": "inputs.inputContentDMC/inputs.inputContentMinBias1:time", + "selection": "", + "option": "*L" + }, + { + "name": "Input_ECM_rate_ratio", + "title": "Input_ECM_rate_ratio", + "varexp": "inputs.inputContentEMC/inputs.inputContentMinBias1:time", + "selection": "", + "option": "*L" + }, + { + "name": "Input_PHO_rate_ratio", + "title": "Input_PHO_rate_ratio", + "varexp": "inputs.inputContentPHO/inputs.inputContentMinBias1:time", + "selection": "", + "option": "*L" + }, + { + "name": "Class_minBias2_rate_ratio", + "title": "Class_minBias2_rate_ratio", + "varexp": "classes.classContentMinBias2/classes.classContentMinBias1:time", + "selection": "", + "option": "*L" + }, + { + "name": "Class_DMC_rate_ratio", + "title": "Class_DMC_rate_ratio", + "varexp": "classes.classContentDMC/classes.classContentMinBias1:time", + "selection": "", + "option": "*L" + }, + { + "name": "Class_EMC_rate_ratio", + "title": "Class_EMC_rate_ratio", + "varexp": "classes.classContentEMC/classes.classContentMinBias1:time", + "selection": "", + "option": "*L" + }, + { + "name": "Class_PHO_rate_ratio", + "title": "Class_PHO_rate_ratio", + "varexp": "classes.classContentPHO/classes.classContentMinBias1:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:CTP/MO/RawData/inputs" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/CTP/src/qc-ctp.json b/Modules/CTP/src/qc-ctp.json new file mode 100644 index 0000000000..4c83bc3071 --- /dev/null +++ b/Modules/CTP/src/qc-ctp.json @@ -0,0 +1,118 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable", + "maxObjectSize": "2097152" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", "": "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel": "11", "": "Message at this level or above are discarded (default: 21 - Trace)", + "filterDiscardFile": "/tmp/_ID_.txt", "": ["If set, the messages discarded because of filterDiscardLevel", + "will go to this file (default: ); The keyword _ID_ is replaced by the device id. Discarded Debug ", + "messages won't go there."] + }, + "bookkeeping": { + "url": "" + } + }, + "tasks": { + "CTPRawData": { + "active": "true", + "taskName": "RawData", + "className": "o2::quality_control_modules::ctp::CTPRawDataReaderTask", + "moduleName": "QcCTP", + "detectorName": "CTP", + "cycleDurationSeconds": "60", + "resetAfterCycles": "1", + "dataSources": [{ + "type": "dataSamplingPolicy", + "name": "ctp-raw" + },{ + "type": "direct", + "query": "ctp-config:CTP/CONFIG/0?lifetime=condition&ccdb-path=CTP/Config/Config&ccdb-run-dependent=1s" + }], + "extendedTaskParameters": { + "default": { + "default": { + "ccdbName": "https://alice-ccdb.cern.ch", + "MBclassName" : "CMTVX-B-NOPF", + "MB1inputName" : "MTVX", + "MB2inputName" : "MVBA", + "consistencyCheck" : "true", + "readCTPconfigInMonitorData": "true" + } + }, + "PHYSICS": { + "default": { + "MBclassName" : "CMTVX-B-NOPF", + "MB1inputName" : "MTVX", + "MB2inputName" : "MVBA" + }, + "pp": { + "MBclassName" : "CMTVX-B-NOPF", + "MB1inputName" : "MTVX", + "MB2inputName" : "MTVA" + }, + "PbPb": { + "MBclassName" : "CMTCE-B-NOPF", + "MB1inputName" : "MTSC", + "MB2inputName" : "MTCE" + } + } + }, + "location": "remote" + } + },"checks": { + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::ctp::RawDataReaderCheck", + "moduleName": "QcCTP", + "policy": "OnEachSeparately", + "detectorName": "CTP", + "dataSource": [{ + "type": "Task", + "name": "CTPRawData", + "MOs": ["bcMinBias1","bcMinBias2","inputs","classes","inputRatio","classRatio","decodeError"] + }], + "checkParameters": { + "thresholdRateBad": "3", + "thresholdRateMedium": "2", + "thresholdRateRatioBad": "3", + "thresholdRateRatioMedium": "2", + "nSigmaBC": "1" + } + } + } + }, + "dataSamplingPolicies": [ + { + "id": "ctp-raw", + "active": "true", + "machines": [], + "query": "random:CTP/RAWDATA", + "outputs": "filter:DS/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "0" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/CTP/test/testQcCTP.cxx b/Modules/CTP/test/testQcCTP.cxx new file mode 100644 index 0000000000..98e8021cde --- /dev/null +++ b/Modules/CTP/test/testQcCTP.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCTP.cxx +/// \author My Name +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::ctp +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::ctp diff --git a/Modules/Common/CMakeLists.txt b/Modules/Common/CMakeLists.txt index 78dddca575..ddd93c9122 100644 --- a/Modules/Common/CMakeLists.txt +++ b/Modules/Common/CMakeLists.txt @@ -1,55 +1,110 @@ -set(MODULE_NAME "QcCommon") +# Produce the final Version.h using template Version.h.in and substituting +# variables. We don't want to polute our source tree with it, thus putting it in +# binary tree. +configure_file("include/Common/Version.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/include/Common/Version.h" @ONLY) -# ---- Files ---- - -set(SRCS src/NonEmpty.cxx src/MeanIsAbove.cxx) - -set(HEADERS include/Common/NonEmpty.h include/Common/MeanIsAbove.h) +# ---- Library ---- -# Produce the final Version.h using template Version.h.in and substituting variables. We don't want to polute our source -# tree with it, thus putting it in binary tree. -configure_file("include/Common/Version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/include/Common/Version.h" @ONLY) -# ---- Library ---- +add_library(O2QcCommon) -add_library(${MODULE_NAME} SHARED ${SRCS} ${MODULE_NAME}Dict.cxx) +target_sources(O2QcCommon + PRIVATE src/IncreasingEntries.cxx + src/WorstOfAllAggregator.cxx + src/QualityTask.cxx + src/QualityTaskConfig.cxx + src/BigScreenCanvas.cxx + src/BigScreen.cxx + src/BigScreenConfig.cxx + src/CcdbInspectorTask.cxx + src/CcdbInspectorTaskConfig.cxx + src/CcdbInspectorCheck.cxx + src/ObjectComparatorInterface.cxx + src/ObjectComparatorDeviation.cxx + src/ObjectComparatorBinByBinDeviation.cxx + src/ObjectComparatorChi2.cxx + src/ObjectComparatorKolmogorov.cxx + src/ReferenceComparatorPlot.cxx + src/ReferenceComparatorTask.cxx + src/ReferenceComparatorTaskConfig.cxx + src/ReferenceComparatorCheck.cxx + src/CheckerThresholdsConfig.cxx + src/TrendCheck.cxx + src/NonEmpty.cxx + src/MeanIsAbove.cxx + src/TH1Reductor.cxx + src/TH2Reductor.cxx + src/THnSparse5Reductor.cxx + src/QualityReductor.cxx + src/EverIncreasingGraph.cxx + src/TH1SliceReductor.cxx + src/TH2SliceReductor.cxx + src/LHCClockPhaseReductor.cxx) target_include_directories( - ${MODULE_NAME} - PUBLIC $ $ - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src -) + O2QcCommon + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) -target_link_libraries(${MODULE_NAME} PUBLIC QualityControl PRIVATE ROOT::Graf) +target_link_libraries(O2QcCommon PUBLIC O2QualityControl O2::DataFormatsQualityControl PRIVATE ROOT::Graf) -install( - TARGETS ${MODULE_NAME} +install(TARGETS O2QcCommon LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -# ---- ROOT dictionary ---- +add_root_dictionary(O2QcCommon + HEADERS include/Common/NonEmpty.h + include/Common/WorstOfAllAggregator.h + include/Common/QualityTask.h + include/Common/BigScreen.h + include/Common/CcdbInspectorTask.h + include/Common/CcdbInspectorCheck.h + include/Common/ObjectComparatorInterface.h + include/Common/ObjectComparatorDeviation.h + include/Common/ObjectComparatorBinByBinDeviation.h + include/Common/ObjectComparatorChi2.h + include/Common/ObjectComparatorKolmogorov.h + include/Common/ReferenceComparatorTask.h + include/Common/ReferenceComparatorCheck.h + include/Common/TrendCheck.h + include/Common/MeanIsAbove.h + include/Common/TH1Ratio.h + include/Common/TH2Ratio.h + include/Common/TH1Reductor.h + include/Common/TH2Reductor.h + include/Common/THnSparse5Reductor.h + include/Common/QualityReductor.h + include/Common/EverIncreasingGraph.h + include/Common/IncreasingEntries.h + include/Common/TH1SliceReductor.h + include/Common/TH2SliceReductor.h + include/Common/LHCClockPhaseReductor.h + LINKDEF include/Common/LinkDef.h) -generate_root_dict(MODULE_NAME ${MODULE_NAME} LINKDEF "include/Common/LinkDef.h" DICT_CLASS "${MODULE_NAME}Dict") +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/Common + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") # ---- Tests ---- -set(TEST_SRCS test/testMeanIsAbove.cxx test/testNonEmpty.cxx) +set(TEST_SRCS + test/testMeanIsAbove.cxx + test/testNonEmpty.cxx + test/testCommonReductors.cxx + test/testCommonHistRatios.cxx + test/testWorstOfAllAggregator.cxx) foreach(test ${TEST_SRCS}) get_filename_component(test_name ${test} NAME) - string( - REGEX - REPLACE - ".cxx" - "" - test_name - ${test_name} - ) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) add_executable(${test_name} ${test}) - target_link_libraries(${test_name} PRIVATE ${MODULE_NAME} Boost::unit_test_framework) + target_link_libraries(${test_name} + PRIVATE O2QcCommon Boost::unit_test_framework) add_test(NAME ${test_name} COMMAND ${test_name}) - set_tests_properties(${test_name} PROPERTIES TIMEOUT 60) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) endforeach() diff --git a/Modules/Common/etc/ccdb-inspector-example.json b/Modules/Common/etc/ccdb-inspector-example.json new file mode 100644 index 0000000000..58af15dcf3 --- /dev/null +++ b/Modules/Common/etc/ccdb-inspector-example.json @@ -0,0 +1,96 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "2", + "periodName": "LHC42x", "": "Period name / Production tag - e.g. LHC22c, LHC22c1b_test", + "passName": "spass", "": "Pass type - e.g. spass, cpass1", + "provenance": "qc", "": "Provenance - qc or qc_mc depending whether it is normal data or monte carlo data" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "ExampleCcdbInspector": { + "active": "true", + "className": "o2::quality_control_modules::common::CcdbInspectorTask", + "moduleName": "QualityControl", + "detectorName": "GLO", + "extendedTaskParameters": { + "default": { + "default": { + "verbose" : "1", + "timeStampTolerance": "60", + "retryTimeout" : "60", + "retryDelay": "10", + "databaseType": "ccdb", + "databaseUrl": "https://alice-ccdb.cern.ch" + } + } + }, + "dataSources": [ + { + "name": "Mean Vertex", + "path": "GLO/Calib/MeanVertex", + "updatePolicy": "periodic", + "cycleDuration": "200", + "validatorName": "o2::quality_control_modules::glo::MeanVertexValidator", + "moduleName": "QcGLO" + }, + { + "name": "CTP Config", + "path": "CTP/Config/Config", + "updatePolicy": "atSOR" + }, + { + "name": "CTP Scalers", + "path": "CTP/Calib/Scalers", + "updatePolicy": "atEOR" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "30 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks": { + "ExampleCcdbInspectorCheck": { + "active": "true", + "className": "o2::quality_control_modules::common::CcdbInspectorCheck", + "moduleName": "QualityControl", + "detectorName": "GLO", + "policy": "OnAll", + "dataSource": [ + { + "type": "PostProcessing", + "name": "ExampleCcdbInspector", + "MOs" : [ + "ObjectsStatus" + ] + } + ] + } + } + } +} diff --git a/Modules/Common/etc/quality-example.json b/Modules/Common/etc/quality-example.json new file mode 100644 index 0000000000..1a9f25baa1 --- /dev/null +++ b/Modules/Common/etc/quality-example.json @@ -0,0 +1,82 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE", + "periodName": "LHC42x", "": "Period name / Production tag - e.g. LHC22c, LHC22c1b_test", + "passName": "spass", "": "Pass type - e.g. spass, cpass1", + "provenance": "qc", "": "Provenance - qc or qc_mc depending whether it is normal data or monte carlo data" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "ExampleQualityTask": { + "active": "true", + "className": "o2::quality_control::postprocessing::QualityTask", + "moduleName": "QualityControl", + "detectorName": "TST", + "customization": [ + { + "name": "AggregatedQualityName", + "value": "Aggregator/AggregatedQuality" + }, + { + "name": "MessageGood", + "value": "All checks are OK" + }, + { + "name": "MessageMedium", + "value": "Add bookkeeping entry" + }, + { + "name": "MessageBad", + "value": "Inform XYZ on-call immediately" + }, + { + "name": "MessageNull", + "value": "Some histograms are empty!!!" + } + ], + "dataSources": [ + { + "type": "repository", + "path": "TST/QO", + "names": [ + "Check1", "Check2", "Check3" + ] + }, + { + "type": "repository", + "path": "TST/QO/Aggregator", + "name": "AggregatedQuality" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol", "10 minutes" + ] + } + } + } +} diff --git a/Modules/Common/etc/reference-comparator-example.json b/Modules/Common/etc/reference-comparator-example.json new file mode 100644 index 0000000000..e3c35dad16 --- /dev/null +++ b/Modules/Common/etc/reference-comparator-example.json @@ -0,0 +1,100 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "ExampleRefComp": { + "active": "true", + "className": "o2::quality_control_modules::common::ReferenceComparatorTask", + "moduleName": "QualityControl", + "detectorName": "MCH", + "extendedTaskParameters": { + "default": { + "default": { + "notOlderThan" : "300", + "referenceRun" : "551875" + } + }, + "PHYSICS": { + "pp": { + "referenceRun" : "551890" + } + } + }, + "dataGroups": [ + { + "name": "Tracks", + "inputPath": "MCH/MO/Tracks/WithCuts", + "referencePath": "MCH/MO/Tracks", + "outputPath": "Tracks/WithCuts", + "normalizeReference": "true", + "drawRatioOnly": "false", + "legendHeight": "0.2", + "drawOption1D": "E", + "drawOption2D": "COL", + "inputObjects": [ + "TrackEta", + "TrackEtaPhi" + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks": { + "ExampleRefCheck": { + "active": "true", + "className": "o2::quality_control_modules::common::ReferenceComparatorCheck", + "moduleName": "QualityControl", + "detectorName": "MCH", + "policy": "OnAny", + "extendedCheckParameters": { + "default": { + "default": { + "moduleName" : "QualityControl", + "comparatorName" : "o2::quality_control_modules::common::ObjectComparatorChi2", + "ratioPlotRange" : "0.5", + "threshold" : "0.5" + } + } + }, + "dataSource": [ + { + "type": "PostProcessing", + "name": "ExampleRefComp", + "MOs" : [ + "Tracks/WithCuts/TrackEta", + "Tracks/WithCuts/TrackEtaPhi" + ] + } + ] + } + } + } +} diff --git a/Modules/Common/include/Common/BigScreen.h b/Modules/Common/include/Common/BigScreen.h new file mode 100644 index 0000000000..b9d0f6b503 --- /dev/null +++ b/Modules/Common/include/Common/BigScreen.h @@ -0,0 +1,59 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file BigScreen.h +/// \author Andrea Ferrero +/// \brief Quality post-processing task that generates a canvas showing the aggregated quality of each system +/// + +#ifndef QUALITYCONTROL_BIGSCREEN_H +#define QUALITYCONTROL_BIGSCREEN_H + +#include "QualityControl/PostProcessingInterface.h" +#include "Common/BigScreenConfig.h" +#include "Common/BigScreenCanvas.h" +#include "QualityControl/Quality.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief Quality post-processing task that generates a canvas showing the aggregated quality of each system +/// +/// The aggregated quality of each system is displayed as a matrix of colored boxes, with the name of the system +/// above the box and the quality string inside the box +/// +/// \author Andrea Ferrero +class BigScreen : public quality_control::postprocessing::PostProcessingInterface +{ + public: + BigScreen() = default; + ~BigScreen() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + /// \brief maximum allowed age of quality objects, in seconds (default: 10 minutes) + int mMaxObjectTimeShift{ 600 }; + /// \brief read quality objects from all runs + bool mIgnoreActivity{ false }; + /// \brief configuration parameters + BigScreenConfig mConfig; + /// \brief canvas with human-readable quality states + std::unique_ptr mCanvas; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_BIGSCREEN_H diff --git a/Modules/Common/include/Common/BigScreenCanvas.h b/Modules/Common/include/Common/BigScreenCanvas.h new file mode 100644 index 0000000000..b6529fb438 --- /dev/null +++ b/Modules/Common/include/Common/BigScreenCanvas.h @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file BigScreenCanvas.h +/// \author Andrea Ferrero +/// \brief Canvas showing the aggregated quality of configurable groups +/// + +#ifndef QUALITYCONTROL_BIGSCREENCANVAS_H +#define QUALITYCONTROL_BIGSCREENCANVAS_H + +#include "QualityControl/Quality.h" +#include +#include +#include + +namespace o2::quality_control_modules::common +{ + +struct BigScreenElement; + +class BigScreenCanvas : public TCanvas +{ + public: + BigScreenCanvas(std::string name, std::string title, int nRows, int nCols, int borderWidth, + int foregroundColor = kBlack, int backgroundColor = kWhite); + ~BigScreenCanvas() = default; + + /// \brief add a box in the canvas at a given index, with "name" displayed above the box + /// + /// The boxes are arranged in a regular grid of mNRows x mNCols in the canvas. + /// The index proceeds from left to right and from top to bottom in the grid, starting from zero and up to (mNRows * mNCols - 1) + void addBox(std::string boxName, int index); + /// \brief set the text message and color of the box identified by "name". + /// + /// The color value follows the ROOT TColor indexing conventions (https://root.cern.ch/doc/master/classTColor.html) + void setText(std::string boxName, int color, std::string text); + /// \brief set the text message and color of the box identified by "name", based on the specified quality flag + void setQuality(std::string boxName, o2::quality_control::core::Quality quality); + /// \brief refresh the canvas and draw all the boxes and labels + void update(); + + private: + /// \brief size of the grid of boxes in the canvas + int mNRows{ 1 }; + int mNCols{ 1 }; + /// \brief size of the border around the colored boxes + int mBorderWidth{ 5 }; + /// \brief empty space between the boxes + float mPadding{ 0.2 }; + /// \brief offset of the label above the boxes + float mLabelOffset{ 0.05 }; + /// \brief colors associated to each quality state (Good/Medium/Bad/Null) + std::unordered_map mColors; + int mForegroundColor{ kBlack }; /// ROOT color index for the foreground text + int mBackgroundColor{ kWhite }; /// ROOT color index for the canvas backgound + std::shared_ptr mBackgoundPad; /// TPad used to draw the background color + /// \brief elements (colored boxes + labels) displayed in the canvas + std::unordered_map> mBoxes; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_BIGSCREENCANVAS_H diff --git a/Modules/Common/include/Common/BigScreenConfig.h b/Modules/Common/include/Common/BigScreenConfig.h new file mode 100644 index 0000000000..10614e3631 --- /dev/null +++ b/Modules/Common/include/Common/BigScreenConfig.h @@ -0,0 +1,44 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file BigScreenConfig.h +/// \author Andrea Ferrero +/// + +#ifndef QUALITYCONTROL_BIGSCREENCONFIG_H +#define QUALITYCONTROL_BIGSCREENCONFIG_H + +#include "QualityControl/PostProcessingConfig.h" +#include +#include + +namespace o2::quality_control_modules::common +{ + +// todo pretty print +/// \brief BigScreen configuration structure +struct BigScreenConfig : quality_control::postprocessing::PostProcessingConfig { + BigScreenConfig() = default; + BigScreenConfig(std::string name, const boost::property_tree::ptree& config); + ~BigScreenConfig() = default; + + struct DataSource { + std::string detector; + std::string path; + }; + + std::vector dataSources; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_BIGSCREENCONFIG_H diff --git a/Modules/Common/include/Common/CcdbInspectorCheck.h b/Modules/Common/include/Common/CcdbInspectorCheck.h new file mode 100644 index 0000000000..0db9ac96ec --- /dev/null +++ b/Modules/Common/include/Common/CcdbInspectorCheck.h @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CcdbInspectorCheck.h +/// \author Andrea Ferrero +/// \brief Check the validity status of the CCDB objects from the summary plot generated by the CcdbInspectorTask +/// + +#ifndef QC_MODULE_COMMON_COMMONCCDBINSPECTORCHECK_H +#define QC_MODULE_COMMON_COMMONCCDBINSPECTORCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::common +{ + +/// \author Andrea Ferrero +/// \brief Check the validity status of the CCDB objects from the summary plot generated by the CcdbInspectorTask +class CcdbInspectorCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CcdbInspectorCheck() = default; + /// Destructor + ~CcdbInspectorCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void reset() override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + ClassDefOverride(CcdbInspectorCheck, 1); +}; + +} // namespace o2::quality_control_modules::common + +#endif // QC_MODULE_COMMON_COMMONCCDBINSPECTORCHECK_H diff --git a/Modules/Common/include/Common/CcdbInspectorTask.h b/Modules/Common/include/Common/CcdbInspectorTask.h new file mode 100644 index 0000000000..a2d44e5303 --- /dev/null +++ b/Modules/Common/include/Common/CcdbInspectorTask.h @@ -0,0 +1,94 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CcdbInspectorTask.h +/// \author Andrea Ferrero +/// \brief Post-processing task that checks the existence, time stamp and validity of CCDB/QCDB objects +/// + +#ifndef QUALITYCONTROL_CCDB_INSPECTOR_TASK_H +#define QUALITYCONTROL_CCDB_INSPECTOR_TASK_H + +#include "QualityControl/PostProcessingInterface.h" +#include "Common/CcdbInspectorTaskConfig.h" +#include "QualityControl/CcdbDatabase.h" + +#include + +#include + +namespace o2::quality_control_modules::common +{ + +class CcdbValidatorInterface; + +/// \brief Post-processing task that checks the existence, time stamp and validity of CCDB/QCDB objects +/// \author Andrea Ferrero +class CcdbInspectorTask final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// possible results of the object inspection + enum class ObjectStatus { + objectStatusNotChecked = -1, ///< the check was skipped + objectStatusValid = 0, ///< object exists and is valid + objectStatusInvalid = 1, ///< object exists but is invalid + objectStatusOld = 2, ///< the last version of the object is older than expected + objectStatusMissing = 3 ///< object cannot be found + }; + + CcdbInspectorTask() = default; + ~CcdbInspectorTask() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + /// returns the creation time stamp and the run number associated to the object described by "path" + /// \param path the path of the object in the DB + /// \param metadata optional metadata descriptors that have to be matched when searching the object + /// \return a tuple providing the object's time-stamps and associated run number (<0>=validFrom, <1>=validUntil, <2>=creationTime, <3>=runNumber) + std::tuple getObjectInfo(const std::string& path, const std::map& metadata); + /// returns a copy of the object described by "path" and valid for a given time-stamp + /// \param path the path of the object in the DB + /// \param tinfo information about the type of the object to be retrieved + /// \param timestamp to be matched with the object's validity + /// \param metadata optional metadata descriptors that have to be matched when searching the object + /// \return the address of the retrieved object, or nulptr if not found + void* getObject(const std::string& path, std::type_info const& tinfo, uint64_t timestamp, const std::map& metadata); + /// searches the object described by the data source and compatible with the time-stamp and activity associated to the trigger + /// \param dataSource the data source describing the object's path and other relevant parameters + /// \param trigger the post-processing trigger with the associated time-stamp and activity + /// \param atFinalize flag indicating wether the function has been called from the finalize() method + ObjectStatus inspectObject(CcdbInspectorTaskConfig::DataSource& dataSource, Trigger trigger, bool atFinalize); + + /// tolerance on the creation time stamp of the objects + int mTimeStampTolerance{ 60000 }; ///< in milliseconds + /// timeout for object query retries at finlaize + int mRetryTimeout{ 60 }; + /// delay between object query retries at finlaize + int mRetryDelay{ 10 }; + /// type and address of the source database + std::string mDatabaseType{ "ccdb" }; + std::string mDatabaseUrl{ "https://alice-ccdb.cern.ch" }; + std::unique_ptr mDatabase; + std::unique_ptr mConfig; + /// external validator modules to inspect the contents of the DB objects + std::unordered_map> mValidators; + /// output plot that summarizes the status of the monitored DB objects + std::unique_ptr mHistObjectsStatus; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_CCDB_INSPECTOR_TASK_H diff --git a/Modules/Common/include/Common/CcdbInspectorTaskConfig.h b/Modules/Common/include/Common/CcdbInspectorTaskConfig.h new file mode 100644 index 0000000000..d33805040c --- /dev/null +++ b/Modules/Common/include/Common/CcdbInspectorTaskConfig.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CcdbInspectorTaskConfig.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Header file for the configuration of CCDB inspector post-processing task +/// + +#ifndef QC_MODULE_COMMON_CCDB_INSPECTOR_PP_CONF_H +#define QC_MODULE_COMMON_CCDB_INSPECTOR_PP_CONF_H + +#include "QualityControl/PostProcessingConfig.h" +#include +#include + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::common +{ + +/// \brief configuration structure for the CcdbInspectorTask +struct CcdbInspectorTaskConfig : PostProcessingConfig { + CcdbInspectorTaskConfig() = default; + CcdbInspectorTaskConfig(std::string name, const boost::property_tree::ptree& config); + ~CcdbInspectorTaskConfig() = default; + + /// update policy associated to each data source + enum class ObjectUpdatePolicy { + updatePolicyPeriodic, ///< the object is updated periodically at fixed time intervals + updatePolicyAtSOR, ///< the object is updated only once at start-of-run + updatePolicyAtEOR ///< the object is updated only once at end-of-run + }; + + /// CCDB object description and associated variables + struct DataSource { + std::string name; ///< mnemonic name of the object + std::string path; ///< object path in the database + std::string validatorName; ///< name of the optional validator class (can be empty) + std::string moduleName; ///< module containing the validator class (mandatory if validatorName is not empty) + ObjectUpdatePolicy updatePolicy; ///< object's update policy + int cycleDuration; ///< time interval between updates for periodic objects + uint64_t lastCreationTimestamp; ///< creation time-stamp of the last valid object that was found + int validObjectsCount; ///< number of valid objects that have been found + int binNumber; ///< bin number associated to this object in the output 2-D plot + }; + + std::vector dataSources; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QC_MODULE_COMMON_CCDB_INSPECTOR_PP_CONF_H diff --git a/Modules/Common/include/Common/CcdbValidatorInterface.h b/Modules/Common/include/Common/CcdbValidatorInterface.h new file mode 100644 index 0000000000..3e2cae8a13 --- /dev/null +++ b/Modules/Common/include/Common/CcdbValidatorInterface.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CcdbValidatorInterface.h +/// \author Andrea Ferrero +/// +#ifndef QUALITYCONTROL_CCDB_VALIDATOR_INTERFACE_H +#define QUALITYCONTROL_CCDB_VALIDATOR_INTERFACE_H + +#include + +namespace o2::quality_control_modules::common +{ + +/// \brief An interface for validating the content of CCDB objects +class CcdbValidatorInterface +{ + public: + /// \brief Constructor + CcdbValidatorInterface() = default; + /// \brief Destructor + virtual ~CcdbValidatorInterface() = default; + + /// \brief return the type_info of the CCDB object to be inspected + virtual const std::type_info& getTinfo() const = 0; + + /// \brief Inspect and validate the content of a given CCDB object + /// \param An object to be inspected + virtual bool validate(void* obj) = 0; +}; + +} // namespace o2::quality_control_modules::common +#endif // QUALITYCONTROL_CCDB_VALIDATOR_INTERFACE_H diff --git a/Modules/Common/include/Common/CheckerThresholdsConfig.h b/Modules/Common/include/Common/CheckerThresholdsConfig.h new file mode 100644 index 0000000000..04c874f365 --- /dev/null +++ b/Modules/Common/include/Common/CheckerThresholdsConfig.h @@ -0,0 +1,71 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckerThresholdsConfig.h +/// \author Andrea Ferrero +/// \brief Utility class handling thresholds and axis ranges and retrieve them from the custom parameters +/// + +#ifndef QC_MODULE_COMMON_CHECKERTHRESHOLDSCONFIG_H +#define QC_MODULE_COMMON_CHECKERTHRESHOLDSCONFIG_H + +#include "QualityControl/Activity.h" +#include "QualityControl/CustomParameters.h" + +#include +#include +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +namespace internal +{ +class Thresholds; +class XYRanges; +} // namespace internal + +class CheckerThresholdsConfig +{ + public: + CheckerThresholdsConfig(const CustomParameters& customParameters, const Activity& activity); + ~CheckerThresholdsConfig() = default; + + /// \brief function to retrieve the thresholds for a given interaction rate, if available + std::array>, 2> getThresholdsForPlot(const std::string& plotName, double rate); + + /// \brief optional X-Y ranges over which the check must be restricted + std::array>, 2> getRangesForPlot(const std::string& plotName); + + private: + void initThresholdsForPlot(const std::string& plotName); + void initRangesForPlot(const std::string& plotName); + + CustomParameters mCustomParameters; + Activity mActivity; + + /// vectors of [min,max,rate] tuples. The first two values are the minimum and maximum threshold values, + /// and the third is the associated reference interaction rate (optional) + std::array, 2> mDefaultThresholds; + std::array>, 2> mThresholds; + + /// \brief optional X-Y ranges over which the check must be restricted + std::array, 2> mDefaultRanges; + std::array>, 2> mRanges; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QC_MODULE_COMMON_CHECKERTHRESHOLDSCONFIG_H diff --git a/Modules/Common/include/Common/EverIncreasingGraph.h b/Modules/Common/include/Common/EverIncreasingGraph.h new file mode 100644 index 0000000000..985408ed1f --- /dev/null +++ b/Modules/Common/include/Common/EverIncreasingGraph.h @@ -0,0 +1,45 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file EverIncreasingGraph.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_MODULE_DAQ_EVERINCREASINGRAPH_H +#define QC_MODULE_DAQ_EVERINCREASINGRAPH_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief Check whether a plot is empty or not. +/// +/// \author Barthelemy von Haller +class EverIncreasingGraph : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + EverIncreasingGraph() = default; + /// Destructor + ~EverIncreasingGraph() override = default; + + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + ClassDefOverride(EverIncreasingGraph, 2); +}; + +} // namespace o2::quality_control_modules::common + +#endif // QC_MODULE_DAQ_EVERINCREASINGRAPH_H diff --git a/Modules/Common/include/Common/IncreasingEntries.h b/Modules/Common/include/Common/IncreasingEntries.h new file mode 100644 index 0000000000..63ea546dbd --- /dev/null +++ b/Modules/Common/include/Common/IncreasingEntries.h @@ -0,0 +1,65 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file IncreasingEntries.h +/// \author My Name +/// + +#ifndef QC_MODULE_COMMON_COMMONINCREASINGENTRIES_H +#define QC_MODULE_COMMON_COMMONINCREASINGENTRIES_H + +#include "QualityControl/CheckInterface.h" +#include + +namespace o2::quality_control_modules::common +{ + +/// \brief Check if the number of Entries has increased or not +/// If it does not increase over the past N cycles (N=1 by default), the quality is bad. +/// The behaviour can be modified with the customParameter "mustIncrease". If set to "false", +/// it will actually have a bad quality if the number of entries increases. +class IncreasingEntries : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + IncreasingEntries() = default; + /// Destructor + ~IncreasingEntries() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + std::map mLastEntries; // moName -> number of entries + + // count the number of faults we have seen in a row for each object + std::map mMoFaultCount; // moName -> number of faults in a row + + // the pave text with the error message + std::shared_ptr mPaveText; + + // store the faults to beautify them later + std::vector mFaultyObjectsNames; + + // decides whether the number of entries must increase or it must remain the same + bool mMustIncrease = true; + // The number of cycles during which the number of entries did not move until we set the quality bad. + int mBadCyclesLimit = 1; + + ClassDefOverride(IncreasingEntries, 3); +}; + +} // namespace o2::quality_control_modules::common + +#endif // QC_MODULE_COMMON_COMMONINCREASINGENTRIES_H diff --git a/Modules/Common/include/Common/LHCClockPhaseReductor.h b/Modules/Common/include/Common/LHCClockPhaseReductor.h new file mode 100644 index 0000000000..c669b903d0 --- /dev/null +++ b/Modules/Common/include/Common/LHCClockPhaseReductor.h @@ -0,0 +1,47 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file LHCClockPhaseReductor.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_LHCCLOCKPHASEREDUCTOR_H +#define QUALITYCONTROL_LHCCLOCKPHASEREDUCTOR_H + +#include "QualityControl/ReductorConditionAny.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief A Reductor which obtains the LHC clock phase. +/// +/// A Reductor which obtains the LHC clock phase based on the corresponding object in CCDB. +/// It produces a branch in the format: "phase/F" +class LHCClockPhaseReductor : public quality_control::postprocessing::ReductorConditionAny +{ + public: + LHCClockPhaseReductor() = default; + ~LHCClockPhaseReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + bool update(ConditionRetriever& retriever) override; + + private: + struct { + Float_t phase; + } mStats; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_LHCCLOCKPHASEREDUCTOR_H diff --git a/Modules/Common/include/Common/LinkDef.h b/Modules/Common/include/Common/LinkDef.h index 30a62d4c9f..e889a847ff 100644 --- a/Modules/Common/include/Common/LinkDef.h +++ b/Modules/Common/include/Common/LinkDef.h @@ -5,4 +5,32 @@ #pragma link C++ class o2::quality_control_modules::common::NonEmpty + ; #pragma link C++ class o2::quality_control_modules::common::MeanIsAbove + ; +#pragma link C++ class o2::quality_control_modules::common::TH1Ratio < TH1F> + ; +#pragma link C++ class o2::quality_control_modules::common::TH1Ratio < TH1D> + ; +#pragma link C++ class o2::quality_control_modules::common::TH2Ratio < TH2F> + ; +#pragma link C++ class o2::quality_control_modules::common::TH2Ratio < TH2D> + ; +#pragma link C++ class o2::quality_control_modules::common::TH1Reductor + ; +#pragma link C++ class o2::quality_control_modules::common::TH2Reductor + ; +#pragma link C++ class o2::quality_control_modules::common::THnSparse5Reductor + ; +#pragma link C++ class o2::quality_control_modules::common::QualityReductor + ; +#pragma link C++ class o2::quality_control_modules::common::EverIncreasingGraph + ; +#pragma link C++ class o2::quality_control_modules::common::QualityTask + ; +#pragma link C++ class o2::quality_control_modules::common::BigScreen + ; +#pragma link C++ class o2::quality_control_modules::common::CcdbInspectorTask + ; +#pragma link C++ class o2::quality_control_modules::common::CcdbInspectorCheck + ; +#pragma link C++ class o2::quality_control_modules::common::ObjectComparatorDeviation + ; +#pragma link C++ class o2::quality_control_modules::common::ObjectComparatorBinByBinDeviation + ; +#pragma link C++ class o2::quality_control_modules::common::ObjectComparatorChi2 + ; +#pragma link C++ class o2::quality_control_modules::common::ObjectComparatorKolmogorov + ; +#pragma link C++ class o2::quality_control_modules::common::ReferenceComparatorTask + ; +#pragma link C++ class o2::quality_control_modules::common::ReferenceComparatorCheck + ; +#pragma link C++ class o2::quality_control_modules::common::TrendCheck + ; +#pragma link C++ class o2::quality_control_modules::common::WorstOfAllAggregator + ; +#pragma link C++ class o2::quality_control_modules::common::IncreasingEntries + ; +#pragma link C++ class o2::quality_control_modules::common::TH1SliceReductor + ; +#pragma link C++ class o2::quality_control_modules::common::TH2SliceReductor + ; +#pragma link C++ class o2::quality_control_modules::common::LHCClockPhaseReductor + ; + +#pragma link C++ function o2::quality_control_modules::common::getFromConfig + ; + #endif diff --git a/Modules/Common/include/Common/MeanIsAbove.h b/Modules/Common/include/Common/MeanIsAbove.h index 4b44ca3971..cc1704ab3d 100644 --- a/Modules/Common/include/Common/MeanIsAbove.h +++ b/Modules/Common/include/Common/MeanIsAbove.h @@ -1,5 +1,16 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// -/// \file CheckInterface.h +/// \file MeanIsAbove.h /// \author Barthelemy von Haller /// @@ -7,8 +18,6 @@ #define QC_MODULE_COMMON_MEANISABOVE_H #include "QualityControl/CheckInterface.h" -#include "QualityControl/MonitorObject.h" -#include "QualityControl/Quality.h" using namespace o2::quality_control::core; @@ -22,19 +31,18 @@ class MeanIsAbove : public o2::quality_control::checker::CheckInterface { public: /// Default constructor - MeanIsAbove(); + MeanIsAbove() = default; /// Destructor ~MeanIsAbove() override = default; - void configure(std::string name) override; - Quality check(const MonitorObject* mo) override; - void beautify(MonitorObject* mo, Quality checkResult = Quality::Null) override; - std::string getAcceptedType() override; + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; private: - float mThreshold; + float mThreshold = 0.0f; - ClassDefOverride(MeanIsAbove, 1) + ClassDefOverride(MeanIsAbove, 2) }; } // namespace o2::quality_control_modules::common diff --git a/Modules/Common/include/Common/NonEmpty.h b/Modules/Common/include/Common/NonEmpty.h index a400b673db..40b2d51190 100644 --- a/Modules/Common/include/Common/NonEmpty.h +++ b/Modules/Common/include/Common/NonEmpty.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file NonEmpty.h /// \author Barthelemy von Haller @@ -7,8 +18,6 @@ #define QC_MODULE_COMMON_NONEMPTY_H #include "QualityControl/CheckInterface.h" -#include "QualityControl/MonitorObject.h" -#include "QualityControl/Quality.h" namespace o2::quality_control_modules::common { @@ -20,16 +29,13 @@ class NonEmpty : public o2::quality_control::checker::CheckInterface { public: /// Default constructor - NonEmpty(); + NonEmpty() = default; /// Destructor - ~NonEmpty() override; - - void configure(std::string name) override; - Quality check(const MonitorObject* mo) override; - void beautify(MonitorObject* mo, Quality checkResult = Quality::Null) override; - std::string getAcceptedType() override; + ~NonEmpty() override = default; - ClassDefOverride(NonEmpty, 1); + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + ClassDefOverride(NonEmpty, 2); }; } // namespace o2::quality_control_modules::common diff --git a/Modules/Common/include/Common/ObjectComparatorBinByBinDeviation.h b/Modules/Common/include/Common/ObjectComparatorBinByBinDeviation.h new file mode 100644 index 0000000000..d819eeba68 --- /dev/null +++ b/Modules/Common/include/Common/ObjectComparatorBinByBinDeviation.h @@ -0,0 +1,48 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorBinByBinDeviation.h +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on the average of the relative deviation between the bins +/// + +#ifndef QUALITYCONTROL_ObjectComparatorBinByBinDeviation_H +#define QUALITYCONTROL_ObjectComparatorBinByBinDeviation_H + +#include "Common/ObjectComparatorInterface.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief A class for comparing two histogram objects based on the average of the relative deviation between the bins +class ObjectComparatorBinByBinDeviation : public ObjectComparatorInterface +{ + public: + /// \brief Constructor + ObjectComparatorBinByBinDeviation() = default; + /// \brief Destructor + virtual ~ObjectComparatorBinByBinDeviation() = default; + + /// \brief comparator configuration via CustomParameters + void configure(const o2::quality_control::core::CustomParameters& customParameters, const std::string& plotName, const o2::quality_control::core::Activity activity = {}) override; + + /// \brief objects comparison function + /// \return the quality resulting from the object comparison + o2::quality_control::core::Quality compare(TObject* object, TObject* referenceObject, std::string& message) override; + + private: + int mMaxAllowedBadBins{ 0 }; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_ObjectComparatorBinByBinDeviation_H diff --git a/Modules/Common/include/Common/ObjectComparatorChi2.h b/Modules/Common/include/Common/ObjectComparatorChi2.h new file mode 100644 index 0000000000..2a87c40475 --- /dev/null +++ b/Modules/Common/include/Common/ObjectComparatorChi2.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorChi2.h +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on a chi2 test +/// + +#ifndef QUALITYCONTROL_ObjectComparatorChi2_H +#define QUALITYCONTROL_ObjectComparatorChi2_H + +#include "Common/ObjectComparatorInterface.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief A class for comparing two histogram objects based on a chi2 test +class ObjectComparatorChi2 : public ObjectComparatorInterface +{ + public: + /// \brief Constructor + ObjectComparatorChi2() = default; + /// \brief Destructor + virtual ~ObjectComparatorChi2() = default; + + /// \brief objects comparison function + /// \return the quality resulting from the object comparison + o2::quality_control::core::Quality compare(TObject* object, TObject* referenceObject, std::string& message) override; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_ObjectComparatorChi2_H diff --git a/Modules/Common/include/Common/ObjectComparatorDeviation.h b/Modules/Common/include/Common/ObjectComparatorDeviation.h new file mode 100644 index 0000000000..0e693e6f41 --- /dev/null +++ b/Modules/Common/include/Common/ObjectComparatorDeviation.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorDeviation.h +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on the average of the relative deviation between the bins +/// + +#ifndef QUALITYCONTROL_ObjectComparatorDeviation_H +#define QUALITYCONTROL_ObjectComparatorDeviation_H + +#include "Common/ObjectComparatorInterface.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief A class for comparing two histogram objects based on the average of the relative deviation between the bins +class ObjectComparatorDeviation : public ObjectComparatorInterface +{ + public: + /// \brief Constructor + ObjectComparatorDeviation() = default; + /// \brief Destructor + virtual ~ObjectComparatorDeviation() = default; + + /// \brief objects comparison function + /// \return the quality resulting from the object comparison + o2::quality_control::core::Quality compare(TObject* object, TObject* referenceObject, std::string& message) override; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_ObjectComparatorDeviation_H diff --git a/Modules/Common/include/Common/ObjectComparatorInterface.h b/Modules/Common/include/Common/ObjectComparatorInterface.h new file mode 100644 index 0000000000..f6363cb7db --- /dev/null +++ b/Modules/Common/include/Common/ObjectComparatorInterface.h @@ -0,0 +1,75 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorInterface.h +/// \author Andrea Ferrero +/// \brief An interface for comparing two TObject +/// + +#ifndef QUALITYCONTROL_ObjectComparatorInterface_H +#define QUALITYCONTROL_ObjectComparatorInterface_H + +#include "QualityControl/Quality.h" +#include "QualityControl/CustomParameters.h" +#include "QualityControl/Activity.h" + +#include + +class TObject; +class TH1; + +namespace o2::quality_control_modules::common +{ + +/// \brief An interface for comparing two TObject +class ObjectComparatorInterface +{ + public: + /// \brief Constructor + ObjectComparatorInterface() = default; + /// \brief Destructor + virtual ~ObjectComparatorInterface() = default; + + /// \brief comparator configuration via CustomParameters + virtual void configure(const o2::quality_control::core::CustomParameters& customParameters, const std::string& plotName, const o2::quality_control::core::Activity activity = {}); + + /// setter/getter methods for the threshold to define the goodness of the comparison + void setThreshold(double threshold) { mThreshold = threshold; } + double getThreshold() { return mThreshold; } + + void setXRange(const std::pair& range) { mRanges[0] = range; } + void setYRange(const std::pair& range) { mRanges[1] = range; } + std::optional> getXRange() { return mRanges[0]; } + std::optional> getYRange() { return mRanges[1]; } + + /// perform a number of sanity checks on the input objects + /// \return a tuple containing pointers to the histogram, the reference histogram, and a boolean indicating the success of the checks + std::tuple checkInputObjects(TObject* object, TObject* referenceObject, std::string& message); + + /// \brief objects comparison function + /// \return the quality resulting from the object comparison + virtual o2::quality_control::core::Quality compare(TObject* object, TObject* referenceObject, std::string& message) = 0; + + protected: + /// \brief helper function to retrieve plot-specific configuration parameters + std::string getParameterForPlot(const o2::quality_control::core::CustomParameters& customParameters, const std::string& parKey, const std::string& plotName, const o2::quality_control::core::Activity& activity); + + private: + /// the threshold to define the goodness of the comparison + double mThreshold{ 0 }; + // optional ranges to restrict the histogram area on which the comparison is performed + std::array>, 2> mRanges; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_ObjectComparatorInterface_H diff --git a/Modules/Common/include/Common/ObjectComparatorKolmogorov.h b/Modules/Common/include/Common/ObjectComparatorKolmogorov.h new file mode 100644 index 0000000000..0ac8868341 --- /dev/null +++ b/Modules/Common/include/Common/ObjectComparatorKolmogorov.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorKolmogorov.h +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on a Kolmogorov test +/// + +#ifndef QUALITYCONTROL_ObjectComparatorKolmogorov_H +#define QUALITYCONTROL_ObjectComparatorKolmogorov_H + +#include "Common/ObjectComparatorInterface.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief A class for comparing two histogram objects based on a Kolmogorov test +class ObjectComparatorKolmogorov : public ObjectComparatorInterface +{ + public: + /// \brief Constructor + ObjectComparatorKolmogorov() = default; + /// \brief Destructor + virtual ~ObjectComparatorKolmogorov() = default; + + /// \brief objects comparison function + /// \return the quality resulting from the object comparison + o2::quality_control::core::Quality compare(TObject* object, TObject* referenceObject, std::string& message) override; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_ObjectComparatorKolmogorov_H diff --git a/Modules/Common/include/Common/QualityReductor.h b/Modules/Common/include/Common/QualityReductor.h new file mode 100644 index 0000000000..a954f32e6f --- /dev/null +++ b/Modules/Common/include/Common/QualityReductor.h @@ -0,0 +1,51 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityReductor.h +/// \author Piotr Konopka +/// +#ifndef QUALITYCONTROL_QUALITYREDUCTOR_H +#define QUALITYCONTROL_QUALITYREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/Quality.h" + +namespace o2::quality_control_modules::common +{ + +// todo fix plots with qualities names - ugly axes with names +/// \brief A Reductor of QualityObjects, stores a name and level of a Quality +/// +/// A Reductor of QualityObjects, stores a name and level of a Quality +/// It produces a branch in the format: "level/i:name/C" +class QualityReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + QualityReductor() = default; + ~QualityReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + static constexpr size_t NAME_SIZE = 8; + + private: + struct { + UInt_t level = quality_control::core::Quality::NullLevel; + char name[NAME_SIZE]; + } mQuality; +}; + +} // namespace o2::quality_control_modules::common + +#endif //QUALITYCONTROL_QUALITYREDUCTOR_H diff --git a/Modules/Common/include/Common/QualityTask.h b/Modules/Common/include/Common/QualityTask.h new file mode 100644 index 0000000000..2d6d885717 --- /dev/null +++ b/Modules/Common/include/Common/QualityTask.h @@ -0,0 +1,110 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityTask.h +/// \author Andrea Ferrero +/// \brief Post-processing of quality flags +/// + +#ifndef QUALITYCONTROL_QUALITYTASK_H +#define QUALITYCONTROL_QUALITYTASK_H + +#include "QualityControl/PostProcessingInterface.h" +#include "Common/QualityTaskConfig.h" +#include "QualityControl/Quality.h" +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::core +{ +class QualityObject; +} + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ +struct Trigger; +} + +namespace o2::quality_control_modules::common +{ + +/// \brief A post-processing task which shows and trends a given list of quality flags +/// +/// A post-processing task which shows and trends a given list of quality flags. +/// The list of quality objects to be monitored is passed through the task's dataSources. +/// The task produces the following output: +/// * A canvas with the value of the quality objects in human-readable format. +/// The aggregated quality, whose name can be specified via configuration keys, +/// is shown at the top of the canvas. +/// Configurable messages can also be associated to the various possible values of +/// the aggregated quality (Good/Medium/Bad/Null) +/// * An histogram with the distribution of the values for each quality object +/// * A trend plot for each of the quality objects, showing the evolution of the values over time +/// +/// \author Andrea Ferrero +class QualityTask : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief helper class to trend the values of a given Quality Object + struct QualityTrendGraph : public TCanvas { + QualityTrendGraph(std::string name, std::string title); + void update(uint64_t time, o2::quality_control::core::Quality q); + static std::string distributionName(const std::string& groupName, const std::string& qualityName); + static std::string trendName(const std::string& groupName, const std::string& qualityName); + + std::unique_ptr mGraph; + std::array, 4> mLabels; + }; + + QualityTask() = default; + ~QualityTask() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + std::pair, bool> getLatestQO( + quality_control::repository::DatabaseInterface& qcdb, const o2::quality_control::core::Activity& activity, const std::string& fullPath, const std::string& group); + + private: + /// \brief configuration parameters + QualityTaskConfig mConfig; + /// \brief QOs are discarded if their creation time stamp is more than mMaxObjectAgeMs milliseconds in the past (set to zero to accept all objects) + int64_t mMaxObjectAgeMs{ 600000 }; + /// \brief latest creation timestamp of each tracked QO + std::unordered_map mLatestTimestamps; + /// \brief colors associated to each quality state (Good/Medium/Bad/Null) + std::unordered_map mColors; + /// \brief numerical IDs associated to each quality state (Good/Medium/Bad/Null) + std::unordered_map mQualityIDs; + /// \brief Quality Objects histograms + std::unordered_map> mHistograms; + /// \brief Quality Objects trends + std::unordered_map> mTrends; + /// \brief canvas with human-readable quality states and messages + std::unique_ptr mQualityCanvas; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_QUALITYTASK_H diff --git a/Modules/Common/include/Common/QualityTaskConfig.h b/Modules/Common/include/Common/QualityTaskConfig.h new file mode 100644 index 0000000000..bef1ece019 --- /dev/null +++ b/Modules/Common/include/Common/QualityTaskConfig.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityTaskConfig.h +/// \author Andrea Ferrero +/// + +#ifndef QUALITYCONTROL_QUALITYTASKCONFIG_H +#define QUALITYCONTROL_QUALITYTASKCONFIG_H + +#include +#include +#include +#include "QualityControl/PostProcessingConfig.h" +#include "QualityControl/Quality.h" + +namespace o2::quality_control_modules::common +{ + +// todo pretty print +/// \brief QualityTask configuration structure +struct QualityTaskConfig : quality_control::postprocessing::PostProcessingConfig { + QualityTaskConfig() = default; + QualityTaskConfig(std::string name, const boost::property_tree::ptree& config); + ~QualityTaskConfig() = default; + + struct QualityConfig { + std::string name; + std::string title; + std::unordered_map messages = { + { quality_control::core::Quality::Null.getName(), "" }, + { quality_control::core::Quality::Bad.getName(), "" }, + { quality_control::core::Quality::Medium.getName(), "" }, + { quality_control::core::Quality::Good.getName(), "" } + }; + }; + + struct QualityGroup { + std::string name; + std::string title; + std::string path; + std::vector ignoreQualitiesDetails{}; + std::vector inputObjects{}; + }; + + std::vector qualityGroups; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_QUALITYTASKCONFIG_H \ No newline at end of file diff --git a/Modules/Common/include/Common/ReferenceComparatorCheck.h b/Modules/Common/include/Common/ReferenceComparatorCheck.h new file mode 100644 index 0000000000..070e97e69f --- /dev/null +++ b/Modules/Common/include/Common/ReferenceComparatorCheck.h @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorCheck.h +/// \author Andrea Ferrero +/// \brief A generic QC check that compares a given set of histograms with their corresponding references +/// + +#ifndef QUALITYCONTROL_ReferenceComparatorCheck_H +#define QUALITYCONTROL_ReferenceComparatorCheck_H + +#include "QualityControl/CheckInterface.h" +#include "Common/ObjectComparatorInterface.h" + +#include +#include + +class TPaveText; +class TCanvas; + +namespace o2::quality_control_modules::common +{ + +/// \brief A generic QC check that compares a given set of histograms with their corresponding references +/// \author Andrea Ferrero +class ReferenceComparatorCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + ReferenceComparatorCheck() = default; + /// Destructor + ~ReferenceComparatorCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void reset() override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + private: + Quality getSinglePlotQuality(std::shared_ptr mo, ObjectComparatorInterface* comparator, std::string& message); + void beautifyRatioPlot(const std::string& moName, TH1* ratioPlot, const Quality& quality); + + std::map mQualityFlags; + std::map> mQualityLabels; + quality_control::core::Activity mActivity; + quality_control::core::Activity mReferenceActivity; + std::string mComparatorModuleName; /// dynamic module providing the object comparator + std::string mComparatorClassName; /// name of the object comparator class + bool mIgnorePeriodForReference{ true }; /// whether to specify the period name in the reference run query + bool mIgnorePassForReference{ true }; /// whether to specify the pass name in the reference run query + size_t mReferenceRun; + double mRatioPlotRange{ 0 }; + /// cached reference MOs + std::unordered_map> mReferencePlots; + /// collection of object comparators with plot-specific settings + std::unordered_map> mComparators; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QC_MODULE_SKELETON_ReferenceComparatorCheck_H diff --git a/Modules/Common/include/Common/ReferenceComparatorPlot.h b/Modules/Common/include/Common/ReferenceComparatorPlot.h new file mode 100644 index 0000000000..4c95cf1ee3 --- /dev/null +++ b/Modules/Common/include/Common/ReferenceComparatorPlot.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorPlot.h +/// \author Andrea Ferrero +/// \brief Utility class for the combined drawing of the current and reference plots, and their ratio +/// + +#ifndef QUALITYCONTROL_REFERENCECOMPARATORPLOT_H +#define QUALITYCONTROL_REFERENCECOMPARATORPLOT_H + +#include +#include +#include + +namespace o2::quality_control_modules::common +{ + +class ReferenceComparatorPlotImpl; + +class ReferenceComparatorPlot +{ + public: + /// ReferenceComparatorPlot constructor + /// \param referenceHistogram pointer to the reference histogram object, used to initialize the internal plots + /// \param outputPath QCDB path were the output canvas is stored + /// \param scaleReference if true the reference plot is sclaled such that its integral matches the one of the current histogram + /// \param drawRatioOnly if true only the ratio between current and reference plot is draw, otherwise also the individual plots are drawn in addition + /// \param drawOption1D ROOT draw option to be used for 1-D plots + /// \param drawOption2D ROOT draw option to be used for 2-D plots + ReferenceComparatorPlot(TH1* referenceHistogram, + int referenceRun, + const std::string& outputPath, + bool scaleReference, + bool drawRatioOnly, + double legendHeight, + bool logScale, + const std::string& drawOption1D, + const std::string& drawOption2D); + virtual ~ReferenceComparatorPlot() = default; + + TObject* getMainCanvas(); + void update(TH1* histogram); + + private: + std::shared_ptr mImplementation; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_REFERENCECOMPARATORTASK_H diff --git a/Modules/Common/include/Common/ReferenceComparatorTask.h b/Modules/Common/include/Common/ReferenceComparatorTask.h new file mode 100644 index 0000000000..67a3994492 --- /dev/null +++ b/Modules/Common/include/Common/ReferenceComparatorTask.h @@ -0,0 +1,83 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorTask.h +/// \author Andrea Ferrero +/// \brief Post-processing task that compares a given set of plots with reference ones +/// + +#ifndef QUALITYCONTROL_REFERENCECOMPARATORTASK_H +#define QUALITYCONTROL_REFERENCECOMPARATORTASK_H + +#include "Common/ReferenceComparatorTaskConfig.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/DatabaseInterface.h" +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::common +{ + +class ReferenceComparatorPlot; + +/// \brief Post-processing task that compares a given set of plots with reference ones +/// +/// For each input plot, the task publishes the ratio between the plot and the corresponding reference. +/// Moreover, for 1-D histograms it also publishes the plot itself with the reference superimposed, for visual comparison. +/// +/// \author Andrea Ferrero +class ReferenceComparatorTask : public quality_control::postprocessing::PostProcessingInterface +{ + public: + ReferenceComparatorTask() = default; + ~ReferenceComparatorTask() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + std::map>& getComparatorPlots() { return mHistograms; } + std::shared_ptr getComparatorPlot(std::string plotName); + + struct HistoWithRef { + std::shared_ptr mPlot; + std::shared_ptr mRefPlot; + std::shared_ptr mRatioPlot; + std::shared_ptr mPadHist; + std::shared_ptr mPadHistRatio; + std::shared_ptr mCanvas; + }; + + private: + size_t mReferenceRun{ 0 }; + int mNotOlderThan{ 120 }; + bool mIgnorePeriodForReference{ true }; /// whether to specify the period name in the reference run query + bool mIgnorePassForReference{ true }; /// whether to specify the pass name in the reference run query + + /// \brief configuration parameters + ReferenceComparatorTaskConfig mConfig; + /// \brief list of plot names, separately for each group + std::map> mPlotNames; + /// \brief reference MOs + std::map> mReferencePlots; + /// \brief histograms with comparison to reference + std::map> mHistograms; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_REFERENCECOMPARATORTASK_H diff --git a/Modules/Common/include/Common/ReferenceComparatorTaskConfig.h b/Modules/Common/include/Common/ReferenceComparatorTaskConfig.h new file mode 100644 index 0000000000..e4e696897f --- /dev/null +++ b/Modules/Common/include/Common/ReferenceComparatorTaskConfig.h @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorTaskConfig.h +/// \author Andrea Ferrero +/// \brief ReferenceComparatorTask configuration structure +/// + +#ifndef QUALITYCONTROL_REFERENCECOMPARATORTASKCONFIG_H +#define QUALITYCONTROL_REFERENCECOMPARATORTASKCONFIG_H + +#include +#include +#include +#include "QualityControl/PostProcessingConfig.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief ReferenceComparatorTask configuration structure +struct ReferenceComparatorTaskConfig : quality_control::postprocessing::PostProcessingConfig { + ReferenceComparatorTaskConfig() = default; + ReferenceComparatorTaskConfig(std::string name, const boost::property_tree::ptree& config); + ~ReferenceComparatorTaskConfig() = default; + + struct DataGroup { + std::string name; + // QCDB path where the source objects are located + std::string inputPath; + // QCDB path where the reference objects are located + std::string referencePath; + // QCDB path where the output plots are uploaded + std::string outputPath; + // wether to normalize the reference plot with respect to the current one + bool normalizeReference{ false }; + // wether to only draw the current/reference ratio, or the inidividual histograms as well + bool drawRatioOnly{ false }; + // space reserved for the legend above the histograms, in fractions of the pad height + double legendHeight{ 0.2 }; + // ROOT option to be used for drawing 1-D plots ("HIST" by default) + std::string drawOption1D{ "HIST" }; + // ROOT option to be used for drawing 2-D plots ("COLZ" by default) + std::string drawOption2D{ "COLZ" }; + // wether to draw the values axis in log scale (Y axis for 1-D plots and Z axis for 2-D plots) + bool logScale{ false }; + // list of QCDB objects in this group + std::vector objects; + }; + + std::vector dataGroups; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_REFERENCECOMPARATORTASKCONFIG_H diff --git a/Modules/Common/include/Common/TH1Ratio.h b/Modules/Common/include/Common/TH1Ratio.h new file mode 100644 index 0000000000..d6b0d578b5 --- /dev/null +++ b/Modules/Common/include/Common/TH1Ratio.h @@ -0,0 +1,99 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file TH1Ratio.h +/// \brief A generic TH1Ratio inheriting MergeInterface +/// +/// \author Piotr Konopka, piotr.jan.konopka@cern.ch, Andrea Ferrero + +#ifndef QUALITYCONTROL_TH1RATIO_H +#define QUALITYCONTROL_TH1RATIO_H + +#include "Mergers/MergeInterface.h" +#include +#include +#include + +namespace o2::quality_control_modules::common +{ + +template +class TH1Ratio : public T, public o2::mergers::MergeInterface +{ + public: + TH1Ratio(); + TH1Ratio(TH1Ratio const& copymerge); + TH1Ratio(const char* name, const char* title, int nbinsx, double xmin, double xmax, bool uniformScaling = false); + TH1Ratio(const char* name, const char* title, int nbinsx, float* xbins, bool uniformScaling = false); + TH1Ratio(const char* name, const char* title, int nbinsx, double* xbins, bool uniformScaling = false); + TH1Ratio(const char* name, const char* title, bool uniformScaling = false); + + ~TH1Ratio(); + + void init(); + + void merge(MergeInterface* const other) override; + + T* getNum() const + { + return mHistoNum; + } + + T* getDen() const + { + return mHistoDen; + } + + void setHasUniformScaling(bool flag) + { + mUniformScaling = flag; + } + + bool hasUniformScaling() const + { + return mUniformScaling; + } + + // Use Binominal errors; only works if Sumw2 was called + void setHasBinominalErrors(bool flag = true) { mBinominalErrors = flag; } + bool hasBinominalErrors() const { return mBinominalErrors; } + + void update(); + + // functions inherited from TH1x + void Reset(Option_t* option = "") override; + void SetName(const char* name) override; + void SetTitle(const char* title) override; + void Copy(TObject& obj) const override; + Bool_t Add(const TH1* h1, const TH1* h2, Double_t c1 = 1, Double_t c2 = 1) override; + Bool_t Add(const TH1* h1, Double_t c1 = 1) override; + void SetBins(Int_t nx, Double_t xmin, Double_t xmax) override; + void Sumw2(Bool_t flag = kTRUE) override; + + private: + T* mHistoNum{ nullptr }; + T* mHistoDen{ nullptr }; + bool mUniformScaling{ true }; + bool mBinominalErrors{ false }; + Bool_t mSumw2Enabled{ kTRUE }; + std::string mTreatMeAs{ T::Class_Name() }; + + ClassDefOverride(TH1Ratio, 3); +}; + +typedef TH1Ratio TH1FRatio; +typedef TH1Ratio TH1DRatio; + +} // namespace o2::quality_control_modules::common + +#include "Common/TH1Ratio.inl" + +#endif // QUALITYCONTROL_TH1RATIO_H diff --git a/Modules/Common/include/Common/TH1Ratio.inl b/Modules/Common/include/Common/TH1Ratio.inl new file mode 100644 index 0000000000..2ccf515c8a --- /dev/null +++ b/Modules/Common/include/Common/TH1Ratio.inl @@ -0,0 +1,378 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file TH1Ratio.inl +/// \brief An generic TH1Ratio inheriting MergeInterface +/// +/// \author Piotr Konopka, piotr.jan.konopka@cern.ch, Andrea Ferrero + +#include "QualityControl/QcInfoLogger.h" + +namespace o2::quality_control_modules::common +{ + +namespace th1ratio_internal +{ + +template +bool copyBinLabels(const T* srcHist, T* destHist) +{ + if (srcHist->GetXaxis()->GetNbins() != destHist->GetXaxis()->GetNbins()) { + return false; + } + + bool result = false; + if (srcHist->GetXaxis()->GetLabels()) { + for (int bin = 1; bin <= srcHist->GetXaxis()->GetNbins(); bin++) { + destHist->GetXaxis()->SetBinLabel(bin, srcHist->GetXaxis()->GetBinLabel(bin)); + } + result = true; + } + return result; +} + +} + +template +TH1Ratio::TH1Ratio() + : T(), + o2::mergers::MergeInterface(), + mUniformScaling(true) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TDirectory::TContext ctx(nullptr); + mHistoNum = new T("num", "num", 10, 0, 10); + mHistoDen = new T("den", "den", 1, -1, 1); + } + + init(); +} + +template +TH1Ratio::TH1Ratio(TH1Ratio const& copymerge) + : T(), + o2::mergers::MergeInterface(), + mUniformScaling(copymerge.hasUniformScaling()) +{ + // do not add cloned histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, 10, 0, 10); + mHistoDen = new T(nameDen, titleDen, 1, -1, 1); + } + copymerge.Copy(*this); + + init(); +} + +template +TH1Ratio::TH1Ratio(const char* name, const char* title, int nbinsx, double xmin, double xmax, bool uniformScaling) + : T(name, title, nbinsx, xmin, xmax), + o2::mergers::MergeInterface(), + mUniformScaling(uniformScaling) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, nbinsx, xmin, xmax); + if (mUniformScaling) { + mHistoDen = new T(nameDen, titleDen, 1, -1, 1); + } else { + mHistoDen = new T(nameDen, titleDen, nbinsx, xmin, xmax); + } + } + + init(); +} + +template +TH1Ratio::TH1Ratio(const char* name, const char* title, int nbinsx, float* xbins, bool uniformScaling) + : T(name, title, nbinsx, xbins), + o2::mergers::MergeInterface(), + mUniformScaling(uniformScaling) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, nbinsx, xbins); + if (mUniformScaling) { + mHistoDen = new T(nameDen, titleDen, 1, -1, 1); + } else { + mHistoDen = new T(nameDen, titleDen, nbinsx, xbins); + } + } + + init(); +} + +template +TH1Ratio::TH1Ratio(const char* name, const char* title, int nbinsx, double* xbins, bool uniformScaling) + : T(name, title, nbinsx, xbins), + o2::mergers::MergeInterface(), + mUniformScaling(uniformScaling) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, nbinsx, xbins); + if (mUniformScaling) { + mHistoDen = new T(nameDen, titleDen, 1, -1, 1); + } else { + mHistoDen = new T(nameDen, titleDen, nbinsx, xbins); + } + } + + init(); +} + +template +TH1Ratio::TH1Ratio(const char* name, const char* title, bool uniformScaling) + : T(name, title, 10, 0, 10), + o2::mergers::MergeInterface(), + mUniformScaling(uniformScaling) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, 10, 0, 10); + if (mUniformScaling) { + mHistoDen = new T(nameDen, titleDen, 1, -1, 1); + } else { + mHistoDen = new T(nameDen, titleDen, 10, 0, 10); + } + } + + init(); +} + +template +TH1Ratio::~TH1Ratio() +{ + if (mHistoNum) { + delete mHistoNum; + } + + if (mHistoDen) { + delete mHistoDen; + } +} + +template +void TH1Ratio::init() +{ + Sumw2(kTRUE); +} + +template +void TH1Ratio::merge(MergeInterface* const other) +{ + if (!mHistoNum || !mHistoDen) { + return; + } + + mHistoNum->Add(dynamic_cast(other)->getNum()); + mHistoDen->Add(dynamic_cast(other)->getDen()); + update(); +} + +template +void TH1Ratio::update() +{ + if (!mHistoNum || !mHistoDen) { + return; + } + + T::Reset(); + if (mHistoNum->GetXaxis()->IsVariableBinSize()) { + T::GetXaxis()->Set(mHistoNum->GetXaxis()->GetNbins(), mHistoNum->GetXaxis()->GetXbins()->GetArray()); + } else { + T::GetXaxis()->Set(mHistoNum->GetXaxis()->GetNbins(), mHistoNum->GetXaxis()->GetXmin(), mHistoNum->GetXaxis()->GetXmax()); + } + T::SetBinsLength(); + + // Copy bin labels between histograms. + // If set, the bin labels of the ratio histograms are copied to the numerator. + // If instead the numerator has bin labels they are copied to the ratio plot. + if (!th1ratio_internal::copyBinLabels(this, mHistoNum)) { + th1ratio_internal::copyBinLabels(mHistoNum, this); + } + + if (mUniformScaling) { + T::Add(mHistoNum); + double entries = mHistoDen->GetBinContent(1); + double norm = (entries > 0) ? 1.0 / entries : 0; + // make sure the sum-of-weights structure is not initialized if not required + if (mSumw2Enabled == kTRUE) { + T::Scale(norm); + } else { + T::Scale(norm, "nosw2"); + } + } else { + // copy bin labels from numerator to denominator (if needed) before dividing, otherwise we get a warning from ROOT + th1ratio_internal::copyBinLabels(mHistoNum, mHistoDen); + T::Divide(mHistoNum, mHistoDen, 1.0, 1.0, mBinominalErrors ? "B" : ""); + } +} + +template +void TH1Ratio::Reset(Option_t* option) +{ + if (mHistoNum) { + mHistoNum->Reset(option); + } + + if (mHistoDen) { + mHistoDen->Reset(option); + } + + T::Reset(option); +} + +template +void TH1Ratio::SetName(const char* name) +{ + T::SetName(name); + + //setting the names (appending the correct ending) + TString nameNum = name + TString("_num"); + TString nameDen = name + TString("_den"); + mHistoNum->SetName(nameNum); + mHistoDen->SetName(nameDen); +} + +template +void TH1Ratio::SetTitle(const char* title) +{ + T::SetTitle(title); + + //setting the titles (appending the correct ending) + TString titleNum = title + TString("_num"); + TString titleDen = title + TString("_den"); + mHistoNum->SetTitle(titleNum); + mHistoDen->SetTitle(titleDen); +} + +template +void TH1Ratio::Copy(TObject& obj) const +{ + auto dest = dynamic_cast(&obj); + if (!dest) { + return; + } + + dest->setHasUniformScaling(hasUniformScaling()); + + T::Copy(obj); + + if (mHistoNum && dest->getNum() && mHistoDen && dest->getDen()) { + mHistoNum->Copy(*(dest->getNum())); + mHistoDen->Copy(*(dest->getDen())); + dest->update(); + } +} + +template +Bool_t TH1Ratio::Add(const TH1* h1, const TH1* h2, Double_t c1, Double_t c2) +{ + auto m1 = dynamic_cast(h1); + if (!m1) { + return kFALSE; + } + + auto m2 = dynamic_cast(h2); + if (!m2) { + return kFALSE; + } + + if (!getNum()->Add(m1->getNum(), m2->getNum(), c1, c2)) { + return kFALSE; + } + if (!getDen()->Add(m1->getDen(), m2->getDen(), c1, c2)) { + return kFALSE; + } + + update(); + return kTRUE; +} + +template +Bool_t TH1Ratio::Add(const TH1* h1, Double_t c1) +{ + auto m1 = dynamic_cast(h1); + if (!m1) { + return kFALSE; + } + + if (!getNum()->Add(m1->getNum(), c1)) { + return kFALSE; + } + if (!getDen()->Add(m1->getDen(), c1)) { + return kFALSE; + } + + update(); + return kTRUE; +} + +template +void TH1Ratio::SetBins(Int_t nx, Double_t xmin, Double_t xmax) +{ + getNum()->SetBins(nx, xmin, xmax); + getDen()->SetBins(nx, xmin, xmax); + T::SetBins(nx, xmin, xmax); +} + +template +void TH1Ratio::Sumw2(Bool_t flag) +{ + if (!mHistoNum || !mHistoDen) { + return; + } + mSumw2Enabled = flag; + if (!flag || !mHistoNum->GetSumw2N()) { // call only if Sumw2 was not called before or if false flag is passed just to reset structures + mHistoNum->Sumw2(flag); + } + if (!flag || !mHistoDen->GetSumw2N()) { + mHistoDen->Sumw2(flag); + } + if (!flag || !T::GetSumw2N()) { + T::Sumw2(flag); + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/include/Common/TH1Reductor.h b/Modules/Common/include/Common/TH1Reductor.h new file mode 100644 index 0000000000..114d6ccee9 --- /dev/null +++ b/Modules/Common/include/Common/TH1Reductor.h @@ -0,0 +1,48 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1Reductor.h +/// \author Piotr Konopka +/// +#ifndef QUALITYCONTROL_TH1REDUCTOR_H +#define QUALITYCONTROL_TH1REDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief A Reductor which obtains the most popular characteristics of TH1. +/// +/// A Reductor which obtains the most popular characteristics of TH1. +/// It produces a branch in the format: "mean/D:stddev:entries" +class TH1Reductor : public quality_control::postprocessing::ReductorTObject +{ + public: + TH1Reductor() = default; + ~TH1Reductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + Double_t mean; + Double_t stddev; + Double_t entries; + } mStats; +}; + +} // namespace o2::quality_control_modules::common + +#endif //QUALITYCONTROL_TH1REDUCTOR_H diff --git a/Modules/Common/include/Common/TH1SliceReductor.h b/Modules/Common/include/Common/TH1SliceReductor.h new file mode 100644 index 0000000000..d40b286521 --- /dev/null +++ b/Modules/Common/include/Common/TH1SliceReductor.h @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1SliceReductor.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TH1SLICEREDUCTOR_H +#define QUALITYCONTROL_TH1SLICEREDUCTOR_H + +#include "QualityControl/SliceInfoTrending.h" +#include "QualityControl/SliceReductor.h" +#include "TH1.h" + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::common +{ +/// \brief A reductor of TH1 histograms for the extended trending of objects. +/// +/// A Reductor for the TH1 histograms. It receives +/// a vector of 'SliceInfo' which size corresponds to the number of slices or +/// pads which need to be trended. +/// + +class TH1SliceReductor : public quality_control::postprocessing::SliceReductor +{ + public: + /// \brief Constructor. + TH1SliceReductor() = default; + /// \brief Destructor. + ~TH1SliceReductor() = default; + + /// \brief Methods from the extended reductor class. + void update(TObject* obj, std::vector& reducedSource, + std::vector>& axis, int& finalNumberPads) override; + + private: + void GetTH1StatsY(TH1* hist, float stats[3], const int lowerBin, const int upperBin); +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_TH1SLICEREDUCTOR_H diff --git a/Modules/Common/include/Common/TH2Ratio.h b/Modules/Common/include/Common/TH2Ratio.h new file mode 100644 index 0000000000..3c79027bcd --- /dev/null +++ b/Modules/Common/include/Common/TH2Ratio.h @@ -0,0 +1,101 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file TH2Ratio.h +/// \brief A generic TH2Ratio inheriting MergeInterface +/// +/// \author Piotr Konopka, piotr.jan.konopka@cern.ch, Sebastien Perrin, Andrea Ferrero + +#ifndef QUALITYCONTROL_TH2RATIO_H +#define QUALITYCONTROL_TH2RATIO_H + +#include "Mergers/MergeInterface.h" +#include +#include +#include + +namespace o2::quality_control_modules::common +{ + +template +class TH2Ratio : public T, public o2::mergers::MergeInterface +{ + public: + TH2Ratio(); + TH2Ratio(TH2Ratio const& copymerge); + TH2Ratio(const char* name, const char* title, int nbinsx, double xmin, double xmax, int nbinsy, double ymin, double ymax, bool uniformScaling = false); + TH2Ratio(const char* name, const char* title, int nbinsx, const float* xbins, int nbinsy, const float* ybins, bool uniformScaling = false); + TH2Ratio(const char* name, const char* title, int nbinsx, const double* xbins, int nbinsy, const double* ybins, bool uniformScaling = false); + TH2Ratio(const char* name, const char* title, int nbinsx, const double* xbins, int nbinsy, double ymin, double ymax, bool uniformScaling = false); + TH2Ratio(const char* name, const char* title, int nbinsx, double xmin, double xmax, int nbinsy, const double* ybins, bool uniformScaling = false); + TH2Ratio(const char* name, const char* title, bool uniformScaling = false); + + ~TH2Ratio(); + + void init(); + + void merge(MergeInterface* const other) override; + + T* getNum() const + { + return mHistoNum; + } + + T* getDen() const + { + return mHistoDen; + } + + void setHasUniformScaling(bool flag) + { + mUniformScaling = flag; + } + + bool hasUniformScaling() const + { + return mUniformScaling; + } + + // Use Binominal errors; only works if Sumw2 was called + void setHasBinominalErrors(bool flag = true) { mBinominalErrors = flag; } + bool hasBinominalErrors() const { return mBinominalErrors; } + + void update(); + + // functions inherited from TH2x + void Reset(Option_t* option = "") override; + void SetName(const char* name) override; + void SetTitle(const char* title) override; + void Copy(TObject& obj) const override; + Bool_t Add(const TH1* h1, const TH1* h2, Double_t c1 = 1, Double_t c2 = 1) override; + Bool_t Add(const TH1* h1, Double_t c1 = 1) override; + void SetBins(Int_t nx, Double_t xmin, Double_t xmax, Int_t ny, Double_t ymin, Double_t ymax) override; + void Sumw2(Bool_t flag = kTRUE) override; + + private: + T* mHistoNum{ nullptr }; + T* mHistoDen{ nullptr }; + bool mUniformScaling{ true }; + bool mBinominalErrors{ false }; + Bool_t mSumw2Enabled{ kTRUE }; + std::string mTreatMeAs{ T::Class_Name() }; + + ClassDefOverride(TH2Ratio, 3); +}; + +typedef TH2Ratio TH2FRatio; +typedef TH2Ratio TH2DRatio; + +} // namespace o2::quality_control_modules::common + +#include "Common/TH2Ratio.inl" + +#endif // QUALITYCONTROL_TH2RATIO_H diff --git a/Modules/Common/include/Common/TH2Ratio.inl b/Modules/Common/include/Common/TH2Ratio.inl new file mode 100644 index 0000000000..2d5226cde7 --- /dev/null +++ b/Modules/Common/include/Common/TH2Ratio.inl @@ -0,0 +1,438 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file TH2Ratio.inl +/// \brief An generic TH2Ratio inheriting MergeInterface +/// +/// \author Piotr Konopka, piotr.jan.konopka@cern.ch, Andrea Ferrero + +#include "QualityControl/QcInfoLogger.h" + +namespace o2::quality_control_modules::common +{ + +namespace th2ratio_internal +{ + +template +bool copyBinLabels(const T* srcHist, T* destHist) +{ + if (srcHist->GetXaxis()->GetNbins() != destHist->GetXaxis()->GetNbins()) { + return false; + } + if (srcHist->GetYaxis()->GetNbins() != destHist->GetYaxis()->GetNbins()) { + return false; + } + + bool result = false; + if (srcHist->GetXaxis()->GetLabels()) { + for (int bin = 1; bin <= srcHist->GetXaxis()->GetNbins(); bin++) { + destHist->GetXaxis()->SetBinLabel(bin, srcHist->GetXaxis()->GetBinLabel(bin)); + } + result = true; + } + if (srcHist->GetYaxis()->GetLabels()) { + for (int bin = 1; bin <= srcHist->GetYaxis()->GetNbins(); bin++) { + destHist->GetYaxis()->SetBinLabel(bin, srcHist->GetYaxis()->GetBinLabel(bin)); + } + result = true; + } + return result; +} + +} + +template +TH2Ratio::TH2Ratio() + : T(), + o2::mergers::MergeInterface(), + mUniformScaling(true) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TDirectory::TContext ctx(nullptr); + mHistoNum = new T("num", "num", 10, 0, 10, 10, 0, 10); + mHistoDen = new T("den", "den", 1, -1, 1, 1, -1, 1); + } + + init(); +} + +template +TH2Ratio::TH2Ratio(TH2Ratio const& copymerge) + : T(), + o2::mergers::MergeInterface(), + mUniformScaling(copymerge.hasUniformScaling()) +{ + // do not add cloned histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, 10, 0, 10, 10, 0, 10); + mHistoDen = new T(nameDen, titleDen, 1, -1, 1, 1, -1, 1); + } + copymerge.Copy(*this); + + init(); +} + +template +TH2Ratio::TH2Ratio(const char* name, const char* title, int nbinsx, double xmin, double xmax, int nbinsy, double ymin, double ymax, bool uniformScaling) + : T(name, title, nbinsx, xmin, xmax, nbinsy, ymin, ymax), + o2::mergers::MergeInterface(), + mUniformScaling(uniformScaling) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, nbinsx, xmin, xmax, nbinsy, ymin, ymax); + if (mUniformScaling) { + mHistoDen = new T(nameDen, titleDen, 1, -1, 1, 1, -1, 1); + } else { + mHistoDen = new T(nameDen, titleDen, nbinsx, xmin, xmax, nbinsy, ymin, ymax); + } + } + + init(); +} + +template +TH2Ratio::TH2Ratio(const char* name, const char* title, int nbinsx, const float *xbins, int nbinsy, const float *ybins, bool uniformScaling) + : T(name, title, nbinsx, xbins, nbinsy, ybins), + o2::mergers::MergeInterface(), + mUniformScaling(uniformScaling) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, nbinsx, xbins, nbinsy, ybins); + if (mUniformScaling) { + mHistoDen = new T(nameDen, titleDen, 1, -1, 1, 1, -1, 1); + } else { + mHistoDen = new T(nameDen, titleDen, nbinsx, xbins, nbinsy, ybins); + } + } + + init(); +} + +template +TH2Ratio::TH2Ratio(const char* name, const char* title, int nbinsx, const double *xbins, int nbinsy, const double *ybins, bool uniformScaling) + : T(name, title, nbinsx, xbins, nbinsy, ybins), + o2::mergers::MergeInterface(), + mUniformScaling(uniformScaling) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, nbinsx, xbins, nbinsy, ybins); + if (mUniformScaling) { + mHistoDen = new T(nameDen, titleDen, 1, -1, 1, 1, -1, 1); + } else { + mHistoDen = new T(nameDen, titleDen, nbinsx, xbins, nbinsy, ybins); + } + } + + init(); +} + +template +TH2Ratio::TH2Ratio(const char* name, const char* title, int nbinsx, const double *xbins, int nbinsy, double ymin, double ymax, bool uniformScaling) + : T(name, title, nbinsx, xbins, nbinsy, ymin, ymax), + o2::mergers::MergeInterface(), + mUniformScaling(uniformScaling) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, nbinsx, xbins, nbinsy, ymin, ymax); + if (mUniformScaling) { + mHistoDen = new T(nameDen, titleDen, 1, -1, 1, 1, -1, 1); + } else { + mHistoDen = new T(nameDen, titleDen, nbinsx, xbins, nbinsy, ymin, ymax); + } + } + + init(); +} + +template +TH2Ratio::TH2Ratio(const char* name, const char* title, int nbinsx, double xmin, double xmax, int nbinsy, const double *ybins, bool uniformScaling) + : T(name, title, nbinsx, xmin, xmax, nbinsy, ybins), + o2::mergers::MergeInterface(), + mUniformScaling(uniformScaling) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, nbinsx, xmin, xmax, nbinsy, ybins); + if (mUniformScaling) { + mHistoDen = new T(nameDen, titleDen, 1, -1, 1, 1, -1, 1); + } else { + mHistoDen = new T(nameDen, titleDen, nbinsx, xmin, xmax, nbinsy, ybins); + } + } + + init(); +} + +template +TH2Ratio::TH2Ratio(const char* name, const char* title, bool uniformScaling) + : T(name, title, 10, 0, 10, 10, 0, 10), + o2::mergers::MergeInterface(), + mUniformScaling(uniformScaling) +{ + // do not add created histograms to gDirectory + // see https://root.cern.ch/doc/master/TEfficiency_8cxx.html + { + TString nameNum = T::GetName() + TString("_num"); + TString nameDen = T::GetName() + TString("_den"); + TString titleNum = T::GetTitle() + TString(" num"); + TString titleDen = T::GetTitle() + TString(" den"); + TDirectory::TContext ctx(nullptr); + mHistoNum = new T(nameNum, titleNum, 10, 0, 10, 10, 0, 10); + if (mUniformScaling) { + mHistoDen = new T(nameDen, titleDen, 1, -1, 1, 1, -1, 1); + } else { + mHistoDen = new T(nameDen, titleDen, 10, 0, 10, 10, 0, 10); + } + } + + init(); +} + +template +TH2Ratio::~TH2Ratio() +{ + if (mHistoNum) { + delete mHistoNum; + } + + if (mHistoDen) { + delete mHistoDen; + } +} + +template +void TH2Ratio::init() +{ + Sumw2(kTRUE); +} + +template +void TH2Ratio::merge(MergeInterface* const other) +{ + if (!mHistoNum || !mHistoDen) { + return; + } + + mHistoNum->Add(dynamic_cast(other)->getNum()); + mHistoDen->Add(dynamic_cast(other)->getDen()); + update(); +} + +template +void TH2Ratio::update() +{ + if (!mHistoNum || !mHistoDen) { + return; + } + + T::Reset(); + if (mHistoNum->GetXaxis()->IsVariableBinSize()) { + T::GetXaxis()->Set(mHistoNum->GetXaxis()->GetNbins(), mHistoNum->GetXaxis()->GetXbins()->GetArray()); + } else { + T::GetXaxis()->Set(mHistoNum->GetXaxis()->GetNbins(), mHistoNum->GetXaxis()->GetXmin(), mHistoNum->GetXaxis()->GetXmax()); + } + if (mHistoNum->GetYaxis()->IsVariableBinSize()) { + T::GetYaxis()->Set(mHistoNum->GetYaxis()->GetNbins(), mHistoNum->GetYaxis()->GetXbins()->GetArray()); + } else { + T::GetYaxis()->Set(mHistoNum->GetYaxis()->GetNbins(), mHistoNum->GetYaxis()->GetXmin(), mHistoNum->GetYaxis()->GetXmax()); + } + T::SetBinsLength(); + + // Copy bin labels between histograms. + // If set, the bin labels of the ratio histograms are copied to the numerator. + // If instead the numerator has bin labels they are copied to the ratio plot. + if (!th2ratio_internal::copyBinLabels(this, mHistoNum)) { + th2ratio_internal::copyBinLabels(mHistoNum, this); + } + + if (mUniformScaling) { + T::Add(mHistoNum); + double entries = mHistoDen->GetBinContent(1, 1); + double norm = (entries > 0) ? 1.0 / entries : 0; + // make sure the sum-of-weights structure is not initialized if not required + if (mSumw2Enabled == kTRUE) { + T::Scale(norm); + } else { + T::Scale(norm, "nosw2"); + } + } else { + // copy bin labels from numerator to denominator (if needed) before dividing, otherwise we get a warning from ROOT + th2ratio_internal::copyBinLabels(mHistoNum, mHistoDen); + T::Divide(mHistoNum, mHistoDen, 1.0, 1.0, mBinominalErrors ? "B" : ""); + } +} + +template +void TH2Ratio::Reset(Option_t* option) +{ + if (mHistoNum) { + mHistoNum->Reset(option); + } + + if (mHistoDen) { + mHistoDen->Reset(option); + } + + T::Reset(option); +} + +template +void TH2Ratio::SetName(const char* name) +{ + T::SetName(name); + + //setting the names (appending the correct ending) + TString nameNum = name + TString("_num"); + TString nameDen = name + TString("_den"); + mHistoNum->SetName(nameNum); + mHistoDen->SetName(nameDen); +} + +template +void TH2Ratio::SetTitle(const char* title) +{ + T::SetTitle(title); + + //setting the titles (appending the correct ending) + TString titleNum = title + TString("_num"); + TString titleDen = title + TString("_den"); + mHistoNum->SetTitle(titleNum); + mHistoDen->SetTitle(titleDen); +} + +template +void TH2Ratio::Copy(TObject& obj) const +{ + auto dest = dynamic_cast(&obj); + if (!dest) { + return; + } + + dest->setHasUniformScaling(hasUniformScaling()); + + T::Copy(obj); + + if (mHistoNum && dest->getNum() && mHistoDen && dest->getDen()) { + mHistoNum->Copy(*(dest->getNum())); + mHistoDen->Copy(*(dest->getDen())); + dest->update(); + } +} + +template +Bool_t TH2Ratio::Add(const TH1* h1, const TH1* h2, Double_t c1, Double_t c2) +{ + auto m1 = dynamic_cast(h1); + if (!m1) { + return kFALSE; + } + + auto m2 = dynamic_cast(h2); + if (!m2) { + return kFALSE; + } + + if (!getNum()->Add(m1->getNum(), m2->getNum(), c1, c2)) { + return kFALSE; + } + if (!getDen()->Add(m1->getDen(), m2->getDen(), c1, c2)) { + return kFALSE; + } + + update(); + return kTRUE; +} + +template +Bool_t TH2Ratio::Add(const TH1* h1, Double_t c1) +{ + auto m1 = dynamic_cast(h1); + if (!m1) { + return kFALSE; + } + + if (!getNum()->Add(m1->getNum(), c1)) { + return kFALSE; + } + if (!getDen()->Add(m1->getDen(), c1)) { + return kFALSE; + } + + update(); + return kTRUE; +} + +template +void TH2Ratio::SetBins(Int_t nx, Double_t xmin, Double_t xmax, + Int_t ny, Double_t ymin, Double_t ymax) +{ + getNum()->SetBins(nx, xmin, xmax, ny, ymin, ymax); + getDen()->SetBins(nx, xmin, xmax, ny, ymin, ymax); + T::SetBins(nx, xmin, xmax, ny, ymin, ymax); +} + +template +void TH2Ratio::Sumw2(Bool_t flag) +{ + if (!mHistoNum || !mHistoDen) { + return; + } + + mSumw2Enabled = flag; + mHistoNum->Sumw2(flag); + mHistoDen->Sumw2(flag); + T::Sumw2(flag); +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/include/Common/TH2Reductor.h b/Modules/Common/include/Common/TH2Reductor.h new file mode 100644 index 0000000000..3385d383ed --- /dev/null +++ b/Modules/Common/include/Common/TH2Reductor.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2Reductor.h +/// \author Piotr Konopka +/// +#ifndef QUALITYCONTROL_TH2REDUCTOR_H +#define QUALITYCONTROL_TH2REDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief A Reductor which obtains the most popular characteristics of TH2. +/// +/// A Reductor which obtains the most popular characteristics of TH2. +/// It produces a branch in the format: "sumw/D:sumw2:sumwx:sumwx2:sumwy:sumwy2:sumwxy:entries" +class TH2Reductor : public quality_control::postprocessing::ReductorTObject +{ + public: + TH2Reductor() = default; + ~TH2Reductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + union { + struct { + Double_t sumw; + Double_t sumw2; + Double_t sumwx; + Double_t sumwx2; + Double_t sumwy; + Double_t sumwy2; + Double_t sumwxy; + } named; + Double_t array[7]; + } sums; + Double_t entries; // is sumw == entries always? maybe not for values which land into the edge bins? + } mStats; +}; + +} // namespace o2::quality_control_modules::common + +#endif //QUALITYCONTROL_TH2REDUCTOR_H diff --git a/Modules/Common/include/Common/TH2SliceReductor.h b/Modules/Common/include/Common/TH2SliceReductor.h new file mode 100644 index 0000000000..f683dbd6a1 --- /dev/null +++ b/Modules/Common/include/Common/TH2SliceReductor.h @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2SliceReductor.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TH2SLICEREDUCTOR_H +#define QUALITYCONTROL_TH2SLICEREDUCTOR_H + +#include "QualityControl/SliceReductor.h" +#include "QualityControl/SliceInfoTrending.h" + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::common +{ +/// \brief A reductor of TH2 histograms for the extended trending of objects. +/// +/// A Reductor for the TH2 histograms. It receives +/// a vector of 'SliceInfo' which size corresponds to the number of slices or +/// pads which need to be trended. +/// + +class TH2SliceReductor : public quality_control::postprocessing::SliceReductor +{ + public: + /// \brief Constructor. + TH2SliceReductor() = default; + /// \brief Destructor. + ~TH2SliceReductor() = default; + + /// \brief Methods from the extended reductor class. + void update(TObject* obj, std::vector& reducedSource, + std::vector>& axis, int& finalNumberPads) override; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QUALITYCONTROL_TH2SLICEREDUCTOR_H diff --git a/Modules/Common/include/Common/THnSparse5Reductor.h b/Modules/Common/include/Common/THnSparse5Reductor.h new file mode 100644 index 0000000000..9d1dc90583 --- /dev/null +++ b/Modules/Common/include/Common/THnSparse5Reductor.h @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file THnSparse5Reductor.h +/// \author Ivan Ravasenga on the model from Piotr Konopka +/// +#ifndef QUALITYCONTROL_THNSPARSE5REDUCTOR_H +#define QUALITYCONTROL_THNSPARSE5REDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief A Reductor which obtains the most popular characteristics of THnSparse up to 5 dimensions. +/// +/// A Reductor which obtains the most popular characteristics of THnSparse up to 5 dimensions. +/// It produces a branch in the format: "mean[NDIM]/D:stddev[NDIM]:entries[NDIM] where NDIM=5" +class THnSparse5Reductor : public quality_control::postprocessing::ReductorTObject +{ + public: + THnSparse5Reductor() = default; + ~THnSparse5Reductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + static constexpr int NDIM = 5; + struct { + Double_t mean[NDIM]; // mean of each axis (up to 5 axes) + Double_t stddev[NDIM]; // stddev of each axis (up to 5 axes) + Double_t entries[NDIM]; + } mStats; +}; + +} // namespace o2::quality_control_modules::common + +#endif //QUALITYCONTROL_THNSPARSE5REDUCTOR_H diff --git a/Modules/Common/include/Common/TrendCheck.h b/Modules/Common/include/Common/TrendCheck.h new file mode 100644 index 0000000000..8edf62a622 --- /dev/null +++ b/Modules/Common/include/Common/TrendCheck.h @@ -0,0 +1,81 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendCheck.h +/// \author Andrea Ferrero +/// \brief Generic checker for trending graphs +/// + +#ifndef QC_MODULE_COMMON_TRENDCHECK_H +#define QC_MODULE_COMMON_TRENDCHECK_H + +#include "QualityControl/CheckInterface.h" + +#include +#include +#include + +class TGraph; +class TObject; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +class CheckerThresholdsConfig; + +/// \brief Generic checker for trending graphs +/// +/// \author Andrea Ferrero +class TrendCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + TrendCheck() = default; + /// Destructor + ~TrendCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + ClassDefOverride(TrendCheck, 1); + + private: + enum ThresholdsMode { + ExpectedRange, + DeviationFromMean, + StdDeviation + }; + + std::array>, 2> getThresholds(std::string key, TGraph* graph); + void getGraphsFromObject(TObject* object, std::vector& graphs); + double getInteractionRate(); + + Activity mActivity; + ThresholdsMode mTrendCheckMode{ ExpectedRange }; + int mNPointsForAverage{ 0 }; + std::pair mQualityLabelPosition{ 0.12, 0.8 }; + std::pair mQualityLabelSize{ 0.5, 0.07 }; + std::shared_ptr mThresholds; + std::unordered_map>>> mAverageTrend; + std::unordered_map>>> mThresholdsTrendBad; + std::unordered_map>>> mThresholdsTrendMedium; + std::unordered_map mQualities; +}; + +} // namespace o2::quality_control_modules::common + +#endif // QC_MODULE_COMMON_TRENDCHECK_H diff --git a/Modules/Common/include/Common/Utils.h b/Modules/Common/include/Common/Utils.h new file mode 100644 index 0000000000..bc8728af34 --- /dev/null +++ b/Modules/Common/include/Common/Utils.h @@ -0,0 +1,115 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Utils.h +/// + +#ifndef QC_MODULE_COMMON_UTILS_H +#define QC_MODULE_COMMON_UTILS_H + +#include + +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/CustomParameters.h" + +namespace o2::quality_control_modules::common +{ + +namespace internal +{ +/// \brief Parses a string to a type +/// \param params String to be cast +/// \return Returns requested type from param +template +T stringToType(const std::string& param) +{ + T retVal{}; + if constexpr (std::is_same_v) { + retVal = std::stoi(param); + } else if constexpr (std::is_same_v) { + retVal = std::stol(param); + } else if constexpr (std::is_same_v) { + retVal = std::stoll(param); + } else if constexpr (std::is_same_v) { + retVal = static_cast(std::stoul(param)); + } else if constexpr (std::is_same_v) { + retVal = std::stoul(param); + } else if constexpr (std::is_same_v) { + retVal = std::stoull(param); + } else if constexpr (std::is_same_v) { + retVal = param; + } else if constexpr (std::is_same_v) { + retVal = std::stof(param); + } else if constexpr (std::is_same_v) { + retVal = std::stod(param); + } else if constexpr (std::is_same_v) { + retVal = std::stold(param); + } else if constexpr (std::is_same_v) { + if ((param == "true") || (param == "True") || (param == "TRUE") || (param == "1")) { + retVal = true; + } else if ((param == "false") || (param == "False") || (param == "FALSE") || (param == "0")) { + retVal = false; + } else { + ILOG(Fatal) << "Cannot decode boolean value from param '" << param << "'" << ENDM; + } + } else { + static_assert(false, "Unsupported type!"); + } + return retVal; +} +} // namespace internal + +/// \brief Gets a task parameter from the config file +/// Convenience function to return a value for a taskParameter given in the config file +/// \param params mCustomParameters from within the QualityControl/TaskInterface +/// \param name Name of the taskParameter +/// \return taskParameter converted to bool, int, float, double or std::string depending on the template type +template +T getFromConfig(const quality_control::core::CustomParameters& params, const std::string_view name, T retVal = T{}) +{ + const auto itParam = params.find(name.data()); + if (itParam == params.end()) { + ILOG(Info, Trace) << "Default parameter - " << name << ": " << retVal << ENDM; + } else { + const auto& param = itParam->second; + retVal = internal::stringToType(param); + ILOG(Info, Trace) << "Custom parameter - " << name << ": " << retVal << ENDM; + } + return retVal; +} + +/// \brief Gets a extended task parameter from the config file +/// Convenience function to return a value for a extended taskParameter given in the config file +/// \param activity mActivity from within the QualityControl/TaskInterface +/// \param params mCustomParameters from within the QualityControl/TaskInterface +/// \param name Name of the taskParameter +/// \param retVal Default value if not specified in config, must be stringyfiable +/// \return taskParameter converted to bool, int, float, double or std::string depending on the template type +template +T getFromExtendedConfig(const quality_control::core::Activity& activity, const quality_control::core::CustomParameters& params, const std::string& name, T retVal = T{}) +{ + std::string parameter; + if (auto param = params.atOptional(name, activity)) { + parameter = param.value(); + } else { + if constexpr (std::is_same_v) { + parameter = params.atOrDefaultValue(name, retVal); + } else { + parameter = params.atOrDefaultValue(name, std::to_string(retVal)); + } + } + return internal::stringToType(parameter); +} + +} // namespace o2::quality_control_modules::common + +#endif // QC_MODULE_COMMON_UTILS_H diff --git a/Modules/Common/include/Common/WorstOfAllAggregator.h b/Modules/Common/include/Common/WorstOfAllAggregator.h new file mode 100644 index 0000000000..cb4471f422 --- /dev/null +++ b/Modules/Common/include/Common/WorstOfAllAggregator.h @@ -0,0 +1,43 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file WorstOfAllAggregator.h +/// \author Piotr Konopka +/// + +#ifndef QUALITYCONTROL_WORSTOFALLAGGREGATOR_H +#define QUALITYCONTROL_WORSTOFALLAGGREGATOR_H + +// ROOT +#include +// QC +#include "QualityControl/AggregatorInterface.h" + +namespace o2::quality_control_modules::common +{ + +/// \brief Aggregator which selects the worst Quality and adds all FlagTypes +/// \author Piotr Konopka +class WorstOfAllAggregator : public o2::quality_control::checker::AggregatorInterface +{ + public: + // Override interface + void configure() override; + std::map + aggregate(o2::quality_control::core::QualityObjectsMapType& qoMap) override; + + ClassDefOverride(WorstOfAllAggregator, 1); +}; + +} // namespace o2::quality_control_modules::common + +#endif //QUALITYCONTROL_WORSTOFALLAGGREGATOR_H diff --git a/Modules/Common/src/BigScreen.cxx b/Modules/Common/src/BigScreen.cxx new file mode 100644 index 0000000000..2dbf7210fc --- /dev/null +++ b/Modules/Common/src/BigScreen.cxx @@ -0,0 +1,154 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file BigScreen.h +/// \author Andrea Ferrero +/// \brief Quality post-processing task that generates a canvas showing the aggregated quality of each system +/// + +#include "Common/BigScreen.h" +#include "Common/Utils.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/ActivityHelpers.h" +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::common +{ + +void BigScreen::configure(const boost::property_tree::ptree& config) +{ + mConfig = BigScreenConfig(getID(), config); +} + +//_________________________________________________________________________________________ + +std::string getParameter(o2::quality_control::core::CustomParameters customParameters, + std::string parName, + const o2::quality_control::core::Activity& activity) +{ + std::string parValue; + auto parOpt = customParameters.atOptional(parName, activity); + if (parOpt.has_value()) { + parValue = parOpt.value(); + } else { + parOpt = customParameters.atOptional(parName); + if (parOpt.has_value()) { + parValue = parOpt.value(); + } + } + return parValue; +} + +void BigScreen::initialize(quality_control::postprocessing::Trigger t, framework::ServiceRegistryRef services) +{ + int nRows = getFromExtendedConfig(t.activity, mCustomParameters, "nRows", 1); + int nCols = getFromExtendedConfig(t.activity, mCustomParameters, "nCols", 1); + int borderWidth = getFromExtendedConfig(t.activity, mCustomParameters, "borderWidth", 5); + int foregroundColor = getFromExtendedConfig(t.activity, mCustomParameters, "foregroundColor", 1); + int backgroundColor = getFromExtendedConfig(t.activity, mCustomParameters, "backgroundColor", 0); + + mMaxObjectTimeShift = getFromExtendedConfig(t.activity, mCustomParameters, "maxObjectTimeShift", mMaxObjectTimeShift); + mIgnoreActivity = getFromExtendedConfig(t.activity, mCustomParameters, "ignoreActivity", mIgnoreActivity); + + auto labels = o2::utils::Str::tokenize(getFromExtendedConfig(t.activity, mCustomParameters, "labels"), ',', false, false); + if (labels.size() > (nRows * nCols)) { + ILOG(Warning, Support) << "Number of labels larger than nRows*nCols, some labels will not be displayed correctly" << ENDM; + } + + mCanvas.reset(); + mCanvas = std::make_unique("BigScreen", "QC Big Screen", nRows, nCols, borderWidth, foregroundColor, backgroundColor); + + int index = 0; + // add the paves associated to each quality source + for (auto label : labels) { + if (!label.empty()) { + mCanvas->addBox(label, index); + } + index += 1; + } + + getObjectsManager()->startPublishing(mCanvas.get(), PublicationPolicy::ThroughStop); +} + +//_________________________________________________________________________________________ +// Helper function for retrieving a QualityObject from the QCDB, in the form of a std::pair, bool> +// A non-null QO is returned in the first element of the pair if the QO is found in the QCDB +// The second element of the pair is set to true if the QO has a time stamp more recent than a user-supplied threshold + +static std::pair, bool> getQO(repository::DatabaseInterface& qcdb, Trigger t, BigScreenConfig::DataSource& source, long notOlderThan, bool ignoreActivity) +{ + // find the time-stamp of the most recent object matching the current activity + // if ignoreActivity is true the activity matching criteria are not applied + Activity activity = ignoreActivity ? Activity{} : t.activity; + auto timestamp = t.timestamp; + const auto objFullPath = t.activity.mProvenance + "/" + source.path; + const auto filterMetadata = activity_helpers::asDatabaseMetadata(activity, false); + const auto objectValidity = qcdb.getLatestObjectValidity(objFullPath, filterMetadata); + if (objectValidity.isValid()) { + timestamp = objectValidity.getMax() - 1; + } else { + ILOG(Info, Support) << "Could not find an object '" << objFullPath << "' for activity " << activity << ENDM; + return { nullptr, false }; + } + + // retrieve QO from CCDB - do not associate to trigger activity if ignoreActivity is true + auto qo = qcdb.retrieveQO(source.path, timestamp, activity); + if (!qo) { + return { nullptr, false }; + } + + long elapsed = static_cast(t.timestamp) - timestamp; + // check if the object is not older than a given number of milliseconds + if (elapsed > notOlderThan) { + return { qo, false }; + } + + return { qo, true }; +} + +//_________________________________________________________________________________________ + +void BigScreen::update(quality_control::postprocessing::Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + for (auto source : mConfig.dataSources) { + // retrieve QO from CCDB, in the form of a std::pair, bool> + // a valid object is returned in the first element of the pair if the QO is found in the QCDB + // the second element of the pair is set to true if the QO has a time stamp not older than the provided threshold + // long notOlderThanMs = (mConfig.mNotOlderThan >= 0) ? static_cast(mConfig.mNotOlderThan) * 1000 : std::numeric_limits::max(); + auto qo = getQO(qcdb, t, source, mMaxObjectTimeShift * 1000, mIgnoreActivity); + if (qo.first) { + if (qo.second) { + mCanvas->setQuality(source.detector, qo.first->getQuality()); + } else { + mCanvas->setText(source.detector, kYellow, "Old"); + } + } else { + mCanvas->setText(source.detector, kGray, "NF"); + } + } + mCanvas->update(); +} + +//_________________________________________________________________________________________ + +void BigScreen::finalize(quality_control::postprocessing::Trigger t, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/BigScreenCanvas.cxx b/Modules/Common/src/BigScreenCanvas.cxx new file mode 100644 index 0000000000..75d551e1d0 --- /dev/null +++ b/Modules/Common/src/BigScreenCanvas.cxx @@ -0,0 +1,123 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file BigScreenCanvas.cxx +/// \author Andrea Ferrero +/// \brief Canvas showing the aggregated quality of configurable groups +/// + +#include "Common/BigScreenCanvas.h" +#include +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +struct BigScreenElement { + BigScreenElement(std::string name, int index, float padding, float labelOffset, int borderWidth, + int foregroundColor) + : mLabel(padding, 1.0 - padding, name.c_str()), + mPave(padding, padding, 1.0 - padding, 1.0 - padding - labelOffset, "NB NDC"), + mBox(padding, padding, 1.0 - padding, 1.0 - padding - labelOffset), + mPadIndex(index + 1) + { + mLabel.SetNDC(); + mLabel.SetTextAlign(11); + mLabel.SetTextSize(0.2); + mLabel.SetTextFont(42); + mLabel.SetTextColor(foregroundColor); + + mPave.SetBorderSize(0); + mPave.SetFillColor(kWhite); + + mBox.SetLineColor(kBlack); + mBox.SetLineWidth(borderWidth); + mBox.SetFillStyle(0); + } + + void DrawInCanvas(TPad* c) + { + c->cd(mPadIndex); + mPave.Draw(); + mBox.Draw(); + mLabel.Draw(); + } + + TText mLabel; + TPaveText mPave; + TBox mBox; + int mPadIndex; +}; + +BigScreenCanvas::BigScreenCanvas(std::string name, std::string title, int nRows, int nCols, int borderWidth, + int foregroundColor, int backgroundColor) + : TCanvas(name.c_str(), title.c_str(), 800, 600), mNRows(nRows), mNCols(nCols), mBorderWidth(borderWidth), mForegroundColor(foregroundColor), mBackgroundColor(backgroundColor) +{ + mColors[Quality::Null.getName()] = kViolet - 6; + mColors[Quality::Bad.getName()] = kRed; + mColors[Quality::Medium.getName()] = kOrange - 3; + mColors[Quality::Good.getName()] = kGreen + 2; + + // TPad filling the whole canvas and used to draw the background color + mBackgoundPad = std::make_shared((name + "_pad").c_str(), (title + "_pad").c_str(), 0, 0, 1, 1); + mBackgoundPad->SetBorderSize(0); + mBackgoundPad->SetBorderMode(0); + mBackgoundPad->SetMargin(0, 0, 0, 0); + mBackgoundPad->SetFillColor(mBackgroundColor); + mBackgoundPad->Draw(); +} + +void BigScreenCanvas::addBox(std::string boxName, int index) +{ + mBoxes[boxName] = std::make_shared(boxName, index, mPadding, mLabelOffset, mBorderWidth, mForegroundColor); +} + +void BigScreenCanvas::setText(std::string boxName, int color, std::string text) +{ + auto index = mBoxes.find(boxName); + if (index == mBoxes.end()) { + return; + } + auto& pave = index->second->mPave; + pave.Clear(); + pave.SetFillColor(color); + pave.AddText((std::string(" ") + text + std::string(" ")).c_str()); +} + +void BigScreenCanvas::setQuality(std::string boxName, Quality quality) +{ + auto color = mColors.find(quality.getName()); + if (color != mColors.end()) { + setText(boxName, color->second, quality.getName()); + } +} + +void BigScreenCanvas::update() +{ + mBackgoundPad->Clear(); + mBackgoundPad->Divide(mNCols, mNRows, 0, 0); + + // set the sub-pads as fully transparent to show the color of the main backgound pad + for (int padIndex = 1; padIndex <= (mNCols * mNRows); padIndex++) { + mBackgoundPad->cd(padIndex); + gPad->SetFillStyle(4000); + } + + for (auto& [key, box] : mBoxes) { + box->DrawInCanvas(mBackgoundPad.get()); + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/BigScreenConfig.cxx b/Modules/Common/src/BigScreenConfig.cxx new file mode 100644 index 0000000000..16b176c221 --- /dev/null +++ b/Modules/Common/src/BigScreenConfig.cxx @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file BigScreenConfig.cxx +/// \author Andrea Ferrero +/// + +#include "Common/BigScreenConfig.h" +#include +#include + +namespace o2::quality_control_modules::common +{ + +BigScreenConfig::BigScreenConfig(std::string name, const boost::property_tree::ptree& config) + : PostProcessingConfig(name, config) +{ + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".dataSources")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + auto tokens = o2::utils::Str::tokenize(sourceName.second.data(), ':', false, true); + if (tokens.size() != 2 || tokens[0].empty()) { + continue; + } + const auto& sourceDet = tokens[0]; + const auto& sourcePath = tokens[1]; + dataSources.push_back({ sourceDet, sourcePath }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + auto sourceName = dataSourceConfig.second.get("name"); + auto tokens = o2::utils::Str::tokenize(sourceName, ':', false, true); + if (tokens.size() != 2 || tokens[0].empty()) { + continue; + } + const auto& sourceDet = tokens[0]; + const auto& sourcePath = tokens[1]; + dataSources.push_back({ sourceDet, sourcePath }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + name + ".dataSources'"); + } + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/CcdbInspectorCheck.cxx b/Modules/Common/src/CcdbInspectorCheck.cxx new file mode 100644 index 0000000000..4ceafd568f --- /dev/null +++ b/Modules/Common/src/CcdbInspectorCheck.cxx @@ -0,0 +1,92 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CcdbInspectorCheck.cxx +/// \author Andrea Ferrero +/// + +#include "Common/CcdbInspectorCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include + +// ROOT +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::common +{ + +void CcdbInspectorCheck::configure() {} + +Quality CcdbInspectorCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + + (void)moName; + if (mo->getName() == "ObjectsStatus") { + auto* h = dynamic_cast(mo->getObject()); + + // initialize the overall quality when processing the first QO + if (result == Quality::Null) { + result.set(Quality::Good); + } + + // loop over objects (X-axis bins) + for (int i = 1; i <= h->GetNbinsX(); i++) { + std::string objectName = h->GetXaxis()->GetBinLabel(i); + + // check that the first Y bin is non-zero, whch corresponds to VALID object status + bool isGood = h->GetBinContent(i, 1) > 0; + if (!isGood) { + result.set(Quality::Bad); + } + // if any of the other Y bins is different from zero, it means that there was n issue accessing the object + for (int j = 2; j <= h->GetNbinsY(); j++) { + std::string flag = h->GetYaxis()->GetBinLabel(j); + if (h->GetBinContent(i, j) > 0) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::Unknown(), objectName + " is " + flag); + } + } + } + } + } + return result; +} + +void CcdbInspectorCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ +} + +void CcdbInspectorCheck::reset() +{ +} + +void CcdbInspectorCheck::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "CcdbInspectorCheck::start : " << activity.mId << ENDM; +} + +void CcdbInspectorCheck::endOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "CcdbInspectorCheck::end : " << activity.mId << ENDM; +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/CcdbInspectorTask.cxx b/Modules/Common/src/CcdbInspectorTask.cxx new file mode 100644 index 0000000000..27b831da15 --- /dev/null +++ b/Modules/Common/src/CcdbInspectorTask.cxx @@ -0,0 +1,327 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CcdbInspectorTask.cxx +/// \author Andrea Ferrero +/// \brief Post-processing task that checks the existence, time stamp and validity of CCDB/QCDB objects +/// + +#include "Common/CcdbInspectorTask.h" +#include "Common/CcdbValidatorInterface.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/ActivityHelpers.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/RootClassFactory.h" + +#include + +#include +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::common +{ + +static std::string toString(CcdbInspectorTask::ObjectStatus status) +{ + switch (status) { + case CcdbInspectorTask::ObjectStatus::objectStatusNotChecked: + return "NotChecked"; + break; + case CcdbInspectorTask::ObjectStatus::objectStatusMissing: + return "Missing"; + break; + case CcdbInspectorTask::ObjectStatus::objectStatusOld: + return "Old"; + break; + case CcdbInspectorTask::ObjectStatus::objectStatusInvalid: + return "Invalid"; + break; + case CcdbInspectorTask::ObjectStatus::objectStatusValid: + return "Valid"; + break; + } + + return ""; +} + +void CcdbInspectorTask::configure(const boost::property_tree::ptree& config) +{ + mConfig = std::make_unique(getID(), config); + + for (const auto& dataSource : mConfig->dataSources) { + if (dataSource.validatorName.empty() || dataSource.moduleName.empty()) { + continue; + } + mValidators[dataSource.name].reset(root_class_factory::create(dataSource.moduleName, dataSource.validatorName)); + ILOG(Info, Devel) << "Loaded CCDB validator module '" << dataSource.validatorName << " from '" << dataSource.moduleName << "'" << ENDM; + } + + mDatabaseType = mCustomParameters.atOptional("databaseType").value_or(mDatabaseType); + mDatabaseUrl = mCustomParameters.atOptional("databaseUrl").value_or(mDatabaseUrl); + + mTimeStampTolerance = std::stoi(mCustomParameters.atOptional("timeStampTolerance").value_or("60")) * 1000; // from seconds to milliseconds + mRetryTimeout = std::stoi(mCustomParameters.atOptional("retryTimeout").value_or("60")); + mRetryDelay = std::stoi(mCustomParameters.atOptional("retryDelay").value_or("10")); + + mDatabase = std::make_unique(); + mDatabase->connect(mDatabaseUrl, "", "", ""); + ILOG(Info, Devel) << "Initialized db connection to '" << mDatabaseUrl << "'" << ENDM; + + mHistObjectsStatus = std::make_unique("ObjectsStatus", "Objects Status", mConfig->dataSources.size(), 0, mConfig->dataSources.size(), 4, -4, 0); + mHistObjectsStatus->SetMinimum(0); + mHistObjectsStatus->SetMaximum(2); + mHistObjectsStatus->SetOption("col"); + + // set histogram bin labels + for (auto& dataSource : mConfig->dataSources) { + mHistObjectsStatus->GetXaxis()->SetBinLabel(dataSource.binNumber, dataSource.name.c_str()); + } + mHistObjectsStatus->GetYaxis()->SetBinLabel(4, "not found"); + mHistObjectsStatus->GetYaxis()->SetBinLabel(3, "too old"); + mHistObjectsStatus->GetYaxis()->SetBinLabel(2, "invalid"); + mHistObjectsStatus->GetYaxis()->SetBinLabel(1, "ok"); +} + +//_________________________________________________________________________________________ + +void CcdbInspectorTask::initialize(Trigger trigger, framework::ServiceRegistryRef) +{ + for (auto& dataSource : mConfig->dataSources) { + bool isPeriodic = dataSource.updatePolicy == CcdbInspectorTaskConfig::ObjectUpdatePolicy::updatePolicyPeriodic; + // for each periodic data source, initialize the last know timestamp to the beginning of the processing, + // such that the existance of the CCDB objects is not checked too early + // for objects that are only created once, the last known time stamp is initialized to zero + // such that the object is unconditionally searched + if (isPeriodic) { + dataSource.lastCreationTimestamp = trigger.timestamp; + } else { + dataSource.lastCreationTimestamp = 0; + } + dataSource.validObjectsCount = 0; + } + + mHistObjectsStatus->Reset(); + + getObjectsManager()->startPublishing(mHistObjectsStatus.get()); + getObjectsManager()->setDefaultDrawOptions(mHistObjectsStatus.get(), "col"); +} + +//_________________________________________________________________________________________ + +std::tuple CcdbInspectorTask::getObjectInfo(const std::string& path, const std::map& metadata) +{ + // find the time-stamp of the most recent object matching the current activity + // if ignoreActivity is true the activity matching criteria are not applied + + auto listing = mDatabase->getListingAsPtree(path, metadata, true); + if (listing.count("objects") == 0) { + ILOG(Warning, Support) << "Could not get a valid listing from db '" << mDatabaseUrl << "' for latestObjectMetadata '" << path << "'" << ENDM; + return std::make_tuple(0, 0, 0, 0); + } + const auto& objects = listing.get_child("objects"); + if (objects.empty()) { + ILOG(Warning, Devel) << "Could not find the file '" << path << "' in the db '" + << mDatabaseUrl << "' for given Activity settings. Zeroes and empty strings are treated as wildcards." << ENDM; + return std::make_tuple(0, 0, 0, 0); + } else if (objects.size() > 1) { + ILOG(Warning, Support) << "Expected just one metadata entry for object '" << path << "'. Trying to continue by using the first." << ENDM; + } + + // retrieve timestamps + const auto& latestObjectMetadata = objects.front().second; + uint64_t creationTime = latestObjectMetadata.get(metadata_keys::created); + uint64_t validFrom = latestObjectMetadata.get(metadata_keys::validFrom); + uint64_t validUntil = latestObjectMetadata.get(metadata_keys::validUntil); + + // retrieve 'runNumber' metadata element, or zero if not set + uint64_t runNumber = latestObjectMetadata.get("runNumber", 0); + + return std::make_tuple(validFrom, validUntil, creationTime, runNumber); +} + +void* CcdbInspectorTask::getObject(const std::string& path, std::type_info const& tinfo, uint64_t timestamp, const std::map& metadata) +{ + std::map headers; + void* obj = mDatabase->retrieveAny(tinfo, path, metadata, timestamp, &headers); + ILOG(Debug, Devel) << "Retrieved '" << path << "' from db '" << mDatabaseUrl << "' for timestamp " << timestamp << ENDM; + + return obj; +} + +CcdbInspectorTask::ObjectStatus CcdbInspectorTask::inspectObject(CcdbInspectorTaskConfig::DataSource& dataSource, Trigger trigger, bool atFinalize) +{ + auto path = dataSource.path; + auto validatorName = dataSource.validatorName; + auto moduleName = dataSource.moduleName; + auto updatePolicy = dataSource.updatePolicy; + auto cycleDuration = dataSource.cycleDuration * 1000; // in milliseconds + auto lastCreationTimestamp = dataSource.lastCreationTimestamp; + bool isPeriodic = updatePolicy == CcdbInspectorTaskConfig::ObjectUpdatePolicy::updatePolicyPeriodic; + + // skip check of periodic objects in the finalize() method, only check those that have EoR update policy + if (atFinalize && updatePolicy != CcdbInspectorTaskConfig::ObjectUpdatePolicy::updatePolicyAtEOR) { + ILOG(Debug, Devel) << "Not checking file '" << path << "' at finalize because policy is not 'At EoR'" << ENDM; + return ObjectStatus::objectStatusNotChecked; + } + + // skip objects that are expected only at the start or end of run, if they have been already found once + if (dataSource.validObjectsCount > 0 && !isPeriodic) { + ILOG(Debug, Devel) << "Skipping non-periodic file '" << path << "' because it was already found" << ENDM; + return ObjectStatus::objectStatusNotChecked; + } + + // skip objects that are expected only at the end of run if we are not in the finalize() method + if (!atFinalize && updatePolicy == CcdbInspectorTaskConfig::ObjectUpdatePolicy::updatePolicyAtEOR) { + ILOG(Debug, Devel) << "Not checking file '" << path << "' with policy 'At EoR'" << ENDM; + return ObjectStatus::objectStatusNotChecked; + } + + // don't check the object if we don't expect it to have been updated yet + if (trigger.timestamp < (lastCreationTimestamp + cycleDuration)) { + ILOG(Debug, Devel) << "Not checking file '" << path << "' with policy 'periodic' because elapsed " + << (trigger.timestamp - lastCreationTimestamp) / 1000 << " seconds is less than cycle duraction (" << dataSource.cycleDuration + << " seconds)" << ENDM; + return ObjectStatus::objectStatusNotChecked; + } + + // verbose messages, only for debugging + if (isPeriodic) { + ILOG(Debug, Devel) << "Checking file '" << path << "' with policy 'periodic' and cycle " << dataSource.cycleDuration + << " seconds, elapsed " << (trigger.timestamp - lastCreationTimestamp) / 1000 << " seconds" << ENDM; + } else if (updatePolicy == CcdbInspectorTaskConfig::ObjectUpdatePolicy::updatePolicyAtSOR) { + ILOG(Debug, Devel) << "Checking file '" << path << "' with policy 'At SoR'," + << " elapsed " << (trigger.timestamp - lastCreationTimestamp) / 1000 << " seconds" << ENDM; + } else if (updatePolicy == CcdbInspectorTaskConfig::ObjectUpdatePolicy::updatePolicyAtEOR) { + ILOG(Debug, Devel) << "Checking file '" << path << "' with policy 'At EoR'," + << " elapsed " << (trigger.timestamp - lastCreationTimestamp) / 1000 << " seconds" << ENDM; + } + + // get timestamps and run numberof the last available object + auto fullObjectPath = (mDatabaseType == "qcdb" ? trigger.activity.mProvenance + "/" : "") + path; + // metadata for CCDB queries, only specifying the run number + std::map metadataCcdb{ { "runNumber", std::to_string(trigger.activity.mId) } }; + auto metadata = mDatabaseType == "qcdb" ? activity_helpers::asDatabaseMetadata(trigger.activity, false) : metadataCcdb; + auto timestamps = getObjectInfo(path, metadata); + auto creationTime = std::get<2>(timestamps); + auto runNumber = std::get<3>(timestamps); + + // the object is considered found if the associated run number (if valid) matches the current one, + // and if the creation time is newer that the last known timestamp + bool isFound = (runNumber <= 0 || trigger.activity.mId == runNumber) && (creationTime > lastCreationTimestamp); + if (!isFound) { + ILOG(Warning, Support) << "File '" << path << "' for run " << trigger.activity.mId << " from db '" << mDatabaseUrl << "' not found" << ENDM; + + // run numbers do not match + if (runNumber > 0 && trigger.activity.mId != runNumber) { + ILOG(Warning, Support) << "Mismatching run numbers for file '" << path << ": " + << runNumber << " != " << trigger.activity.mId << ENDM; + } + + // object was created in the past + if (creationTime < lastCreationTimestamp) { + ILOG(Warning, Support) << "File '" << path << "' is older than " << lastCreationTimestamp << ENDM; + } + } else { + ILOG(Info, Devel) << "File '" << path << "' found in the db '" << mDatabaseUrl << "' with creationTime=" << creationTime << " and runNumber=" << runNumber + << " for activity " << trigger.activity << ENDM; + } + + // The object is considered old if the timestamp is more than one cycle duration in the past. + // We add some tolerance to compensate possible variations in the creation time stamp + // This condition is ignored when checking non-periodic objects, since in this case we just + // need to know that there was one object created after the start of run + bool isOld = isPeriodic ? (creationTime < (lastCreationTimestamp + cycleDuration - mTimeStampTolerance)) && (isFound) : false; + + if (isOld) { + ILOG(Warning, Support) << "File '" << path << "' for run " << trigger.activity.mId << " from db '" << mDatabaseUrl << "' is too old" << ENDM; + } + + bool isValid = true; + if (isFound && !isOld) { + // creation time is valid, update the last known timestamp of this data source + dataSource.lastCreationTimestamp = creationTime; + dataSource.validObjectsCount += 1; + ILOG(Debug, Devel) << "Found file '" << path << "' from db '" << mDatabaseUrl << "' for run " << trigger.activity.mId + << " creationTime=" << creationTime << ENDM; + + // run the validator on the object, skip if no validator is associated to this data source + // only executed if the time stamp of the object is valid + auto validatorIter = mValidators.find(dataSource.name); + if (validatorIter != mValidators.end() && validatorIter->second) { + auto timestamp = std::get<1>(timestamps) - 1; + void* obj = getObject(fullObjectPath, validatorIter->second->getTinfo(), timestamp, metadata); + ILOG(Debug, Devel) << "Retrieved object for file '" << path << "' from db '" << mDatabaseUrl << "' for timestamp " << timestamp + << " obj=" << obj << ENDM; + isValid = validatorIter->second->validate(obj); + ILOG(Debug, Devel) << "Validated object for file '" << path << "' from db '" << mDatabaseUrl << "' for timestamp " << timestamp + << " isValid=" << isValid << ENDM; + } + } + + ObjectStatus objectStatus = ObjectStatus::objectStatusNotChecked; + + if (!isFound) { + objectStatus = ObjectStatus::objectStatusMissing; + } else if (isOld) { + objectStatus = ObjectStatus::objectStatusOld; + } else if (!isValid) { + objectStatus = ObjectStatus::objectStatusInvalid; + } else { + objectStatus = ObjectStatus::objectStatusValid; + } + ILOG(Info, Devel) << "File '" << path << "' from db '" << mDatabaseUrl << "': status is " << toString(objectStatus) << ENDM; + + // update contents of bin associated to current data source + for (int biny = 1; biny <= mHistObjectsStatus->GetYaxis()->GetNbins(); biny++) { + mHistObjectsStatus->SetBinContent(dataSource.binNumber, biny, 0); + } + mHistObjectsStatus->SetBinContent(dataSource.binNumber, static_cast(objectStatus) + 1, 1); + + return objectStatus; +} + +void CcdbInspectorTask::update(Trigger t, framework::ServiceRegistryRef) +{ + for (auto& dataSource : mConfig->dataSources) { + inspectObject(dataSource, t, false); + } +} + +//_________________________________________________________________________________________ + +void CcdbInspectorTask::finalize(Trigger t, framework::ServiceRegistryRef) +{ + ILOG(Info, Devel) << "Checking objects at finalize() " << ENDM; + int elapsed = 0; + while (elapsed < mRetryTimeout) { + bool allFound = true; + for (auto& dataSource : mConfig->dataSources) { + auto status = inspectObject(dataSource, t, true); + if (static_cast(status) > 0) { + allFound = false; + } + } + + if (allFound) { + break; + } + + std::this_thread::sleep_for(std::chrono::seconds(mRetryDelay)); + elapsed += mRetryDelay; + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/CcdbInspectorTaskConfig.cxx b/Modules/Common/src/CcdbInspectorTaskConfig.cxx new file mode 100644 index 0000000000..ff9721c780 --- /dev/null +++ b/Modules/Common/src/CcdbInspectorTaskConfig.cxx @@ -0,0 +1,57 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CcdbInspectorTaskConfig.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief File for the configuration of CCDB inspector post-processing task +/// + +#include "Common/CcdbInspectorTaskConfig.h" +#include +#include + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::common +{ + +CcdbInspectorTaskConfig::CcdbInspectorTaskConfig(std::string name, const boost::property_tree::ptree& config) + : PostProcessingConfig(name, config) +{ + // Data source configuration + int bin = 1; + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".dataSources")) { + auto name = dataSourceConfig.second.get("name"); + auto path = dataSourceConfig.second.get("path"); + auto validatorName = dataSourceConfig.second.get("validatorName", std::string("")); + auto moduleName = dataSourceConfig.second.get("moduleName", std::string("")); + + auto updatePolicyString = dataSourceConfig.second.get("updatePolicy", std::string("periodic")); + ObjectUpdatePolicy updatePolicy; + if (updatePolicyString == "periodic") { + updatePolicy = ObjectUpdatePolicy::updatePolicyPeriodic; + } + if (updatePolicyString == "atSOR") { + updatePolicy = ObjectUpdatePolicy::updatePolicyAtSOR; + } + if (updatePolicyString == "atEOR") { + updatePolicy = ObjectUpdatePolicy::updatePolicyAtEOR; + } + + auto cycleDuration = dataSourceConfig.second.get("cycleDuration", 0); + uint64_t timestamp = 0; + int valibObjectCount = 0; + dataSources.push_back({ name, path, validatorName, moduleName, updatePolicy, cycleDuration, timestamp, valibObjectCount, bin }); + bin += 1; + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/CheckerThresholdsConfig.cxx b/Modules/Common/src/CheckerThresholdsConfig.cxx new file mode 100644 index 0000000000..155eed92e1 --- /dev/null +++ b/Modules/Common/src/CheckerThresholdsConfig.cxx @@ -0,0 +1,264 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckerThresholdsConfig.cxx +/// \author Andrea Ferrero +/// \brief Utility class handling thresholds and axis ranges and retrieve them from the custom parameters +/// + +#include "Common/CheckerThresholdsConfig.h" +#include "Common/Utils.h" +#include "QualityControl/QcInfoLogger.h" +#include + +namespace o2::quality_control_modules::common +{ + +namespace internal +{ + +class Thresholds +{ + public: + /// \brief thersholds initialization from custom parameters + Thresholds(const CustomParameters& customParameters, const std::string& plotName, const std::string& qualityLabel, const Activity& activity); + + /// \brief function to retrieve the thresholds for a given interaction rate, if available + std::optional> getThresholdsForRate(double rate); + + private: + /// vectors of [min,max,rate] tuples. The first two values are the minimum and maximum threshold values, + /// and the third is the associated reference interaction rate (optional) + std::vector>> mThresholds; +}; + +Thresholds::Thresholds(const CustomParameters& customParameters, const std::string& plotName, const std::string& qualityLabel, const Activity& activity) +{ + // get configuration parameter associated to the key + std::string parKey = std::string("thresholds") + qualityLabel; + if (!plotName.empty()) { + parKey += ":"; + parKey += plotName; + } + + std::string parValue = getFromExtendedConfig(activity, customParameters, parKey); + if (parValue.empty()) { + return; + } + + // extract information for each nominal interaction rate value + auto interactionRatePoints = o2::utils::Str::tokenize(parValue, ';', false, true); + for (const auto& interactionRatePoint : interactionRatePoints) { + double thresholdMin = 0; + double thresholdMax = 0; + std::optional nominalRate; + + // extract interaction rate and threshold pairs + auto rateAndThresholds = o2::utils::Str::tokenize(interactionRatePoint, ':', false, true); + std::string thresholdValues; + if (rateAndThresholds.size() == 2) { + // the token contains both the rate and threshold values + // the rate is comverted from kHz to Hz + try { + nominalRate = std::stod(rateAndThresholds[0]) * 1000; + } catch (std::exception& exception) { + ILOG(Error, Support) << "Cannot convert values from string to double for " << qualityLabel << " thresholds of plot \"" << plotName << "\", string is " << rateAndThresholds[0] << ENDM; + } + thresholdValues = rateAndThresholds[1]; + } else { + // the token only contains threshold values, which are then valid for any rate + thresholdValues = rateAndThresholds[0]; + } + + // extract the comma-separated minimum and maximum threshold values + std::vector thresholdMinMax = o2::utils::Str::tokenize(thresholdValues, ',', false, true); + if (thresholdMinMax.size() == 2) { + try { + thresholdMin = std::stod(thresholdMinMax[0]); + thresholdMax = std::stod(thresholdMinMax[1]); + } catch (std::exception& exception) { + ILOG(Error, Support) << "Cannot convert values from string to double for " << qualityLabel << " thresholds of plot \"" << plotName << "\", string is " << thresholdValues << ENDM; + } + } + + mThresholds.emplace_back(std::make_tuple(thresholdMin, thresholdMax, nominalRate)); + } +} + +static std::pair interpolateThresholds(double fraction, const std::tuple>& thresholdsLow, const std::tuple>& thresholdsHigh) +{ + double thresholdMin = std::get<0>(thresholdsLow) * (1.0 - fraction) + std::get<0>(thresholdsHigh) * fraction; + double thresholdMax = std::get<1>(thresholdsLow) * (1.0 - fraction) + std::get<1>(thresholdsHigh) * fraction; + return std::make_pair(thresholdMin, thresholdMax); +} + +std::optional> Thresholds::getThresholdsForRate(double rate) +{ + std::optional> result; + + // index and corresponding rate of the element immediately below the requested rate + int indexLow = -1; + double rateLow = -1; + + // index and corresponding rate of the element immediately above the requested rate + int indexHigh = -1; + double rateHigh = -1; + + // search the closest element above and below the requested rate + for (int index = 0; index < mThresholds.size(); index++) { + const auto& element = mThresholds[index]; + // skip if rate is not specified + if (!std::get<2>(element)) { + continue; + } + double rateCurrent = std::get<2>(element).value(); + + if (rateCurrent <= rate) { + if (indexLow < 0 || rateLow < rateCurrent) { + indexLow = index; + rateLow = rateCurrent; + } + } + + if (rateCurrent >= rate) { + if (indexHigh < 0 || rateHigh > rateCurrent) { + indexHigh = index; + rateHigh = rateCurrent; + } + } + } + + if (indexLow >= 0 && indexHigh >= 0 && indexLow != indexHigh) { + double fraction = (rate - rateLow) / (rateHigh - rateLow); + result = interpolateThresholds(fraction, mThresholds[indexLow], mThresholds[indexHigh]); + } else if (indexLow >= 0) { + result = std::make_pair(std::get<0>(mThresholds[indexLow]), std::get<1>(mThresholds[indexLow])); + } else if (indexHigh >= 0) { + result = std::make_pair(std::get<0>(mThresholds[indexHigh]), std::get<1>(mThresholds[indexHigh])); + } else if (mThresholds.size() > 0) { + result = std::make_pair(std::get<0>(mThresholds[0]), std::get<1>(mThresholds[0])); + } + + return result; +} + +class XYRanges +{ + public: + /// \brief range initialization from custom parameters + XYRanges(const CustomParameters& customParameters, const std::string& plotName, const std::string& axisName, const Activity& activity); + + /// \brief retrieve the axis range over which the check must be restricted + std::optional> getRange() { return mRange; } + + private: + /// \brief axis range over which the check must be restricted + std::optional> mRange; +}; + +XYRanges::XYRanges(const CustomParameters& customParameters, const std::string& plotName, const std::string& axisName, const Activity& activity) +{ + // build the key associated to the configuration parameter + std::string parKey = std::string("range") + axisName; + if (!plotName.empty()) { + parKey += ":"; + parKey += plotName; + } + + std::string parValue = getFromExtendedConfig(activity, customParameters, parKey); + if (parValue.empty()) { + return; + } + + // extract min and max values of the range + auto rangeMinMax = o2::utils::Str::tokenize(parValue, ',', false, true); + if (rangeMinMax.size() != 2) { + ILOG(Error, Support) << "Cannot parse values for " << axisName << " axis range of plot \"" << plotName << "\", string is " << parValue << ENDM; + return; + } + + try { + mRange = std::make_pair(std::stof(rangeMinMax[0]), std::stof(rangeMinMax[1])); + } catch (std::exception& exception) { + ILOG(Error, Support) << "Cannot convert values from string to double for " << axisName << " axis range of plot \"" << plotName << "\", string is " << parValue << ENDM; + } +} + +} // namespace internal + +CheckerThresholdsConfig::CheckerThresholdsConfig(const CustomParameters& customParameters, const Activity& activity) + : mCustomParameters(customParameters), mActivity(activity) +{ + mDefaultThresholds[0] = std::make_shared(mCustomParameters, std::string(), std::string("Bad"), mActivity); + mDefaultThresholds[1] = std::make_shared(mCustomParameters, std::string(), std::string("Medium"), mActivity); + + mDefaultRanges[0] = std::make_shared(mCustomParameters, std::string(), std::string("X"), mActivity); + mDefaultRanges[1] = std::make_shared(mCustomParameters, std::string(), std::string("Y"), mActivity); +} + +void CheckerThresholdsConfig::initThresholdsForPlot(const std::string& plotName) +{ + // if not done yet, read from the configuration the threshold settings specific to plotName + using MapElementType = std::unordered_map>::value_type; + if (mThresholds[0].count(plotName) == 0) { + mThresholds[0].emplace(MapElementType(plotName, std::make_shared(mCustomParameters, plotName, std::string("Bad"), mActivity))); + mThresholds[1].emplace(MapElementType(plotName, std::make_shared(mCustomParameters, plotName, std::string("Medium"), mActivity))); + } +} + +std::array>, 2> CheckerThresholdsConfig::getThresholdsForPlot(const std::string& plotName, double rate) +{ + initThresholdsForPlot(plotName); + + std::array>, 2> result; + + // get thresholds for Bad and Medium quality + for (size_t index = 0; index < 2; index++) { + result[index] = mThresholds[index][plotName]->getThresholdsForRate(rate); + if (!result[index]) { + // try with default threshold settings if the plot-specific ones are not available + result[index] = mDefaultThresholds[index]->getThresholdsForRate(rate); + } + } + + return result; +} + +void CheckerThresholdsConfig::initRangesForPlot(const std::string& plotName) +{ + // if not done yet, read from the configuration the ranges settings specific to plotName + using MapElementType = std::unordered_map>::value_type; + if (mRanges[0].count(plotName) == 0) { + mRanges[0].emplace(MapElementType(plotName, std::make_shared(mCustomParameters, plotName, std::string("X"), mActivity))); + mRanges[1].emplace(MapElementType(plotName, std::make_shared(mCustomParameters, plotName, std::string("Y"), mActivity))); + } +} + +std::array>, 2> CheckerThresholdsConfig::getRangesForPlot(const std::string& plotName) +{ + initRangesForPlot(plotName); + + std::array>, 2> result; + + // get ranges for X and Y axes + for (size_t index = 0; index < 2; index++) { + result[index] = mRanges[index][plotName]->getRange(); + if (!result[index]) { + // try with default threshold settings if the plot-specific ones are not available + result[index] = mDefaultRanges[index]->getRange(); + } + } + + return result; +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/EverIncreasingGraph.cxx b/Modules/Common/src/EverIncreasingGraph.cxx new file mode 100644 index 0000000000..e08759b3fd --- /dev/null +++ b/Modules/Common/src/EverIncreasingGraph.cxx @@ -0,0 +1,83 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file EverIncreasingGraph.cxx +/// \author Barthelemy von Haller +/// + +#include "Common/EverIncreasingGraph.h" + +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" + +// ROOT +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::common +{ +Quality EverIncreasingGraph::check(std::map>* moMap) +{ + auto mo = moMap->begin()->second; + Quality result = Quality::Good; + auto* g = dynamic_cast(mo->getObject()); + if (g == nullptr) { + return Quality::Null; + } + + // simplistic and inefficient way to check that points are always increasing + int nbPoints = g->GetN(); + double lastY = std::numeric_limits::lowest(); + for (int i = 1; i < nbPoints; i++) { + double x, y; + g->GetPoint(i, x, y); + if (y < lastY) { + result = Quality::Bad; + break; + } + lastY = y; + } + + return result; +} + +void EverIncreasingGraph::beautify(std::shared_ptr mo, Quality checkResult) +{ + ILOG(Info, Support) << "EverIncreasingGraph::Beautify" << ENDM; + + if (checkResult == Quality::Null || checkResult == Quality::Medium) { + return; + } + + auto* g = dynamic_cast(mo->getObject()); + if (!g) { + ILOG(Error, Support) << "MO should be a graph" << ENDM; + return; + } + + auto* paveText = new TPaveText(0.3, 0.8, 0.7, 0.95, "NDC"); + if (checkResult == Quality::Good) { + paveText->SetFillColor(kGreen); + paveText->AddText("No anomalies"); + } else if (checkResult == Quality::Bad) { + paveText->SetFillColor(kRed); + paveText->AddText("Values are not always increasing"); + } + g->GetListOfFunctions()->AddLast(paveText); +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/IncreasingEntries.cxx b/Modules/Common/src/IncreasingEntries.cxx new file mode 100644 index 0000000000..c76167b8f0 --- /dev/null +++ b/Modules/Common/src/IncreasingEntries.cxx @@ -0,0 +1,107 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file IncreasingEntries.cxx +/// \author Barthélémy von Haller +/// + +#include "Common/IncreasingEntries.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/stringUtils.h" +// ROOT +#include +#include + +#include +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::common +{ + +void IncreasingEntries::configure() +{ + auto option = mCustomParameters.atOptional("mustIncrease"); + mMustIncrease = option.has_value() ? decodeBool(option.value()) : true; + ILOG(Debug, Support) << "mustIncrease: " << mMustIncrease << ENDM; + + option = mCustomParameters.atOptional("nBadCyclesLimit"); + mBadCyclesLimit = option.has_value() ? stoi(option.value()) : 1; + ILOG(Debug, Support) << "nBadCyclesLimit: " << mBadCyclesLimit << ENDM; + + mPaveText = make_shared(1, 0.125, 0.6, 0, "NDC"); + mPaveText->SetFillColor(kRed); + mPaveText->SetMargin(0); + if (mMustIncrease) { + mPaveText->AddText("Number of Entries has *not* changed"); + mPaveText->AddText(string("in the past ") + mBadCyclesLimit + " cycle(s)"); + } else { + mPaveText->AddText("Number of Entries has *changed*"); + mPaveText->AddText(string("in the past ") + mBadCyclesLimit + " cycle(s)"); + } +} + +Quality IncreasingEntries::check(std::map>* moMap) +{ + Quality result = Quality::Good; + mFaultyObjectsNames.clear(); + + for (auto& [moName, mo] : *moMap) { + + TH1* histo = dynamic_cast(mo->getObject()); + if (histo == nullptr) { + continue; + } + + const double previousNumberEntries = mLastEntries.count(moName) > 0 ? mLastEntries.at(moName) : 0; + const double currentNumberEntries = histo->GetEntries(); + size_t faultCount = mMoFaultCount.count(moName) > 0 ? mMoFaultCount.at(moName) : 0; + + if (mMustIncrease == (previousNumberEntries == currentNumberEntries)) { + faultCount++; + } else { + faultCount = 0; + } + + if (faultCount >= mBadCyclesLimit) { + result = Quality::Bad; + mFaultyObjectsNames.push_back(mo->getName()); + if (mMustIncrease) { + result.addFlag(FlagTypeFactory::NoDetectorData(), "Number of entries stopped increasing."); + } else { + result.addFlag(FlagTypeFactory::Unknown(), "Number of entries has increased."); + } + } + + mMoFaultCount[moName] = faultCount; + mLastEntries[moName] = currentNumberEntries; + } + return result; +} + +void IncreasingEntries::beautify(std::shared_ptr mo, Quality checkResult) +{ + // only add the pavetext on the faulty plots + if (std::count(mFaultyObjectsNames.begin(), mFaultyObjectsNames.end(), mo->getName())) { + TH1* histo = dynamic_cast(mo->getObject()); + if (histo) { + histo->GetListOfFunctions()->Add(mPaveText->Clone()); + } + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/LHCClockPhaseReductor.cxx b/Modules/Common/src/LHCClockPhaseReductor.cxx new file mode 100644 index 0000000000..3ea1bb02dd --- /dev/null +++ b/Modules/Common/src/LHCClockPhaseReductor.cxx @@ -0,0 +1,43 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file LHCClockPhaseReductor.cxx +/// \author Piotr Konopka +/// + +#include "Common/LHCClockPhaseReductor.h" + +#include + +namespace o2::quality_control_modules::common +{ + +void* LHCClockPhaseReductor::getBranchAddress() +{ + return &mStats; +} + +const char* LHCClockPhaseReductor::getBranchLeafList() +{ + return "phase/F"; +} + +bool LHCClockPhaseReductor::update(ConditionRetriever& retriever) +{ + if (auto lhcPhase = retriever.retrieve()) { + mStats.phase = lhcPhase->getLHCphase(0); + return true; + } + return false; +} + +} // namespace o2::quality_control_modules::common \ No newline at end of file diff --git a/Modules/Common/src/MeanIsAbove.cxx b/Modules/Common/src/MeanIsAbove.cxx index b913c7c5a7..c59be784dc 100644 --- a/Modules/Common/src/MeanIsAbove.cxx +++ b/Modules/Common/src/MeanIsAbove.cxx @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file MeanIsAbove.cxx /// \author Barthelemy von Haller @@ -10,36 +21,26 @@ #include #include #include -// O2 -#include "Configuration/ConfigurationFactory.h" +// QC +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" ClassImp(o2::quality_control_modules::common::MeanIsAbove) using namespace std; -using namespace o2::configuration; namespace o2::quality_control_modules::common { -MeanIsAbove::MeanIsAbove() : mThreshold(0.0) {} - -void MeanIsAbove::configure(std::string name) +void MeanIsAbove::configure() { - // TODO use the configuration system to set the params - // try { - // auto configFile = ConfigurationFactory::getConfiguration("file:../example.ini"); // not ok... - // } catch (string &exception) { - // cout << "error getting config file in MeanIsAbove : " << exception << endl; - // mThreshold = 1.0f; - // return; - // } - mThreshold = 1.0f; // std::stof(configFile.getValue("Checks.checkMeanIsAbove/threshold")); + mThreshold = stof(mCustomParameters.at("meanThreshold")); } -std::string MeanIsAbove::getAcceptedType() { return "TH1"; } - -Quality MeanIsAbove::check(const MonitorObject* mo) +Quality MeanIsAbove::check(std::map>* moMap) { + auto mo = moMap->begin()->second; auto* th1 = dynamic_cast(mo->getObject()); if (!th1) { // TODO @@ -51,13 +52,13 @@ Quality MeanIsAbove::check(const MonitorObject* mo) return Quality::Bad; } -void MeanIsAbove::beautify(MonitorObject* mo, Quality checkResult) +void MeanIsAbove::beautify(std::shared_ptr mo, Quality checkResult) { // A line is drawn at the level of the threshold. // Its colour depends on the quality. - if (!this->isObjectCheckable(mo)) { - cerr << "object not checkable" << endl; + if (!mo->encapsulatedInheritsFrom("TH1")) { + ILOG(Error, Support) << "object not checkable" << ENDM; return; } diff --git a/Modules/Common/src/NonEmpty.cxx b/Modules/Common/src/NonEmpty.cxx index 10057b8392..9e6bef0319 100644 --- a/Modules/Common/src/NonEmpty.cxx +++ b/Modules/Common/src/NonEmpty.cxx @@ -1,10 +1,22 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file NonEmpty.cxx /// \author Barthelemy von Haller /// #include "Common/NonEmpty.h" - +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" // ROOT #include @@ -14,15 +26,9 @@ ClassImp(o2::quality_control_modules::common::NonEmpty) namespace o2::quality_control_modules::common { - - NonEmpty::NonEmpty() {} - - NonEmpty::~NonEmpty() {} - - void NonEmpty::configure(std::string name) {} - - Quality NonEmpty::check(const MonitorObject* mo) + Quality NonEmpty::check(std::map> * moMap) { + auto mo = moMap->begin()->second; auto result = Quality::Null; // The framework guarantees that the encapsulated object is of the accepted type. @@ -40,9 +46,7 @@ ClassImp(o2::quality_control_modules::common::NonEmpty) return result; } - std::string NonEmpty::getAcceptedType() { return "TH1"; } - - void NonEmpty::beautify(MonitorObject* mo, Quality checkResult) + void NonEmpty::beautify(std::shared_ptr /*mo*/, Quality /*checkResult*/) { // NOOP } diff --git a/Modules/Common/src/ObjectComparatorBinByBinDeviation.cxx b/Modules/Common/src/ObjectComparatorBinByBinDeviation.cxx new file mode 100644 index 0000000000..724d67390b --- /dev/null +++ b/Modules/Common/src/ObjectComparatorBinByBinDeviation.cxx @@ -0,0 +1,100 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorBinByBinDeviation.cxx +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on the average of the relative deviation between the bins +/// + +#include "Common/ObjectComparatorBinByBinDeviation.h" +#include "Common/Utils.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include + +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +void ObjectComparatorBinByBinDeviation::configure(const CustomParameters& customParameters, const std::string& plotName, const Activity activity) +{ + ObjectComparatorInterface::configure(customParameters, plotName, activity); + + // get configuration parameter associated to the key + std::string parValue = getParameterForPlot(customParameters, std::string("maxAllowedBadBins"), plotName, activity); + if (parValue.empty()) { + return; + } + + try { + mMaxAllowedBadBins = std::stoi(parValue); + } catch (std::exception& exception) { + ILOG(Error, Support) << "Cannot convert maxAllowedBadBins for plot \"" << plotName << "\", string is " << parValue << ENDM; + return; + } +} + +Quality ObjectComparatorBinByBinDeviation::compare(TObject* object, TObject* referenceObject, std::string& message) +{ + auto checkResult = checkInputObjects(object, referenceObject, message); + if (!std::get<2>(checkResult)) { + return Quality::Null; + } + + auto* histogram = std::get<0>(checkResult); + auto* referenceHistogram = std::get<1>(checkResult); + + int binRangeX[2] = { 1, histogram->GetXaxis()->GetNbins() }; + if (getXRange().has_value()) { + binRangeX[0] = histogram->GetXaxis()->FindBin(getXRange()->first); + binRangeX[1] = histogram->GetXaxis()->FindBin(getXRange()->second); + } + + int binRangeY[2] = { 1, histogram->GetYaxis()->GetNbins() }; + if (getYRange().has_value()) { + binRangeY[0] = histogram->GetYaxis()->FindBin(getYRange()->first); + binRangeY[1] = histogram->GetYaxis()->FindBin(getYRange()->second); + } + + int binRangeZ[2] = { 1, histogram->GetZaxis()->GetNbins() }; + + // compute the average relative deviation between the bins + double averageDeviation = 0; + int numberOfBadBins = 0; + for (int binX = binRangeX[0]; binX <= binRangeX[1]; binX++) { + for (int binY = binRangeY[0]; binY <= binRangeY[1]; binY++) { + for (int binZ = binRangeZ[0]; binZ <= binRangeZ[1]; binZ++) { + int bin = histogram->GetBin(binX, binY, binZ); + double val = histogram->GetBinContent(bin); + double refVal = referenceHistogram->GetBinContent(bin); + double deviation = (refVal == 0) ? 0 : std::abs((val - refVal) / refVal); + if (deviation > getThreshold()) { + numberOfBadBins += 1; + } + } + } + } + + // compare the average deviation with the maximum allowed value + if (numberOfBadBins > mMaxAllowedBadBins) { + message = fmt::format("bins above {:.2f}: {} > {}", getThreshold(), numberOfBadBins, mMaxAllowedBadBins); + return Quality::Bad; + } + + return Quality::Good; +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ObjectComparatorChi2.cxx b/Modules/Common/src/ObjectComparatorChi2.cxx new file mode 100644 index 0000000000..6368b69193 --- /dev/null +++ b/Modules/Common/src/ObjectComparatorChi2.cxx @@ -0,0 +1,81 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorChi2.cxx +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on a chi2 test +/// + +#include "Common/ObjectComparatorChi2.h" +// ROOT +#include + +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +Quality ObjectComparatorChi2::compare(TObject* object, TObject* referenceObject, std::string& message) +{ + auto checkResult = checkInputObjects(object, referenceObject, message); + if (!std::get<2>(checkResult)) { + return Quality::Null; + } + + auto* histogram = std::get<0>(checkResult); + auto* referenceHistogram = std::get<1>(checkResult); + + // if X and/or Y ranges are specified, set the appropriate bin range in the corresponding axis + // to restrict the histogram area where the test is applied + const double epsilon = 1.0e-6; + if (getXRange().has_value()) { + int binMin = histogram->GetXaxis()->FindBin(getXRange()->first); + // subtract a small amount to the upper edge to avoid getting the next bin + int binMax = histogram->GetXaxis()->FindBin(getXRange()->second - epsilon); + + histogram->GetXaxis()->SetRange(binMin, binMax); + referenceHistogram->GetXaxis()->SetRange(binMin, binMax); + } + + if (getYRange().has_value()) { + int binMin = histogram->GetYaxis()->FindBin(getYRange()->first); + // subtract a small amount to the upper edge to avoid getting the next bin + int binMax = histogram->GetYaxis()->FindBin(getYRange()->second - epsilon); + + histogram->GetYaxis()->SetRange(binMin, binMax); + referenceHistogram->GetYaxis()->SetRange(binMin, binMax); + } + + // perform a chi2 compatibility test between the two histograms + // it assumes that both histigrams represent counts, but the reference might + // have been rescaled to match the integral of the current histogram + double testProbability = histogram->Chi2Test(referenceHistogram, "UU NORM"); + + // reset the axis ranges + histogram->GetXaxis()->SetRange(0, 0); + histogram->GetYaxis()->SetRange(0, 0); + referenceHistogram->GetXaxis()->SetRange(0, 0); + referenceHistogram->GetYaxis()->SetRange(0, 0); + + // compare the chi2 probability with the minimum allowed value + if (testProbability < getThreshold()) { + message = fmt::format("chi2 test failed: {:.2f} < {:.2f}", testProbability, getThreshold()); + return Quality::Bad; + } + + return Quality::Good; +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ObjectComparatorDeviation.cxx b/Modules/Common/src/ObjectComparatorDeviation.cxx new file mode 100644 index 0000000000..3ecfe8901a --- /dev/null +++ b/Modules/Common/src/ObjectComparatorDeviation.cxx @@ -0,0 +1,85 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorDeviation.cxx +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on the average of the relative deviation between the bins +/// + +#include "Common/ObjectComparatorDeviation.h" +// ROOT +#include + +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +Quality ObjectComparatorDeviation::compare(TObject* object, TObject* referenceObject, std::string& message) +{ + auto checkResult = checkInputObjects(object, referenceObject, message); + if (!std::get<2>(checkResult)) { + return Quality::Null; + } + + auto* histogram = std::get<0>(checkResult); + auto* referenceHistogram = std::get<1>(checkResult); + + const double epsilon = 1.0e-6; + int binRangeX[2] = { 1, histogram->GetXaxis()->GetNbins() }; + int binRangeY[2] = { 1, histogram->GetYaxis()->GetNbins() }; + int binRangeZ[2] = { 1, histogram->GetZaxis()->GetNbins() }; + + if (getXRange().has_value()) { + binRangeX[0] = histogram->GetXaxis()->FindBin(getXRange()->first); + // subtract a small amount to the upper edge to avoid getting the next bin + binRangeX[1] = histogram->GetXaxis()->FindBin(getXRange()->second - epsilon); + } + + if (getYRange().has_value()) { + binRangeY[0] = histogram->GetYaxis()->FindBin(getYRange()->first); + // subtract a small amount to the upper edge to avoid getting the next bin + binRangeY[1] = histogram->GetYaxis()->FindBin(getYRange()->second - epsilon); + } + + // compute the average relative deviation between the bins + double averageDeviation = 0; + int numberOfBins = 0; + for (int binX = binRangeX[0]; binX <= binRangeX[1]; binX++) { + for (int binY = binRangeY[0]; binY <= binRangeY[1]; binY++) { + for (int binZ = binRangeZ[0]; binZ <= binRangeZ[1]; binZ++) { + int bin = histogram->GetBin(binX, binY, binZ); + double val = histogram->GetBinContent(bin); + double refVal = referenceHistogram->GetBinContent(bin); + double deviation = (refVal == 0) ? 0 : std::abs((val - refVal) / refVal); + averageDeviation += deviation; + numberOfBins += 1; + } + } + } + if (numberOfBins > 0) { + averageDeviation /= numberOfBins; + } + + // compare the average deviation with the maximum allowed value + if (averageDeviation > getThreshold()) { + message = fmt::format("large deviation: {:.2f} > {:.2f}", averageDeviation, getThreshold()); + return Quality::Bad; + } + + return Quality::Good; +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ObjectComparatorInterface.cxx b/Modules/Common/src/ObjectComparatorInterface.cxx new file mode 100644 index 0000000000..9843f6989a --- /dev/null +++ b/Modules/Common/src/ObjectComparatorInterface.cxx @@ -0,0 +1,131 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorInterface.cxx +/// \author Andrea Ferrero +/// \brief An interface for comparing two TObject +/// + +#include "Common/ObjectComparatorInterface.h" +#include "Common/Utils.h" +#include "QualityControl/QcInfoLogger.h" +#include +// ROOT +#include + +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +std::string ObjectComparatorInterface::getParameterForPlot(const CustomParameters& customParameters, const std::string& parKey, const std::string& plotName, const Activity& activity) +{ + // get configuration parameter associated to the key + std::string parValue; + if (!plotName.empty()) { + // first try to retrieve the plot-specific value, if existing + auto parKeyForPlot = parKey + ":" + plotName; + parValue = getFromExtendedConfig(activity, customParameters, parKeyForPlot); + } + + if (parValue.empty()) { + // get the default value if the plot-specific one is not found + parValue = getFromExtendedConfig(activity, customParameters, parKey); + } + + return parValue; +} + +void ObjectComparatorInterface::configure(const CustomParameters& customParameters, const std::string& plotName, const Activity activity) +{ + // get configuration parameter associated to the key + std::string parValue = getParameterForPlot(customParameters, std::string("threshold"), plotName, activity); + + if (parValue.empty()) { + ILOG(Error, Support) << "Reference comparator threshold not found for plot \"" << plotName << "\"" << ENDM; + return; + } + + try { + mThreshold = std::stod(parValue); + } catch (std::exception& exception) { + ILOG(Error, Support) << "Cannot convert reference comparator threshold for plot \"" << plotName << "\", string is " << parValue << ENDM; + return; + } + + // get optional axis ranges + std::array axisNames{ "X", "Y" }; + for (int index = 0; index < 2; index++) { + // get configuration parameter associated to the key + std::string parValue = getParameterForPlot(customParameters, std::string("range") + axisNames[index], plotName, activity); + + if (parValue.empty()) { + continue; + } + + // extract min and max values of the range + auto rangeMinMax = o2::utils::Str::tokenize(parValue, ',', false, true); + if (rangeMinMax.size() != 2) { + ILOG(Error, Support) << "Cannot parse values for " << axisNames[index] << " axis range of plot \"" << plotName << "\", string is " << parValue << ENDM; + return; + } + + try { + mRanges[index] = std::make_pair(std::stod(rangeMinMax[0]), std::stod(rangeMinMax[1])); + } catch (std::exception& exception) { + ILOG(Error, Support) << "Cannot convert values from string to double for " << axisNames[index] << " axis range of plot \"" << plotName << "\", string is " << parValue << ENDM; + } + } +} + +std::tuple ObjectComparatorInterface::checkInputObjects(TObject* object, TObject* referenceObject, std::string& message) +{ + TH1* histogram = nullptr; + TH1* referenceHistogram = nullptr; + + if (!object || !referenceObject) { + message = "missing objects"; + return std::make_tuple(histogram, referenceHistogram, false); + } + + // only consider objects that inherit from TH1 + histogram = dynamic_cast(object); + referenceHistogram = dynamic_cast(referenceObject); + + if (!histogram || !referenceHistogram) { + message = "objects are not TH1"; + return std::make_tuple(histogram, referenceHistogram, false); + } + + // the object and the reference must correspond to the same ROOT class + if (histogram->IsA() != referenceHistogram->IsA()) { + message = "incompatible objects"; + return std::make_tuple(histogram, referenceHistogram, false); + } + + if (referenceHistogram->GetEntries() < 1) { + message = "empty reference plot"; + return std::make_tuple(histogram, referenceHistogram, false); + } + + if (histogram->GetNcells() < 3 || histogram->GetNcells() != referenceHistogram->GetNcells()) { + message = "incompatible number of bins"; + return std::make_tuple(histogram, referenceHistogram, false); + } + + return std::make_tuple(histogram, referenceHistogram, true); +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ObjectComparatorKolmogorov.cxx b/Modules/Common/src/ObjectComparatorKolmogorov.cxx new file mode 100644 index 0000000000..3f84763a4a --- /dev/null +++ b/Modules/Common/src/ObjectComparatorKolmogorov.cxx @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ObjectComparatorKolmogorov.cxx +/// \author Andrea Ferrero +/// \brief A class for comparing two histogram objects based on a Kolmogorov test +/// + +#include "Common/ObjectComparatorKolmogorov.h" +// ROOT +#include + +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +Quality ObjectComparatorKolmogorov::compare(TObject* object, TObject* referenceObject, std::string& message) +{ + auto checkResult = checkInputObjects(object, referenceObject, message); + if (!std::get<2>(checkResult)) { + return Quality::Null; + } + + auto* histogram = std::get<0>(checkResult); + auto* referenceHistogram = std::get<1>(checkResult); + + // perform a Kolmogorov compatibility test between the two histograms + // it assumes that both histigrams represent counts, but the reference might + // have been rescaled to match the integral of the current histogram + double testProbability = histogram->KolmogorovTest(referenceHistogram, "UU NORM"); + + // compare the Kolmogorov probability with the minimum allowed value + if (testProbability < getThreshold()) { + message = fmt::format("Kolmogorov test failed: {:.2f} < {:.2f}", testProbability, getThreshold()); + return Quality::Bad; + } + + return Quality::Good; +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/QualityReductor.cxx b/Modules/Common/src/QualityReductor.cxx new file mode 100644 index 0000000000..9a515425a7 --- /dev/null +++ b/Modules/Common/src/QualityReductor.cxx @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityReductor.cxx +/// \author Piotr Konopka +/// + +#include "Common/QualityReductor.h" + +#include "QualityControl/QualityObject.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +void* QualityReductor::getBranchAddress() +{ + return &mQuality; +} + +const char* QualityReductor::getBranchLeafList() +{ + return "level/i:name/C"; +} + +void QualityReductor::update(TObject* obj) +{ + auto qo = dynamic_cast(obj); + if (qo) { + auto quality = qo->getQuality(); + + size_t last = quality.getName().length() < NAME_SIZE ? quality.getName().length() : NAME_SIZE - 1; + strncpy(mQuality.name, quality.getName().c_str(), last + 1); + mQuality.name[last] = '\0'; + + mQuality.level = quality.getLevel(); + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/QualityTask.cxx b/Modules/Common/src/QualityTask.cxx new file mode 100644 index 0000000000..35defa0172 --- /dev/null +++ b/Modules/Common/src/QualityTask.cxx @@ -0,0 +1,347 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityTask.h +/// \author Andrea Ferrero +/// \author Piotr Konopka +/// \brief Post-processing of quality flags +/// + +#include "Common/QualityTask.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include +#include +#include +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::common +{ + +QualityTask::QualityTrendGraph::QualityTrendGraph(std::string name, std::string title) : TCanvas(name.c_str(), title.c_str()) +{ + SetGridy(1); + + mGraph = std::make_unique(0); + mGraph->SetTitle(title.c_str()); + mGraph->SetMarkerStyle(kCircle); + mGraph->SetTitle(fmt::format("{};time;", title).c_str()); + + // add labels in the middle of the vertical axis bins + mLabels[0] = std::make_unique(0.09, 0.1 + 0.8 / 8.0, "Null"); + mLabels[1] = std::make_unique(0.09, 0.1 + 3.0 * 0.8 / 8.0, "Bad"); + mLabels[2] = std::make_unique(0.09, 0.1 + 5.0 * 0.8 / 8.0, "Medium"); + mLabels[3] = std::make_unique(0.09, 0.1 + 7.0 * 0.8 / 8.0, "Good"); + for (auto& l : mLabels) { + l->SetNDC(); + l->SetTextAlign(32); + l->SetTextSize(0.08); + l->SetTextFont(42); + } +} + +//_________________________________________________________________________________________ + +void QualityTask::QualityTrendGraph::update(uint64_t time, Quality q) +{ + Clear(); + cd(); + + // set the position of the point based on the value of the quality flag + float val = 0.5; + if (q == Quality::Bad) { + val = 1.5; + } + if (q == Quality::Medium) { + val = 2.5; + } + if (q == Quality::Good) { + val = 3.5; + } + + // add a new point to the graph + mGraph->AddPoint(time, val); + + // draw the graph and format the axes + mGraph->Draw("APL"); + mGraph->GetXaxis()->SetTimeDisplay(1); + mGraph->GetXaxis()->SetNdivisions(505); + mGraph->GetXaxis()->SetTimeOffset(0.0); + mGraph->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + mGraph->GetYaxis()->SetTickLength(0.0); + mGraph->GetYaxis()->SetLabelSize(0.0); + mGraph->GetYaxis()->SetNdivisions(3, kFALSE); + mGraph->SetMinimum(0); + mGraph->SetMaximum(4); + + // draw labels for vertical axis + for (auto& l : mLabels) { + l->Draw(); + } +} + +std::string QualityTask::QualityTrendGraph::distributionName(const std::string& groupName, const std::string& qualityName) +{ + return "Distributions/" + groupName + "/" + qualityName; +} + +std::string QualityTask::QualityTrendGraph::trendName(const std::string& groupName, const std::string& qualityName) +{ + return "Trends/" + groupName + "/" + qualityName; +} + +static std::string fullQoPath(const std::string& path, const std::string& name) +{ + return path + "/" + name; +} + +static std::string uniqueQoID(const std::string& group, const std::string& fullPath) +{ + return group + "/" + fullPath; +} + +//_________________________________________________________________________________________ + +void QualityTask::configure(const boost::property_tree::ptree& config) +{ + mConfig = QualityTaskConfig(getID(), config); +} + +//_________________________________________________________________________________________ + +static void setQualityLabels(TH1F* h) +{ + TAxis* ax = h->GetXaxis(); + ax->SetBinLabel(1, "Null"); + ax->ChangeLabel(1, 45); + ax->SetBinLabel(2, "Bad"); + ax->ChangeLabel(2, 45); + ax->SetBinLabel(3, "Medium"); + ax->ChangeLabel(3, 45); + ax->SetBinLabel(4, "Good"); + ax->ChangeLabel(4, 45); +} + +//_________________________________________________________________________________________ + +void QualityTask::initialize(quality_control::postprocessing::Trigger t, framework::ServiceRegistryRef services) +{ + mLatestTimestamps.clear(); + mColors.clear(); + mQualityIDs.clear(); + mHistograms.clear(); + mTrends.clear(); + mQualityCanvas.reset(); + + mColors[Quality::Null.getName()] = kViolet - 6; + mColors[Quality::Bad.getName()] = kRed; + mColors[Quality::Medium.getName()] = kOrange - 3; + mColors[Quality::Good.getName()] = kGreen + 2; + mColors["Missing"] = kBlue + 2; + + mQualityIDs[Quality::Null.getName()] = 0; + mQualityIDs[Quality::Bad.getName()] = 1; + mQualityIDs[Quality::Medium.getName()] = 2; + mQualityIDs[Quality::Good.getName()] = 3; + + std::string key("maxObjectAgeSeconds"); + auto value = mCustomParameters.atOptional(key, t.activity); + if (!value) { + value = mCustomParameters.atOptional(key).value_or("600"); + } + mMaxObjectAgeMs = std::stoi(*value) * 1000; + + // instantiate the histograms and trends, one for each of the quality objects in the data sources list + for (const auto& qualityGroupConfig : mConfig.qualityGroups) { + for (const auto& qualityConfig : qualityGroupConfig.inputObjects) { + auto qoID = uniqueQoID(qualityGroupConfig.name, fullQoPath(qualityGroupConfig.path, qualityConfig.name)); + mLatestTimestamps[qoID] = 0; + + auto distributionName = QualityTrendGraph::distributionName(qualityGroupConfig.name, qualityConfig.name); + auto distributionTitle = qualityConfig.title.empty() ? qualityConfig.name : qualityConfig.title; + auto distrIter = mHistograms.emplace(std::make_pair(distributionName, std::make_unique(distributionName.c_str(), distributionTitle.c_str(), 4, 0, 4))); + setQualityLabels(distrIter.first->second.get()); + getObjectsManager()->startPublishing(distrIter.first->second.get(), PublicationPolicy::ThroughStop); + + auto trendName = QualityTrendGraph::trendName(qualityGroupConfig.name, qualityConfig.name); + auto trendTitle = qualityConfig.title.empty() ? qualityConfig.name : qualityConfig.title; + auto iter2 = mTrends.emplace(std::make_pair(trendName, std::make_unique(trendName.c_str(), trendTitle))); + getObjectsManager()->startPublishing(iter2.first->second.get(), PublicationPolicy::ThroughStop); + getObjectsManager()->setDisplayHint(iter2.first->second.get(), "gridy"); + } + } + + // canvas for the human-readable messages + mQualityCanvas = std::make_unique("QualitySummary", "Quality Summary", 800, 600); + getObjectsManager()->startPublishing(mQualityCanvas.get(), PublicationPolicy::ThroughStop); +} + +//_________________________________________________________________________________________ +// Helper function for retrieving a QualityObject from the QCDB, in the form of a std::pair, bool> +// A non-null QO is returned in the first element of the pair if the QO is found in the QCDB +// The second element of the pair is set to true if the QO has a time stamp more recent than the last retrieved one + +std::pair, bool> QualityTask::getLatestQO( + repository::DatabaseInterface& qcdb, const Activity& activity, const std::string& fullPath, const std::string& group) +{ + // retrieve QO from CCDB + auto qo = qcdb.retrieveQO(fullPath, repository::DatabaseInterface::Timestamp::Latest, activity); + if (!qo) { + return { nullptr, false }; + } + // get the MO creation time stamp + long thisTimestamp = (qo->getMetadataMap().count(repository::metadata_keys::created) > 0) ? std::stol(qo->getMetadataMap().at(repository::metadata_keys::created)) : 0; + + // check if the object is not older than a given number of milliseconds + if (mMaxObjectAgeMs > 0) { + uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + auto elapsed = now - thisTimestamp; + ILOG(Info, Devel) << "Quality Object '" << fullPath << "' for activity " << activity << " was created " << elapsed << " ms in the past" << ENDM; + if (elapsed > mMaxObjectAgeMs) { + ILOG(Warning, Support) << "Quality Object '" << fullPath << "' for activity " << activity << " is too old: " << elapsed << " > " << mMaxObjectAgeMs << " ms" << ENDM; + return { nullptr, false }; + } + } + + // check if the object is newer than the last visited one + auto qoID = uniqueQoID(group, fullPath); + auto lastTimestamp = mLatestTimestamps[qoID]; + if (thisTimestamp <= lastTimestamp) { + return { qo, false }; + } + + // update the time stamp of the last visited object + mLatestTimestamps[qoID] = thisTimestamp; + + return { qo, true }; +} + +//_________________________________________________________________________________________ + +void QualityTask::update(quality_control::postprocessing::Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + struct Separator { + }; + struct TextAlign { + Short_t value; + }; + struct Message { + std::string text; + }; + std::vector> lines; + + for (const auto& qualityGroupConfig : mConfig.qualityGroups) { + if (!qualityGroupConfig.title.empty()) { + lines.emplace_back(Message{ qualityGroupConfig.title }); + lines.emplace_back(TextAlign{ 22 }); + } + + for (const auto& qualityConfig : qualityGroupConfig.inputObjects) { + auto fullPath = fullQoPath(qualityGroupConfig.path, qualityConfig.name); + auto& qualityTitle = qualityConfig.title.empty() ? qualityConfig.name : qualityConfig.title; + // retrieve QO from CCDB, in the form of a std::pair, bool> + // a valid object is returned in the first element of the pair if the QO is found in the QCDB + // the second element of the pair is set to true if the QO has a time stamp more recent than the last retrieved one + auto [qo, wasUpdated] = getLatestQO(qcdb, t.activity, fullPath, qualityGroupConfig.name); + if (!qo) { + lines.emplace_back(Message{ fmt::format("#color[{}]{{{} : quality missing!}}", mColors["Missing"], qualityTitle) }); + lines.emplace_back(TextAlign{ 12 }); + continue; + } + + const auto& quality = qo->getQuality(); + std::string qoName = qo->getName(); + std::string qoValue = quality.getName(); + int qID = mQualityIDs[qoValue]; + lines.emplace_back(Message{ fmt::format("#color[{}]{{{} : {}}}", mColors[qoValue], qualityTitle, qoValue) }); + lines.emplace_back(TextAlign{ 12 }); + + if (auto& message = qualityConfig.messages.at(qoValue); !message.empty()) { + lines.emplace_back(Message{ fmt::format("#color[{}]{{{}}}", mColors[qoValue], message) }); + lines.emplace_back(TextAlign{ 12 }); + } + + if (std::find(qualityGroupConfig.ignoreQualitiesDetails.begin(), qualityGroupConfig.ignoreQualitiesDetails.end(), quality) == qualityGroupConfig.ignoreQualitiesDetails.end()) { + for (const auto& [flag, comment] : qo->getFlags()) { + if (comment.empty()) { + lines.emplace_back(Message{ fmt::format("#color[{}]{{#rightarrow Flag: {}}}", kGray + 2, flag.getName()) }); + } else { + lines.emplace_back(Message{ fmt::format("#color[{}]{{#rightarrow Flag: {}: {}}}", kGray + 2, flag.getName(), comment) }); + } + lines.emplace_back(TextAlign{ 12 }); + } + } + + // only update plots/trends when the objects is wasUpdated + if (wasUpdated) { + // fill quality histogram + auto distributionName = QualityTrendGraph::distributionName(qualityGroupConfig.name, qualityConfig.name); + auto distroIter = mHistograms.find(distributionName); + if (distroIter != mHistograms.end()) { + distroIter->second->Fill(qID + 0.5); + } + + // add point in quality trending plot + auto trendName = QualityTrendGraph::trendName(qualityGroupConfig.name, qualityConfig.name); + auto trendIter = mTrends.find(trendName); + if (trendIter != mTrends.end()) { + auto qoID = uniqueQoID(qualityGroupConfig.name, fullPath); + auto timestampSeconds = mLatestTimestamps[qoID] / 1000; // ROOT expects seconds since epoch + trendIter->second->update(timestampSeconds, qo->getQuality()); + } + } + } + lines.emplace_back(Separator{}); + } + lines.pop_back(); // remove the last separator + + // draw lines in canvas + mQualityCanvas->Clear(); + mQualityCanvas->cd(1); + + auto* msg = new TPaveText(0.05, 0.05, 0.95, 0.95, "NDC"); + msg->SetLineColor(kBlack); + msg->SetFillColor(kWhite); + msg->SetBorderSize(0); + + for (const auto& line : lines) { + if (std::holds_alternative(line)) { + msg->AddText(std::get(line).text.c_str()); + } else if (std::holds_alternative(line)) { + msg->AddLine(); + ((TLine*)msg->GetListOfLines()->Last())->SetLineWidth(3); + ((TLine*)msg->GetListOfLines()->Last())->SetLineStyle(9); + ((TLine*)msg->GetListOfLines()->Last())->SetLineColor(kBlack); + } else if (std::holds_alternative(line)) { + ((TText*)msg->GetListOfLines()->Last())->SetTextAlign(std::get(line).value); + } + } + + msg->Draw(); +} + +//_________________________________________________________________________________________ + +void QualityTask::finalize(quality_control::postprocessing::Trigger t, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/QualityTaskConfig.cxx b/Modules/Common/src/QualityTaskConfig.cxx new file mode 100644 index 0000000000..027bc1b138 --- /dev/null +++ b/Modules/Common/src/QualityTaskConfig.cxx @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityTaskConfig.cxx +/// \author Andrea Ferrero +/// \author Piotr Konopka +/// + +#include "Common/QualityTaskConfig.h" +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::common +{ + +QualityTaskConfig::QualityTaskConfig(std::string name, const boost::property_tree::ptree& config) + : PostProcessingConfig(name, config) +{ + for (const auto& qualityGroupConfig : config.get_child("qc.postprocessing." + name + ".qualityGroups")) { + QualityGroup qualityGroup{ + qualityGroupConfig.second.get("name"), + qualityGroupConfig.second.get("title", ""), + qualityGroupConfig.second.get("path"), + }; + if (const auto& ignoredQualities = qualityGroupConfig.second.get_child_optional("ignoreQualitiesDetails"); ignoredQualities.has_value()) { + for (const auto& ignoredQuality : ignoredQualities.value()) { + qualityGroup.ignoreQualitiesDetails.emplace_back(Quality::fromString(ignoredQuality.second.get_value())); + } + } + + if (const auto& inputObjects = qualityGroupConfig.second.get_child_optional("inputObjects"); inputObjects.has_value()) { + for (const auto& inputObject : inputObjects.value()) { + QualityConfig qualityConfig{ + inputObject.second.get("name"), + inputObject.second.get("title", "") + }; + qualityConfig.messages[Quality::Good.getName()] = inputObject.second.get("messageGood", ""); + qualityConfig.messages[Quality::Medium.getName()] = inputObject.second.get("messageMedium", ""); + qualityConfig.messages[Quality::Bad.getName()] = inputObject.second.get("messageBad", ""); + qualityConfig.messages[Quality::Null.getName()] = inputObject.second.get("messageNull", ""); + qualityGroup.inputObjects.emplace_back(std::move(qualityConfig)); + } + } + + qualityGroups.emplace_back(std::move(qualityGroup)); + } +} + +} // namespace o2::quality_control_modules::common \ No newline at end of file diff --git a/Modules/Common/src/ReferenceComparatorCheck.cxx b/Modules/Common/src/ReferenceComparatorCheck.cxx new file mode 100644 index 0000000000..d582403c44 --- /dev/null +++ b/Modules/Common/src/ReferenceComparatorCheck.cxx @@ -0,0 +1,340 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorCheck.cxx +/// \author Andrea Ferrero +/// + +#include "Common/ReferenceComparatorCheck.h" +#include "QualityControl/ReferenceUtils.h" +#include "Common/TH1Ratio.h" +#include "Common/Utils.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/RootClassFactory.h" + +#include +#include + +#include + +#include + +// ROOT +#include +#include +#include +#include +#include + +using namespace o2::quality_control; + +namespace o2::quality_control_modules::common +{ + +void ReferenceComparatorCheck::configure() +{ +} + +void ReferenceComparatorCheck::startOfActivity(const Activity& activity) +{ + mComparatorModuleName = getFromExtendedConfig(activity, mCustomParameters, "moduleName", ""); + mComparatorClassName = getFromExtendedConfig(activity, mCustomParameters, "comparatorName", ""); + mReferenceRun = getFromExtendedConfig(activity, mCustomParameters, "referenceRun", 0); + mIgnorePeriodForReference = getFromExtendedConfig(activity, mCustomParameters, "ignorePeriodForReference", true); + mIgnorePassForReference = getFromExtendedConfig(activity, mCustomParameters, "ignorePassForReference", true); + mRatioPlotRange = getFromExtendedConfig(activity, mCustomParameters, "ratioPlotRange", mRatioPlotRange); + + mActivity = activity; + + mReferenceActivity = activity; + mReferenceActivity.mId = mReferenceRun; + if (mIgnorePeriodForReference) { + mReferenceActivity.mPeriodName = ""; + } + if (mIgnorePassForReference) { + mReferenceActivity.mPassName = ""; + } + + // clear the cache of comparators and reference plots + mComparators.clear(); + mReferencePlots.clear(); +} + +void ReferenceComparatorCheck::endOfActivity(const Activity& activity) +{ +} + +static std::pair getPlotsFromCanvas(TCanvas* canvas) +{ + std::string dummyMessage; + return o2::quality_control::checker::getPlotsFromCanvas(canvas, dummyMessage); +} + +// Get the current and reference histograms from the canvas, and compare them using the comparator object passed as parameter +static Quality compare(TCanvas* canvas, ObjectComparatorInterface* comparator, std::string& message) +{ + if (!canvas) { + message = "missing canvas"; + return Quality::Null; + } + if (!comparator) { + message = "missing comparator"; + return Quality::Null; + } + + // extract the histograms from the canvas + auto plots = o2::quality_control::checker::getPlotsFromCanvas(canvas, message); + if (!plots.first || !plots.second) { + return Quality::Null; + } + + // Return the result of the comparison. Details are stored in the "message" string. + return comparator->compare(plots.first, plots.second, message); +} + +Quality ReferenceComparatorCheck::getSinglePlotQuality(std::shared_ptr mo, ObjectComparatorInterface* comparator, std::string& message) +{ + // retrieve the reference plot and compare + auto* th1 = dynamic_cast(mo->getObject()); + if (th1 == nullptr) { + message = "The MonitorObject is not a TH1"; + return Quality::Null; + } + if (!comparator) { + message = "missing comparator"; + return Quality::Null; + } + + // get path of mo and ref (we have to remove the provenance) + std::string path = RepoPathUtils::getPathNoProvenance(mo); + + if (mReferenceRun == 0) { + message = "No reference run provided"; + return Quality::Null; + } + + // retrieve the reference plot only once and cache it for later use + if (mReferencePlots.count(path) == 0) { + mReferencePlots[path] = retrieveReference(path, mReferenceActivity); + } + + auto referencePlot = mReferencePlots[path]; + if (!referencePlot) { + message = "Reference plot not found"; + return Quality::Null; + } + auto* ref = dynamic_cast(referencePlot->getObject()); + if (!ref) { + message = "The reference plot is not a TH1"; + return Quality::Null; + } + return comparator->compare(th1, ref, message); +} + +static std::string getBaseName(std::string name) +{ + auto pos = name.rfind("/"); + return ((pos < std::string::npos) ? name.substr(pos + 1) : name); +} + +//_________________________________________________________________________________________ +// +// Loop over all the input MOs and compare each of them with the corresponding MO from the reference run +// The final quality is the combination of the individual values stored in the mQualityFlags map + +Quality ReferenceComparatorCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [key, mo] : *moMap) { + auto moName = mo->getName(); + auto moKey = getBaseName(moName); + Quality quality; + std::string message; + + if (!mComparatorModuleName.empty() && !mComparatorClassName.empty() && mComparators.count(moKey) == 0) { + mComparators[moKey].reset(root_class_factory::create(mComparatorModuleName, mComparatorClassName)); + if (mComparators[moKey]) { + mComparators[moKey]->configure(mCustomParameters, moKey, mActivity); + } + } + ObjectComparatorInterface* comparator = mComparators[moKey].get(); + if (!comparator) { + continue; + } + + // run the comparison algorithm + if (mo->getObject()->IsA() == TClass::GetClass()) { + // We got a canvas. It contains the plot and its reference. + auto* canvas = dynamic_cast(mo->getObject()); + quality = compare(canvas, comparator, message); + } else if (mo->getObject()->InheritsFrom(TH1::Class())) { + // We got a plot, we have to find the reference before calling the comparator + quality = getSinglePlotQuality(mo, comparator, message); + } else { + ILOG(Warning, Ops) << "Compared Monitor Object '" << mo->getName() << "' is not a TCanvas or a TH1, the detector QC responsible should review the configuration" << ENDM; + continue; + } + + // initialize or update the overall quality + if (result == Quality::Null || quality.isWorseThan(result)) { + result.set(quality); + } + + // add the message if not empty + if (!message.empty()) { + quality.addFlag(FlagTypeFactory::Unknown(), fmt::format("{} {}", moName, message)); + result.addFlag(FlagTypeFactory::Unknown(), fmt::format("{} {}", moName, message)); + } + + // store the quality associated to this MO + // will be used to beautify the corresponding plots + mQualityFlags[moName] = quality; + } + + return result; +} + +void ReferenceComparatorCheck::reset() +{ + mQualityFlags.clear(); +} + +// return the ROOT color index associated to a give quality level +static int getQualityColor(const Quality& q) +{ + if (q == Quality::Null) + return kViolet - 6; + if (q == Quality::Bad) + return kRed; + if (q == Quality::Medium) + return kOrange - 3; + if (q == Quality::Good) + return kGreen + 2; + + return 0; +} + +static void updateQualityLabel(TPaveText* label, const Quality& quality) +{ + // draw the quality label with the text color corresponding to the quality level + label->SetTextColor(getQualityColor(quality)); + label->AddText(quality.getName().c_str()); + + // add the first flag below the quality label, or an empty line if no flags are set + auto flags = quality.getFlags(); + std::string message = flags.empty() ? "" : flags.front().second; + auto pos = message.find(" "); + if (pos != std::string::npos) { + message.erase(0, pos + 1); + } + auto* text = label->AddText(message.c_str()); + text->SetTextColor(kGray + 1); +} + +// Write the quality level and flags in the existing PaveText inside the canvas +static void setQualityLabel(TCanvas* canvas, const Quality& quality) +{ + if (!canvas) { + return; + } + + canvas->cd(); + + // search for the label to show the quality status + TIter next(canvas->GetListOfPrimitives()); + TObject* obj; + while ((obj = next())) { + auto* label = dynamic_cast(obj); + if (!label) { + continue; + } + + updateQualityLabel(label, quality); + break; + } +} + +void ReferenceComparatorCheck::beautifyRatioPlot(const std::string& moName, TH1* ratioPlot, const Quality& quality) +{ + // currently the range indicator is only implemented for 1-D histograms + if (!ratioPlot || ratioPlot->InheritsFrom("TH2")) { + return; + } + + // set vertical range according to preferences + if (mRatioPlotRange > 0) { + ratioPlot->SetMinimum(1.0 - mRatioPlotRange); + ratioPlot->SetMaximum(1.0 + mRatioPlotRange); + } + + // get the comparator associated to this plot + ObjectComparatorInterface* comparator{ nullptr }; + auto moKey = getBaseName(moName); + if (mComparators.count(moKey) > 0) { + comparator = mComparators[moKey].get(); + } + if (!comparator) { + return; + } + + // get the X-axis range, if defined + auto rangeX = comparator->getXRange(); + if (!rangeX) { + return; + } + + // draw an horizontal double arrow marking the X-axis range + double xMin = ratioPlot->GetXaxis()->GetBinLowEdge(ratioPlot->GetXaxis()->FindBin(rangeX->first)); + double xMax = ratioPlot->GetXaxis()->GetBinUpEdge(ratioPlot->GetXaxis()->FindBin(rangeX->second - 1.0e-6)); + double yMin = ratioPlot->GetMinimum(); + double yMax = ratioPlot->GetMaximum(); + double yArrow = yMin + 0.1 * (yMax - yMin); + auto arrow = new TArrow(xMin, yArrow, xMax, yArrow, 0.015, "<|>"); + arrow->SetLineColor(getQualityColor(quality)); + arrow->SetFillColor(getQualityColor(quality)); + ratioPlot->GetListOfFunctions()->Add(arrow); +} + +void ReferenceComparatorCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + // get the quality associated to the current MO + auto moName = mo->getName(); + auto quality = mQualityFlags[moName]; + + auto* canvas = dynamic_cast(mo->getObject()); + if (canvas) { + // retrieve the reference plot from the canvas and set the line color according to the quality + auto plots = getPlotsFromCanvas(canvas); + if (plots.second) { + plots.second->SetLineColor(getQualityColor(quality)); + } + + // draw the quality label on the plot + setQualityLabel(canvas, quality); + + // draw a double-arrow indicating the horizontal range for the check, if it is set + beautifyRatioPlot(moName, o2::quality_control::checker::getRatioPlotFromCanvas(canvas), quality); + } else { + // draw the quality label directly on the plot if the MO is an histogram + auto* th1 = dynamic_cast(mo->getObject()); + if (th1) { + auto* qualityLabel = new TPaveText(0.75, 0.65, 0.98, 0.75, "brNDC"); + updateQualityLabel(qualityLabel, quality); + th1->GetListOfFunctions()->Add(qualityLabel); + } + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ReferenceComparatorPlot.cxx b/Modules/Common/src/ReferenceComparatorPlot.cxx new file mode 100644 index 0000000000..111cc89dbe --- /dev/null +++ b/Modules/Common/src/ReferenceComparatorPlot.cxx @@ -0,0 +1,587 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorPlot.xx +/// \author Andrea Ferrero +/// \brief Utility class for the combined drawing of the current and reference plots, and their ratio +/// + +#include "Common/ReferenceComparatorPlot.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::common +{ + +static bool isBinningIdentical(TH1* h1, TH1* h2) +{ + // check consistency of X-axis binning + if (h1->GetXaxis()->GetNbins() != h2->GetXaxis()->GetNbins()) { + return false; + } + if (h1->GetXaxis()->GetXmin() != h2->GetXaxis()->GetXmin()) { + return false; + } + if (h1->GetXaxis()->GetXmax() != h2->GetXaxis()->GetXmax()) { + return false; + } + + // check consistency of Y-axis binning + if (h1->GetYaxis()->GetNbins() != h2->GetYaxis()->GetNbins()) { + return false; + } + if (h1->GetYaxis()->GetXmin() != h2->GetYaxis()->GetXmin()) { + return false; + } + if (h1->GetYaxis()->GetXmax() != h2->GetYaxis()->GetXmax()) { + return false; + } + + // check consistency of Z-axis binning + if (h1->GetZaxis()->GetNbins() != h2->GetZaxis()->GetNbins()) { + return false; + } + if (h1->GetZaxis()->GetXmin() != h2->GetZaxis()->GetXmin()) { + return false; + } + if (h1->GetZaxis()->GetXmax() != h2->GetZaxis()->GetXmax()) { + return false; + } + + return true; +} + +//_________________________________________________________________________________________ + +static void copyAndScaleHistograms(TH1* histogram, TH1* referenceHistogram, TH1* outputHisto, TH1* outputRefHisto, bool scaleReference) +{ + if (!histogram || !referenceHistogram || !outputHisto || !outputRefHisto) { + ILOG(Warning, Devel) << "histogram is nullptr" << ENDM; + return; + } + + if (!isBinningIdentical(histogram, referenceHistogram)) { + ILOG(Warning, Devel) << "mismatch in axis dimensions for '" << histogram->GetName() << "'" << ENDM; + return; + } + + outputHisto->Reset(); + outputHisto->Add(histogram); + + outputRefHisto->Reset(); + outputRefHisto->Add(referenceHistogram); + + if (scaleReference) { + // the reference histogram is scaled to match the integral of the current histogram + double integral = histogram->Integral(); + double integralRef = referenceHistogram->Integral(); + if (integral != 0 && integralRef != 0) { + outputRefHisto->Scale(integral / integralRef); + } + } +} + +template +static std::shared_ptr createHisto1D(const char* name, const char* title, TH1* source) +{ + std::shared_ptr result; + if (source->GetXaxis()->IsVariableBinSize()) { + result = std::make_shared(name, title, + source->GetXaxis()->GetNbins(), + source->GetXaxis()->GetXbins()->GetArray()); + } else { + result = std::make_shared(name, title, + source->GetXaxis()->GetNbins(), + source->GetXaxis()->GetXmin(), + source->GetXaxis()->GetXmax()); + } + + return result; +} + +template +static std::shared_ptr createHisto2D(const char* name, const char* title, TH1* source) +{ + std::shared_ptr result; + if (source->GetXaxis()->IsVariableBinSize() && source->GetYaxis()->IsVariableBinSize()) { + result = std::make_shared(name, title, + source->GetXaxis()->GetNbins(), + source->GetXaxis()->GetXbins()->GetArray(), + source->GetYaxis()->GetNbins(), + source->GetYaxis()->GetXbins()->GetArray()); + } else if (source->GetXaxis()->IsVariableBinSize() && !source->GetYaxis()->IsVariableBinSize()) { + result = std::make_shared(name, title, + source->GetXaxis()->GetNbins(), + source->GetXaxis()->GetXbins()->GetArray(), + source->GetYaxis()->GetNbins(), + source->GetYaxis()->GetXmin(), + source->GetYaxis()->GetXmax()); + } else if (!source->GetXaxis()->IsVariableBinSize() && source->GetYaxis()->IsVariableBinSize()) { + result = std::make_shared(name, title, + source->GetXaxis()->GetNbins(), + source->GetXaxis()->GetXmin(), + source->GetXaxis()->GetXmax(), + source->GetYaxis()->GetNbins(), + source->GetYaxis()->GetXbins()->GetArray()); + } else { + result = std::make_shared(name, title, + source->GetXaxis()->GetNbins(), + source->GetXaxis()->GetXmin(), + source->GetXaxis()->GetXmax(), + source->GetYaxis()->GetNbins(), + source->GetYaxis()->GetXmin(), + source->GetYaxis()->GetXmax()); + } + + return result; +} + +//_________________________________________________________________________________________ + +class ReferenceComparatorPlotImpl +{ + public: + ReferenceComparatorPlotImpl(TH1* referenceHistogram, bool scaleReference) + : mReferenceHistogram(referenceHistogram), mScaleReference(scaleReference) + { + } + + virtual ~ReferenceComparatorPlotImpl() = default; + + virtual TObject* getMainCanvas() + { + return nullptr; + } + + TH1* getReferenceHistogram() + { + return mReferenceHistogram; + } + + void setScaleRef(bool scaleReference) + { + mScaleReference = scaleReference; + } + + bool getScaleReference() { return mScaleReference; } + + virtual void update(TH1* histogram) = 0; + + private: + TH1* mReferenceHistogram{ nullptr }; + bool mScaleReference{ true }; +}; + +template +class ReferenceComparatorPlotImpl1D : public ReferenceComparatorPlotImpl +{ + public: + ReferenceComparatorPlotImpl1D(TH1* referenceHistogram, + int referenceRun, + const std::string& outputPath, + bool scaleReference, + bool drawRatioOnly, + double legendHeight, + bool logScale, + const std::string& drawOption) + : ReferenceComparatorPlotImpl(referenceHistogram, scaleReference), mLegendHeight(legendHeight), mLogScale(logScale) + { + float labelSize = 0.04; + + if (!referenceHistogram) { + return; + } + + // full name of the main canvas + std::string canvasName = outputPath; + mCanvas = std::make_shared(canvasName.c_str(), canvasName.c_str(), 800, 600); + + // Pad where the histogram ratio is drawn. If drawRatioOnly is true the pad is placed on top + // without any transparency, and fully covers the other pad + mCanvas->cd(); + if (drawRatioOnly) { + mPadHistRatio = std::make_shared((canvasName + "_PadHistRatio").c_str(), "PadHistRatio", 0, 0, 1, 1); + } else { + mPadHistRatio = std::make_shared((canvasName + "_PadHistRatio").c_str(), "PadHistRatio", 0, 1.0 / 3, 1, 1); + mPadHistRatio->SetTopMargin(0.15); + mPadHistRatio->SetBottomMargin(0.5); + mPadHistRatio->SetLeftMargin(0.1); + mPadHistRatio->SetRightMargin(0.1); + mPadHistRatio->SetGridy(); + } + + // Pad where the histograms are drawn. If drawRatioOnly is true the pad is placed hidden + // behind the second pad where the ratio histogram is drawn + mCanvas->cd(); + if (drawRatioOnly) { + mPadHist = std::make_shared((canvasName + "_PadHist").c_str(), "PadHist", 0.2, 0.2, 0.8, 0.8); + } else { + mPadHist = std::make_shared((canvasName + "_PadHist").c_str(), "PadHist", 0, 0, 1, 2.0 / 3 - 0.01); + mPadHist->SetTopMargin(0); + mPadHist->SetBottomMargin(0.15); + mPadHist->SetLeftMargin(0.1); + mPadHist->SetRightMargin(0.1); + } + + if (drawRatioOnly) { + // If drawRatioOnly is true the pad with the ratio plot is placed on top + mCanvas->cd(); + mPadHist->Draw(); + mCanvas->cd(); + mPadHistRatio->Draw(); + } else { + // otherwise the pad with the superimposed histograms goes on top + mCanvas->cd(); + mPadHistRatio->Draw(); + mCanvas->cd(); + mPadHist->Draw(); + } + + // histogram from the current run + mPadHist->cd(); + mPlot = createHisto1D((canvasName + "_hist").c_str(), "", referenceHistogram); + mPlot->GetXaxis()->SetTitle(referenceHistogram->GetXaxis()->GetTitle()); + mPlot->GetXaxis()->SetLabelSize(labelSize); + mPlot->GetXaxis()->SetTitleSize(labelSize); + mPlot->GetXaxis()->SetTitleOffset(1.0); + mPlot->GetYaxis()->SetTitle(referenceHistogram->GetYaxis()->GetTitle()); + mPlot->GetYaxis()->SetLabelSize(labelSize); + mPlot->GetYaxis()->SetTitleSize(labelSize); + mPlot->GetYaxis()->SetTitleOffset(1.0); + mPlot->SetLineColor(kBlack); + mPlot->SetStats(0); + mPlot->SetOption(drawOption.c_str()); + mPlot->Draw(drawOption.c_str()); + + // histogram from the reference run + mReferencePlot = createHisto1D((canvasName + "_hist_ref").c_str(), "", referenceHistogram); + mReferencePlot->SetLineColor(kBlue); + mReferencePlot->SetOption((drawOption + "SAME").c_str()); + mReferencePlot->Draw((drawOption + "SAME").c_str()); + + if (!drawRatioOnly && mLegendHeight > 0) { + mHistogramLegend = std::make_shared(0.2, 0.91, 0.8, 0.98); + mHistogramLegend->SetNColumns(2); + mHistogramLegend->SetBorderSize(0); + mHistogramLegend->SetFillStyle(0); + mHistogramLegend->AddEntry(mPlot.get(), "current run", "l"); + mHistogramLegend->AddEntry(mReferencePlot.get(), TString::Format("reference run %d", referenceRun), "l"); + mHistogramLegend->Draw(); + } + + // histogram with current/reference ratio + mPadHistRatio->cd(); + mRatioPlot = createHisto1D((canvasName + "_hist_ratio").c_str(), "", referenceHistogram); + if (drawRatioOnly) { + mRatioPlot->SetTitle(TString::Format("%s (ref. %d)", referenceHistogram->GetTitle(), referenceRun)); + mRatioPlot->GetXaxis()->SetTitle(referenceHistogram->GetXaxis()->GetTitle()); + mRatioPlot->GetYaxis()->SetTitle("current / reference"); + } else { + // hide the X axis labels + mRatioPlot->GetXaxis()->SetLabelSize(0); + mRatioPlot->GetXaxis()->SetTitleSize(0); + mRatioPlot->GetYaxis()->SetTitle("ratio"); + mRatioPlot->GetYaxis()->CenterTitle(kTRUE); + mRatioPlot->GetYaxis()->SetNdivisions(5); + mRatioPlot->GetYaxis()->SetLabelSize(labelSize); + mRatioPlot->GetYaxis()->SetTitleSize(labelSize); + mRatioPlot->GetYaxis()->SetTitleOffset(1.0); + } + mRatioPlot->SetLineColor(kBlack); + mRatioPlot->SetStats(0); + mRatioPlot->SetOption(drawOption.c_str()); + mRatioPlot->Draw(drawOption.c_str()); + if (drawRatioOnly) { + mRatioPlot->SetMinimum(0); + mRatioPlot->SetMaximum(2); + } else { + // set the minimum and maximum sligtly above 0 and below 2.0, such that the first and last bin labels are not shown + mRatioPlot->SetMinimum(0.001); + mRatioPlot->SetMaximum(1.999); + } + + mCanvas->cd(); + // We place an empty TPaveText in the good place, it will be used by the checker + // to draw the quality labels and flags + mQualityLabel = std::make_shared(0.00, 0.9, 0.9, 0.98, "brNDC"); + mQualityLabel->SetBorderSize(0); + mQualityLabel->SetFillStyle(0); + mQualityLabel->SetTextAlign(12); + mQualityLabel->SetTextFont(42); + mQualityLabel->Draw(); + + if (!drawRatioOnly) { + // draw the histogram title + mHistogramTitle = std::make_shared(0.1, 0.94, 0.9, 1.0, "brNDC"); + mHistogramTitle->SetBorderSize(0); + mHistogramTitle->SetFillStyle(0); + mHistogramTitle->SetTextAlign(22); + mHistogramTitle->SetTextFont(42); + mHistogramTitle->AddText(TString::Format("%s (ref. %d)", referenceHistogram->GetTitle(), referenceRun)); + mHistogramTitle->Draw(); + } + } + + TObject* getMainCanvas() + { + return mCanvas.get(); + } + + void update(TH1* hist) + { + TH1* referenceHistogram = getReferenceHistogram(); + if (!hist || !referenceHistogram) { + return; + } + + copyAndScaleHistograms(hist, referenceHistogram, mPlot.get(), mReferencePlot.get(), getScaleReference()); + + double max = std::max(mPlot->GetMaximum(), mReferencePlot->GetMaximum()); + double histMax = (mLegendHeight > 0) ? (1.0 + mLegendHeight) * max : 1.05 * max; + mPlot->SetMaximum(histMax); + mReferencePlot->SetMaximum(histMax); + if (mPadHist) { + mPadHist->SetLogy(mLogScale ? kTRUE : kFALSE); + } + + mRatioPlot->Reset(); + mRatioPlot->Add(mPlot.get()); + mRatioPlot->Divide(mReferencePlot.get()); + mRatioPlot->SetMinimum(0.001); + mRatioPlot->SetMaximum(1.999); + } + + private: + std::shared_ptr mCanvas; + std::shared_ptr mPadHist; + std::shared_ptr mPadHistRatio; + std::shared_ptr mPlot; + std::shared_ptr mReferencePlot; + std::shared_ptr mRatioPlot; + std::shared_ptr mBorderTop; + std::shared_ptr mBorderMiddle; + std::shared_ptr mBorderRight; + std::shared_ptr mQualityLabel; + std::shared_ptr mHistogramTitle; + std::shared_ptr mHistogramLegend; + double mLegendHeight; + bool mLogScale; +}; + +template +class ReferenceComparatorPlotImpl2D : public ReferenceComparatorPlotImpl +{ + public: + ReferenceComparatorPlotImpl2D(TH1* referenceHistogram, + int referenceRun, + const std::string& outputPath, + bool scaleReference, + bool drawRatioOnly, + bool logScale, + const std::string& drawOption) + : ReferenceComparatorPlotImpl(referenceHistogram, scaleReference), mLogScale(logScale) + { + if (!referenceHistogram) { + return; + } + + // full name of the main canvas + std::string canvasName = outputPath; + mCanvas = std::make_shared(canvasName.c_str(), canvasName.c_str(), 800, 600); + + // Size of the pad for the ratio histogram, relative to the canvas size + // Only used when drawRatioOnly is false, and both the ratio and the individual histograms are drawn + const float padSizeRatio = 2.0 / 3; + + // Pad where the current histogram is drawn. If drawRatioOnly is true the pad is draw hidden + // behind the main pad where the ratio histogram is drawn + mCanvas->cd(); + if (!drawRatioOnly) { + // the pad occupies a bottom-left portion of the canvas with a size equal to (1-padSizeRatio) + mPadHist = std::make_shared((canvasName + "_PadHist").c_str(), "PadHist", 0, 0, 0.5, 1.0 - padSizeRatio); + } else { + // hide the pad below the one with the ratio plot + mPadHist = std::make_shared((canvasName + "_PadHist").c_str(), "PadHist", 0.1, 2.0 / 3, 0.5, 0.9); + } + mPadHist->Draw(); + + // Pad where the reference histogram is drawn. If drawRatioOnly is true the pad is draw hidden + // behind the main pad where the ratio histogram is drawn + mCanvas->cd(); + if (!drawRatioOnly) { + // the pad occupies a bottom-right portion of the canvas with a size equal to (1-padSizeRatio) + mPadHistRef = std::make_shared((canvasName + "_PadHistRef").c_str(), "PadHistRef", 0.5, 0, 1, 1.0 - padSizeRatio); + } else { + // hide the pad below the one with thenratio plot + mPadHistRef = std::make_shared((canvasName + "_PadHistRef").c_str(), "PadHistRef", 0.5, 2.0 / 3, 0.9, 0.9); + } + mPadHistRef->Draw(); + + // Pad where the histogram ratio is drawn. If drawRatioOnly is true the pad occupies the full canvas + mCanvas->cd(); + if (!drawRatioOnly) { + // the pad occupies a top portion of the canvas with a size equal to padSizeRatio + mPadHistRatio = std::make_shared((canvasName + "_PadHistRatio").c_str(), "PadHistRatio", 0, 1.0 - padSizeRatio, 1, 1); + // The top margin of the pad is increased in a way inversely proportional to the pad size. + // The resulting margin corresponds to 0.1 in the outer canvas coordinates, such that the histogram + // title is drawn with the usual text size + mPadHistRatio->SetTopMargin(0.1 / padSizeRatio); + } else { + // the pad occupies the full canvas + mPadHistRatio = std::make_shared((canvasName + "_PadHistRatio").c_str(), "PadHistRatio", 0, 0, 1, 1); + } + mPadHistRatio->Draw(); + + // histogram from the current run + mPadHist->cd(); + mPlot = createHisto2D((canvasName + "_hist").c_str(), + referenceHistogram->GetTitle(), + referenceHistogram); + mPlot->GetXaxis()->SetTitle(referenceHistogram->GetXaxis()->GetTitle()); + mPlot->GetYaxis()->SetTitle(referenceHistogram->GetYaxis()->GetTitle()); + mPlot->SetStats(0); + mPlot->SetOption(drawOption.c_str()); + mPlot->Draw(drawOption.c_str()); + + // histogram from the reference run + mPadHistRef->cd(); + mReferencePlot = createHisto2D((canvasName + "_hist_ref").c_str(), + TString::Format("%s (ref. %d)", referenceHistogram->GetTitle(), referenceRun), + referenceHistogram); + mReferencePlot->GetXaxis()->SetTitle(referenceHistogram->GetXaxis()->GetTitle()); + mReferencePlot->GetYaxis()->SetTitle(referenceHistogram->GetYaxis()->GetTitle()); + mReferencePlot->SetStats(0); + mReferencePlot->SetOption(drawOption.c_str()); + mReferencePlot->Draw(drawOption.c_str()); + + // histogram with current/reference ratio + mPadHistRatio->cd(); + mRatioPlot = createHisto2D((canvasName + "_hist_ratio").c_str(), + TString::Format("%s (ratio wrt %d)", referenceHistogram->GetTitle(), referenceRun), + referenceHistogram); + mRatioPlot->GetXaxis()->SetTitle(referenceHistogram->GetXaxis()->GetTitle()); + mRatioPlot->GetYaxis()->SetTitle(referenceHistogram->GetYaxis()->GetTitle()); + if (!drawRatioOnly) { + mRatioPlot->GetZaxis()->SetTitle("ratio"); + } else { + mRatioPlot->GetZaxis()->SetTitle("current / reference"); + } + mRatioPlot->SetStats(0); + mRatioPlot->SetOption("COLZ"); + mRatioPlot->Draw("COLZ"); + mRatioPlot->SetMinimum(0); + mRatioPlot->SetMaximum(2); + + // We place an empty TPaveText in the good place, it will be used by the checker + // to draw the quality labels and flags + mCanvas->cd(); + mQualityLabel = std::make_shared(0.0, 0.9, 0.9, 0.98, "brNDC"); + mQualityLabel->SetBorderSize(0); + mQualityLabel->SetFillStyle(0); + mQualityLabel->SetTextAlign(12); + mQualityLabel->SetTextFont(42); + mQualityLabel->Draw(); + } + + TObject* getMainCanvas() + { + return mCanvas.get(); + } + + void update(TH1* histogram) + { + TH1* referenceHistogram = getReferenceHistogram(); + if (!histogram || !referenceHistogram) { + return; + } + + copyAndScaleHistograms(histogram, referenceHistogram, mPlot.get(), mReferencePlot.get(), getScaleReference()); + + if (mPadHist) { + mPadHist->SetLogz(mLogScale ? kTRUE : kFALSE); + } + if (mPadHistRef) { + mPadHistRef->SetLogz(mLogScale ? kTRUE : kFALSE); + } + + mRatioPlot->Reset(); + mRatioPlot->Add(mPlot.get()); + mRatioPlot->Divide(mReferencePlot.get()); + mRatioPlot->SetMinimum(0); + mRatioPlot->SetMaximum(2); + } + + private: + std::shared_ptr mCanvas; + std::shared_ptr mPadHist; + std::shared_ptr mPadHistRef; + std::shared_ptr mPadHistRatio; + std::shared_ptr mPlot; + std::shared_ptr mReferencePlot; + std::shared_ptr mRatioPlot; + std::shared_ptr mQualityLabel; + bool mLogScale; +}; + +ReferenceComparatorPlot::ReferenceComparatorPlot(TH1* referenceHistogram, + int referenceRun, + const std::string& outputPath, + bool scaleReference, + bool drawRatioOnly, + double legendHeight, + bool logScale, + const std::string& drawOption1D, + const std::string& drawOption2D) +{ + // histograms with integer values are promoted to floating point or double to allow correctly scaling the reference and computing the ratios + + // 1-D histograms + if (referenceHistogram->InheritsFrom("TH1C") || referenceHistogram->InheritsFrom("TH1S") || referenceHistogram->InheritsFrom("TH1F")) { + mImplementation = std::make_shared>(referenceHistogram, referenceRun, outputPath, scaleReference, drawRatioOnly, legendHeight, logScale, drawOption1D); + } + + if (referenceHistogram->InheritsFrom("TH1I") || referenceHistogram->InheritsFrom("TH1D")) { + mImplementation = std::make_shared>(referenceHistogram, referenceRun, outputPath, scaleReference, drawRatioOnly, legendHeight, logScale, drawOption1D); + } + + // 2-D histograms + if (referenceHistogram->InheritsFrom("TH2C") || referenceHistogram->InheritsFrom("TH2S") || referenceHistogram->InheritsFrom("TH2F")) { + mImplementation = std::make_shared>(referenceHistogram, referenceRun, outputPath, scaleReference, drawRatioOnly, logScale, drawOption2D); + } + + if (referenceHistogram->InheritsFrom("TH2I") || referenceHistogram->InheritsFrom("TH2D")) { + mImplementation = std::make_shared>(referenceHistogram, referenceRun, outputPath, scaleReference, drawRatioOnly, logScale, drawOption2D); + } +} + +TObject* ReferenceComparatorPlot::getMainCanvas() +{ + return (mImplementation.get() ? mImplementation->getMainCanvas() : nullptr); +} + +void ReferenceComparatorPlot::update(TH1* histogram) +{ + if (mImplementation) { + mImplementation->update(histogram); + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ReferenceComparatorTask.cxx b/Modules/Common/src/ReferenceComparatorTask.cxx new file mode 100644 index 0000000000..87f515eef6 --- /dev/null +++ b/Modules/Common/src/ReferenceComparatorTask.cxx @@ -0,0 +1,221 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorTask.xx +/// \author Andrea Ferrero +/// \brief Post-processing task that compares a given set of plots with reference ones +/// + +#include "Common/ReferenceComparatorTask.h" +#include "Common/ReferenceComparatorPlot.h" +#include "Common/Utils.h" +#include "QualityControl/ReferenceUtils.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/ActivityHelpers.h" +// ROOT +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::common +{ + +//_________________________________________________________________________________________ +// Helper function for retrieving a MonitorObject from the QCDB, in the form of a std::pair, bool> +// A non-null MO is returned in the first element of the pair if the MO is found in the QCDB +// The second element of the pair is set to true if the MO has a time stamp more recent than a user-supplied threshold + +static std::pair, bool> getMO(repository::DatabaseInterface& qcdb, const std::string& fullPath, Trigger trigger, long notOlderThan) +{ + // find the time-stamp of the most recent object matching the current activity + // if ignoreActivity is true the activity matching criteria are not applied + auto objectTimestamp = trigger.timestamp; + const auto filterMetadata = activity_helpers::asDatabaseMetadata(trigger.activity, false); + const auto objectValidity = qcdb.getLatestObjectValidity(trigger.activity.mProvenance + "/" + fullPath, filterMetadata); + if (objectValidity.isValid()) { + objectTimestamp = objectValidity.getMax() - 1; + } else { + ILOG(Warning, Devel) << "Could not find the object '" << fullPath << "' for activity " << trigger.activity << ENDM; + return { nullptr, false }; + } + + auto [success, path, name] = o2::quality_control::core::RepoPathUtils::splitObjectPath(fullPath); + if (!success) { + return { nullptr, false }; + } + // retrieve QO from CCDB - do not associate to trigger activity if ignoreActivity is true + auto qo = qcdb.retrieveMO(path, name, objectTimestamp, trigger.activity); + if (!qo) { + return { nullptr, false }; + } + + long elapsed = static_cast(trigger.timestamp) - objectTimestamp; + // check if the object is not older than a given number of milliseconds + if (elapsed > notOlderThan) { + ILOG(Warning, Devel) << "Object '" << fullPath << "' for activity " << trigger.activity << " is too old: " << elapsed << " > " << notOlderThan << ENDM; + return { qo, false }; + } + + return { qo, true }; +} + +//_________________________________________________________________________________________ + +void ReferenceComparatorTask::configure(const boost::property_tree::ptree& config) +{ + mConfig = ReferenceComparatorTaskConfig(getID(), config); +} + +//_________________________________________________________________________________________ + +static std::string getCustomParameter(const o2::quality_control::core::CustomParameters& customParameters, const std::string& key, const Activity& activity, const std::string& defaultValue) +{ + std::string value; + auto valueOptional = customParameters.atOptional(key, activity); + if (valueOptional.has_value()) { + value = valueOptional.value(); + } else { + value = customParameters.atOptional(key).value_or(defaultValue); + } + + return value; +} + +//_________________________________________________________________________________________ + +void ReferenceComparatorTask::initialize(quality_control::postprocessing::Trigger trigger, framework::ServiceRegistryRef services) +{ + // reset all existing objects + mPlotNames.clear(); + mReferencePlots.clear(); + mHistograms.clear(); + + auto& qcdb = services.get(); + mNotOlderThan = getFromExtendedConfig(trigger.activity, mCustomParameters, "notOlderThan", 120); + mReferenceRun = getFromExtendedConfig(trigger.activity, mCustomParameters, "referenceRun", 0); + mIgnorePeriodForReference = getFromExtendedConfig(trigger.activity, mCustomParameters, "ignorePeriodForReference", true); + mIgnorePassForReference = getFromExtendedConfig(trigger.activity, mCustomParameters, "ignorePassForReference", true); + + ILOG(Info, Devel) << "Reference run set to '" << mReferenceRun << "' for activity " << trigger.activity << ENDM; + + auto referenceActivity = trigger.activity; + referenceActivity.mId = mReferenceRun; + if (mIgnorePeriodForReference) { + referenceActivity.mPeriodName = ""; + } + if (mIgnorePassForReference) { + referenceActivity.mPassName = ""; + } + + // load and initialize the input groups + for (auto group : mConfig.dataGroups) { + auto groupName = group.name; + auto& plotVec = mPlotNames[groupName]; + for (auto path : group.objects) { + auto fullPath = group.inputPath + "/" + path; + auto fullRefPath = group.referencePath + "/" + path; + auto fullOutPath = group.outputPath + "/" + path; + + // retrieve the reference MO + auto referencePlot = o2::quality_control::checker::getReferencePlot(&qcdb, fullRefPath, referenceActivity); + if (!referencePlot) { + ILOG(Warning, Support) << "Could not load reference plot for object \"" << fullRefPath << "\" and activity " << referenceActivity << ENDM; + continue; + } + + // extract the reference histogram + TH1* referenceHistogram = dynamic_cast(referencePlot->getObject()); + if (!referenceHistogram) { + continue; + } + + // store the reference MO + mReferencePlots[fullPath] = referencePlot; + + // fill an array with the full paths of the plots associated to this group + plotVec.push_back(fullPath); + + // create and store the plotter object + mHistograms[fullPath] = std::make_shared(referenceHistogram, mReferenceRun, fullOutPath, + group.normalizeReference, + group.drawRatioOnly, + group.legendHeight, + group.logScale, + group.drawOption1D, + group.drawOption2D); + auto* outObject = mHistograms[fullPath]->getMainCanvas(); + // publish the object created by the plotter + if (outObject) { + getObjectsManager()->startPublishing(outObject); + } + } + } +} + +//_________________________________________________________________________________________ + +std::shared_ptr ReferenceComparatorTask::getComparatorPlot(std::string plotName) +{ + // check if a corresponding output plot was initialized + auto iter = mHistograms.find(plotName); + if (iter == mHistograms.end()) { + return {}; + } + return iter->second; +} + +//_________________________________________________________________________________________ + +void ReferenceComparatorTask::update(quality_control::postprocessing::Trigger trigger, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + for (auto& [groupName, plotVec] : mPlotNames) { + for (auto& plotName : plotVec) { + // get object for current timestamp - age limit is converted to milliseconds + auto object = getMO(qcdb, plotName, trigger, mNotOlderThan * 1000); + + // skip objects that are not found or too old + if (!object.first || !object.second) { + continue; + } + + // only process objects inheriting from TH1 + auto* histogram = dynamic_cast(object.first->getObject()); + if (!histogram) { + continue; + } + + // check if a corresponding output plot was initialized + auto iter = mHistograms.find(plotName); + if (iter == mHistograms.end()) { + continue; + } + + // update the plot ratios and the histograms with superimposed reference + iter->second->update(histogram); + } + } +} + +//_________________________________________________________________________________________ + +void ReferenceComparatorTask::finalize(quality_control::postprocessing::Trigger t, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/ReferenceComparatorTaskConfig.cxx b/Modules/Common/src/ReferenceComparatorTaskConfig.cxx new file mode 100644 index 0000000000..c2fd0eb836 --- /dev/null +++ b/Modules/Common/src/ReferenceComparatorTaskConfig.cxx @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReferenceComparatorTaskConfig.cxx +/// \author Andrea Ferrero +/// + +#include "Common/ReferenceComparatorTaskConfig.h" +#include +#include + +namespace o2::quality_control_modules::common +{ + +ReferenceComparatorTaskConfig::ReferenceComparatorTaskConfig(std::string name, const boost::property_tree::ptree& config) + : PostProcessingConfig(name, config) +{ + for (const auto& dataGroupConfig : config.get_child("qc.postprocessing." + name + ".dataGroups")) { + auto inputPath = dataGroupConfig.second.get("inputPath"); + auto referencePath = dataGroupConfig.second.get("referencePath", inputPath); + DataGroup dataGroup{ + dataGroupConfig.second.get("name"), + inputPath, + referencePath, + dataGroupConfig.second.get("outputPath"), + dataGroupConfig.second.get("normalizeReference", false), + dataGroupConfig.second.get("drawRatioOnly", false), + dataGroupConfig.second.get("legendHeight", 0.2), + dataGroupConfig.second.get("drawOption1D", "HIST"), + dataGroupConfig.second.get("drawOption2D", "COLZ"), + dataGroupConfig.second.get("logScale", false) + }; + if (const auto& inputObjects = dataGroupConfig.second.get_child_optional("inputObjects"); inputObjects.has_value()) { + for (const auto& inputObject : inputObjects.value()) { + dataGroup.objects.emplace_back(inputObject.second.data()); + } + } + + dataGroups.emplace_back(std::move(dataGroup)); + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/TH1Reductor.cxx b/Modules/Common/src/TH1Reductor.cxx new file mode 100644 index 0000000000..43d09d07f1 --- /dev/null +++ b/Modules/Common/src/TH1Reductor.cxx @@ -0,0 +1,44 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1Reductor.cxx +/// \author Piotr Konopka +/// + +#include +#include "Common/TH1Reductor.h" + +namespace o2::quality_control_modules::common +{ + +void* TH1Reductor::getBranchAddress() +{ + return &mStats; +} + +const char* TH1Reductor::getBranchLeafList() +{ + return "mean/D:stddev:entries"; +} + +void TH1Reductor::update(TObject* obj) +{ + // todo: use GetStats() instead? + auto histo = dynamic_cast(obj); + if (histo) { + mStats.entries = histo->GetEntries(); + mStats.stddev = histo->GetStdDev(); + mStats.mean = histo->GetMean(); + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/TH1SliceReductor.cxx b/Modules/Common/src/TH1SliceReductor.cxx new file mode 100644 index 0000000000..00ebd0d3f6 --- /dev/null +++ b/Modules/Common/src/TH1SliceReductor.cxx @@ -0,0 +1,178 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1SliceReductor.cxx +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#include "QualityControl/QcInfoLogger.h" +#include "Common/TH1SliceReductor.h" +#include +#include +#include + +namespace o2::quality_control_modules::common +{ +void TH1SliceReductor::update(TObject* obj, std::vector& reducedSource, + std::vector>& axis, + int& finalNumberPads) +{ + // Define the local variables in the default case: 1 single pad + // (no multipad canvas, nor slicer), and slicer axes size set to 1 (no slicing). + TList* padList = nullptr; // List of TPads if input TCanvas. + TH1* histo = nullptr; // Pointer to the histogram to trend. + int numberPads = 1; // Number of input objects. + int numberSlices = 1; // Default value for the inner slicer axis. + bool useSlicing = false; + + // GANESHA add protection that axisSize == 1. But, do we allow that the json contains also y or z axis i.e. or protection would be if( (int)axis.size() < 1 ) + if ((int)axis.size() != 1) { + ILOG(Error, Support) << "Error: 'axisDivision' in json not configured properly for TH1Reductor. Should contain exactly one axis." << ENDM; + } + + // Get the number of pads, and their list in case of an input canvas. + const bool isCanvas = (obj->IsA() == TCanvas::Class()); + if (isCanvas) { + auto canvas = static_cast(obj); + padList = static_cast(canvas->GetListOfPrimitives()); + padList->SetOwner(kTRUE); + numberPads = padList->GetEntries(); + } else { // Non-canvas case: number of input pads always 1, i.e. only one histogram passed. + if (axis[0].size() > 1) { // Ensure the slicer config is valid, either to select a certain range, or obtain multiple slices. + // Otherwise, full range of histo will be used. + numberSlices = (int)axis[0].size() - 1; // axis[0].size() = number of boundaries. + useSlicing = true; // Enable the use of custom boundaries. + } else { + ILOG(Info, Support) << "Not enough axis boundaries for slicing. Will use full histogram range." << ENDM; + } + } + ILOG(Info, Support) << "Number of input histograms for the trending of " + << obj->GetName() << ": " << numberPads << ENDM; + + // Access the histograms embedded in 'obj'. + for (int iPad = 0; iPad < numberPads; iPad++) { + if (isCanvas) { + auto pad = static_cast(padList->At(iPad)); + histo = static_cast(pad->GetListOfPrimitives()->At(0)); + } else { // 'obj' is already a single histogram. + histo = static_cast(obj); + } + + if (histo) { + // Bin Numbers for correctly getting the statistical properties + int binXLow = 0; + int binXUp = 0; + + // Get the trending quantities defined in 'SlicerInfo'. + for (int j = 0; j < numberSlices; j++) { + std::string thisRange; + float sliceLabel = 0.; + + if (useSlicing) { + getBinSlices(histo->GetXaxis(), axis[0][j], axis[0][j + 1], binXLow, binXUp, sliceLabel); + histo->GetXaxis()->SetRange(binXLow, binXUp); + thisRange = fmt::format("{0:s} - RangeX: [{1:.1f}, {2:.1f}]", histo->GetTitle(), axis[0][j], axis[0][j + 1]); + } else { + if (isCanvas) { + thisRange = fmt::format("{0:s}", histo->GetTitle()); + sliceLabel = (float)(j); + } else { + thisRange = fmt::format("{0:s} - RangeX (default): [{1:.1f}, {2:.1f}]", histo->GetTitle(), histo->GetXaxis()->GetXmin(), histo->GetXaxis()->GetXmax()); + sliceLabel = (histo->GetXaxis()->GetXmin() + histo->GetXaxis()->GetXmax()) / 2.; + } + binXLow = 1; + binXUp = histo->GetNbinsX(); + } + + finalNumberPads++; + SliceInfo mySlice; + mySlice.entries = histo->Integral(binXLow, binXUp); + mySlice.meanX = histo->GetMean(1); + mySlice.stddevX = histo->GetStdDev(1); + if (mySlice.entries != 0) { + mySlice.errMeanX = mySlice.stddevX / (sqrt(mySlice.entries)); + } else { + mySlice.errMeanX = 0.; + } + + float StatsY[3]; // 0 Mean, 1 Stddev, 2 Error + GetTH1StatsY(histo, StatsY, binXLow, binXUp); + + mySlice.meanY = StatsY[0]; + mySlice.stddevY = StatsY[1]; + mySlice.errMeanY = StatsY[2]; + mySlice.sliceLabelX = sliceLabel; + mySlice.sliceLabelY = 0.; + mySlice.title = thisRange; + + reducedSource.emplace_back(mySlice); + } + + } else { + ILOG(Error, Support) << "Error: 'histo' not found." << ENDM; + } + } // All the vector elements have been updated. +} + +void TH1SliceReductor::GetTH1StatsY(TH1* hist, float stats[3], + const int lowerBin, const int upperBin) +{ + const int nTotalBins = hist->GetNbinsX(); + const int iterateBins = upperBin - lowerBin + 1; // Amount of bins included in the calculation. + // Includes lowerBin and upperBin. + + // Safety measures. + if (lowerBin <= 0 || upperBin <= 0) { + ILOG(Error, Support) << "Error: Negative bin in TH1SliceReductor::GetTH1StatsY" << ENDM; + exit(0); + } + if (upperBin < lowerBin) { + ILOG(Error, Support) << "Error: Upper bin smaller than lower bin in TH1SliceReductor::GetTH1StatsY" << ENDM; + exit(0); + } + if (nTotalBins < iterateBins) { + ILOG(Error, Support) << "Error: Bin region bigger than total amount of bins TH1SliceReductor::GetTH1StatsY" << ENDM; + exit(0); + } + + float meanY = 0.; + float stddevY = 0.; + float errMeanY = 0.; + + for (int i = lowerBin; i <= upperBin; i++) { + meanY += hist->GetBinContent(i); + } + + meanY /= (float)iterateBins; + + for (int i = lowerBin; i <= upperBin; i++) { + stddevY += pow(meanY - hist->GetBinContent(i), 2.); + } + + if (iterateBins == 1.) { + stddevY = hist->GetBinError(lowerBin) * sqrt(hist->GetBinContent(lowerBin)); + errMeanY = hist->GetBinError(lowerBin); + } else { + stddevY /= ((float)iterateBins - 1.); + errMeanY = stddevY / ((float)iterateBins); + stddevY = sqrt(stddevY); + errMeanY = sqrt(errMeanY); + } + + stats[0] = meanY; + stats[1] = stddevY; + stats[2] = errMeanY; +} // TH1SliceReductor::GetTH1StatsY(TH1* hist, float stats[3], float LowerBoundary, float UpperBoundary) + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/TH2Reductor.cxx b/Modules/Common/src/TH2Reductor.cxx new file mode 100644 index 0000000000..0a326e205f --- /dev/null +++ b/Modules/Common/src/TH2Reductor.cxx @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2Reductor.cxx +/// \author Piotr Konopka +/// + +#include +#include "Common/TH2Reductor.h" + +namespace o2::quality_control_modules::common +{ + +void* TH2Reductor::getBranchAddress() +{ + return &mStats; +} + +const char* TH2Reductor::getBranchLeafList() +{ + return "sumw/D:sumw2:sumwx:sumwx2:sumwy:sumwy2:sumwxy:entries"; +} + +void TH2Reductor::update(TObject* obj) +{ + auto histo = dynamic_cast(obj); + if (histo) { + histo->GetStats(mStats.sums.array); + mStats.entries = histo->GetEntries(); + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/TH2SliceReductor.cxx b/Modules/Common/src/TH2SliceReductor.cxx new file mode 100644 index 0000000000..6ab73a97f7 --- /dev/null +++ b/Modules/Common/src/TH2SliceReductor.cxx @@ -0,0 +1,165 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2SliceReductor.cxx +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#include "QualityControl/QcInfoLogger.h" +#include "Common/TH2SliceReductor.h" +#include +#include +#include +#include + +namespace o2::quality_control_modules::common +{ +void TH2SliceReductor::update(TObject* obj, std::vector& reducedSource, + std::vector>& axis, + int& finalNumberPads) +{ + // Define the local variables in the default case: 1 single pad + // (no multipad canvas, nor slicer), and slicer axes size set to 1 (no slicing). + TList* padList = nullptr; // List of TPads if input TCanvas. + TH2* histo = nullptr; // Pointer to the histogram to trend. + int numberPads = 1; // Number of input objects. + int numberSlicesX = 1; // Default value for the X axis slicer. + int numberSlicesY = 1; // Default value for the Y axis slicer. + bool useSlicingX = false; + bool useSlicingY = false; + + // GANESHA add protection that number of axes == 2. + if ((int)axis.size() != 2) { + ILOG(Error, Support) << "Error: 'axisDivision' in json not configured properly for TH2Reductor. Should contain exactly two axes." << ENDM; + } + + // Get the number of pads, and their list in case of an input canvas. + const bool isCanvas = (obj->IsA() == TCanvas::Class()); + if (isCanvas) { + auto canvas = static_cast(obj); + padList = static_cast(canvas->GetListOfPrimitives()); + padList->SetOwner(kTRUE); + numberPads = padList->GetEntries(); + } else { // Non-canvas case: number of input pads always 1, i.e. only one histogram passed. + if (axis[0].size() > 1) { // Ensure the slicer config X is valid, either to select a certain range, or obtain multiple slices. + // Otherwise, full range of histo will be used. + numberSlicesX = (int)axis[0].size() - 1; // axis[0].size() = number of boundaries. + useSlicingX = true; // Enable the use of custom boundaries. + } else { + ILOG(Info, Support) << "Not enough axis boundaries for slicing on X. Will use full histogram range along X." << ENDM; + } + + if (axis[1].size() > 1) { // Ensure the slicer config Y is valid, either to select a certain range, or obtain multiple slices. + // Otherwise, full range of histo will be used. + numberSlicesY = (int)axis[1].size() - 1; // axis[1].size() = number of boundaries. + useSlicingY = true; // Enable the use of custom boundaries. + } else { + ILOG(Info, Support) << "Not enough axis boundaries for slicing on Y. Will use full histogram range along Y." << ENDM; + } + } + + ILOG(Info, Support) << "Number of input histograms for the trending of " + << obj->GetName() << ": " << numberPads << ENDM; + + double labelIterator = 0; + + // Access the histograms embedded in 'obj'. + for (int iPad = 0; iPad < numberPads; iPad++) { + if (isCanvas) { + auto pad = static_cast(padList->At(iPad)); + histo = static_cast(pad->GetListOfPrimitives()->At(0)); + } else { // 'obj' is already a single histogram. + histo = static_cast(obj); + } + + if (histo) { + + // Bin Numbers for correctly getting the statistical properties + int binXLow = 0; + int binXUp = 0; + int binYLow = 0; + int binYUp = 0; + + // Get the trending quantities defined in 'SlicerInfo'. + // The two for-loop do only one pass if we have an input canvas. + + for (int iX = 0; iX < numberSlicesX; iX++) { + std::string thisRange; + float sliceLabelX = 0.; + + if (useSlicingX) { + getBinSlices(histo->GetXaxis(), axis[0][iX], axis[0][iX + 1], binXLow, binXUp, sliceLabelX); + histo->GetXaxis()->SetRange(binXLow, binXUp); + thisRange = fmt::format("{0:s} - RangeX: [{1:.1f}, {2:.1f}]", histo->GetTitle(), axis[0][iX], axis[0][iX + 1]); + } else { + if (isCanvas) { + thisRange = fmt::format("{0:s}", histo->GetTitle()); + sliceLabelX = (float)(iX); + } else { + thisRange = fmt::format("{0:s} - RangeX (default): [{1:.1f}, {2:.1f}]", histo->GetTitle(), histo->GetXaxis()->GetXmin(), histo->GetXaxis()->GetXmax()); + sliceLabelX = (histo->GetXaxis()->GetXmin() + histo->GetXaxis()->GetXmax()) / 2.; + } + binXLow = 1; + binXUp = histo->GetNbinsX(); + } + auto thisRangeX = thisRange; // Backup x-title Needed to have proper y titles for each slice when looping over y slices + + for (int jY = 0; jY < numberSlicesY; jY++) { + float sliceLabelY = 0.; + + if (useSlicingY) { + getBinSlices(histo->GetYaxis(), axis[1][jY], axis[1][jY + 1], binYLow, binYUp, sliceLabelY); + histo->GetYaxis()->SetRange(binYLow, binYUp); + thisRange = thisRangeX + fmt::format(" and RangeY: [{0:.1f}, {1:.1f}]", axis[1][jY], axis[1][jY + 1]); + } else { + thisRange += thisRangeX + fmt::format(" and RangeY (default): [{0:.1f}, {1:.1f}]", histo->GetYaxis()->GetXmin(), histo->GetYaxis()->GetXmax()); + sliceLabelY = (histo->GetYaxis()->GetXmin() + histo->GetYaxis()->GetXmax()) / 2.; + binYLow = 1; + binYUp = histo->GetNbinsY(); + } + + finalNumberPads++; + SliceInfo mySlice; + mySlice.entries = histo->Integral(binXLow, binXUp, binYLow, binYUp, ""); + mySlice.meanX = histo->GetMean(1); + mySlice.stddevX = histo->GetStdDev(1); + if (mySlice.entries != 0) { + mySlice.errMeanX = mySlice.stddevX / (sqrt(mySlice.entries)); + } else { + mySlice.errMeanX = 0.; + } + + mySlice.meanY = histo->GetMean(2); + mySlice.stddevY = histo->GetStdDev(2); + if (mySlice.entries != 0) { + mySlice.errMeanY = mySlice.stddevY / (sqrt(mySlice.entries)); + } else { + mySlice.errMeanY = 0.; + } + + mySlice.sliceLabelX = sliceLabelX; + mySlice.sliceLabelY = sliceLabelY; + mySlice.title = thisRange; + + reducedSource.emplace_back(mySlice); + } + } + + } else { + ILOG(Error, Support) << "Error: 'histo' not found." << ENDM; + } + } // All the vector elements have been updated. +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/THnSparse5Reductor.cxx b/Modules/Common/src/THnSparse5Reductor.cxx new file mode 100644 index 0000000000..df08992e69 --- /dev/null +++ b/Modules/Common/src/THnSparse5Reductor.cxx @@ -0,0 +1,56 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file THnSparse5Reductor.cxx +/// \author Ivan Ravasenga from the model by Piotr Konopka +/// + +#include +#include +#include "Common/THnSparse5Reductor.h" + +namespace o2::quality_control_modules::common +{ + +void* THnSparse5Reductor::getBranchAddress() +{ + return &mStats; +} + +const char* THnSparse5Reductor::getBranchLeafList() +{ + return Form("mean[%i]/D:stddev[%i]:entries[%i]", NDIM, NDIM, NDIM); +} + +void THnSparse5Reductor::update(TObject* obj) +{ + // todo: use GetStats() instead? + auto sparsehisto = dynamic_cast(obj); + if (sparsehisto) { + Int_t dim = sparsehisto->GetNdimensions(); + for (int i = 0; i < NDIM; i++) { + if (i < dim) { + TH1D* hproj = (TH1D*)sparsehisto->Projection(i); + mStats.entries[i] = hproj->GetEntries(); + mStats.mean[i] = hproj->GetMean(); + mStats.stddev[i] = hproj->GetStdDev(); + delete hproj; + } else { + mStats.entries[i] = -1; + mStats.mean[i] = -1; + mStats.stddev[i] = -1; + } + } + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/TrendCheck.cxx b/Modules/Common/src/TrendCheck.cxx new file mode 100644 index 0000000000..68f30fe0fc --- /dev/null +++ b/Modules/Common/src/TrendCheck.cxx @@ -0,0 +1,467 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendCheck.cxx +/// \author Andrea Ferrero +/// + +#include "Common/TrendCheck.h" +#include "Common/CheckerThresholdsConfig.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::common +{ + +void TrendCheck::configure() +{ +} + +void TrendCheck::startOfActivity(const Activity& activity) +{ + mActivity = activity; + + // initialize the thresholds configuration + mThresholds = std::make_shared(mCustomParameters, mActivity); + + // comparison method between actual values and thresholds + std::string parKey = "trendCheckMode"; + auto parOpt = mCustomParameters.atOptional(parKey, mActivity); + if (!parOpt) { + parOpt = mCustomParameters.atOptional(parKey); + } + mTrendCheckMode = ExpectedRange; + if (parOpt.has_value()) { + if (parOpt.value() == "DeviationFromMean") { + mTrendCheckMode = DeviationFromMean; + } else if (parOpt.value() == "StdDeviation") { + mTrendCheckMode = StdDeviation; + } else if (parOpt.value() != "ExpectedRange") { + ILOG(Warning, Devel) << "unrecognized threshold mode \"" << parOpt.value() << "\", using default \"ExpectedRange\" mode" << ENDM; + } + } + switch (mTrendCheckMode) { + case ExpectedRange: + ILOG(Info, Support) << "thresholds mode set to \"ExpectedRange\"" << ENDM; + break; + case DeviationFromMean: + ILOG(Info, Support) << "thresholds mode set to \"DeviationFromMean\"" << ENDM; + break; + case StdDeviation: + ILOG(Info, Support) << "thresholds mode set to \"StdDeviation\"" << ENDM; + break; + } + + // number of points, excluding the last one, used to compute the average and standard deviation of the trend + // all points are considered if the value is smaller or equal to zero + parKey = "nPointsForAverage"; + parOpt = mCustomParameters.atOptional(parKey, mActivity); + if (!parOpt) { + parOpt = mCustomParameters.atOptional(parKey); + } + if (parOpt.has_value()) { + if (parOpt.value() == "all") { + mNPointsForAverage = 0; + } else { + mNPointsForAverage = std::stoi(parOpt.value()); + } + } + + if (mNPointsForAverage == 0) { + ILOG(Info, Support) << "using all points for statistics calculation" << ENDM; + } else { + ILOG(Info, Support) << "using at most " << mNPointsForAverage << " points for statistics calculation" << ENDM; + } + + // position and size of the label showing the result of the check + parKey = "qualityLabelPosition"; + parOpt = mCustomParameters.atOptional(parKey, mActivity); + if (!parOpt) { + parOpt = mCustomParameters.atOptional(parKey); + } + if (parOpt.has_value()) { + auto position = o2::utils::Str::tokenize(parOpt.value(), ',', false, true); + if (position.size() == 2) { + mQualityLabelPosition = std::make_pair(std::stof(position[0]), std::stof(position[1])); + } + } + + parKey = "qualityLabelSize"; + parOpt = mCustomParameters.atOptional(parKey, mActivity); + if (!parOpt) { + parOpt = mCustomParameters.atOptional(parKey); + } + if (parOpt.has_value()) { + auto position = o2::utils::Str::tokenize(parOpt.value(), ',', false, true); + if (position.size() == 2) { + mQualityLabelSize = std::make_pair(std::stof(position[0]), std::stof(position[1])); + } + } +} + +void TrendCheck::endOfActivity(const Activity& activity) +{ + // reset all members + mActivity = Activity{}; + mAverageTrend.clear(); + mThresholdsTrendBad.clear(); + mThresholdsTrendMedium.clear(); + mQualities.clear(); +} + +static std::string getBaseName(std::string name) +{ + auto pos = name.rfind("/"); + return ((pos < std::string::npos) ? name.substr(pos + 1) : name); +} + +// compute the mean and standard deviation from a given number of points bewfore the last one +// all points are used if nPointsForAverage <= 0 +static std::optional> getGraphStatistics(TGraph* graph, int nPointsForAverage) +{ + std::optional> result; + + int nPoints = graph->GetN(); + // we need at least two points before the last one + if (nPoints < 3) { + return result; + } + + // index of the last-but-one point + int pointIndexMax = nPoints - 2; + // index of the starting point for the mean and stadrad deviation computation + int pointIndexMin = (nPointsForAverage > 0 && pointIndexMax >= nPointsForAverage) ? pointIndexMax - nPointsForAverage + 1 : 0; + // number of points used for the computation, must be greater than one + int nPointsForStats = pointIndexMax - pointIndexMin + 1; + if (nPointsForStats < 2) { + return result; + } + + // compute the mean of the points + double mean = 0; + for (int pointIndex = pointIndexMin; pointIndex <= pointIndexMax; pointIndex++) { + mean += graph->GetPointY(pointIndex); + } + mean /= nPointsForStats; + + // compute the standard deviation of the points + double stdDev = 0; + for (int pointIndex = pointIndexMin; pointIndex <= pointIndexMax; pointIndex++) { + double delta = graph->GetPointY(pointIndex) - mean; + stdDev += delta * delta; + } + stdDev /= (nPointsForStats - 1) * nPointsForStats; + stdDev = std::sqrt(stdDev); + + result = std::make_pair(mean, stdDev); + return result; +} + +double TrendCheck::getInteractionRate() +{ + return 750000; +} + +/// \brief compute the thresholds for a given graph, taking into account the current interaction rate +/// +/// \param key string identifying some plot-specific threshold values +/// \param graph the graph object to be checked +/// \return an array of optional (min,max) threshold values for Bad and Medium qualities +std::array>, 2> TrendCheck::getThresholds(std::string plotName, TGraph* graph) +{ + double rate = getInteractionRate(); + std::array>, 2> result = mThresholds->getThresholdsForPlot(plotName, rate); + if (!result[0]) { + ILOG(Warning, Support) << "Cannot retrieve thresholds for \"" << plotName << "\"" << ENDM; + return result; + } + + if (mTrendCheckMode != ExpectedRange) { + // the thresholds retrieved from the configuration need to be converted into absolute values + auto graphStatistics = getGraphStatistics(graph, mNPointsForAverage); + if (!graphStatistics) { + result[0].reset(); + result[1].reset(); + return result; + } + + if (mTrendCheckMode == DeviationFromMean) { + double mean = graphStatistics.value().first; + // the thresholds retrieved from the configuration are relative to the mean value of the last N points, we need to convert them into absolute values + if (result[0].has_value()) { + result[0].value().first = mean + result[0].value().first * std::fabs(mean); + result[0].value().second = mean + result[0].value().second * std::fabs(mean); + } + + if (result[1].has_value()) { + result[1].value().first = mean + result[1].value().first * std::fabs(mean); + result[1].value().second = mean + result[1].value().second * std::fabs(mean); + } + } else if (mTrendCheckMode == StdDeviation) { + // the thresholds retrieved from the configuration are expressed as number of sigmas from the mean value of the last N points, we need to convert them into absolute values + double mean = graphStatistics.value().first; + double stdDevOfMean = graphStatistics.value().second; + double lastPointValue = graph->GetPointY(graph->GetN() - 1); + double lastPointError = graph->GetErrorY(graph->GetN() - 1); + if (lastPointError < 0) { + lastPointError = 0; + } + const double totalError = sqrt(stdDevOfMean * stdDevOfMean + lastPointError * lastPointError); + + result[0].value().first = mean + result[0].value().first * totalError; + result[0].value().second = mean + result[0].value().second * totalError; + + if (result[1].has_value()) { + result[1].value().first = mean + result[1].value().first * totalError; + result[1].value().second = mean + result[1].value().second * totalError; + } + } + } + + return result; +} + +// helper function to retrieve a TGraph drawn inside a TPad +TGraph* getGraphFromPad(TPad* pad) +{ + if (!pad) { + return nullptr; + } + + TGraph* graph{ nullptr }; + int jList = pad->GetListOfPrimitives()->LastIndex(); + for (; jList >= 0; jList--) { + graph = dynamic_cast(pad->GetListOfPrimitives()->At(jList)); + if (graph) { + break; + } + } + + return graph; +} + +// retrieve a vector containing all the TGraph objects embedded in a given TObject +// the TObject can also be the TGraph itself +void TrendCheck::getGraphsFromObject(TObject* object, std::vector& graphs) +{ + TGraph* graph = dynamic_cast(object); + if (graph) { + graphs.push_back(graph); + } else { + // extract the TGraph from the canvas + TCanvas* canvas = dynamic_cast(object); + auto graph = getGraphFromPad(canvas); + if (graph) { + graphs.push_back(graph); + } else { + ILOG(Info, Devel) << "No TGraph found in the canvas, checking sub-pads" << ENDM; + // loop over the pads in the canvas and extract the TGraph from each pad + const int numberPads = canvas->GetListOfPrimitives()->GetEntries(); + for (int iPad = 0; iPad < numberPads; iPad++) { + auto pad = dynamic_cast(canvas->GetListOfPrimitives()->At(iPad)); + graph = getGraphFromPad(pad); + if (graph) { + graphs.push_back(graph); + } + } + } + } +} + +Quality TrendCheck::check(std::map>* moMap) +{ + for (auto& [moKey, mo] : *moMap) { + + std::vector graphs; + getGraphsFromObject(mo->getObject(), graphs); + if (graphs.empty()) { + continue; + } + + auto moName = mo->getName(); + auto key = getBaseName(moName); + + for (size_t graphIndex = 0; graphIndex < graphs.size(); graphIndex++) { + + TGraph* graph = graphs[graphIndex]; + if (!graph) { + continue; + } + + auto graphName = moName + "_" + std::to_string(graphIndex); + + // check that the graph is not empty + int nPoints = graph->GetN(); + if (nPoints < 1) { + continue; + } + + // get the value for the last point + double value = graph->GetPointY(nPoints - 1); + + // get acceptable range for the current plot + auto thresholds = getThresholds(key, graph); + // check that at least the thresholds for Bad quality are available + if (!thresholds[0]) { + continue; + } + + // Quality is good by default, unless the last point is outside the acceptable range + mQualities[graphName] = Quality::Good; + + mThresholdsTrendBad[graphName].emplace_back(graph->GetPointX(nPoints - 1), thresholds[0].value()); + if (thresholds[1].has_value()) { + mThresholdsTrendMedium[graphName].emplace_back(graph->GetPointX(nPoints - 1), thresholds[1].value()); + } + + if (value < thresholds[0]->first || value > thresholds[0]->second) { + mQualities[graphName] = Quality::Bad; + } else if (thresholds[1].has_value()) { + if (value < thresholds[1]->first || value > thresholds[1]->second) { + mQualities[graphName] = Quality::Medium; + } + } + } + } + + // we return the worse quality of all the objects we checked, but we preserve all FlagTypes + Quality result = mQualities.empty() ? Quality::Null : Quality::Good; + for (auto& [key, quality] : mQualities) { + (void)key; + if (quality.isWorseThan(result)) { + result.set(quality); + } + for (const auto& flag : quality.getFlags()) { + result.addFlag(flag.first, flag.second); + } + } + + return result; +} + +static void drawThresholds(TGraph* graph, const std::vector>>& thresholds, int lineColor, int lineStyle) +{ + if (thresholds.empty()) { + return; + } + + double rangeMin = graph->GetMinimum(); + double rangeMax = graph->GetMaximum(); + + rangeMin = TMath::Min(rangeMin, TMath::MinElement(graph->GetN(), graph->GetY())); + rangeMax = TMath::Max(rangeMax, TMath::MaxElement(graph->GetN(), graph->GetY())); + + double* xValues = new double[thresholds.size()]; + double* yValuesMin = new double[thresholds.size()]; + double* yValuesMax = new double[thresholds.size()]; + + for (size_t index = 0; index < thresholds.size(); index++) { + const auto& thresholdsPoint = thresholds[index]; + xValues[index] = thresholdsPoint.first; + yValuesMin[index] = thresholdsPoint.second.first; + yValuesMax[index] = thresholdsPoint.second.second; + + // add some margin above an below the threshold values, if needed + auto delta = yValuesMax[index] - yValuesMin[index]; + rangeMin = TMath::Min(rangeMin, yValuesMin[index] - 0.1 * delta); + rangeMax = TMath::Max(rangeMax, yValuesMax[index] + 0.1 * delta); + } + + TPolyLine* lineMin = new TPolyLine(thresholds.size(), xValues, yValuesMin); + lineMin->SetLineColor(lineColor); + lineMin->SetLineStyle(lineStyle); + lineMin->SetLineWidth(2); + graph->GetListOfFunctions()->Add(lineMin); + + TPolyLine* lineMax = new TPolyLine(thresholds.size(), xValues, yValuesMax); + lineMax->SetLineColor(lineColor); + lineMax->SetLineStyle(lineStyle); + lineMax->SetLineWidth(2); + graph->GetListOfFunctions()->Add(lineMax); + + graph->SetMinimum(rangeMin); + graph->SetMaximum(rangeMax); +} + +// return the ROOT color index associated to a give quality level +static int getQualityColor(const Quality& q) +{ + if (q == Quality::Null) + return kViolet - 6; + if (q == Quality::Bad) + return kRed; + if (q == Quality::Medium) + return kOrange - 3; + if (q == Quality::Good) + return kGreen + 2; + + return 0; +} + +void TrendCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + std::vector graphs; + getGraphsFromObject(mo->getObject(), graphs); + if (graphs.empty()) { + return; + } + + auto moName = mo->getName(); + + for (size_t graphIndex = 0; graphIndex < graphs.size(); graphIndex++) { + + TGraph* graph = graphs[graphIndex]; + if (!graph || graph->GetN() < 1) { + return; + } + + auto graphName = moName + "_" + std::to_string(graphIndex); + + if (!mQualities.contains(graphName)) { + continue; + } + Quality quality = mQualities[graphName]; + + if (mThresholdsTrendMedium.count(graphName) > 0) { + const auto& thresholds = mThresholdsTrendMedium[graphName]; + drawThresholds(graph, thresholds, kOrange, 9); + } + + if (mThresholdsTrendBad.count(graphName) > 0) { + const auto& thresholds = mThresholdsTrendBad[graphName]; + drawThresholds(graph, thresholds, kRed, 9); + } + + // draw the label with the check result + TPaveText* label = new TPaveText(mQualityLabelPosition.first, + mQualityLabelPosition.second, + mQualityLabelPosition.first + mQualityLabelSize.first, + mQualityLabelPosition.second + mQualityLabelSize.second, + "brNDC"); + label->SetTextColor(getQualityColor(quality)); + label->AddText(quality.getName().c_str()); + label->SetFillStyle(0); + label->SetBorderSize(0); + label->SetTextAlign(12); + graph->GetListOfFunctions()->Add(label); + } +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/src/WorstOfAllAggregator.cxx b/Modules/Common/src/WorstOfAllAggregator.cxx new file mode 100644 index 0000000000..d8585c3a33 --- /dev/null +++ b/Modules/Common/src/WorstOfAllAggregator.cxx @@ -0,0 +1,56 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file WorstOfAllAggregator.cxx +/// \author Piotr Konopka +/// + +#include "Common/WorstOfAllAggregator.h" +#include "QualityControl/QcInfoLogger.h" +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::common +{ + +void WorstOfAllAggregator::configure() +{ +} + +std::map WorstOfAllAggregator::aggregate(QualityObjectsMapType& qoMap) +{ + if (qoMap.empty()) { + Quality null = Quality::Null; + null.addFlag(FlagTypeFactory::UnknownQuality(), + "QO map given to the aggregator '" + mName + "' is empty."); + return { { mName, null } }; + } + + // we return the worse quality of all the objects we receive, but we preserve all FlagTypes + Quality current = Quality::Good; + for (const auto& [qoName, qo] : qoMap) { + (void)qoName; + if (qo->getQuality().isWorseThan(current)) { + current.set(qo->getQuality()); + } + for (const auto& flag : qo->getFlags()) { + current.addFlag(flag.first, flag.second); + } + } + ILOG(Info, Devel) << "Aggregated Quality: " << current << ENDM; + + return { { mName, current } }; +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Common/test/testCommonHistRatios.cxx b/Modules/Common/test/testCommonHistRatios.cxx new file mode 100644 index 0000000000..c36557afc9 --- /dev/null +++ b/Modules/Common/test/testCommonHistRatios.cxx @@ -0,0 +1,305 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCommonHistRatios.cxx +/// \author Andrea Ferrero +/// + +#include "QualityControl/QualityObject.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" + +#define BOOST_TEST_MODULE CommonHistRatios test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::common; + +BOOST_AUTO_TEST_CASE(test_TH1FRatioUniform) +{ + auto histo1 = std::make_unique("test1", "test1", 10, 0, 10.0, true); + auto histo2 = std::make_unique("test2", "test2", 10, 0, 10.0, true); + auto histoMerged = std::make_unique("testMerged", "testMerged", 10, 0, 10.0, true); + + for (int bin = 1; bin <= 10; bin++) { + histo1->getNum()->SetBinContent(bin, bin * 4); + histo2->getNum()->SetBinContent(bin, bin * 5); + } + + histo1->getDen()->SetBinContent(1, 2); + histo2->getDen()->SetBinContent(1, 3); + + histo1->update(); + histo2->update(); + + histoMerged->merge(histo1.get()); + histoMerged->merge(histo2.get()); + + for (int bin = 1; bin <= 10; bin++) { + float value = 9.0 * bin / 5.0; + BOOST_REQUIRE_EQUAL(histoMerged->GetBinContent(bin), value); + } +} + +BOOST_AUTO_TEST_CASE(test_TH1FRatio) +{ + auto histo1 = std::make_unique("test1", "test1", 10, 0, 10.0, false); + auto histo2 = std::make_unique("test2", "test2", 10, 0, 10.0, false); + auto histoMerged = std::make_unique("testMerged", "testMerged", 10, 0, 10.0, false); + + for (int bin = 1; bin <= 10; bin++) { + histo1->getNum()->SetBinContent(bin, bin * bin * 4); + histo1->getDen()->SetBinContent(bin, bin * 3); + + histo2->getNum()->SetBinContent(bin, bin * bin * 5); + histo2->getDen()->SetBinContent(bin, bin * 4); + } + + histo1->update(); + histo2->update(); + + histoMerged->merge(histo1.get()); + histoMerged->merge(histo2.get()); + + for (int bin = 1; bin <= 10; bin++) { + float value = 9.0 * bin / 7.0; + BOOST_REQUIRE_EQUAL(histoMerged->GetBinContent(bin), value); + } +} + +BOOST_AUTO_TEST_CASE(test_TH1FRatioCopy) +{ + auto histo1 = std::make_unique("test1", "test1", 10, 0, 10.0, true); + auto histo2 = std::make_unique("test2", "test2", 100, 0, 10.0, false); + auto histo3 = std::make_unique("test3", "test3"); + + for (int bin = 1; bin <= 10; bin++) { + histo1->getNum()->SetBinContent(bin, bin * 4); + } + + histo1->getDen()->SetBinContent(1, 2); + + histo1->update(); + + histo1->Copy(*(histo2.get())); + histo1->Copy(*(histo3.get())); + + BOOST_REQUIRE_EQUAL(histo2->hasUniformScaling(), true); + BOOST_REQUIRE_EQUAL(histo3->hasUniformScaling(), true); + + BOOST_REQUIRE_EQUAL(histo2->GetXaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo3->GetXaxis()->GetNbins(), 10); + + BOOST_REQUIRE_EQUAL(histo2->getNum()->GetXaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo3->getNum()->GetXaxis()->GetNbins(), 10); + + BOOST_REQUIRE_EQUAL(histo2->getDen()->GetXaxis()->GetNbins(), 1); + BOOST_REQUIRE_EQUAL(histo3->getDen()->GetXaxis()->GetNbins(), 1); + + BOOST_REQUIRE_EQUAL(histo2->getDen()->GetBinContent(1), 2); + BOOST_REQUIRE_EQUAL(histo3->getDen()->GetBinContent(1), 2); + + for (int bin = 1; bin <= 10; bin++) { + float value_num = bin * 4.0; + BOOST_REQUIRE_EQUAL(histo2->getNum()->GetBinContent(bin), value_num); + BOOST_REQUIRE_EQUAL(histo3->getNum()->GetBinContent(bin), value_num); + float value = bin * 2.0; + BOOST_REQUIRE_EQUAL(histo2->GetBinContent(bin), value); + BOOST_REQUIRE_EQUAL(histo3->GetBinContent(bin), value); + } +} + +BOOST_AUTO_TEST_CASE(test_TH1FRatioClone) +{ + auto histo1 = std::make_unique("test1", "test1", 10, 0, 10.0, true); + + for (int bin = 1; bin <= 10; bin++) { + histo1->getNum()->SetBinContent(bin, bin * 4); + } + + histo1->getDen()->SetBinContent(1, 2); + + histo1->update(); + + TH1FRatio* histo2 = (TH1FRatio*)histo1->Clone("test1_clone"); + + BOOST_REQUIRE_EQUAL(histo2->GetName(), "test1_clone"); + BOOST_REQUIRE_EQUAL(histo2->hasUniformScaling(), true); + BOOST_REQUIRE_EQUAL(histo2->GetXaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo2->getNum()->GetXaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo2->getDen()->GetXaxis()->GetNbins(), 1); + + BOOST_REQUIRE_EQUAL(histo2->getDen()->GetBinContent(1), 2); + + for (int bin = 1; bin <= 10; bin++) { + float value_num = bin * 4.0; + BOOST_REQUIRE_EQUAL(histo2->getNum()->GetBinContent(bin), value_num); + float value = bin * 2.0; + BOOST_REQUIRE_EQUAL(histo2->GetBinContent(bin), value); + } +} + +BOOST_AUTO_TEST_CASE(test_TH2FRatioUniform) +{ + auto histo1 = std::make_unique("test1", "test1", 10, 0, 10.0, 10, 0, 10.0, true); + auto histo2 = std::make_unique("test2", "test2", 10, 0, 10.0, 10, 0, 10.0, true); + auto histoMerged = std::make_unique("testMerged", "testMerged", 10, 0, 10.0, 10, 0, 10.0, true); + + for (int ybin = 1; ybin <= 10; ybin++) { + for (int xbin = 1; xbin <= 10; xbin++) { + histo1->getNum()->SetBinContent(xbin, ybin, xbin * ybin * 4); + histo2->getNum()->SetBinContent(xbin, ybin, xbin * ybin * 5); + } + } + + histo1->getDen()->SetBinContent(1, 1, 2); + histo2->getDen()->SetBinContent(1, 1, 3); + + histo1->update(); + histo2->update(); + + histoMerged->merge(histo1.get()); + histoMerged->merge(histo2.get()); + + for (int ybin = 1; ybin <= 10; ybin++) { + for (int xbin = 1; xbin <= 10; xbin++) { + float value = 9.0 * xbin * ybin / 5.0; + BOOST_REQUIRE_EQUAL(histoMerged->GetBinContent(xbin, ybin), value); + } + } +} + +BOOST_AUTO_TEST_CASE(test_TH2FRatio) +{ + auto histo1 = std::make_unique("test1", "test1", 10, 0, 10.0, 10, 0, 10.0, false); + auto histo2 = std::make_unique("test2", "test2", 10, 0, 10.0, 10, 0, 10.0, false); + auto histoMerged = std::make_unique("testMerged", "testMerged", 10, 0, 10.0, 10, 0, 10.0, false); + + for (int ybin = 1; ybin <= 10; ybin++) { + for (int xbin = 1; xbin <= 10; xbin++) { + histo1->getNum()->SetBinContent(xbin, ybin, xbin * ybin * xbin * ybin * 4); + histo1->getDen()->SetBinContent(xbin, ybin, xbin * ybin * 3); + + histo2->getNum()->SetBinContent(xbin, ybin, xbin * ybin * xbin * ybin * 5); + histo2->getDen()->SetBinContent(xbin, ybin, xbin * ybin * 4); + } + } + + histo1->update(); + histo2->update(); + + histoMerged->merge(histo1.get()); + histoMerged->merge(histo2.get()); + + for (int ybin = 1; ybin <= 10; ybin++) { + for (int xbin = 1; xbin <= 10; xbin++) { + float value = 9.0 * xbin * ybin / 7.0; + BOOST_REQUIRE_EQUAL(histoMerged->GetBinContent(xbin, ybin), value); + } + } +} + +BOOST_AUTO_TEST_CASE(test_TH2FRatioCopy) +{ + auto histo1 = std::make_unique("test1", "test1", 10, 0, 10.0, 10, 0, 10.0, true); + auto histo2 = std::make_unique("test2", "test2", 100, 0, 10.0, 100, 0, 10.0, false); + auto histo3 = std::make_unique("test3", "test3"); + + for (int ybin = 1; ybin <= 10; ybin++) { + for (int xbin = 1; xbin <= 10; xbin++) { + histo1->getNum()->SetBinContent(xbin, ybin, xbin * ybin * 4); + } + } + + histo1->getDen()->SetBinContent(1, 1, 2); + + histo1->update(); + + histo1->Copy(*(histo2.get())); + histo1->Copy(*(histo3.get())); + + BOOST_REQUIRE_EQUAL(histo2->hasUniformScaling(), true); + BOOST_REQUIRE_EQUAL(histo3->hasUniformScaling(), true); + + BOOST_REQUIRE_EQUAL(histo2->GetXaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo3->GetXaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo2->GetYaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo3->GetYaxis()->GetNbins(), 10); + + BOOST_REQUIRE_EQUAL(histo2->getNum()->GetXaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo3->getNum()->GetXaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo2->getNum()->GetYaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo3->getNum()->GetYaxis()->GetNbins(), 10); + + BOOST_REQUIRE_EQUAL(histo2->getDen()->GetXaxis()->GetNbins(), 1); + BOOST_REQUIRE_EQUAL(histo3->getDen()->GetXaxis()->GetNbins(), 1); + BOOST_REQUIRE_EQUAL(histo2->getDen()->GetYaxis()->GetNbins(), 1); + BOOST_REQUIRE_EQUAL(histo3->getDen()->GetYaxis()->GetNbins(), 1); + + BOOST_REQUIRE_EQUAL(histo2->getDen()->GetBinContent(1, 1), 2); + BOOST_REQUIRE_EQUAL(histo3->getDen()->GetBinContent(1, 1), 2); + + for (int ybin = 1; ybin <= 10; ybin++) { + for (int xbin = 1; xbin <= 10; xbin++) { + float value_num = xbin * ybin * 4; + BOOST_REQUIRE_EQUAL(histo2->getNum()->GetBinContent(xbin, ybin), value_num); + BOOST_REQUIRE_EQUAL(histo3->getNum()->GetBinContent(xbin, ybin), value_num); + float value = xbin * ybin * 2; + BOOST_REQUIRE_EQUAL(histo2->GetBinContent(xbin, ybin), value); + BOOST_REQUIRE_EQUAL(histo3->GetBinContent(xbin, ybin), value); + } + } +} + +BOOST_AUTO_TEST_CASE(test_TH2FRatioClone) +{ + auto histo1 = std::make_unique("test1", "test1", 10, 0, 10.0, 10, 0, 10.0, true); + + for (int ybin = 1; ybin <= 10; ybin++) { + for (int xbin = 1; xbin <= 10; xbin++) { + histo1->getNum()->SetBinContent(xbin, ybin, xbin * ybin * 4); + } + } + + histo1->getDen()->SetBinContent(1, 1, 2); + + histo1->update(); + + TH2FRatio* histo2 = (TH2FRatio*)histo1->Clone("test1_clone"); + + BOOST_REQUIRE_EQUAL(histo2->GetName(), "test1_clone"); + BOOST_REQUIRE_EQUAL(histo2->hasUniformScaling(), true); + + BOOST_REQUIRE_EQUAL(histo2->GetXaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo2->GetYaxis()->GetNbins(), 10); + + BOOST_REQUIRE_EQUAL(histo2->getNum()->GetXaxis()->GetNbins(), 10); + BOOST_REQUIRE_EQUAL(histo2->getNum()->GetYaxis()->GetNbins(), 10); + + BOOST_REQUIRE_EQUAL(histo2->getDen()->GetXaxis()->GetNbins(), 1); + BOOST_REQUIRE_EQUAL(histo2->getDen()->GetYaxis()->GetNbins(), 1); + + BOOST_REQUIRE_EQUAL(histo2->getDen()->GetBinContent(1, 1), 2); + + for (int ybin = 1; ybin <= 10; ybin++) { + for (int xbin = 1; xbin <= 10; xbin++) { + float value_num = xbin * ybin * 4; + BOOST_REQUIRE_EQUAL(histo2->getNum()->GetBinContent(xbin, ybin), value_num); + float value = xbin * ybin * 2; + BOOST_REQUIRE_EQUAL(histo2->GetBinContent(xbin, ybin), value); + } + } +} diff --git a/Modules/Common/test/testCommonReductors.cxx b/Modules/Common/test/testCommonReductors.cxx new file mode 100644 index 0000000000..d0d0f32bb9 --- /dev/null +++ b/Modules/Common/test/testCommonReductors.cxx @@ -0,0 +1,158 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testCommonReductors.cxx +/// \author Piotr Konopka +/// + +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/QualityObject.h" +#include "Common/TH1Reductor.h" +#include "Common/TH2Reductor.h" +#include "Common/QualityReductor.h" +#include +#include +#include + +#define BOOST_TEST_MODULE CommonReductors test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::common; + +BOOST_AUTO_TEST_CASE(test_TH1Reductor) +{ + auto histo = std::make_unique("test", "test", 10, 0, 10.0); + auto reductor = std::make_unique(); + + auto tree = std::make_unique(); + tree->Branch("histo", reductor->getBranchAddress(), reductor->getBranchLeafList()); + + histo->Fill(5); + reductor->update(histo.get()); + tree->Fill(); + histo->Fill(1); + reductor->update(histo.get()); + tree->Fill(); + histo->Fill(6); + histo->Fill(8); + reductor->update(histo.get()); + tree->Fill(); + + BOOST_REQUIRE_EQUAL(tree->GetEntries(), 3); + tree->Draw("histo.mean:histo.stddev:histo.entries", "", "goff"); + + Double_t* means = tree->GetVal(0); + BOOST_CHECK_CLOSE(means[0], 5, 0.01); + BOOST_CHECK_CLOSE(means[1], 3, 0.01); + BOOST_CHECK_CLOSE(means[2], 5, 0.01); + + Double_t* stddevs = tree->GetVal(1); + BOOST_CHECK_CLOSE(stddevs[0], 0, 0.01); + BOOST_CHECK_CLOSE(stddevs[1], 2, 0.01); + BOOST_CHECK_CLOSE(stddevs[2], 2.55, 0.05); + + Double_t* entries = tree->GetVal(2); + BOOST_CHECK_CLOSE(entries[0], 1, 0.01); + BOOST_CHECK_CLOSE(entries[1], 2, 0.01); + BOOST_CHECK_CLOSE(entries[2], 4, 0.01); +} + +BOOST_AUTO_TEST_CASE(test_TH2Reductor) +{ + auto histo = std::make_unique("test", "test", 10, 0.0, 10.0, 10, 0.0, 10.0); + auto reductor = std::make_unique(); + + auto tree = std::make_unique(); + tree->Branch("histo", reductor->getBranchAddress(), reductor->getBranchLeafList()); + + histo->Fill(5, 5); + reductor->update(histo.get()); + tree->Fill(); + histo->Fill(1, 1); + reductor->update(histo.get()); + tree->Fill(); + histo->Fill(6, 6); + histo->Fill(8, 8); + reductor->update(histo.get()); + tree->Fill(); + + BOOST_REQUIRE_EQUAL(tree->GetEntries(), 3); + tree->Draw("histo.sumw:histo.sumwxy:histo.entries", "", "goff"); + + Double_t* sumws = tree->GetVal(0); + BOOST_CHECK_CLOSE(sumws[0], 1, 0.01); + BOOST_CHECK_CLOSE(sumws[1], 2, 0.01); + BOOST_CHECK_CLOSE(sumws[2], 4, 0.01); + + Double_t* sumwxys = tree->GetVal(1); + BOOST_CHECK_CLOSE(sumwxys[0], 25, 0.01); + BOOST_CHECK_CLOSE(sumwxys[1], 25 + 1, 0.01); + BOOST_CHECK_CLOSE(sumwxys[2], 25 + 1 + 36 + 64, 0.01); + + Double_t* entries = tree->GetVal(2); + BOOST_CHECK_CLOSE(entries[0], 1, 0.01); + BOOST_CHECK_CLOSE(entries[1], 2, 0.01); + BOOST_CHECK_CLOSE(entries[2], 4, 0.01); +} + +BOOST_AUTO_TEST_CASE(test_QualityReductor) +{ + auto reductor = std::make_unique(); + + auto tree = std::make_unique(); + tree->Branch("quality", reductor->getBranchAddress(), reductor->getBranchLeafList()); + + QualityObject qoBad(Quality::Bad, "check1"); + QualityObject qoMedium(Quality::Medium, "check1"); + QualityObject qoGood(Quality::Good, "check1"); + + reductor->update(&qoBad); + tree->Fill(); + reductor->update(&qoBad); + reductor->update(&qoGood); + tree->Fill(); + reductor->update(&qoGood); + tree->Fill(); + reductor->update(&qoGood); + reductor->update(&qoMedium); + tree->Fill(); + + BOOST_REQUIRE_EQUAL(tree->GetEntries(), 4); + tree->Draw("quality.level", "", "goff"); + + Double_t* levels = tree->GetVal(0); + BOOST_CHECK_CLOSE(levels[0], 3, 0.01); + BOOST_CHECK_CLOSE(levels[1], 1, 0.01); + BOOST_CHECK_CLOSE(levels[2], 1, 0.01); + BOOST_CHECK_CLOSE(levels[3], 2, 0.01); + + struct { + UInt_t level; + char name[QualityReductor::NAME_SIZE]; + } qualityStats; + tree->GetBranch("quality")->SetAddress(&qualityStats); + + tree->GetEntry(0); + BOOST_CHECK(!strncmp(qualityStats.name, "Bad", QualityReductor::NAME_SIZE)); + tree->GetEntry(1); + BOOST_CHECK(!strncmp(qualityStats.name, "Good", QualityReductor::NAME_SIZE)); + tree->GetEntry(2); + BOOST_CHECK(!strncmp(qualityStats.name, "Good", QualityReductor::NAME_SIZE)); + tree->GetEntry(3); + BOOST_CHECK(!strncmp(qualityStats.name, "Medium", QualityReductor::NAME_SIZE)); +} \ No newline at end of file diff --git a/Modules/Common/test/testMeanIsAbove.cxx b/Modules/Common/test/testMeanIsAbove.cxx index e8def19175..89c856fb28 100644 --- a/Modules/Common/test/testMeanIsAbove.cxx +++ b/Modules/Common/test/testMeanIsAbove.cxx @@ -1,66 +1,78 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file Publisher_test.cpp /// \author Barthelemy von Haller /// //#define private public // hack to have access to everything -#include "../include/Common/MeanIsAbove.h" +#include "Common/MeanIsAbove.h" #define BOOST_TEST_MODULE MO test #define BOOST_TEST_MAIN #define BOOST_TEST_DYN_LINK + +#include "QualityControl/MonitorObject.h" #include #include #include -#include namespace o2::quality_control_modules::common { BOOST_AUTO_TEST_CASE(test_checks) { - o2::quality_control::core::MonitorObject mo; - mo.addCheck("test", "test", "test"); - mo.setQualityForCheck("test", Quality::Null); + std::shared_ptr mo(new MonitorObject()); // here we are the owner of the histo TH1F th1f("h1", "h1", 10, 0, 9); - mo.setObject(&th1f); - mo.setIsOwner(false); + mo->setObject(&th1f); + mo->setIsOwner(false); + + std::map> moMap = { { "test", mo } }; MeanIsAbove check; - check.configure("mytest"); - Quality quality = check.check(&mo); + CustomParameters customParameters; + customParameters["meanThreshold"] = "1.0"; + check.setCustomParameters(customParameters); + Quality quality = check.check(&moMap); BOOST_CHECK_EQUAL(quality, Quality::Bad); th1f.Fill(1); // the threshold is set to 1 -> bad - quality = check.check(&mo); + quality = check.check(&moMap); BOOST_CHECK_EQUAL(quality, Quality::Bad); th1f.Fill(2); // the threshold is set to 1 -> good - quality = check.check(&mo); + quality = check.check(&moMap); BOOST_CHECK_EQUAL(quality, Quality::Good); - check.beautify(&mo); // add a line + check.beautify(mo, Quality::Null); // add a line BOOST_CHECK_EQUAL(1, th1f.GetListOfFunctions()->GetEntries()); - check.beautify(&mo); + check.beautify(mo, Quality::Null); // Should update the line, not add one --> TODO THIS FAILS // BOOST_CHECK_EQUAL(numberFunctions, th1f.GetListOfFunctions()->GetEntries()); // no modifications to the plot } BOOST_AUTO_TEST_CASE(test_types) { - o2::quality_control::core::MonitorObject mo; - mo.addCheck("test", "test", "test"); - mo.setQualityForCheck("test", Quality::Null); + std::shared_ptr mo(new MonitorObject()); // here we are the owner of the histo TObject obj; - mo.setObject(&obj); - mo.setIsOwner(false); + mo->setObject(&obj); + mo->setIsOwner(false); + + std::map> moMap = { { "test", mo } }; MeanIsAbove check; - check.configure("mytest"); - BOOST_TEST(!check.isObjectCheckable(&mo)); - Quality quality = check.check(&mo); + Quality quality = check.check(&moMap); BOOST_CHECK_EQUAL(quality, Quality::Null); } diff --git a/Modules/Common/test/testNonEmpty.cxx b/Modules/Common/test/testNonEmpty.cxx index 8081ed9843..ba2fdf5847 100644 --- a/Modules/Common/test/testNonEmpty.cxx +++ b/Modules/Common/test/testNonEmpty.cxx @@ -1,16 +1,27 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file testNonEmpty.cxx /// \author Barthelemy von Haller /// -#include "../include/Common/NonEmpty.h" +#include "Common/NonEmpty.h" #define BOOST_TEST_MODULE Publisher test #define BOOST_TEST_MAIN #define BOOST_TEST_DYN_LINK #include -#include +#include "QualityControl/MonitorObject.h" #include namespace o2::quality_control_modules::common @@ -19,27 +30,18 @@ namespace o2::quality_control_modules::common BOOST_AUTO_TEST_CASE(checkable) { TH1F histo("testObject", "test", 100, 0, 99); - MonitorObject monitorObject(&histo, "task"); + MonitorObject monitorObject(&histo, "task", "testClass", "TST"); monitorObject.setIsOwner(false); NonEmpty myCheck; - myCheck.configure("test"); - - BOOST_CHECK_EQUAL(myCheck.getAcceptedType(), "TH1"); - BOOST_CHECK_EQUAL(myCheck.isObjectCheckable(&monitorObject), true); - - TObject obj; - monitorObject.setObject(&obj); - BOOST_CHECK_EQUAL(myCheck.isObjectCheckable(&monitorObject), false); } BOOST_AUTO_TEST_CASE(beautify) { auto* histo = new TH1F("testObject", "test", 100, 0, 99); - MonitorObject monitorObject(histo, "task"); // here we are the owner of the histo + std::shared_ptr monitorObject(new MonitorObject(histo, "task", "testClass", "TST")); // here we are the owner of the histo NonEmpty myCheck; - myCheck.configure("test"); - myCheck.beautify(&monitorObject, Quality::Null); + myCheck.beautify(monitorObject, Quality::Null); BOOST_CHECK_EQUAL(histo->GetFillColor(), kWhite); /*myCheck.beautify(&monitorObject, Quality::Bad); @@ -55,19 +57,21 @@ BOOST_AUTO_TEST_CASE(beautify) BOOST_AUTO_TEST_CASE(nonempty) { TH1F histo("testObject", "test", 100, 0, 99); - MonitorObject monitorObject(&histo, "task"); - monitorObject.setIsOwner(false); + std::shared_ptr monitorObject(new MonitorObject(&histo, "task", "testClass", "TST")); // here we are the owner of the histo + monitorObject->setIsOwner(false); NonEmpty myCheck; - Quality quality = myCheck.check(&monitorObject); + std::map> moMap = { { "test", monitorObject } }; + + Quality quality = myCheck.check(&moMap); BOOST_CHECK_EQUAL(quality, Quality::Bad); histo.Fill(1); - quality = myCheck.check(&monitorObject); + quality = myCheck.check(&moMap); BOOST_CHECK_EQUAL(quality, Quality::Good); histo.Reset(); - quality = myCheck.check(&monitorObject); + quality = myCheck.check(&moMap); BOOST_CHECK_EQUAL(quality, Quality::Bad); } diff --git a/Modules/Common/test/testWorstOfAllAggregator.cxx b/Modules/Common/test/testWorstOfAllAggregator.cxx new file mode 100644 index 0000000000..f86109b891 --- /dev/null +++ b/Modules/Common/test/testWorstOfAllAggregator.cxx @@ -0,0 +1,85 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testWorstOfAllAggregator.cxx +/// \author Piotr Konopka +/// + +#include "Common/WorstOfAllAggregator.h" + +#define BOOST_TEST_MODULE WorstOfAllAggregator test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK +#include + +#include +#include +#include + +using namespace o2::quality_control::checker; +using namespace o2::quality_control::core; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::common +{ + +BOOST_AUTO_TEST_CASE(test_WorstOfAllAggregator) +{ + WorstOfAllAggregator agg1; + agg1.configure(); + agg1.setName("agg1"); + + // prepare data + std::shared_ptr qoNull = std::make_shared(Quality::Null, "testCheckNull", "TST"); + qoNull->addFlag(FlagTypeFactory::BadTracking(), "oh no"); + std::shared_ptr qoGood = std::make_shared(Quality::Good, "testCheckGood", "TST"); + std::shared_ptr qoMedium = std::make_shared(Quality::Medium, "testCheckMedium", "TST"); + qoMedium->addFlag(FlagTypeFactory::BadPID(), "booo"); + std::shared_ptr qoBad = std::make_shared(Quality::Bad, "testCheckBad", "TST"); + QualityObjectsMapType input; + + std::map result1 = agg1.aggregate(input); + BOOST_REQUIRE_EQUAL(result1.size(), 1); + BOOST_CHECK_EQUAL(result1["agg1"], Quality::Null); // because empty vector passed + BOOST_REQUIRE_EQUAL(result1["agg1"].getFlags().size(), 1); + BOOST_CHECK_EQUAL(result1["agg1"].getFlags().at(0).first, FlagTypeFactory::UnknownQuality()); + + input[qoGood->getName()] = qoGood; + std::map result2 = agg1.aggregate(input); + BOOST_REQUIRE_EQUAL(result2.size(), 1); + BOOST_CHECK_EQUAL(result2["agg1"], Quality::Good); + BOOST_CHECK_EQUAL(result2["agg1"].getFlags().size(), 0); + + input[qoMedium->getName()] = qoMedium; + std::map result3 = agg1.aggregate(input); + BOOST_REQUIRE_EQUAL(result3.size(), 1); + BOOST_CHECK_EQUAL(result3["agg1"], Quality::Medium); + BOOST_REQUIRE_EQUAL(result3["agg1"].getFlags().size(), 1); + BOOST_CHECK_EQUAL(result3["agg1"].getFlags().at(0).first, FlagTypeFactory::BadPID()); + + input[qoBad->getName()] = qoBad; + std::map result4 = agg1.aggregate(input); + BOOST_REQUIRE_EQUAL(result4.size(), 1); + BOOST_CHECK_EQUAL(result4["agg1"], Quality::Bad); + BOOST_REQUIRE_EQUAL(result4["agg1"].getFlags().size(), 1); + BOOST_CHECK_EQUAL(result4["agg1"].getFlags().at(0).first, FlagTypeFactory::BadPID()); + + input[qoNull->getName()] = qoNull; + std::map result5 = agg1.aggregate(input); + BOOST_REQUIRE_EQUAL(result5.size(), 1); + BOOST_CHECK_EQUAL(result5["agg1"], Quality::Null); + BOOST_REQUIRE_EQUAL(result5["agg1"].getFlags().size(), 2); + BOOST_CHECK_EQUAL(result5["agg1"].getFlags().at(0).first, FlagTypeFactory::BadPID()); + BOOST_CHECK_EQUAL(result5["agg1"].getFlags().at(1).first, FlagTypeFactory::BadTracking()); +} + +} // namespace o2::quality_control_modules::common diff --git a/Modules/Daq/CMakeLists.txt b/Modules/Daq/CMakeLists.txt index bc1e89788b..6fe323f1b1 100644 --- a/Modules/Daq/CMakeLists.txt +++ b/Modules/Daq/CMakeLists.txt @@ -1,41 +1,33 @@ -set(MODULE_NAME "QcDaq") - -# ---- Files ---- - -set( - SRCS - src/DaqTask.cxx - src/EverIncreasingGraph.cxx -) - -set( - HEADERS - include/Daq/DaqTask.h - include/Daq/EverIncreasingGraph.h -) - # ---- Library ---- -add_library(${MODULE_NAME} SHARED ${SRCS} ${MODULE_NAME}Dict.cxx) +add_library(O2QcDaq) + +target_sources(O2QcDaq PRIVATE src/DaqTask.cxx) target_include_directories( - ${MODULE_NAME} - PUBLIC $ $ + O2QcDaq + PUBLIC $ + $ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ) -target_link_libraries(${MODULE_NAME} PUBLIC QualityControl PRIVATE ROOT::Gpad) +target_link_libraries(O2QcDaq PUBLIC O2QualityControl PRIVATE + ROOT::Gpad + O2::DetectorsRaw + O2QcCommon) + +add_root_dictionary(O2QcDaq + HEADERS include/Daq/DaqTask.h + LINKDEF include/Daq/LinkDef.h) -install( - TARGETS ${MODULE_NAME} +install(TARGETS O2QcDaq LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) - -# ---- ROOT dictionary ---- + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -generate_root_dict(MODULE_NAME ${MODULE_NAME} LINKDEF "include/Daq/LinkDef.h" DICT_CLASS "${MODULE_NAME}Dict") +# Install headers +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/Daq + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") # ---- Tests ---- @@ -46,7 +38,10 @@ foreach(test ${TEST_SRCS}) string(REGEX REPLACE ".cxx" "" test_name ${test_name}) add_executable(${test_name} ${test}) - target_link_libraries(${test_name} PRIVATE ${MODULE_NAME} Boost::unit_test_framework) + target_link_libraries(${test_name} + PRIVATE O2QcDaq Boost::unit_test_framework) add_test(NAME ${test_name} COMMAND ${test_name}) - set_tests_properties(${test_name} PROPERTIES TIMEOUT 60) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) endforeach() diff --git a/Modules/Daq/etc/daq.json b/Modules/Daq/etc/daq.json new file mode 100644 index 0000000000..b85850e62d --- /dev/null +++ b/Modules/Daq/etc/daq.json @@ -0,0 +1,88 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "DaqTask": { + "active": "true", + "className": "o2::quality_control_modules::daq::DaqTask", + "moduleName": "QcDaq", + "detectorName": "ITS", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query": "raw:ITS/RAWDATA" + }, + "taskParameters": { + "TFSizeBins":"100", + "TFSizeMin":"0", + "TFSizeMax":"2047", + "payloadSizeInputsBins":"100", + "payloadSizeInputsMin":"0", + "payloadSizeInputsMax":"2047", + "numberRDHsBins":"100", + "numberRDHsMin":"0", + "numberRDHsMax":"2047", + "sumRdhSizesInTFBins":"100", + "sumRdhSizesInTFMin":"0", + "sumRdhSizesInTFMax":"2047", + "RdhSizesBins":"100", + "RdhSizesMin":"0", + "RdhSizesMax":"2047", + "RdhPayloadSizeBins":"100", + "RdhPayloadSizeMin":"0", + "RdhPayloadSizeMax":"2047", + "CRUidBins":"100", + "CRUidMin":"0", + "CRUidMax":"2047" + } + } + }, + "checks": { + "checkNonEmpty/payloadSize": { + "active": "true", + "className": "o2::quality_control_modules::common::NonEmpty", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "ITS", + "dataSource": [{ + "type": "Task", + "name": "DaqTask", + "MOs": ["payloadSize"] + }] + }, + "checkNonEmpty/IDs": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "ITS", + "dataSource": [{ + "type": "Task", + "name": "DaqTask", + "MOs": ["IDs"] + }] + } + } + } +} diff --git a/Modules/Daq/include/Daq/DaqTask.h b/Modules/Daq/include/Daq/DaqTask.h index c7aad28116..0cf967da8a 100644 --- a/Modules/Daq/include/Daq/DaqTask.h +++ b/Modules/Daq/include/Daq/DaqTask.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file DaqTask.h /// \author Barthelemy von Haller @@ -6,48 +17,55 @@ #ifndef QC_MODULE_DAQ_DAQTASK_H #define QC_MODULE_DAQ_DAQTASK_H -#include "QualityControl/TaskInterface.h" -#include -#include +// ROOT +#include +#include -class TH1F; -class TGraph; +#include "QualityControl/TaskInterface.h" +#include using namespace o2::quality_control::core; namespace o2::quality_control_modules::daq { - -/// \brief Example Quality Control Task -/// It is final because there is no reason to derive from it. Just remove it if needed. +/// \brief Dataflow task +/// It does only look at the header and plots sizes (e.g. payload). +/// It also can print the headers and the payloads by setting printHeaders to "1" +/// and printPayload to "hex" or "bin" in the config file under "taskParameters". /// \author Barthelemy von Haller -class DaqTask /*final*/ : public TaskInterface // todo add back the "final" when doxygen is fixed +class DaqTask final : public o2::quality_control::core::TaskInterface { public: /// \brief Constructor - DaqTask(); - /// Destructor - ~DaqTask() override; + DaqTask() = default; // Definition of the methods for the template method pattern void initialize(o2::framework::InitContext& ctx) override; - void startOfActivity(Activity& activity) override; + void startOfActivity(const o2::quality_control::core::Activity& activity) override; void startOfCycle() override; void monitorData(o2::framework::ProcessingContext& ctx) override; void endOfCycle() override; - void endOfActivity(Activity& activity) override; + void endOfActivity(const o2::quality_control::core::Activity& activity) override; void reset() override; private: - TH1F* mPayloadSize; - TGraph* mIds; - int mNPoints; - TH1F* mNumberSubblocks; - TH1F* mSubPayloadSize; - UInt_t mTimeLastRecord; - TObjString* mObjString; - TCanvas* mCanvas; - TPaveText* mPaveText; + void printInputPayload(const header::DataHeader* header, const char* payload, size_t payloadSize); + void monitorInputRecord(o2::framework::InputRecord& inputRecord); + void monitorRDHs(o2::framework::InputRecord& inputRecord); + int getIntParam(const std::string paramName, int defaultValue = 0); + + // ** general information + // ** objects we publish ** + + // Message related + // Block = the whole InputRecord, i.e. the thing we receive and analyse in monitorData(...) + // SubBlock = a single input of the InputRecord + std::unique_ptr mTFRecordPayloadSize; // filled w/ the sum of the payload size of all the inputs of an inputrecord + std::unique_ptr mInputSize; // filled w/ the size of the inputs in each InputRecord we encounter + std::unique_ptr mNumberRDHs; // filled w/ the number of RDHs found in each InputRecord we encounter + std::unique_ptr mSumRDHSizesInTF; // filled w/ the the sum of RDH memory sizes per InputRecord + std::unique_ptr mSumRDHSizesInRDH; // filled w/ the RDH memory sizes for each RDH + std::unique_ptr mRDHSizesPerCRUIds; // filled w/ the RDH payload size per CRUId }; } // namespace o2::quality_control_modules::daq diff --git a/Modules/Daq/include/Daq/EverIncreasingGraph.h b/Modules/Daq/include/Daq/EverIncreasingGraph.h deleted file mode 100644 index ff2f528903..0000000000 --- a/Modules/Daq/include/Daq/EverIncreasingGraph.h +++ /dev/null @@ -1,42 +0,0 @@ -/// -/// \file EverIncreasingGraph.h -/// \author Barthelemy von Haller -/// - -#ifndef QC_MODULE_DAQ_EVERINCREASINGRAPH_H -#define QC_MODULE_DAQ_EVERINCREASINGRAPH_H - -#include - -#include "QualityControl/CheckInterface.h" -#include "QualityControl/MonitorObject.h" -#include "QualityControl/Quality.h" - -namespace o2::quality_control_modules::daq -{ - -/// \brief Check whether a plot is empty or not. -/// -/// \author Barthelemy von Haller -class EverIncreasingGraph : public o2::quality_control::checker::CheckInterface -{ - public: - /// Default constructor - EverIncreasingGraph(); - /// Destructor - ~EverIncreasingGraph() override; - - void configure(std::string name) override; - Quality check(const MonitorObject* mo) override; - void beautify(MonitorObject* mo, Quality checkResult = Quality::Null) override; - std::string getAcceptedType() override; - - private: - DataBlockId mLastId; - - ClassDefOverride(EverIncreasingGraph, 1); -}; - -} // namespace o2::quality_control_modules::daq - -#endif // QC_MODULE_DAQ_EVERINCREASINGRAPH_H diff --git a/Modules/Daq/include/Daq/LinkDef.h b/Modules/Daq/include/Daq/LinkDef.h index 9934e9d473..0a9ebbafe0 100644 --- a/Modules/Daq/include/Daq/LinkDef.h +++ b/Modules/Daq/include/Daq/LinkDef.h @@ -4,6 +4,4 @@ #pragma link off all functions; #pragma link C++ class o2::quality_control_modules::daq::DaqTask + ; -#pragma link C++ class o2::quality_control_modules::daq::EverIncreasingGraph + ; - #endif diff --git a/Modules/Daq/src/DaqTask.cxx b/Modules/Daq/src/DaqTask.cxx index f06d88d3e0..0a65630a25 100644 --- a/Modules/Daq/src/DaqTask.cxx +++ b/Modules/Daq/src/DaqTask.cxx @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file DaqTask.cxx /// \author Barthelemy von Haller @@ -5,130 +16,250 @@ #include "Daq/DaqTask.h" +// QC #include "QualityControl/QcInfoLogger.h" -#include -#include -#include -#include -#include +#include "QualityControl/stringUtils.h" +// O2 +#include +#include +#include +#include +#include +#include +#include "Common/Utils.h" using namespace std; +using namespace o2::raw; +using namespace o2::framework; +using namespace o2::header; namespace o2::quality_control_modules::daq { -DaqTask::DaqTask() - : TaskInterface(), - mPayloadSize(nullptr), - mIds(nullptr), - mNPoints(0), - mNumberSubblocks(nullptr), - mSubPayloadSize(nullptr), - mObjString(nullptr), - mCanvas(nullptr), - mPaveText(nullptr) +int DaqTask::getIntParam(const std::string paramName, int defaultValue) { + return common::getFromConfig(mCustomParameters, paramName, defaultValue); } -DaqTask::~DaqTask() +void DaqTask::initialize(o2::framework::InitContext& /*ctx*/) { - delete mPaveText; - delete mCanvas; - delete mObjString; - delete mPayloadSize; - delete mIds; - delete mNumberSubblocks; - delete mSubPayloadSize; + ILOG(Debug, Devel) << "initializiation of DaqTask" << ENDM; + + mTFRecordPayloadSize = std::make_unique("TFSize", "Total payload size in TF;bytes", + getIntParam("TFSizeBins", 128), + getIntParam("TFSizeMin", 0), + getIntParam("TFSizeMax", 2047)); + getObjectsManager()->startPublishing(mTFRecordPayloadSize.get(), PublicationPolicy::Forever); + + mInputSize = std::make_unique("payloadSizeInputs", "Payload size of the inputs;bytes", + getIntParam("payloadSizeInputsBins", 128), + getIntParam("payloadSizeInputsMin", 0), + getIntParam("payloadSizeInputsMax", 2047)); + getObjectsManager()->startPublishing(mInputSize.get(), PublicationPolicy::Forever); + + mNumberRDHs = std::make_unique("numberRdhs", "Number of RDHs in TF;RDH count", + getIntParam("numberRDHsBins", 100), + getIntParam("numberRDHsMin", 1), + getIntParam("numberRDHsMax", 100)); + getObjectsManager()->startPublishing(mNumberRDHs.get(), PublicationPolicy::Forever); + + mSumRDHSizesInTF = std::make_unique("sumRdhSizesInTF", "Sum of RDH sizes in TF;bytes", + getIntParam("sumRdhSizesInTFBins", 128), + getIntParam("sumRdhSizesInTFMin", 0), + getIntParam("sumRdhSizesInTFMax", 2047)); + getObjectsManager()->startPublishing(mSumRDHSizesInTF.get(), PublicationPolicy::Forever); + + mSumRDHSizesInRDH = std::make_unique("RdhSizes", "RDH sizes;bytes", + getIntParam("RdhSizesBins", 128), + getIntParam("RdhSizesMin", 0), + getIntParam("RdhSizesMax", 2047)); + getObjectsManager()->startPublishing(mSumRDHSizesInRDH.get(), PublicationPolicy::Forever); + + mRDHSizesPerCRUIds = std::make_unique("RdhPayloadSizePerCRUid", "RDH payload size per CRU", + getIntParam("CRUidBins", (1 << 12) - 1), // CRU id is defined as 12 bits (see O2 RAWDataHeader.h cruID) + getIntParam("CRUidMin", 0), + getIntParam("CRUidMax", 500), + getIntParam("RdhPayloadSizeBins", 128), + getIntParam("RdhPayloadSizeMin", 0), + getIntParam("RdhPayloadSizeMax", 2047)); + mRDHSizesPerCRUIds->GetXaxis()->SetTitle("CRU Id"); + mRDHSizesPerCRUIds->GetYaxis()->SetTitle("bytes"); + getObjectsManager()->startPublishing(mRDHSizesPerCRUIds.get(), PublicationPolicy::Forever); } -void DaqTask::initialize(o2::framework::InitContext& ctx) +void DaqTask::startOfActivity(const Activity& activity) { - QcInfoLogger::GetInstance() << "initialize DaqTask" << AliceO2::InfoLogger::InfoLogger::endm; - - mPayloadSize = new TH1F("payloadSize", "Payload size of blocks;bytes", 2048, 0, 2047); - mPayloadSize->SetCanExtend(TH1::kXaxis); - getObjectsManager()->startPublishing(mPayloadSize); - getObjectsManager()->addCheck(mPayloadSize, "checkNonEmpty", "o2::quality_control_modules::common::NonEmpty", - "QcCommon"); - mNumberSubblocks = new TH1F("numberSubBlocks", "Number of subblocks", 100, 1, 100); - getObjectsManager()->startPublishing(mNumberSubblocks); - mSubPayloadSize = new TH1F("PayloadSizeSubBlocks", "Payload size of subblocks;bytes", 2048, 0, 2047); - mSubPayloadSize->SetCanExtend(TH1::kXaxis); - getObjectsManager()->startPublishing(mSubPayloadSize); - - mIds = new TGraph(); - mIds->SetName("IDs"); - mIds->GetXaxis()->SetTimeDisplay(1); - mIds->GetXaxis()->SetNdivisions(-503); - mIds->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M:%S"); - mIds->GetXaxis()->SetTimeOffset(0, "gmt"); - mIds->SetMarkerStyle(20); - mIds->GetXaxis()->SetLabelSize(0.02); - mIds->GetYaxis()->SetLabelSize(0.02); - getObjectsManager()->startPublishing(mIds); - getObjectsManager()->addCheck(mIds, "checkIncreasingIDs", "o2::quality_control_modules::daq::EverIncreasingGraph", - "QcDaq"); - getObjectsManager()->addCheck(mIds, "checkNonEmpty", "o2::quality_control_modules::common::NonEmpty", "QcCommon"); - - mObjString = new TObjString("hello"); - getObjectsManager()->startPublishing(mObjString); - - mCanvas = new TCanvas(); - mCanvas->cd(); - getObjectsManager()->startPublishing(mCanvas); - - mPaveText = new TPaveText(0.1, 0.1, 0.9, 0.9); - mPaveText->AddText("hello"); - mPaveText->SetName(""); - getObjectsManager()->startPublishing(mPaveText); + ILOG(Debug, Devel) << "startOfActivity: " << activity.mId << ENDM; + reset(); } -void DaqTask::startOfActivity(Activity& activity) +void DaqTask::startOfCycle() { - QcInfoLogger::GetInstance() << "startOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; - mPayloadSize->Reset(); - mNumberSubblocks->Reset(); - mSubPayloadSize->Reset(); - mIds->Set(0); - mNPoints = 0; + ILOG(Debug, Devel) << "startOfCycle" << ENDM; } -void DaqTask::startOfCycle() { QcInfoLogger::GetInstance() << "startOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; } - void DaqTask::monitorData(o2::framework::ProcessingContext& ctx) { - // what does it mean to have several inputs ? is it that we defined several in the config file ? - // If I am connected to the readout can I ever receive several inputs ? - // TODO maybe we need a special daq task for the readout because we have to look into the weird data structure. + monitorInputRecord(ctx.inputs()); + monitorRDHs(ctx.inputs()); +} - // in a loop - uint32_t totalPayloadSize = 0; - for (auto&& input : ctx.inputs()) { - const auto* header = o2::header::get(input.header); - uint32_t size = header->payloadSize; - mSubPayloadSize->Fill(size); - totalPayloadSize += size; - } +void DaqTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} - mPayloadSize->Fill(totalPayloadSize); - mNumberSubblocks->Fill(ctx.inputs().size()); +void DaqTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; - // TODO if data has an id (like event id), we should plot it - // TDatime now; - // if ((now.Get() - mTimeLastRecord) >= 1) { - // mIds->SetPoint(mNPoints, now.Convert(), ctx.inputs().begin()); - // mNPoints++; - // mTimeLastRecord = now.Get(); - // } + getObjectsManager()->stopPublishing(mTFRecordPayloadSize.get()); + getObjectsManager()->stopPublishing(mInputSize.get()); + getObjectsManager()->stopPublishing(mNumberRDHs.get()); + getObjectsManager()->stopPublishing(mSumRDHSizesInRDH.get()); + getObjectsManager()->stopPublishing(mSumRDHSizesInTF.get()); + getObjectsManager()->stopPublishing(mRDHSizesPerCRUIds.get()); } -void DaqTask::endOfCycle() { QcInfoLogger::GetInstance() << "endOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; } +void DaqTask::reset() +{ + ILOG(Info, Support) << "Reset" << ENDM; + + // TODO if the number of plots grows we should probably have a container with pointers/references to all of them. + // then we can just iterate over. -void DaqTask::endOfActivity(Activity& activity) + mTFRecordPayloadSize->Reset(); + mInputSize->Reset(); + mNumberRDHs->Reset(); + mSumRDHSizesInRDH->Reset(); + mSumRDHSizesInTF->Reset(); + mRDHSizesPerCRUIds->Reset(); +} + +void DaqTask::printInputPayload(const header::DataHeader* header, const char* payload, size_t payloadSize) { - QcInfoLogger::GetInstance() << "endOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; + std::vector representation; + if (mCustomParameters["printInputPayload"] == "hex") { + representation = getHexRepresentation((unsigned char*)payload, payloadSize); + } else if (mCustomParameters["printInputPayload"] == "bin") { + representation = getBinRepresentation((unsigned char*)payload, payloadSize); + } + size_t limit = std::numeric_limits::max(); + if (mCustomParameters.count("printInputPayloadLimit") > 0) { + limit = std::stoi(mCustomParameters["printInputPayloadLimit"]); + } + + for (size_t i = 0; i < representation.size();) { + ILOG(Info, Ops) << std::setw(4) << i << " : "; + for (size_t col = 0; col < 4; col++) { + for (size_t word = 0; word < 2; word++) { + if (i + col * 2 + word < representation.size()) { + ILOG(Info, Ops) << representation[i + col * 2 + word]; + } else { + ILOG(Info, Ops) << " "; + } + } + ILOG(Info, Ops) << " | "; + } + ILOG(Info, Ops) << ENDM; + i = i + 8; + // limit output but we don't troncate the lines. + if (i > limit) { + return; + } + } } -void DaqTask::reset() { QcInfoLogger::GetInstance() << "Reset" << AliceO2::InfoLogger::InfoLogger::endm; } +void DaqTask::monitorInputRecord(InputRecord& inputRecord) +{ + uint32_t totalPayloadSize = 0; + for (const auto& input : InputRecordWalker(inputRecord)) { + if (input.header != nullptr) { + const auto* header = DataRefUtils::getHeader(input); + const char* payload = input.payload; + + // payload size + auto size = DataRefUtils::getPayloadSize(input); + mInputSize->Fill(size); + totalPayloadSize += size; + + // printing + if (mCustomParameters.count("printInputHeader") > 0 && mCustomParameters["printInputHeader"] == "true") { + std::cout << fmt::format("{}", *header) << std::endl; + } + if (mCustomParameters.count("printInputPayload") > 0) { + printInputPayload(header, payload, size); + } + } else { + ILOG(Warning, Support) << "Received an input with an empty header" << ENDM; + } + } + mTFRecordPayloadSize->Fill(totalPayloadSize); +} + +template +void printPage(const T& data) +{ + auto const* raw = data.raw(); // retrieving the raw pointer of the page + auto const* rawPayload = data.data(); // retrieving payload pointer of the page + size_t rawPayloadSize = data.size(); // size of payload + size_t offset = data.offset(); // offset of payload in the raw page + + ILOG(Info, Ops) << "Page: " << ENDM; + ILOG(Info, Ops) << " payloadSize: " << rawPayloadSize << ENDM; + ILOG(Info, Ops) << " raw pointer of the page: " << (void*)raw << ENDM; + ILOG(Info, Ops) << " payload pointer of the page: " << (void*)rawPayload << ENDM; + ILOG(Info, Ops) << " offset of payload in the raw page: " << (void*)offset << ENDM; +} + +void DaqTask::monitorRDHs(o2::framework::InputRecord& inputRecord) +{ + // Use the DPLRawParser to get information about the Pages and RDHs stored in the inputRecord + o2::framework::DPLRawParser parser(inputRecord); + size_t totalSize = 0; + size_t rdhCounter = 0; + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + // TODO for some reason this does not work + // ILOG(Info, Ops) << "Header: " << ENDM; + // const auto* header = o2::header::get(it.o2DataHeader()); + // header->print(); + // it.o2DataHeader()->print(); + + // print page + if (mCustomParameters.count("printPageInfo") > 0 && mCustomParameters["printPageInfo"] == "true") { + printPage(it); + } + + // retrieving RDH + const auto& rdh = reinterpret_cast(it.raw()); + if (!rdh) { + ILOG(Info, Ops) << "Cannot parse data to RAW data header" << ENDM; + continue; + } + + // print RDH + if (mCustomParameters.count("printRDH") > 0 && mCustomParameters["printRDH"] == "true") { + ILOG(Info, Ops) << "RDH: " << ENDM; + RDHUtils::printRDH(rdh); + } + + // RDH plots + try { + const auto rdhSize = RDHUtils::getMemorySize(rdh) - RDHUtils::getHeaderSize(rdh); + mSumRDHSizesInRDH->Fill(rdhSize); + totalSize += rdhSize; + rdhCounter++; + + mRDHSizesPerCRUIds->Fill(RDHUtils::getCRUID(rdh), rdhSize); + + } catch (std::runtime_error& e) { + ILOG(Error, Devel) << "Caught an exception when accessing the rdh fields: \n" + << e.what() << ENDM; + } + } + + mSumRDHSizesInTF->Fill(totalSize); + mNumberRDHs->Fill(rdhCounter); +} } // namespace o2::quality_control_modules::daq diff --git a/Modules/Daq/src/EverIncreasingGraph.cxx b/Modules/Daq/src/EverIncreasingGraph.cxx deleted file mode 100644 index 43cbea3b05..0000000000 --- a/Modules/Daq/src/EverIncreasingGraph.cxx +++ /dev/null @@ -1,75 +0,0 @@ -/// -/// \file EverIncreasingGraph.cxx -/// \author Barthelemy von Haller -/// - -#include "Daq/EverIncreasingGraph.h" - -// ROOT -#include -#include -#include -#include - -using namespace std; - -ClassImp(o2::quality_control_modules::daq::EverIncreasingGraph) - - namespace o2::quality_control_modules::daq -{ - - EverIncreasingGraph::EverIncreasingGraph() {} - - EverIncreasingGraph::~EverIncreasingGraph() {} - - void EverIncreasingGraph::configure(std::string name) {} - - Quality EverIncreasingGraph::check(const MonitorObject* mo) - { - Quality result = Quality::Good; - auto* g = dynamic_cast(mo->getObject()); - - // simplistic and inefficient way to check that points are always increasing - int nbPoints = g->GetN(); - double lastY = -DBL_MAX; - for (int i = 1; i < nbPoints; i++) { - double x, y; - g->GetPoint(i, x, y); - if (y < lastY) { - result = Quality::Bad; - break; - } - lastY = y; - } - - return result; - } - - std::string EverIncreasingGraph::getAcceptedType() { return "TGraph"; } - - void EverIncreasingGraph::beautify(MonitorObject* mo, Quality checkResult) - { - cout << "Beautify" << endl; - - if (checkResult == Quality::Null || checkResult == Quality::Medium) { - return; - } - - auto* g = dynamic_cast(mo->getObject()); - if (!g) { - cerr << "MO should be a graph" << endl; - return; - } - - auto* paveText = new TPaveText(0.3, 0.8, 0.7, 0.95, "NDC"); - if (checkResult == Quality::Good) { - paveText->SetFillColor(kGreen); - paveText->AddText("No anomalies"); - } else if (checkResult == Quality::Bad) { - paveText->SetFillColor(kRed); - paveText->AddText("Block IDs are not always increasing"); - } - g->GetListOfFunctions()->AddLast(paveText); - } - -} // namespace daq::quality_control_modules::daq diff --git a/Modules/Daq/test/testQcDaq.cxx b/Modules/Daq/test/testQcDaq.cxx index 9d688fea76..e9333367ff 100644 --- a/Modules/Daq/test/testQcDaq.cxx +++ b/Modules/Daq/test/testQcDaq.cxx @@ -4,14 +4,14 @@ /// #include "Daq/DaqTask.h" -#include "QualityControl/TaskFactory.h" -#include +//#include "QualityControl/TaskFactory.h" +//#include #define BOOST_TEST_MODULE Publisher test #define BOOST_TEST_MAIN #define BOOST_TEST_DYN_LINK -#include +//#include #include using namespace std; @@ -19,29 +19,38 @@ using namespace std; namespace o2::quality_control_modules::daq { +struct Config { + std::string taskName = "test"; + std::string detectorName = "TST"; + std::string consulUrl = "invalid"; +}; + BOOST_AUTO_TEST_CASE(instantiate_task) { DaqTask task; - TaskConfig config; - auto manager = make_shared(config); + Config config; + config.consulUrl = ""; + config.taskName = "qcDaqTest"; + config.detectorName = "DAQ"; + auto manager = make_shared(config.taskName, "DaqTask", config.detectorName, 0); task.setObjectsManager(manager); // o2::framework::InitContext ctx; // task.initialize(ctx); // TODO -// BOOST_CHECK(manager->getMonitorObject("payloadSize")->getObject() != nullptr); + // BOOST_CHECK(manager->getMonitorObject("payloadSize")->getObject() != nullptr); - Activity activity; -// task.startOfActivity(activity); -// task.startOfCycle(); + // Activity activity; + // task.startOfActivity(activity); + // task.startOfCycle(); // auto producer = AliceO2::DataSampling::DataBlockProducer(false, 1024); // DataSetReference dataSet = producer.getDataSet(); // task.monitorDataBlock(dataSet);// TODO -// TH1F* histo = (TH1F*)manager->getMonitorObject("payloadSize")->getObject(); -// BOOST_CHECK(histo->GetEntries() == 1); + // TH1F* histo = (TH1F*)manager->getMonitorObject("payloadSize")->getObject(); + // BOOST_CHECK(histo->GetEntries() == 1); -// task.endOfCycle(); -// task.endOfActivity(activity); + // task.endOfCycle(); + // task.endOfActivity(activity); } } // namespace o2::quality_control_modules::daq diff --git a/Modules/EMCAL/CMakeLists.txt b/Modules/EMCAL/CMakeLists.txt new file mode 100644 index 0000000000..08f45142b3 --- /dev/null +++ b/Modules/EMCAL/CMakeLists.txt @@ -0,0 +1,81 @@ +# ---- Library ---- + +add_library(O2QcEMCAL) + +target_sources(O2QcEMCAL PRIVATE src/FECRateVisualization.cxx src/TriggerTask.cxx src/PedestalTask.cxx src/BCTask.cxx src/RawErrorCheck.cxx src/RawTask.cxx src/RawCheck.cxx src/CellTask.cxx src/CellCheck.cxx src/DigitsQcTask.cxx src/DigitCheck.cxx src/OccupancyReductor.cxx src/OccupancyToFECReductor.cxx src/ClusterTask.cxx src/RawErrorTask.cxx src/CalibMonitoringTask.cxx src/SupermoduleProjectorTask.cxx src/BadChannelMapReductor.cxx src/TimeCalibParamReductor.cxx src/SupermoduleProjectionReductor.cxx src/SubdetectorProjectionReductor.cxx src/BCVisualization.cxx src/CalibCheck.cxx src/NumPatchesPerFastORCheck.cxx src/PedestalChannelCheck.cxx src/PayloadPerEventDDLCheck.cxx src/RawErrorCheckAll.cxx src/CellTimeCalibCheck.cxx src/CellAmpCheck.cxx src/TrendGraphCheck.cxx src/NumPhysTriggCheck.cxx) + +target_include_directories( + O2QcEMCAL + PUBLIC $ $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +target_link_libraries(O2QcEMCAL PUBLIC O2QualityControl O2::DetectorsBase O2::EMCALBase O2::EMCALReconstruction O2::CCDB O2::EMCALCalib O2::DataFormatsCTP ROOT::Spectrum) + +add_root_dictionary(O2QcEMCAL + HEADERS include/EMCAL/DigitsQcTask.h + include/EMCAL/BCTask.h + include/EMCAL/DigitCheck.h + include/EMCAL/RawTask.h + include/EMCAL/RawCheck.h + include/EMCAL/CellTask.h + include/EMCAL/CellCheck.h + include/EMCAL/OccupancyReductor.h + include/EMCAL/OccupancyToFECReductor.h + include/EMCAL/ClusterTask.h + include/EMCAL/RawErrorTask.h + include/EMCAL/RawErrorCheck.h + include/EMCAL/CalibMonitoringTask.h + include/EMCAL/SupermoduleProjectorTask.h + include/EMCAL/BadChannelMapReductor.h + include/EMCAL/TimeCalibParamReductor.h + include/EMCAL/SupermoduleProjectionReductor.h + include/EMCAL/SubdetectorProjectionReductor.h + include/EMCAL/BCVisualization.h + include/EMCAL/PedestalTask.h + include/EMCAL/TriggerTask.h + include/EMCAL/CalibCheck.h + include/EMCAL/NumPatchesPerFastORCheck.h + include/EMCAL/PedestalChannelCheck.h + include/EMCAL/PayloadPerEventDDLCheck.h + include/EMCAL/RawErrorCheckAll.h + include/EMCAL/CellTimeCalibCheck.h + include/EMCAL/CellAmpCheck.h + include/EMCAL/TrendGraphCheck.h + include/EMCAL/DrawGridlines.h + include/EMCAL/FECRateVisualization.h + include/EMCAL/NumPhysTriggCheck.h + include/EMCAL/NumPhysTriggCheck.h + include/EMCAL/IndicesConverter.h + LINKDEF include/EMCAL/LinkDef.h) + +install(TARGETS O2QcEMCAL + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install( + DIRECTORY etc DESTINATION Modules/EMCAL +) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/EMCAL + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Executables ---- + +# ---- Tests ---- +set( + TEST_SRCS +) + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} PRIVATE ${MODULE_NAME} Boost::unit_test_framework O2::CCDB O2::EMCALCalib) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() diff --git a/Modules/EMCAL/doxymodules.h b/Modules/EMCAL/doxymodules.h new file mode 100644 index 0000000000..1ec9528c8c --- /dev/null +++ b/Modules/EMCAL/doxymodules.h @@ -0,0 +1,39 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/** + * @defgroup QCEMCAL EMCAL QC + * @brief EMCAL QualityControl + */ + +/** + * @defgroup EMCALQCTasks EMCAL tasks + * @brief EMCAL QC Tasks + * @ingroup QCEMCAL + */ + +/** + * @defgroup EMCALQCCheckers EMCAL checkers + * @brief EMCAL QC checkers + * @ingroup QCEMCAL + */ + +/** + * @defgroup EMCALQCCPostprocessing EMCAL postprocessing + * @brief EMCAL QC post-processing + * @ingroup QCEMCAL + */ + +/** + * @defgroup EMCALQCCReductors EMCAL reductors + * @brief EMCAL QC reductors + * @ingroup QCEMCAL + */ \ No newline at end of file diff --git a/Modules/EMCAL/etc/cells.json b/Modules/EMCAL/etc/cells.json new file mode 100644 index 0000000000..3608861a69 --- /dev/null +++ b/Modules/EMCAL/etc/cells.json @@ -0,0 +1,142 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "emcccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "CellTask": { + "active": "true", + "className": "o2::quality_control_modules::emcal::CellTask", + "moduleName": "QcEMCAL", + "detectorName": "EMC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query": "emcal-cells:EMC/CELLS/0;emcal-triggerecords:EMC/CELLSSTRGR/0" + }, + "taskParameters": { + "nothing": "rien" + }, + "location": "remote" + } + }, + "checks": { + "checkAmplHighG": { + "active": "true", + "className": "o2::quality_control_modules::emcal::CellCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [ + { + "type": "Task", + "name": "CellTask", + "MOs": [ + "cellAmplitudeHG" + ] + } + ] + }, + "checkAmplLowG": { + "active": "true", + "className": "o2::quality_control_modules::emcal::CellCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [ + { + "type": "Task", + "name": "CellTask", + "MOs": [ + "cellAmplitudeLG" + ] + } + ] + }, + "checkDigitTimeHighG": { + "active": "true", + "className": "o2::quality_control_modules::emcal::CellCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [ + { + "type": "Task", + "name": "CellTask", + "MOs": [ + "cellTimeHG" + ] + } + ] + }, + "checkDigitTimeLowG": { + "active": "true", + "className": "o2::quality_control_modules::emcal::CellCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [ + { + "type": "Task", + "name": "CellTask", + "MOs": [ + "cellTimeLG" + ] + } + ] + }, + "checkAmplEMCAL": { + "active": "true", + "className": "o2::quality_control_modules::emcal::CellCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [ + { + "type": "Task", + "name": "CellTask", + "MOs": [ + "cellAmplitudeEMCAL" + ] + } + ] + }, + "checkAmplDCAL": { + "active": "true", + "className": "o2::quality_control_modules::emcal::CellCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [ + { + "type": "Task", + "name": "CellTask", + "MOs": [ + "cellAmplitudeDCAL" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [] +} \ No newline at end of file diff --git a/Modules/EMCAL/etc/clusters-use-internal-clusterizer.json b/Modules/EMCAL/etc/clusters-use-internal-clusterizer.json new file mode 100644 index 0000000000..307792f811 --- /dev/null +++ b/Modules/EMCAL/etc/clusters-use-internal-clusterizer.json @@ -0,0 +1,46 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "http://ccdb-test.cern.ch:8080/", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://alice-ccdb.cern.ch/" + } + }, + "tasks": { + "ClustersVivek": { + "active": "true", + "className": "o2::quality_control_modules::emcal::ClusterTask", + "moduleName": "QcEMCAL", + "detectorName": "EMC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query": "emcal-cells:EMC/CELLS;emcal-cellstriggerecords:EMC/CELLSTRGR" + }, + "taskParameters": { + "useInternalClusterizer": "true", "":"switching clusterizer: true = internal and false = framework" + }, + "location": "remote" + } + } + }, + "dataSamplingPolicies": [ + + ] + } diff --git a/Modules/EMCAL/etc/clusters.json b/Modules/EMCAL/etc/clusters.json new file mode 100644 index 0000000000..54c677c6f3 --- /dev/null +++ b/Modules/EMCAL/etc/clusters.json @@ -0,0 +1,46 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "http://ccdb-test.cern.ch:8080/", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://alice-ccdb.cern.ch/" + } + }, + "tasks": { + "ClustersVivek": { + "active": "true", + "className": "o2::quality_control_modules::emcal::ClusterTask", + "moduleName": "QcEMCAL", + "detectorName": "EMC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query": "emcal-cells:EMC/CELLS;emcal-cellstriggerecords:EMC/CELLSTRGR;emcal-clusters:EMC/CLUSTERS;emcal-clustertriggerecords:EMC/CLUSTERSTRGR;emcal-cellindices:EMC/INDICES;emcal-citriggerecords:EMC/INDICESTRGR" + }, + "taskParameters": { + "useInternalClusterizer": "false", "":"switching clusterizer: true = internal and false = framework" + }, + "location": "remote" + } + } + }, + "dataSamplingPolicies": [ + + ] + } diff --git a/Modules/EMCAL/etc/decodererrors.json b/Modules/EMCAL/etc/decodererrors.json new file mode 100644 index 0000000000..17848831cb --- /dev/null +++ b/Modules/EMCAL/etc/decodererrors.json @@ -0,0 +1,66 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "RawErrors": { + "active": "true", + "className": "o2::quality_control_modules::emcal::RawErrorTask", + "moduleName": "QcEMCAL", + "detectorName": "EMC", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query": "rawerrors:EMC/DECODERERR" + }, + "location": "remote" + } + }, + "checks": { + "RawErrorsCheck": { + "active": "true", + "className": "o2::quality_control_modules::emcal::RawErrorCheck", + "moduleName": "QcEMCAL", + "policy": "OnEachSeparately", + "detectorName": "EMC", + "dataSource": [ + { + "type": "Task", + "name": "RawErrors", + "MOs": [ + "RawDataErrors", + "PageErrors", + "MajorAltroErrors", + "MinorAltroError", + "RawFitError", + "GeometryError", + "GainTypeError", + "NoHGPerDDL", + "NoLGPerDDL", + "ChannelLGnoHG", + "ChannelHGnoLG" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [] +} \ No newline at end of file diff --git a/Modules/EMCAL/etc/digits.json b/Modules/EMCAL/etc/digits.json new file mode 100644 index 0000000000..d45d62b977 --- /dev/null +++ b/Modules/EMCAL/etc/digits.json @@ -0,0 +1,120 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "emcccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "CellTask": { + "active": "true", + "className": "o2::quality_control_modules::emcal::DigitsQcTask", + "moduleName": "QcEMCAL", + "detectorName": "EMC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query": "emcal-digits:EMC/DIGITS/0;emcal-triggerecords:EMC/DIGITSTRGR/0" + }, + "taskParameters": { + "nothing": "rien" + }, + "location": "remote" + } + }, + "checks": { + "checkAmplHighG": { + "active": "true", + "className": "o2::quality_control_modules::emcal::DigitCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [{ + "type": "Task", + "name": "CellTask", + "MOs": ["digitAmplitudeHG"] + }] + }, + "checkAmplLowG": { + "active": "true", + "className": "o2::quality_control_modules::emcal::DigitCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [{ + "type": "Task", + "name": "CellTask", + "MOs": ["digitAmplitudeLG"] + }] + }, + "checkDigitTimeHighG": { + "active": "true", + "className": "o2::quality_control_modules::emcal::DigitCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [{ + "type": "Task", + "name": "CellTask", + "MOs": ["digitTimeHG"] + }] + }, + "checkDigitTimeLowG": { + "active": "true", + "className": "o2::quality_control_modules::emcal::DigitCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [{ + "type": "Task", + "name": "CellTask", + "MOs": ["digitTimeLG"] + }] + }, + "checkAmplEMCAL": { + "active": "true", + "className": "o2::quality_control_modules::emcal::DigitCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [{ + "type": "Task", + "name": "CellTask", + "MOs": ["digitAmplitudeEMCAL"] + }] + }, + "checkAmplDCAL": { + "active": "true", + "className": "o2::quality_control_modules::emcal::DigitCheck", + "moduleName": "QcEMCAL", + "policy": "OnAny", + "detectorName": "EMC", + "dataSource": [{ + "type": "Task", + "name": "CellTask", + "MOs": ["digitAmplitudeDCAL"] + }] + } + } + }, + "dataSamplingPolicies": [ + + ] + } diff --git a/Modules/EMCAL/etc/readout-stfb-remote.json b/Modules/EMCAL/etc/readout-stfb-remote.json new file mode 100644 index 0000000000..0d1a1753af --- /dev/null +++ b/Modules/EMCAL/etc/readout-stfb-remote.json @@ -0,0 +1,62 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "emcccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "RawTask": { + "active": "true", + "className": "o2::quality_control_modules::emcal::RawTask", + "moduleName": "QcEMCAL", + "detectorName": "EMC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "readout" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "alio2-cr1-qc02.cern.ch", + "remotePort": "47702", + "mergingMode": "delta" + } + } + }, + "dataSamplingPolicies": [ + { + "id": "readout", + "active": "true", + "machines": [], + "query" : "readout:EMC/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/EMCAL/etc/readout-stfb.json b/Modules/EMCAL/etc/readout-stfb.json new file mode 100644 index 0000000000..880d016fb5 --- /dev/null +++ b/Modules/EMCAL/etc/readout-stfb.json @@ -0,0 +1,56 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "emcccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "RawTask": { + "active": "true", + "className": "o2::quality_control_modules::emcal::RawTask", + "moduleName": "QcEMCAL", + "detectorName": "EMC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "readout" + }, + "location": "remote" + } + } + }, + "dataSamplingPolicies": [ + { + "id": "readout", + "active": "true", + "machines": [], + "query" : "readout:EMC/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/EMCAL/etc/readout.json b/Modules/EMCAL/etc/readout.json new file mode 100644 index 0000000000..f1f7c83370 --- /dev/null +++ b/Modules/EMCAL/etc/readout.json @@ -0,0 +1,56 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "emcccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "RawTask": { + "active": "true", + "className": "o2::quality_control_modules::emcal::RawTask", + "moduleName": "QcEMCAL", + "detectorName": "EMC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "readout" + }, + "location": "remote" + } + } + }, + "dataSamplingPolicies": [ + { + "id": "readout", + "active": "true", + "machines": [], + "query" : "readout:ROUT/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/EMCAL/include/EMCAL/BCTask.h b/Modules/EMCAL/include/EMCAL/BCTask.h new file mode 100644 index 0000000000..fd10f0d5cf --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/BCTask.h @@ -0,0 +1,113 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_EMCAL_EMCALBCTASK_H +#define QC_MODULE_EMCAL_EMCALBCTASK_H + +#include "QualityControl/TaskInterface.h" + +#include +#include +#include + +class TH1F; + +namespace o2::ctp +{ +class CTPConfiguration; +} + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::emcal +{ + +/// \class BCTask +/// \brief Task monitoring the BC distribution of EMCAL objects and triggers in CTP +/// \ingroup EMCALQCTasks +/// \author Markus Fasel , Oak Ridge National Laboratory +/// +/// Monitoring the BCs from EMCAL readout and the various EMCAL triggers from CTP +/// readout. +/// +/// Attention: Task requires to have CTP readout in the data. +class BCTask final : public TaskInterface +{ + public: + /// \enum TriggerClassIndex + /// \brief Index of the a given trigger class mask in the class mask array + enum TriggerClassIndex { + EMCMinBias, ///< EMCAL min bias trigger + EMCL0, ///< EMCAL Level-0 trigger + DMCL0, ///< DCAL Level-0 trigger + NTriggerClasses ///< All triggers + }; + /// \brief Constructor + BCTask() = default; + /// Destructor + ~BCTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) override; + + protected: + /// \brief Load trigger configuration for current run and timestamp + /// \param ctpconfig CTP configuration + void loadTriggerClasses(const o2::ctp::CTPConfiguration* ctpconfig); + + /// \brief Parse trigger selection from the task parameters + void parseTriggerSelection(); + + private: + /// \brief Beam presence mode + enum class BeamPresenceMode_t { + ASIDE, ///< Beam only in A-side + BOTH, ///< Beam in both sides + CSIDE, ///< Beam only in C-side + EMPTY, ///< No beam in either of the side + NONE, ///< No beam in LHC + ANY ///< Any of the configurations + }; + + /// \brief Get token of the beam presence mode + /// \param beammode Beam mode + /// \return Beam mode token (empty if ANY or unknown) + std::string getBeamPresenceModeToken(BeamPresenceMode_t beammode) const; + + /// \brief Get the beam mode + /// \param beamname Name of the beam mode + /// \return Beam mode (ANY if unknown) + BeamPresenceMode_t getBeamPresenceMode(const std::string_view beamname) const; + + TH1F* mBCReadout = nullptr; ///< BC distribution from EMCAL readout + TH1F* mBCIncomplete = nullptr; ///< BC distribution of incomplete-rejected triggers + TH1F* mBCEMCAny = nullptr; ///< BC distribution from CTP, any trigger + TH1F* mBCMinBias = nullptr; ///< BC distribution from CTP, EMCAL min. bias trigger + TH1F* mBCL0EMCAL = nullptr; ///< BC distribution from CTP, EMCAL L0 trigger + TH1F* mBCL0DCAL = nullptr; ///< BC distribution from CTP, DCAL trigger + + uint32_t mCurrentRun = -1; ///< Current run + std::array mTriggerClassIndices; ///< Trigger class mask from for different EMCAL triggers from CTP configuration + uint64_t mAllEMCALClasses = 0; ///< All trigger classes firing the EMCAL trigger cluster + std::unordered_map> mTriggerAliases; ///< Trigger aliases + BeamPresenceMode_t mBeamMode = BeamPresenceMode_t::BOTH; ///< Beam mode +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALBCTASK_H diff --git a/Modules/EMCAL/include/EMCAL/BCVisualization.h b/Modules/EMCAL/include/EMCAL/BCVisualization.h new file mode 100644 index 0000000000..c490aaca77 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/BCVisualization.h @@ -0,0 +1,91 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file BCVisualization.h +/// \author Markus Fasel +/// + +#ifndef QUALITYCONTROL_BCVISUALIZATION_H +#define QUALITYCONTROL_BCVISUALIZATION_H + +// QC includes +#include "QualityControl/PostProcessingInterface.h" +#include +#include +#include +#include + +class TCanvas; +class TH1; + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Visualization of BC distributions and determination of Shift between EMCAL and CTP +class BCVisualization final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + enum class MethodBCShift_t { + LEADING_BC, + ISOLATED_BC, + UNKNOWN + }; + /// \brief Constructor + BCVisualization() = default; + /// \brief Destructor + ~BCVisualization() = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef services) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + void reset(); + + private: + std::tuple determineBCShift(const TH1* emchist, const TH1* ctphist, MethodBCShift_t method) const; + std::tuple determineBCShiftIsolated(const TH1* emchist, const TH1* ctphist) const; + std::tuple determineBCShiftLeading(const TH1* emchist, const TH1* ctphist) const; + std::vector getIsolatedBCs(const TH1* bchist) const; + std::vector getShifted(const std::vector& bcEMC, int shift) const; + int getNMatching(const std::vector& bcShiftedEMC, const std::vector& bcCTP) const; + int getLeadingBC(const TH1* histogram) const; + MethodBCShift_t getMethod(const std::string_view methodstring) const; + std::string getMethodString(MethodBCShift_t method) const; + TCanvas* mOutputCanvas = nullptr; ///< output plot + TH1* mFrame = nullptr; ///< Frame for output canvas + std::string mDataPath; ///< Path in QCDB with histos + MethodBCShift_t mShiftEvaluation = MethodBCShift_t::ISOLATED_BC; ///< Method used to determine the BC shift + int mMinNumberOfEntriesBCShift = 100; ///< Min. number of entries for BC shift calculation +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_BCVISUALIZATION_H diff --git a/Modules/EMCAL/include/EMCAL/BadChannelMapReductor.h b/Modules/EMCAL/include/EMCAL/BadChannelMapReductor.h new file mode 100644 index 0000000000..2f239769d2 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/BadChannelMapReductor.h @@ -0,0 +1,98 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_EMCAL_BADCHANNELMAPREDUCTOR_H +#define QUALITYCONTROL_EMCAL_BADCHANNELMAPREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2 +{ +namespace emcal +{ +class Geometry; +} +} // namespace o2 + +namespace o2::quality_control_modules::emcal +{ + +/// \class BadChannelMapReductor +/// \brief Dedicated reductor for EMCAL bad channel map +/// \ingroup EMCALQCCReductors +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \since November 1st, 2022 +/// +/// Produces entries: +/// - Bad/Dead/Non-good channels for Full acceptance/Subdetector/Supermodule +/// - Fraction Bad/Dead/Non-good channels for Full acceptance/Subdetector/Supermodule +/// - Index of the Supermodule with the highest number of Bad/Dead/Non-good channels +class BadChannelMapReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + /// \brief Constructor + BadChannelMapReductor(); + /// \brief Destructor + virtual ~BadChannelMapReductor() = default; + + /// \brief Get branch address of structure with data + /// \return Branch address + void* getBranchAddress() override; + + /// \brief Get list of variables providede by reductor + /// \return List of variables (leaflist) + const char* getBranchLeafList() override; + + /// \brief Extract information from bad channel histogram and fill observables + /// \param obj Input object to get the data from + void update(TObject* obj) override; + + private: + /// \brief Check whether a certain position is within the PHOS region + /// \param column Column number of the position + /// \param row Row number of the position + bool isPHOSRegion(int column, int row) const; + + o2::emcal::Geometry* mGeometry; ///< EMCAL geometry + struct { + Int_t mBadChannelsTotal; ///< Total number of bad channels + Int_t mDeadChannelsTotal; ///< Total number of dead channels + Int_t mNonGoodChannelsTotal; ///< Total number of channels which are dead or bad + Int_t mBadChannelsEMCAL; ///< Number of bad channels in EMCAL + Int_t mBadChannelsDCAL; ///< Number of bad channels in DCAL + Int_t mDeadChannelsEMCAL; ///< Number of dead channels in EMCAL + Int_t mDeadChannelsDCAL; ///< Number of dead channels in DCAL + Int_t mNonGoodChannelsEMCAL; ///< Number of channels which are dead or bad in EMCAL + Int_t mNonGoodChannelsDCAL; ///< Number of channels which are dead or bad in DCAL + Int_t mBadChannelsSM[20]; ///< Number of bad channels per Supermodule + Int_t mDeadChannelsSM[20]; ///< Number of dead channels per Supermodule + Int_t mNonGoodChannelsSM[20]; ///< Number of channels which are dead or bad per Supermodule + Int_t mSupermoduleMaxBad; ///< Index of the supermodule with the highest number of bad channels + Int_t mSupermoduleMaxDead; ///< Index of the supermodule with the highest number of dead channels + Int_t mSupermoduleMaxNonGood; ///< Index of the supermodule with the highest number of channels which are dead or bad + Double_t mFractionBadTotal; ///< Total fraction of bad channels + Double_t mFractionDeadTotal; ///< Total fraction of dead channels + Double_t mFractionNonGoodTotal; ///< Total fraction of channels which are dead or bad + Double_t mFractionBadEMCAL; ///< Fraction of bad channels in EMCAL + Double_t mFractionBadDCAL; ///< Fraction of bad channels in DCAL + Double_t mFractionDeadEMCAL; ///< Fraction of dead channels in EMCAL + Double_t mFractionDeadDCAL; ///< Fraction of dead channels in DCAL + Double_t mFractionNonGoodEMCAL; ///< Fraction of channels in EMCAL which are dead or bad + Double_t mFractionNonGoodDCAL; ///< Fraction of channels in EMCAL which are dead or bad + Double_t mFractionBadSM[20]; ///< Fraction of bad channels per Supermodule + Double_t mFractionDeadSM[20]; ///< Fraction of dead channels per Supermodule + Double_t mFractionNonGoodSM[20]; ///< Fraction of channels which are dead or bad per Supermodule + } mStats; ///< Trending data point +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_TH2REDUCTOR_ \ No newline at end of file diff --git a/Modules/EMCAL/include/EMCAL/CalibCheck.h b/Modules/EMCAL/include/EMCAL/CalibCheck.h new file mode 100644 index 0000000000..5c48fa091c --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/CalibCheck.h @@ -0,0 +1,69 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibCheck.h +/// \author Sierra Cantway +/// + +#ifndef QC_MODULE_EMCAL_EMCALCALIBCHECK_H +#define QC_MODULE_EMCAL_EMCALCALIBCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Check whether a plot is good or not. +/// +/// \author Sierra Cantway +class CalibCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CalibCheck() = default; + /// Destructor + ~CalibCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + /************************************************ + * threshold cuts * + ************************************************/ + + float mBadThresholdMaskStatsAll = 10.; ///< Bad Threshold used in the Max Stats All bad and dead channels check + float mBadThresholdTimeCalibCoeff = 10.; ///< Bad Threshold used in the time Calib Coeff points outside of mean check + float mBadThresholdFractionGoodCellsEvent = 0.; ///< Bad Threshold used in the fraction Good Cells per Event check + float mBadThresholdFractionGoodCellsSupermodule = 0.; ///< Bad Threshold used in the fraction Good Cells per Supermodule check + float mBadThresholdCellAmplitudeSupermoduleCalibPHYS = 10.; ///< Bad Threshold used in the PHYS Cell amplitude (Calib) vs. supermodule ID check + float mBadThresholdCellTimeSupermoduleCalibPHYS = 10.; ///< Bad Threshold used in the PHYS Cell Time (Calib) vs. supermodule ID (High gain) check + float mBadThresholdChannelsFEC = 200.; ///< Bad Threshold used in the Number of Dead/Bad/Dead+Bad per FEC check + + float mMedThresholdMaskStatsAll = 10.; ///< Medium Threshold used in the Max Stats All bad and dead channels check + float mMedThresholdTimeCalibCoeff = 10.; ///< Medium Threshold used in the time Calib Coeff points outside of mean check + float mMedThresholdFractionGoodCellsEvent = 10.; ///< Medium Threshold used in the fraction Good Cells per Event check + float mMedThresholdFractionGoodCellsSupermodule = 10.; ///< Medium Threshold used in the fraction Good Cells per Supermodule check + float mMedThresholdCellAmplitudeSupermoduleCalibPHYS = 10.; ///< Medium Threshold used in the PHYS Cell amplitude (Calib) vs. supermodule ID check + float mMedThresholdCellTimeSupermoduleCalibPHYS = 10.; ///< Medium Threshold used in the PHYS Cell Time (Calib) vs. supermodule ID (High gain) check + float mMedThresholdChannelsFEC = 100.; ///< Bad Threshold used in the Number of Dead/Bad/Dead+Bad per FEC check + + float mSigmaTimeCalibCoeff = 2.; ///< Number of sigmas used in the timeCalibCoeff points outside of mean check + + ClassDefOverride(CalibCheck, 1); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALCALIBCHECK_H diff --git a/Modules/EMCAL/include/EMCAL/CalibMonitoringTask.h b/Modules/EMCAL/include/EMCAL/CalibMonitoringTask.h new file mode 100644 index 0000000000..856c92a4ca --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/CalibMonitoringTask.h @@ -0,0 +1,112 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_CALIBMONITORINGTASK_H +#define QUALITYCONTROL_CALIBMONITORINGTASK_H + +// QC includes +#include "QualityControl/PostProcessingInterface.h" +#include "EMCALCalib/CalibDB.h" +#include "EMCALBase/TriggerMappingV2.h" +#include "EMCALBase/Geometry.h" +#include +#include +#include + +class TCanvas; +class TPaveText; +class TH1; +class TH2; + +namespace o2::emcal +{ +class CalibDB; +class MappingHandler; +} // namespace o2::emcal + +namespace o2::quality_control_modules::emcal +{ + +/// \class CalibMonitoringTask +/// \brief Quality Control task for the calibration data of the EMCAL +/// \ingroup EMCALQCCPostprocessing +/// \author Cristina Terrevoli +/// \author Markus Fasel , Oak Rige National Laboratory +class CalibMonitoringTask final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + CalibMonitoringTask() = default; + /// \brief Destructor + ~CalibMonitoringTask() = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + void reset(); + + private: + int GetTRUIndexFromSTUIndex(Int_t id, Int_t detector); + int GetChannelForMaskRun2(int mask, int bitnumber, bool onethirdsm); + std::vector GetAbsFastORIndexFromMask(); + + std::vector mCalibObjects; ///< list of vectors of parm objects to be processed + TH1* mTimeCalibParamHisto = nullptr; ///< Monitor Time Calib Param + TH2* mTimeCalibParamPosition = nullptr; ///< Monitor time calib param as function of the position in EMCAL + TH2* mBadChannelMapHisto = nullptr; ///< Monitor bad channel map + TH1* mMaskStatsEMCALHisto = nullptr; ///< Monitor number of good, bad, dead cells in emcal only + TH1* mMaskStatsDCALHisto = nullptr; ///< Monitor number of good, bad, dead cells in dcal only + TH1* mMaskStatsAllHisto = nullptr; ///< Monitor number of good, bad, dead cells in emcal + dcal only + TH2* mMaskStatsSupermoduleHisto = nullptr; ///< Monitor number of good, bad, and dead cells per supermodule + TH2* mNumberOfBadChannelsFEC = nullptr; ///< Number of bad channels per FEC + TH2* mNumberOfDeadChannelsFEC = nullptr; ///< Number of dead channels per FEC + TH2* mNumberOfNonGoodChannelsFEC = nullptr; ///< Number of dead+bad channels per FEC + TH1* mSRUFirmwareVersion = nullptr; ///< The SRU Firmware version as function of supermodule ID + TH1* mActiveDDLs = nullptr; ///< Monitor which DDLs are active + TH1* mTRUThresholds = nullptr; ///< The L0 threshold vs TRU ID PHYS + TH1* mL0Algorithm = nullptr; ///< The L0 algorithm vs TRU ID + TH1* mRollbackSTU = nullptr; ///< The Rollback buffer vs TRU ID + TH2* mTRUMaskPositionHisto = nullptr; ///< The FastOR Mask Position in Eta, Phi Histogram + std::unique_ptr mCalibDB; ///< EMCAL calibration DB handler + std::unique_ptr mMapper; ///< EMCAL mapper + o2::emcal::BadChannelMap* mBadChannelMap; ///< EMCAL channel map + o2::emcal::TimeCalibrationParams* mTimeCalib; ///< EMCAL time calib + o2::emcal::FeeDCS* mFeeDCS; ///< EMCAL FEE DCS + + o2::emcal::Geometry* mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); ///< Geometry for mapping position between SM and full EMCAL + std::unique_ptr mTriggerMapping = std::make_unique(mGeometry); ///!> mStoreMaps{}; ///< meta data to be stored with the output in the QCDB +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_CALIBMONITORINGTASK_H diff --git a/Modules/EMCAL/include/EMCAL/CellAmpCheck.h b/Modules/EMCAL/include/EMCAL/CellAmpCheck.h new file mode 100644 index 0000000000..e46acf4b29 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/CellAmpCheck.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright +// holders. All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CellAmpCheck.h +/// \author Ananya Rai +/// + +#ifndef QC_MODULE_EMCAL_EMCALCELLAMPCHECK_H +#define QC_MODULE_EMCAL_EMCALCELLAMPCHECK_H + +#include "QualityControl/CheckInterface.h" +#include + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Check whether cell amplitude is okay by comparing with reference +/// histogram +/// +/// \author Ananya Rai +class CellAmpCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CellAmpCheck() = default; + /// Destructor + ~CellAmpCheck() override = default; + + // Override interface + void configure() override; + Quality + check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, + Quality checkResult = Quality::Null) override; + + private: + // /************************************************ + // * reference histograms * + // ************************************************/ + TH1* mCellAmpReference = nullptr; + /************************************************ + * Chi Square threshold cuts * + ************************************************/ + double mChiSqMedThresh = + 3; ///< If ChiSq greater than this value, quality is medium + double mChiSqBadThresh = 5; ///< threshold of bad ChiSq + ClassDefOverride(CellAmpCheck, 2); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALCELLAMPCHECK_H diff --git a/Modules/EMCAL/include/EMCAL/CellCheck.h b/Modules/EMCAL/include/EMCAL/CellCheck.h new file mode 100644 index 0000000000..1463a538e7 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/CellCheck.h @@ -0,0 +1,44 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CellCheck.h +/// \author Cristina Terrevoli +/// + +#ifndef QC_MODULE_EMCAL_EMCALCELLCHECK_HH +#define QC_MODULE_EMCAL_EMCALCELLCHECK_HH + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Check whether a plot is empty or not. +/// +/// \author Barthelemy von Haller +class CellCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CellCheck() = default; + /// Destructor + ~CellCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(CellCheck, 1); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALCELLCHECK_HH diff --git a/Modules/EMCAL/include/EMCAL/CellTask.h b/Modules/EMCAL/include/EMCAL/CellTask.h new file mode 100644 index 0000000000..8f5d97877b --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/CellTask.h @@ -0,0 +1,239 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_EMCAL_CELLTASK_H +#define QC_MODULE_EMCAL_CELLTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include +#include +#include +#include +#include "CommonDataFormat/InteractionRecord.h" +#include "CommonDataFormat/RangeReference.h" +#include "Headers/DataHeader.h" +#include "DataFormatsEMCAL/TriggerRecord.h" +#include "QualityControl/PostProcessingInterface.h" + +class TH1; +class TH2; + +using namespace o2::quality_control::core; + +namespace o2 +{ +namespace emcal +{ +class Geometry; +class BadChannelMap; +class TimeCalibrationParams; +class GainCalibrationFactors; +class Cell; +} // namespace emcal + +namespace quality_control_modules::emcal +{ + +/// \class CellTask +/// \brief QC Task for EMCAL cells +/// \author Cristina Terrevoli +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \ingroup EMCALQCTasks +/// \since February 14th, 2022 +/// +/// The main monitoring component for EMCAL cell (energy and time measurement in tower). +/// Monitoring observables: +/// - Amplitude for different towers +/// - Time for different towers +class CellTask final : public TaskInterface +{ + public: + struct TaskSettings { + bool mHasAmpVsCellID; + bool mHasTimeVsCellID; + bool mHasHistosCalib; + bool mCalibrateEnergy; + bool mIsHighMultiplicity; + + double mAmpThresholdTimePhys = 0.15; + double mAmpThresholdTimeCalib = 0.3; + double mThresholdPHYS = 0.2; + double mThresholdCAL = 0.5; + double mThresholdTotalEnergy = 0.; + double mThresholdAvTime = 25.; + double mThresholdAvEnergy = 0.5; + + int mMultiplicityRange = 0; + int mMultiplicityRangeDetector = 0; + int mMultiplicityRangeThreshold = 0; + int mMultiplicityRangeSM = 0; + int mMultiplicityRangeSMThreshold = 0; + double mTotalEnergyRange = 0.; + double mTotalEnergyRangeDetector = 0.; + double mTotalEnergyRangeSM = 0.; + double mMaxTimeTotalEnergy = DBL_MAX; + }; + struct CellHistograms { + o2::emcal::Geometry* mGeometry = nullptr; + double mCellThreshold; + double mAmplitudeThresholdTime; + double mThresholdAvTime; + double mThresholdAvEnergy; + // std::array mCellAmplitude = {}; ///< Cell amplitude + TH2* mCellAmplitude = nullptr; ///< Cell amplitude + // std::array mCellTime; ///< Cell time + TH2* mCellTime = nullptr; ///< Cell time + // std::array mCellAmplitudeCalib = {}; ///< Cell amplitude calibrated + TH2* mCellAmplitudeCalib = nullptr; ///< Cell amplitude calibrated + // std::array mCellTimeCalib; ///< Cell time calibrated + TH2* mCellTimeCalib = nullptr; ///< Cell time calibrated + + TH2* mCellAmpSupermodule = nullptr; ///< Cell amplitude all cells versus supermodule + TH2* mCellAmpSupermoduleCalib = nullptr; ///< Cell amplitude good cells versus supermodule + TH2* mCellTimeSupermodule = nullptr; ///< Uncalibrated cell time versus supermodule + TH2* mCellTimeSupermoduleCalib = nullptr; ///< Calibrated cell time (good cells) versus supermodule + TH2* mCellAmpSupermoduleBad = nullptr; ///< Cell amplitude bad cells versus supermodule + TH2* mCellAmplitudeTime = nullptr; ///< Cell amplitude vs. time (raw) + TH2* mCellAmplitudeTimeCalib = nullptr; ///< Cell amplitude vs. time (calibrated) + + TH2* mCellOccupancy = nullptr; ///< Cell occupancy EMCAL and DCAL + TH2* mCellOccupancyThr = nullptr; ///< Cell occupancy EMCAL and DCAL with Energy trheshold + TH2* mCellOccupancyThrBelow = nullptr; ///< Cell occupancy EMCAL and DCAL with Energy trheshold + TH2* mCellOccupancyGood = nullptr; ///< Cell occupancy EMCAL and DCAL good cells + TH2* mCellOccupancyBad = nullptr; ///< Cell occupancy EMCAL and DCAL bad cells + TH2* mIntegratedOccupancy = nullptr; ///< Cell integrated occupancy + TH2* mAverageCellEnergy = nullptr; ///< Average cell energy + TH2* mAverageCellTime = nullptr; ///< Average cell time + TH2* mAverageCellEnergyConstrained = nullptr; ///< Average cell energy + TH2* mAverageCellTimeConstrained = nullptr; ///< Average cell time + TH1* mCellAmplitude_tot = nullptr; ///< Cell amplitude in EMCAL,DCAL + TH1* mCellAmplitudeEMCAL = nullptr; ///< Cell amplitude in EMCAL + TH1* mCellAmplitudeDCAL = nullptr; ///< Cell amplitude in DCAL + TH1* mCellAmplitudeCalib_tot = nullptr; ///< Cell amplitude Calib in EMCAL,DCAL + TH1* mCellAmplitudeCalib_EMCAL = nullptr; ///< Cell amplitude Calib in EMCAL + TH1* mCellAmplitudeCalib_DCAL = nullptr; ///< Cell amplitude Calib in DCAL + std::array mCellTimeBC; ///< Cell amplitude in EMCAL for each bc + TH1* mCellTimeSupermodule_tot = nullptr; ///< Cell time in EMCAL,DCAL per SuperModule + TH1* mCellTimeSupermoduleEMCAL = nullptr; ///< Cell time in EMCAL per SuperModule + TH1* mCellTimeSupermoduleDCAL = nullptr; ///< Cell time in DCAL per SuperModule + TH1* mCellTimeSupermoduleCalib_tot = nullptr; ///< Cell time in EMCAL,DCAL per SuperModule + TH1* mCellTimeSupermoduleCalib_EMCAL = nullptr; ///< Cell time in EMCAL per SuperModule + TH1* mCellTimeSupermoduleCalib_DCAL = nullptr; ///< Cell time in DCAL per SuperModule + TH1* mnumberEvents = nullptr; ///< Number of Events for normalization + std::array mCellTimeSupermoduleEMCAL_Gain = { nullptr, nullptr }; ///< Cell time in EMCAL per high low Gain + std::array mCellTimeSupermoduleDCAL_Gain = { nullptr, nullptr }; ///< Digit time in DCAL per high low Gain + + void initForTrigger(const std::string trigger, const TaskSettings& settings); + void startPublishing(o2::quality_control::core::ObjectsManager& manager); + void reset(); + void clean(); + + void fillHistograms(const o2::emcal::Cell& cell, bool isGood, double timeoffset, double energycalib, int bcphase); + void countEvent(); + }; + + /// \brief Constructor + CellTask() = default; + /// Destructor + ~CellTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) override; + + bool hasConfigValue(const std::string_view key); + std::string getConfigValue(const std::string_view key); + std::string getConfigValueLower(const std::string_view key); + + private: + struct SubEvent { + header::DataHeader::SubSpecificationType mSpecification; + dataformats::RangeReference mCellRange; + }; + + struct CombinedEvent { + InteractionRecord mInteractionRecord; + uint32_t mTriggerType; + std::vector mSubevents; + + [[nodiscard]] int getNumberOfObjects() const + { + int nObjects = 0; + for (auto ev : mSubevents) { + nObjects += ev.mCellRange.getEntries(); + } + return nObjects; + } + + int getNumberOfSubevents() + { + return mSubevents.size(); + }; + }; + void parseMultiplicityRanges(); + void initDefaultMultiplicityRanges(); + void loadCalibrationObjects(o2::framework::ProcessingContext& ctx); + + [[nodiscard]] std::vector buildCombinedEvents(const std::unordered_map>& triggerrecords) const; + TaskSettings mTaskSettings; ///< Settings of the task steered via task parameters + Bool_t mIgnoreTriggerTypes = false; ///< Do not differenciate between trigger types, treat all triggers as phys. triggers + std::map mHistogramContainer; ///< Container with histograms per trigger class + o2::emcal::Geometry* mGeometry = nullptr; ///< EMCAL geometry + const o2::emcal::BadChannelMap* mBadChannelMap = nullptr; ///< EMCAL channel map + const o2::emcal::TimeCalibrationParams* mTimeCalib = nullptr; ///< EMCAL time calib + const o2::emcal::GainCalibrationFactors* mEnergyCalib = nullptr; ///< EMCAL energy calibration + int mTimeFramesPerCycles = 0; ///< TF per cycles + + TH1* mEvCounterTF = nullptr; ///< Number of Events per timeframe + TH1* mEvCounterTFPHYS = nullptr; ///< Number of Events per timeframe per PHYS + TH1* mEvCounterTFCALIB = nullptr; ///< Number of Events per timeframe per CALIB + TH1* mTFPerCyclesTOT = nullptr; ///< Number of Time Frame per cycles TOT + TH1* mTFPerCycles = nullptr; ///< Number of Time Frame per cycles per MonitorData + TH1* mBCCounterPHYS = nullptr; ///< Number of physics triggers in bunch crossing + TH1* mBCCounterCalib = nullptr; ///< Number of calib triggers in bunch crossing + TH1* mCellsMaxSM = nullptr; ///< Supermodule with the largest amount of cells + + TH2* mCells_ev_sm = nullptr; ///< Number of Cells per events per supermodule + TH2* mCells_ev_smThr = nullptr; ///< Number of Cells with Threshold per events per supermodule + TH2* mCells_ev_sm_good = nullptr; ///< Number of good Cells per events per supermodule + TH2* mCells_ev_sm_bad = nullptr; ///< Number of bad Cells per events per supermodule + TH1* mCells_ev = nullptr; ///< Number of Cells per events + TH1* mCells_ev_good = nullptr; ///< Number of good Cells per events + TH1* mCells_ev_bad = nullptr; ///< Number of bad Cells per events + TH1* mCells_ev_Thres = nullptr; ///< Number of Cells with Threshold per events + TH1* mCells_ev_EMCAL = nullptr; ///< Number of Cells per events for EMCAL + TH1* mCells_ev_EMCAL_Thres = nullptr; ///< Number of Cells with Threshold per events for EMCAL + TH1* mCells_ev_EMCAL_good = nullptr; ///< Number of good Cells per events for EMCAL + TH1* mCells_ev_EMCAL_bad = nullptr; ///< Number of bad Cells per events for EMCAL + TH1* mCells_ev_DCAL = nullptr; ///< Number of Cells per events for DCAL + TH1* mCells_ev_DCAL_Thres = nullptr; ///< Number of Cells per events with Threshold for DCAL + TH1* mCells_ev_DCAL_good = nullptr; ///< Number of Cells per events for DCAL + TH1* mCells_ev_DCAL_bad = nullptr; ///< Number of Cells per events for DCAL + TH2* mFracGoodCellsEvent = nullptr; ///< Fraction of good cells / event (all / EMCAL / DCAL) + TH2* mFracGoodCellsSM = nullptr; ///< Fraction of good cells / supermodule + TH1* mTotalEnergy = nullptr; ///< Total energy / event + TH2* mTotalEnergyCorr = nullptr; ///< Total energy correlation EMCAL - DCAL + TH2* mTotalEnergySM = nullptr; ///< Total energy per supermodule / event +}; + +} // namespace quality_control_modules::emcal +} // namespace o2 + +#endif // QC_MODULE_EMCAL_CELLTASK_H diff --git a/Modules/EMCAL/include/EMCAL/CellTimeCalibCheck.h b/Modules/EMCAL/include/EMCAL/CellTimeCalibCheck.h new file mode 100644 index 0000000000..5a4eb91363 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/CellTimeCalibCheck.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CellTimeCalibCheck.h +/// \author Ananya Rai +/// + +#ifndef QC_MODULE_EMCAL_EMCALCELLTIMECALIBCHECK_H +#define QC_MODULE_EMCAL_EMCALCELLTIMECALIBCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Check for the existence of secondary peaks above some threshold value +/// +/// \author Ananya Rai +class CellTimeCalibCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CellTimeCalibCheck() = default; + /// Destructor + ~CellTimeCalibCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + /************************************************ + * threshold cuts * + ************************************************/ + double mSigmaTSpectrum = 0.1; ///< TSpectrum parameter sigma + double mThreshTSpectrum = 0.01; ///< TSpectrum parameter threshold + + ClassDefOverride(CellTimeCalibCheck, 2); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALCELLTIMECALIBCHECK_H diff --git a/Modules/EMCAL/include/EMCAL/ClusterTask.h b/Modules/EMCAL/include/EMCAL/ClusterTask.h new file mode 100644 index 0000000000..baa0920d58 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/ClusterTask.h @@ -0,0 +1,369 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterTask.h +/// \author Vivek Kumar Singh +/// + +#ifndef QC_MODULE_EMC_EMCCLUSTERTASK_H +#define QC_MODULE_EMC_EMCCLUSTERTASK_H + +#include +#include +#include +#include +#include +#include + +#include "QualityControl/TaskInterface.h" +#include +#include +#include + +class TH1; +class TH2; +class TLorentzVector; + +using namespace o2::quality_control::core; + +namespace o2::emcal +{ +class AnalysisCluster; +class BadChannelMap; +class GainCalibrationFactors; +class Geometry; +class TimeCalibrationParams; +} // namespace o2::emcal + +namespace o2::quality_control_modules::emcal +{ + +/// \class ClusterTask +/// \brief QC task analysing EMCAL clusters +/// \ingroup EMCALQCTasks +/// \author Vivek Kumar Singh +/// \since December 3rd, 2021 +class ClusterTask final : public TaskInterface +{ + public: + /// \struct ClusterizerParams + /// \brief Parameters used for clusterization + struct ClusterizerParams { + double mMaxTimeDeltaCells = 1000.; ///< Max. time difference between cells in cluster + double mMinCellTime = -300.; ///< Min. accepted cell time (in ns) + double mMaxCellTime = 300.; ///< Max. accepted cell time (in ns) + double mSeedThreshold = 0.5; ///< Min. energy of the seed cell (in GeV) + double mCellThreshold = 0.1; ///< Min. energy of cells attached to cluster (in GeV) + double mGradientCut = 0.03; ///< cut value for gradient cut (cluster splitting) + bool mDoEnergyGradientCut = false; ///< Switch on/off gradientCut + + /// \brief Print params to output stream + /// \param stream Stream used for printing + void print(std::ostream& stream) const; + }; + + /// \struct InputBindings + /// \brief Bindings of input containers used as task input + struct InputBindings { + std::string mCellBinding = "emcal-cells"; ///< Binding of the cell input container + std::string mCellTriggerRecordBinding = "emcal-cellstriggerecords"; ///< Binding of the trigger record container connected to cell inputs + std::string mClusterBinding = "emcal-clusters"; ///< Binding of the cluster input container (no internal clusterizer mode) + std::string mClusterTriggerRecordBinding = ""; ///< Binding of the triger record container connected to clusters (no internal clusterizer mode) + std::string mCellIndexBinding = "emcal-cellindices"; ///< Binding of the cell index container (no internal clusterizer mode) + std::string mCellIndexTriggerRecordBinding = "emcal-citriggerecords"; ///< Binding of the trigger record container connected to cell indices (no internal clusterizer mode) + }; + + ///< \struct MesonClusterSelection + ///< \brief Cluster selection for meson candidates + struct MesonClusterSelection { + double mMinE = 0.5; ///< Min. Cluster E + double mMaxTime = 25.; ///< Max cluster time relative to 0 + int mMinNCell = 2; ///< Min. Number of cells in cluster + double mMinM02 = -DBL_MAX; ///< Min. M02 (shower shape) + double mMaxM02 = DBL_MAX; ///< Max. M02 (shower shape) + double mMinM20 = -DBL_MAX; ///< Min. M20 (shower shape) + double mMaxM20 = DBL_MAX; ///< Max. M20 (shower shape) + bool mRejectExotics = true; ///< Reject exotic clusters + + /// \brief Select cluster based on cluster cuts + /// \param cluster Cluster to be checked + /// \return true if the cluster is selected, false otherwise + bool isSelected(const o2::emcal::AnalysisCluster& cluster) const; + + /// \brief Print cuts to output stream + /// \param stream Stream used for printing + void print(std::ostream& stream) const; + }; + + /// \struct MesonSelection + /// \brief Cuts applied in meson candidate selection + struct MesonSelection { + double mMinPt = 2.; ///< Min. meson candidate pt + + /// \brief Select meson candidate based on topological and kinematic cuts + /// \param mesonCandidate Meson candidate to be checked + /// \return true if the meson candidate is selected, false otherwise + bool isSelected(const TLorentzVector& mesonCandidate) const; + + /// \brief Print cuts to output stream + /// \param stream Stream used for printing + void print(std::ostream& stream) const; + }; + + /// \struct TaskParams + /// \brief Other task parameters + struct TaskParams { + bool mInternalClusterizer = false; ///< Use run internal clusterizer, do not subscribe to external cluster collection + bool mCalibrate = false; ///< Perform recalibration + bool mFillInvMassMeson = false; ///< Fill invariant mass of meson candidates + bool mFillControlHistograms = false; ///< Fill control histograms at cell level + int mMultiplicityRange = 200; ///< Range for multiplicity histograms + int mMesonMinClusterMultiplicity = 0; ///< Min. cluster multiplicty meson selection + int mMesonMaxClusterMultiplicity = INT_MAX; ///< Max. cluster multiplicity meson selection + double mMinELeadingMeson = 0; ///< Min. energy leading leg meson candidates + + /// \brief Print task parameters to output stream + /// \param stream Stream used for printing + void print(std::ostream& stream) const; + }; + + /// \brief Constructor + ClusterTask() = default; + /// \brief Destructor + ~ClusterTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override { resetHistograms(); } + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) override; + + /// \brief Get the eta/phi position of a cluster + /// \return tuple with [eta, phi] + static std::tuple getClusterEtaPhi(const o2::emcal::AnalysisCluster& cluster); + + protected: + /// \brief Reset all histograms + void resetHistograms(); + + /// \brief trigger loading calibration objects from DPL CCDB backend + /// \param ctx Processing context + void loadCalibrationObjects(o2::framework::ProcessingContext& ctx); + + /// \brief Fill all histograms for a given timeframe + /// + /// Fill all cluster based histograms using the input collections for cluster, indides and cells + /// including the corresponding trigger records. Input collections can come from interanl or external + /// clusterization. + /// + /// \param cells Input cells + /// \param cellTriggerRecords Trigger records related to input cells + /// \param clusters Input clusters + /// \param clusterTriggerRecords Trigger records related to input clusters + /// \param clusterIndices Indices of cells in event beloging to the same cluster + /// \param cellIndexTriggerRecords Trigger records related to cell indices + void analyseTimeframe(const gsl::span& cells, const gsl::span& cellTriggerRecords, const gsl::span clusters, const gsl::span clusterTriggerRecords, const gsl::span clusterIndices, const gsl::span cellIndexTriggerRecords); + + /// \brief Run internal clusterization + /// + /// Run internal clusterization and perform fill output collections with clusters. Settings of the clusterization + /// are steered via the clusterizerParams. + /// + /// \param [in] cells Input cells for clusterization + /// \param [in] cellTriggerRecords Trigger records of cell collection + /// \param [out] clusters Reconstructed clusters + /// \param [out] clusterTriggerRecords Trigger records of cluster collection + /// \param [out] clusterIndices Indices of cells belonging to cluster + /// \param [out] clusterIndexTriggerRecords Trigger record of cluster-cell indices + void findClustersInternal(const gsl::span& cells, const gsl::span& cellTriggerRecords, std::vector& clusters, std::vector& clusterTriggerRecords, std::vector& clusterIndices, std::vector& clusterIndexTriggerRecords); + + /// \brief Build Pi0 mesons and fill histograms + /// + /// Function runs per event. Cluster selection is applied internally. Pi0 candidates + /// are created per subdetector (EMCAL and DCAL). Histograms monitoring the invariant + /// mass with respect to certain observables are filled internally. + /// + /// \param fullclusters List of all analysis clusters for the given event + /// \param isEMCAL Check whether the pi0 candidates are created in the EMCAL or DCAL acceptance + void buildAndAnalysePiOs(const gsl::span fullclusters, bool isEMCAL); + + /// \brief Retrieve lorentz vector for cluster + /// + /// Vertex position unknown in the QC, assuming the vertex to be at + /// (0,0,0) + /// + /// \param fullcluster Full analysis cluster + /// \return Lorentz vector for cluster + TLorentzVector buildClusterVector(const o2::emcal::AnalysisCluster& fullcluster) const; + + /// \brief Perform calibration at cell level + /// + /// Calibrate cell energy and cell time using the CCDB objects cached in the task, + /// and remove bad channels. + /// + /// \param [in] cells Uncalibrated input cells + /// \param [in] triggerRecords Trigger records for uncalibrated cells + /// \param [out] calibratedCells Cells per timeframe after calibration + /// \param [out] calibratedTriggerRecords [out] Trigger records after calibration + void getCalibratedCells(const gsl::span& cells, const gsl::span& triggerRecords, std::vector& calibratedCells, std::vector& calibratedTriggerRecords); + + /// \brief Configure clusterization settings for the internal clusterizer based on the task parameters + void configureClusterizerSettings(); + + /// \brief configure bindings of input containers + void configureBindings(); + + /// \brief Configure meson selection (cluster and pair cuts) for meson candidate histograms + void configureMesonSelection(); + + /// \brief Configure task parameters + void configureTaskParameters(); + + /// \brief Check for config value in taskParameter list + /// \param key Key to check + /// \return true if the key is found in the taskParameters, false otherwise + bool hasConfigValue(const std::string_view key); + + /// \brief Get a configuration value from the taskParameter list (case sensitive) + /// \param key Key to check + /// \return Value assinged to key if present, empty string otherwise + std::string getConfigValue(const std::string_view key); + + /// \brief Get a configuration value from the taskParameter list (case sensitive lower case) + /// \param key Key to check + /// \return Value assinged to key in lower letters if present, empty string otherwise + std::string getConfigValueLower(const std::string_view key); + + /// \brief Fill cluster histograms for physics triggers + /// \param cluster Cluster to be analysed + /// \return True if the cluster is an EMCAL cluster, false if it is a DCAL cluster + bool fillClusterHistogramsPhysics(const o2::emcal::AnalysisCluster& cluster); + + /// \brief Fill cluster histograms for calib (LED) triggers + /// \param cluster Cluster to be analysed + void fillClusterHistogramsLED(const o2::emcal::AnalysisCluster& cluster); + + private: + /// \enum DetType_t + /// \brief Type of subdetector (for detector-specific histograms) + enum DetType_t { + ALL_DET = 0, ///< Both subdetectors (EMCAL+DCAL) + EMCAL_DET = 1, ///< Only EMCAL + DCAL_DET = 2, ///< Only DCAL + NUM_DETS = 3 ///< Number of subdetectors + }; + o2::emcal::Geometry* mGeometry = nullptr; ///< EMCAL geometry + std::unique_ptr> mEventHandler; ///< Event handler for event loop + std::unique_ptr> mClusterFactory; ///< Cluster factory for cluster kinematics + std::unique_ptr> mClusterizer; ///< Internal clusterizer + TaskParams mTaskParameters; ///< Task parameters (i.e. histogram ranges) + ClusterizerParams mClusterizerSettings; ///< Settings for internal clusterizer + InputBindings mTaskInputBindings; ///< Bindings for input containers + MesonClusterSelection mMesonClusterCuts; ///< Cuts used in the meson selection + MesonSelection mMesonCuts; ///< Cuts applied in meson selection + + /////////////////////////////////////////////////////////////////////////////// + /// Calibration objects (for recalibration in case of interal clusterizer) /// + /////////////////////////////////////////////////////////////////////////////// + const o2::emcal::BadChannelMap* mBadChannelMap = nullptr; ///< EMCAL channel map + const o2::emcal::TimeCalibrationParams* mTimeCalib = nullptr; ///< EMCAL time calib + const o2::emcal::GainCalibrationFactors* mEnergyCalib = nullptr; ///< EMCAL energy calib factors + + /////////////////////////////////////////////////////////////////////////////// + /// Control histograms input cells /// + /////////////////////////////////////////////////////////////////////////////// + TH2* mHistCellEnergyTimeUsed = nullptr; ///< Control histogram cell energy vs time all cells for clusterizing (optional) + TH2* mHistCellEnergyTimePhys = nullptr; ///< Control histogram cell energy vs time all cells for clusterizing physics trigger (optional) + TH2* mHistCellEnergyTimeCalib = nullptr; ///< Control histogram cell energy vs time all cells for clusterizing calib trigger (optional) + + /////////////////////////////////////////////////////////////////////////////// + /// Histograms for physics events /// + /////////////////////////////////////////////////////////////////////////////// + TH1* mHistNclustPerTF = nullptr; ///< Histogram number of clusters per timeframe + TH1* mHistNclustPerTFSelected = nullptr; ///< Histogram number of selected clusters per timeframe + TH1* mHistNclustPerEvt = nullptr; ///< Histogram number of clusters per event + TH1* mHistNclustPerEvtSelected = nullptr; ///< Histogram number of selected clusters per event + TH2* mHistClustEtaPhi = nullptr; ///< Histogram cluster acceptance as function of eta and phi + TH2* mHistClustEtaPhiMaxCluster = nullptr; ///< Histogram postition of the leading cluster + TH1* mHistNclustSupermodule = nullptr; ///< Histogram number of clusters per supermodule + TH2* mHistNClustPerEventSupermodule = nullptr; ///< Histogram number of clusters per event and supermodule + TH1* mHistSupermoduleIDMaxCluster = nullptr; ///< ID of the supermodule of the maximum cluster + + std::array mHistTime; ///< Histogram cluster time vs energy (ALL/EMCAL/DCAL clusters) + std::array mHistClustE; ///< Histogram cluster energy (ALL/EMCAL/DCAL clusters) + std::array mHistNCells; ///< Histogram number of cells per cluster (ALL/EMCAL/DCAL clusters) + std::array mHistM02; ///< Histogram M02 per cluster (ALL/EMCAL/DCAL clusters) + std::array mHistM20; ///< Histogram M20 per cluster (ALL/EMCAL/DCAL clusters) + std::array mHistM02VsClustE; ///< Histogram M02 vs. cluster energy (ALL/EMCAL/DCAL clusters) + std::array mHistM20VsClustE; ///< Histogram M20 vs. cluster energy (ALL/EMCAL/DCAL clusters) + std::array mHistClustEMaxCluster; ///< Histogram Energy of the leading cluster / event + std::array mHistClustTimeMaxCluster; ///< Histogram Time of the leading cluster / event + + /////////////////////////////////////////////////////////////////////////////// + /// Supermodule dependent histograms /// + /////////////////////////////////////////////////////////////////////////////// + TH2* mHistClusterTimeSupermodule = nullptr; ///< Cluster time vs. supermodule ID + TH2* mHistClusterEnergySupermodule = nullptr; ///< Cluster energy vs. supermodule ID + TH2* mHistClusterNCellSupermodule = nullptr; ///< Number of cells vs. supermodule ID + TH2* mHistMaxClusterEnergySupermodule = nullptr; ///< Max. cluster energy vs. supermodule ID + TH2* mHistMaxClusterTimeSupermodule = nullptr; ///< Time of the max. cluster vs. supermodule ID + + /////////////////////////////////////////////////////////////////////////////// + /// Histograms for LED events /// + /////////////////////////////////////////////////////////////////////////////// + TH1* mHistNClustPerEvt_Calib = nullptr; ///< Histogram number of clusters per calib event + TH1* mHistNClustPerEvtSelected_Calib = nullptr; ///< Histogram number of selected clusters per calib event + TH2* mHistClusterEtaPhi_Calib = nullptr; ///< Histogram cluster acceptance as function of eta and phi in calib events + TH1* mHistClusterEnergy_Calib = nullptr; ///< Histogram cluster energy in calib events + TH2* mHistClusterEnergyTime_Calib = nullptr; ///< Histogram cluster energy vs. time in calib events + TH2* mHistClusterEnergyCells_Calib = nullptr; ///< Histogram cluster energy vs. cells in calib events + + /////////////////////////////////////////////////////////////////////////////// + /// Histograms for meson candidates /// + /////////////////////////////////////////////////////////////////////////////// + TH1* mHistMassDiphoton_EMCAL = nullptr; ///< Histogram diphoton mass integrated for meson candidates in EMCAL + TH1* mHistMassDiphoton_DCAL = nullptr; ///< Histogram diphoton mass integrated for meson candidates in DCAL + TH2* mHistMassDiphotonPt_EMCAL = nullptr; ///< Histogram diphoton mass integrated vs. pt for meson candidates in EMCAL + TH2* mHistMassDiphotonPt_DCAL = nullptr; ///< Histogram diphoton mass integrated vs. pt for meson candidates in DCAL +}; + +/// \brief Output stream operator for clusterizer parameters in the ClusterTask +/// \param stream Stream used for printing the clusterizer param object +/// \param params ClusterizerParams to be printed +/// \return Stream after printing the params +std::ostream& operator<<(std::ostream& stream, const ClusterTask::ClusterizerParams& params); + +/// \brief Output stream operator for meson cluster selection cuts in the ClusterTask +/// \param stream Stream used for printing the meson cluster selection cuts object +/// \param cuts Meson cluster cuts to be printed +/// \return Stream after printing the cuts +std::ostream& operator<<(std::ostream& stream, const ClusterTask::MesonClusterSelection& cuts); + +/// \brief Output stream operator for meson candidate selection cuts in the ClusterTask +/// \param stream Stream used for printing the meson candidate selecton cuts object +/// \param cuts Meson candidate cuts to be printed +/// \return Stream after printing the cuts +std::ostream& operator<<(std::ostream& stream, const ClusterTask::MesonSelection& cuts); + +/// \brief Output stream operator for task parameters in the ClusterTask +/// \param stream Stream used for printing the task parameter object +/// \param cuts Task parameters to be printed +/// \return Stream after printing the parameters +std::ostream& operator<<(std::ostream& stream, const ClusterTask::TaskParams& params); + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMC_EMCCLUSTERTASK_H diff --git a/Modules/EMCAL/include/EMCAL/DigitCheck.h b/Modules/EMCAL/include/EMCAL/DigitCheck.h new file mode 100644 index 0000000000..9ff210e8c5 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/DigitCheck.h @@ -0,0 +1,41 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_EMCAL_EMCALDIGITCHECK_H +#define QC_MODULE_EMCAL_EMCALDIGITCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::emcal +{ + +/// \class DigitsCheck +/// \brief Checker for histograms from the digits QC task +/// \ingroup EMCALQCCheckers +/// \author Cristina Terrevoli +/// \since September 30th, 2019 +class DigitCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + DigitCheck() = default; + /// Destructor + ~DigitCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(DigitCheck, 2); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALDIGITCHECK_H diff --git a/Modules/EMCAL/include/EMCAL/DigitsQcTask.h b/Modules/EMCAL/include/EMCAL/DigitsQcTask.h new file mode 100644 index 0000000000..39beadb137 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/DigitsQcTask.h @@ -0,0 +1,164 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsQcTask.h +/// \author Markus Fasel, Cristina Terrevoli +/// + +#ifndef QC_MODULE_EMCAL_DIGITSQCTASK_H +#define QC_MODULE_EMCAL_DIGITSQCTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include +#include +#include +#include "CommonDataFormat/InteractionRecord.h" +#include "CommonDataFormat/RangeReference.h" +#include "Headers/DataHeader.h" +#include "DataFormatsEMCAL/TriggerRecord.h" + +class TH1; +class TH2; + +using namespace o2::quality_control::core; + +namespace o2 +{ +namespace emcal +{ +class Geometry; +class BadChannelMap; +class TimeCalibrationParams; +class Cell; +} // namespace emcal + +namespace quality_control_modules +{ +namespace emcal +{ + +/// \brief QC Task for EMCAL digits +/// \author Markus Fasel +/// +/// The main monitoring component for EMCAL digits (energy and time measurement in tower). +/// Monitoring observables: +/// - Digit amplitude for different towers +/// - Digit time for different towers +class DigitsQcTask final : public TaskInterface +{ + public: + struct DigitsHistograms { + o2::emcal::Geometry* mGeometry; + double mCellThreshold = 0; // + //std::array mDigitAmplitude; ///< Digit amplitude + TH2* mDigitAmplitude = nullptr; ///< Digit amplitude + // std::array mDigitTime; ///< Digit time + TH2* mDigitTime = nullptr; ///< Digit time + //std::array mDigitAmplitudeCalib; ///< Digit amplitude calibrated + TH2* mDigitAmplitudeCalib = nullptr; ///< Digit amplitude calibrated + // std::array mDigitTimeCalib; ///< Digit time calibrated + TH2* mDigitTimeCalib = nullptr; ///< Digit time calibrated + + TH2* mDigitAmpSupermodule = nullptr; + TH2* mDigitAmpSupermoduleCalib = nullptr; + TH2* mDigitTimeSupermodule = nullptr; + TH2* mDigitTimeSupermoduleCalib = nullptr; + + TH2* mDigitOccupancy = nullptr; ///< Digit occupancy EMCAL and DCAL + TH2* mDigitOccupancyThr = nullptr; ///< Digit occupancy EMCAL and DCAL with Energy trheshold + TH2* mDigitOccupancyThrBelow = nullptr; ///< Digit occupancy EMCAL and DCAL with Energy trheshold + TH2* mIntegratedOccupancy = nullptr; ///< Digit integrated occupancy + TH1* mDigitAmplitude_tot = nullptr; ///< Digit amplitude in EMCAL,DCAL + TH1* mDigitAmplitudeEMCAL = nullptr; ///< Digit amplitude in EMCAL + std::unordered_map> mDigitTimeBC; ///< Digit amplitude in EMCAL if bc==0 + TH1* mDigitAmplitudeDCAL = nullptr; ///< Digit amplitude in DCAL + TH1* mDigitTimeSupermodule_tot = nullptr; ///< Digit time in EMCAL,DCAL per SuperModule + TH1* mDigitTimeSupermoduleEMCAL = nullptr; ///< Digit time in EMCAL per SuperModule + TH1* mDigitTimeSupermoduleDCAL = nullptr; ///< Digit time in DCAL per SuperModule + TH1* mnumberEvents = nullptr; ///< Number of Events for normalization + + void initForTrigger(const std::string trigger, bool hasAmpVsCellID, bool hasTimeVsCellID, bool hasHistosCalib2D); + void startPublishing(o2::quality_control::core::ObjectsManager& manager); + void reset(); + void clean(); + + void fillHistograms(const o2::emcal::Cell& cell, bool isGood, double timeoffset, int bcphase); + void countEvent(); + }; + + TH1* mEvCounterTF = nullptr; ///< Number of Events per timeframe + TH1* mEvCounterTFPHYS = nullptr; ///< Number of Events per timeframe per PHYS + TH1* mEvCounterTFCALIB = nullptr; ///< Number of Events per timeframe per CALIB + TH1* mTFPerCyclesTOT = nullptr; ///< Number of Time Frame per cycles TOT + TH1* mTFPerCycles = nullptr; ///< Number of Time Frame per cycles per MonitorData + TH1* mDigitsMaxSM = nullptr; ///< Supermodule with the largest amount of digits + + /// \brief Constructor + DigitsQcTask() = default; + /// Destructor + ~DigitsQcTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + bool hasConfigValue(const std::string_view key); + std::string getConfigValue(const std::string_view key); + std::string getConfigValueLower(const std::string_view key); + + private: + struct SubEvent { + header::DataHeader::SubSpecificationType mSpecification; + dataformats::RangeReference mCellRange; + }; + + struct CombinedEvent { + InteractionRecord mInteractionRecord; + uint32_t mTriggerType; + std::vector mSubevents; + + int getNumberOfObjects() const + { + int nObjects = 0; + for (auto ev : mSubevents) { + nObjects += ev.mCellRange.getEntries(); + } + return nObjects; + } + + int getNumberOfSubevents() + { + return mSubevents.size(); + }; + }; + std::vector buildCombinedEvents(const std::unordered_map>& triggerrecords) const; + Bool_t mIgnoreTriggerTypes = false; ///< Do not differenciate between trigger types, treat all triggers as phys. triggers + std::map mHistogramContainer; ///< Container with histograms per trigger class + o2::emcal::Geometry* mGeometry = nullptr; ///< EMCAL geometry + o2::emcal::BadChannelMap* mBadChannelMap; ///< EMCAL channel map + o2::emcal::TimeCalibrationParams* mTimeCalib; ///< EMCAL time calib + int mTimeFramesPerCycles = 0; ///< TF per cycles +}; + +} // namespace emcal +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_EMCAL_DIGITSQCTASK_H diff --git a/Modules/EMCAL/include/EMCAL/DrawGridlines.h b/Modules/EMCAL/include/EMCAL/DrawGridlines.h new file mode 100644 index 0000000000..3ef2357700 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/DrawGridlines.h @@ -0,0 +1,258 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_DRAWGRIDLINES_H +#define QUALITYCONTROL_DRAWGRIDLINES_H + +#include "TLine.h" +#include "TH2D.h" +#include "QualityControl/QcInfoLogger.h" + +namespace o2::quality_control_modules::emcal +{ + +/// \file DrawGridlines.h +/// \brief Quality Control helper task for drawing the EMCAL gridlines +/// \author Sierra Cantway + +class DrawGridlines : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + DrawGridlines() = default; + /// \brief Destructor + ~DrawGridlines() = default; + + /// \brief Draw the gridlines for the Supermodule limits in the Trigger Geometry + static void DrawSMGridInTriggerGeo(TH2* histo = nullptr) + { + if (histo == nullptr) { + return; + } + + // check if the gridlines are already drawn by looking for a line at the first SM boundary + ResetLines(histo); + + // EMCAL + for (int iside = 0; iside <= 48; iside += 24) { + auto smline = new TLine(static_cast(iside) - 0.5, -0.5, static_cast(iside) - 0.5, 63.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + for (int iphi = 0; iphi < 60; iphi += 12) { + auto smline = new TLine(-0.5, static_cast(iphi) - 0.5, 47.5, static_cast(iphi) - 0.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + for (auto iphi = 60; iphi <= 64; iphi += 4) { + auto smline = new TLine(-0.5, static_cast(iphi) - 0.5, 47.5, static_cast(iphi) - 0.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + + // DCAL + for (int side = 0; side < 2; side++) { + int sideoffset = (side == 0) ? 0 : 32; + for (int isepeta = 0; isepeta < 2; isepeta++) { + int etaoffset = sideoffset + isepeta * 16; + auto smline = new TLine(static_cast(etaoffset) - 0.5, 63.5, static_cast(etaoffset) - 0.5, 99.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + for (auto iphi = 76; iphi <= 88; iphi += 12) { + auto smline = new TLine(static_cast(sideoffset) - 0.5, static_cast(iphi) - 0.5, static_cast(sideoffset + 16) - 0.5, static_cast(iphi) - 0.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + } + for (auto iphi = 100; iphi <= 104; iphi += 4) { + auto smline = new TLine(-0.5, static_cast(iphi) - 0.5, 47.5, static_cast(iphi) - 0.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + for (auto ieta = 0; ieta <= 48; ieta += 24) { + auto smline = new TLine(static_cast(ieta) - 0.5, 99.5, static_cast(ieta) - 0.5, 103.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + }; + + /// \brief Draw the gridlines for the TRU limits + static void DrawTRUGrid(TH2* histo = nullptr) + { + if (histo == nullptr) { + return; + } + + // check if the gridlines are already drawn by looking for a line at the first SM boundary + ResetLines(histo); + + // EMCAL + for (int side = 0; side < 2; side++) { + int sideoffset = 24 * side; + for (int itru = 0; itru < 2; itru++) { + int truoffset = sideoffset + (itru + 1) * 8; + auto truline = new TLine(static_cast(truoffset) - 0.5, -0.5, static_cast(truoffset) - 0.5, 59.5); + truline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(truline); + } + } + + // DCAL + for (int side = 0; side < 2; side++) { + int sideoffset = (side == 0) ? 0 : 32; + auto truseparator = new TLine(static_cast(sideoffset + 8) - 0.5, 63.5, static_cast(sideoffset + 8) - 0.5, 99.5); + truseparator->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(truseparator); + } + }; + + /// \brief Draw the gridlines in the standard cell geometry + static void DrawSMGridInStdGeo(TH2* histo = nullptr) + { + if (histo == nullptr) { + return; + } + + // check if the gridlines are already drawn by looking for a line at the first SM boundary + ResetLines(histo); + + // EMCAL + for (int iside = 0; iside <= 96; iside += 48) { + auto smline = new TLine(static_cast(iside) - 0.5, -0.5, static_cast(iside) - 0.5, 127.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + for (int iphi = 0; iphi < 120; iphi += 24) { + auto smline = new TLine(-0.5, static_cast(iphi) - 0.5, 95.5, static_cast(iphi) - 0.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + for (auto iphi = 120; iphi <= 128; iphi += 8) { + auto smline = new TLine(-0.5, static_cast(iphi) - 0.5, 95.5, static_cast(iphi) - 0.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + + // DCAL + for (int side = 0; side < 2; side++) { + int sideoffset = (side == 0) ? 0 : 64; + for (int isepeta = 0; isepeta < 2; isepeta++) { + int etaoffset = sideoffset + isepeta * 32; + auto smline = new TLine(static_cast(etaoffset) - 0.5, 127.5, static_cast(etaoffset) - 0.5, 199.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + for (auto iphi = 152; iphi <= 176; iphi += 24) { + auto smline = new TLine(static_cast(sideoffset) - 0.5, static_cast(iphi) - 0.5, static_cast(sideoffset + 32) - 0.5, static_cast(iphi) - 0.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + } + for (auto iphi = 200; iphi <= 208; iphi += 8) { + auto smline = new TLine(-0.5, static_cast(iphi) - 0.5, 95.5, static_cast(iphi) - 0.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + for (auto ieta = 0; ieta <= 96; ieta += 48) { + auto smline = new TLine(static_cast(ieta) - 0.5, 199.5, static_cast(ieta) - 0.5, 207.5); + smline->SetLineWidth(2); + + histo->GetListOfFunctions()->Add(smline); + } + }; + + /// \brief Draw the gridlines for the FastOR limits + static void DrawFastORGrid(TH2* histo = nullptr) + { + if (histo == nullptr) { + return; + } + + // check if the gridlines are already drawn by looking for a line at the first SM boundary + ResetLines(histo); + + // EMCAL + for (int iphi = 1; iphi < 64; iphi++) { + auto fastorLine = new TLine(-0.5, static_cast(iphi) - 0.5, 47.5, static_cast(iphi) - 0.5); + histo->GetListOfFunctions()->Add(fastorLine); + } + for (int ieta = 1; ieta < 48; ieta++) { + auto fastorLine = new TLine(static_cast(ieta) - 0.5, -0.5, static_cast(ieta) - 0.5, 63.5); + histo->GetListOfFunctions()->Add(fastorLine); + } + + // DCAL + for (int side = 0; side < 2; side++) { + int sideoffset = (side == 0) ? 0 : 32; + for (int ieta = 0; ieta <= 16; ieta++) { + int etaoffset = sideoffset + ieta; + auto fastorline = new TLine(static_cast(etaoffset - 0.5), 63.5, static_cast(etaoffset) - 0.5, 99.5); + histo->GetListOfFunctions()->Add(fastorline); + } + for (int iphi = 0; iphi <= 36; iphi++) { + int phioffset = iphi + 64; + auto fastorline = new TLine(static_cast(sideoffset - 0.5), static_cast(phioffset - 0.5), static_cast(sideoffset + 16) - 0.5, static_cast(phioffset) - 0.5); + histo->GetListOfFunctions()->Add(fastorline); + } + } + for (auto ieta = 1; ieta < 48; ieta++) { + auto etaline = new TLine(static_cast(ieta) - 0.5, 99.5, static_cast(ieta) - 0.5, 103.5); + histo->GetListOfFunctions()->Add(etaline); + } + for (auto iphi = 101; iphi <= 103; iphi++) { + auto philine = new TLine(-0.5, static_cast(iphi) - 0.5, 47.5, static_cast(iphi) - 0.5); + histo->GetListOfFunctions()->Add(philine); + } + }; + + static void ResetLines(TH1* histo = nullptr) + { + if (histo == nullptr) { + return; + } + + auto* funcs = histo->GetListOfFunctions(); + if (!funcs) { + return; + } + + // Remove previously added grid lines + TIter it(funcs); + TObject* obj = nullptr; + while ((obj = it())) { + if (obj->InheritsFrom(TLine::Class())) { + ILOG(Debug, Support) << "Removing existing grid line from histogram " << histo->GetName() << ENDM; + funcs->Remove(obj); + delete obj; + } + } + }; +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_DRAWGRIDLINES_H diff --git a/Modules/EMCAL/include/EMCAL/FECRateVisualization.h b/Modules/EMCAL/include/EMCAL/FECRateVisualization.h new file mode 100644 index 0000000000..30c0d8eec0 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/FECRateVisualization.h @@ -0,0 +1,70 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file FECRateVisualization.h +/// \author Markus Fasel +/// + +#ifndef QUALITYCONTROL_SKELETONPOSTPROCESSING_H +#define QUALITYCONTROL_SKELETONPOSTPROCESSING_H + +#include "QualityControl/PostProcessingInterface.h" +#include +#include +#include +#include + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Example Quality Control Postprocessing Task +/// \author My Name +class FECRateVisualization final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + FECRateVisualization() = default; + /// \brief Destructor + ~FECRateVisualization() override; + + /// \brief Configuration a post-processing task. + /// Configuration of a post-processing task. The task may create variables that shall live throughout its lifetime. + /// \param config boost property with the full QC configuration file + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + std::array, 20> mSupermoduleCanvas; /// Canvas with FEC Rates per supermodule + o2::emcal::IndicesConverter mIndicesConverter; ///< Converter for online-offline supermodule indices + double mMaxRate = 2000.; +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_SKELETONPOSTPROCESSING_H \ No newline at end of file diff --git a/Modules/EMCAL/include/EMCAL/IndicesConverter.h b/Modules/EMCAL/include/EMCAL/IndicesConverter.h new file mode 100644 index 0000000000..d208dcf19d --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/IndicesConverter.h @@ -0,0 +1,65 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_INDICESCONVERTER_H +#define QUALITYCONTROL_INDICESCONVERTER_H +#include +#include + +namespace o2::emcal +{ +class IndicesConverter +{ + public: + IndicesConverter() + { + Initialize(); + } + ~IndicesConverter() = default; + + void Initialize() + { + // Initialize the map with online and offline supermodule indices + for (int i = 0; i < 20; i++) { + std::string SMSide = "A"; + if (i % 2 != 0) { + SMSide = "C"; + } + int SMRowIndex = (i / 2) + (i >= 12 ? 3 : 0); // Adjust row index for DCAL + mOnlineToOfflineSMMap[i] = "SM" + SMSide + std::to_string(SMRowIndex); + } + } + + int GetOfflineSMIndex(const std::string& onlineSMIndex) const + { + for (const auto& pair : mOnlineToOfflineSMMap) { + if (pair.second == onlineSMIndex) { + return pair.first; + } + } + return -1; // Return -1 if no matching index is found + } + std::string GetOnlineSMIndex(const int offlineSMIndex) const + { + if (mOnlineToOfflineSMMap.find(offlineSMIndex) != mOnlineToOfflineSMMap.end()) { + return mOnlineToOfflineSMMap.at(offlineSMIndex); + } else { + return "Invalid SM Index"; + } + } + + private: + std::map mOnlineToOfflineSMMap; ///< Map for conversion between online and offline supermodule indices +}; + +} // namespace o2::emcal + +#endif // QUALITYCONTROL_INDICESCONVERTER_H \ No newline at end of file diff --git a/Modules/EMCAL/include/EMCAL/LinkDef.h b/Modules/EMCAL/include/EMCAL/LinkDef.h new file mode 100644 index 0000000000..30cad874b9 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/LinkDef.h @@ -0,0 +1,45 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::emcal::DigitsQcTask + ; +#pragma link C++ class o2::quality_control_modules::emcal::DigitCheck + ; +#pragma link C++ class o2::quality_control_modules::emcal::CellTask + ; +#pragma link C++ class o2::quality_control_modules::emcal::CellCheck + ; +#pragma link C++ class o2::quality_control_modules::emcal::RawTask + ; +#pragma link C++ class o2::quality_control_modules::emcal::RawCheck + ; +#pragma link C++ class o2::quality_control_modules::emcal::OccupancyReductor + ; +#pragma link C++ class o2::quality_control_modules::emcal::OccupancyToFECReductor + ; +#pragma link C++ class o2::quality_control_modules::emcal::ClusterTask + ; +#pragma link C++ class o2::quality_control_modules::emcal::RawErrorTask + ; +#pragma link C++ class o2::quality_control_modules::emcal::RawErrorCheck + ; +#pragma link C++ class o2::quality_control_modules::emcal::CalibMonitoringTask + ; +#pragma link C++ class o2::quality_control_modules::emcal::SupermoduleProjectorTask + ; +#pragma link C++ class o2::quality_control_modules::emcal::BadChannelMapReductor + ; +#pragma link C++ class o2::quality_control_modules::emcal::TimeCalibParamReductor + ; +#pragma link C++ class o2::quality_control_modules::emcal::SupermoduleProjectionReductorBase + ; +#pragma link C++ class o2::quality_control_modules::emcal::SupermoduleProjectionReductorX + ; +#pragma link C++ class o2::quality_control_modules::emcal::SupermoduleProjectionReductorY + ; +#pragma link C++ class o2::quality_control_modules::emcal::SubdetectorProjectionReductor + ; +#pragma link C++ class o2::quality_control_modules::emcal::BCTask + ; +#pragma link C++ class o2::quality_control_modules::emcal::BCVisualization + ; +#pragma link C++ class o2::quality_control_modules::emcal::PedestalTask + ; +#pragma link C++ class o2::quality_control_modules::emcal::CalibCheck + ; +#pragma link C++ class o2::quality_control_modules::emcal::TriggerTask + ; +#pragma link C++ class o2::quality_control_modules::emcal::NumPatchesPerFastORCheck + ; +#pragma link C++ class o2::quality_control_modules::emcal::PedestalChannelCheck + ; +#pragma link C++ class o2::quality_control_modules::emcal::NumPhysTriggCheck + ; + +#pragma link C++ class o2::quality_control_modules::emcal::PayloadPerEventDDLCheck + ; + +#pragma link C++ class o2::quality_control_modules::emcal::CellTimeCalibCheck + ; +#pragma link C++ class o2::quality_control_modules::emcal::CellAmpCheck + ; +#pragma link C++ class o2::quality_control_modules::emcal::TrendGraphCheck + ; + +#pragma link C++ class o2::quality_control_modules::emcal::RawErrorCheckAll + ; +#pragma link C++ class o2::quality_control_modules::emcal::DrawGridlines + ; + +#pragma link C++ class o2::quality_control_modules::emcal::FECRateVisualization+; + +#endif diff --git a/Modules/EMCAL/include/EMCAL/NumPatchesPerFastORCheck.h b/Modules/EMCAL/include/EMCAL/NumPatchesPerFastORCheck.h new file mode 100644 index 0000000000..6b71830b6d --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/NumPatchesPerFastORCheck.h @@ -0,0 +1,117 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file NumPatchesPerFastORCheck.h +/// \author Sierra Cantway +/// + +#ifndef QC_MODULE_EMCAL_EMCALNUMPATCHESPERFASTORCHECK_H +#define QC_MODULE_EMCAL_EMCALNUMPATCHESPERFASTORCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "EMCALBase/TriggerMappingV2.h" +#include "EMCALBase/Geometry.h" +#include +#include + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Check whether a plot is good or not. +/// +/// \author Sierra Cantway +class NumPatchesPerFastORCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + NumPatchesPerFastORCheck() = default; + /// Destructor + ~NumPatchesPerFastORCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + struct FastORNoiseInfo { + int mCounts; + int mTRUIndex; + int mFastORIndex; + int mPosPhi; + int mPosEta; + + bool operator==(const FastORNoiseInfo& other) const + { + return mCounts == other.mCounts && mFastORIndex == other.mFastORIndex; + } + bool operator<(const FastORNoiseInfo& other) const + { + if (mCounts < other.mCounts) { + return true; + } + if (mTRUIndex == other.mTRUIndex) { + return mFastORIndex < other.mFastORIndex; + } + return false; + } + bool operator>(const FastORNoiseInfo& other) const + { + if (mCounts > other.mCounts) { + return true; + } + if (mTRUIndex == other.mTRUIndex) { + return mFastORIndex > other.mFastORIndex; + } + return false; + } + }; + + struct FastORNoiseLevel { + int mCounts; + int mFastORID; + int mPosGlobalPhi; + int mPosGlobalEta; + bool mRejected; + + bool operator==(const FastORNoiseLevel& other) const + { + return (mCounts == other.mCounts && mFastORID == other.mFastORID); + } + + bool operator<(const FastORNoiseLevel& other) const + { + if (mCounts == other.mCounts) { + return mFastORID < other.mFastORID; + } + return mCounts < other.mCounts; + } + }; + + private: + /************************************************ + * threshold cuts * + ************************************************/ + + float mBadSigmaNumPatchesPerFastOR = 5.; ///< Number of sigmas used in the Number of Patches Per FastOR fastORs outside of mean bad check + float mMedSigmaNumPatchesPerFastOR = 999.; ///< Number of sigmas used in the Number of Patches Per FastOR fastORs outside of mean medium check + int mLogLevelIL = 0; ///< Log level on InfoLogger + + o2::emcal::Geometry* mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); ///< Geometry for mapping position between SM and full EMCAL + std::unique_ptr mTriggerMapping = std::make_unique(mGeometry); ///!> mNoisyTRUPositions; ///< Positions of all found noisy TRUs (bad quality) + std::set> mHighCountTRUPositions; ///< Positions of all FastORs with high count rate (medium Quality) + + ClassDefOverride(NumPatchesPerFastORCheck, 1); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALNUMPATCHESPERFASTORCHECK_H diff --git a/Modules/EMCAL/include/EMCAL/NumPhysTriggCheck.h b/Modules/EMCAL/include/EMCAL/NumPhysTriggCheck.h new file mode 100644 index 0000000000..03b44abda8 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/NumPhysTriggCheck.h @@ -0,0 +1,57 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file NumPhysTriggCheck.h +/// \author Joshua Koenig +/// + +#ifndef QC_MODULE_EMCAL_EMCALNUMPHYSTRIGCHECK_HH +#define QC_MODULE_EMCAL_EMCALNUMPHYSTRIGCHECK_HH + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Check whether a plot is empty or not. +/// +/// \author Barthelemy von Haller +class NumPhysTriggCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// \brief Default constructor + NumPhysTriggCheck() = default; + /// \brief Destructor + ~NumPhysTriggCheck() override = default; + + /// \brief Configure checker setting thresholds from taskParameters where specified + void configure() override; + + /// \brief Check whether the number of physics triggers for the current trigger falls below a certain threshold copared to the maximum in the time range + /// \param moMap List of histos to check + /// \return Quality of the selection + Quality check(std::map>* moMap) override; + + /// \brief Beautify the monitoring objects + /// \param mo Monitoring object to beautify + /// \param checkResult Quality status of this checker + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + ClassDefOverride(NumPhysTriggCheck, 1); + + private: + double mFracToMaxGood = 0.5; ///< Thresholds for minimum fraction of physics triggers compared to maximum +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALNUMPHYSTRIGCHECK_HH diff --git a/Modules/EMCAL/include/EMCAL/OccupancyReductor.h b/Modules/EMCAL/include/EMCAL/OccupancyReductor.h new file mode 100644 index 0000000000..a3af40d764 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/OccupancyReductor.h @@ -0,0 +1,70 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#ifndef QUALITYCONTROL_EMCAL_OCCUPANCYREDUCTOR_H +#define QUALITYCONTROL_EMCAL_OCCUPANCYREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2 +{ +namespace emcal +{ +class Geometry; +} // namespace emcal +} // namespace o2 + +namespace o2::quality_control_modules::emcal +{ + +/// \class OccupancyReductor +/// \brief Reductor for Occupancy histograms +/// \ingroup EMCALQCCReductors +/// \author Cristina Terrevoli +/// \since November 2nd, 2022 +/// +/// Extracting number of entries above 0 for each supermodule area. In addition extracting +/// also mean and rms per supermodule, and mean and rms over all non-0 objects +class OccupancyReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + /// \brief Constructor + OccupancyReductor(); + + /// \brief Destructor + virtual ~OccupancyReductor() = default; + + /// \brief Get branch address of structure with data + /// \return Branch address + void* getBranchAddress() override; + + /// \brief Get list of variables providede by reductor + /// \return List of variables (leaflist) + const char* getBranchLeafList() override; + + /// \brief Calculate observables per SM and update tree + /// \param obj Input object to get the data from + void update(TObject* obj) override; + + private: + o2::emcal::Geometry* mGeometry; ///< EMCAL Geometry + struct { + Double_t mCountTotal; ///< Total counts + Double_t mAverage; ///< Average value + Double_t mRMS; ///< RMS value + Double_t mCountSM[20]; ///< Counts per SM + Double_t mAverageSM[20]; ///< Average value per SM + Double_t mRMSSM[20]; ///< RMS value per SM + } mStats; +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_EMCAL_OCCUPANCYREDUCTOR_H \ No newline at end of file diff --git a/Modules/EMCAL/include/EMCAL/OccupancyToFECReductor.h b/Modules/EMCAL/include/EMCAL/OccupancyToFECReductor.h new file mode 100644 index 0000000000..e77b2c2b1a --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/OccupancyToFECReductor.h @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#ifndef QUALITYCONTROL_EMCAL_OCCUPANCYTOFECREDUCTOR_H +#define QUALITYCONTROL_EMCAL_OCCUPANCYTOFECREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" +#include "EMCALBase/Mapper.h" + +namespace o2 +{ +namespace emcal +{ +class Geometry; +} // namespace emcal +} // namespace o2 + +namespace o2::quality_control_modules::emcal +{ + +/// \class OccupancyToFECReductor +/// \brief Reductor for Occupancy histograms exporting to FEC granularity +/// \ingroup EMCALQCCReductors +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \since May 22nd, 2024 +/// +/// Extracting number of entries above 0 for each FEC area. In addition extracting +/// also mean and rms per FEC, and mean and rms over all non-0 objects +/// +/// The reductor exports 2400 data points, therefore it should be used with care. +/// One should only enable the reductor manually in case trending per supermodule +/// indicates a problem. +class OccupancyToFECReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + /// \brief Constructor + OccupancyToFECReductor(); + + /// \brief Destructor + virtual ~OccupancyToFECReductor() = default; + + /// \brief Get branch address of structure with data + /// \return Branch address + void* getBranchAddress() override; + + /// \brief Get list of variables providede by reductor + /// \return List of variables (leaflist) + const char* getBranchLeafList() override; + + /// \brief Calculate observables per SM and update tree + /// \param obj Input object to get the data from + void update(TObject* obj) override; + + private: + o2::emcal::Geometry* mGeometry; ///< EMCAL Geometry + o2::emcal::MappingHandler mMapper; ///< EMCAL Mapper (to find the FEC ID from the online ID of a channel) + struct { + Double_t mCountFEC[800]; ///< Counts per FEC + Double_t mAverageFEC[800]; ///< Average value per FEC + Double_t mRMSFEC[800]; ///< RMS value per FEC + } mStats; +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_EMCAL_OCCUPANCYTOFECREDUCTOR_H \ No newline at end of file diff --git a/Modules/EMCAL/include/EMCAL/PayloadPerEventDDLCheck.h b/Modules/EMCAL/include/EMCAL/PayloadPerEventDDLCheck.h new file mode 100644 index 0000000000..79325018d1 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/PayloadPerEventDDLCheck.h @@ -0,0 +1,55 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CellOccupancyCheck.h +/// \author Ananya Rai +/// + +#ifndef QC_MODULE_EMCAL_EMCALPAYLOADPEREVENTDDLCHECK_H +#define QC_MODULE_EMCAL_EMCALPAYLOADPEREVENTDDLCHECK_H + +#include "QualityControl/CheckInterface.h" +#include + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Check whether payload/event for DDL is okay +/// +/// \author Ananya Rai +class PayloadPerEventDDLCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + PayloadPerEventDDLCheck() = default; + /// Destructor + ~PayloadPerEventDDLCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + // /************************************************ + // * reference histograms * + // ************************************************/ + TH2* mCalibReference = nullptr; + double mChiSqMedPayloadThresh = 3; ///< If ChiSq greater than this value, quality is medium + double mChiSqBadLowPayloadThresh = 5; ///< Low threshold of bad ChiSq + + ClassDefOverride(PayloadPerEventDDLCheck, 3); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALPAYLOADPEREVENTDDLCHECK_H diff --git a/Modules/EMCAL/include/EMCAL/PedestalChannelCheck.h b/Modules/EMCAL/include/EMCAL/PedestalChannelCheck.h new file mode 100644 index 0000000000..89abfa1f33 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/PedestalChannelCheck.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalChannelCheck.h +/// \author Sierra Cantway +/// + +#ifndef QC_MODULE_EMCAL_EMCALPEDESTALCHANNELCHECK_H +#define QC_MODULE_EMCAL_EMCALPEDESTALCHANNELCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Check whether a plot is good or not. +/// +/// \author Sierra Cantway +class PedestalChannelCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + PedestalChannelCheck() = default; + /// Destructor + ~PedestalChannelCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + /************************************************ + * threshold cuts * + ************************************************/ + + float mBadPedestalChannelADCRangeLow = 20.; ///< Bad Low Threshold for mean ADC used in the Pedestal Channels check + float mBadPedestalChannelADCRangeHigh = 100.; ///< Bad High Threshold for mean ADC used in the Pedestal Channels check + float mBadPedestalChannelOverMeanThreshold = 0.2; ///< Bad Threshold used in the time Pedestal Channels points outside of mean check + + float mMedPedestalChannelADCRangeLow = 40.; ///< Med Low Threshold for mean ADC used in the Pedestal Channels check + float mMedPedestalChannelADCRangeHigh = 80.; ///< Med High Threshold for mean ADC used in the Pedestal Channels check + float mMedPedestalChannelOverMeanThreshold = 0.1; ///< Med Threshold used in the time Pedestal Channels points outside of mean check + + float mSigmaPedestalChannel = 2.; ///< Number of sigmas used in the Pedestal Channel points outside of mean check + + ClassDefOverride(PedestalChannelCheck, 1); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALPEDESTALCHANNELCHECK_H diff --git a/Modules/EMCAL/include/EMCAL/PedestalTask.h b/Modules/EMCAL/include/EMCAL/PedestalTask.h new file mode 100644 index 0000000000..0ba13d2dbd --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/PedestalTask.h @@ -0,0 +1,70 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_EMCAL_EMCALPEDESTALTASK_H +#define QC_MODULE_EMCAL_EMCALPEDESTALTASK_H + +#include "QualityControl/TaskInterface.h" + +class TH1; +class TH2; + +using namespace o2::quality_control::core; + +namespace o2::emcal +{ +class Geometry; +} + +namespace o2::quality_control_modules::emcal +{ + +/// \class PedestalTask +/// \brief Monitoring of the Pedestals extracted by the pedestal calibration +/// \ingroup EMCALQCTasks +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \since March 27th, 2024 +class PedestalTask final : public TaskInterface +{ + public: + /// \brief Constructor + PedestalTask() = default; + /// Destructor + ~PedestalTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + TH1* mPedestalChannelFECHG = nullptr; + TH1* mPedestalChannelFECLG = nullptr; + TH1* mPedestalChannelLEDMONHG = nullptr; + TH1* mPedestalChannelLEDMONLG = nullptr; + + TH2* mPedestalPositionFECHG = nullptr; + TH2* mPedestalPositionFECLG = nullptr; + TH2* mPedestalPositionLEDMONHG = nullptr; + TH2* mPedestalPositionLEDMONLG = nullptr; + + int mNumberObjectsFetched = 0; + + o2::emcal::Geometry* mGeometry = nullptr; +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALPEDESTALTASK_H diff --git a/Modules/EMCAL/include/EMCAL/RawCheck.h b/Modules/EMCAL/include/EMCAL/RawCheck.h new file mode 100644 index 0000000000..65c98a7808 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/RawCheck.h @@ -0,0 +1,95 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_EMCAL_EMCALRAWCHECK_H +#define QC_MODULE_EMCAL_EMCALRAWCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "EMCAL/IndicesConverter.h" +#include +#include +#include + +class TH1; +class TH2; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::emcal +{ + +/// \class RawCheck +/// \brief EMCAL raw data check +/// \ingroup EMCALQCCheckers +/// \author Cristina Terrevoli +/// \since March 4th, 2020 +class RawCheck final : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + RawCheck() = default; + /// Destructor + ~RawCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + Quality runPedestalCheck1D(TH1* adcdist, double minentries, double signalfraction) const; + std::vector runPedestalCheck2D(TH2* adchist, double minentries, double signalfraction) const; + std::array, 20> reduceBadFECs(std::vector fecids) const; + int getExepctedFECs(int smID) const; + std::tuple getCutsBunchMinAmpHist(bool perSM, bool perFEC); + + void loadConfigValueInt(const std::string_view configvalue, int& target); + void loadConfigValueDouble(const std::string_view configvalue, double& target); + void loadConfigValueBool(const std::string_view configvalue, bool& target); + + /************************************************ + * Switches for InfoLogger Messages * + ************************************************/ + bool mIlMessageBunchMinAmpCheckSM = false; ///< Switch for IL message for Bunch Min. amplitude check at supermodule level + bool mIlMessageBunchMinAmpCheckDetector = false; ///< Switch for IL message for Bunch Min. amplitude check at subdetector level + bool mIlMessageBunchMinAmpCheckFull = false; ///< Switch for IL message for Bunch Min. amplitude check at full detector level + bool mILMessageRawErrorCheck = false; ///< Switch for IL message for error code + bool mILMessageNoisyFECCheck = false; ///< Switch for IL message for noisy FEC + bool mILMessagePayloadSizeCheck = false; ///< Switch for IL message for large payload size + + /************************************************ + * Conversion between online and offline indices * + ************************************************/ + o2::emcal::IndicesConverter mIndicesConverter; ///< Converter for online and offline supermodule indices + + /************************************************ + * sigma cuts * + ************************************************/ + double mNsigmaFECMaxPayload = 2.; ///< Number of sigmas used in the FEC max payload check + double mNsigmaPayloadSize = 2.; ///< Number of sigmas used in the payload size check + + /************************************************ + * Settings for bunch min. amp checker * + ************************************************/ + + int mBunchMinCheckMinEntries = 0; ///< Min. number of entries for bunch min. amplitude in evaluation region + double mBunchMinCheckFractionSignal = 0.5; ///< Fraction of entries in signal region for bunch min. amp checker + int mBunchMinCheckMinEntriesSM = 0; ///< Min. number of entries for bunch min. amplitude in evaluation region (SM-based histograms, optional) + double mBunchMinCheckFractionSignalSM = 0.; ///< Fraction of entries in signal region for bunch min. amp checker (SM-based histograms, optional) + int mBunchMinCheckMinEntriesFEC = 0; ///< Min. number of entries for bunch min. amplitude in evaluation region (FEC-based histograms, optional) + double mBunchMinCheckFractionSignalFEC = 0.; ///< Fraction of entries in signal region for bunch min. amp checker (FEC-based histograms, optional) + + ClassDefOverride(RawCheck, 2); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALRAWCHECK_H diff --git a/Modules/EMCAL/include/EMCAL/RawErrorCheck.h b/Modules/EMCAL/include/EMCAL/RawErrorCheck.h new file mode 100644 index 0000000000..7417cffb6e --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/RawErrorCheck.h @@ -0,0 +1,118 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawErrorCheck.h +/// \author Markus Fasel +/// + +#ifndef QC_MODULE_EMCAL_EMCALRAWERRORCHECK_H +#define QC_MODULE_EMCAL_EMCALRAWERRORCHECK_H + +#include "QualityControl/CheckInterface.h" + +#include + +namespace o2::emcal +{ +class Geometry; +} + +namespace o2::quality_control_modules::emcal +{ + +/// \class RawErrorCheck +/// \brief Checker for histograms with error code published by the RawErrorTask +/// \author Markus Fasel +/// +/// Checking for presence of an error code. Any presence of error code (non-0 entry) +/// will define data as bad. +class RawErrorCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// \brief Default constructor + RawErrorCheck() = default; + /// \brief Destructor + ~RawErrorCheck() override = default; + + /// \brief Configure checker setting thresholds from taskParameters where specified + void configure() override; + + /// \brief Check for each object whether the number of errors of type is above the expected threshold + /// \param moMap List of histos to check + /// \return Quality of the selection + Quality check(std::map>* moMap) override; + + /// \brief Beautify the monitoring objects showing the quality determination and a supermodule grid for channel based histograms + /// \param mo Monitoring object to beautify + /// \param checkResult Quality status of this checker + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + ClassDefOverride(RawErrorCheck, 2); + + private: + /// \brief Decode key of a configurable parameter as boolean + /// \param value Value to be decoded (true or false, case-insensitive) + /// \return Boolean representation of the value + /// \throw std::runtime_error in case value is not a boolean value + bool decodeBool(std::string value) const; + + /// \brief Find error code of the raw decoder error + /// \param errorname Name of the raw decoder error + /// \return Error code of the error name (-1 in case not found) + int findErrorCodeRDE(const std::string_view errorname) const; + + /// \brief Find error code of the page error + /// \param errorname Name of the page error + /// \return Error code of the error name (-1 in case not found) + int findErrorCodePE(const std::string_view errorname) const; + + /// \brief Find error code of the major ALTRO decoding error + /// \param errorname Name of the major ALTRO decoding error + /// \return Error code of the error name (-1 in case not found) + int findErrorCodeMAAE(const std::string_view errorname) const; + + /// \brief Find error code of the minor ALTRO decoding error + /// \param errorname Name of minor ALTRO decoding error + /// \return Error code of the error name (-1 in case not found) + int findErrorCodeMIAE(const std::string_view errorname) const; + + /// \brief Find error code of the raw fit error + /// \param errorname Name of the raw fit error + /// \return Error code of the error name (-1 in case not found) + int findErrorCodeRFE(const std::string_view errorname) const; + + /// \brief Find error code of the geometry error + /// \param errorname Name of the geometry error + /// \return Error code of the error name (-1 in case not found) + int findErrorCodeGEE(const std::string_view errorname) const; + + /// \brief Find error code of the gain type error + /// \param errorname Name of the gain type error + /// \return Error code of the error name (-1 in case not found) + int findErrorCodeGTE(const std::string_view errorname) const; + + o2::emcal::Geometry* mGeometry; ///< Geometry for mapping position between SM and full EMCAL + bool mNotifyInfologger = true; ///< Switch for notification to infologger + std::map mErrorCountThresholdRDE; ///< Thresholds for Raw Decoder Error histogram + std::map mErrorCountThresholdPE; ///< Thresholds for Page Error histogram + std::map mErrorCountThresholdMAAE; ///< Thresholds for Major ALTRO Error histogram + std::map mErrorCountThresholdMIAE; ///< Thresholds for Minor ALTRO Error histogram + std::map mErrorCountThresholdRFE; ///< Thresholds for Raw Fit Error histogram + std::map mErrorCountThresholdGEE; ///< Thresholds for Geometry Error histogram + std::map mErrorCountThresholdGTE; ///< Thresholds for Gain Type Error histogram + + std::map> mErrorCountThresholdRDESummary; ///< Thresholds for Raw Data Error Summary histograms. Values are for error and warning +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALRAWERRORCHECK_H diff --git a/Modules/EMCAL/include/EMCAL/RawErrorCheckAll.h b/Modules/EMCAL/include/EMCAL/RawErrorCheckAll.h new file mode 100644 index 0000000000..89283a7314 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/RawErrorCheckAll.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawErrorCheckAll.h +/// \author Ananya Rai +/// + +#ifndef QC_MODULE_EMCAL_EMCALRAWERRORCHECKALL_H +#define QC_MODULE_EMCAL_EMCALRAWERRORCHECKALL_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Check if the number of raw errors is below a certain threshold via a moving average. +/// +/// \author Ananya Rai +class RawErrorCheckAll : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + RawErrorCheckAll() = default; + /// Destructor + ~RawErrorCheckAll() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + /************************************************ + * threshold cuts * + ************************************************/ + int mPeriodMovAvg = 10000; ///< Period for moving average + double mBadThreshold = 10000; ///< Threshold to be considered bad + + ClassDefOverride(RawErrorCheckAll, 1); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALRAWERRORCHECKALL_H diff --git a/Modules/EMCAL/include/EMCAL/RawErrorTask.h b/Modules/EMCAL/include/EMCAL/RawErrorTask.h new file mode 100644 index 0000000000..9b5a333342 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/RawErrorTask.h @@ -0,0 +1,81 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_EMCAL_EMCALRAWERRORTASK_H +#define QC_MODULE_EMCAL_EMCALRAWERRORTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include + +class TH2; +class TH1; + +using namespace o2::quality_control::core; +namespace o2::emcal +{ +class Geometry; +class MappingHandler; +} // namespace o2::emcal +namespace o2::quality_control_modules::emcal +{ + +/// \class RawErrorTask +/// \brief Monitoring task for raw decoding errors from the EMCAL reconstruction +/// \author Cristina Terrevoli +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \ingroup EMCALQCTasks +/// \since June 14th, 2022 +class RawErrorTask final : public TaskInterface +{ + public: + /// \brief Constructor + RawErrorTask() = default; + /// Destructor + ~RawErrorTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + std::string getConfigValue(const std::string_view key); + std::string getConfigValueLower(const std::string_view key); + + TH2* mErrorTypeAll = nullptr; ///< Base histogram any raw data error + TH2* mErrorTypeAltro = nullptr; ///< Major ALTRO payload decoding errors + TH2* mErrorTypePage = nullptr; ///< DMA page decoding errors + TH2* mErrorTypeMinAltro = nullptr; ///< Minor ALTRO payload decoding errors + TH2* mErrorTypeFit = nullptr; ///< Raw fit errors + TH2* mErrorTypeGeometry = nullptr; ///< Geometry errors + TH2* mErrorTypeGain = nullptr; ///< Gain type errors + TH1* mErrorTypeUnknown = nullptr; ///< Counter of defined error codes + TH2* mErrorGainLow = nullptr; ///< FEC with LGnoHG error + TH2* mErrorGainHigh = nullptr; ///< FEC with HGnoLG error + TH2* mChannelGainLow = nullptr; ///< Tower with LGnoHG error + TH2* mChannelGainHigh = nullptr; ///< Tower with HGnoLG error + TH2* mFecIdMinorAltroError = nullptr; ///< Minor Altro Error per DDL + TH2* mTRUErrorType = nullptr; ///< TRU decoding error type + TH2* mTRUErrorPosition = nullptr; ///< TRU decoding error position + + bool mExcludeGainErrorsFromOverview; ///< exclude gain error from global overview panel + + o2::emcal::Geometry* mGeometry = nullptr; ///< EMCAL geometry + std::unique_ptr mMapper; ///< EMCAL mapper +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALRAWERRORTASK_H diff --git a/Modules/EMCAL/include/EMCAL/RawTask.h b/Modules/EMCAL/include/EMCAL/RawTask.h new file mode 100644 index 0000000000..348fd05497 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/RawTask.h @@ -0,0 +1,140 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_EMCAL_EMCALRAWTASK_H +#define QC_MODULE_EMCAL_EMCALRAWTASK_H + +#include "QualityControl/TaskInterface.h" +#include "EMCALBase/Mapper.h" +#include +#include +#include +#include +#include + +#include "DetectorsRaw/RDHUtils.h" +#include "Headers/RAWDataHeader.h" +#include +#include + +class TH1F; +class TH2F; +class TProfile2D; + +using namespace o2::quality_control::core; + +namespace o2::emcal +{ +class Geometry; +} + +namespace o2::quality_control_modules::emcal +{ + +/// \class RawTask +/// \brief Monitoring task for observables directly obtained from EMCAL FEC raw data +/// \author Cristina Terrevoli +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \ingroup EMCALQCTasks +/// \since December 17th, 2019 +class RawTask final : public TaskInterface +{ + public: + /// \brief Constructor + RawTask() = default; + /// Destructor + ~RawTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + /// \brief Set the data origin + /// \param origin Data origin + /// + /// Normally data origin is EMC, however in case the + /// Task subscribes directly to readout or the origin + /// is different in the STFbuilder this needs to be handled + /// accordingly + void setDataOrigin(const std::string_view origin) { mDataOrigin = origin; } + + enum class EventType { + CAL_EVENT, + PHYS_EVENT + }; + + private: + /// \struct RawEventType + /// \brief Key type for maps caching event information from different subevents, containing also trigger information + struct RawEventType { + o2::InteractionRecord mIR; + uint32_t mTrigger; + bool operator==(const RawEventType& other) const { return mIR == other.mIR; } + bool operator<(const RawEventType& other) const { return mIR < other.mIR; } + }; + + /// \struct RawEventTypeHash + /// \brief Hash-value calculation for struct RawEventType, used in std::unordered_map + struct RawEventTypeHash { + /// \brief Hash function, purely based on bc and orbit ID as they are unique for a collision + /// \param evtype Raw event information with event interaction record + /// \return Hash value + std::size_t operator()(const RawEventType& evtype) const + { + size_t h1 = std::hash()(evtype.mIR.bc); + size_t h2 = std::hash()(evtype.mIR.orbit); + return h1 ^ (h2 << 1); + } + }; + + bool isLostTimeframe(framework::ProcessingContext& ctx) const; + + o2::emcal::Geometry* mGeometry = nullptr; ///< EMCAL geometry + std::unique_ptr mMappings; ///< Mappings Hardware address -> Channel + std::string mDataOrigin = "EMC"; + TH1* mMessageCounter = nullptr; + TH1* mNumberOfSuperpagesPerMessage = nullptr; + TH1* mNumberOfPagesPerMessage = nullptr; + TH1* mTotalDataVolume = nullptr; ///< Total data volume + TH1* mNbunchPerChan = nullptr; ///< Number of bunch per Channel + TH1* mNofADCsamples = nullptr; ///< Number of ADC samples per Channel + TH1* mADCsize = nullptr; ///< ADC size per bunch + TH2* mFECmaxCountperSM = nullptr; ///< max number of hit channels per SM + TH2* mFECmaxIDperSM = nullptr; ///< FEC ID max number of hit channels per SM + std::unordered_map mBunchMinRawAmpSM; ///< Min Raw amplitude per Supermodule + std::unordered_map mBunchMinRawAmpFEC; ///< Min Raw amplitude per FEC + std::unordered_map mBunchMaxRawAmpSM; ///< Max Raw amplitude per Supermodule + std::unordered_map mBunchMaxRawAmpFEC; ///< Max Raw amplitude per FEC + std::unordered_map mSMMinRawAmpSM; ///< Min Raw amplitude per Supermodule + std::unordered_map mSMMaxRawAmpSM; ///< Max Raw amplitude per Supermodule + std::unordered_map mRMSBunchADCRCFull; ///< ADC rms for EMCAL+DCAL togheter + std::unordered_map mMeanBunchADCRCFull; ///< ADC mean + std::unordered_map mMaxChannelADCRCFull; ///< ADC max + std::unordered_map mMinChannelADCRCFull; ///< ADC min + TH2* mErrorTypeAltro = nullptr; ///< Error from AltroDecoder + TH2* mPayloadSizePerDDL = nullptr; ///< Payload size per ddl + TH1* mPayloadSizePerDDL_1D = nullptr; ///< Accumulated Payload size per ddl + TH2* mPayloadSizeTFPerDDL = nullptr; ///< Payload size per TimeFrame per ddl + TH1* mPayloadSizeTFPerDDL_1D = nullptr; ///< Accumulated Payload size per TimeFrame per ddl + TH1* mTFerrorCounter = nullptr; ///< Number of TF builder errors + Int_t mNumberOfSuperpages = 0; ///< Simple total superpage counter + Int_t mNumberOfPages = 0; ///< Simple total number of superpages counter + Int_t mNumberOfMessages = 0; +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALRAWTASK_H diff --git a/Modules/EMCAL/include/EMCAL/SubdetectorProjectionReductor.h b/Modules/EMCAL/include/EMCAL/SubdetectorProjectionReductor.h new file mode 100644 index 0000000000..dcc0573bf0 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/SubdetectorProjectionReductor.h @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#ifndef QUALITYCONTROL_EMCAL_SUDETECTORPROJECTIONREDUCTOR_H +#define QUALITYCONTROL_EMCAL_SUDETECTORPROJECTIONREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Reductor of slices per subdetector. +/// +/// Obtaining number of entries, mean, sigma and max for each slice +/// of the input histogram (subdetector dimension) +class SubdetectorProjectionReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + SubdetectorProjectionReductor() = default; + virtual ~SubdetectorProjectionReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + Double_t mCountsTotal; + Double_t mCountsEMCAL; + Double_t mCountsDCAL; + Double_t mMeanTotal; + Double_t mMeanEMCAL; + Double_t mMeanDCAL; + Double_t mSigmaTotal; + Double_t mSigmaEMCAL; + Double_t mSigmaDCAL; + } mStats; +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_EMCAL_SUDETECTORPROJECTIONREDUCTOR_H \ No newline at end of file diff --git a/Modules/EMCAL/include/EMCAL/SupermoduleProjectionReductor.h b/Modules/EMCAL/include/EMCAL/SupermoduleProjectionReductor.h new file mode 100644 index 0000000000..2f4e74d771 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/SupermoduleProjectionReductor.h @@ -0,0 +1,59 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#ifndef QUALITYCONTROL_EMCAL_SUPERMODULEPROJECTIONREDUCTOR_H +#define QUALITYCONTROL_EMCAL_SUPERMODULEPROJECTIONREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" +namespace o2::quality_control_modules::emcal +{ + +/// \brief Reductor of slices per supermodule. +/// +/// Obtaining number of entries, mean, sigma and max for each slice +/// of the input histogram (supermodule dimension) +class SupermoduleProjectionReductorBase : public quality_control::postprocessing::ReductorTObject +{ + public: + SupermoduleProjectionReductorBase() = default; + virtual ~SupermoduleProjectionReductorBase() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + void setSupermoduleAxisX() { mSupermoduleAxisX = true; } + void setSupermoduleAxisY() { mSupermoduleAxisX = false; } + + private: + struct { + Double_t mCountSM[20]; + Double_t mMeanSM[20]; + Double_t mSigmaSM[20]; + Double_t mMaxSM[20]; + } mStats; + Bool_t mSupermoduleAxisX = true; +}; + +class SupermoduleProjectionReductorX : public SupermoduleProjectionReductorBase +{ + public: + SupermoduleProjectionReductorX() : SupermoduleProjectionReductorBase() { setSupermoduleAxisX(); } +}; + +class SupermoduleProjectionReductorY : public SupermoduleProjectionReductorBase +{ + public: + SupermoduleProjectionReductorY() : SupermoduleProjectionReductorBase() { setSupermoduleAxisY(); } +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_EMCAL_SUPERMODULEPROJECTIONREDUCTOR_H \ No newline at end of file diff --git a/Modules/EMCAL/include/EMCAL/SupermoduleProjectorTask.h b/Modules/EMCAL/include/EMCAL/SupermoduleProjectorTask.h new file mode 100644 index 0000000000..51300613cd --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/SupermoduleProjectorTask.h @@ -0,0 +1,107 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SupermoduleProjectorTask.h +/// \author Cristina Terrevoli, Markus Fasel +/// + +#ifndef QUALITYCONTROL_SUPERMODULEPROJECTORTASK_H +#define QUALITYCONTROL_SUPERMODULEPROJECTORTASK_H + +// QC includes +#include "QualityControl/QualityObject.h" +#include "QualityControl/PostProcessingInterface.h" +#include "EMCAL/IndicesConverter.h" +#include +#include +#include +#include +#include + +class TCanvas; +class TPaveText; +class TH1; +class TH2; + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Quality Control task for the calibration data of the EMCAL +class SupermoduleProjectorTask final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + // ported from TrendingConfiguration + struct DataSource { + std::string type; + std::string path; + std::string name; + }; + struct PlotAttributes { + std::string titleX; + std::string titleY; + double minX = DBL_MIN; + double maxX = DBL_MAX; + bool logx = false; + bool logy = false; + std::string qualityPath; + }; + + /// \brief Constructor + SupermoduleProjectorTask() = default; + /// \brief Destructor + ~SupermoduleProjectorTask() = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef services) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + void reset(); + + private: + std::vector getDataSources(std::string name, const boost::property_tree::ptree& config); + std::map parseCustomizations(std::string name, const boost::property_tree::ptree& config); + std::map parseQuality(const quality_control::core::QualityObject& qo) const; + + /// \brief Make projections of the monitoring object per supermodule + /// \param mo Monitoring object to project + /// \param plot Canvas to plot the projections on + /// \param customizations Plotting customizations for all pads (optional) + void makeProjections(quality_control::core::MonitorObject& mo, TCanvas& plot, const PlotAttributes* customizations = nullptr, const quality_control::core::QualityObject* qo = nullptr); + + std::vector mDataSources; ///< Data sources to be projected + std::map mCanvasHandler; ///< Mapping between data source and output canvas + std::map mAttributeHandler; ///< Customizations for canvases (i.e. axis titles) + o2::emcal::IndicesConverter mIndicesConverter; ///< Converter for online-offline supermodule indices +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_CALIBMONITORINGTASK_H diff --git a/Modules/EMCAL/include/EMCAL/TimeCalibParamReductor.h b/Modules/EMCAL/include/EMCAL/TimeCalibParamReductor.h new file mode 100644 index 0000000000..b716fc2e1e --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/TimeCalibParamReductor.h @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_EMCAL_TIMECALIBPARAMREDUCTOR_H +#define QUALITYCONTROL_EMCAL_TIMECALIBPARAMREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2 +{ +namespace emcal +{ +class Geometry; +} +} // namespace o2 + +namespace o2::quality_control_modules::emcal +{ + +/// \class TimeCalibParamReductor +/// \brief Dedicated reductor for EMCAL time calibration param +/// \ingroup EMCALQCCReductors +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \since November 1st, 2022 +/// +/// Produces entries: +/// - Channels with failed fit for Full acceptance/Subdetector/Supermodule +/// - Fraction channels with failed fit for Full acceptance/Subdetector/Supermodule +/// - Mean time shift +/// - Sigma time shift +class TimeCalibParamReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + /// \brief Constructor + TimeCalibParamReductor(); + + /// \brief Destructor + virtual ~TimeCalibParamReductor() = default; + + /// \brief Get branch address of structure with data + /// \return Branch address + void* getBranchAddress() override; + + /// \brief Get list of variables providede by reductor + /// \return List of variables (leaflist) + const char* getBranchLeafList() override; + + /// \brief Extract information from time calibration histogram and fill observables + /// \param obj Input object to get the data from + void update(TObject* obj) override; + + private: + /// \brief Check whether a certain position is within the PHOS region + /// \param column Column number of the position + /// \param row Row number of the position + bool isPHOSRegion(int column, int row) const; + + o2::emcal::Geometry* mGeometry; ///< EMCAL geometry + struct { + Int_t mFailedParams; ///< Total number of failed time calibration params + Int_t mFailedParamsEMCAL; ///< Number of failed time calibration params in EMCAL + Int_t mFailedParamsDCAL; ///< Number of failed time calibration params in DCAL + Int_t mFailedParamSM[20]; ///< Number of failed time calibration params per supermodule + Int_t mSupermoduleMaxFailed; ///< Index of the supermodule with the largest amount of failed time calibration params + Double_t mFractionFailed; ///< Total fraction of failed time calibration params + Double_t mFractionFailedEMCAL; ///< Fraction of failed time calibration params in EMCAL + Double_t mFractionFailedDCAL; ///< Fraction of failed time calibration params in DCAL + Double_t mFractionFailedSM[20]; ///< Fraction of failed time calibration params per supermodule + Double_t mMeanShiftGood; ///< Mean time caliration param of good cells + Double_t mSigmaShiftGood; ///< Sigma of the time calibration params of good cells + } mStats; ///< Trending data point +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QUALITYCONTROL_TH2REDUCTOR_ \ No newline at end of file diff --git a/Modules/EMCAL/include/EMCAL/TrendGraphCheck.h b/Modules/EMCAL/include/EMCAL/TrendGraphCheck.h new file mode 100644 index 0000000000..39e9f91ccf --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/TrendGraphCheck.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright +// holders. All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendGraphCheck.h +/// \author Ananya Rai +/// + +#ifndef QC_MODULE_EMCAL_EMCALTRENDGRAPHCHECK_H +#define QC_MODULE_EMCAL_EMCALTRENDGRAPHCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::emcal +{ + +/// \brief Check if the trend rate is as expected. +/// +/// \author Ananya Rai +class TrendGraphCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + TrendGraphCheck() = default; + /// Destructor + ~TrendGraphCheck() override = default; + + // Override interface + void configure() override; + Quality + check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, + Quality checkResult = Quality::Null) override; + + private: + /************************************************ + * threshold cuts * + ************************************************/ + int mPeriodMovAvg = 5; ///< Period for moving average + double mBadThresholdLow = + 10; ///< If rate lower than this threshold - medium, keep checking + double mBadThresholdHigh = + 1000; ///< If rate higher than this thereshold - medium, keep checking + double mBadDiff = + 20; ///< If difference is higher than this between consecutive rates - bad + + ClassDefOverride(TrendGraphCheck, 5); +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALTRENDGRAPHCHECK_H diff --git a/Modules/EMCAL/include/EMCAL/TriggerTask.h b/Modules/EMCAL/include/EMCAL/TriggerTask.h new file mode 100644 index 0000000000..d4e949c6b9 --- /dev/null +++ b/Modules/EMCAL/include/EMCAL/TriggerTask.h @@ -0,0 +1,147 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#ifndef QC_MODULE_EMCAL_EMCALTRIGGERTASK_H +#define QC_MODULE_EMCAL_EMCALTRIGGERTASK_H + +#include "QualityControl/TaskInterface.h" +#include "EMCALBase/TriggerMappingV2.h" +#include +#include +#include + +class TH1; +class TH2; +class TProfile2D; + +namespace o2 +{ +class InteractionRecord; +} + +namespace o2::emcal +{ +class CompressedTRU; +class CompressedTriggerPatch; +class CompressedL0TimeSum; +class TriggerRecord; +}; // namespace o2::emcal + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::emcal +{ + +/// \class TriggerTask +/// \brief Task monitoring EMCAL trigger observables +/// \ingroup EMCALQCTasks +/// \author Markus Fasel , Oak Ridge National Laboratory +/// \since April 19, 2024 +/// +/// Task monitoring basic observables of the EMCAL trigger system. The task subscibes to: +/// +/// || Binding || Channel || Information || +/// |---------------|-----------------|---------------------------------------| +/// | truinfo | EMC/TRUS | TRU data | +/// | trurecords | EMC/TRUSTRGR | Trigger records of TRU data | +/// | patchinfos | EMC/PATCHES | Trigger patches | +/// | patchrecords | EMC/PATCHESTRGR | Trigger records of trigger patches | +/// | timesums | EMC/FASTORS | L0 FastOR timesums | +/// |timesumrecords | EMC/FASTORSTRGR | Trigger records of L0 FastOR timesums | +/// +/// Monitoring per timeframe is done in the function monitorData, which delegates +/// the monitoring per trigger to an internal function processEvent. +/// +/// The task defines the following task parameters: +/// - None so far +class TriggerTask final : public TaskInterface +{ + public: + /// \brief Constructor + TriggerTask() = default; + /// \brief Destructor + ~TriggerTask() override; + + /// \brief Initialize task (histograms and trigger mapping) + /// \param ctx InitContenxt + void initialize(o2::framework::InitContext& ctx) override; + + /// \brief Operations performed at the start of activity (start of run) + /// \param activity Activity + void startOfActivity(const Activity& activity) override; + + /// \brief Operations at performed at the start of a new monitoring cycle + void startOfCycle() override; + + /// \brief Monitoring data for a given timeframe + /// \param ctx Processing context with data + /// + /// As the data in EMCAL is organized in triggers within the container the + /// monitoring is delegated to the internal function processEvent. monitorData + /// subscribes to the timeframe-based container of the TRU information, trigger + /// patches and timesums, as well as their corresponding trigger records. Iteration + /// over all BCs found in any of the containers via getAllBCs is performed, and + /// for each trigger the TRU, patch and FastOR information is extracted from the + /// timeframe-based containers. + void monitorData(o2::framework::ProcessingContext& ctx) override; + + /// \brief Operations performed at the end of the current monitoring cycle + void endOfCycle() override; + + /// \brief Operations performed at the end fo the activity (end of run) + /// \param activity Activity + void endOfActivity(const Activity& activity) override; + + /// \brief Reset all histograms + void reset() override; + + private: + /// \brief Fill monitoring histograms for a single event + /// \param trudata TRU data for the event + /// \param triggerpatches Trigger patches found in the event + /// \param timesums L0 timesums found in the event + void processEvent(const gsl::span trudata, const gsl::span triggerpatches, const gsl::span timesums); + + /// \brief Find all BCs with data in either of the components (or all) + /// \param trurecords Trigger records for TRU data + /// \param patchrecords Trigger records for trigger patches + /// \param timesumrecords Trigger records for L0 timesums + /// \return List of found BCs (sorted) + std::vector getAllBCs(const gsl::span trurecords, const gsl::span patchrecords, const gsl::span timesumrecords) const; + + std::unique_ptr mTriggerMapping; ///< Trigger mapping + TH1* mTRUFired = nullptr; ///< Histogram with counters per TRU fired + TH1* mFastORFired = nullptr; ///< Histogram with counters per FastOR fired (in patches) + TH2* mPositionFasORFired = nullptr; ///< Histogram with the position of fired FastORs (in patches) + TH1* mNumberOfTRUsPerEvent = nullptr; ///< Counter histogram for number of fired TRUs per event + TH1* mNumberOfPatchesPerEvent = nullptr; ///< Counter histogram for number of fired patches per event + TH1* mPatchEnergySpectrumPerEvent = nullptr; ///< Histogram for integrated patch energy spectrum + TH1* mLeadingPatchEnergySpectrumPerEvent = nullptr; ///< Histogram for integrated leading patch energy spectrum + TH2* mPatchEnergyTRU = nullptr; ///< Histogram for patch energy spectrum per TRU + TH2* mLeadingPatchEnergyTRU = nullptr; ///< Histogram for leading patch energy spectrum per TRU + TH2* mNumberOfPatchesPerTRU = nullptr; ///< Counter histogram for number of patches per TRU + TH2* mPatchIndexFired = nullptr; ///< Histogram with the fired patch index per TRU + TH2* mPatchIndexLeading = nullptr; ///< Histogram with the leading fired patch index per TRU + TH2* mTRUTime = nullptr; ///< Histogram with TRU time vs TRU index + TH2* mPatchTime = nullptr; ///< Histogram with patch time vs. TRU index + TH2* mLeadingPatchTime = nullptr; ///< Histogram with patch time of the leading patch vs. TRU index + TH1* mNumberTimesumsEvent = nullptr; ///< Counter histogram with number of non-0 FastOR timesums per event + TH1* mL0Timesums = nullptr; ///< ADC spectrum of the FastOR timesums + TH2* mL0TimesumsTRU = nullptr; ///< ADC spectrum of the the FastOR timesums per TRU + TH1* mADCMaxTimesum = nullptr; ///< ADC spectrum of the leading FastOR timesum per event + TH1* mFastORIndexMaxTimesum = nullptr; ///< Index of the leading FastOR timesum per event + TH2* mPositionMaxTimesum = nullptr; ///< Position of teh leading FastOR timsum per event + TH2* mIntegratedTimesums = nullptr; ///< Integrated ADC timesum + TProfile2D* mAverageTimesum = nullptr; ///< Average ADC timesum +}; + +} // namespace o2::quality_control_modules::emcal + +#endif // QC_MODULE_EMCAL_EMCALTRIGGERTASK_H diff --git a/Modules/EMCAL/src/BCTask.cxx b/Modules/EMCAL/src/BCTask.cxx new file mode 100644 index 0000000000..52ab8b2a02 --- /dev/null +++ b/Modules/EMCAL/src/BCTask.cxx @@ -0,0 +1,273 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include +#include + +#include + +#include "CommonConstants/LHCConstants.h" +#include "CommonConstants/Triggers.h" +#include "DataFormatsCTP/Configuration.h" +#include "DataFormatsCTP/Digits.h" +#include "DataFormatsEMCAL/Constants.h" +#include "DataFormatsEMCAL/TriggerRecord.h" + +#include "QualityControl/QcInfoLogger.h" +#include "EMCAL/BCTask.h" +#include +#include +#include + +namespace o2::quality_control_modules::emcal +{ + +BCTask::~BCTask() +{ + delete mBCReadout; + delete mBCIncomplete; + delete mBCEMCAny; + delete mBCMinBias; + delete mBCL0EMCAL; + delete mBCL0DCAL; +} + +void BCTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + parseTriggerSelection(); + constexpr unsigned int LHC_MAX_BC = o2::constants::lhc::LHCMaxBunches; + mBCReadout = new TH1F("BCEMCALReadout", "BC distribution EMCAL readout", LHC_MAX_BC, -0.5, LHC_MAX_BC - 0.5); + getObjectsManager()->startPublishing(mBCReadout); + mBCIncomplete = new TH1F("BCIncomplete", "BC distribution EMCAL incomplete triggers", LHC_MAX_BC, -0.5, LHC_MAX_BC - 0.5); + getObjectsManager()->startPublishing(mBCIncomplete); + mBCEMCAny = new TH1F("BCCTPEMCALAny", "BC distribution CTP EMCAL any triggered", LHC_MAX_BC, -0.5, LHC_MAX_BC - 0.5); + getObjectsManager()->startPublishing(mBCEMCAny); + mBCMinBias = new TH1F("BCCTPEMCALMinBias", "BC distribution CTP EMCAL min. bias triggered", LHC_MAX_BC, -0.5, LHC_MAX_BC - 0.5); + getObjectsManager()->startPublishing(mBCMinBias); + mBCL0EMCAL = new TH1F("BCCTPEMCALL0", "BC distribution CTP EMCAL LO-triggered", LHC_MAX_BC, -0.5, LHC_MAX_BC - 0.5); + getObjectsManager()->startPublishing(mBCL0EMCAL); + mBCL0DCAL = new TH1F("BCCTPDCALL0", "BC distribution CTP DCAL L0-triggered", LHC_MAX_BC, -0.5, LHC_MAX_BC - 0.5); + getObjectsManager()->startPublishing(mBCL0DCAL); + std::fill(mTriggerClassIndices.begin(), mTriggerClassIndices.end(), 0); +} + +void BCTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + mCurrentRun = activity.mId; + reset(); +} + +void BCTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void BCTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto ctpconfig = ctx.inputs().get("ctp-config"); + auto emctriggers = ctx.inputs().get>("emcal-triggers"); + for (const auto& emctrg : emctriggers) { + if (emctrg.getTriggerBits() & o2::trigger::PhT) { + mBCReadout->Fill(emctrg.getBCData().bc); + if (emctrg.getTriggerBits() & o2::emcal::triggerbits::Inc) { + mBCIncomplete->Fill(emctrg.getBCData().bc); + } + } + } + + auto ctpdigits = ctx.inputs().get>("ctp-digits"); + for (const auto& ctpdigit : ctpdigits) { + auto bc = ctpdigit.intRecord.bc; + uint64_t classMaskCTP = ctpdigit.CTPClassMask.to_ulong(); + if (classMaskCTP & mAllEMCALClasses) { + mBCEMCAny->Fill(bc); + } + // Distinguish between a couple of triggers + if (classMaskCTP & mTriggerClassIndices[EMCMinBias]) { + mBCMinBias->Fill(bc); + } + if (classMaskCTP & mTriggerClassIndices[EMCL0]) { + mBCL0EMCAL->Fill(bc); + } + if (classMaskCTP & mTriggerClassIndices[DMCL0]) { + mBCL0DCAL->Fill(bc); + } + } +} + +void BCTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void BCTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void BCTask::finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) +{ + if (matcher == o2::framework::ConcreteDataMatcher("CTP", "CONFIG", 0)) { + auto triggerconfig = reinterpret_cast(obj); + if (triggerconfig) { + ILOG(Info, Support) << "Loading EMCAL trigger classes for new trigger configuration: " << ENDM; + loadTriggerClasses(triggerconfig); + } + } +} + +void BCTask::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mBCReadout->Reset(); + mBCIncomplete->Reset(); + mBCEMCAny->Reset(); + mBCMinBias->Reset(); + mBCL0EMCAL->Reset(); + mBCL0DCAL->Reset(); +} + +void BCTask::parseTriggerSelection() +{ + auto parse_triggers = [](const std::string_view configstring) -> std::vector { + std::vector result; + std::stringstream parser(configstring.data()); + std::string alias; + while (std::getline(parser, alias, ',')) { + result.emplace_back(alias); + } + return result; + }; + // Default trigger alias + if (auto param = mCustomParameters.find("AliasMB"); param != mCustomParameters.end()) { + mTriggerAliases["MB"] = parse_triggers(param->second); + } else { + mTriggerAliases["MB"] = { "C0TVX" }; + } + if (auto param = mCustomParameters.find("Alias0EMC"); param != mCustomParameters.end()) { + mTriggerAliases["0EMC"] = parse_triggers(param->second); + } else { + mTriggerAliases["0EMC"] = { "CTVXEMC" }; + } + if (auto param = mCustomParameters.find("Alias0DMC"); param != mCustomParameters.end()) { + mTriggerAliases["0DMC"] = parse_triggers(param->second); + } else { + mTriggerAliases["0DMC"] = { "CTVXDMC" }; + } + + if (auto param = mCustomParameters.find("BeamMode"); param != mCustomParameters.end()) { + mBeamMode = getBeamPresenceMode(param->second); + } +} + +std::string BCTask::getBeamPresenceModeToken(BeamPresenceMode_t beammode) const +{ + std::string token; + switch (beammode) { + case BeamPresenceMode_t::BOTH: + token = "B"; + break; + case BeamPresenceMode_t::ASIDE: + token = "A"; + break; + case BeamPresenceMode_t::CSIDE: + token = "C"; + break; + case BeamPresenceMode_t::EMPTY: + token = "E"; + break; + case BeamPresenceMode_t::NONE: + token = "NONE"; + break; + default: + break; + } + return token; +} + +BCTask::BeamPresenceMode_t BCTask::getBeamPresenceMode(const std::string_view beamname) const +{ + if (beamname == "ASIDE") { + return BeamPresenceMode_t::ASIDE; + } + if (beamname == "CSIDE") { + return BeamPresenceMode_t::CSIDE; + } + if (beamname == "BOTH") { + return BeamPresenceMode_t::BOTH; + } + if (beamname == "EMPTY") { + return BeamPresenceMode_t::EMPTY; + } + if (beamname == "NONE") { + return BeamPresenceMode_t::NONE; + } + return BeamPresenceMode_t::ANY; +} + +void BCTask::loadTriggerClasses(const o2::ctp::CTPConfiguration* ctpconfig) +{ + auto tokenize = [](const std::string_view trgclass, char delimiter) -> std::vector { + std::vector tokens; + std::stringstream tokenizer(trgclass.data()); + std::string buf; + while (std::getline(tokenizer, buf, delimiter)) { + tokens.emplace_back(buf); + } + + return tokens; + }; + mAllEMCALClasses = 0; + std::fill(mTriggerClassIndices.begin(), mTriggerClassIndices.end(), 0); + std::map triggerClassTypeLookup = { { "MB", EMCMinBias }, { "0EMC", EMCL0 }, { "0DMC", DMCL0 } }; + + for (auto& cls : ctpconfig->getCTPClasses()) { + auto trgclsname = boost::algorithm::to_upper_copy(cls.name); + auto tokens = tokenize(trgclsname, '-'); + auto triggercluster = boost::algorithm::to_upper_copy(cls.cluster->name); + if (triggercluster.find("EMC") == std::string::npos) { + // Not an EMCAL trigger class + continue; + } + if (tokens.size() > 1) { + if (mBeamMode != BeamPresenceMode_t::ANY) { + if (tokens[1] != getBeamPresenceModeToken(mBeamMode)) { + // beam mode not matching + continue; + } + } + } + ILOG(Info, Support) << "EMCAL trigger cluster: Found trigger class: " << trgclsname << " with mask " << std::bitset<64>(cls.classMask) << ENDM; + mAllEMCALClasses |= cls.classMask; + for (auto& [triggertype, triggeraliases] : mTriggerAliases) { + int maskIndex = -1; + auto maskIndexPtr = triggerClassTypeLookup.find(triggertype); + if (maskIndexPtr != triggerClassTypeLookup.end()) { + maskIndex = maskIndexPtr->second; + } else { + continue; + } + for (auto alias : triggeraliases) { + if (tokens[0] == alias) { + ILOG(Info, Support) << "Identified trigger class " << cls.name << " as " << triggertype << " trigger class" << ENDM; + mTriggerClassIndices[maskIndex] |= cls.classMask; + } + } + } + } + ILOG(Info, Support) << "Combined mask any EMC trigger: " << std::bitset<64>(mAllEMCALClasses) << ENDM; + ILOG(Info, Support) << "Combined mask for MB triggers: " << std::bitset<64>(mTriggerClassIndices[EMCMinBias]) << ENDM; + ILOG(Info, Support) << "Combined mask for 0EMC triggers: " << std::bitset<64>(mTriggerClassIndices[EMCL0]) << ENDM; + ILOG(Info, Support) << "Combined mask for 0DMC triggers: " << std::bitset<64>(mTriggerClassIndices[DMCL0]) << ENDM; +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/BCVisualization.cxx b/Modules/EMCAL/src/BCVisualization.cxx new file mode 100644 index 0000000000..5b5c3ee4a9 --- /dev/null +++ b/Modules/EMCAL/src/BCVisualization.cxx @@ -0,0 +1,315 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "EMCAL/BCVisualization.h" +#include "QualityControl/DatabaseInterface.h" +#include "CommonConstants/LHCConstants.h" + +// root includes +#include "TCanvas.h" +#include "TLegend.h" +#include "TPaveText.h" +#include "TH1D.h" + +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::emcal +{ + +void BCVisualization::configure(const boost::property_tree::ptree& config) +{ + ILOG(Info, Support) << "Configuring BC visualization" << ENDM; + auto taskConfig = config.get_child_optional("qc.postprocessing." + getID() + ".configuration"); + if (taskConfig) { + auto cfgMethod = taskConfig.get().get_child_optional("Method"); + if (cfgMethod) { + mShiftEvaluation = getMethod(cfgMethod->get_value()); + ILOG(Info, Support) << "Applying method for BC shift evaluation: " << getMethodString(mShiftEvaluation) << ENDM; + if (mShiftEvaluation == MethodBCShift_t::UNKNOWN) { + ILOG(Warning, Support) << "Unknown method for shift evaluation - BC shift cannot be determined" << ENDM; + } + } + + auto cfgMinEntries = taskConfig.get().get_child_optional("MinEntries"); + if (cfgMinEntries) { + mMinNumberOfEntriesBCShift = cfgMinEntries.get().get_value(); + ILOG(Info, Support) << "Request min. entries in control bunch(es): " << mMinNumberOfEntriesBCShift << ENDM; + } + } + auto dataSources = config.get_child("qc.postprocessing." + getID() + ".dataSources"); + for (auto& dataSourceConfig : dataSources) { + mDataPath = dataSourceConfig.second.get("path"); + } + ILOG(Info) << "Using data path: " << mDataPath << ENDM; + ILOG(Info) << "Configuration done" << ENDM; +} + +void BCVisualization::initialize(Trigger, framework::ServiceRegistryRef) +{ + QcInfoLogger::setDetector("EMC"); + ILOG(Debug, Devel) << "initialize BC plotter" << ENDM; + mOutputCanvas = new TCanvas("BCCompEMCvsCTP", "Comparison BC EMC vs CTP", 800, 600); + getObjectsManager()->startPublishing(mOutputCanvas); +} + +void BCVisualization::update(Trigger t, framework::ServiceRegistryRef services) +{ + constexpr unsigned int LHC_MAX_BC = o2::constants::lhc::LHCMaxBunches; + auto& qcdb = services.get(); + auto moBCEMC = qcdb.retrieveMO(mDataPath, "BCEMCALReadout", t.timestamp, t.activity), + moBCCTP = qcdb.retrieveMO(mDataPath, "BCCTPEMCALAny", t.timestamp, t.activity); + + if (moBCCTP == nullptr || moBCEMC == nullptr) { + ILOG(Error, Support) << "at least one of the expected objects (BCEMCALReadout and BCCTPEMCALAny) could not be " + << "retrieved, skipping this update" << ENDM; + return; + } + mOutputCanvas->Clear(); + mOutputCanvas->cd(); + auto histBCEMC = static_cast(moBCEMC->getObject()->Clone()), + histBCCTP = static_cast(moBCCTP->getObject()->Clone()); + if (histBCEMC == nullptr || histBCCTP == nullptr) { + ILOG(Error, Support) << "could not cast BCEMC or BCCTP to TH1, will not update the visualization" << ENDM; + return; + } + histBCEMC->SetDirectory(nullptr); + histBCCTP->SetDirectory(nullptr); + double yrange = 1.5 * std::max(histBCCTP->GetBinContent(histBCCTP->GetMaximumBin()), histBCEMC->GetBinContent(histBCEMC->GetMaximumBin())); + if (!mFrame) { + mFrame = new TH1F("frameBCPlot", "Trigger BC comparison CTP - EMC; BC ID; Number of BCs", LHC_MAX_BC, -0.5, LHC_MAX_BC - 0.5); + mFrame->SetStats(false); + mFrame->GetYaxis()->SetRangeUser(0, yrange); + } else { + mFrame->GetYaxis()->SetRangeUser(0, yrange); + } + mFrame->Draw("axis"); + + histBCEMC->SetStats(false); + histBCEMC->SetMarkerColor(kBlue); + histBCEMC->SetLineColor(kBlue); + histBCEMC->SetMarkerStyle(20); + histBCEMC->Draw("epsame"); + + histBCCTP->SetStats(false); + histBCCTP->SetMarkerColor(kRed); + histBCCTP->SetLineColor(kRed); + histBCCTP->SetMarkerStyle(25); + histBCCTP->Draw("epsame"); + + auto leg = new TLegend(0.6, 0.7, 0.89, 0.89); + leg->SetBorderSize(0); + leg->SetFillStyle(0); + leg->SetTextFont(42); + leg->AddEntry(histBCEMC, "EMC triggers, EMC data", "lep"); + leg->AddEntry(histBCCTP, "EMC triggers, CTP data", "lep"); + leg->Draw(); + + std::string message = "Shift cannot be obtained"; + Color_t textcolor = kBlack; + if (mShiftEvaluation != MethodBCShift_t::UNKNOWN && histBCEMC->GetEntries() && histBCCTP->GetEntries()) { + auto [status, bcshift] = determineBCShift(histBCEMC, histBCCTP, mShiftEvaluation); + if (status) { + message = "BC shift EMC vs CTP: " + std::to_string(bcshift) + " BCs"; + if (bcshift == 0) { + textcolor = kGreen + 2; + } else { + textcolor = kRed; + } + } + } + + auto shiftlabel = new TPaveText(0.15, 0.8, 0.5, 0.89, "NDC"); + shiftlabel->SetBorderSize(0); + shiftlabel->SetFillStyle(0); + shiftlabel->SetTextFont(42); + shiftlabel->SetTextColor(textcolor); + shiftlabel->AddText(message.data()); + shiftlabel->Draw(); +} + +void BCVisualization::finalize(Trigger t, framework::ServiceRegistryRef) +{ + getObjectsManager()->stopPublishing(mOutputCanvas); + delete mOutputCanvas; + mOutputCanvas = nullptr; +} + +void BCVisualization::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Support) << "Resetting the histogram" << ENDM; +} + +std::tuple BCVisualization::determineBCShift(const TH1* emchist, const TH1* ctphist, MethodBCShift_t method) const +{ + switch (method) { + case MethodBCShift_t::ISOLATED_BC: + return determineBCShiftIsolated(emchist, ctphist); + case MethodBCShift_t::LEADING_BC: + return determineBCShiftLeading(emchist, ctphist); + default: + break; + } + return std::make_tuple(false, 0); +} +std::tuple BCVisualization::determineBCShiftIsolated(const TH1* emchist, const TH1* ctphist) const +{ + auto isolatedEMC = getIsolatedBCs(emchist), + isolatedCTP = getIsolatedBCs(ctphist); + + std::stringstream emcmaker; + bool first = true; + for (auto bc : isolatedEMC) { + if (first) { + first = false; + } else { + emcmaker << ", "; + } + emcmaker << bc; + } + ILOG(Debug, Support) << "isolated bcs emc: " << emcmaker.str() << ENDM; + std::stringstream ctpmaker; + first = true; + for (auto bc : isolatedCTP) { + if (first) { + first = false; + } else { + ctpmaker << ", "; + } + ctpmaker << bc; + } + ILOG(Debug, Support) << "isolated bcs ctp: " << emcmaker.str() << ENDM; + + int shift = 0; + bool status = false; + if (isolatedEMC.size() && isolatedCTP.size()) { + int currentshift = 0; + int currentmax = 0; + for (auto opt : isolatedCTP) { + currentshift = opt - isolatedEMC[0]; + auto shiftedEMC = getShifted(isolatedEMC, currentshift); + auto entriesOverlapping = getNMatching(shiftedEMC, isolatedCTP); + if (entriesOverlapping > currentmax) { + currentmax = entriesOverlapping; + shift = currentshift; + } + } + status = true; + } + return std::make_tuple(status, shift); +} + +std::tuple BCVisualization::determineBCShiftLeading(const TH1* emchist, const TH1* ctphist) const +{ + auto leadingBCEMC = getLeadingBC(emchist), + leadingBCCTP = getLeadingBC(ctphist); + bool status = false; + int shift = 0; + if (emchist->GetBinContent(leadingBCEMC + 1) > mMinNumberOfEntriesBCShift && ctphist->GetBinContent(leadingBCCTP + 1) > mMinNumberOfEntriesBCShift) { + status = true; + shift = leadingBCCTP - leadingBCEMC; + } + return std::make_tuple(status, shift); +} + +std::vector BCVisualization::getIsolatedBCs(const TH1* bchist) const +{ + std::vector bcs; + for (int ibc = 1; ibc < bchist->GetXaxis()->GetNbins(); ibc++) { + auto nentriesBefore = bchist->GetBinContent(ibc), + nentriesBC = bchist->GetBinContent(ibc + 1), + nentriesAfter = bchist->GetBinContent(ibc + 2); + if (nentriesBC < mMinNumberOfEntriesBCShift) { + continue; + } + if (nentriesBefore < 0.1 * nentriesBC && nentriesAfter < nentriesBC * 0.1) { + bcs.emplace_back(ibc); + } + } + return bcs; +} + +std::vector BCVisualization::getShifted(const std::vector& bcEMC, int shift) const +{ + std::vector result; + for (auto bc : bcEMC) { + auto shifted = bc - shift; + if (shifted < 0) { + shifted += o2::constants::lhc::LHCMaxBunches; + } + if (shifted >= o2::constants::lhc::LHCMaxBunches) { + shifted -= o2::constants::lhc::LHCMaxBunches; + } + result.emplace_back(shifted); + } + return result; +} + +int BCVisualization::getNMatching(const std::vector& bcShiftedEMC, const std::vector& bcCTP) const +{ + int entriesOverlapping = 0; + for (auto bcEMC : bcShiftedEMC) { + if (std::find(bcCTP.begin(), bcCTP.end(), bcEMC) != bcCTP.end()) { + entriesOverlapping++; + } + } + return entriesOverlapping; +} + +int BCVisualization::getLeadingBC(const TH1* hist) const +{ + int currentBC = 0; + double currentEntries = -1; + for (int ibc = 0; ibc < o2::constants::lhc::LHCMaxBunches; ibc++) { + auto counts = hist->GetBinContent(ibc + 1); + if (counts > currentEntries) { + currentBC = ibc; + currentEntries = counts; + } + } + return currentBC; +} + +BCVisualization::MethodBCShift_t BCVisualization::getMethod(const std::string_view methodstring) const +{ + auto methodstringUpper = boost::algorithm::to_upper_copy(std::string(methodstring)); + if (methodstringUpper == "LEADING_BC") { + return MethodBCShift_t::LEADING_BC; + } + if (methodstringUpper == "ISOLATED_BC") { + return MethodBCShift_t::ISOLATED_BC; + } + return MethodBCShift_t::UNKNOWN; +} + +std::string BCVisualization::getMethodString(MethodBCShift_t method) const +{ + switch (method) { + case MethodBCShift_t::LEADING_BC: + return "Leading_BC"; + + case MethodBCShift_t::ISOLATED_BC: + return "Isolated_BC"; + + default: + break; + } + return "Unknown"; +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/BadChannelMapReductor.cxx b/Modules/EMCAL/src/BadChannelMapReductor.cxx new file mode 100644 index 0000000000..db58e41978 --- /dev/null +++ b/Modules/EMCAL/src/BadChannelMapReductor.cxx @@ -0,0 +1,124 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include +#include +#include +#include +#include + +using namespace o2::quality_control_modules::emcal; + +BadChannelMapReductor::BadChannelMapReductor() : ReductorTObject(), mGeometry(nullptr), mStats() +{ + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); +} + +void* BadChannelMapReductor::getBranchAddress() +{ + return &mStats; +} + +const char* BadChannelMapReductor::getBranchLeafList() +{ + return "BadChannelsTotal/I:DeadChannelsTotal:NonGoodChannelsTotal:BadChannelsEMCAL:BadChannelsDCAL:DeadChannelsEMCAL:DeadChannelsDCAL:NonGoodChannelsEMCAL:NonGoodChannelsDCAL:BadChannelsSM[20]:DeadChannelsSM[20]:NonGoodChannelsSM[20]:SupermoduleMaxBad:SupermoduleMaxDead:SupermoduleMaxNonGood:FractionBadTotal/D:FractionDeadTotal:FractionNonGoodTotal:FractionBadEMCAL:FractionBadDCAL:FractionDeadEMCAL:FractionDeadDCAL:FractionNonGoodEMCAL:FractionNonGoodDCAL:FractionBadSupermodule[20]:FractionDeadSupermodule[20]:FractionNonGoodSupermodule[20]"; +} + +void BadChannelMapReductor::update(TObject* obj) +{ + const std::map channelsSMTYPE = { { o2::emcal::EMCAL_STANDARD, 1152 }, { o2::emcal::EMCAL_THIRD, 384 }, { o2::emcal::DCAL_STANDARD, 768 }, { o2::emcal::DCAL_EXT, 384 } }; + constexpr int CHANNELS_TOTAL = 17664, CHANNELS_EMC = 12288, CHANNELS_DCAL = CHANNELS_TOTAL - CHANNELS_EMC; + memset(&mStats, 0, sizeof(mStats)); + auto badChannelMap = dynamic_cast(obj); + if (!badChannelMap) { + ILOG(Error, Support) << "Object " << obj->GetName() << " not a proper bad channel histogram, or does not exist. Not possible to analyse" << ENDM; + return; + } + for (int icolumn = 0; icolumn < badChannelMap->GetXaxis()->GetNbins(); icolumn++) { + for (int irow = 0; irow < badChannelMap->GetYaxis()->GetNbins(); irow++) { + if (isPHOSRegion(icolumn, irow)) { + continue; + } + auto status = badChannelMap->GetBinContent(icolumn + 1, irow + 1); + try { + auto [sm, mod, modrow, modcol] = mGeometry->GetCellIndexFromGlobalRowCol(irow, icolumn); + if (status == 1) { // bad channel + mStats.mNonGoodChannelsTotal++; + mStats.mNonGoodChannelsSM[sm]++; + mStats.mBadChannelsTotal++; + mStats.mBadChannelsSM[sm]++; + if (sm < 12) { + mStats.mNonGoodChannelsEMCAL++; + mStats.mBadChannelsEMCAL++; + } else { + mStats.mNonGoodChannelsDCAL++; + mStats.mBadChannelsDCAL++; + } + } else if (status == 2) { + mStats.mNonGoodChannelsTotal++; + mStats.mNonGoodChannelsSM[sm]++; + mStats.mDeadChannelsTotal++; + mStats.mDeadChannelsSM[sm]++; + if (sm < 12) { + mStats.mNonGoodChannelsEMCAL++; + mStats.mDeadChannelsEMCAL++; + } else { + mStats.mNonGoodChannelsDCAL++; + mStats.mDeadChannelsDCAL++; + } + } + } catch (o2::emcal::RowColException& e) { + ILOG(Error, Support) << e.what() << ENDM; + } + } + } + mStats.mFractionBadTotal = static_cast(mStats.mBadChannelsTotal) / static_cast(CHANNELS_TOTAL); + mStats.mFractionDeadTotal = static_cast(mStats.mDeadChannelsTotal) / static_cast(CHANNELS_TOTAL); + mStats.mFractionNonGoodTotal = static_cast(mStats.mNonGoodChannelsTotal) / static_cast(CHANNELS_TOTAL); + mStats.mFractionBadEMCAL = static_cast(mStats.mBadChannelsEMCAL) / static_cast(CHANNELS_EMC); + mStats.mFractionDeadEMCAL = static_cast(mStats.mDeadChannelsEMCAL) / static_cast(CHANNELS_EMC); + mStats.mFractionBadDCAL = static_cast(mStats.mBadChannelsDCAL) / static_cast(CHANNELS_DCAL); + mStats.mFractionDeadDCAL = static_cast(mStats.mDeadChannelsDCAL) / static_cast(CHANNELS_DCAL); + mStats.mFractionNonGoodEMCAL = static_cast(mStats.mNonGoodChannelsEMCAL) / static_cast(CHANNELS_EMC); + mStats.mFractionNonGoodDCAL = static_cast(mStats.mNonGoodChannelsDCAL) / static_cast(CHANNELS_DCAL); + int currentbad = -1, currentdead = -1, currentnongood = -1; + for (int ism = 0; ism < 20; ism++) { + try { + auto smtype = mGeometry->GetSMType(ism); + auto nchannels = channelsSMTYPE.find(smtype); + if (nchannels == channelsSMTYPE.end()) { + ILOG(Error, Support) << "Unhandled Supermodule type" << ENDM; + continue; + } + mStats.mFractionDeadSM[ism] = static_cast(mStats.mDeadChannelsSM[ism]) / nchannels->second; + mStats.mFractionBadSM[ism] = static_cast(mStats.mBadChannelsSM[ism]) / static_cast(nchannels->second); + mStats.mFractionNonGoodSM[ism] = static_cast(mStats.mNonGoodChannelsSM[ism]) / static_cast(nchannels->second); + if (mStats.mBadChannelsSM[ism] > currentbad) { + currentbad = mStats.mBadChannelsSM[ism]; + mStats.mSupermoduleMaxBad = ism; + } + if (mStats.mDeadChannelsSM[ism] > currentdead) { + currentdead = mStats.mDeadChannelsSM[ism]; + mStats.mSupermoduleMaxDead = ism; + } + if (mStats.mNonGoodChannelsSM[ism] > currentnongood) { + currentnongood = mStats.mNonGoodChannelsSM[ism]; + mStats.mSupermoduleMaxNonGood = ism; + } + } catch (o2::emcal::SupermoduleIndexException& e) { + ILOG(Error, Support) << e.what() << ENDM; + } + } +} + +bool BadChannelMapReductor::isPHOSRegion(int column, int row) const +{ + return (column >= 32 && column < 64) && (row >= 128 && row < 200); +} \ No newline at end of file diff --git a/Modules/EMCAL/src/CalibCheck.cxx b/Modules/EMCAL/src/CalibCheck.cxx new file mode 100644 index 0000000000..c59708c402 --- /dev/null +++ b/Modules/EMCAL/src/CalibCheck.cxx @@ -0,0 +1,589 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibCheck.cxx +/// \author Sierra Cantway +/// + +#include "EMCAL/CalibCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::emcal +{ + +void CalibCheck::configure() +{ + // configure threshold-based checkers for bad quality + auto nBadThresholdMaskStatsAll = mCustomParameters.find("BadThresholdMaskStatsAll"); + if (nBadThresholdMaskStatsAll != mCustomParameters.end()) { + try { + mBadThresholdMaskStatsAll = std::stof(nBadThresholdMaskStatsAll->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nBadThresholdMaskStatsAll->second.data()) << ENDM; + } + } + + auto nBadThresholdTimeCalibCoeff = mCustomParameters.find("BadThresholdTimeCalibCoeff"); + if (nBadThresholdTimeCalibCoeff != mCustomParameters.end()) { + try { + mBadThresholdTimeCalibCoeff = std::stof(nBadThresholdTimeCalibCoeff->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nBadThresholdTimeCalibCoeff->second.data()) << ENDM; + } + } + + auto nBadThresholdCellAmplitudeSupermoduleCalibPHYS = mCustomParameters.find("BadThresholdCellAmplitudeSupermoduleCalibPHYS"); + if (nBadThresholdCellAmplitudeSupermoduleCalibPHYS != mCustomParameters.end()) { + try { + mBadThresholdCellAmplitudeSupermoduleCalibPHYS = std::stof(nBadThresholdCellAmplitudeSupermoduleCalibPHYS->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nBadThresholdCellAmplitudeSupermoduleCalibPHYS->second.data()) << ENDM; + } + } + + auto nBadThresholdFractionGoodCellsEvent = mCustomParameters.find("BadThresholdFractionGoodCellsEvent"); + if (nBadThresholdFractionGoodCellsEvent != mCustomParameters.end()) { + try { + mBadThresholdFractionGoodCellsEvent = std::stof(nBadThresholdFractionGoodCellsEvent->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nBadThresholdFractionGoodCellsEvent->second.data()) << ENDM; + } + } + + auto nBadThresholdFractionGoodCellsSupermodule = mCustomParameters.find("BadThresholdFractionGoodCellsSupermodule"); + if (nBadThresholdFractionGoodCellsSupermodule != mCustomParameters.end()) { + try { + mBadThresholdFractionGoodCellsSupermodule = std::stof(nBadThresholdFractionGoodCellsSupermodule->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nBadThresholdFractionGoodCellsSupermodule->second.data()) << ENDM; + } + } + + auto nBadThresholdCellTimeSupermoduleCalibPHYS = mCustomParameters.find("BadThresholdCellTimeSupermoduleCalibPHYS"); + if (nBadThresholdCellTimeSupermoduleCalibPHYS != mCustomParameters.end()) { + try { + mBadThresholdCellTimeSupermoduleCalibPHYS = std::stof(nBadThresholdCellTimeSupermoduleCalibPHYS->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nBadThresholdCellTimeSupermoduleCalibPHYS->second.data()) << ENDM; + } + } + + auto nBadThresholdChannelsFEC = mCustomParameters.find("BadThresholdChannelsFEC"); + if (nBadThresholdChannelsFEC != mCustomParameters.end()) { + try { + mBadThresholdChannelsFEC = std::stof(nBadThresholdChannelsFEC->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nBadThresholdChannelsFEC->second.data()) << ENDM; + } + } + + // configure threshold-based checkers for medium quality + auto nMedThresholdMaskStatsAll = mCustomParameters.find("MedThresholdMaskStatsAll"); + if (nMedThresholdMaskStatsAll != mCustomParameters.end()) { + try { + mMedThresholdMaskStatsAll = std::stof(nMedThresholdMaskStatsAll->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nMedThresholdMaskStatsAll->second.data()) << ENDM; + } + } + + auto nMedThresholdTimeCalibCoeff = mCustomParameters.find("MedThresholdTimeCalibCoeff"); + if (nMedThresholdTimeCalibCoeff != mCustomParameters.end()) { + try { + mMedThresholdTimeCalibCoeff = std::stof(nMedThresholdTimeCalibCoeff->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nMedThresholdTimeCalibCoeff->second.data()) << ENDM; + } + } + + auto nMedThresholdCellAmplitudeSupermoduleCalibPHYS = mCustomParameters.find("MedThresholdCellAmplitudeSupermoduleCalibPHYS"); + if (nMedThresholdCellAmplitudeSupermoduleCalibPHYS != mCustomParameters.end()) { + try { + mMedThresholdCellAmplitudeSupermoduleCalibPHYS = std::stof(nMedThresholdCellAmplitudeSupermoduleCalibPHYS->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nMedThresholdCellAmplitudeSupermoduleCalibPHYS->second.data()) << ENDM; + } + } + + auto nMedThresholdFractionGoodCellsEvent = mCustomParameters.find("MedThresholdFractionGoodCellsEvent"); + if (nMedThresholdFractionGoodCellsEvent != mCustomParameters.end()) { + try { + mMedThresholdFractionGoodCellsEvent = std::stof(nMedThresholdFractionGoodCellsEvent->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nMedThresholdFractionGoodCellsEvent->second.data()) << ENDM; + } + } + + auto nMedThresholdFractionGoodCellsSupermodule = mCustomParameters.find("MedThresholdFractionGoodCellsSupermodule"); + if (nMedThresholdFractionGoodCellsSupermodule != mCustomParameters.end()) { + try { + mMedThresholdFractionGoodCellsSupermodule = std::stof(nMedThresholdFractionGoodCellsSupermodule->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nMedThresholdFractionGoodCellsSupermodule->second.data()) << ENDM; + } + } + + auto nMedThresholdCellTimeSupermoduleCalibPHYS = mCustomParameters.find("MedThresholdCellTimeSupermoduleCalibPHYS"); + if (nMedThresholdCellTimeSupermoduleCalibPHYS != mCustomParameters.end()) { + try { + mMedThresholdCellTimeSupermoduleCalibPHYS = std::stof(nMedThresholdCellTimeSupermoduleCalibPHYS->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nMedThresholdCellTimeSupermoduleCalibPHYS->second.data()) << ENDM; + } + } + + auto nMedThresholdChannelsFEC = mCustomParameters.find("MedThresholdChannelsFEC"); + if (nMedThresholdChannelsFEC != mCustomParameters.end()) { + try { + mMedThresholdChannelsFEC = std::stof(nMedThresholdChannelsFEC->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nMedThresholdChannelsFEC->second.data()) << ENDM; + } + } + + // configure nsigma-based checkers + auto nSigmaTimeCalibCoeff = mCustomParameters.find("SigmaTimeCalibCoeff"); + if (nSigmaTimeCalibCoeff != mCustomParameters.end()) { + try { + mSigmaTimeCalibCoeff = std::stof(nSigmaTimeCalibCoeff->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nSigmaTimeCalibCoeff->second.data()) << ENDM; + } + } +} + +Quality CalibCheck::check(std::map>* moMap) +{ + auto mo = moMap->begin()->second; + Quality result = Quality::Good; + + if (mo->getName() == "MaskStatsAllHisto" || mo->getName() == "MaskStatsEMCALHisto" || mo->getName() == "MaskStatsDCALHisto") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + int bad_bins_total = 0; + if (h->GetNbinsX() >= 3) { + bad_bins_total = h->GetBinContent(2) + h->GetBinContent(3); + } + + Float_t bad_bins_fraction = (float)bad_bins_total / (float)(h->GetEntries()); + + if (bad_bins_fraction > mBadThresholdMaskStatsAll) { + result = Quality::Bad; + } else if (bad_bins_fraction > mMedThresholdMaskStatsAll) { + result = Quality::Medium; + } + } + } + + if (mo->getName() == "timeCalibCoeff") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + std::vector smcounts; + for (auto ib : ROOT::TSeqI(0, h->GetXaxis()->GetNbins())) { + auto countSM = h->GetBinContent(ib + 1); + if (countSM > 0) { + smcounts.emplace_back(countSM); + } + } + if (!smcounts.size()) { + result = Quality::Medium; + } else { + TRobustEstimator meanfinder; + double mean, sigma; + meanfinder.EvaluateUni(smcounts.size(), smcounts.data(), mean, sigma); + int outside_counts = 0.0; + for (auto ib : ROOT::TSeqI(0, h->GetXaxis()->GetNbins())) { + if (h->GetBinContent(ib + 1) > (mean + mSigmaTimeCalibCoeff * sigma) || h->GetBinContent(ib + 1) < (mean - mSigmaTimeCalibCoeff * sigma)) { + outside_counts += 1; + } + } + Float_t out_counts_frac = (float)(outside_counts) / (float)(h->GetEntries()); + + if (out_counts_frac > mBadThresholdTimeCalibCoeff) { + result = Quality::Bad; + } else if (out_counts_frac > mMedThresholdTimeCalibCoeff) { + result = Quality::Medium; + } + } + } + } + + if (mo->getName() == "fractionGoodCellsEvent") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + for (int j = 1; j <= h->GetNbinsX(); j++) { + std::unique_ptr h_det(h->ProjectionY(Form("h_det_%d", j), j, j)); + if (h_det->GetEntries() == 0) { + continue; + } + + Float_t avg_frac = 0.0; + for (int i = 1; i <= h_det->GetNbinsX(); i++) { + avg_frac += h_det->GetBinContent(i) * h_det->GetBinCenter(i); + } + avg_frac = avg_frac / (h_det->GetEntries()); + + if (avg_frac < mBadThresholdFractionGoodCellsEvent) { + result = Quality::Bad; + break; + } else if (avg_frac < mMedThresholdFractionGoodCellsEvent) { + result = Quality::Medium; + } + } + } + } + + if (mo->getName() == "fractionGoodCellsSupermodule") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + for (int j = 1; j <= h->GetNbinsX(); j++) { + std::unique_ptr h_supermod(h->ProjectionY(Form("h_supermod_%d", j), j, j)); + if (h_supermod->GetEntries() == 0) { + continue; + } + + Float_t avg_frac = 0.0; + for (int i = 1; i <= h_supermod->GetNbinsX(); i++) { + avg_frac += h_supermod->GetBinContent(i) * h_supermod->GetBinCenter(i); + } + avg_frac = avg_frac / (h_supermod->GetEntries()); + + if (avg_frac < mBadThresholdFractionGoodCellsSupermodule) { + result = Quality::Bad; + break; + } else if (avg_frac < mMedThresholdFractionGoodCellsSupermodule) { + result = Quality::Medium; + } + } + } + } + + if (mo->getName() == "cellAmplitudeSupermoduleCalib_PHYS") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + std::unique_ptr h_allsupermod_proj(h->ProjectionX("h_allsupermod_proj")); + h_allsupermod_proj->Scale(1.0 / h_allsupermod_proj->Integral()); + for (int i = 1; i <= h->GetNbinsY(); i++) { + std::unique_ptr h_supermod(h->ProjectionX(Form("h_supermod_%d", i), i, i)); + if (h_supermod->GetEntries() == 0) { + result = Quality::Medium; + } else { + h_supermod->Scale(1.0 / h_supermod->Integral()); + + Double_t chi2_SM = h_supermod->Chi2Test(h_allsupermod_proj.get(), "UU NORM CHI2/NDF"); + if (chi2_SM > mBadThresholdCellAmplitudeSupermoduleCalibPHYS) { + result = Quality::Bad; + break; + } else if (chi2_SM > mMedThresholdCellAmplitudeSupermoduleCalibPHYS) { + result = Quality::Medium; + } + } + } + } + } + + if (mo->getName() == "cellTimeSupermoduleCalib_PHYS") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + std::unique_ptr h_allsupermod_proj(h->ProjectionX("h_supermod_proj")); + if (h_allsupermod_proj->GetEntries() == 0) { + result = Quality::Medium; + } else { + Double_t mean_allsupermod = h_allsupermod_proj->GetMean(); + if (TMath::Abs(mean_allsupermod) > mBadThresholdCellTimeSupermoduleCalibPHYS) { + result = Quality::Bad; + } else if (TMath::Abs(mean_allsupermod) > mMedThresholdCellTimeSupermoduleCalibPHYS) { + result = Quality::Medium; + } + + for (int j = 1; j <= h->GetNbinsY(); j++) { + std::unique_ptr h_supermod(h->ProjectionY(Form("h_supermod_%d", j), j, j)); + if (h_supermod->GetEntries() == 0) { + result = Quality::Medium; + } else { + Double_t mean_isupermod = h_supermod->GetMean(); + if (TMath::Abs(mean_isupermod) > mBadThresholdCellTimeSupermoduleCalibPHYS) { + result = Quality::Bad; + break; + } else if (TMath::Abs(mean_isupermod) > mMedThresholdCellTimeSupermoduleCalibPHYS) { + result = Quality::Medium; + } + } + } + } + } + } + + if (mo->getName() == "NumberNonGoodChannelsFEC" || mo->getName() == "NumberDeadChannelsFEC" || mo->getName() == "NumberBadChannelsFEC") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + std::unique_ptr h_allsupermod_proj(h->ProjectionX("h_allsupermod_proj")); + for (int iFECId = 1; iFECId <= h->GetNbinsX(); iFECId++) { + Float_t numChannelsAllSMs = h_allsupermod_proj->GetBinContent(iFECId); + if (numChannelsAllSMs > mBadThresholdChannelsFEC) { + result = Quality::Bad; + break; + } else if (numChannelsAllSMs > mMedThresholdChannelsFEC) { + result = Quality::Medium; + } + } + } + } + + return result; +} + +void CalibCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "MaskStatsAllHisto" || mo->getName() == "MaskStatsEMCALHisto" || mo->getName() == "MaskStatsDCALHisto") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.12, 0.84, 0.88, 0.94, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + msg->Clear(); + msg->AddText("Data OK: Fraction of Bad+Dead Channels Low"); + msg->SetFillColor(kGreen); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + stringstream msg_bad; + msg_bad << "Error: Fraction of Bad+Dead Channels Above " << mBadThresholdMaskStatsAll << std::endl; + msg->Clear(); + msg->AddText(msg_bad.str().c_str()); + msg->AddText("Call EMCAL oncall"); + msg->SetFillColor(kRed); + msg->Draw(); + + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + stringstream msg_med; + msg_med << "Warning: Plot Empty or Fraction of Bad+Dead Channels Above " << mMedThresholdMaskStatsAll << std::endl; + msg->Clear(); + msg->AddText(msg_med.str().c_str()); + msg->SetFillColor(kOrange); + msg->Draw(); + } + } + + if (mo->getName() == "timeCalibCoeff") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.12, 0.84, 0.88, 0.94, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + msg->Clear(); + msg->AddText("Data OK: Fraction of Times Outside Mean Low"); + msg->SetFillColor(kGreen); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + stringstream msg_bad; + msg_bad << "Error: Fraction of Times Outside Mean Above " << mBadThresholdTimeCalibCoeff << std::endl; + msg->Clear(); + msg->AddText(msg_bad.str().c_str()); + msg->AddText("Call EMCAL oncall"); + msg->SetFillColor(kRed); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + stringstream msg_med; + msg_med << "Warning: Plot Empty or Fraction of Times Outside Mean Above " << mMedThresholdTimeCalibCoeff << std::endl; + msg->Clear(); + msg->AddText(msg_med.str().c_str()); + msg->SetFillColor(kOrange); + msg->Draw(); + } + } + + if (mo->getName() == "fractionGoodCellsEvent") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.12, 0.84, 0.88, 0.94, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + msg->Clear(); + msg->AddText("Data OK: Fraction of Good Cells High for All Detectors"); + msg->SetFillColor(kGreen); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + stringstream msg_bad; + msg_bad << "Error: 1+ Detectors Have Fraction of Good Cells Below " << mBadThresholdFractionGoodCellsEvent << std::endl; + msg->Clear(); + msg->AddText(msg_bad.str().c_str()); + msg->AddText("Call EMCAL oncall"); + msg->SetFillColor(kRed); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + stringstream msg_med; + msg_med << "Warning: Plot Empty or 1+ Detectors Have Fraction of Good Cells Below " << mMedThresholdFractionGoodCellsEvent << std::endl; + msg->Clear(); + msg->AddText(msg_med.str().c_str()); + msg->SetFillColor(kOrange); + msg->Draw(); + } + } + + if (mo->getName() == "fractionGoodCellsSupermodule") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.12, 0.84, 0.88, 0.94, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + msg->Clear(); + msg->AddText("Data OK: Fraction of Good Cells High for All Supermodules"); + msg->SetFillColor(kGreen); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + stringstream msg_bad; + msg_bad << "Error: 1+ Supermodules Have Fraction of Good Cells Below " << mBadThresholdFractionGoodCellsSupermodule << std::endl; + msg->Clear(); + msg->AddText(msg_bad.str().c_str()); + msg->AddText("Call EMCAL oncall"); + msg->SetFillColor(kRed); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + stringstream msg_med; + msg_med << "Warning: Plot Empty or 1+ Supermodules Have Fraction of Good Cells Below " << mMedThresholdFractionGoodCellsSupermodule << std::endl; + msg->Clear(); + msg->AddText(msg_med.str().c_str()); + msg->SetFillColor(kOrange); + msg->Draw(); + } + } + + if (mo->getName() == "cellAmplitudeSupermoduleCalib_PHYS") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.12, 0.84, 0.88, 0.94, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + msg->Clear(); + msg->AddText("Data OK: Cell Amplitude Distributions Similar for All Supermodules"); + msg->SetFillColor(kGreen); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + stringstream msg_bad; + msg_bad << "Error: 1+ Supermodules Have Anomalous Cell Amplitude Distributions With Chi2/NDF Above " << mBadThresholdCellAmplitudeSupermoduleCalibPHYS << std::endl; + msg->Clear(); + msg->AddText(msg_bad.str().c_str()); + msg->AddText("Call EMCAL oncall"); + msg->SetFillColor(kRed); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + stringstream msg_med; + msg_med << "Warning: 1+ Supermodules Empty or 1+ Supermodules Have Anomalous Cell Amplitude Distributions With Chi2/NDF Above " << mMedThresholdCellAmplitudeSupermoduleCalibPHYS << std::endl; + msg->Clear(); + msg->AddText(msg_med.str().c_str()); + msg->SetFillColor(kOrange); + msg->Draw(); + } + } + + if (mo->getName() == "cellTimeSupermoduleCalib_PHYS") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.12, 0.84, 0.88, 0.94, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + msg->Clear(); + msg->AddText("Data OK: Mean Cell Time Within Limits"); + msg->SetFillColor(kGreen); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + stringstream msg_bad; + msg_bad << "Error: 1+ Supermodules Have |Mean Cell Time| Above " << mBadThresholdCellTimeSupermoduleCalibPHYS << std::endl; + msg->Clear(); + msg->AddText(msg_bad.str().c_str()); + msg->AddText("Call EMCAL oncall"); + msg->SetFillColor(kRed); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + stringstream msg_med; + msg_med << "Warning: 1+ Supermodules Empty or 1+ Supermodules Have |Mean Cell Time| Above " << mMedThresholdCellTimeSupermoduleCalibPHYS << std::endl; + msg->Clear(); + msg->AddText(msg_med.str().c_str()); + msg->SetFillColor(kOrange); + msg->Draw(); + } + } + + if (mo->getName() == "NumberNonGoodChannelsFEC" || mo->getName() == "NumberDeadChannelsFEC" || mo->getName() == "NumberBadChannelsFEC") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.12, 0.84, 0.88, 0.94, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + msg->Clear(); + msg->AddText("Data OK: Number of Dead/Bad/Dead+Bad Channels Within Limit"); + msg->SetFillColor(kGreen); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + stringstream msg_bad; + msg_bad << "Error: 1+ FECs Have Number of Dead/Bad/Dead+Bad Channels Above " << mBadThresholdChannelsFEC << std::endl; + msg->Clear(); + msg->AddText(msg_bad.str().c_str()); + msg->AddText("Call EMCAL oncall"); + msg->SetFillColor(kRed); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + stringstream msg_med; + msg_med << "Warning: Plot Empty or 1+ FECs Have Number of Dead/Bad/Dead+Bad Channels Above " << mMedThresholdChannelsFEC << std::endl; + msg->Clear(); + msg->AddText(msg_med.str().c_str()); + msg->SetFillColor(kOrange); + msg->Draw(); + } + } +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/CalibMonitoringTask.cxx b/Modules/EMCAL/src/CalibMonitoringTask.cxx new file mode 100644 index 0000000000..51dab8a927 --- /dev/null +++ b/Modules/EMCAL/src/CalibMonitoringTask.cxx @@ -0,0 +1,441 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file +/// \author Cristina Terrevoli, Markus Fasel +/// + +#include "CCDB/CCDBTimeStampUtils.h" +#include "EMCALBase/Geometry.h" +#include "EMCALBase/Mapper.h" +#include "EMCALReconstruction/Channel.h" +#include "EMCALCalib/BadChannelMap.h" +#include "EMCALCalib/TimeCalibrationParams.h" +#include "EMCALCalib/FeeDCS.h" +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "EMCAL/CalibMonitoringTask.h" +#include "EMCAL/DrawGridlines.h" + +// root includes +#include "TCanvas.h" +#include "TPaveText.h" +#include "TH1D.h" +#include "TH2D.h" +#include "TLine.h" +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::emcal +{ + +int CalibMonitoringTask::GetTRUIndexFromSTUIndex(Int_t id, Int_t detector) +{ + Int_t kEMCAL = 0; + Int_t kDCAL = 1; + + if ((id > 31 && detector == kEMCAL) || (id > 13 && detector == kDCAL) || id < 0) { + return -1; // Error Condition + } + + if (detector == kEMCAL) { + return id; + } else if (detector == kDCAL) { + return 32 + ((int)(id / 4) * 6) + ((id % 4 < 2) ? (id % 4) : (id % 4 + 2)); + } + return -1; +} + +int CalibMonitoringTask::GetChannelForMaskRun2(int mask, int bitnumber, bool onethirdsm) +{ + if (onethirdsm) + return mask * 16 + bitnumber; + const int kChannelMap[6][16] = { { 8, 9, 10, 11, 20, 21, 22, 23, 32, 33, 34, 35, 44, 45, 46, 47 }, // Channels in mask0 + { 56, 57, 58, 59, 68, 69, 70, 71, 80, 81, 82, 83, 92, 93, 94, 95 }, // Channels in mask1 + { 4, 5, 6, 7, 16, 17, 18, 19, 28, 29, 30, 31, 40, 41, 42, 43 }, // Channels in mask2 + { 52, 53, 54, 55, 64, 65, 66, 67, 76, 77, 78, 79, 88, 89, 90, 91 }, // Channels in mask3 + { 0, 1, 2, 3, 12, 13, 14, 15, 24, 25, 26, 27, 36, 37, 38, 39 }, // Channels in mask4 + { 48, 49, 50, 51, 60, 61, 62, 63, 72, 73, 74, 75, 84, 85, 86, 87 } }; // Channels in mask5 + return kChannelMap[mask][bitnumber]; +} + +std::vector CalibMonitoringTask::GetAbsFastORIndexFromMask() +{ + std::vector maskedfastors; + int itru = 0; + for (Int_t i = 0; i < 46; i++) { + int localtru = itru % 32, detector = itru >= 32 ? 1 : 0, + globaltru = GetTRUIndexFromSTUIndex(localtru, detector); + bool onethirdsm = ((globaltru >= 30 && globaltru < 32) || (globaltru >= 50 && globaltru < 52)); + for (int ipos = 0; ipos < 6; ipos++) { + auto regmask = mFeeDCS->getTRUDCS(i).getMaskReg(ipos); + std::bitset<16> bitsregmask(regmask); + for (int ibit = 0; ibit < 16; ibit++) { + if (bitsregmask.test(ibit)) { + auto channel = GetChannelForMaskRun2(ipos, ibit, onethirdsm); + int absfastor = mTriggerMapping->getAbsFastORIndexFromIndexInTRU(globaltru, channel); + maskedfastors.push_back(absfastor); + } + } + } + itru++; + } + return maskedfastors; +} + +void CalibMonitoringTask::configure(const boost::property_tree::ptree& config) +{ + mCalibDB = std::make_unique(config.get("qc.config.conditionDB.url").data()); + + for (const auto& output : config.get_child("qc.postprocessing." + getID() + ".calibObjects")) { + mCalibObjects.emplace_back(output.second.data()); + } +} + +void CalibMonitoringTask::initialize(Trigger, framework::ServiceRegistryRef) +{ + QcInfoLogger::setDetector("EMC"); + ILOG(Debug, Devel) << "initialize CalibTask" << ENDM; + // initialize histograms to be monitored as data member + for (const auto& obj : mCalibObjects) { + if (obj == "TimeCalibParams") { + if (!mTimeCalibParamHisto) { + mTimeCalibParamHisto = new TH1D("timeCalibCoeff", "Time Calib Coeff", 17644, -0.5, 17643.5); // + mTimeCalibParamHisto->GetXaxis()->SetTitle("Cell Id"); + mTimeCalibParamHisto->GetYaxis()->SetTitle("Time (ns)"); + mTimeCalibParamHisto->SetStats(false); + } + getObjectsManager()->startPublishing(mTimeCalibParamHisto); + + if (!mTimeCalibParamPosition) { + mTimeCalibParamPosition = new TH2D("timeCalibPosition", "Time Calib Coeff in 2D", 96, -0.5, 95.5, 208, -0.5, 207.5); + mTimeCalibParamPosition->GetXaxis()->SetTitle("column (#eta)"); + mTimeCalibParamPosition->GetYaxis()->SetTitle("row (#phi)"); + mTimeCalibParamPosition->SetStats(false); + } + getObjectsManager()->startPublishing(mTimeCalibParamPosition); + } + if (obj == "BadChannelMap") { + if (!mBadChannelMapHisto) { + mBadChannelMapHisto = new TH2D("badChannelMap", "Pos. of Bad Channel", 96, -0.5, 95.5, 208, -0.5, 207.5); + mBadChannelMapHisto->GetXaxis()->SetTitle("column (#eta)"); + mBadChannelMapHisto->GetYaxis()->SetTitle("row (#phi)"); + mBadChannelMapHisto->SetStats(false); + } + getObjectsManager()->startPublishing(mBadChannelMapHisto); + + // histogram for number of bad, dead, good channels in emcal only + if (!mMaskStatsEMCALHisto) { + mMaskStatsEMCALHisto = new TH1D("MaskStatsEMCALHisto", "Number of Good/Dead/Bad Channels in EMCAL Only", 3, -0.5, 2.5); + mMaskStatsEMCALHisto->GetXaxis()->SetTitle("channel status"); + mMaskStatsEMCALHisto->GetYaxis()->SetTitle("Number of channels"); + mMaskStatsEMCALHisto->GetXaxis()->SetBinLabel(1, "Good cells"); + mMaskStatsEMCALHisto->GetXaxis()->SetBinLabel(2, "Bad cells"); + mMaskStatsEMCALHisto->GetXaxis()->SetBinLabel(3, "Dead cells"); + mMaskStatsEMCALHisto->SetStats(false); + } + getObjectsManager()->startPublishing(mMaskStatsEMCALHisto); + + // histogram for number of bad, dead, good channels in emcal only + if (!mMaskStatsDCALHisto) { + mMaskStatsDCALHisto = new TH1D("MaskStatsDCALHisto", "Number of Good/Dead/Bad Channels in DCAL Only", 3, -0.5, 2.5); + mMaskStatsDCALHisto->GetXaxis()->SetTitle("channel status"); + mMaskStatsDCALHisto->GetYaxis()->SetTitle("Number of channels"); + mMaskStatsDCALHisto->GetXaxis()->SetBinLabel(1, "Good cells"); + mMaskStatsDCALHisto->GetXaxis()->SetBinLabel(2, "Bad cells"); + mMaskStatsDCALHisto->GetXaxis()->SetBinLabel(3, "Dead cells"); + mMaskStatsDCALHisto->SetStats(false); + } + getObjectsManager()->startPublishing(mMaskStatsDCALHisto); + + // histogram for number of bad, dead, good channels in all + if (!mMaskStatsAllHisto) { + mMaskStatsAllHisto = new TH1D("MaskStatsAllHisto", "Number of Good/Dead/Bad Channels in EMCAL + DCAL", 3, -0.5, 2.5); + mMaskStatsAllHisto->GetXaxis()->SetTitle("channel status"); + mMaskStatsAllHisto->GetYaxis()->SetTitle("Number of channels"); + mMaskStatsAllHisto->GetXaxis()->SetBinLabel(1, "Good cells"); + mMaskStatsAllHisto->GetXaxis()->SetBinLabel(2, "Bad cells"); + mMaskStatsAllHisto->GetXaxis()->SetBinLabel(3, "Dead cells"); + mMaskStatsAllHisto->SetStats(false); + } + getObjectsManager()->startPublishing(mMaskStatsAllHisto); + + // histogram number of bad, good and dead channels per supermodule + if (!mMaskStatsSupermoduleHisto) { + mMaskStatsSupermoduleHisto = new TH2D("MaskStatsSupermoduleHisto", "Number of Good/Dead/Bad Channels per supermodule", 3, -0.5, 2.5, 20, -0.5, 19.5); + mMaskStatsSupermoduleHisto->GetXaxis()->SetTitle("channel status"); + mMaskStatsSupermoduleHisto->GetYaxis()->SetTitle("Supermodule ID"); + mMaskStatsSupermoduleHisto->GetXaxis()->SetBinLabel(1, "Good cells"); + mMaskStatsSupermoduleHisto->GetXaxis()->SetBinLabel(2, "Bad cells"); + mMaskStatsSupermoduleHisto->GetXaxis()->SetBinLabel(3, "Dead cells"); + mMaskStatsSupermoduleHisto->SetStats(false); + } + getObjectsManager()->startPublishing(mMaskStatsSupermoduleHisto); + + if (!mNumberOfBadChannelsFEC) { + mNumberOfBadChannelsFEC = new TH2D("NumberBadChannelsFEC", "Number of bad channels per FEC", 40, -0.5, 39.5, 20., -0.5, 19.5); + mNumberOfBadChannelsFEC->GetXaxis()->SetTitle("FEC ID"); + mNumberOfBadChannelsFEC->GetYaxis()->SetTitle("Supermodule ID"); + mNumberOfBadChannelsFEC->SetStats(false); + } + getObjectsManager()->startPublishing(mNumberOfBadChannelsFEC); + + if (!mNumberOfDeadChannelsFEC) { + mNumberOfDeadChannelsFEC = new TH2D("NumberDeadChannelsFEC", "Number of dead channels per FEC", 40, -0.5, 39.5, 20., -0.5, 19.5); + mNumberOfDeadChannelsFEC->GetXaxis()->SetTitle("FEC ID"); + mNumberOfDeadChannelsFEC->GetYaxis()->SetTitle("Supermodule ID"); + mNumberOfDeadChannelsFEC->SetStats(false); + } + getObjectsManager()->startPublishing(mNumberOfDeadChannelsFEC); + + if (!mNumberOfNonGoodChannelsFEC) { + mNumberOfNonGoodChannelsFEC = new TH2D("NumberNonGoodChannelsFEC", "Number of dead+bad channels per FEC", 40, -0.5, 39.5, 20., -0.5, 19.5); + mNumberOfNonGoodChannelsFEC->GetXaxis()->SetTitle("FEC ID"); + mNumberOfNonGoodChannelsFEC->GetYaxis()->SetTitle("Supermodule ID"); + mNumberOfNonGoodChannelsFEC->SetStats(false); + } + getObjectsManager()->startPublishing(mNumberOfNonGoodChannelsFEC); + } + if (obj == "FeeDCS") { + if (!mSRUFirmwareVersion) { + mSRUFirmwareVersion = new TH1D("SRUFirmwareVersion", "SRUFirmwareVersion of Supermodule", 20., -0.5, 19.5); + mSRUFirmwareVersion->GetXaxis()->SetTitle("Supermodule ID"); + mSRUFirmwareVersion->GetYaxis()->SetTitle("SRUFirmwareVersion"); + mSRUFirmwareVersion->SetStats(false); + } + getObjectsManager()->startPublishing(mSRUFirmwareVersion); + + if (!mActiveDDLs) { + mActiveDDLs = new TH1D("ActiveDDLs", "Active Status of DDLs", 46., -0.5, 45.5); + mActiveDDLs->GetXaxis()->SetTitle("DDL ID"); + mActiveDDLs->GetYaxis()->SetTitle("Active Status"); + mActiveDDLs->SetStats(false); + } + getObjectsManager()->startPublishing(mActiveDDLs); + + if (!mTRUThresholds) { + mTRUThresholds = new TH1D("TRUThresholds", "L0 threshold of TRUs", 46., -0.5, 45.5); + mTRUThresholds->GetXaxis()->SetTitle("TRU ID"); + mTRUThresholds->GetYaxis()->SetTitle("L0 threshold"); + mTRUThresholds->SetStats(false); + } + getObjectsManager()->startPublishing(mTRUThresholds); + + if (!mL0Algorithm) { + mL0Algorithm = new TH1D("L0Algorithm", "L0 algorithm of TRUs", 46., -0.5, 45.5); + mL0Algorithm->GetXaxis()->SetTitle("TRU ID"); + mL0Algorithm->GetYaxis()->SetTitle("L0 algorithm"); + mL0Algorithm->SetStats(false); + } + getObjectsManager()->startPublishing(mL0Algorithm); + + if (!mRollbackSTU) { + mRollbackSTU = new TH1D("RollbackSTU", "Rollback Buffer of TRUs", 46., -0.5, 45.5); + mRollbackSTU->GetXaxis()->SetTitle("TRU ID"); + mRollbackSTU->GetYaxis()->SetTitle("Rollback Buffer"); + mRollbackSTU->SetStats(false); + } + getObjectsManager()->startPublishing(mRollbackSTU); + + if (!mTRUMaskPositionHisto) { + mTRUMaskPositionHisto = new TH2D("TRUMaskPositionHisto", "TRU Mask Position Histogram", 48., -0.5, 47.5, 104, -0.5, 103.5); + mTRUMaskPositionHisto->GetXaxis()->SetTitle("FastOR Abs Eta"); + mTRUMaskPositionHisto->GetYaxis()->SetTitle("FastOR Abs Phi"); + mTRUMaskPositionHisto->SetStats(false); + } + getObjectsManager()->startPublishing(mTRUMaskPositionHisto); + } + } + o2::emcal::Geometry::GetInstanceFromRunNumber(300000); + mMapper = std::make_unique(); +} + +void CalibMonitoringTask::update(Trigger t, framework::ServiceRegistryRef) +{ + auto geo = o2::emcal::Geometry::GetInstance(); + std::map metadata; + reset(); + for (const auto& obj : mCalibObjects) { + if (obj == "BadChannelMap") { + mBadChannelMap = mCalibDB->readBadChannelMap(t.timestamp, metadata); + if (!mBadChannelMap) { + ILOG(Info, Support) << "No Bad Channel Map object " << ENDM; + continue; + } + TH2* hist_temp2 = 0x0; + hist_temp2 = mBadChannelMap->getHistogramRepresentation(); + for (Int_t i = 0; i < hist_temp2->GetNbinsX(); i++) { + for (Int_t j = 0; j < hist_temp2->GetNbinsY(); j++) { + mBadChannelMapHisto->SetBinContent(i + 1, j + 1, hist_temp2->GetBinContent(i + 1, j + 1)); + } + } + + // loop over all cells and check channel status + int minCellDCAL = 12288; + for (int cellID = 0; cellID < 17664; cellID++) { + auto cellStatus = mBadChannelMap->getChannelStatus(cellID); + auto [supermodule, module, modphi, modeta] = geo->GetCellIndex(cellID); + auto [ddl, onlinerow, onlinecol] = geo->getOnlineID(cellID); + auto hwaddress = mMapper->getMappingForDDL(ddl).getHardwareAddress(onlinerow, onlinecol, o2::emcal::ChannelType_t::HIGH_GAIN); + auto fecID = mMapper->getFEEForChannelInDDL(ddl, o2::emcal::Channel::getFecIndexFromHwAddress(hwaddress), o2::emcal::Channel::getBranchIndexFromHwAddress(hwaddress)); + int statusbin = 0; + if (cellStatus == o2::emcal::BadChannelMap::MaskType_t::BAD_CELL) { + statusbin = 1; + mNumberOfBadChannelsFEC->Fill(fecID, supermodule); + mNumberOfNonGoodChannelsFEC->Fill(fecID, supermodule); + } else if (cellStatus == o2::emcal::BadChannelMap::MaskType_t::DEAD_CELL) { + statusbin = 2; + mNumberOfDeadChannelsFEC->Fill(fecID, supermodule); + mNumberOfNonGoodChannelsFEC->Fill(fecID, supermodule); + } + mMaskStatsAllHisto->Fill(statusbin); + mMaskStatsSupermoduleHisto->Fill(statusbin, supermodule); + if (cellID < minCellDCAL) { + mMaskStatsEMCALHisto->Fill(statusbin); + } else { + mMaskStatsDCALHisto->Fill(statusbin); + } + } + } + + if (obj == "TimeCalibParams") { + mTimeCalib = mCalibDB->readTimeCalibParam(t.timestamp, metadata); + if (!mTimeCalib) { + ILOG(Info, Support) << " No Time Calib object " << ENDM; + continue; + } + TH1* hist_temp = 0x0; + hist_temp = mTimeCalib->getHistogramRepresentation(false); // we monitor for the moment only the high gain + for (Int_t i = 0; i < hist_temp->GetNbinsX(); i++) { + mTimeCalibParamHisto->SetBinContent(i, hist_temp->GetBinContent(i + 1)); + auto [row, column] = geo->GlobalRowColFromIndex(i); + mTimeCalibParamPosition->SetBinContent(column + 1, row + 1, hist_temp->GetBinContent(i + 1)); + } + } + if (obj == "FeeDCS") { + mFeeDCS = mCalibDB->readFeeDCSData(t.timestamp, metadata); + if (!mFeeDCS) { + ILOG(Info, Support) << " No FEE DCS object " << ENDM; + continue; + } + for (Int_t i = 0; i < mSRUFirmwareVersion->GetNbinsX(); i++) { + mSRUFirmwareVersion->SetBinContent(i + 1, mFeeDCS->getSRUFWversion(i)); + } + for (Int_t i = 0; i < mActiveDDLs->GetNbinsX(); i++) { + mActiveDDLs->SetBinContent(i + 1, mFeeDCS->isDDLactive(i)); + } + for (Int_t i = 0; i < mTRUThresholds->GetNbinsX(); i++) { + mTRUThresholds->SetBinContent(i + 1, static_cast(mFeeDCS->getTRUDCS(i).getGTHRL0())); + } + for (Int_t i = 0; i < mL0Algorithm->GetNbinsX(); i++) { + mL0Algorithm->SetBinContent(i + 1, mFeeDCS->getTRUDCS(i).getL0SEL()); + } + + for (Int_t i = 0; i < mRollbackSTU->GetNbinsX(); i++) { + mRollbackSTU->SetBinContent(i + 1, mFeeDCS->getTRUDCS(i).getRLBKSTU()); + } + + auto fastORs = GetAbsFastORIndexFromMask(); + + for (auto fastORID : fastORs) { + auto [eta, phi] = mTriggerMapping->getPositionInEMCALFromAbsFastORIndex(fastORID); + mTRUMaskPositionHisto->SetBinContent(eta + 1, phi + 1, 1.); + } + + o2::quality_control_modules::emcal::DrawGridlines::DrawFastORGrid(mTRUMaskPositionHisto); + o2::quality_control_modules::emcal::DrawGridlines::DrawTRUGrid(mTRUMaskPositionHisto); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInTriggerGeo(mTRUMaskPositionHisto); + } + } +} + +void CalibMonitoringTask::finalize(Trigger t, framework::ServiceRegistryRef) +{ + for (const auto& obj : mCalibObjects) { + if (obj == "BadChannelMap") { + getObjectsManager()->stopPublishing(mBadChannelMapHisto); + getObjectsManager()->stopPublishing(mMaskStatsEMCALHisto); + getObjectsManager()->stopPublishing(mMaskStatsDCALHisto); + getObjectsManager()->stopPublishing(mMaskStatsAllHisto); + getObjectsManager()->stopPublishing(mMaskStatsSupermoduleHisto); + getObjectsManager()->stopPublishing(mNumberOfBadChannelsFEC); + getObjectsManager()->stopPublishing(mNumberOfDeadChannelsFEC); + getObjectsManager()->stopPublishing(mNumberOfNonGoodChannelsFEC); + } + if (obj == "TimeCalibParams") { + getObjectsManager()->stopPublishing(mTimeCalibParamHisto); + getObjectsManager()->stopPublishing(mTimeCalibParamPosition); + } + if (obj == "FeeDCS") { + getObjectsManager()->stopPublishing(mSRUFirmwareVersion); + getObjectsManager()->stopPublishing(mActiveDDLs); + getObjectsManager()->stopPublishing(mTRUThresholds); + getObjectsManager()->stopPublishing(mL0Algorithm); + getObjectsManager()->stopPublishing(mRollbackSTU); + getObjectsManager()->stopPublishing(mTRUMaskPositionHisto); + } + } +} +void CalibMonitoringTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Support) << "Resetting the histogram" << ENDM; + if (mBadChannelMapHisto) { + mBadChannelMapHisto->Reset(); + } + if (mMaskStatsEMCALHisto) { + mMaskStatsEMCALHisto->Reset(); + } + if (mMaskStatsDCALHisto) { + mMaskStatsDCALHisto->Reset(); + } + if (mMaskStatsAllHisto) { + mMaskStatsAllHisto->Reset(); + } + if (mMaskStatsSupermoduleHisto) { + mMaskStatsSupermoduleHisto->Reset(); + } + if (mNumberOfBadChannelsFEC) { + mNumberOfBadChannelsFEC->Reset(); + } + if (mNumberOfDeadChannelsFEC) { + mNumberOfDeadChannelsFEC->Reset(); + } + if (mNumberOfNonGoodChannelsFEC) { + mNumberOfNonGoodChannelsFEC->Reset(); + } + if (mTimeCalibParamHisto) { + mTimeCalibParamHisto->Reset(); + } + if (mSRUFirmwareVersion) { + mSRUFirmwareVersion->Reset(); + } + if (mActiveDDLs) { + mActiveDDLs->Reset(); + } + if (mTRUThresholds) { + mTRUThresholds->Reset(); + } + if (mL0Algorithm) { + mL0Algorithm->Reset(); + } + if (mRollbackSTU) { + mRollbackSTU->Reset(); + } + if (mTRUMaskPositionHisto) { + mTRUMaskPositionHisto->Reset(); + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/CellAmpCheck.cxx b/Modules/EMCAL/src/CellAmpCheck.cxx new file mode 100644 index 0000000000..7bdef5ce1a --- /dev/null +++ b/Modules/EMCAL/src/CellAmpCheck.cxx @@ -0,0 +1,114 @@ +#include "EMCAL/CellAmpCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Quality.h" + +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::emcal +{ +void CellAmpCheck::configure() +{ + // configure threshold-based checkers + auto nChiSqMedThresh = mCustomParameters.find("ChiSqMedThresh"); + if (nChiSqMedThresh != mCustomParameters.end()) { + try { + mChiSqMedThresh = std::stod(nChiSqMedThresh->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nChiSqMedThresh->second.data() + << " not a double" << ENDM; + } + } + + auto nBadChiSqThresh = mCustomParameters.find("BadChiSqThresh"); + if (nBadChiSqThresh != mCustomParameters.end()) { + try { + mChiSqBadThresh = std::stod(nBadChiSqThresh->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nBadChiSqThresh->second.data() + << " not a double" << ENDM; + } + } +} + +Quality CellAmpCheck::check( + std::map>* moMap) +{ + if (!mCellAmpReference) { + // Load reference histogram from the CCDB + mCellAmpReference = + this->retrieveConditionAny("EMC/QCREF/CellAmpCalibCheck"); + } + if (!mCellAmpReference) { + // If histogram not found print out error message + ILOG(Error, Support) << "Could not find the reference histogram!" << ENDM; + } + + Quality result = Quality::Good; + for (auto& [moName, mo] : *moMap) { + if (mo->getName() == "cellAmplitudeCalib_PHYS") { + + auto* h = dynamic_cast(mo->getObject()); + h->Scale(1. / h->Integral()); + mCellAmpReference->Scale(1. / mCellAmpReference->Integral()); + + Double_t chisq_val = h->Chi2Test(mCellAmpReference, "UU NORM CHI2/NDF"); + if (chisq_val > mChiSqMedThresh) { + result = Quality::Medium; + } + if (mChiSqBadThresh < chisq_val) { + result = Quality::Bad; + } + } + } + return result; +} + +void CellAmpCheck::beautify(std::shared_ptr mo, + Quality checkResult) +{ + if (mo->getName() == "cellAmplitudeCalib_PHYS") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("Cell amplitude within expectations."); + msg->SetFillColor(kGreen); + msg->Draw(); + // + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red" << ENDM; + msg->Clear(); + msg->AddText("Suspicious cell amplitude detected."); + msg->AddText("If NOT a technical run,"); + msg->AddText("call EMCAL on-call."); + msg->Draw(); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange" << ENDM; + msg->Clear(); + msg->AddText("WARNING: Deviation from expectations. Keep monitoring"); + msg->Draw(); + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/CellCheck.cxx b/Modules/EMCAL/src/CellCheck.cxx new file mode 100644 index 0000000000..241f4ea977 --- /dev/null +++ b/Modules/EMCAL/src/CellCheck.cxx @@ -0,0 +1,261 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include "EMCAL/CellCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::emcal +{ + +Quality CellCheck::check(std::map>* moMap) +{ + auto mo = moMap->begin()->second; + Quality result = Quality::Good; + // cellAmplidute_PHYS + // cellTime_PHYS + std::vector amplitudeHist = { "cellAmplitudeEMCAL", "cellAmplitudeDCAL", "cellAmplitude_PHYS" }; + if (std::find(amplitudeHist.begin(), amplitudeHist.end(), mo->getName()) != amplitudeHist.end()) { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) + result = Quality::Bad; + } + if (mo->getName() == "SMMaxNumDigits") { + double errormargin = 2.; + auto hist = dynamic_cast(mo->getObject()); + if (hist == nullptr) { + return Quality::Null; + } + std::vector smcounts; + for (auto ib : ROOT::TSeqI(0, hist->GetXaxis()->GetNbins())) { + auto countSM = hist->GetBinContent(ib + 1); + if (countSM > 0) + smcounts.emplace_back(countSM); + } + if (!smcounts.size()) { + result = Quality::Medium; + } else { + TRobustEstimator meanfinder; + double mean, sigma; + meanfinder.EvaluateUni(smcounts.size(), smcounts.data(), mean, sigma); + for (auto ib : ROOT::TSeqI(0, hist->GetXaxis()->GetNbins())) { + if (hist->GetBinContent(ib + 1) > mean + errormargin * sigma) { + result = Quality::Bad; + break; + } + } + } + } + + /* if (mo->getName() == "cellTimeHG") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) + result = Quality::Bad; + else { + Float_t timeMean = h->GetMean(); + Float_t timeThrs = 60.; + if (timeMean < timeThrs - 20. || timeMean > timeThrs + 20.) { + result = Quality::Bad; + } + } + } + + if (mo->getName() == "cellTimeLG") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) + result = Quality::Bad; + else { + Float_t timeMean = h->GetMean(); + Float_t timeThrs = 60; + if (timeMean < timeThrs - 20 || timeMean > timeThrs + 20) { + result = Quality::Bad; + } + } + } + */ + return result; +} + +void CellCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName().find("Time") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + return; + } + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("Mean inside limits: OK!!!"); + msg->SetFillColor(kGreen); + // + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + msg->Clear(); + msg->AddText("Mean outside limits or no entries"); + msg->AddText("If NOT a technical run,"); + msg->AddText("call EMCAL on-call."); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } + if (mo->getName().find("Amplitude") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + return; + } + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("Mean inside limits: OK!!!"); + msg->SetFillColor(kGreen); + // + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + msg->Clear(); + msg->AddText("Mean outside limits or no entries"); + msg->AddText("If NOT a technical run,"); + msg->AddText("call EMCAL on-call."); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } + if (mo->getName() == "SMMaxNumDigits") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + return; + } + if (checkResult == Quality::Good) { + TLatex* msg = new TLatex(0.2, 0.8, "#color[418]{Data OK}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + TLatex* msg = new TLatex(0.2, 0.8, "#color[2]{Noisy supermodule detected}"); + // Large payload in several DDLs + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + msg = new TLatex(0.2, 0.7, "#color[2]{If NOT techn.run: call EMCAL oncall}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + TLatex* msg = new TLatex(0.2, 0.8, "#color[42]{empty:if in run, call EMCAL-oncall}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } + h->SetLineColor(kBlack); + } + if (mo->getName().find("cellOccupancy") != std::string::npos) { + auto* h2D = dynamic_cast(mo->getObject()); + if (h2D == nullptr) { + return; + } + // orizontal + TLine* l1 = new TLine(-0.5, 24, 95.5, 24); + TLine* l2 = new TLine(-0.5, 48, 95.5, 48); + TLine* l3 = new TLine(-0.5, 72, 95.5, 72); + TLine* l4 = new TLine(-0.5, 96, 95.5, 96); + TLine* l5 = new TLine(-0.5, 120, 95.5, 120); + TLine* l6 = new TLine(-0.5, 128, 95.5, 128); + + TLine* l7 = new TLine(-0.5, 152, 31.5, 152); + TLine* l8 = new TLine(63.5, 152, 95.5, 152); + + TLine* l9 = new TLine(-0.5, 176, 31.5, 176); + TLine* l10 = new TLine(63.5, 176, 95.5, 176); + + TLine* l11 = new TLine(-0.5, 200, 95.5, 200); + TLine* l12 = new TLine(47.5, 200, 47.5, 207.5); + + // vertical + TLine* l13 = new TLine(47.5, -0.5, 47.5, 128); + TLine* l14 = new TLine(31.5, 128, 31.5, 200); + TLine* l15 = new TLine(63.5, 128, 63.5, 200); + + h2D->GetListOfFunctions()->Add(l1); + h2D->GetListOfFunctions()->Add(l2); + h2D->GetListOfFunctions()->Add(l3); + h2D->GetListOfFunctions()->Add(l4); + h2D->GetListOfFunctions()->Add(l5); + h2D->GetListOfFunctions()->Add(l6); + h2D->GetListOfFunctions()->Add(l7); + h2D->GetListOfFunctions()->Add(l8); + h2D->GetListOfFunctions()->Add(l9); + h2D->GetListOfFunctions()->Add(l10); + h2D->GetListOfFunctions()->Add(l11); + h2D->GetListOfFunctions()->Add(l12); + h2D->GetListOfFunctions()->Add(l13); + h2D->GetListOfFunctions()->Add(l14); + h2D->GetListOfFunctions()->Add(l15); + + l1->Draw(); + l2->Draw(); + l3->Draw(); + l4->Draw(); + l5->Draw(); + l6->Draw(); + l7->Draw(); + l8->Draw(); + l9->Draw(); + l10->Draw(); + l11->Draw(); + l12->Draw(); + l13->Draw(); + l14->Draw(); + l15->Draw(); + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/CellTask.cxx b/Modules/EMCAL/src/CellTask.cxx new file mode 100644 index 0000000000..683da734d2 --- /dev/null +++ b/Modules/EMCAL/src/CellTask.cxx @@ -0,0 +1,1273 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CellTask.cxx +/// \author Markus Fasel, Cristina Terrevoli +/// + +#include +#include +#include + +#include +#include +#include + +#include +#include "QualityControl/QcInfoLogger.h" +#include "EMCAL/CellTask.h" +#include "CommonConstants/LHCConstants.h" +#include "DataFormatsEMCAL/Cell.h" +#include "EMCALBase/Geometry.h" +#include "EMCALCalib/CalibDB.h" +#include "EMCALCalib/BadChannelMap.h" +#include "EMCALCalib/GainCalibrationFactors.h" +#include "EMCALCalib/TimeCalibrationParams.h" +#include +#include +#include +#include +#include +#include +#include "EMCAL/DrawGridlines.h" +#include + +namespace o2::quality_control_modules::emcal +{ + +CellTask::~CellTask() +{ + auto cleanOptional = [](auto* hist) { + if (hist) { + delete hist; + } + }; + for (auto en : mHistogramContainer) { + en.second.clean(); + } + cleanOptional(mEvCounterTF); + cleanOptional(mEvCounterTFPHYS); + cleanOptional(mEvCounterTFCALIB); + cleanOptional(mTFPerCycles); + cleanOptional(mTFPerCyclesTOT); + cleanOptional(mBCCounterPHYS); + cleanOptional(mBCCounterCalib); + cleanOptional(mCellsMaxSM); + cleanOptional(mCells_ev_sm); + cleanOptional(mCells_ev_sm_good); + cleanOptional(mCells_ev_sm_bad); + cleanOptional(mCells_ev_smThr); + cleanOptional(mCells_ev); + cleanOptional(mCells_ev_good); + cleanOptional(mCells_ev_bad); + cleanOptional(mCells_ev_Thres); + cleanOptional(mCells_ev_EMCAL); + cleanOptional(mCells_ev_EMCAL_good); + cleanOptional(mCells_ev_EMCAL_bad); + cleanOptional(mCells_ev_EMCAL_Thres); + cleanOptional(mCells_ev_DCAL); + cleanOptional(mCells_ev_DCAL_good); + cleanOptional(mCells_ev_DCAL_bad); + cleanOptional(mCells_ev_DCAL_Thres); + cleanOptional(mFracGoodCellsEvent); + cleanOptional(mFracGoodCellsSM); + cleanOptional(mTotalEnergy); + cleanOptional(mTotalEnergyCorr); + cleanOptional(mTotalEnergySM); +} + +void CellTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + QcInfoLogger::setDetector("EMC"); + ILOG(Debug, Devel) << "initialize CellTask" << ENDM; + // define histograms + + auto get_bool = [](const std::string_view input) -> bool { + return input == "true"; + }; + + auto get_double = [](const std::string_view input) -> double { + double result = 0.; + if (input.length()) { + try { + result = std::stof(input.data()); + } catch (...) { + } + } + return result; + }; + + if (hasConfigValue("debuggerDelay")) { + if (get_bool("debuggerDelay")) { + // set delay in order to allow for attaching the debugger + sleep(20); + } + } + + mTaskSettings.mHasAmpVsCellID = get_bool(getConfigValueLower("hasAmpVsCell")), + mTaskSettings.mHasTimeVsCellID = get_bool(getConfigValueLower("hasTimeVsCell")), + mTaskSettings.mHasHistosCalib = get_bool(getConfigValueLower("hasHistCalib")); + mTaskSettings.mCalibrateEnergy = get_bool(getConfigValue("calibrateEnergy")); + mTaskSettings.mIsHighMultiplicity = get_bool(getConfigValue("highMultiplicity")); + if (hasConfigValue("thresholdTimePhys")) { + mTaskSettings.mAmpThresholdTimePhys = get_double(getConfigValue("thresholdTimePhys")); + } + if (hasConfigValue("thresholdTimeCalib")) { + mTaskSettings.mAmpThresholdTimeCalib = get_double(getConfigValue("thresholdTimeCalib")); + } + if (hasConfigValue("thresholdCAL")) { + mTaskSettings.mThresholdCAL = get_double(getConfigValue("thresholdCAL")); + } + if (hasConfigValue("thresholdPHYS")) { + mTaskSettings.mThresholdPHYS = get_double(getConfigValue("thresholdPHYS")); + } + if (hasConfigValue("thresholdTotalEnergy")) { + mTaskSettings.mThresholdTotalEnergy = get_double(getConfigValue("thresholdTotalEnergy")); + } + if (hasConfigValue("ThresholdAvEnergy")) { + mTaskSettings.mThresholdAvEnergy = get_double(getConfigValue("ThresholdAvEnergy")); + } + if (hasConfigValue("ThresholdAvTime")) { + mTaskSettings.mThresholdAvTime = get_double(getConfigValue("ThresholdAvTime")); + } + ILOG(Info, Support) << "Apply energy calibration: " << (mTaskSettings.mCalibrateEnergy ? "yes" : "no") << ENDM; + ILOG(Info, Support) << "Amplitude cut time histograms (PhysTrigger) " << mTaskSettings.mAmpThresholdTimePhys << ENDM; + ILOG(Info, Support) << "Amplitude cut time histograms (CalibTrigger) " << mTaskSettings.mAmpThresholdTimeCalib << ENDM; + ILOG(Info, Support) << "Amplitude cut occupancy histograms (PhysTrigger) " << mTaskSettings.mThresholdPHYS << ENDM; + ILOG(Info, Support) << "Amplitude cut occupancy histograms (CalibTrigger) " << mTaskSettings.mThresholdCAL << ENDM; + ILOG(Info, Support) << "Energy threshold av. energy histogram (constrained) " << mTaskSettings.mThresholdAvEnergy << " GeV/c" << ENDM; + ILOG(Info, Support) << "Time threshold av. time histogram (constrained) " << mTaskSettings.mThresholdAvTime << " ns" << ENDM; + ILOG(Info, Support) << "Multiplicity mode: " << (mTaskSettings.mIsHighMultiplicity ? "High multiplicity" : "Low multiplicity") << ENDM; + + mIgnoreTriggerTypes = get_bool(getConfigValue("ignoreTriggers")); + + if (mTaskSettings.mHasAmpVsCellID) { + ILOG(Debug, Support) << "Enabling histograms : Amplitude vs. cellID" << ENDM; + } + if (mTaskSettings.mHasTimeVsCellID) { + ILOG(Debug, Support) << "Enabling histograms : Time vs. cellID" << ENDM; + } + if (mTaskSettings.mHasHistosCalib) { + ILOG(Debug, Support) << "Enabling calibrated histograms" << ENDM; + } + + parseMultiplicityRanges(); + initDefaultMultiplicityRanges(); + + // initialize geometry + if (!mGeometry) { + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); + } + + std::array triggers = { { "CAL", "PHYS" } }; + for (const auto& trg : triggers) { + CellHistograms histos; + histos.mGeometry = mGeometry; + histos.initForTrigger(trg.data(), mTaskSettings); + histos.startPublishing(*getObjectsManager()); + mHistogramContainer[trg] = histos; + } // trigger type + // new histos + mTFPerCyclesTOT = new TH1D("NumberOfTFperCycles_TOT", "NumberOfTFperCycles_TOT", 100, -0.5, 99.5); // + mTFPerCyclesTOT->GetXaxis()->SetTitle("NumberOfTFperCyclesTOT"); + mTFPerCyclesTOT->GetYaxis()->SetTitle("Counts"); + getObjectsManager()->startPublishing(mTFPerCyclesTOT); + + mTFPerCycles = new TH1D("NumberOfTFperCycles_1", "NumberOfTFperCycles_1", 1, -0.5, 1.5); + mTFPerCycles->GetXaxis()->SetTitle("NumberOfTFperCycles"); + mTFPerCycles->GetYaxis()->SetTitle("Counts"); + getObjectsManager()->startPublishing(mTFPerCycles); + + mBCCounterPHYS = new TH1D("NumberOfTriggerPerBC_PHYS", "Number of Triggers in bunch crossing (physics triggers)", o2::constants::lhc::LHCMaxBunches + 1, -0.5, o2::constants::lhc::LHCMaxBunches + 0.5); + mBCCounterPHYS->GetXaxis()->SetTitle("Bunch crossing"); + mBCCounterPHYS->GetYaxis()->SetTitle("Number of triggers"); + getObjectsManager()->startPublishing(mBCCounterPHYS); + + mBCCounterCalib = new TH1D("NumberOfTriggerPerBC_CALIB", "Number of Triggers in bunch crossing (calibration triggers)", o2::constants::lhc::LHCMaxBunches + 1, -0.5, o2::constants::lhc::LHCMaxBunches + 0.5); + mBCCounterCalib->GetXaxis()->SetTitle("Bunch crossing"); + mBCCounterCalib->GetYaxis()->SetTitle("Number of triggers"); + getObjectsManager()->startPublishing(mBCCounterCalib); + + mEvCounterTF = new TH1D("NEventsPerTF", "NEventsPerTF", 401, -0.5, 400.5); + mEvCounterTF->GetXaxis()->SetTitle("NEventsPerTimeFrame"); + mEvCounterTF->GetYaxis()->SetTitle("Counts"); + getObjectsManager()->startPublishing(mEvCounterTF); + + mEvCounterTFPHYS = new TH1D("NEventsPerTFPHYS", "NEventsPerTFPHYS", 401, -0.5, 400.5); + mEvCounterTFPHYS->GetXaxis()->SetTitle("NEventsPerTimeFrame_PHYS"); + mEvCounterTFPHYS->GetYaxis()->SetTitle("Counts"); + getObjectsManager()->startPublishing(mEvCounterTFPHYS); + + mEvCounterTFCALIB = new TH1D("NEventsPerTFCALIB", "NEventsPerTFCALIB", 6, -0.5, 5.5); + mEvCounterTFCALIB->GetXaxis()->SetTitle("NEventsPerTimeFrame_CALIB"); + mEvCounterTFCALIB->GetYaxis()->SetTitle("Counts"); + getObjectsManager()->startPublishing(mEvCounterTFCALIB); + + mCellsMaxSM = new TH1D("SMMaxNumCells", "Supermodule with largest amount of cells", 20, -0.5, 19.5); + mCellsMaxSM->GetXaxis()->SetTitle("Supermodule"); + mCellsMaxSM->GetYaxis()->SetTitle("counts"); + getObjectsManager()->startPublishing(mCellsMaxSM); + + mCells_ev_sm = new TH2D("ncellsPerEventSupermodule", "# of Cells per Events vs supermodule ID", mTaskSettings.mMultiplicityRangeSM, 0, mTaskSettings.mMultiplicityRangeSM, 20, -0.5, 19.5); + mCells_ev_sm->GetYaxis()->SetTitle("Supermodule"); + mCells_ev_sm->GetXaxis()->SetTitle("Cells/Event"); + mCells_ev_sm->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_sm); + + mCells_ev_smThr = new TH2D("ncellsPerEventSupermoduleWThr", "# of Cells per Events vs supermodule ID Threshold", mTaskSettings.mMultiplicityRangeSMThreshold, 0, mTaskSettings.mMultiplicityRangeSMThreshold, 20, -0.5, 19.5); + mCells_ev_smThr->GetYaxis()->SetTitle("Supermodule"); + mCells_ev_smThr->GetXaxis()->SetTitle("Cells/Event"); + mCells_ev_smThr->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_smThr); + + mCells_ev = new TH1D("ncellsPerEventTot", "# of Cells per event", mTaskSettings.mMultiplicityRange, 0, mTaskSettings.mMultiplicityRange); + mCells_ev->GetXaxis()->SetTitle("Cells/Event"); + mCells_ev->GetYaxis()->SetTitle("Events"); + mCells_ev->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev); + + mCells_ev_Thres = new TH1D("ncellPerEventTot_Thres", "# of Cells per event above threshold", mTaskSettings.mMultiplicityRangeThreshold, 0, mTaskSettings.mMultiplicityRangeThreshold); + mCells_ev_Thres->SetStats(false); + mCells_ev_Thres->GetXaxis()->SetTitle("Cells/Event"); + mCells_ev_Thres->GetYaxis()->SetTitle("Events"); + getObjectsManager()->startPublishing(mCells_ev_Thres); + + mCells_ev_EMCAL = new TH1D("ncellsPerEventEMCALTot", "# of Cells per events in EMCAL", mTaskSettings.mMultiplicityRangeDetector, 0, mTaskSettings.mMultiplicityRangeDetector); + mCells_ev_EMCAL->GetXaxis()->SetTitle("Cells/Event"); + mCells_ev_EMCAL->GetYaxis()->SetTitle("Events"); + mCells_ev_EMCAL->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_EMCAL); + + mCells_ev_EMCAL_Thres = new TH1D("ncellPerEventEMCALTot_Thres", "# of Cells per event in EMCAL above threshold", mTaskSettings.mMultiplicityRangeThreshold, 0, mTaskSettings.mMultiplicityRangeThreshold); + mCells_ev_EMCAL_Thres->GetXaxis()->SetTitle("Cells/Event"); + mCells_ev_EMCAL_Thres->GetYaxis()->SetTitle("Events"); + mCells_ev_EMCAL_Thres->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_EMCAL_Thres); + + mCells_ev_DCAL = new TH1D("ncellsPerEventDCALTot", "# of Cells per event in DCAL", mTaskSettings.mMultiplicityRangeDetector, 0, mTaskSettings.mMultiplicityRangeDetector); + mCells_ev_DCAL->GetXaxis()->SetTitle("Cells/Event"); + mCells_ev_DCAL->GetYaxis()->SetTitle("Events"); + mCells_ev_DCAL->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_DCAL); + + mCells_ev_DCAL_Thres = new TH1D("ncellPerEventDCALTot_Thres", "# of Cells per event in DCAL above threshold", mTaskSettings.mMultiplicityRangeThreshold, 0, mTaskSettings.mMultiplicityRangeThreshold); + mCells_ev_DCAL_Thres->GetXaxis()->SetTitle("Cells/Event"); + mCells_ev_DCAL_Thres->GetYaxis()->SetTitle("Events"); + mCells_ev_DCAL_Thres->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_DCAL_Thres); + + if (mTaskSettings.mHasHistosCalib) { + mCells_ev_sm_good = new TH2D("ncellsGoodPerEventSupermodule", "# of good Cells per Events vs supermodule ID", mTaskSettings.mMultiplicityRangeSM, 0, mTaskSettings.mMultiplicityRangeSM, 20, -0.5, 19.5); + mCells_ev_sm_good->GetYaxis()->SetTitle("Supermodule"); + mCells_ev_sm_good->GetXaxis()->SetTitle("Good cells/Event"); + mCells_ev_sm_good->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_sm_good); + + mCells_ev_sm_bad = new TH2D("ncellsBadPerEventSupermodule", "# of bad Cells per Events vs supermodule ID", mTaskSettings.mMultiplicityRangeSM, 0, mTaskSettings.mMultiplicityRangeSM, 20, -0.5, 19.5); + mCells_ev_sm_bad->GetYaxis()->SetTitle("Supermodule"); + mCells_ev_sm_bad->GetXaxis()->SetTitle("Bad cells/Event"); + mCells_ev_sm_bad->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_sm_bad); + + mCells_ev_good = new TH1D("ncellsGoodPerEventTot", "# good of Cells per event", mTaskSettings.mMultiplicityRange, 0, mTaskSettings.mMultiplicityRange); + mCells_ev_good->GetXaxis()->SetTitle("Good cells/Event"); + mCells_ev_good->GetYaxis()->SetTitle("Events"); + mCells_ev_good->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_good); + + mCells_ev_bad = new TH1D("ncellsBadPerEventTot", "# bad of Cells per event", mTaskSettings.mMultiplicityRange, 0, mTaskSettings.mMultiplicityRange); + mCells_ev_bad->GetXaxis()->SetTitle("Bad cells/Event"); + mCells_ev_bad->GetYaxis()->SetTitle("Events"); + mCells_ev_bad->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_bad); + + mCells_ev_EMCAL_good = new TH1D("ncellsGoodPerEventEMCALTot", "# of good Cells per events in EMCAL", mTaskSettings.mMultiplicityRangeDetector, 0, mTaskSettings.mMultiplicityRangeDetector); + mCells_ev_EMCAL_good->GetXaxis()->SetTitle("Good cells/Event"); + mCells_ev_EMCAL_good->GetYaxis()->SetTitle("Events"); + mCells_ev_EMCAL_good->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_EMCAL_good); + + mCells_ev_EMCAL_bad = new TH1D("ncellsBadPerEventEMCALTot", "# of bad Cells per events in EMCAL", mTaskSettings.mMultiplicityRangeDetector, 0, mTaskSettings.mMultiplicityRangeDetector); + mCells_ev_EMCAL_bad->GetXaxis()->SetTitle("Bad cells/Event"); + mCells_ev_EMCAL_bad->GetYaxis()->SetTitle("Events"); + mCells_ev_EMCAL_bad->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_EMCAL_bad); + + mCells_ev_DCAL_good = new TH1D("ncellsGoodPerEventDCALTot", "# of good Cells per event in DCAL", mTaskSettings.mMultiplicityRangeDetector, 0, mTaskSettings.mMultiplicityRangeDetector); + mCells_ev_DCAL_good->GetXaxis()->SetTitle("Good cells/Event"); + mCells_ev_DCAL_good->GetYaxis()->SetTitle("Events"); + mCells_ev_DCAL_good->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_DCAL_good); + + mCells_ev_DCAL_bad = new TH1D("ncellsBaddPerEventDCALTot", "# of bad Cells per event in DCAL", mTaskSettings.mMultiplicityRangeDetector, 0, mTaskSettings.mMultiplicityRangeDetector); + mCells_ev_DCAL_bad->GetXaxis()->SetTitle("Badd cells/Event"); + mCells_ev_DCAL_bad->GetYaxis()->SetTitle("Events"); + mCells_ev_DCAL_bad->SetStats(false); + getObjectsManager()->startPublishing(mCells_ev_DCAL_bad); + + mFracGoodCellsEvent = new TH2D("fractionGoodCellsEvent", "Fraction of good cells / event", 3, -0.5, 2.5, 11, 0., 1.1); + mFracGoodCellsEvent->GetXaxis()->SetBinLabel(1, "All"); + mFracGoodCellsEvent->GetXaxis()->SetBinLabel(2, "EMCAL"); + mFracGoodCellsEvent->GetXaxis()->SetBinLabel(3, "DCAL"); + mFracGoodCellsEvent->GetYaxis()->SetTitle("Fraction good"); + mFracGoodCellsEvent->SetStats(false); + getObjectsManager()->startPublishing(mFracGoodCellsEvent); + + mFracGoodCellsSM = new TH2D("fractionGoodCellsSupermodule", "Fraction of good cells / supermodule", 20, -0.5, 19.5, 11, 0., 1.1); + mFracGoodCellsSM->GetXaxis()->SetTitle("Supermodule ID"); + mFracGoodCellsSM->GetYaxis()->SetTitle("Fraction good"); + mFracGoodCellsSM->SetStats(false); + getObjectsManager()->startPublishing(mFracGoodCellsSM); + } + + mTotalEnergy = new TH1D("totalEnergy", "Total energy / event", mTaskSettings.mTotalEnergyRange, 0., mTaskSettings.mTotalEnergyRange); + mTotalEnergy->GetXaxis()->SetTitle("E_{tot} (GeV)"); + mTotalEnergy->GetYaxis()->SetTitle("Number of events"); + mTotalEnergy->SetStats(false); + getObjectsManager()->startPublishing(mTotalEnergy); + + mTotalEnergyCorr = new TH2D("totalEnergyCorr", "Total energy EMCAL vs. DCAL / event", mTaskSettings.mTotalEnergyRangeDetector, 0., mTaskSettings.mTotalEnergyRangeDetector, mTaskSettings.mTotalEnergyRangeDetector, 0., mTaskSettings.mTotalEnergyRangeDetector); + mTotalEnergyCorr->GetXaxis()->SetTitle("EMCAL E_{tot} (GeV)"); + mTotalEnergyCorr->GetYaxis()->SetTitle("DCAL E_{tot} (GeV)"); + getObjectsManager()->startPublishing(mTotalEnergyCorr); + + mTotalEnergySM = new TH2D("totalEnergySupermodule", "Total energy in supermodule / event", mTaskSettings.mTotalEnergyRangeSM, 0., mTaskSettings.mTotalEnergyRangeSM, 20, -0.5, 19.5); + mTotalEnergySM->GetXaxis()->SetTitle("E_{tot} (GeV)"); + mTotalEnergySM->GetYaxis()->SetTitle("SupermoduleID"); + getObjectsManager()->startPublishing(mTotalEnergySM); +} + +void CellTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + reset(); +} + +void CellTask::startOfCycle() +{ + mTimeFramesPerCycles = 0; + ILOG(Debug, Support) << "startOfCycle" << ENDM; +} + +void CellTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + mTFPerCycles->Fill(1); // number of timeframe process per cycle + mTimeFramesPerCycles++; + // check if we have payoad + using MaskType_t = o2::emcal::BadChannelMap::MaskType_t; + + // Handling of inputs from multiple subevents (multiple FLPs) + // Build maps of trigger records and cells according to the subspecification + // and combine trigger records from different maps into a single map of range + // references and subspecifications + std::unordered_map> cellSubEvents; + std::unordered_map> triggerRecordSubevents; + + loadCalibrationObjects(ctx); + + auto posCells = ctx.inputs().getPos("emcal-cells"), + posTriggerRecords = ctx.inputs().getPos("emcal-triggerecords"); + auto numSlotsCells = ctx.inputs().getNofParts(posCells), + numSlotsTriggerRecords = ctx.inputs().getNofParts(posTriggerRecords); + for (decltype(numSlotsCells) islot = 0; islot < numSlotsCells; islot++) { + auto celldata = ctx.inputs().getByPos(posCells, islot); + auto subspecification = framework::DataRefUtils::getHeader(celldata)->subSpecification; + // Discard message if it is a deadbeaf message (empty timeframe) + if (subspecification == 0xDEADBEEF) { + continue; + } + cellSubEvents[subspecification] = ctx.inputs().get>(celldata); + } + for (decltype(numSlotsTriggerRecords) islot = 0; islot < numSlotsTriggerRecords; islot++) { + auto trgrecorddata = ctx.inputs().getByPos(posTriggerRecords, islot); + auto subspecification = framework::DataRefUtils::getHeader(trgrecorddata)->subSpecification; + // Discard message if it is a deadbeaf message (empty timeframe) + if (subspecification == 0xDEADBEEF) { + continue; + } + triggerRecordSubevents[subspecification] = ctx.inputs().get>(trgrecorddata); + } + + auto combinedEvents = buildCombinedEvents(triggerRecordSubevents); + + // ILOG(Info, Support) <<"Received " << cellcontainer.size() << " cells " << ENDM; + int eventcounter = 0; + int eventcounterCALIB = 0; + int eventcounterPHYS = 0; + std::array numCellsSM; + std::array numCellsSM_Thres; + std::array numCellsGood; + std::array numCellsBad; + std::array totalEnergies; + std::fill(numCellsSM.begin(), numCellsSM.end(), 0); + std::fill(numCellsSM_Thres.begin(), numCellsSM_Thres.end(), 0); + for (auto trg : combinedEvents) { + if (!trg.getNumberOfObjects()) { + continue; + } + ILOG(Debug, Support) << "Next event " << eventcounter << " has " << trg.getNumberOfObjects() << " cells from " << trg.getNumberOfSubevents() << " subevent(s)" << ENDM; + + // trigger type + auto triggertype = trg.mTriggerType; + bool isPhysTrigger = mIgnoreTriggerTypes || (triggertype & o2::trigger::PhT), + isCalibTrigger = (!mIgnoreTriggerTypes) && (triggertype & o2::trigger::Cal); + std::string trgClass; + if (isPhysTrigger) { + trgClass = "PHYS"; + eventcounterPHYS++; + if (mBCCounterPHYS) { + mBCCounterPHYS->Fill(trg.mInteractionRecord.bc); + } + } else if (isCalibTrigger) { + trgClass = "CAL"; + eventcounterCALIB++; + if (mBCCounterCalib) { + mBCCounterCalib->Fill(trg.mInteractionRecord.bc); + } + } else { + ILOG(Error, Support) << " Unmonitored trigger class requested " << ENDM; + continue; + } + auto bcphase = trg.mInteractionRecord.bc % 4; // to be fixed:4 histos for EMCAL, 4 histos for DCAL + // force BC phase for LED triggers to be 0 + if (isCalibTrigger) { + bcphase = 0; + } + auto histos = mHistogramContainer[trgClass]; + std::fill(numCellsSM.begin(), numCellsSM.end(), 0); + std::fill(numCellsSM_Thres.begin(), numCellsSM_Thres.end(), 0); + std::fill(numCellsGood.begin(), numCellsGood.end(), 0); + std::fill(numCellsBad.begin(), numCellsBad.end(), 0); + std::fill(totalEnergies.begin(), totalEnergies.end(), 0.); + + // iterate over subevents + for (auto& subev : trg.mSubevents) { + auto cellsSubspec = cellSubEvents.find(subev.mSpecification); + if (cellsSubspec == cellSubEvents.end()) { + ILOG(Error, Support) << "No cell data found for subspecification " << subev.mSpecification << ENDM; + } else { + ILOG(Debug, Support) << subev.mCellRange.getEntries() << " cells in subevent from equipment " << subev.mSpecification << ENDM; + gsl::span eventcells(cellsSubspec->second.data() + subev.mCellRange.getFirstEntry(), subev.mCellRange.getEntries()); + for (auto cell : eventcells) { + if (cell.getLEDMon()) { + // Drop LEDMON cells + continue; + } + // int index = cell.getHighGain() ? 0 : (cell.getLowGain() ? 1 : -1); + // int index = cell.getHighGain() ? 0 : 1; + auto timeoffset = mTimeCalib ? mTimeCalib->getTimeCalibParam(cell.getTower(), cell.getLowGain()) : 0.; + auto energycalib = mEnergyCalib ? mEnergyCalib->getGainCalibFactors(cell.getTower()) : 1.; + bool goodcell = true; + if (mBadChannelMap) { + goodcell = mBadChannelMap->getChannelStatus(cell.getTower()) == MaskType_t::GOOD_CELL; + } + histos.fillHistograms(cell, goodcell, timeoffset, energycalib, bcphase); + if (isPhysTrigger) { + auto [sm, mod, iphi, ieta] = mGeometry->GetCellIndex(cell.getTower()); + numCellsSM[sm]++; + if (cell.getEnergy() > mTaskSettings.mThresholdPHYS) { + numCellsSM_Thres[sm]++; + } + if (goodcell) { + numCellsGood[sm]++; + auto cellenergy = cell.getAmplitude() * energycalib; + auto celltime = cell.getTimeStamp() - timeoffset; + if (cellenergy > mTaskSettings.mThresholdTotalEnergy) { + if (std::abs(celltime) < mTaskSettings.mMaxTimeTotalEnergy) { + totalEnergies[sm] += cellenergy; + } + } + } else { + numCellsBad[sm]++; + } + } + } + } + } + histos.countEvent(); + + if (isPhysTrigger) { + auto maxSM = std::max_element(numCellsSM.begin(), numCellsSM.end()); + auto indexMaxSM = maxSM - numCellsSM.begin(); + mCellsMaxSM->Fill(indexMaxSM); + // fill histo 1) + int mCell_all = 0, mCell_EMCAL = 0, mCell_DCAL = 0; + int mCell_all_Thres = 0, mCell_EMCAL_Thres = 0, mCell_DCAL_Thres = 0; + // make statistics good cells + int nGoodAll = 0, nGoodEMCAL = 0, nGoodDCAL = 0, nBadAll = 0, nBadEMCAL = 0, nBadDCAL = 0; + for (int ism = 0; ism < 20; ism++) { + mCells_ev_sm->Fill(numCellsSM[ism], ism); // for experts + mCells_ev_smThr->Fill(numCellsSM_Thres[ism], ism); // for experts + + mCell_all += numCellsSM[ism]; + mCell_all_Thres += numCellsSM_Thres[ism]; + nGoodAll += numCellsGood[ism]; + nBadAll += numCellsBad[ism]; + if (ism < 12) { + mCell_EMCAL += numCellsSM[ism]; + mCell_EMCAL_Thres += numCellsSM_Thres[ism]; + nGoodEMCAL += numCellsGood[ism]; + nBadEMCAL += numCellsBad[ism]; + } else { + mCell_DCAL += numCellsSM[ism]; + mCell_DCAL_Thres += numCellsSM_Thres[ism]; + nGoodDCAL += numCellsGood[ism]; + nBadDCAL += numCellsBad[ism]; + } + if (mTaskSettings.mHasHistosCalib) { + mCells_ev_sm_good->Fill(numCellsGood[ism], ism); + mCells_ev_sm_bad->Fill(numCellsBad[ism], ism); + if (numCellsGood[ism] + numCellsBad[ism]) { + mFracGoodCellsSM->Fill(ism, static_cast(numCellsGood[ism]) / static_cast(numCellsGood[ism] + numCellsBad[ism])); + } + } + } + mCells_ev->Fill(mCell_all); + mCells_ev_EMCAL->Fill(mCell_EMCAL); + mCells_ev_DCAL->Fill(mCell_DCAL); + + mCells_ev_Thres->Fill(mCell_all_Thres); + mCells_ev_EMCAL_Thres->Fill(mCell_EMCAL_Thres); + mCells_ev_DCAL_Thres->Fill(mCell_DCAL_Thres); + + if (mTaskSettings.mHasHistosCalib) { + mCells_ev_good->Fill(nGoodAll); + mCells_ev_EMCAL_good->Fill(nGoodEMCAL); + mCells_ev_DCAL_good->Fill(nGoodDCAL); + mCells_ev_bad->Fill(nBadAll); + mCells_ev_EMCAL_bad->Fill(nBadEMCAL); + mCells_ev_DCAL_bad->Fill(nBadDCAL); + if (nGoodAll + nBadAll) { + mFracGoodCellsEvent->Fill(0., static_cast(nGoodAll) / static_cast(nGoodAll + nBadAll)); + } + if (nGoodEMCAL + nBadEMCAL) { + mFracGoodCellsEvent->Fill(1., static_cast(nGoodEMCAL) / static_cast(nGoodEMCAL + nBadEMCAL)); + } + if (nGoodDCAL + nBadDCAL) { + mFracGoodCellsEvent->Fill(2., static_cast(nGoodDCAL) / static_cast(nGoodDCAL + nBadDCAL)); + } + } + + double totalEnergySum = 0., totalEnergyEMCAL = 0., totalEnergyDCAL = 0.; + for (std::size_t ism = 0; ism < 20; ism++) { + mTotalEnergySM->Fill(totalEnergies[ism], ism); + totalEnergySum += totalEnergies[ism]; + if (ism < 12) { + totalEnergyEMCAL += totalEnergies[ism]; + } else { + totalEnergyDCAL += totalEnergies[ism]; + } + } + mTotalEnergy->Fill(totalEnergySum); + mTotalEnergyCorr->Fill(totalEnergyEMCAL, totalEnergyDCAL); + } + + eventcounter++; + } + mEvCounterTF->Fill(eventcounter); + mEvCounterTFPHYS->Fill(eventcounterPHYS); + mEvCounterTFCALIB->Fill(eventcounterCALIB); + // event counter per TimeFrame (range 0-100) for the moment (parameter) +} + +void CellTask::endOfCycle() +{ + mTFPerCyclesTOT->Fill(mTimeFramesPerCycles); // do not reset this histo + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void CellTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void CellTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Support) << "Resetting the histogram" << ENDM; + for (auto cont : mHistogramContainer) { + cont.second.reset(); + } + auto resetOptional = [](auto* hist) { + if (hist) { + hist->Reset(); + } + }; + resetOptional(mEvCounterTF); + resetOptional(mEvCounterTFPHYS); + resetOptional(mEvCounterTFCALIB); + resetOptional(mTFPerCycles); + resetOptional(mTFPerCyclesTOT); + resetOptional(mBCCounterPHYS); + resetOptional(mBCCounterCalib); + resetOptional(mCellsMaxSM); + resetOptional(mCells_ev_sm); + resetOptional(mCells_ev_sm_good); + resetOptional(mCells_ev_sm_bad); + resetOptional(mCells_ev_smThr); + resetOptional(mCells_ev); + resetOptional(mCells_ev_good); + resetOptional(mCells_ev_bad); + resetOptional(mCells_ev_Thres); + resetOptional(mCells_ev_EMCAL); + resetOptional(mCells_ev_EMCAL_good); + resetOptional(mCells_ev_EMCAL_bad); + resetOptional(mCells_ev_EMCAL_Thres); + resetOptional(mCells_ev_DCAL); + resetOptional(mCells_ev_DCAL_good); + resetOptional(mCells_ev_DCAL_bad); + resetOptional(mCells_ev_DCAL_Thres); + resetOptional(mFracGoodCellsEvent); + resetOptional(mFracGoodCellsSM); + resetOptional(mTotalEnergy); + resetOptional(mTotalEnergyCorr); + resetOptional(mTotalEnergySM); +} + +void CellTask::finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) +{ + if (matcher == o2::framework::ConcreteDataMatcher("EMC", "BADCHANNELMAP", 0)) { + mBadChannelMap = reinterpret_cast(obj); + if (mBadChannelMap) { + ILOG(Info, Support) << "Updated EMCAL bad channel map " << ENDM; + } + } + if (matcher == o2::framework::ConcreteDataMatcher("EMC", "TIMECALIBPARAM", 0)) { + mTimeCalib = reinterpret_cast(obj); + if (mTimeCalib) { + ILOG(Info, Support) << "Updated EMCAL time calibration" << ENDM; + } + } + if (matcher == o2::framework::ConcreteDataMatcher("EMC", "GAINCALIBPARAM", 0)) { + mEnergyCalib = reinterpret_cast(obj); + if (mEnergyCalib) { + ILOG(Info, Support) << "Update EMCAL gain calibration" << ENDM; + } + } +} + +void CellTask::loadCalibrationObjects(o2::framework::ProcessingContext& ctx) +{ + if (mTaskSettings.mHasHistosCalib) { + ctx.inputs().get("badchannelmap"); + ctx.inputs().get("timecalib"); + } + if (mTaskSettings.mCalibrateEnergy) { + ctx.inputs().get("energycalib"); + } +} + +void CellTask::initDefaultMultiplicityRanges() +{ + if (!mTaskSettings.mMultiplicityRange) { + mTaskSettings.mMultiplicityRange = mTaskSettings.mIsHighMultiplicity ? 10000 : 1000; + } + if (!mTaskSettings.mMultiplicityRangeDetector) { + mTaskSettings.mMultiplicityRangeDetector = mTaskSettings.mIsHighMultiplicity ? 4000 : 300; + } + if (!mTaskSettings.mMultiplicityRangeThreshold) { + mTaskSettings.mMultiplicityRangeThreshold = mTaskSettings.mIsHighMultiplicity ? 1000 : 100; + } + if (!mTaskSettings.mMultiplicityRangeSM) { + mTaskSettings.mMultiplicityRangeSM = mTaskSettings.mIsHighMultiplicity ? 1000 : 100; + } + if (!mTaskSettings.mMultiplicityRangeSMThreshold) { + mTaskSettings.mMultiplicityRangeSMThreshold = mTaskSettings.mIsHighMultiplicity ? 500 : 20; + } + if (std::abs(mTaskSettings.mTotalEnergyRange) < 1e-5) { + mTaskSettings.mTotalEnergyRange = mTaskSettings.mIsHighMultiplicity ? 4000 : 200; + } + if (std::abs(mTaskSettings.mTotalEnergyRangeDetector) < 1e-5) { + mTaskSettings.mTotalEnergyRangeDetector = mTaskSettings.mIsHighMultiplicity ? 4000 : 200; + } + if (std::abs(mTaskSettings.mTotalEnergyRangeSM) < 1e-5) { + mTaskSettings.mTotalEnergyRangeSM = mTaskSettings.mIsHighMultiplicity ? 500 : 50; + } +} + +void CellTask::parseMultiplicityRanges() +{ + if (hasConfigValue("MultiplicityRange")) { + mTaskSettings.mMultiplicityRange = std::stoi(getConfigValue("MultiplicityRange")); + } + if (hasConfigValue("MultiplicityRangeDetector")) { + mTaskSettings.mMultiplicityRangeDetector = std::stoi(getConfigValue("MultiplicityRangeDetector")); + } + if (hasConfigValue("MultiplicityRangeThreshold")) { + mTaskSettings.mMultiplicityRangeThreshold = std::stoi(getConfigValue("MultiplicityRangeThreshold")); + } + if (hasConfigValue("MultiplicityRangeSM")) { + mTaskSettings.mMultiplicityRangeSM = std::stoi(getConfigValue("MultiplicityRangeSM")); + } + if (hasConfigValue("MultiplicityRangeSMThreshold")) { + mTaskSettings.mMultiplicityRangeSMThreshold = std::stoi(getConfigValue("MultiplicityRangeSMThreshold")); + } + if (hasConfigValue("TotalEnergyRange")) { + mTaskSettings.mTotalEnergyRange = std::stod(getConfigValue("TotalEnergyRange")); + } + if (hasConfigValue("TotalEnergyRangeDetector")) { + mTaskSettings.mTotalEnergyRangeDetector = std::stod(getConfigValue("TotalEnergyRangeDetector")); + } + if (hasConfigValue("TotalEnergyRangeSM")) { + mTaskSettings.mTotalEnergyRangeSM = std::stod(getConfigValue("TotalEnergyRangeSM")); + } + if (hasConfigValue("TotalEnergyMaxCellTime")) { + mTaskSettings.mMaxTimeTotalEnergy = std::stod(getConfigValue("TotalEnergyMaxCellTime")); + } +} + +std::vector CellTask::buildCombinedEvents(const std::unordered_map>& triggerrecords) const +{ + std::vector events; + + // Search interaction records from all subevents + std::set allInteractions; + for (auto& [subspecification, trgrec] : triggerrecords) { + for (auto rec : trgrec) { + auto eventIR = rec.getBCData(); + if (allInteractions.find(eventIR) == allInteractions.end()) { + allInteractions.insert(eventIR); + } + } + } + + // iterate over all subevents for all bunch crossings + for (auto collision : allInteractions) { + CombinedEvent nextevent; + nextevent.mInteractionRecord = collision; + bool first = true, + hasSubevent = false; + for (auto [subspecification, records] : triggerrecords) { + auto found = std::find_if(records.begin(), records.end(), [&collision](const o2::emcal::TriggerRecord& rec) { return rec.getBCData() == collision; }); + if (found != records.end()) { + hasSubevent = true; + if (first) { + nextevent.mTriggerType = found->getTriggerBits(); + first = false; + } + nextevent.mSubevents.push_back({ subspecification, o2::dataformats::RangeReference(found->getFirstEntry(), found->getNumberOfObjects()) }); + } + } + if (hasSubevent) { + events.emplace_back(nextevent); + } + } + return events; +} + +bool CellTask::hasConfigValue(const std::string_view key) +{ + if (auto param = mCustomParameters.find(key.data()); param != mCustomParameters.end()) { + return true; + } + return false; +} + +std::string CellTask::getConfigValue(const std::string_view key) +{ + std::string result; + if (auto param = mCustomParameters.find(key.data()); param != mCustomParameters.end()) { + result = param->second; + } + return result; +} + +std::string CellTask::getConfigValueLower(const std::string_view key) +{ + auto input = getConfigValue(key); + std::string result; + if (input.length()) { + result = boost::algorithm::to_lower_copy(input); + } + return result; +} + +void CellTask::CellHistograms::initForTrigger(const std::string trigger, const TaskSettings& settings) +{ // hasAmpVsCellID, bool hasTimeVsCellID, bool hasHistosCalib2D) + if (trigger == "PYHS") { + mCellThreshold = settings.mThresholdPHYS; + mAmplitudeThresholdTime = settings.mAmpThresholdTimePhys; + } else { + mCellThreshold = settings.mThresholdCAL; + mAmplitudeThresholdTime = settings.mAmpThresholdTimeCalib; + } + mThresholdAvTime = settings.mThresholdAvTime; + mThresholdAvEnergy = settings.mThresholdAvEnergy; + auto histBuilder1D = [trigger](const std::string name, const std::string title, int nbinsx, double xmin, double xmax) -> TH1* { + std::string histname = name + "_" + trigger, + histtitle = title + " " + trigger; + return new TH1D(histname.data(), histtitle.data(), nbinsx, xmin, xmax); + }; + auto histBuilder2D = [trigger](const std::string_view name, const std::string_view title, int nbinsx, double xmin, double xmax, int nbinsy, double ymin, double ymax, bool profile) -> TH2* { + std::string histname = std::string(name.data()) + "_" + trigger, + histtitle = std::string(title.data()) + " " + trigger; + if (profile) { + return new TProfile2D(histname.data(), histtitle.data(), nbinsx, xmin, xmax, nbinsy, ymin, ymax); + } + return new TH2D(histname.data(), histtitle.data(), nbinsx, xmin, xmax, nbinsy, ymin, ymax); + }; + + std::map maxAmps = { { "PHYS", 50. }, { "CAL", 10. } }; + double maxAmp = maxAmps[trigger]; + + bool isPhysTrigger = trigger == "PHYS"; + if (isPhysTrigger) { + if (settings.mHasAmpVsCellID) { + mCellAmplitude = histBuilder2D("cellAmplitudeHG", "Cell Amplitude (High gain)", 80, 0, 16, 17664, -0.5, 17663.5, false); + mCellAmplitude->SetStats(false); + // mCellAmplitude[1] = histBuilder2D("cellAmplitudeLG", "Cell Amplitude (Low gain)", 100, 0, 100, 17664, -0.5, 17663.5, false); + if (settings.mHasHistosCalib) { + mCellAmplitudeCalib = histBuilder2D("cellAmplitudeHGCalib", "Cell Amplitude (High gain)", 80, 0, 16, 17664, -0.5, 17663.5, false); + mCellAmplitudeCalib->SetStats(false); + // mCellAmplitudeCalib[1] = histBuilder2D("cellAmplitudeLGCalib", "Cell Amplitude (Low gain)", 100, 0, 100, 17664, -0.5, 17663.5, false); + } + } + if (settings.mHasTimeVsCellID) { + mCellTime = histBuilder2D("cellTimeHG", "Cell Time (High gain)", 400, -200, 200, 17664, -0.5, 17663.5, false); // + mCellTime->SetStats(false); + // mCellTime[1] = histBuilder2D("cellTimeLG", "Cell Time (Low gain)", 400, -200, 200, 17664, -0.5, 17663.5, false); + if (settings.mHasHistosCalib) { + mCellTimeCalib = histBuilder2D("cellTimeHGCalib", "Cell Time Calib (High gain)", 400, -200, 200, 17664, -0.5, 17663.5, false); + mCellTimeCalib->SetStats(false); + // mCellTimeCalib[1] = histBuilder2D("cellTimeLGCalib", "Cell Time Calib (Low gain)", 400, -200, 200, 17664, -0.5, 17663.5, false); + } + } + + if (settings.mHasHistosCalib) { + mCellAmpSupermoduleCalib = histBuilder2D("cellAmplitudeSupermoduleCalib", "Cell amplitude (Calib) vs. supermodule ID ", 4 * static_cast(maxAmp), 0., maxAmp, 20, -0.5, 19.5, false); + mCellAmpSupermoduleCalib->SetStats(false); + mCellTimeSupermoduleCalib = histBuilder2D("cellTimeSupermoduleCalib", "Cell Time (Calib) vs. supermodule ID (High gain)", 600, -400, 800, 20, -0.5, 19.5, false); + mCellTimeSupermoduleCalib->SetStats(false); + mCellAmpSupermoduleBad = histBuilder2D("cellAmplitudeSupermoduleBad", "Cell amplitude (bad cells) vs. supermodule ID", 4 * static_cast(maxAmp), 0., maxAmp, 20, -0.5, 19.5, false); + mCellAmpSupermoduleBad->SetStats(false); + mCellOccupancyGood = histBuilder2D("cellOccupancyGood", "Cell occupancy good cells", 96, -0.5, 95.5, 208, -0.5, 207.5, false); + mCellOccupancyGood->SetStats(false); + mCellOccupancyBad = histBuilder2D("cellOccupancyBad", "Cell occupancy bad cells", 96, -0.5, 95.5, 208, -0.5, 207.5, false); + mCellOccupancyBad->SetStats(false); + + mCellAmplitudeCalib_tot = histBuilder1D("cellAmplitudeCalib", "Cell amplitude Calib in EMCAL,DCAL", 4 * static_cast(maxAmp), 0., maxAmp); + mCellAmplitudeCalib_EMCAL = histBuilder1D("cellAmplitudeCalib_EMCAL", "Cell amplitude Calib in EMCAL", 4 * static_cast(maxAmp), 0., maxAmp); + mCellAmplitudeCalib_DCAL = histBuilder1D("cellAmplitudeCalib_DCAL", "Cell amplitude Calib in DCAL", 4 * static_cast(maxAmp), 0., maxAmp); + + mCellTimeSupermoduleCalib_tot = histBuilder1D("cellTimeCalib", "Cell Time Calib EMCAL,DCAL", 600, -400, 800); + mCellTimeSupermoduleCalib_EMCAL = histBuilder1D("cellTimeCalib_EMCAL", "Cell Time Calib EMCAL", 600, -400, 800); + mCellTimeSupermoduleCalib_DCAL = histBuilder1D("cellTimeCalib_DCAL", "Cell Time Calib DCAL", 600, -400, 800); + + mCellAmplitudeTimeCalib = histBuilder2D("cellAmplitudeTimeCalib", "Cell amplitude vs. time (calibrated); E (GeV); t (ns)", 500, 0., 50., 800, -400., 400., false); + } + } + mCellAmpSupermodule = histBuilder2D("cellAmplitudeSupermodule", "Cell amplitude vs. supermodule ID ", 4 * static_cast(maxAmp), 0., maxAmp, 20, -0.5, 19.5, false); + mCellAmpSupermodule->SetStats(false); + mCellTimeSupermodule = histBuilder2D("cellTimeSupermodule", "Cell Time vs. supermodule ID ", 600, -400, 800, 20, -0.5, 19.5, false); + mCellTimeSupermodule->SetStats(false); + + mCellOccupancy = histBuilder2D("cellOccupancyEMC", "Cell Occupancy EMCAL", 96, -0.5, 95.5, 208, -0.5, 207.5, false); + mCellOccupancy->SetStats(false); + mCellOccupancyThr = histBuilder2D("cellOccupancyEMCwThr", Form("Cell Occupancy EMCAL,DCAL with E>%.1f GeV/c", mCellThreshold), 96, -0.5, 95.5, 208, -0.5, 207.5, false); + mCellOccupancyThr->SetStats(false); + mCellOccupancyThrBelow = histBuilder2D("cellOccupancyEMCwThrBelow", Form("Cell Occupancy EMCAL,DCAL with E<%.1f GeV/c", mCellThreshold), 96, -0.5, 95.5, 208, -0.5, 207.5, false); + mCellOccupancyThrBelow->SetStats(false); + + mAverageCellEnergy = histBuilder2D("averageCellEnergy", "Average cell energy", 96, -0.5, 95.5, 208, -0.5, 207.5, true); + mAverageCellEnergy->GetXaxis()->SetTitle("col"); + mAverageCellEnergy->GetYaxis()->SetTitle("row"); + mAverageCellEnergy->SetStats(false); + + mAverageCellTime = histBuilder2D("averageCellTime", "Average cell time", 96, -0.5, 95.5, 208, -0.5, 207.5, true); + mAverageCellTime->GetXaxis()->SetTitle("col"); + mAverageCellTime->GetYaxis()->SetTitle("row"); + mAverageCellTime->SetStats(false); + + mAverageCellEnergyConstrained = histBuilder2D("averageCellEnergyConstrained", Form("Average cell energy (E > %.1f GeV/c)", settings.mThresholdAvEnergy), 96, -0.5, 95.5, 208, -0.5, 207.5, true); + mAverageCellEnergyConstrained->GetXaxis()->SetTitle("col"); + mAverageCellEnergyConstrained->GetYaxis()->SetTitle("row"); + mAverageCellEnergyConstrained->SetStats(false); + + mAverageCellTimeConstrained = histBuilder2D("averageCellTimeConstrained", Form("Average cell time (|t| < %.1f ns)", settings.mThresholdAvTime), 96, -0.5, 95.5, 208, -0.5, 207.5, true); + mAverageCellTimeConstrained->GetXaxis()->SetTitle("col"); + mAverageCellTimeConstrained->GetYaxis()->SetTitle("row"); + mAverageCellTimeConstrained->SetStats(false); + + mIntegratedOccupancy = histBuilder2D("cellOccupancyInt", "Cell Occupancy Integrated", 96, -0.5, 95.5, 208, -0.5, 207.5, true); + mIntegratedOccupancy->GetXaxis()->SetTitle("col"); + mIntegratedOccupancy->GetYaxis()->SetTitle("row"); + mIntegratedOccupancy->SetStats(false); + // 1D histograms for showing the integrated spectrum + + mCellTimeSupermodule_tot = histBuilder1D("cellTime", "Cell Time EMCAL,DCAL", 600, -400, 800); + mCellTimeSupermoduleEMCAL = histBuilder1D("cellTimeEMCAL", "Cell Time EMCAL", 600, -400, 800); + mCellTimeSupermoduleDCAL = histBuilder1D("cellTimeDCAL", "Cell Time DCAL", 600, -400, 800); + + mCellTimeSupermoduleEMCAL_Gain[0] = histBuilder1D("cellTimeEMCAL_highGain", "Cell Time EMCAL highGain", 600, -400, 800); + mCellTimeSupermoduleEMCAL_Gain[1] = histBuilder1D("cellTimeEMCAL_lowGain", "Cell Time EMCAL lowGain", 600, -400, 800); + mCellTimeSupermoduleDCAL_Gain[0] = histBuilder1D("cellTimeDCAL_highGain", "Cell Time DCAL highGain", 600, -400, 800); + mCellTimeSupermoduleDCAL_Gain[1] = histBuilder1D("cellTimeDCAL_lowGain", "Cell Time DCAL lowGain", 600, -400, 800); + mCellAmplitude_tot = histBuilder1D("cellAmplitude", "Cell amplitude in EMCAL,DCAL", 4 * static_cast(maxAmp), 0., maxAmp); + mCellAmplitudeEMCAL = histBuilder1D("cellAmplitudeEMCAL", "Cell amplitude in EMCAL", 4 * static_cast(maxAmp), 0., maxAmp); + mCellAmplitudeDCAL = histBuilder1D("cellAmplitudeDCAL", "Cell amplitude in DCAL", 4 * static_cast(maxAmp), 0., maxAmp); + + mCellAmplitudeTime = histBuilder2D("cellAmplitudeTime", "Cell amplitude vs. time; E (GeV); t (ns)", 500, 0., 50., 800, -400., 400., false); + + mnumberEvents = histBuilder1D("NumberOfEvents", "Number Of Events", 1, 0.5, 1.5); + // + std::fill(mCellTimeBC.begin(), mCellTimeBC.end(), nullptr); + if (isPhysTrigger) { + // Phys. trigger: monitor all bunch crossings + for (auto bcID = 0; bcID < 4; bcID++) { + mCellTimeBC[bcID] = histBuilder1D(Form("cellTimeBC%d", bcID), Form("Cell Time BC%d", bcID), 600, -400, 800); + } + } else { + // Calib trigger: Only bc0; + mCellTimeBC[0] = histBuilder1D("cellTimeBC0", "Cell Time BC0", 600, -400, 800); + } +} + +void CellTask::CellHistograms::fillHistograms(const o2::emcal::Cell& cell, bool goodCell, double timecalib, double energycalib, int bcphase) +{ + auto fillOptional1D = [](TH1* hist, double x, double weight = 1.) { + if (hist) { + hist->Fill(x, weight); + } + }; + auto fillOptional2D = [](TH2* hist, double x, double y, double weight = 1.) { + if (hist) { + hist->Fill(x, y, weight); + } + }; + + double energy = cell.getEnergy() * energycalib; + + fillOptional2D(mCellAmplitude, energy, cell.getTower()); + // fillOptional2D(mCellAmplitude[index], cell.getEnergy(), cell.getTower()); + + if (goodCell) { + fillOptional2D(mCellAmplitudeCalib, energy, cell.getTower()); + fillOptional2D(mCellTimeCalib, cell.getTimeStamp() - timecalib, cell.getTower()); + // fillOptional2D(mCellAmplitudeCalib[index], cell.getEnergy(), cell.getTower()); + // fillOptional2D(mCellTimeCalib[index], cell.getTimeStamp() - timeoffset, cell.getTower()); + } + fillOptional2D(mCellTime, cell.getTimeStamp(), cell.getTower()); + // fillOptional2D(mCellTime[index], cell.getTimeStamp(), cell.getTower()); + + try { + auto [row, col] = mGeometry->GlobalRowColFromIndex(cell.getTower()); + if (cell.getEnergy() > 0) { + fillOptional2D(mCellOccupancy, col, row); + } + if (cell.getEnergy() > mCellThreshold) { + fillOptional2D(mCellOccupancyThr, col, row); + } else { + fillOptional2D(mCellOccupancyThrBelow, col, row); + } + if (goodCell) { + fillOptional2D(mCellOccupancyGood, col, row); + fillOptional2D(mAverageCellEnergy, col, row, energy); + if (energy > mThresholdAvEnergy) { + fillOptional2D(mAverageCellEnergyConstrained, col, row, energy); + }; + fillOptional2D(mAverageCellTime, col, row, cell.getTimeStamp() - timecalib); + if (std::abs(cell.getTimeStamp()) < mThresholdAvTime) { + fillOptional2D(mAverageCellTimeConstrained, col, row, cell.getTimeStamp() - timecalib); + } + } else { + fillOptional2D(mCellOccupancyBad, col, row); + } + + fillOptional2D(mIntegratedOccupancy, col, row, energy); + + } catch (o2::emcal::InvalidCellIDException& e) { + ILOG(Error, Support) << "Invalid cell ID: " << e.getCellID() << ENDM; + }; + + try { + auto cellindices = mGeometry->GetCellIndex(cell.getTower()); + auto supermoduleID = std::get<0>(cellindices); + fillOptional2D(mCellAmpSupermodule, energy, supermoduleID); + fillOptional2D(mCellAmplitudeTime, energy, cell.getTimeStamp()); + if (cell.getEnergy() > mAmplitudeThresholdTime) { + fillOptional2D(mCellTimeSupermodule, cell.getTimeStamp(), supermoduleID); + } + if (goodCell) { + fillOptional2D(mCellAmpSupermoduleCalib, energy, supermoduleID); + if (energy > mAmplitudeThresholdTime) { + fillOptional2D(mCellTimeSupermoduleCalib, cell.getTimeStamp() - timecalib, supermoduleID); + fillOptional2D(mCellAmplitudeTimeCalib, energy, cell.getTimeStamp() - timecalib); + } + } else { + fillOptional2D(mCellAmpSupermoduleBad, energy, supermoduleID); + } + fillOptional1D(mCellAmplitude_tot, energy); + if (goodCell) { + fillOptional1D(mCellAmplitudeCalib_tot, energy); // EMCAL+DCAL Calib, shifter + } + if (energy > mAmplitudeThresholdTime) { + fillOptional1D(mCellTimeSupermodule_tot, cell.getTimeStamp()); // EMCAL+DCAL shifter + if (goodCell) { + fillOptional1D(mCellTimeSupermoduleCalib_tot, cell.getTimeStamp() - timecalib); // EMCAL+DCAL Calib shifter + } + } + // check Gain + int index = cell.getHighGain() ? 0 : 1; //(0=highGain, 1 = lowGain) + if (supermoduleID < 12) { // EMCAL + if (energy > mAmplitudeThresholdTime) { + fillOptional1D(mCellTimeSupermoduleEMCAL, cell.getTimeStamp()); + if (goodCell) { + fillOptional1D(mCellTimeSupermoduleCalib_EMCAL, cell.getTimeStamp() - timecalib); + } + fillOptional1D(mCellTimeSupermoduleEMCAL_Gain[index], cell.getTimeStamp()); + } + fillOptional1D(mCellAmplitudeEMCAL, energy); + if (goodCell) { + fillOptional1D(mCellAmplitudeCalib_EMCAL, energy); + } + } else { + fillOptional1D(mCellAmplitudeDCAL, energy); + if (goodCell) { + fillOptional1D(mCellAmplitudeCalib_DCAL, energy); + } + if (energy > mAmplitudeThresholdTime) { + fillOptional1D(mCellTimeSupermoduleDCAL, cell.getTimeStamp()); + if (goodCell) { + fillOptional1D(mCellTimeSupermoduleCalib_DCAL, cell.getTimeStamp() - timecalib); + } + fillOptional1D(mCellTimeSupermoduleDCAL_Gain[index], cell.getTimeStamp()); + } + } + // bc phase histograms + if (energy > mAmplitudeThresholdTime) { + auto bchistos = mCellTimeBC[bcphase]; + auto histcontainer = bchistos; + histcontainer->Fill(cell.getTimeStamp()); + } + } catch (o2::emcal::InvalidCellIDException& e) { + ILOG(Info, Support) << "Invalid cell ID: " << e.getCellID() << ENDM; + } +} + +void CellTask::CellHistograms::countEvent() +{ + mnumberEvents->Fill(1); +} + +void CellTask::CellHistograms::startPublishing(o2::quality_control::core::ObjectsManager& manager) +{ + auto publishOptional = [&manager](TH1* hist) { + if (hist) { + manager.startPublishing(hist); + } + }; + + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mCellOccupancy); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mCellOccupancyThr); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mCellOccupancyThrBelow); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mCellOccupancyGood); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mCellOccupancyBad); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mIntegratedOccupancy); + + publishOptional(mCellTime); + publishOptional(mCellTimeCalib); + publishOptional(mCellAmplitude); + publishOptional(mCellAmplitudeCalib); + publishOptional(mCellAmpSupermodule); + publishOptional(mCellAmpSupermoduleCalib); + publishOptional(mCellAmpSupermoduleBad); + publishOptional(mCellTimeSupermodule); + publishOptional(mCellTimeSupermodule_tot); + publishOptional(mCellTimeSupermoduleEMCAL); + publishOptional(mCellTimeSupermoduleDCAL); + publishOptional(mCellTimeSupermoduleCalib_tot); + publishOptional(mCellTimeSupermoduleCalib_EMCAL); + publishOptional(mCellTimeSupermoduleCalib_DCAL); + publishOptional(mCellTimeSupermoduleCalib); + publishOptional(mCellAmplitudeTime); + publishOptional(mCellAmplitudeTimeCalib); + publishOptional(mCellAmplitude_tot); + publishOptional(mCellAmplitudeEMCAL); + publishOptional(mCellAmplitudeDCAL); + publishOptional(mCellAmplitudeCalib_tot); + publishOptional(mCellAmplitudeCalib_EMCAL); + publishOptional(mCellAmplitudeCalib_DCAL); + publishOptional(mCellOccupancy); + publishOptional(mCellOccupancyThr); + publishOptional(mCellOccupancyThrBelow); + publishOptional(mCellOccupancyGood); + publishOptional(mCellOccupancyBad); + publishOptional(mIntegratedOccupancy); + publishOptional(mAverageCellEnergy); + publishOptional(mAverageCellTime); + publishOptional(mAverageCellEnergyConstrained); + publishOptional(mAverageCellTimeConstrained); + publishOptional(mnumberEvents); + + for (auto histos : mCellTimeSupermoduleEMCAL_Gain) { + publishOptional(histos); + } + for (auto histos : mCellTimeSupermoduleDCAL_Gain) { + publishOptional(histos); + } + + for (auto histos : mCellTimeBC) { + publishOptional(histos); + } +} + +void CellTask::CellHistograms::reset() +{ + auto resetOptional = [](TH1* hist) { + if (hist) { + hist->Reset(); + } + }; + + resetOptional(mCellTime); + resetOptional(mCellTimeCalib); + resetOptional(mCellAmplitude); + resetOptional(mCellAmplitudeCalib); + resetOptional(mCellAmpSupermodule); + resetOptional(mCellAmpSupermoduleCalib); + resetOptional(mCellAmpSupermoduleBad); + resetOptional(mCellTimeSupermodule); + resetOptional(mCellTimeSupermodule_tot); + resetOptional(mCellTimeSupermoduleEMCAL); + resetOptional(mCellTimeSupermoduleDCAL); + resetOptional(mCellTimeSupermoduleCalib_tot); + resetOptional(mCellTimeSupermoduleCalib_EMCAL); + resetOptional(mCellTimeSupermoduleCalib_DCAL); + resetOptional(mCellTimeSupermoduleCalib); + resetOptional(mCellAmplitude_tot); + resetOptional(mCellAmplitudeEMCAL); + resetOptional(mCellAmplitudeDCAL); + resetOptional(mCellAmplitudeCalib_tot); + resetOptional(mCellAmplitudeCalib_EMCAL); + resetOptional(mCellAmplitudeCalib_DCAL); + resetOptional(mCellAmplitudeTime); + resetOptional(mCellAmplitudeTimeCalib); + resetOptional(mCellOccupancy); + resetOptional(mCellOccupancyThr); + resetOptional(mCellOccupancyThrBelow); + resetOptional(mCellOccupancyGood); + resetOptional(mCellOccupancyBad); + resetOptional(mIntegratedOccupancy); + resetOptional(mAverageCellEnergy); + resetOptional(mAverageCellTime); + resetOptional(mAverageCellEnergyConstrained); + resetOptional(mAverageCellTimeConstrained); + resetOptional(mnumberEvents); + + for (auto histos : mCellTimeSupermoduleEMCAL_Gain) { + resetOptional(histos); + } + for (auto histos : mCellTimeSupermoduleDCAL_Gain) { + resetOptional(histos); + } + + for (auto histos : mCellTimeBC) { + resetOptional(histos); + } + + // Draw Grid Lines + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mCellOccupancy); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mCellOccupancyThr); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mCellOccupancyThrBelow); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mCellOccupancyGood); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mCellOccupancyBad); + o2::quality_control_modules::emcal::DrawGridlines::DrawSMGridInStdGeo(mIntegratedOccupancy); +} + +void CellTask::CellHistograms::clean() +{ + delete mCellTime; + delete mCellTimeCalib; + delete mCellAmplitude; + delete mCellAmplitudeCalib; + delete mCellAmpSupermodule; + delete mCellAmpSupermoduleCalib; + delete mCellAmpSupermoduleBad; + delete mCellTimeSupermodule; + delete mCellTimeSupermodule_tot; + delete mCellTimeSupermoduleEMCAL; + delete mCellTimeSupermoduleDCAL; + delete mCellTimeSupermoduleCalib_tot; + delete mCellTimeSupermoduleCalib_EMCAL; + delete mCellTimeSupermoduleCalib_DCAL; + delete mCellTimeSupermoduleCalib; + delete mCellAmplitude_tot; + delete mCellAmplitudeEMCAL; + delete mCellAmplitudeDCAL; + delete mCellAmplitudeCalib_tot; + delete mCellAmplitudeCalib_EMCAL; + delete mCellAmplitudeCalib_DCAL; + delete mCellAmplitudeTime; + delete mCellAmplitudeTimeCalib; + delete mCellOccupancy; + delete mCellOccupancyThr; + delete mCellOccupancyThrBelow; + delete mCellOccupancyGood; + delete mCellOccupancyBad; + delete mIntegratedOccupancy; + delete mAverageCellEnergy; + delete mAverageCellTime; + delete mAverageCellEnergyConstrained; + delete mAverageCellTimeConstrained; + delete mnumberEvents; + + mCellTime = nullptr; + mCellTimeCalib = nullptr; + mCellAmplitude = nullptr; + mCellAmplitudeCalib = nullptr; + mCellAmpSupermodule = nullptr; + mCellAmpSupermoduleCalib = nullptr; + mCellAmpSupermoduleBad = nullptr; + mCellTimeSupermodule = nullptr; + mCellTimeSupermodule_tot = nullptr; + mCellTimeSupermoduleEMCAL = nullptr; + mCellTimeSupermoduleDCAL = nullptr; + mCellTimeSupermoduleCalib_tot = nullptr; + mCellTimeSupermoduleCalib_EMCAL = nullptr; + mCellTimeSupermoduleCalib_DCAL = nullptr; + mCellTimeSupermoduleCalib = nullptr; + mCellAmplitude_tot = nullptr; + mCellAmplitudeEMCAL = nullptr; + mCellAmplitudeDCAL = nullptr; + mCellAmplitudeCalib_tot = nullptr; + mCellAmplitudeCalib_EMCAL = nullptr; + mCellAmplitudeCalib_DCAL = nullptr; + mCellAmplitudeTime = nullptr; + mCellAmplitudeTimeCalib = nullptr; + mCellOccupancy = nullptr; + mCellOccupancyThr = nullptr; + mCellOccupancyThrBelow = nullptr; + mCellOccupancyGood = nullptr; + mCellOccupancyBad = nullptr; + mIntegratedOccupancy = nullptr; + mAverageCellEnergy = nullptr; + mAverageCellTime = nullptr; + mAverageCellEnergyConstrained = nullptr; + mAverageCellTimeConstrained = nullptr; + mnumberEvents = nullptr; + + for (auto*& histos : mCellTimeSupermoduleEMCAL_Gain) { + delete histos; + histos = nullptr; + } + for (auto*& histos : mCellTimeSupermoduleDCAL_Gain) { + delete histos; + histos = nullptr; + } + for (auto*& histos : mCellTimeBC) { + delete histos; + histos = nullptr; + } +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/CellTimeCalibCheck.cxx b/Modules/EMCAL/src/CellTimeCalibCheck.cxx new file mode 100644 index 0000000000..a2d5041cc1 --- /dev/null +++ b/Modules/EMCAL/src/CellTimeCalibCheck.cxx @@ -0,0 +1,111 @@ +#include "QualityControl/MonitorObject.h" +#include "EMCAL/CellTimeCalibCheck.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Quality.h" + +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::emcal +{ +void CellTimeCalibCheck::configure() +{ + // configure threshold-based checkers + auto nThreshTSpectrum = mCustomParameters.find("ThreshTSpectrum"); + if (nThreshTSpectrum != mCustomParameters.end()) { + try { + mThreshTSpectrum = std::stod(nThreshTSpectrum->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nThreshTSpectrum->second << " not a double" << ENDM; + } + } + + auto nSigmaTSpectrum = mCustomParameters.find("SigmaTSpectrum"); + if (nSigmaTSpectrum != mCustomParameters.end()) { + try { + mSigmaTSpectrum = std::stod(nSigmaTSpectrum->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nSigmaTSpectrum->second << " not a double" << ENDM; + } + } +} + +Quality CellTimeCalibCheck::check(std::map>* moMap) +{ + + Quality result = Quality::Good; + for (auto& [moName, mo] : *moMap) { + if (mo->getName() == "cellTimeCalib_PHYS") { + + auto* h = dynamic_cast(mo->getObject()); + h->GetXaxis()->SetRangeUser(-200, 200); // limit range to find peaks + + TSpectrum peakfinder; + double threshold; + Int_t nfound = peakfinder.Search(h, mSigmaTSpectrum, "nobackground", mThreshTSpectrum); // Search for peaks + Double_t* xpeaks = peakfinder.GetPositionX(); + Double_t* ypeaks = peakfinder.GetPositionY(); + std::sort(ypeaks, ypeaks + nfound, std::greater()); // sort peaks in descending order to easy pick the y value of max peak + double max_peak = ypeaks[0]; + threshold = 0.4 * max_peak; + + for (Int_t n_peak = 0; n_peak < nfound; n_peak++) { + Int_t bin = h->GetXaxis()->FindBin(xpeaks[n_peak]); + Double_t yp = h->GetBinContent(bin); + + if (yp >= threshold && yp != max_peak) { + result = Quality::Bad; + } + } + h->GetXaxis()->SetRangeUser(-400, 800); // set range back to full range + } + } + return result; +} + +void CellTimeCalibCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "cellTimeCalib_PHYS") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "Could not cast 'cellTimeCalib_PHYS' to TH1*, skipping" << ENDM; + return; + } + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("Data: OK!!!"); + msg->SetFillColor(kGreen); + // + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red" << ENDM; + msg->Clear(); + msg->AddText("Secondary peak amplitude is high"); + msg->AddText("If NOT a technical run,"); + msg->AddText("call EMCAL on-call."); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange" << ENDM; + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/ClusterTask.cxx b/Modules/EMCAL/src/ClusterTask.cxx new file mode 100644 index 0000000000..cd4e80543b --- /dev/null +++ b/Modules/EMCAL/src/ClusterTask.cxx @@ -0,0 +1,1091 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "EMCAL/ClusterTask.h" + +namespace o2::quality_control_modules::emcal +{ + +ClusterTask::~ClusterTask() +{ + auto conditionalDelete = [](auto obj) { + if (obj) { + delete obj; + } + }; + + conditionalDelete(mHistCellEnergyTimeUsed); + conditionalDelete(mHistCellEnergyTimePhys); + conditionalDelete(mHistCellEnergyTimeCalib); + + conditionalDelete(mHistNclustPerTF); + conditionalDelete(mHistNclustPerEvt); + conditionalDelete(mHistClustEtaPhi); + conditionalDelete(mHistClustEtaPhiMaxCluster); + conditionalDelete(mHistNclustPerTFSelected); + conditionalDelete(mHistNclustPerEvtSelected); + conditionalDelete(mHistNclustSupermodule); + conditionalDelete(mHistNClustPerEventSupermodule); + conditionalDelete(mHistSupermoduleIDMaxCluster); + + for (int idet = 0; idet < NUM_DETS; idet++) { + conditionalDelete(mHistTime[idet]); + conditionalDelete(mHistClustE[idet]); + conditionalDelete(mHistNCells[idet]); + conditionalDelete(mHistM02[idet]); + conditionalDelete(mHistM20[idet]); + conditionalDelete(mHistM02VsClustE[idet]); + conditionalDelete(mHistM20VsClustE[idet]); + conditionalDelete(mHistClustEMaxCluster[idet]); + conditionalDelete(mHistClustTimeMaxCluster[idet]); + } + + conditionalDelete(mHistClusterTimeSupermodule); + conditionalDelete(mHistClusterEnergySupermodule); + conditionalDelete(mHistClusterNCellSupermodule); + conditionalDelete(mHistMaxClusterEnergySupermodule); + conditionalDelete(mHistMaxClusterTimeSupermodule); + + conditionalDelete(mHistMassDiphoton_EMCAL); + conditionalDelete(mHistMassDiphoton_DCAL); + conditionalDelete(mHistMassDiphotonPt_EMCAL); + conditionalDelete(mHistMassDiphotonPt_DCAL); + + conditionalDelete(mHistNClustPerEvt_Calib); + conditionalDelete(mHistNClustPerEvtSelected_Calib); + conditionalDelete(mHistClusterEtaPhi_Calib); + conditionalDelete(mHistClusterEnergy_Calib); + conditionalDelete(mHistClusterEnergyTime_Calib); + conditionalDelete(mHistClusterEnergyCells_Calib); +} + +void ClusterTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + QcInfoLogger::setDetector("EMC"); // svk + ILOG(Debug, Devel) << "initialize ClusterTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + auto get_bool = [](const std::string_view input) -> bool { // svk + return input == "true"; + }; + + if (hasConfigValue("debuggerDelay")) { + if (get_bool("debuggerDelay")) { + // set delay in order to allow for attaching the debugger + sleep(20); + } + } + + configureBindings(); + configureTaskParameters(); + + if (mTaskParameters.mInternalClusterizer) { + /// Configure clusterizer settings + configureClusterizerSettings(); + } + + if (mTaskParameters.mFillInvMassMeson) { + configureMesonSelection(); + } + + mEventHandler = std::make_unique>(); + mClusterFactory = std::make_unique>(); + + // initialize geometry + if (!mGeometry) + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); // svk + mClusterFactory->setGeometry(mGeometry); + + ////////////////////////////////////////////////////////////// + // Counter histograms // + ////////////////////////////////////////////////////////////// + mHistNclustPerTF = new TH1F("NclustPerTF", "Number of clusters per time frame; N_{Cluster}/TF; Yield", 2000, 0.0, 200000.0); + getObjectsManager()->startPublishing(mHistNclustPerTF); + + mHistNclustPerEvt = new TH1F("NclustPerEvt", "Number of clusters per event; N_{Cluster}/Event; Yield", mTaskParameters.mMultiplicityRange, 0.0, mTaskParameters.mMultiplicityRange); + getObjectsManager()->startPublishing(mHistNclustPerEvt); + + mHistClustEtaPhi = new TH2F("ClustEtaPhi", "Cluster #eta and #phi distribution; #eta; #phi", 100, -1.0, 1.0, 100, 0.0, 2 * TMath::Pi()); + getObjectsManager()->startPublishing(mHistClustEtaPhi); + + mHistClustEtaPhiMaxCluster = new TH2F("ClustEtaPhiMaxCluster", "Cluster #eta and #phi of the leading cluster; #eta; #phi", 100, -1.0, 1.0, 100, 0.0, 2 * TMath::Pi()); + getObjectsManager()->startPublishing(mHistClustEtaPhiMaxCluster); + + mHistNclustPerTFSelected = new TH1F("NclustPerTFSel", "Number of selected clusters per time frame; N_{Cluster}/TF; yield", 2000, 0.0, 200000.0); + getObjectsManager()->startPublishing(mHistNclustPerTFSelected); + + mHistNclustPerEvtSelected = new TH1F("NclustPerEvtSel", "Number of clusters per event; N_{Cluster}/Event; yield", mTaskParameters.mMultiplicityRange, 0.0, mTaskParameters.mMultiplicityRange); + getObjectsManager()->startPublishing(mHistNclustPerEvtSelected); + + mHistNclustSupermodule = new TH1D("NClusterPerSupermodule", "Number of clusters per supermodule; Supermodule ID; Number of clusters", 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHistNclustSupermodule); + + mHistNClustPerEventSupermodule = new TH2D("NClustersPerEventSupermodule", "Number of clusters per event per supermodule; Number of cluster / event; Supermodule ID", mTaskParameters.mMultiplicityRange, 0., mTaskParameters.mMultiplicityRange, 20., -0.5, 19.5); + getObjectsManager()->startPublishing(mHistNClustPerEventSupermodule); + + ////////////////////////////////////////////////////////////// + // Control histograms (optional) // + ////////////////////////////////////////////////////////////// + if (mTaskParameters.mFillControlHistograms) { + mHistCellEnergyTimeUsed = new TH2D("CellEnergyTimeUsedAll", "Cell energy vs time (all cells for clustering); E_{cell} (GeV); t_{cell} (ns)", 500, 0, 50, 1800, -900, 900); + getObjectsManager()->startPublishing(mHistCellEnergyTimeUsed); + mHistCellEnergyTimePhys = new TH2D("CellEnergyTimeUsedPhys", "Cell energy vs time (all cells for clustering, phys events); E_{cell} (GeV); t_{cell} (ns)", 500, 0, 50, 1800, -900, 900); + getObjectsManager()->startPublishing(mHistCellEnergyTimePhys); + mHistCellEnergyTimeCalib = new TH2D("CellEnergyTimeUsedCalib", "Cell energy vs time (all cells for clustering, calib events); E_{cell} (GeV); t_{cell} (ns)", 500, 0, 50, 1800, -900, 900); + getObjectsManager()->startPublishing(mHistCellEnergyTimeCalib); + } + + ////////////////////////////////////////////////////////////// + // Cluster distribution histograms (All/EMCAL/DCAL) // + ////////////////////////////////////////////////////////////// + for (auto idet = 0; idet < NUM_DETS; idet++) { + std::string detname, dettitle; + switch (idet) { + case DetType_t::ALL_DET: + detname = "All"; + dettitle = "EMCAL+DCAL"; + break; + case DetType_t::EMCAL_DET: + detname = "EMCal"; + dettitle = "EMCAL"; + break; + case DetType_t::DCAL_DET: + detname = "DCal"; + dettitle = "DCAL"; + break; + default: + break; + } + mHistTime[idet] = new TH2F(Form("Time_%s", detname.data()), Form("Cluster time (%s); E_{cl} (GeV); t(ns)", dettitle.data()), 500, 0, 50, 1800, -900, 900); + getObjectsManager()->startPublishing(mHistTime[idet]); + + mHistClustE[idet] = new TH1F(Form("ClustE_%s", detname.data()), Form("Cluster energy distribution (%s); E_{cl} (GeV); Yield", dettitle.data()), 500, 0.0, 50.0); + getObjectsManager()->startPublishing(mHistClustE[idet]); + + mHistNCells[idet] = new TH2F(Form("Cells_%s", detname.data()), Form("No of cells in a cluster (%s); E_{cl} (GeV); N^{EMC}_{cells}", dettitle.data()), 500, 0, 50, 30, 0, 30); + getObjectsManager()->startPublishing(mHistNCells[idet]); + + mHistM02[idet] = new TH1F(Form("M02_%s", detname.data()), Form("M02 distribution (%s); M02; Yield", dettitle.data()), 200, 0.0, 2.0); + getObjectsManager()->startPublishing(mHistM02[idet]); + + mHistM20[idet] = new TH1F(Form("M20_%s", detname.data()), Form("M20 distribution (%s); M20; Yield", dettitle.data()), 200, 0.0, 2.0); + getObjectsManager()->startPublishing(mHistM20[idet]); + + mHistM02VsClustE[idet] = new TH2F(Form("M02_Vs_ClustE_%s", detname.data()), Form("M02 vs cluster energy (%s); E_{cl} (GeV); M02", dettitle.data()), 500, 0, 50.0, 200, 0.0, 2.0); + getObjectsManager()->startPublishing(mHistM02VsClustE[idet]); + + mHistM20VsClustE[idet] = new TH2F(Form("M20_Vs_ClustE_%s", detname.data()), Form("M20 vs cluster energy (%s); E_{cl} (GeV); M02", dettitle.data()), 500, 0, 50.0, 200, 0.0, 2.0); + getObjectsManager()->startPublishing(mHistM20VsClustE[idet]); + + mHistClustEMaxCluster[idet] = new TH1D(Form("ClusterELeading_%s", detname.data()), Form("Energy of the leading cluster (%s); E_{cl,max}; Yield", dettitle.data()), 500, 0., 50.); + getObjectsManager()->startPublishing(mHistClustEMaxCluster[idet]); + + mHistClustTimeMaxCluster[idet] = new TH1D(Form("ClusterTimeLeading_%s", detname.data()), Form("Time of the leading cluster (%s); t_{cl,max} (ns); Yield", dettitle.data()), 1800, -900., 900.); + getObjectsManager()->startPublishing(mHistClustTimeMaxCluster[idet]); + } + + ////////////////////////////////////////////////////////////// + // Supermodule dependent histograms // + ////////////////////////////////////////////////////////////// + mHistClusterTimeSupermodule = new TH2D("ClusterTimeSupermodule", "Cluster time vs. Supermodule ID; t_{cl} (ns); Supermodule ID", 600, -400, 800, 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHistClusterTimeSupermodule); + mHistClusterEnergySupermodule = new TH2D("ClusterEnergySupermodule", "Cluster energy vs. supermodule ID; E_{cl} (GeV); Supermodule ID", 500, 0.0, 50.0, 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHistClusterEnergySupermodule); + mHistClusterNCellSupermodule = new TH2D("ClusterNCellsSupermodule", "Number of cells vs. supermodule ID; N_{cell,clust}; Supermodule ID", 30, 0, 30, 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHistClusterNCellSupermodule); + mHistMaxClusterEnergySupermodule = new TH2D("LeadingClusterEnergySupermodule", "Cluster energy of the leading cluster vs. supermodule ID; E_{cl} (GeV); Supermodule ID", 500, 0.0, 50.0, 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHistMaxClusterEnergySupermodule); + mHistMaxClusterTimeSupermodule = new TH2D("LeadingClusterTimeSupermodule", "Cluster time of the leading cluster vs. supermodule ID; t_{cl} (ns); Supermodule ID", 600, -400, 800, 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHistMaxClusterTimeSupermodule); + mHistSupermoduleIDMaxCluster = new TH1D("SupermoduleIDMaxCluster", "Supermodule ID of the leading cluster; Supermodule ID; Number of events", 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mHistSupermoduleIDMaxCluster); + + ////////////////////////////////////////////////////////////// + // Calib trigger histograms // + ////////////////////////////////////////////////////////////// + + mHistNClustPerEvt_Calib = new TH1F("NclustPerEvt_Calib", "Number of clusters per calib event; N_{Cluster}/Event; Yield", 4000, 0.0, 4000.0); + getObjectsManager()->startPublishing(mHistNClustPerEvt_Calib); + + mHistNClustPerEvtSelected_Calib = new TH1F("NclustPerEvtSel_Calib", "Number of selected clusters per calib event; N_{clusters}/Event; Yield", 4000, 0.0, 4000.0); + getObjectsManager()->startPublishing(mHistNClustPerEvtSelected_Calib); + + mHistClusterEtaPhi_Calib = new TH2D("ClustEtaPhi_LED", "Cluster #eta-#phi position in LED events; #eta; #phi", 100, -1.0, 1.0, 100, 0.0, 2 * TMath::Pi()); + getObjectsManager()->startPublishing(mHistClusterEtaPhi_Calib); + + mHistClusterEnergy_Calib = new TH1D("ClusterEnergy_LED", "Cluster energy in LED events; E_{cl} (GeV); dN/dE_{cl} (GeV^{-1})", 200, 0., 20.); + getObjectsManager()->startPublishing(mHistClusterEnergy_Calib); + + mHistClusterEnergyTime_Calib = new TH2D("ClusterEnergyTime_LED", "Cluster energy vs. time in LED events; E_{cl} (GeV); t (ns)", 500, 0, 50, 1800, -900, 900); + getObjectsManager()->startPublishing(mHistClusterEnergyTime_Calib); + + mHistClusterEnergyCells_Calib = new TH2D("CusterEnergyNcell_LED", "Cluster energy vs. ncell in LED events; E_{cl} (GeV); n_{cell}", 500, 0, 50, 100, 0, 100); + getObjectsManager()->startPublishing(mHistClusterEnergyCells_Calib); + + ////////////////////////////////////////////////////////////// + // Meson histograms // + ////////////////////////////////////////////////////////////// + + if (mTaskParameters.mFillInvMassMeson) { + mHistMassDiphoton_EMCAL = new TH1D("InvMassDiphoton_EMCAL", "Diphoton invariant mass for pairs in EMCAL; m_{#gamma#gamma} (GeV/c^{2}); Number of candidates", 400, 0., 0.8); + getObjectsManager()->startPublishing(mHistMassDiphoton_EMCAL); + + mHistMassDiphoton_DCAL = new TH1F("InvMassDiphoton_DCAL", "Diphoton invariant mass for pairs in DCAL; m_{#gamma#gamma} (GeV/c^{2}); Number of candidates", 400, 0., 0.8); + getObjectsManager()->startPublishing(mHistMassDiphoton_DCAL); + + mHistMassDiphotonPt_EMCAL = new TH2D("InvMassDiphotonPt_EMCAL", "Diphoton invariant mass vs p_{t} for paris in EMCAL; m_{#gamma#gamma} (GeV/c^{2}); p_{t,#gamma#gamma} (GeV/c)", 400, 0., 0.8, 200, 0., 20.); + getObjectsManager()->startPublishing(mHistMassDiphotonPt_EMCAL); + + mHistMassDiphotonPt_DCAL = new TH2D("InvMassDiphotonPt_DCAL", "Diphoton invariant mass vs p_{t} for paris in DCAL; m_{#gamma#gamma} (GeV/c^{2}); p_{t,#gamma#gamma} (GeV/c)", 400, 0., 0.8, 200, 0., 20.); + getObjectsManager()->startPublishing(mHistMassDiphotonPt_DCAL); + } +} + +void ClusterTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + resetHistograms(); +} + +void ClusterTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ClusterTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + if (!o2::base::GeometryManager::isGeometryLoaded()) { + static bool displayGeometryError = true; + if (displayGeometryError) { + ILOG(Error, Support) << "Cluster QC needs access to the Geometry - please initialize the GRPGeomHelper in your task configuration" << ENDM; + displayGeometryError = false; + } + return; + } + auto cell = ctx.inputs().get>(mTaskInputBindings.mCellBinding.data()); + auto cellTR = ctx.inputs().get>(mTaskInputBindings.mCellTriggerRecordBinding.data()); + + if (mTaskParameters.mInternalClusterizer) { + std::vector cluster; + std::vector cellIndex; + std::vector clusterTR, cellIndexTR; + + // prepare optional recalibration before clusterization + std::vector calibratedCells; + std::vector calibratedTriggerRecords; + gsl::span inputcells; + gsl::span inputTriggerRecords; + if (mTaskParameters.mCalibrate) { + // build recalibrated cell collection; + ILOG(Debug, Support) << "Calibrate cells" << ENDM; + loadCalibrationObjects(ctx); + getCalibratedCells(cell, cellTR, calibratedCells, calibratedTriggerRecords); + inputcells = gsl::span(calibratedCells); + inputTriggerRecords = gsl::span(calibratedTriggerRecords); + } else { + // No calibration performed, forward external cell collection + ILOG(Debug, Support) << "No calibration performed - forward containers" << ENDM; + inputcells = cell; + inputTriggerRecords = cellTR; + } + + ILOG(Debug, Support) << "Received " << cell.size() << " cells from " << cellTR.size() << " triggers, " << inputcells.size() << " cells from " << inputTriggerRecords.size() << " triggers to clusterizer" << ENDM; + + findClustersInternal(inputcells, inputTriggerRecords, cluster, clusterTR, cellIndex, cellIndexTR); // do internal clustering here + ILOG(Debug, Support) << "Found " << cluster.size() << " CLusters " << ENDM; + ILOG(Debug, Support) << "Found " << clusterTR.size() << " Cluster Trigger Records " << ENDM; + ILOG(Debug, Support) << "Found " << cellIndex.size() << " Cell Index " << ENDM; + ILOG(Debug, Support) << "Found " << cellIndexTR.size() << " Cell Index Records " << ENDM; + + analyseTimeframe(inputcells, inputTriggerRecords, cluster, clusterTR, cellIndex, cellIndexTR); // Fill histos + + } else { + auto cluster = ctx.inputs().get>(mTaskInputBindings.mClusterBinding.data()); + auto clusterTR = ctx.inputs().get>(mTaskInputBindings.mClusterTriggerRecordBinding.data()); + auto cellIndex = ctx.inputs().get>(mTaskInputBindings.mCellIndexBinding.data()); + auto cellIndexTR = ctx.inputs().get>(mTaskInputBindings.mCellIndexTriggerRecordBinding.data()); + + analyseTimeframe(cell, cellTR, cluster, clusterTR, cellIndex, cellIndexTR); // Fill histos + } +} + +void ClusterTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ClusterTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ClusterTask::finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) +{ + if (matcher == o2::framework::ConcreteDataMatcher("EMC", "BADCHANNELMAP", 0)) { + mBadChannelMap = reinterpret_cast(obj); + if (mBadChannelMap) { + ILOG(Info, Support) << "Updated EMCAL bad channel map " << ENDM; + } + } + if (matcher == o2::framework::ConcreteDataMatcher("EMC", "TIMECALIBPARAM", 0)) { + mTimeCalib = reinterpret_cast(obj); + if (mTimeCalib) { + ILOG(Info, Support) << "Updated EMCAL time calibration" << ENDM; + } + } + if (matcher == o2::framework::ConcreteDataMatcher("EMC", "GAINCALIBPARAM", 0)) { + mEnergyCalib = reinterpret_cast(obj); + if (mEnergyCalib) { + ILOG(Info, Support) << "Update EMCAL gain calibration" << ENDM; + } + } +} + +void ClusterTask::loadCalibrationObjects(o2::framework::ProcessingContext& ctx) +{ + if (mTaskParameters.mInternalClusterizer && mTaskParameters.mCalibrate) { + ctx.inputs().get("badchannelmap"); + ctx.inputs().get("timecalib"); + ctx.inputs().get("energycalib"); + } +} + +//_____________________________ Fill function _____________________________ +void ClusterTask::analyseTimeframe(const gsl::span& cells, const gsl::span& cellTriggerRecords, const gsl::span clusters, const gsl::span clusterTriggerRecords, const gsl::span clusterIndices, const gsl::span cellIndexTriggerRecords) +{ + + struct MaxCluster { + double energy = 0.; + double time = 0.; + double eta = 0.; + double phi = 0.; + int supermoduleID = -1; + bool initialized = false; + }; + + auto resetMaxCluster = [](MaxCluster& currentmax) { + currentmax.energy = 0.; + currentmax.time = 0.; + currentmax.eta = 0.; + currentmax.phi = 0.; + currentmax.supermoduleID = -1; + currentmax.initialized = 0; + }; + + auto isMaxCluster = [](MaxCluster& currentmax, const o2::emcal::AnalysisCluster& currentcluster) { + if (!currentmax.initialized) { + return true; + } + return currentcluster.E() > currentmax.energy; + }; + + auto fillMaxCluster = [](MaxCluster& currentmax, const o2::emcal::AnalysisCluster& currentcluster) { + currentmax.energy = currentcluster.E(); + currentmax.time = currentcluster.getClusterTime(); + auto [eta, phi] = ClusterTask::getClusterEtaPhi(currentcluster); + currentmax.eta = eta; + currentmax.phi = phi; + currentmax.initialized = true; + }; + + mEventHandler->reset(); + // mClusterFactory->reset(); + + mEventHandler->setClusterData(clusters, clusterIndices, clusterTriggerRecords, cellIndexTriggerRecords); + mEventHandler->setCellData(cells, cellTriggerRecords); + + mHistNclustPerTF->Fill(clusters.size()); + int nClustersTimeframeSelected = 0; + + std::array numberOfClustersSupermodule; + std::array maxClusterSupermodule; + std::array maxClusterDets; + for (int iev = 0; iev < mEventHandler->getNumberOfEvents(); iev++) { + auto inputEvent = mEventHandler->buildEvent(iev); + auto trigger = inputEvent.mTriggerBits; + auto isCalibTrigger = (trigger & o2::trigger::Cal), + isPhysicsTrigger = (trigger & o2::trigger::PhT); + if (isCalibTrigger) { + mHistNClustPerEvt_Calib->Fill(inputEvent.mClusters.size()); + } + if (isPhysicsTrigger) { + mHistNclustPerEvt->Fill(inputEvent.mClusters.size()); + } + + mClusterFactory->reset(); + mClusterFactory->setContainer(inputEvent.mClusters, inputEvent.mCells, inputEvent.mCellIndices); + std::fill(numberOfClustersSupermodule.begin(), numberOfClustersSupermodule.end(), 0); + std::for_each(maxClusterSupermodule.begin(), maxClusterSupermodule.end(), resetMaxCluster); + std::for_each(maxClusterDets.begin(), maxClusterDets.end(), resetMaxCluster); + + std::vector selclustersEMCAL, selclustersDCAL; + int nClustersEventSelected = 0; + for (int icl = 0; icl < mClusterFactory->getNumberOfClusters(); icl++) { + + o2::emcal::AnalysisCluster analysisCluster = mClusterFactory->buildCluster(icl); + if (isPhysicsTrigger) { + if (analysisCluster.getIsExotic()) { + continue; + } + auto clsTypeEMC = fillClusterHistogramsPhysics(analysisCluster); + if (mTaskParameters.mFillInvMassMeson && mMesonClusterCuts.isSelected(analysisCluster)) { + auto clustervec = buildClusterVector(analysisCluster); + if (clsTypeEMC) { + selclustersEMCAL.push_back(clustervec); + } else { + selclustersDCAL.push_back(clustervec); + } + } + nClustersEventSelected++; + nClustersTimeframeSelected++; + int supermoduleID = -1; + try { + auto [eta, phi] = getClusterEtaPhi(analysisCluster); + supermoduleID = mGeometry->SuperModuleNumberFromEtaPhi(eta, phi); + numberOfClustersSupermodule[supermoduleID]++; + if (isMaxCluster(maxClusterSupermodule[supermoduleID], analysisCluster)) { + fillMaxCluster(maxClusterSupermodule[supermoduleID], analysisCluster); + } + } catch (o2::emcal::InvalidPositionException& e) { + } + if (isMaxCluster(maxClusterDets[ALL_DET], analysisCluster)) { + fillMaxCluster(maxClusterDets[ALL_DET], analysisCluster); + if (supermoduleID > -1) { + maxClusterDets[ALL_DET].supermoduleID = supermoduleID; + } + } + if (clsTypeEMC) { + if (isMaxCluster(maxClusterDets[EMCAL_DET], analysisCluster)) { + fillMaxCluster(maxClusterDets[EMCAL_DET], analysisCluster); + if (supermoduleID > -1) { + maxClusterDets[EMCAL_DET].supermoduleID = supermoduleID; + } + } + } else { + if (isMaxCluster(maxClusterDets[DCAL_DET], analysisCluster)) { + fillMaxCluster(maxClusterDets[DCAL_DET], analysisCluster); + if (supermoduleID > -1) { + maxClusterDets[EMCAL_DET].supermoduleID = supermoduleID; + } + } + } + } + if (isCalibTrigger) { + if (!analysisCluster.getIsExotic()) { + fillClusterHistogramsLED(analysisCluster); + nClustersEventSelected++; + nClustersTimeframeSelected++; + } + } + + } // cls loop + if (isPhysicsTrigger && mTaskParameters.mFillInvMassMeson) { + // Apply centrality selection (paticularly for heavy-ion) + // Selection based on cluster multiplicity + if (nClustersEventSelected >= mTaskParameters.mMesonMinClusterMultiplicity && nClustersEventSelected <= mTaskParameters.mMesonMaxClusterMultiplicity) { + buildAndAnalysePiOs(selclustersEMCAL, true); + buildAndAnalysePiOs(selclustersDCAL, false); + } + } + if (isPhysicsTrigger) { + mHistNclustPerEvtSelected->Fill(nClustersEventSelected); + for (int ism = 0; ism < 20; ism++) { + mHistNClustPerEventSupermodule->Fill(numberOfClustersSupermodule[ism], ism); + if (maxClusterSupermodule[ism].initialized) { + mHistMaxClusterEnergySupermodule->Fill(maxClusterSupermodule[ism].energy, ism); + mHistMaxClusterTimeSupermodule->Fill(maxClusterSupermodule[ism].time, ism); + } + } + for (int detType = 0; detType < NUM_DETS; detType++) { + if (maxClusterDets[detType].initialized) { + mHistClustEMaxCluster[detType]->Fill(maxClusterDets[detType].energy); + mHistClustTimeMaxCluster[detType]->Fill(maxClusterDets[detType].time); + if (detType == ALL_DET) { + mHistClustEtaPhiMaxCluster->Fill(maxClusterDets[detType].eta, maxClusterDets[detType].phi); + mHistSupermoduleIDMaxCluster->Fill(maxClusterDets[detType].supermoduleID); + } + } + } + } + if (isCalibTrigger) { + mHistNClustPerEvtSelected_Calib->Fill(nClustersEventSelected); + } + } // event loop + mHistNclustPerTFSelected->Fill(nClustersTimeframeSelected); +} + +void ClusterTask::buildAndAnalysePiOs(const gsl::span clustervectors, bool isEMCAL) +{ + ILOG(Debug, Support) << "Next event: " << clustervectors.size() << " clusters from detector " << (isEMCAL ? "EMCAL" : "DCAL") << " for meson search" << ENDM; + auto histMass1D = isEMCAL ? mHistMassDiphoton_EMCAL : mHistMassDiphoton_DCAL; + auto histMassPt = isEMCAL ? mHistMassDiphotonPt_EMCAL : mHistMassDiphotonPt_DCAL; + if (clustervectors.size() > 1) { + for (int icl = 0; icl < clustervectors.size() - 1; icl++) { + for (int jcl = icl + 1; jcl < clustervectors.size(); jcl++) { + auto emax = clustervectors[icl].E() > clustervectors[jcl].E() ? clustervectors[icl].E() : clustervectors[jcl].E(); + if (emax < mTaskParameters.mMinELeadingMeson) { + continue; + } + TLorentzVector pi0candidate = clustervectors[icl] + clustervectors[jcl]; + if (!mMesonCuts.isSelected(pi0candidate)) { + continue; + } + histMassPt->Fill(pi0candidate.M(), pi0candidate.Pt()); + histMass1D->Fill(pi0candidate.M()); + } + } + } +} + +TLorentzVector ClusterTask::buildClusterVector(const o2::emcal::AnalysisCluster& fullcluster) const +{ + auto pos = fullcluster.getGlobalPosition(); + auto theta = 2 * std::atan2(std::exp(-pos.Eta()), 1); + auto px = fullcluster.E() * std::sin(theta) * std::cos(pos.Phi()); + auto py = fullcluster.E() * std::sin(theta) * std::sin(pos.Phi()); + auto pz = fullcluster.E() * std::cos(theta); + TLorentzVector clustervec; + clustervec.SetPxPyPzE(px, py, pz, fullcluster.E()); + return clustervec; +} + +bool ClusterTask::fillClusterHistogramsPhysics(const o2::emcal::AnalysisCluster& cluster) +{ + Double_t clustE = cluster.E(); + Double_t clustT = cluster.getClusterTime(); // svk converted to ns adapted from Run2; + + ///////////////////////////////// + // Select EMCAL or DCAL clusters// + ///////////////////////////////// + Bool_t clsTypeEMC; + auto [emceta, emcphi] = getClusterEtaPhi(cluster); + mHistClustEtaPhi->Fill(emceta, emcphi); + + DetType_t dettype = DetType_t::ALL_DET; // Use ALL_DET as default value, will be replaced by actual subdetector + if (emcphi < 4.) { + dettype = DetType_t::EMCAL_DET; // EMCAL : 80 < phi < 187 + } else { + dettype = DetType_t::DCAL_DET; // DCAL : 260 < phi < 327 + } + + try { + auto supermoduleID = mGeometry->SuperModuleNumberFromEtaPhi(emceta, emcphi); + mHistNclustSupermodule->Fill(supermoduleID); + mHistClusterTimeSupermodule->Fill(clustT, supermoduleID); + mHistClusterEnergySupermodule->Fill(clustE, supermoduleID); + mHistClusterNCellSupermodule->Fill(cluster.getNCells(), supermoduleID); + } catch (o2::emcal::InvalidPositionException& e) { + // eventually monitor position errors in the future + } + + // First: Distributions both detectors together + mHistTime[DetType_t::ALL_DET]->Fill(clustE, clustT); + mHistClustE[DetType_t::ALL_DET]->Fill(clustE); + mHistNCells[DetType_t::ALL_DET]->Fill(clustE, cluster.getNCells()); + mHistM02[DetType_t::ALL_DET]->Fill(cluster.getM02()); + mHistM20[DetType_t::ALL_DET]->Fill(cluster.getM20()); + mHistM02VsClustE[DetType_t::ALL_DET]->Fill(clustE, cluster.getM02()); + mHistM20VsClustE[DetType_t::ALL_DET]->Fill(clustE, cluster.getM20()); + + // Second: Distributions per subdetector + mHistTime[dettype]->Fill(clustE, clustT); + mHistClustE[dettype]->Fill(clustE); + mHistNCells[dettype]->Fill(clustE, cluster.getNCells()); + mHistM02[dettype]->Fill(cluster.getM02()); + mHistM20[dettype]->Fill(cluster.getM20()); + mHistM02VsClustE[dettype]->Fill(clustE, cluster.getM02()); + mHistM20VsClustE[dettype]->Fill(clustE, cluster.getM20()); + + return dettype == DetType_t::EMCAL_DET; +} + +void ClusterTask::fillClusterHistogramsLED(const o2::emcal::AnalysisCluster& cluster) +{ + math_utils::Point3D emcx = cluster.getGlobalPosition(); + + TVector3 clustpos(emcx.X(), emcx.Y(), emcx.Z()); + Double_t emcphi = TVector2::Phi_0_2pi(clustpos.Phi()); + Double_t emceta = clustpos.Eta(); + mHistClusterEnergy_Calib->Fill(cluster.E()); + mHistClusterEtaPhi_Calib->Fill(emceta, emcphi); + mHistClusterEnergyTime_Calib->Fill(cluster.E(), cluster.getClusterTime()); + mHistClusterEnergyCells_Calib->Fill(cluster.E(), cluster.getNCells()); +} + +std::tuple ClusterTask::getClusterEtaPhi(const o2::emcal::AnalysisCluster& cluster) +{ + math_utils::Point3D emcx = cluster.getGlobalPosition(); + + TVector3 clustpos(emcx.X(), emcx.Y(), emcx.Z()); + return std::make_tuple(clustpos.Eta(), TVector2::Phi_0_2pi(clustpos.Phi())); +} + +//_____________________________ Internal clusteriser function _____________________________ +void ClusterTask::findClustersInternal(const gsl::span& cells, const gsl::span& cellTriggerRecords, std::vector& clusters, std::vector& clusterTriggerRecords, std::vector& clusterIndices, std::vector& clusterIndexTriggerRecords) +{ + LOG(debug) << "[EMCALClusterizer - findClustersInternal] called"; + // mTimer.Start(false); + + if (!mClusterizer) { + mClusterizer = std::make_unique>(); + ILOG(Info, Support) << "Internal clusterizer initialized" << ENDM; + mClusterizer->setGeometry(mGeometry); // svk - set geometry for clusterizer + + // Initialize clusterizer from clusterizer params + ILOG(Info, Support) << mClusterizerSettings << ENDM; + mClusterizer->initialize(mClusterizerSettings.mMaxTimeDeltaCells, mClusterizerSettings.mMinCellTime, mClusterizerSettings.mMaxCellTime, mClusterizerSettings.mGradientCut, mClusterizerSettings.mDoEnergyGradientCut, mClusterizerSettings.mSeedThreshold, mClusterizerSettings.mCellThreshold); + } + + clusters.clear(); + clusterTriggerRecords.clear(); + clusterIndices.clear(); + clusterIndexTriggerRecords.clear(); + + int currentStartClusters = 0; // clusters->size(); //svk + int currentStartIndices = 0; // clusterIndices->size(); //svk + + for (const auto& iTrgRcrd : cellTriggerRecords) { + LOG(debug) << " findClustersInternal loop over iTrgRcrd " << ENDM; + + mClusterizer->clear(); + if (cells.size() && iTrgRcrd.getNumberOfObjects()) { + LOG(debug) << " Number of cells put in " << cells.size() << ENDM; + auto cellsEvent = cells.subspan(iTrgRcrd.getFirstEntry(), iTrgRcrd.getNumberOfObjects()); + if (iTrgRcrd.getTriggerBits() & o2::trigger::Cal) { + // In case of calib trigger drop LEDMON cells + // both from clusterizing and internal cell monitoring + // LEDMONs are organized in strip modules, they + // cannot be clustered + // they appear after FEC cells in the cell vector, + // consequently it is sufficient to restrict the cell + // vector to the first N cells. Also the cell index in + // the cluster won't be disturbed. + int rangeFECCells = -1, currentIndex = 0; + for (const auto& cell : cellsEvent) { + if (cell.getLEDMon()) { + rangeFECCells = currentIndex; + break; + } + currentIndex++; + } + if (rangeFECCells > -1) { + cellsEvent = cellsEvent.subspan(0, rangeFECCells); + } + } + if (mTaskParameters.mFillControlHistograms) { + auto isCalibTrigger = (iTrgRcrd.getTriggerBits() & o2::trigger::Cal), + isPhysicsTrigger = (iTrgRcrd.getTriggerBits() & o2::trigger::PhT); + for (auto& cell : cellsEvent) { + mHistCellEnergyTimeUsed->Fill(cell.getAmplitude(), cell.getTimeStamp()); + if (isPhysicsTrigger) { + mHistCellEnergyTimePhys->Fill(cell.getAmplitude(), cell.getTimeStamp()); + } + if (isCalibTrigger) { + mHistCellEnergyTimeCalib->Fill(cell.getAmplitude(), cell.getTimeStamp()); + } + } + } + mClusterizer->findClusters(cellsEvent); // Find clusters on cells/digits (pass by ref) + } + + auto outputClustersTemp = mClusterizer->getFoundClusters(); + LOG(debug) << " Number of Clusters in outputClustersTemp " << outputClustersTemp->size() << ENDM; + + auto outputCellDigitIndicesTemp = mClusterizer->getFoundClustersInputIndices(); + + std::copy(outputClustersTemp->begin(), outputClustersTemp->end(), std::back_inserter(clusters)); + std::copy(outputCellDigitIndicesTemp->begin(), outputCellDigitIndicesTemp->end(), std::back_inserter(clusterIndices)); + + clusterTriggerRecords.emplace_back(iTrgRcrd.getBCData(), currentStartClusters, outputClustersTemp->size()).setTriggerBits(iTrgRcrd.getTriggerBits()); // svk + clusterIndexTriggerRecords.emplace_back(iTrgRcrd.getBCData(), currentStartIndices, outputCellDigitIndicesTemp->size()).setTriggerBits(iTrgRcrd.getTriggerBits()); // svk + + currentStartClusters = clusters.size(); // svk + currentStartIndices = clusterIndices.size(); // svk + } + LOG(debug) << "[EMCALClusterizer - findClustersInternal] Writing " << clusters.size() << " clusters ..."; + // mTimer.Stop(); +} + +void ClusterTask::getCalibratedCells(const gsl::span& cells, const gsl::span& triggerRecords, std::vector& calibratedCells, std::vector& calibratedTriggerRecords) +{ + int currentlast = 0; + for (const auto& trg : triggerRecords) { + if (!trg.getNumberOfObjects()) { + // No EMCAL cells in event - post empty trigger record + o2::emcal::TriggerRecord nexttrigger(trg.getBCData(), currentlast, 0); + nexttrigger.setTriggerBits(trg.getTriggerBits()); + calibratedTriggerRecords.push_back(nexttrigger); + continue; + } + for (const auto& inputcell : gsl::span(cells.data() + trg.getFirstEntry(), trg.getNumberOfObjects())) { + if (mBadChannelMap) { + if (mBadChannelMap->getChannelStatus(inputcell.getTower()) != o2::emcal::BadChannelMap::MaskType_t::GOOD_CELL) { + // Cell marked as bad or warm, continue + continue; + } + } + auto cellamplitude = inputcell.getAmplitude(); + auto celltime = inputcell.getTimeStamp(); + if (mTimeCalib) { + celltime -= mTimeCalib->getTimeCalibParam(inputcell.getTower(), inputcell.getLowGain()); + } + if (mEnergyCalib) { + cellamplitude *= mEnergyCalib->getGainCalibFactors(inputcell.getTower()); + } + calibratedCells.emplace_back(inputcell.getTower(), cellamplitude, celltime, inputcell.getType()); + } + int ncellsEvent = calibratedCells.size() - currentlast; + o2::emcal::TriggerRecord nexttrigger(trg.getBCData(), currentlast, ncellsEvent); + nexttrigger.setTriggerBits(trg.getTriggerBits()); + calibratedTriggerRecords.push_back(nexttrigger); + currentlast = calibratedCells.size(); + } +} + +void ClusterTask::configureBindings() +{ + if (hasConfigValue("bindingCells")) { + mTaskInputBindings.mCellBinding = getConfigValue("bindingCells"); + } + if (hasConfigValue("bindingCellTriggerRecords")) { + mTaskInputBindings.mCellTriggerRecordBinding = getConfigValue("bindingCellTriggerRecords"); + } + if (hasConfigValue("bindingClusters")) { + mTaskInputBindings.mClusterBinding = getConfigValue("bindingClusters"); + } + if (hasConfigValue("bindingClusterTriggerRecords")) { + mTaskInputBindings.mClusterTriggerRecordBinding = getConfigValue("bindingClusterTriggerRecords"); + } + if (hasConfigValue("bindingCellIndices")) { + mTaskInputBindings.mCellIndexBinding = getConfigValue("bindingCellIndices"); + } + if (hasConfigValue("bindingCellIndexTriggerRecords")) { + mTaskInputBindings.mCellIndexTriggerRecordBinding = getConfigValue("bindingCellIndexTriggerRecords"); + } +} + +void ClusterTask::configureClusterizerSettings() +{ + auto get_bool = [](const std::string_view input) -> bool { + return input == "true"; + }; + if (hasConfigValue("clusterizerMinTime")) { + mClusterizerSettings.mMinCellTime = std::stod(getConfigValue("clusterizerMinTime")); + } + if (hasConfigValue("clusterizerMaxTime")) { + mClusterizerSettings.mMaxCellTime = std::stod(getConfigValue("clusterizerMaxTime")); + } + if (hasConfigValue("clusterizerMaxTimeDelta")) { + mClusterizerSettings.mMaxTimeDeltaCells = std::stod(getConfigValue("clusterizerMaxTimeDelta")); + } + if (hasConfigValue("clusterizerSeedThreshold")) { + mClusterizerSettings.mSeedThreshold = std::stof(getConfigValue("clusterizerSeedThreshold")); + } + if (hasConfigValue("clusterizerCellTreshold")) { + mClusterizerSettings.mCellThreshold = std::stof(getConfigValue("clusterizerCellTreshold")); + } + if (hasConfigValue("clusterizerDoGradientCut")) { + mClusterizerSettings.mDoEnergyGradientCut = get_bool(getConfigValueLower("clusterizerDoGradientCut")); + } + if (hasConfigValue("clusterizerGradientCut")) { + mClusterizerSettings.mGradientCut = std::stof(getConfigValue("clusterizerGradientCut")); + } +} + +void ClusterTask::configureMesonSelection() +{ + auto get_bool = [](const std::string_view input) -> bool { + return input == "true"; + }; + if (hasConfigValue("mesonClusterMinE")) { + mMesonClusterCuts.mMinE = std::stod(getConfigValue("mesonClusterMinE")); + } + if (hasConfigValue("mesonClusterMaxTime")) { + mMesonClusterCuts.mMaxTime = std::stod(getConfigValue("mesonClusterMaxTime")); + } + if (hasConfigValue("mesonClusterMinCells")) { + mMesonClusterCuts.mMinNCell = std::stoi(getConfigValue("mesonClusterMinCells")); + } + if (hasConfigValue("mesonClustersRejectExotics")) { + mMesonClusterCuts.mRejectExotics = std::stoi(getConfigValue("mesonClustersRejectExotics")); + } + if (hasConfigValue("mesonClusterMinM02")) { + mMesonClusterCuts.mMinM02 = std::stod(getConfigValueLower("mesonClusterMinM02")); + } + if (hasConfigValue("mesonClusterMaxM02")) { + mMesonClusterCuts.mMaxM02 = std::stod(getConfigValueLower("mesonClusterMaxM02")); + } + if (hasConfigValue("mesonClusterMinM20")) { + mMesonClusterCuts.mMinM20 = std::stod(getConfigValueLower("mesonClusterMinM20")); + } + if (hasConfigValue("mesonClusterMaxM20")) { + mMesonClusterCuts.mMaxM20 = std::stod(getConfigValueLower("mesonClusterMaxM20")); + } + if (hasConfigValue("mesonMinPt")) { + mMesonCuts.mMinPt = std::stod(getConfigValue("mesonMinPt")); + } + + ILOG(Info, Support) << mMesonClusterCuts; + ILOG(Info, Support) << mMesonCuts; +} + +void ClusterTask::configureTaskParameters() +{ + auto get_bool = [](const std::string_view input) -> bool { + return input == "true"; + }; + + /* + int mMultiplicityRange = 200; ///< Range for multiplicity histograms + */ + if (hasConfigValue("useInternalClusterizer")) { + mTaskParameters.mInternalClusterizer = get_bool(getConfigValueLower("useInternalClusterizer")); + } + if (hasConfigValue("calibrateCells")) { + mTaskParameters.mCalibrate = get_bool(getConfigValueLower("calibrateCells")); + } + if (hasConfigValue("fillControlHistograms")) { + mTaskParameters.mFillControlHistograms = get_bool(getConfigValueLower("fillControlHistograms")); + } + if (hasConfigValue("hasInvMassMesons")) { + mTaskParameters.mFillInvMassMeson = get_bool(getConfigValueLower("hasInvMassMesons")); + } + if (hasConfigValue("MultiplicityRange")) { + mTaskParameters.mMultiplicityRange = std::stoi(getConfigValueLower("MultiplicityRange")); + } + if (hasConfigValue("mesonMinClusterMultiplicity")) { + mTaskParameters.mMesonMinClusterMultiplicity = std::stoi(getConfigValueLower("mesonMinClusterMultiplicity")); + } + if (hasConfigValue("mesonMaxClusterMultiplicity")) { + mTaskParameters.mMesonMaxClusterMultiplicity = std::stoi(getConfigValueLower("mesonMaxClusterMultiplicity")); + } + if (hasConfigValue("mesonMinEClusterLeading")) { + mTaskParameters.mMinELeadingMeson = std::stod(getConfigValueLower("mesonMinEClusterLeading")); + } + + ILOG(Info, Support) << mTaskParameters; +} + +void ClusterTask::resetHistograms() +{ + auto conditionalReset = [](auto obj) { + if (obj) { + obj->Reset(); + } + }; + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + + conditionalReset(mHistCellEnergyTimeUsed); + conditionalReset(mHistCellEnergyTimePhys); + conditionalReset(mHistCellEnergyTimeCalib); + conditionalReset(mHistNclustPerTFSelected); + conditionalReset(mHistNclustPerEvtSelected); + conditionalReset(mHistNclustSupermodule); + conditionalReset(mHistNClustPerEventSupermodule); + conditionalReset(mHistSupermoduleIDMaxCluster); + + conditionalReset(mHistNclustPerTF); + conditionalReset(mHistNclustPerEvt); + conditionalReset(mHistClustEtaPhi); + conditionalReset(mHistClustEtaPhiMaxCluster); + + for (auto idet = 0; idet < NUM_DETS; idet++) { + conditionalReset(mHistTime[idet]); + conditionalReset(mHistClustE[idet]); + conditionalReset(mHistNCells[idet]); + conditionalReset(mHistM02[idet]); + conditionalReset(mHistM20[idet]); + conditionalReset(mHistM02VsClustE[idet]); + conditionalReset(mHistM20VsClustE[idet]); + conditionalReset(mHistClustEMaxCluster[idet]); + conditionalReset(mHistClustTimeMaxCluster[idet]); + } + + conditionalReset(mHistClusterTimeSupermodule); + conditionalReset(mHistClusterEnergySupermodule); + conditionalReset(mHistClusterNCellSupermodule); + conditionalReset(mHistMaxClusterEnergySupermodule); + conditionalReset(mHistMaxClusterTimeSupermodule); + + conditionalReset(mHistMassDiphoton_EMCAL); + conditionalReset(mHistMassDiphoton_DCAL); + conditionalReset(mHistMassDiphotonPt_EMCAL); + conditionalReset(mHistMassDiphotonPt_DCAL); + + conditionalReset(mHistNClustPerEvt_Calib); + conditionalReset(mHistNClustPerEvtSelected_Calib); + conditionalReset(mHistClusterEtaPhi_Calib); + conditionalReset(mHistClusterEnergy_Calib); + conditionalReset(mHistClusterEnergyTime_Calib); + conditionalReset(mHistClusterEnergyCells_Calib); +} + +bool ClusterTask::hasConfigValue(const std::string_view key) +{ + if (auto param = mCustomParameters.find(key.data()); param != mCustomParameters.end()) { + return true; + } + return false; +} + +std::string ClusterTask::getConfigValue(const std::string_view key) +{ + std::string result; + if (auto param = mCustomParameters.find(key.data()); param != mCustomParameters.end()) { + result = param->second; + } + return result; +} + +std::string ClusterTask::getConfigValueLower(const std::string_view key) +{ + auto input = getConfigValue(key); + std::string result; + if (input.length()) { + result = boost::algorithm::to_lower_copy(input); + } + return result; +} + +void ClusterTask::ClusterizerParams::print(std::ostream& stream) const +{ + stream << "Clusterizer Parameters: \n" + << "=============================================\n" + << "Min. time: " << mMinCellTime << " ns\n" + << "Max. time: " << mMaxCellTime << " ns\n" + << "Max. time difference between cells: " << mMaxTimeDeltaCells << " ns\n" + << "Seed threshold: " << mSeedThreshold << " GeV\n" + << "Cell threshold: " << mCellThreshold << " GeV\n" + << "Perform gradient cut: " << (mDoEnergyGradientCut ? "yes" : "no") << "\n" + << "Gradient cut: " << mGradientCut << "\n"; +} + +bool ClusterTask::MesonClusterSelection::isSelected(const o2::emcal::AnalysisCluster& cluster) const +{ + if (cluster.E() < mMinE) { + return false; + } + if (std::abs(cluster.getClusterTime()) > mMaxTime) { + return false; + } + if (cluster.getNCells() < mMinNCell) { + return false; + } + if (cluster.getM02() < mMinM02 || cluster.getM02() > mMaxM02) { + return false; + } + if (cluster.getM20() < mMinM20 || cluster.getM20() > mMaxM20) { + return false; + } + if (mRejectExotics && cluster.getIsExotic()) { + return false; + } + return true; +} + +void ClusterTask::MesonClusterSelection::print(std::ostream& stream) const +{ + stream << "Cluster selection for meson candidates: \n" + << "======================================================\n" + << "Min. energy: " << mMinE << " GeV\n" + << "Max. time: " << mMaxTime << " ns\n" + << "Min. number of cells: " << mMinNCell << "\n" + << "Min. M02: " << mMinM02 << "\n" + << "Max. M02: " << mMaxM02 << "\n" + << "Min. M20: " << mMinM20 << "\n" + << "Max. M20: " << mMaxM20 << "\n" + << "Reject exotics: " << (mRejectExotics ? "yes" : "no") << "\n"; +} + +bool ClusterTask::MesonSelection::isSelected(const TLorentzVector& mesonCandidate) const +{ + if (mesonCandidate.Pt() < mMinPt) { + return false; + } + return true; +} + +void ClusterTask::MesonSelection::print(std::ostream& stream) const +{ + stream << "Meson candidates selection: \n" + << "======================================================\n" + << "Min. pt: " << mMinPt << " GeV/c\n"; +} + +void ClusterTask::TaskParams::print(std::ostream& stream) const +{ + stream << "General task settings: \n" + << "======================================================\n" + << "Internal clusterizer: " << (mInternalClusterizer ? "enabled" : "disabled") << "\n" + << "Calibrate cells before clusterization: " << (mCalibrate ? "yes" : "no") << "\n" + << "Filling cell-level control histograms: " << (mFillControlHistograms ? "yes" : "no") << "\n" + << "Invariant mass histograms for Meson candidates: " << (mFillInvMassMeson ? "enabled" : "disabled") << "\n" + << "Max. range of multiplicity histograms: " << mMultiplicityRange << "\n" + << "Min. cluster multiplicity for Meson candidates: " << mMesonMinClusterMultiplicity << "\n" + << "Max. cluster mutliplicity for Meson candidates: " << mMesonMaxClusterMultiplicity << "\n" + << "Min. energy leading for Meson candidates: " << mMinELeadingMeson << " GeV\n"; +} + +std::ostream& operator<<(std::ostream& stream, const ClusterTask::ClusterizerParams& params) +{ + params.print(stream); + return stream; +} + +std::ostream& operator<<(std::ostream& stream, const ClusterTask::MesonClusterSelection& cuts) +{ + cuts.print(stream); + return stream; +} + +std::ostream& operator<<(std::ostream& stream, const ClusterTask::MesonSelection& cuts) +{ + cuts.print(stream); + return stream; +} + +std::ostream& operator<<(std::ostream& stream, const ClusterTask::TaskParams& cuts) +{ + cuts.print(stream); + return stream; +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/DigitCheck.cxx b/Modules/EMCAL/src/DigitCheck.cxx new file mode 100644 index 0000000000..b770dab03d --- /dev/null +++ b/Modules/EMCAL/src/DigitCheck.cxx @@ -0,0 +1,190 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include "EMCAL/DigitCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::emcal +{ + +Quality DigitCheck::check(std::map>* moMap) +{ + auto mo = moMap->begin()->second; + Quality result = Quality::Good; + // digitAmplidute_PHYS + // digitTime_PHYS + std::vector amplitudeHist = { "digitAmplitudeEMCAL", "digitAmplitudeDCAL", "digitAmplitude_PHYS" }; + if (std::find(amplitudeHist.begin(), amplitudeHist.end(), mo->getName()) != amplitudeHist.end()) { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) + result = Quality::Bad; + } + if (mo->getName() == "SMMaxNumDigits") { + double errormargin = 2.; + auto hist = dynamic_cast(mo->getObject()); + std::vector smcounts; + for (auto ib : ROOT::TSeqI(0, hist->GetXaxis()->GetNbins())) { + auto countSM = hist->GetBinContent(ib + 1); + if (countSM > 0) + smcounts.emplace_back(countSM); + } + if (!smcounts.size()) { + result = Quality::Medium; + } else { + TRobustEstimator meanfinder; + double mean, sigma; + meanfinder.EvaluateUni(smcounts.size(), smcounts.data(), mean, sigma); + for (auto ib : ROOT::TSeqI(0, hist->GetXaxis()->GetNbins())) { + if (hist->GetBinContent(ib + 1) > mean + errormargin * sigma) { + result = Quality::Bad; + break; + } + } + } + } + + /* if (mo->getName() == "digitTimeHG") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) + result = Quality::Bad; + else { + Float_t timeMean = h->GetMean(); + Float_t timeThrs = 60.; + if (timeMean < timeThrs - 20. || timeMean > timeThrs + 20.) { + result = Quality::Bad; + } + } + } + + if (mo->getName() == "digitTimeLG") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) + result = Quality::Bad; + else { + Float_t timeMean = h->GetMean(); + Float_t timeThrs = 60; + if (timeMean < timeThrs - 20 || timeMean > timeThrs + 20) { + result = Quality::Bad; + } + } + } + */ + return result; +} + +void DigitCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName().find("Time") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("Mean inside limits: OK!!!"); + msg->SetFillColor(kGreen); + // + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + msg->Clear(); + msg->AddText("Mean outside limits or no entries"); + msg->AddText("If NOT a technical run,"); + msg->AddText("call EMCAL on-call."); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } + if (mo->getName().find("Amplitude") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("Mean inside limits: OK!!!"); + msg->SetFillColor(kGreen); + // + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + msg->Clear(); + msg->AddText("Mean outside limits or no entries"); + msg->AddText("If NOT a technical run,"); + msg->AddText("call EMCAL on-call."); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } + if (mo->getName() == "SMMaxNumDigits") { + auto* h = dynamic_cast(mo->getObject()); + if (checkResult == Quality::Good) { + TLatex* msg = new TLatex(0.2, 0.8, "#color[418]{Data OK}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + TLatex* msg = new TLatex(0.2, 0.8, "#color[2]{Noisy supermodule detected}"); + // Large payload in several DDLs + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + msg = new TLatex(0.2, 0.7, "#color[2]{If NOT techn.run: call EMCAL oncall}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + TLatex* msg = new TLatex(0.2, 0.8, "#color[42]{empty:if in run, call EMCAL-oncall}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } + h->SetLineColor(kBlack); + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/DigitsQcTask.cxx b/Modules/EMCAL/src/DigitsQcTask.cxx new file mode 100644 index 0000000000..c053291cbd --- /dev/null +++ b/Modules/EMCAL/src/DigitsQcTask.cxx @@ -0,0 +1,695 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsQcTask.cxx +/// \author Markus Fasel, Cristina Terrevoli +/// + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include "QualityControl/QcInfoLogger.h" +#include "EMCAL/DigitsQcTask.h" +#include "DataFormatsEMCAL/Cell.h" +#include "EMCALBase/Geometry.h" +#include "EMCALCalib/BadChannelMap.h" +#include "EMCALCalib/TimeCalibrationParams.h" +#include +#include +#include +#include +#include +#include + +namespace o2 +{ +namespace quality_control_modules +{ +namespace emcal +{ + +DigitsQcTask::~DigitsQcTask() +{ + for (auto en : mHistogramContainer) { + en.second.clean(); + } + if (mEvCounterTF) + delete mEvCounterTF; + if (mEvCounterTFPHYS) + delete mEvCounterTFPHYS; + if (mEvCounterTFCALIB) + delete mEvCounterTFCALIB; + if (mTFPerCycles) + delete mTFPerCycles; + if (mTFPerCyclesTOT) + delete mTFPerCyclesTOT; + if (mDigitsMaxSM) + delete mDigitsMaxSM; +} + +void DigitsQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + QcInfoLogger::setDetector("EMC"); + ILOG(Debug, Devel) << "initialize DigitsQcTask" << ENDM; + //define histograms + + auto get_bool = [](const std::string_view input) -> bool { + return input == "true"; + }; + + auto get_double = [](const std::string_view input) -> double { + double result = 0.; + if (input.length()) { + try { + result = std::stof(input.data()); + } catch (...) { + } + } + return result; + }; + + auto hasAmpVsCell = get_bool(getConfigValueLower("hasAmpVsCell")), + hasTimeVsCell = get_bool(getConfigValueLower("hasTimeVsCell")), + hasCalib2D = get_bool(getConfigValueLower("hasHistValibVsCell")); + + mIgnoreTriggerTypes = get_bool(getConfigValue("ignoreTriggers")); + //add a second threshold + double thresholdCAL = hasConfigValue("threshold") ? get_double(getConfigValue("threshold")) : 0.5; + double thresholdPHYS = hasConfigValue("threshold") ? get_double(getConfigValue("threshold")) : 0.2; + + if (hasAmpVsCell) { + ILOG(Debug, Support) << "Enabling histograms : Amplitude vs. cellID" << ENDM; + } + if (hasTimeVsCell) { + ILOG(Debug, Support) << "Enabling histograms : Time vs. cellID" << ENDM; + } + if (hasCalib2D) { + ILOG(Debug, Support) << "Enabling calibrated histograms" << ENDM; + } + + // initialize geometry + if (!mGeometry) + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); + + std::array triggers = { { "CAL", "PHYS" } }; + for (const auto& trg : triggers) { + DigitsHistograms histos; + histos.mCellThreshold = (trg == "CAL") ? thresholdCAL : thresholdPHYS; + histos.mGeometry = mGeometry; + histos.initForTrigger(trg.data(), hasAmpVsCell, hasTimeVsCell, hasCalib2D); + histos.startPublishing(*getObjectsManager()); + mHistogramContainer[trg] = histos; + } //trigger type + //new histos` + mTFPerCyclesTOT = new TH1D("NumberOfTFperCycles_TOT", "NumberOfTFperCycles_TOT", 100, -0.5, 99.5); // + mTFPerCyclesTOT->GetXaxis()->SetTitle("NumberOfTFperCyclesTOT"); + mTFPerCyclesTOT->GetYaxis()->SetTitle("Counts"); + getObjectsManager()->startPublishing(mTFPerCyclesTOT); + + mTFPerCycles = new TH1D("NumberOfTFperCycles_1", "NumberOfTFperCycles_1", 1, -0.5, 1.5); + mTFPerCycles->GetXaxis()->SetTitle("NumberOfTFperCycles"); + mTFPerCycles->GetYaxis()->SetTitle("Counts"); + getObjectsManager()->startPublishing(mTFPerCycles); + + mEvCounterTF = new TH1D("NEventsPerTF", "NEventsPerTF", 401, -0.5, 400.5); + mEvCounterTF->GetXaxis()->SetTitle("NEventsPerTimeFrame"); + mEvCounterTF->GetYaxis()->SetTitle("Counts"); + getObjectsManager()->startPublishing(mEvCounterTF); + + mEvCounterTFPHYS = new TH1D("NEventsPerTFPHYS", "NEventsPerTFPHYS", 401, -0.5, 400.5); + mEvCounterTFPHYS->GetXaxis()->SetTitle("NEventsPerTimeFrame_PHYS"); + mEvCounterTFPHYS->GetYaxis()->SetTitle("Counts"); + getObjectsManager()->startPublishing(mEvCounterTFPHYS); + + mEvCounterTFCALIB = new TH1D("NEventsPerTFCALIB", "NEventsPerTFCALIB", 6, -0.5, 5.5); + mEvCounterTFCALIB->GetXaxis()->SetTitle("NEventsPerTimeFrame_CALIB"); + mEvCounterTFCALIB->GetYaxis()->SetTitle("Counts"); + getObjectsManager()->startPublishing(mEvCounterTFCALIB); + + mDigitsMaxSM = new TH1D("SMMaxNumDigits", "Supermodule with largest amount of digits", 20, -0.5, 19.5); + mDigitsMaxSM->GetXaxis()->SetTitle("Supermodule"); + mDigitsMaxSM->GetYaxis()->SetTitle("counts"); + getObjectsManager()->startPublishing(mDigitsMaxSM); +} + +void DigitsQcTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + reset(); +} + +void DigitsQcTask::startOfCycle() +{ + mTimeFramesPerCycles = 0; + ILOG(Debug, Support) << "startOfCycle" << ENDM; + mBadChannelMap = retrieveConditionAny("EMC/Calib/BadChannels"); + //it was EMC/BadChannelMap + if (!mBadChannelMap) + ILOG(Info, Support) << "No Bad Channel Map object " << ENDM; + + mTimeCalib = retrieveConditionAny("EMC/Calib/Time"); + //"EMC/TimeCalibrationParams + if (!mTimeCalib) + ILOG(Info, Support) << " No Time Calib object " << ENDM; +} + +void DigitsQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + mTFPerCycles->Fill(1); //number of timeframe process per cycle + mTimeFramesPerCycles++; + // check if we have payoad + using MaskType_t = o2::emcal::BadChannelMap::MaskType_t; + + // Handling of inputs from multiple subevents (multiple FLPs) + // Build maps of trigger records and cells according to the subspecification + // and combine trigger records from different maps into a single map of range + // references and subspecifications + std::unordered_map> cellSubEvents; + std::unordered_map> triggerRecordSubevents; + + auto posCells = ctx.inputs().getPos("emcal-digits"), + posTriggerRecords = ctx.inputs().getPos("emcal-triggerecords"); + auto numSlotsCells = ctx.inputs().getNofParts(posCells), + numSlotsTriggerRecords = ctx.inputs().getNofParts(posTriggerRecords); + for (decltype(numSlotsCells) islot = 0; islot < numSlotsCells; islot++) { + auto celldata = ctx.inputs().getByPos(posCells, islot); + auto subspecification = framework::DataRefUtils::getHeader(celldata)->subSpecification; + // Discard message if it is a deadbeaf message (empty timeframe) + if (subspecification == 0xDEADBEEF) { + continue; + } + cellSubEvents[subspecification] = ctx.inputs().get>(celldata); + } + for (decltype(numSlotsTriggerRecords) islot = 0; islot < numSlotsTriggerRecords; islot++) { + auto trgrecorddata = ctx.inputs().getByPos(posTriggerRecords, islot); + auto subspecification = framework::DataRefUtils::getHeader(trgrecorddata)->subSpecification; + // Discard message if it is a deadbeaf message (empty timeframe) + if (subspecification == 0xDEADBEEF) { + continue; + } + triggerRecordSubevents[subspecification] = ctx.inputs().get>(trgrecorddata); + } + + auto combinedEvents = buildCombinedEvents(triggerRecordSubevents); + + // ILOG(Info, Support) <<"Received " << digitcontainer.size() << " digits " << ENDM; + int eventcounter = 0; + int eventcounterCALIB = 0; + int eventcounterPHYS = 0; + std::array numDigitsSM; + std::fill(numDigitsSM.begin(), numDigitsSM.end(), 0); + for (auto trg : combinedEvents) { + if (!trg.getNumberOfObjects()) { + continue; + } + ILOG(Debug, Support) << "Next event " << eventcounter << " has " << trg.getNumberOfObjects() << " digits from " << trg.getNumberOfSubevents() << " subevent(s)" << ENDM; + + //trigger type + auto triggertype = trg.mTriggerType; + bool isPhysTrigger = mIgnoreTriggerTypes || (triggertype & o2::trigger::PhT), + isCalibTrigger = (!mIgnoreTriggerTypes) && (triggertype & o2::trigger::Cal); + std::string trgClass; + if (isPhysTrigger) { + trgClass = "PHYS"; + eventcounterPHYS++; + } else if (isCalibTrigger) { + trgClass = "CAL"; + eventcounterCALIB++; + } else { + ILOG(Error, Support) << " Unmonitored trigger class requested " << ENDM; + continue; + } + + auto bcphase = trg.mInteractionRecord.bc % 4; // to be fixed:4 histos for EMCAL, 4 histos for DCAL + auto histos = mHistogramContainer[trgClass]; + std::fill(numDigitsSM.begin(), numDigitsSM.end(), 0); + + // iterate over subevents + for (auto& subev : trg.mSubevents) { + auto cellsSubspec = cellSubEvents.find(subev.mSpecification); + if (cellsSubspec == cellSubEvents.end()) { + ILOG(Error, Support) << "No cell data found for subspecification " << subev.mSpecification << ENDM; + } else { + ILOG(Debug, Support) << subev.mCellRange.getEntries() << " digits in subevent from equipment " << subev.mSpecification << ENDM; + gsl::span eventdigits(cellsSubspec->second.data() + subev.mCellRange.getFirstEntry(), subev.mCellRange.getEntries()); + int ndigit = 0, ndigitGlobal = subev.mCellRange.getFirstEntry(); + for (auto digit : eventdigits) { + //int index = digit.getHighGain() ? 0 : (digit.getLowGain() ? 1 : -1); + //if (index < 0) + // continue; + auto timeoffset = mTimeCalib ? mTimeCalib->getTimeCalibParam(digit.getTower(), digit.getLowGain()) : 0.; + bool goodcell = true; + if (mBadChannelMap) { + goodcell = mBadChannelMap->getChannelStatus(digit.getTower()) != MaskType_t::GOOD_CELL; + } + histos.fillHistograms(digit, goodcell, timeoffset, bcphase); + if (isPhysTrigger) { + auto [sm, mod, iphi, ieta] = mGeometry->GetCellIndex(digit.getTower()); + numDigitsSM[sm]++; + } + ndigit++; + ndigitGlobal++; + } + } + } + histos.countEvent(); + + if (isPhysTrigger) { + auto maxSM = std::max_element(numDigitsSM.begin(), numDigitsSM.end()); + auto indexMaxSM = maxSM - numDigitsSM.begin(); + mDigitsMaxSM->Fill(indexMaxSM); + } + + eventcounter++; + } + mEvCounterTF->Fill(eventcounter); + mEvCounterTFPHYS->Fill(eventcounterPHYS); + mEvCounterTFCALIB->Fill(eventcounterCALIB); + //event counter per TimeFrame (range 0-100) for the moment (parameter) +} + +void DigitsQcTask::endOfCycle() +{ + mTFPerCyclesTOT->Fill(mTimeFramesPerCycles); // do not reset this histo + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void DigitsQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void DigitsQcTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Support) << "Resetting the histogram" << ENDM; + for (auto cont : mHistogramContainer) { + cont.second.reset(); + } + if (mEvCounterTF) + mEvCounterTF->Reset(); + if (mEvCounterTFPHYS) + mEvCounterTFPHYS->Reset(); + if (mEvCounterTFCALIB) + mEvCounterTFCALIB->Reset(); + if (mTFPerCycles) + mTFPerCycles->Reset(); + if (mDigitsMaxSM) + mDigitsMaxSM->Reset(); +} + +std::vector DigitsQcTask::buildCombinedEvents(const std::unordered_map>& triggerrecords) const +{ + std::vector events; + + // Search interaction records from all subevents + std::set allInteractions; + for (auto& [subspecification, trgrec] : triggerrecords) { + for (auto rec : trgrec) { + auto eventIR = rec.getBCData(); + if (allInteractions.find(eventIR) == allInteractions.end()) + allInteractions.insert(eventIR); + } + } + + // iterate over all subevents for all bunch crossings + for (auto collision : allInteractions) { + CombinedEvent nextevent; + nextevent.mInteractionRecord = collision; + bool first = true, + hasSubevent = false; + for (auto [subspecification, records] : triggerrecords) { + auto found = std::find_if(records.begin(), records.end(), [&collision](const o2::emcal::TriggerRecord& rec) { return rec.getBCData() == collision; }); + if (found != records.end()) { + hasSubevent = true; + if (first) { + nextevent.mTriggerType = found->getTriggerBits(); + first = false; + } + nextevent.mSubevents.push_back({ subspecification, o2::dataformats::RangeReference(found->getFirstEntry(), found->getNumberOfObjects()) }); + } + } + if (hasSubevent) + events.emplace_back(nextevent); + } + return events; +} + +bool DigitsQcTask::hasConfigValue(const std::string_view key) +{ + if (auto param = mCustomParameters.find(key.data()); param != mCustomParameters.end()) { + return true; + } + return false; +} + +std::string DigitsQcTask::getConfigValue(const std::string_view key) +{ + std::string result; + if (auto param = mCustomParameters.find(key.data()); param != mCustomParameters.end()) { + result = param->second; + } + return result; +} + +std::string DigitsQcTask::getConfigValueLower(const std::string_view key) +{ + auto input = getConfigValue(key); + std::string result; + if (input.length()) { + result = boost::algorithm::to_lower_copy(input); + } + return result; +} + +void DigitsQcTask::DigitsHistograms::initForTrigger(const std::string trigger, bool hasAmpVsCellID, bool hasTimeVsCellID, bool hasHistosCalib2D) +{ + auto histBuilder1D = [trigger](const std::string name, const std::string title, int nbinsx, double xmin, double xmax) -> TH1* { + std::string histname = name + "_" + trigger, + histtitle = title + " " + trigger; + return new TH1D(histname.data(), histtitle.data(), nbinsx, xmin, xmax); + }; + auto histBuilder2D = [trigger](const std::string_view name, const std::string_view title, int nbinsx, double xmin, double xmax, int nbinsy, double ymin, double ymax, bool profile) -> TH2* { + std::string histname = std::string(name.data()) + "_" + trigger, + histtitle = std::string(title.data()) + " " + trigger; + if (profile) + return new TProfile2D(histname.data(), histtitle.data(), nbinsx, xmin, xmax, nbinsy, ymin, ymax); + return new TH2D(histname.data(), histtitle.data(), nbinsx, xmin, xmax, nbinsy, ymin, ymax); + }; + + std::map maxAmps = { { "PHYS", 50. }, { "CAL", 10. } }; + double maxAmp = maxAmps[trigger]; + + bool isPhysTrigger = trigger == "PHYS"; + if (isPhysTrigger) { + if (hasAmpVsCellID) { + mDigitAmplitude = histBuilder2D("digitAmplitudeHG", "Digit Amplitude (High gain)", 80, 0, 16, 17664, -0.5, 17663.5, false); + mDigitAmplitude->SetStats(0); + //mDigitAmplitude[1] = histBuilder2D("digitAmplitudeLG", "Digit Amplitude (Low gain)", 100, 0, 100, 17664, -0.5, 17663.5, false); + if (hasHistosCalib2D) { + mDigitAmplitudeCalib = histBuilder2D("digitAmplitudeHGCalib", "Digit Amplitude (High gain)", 80, 0, 16, 17664, -0.5, 17663.5, false); + mDigitAmplitudeCalib->SetStats(0); + //mDigitAmplitudeCalib[1] = histBuilder2D("digitAmplitudeLGCalib", "Digit Amplitude (Low gain)", 100, 0, 100, 17664, -0.5, 17663.5, false); + } + } + if (hasTimeVsCellID) { + mDigitTime = histBuilder2D("digitTimeHG", "Digit Time (High gain)", 400, -200, 200, 17664, -0.5, 17663.5, false); + mDigitTime->SetStats(0); + // mDigitTime[1] = histBuilder2D("digitTimeLG", "Digit Time (Low gain)", 400, -200, 200, 17664, -0.5, 17663.5, false); + if (hasHistosCalib2D) { + mDigitTimeCalib = histBuilder2D("digitTimeHGCalib", "Digit Time Calib (High gain)", 400, -200, 200, 17664, -0.5, 17663.5, false); + mDigitTimeCalib->SetStats(0); + // mDigitTimeCalib[1] = histBuilder2D("digitTimeLGCalib", "Digit Time Calib (Low gain)", 400, -200, 200, 17664, -0.5, 17663.5, false); + } + } + + mDigitAmpSupermodule = histBuilder2D("digitAmplitudeSupermodule", "Digit amplitude vs. supermodule ID ", 4 * static_cast(maxAmp), 0., maxAmp, 20, -0.5, 19.5, false); + mDigitAmpSupermodule->SetStats(0); + mDigitTimeSupermodule = histBuilder2D("digitTimeSupermodule", "Digit Time vs. supermodule ID ", 600, -400, 800, 20, -0.5, 19.5, false); + mDigitTimeSupermodule->SetStats(0); + if (hasHistosCalib2D) { + mDigitAmpSupermoduleCalib = histBuilder2D("digitAmplitudeSupermoduleCalib", "Digit amplitude (Calib) vs. supermodule ID ", 4 * static_cast(maxAmp), 0., maxAmp, 20, -0.5, 19.5, false); + mDigitAmpSupermoduleCalib->SetStats(0); + mDigitTimeSupermoduleCalib = histBuilder2D("digitTimeSupermoduleCalib", "Digit Time (Calib) vs. supermodule ID (High gain)", 600, -400, 800, 20, -0.5, 19.5, false); + mDigitTimeSupermoduleCalib->SetStats(0); + } + } + + mDigitOccupancy = histBuilder2D("digitOccupancyEMC", "Digit Occupancy EMCAL", 96, -0.5, 95.5, 208, -0.5, 207.5, false); + mDigitOccupancy->SetStats(0); + mDigitOccupancyThr = histBuilder2D("digitOccupancyEMCwThr", Form("Digit Occupancy EMCAL,DCAL with E>%.1f GeV/c", mCellThreshold), 96, -0.5, 95.5, 208, -0.5, 207.5, false); + mDigitOccupancyThr->SetStats(0); + mDigitOccupancyThrBelow = histBuilder2D("digitOccupancyEMCwThrBelow", Form("Digit Occupancy EMCAL,DCAL with E<%.1f GeV/c", mCellThreshold), 96, -0.5, 95.5, 208, -0.5, 207.5, false); + mDigitOccupancyThrBelow->SetStats(0); + + mIntegratedOccupancy = histBuilder2D("digitOccupancyInt", "Digit Occupancy Integrated", 96, -0.5, 95.5, 208, -0.5, 207.5, true); + mIntegratedOccupancy->GetXaxis()->SetTitle("col"); + mIntegratedOccupancy->GetYaxis()->SetTitle("row"); + mIntegratedOccupancy->SetStats(0); + // 1D histograms for showing the integrated spectrum + + mDigitTimeSupermodule_tot = histBuilder1D("digitTime", "Digit Time EMCAL,DCAL", 600, -400, 800); + mDigitTimeSupermoduleEMCAL = histBuilder1D("digitTimeEMCAL", "Digit Time EMCAL", 600, -400, 800); + mDigitTimeSupermoduleDCAL = histBuilder1D("digitTimeDCAL", "Digit Time DCAL", 600, -400, 800); + mDigitAmplitude_tot = histBuilder1D("digitAmplitude", "Digit amplitude in EMCAL,DCAL", 4 * static_cast(maxAmp), 0., maxAmp); + mDigitAmplitudeEMCAL = histBuilder1D("digitAmplitudeEMCAL", "Digit amplitude in EMCAL", 4 * static_cast(maxAmp), 0., maxAmp); + mDigitAmplitudeDCAL = histBuilder1D("digitAmplitudeDCAL", "Digit amplitude in DCAL", 4 * static_cast(maxAmp), 0., maxAmp); + mnumberEvents = histBuilder1D("NumberOfEvents", "Number Of Events", 1, 0.5, 1.5); + if (isPhysTrigger) { + // Phys. trigger: monitor all bunch crossings + for (auto bcID = 0; bcID < 4; bcID++) { + std::array digitTimeBCSM; + for (auto smID = 0; smID < 20; smID++) { + digitTimeBCSM[smID] = histBuilder1D(Form("digitTimeBC%dSM%d", bcID, smID), Form("Digit Time BC%d SM%d", bcID, smID), 600, -400, 800); + } + mDigitTimeBC[bcID] = digitTimeBCSM; + } + } else { + // Calib trigger: Only bc0; + std::array digitTimeBCSM; + for (auto smID = 0; smID < 20; smID++) { + digitTimeBCSM[smID] = histBuilder1D(Form("digitTimeBC0SM%d", smID), Form("Digit Time BC0 SM%d", smID), 600, -400, 800); + } + mDigitTimeBC[0] = digitTimeBCSM; + } +} + +void DigitsQcTask::DigitsHistograms::fillHistograms(const o2::emcal::Cell& digit, bool goodCell, double timecalib, int bcphase) +{ + auto fillOptional1D = [](TH1* hist, double x, double weight = 1.) { + if (hist) + hist->Fill(x, weight); + }; + auto fillOptional2D = [](TH2* hist, double x, double y, double weight = 1.) { + if (hist) + hist->Fill(x, y, weight); + }; + + fillOptional2D(mDigitAmplitude, digit.getEnergy(), digit.getTower()); + //fillOptional2D(mDigitAmplitude[index], digit.getEnergy(), digit.getTower()); + + if (goodCell) { + fillOptional2D(mDigitAmplitudeCalib, digit.getEnergy(), digit.getTower()); + fillOptional2D(mDigitTimeCalib, digit.getTimeStamp() - timecalib, digit.getTower()); + //fillOptional2D(mDigitAmplitudeCalib[index], digit.getEnergy(), digit.getTower()); + //fillOptional2D(mDigitTimeCalib[index], digit.getTimeStamp() - timeoffset, digit.getTower()); + } + fillOptional2D(mDigitTime, digit.getTimeStamp(), digit.getTower()); + //fillOptional2D(mDigitTime[index], digit.getTimeStamp(), digit.getTower()); + + try { + auto [row, col] = mGeometry->GlobalRowColFromIndex(digit.getTower()); + if (digit.getEnergy() > 0) { + fillOptional2D(mDigitOccupancy, col, row); + } + if (digit.getEnergy() > mCellThreshold) { + fillOptional2D(mDigitOccupancyThr, col, row); + } else { + fillOptional2D(mDigitOccupancyThrBelow, col, row); + } + + fillOptional2D(mIntegratedOccupancy, col, row, digit.getEnergy()); + + } catch (o2::emcal::InvalidCellIDException& e) { + ILOG(Error, Support) << "Invalid cell ID: " << e.getCellID() << ENDM; + }; + + try { + auto cellindices = mGeometry->GetCellIndex(digit.getTower()); + auto supermoduleID = std::get<0>(cellindices); + fillOptional2D(mDigitAmpSupermodule, digit.getEnergy(), supermoduleID); + if (digit.getEnergy() > 0.3) + fillOptional2D(mDigitTimeSupermodule, digit.getTimeStamp(), supermoduleID); + if (goodCell) { + fillOptional2D(mDigitAmpSupermoduleCalib, digit.getEnergy(), supermoduleID); + if (digit.getEnergy() > 0.3) + fillOptional2D(mDigitTimeSupermoduleCalib, digit.getTimeStamp() - timecalib, supermoduleID); + } + fillOptional1D(mDigitAmplitude_tot, digit.getEnergy()); //EMCAL+DCAL, shifter + if (digit.getEnergy() > 0.15) + fillOptional1D(mDigitTimeSupermodule_tot, digit.getTimeStamp()); //EMCAL+DCAL shifter + if (supermoduleID < 12) { + + if (digit.getEnergy() > 0.15) + fillOptional1D(mDigitTimeSupermoduleEMCAL, digit.getTimeStamp()); + fillOptional1D(mDigitAmplitudeEMCAL, digit.getEnergy()); //EMCAL + } else { + fillOptional1D(mDigitAmplitudeDCAL, digit.getEnergy()); + if (digit.getEnergy() > 0.15) + fillOptional1D(mDigitTimeSupermoduleDCAL, digit.getTimeStamp()); + } + // bc phase histograms + if (digit.getEnergy() > 0.15) { + auto bchistos = mDigitTimeBC.find(bcphase); + if (bchistos != mDigitTimeBC.end()) { + auto histcontainer = bchistos->second; + histcontainer[supermoduleID]->Fill(digit.getTimeStamp()); + } + } + } catch (o2::emcal::InvalidCellIDException& e) { + ILOG(Info, Support) << "Invalid cell ID: " << e.getCellID() << ENDM; + } +} + +void DigitsQcTask::DigitsHistograms::countEvent() +{ + mnumberEvents->Fill(1); +} + +void DigitsQcTask::DigitsHistograms::startPublishing(o2::quality_control::core::ObjectsManager& manager) +{ + auto publishOptional = [&manager](TH1* hist) { + if (hist) + manager.startPublishing(hist); + }; + + publishOptional(mDigitTime); + publishOptional(mDigitTimeCalib); + publishOptional(mDigitAmplitude); + publishOptional(mDigitAmplitudeCalib); + publishOptional(mDigitAmpSupermodule); + publishOptional(mDigitAmpSupermoduleCalib); + publishOptional(mDigitTimeSupermodule); + publishOptional(mDigitTimeSupermodule_tot); + publishOptional(mDigitTimeSupermoduleEMCAL); + publishOptional(mDigitTimeSupermoduleDCAL); + publishOptional(mDigitTimeSupermoduleCalib); + publishOptional(mDigitAmplitude_tot); + publishOptional(mDigitAmplitudeEMCAL); + publishOptional(mDigitAmplitudeDCAL); + publishOptional(mDigitOccupancy); + publishOptional(mDigitOccupancyThr); + publishOptional(mDigitOccupancyThrBelow); + publishOptional(mIntegratedOccupancy); + publishOptional(mnumberEvents); + for (auto& [bcID, histos] : mDigitTimeBC) { + for (auto hist : histos) + publishOptional(hist); + } + // publishOptional(mEvCounterTF); + // publishOptional(mEvCounterTFPHYS); + // publishOptional(mEvCounterTFCALIB); + // publishOptional(mTFPerCycles); +} + +void DigitsQcTask::DigitsHistograms::reset() +{ + + // for (auto h : mDigitAmplitude) { + // h->Reset(); + // } + // for (auto h : mDigitTime) { + // h->Reset(); + // } + // for (auto h : mDigitAmplitudeCalib) { + // h->Reset(); + // } + // for (auto h : mDigitTimeCalib) { + // h->Reset(); + // } + + auto resetOptional = [](TH1* hist) { + if (hist) + hist->Reset(); + }; + + resetOptional(mDigitTime); + resetOptional(mDigitTimeCalib); + resetOptional(mDigitAmplitude); + resetOptional(mDigitAmplitudeCalib); + resetOptional(mDigitAmpSupermodule); + resetOptional(mDigitAmpSupermoduleCalib); + resetOptional(mDigitTimeSupermodule); + resetOptional(mDigitTimeSupermodule_tot); + resetOptional(mDigitTimeSupermoduleEMCAL); + resetOptional(mDigitTimeSupermoduleDCAL); + resetOptional(mDigitTimeSupermoduleCalib); + resetOptional(mDigitAmplitude_tot); + resetOptional(mDigitAmplitudeEMCAL); + resetOptional(mDigitAmplitudeDCAL); + resetOptional(mDigitOccupancy); + resetOptional(mDigitOccupancyThr); + resetOptional(mDigitOccupancyThrBelow); + resetOptional(mIntegratedOccupancy); + resetOptional(mnumberEvents); + for (auto& [bcID, histos] : mDigitTimeBC) { + for (auto hist : histos) + resetOptional(hist); + } + // resetOptional(mEvCounterTF); + // resetOptional(mEvCounterTFPHYS); + // resetOptional(mEvCounterTFCALIB); + // resetOptional(mTFPerCycles); +} + +void DigitsQcTask::DigitsHistograms::clean() +{ + //for (auto h : mDigitAmplitude) { + // delete h; + //} + // for (auto h : mDigitTime) { + // delete h; + // } + //for (auto h : mDigitAmplitudeCalib) { + // delete h; + //} + //for (auto h : mDigitTimeCalib) { + // delete h; + // } + + auto cleanOptional = [](TObject* hist) { + if (hist) + delete hist; + }; + + cleanOptional(mDigitTime); + cleanOptional(mDigitTimeCalib); + cleanOptional(mDigitAmplitude); + cleanOptional(mDigitAmplitudeCalib); + cleanOptional(mDigitAmpSupermodule); + cleanOptional(mDigitAmpSupermoduleCalib); + cleanOptional(mDigitTimeSupermodule); + cleanOptional(mDigitTimeSupermodule_tot); + cleanOptional(mDigitTimeSupermoduleEMCAL); + cleanOptional(mDigitTimeSupermoduleDCAL); + cleanOptional(mDigitTimeSupermoduleCalib); + cleanOptional(mDigitAmplitude_tot); + cleanOptional(mDigitAmplitudeEMCAL); + cleanOptional(mDigitAmplitudeDCAL); + cleanOptional(mDigitOccupancy); + cleanOptional(mDigitOccupancyThr); + cleanOptional(mDigitOccupancyThrBelow); + cleanOptional(mIntegratedOccupancy); + cleanOptional(mnumberEvents); + for (auto& [bcID, histos] : mDigitTimeBC) { + for (auto hist : histos) + cleanOptional(hist); + } + // cleanOptional(mEvCounterTF); + // cleanOptional(mEvCounterTFPHYS); + // cleanOptional(mEvCounterTFCALIB); + // cleanOptional(mTFPerCycles); + // +} + +} // namespace emcal +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/EMCAL/src/FECRateVisualization.cxx b/Modules/EMCAL/src/FECRateVisualization.cxx new file mode 100644 index 0000000000..b7136a051e --- /dev/null +++ b/Modules/EMCAL/src/FECRateVisualization.cxx @@ -0,0 +1,166 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include "EMCAL/FECRateVisualization.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::emcal +{ + +FECRateVisualization::~FECRateVisualization() +{ +} + +void FECRateVisualization::configure(const boost::property_tree::ptree& config) +{ + auto taskConfig = config.get_child_optional("qc.postprocessing." + getID() + ".configuration"); + if (taskConfig) { + auto cfgMaxRate = taskConfig.get().get_child_optional("MaxRate"); + if (cfgMaxRate) { + mMaxRate = cfgMaxRate->get_value(); + ILOG(Info, Support) << "Found max FEC rate: " << mMaxRate << ENDM; + } + } +} + +void FECRateVisualization::initialize(Trigger, framework::ServiceRegistryRef) +{ + mIndicesConverter.Initialize(); + for (int ism = 0; ism < 20; ism++) { + mSupermoduleCanvas[ism] = std::make_unique(Form("FECRatesSM%d", ism), Form("FEC rates for supermodule %d", ism), 800, 600); + getObjectsManager()->startPublishing(mSupermoduleCanvas[ism].get(), PublicationPolicy::Forever); + } +} + +void FECRateVisualization::update(Trigger t, framework::ServiceRegistryRef services) +{ + std::cout << "Using max rate: " << mMaxRate << std::endl; + std::array colors = { { kRed, kGreen, kBlue, kOrange, kTeal, kMagenta, kCyan, kViolet, kGray, kBlack } }; + std::array markers = { { 20, 21, 22, 24, 25, 26, 27, 28, 29, 30 } }; + + const std::array fecs_small_A = { { 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13 } }, + fecs_small_C = { { 27, 28, 29, 31, 32, 33, 34, 39, 36, 37, 38, 39 } }; + const std::array fecs_DCAL = { { 1, 2, 3, 4, 5, 6, 7, 8, 14, 15, 16, 17, 18, 19, 21, 22, 27, 28, 29, 31, 32, 33, 34, 35 } }; + enum SMType { + FULL, + SMALL_A, + SMALL_C, + DCAL + }; + const std::array smtypes{ { FULL, FULL, FULL, FULL, FULL, FULL, FULL, FULL, FULL, FULL, SMALL_A, SMALL_C, DCAL, DCAL, DCAL, DCAL, DCAL, DCAL, SMALL_C, SMALL_A } }; + + auto& qcdb = services.get(); + auto mo = qcdb.retrieveMO("EMC/MO/CellTrendingDetailEMCAL", "CellTrendingDetailEMCAL", t.timestamp, t.activity); + TTree* datatree = mo ? static_cast(mo->getObject()) : nullptr; + if (!datatree) { + ILOG(Error, Support) << "Tree not found" << ENDM; + return; + } + + for (int smID = 0; smID < 20; smID++) { + int minfec = smID * 40; + mSupermoduleCanvas[smID]->Clear(); + mSupermoduleCanvas[smID]->Divide(2, 2); + mSupermoduleCanvas[smID]->SetTopMargin(0.1); + int currentpad = 0; + TLegend* leg = nullptr; + bool isFirst = true; + for (int ifec = 0; ifec < 40; ifec++) { + if (ifec % 10 == 0) { + currentpad++; + mSupermoduleCanvas[smID]->cd(currentpad); + + leg = new TLegend(0.15, 0.75, 0.89, 0.89); + leg->SetBorderSize(0); + leg->SetFillStyle(0); + leg->SetTextFont(42); + leg->SetNColumns(5); + isFirst = true; + } + bool found = true; + switch (smtypes[smID]) { + case SMType::FULL: + // exclude LEDMON (0) and TRU FECs (10, 20, 30) as they leave no data in the occupancy plot + found = (ifec != 0) && (ifec != 10) && (ifec != 20) && (ifec != 30); + break; + case SMType::SMALL_A: + found = std::find(fecs_small_A.begin(), fecs_small_A.end(), ifec) != fecs_small_A.end(); + break; + case SMType::SMALL_C: + found = std::find(fecs_small_C.begin(), fecs_small_C.end(), ifec) != fecs_small_C.end(); + break; + case SMType::DCAL: + found = std::find(fecs_DCAL.begin(), fecs_DCAL.end(), ifec) != fecs_DCAL.end(); + break; + default: + break; + }; + if (!found) { + continue; + } + int globalfec = ifec + minfec; + const char* drawoptions = (isFirst) ? "pl" : "plsame"; + datatree->Draw(Form("cellOccupancyEMCwThr_PHYS.fecCounts[%d]:time", globalfec), "", drawoptions); + // gPad->GetListOfPrimitives()->ls(); + auto targetgraph = static_cast(gPad->GetPrimitive("Graph")); + targetgraph->SetName(Form("fectrend%d", ifec)); + targetgraph->SetMarkerColor(colors[ifec % 10]); + targetgraph->SetLineColor(colors[ifec % 10]); + targetgraph->SetMarkerStyle(markers[ifec % 10]); + leg->AddEntry(targetgraph, Form("FEC %d", ifec), "lep"); + + if (isFirst) { + auto frame = static_cast(gPad->GetPrimitive("htemp")); + frame->SetName(Form("frame_SM%d_branch_%d", smID, ifec / 10)); + frame->GetXaxis()->SetTimeDisplay(1); + frame->GetXaxis()->SetNdivisions(503); + frame->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + frame->GetXaxis()->SetTimeOffset(0, "gmt"); + frame->GetYaxis()->SetLimits(0., mMaxRate); + frame->SetYTitle("FEC counts / minute"); + frame->SetTitle(Form("Branch %d", ifec / 10)); + leg->Draw(); + isFirst = false; + } + + gPad->Update(); + } + + mSupermoduleCanvas[smID]->cd(); + TLatex* msg = new TLatex(0.35, 0.96, Form("Supermodule %d (%s)", smID, mIndicesConverter.GetOnlineSMIndex(smID).data())); + msg->SetNDC(); + msg->SetTextSize(0.05); + msg->SetTextFont(62); + msg->Draw(); + mSupermoduleCanvas[smID]->Update(); + } +} + +void FECRateVisualization::finalize(Trigger, framework::ServiceRegistryRef) +{ + for (auto& canvas : mSupermoduleCanvas) { + getObjectsManager()->stopPublishing(canvas.get()); + } +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/NumPatchesPerFastORCheck.cxx b/Modules/EMCAL/src/NumPatchesPerFastORCheck.cxx new file mode 100644 index 0000000000..86f9a8a690 --- /dev/null +++ b/Modules/EMCAL/src/NumPatchesPerFastORCheck.cxx @@ -0,0 +1,318 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file NumPatchesPerFastORCheck.cxx +/// \author Sierra Cantway +/// + +#include "EMCAL/NumPatchesPerFastORCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include "EMCALBase/Geometry.h" +#include +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::emcal +{ + +bool compareDescending(const NumPatchesPerFastORCheck::FastORNoiseLevel& noise1, const NumPatchesPerFastORCheck::FastORNoiseLevel& noise2) +{ + if (noise1.mCounts == noise2.mCounts) { + return noise1.mFastORID > noise2.mFastORID; // Sort by mFastORID in descending order if mCounts are equal + } + return noise1.mCounts > noise2.mCounts; // Used to sort by count in descending order +} + +void NumPatchesPerFastORCheck::configure() +{ + // configure nsigma-based checkers + auto nBadSigmaNumPatchesPerFastOR = mCustomParameters.find("BadSigmaNumPatchesPerFastOR"); + if (nBadSigmaNumPatchesPerFastOR != mCustomParameters.end()) { + try { + mBadSigmaNumPatchesPerFastOR = std::stof(nBadSigmaNumPatchesPerFastOR->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nBadSigmaNumPatchesPerFastOR->second << " not a float" << ENDM; + } + } + auto nMedSigmaNumPatchesPerFastOR = mCustomParameters.find("MedSigmaNumPatchesPerFastOR"); + if (nMedSigmaNumPatchesPerFastOR != mCustomParameters.end()) { + try { + mMedSigmaNumPatchesPerFastOR = std::stof(nMedSigmaNumPatchesPerFastOR->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nMedSigmaNumPatchesPerFastOR->second << " not a float" << ENDM; + } + } + + auto logLevelIL = mCustomParameters.find("LogLevelIL"); + if (logLevelIL != mCustomParameters.end()) { + try { + mLogLevelIL = std::stoi(logLevelIL->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << logLevelIL->second << " not an integer" << ENDM; + } + } +} + +Quality NumPatchesPerFastORCheck::check(std::map>* moMap) +{ + auto mo = moMap->begin()->second; + Quality result = Quality::Good; + mNoisyTRUPositions.clear(); + mHighCountTRUPositions.clear(); + std::stringstream messagebuilder; + + std::vector candBadFastORs; + std::vector finalBadFastORs; + std::vector candMedFastORs; + std::vector finalMedFastORs; + + if (mo->getName() == "NumberOfPatchesWithFastOR") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + + // Determine the threshold for bad and medium FastOR candidates + std::vector smcounts; + for (auto ib : ROOT::TSeqI(0, h->GetXaxis()->GetNbins())) { + auto countSM = h->GetBinContent(ib + 1); + if (countSM > 0) { + smcounts.emplace_back(countSM); + } + } + if (!smcounts.size()) { + result = Quality::Medium; + } else { + TRobustEstimator meanfinder; + double mean, sigma; + meanfinder.EvaluateUni(smcounts.size(), smcounts.data(), mean, sigma); + + double thresholdBad = mean + mBadSigmaNumPatchesPerFastOR * sigma, + thresholdMedium = mean + mMedSigmaNumPatchesPerFastOR * sigma; + + // Find the noisy FastOR candidates + for (auto ib : ROOT::TSeqI(0, h->GetXaxis()->GetNbins())) { + if (h->GetBinContent(ib + 1) > thresholdMedium) { + if (result != Quality::Bad) { + result = Quality::Medium; + } + auto [posEta, posPhi] = mTriggerMapping->getPositionInEMCALFromAbsFastORIndex(h->GetXaxis()->GetBinCenter(ib + 1)); + FastORNoiseLevel cand{ static_cast(h->GetBinContent(ib + 1)), static_cast(h->GetXaxis()->GetBinCenter(ib + 1)), static_cast(posPhi), static_cast(posEta), false }; + candMedFastORs.push_back(cand); + } + if (h->GetBinContent(ib + 1) > thresholdBad) { + result = Quality::Bad; + auto [posEta, posPhi] = mTriggerMapping->getPositionInEMCALFromAbsFastORIndex(h->GetXaxis()->GetBinCenter(ib + 1)); + FastORNoiseLevel cand{ static_cast(h->GetBinContent(ib + 1)), static_cast(h->GetXaxis()->GetBinCenter(ib + 1)), static_cast(posPhi), static_cast(posEta), false }; + candBadFastORs.push_back(cand); + } + } + + // Sort the noisy FastOR candidates in descending counts order + std::sort(candBadFastORs.begin(), candBadFastORs.end(), compareDescending); + std::sort(candMedFastORs.begin(), candMedFastORs.end(), compareDescending); + + // Array to store whether eta,phi position should be removed + const int rows = 48; // eta 0-47 + const int cols = 104; // phi 0-103 + bool bad_ignore[rows][cols] = { false }; + bool med_ignore[rows][cols] = { false }; + + // Loop over the candidate Bad FastORs to find the smaller nearby FastORs + for (std::vector::iterator i = candBadFastORs.begin(); i != candBadFastORs.end(); i++) { + + if (i->mRejected) { + continue; + } + + // Check at the begining if the element's phi eta should be ignored and if so set rejected = true and skip it. + int high_posEta = i->mPosGlobalEta; + int high_posPhi = i->mPosGlobalPhi; + + if (bad_ignore[high_posEta][high_posPhi] == true) { + i->mRejected = true; + continue; + } + + // Ignore what is near the current highest FastOR + for (int eta = high_posEta - 1; eta <= high_posEta + 1; eta++) { + for (int phi = high_posPhi - 1; phi <= high_posPhi + 1; phi++) { + if (eta >= 0 && eta < rows && phi >= 0 && phi < cols && !(eta == high_posEta && phi == high_posPhi)) { + bad_ignore[eta][phi] = true; + } + } + } + + // Save the final bad FastORs + FastORNoiseLevel finalFastOR{ i->mCounts, i->mFastORID, i->mPosGlobalPhi, i->mPosGlobalEta, i->mRejected }; + finalBadFastORs.push_back(finalFastOR); + } + + // Save the positions of the final Bad FastORs and display the error message + for (std::vector::iterator itFinal = finalBadFastORs.begin(); itFinal != finalBadFastORs.end(); itFinal++) { + auto [truID, fastorTRU] = mTriggerMapping->getTRUFromAbsFastORIndex(itFinal->mFastORID); + auto [truID1, posEta, posPhi] = mTriggerMapping->getPositionInTRUFromAbsFastORIndex(itFinal->mFastORID); + FastORNoiseInfo obj{ itFinal->mCounts, static_cast(truID), static_cast(fastorTRU), static_cast(posPhi), static_cast(posEta) }; + std::string errorMessage = "TRU " + std::to_string(truID) + " has a noisy FastOR at position " + std::to_string(fastorTRU) + " (eta " + std::to_string(posEta) + ", phi " + std::to_string(posPhi) + ") in TRU. (" + std::to_string(itFinal->mCounts) + "counts)"; + messagebuilder << errorMessage << std::endl; + if (mLogLevelIL > 1) { + ILOG(Error, Support) << errorMessage << ENDM; + } + mNoisyTRUPositions.insert(obj); + } + + // Loop over the candidate Med FastORs to find the smaller nearby FastORs + for (std::vector::iterator i = candMedFastORs.begin(); i != candMedFastORs.end(); i++) { + + if (i->mRejected) { + continue; + } + + // Check at the begining if the element's phi eta should be ignored and if so set rejected = true and skip it. + int high_posEta = i->mPosGlobalEta; + int high_posPhi = i->mPosGlobalPhi; + + if (med_ignore[high_posEta][high_posPhi] == true) { + i->mRejected = true; + continue; + } + + // Ignore what is near the current highest FastOR + for (int eta = high_posEta - 1; eta <= high_posEta + 1; eta++) { + for (int phi = high_posPhi - 1; phi <= high_posPhi + 1; phi++) { + if (eta >= 0 && eta < rows && phi >= 0 && phi < cols && !(eta == high_posEta && phi == high_posPhi)) { + med_ignore[eta][phi] = true; + } + } + } + + // Save the final med FastORs + FastORNoiseLevel finalFastOR{ i->mCounts, i->mFastORID, i->mPosGlobalPhi, i->mPosGlobalEta, i->mRejected }; + finalMedFastORs.push_back(finalFastOR); + } + + // Save the positions of the final Med FastORs and display the error message + for (std::vector::iterator itFinal = finalMedFastORs.begin(); itFinal != finalMedFastORs.end(); itFinal++) { + auto [truID, fastorTRU] = mTriggerMapping->getTRUFromAbsFastORIndex(itFinal->mFastORID); + auto [truID1, posEta, posPhi] = mTriggerMapping->getPositionInTRUFromAbsFastORIndex(itFinal->mFastORID); + FastORNoiseInfo obj{ itFinal->mCounts, static_cast(truID), static_cast(fastorTRU), static_cast(posPhi), static_cast(posEta) }; + std::string errorMessage = "TRU " + std::to_string(truID) + " has a high rate in FastOR at position " + std::to_string(fastorTRU) + " (eta " + std::to_string(posEta) + ", phi " + std::to_string(posPhi) + ") in TRU. (" + std::to_string(itFinal->mCounts) + "counts)"; + messagebuilder << errorMessage << std::endl; + if (mLogLevelIL > 2) { + ILOG(Warning, Support) << errorMessage << ENDM; + } + mHighCountTRUPositions.insert(obj); + } + } + } + } + result.addFlag(o2::quality_control::FlagTypeFactory::BadEMCalorimetry(), messagebuilder.str()); + + return result; +} + +void NumPatchesPerFastORCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "NumberOfPatchesWithFastOR") { + auto* h = dynamic_cast(mo->getObject()); + if (checkResult == Quality::Good) { + TPaveText* msg = new TPaveText(0.12, 0.84, 0.88, 0.94, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + msg->AddText("Data OK: No Outlier Noisy FastORs"); + msg->SetFillColor(kGreen); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + TLatex* msg; + msg = new TLatex(0.15, 0.84, "#color[2]{Error: Noisy TRU(s)}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + int iErr = 0; + for (const auto& noiseinfo : mNoisyTRUPositions) { + stringstream errorMessageIndiv; + errorMessageIndiv << "Position " << noiseinfo.mFastORIndex << " (eta " << noiseinfo.mPosEta << ", phi " << noiseinfo.mPosPhi << ") in TRU " << noiseinfo.mTRUIndex << " is noisy. (" << noiseinfo.mCounts << " counts)" << std::endl; + msg = new TLatex(0.15, 0.8 - iErr / 25., errorMessageIndiv.str().c_str()); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + if (mLogLevelIL > 0) { + ILOG(Error, Support) << errorMessageIndiv.str() << ENDM; + } + iErr++; + } + for (const auto& noiseinfo : mHighCountTRUPositions) { + stringstream errorMessageIndiv; + errorMessageIndiv << "Position " << noiseinfo.mFastORIndex << " (eta " << noiseinfo.mPosEta << ", phi " << noiseinfo.mPosPhi << ") in TRU " << noiseinfo.mTRUIndex << " has high counts. (" << noiseinfo.mCounts << " counts)" << std::endl; + msg = new TLatex(0.15, 0.8 - iErr / 25., errorMessageIndiv.str().c_str()); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kOrange); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + if (mLogLevelIL > 0) { + ILOG(Warning, Support) << errorMessageIndiv.str() << ENDM; + } + iErr++; + } + } else if (checkResult == Quality::Medium) { + TLatex* msg; + msg = new TLatex(0.15, 0.84, "#color[2]{Error: High rate TRU(s)}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + int iErr = 0; + for (const auto& noiseinfo : mHighCountTRUPositions) { + stringstream errorMessageIndiv; + errorMessageIndiv << "Position " << noiseinfo.mFastORIndex << " (eta " << noiseinfo.mPosEta << ", phi " << noiseinfo.mPosPhi << ") in TRU " << noiseinfo.mTRUIndex << " has high counts. (" << noiseinfo.mCounts << " counts)" << std::endl; + msg = new TLatex(0.15, 0.8 - iErr / 25., errorMessageIndiv.str().c_str()); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kOrange); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + if (mLogLevelIL > 0) { + ILOG(Warning, Support) << errorMessageIndiv.str() << ENDM; + } + iErr++; + } + } + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/NumPhysTriggCheck.cxx b/Modules/EMCAL/src/NumPhysTriggCheck.cxx new file mode 100644 index 0000000000..428001f544 --- /dev/null +++ b/Modules/EMCAL/src/NumPhysTriggCheck.cxx @@ -0,0 +1,113 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include "EMCAL/NumPhysTriggCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::emcal +{ + +void NumPhysTriggCheck::configure() +{ + // configure threshold-based checkers for bad quality + auto fracToMaxGood = mCustomParameters.find("FracToMaxGood"); + if (fracToMaxGood != mCustomParameters.end()) { + try { + mFracToMaxGood = std::stof(fracToMaxGood->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", fracToMaxGood->second.data()) << ENDM; + } + } +} + +Quality NumPhysTriggCheck::check(std::map>* moMap) +{ + auto mo = moMap->begin()->second; + Quality result = Quality::Good; + + if (mo->getName().find("NPhysTriggersTFSlice") != std::string::npos) { + auto Can = dynamic_cast(mo->getObject()); + if (Can == nullptr) { + return Quality::Null; + } + TGraph* gr = nullptr; + TList* primitives = Can->GetListOfPrimitives(); + for (TObject* obj : *primitives) { + if (obj->InheritsFrom("TGraph")) { + gr = (TGraph*)obj; + break; + } + } + if (gr == nullptr) { + return Quality::Null; + } + if (gr->GetN() == 0) { + return Quality::Bad; + } + double maxVal = 0.; + for (int i = 0; i < gr->GetN(); ++i) { + if (gr->GetPointY(i) > maxVal) { + maxVal = gr->GetPointY(i); + } + } + double currentVal = gr->GetPointY(gr->GetN() - 1); + if (currentVal < mFracToMaxGood * maxVal) { + result = Quality::Bad; + } + } + + return result; +} + +void NumPhysTriggCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName().find("NPhysTriggersTFSlice") != std::string::npos) { + auto Can = dynamic_cast(mo->getObject()); + if (Can == nullptr) { + return; + } + + Can->cd(); + TPaveText* msg = new TPaveText(0.17, 0.2, 0.5, 0.3, "NDC"); + msg->SetName(Form("%s_msg", mo->GetName())); + + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("Data quality: GOOD"); + msg->SetFillColor(kGreen); + msg->Draw("same"); + Can->Update(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad << ENDM"; + msg->Clear(); + msg->AddText("Data quality: BAD"); + msg->SetFillColor(kRed); + msg->Draw("same"); + Can->Update(); + } + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/OccupancyReductor.cxx b/Modules/EMCAL/src/OccupancyReductor.cxx new file mode 100644 index 0000000000..e3f7b4a015 --- /dev/null +++ b/Modules/EMCAL/src/OccupancyReductor.cxx @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include "TH2.h" +#include "EMCALBase/Geometry.h" +#include "EMCAL/OccupancyReductor.h" +#include "MathUtils/Utils.h" + +using namespace o2::quality_control_modules::emcal; + +OccupancyReductor::OccupancyReductor() : ReductorTObject(), mGeometry(nullptr), mStats() +{ + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); +} + +void* OccupancyReductor::getBranchAddress() +{ + return &mStats; +} + +const char* OccupancyReductor::getBranchLeafList() +{ + return "totalCounts/D:average:RMS:smCounts[20]:averageSM[20]:RMSSM[20]"; +} + +void OccupancyReductor::update(TObject* obj) +{ + memset(mStats.mCountSM, 0, sizeof(double) * 20); + memset(mStats.mAverageSM, 0, sizeof(double) * 20); + memset(mStats.mRMSSM, 0, sizeof(double) * 20); + std::array statAccumulatorsSM; + TH2* digitOccupancyHistogram = static_cast(obj); + o2::math_utils::StatAccumulator statAccumulatorTotal; + mStats.mCountTotal = digitOccupancyHistogram->GetEntries(); + for (int icol = 0; icol < digitOccupancyHistogram->GetXaxis()->GetNbins(); icol++) { + for (int irow = 0; irow < digitOccupancyHistogram->GetYaxis()->GetNbins(); irow++) { + double count = digitOccupancyHistogram->GetBinContent(icol + 1, irow + 1); + if (count) { + statAccumulatorTotal.add(count); + auto cellindex = mGeometry->GetCellIndexFromGlobalRowCol(irow, icol); + auto smod = std::get<0>(cellindex); + mStats.mCountSM[smod] += count; + statAccumulatorsSM[smod].add(count); + } + } + } + auto [mean, rms] = statAccumulatorTotal.getMeanRMS2(); + mStats.mAverage = mean; + mStats.mRMS = rms; + for (int ism = 0; ism < 20; ism++) { + auto [meanSM, rmsSM] = statAccumulatorsSM[ism].getMeanRMS2(); + mStats.mAverageSM[ism] = meanSM; + mStats.mRMSSM[ism] = rmsSM; + } +} \ No newline at end of file diff --git a/Modules/EMCAL/src/OccupancyToFECReductor.cxx b/Modules/EMCAL/src/OccupancyToFECReductor.cxx new file mode 100644 index 0000000000..89a794fd74 --- /dev/null +++ b/Modules/EMCAL/src/OccupancyToFECReductor.cxx @@ -0,0 +1,75 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include "TH2.h" +#include "EMCALReconstruction/Channel.h" +#include "EMCALBase/Geometry.h" +#include "MathUtils/Utils.h" +#include "EMCAL/OccupancyToFECReductor.h" +#include + +using namespace o2::quality_control_modules::emcal; + +OccupancyToFECReductor::OccupancyToFECReductor() : ReductorTObject(), mGeometry(nullptr), mStats() +{ + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); +} + +void* OccupancyToFECReductor::getBranchAddress() +{ + return &mStats; +} + +const char* OccupancyToFECReductor::getBranchLeafList() +{ + return "fecCounts[800]/D:averageFEC[800]:RMSFEC[800]"; +} + +void OccupancyToFECReductor::update(TObject* obj) +{ + memset(mStats.mCountFEC, 0, sizeof(double) * 800); + memset(mStats.mAverageFEC, 0, sizeof(double) * 800); + memset(mStats.mRMSFEC, 0, sizeof(double) * 800); + TH2* digitOccupancyHistogram = dynamic_cast(obj); + if (!digitOccupancyHistogram) { + ILOG(Error, Support) << "Object " << obj->GetName() << " not a proper occupancy histogram, or does not exist. Not possible to analyse" << ENDM; + return; + } + std::array statAccumulatorsSM; + o2::math_utils::StatAccumulator statAccumulatorTotal; + for (int icol = 0; icol < digitOccupancyHistogram->GetXaxis()->GetNbins(); icol++) { + for (int irow = 0; irow < digitOccupancyHistogram->GetYaxis()->GetNbins(); irow++) { + double count = digitOccupancyHistogram->GetBinContent(icol + 1, irow + 1); + if (count) { + statAccumulatorTotal.add(count); + try { + auto [smod, mod, phimod, etamod] = mGeometry->GetCellIndexFromGlobalRowCol(irow, icol); + auto absCellID = mGeometry->GetCellAbsIDFromGlobalRowCol(irow, icol); + auto [ddl, onlinerow, onlinecol] = mGeometry->getOnlineID(absCellID); + auto hwaddress = mMapper.getMappingForDDL(ddl).getHardwareAddress(onlinerow, onlinecol, o2::emcal::ChannelType_t::HIGH_GAIN); + auto localfec = mMapper.getFEEForChannelInDDL(ddl, o2::emcal::Channel::getFecIndexFromHwAddress(hwaddress), o2::emcal::Channel::getBranchIndexFromHwAddress(hwaddress)); + auto globalfec = smod * 40 + localfec; + mStats.mCountFEC[globalfec] += count; + statAccumulatorsSM[globalfec].add(count); + } catch (std::exception& e) { + ILOG(Error, Support) << e.what() << ENDM; + } + } + } + } + auto [mean, rms] = statAccumulatorTotal.getMeanRMS2(); + for (int ism = 0; ism < 800; ism++) { + auto [meanSM, rmsSM] = statAccumulatorsSM[ism].getMeanRMS2(); + mStats.mAverageFEC[ism] = meanSM; + mStats.mRMSFEC[ism] = rmsSM; + } +} \ No newline at end of file diff --git a/Modules/EMCAL/src/PayloadPerEventDDLCheck.cxx b/Modules/EMCAL/src/PayloadPerEventDDLCheck.cxx new file mode 100644 index 0000000000..853d6bdf15 --- /dev/null +++ b/Modules/EMCAL/src/PayloadPerEventDDLCheck.cxx @@ -0,0 +1,110 @@ +#include "QualityControl/MonitorObject.h" +#include "EMCAL/PayloadPerEventDDLCheck.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Quality.h" + +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::emcal +{ + +// configure threshold-based checkers +void PayloadPerEventDDLCheck::configure() +{ + auto nChiSqMedPayloadThresh = mCustomParameters.find("ChiSqMedPayloadThresh"); + if (nChiSqMedPayloadThresh != mCustomParameters.end()) { + try { + mChiSqMedPayloadThresh = std::stod(nChiSqMedPayloadThresh->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nChiSqMedPayloadThresh->second << " not a double" << ENDM; + } + } + + auto nBadChiSqLowPayloadThresh = mCustomParameters.find("BadChiSqLowPayloadThresh"); + if (nBadChiSqLowPayloadThresh != mCustomParameters.end()) { + try { + mChiSqBadLowPayloadThresh = std::stod(nBadChiSqLowPayloadThresh->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nBadChiSqLowPayloadThresh->second << " not a double" << ENDM; + } + } +} + +Quality PayloadPerEventDDLCheck::check(std::map>* moMap) +{ + if (!mCalibReference) { + // Load reference histogram from the CCDB + mCalibReference = this->retrieveConditionAny("EMC/QCREF/PayloadSizeCheck"); + } + + auto mo = moMap->begin()->second; + Quality result = Quality::Good; + + if (mo->getName() == "PayloadSizePerDDL") { + + auto* h = dynamic_cast(mo->getObject()); + + for (int i = 1; i <= h->GetNbinsX(); i++) { + std::unique_ptr h_y(h->ProjectionY(Form("h_y_%d", i), i, i)); + h_y->Scale(1. / h_y->Integral()); + std::unique_ptr h_y_ref(mCalibReference->ProjectionY(Form("h_y_ref_%d", i), i, i)); + h_y_ref->Scale(1. / h_y_ref->Integral()); + Double_t chisq_val = h_y->Chi2Test(h_y_ref.get(), "UU NORM CHI2/NDF"); + + if (chisq_val > mChiSqMedPayloadThresh) { + result = Quality::Medium; + continue; + } + if (mChiSqBadLowPayloadThresh < chisq_val) { + result = Quality::Bad; + break; + } + } + } + return result; +} + +void PayloadPerEventDDLCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "PayloadSizePerDDL") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("DATA OK!"); + msg->SetFillColor(kGreen); + // + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red" << ENDM; + msg->Clear(); + msg->AddText("Suspicious payload size detected."); + msg->AddText("If NOT a technical run,"); + msg->AddText("call EMCAL on-call."); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + msg->Clear(); + msg->AddText("WARNING: Deviation from expectations. Keep monitoring"); + ILOG(Debug, Devel) << "Quality::medium, setting to orange" << ENDM; + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/PedestalChannelCheck.cxx b/Modules/EMCAL/src/PedestalChannelCheck.cxx new file mode 100644 index 0000000000..a9c96be80a --- /dev/null +++ b/Modules/EMCAL/src/PedestalChannelCheck.cxx @@ -0,0 +1,200 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalChannelCheck.cxx +/// \author Sierra Cantway +/// + +#include "EMCAL/PedestalChannelCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::emcal +{ + +void PedestalChannelCheck::configure() +{ + // configure threshold-based checkers for bad quality + auto nBadPedestalChannelADCRangeLow = mCustomParameters.find("BadPedestalChannelADCRangeLow"); + if (nBadPedestalChannelADCRangeLow != mCustomParameters.end()) { + try { + mBadPedestalChannelADCRangeLow = std::stof(nBadPedestalChannelADCRangeLow->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nBadPedestalChannelADCRangeLow->second.data()) << ENDM; + } + } + + auto nBadPedestalChannelADCRangeHigh = mCustomParameters.find("BadPedestalChannelADCRangeHigh"); + if (nBadPedestalChannelADCRangeHigh != mCustomParameters.end()) { + try { + mBadPedestalChannelADCRangeHigh = std::stof(nBadPedestalChannelADCRangeHigh->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nBadPedestalChannelADCRangeHigh->second.data()) << ENDM; + } + } + + auto nBadPedestalChannelOverMeanThreshold = mCustomParameters.find("BadPedestalChannelOverMeanThreshold"); + if (nBadPedestalChannelOverMeanThreshold != mCustomParameters.end()) { + try { + mBadPedestalChannelOverMeanThreshold = std::stof(nBadPedestalChannelOverMeanThreshold->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nBadPedestalChannelOverMeanThreshold->second.data()) << ENDM; + } + } + + // configure threshold-based checkers for medium quality + auto nMedPedestalChannelADCRangeLow = mCustomParameters.find("MedPedestalChannelADCRangeLow"); + if (nMedPedestalChannelADCRangeLow != mCustomParameters.end()) { + try { + mMedPedestalChannelADCRangeLow = std::stof(nMedPedestalChannelADCRangeLow->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nMedPedestalChannelADCRangeLow->second.data()) << ENDM; + } + } + + auto nMedPedestalChannelADCRangeHigh = mCustomParameters.find("MedPedestalChannelADCRangeHigh"); + if (nMedPedestalChannelADCRangeHigh != mCustomParameters.end()) { + try { + mMedPedestalChannelADCRangeHigh = std::stof(nMedPedestalChannelADCRangeHigh->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nMedPedestalChannelADCRangeHigh->second.data()) << ENDM; + } + } + + auto nMedPedestalChannelOverMeanThreshold = mCustomParameters.find("MedPedestalChannelOverMeanThreshold"); + if (nMedPedestalChannelOverMeanThreshold != mCustomParameters.end()) { + try { + mMedPedestalChannelOverMeanThreshold = std::stof(nMedPedestalChannelOverMeanThreshold->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nMedPedestalChannelOverMeanThreshold->second.data()) << ENDM; + } + } + + // configure nsigma-based checkers + auto nSigmaPedestalChannel = mCustomParameters.find("SigmaPedestalChannel"); + if (nSigmaPedestalChannel != mCustomParameters.end()) { + try { + mSigmaPedestalChannel = std::stof(nSigmaPedestalChannel->second); + } catch (std::exception& e) { + ILOG(Error, Support) << fmt::format("Value {} not a float", nSigmaPedestalChannel->second.data()) << ENDM; + } + } +} + +Quality PedestalChannelCheck::check(std::map>* moMap) +{ + auto mo = moMap->begin()->second; + Quality result = Quality::Good; + std::stringstream messagebuilder; + + std::array channelHists = { { "mPedestalChannelFECHG", "mPedestalChannelFECLG", "mPedestalChannelLEDMONHG", "mPedestalChannelLEDMONLG" } }; + if (std::find(channelHists.begin(), channelHists.end(), mo->getName()) != channelHists.end()) { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + std::vector smcounts; + for (auto ib : ROOT::TSeqI(0, h->GetXaxis()->GetNbins())) { + auto countSM = h->GetBinContent(ib + 1); + if (countSM > 0) { + smcounts.emplace_back(countSM); + } + } + if (!smcounts.size()) { + result = Quality::Medium; + } else { + TRobustEstimator meanfinder; + double mean, sigma; + meanfinder.EvaluateUni(smcounts.size(), smcounts.data(), mean, sigma); + int outside_counts = 0.0; + if (mean < mBadPedestalChannelADCRangeLow || mean > mBadPedestalChannelADCRangeHigh) { + result = Quality::Bad; + messagebuilder << "Error: Average Pedestal outside limits (" << mBadPedestalChannelADCRangeLow << "-" << mBadPedestalChannelADCRangeHigh << ") " << std::endl; + } else if (mean < mMedPedestalChannelADCRangeLow || mean > mMedPedestalChannelADCRangeHigh) { + result = Quality::Medium; + messagebuilder << "Warning: Average Pedestal outside limits (" << mMedPedestalChannelADCRangeLow << "-" << mMedPedestalChannelADCRangeHigh << ") " << std::endl; + } + + for (auto ib : ROOT::TSeqI(0, h->GetXaxis()->GetNbins())) { + if (h->GetBinContent(ib + 1) > (mean + mSigmaPedestalChannel * sigma)) { + outside_counts += 1; + } + } + Float_t out_counts_frac = (float)(outside_counts) / (float)(h->GetXaxis()->GetNbins()); + + if (out_counts_frac > mBadPedestalChannelOverMeanThreshold) { + result = Quality::Bad; + messagebuilder << "Error: Fraction of Outliers Above " << mMedPedestalChannelOverMeanThreshold << std::endl; + } else if (out_counts_frac > mMedPedestalChannelOverMeanThreshold) { + result = Quality::Medium; + messagebuilder << "Warning: Fraction of Outliers Above " << mMedPedestalChannelOverMeanThreshold << std::endl; + } + } + } + } + + result.addFlag(o2::quality_control::FlagTypeFactory::BadEMCalorimetry(), messagebuilder.str()); + + return result; +} + +void PedestalChannelCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + std::array channelHists = { { "mPedestalChannelFECHG", "mPedestalChannelFECLG", "mPedestalChannelLEDMONHG", "mPedestalChannelLEDMONLG" } }; + if (std::find(channelHists.begin(), channelHists.end(), mo->getName()) != channelHists.end()) { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.12, 0.84, 0.88, 0.94, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + msg->Clear(); + msg->AddText("Data OK: Average Pedestal & Fraction of Outliers Within Limits"); + msg->SetFillColor(kGreen); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + stringstream msg_bad; + msg_bad << "Error: Average Pedestal outside limits (" << mBadPedestalChannelADCRangeLow << "-" << mBadPedestalChannelADCRangeHigh << ") or Fraction of Outliers Above " << mBadPedestalChannelOverMeanThreshold << std::endl; + msg->Clear(); + msg->AddText(msg_bad.str().c_str()); + msg->AddText("Call EMCAL oncall"); + msg->SetFillColor(kRed); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + stringstream msg_med; + msg_med << "Warning: Average Pedestal outside limits (" << mMedPedestalChannelADCRangeLow << "-" << mMedPedestalChannelADCRangeHigh << ") or Fraction of Outliers Above " << mMedPedestalChannelOverMeanThreshold << std::endl; + msg->Clear(); + msg->AddText(msg_med.str().c_str()); + msg->SetFillColor(kOrange); + msg->Draw(); + } + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/PedestalTask.cxx b/Modules/EMCAL/src/PedestalTask.cxx new file mode 100644 index 0000000000..5ad59f8d2a --- /dev/null +++ b/Modules/EMCAL/src/PedestalTask.cxx @@ -0,0 +1,156 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalTask.cxx +/// \author Markus Fasel +/// + +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "EMCAL/PedestalTask.h" +#include +#include +#include +#include + +namespace o2::quality_control_modules::emcal +{ + +PedestalTask::~PedestalTask() +{ + delete mPedestalChannelFECHG; + delete mPedestalChannelFECLG; + delete mPedestalChannelLEDMONHG; + delete mPedestalChannelLEDMONLG; + + delete mPedestalPositionFECHG; + delete mPedestalPositionFECLG; + delete mPedestalPositionLEDMONHG; + delete mPedestalPositionLEDMONLG; +} + +void PedestalTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + const int NFECCHANNELS = 17664, + NLEDMONCHANNELS = 480, + NCOLSFEC = 96, + NROWSFEC = 208, + NCOLSLEDMON = 48, + NROWSLEDMON = 10; + mPedestalChannelFECHG = new TH1D("mPedestalChannelFECHG", "Pedestals (FEC, HG); tower ID; Pedestal (ADC)", NFECCHANNELS, -0.5, NFECCHANNELS - 0.5); + mPedestalChannelFECLG = new TH1D("mPedestalChannelFECLG", "Pedestals (FEC, LG); tower ID; Pedestal (ADC)", NFECCHANNELS, -0.5, NFECCHANNELS - 0.5); + mPedestalChannelLEDMONHG = new TH1D("mPedestalChannelLEDMONHG", "Pedestals (LEDMON, HG); tower ID; Pedestal (ADC)", NLEDMONCHANNELS, -0.5, NLEDMONCHANNELS - 0.5); + mPedestalChannelLEDMONLG = new TH1D("mPedestalChannelLEDMONLG", "Pedestals (LEDMON, LG); tower ID; Pedestal (ADC)", NLEDMONCHANNELS, -0.5, NLEDMONCHANNELS - 0.5); + + mPedestalPositionFECHG = new TH2D("mPedestalPositionFECHG", "Pedestals (FEC, HG); column; row", NCOLSFEC, -0.5, NCOLSFEC - 0.5, NROWSFEC, -0.5, NROWSFEC - 0.5); + mPedestalPositionFECLG = new TH2D("mPedestalPositionFECLG", "Pedestals (FEC, LG); column; row", NCOLSFEC, -0.5, NCOLSFEC - 0.5, NROWSFEC, -0.5, NROWSFEC - 0.5); + mPedestalPositionLEDMONHG = new TH2D("mPedestalPositionLEDMONHG", "Pedestals (LEDMON, HG); column; row", NCOLSLEDMON, -0.5, NCOLSLEDMON - 0.5, NROWSLEDMON, -0.5, NROWSLEDMON - 0.5); + mPedestalPositionLEDMONLG = new TH2D("mPedestalPositionLEDMONLG", "Pedestals (LEDMON, LG); column; row", NCOLSLEDMON, -0.5, NCOLSLEDMON - 0.5, NROWSLEDMON, -0.5, NROWSLEDMON - 0.5); + + getObjectsManager()->startPublishing(mPedestalChannelFECHG); + getObjectsManager()->startPublishing(mPedestalChannelFECLG); + getObjectsManager()->startPublishing(mPedestalChannelLEDMONHG); + getObjectsManager()->startPublishing(mPedestalChannelLEDMONLG); + getObjectsManager()->startPublishing(mPedestalPositionFECHG); + getObjectsManager()->startPublishing(mPedestalPositionFECLG); + getObjectsManager()->startPublishing(mPedestalPositionLEDMONHG); + getObjectsManager()->startPublishing(mPedestalPositionLEDMONLG); + + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); +} + +void PedestalTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; +} + +void PedestalTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + reset(); +} + +void PedestalTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + ILOG(Debug, Support) << "Start monitoring data" << ENDM; + bool hasPedestalsCCDB = false; + for (auto&& input : o2::framework::InputRecordWalker(ctx.inputs())) { + // get message header + if (input.header != nullptr && input.payload != nullptr) { + auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(input); + const auto* header = o2::framework::DataRefUtils::getHeader(input); + if (payloadSize) { + if ((strcmp(header->dataOrigin.str, "CLP") == 0) && (strcmp(header->dataDescription.str, "EMC_PEDCALIB") == 0)) { + ILOG(Info, Support) << "Pedestals for Pads found" << ENDM; + hasPedestalsCCDB = true; + } + } + } + } + + if (hasPedestalsCCDB) { + auto pedestals = o2::framework::DataRefUtils::as>(ctx.inputs().get("peds")); + if (pedestals) { + mNumberObjectsFetched++; + for (int ichan = 0; ichan < mPedestalChannelFECHG->GetXaxis()->GetNbins(); ichan++) { + auto pedestalLow = pedestals->getPedestalValue(ichan, true, false), + pedestalHigh = pedestals->getPedestalValue(ichan, false, false); + mPedestalChannelFECHG->SetBinContent(ichan + 1, pedestalHigh); + mPedestalChannelFECLG->SetBinContent(ichan + 1, pedestalLow); + auto [row, col] = mGeometry->GlobalRowColFromIndex(ichan); + mPedestalPositionFECHG->SetBinContent(col + 1, row + 1, pedestalHigh); + mPedestalPositionFECLG->SetBinContent(col + 1, row + 1, pedestalLow); + } + for (auto ichan = 0; ichan < mPedestalChannelLEDMONHG->GetXaxis()->GetNbins(); ichan++) { + auto pedestalLow = pedestals->getPedestalValue(ichan, true, true), + pedestalHigh = pedestals->getPedestalValue(ichan, false, true); + mPedestalChannelLEDMONHG->SetBinContent(ichan + 1, pedestalHigh); + mPedestalChannelLEDMONLG->SetBinContent(ichan + 1, pedestalLow); + int col = ichan % 48, + row = ichan / 48; + mPedestalPositionLEDMONHG->SetBinContent(col + 1, row + 1, pedestalHigh); + mPedestalPositionLEDMONLG->SetBinContent(col + 1, row + 1, pedestalLow); + } + } + } + ILOG(Info, Support) << "Number of CCDB fetches of pedestal objects: " << mNumberObjectsFetched << ENDM; +} + +void PedestalTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void PedestalTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void PedestalTask::reset() +{ + mPedestalChannelFECHG->Reset(); + mPedestalChannelFECLG->Reset(); + mPedestalChannelLEDMONHG->Reset(); + mPedestalChannelLEDMONLG->Reset(); + + mPedestalPositionFECHG->Reset(); + mPedestalPositionFECLG->Reset(); + mPedestalPositionLEDMONHG->Reset(); + mPedestalPositionLEDMONLG->Reset(); + + mNumberObjectsFetched = 0; +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/RawCheck.cxx b/Modules/EMCAL/src/RawCheck.cxx new file mode 100644 index 0000000000..970307d553 --- /dev/null +++ b/Modules/EMCAL/src/RawCheck.cxx @@ -0,0 +1,591 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include +#include "EMCAL/RawCheck.h" +#include "QualityControl/stringUtils.h" + +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::emcal +{ + +void RawCheck::configure() +{ + // switch on/off messages on the infoLogger + loadConfigValueBool("MessageBunchMinAmpSM", mIlMessageBunchMinAmpCheckSM); + loadConfigValueBool("MessageBunchMinAmpDetector", mIlMessageBunchMinAmpCheckDetector); + loadConfigValueBool("MessageBunchMinAmpFull", mIlMessageBunchMinAmpCheckFull); + loadConfigValueBool("MessageErrorCheck", mILMessageRawErrorCheck); + loadConfigValueBool("MessageNoisyFECCheck", mILMessageNoisyFECCheck); + loadConfigValueBool("MessagePayloadSizeCheck", mILMessagePayloadSizeCheck); + // configure nsigma sigma-based checkers + loadConfigValueDouble("SigmaFECMaxPayload", mNsigmaFECMaxPayload); + loadConfigValueDouble("SigmaPayloadSize", mNsigmaPayloadSize); + + // configure bunch min. amp checker + loadConfigValueInt("BunchMinAmpMinEntries", mBunchMinCheckMinEntries); + loadConfigValueDouble("BunchMinAmpFractionSignal", mBunchMinCheckFractionSignal); + loadConfigValueInt("BunchMinAmpMinEntriesSM", mBunchMinCheckMinEntriesSM); + loadConfigValueDouble("BunchMinAmpFractionSignalSM", mBunchMinCheckFractionSignalSM); + loadConfigValueInt("BunchMinAmpMinEntriesFEC", mBunchMinCheckMinEntriesFEC); + loadConfigValueDouble("BunchMinAmpFractionSignalFEC", mBunchMinCheckFractionSignalFEC); + + mIndicesConverter.Initialize(); +} + +Quality RawCheck::check(std::map>* moMap) +{ + auto mo = moMap->begin()->second; + Quality result = Quality::Good; + if (mo->getName() == "ErrorTypePerSM") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() != 0) { + result = Quality::Bad; + } // checker for the error type per SM + } + if (mo->getName().find("BunchMinRawAmplitude") == 0) { + if (mo->getObject()->InheritsFrom(TH2::Class())) { + auto hist = static_cast(mo->getObject()); + if (!hist->GetEntries()) { + result = Quality::Medium; + } else { + bool perSM = hist->GetYaxis()->GetNbins() == 20; + auto [minentries, siganlfraction] = getCutsBunchMinAmpHist(perSM, !perSM); + std::vector badModules; + auto modulequalities = runPedestalCheck2D(hist, minentries, siganlfraction); + for (int imod = 0; imod < modulequalities.size(); imod++) { + if (modulequalities[imod] == Quality::Bad) { + badModules.emplace_back(imod); + } + } + if (badModules.size()) { + std::string message; + if (perSM) { + result = Quality::Bad; + std::stringstream messagebuilder; + for (auto imod : badModules) { + messagebuilder << "Pedestals peak detected in SM " << imod << " " << mIndicesConverter.GetOnlineSMIndex(imod) << std::endl; + } + message = messagebuilder.str(); + } else { + // check if we have Full SMs bad (at least 60 % of the expected FECs bad) + auto fecsPerSM = reduceBadFECs(badModules); + std::bitset<20> badsms; + for (int ism = 0; ism < fecsPerSM.size(); ism++) { + if (static_cast(fecsPerSM[ism].size()) > 0.6 * static_cast(getExepctedFECs(ism))) { + badsms.set(ism, true); + } + } + if (badsms.any()) { + result = Quality::Bad; + } else { + result = Quality::Medium; + } + std::stringstream messagebuilder; + for (int ism = 0; ism < fecsPerSM.size(); ism++) { + if (badsms.test(ism)) { + messagebuilder << "Pedestals not set full SM " << ism << " " << mIndicesConverter.GetOnlineSMIndex(ism) << std::endl; + } else { + for (auto& fec : fecsPerSM[ism]) { + messagebuilder << "Pedestals not set SM " << ism << " " << mIndicesConverter.GetOnlineSMIndex(ism) << " FEC " << fec << std::endl; + } + } + } + message = messagebuilder.str(); + } + result.addFlag(o2::quality_control::FlagTypeFactory::BadEMCalorimetry(), message); + } + } + } else if (mo->getObject()->InheritsFrom(TH1::Class())) { + auto hist = static_cast(mo->getObject()); + if (!hist->GetEntries()) { + result = Quality::Medium; + } else { + auto [minentries, siganlfraction] = getCutsBunchMinAmpHist(mo->getName().find("SM") != std::string::npos, false); + result = runPedestalCheck1D(hist, mBunchMinCheckMinEntries, mBunchMinCheckFractionSignal); + } + } + } + if (mo->getName() == "FECidMaxChWithInput_perSM") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + bool hasBadFEC = false; + for (int ism = 0; ism < h->GetXaxis()->GetNbins(); ism++) { + std::unique_ptr smbin(h->ProjectionY("nextsm", ism + 1, ism + 1)); + if (!smbin->GetEntries()) + continue; + // get count rates for truncated mean + std::vector feccounts; + for (int ifec = 0; ifec < smbin->GetXaxis()->GetNbins(); ifec++) { + double countrate = smbin->GetBinContent(ifec + 1); + if (countrate > DBL_EPSILON) + feccounts.push_back(countrate); + } + // evaluate truncated mean to find outliers + double mean, sigma; + TRobustEstimator estimmator; + estimmator.EvaluateUni(feccounts.size(), feccounts.data(), mean, sigma); + // compare count rate of each FEC to truncated mean + for (int ifec = 0; ifec < smbin->GetXaxis()->GetNbins(); ifec++) { + double countrate = smbin->GetBinContent(ifec + 1); + if (countrate > mean + mNsigmaFECMaxPayload * sigma) { + hasBadFEC = true; + break; + } + } + if (hasBadFEC) + break; + } + if (hasBadFEC) + result = Quality::Bad; + } + } + if (mo->getName().find("PayloadSize") != std::string::npos && mo->getName().find("1D") == std::string::npos) { + // 2D checkers for payload size + std::map meanPayloadSizeDDL; + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } else { + std::vector meanPayloads; + for (int ism = 0; ism < h->GetXaxis()->GetNbins(); ism++) { + std::unique_ptr smbin(h->ProjectionY("nextsm", ism + 1, ism + 1)); + if (!smbin->GetEntries()) { + meanPayloadSizeDDL[ism] = 0; + continue; + } + auto meanPayload = smbin->GetMean(); + meanPayloads.push_back(meanPayload); + meanPayloadSizeDDL[ism] = meanPayload; + // get count rates for truncated mean + } + double mean, sigma; + TRobustEstimator estimmator; + estimmator.EvaluateUni(meanPayloads.size(), meanPayloads.data(), mean, sigma); + for (auto [ddlID, payloadmean] : meanPayloadSizeDDL) { + if (payloadmean > mean + mNsigmaPayloadSize * sigma) { + result = Quality::Bad; + break; + } + } + } + } + if (mo->getName().find("PayloadSize") != std::string::npos && mo->getName().find("1D") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h->GetEntries()) { + result = Quality::Medium; + } else { + // get count rates for truncated mean + std::vector payloadSize1D; + for (int ipay = 0; ipay < h->GetXaxis()->GetNbins(); ipay++) { + double countrate = h->GetBinContent(ipay + 1); + if (countrate > DBL_EPSILON) + payloadSize1D.push_back(countrate); + } + // evaluate truncated mean to find outliers + double mean, sigma; + TRobustEstimator estimmator; + estimmator.EvaluateUni(payloadSize1D.size(), payloadSize1D.data(), mean, sigma); + // compare count rate of each FEC to truncated mean + for (int ipay = 0; ipay < h->GetXaxis()->GetNbins(); ipay++) { + double countrate = h->GetBinContent(ipay + 1); + if (countrate > mean + mNsigmaPayloadSize * sigma) { + result = Quality::Bad; + } + } + } + } + return result; +} + +void RawCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName().find("Error") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + + if (checkResult == Quality::Good) { + // check the error type, loop on X entries to find the SM and on Y to find the Error Number + TLatex* msg = new TLatex(0.2, 0.8, "#color[418]{No Error: OK}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + if (mILMessageRawErrorCheck) { + ILOG(Error, Support) << " QualityBad:Presence of Error Code" << ENDM; + } + TLatex* msg = new TLatex(0.2, 0.8, "#color[2]{Presence of Error Code: call EMCAL oncall}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Info, Support) << "Quality::medium, setting to orange" << ENDM; + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } + if (mo->getName().find("BunchMinRawAmplitude") != std::string::npos) { + if (mo->getObject()->InheritsFrom(TH2::Class())) { + auto* h = dynamic_cast(mo->getObject()); + if (checkResult == Quality::Good) { + TLatex* msg = new TLatex(0.3, 0.8, "#color[418]{Data OK}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + h->SetFillColor(kGreen); + } else { + auto flags = checkResult.getFlags(); + auto emflag = std::find_if(flags.begin(), flags.end(), [](const std::pair& testflag) -> bool { + bool found = testflag.first == quality_control::FlagTypeFactory::BadEMCalorimetry(); + return testflag.first == quality_control::FlagTypeFactory::BadEMCalorimetry(); + }); + if (emflag != flags.end()) { + auto message = emflag->second; + std::stringstream parser(message); + std::string buffer; + double currenty = 0.8; + bool hasSM = false; + int ilines = 0; // Display only the 5 first messages + while (std::getline(parser, buffer, '\n')) { + ilines++; + bool isFEC = buffer.find("FEC") != std::string::npos; + if (!isFEC) { + hasSM = true; + } + if (ilines > 5) { + continue; + } + int textcolor = isFEC ? 42 : 2; + TLatex* msg = new TLatex(0.3, currenty, Form("#color[%d]{%s}", textcolor, buffer.data())); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + currenty -= 0.1; + } + int textcolor = hasSM ? 2 : 42; + TLatex* msg = new TLatex(0.3, currenty, Form("#color[%d]{If NOT techn.run: call EMCAL oncall}", textcolor)); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + currenty -= 0.1; + } + } + } else { + auto* h = dynamic_cast(mo->getObject()); + if (checkResult == Quality::Good) { + TLatex* msg = new TLatex(0.3, 0.8, "#color[418]{Data OK}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + bool showMessage = false; + if (mIlMessageBunchMinAmpCheckSM && (mo->getName().find("SM") != std::string::npos)) { + showMessage = true; + } else if (mIlMessageBunchMinAmpCheckDetector && (mo->getName().find("EMCAL") != std::string::npos || mo->getName().find("DCAL") != std::string::npos)) { + showMessage = true; + } else if (mIlMessageBunchMinAmpCheckFull && (mo->getName().find("Full") != std::string::npos)) { + showMessage = true; + } + if (showMessage) { + ILOG(Error, Support) << " QualityBad:Bunch min Amplitude outside limits (" << mo->getName() << ")" << ENDM; + } + TLatex* msg = new TLatex(0.2, 0.8, "#color[2]{Pedestal peak detected}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + msg = new TLatex(0.2, 0.7, "#color[2]{If NOT techn.run: call EMCAL oncall}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Info, Support) << "Quality::medium, setting to orange" << ENDM; + TLatex* msg = new TLatex(0.2, 0.8, "#color[42]{empty:if in run, call EMCAL oncall}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } + h->SetLineColor(kBlack); + } + } + std::vector payloadhists = { "FECidMaxChWithInput_perSM", "PayloadSizePerDDL", "PayloadSizeTFPerDDL", "PayloadSizeTFPerDDL_1D", "PayloadSizePerDDL_1D" }; + if (std::find(payloadhists.begin(), payloadhists.end(), mo->getName()) != payloadhists.end()) { + auto* h = dynamic_cast(mo->getObject()); + if (checkResult == Quality::Good) { + TLatex* msg = new TLatex(0.2, 0.8, "#color[418]{Data OK}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + TLatex* msg; + if (mo->getName() == "FECidMaxChWithInput_perSM") { + if (mILMessageNoisyFECCheck) { + ILOG(Error, Support) << "Noisy FEC detected" << ENDM; + } + msg = new TLatex(0.2, 0.8, "#color[2]{Noisy FEC detected}"); + } + if (mo->getName().find("PayloadSize") != std::string::npos) { + if (mILMessagePayloadSizeCheck) { + ILOG(Error, Support) << "Large payload in several DDLs" << ENDM; + } + msg = new TLatex(0.2, 0.8, "#color[2]{Large payload in several DDLs}"); + } + // Large payload in several DDLs + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + msg = new TLatex(0.2, 0.7, "#color[2]{If NOT techn.run: call EMCAL oncall}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } else if (checkResult == Quality::Medium) { + ILOG(Info, Support) << "Quality::medium, setting to orange" << ENDM; + TLatex* msg = new TLatex(0.2, 0.8, "#color[42]{empty:if in run, call EMCAL-oncall}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } + h->SetLineColor(kBlack); + } + if (mo->getName().find("ADC_EMCAL") != std::string::npos) { + auto* h2D = dynamic_cast(mo->getObject()); + // orizontal + TLine* l1 = new TLine(-0.5, 24, 95.5, 24); + TLine* l2 = new TLine(-0.5, 48, 95.5, 48); + TLine* l3 = new TLine(-0.5, 72, 95.5, 72); + TLine* l4 = new TLine(-0.5, 96, 95.5, 96); + TLine* l5 = new TLine(-0.5, 120, 95.5, 120); + TLine* l6 = new TLine(-0.5, 128, 95.5, 128); + + TLine* l7 = new TLine(-0.5, 152, 31.5, 152); + TLine* l8 = new TLine(63.5, 152, 95.5, 152); + + TLine* l9 = new TLine(-0.5, 176, 31.5, 176); + TLine* l10 = new TLine(63.5, 176, 95.5, 176); + + TLine* l11 = new TLine(-0.5, 200, 95.5, 200); + TLine* l12 = new TLine(47.5, 200, 47.5, 207.5); + + // vertical + TLine* l13 = new TLine(47.5, -0.5, 47.5, 128); + TLine* l14 = new TLine(31.5, 128, 31.5, 200); + TLine* l15 = new TLine(63.5, 128, 63.5, 200); + + h2D->GetListOfFunctions()->Add(l1); + h2D->GetListOfFunctions()->Add(l2); + h2D->GetListOfFunctions()->Add(l3); + h2D->GetListOfFunctions()->Add(l4); + h2D->GetListOfFunctions()->Add(l5); + h2D->GetListOfFunctions()->Add(l6); + h2D->GetListOfFunctions()->Add(l7); + h2D->GetListOfFunctions()->Add(l8); + h2D->GetListOfFunctions()->Add(l9); + h2D->GetListOfFunctions()->Add(l10); + h2D->GetListOfFunctions()->Add(l11); + h2D->GetListOfFunctions()->Add(l12); + h2D->GetListOfFunctions()->Add(l13); + h2D->GetListOfFunctions()->Add(l14); + h2D->GetListOfFunctions()->Add(l15); + + l1->Draw(); + l2->Draw(); + l3->Draw(); + l4->Draw(); + l5->Draw(); + l6->Draw(); + l7->Draw(); + l8->Draw(); + l9->Draw(); + l10->Draw(); + l11->Draw(); + l12->Draw(); + l13->Draw(); + l14->Draw(); + l15->Draw(); + } +} + +Quality RawCheck::runPedestalCheck1D(TH1* adchist, double minentries, double signalfraction) const +{ + // checker for the raw ampl histos (second peak around 40) + Quality result = Quality::Good; + Float_t totentries = adchist->Integral(10, 100); // Exclude dominant part of the channels for which the signal is in the noise range below pedestal + if (totentries > minentries) { // Do not check if we have only a few counts in the signal region + + Float_t entriesBadRegion = adchist->Integral(20, 60); + int first = adchist->GetXaxis()->GetFirst(), + last = adchist->GetXaxis()->GetLast(); + adchist->GetXaxis()->SetRangeUser(20, 60); + Int_t maxbin = adchist->GetMaximumBin(); + adchist->GetXaxis()->SetRange(first, last); + if (maxbin > 20 && maxbin < 50) { + // Float_t entries = h->GetBinContent(maxbin + 1); + if (entriesBadRegion > signalfraction * totentries) { + result = Quality::Bad; + } + } + } + return result; +} + +std::vector RawCheck::runPedestalCheck2D(TH2* adchist, double minentries, double signalfraction) const +{ + std::vector result; + for (int ib = 0; ib < adchist->GetYaxis()->GetNbins(); ib++) { + std::unique_ptr projectionArea(adchist->ProjectionX("projectionModule", ib + 1, ib + 1)); + if (projectionArea->GetEntries()) { + auto quality = runPedestalCheck1D(projectionArea.get(), minentries, signalfraction); + result.emplace_back(quality); + } else { + result.emplace_back(Quality::Good); + } + } + return result; +} + +std::tuple RawCheck::getCutsBunchMinAmpHist(bool perSM, bool perFEC) +{ + int minentries = mBunchMinCheckMinEntries; + double signalfraction = mBunchMinCheckFractionSignal; + if (perSM) { + if (mBunchMinCheckMinEntriesSM) { + minentries = mBunchMinCheckMinEntriesSM; + } + if (mBunchMinCheckFractionSignalSM > DBL_EPSILON) { + signalfraction = mBunchMinCheckFractionSignalSM; + } + } else if (perFEC) { + if (mBunchMinCheckMinEntriesFEC) { + minentries = mBunchMinCheckMinEntriesFEC; + } + if (mBunchMinCheckFractionSignalFEC > DBL_EPSILON) { + signalfraction = mBunchMinCheckFractionSignalFEC; + } + } + return std::make_tuple(minentries, signalfraction); +} + +std::array, 20> RawCheck::reduceBadFECs(std::vector fecids) const +{ + std::array, 20> smfecs; + for (auto fec : fecids) { + int smID = fec / 40, + fecSM = fec % 40; + smfecs[smID].emplace_back(fecSM); + } + return smfecs; +} + +int RawCheck::getExepctedFECs(int smID) const +{ + if (smID < 10) { + // EMCAL full + return 36; // 40 - TRU fecs + } else if (smID >= 12 && smID < 18) { + // DCAL 2/3 + return 24; + } else { + // small SMs + return 12; + } +} + +void RawCheck::loadConfigValueInt(const std::string_view configvalue, int& target) +{ + auto found = mCustomParameters.find(configvalue.data()); + if (found != mCustomParameters.end()) { + try { + target = std::stoi(found->second.data()); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << found->second << " not of expected type (int)" << ENDM; + } + } +} + +void RawCheck::loadConfigValueDouble(const std::string_view configvalue, double& target) +{ + auto found = mCustomParameters.find(configvalue.data()); + if (found != mCustomParameters.end()) { + try { + target = std::stod(found->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << found->second << " not of expected type (double)" << ENDM; + } + } +} + +void RawCheck::loadConfigValueBool(const std::string_view configvalue, bool& target) +{ + auto found = mCustomParameters.find(configvalue.data()); + if (found != mCustomParameters.end()) { + try { + mIlMessageBunchMinAmpCheckSM = decodeBool(found->second); + } catch (std::exception& e) { + ILOG(Error, Support) << e.what() << ENDM; + } + } +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/RawErrorCheck.cxx b/Modules/EMCAL/src/RawErrorCheck.cxx new file mode 100644 index 0000000000..3a7a3928c3 --- /dev/null +++ b/Modules/EMCAL/src/RawErrorCheck.cxx @@ -0,0 +1,550 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include +#include +#include + +#include "DataFormatsEMCAL/ErrorTypeFEE.h" +#include "EMCALBase/Geometry.h" +#include "EMCALReconstruction/AltroDecoder.h" +#include "EMCALReconstruction/CaloRawFitter.h" +#include "EMCALReconstruction/RawDecodingError.h" +#include "EMCALReconstruction/ReconstructionErrors.h" + +#include "EMCAL/RawErrorCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include +#include + +#include +#include + +using namespace o2::quality_control; + +namespace o2::quality_control_modules::emcal +{ + +void RawErrorCheck::configure() +{ + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); + + // switch on/off messages on the infoLogger + auto switchNotifyIL = mCustomParameters.find("NotifyInfologger"); + if (switchNotifyIL != mCustomParameters.end()) { + try { + mNotifyInfologger = decodeBool(switchNotifyIL->second); + } catch (std::exception& e) { + ILOG(Error, Support) << e.what() << ENDM; + } + } + const std::string keyThreshRawdataErrors = "ThresholdRDE", + keyThreshPageError = "ThresholdPE", + keyThreshMajorAltroError = "ThresholdMAAE", + keyThreshMinorAltroError = "ThresholdMIAE", + keyThresRawFitError = "ThresholdRFE", + keyThresholdGeometryError = "ThresholdGEE", + keyThresholdGainTypeError = "ThresholdGTE"; + + try { + for (auto& [param, value] : mCustomParameters.getAllDefaults()) { + if (param.find(keyThreshRawdataErrors) == 0) { + auto errortype = param.substr(keyThreshRawdataErrors.length()); + auto errorcode = findErrorCodeRDE(errortype); + if (errorcode > -1) { + try { + auto threshold = std::stoi(value); + ILOG(Info) << "Setting custom threshold in Histogram RawDataErrors: " << errortype << " <= " << threshold << ENDM; + mErrorCountThresholdRDE[errorcode] = threshold; + } catch (...) { + ILOG(Error) << "Thresholds for histogram RawDataErrors: Failure in decoding threshold value (" << value << ") for error type " << errortype << ENDM; + } + } else { + ILOG(Error) << "Thresholds for histogram RawDataErrors: Requested error type " << errortype << " not found" << ENDM; + } + } + + // Raw data error summary hists start with RDESummaryErr for error values and RDESummaryWarn for warning values + std::string strSummaryErr = "RDESummaryErr"; + if (param.starts_with(strSummaryErr)) { + auto errortype = param.substr(strSummaryErr.length()); + auto errorcode = findErrorCodeRDE(errortype); + if (errorcode > -1) { + try { + auto threshold = std::stoi(value); + ILOG(Info) << "Setting custom threshold in Histogram RawDataErrors Summary Errors: " << errortype << " <= " << threshold << ENDM; + mErrorCountThresholdRDESummary[errorcode][0] = threshold; + } catch (...) { + ILOG(Error) << "Thresholds for histogram RawDataErrors Summary Errors: Failure in decoding threshold value (" << value << ") for error type " << errortype << ENDM; + } + } else { + ILOG(Error) << "Thresholds for histogram RawDataErrors Summary Errors: Requested error type " << errortype << " not found" << ENDM; + } + } + + std::string strSummaryWarn = "RDESummaryWarn"; + if (param.starts_with(strSummaryWarn)) { + auto errortype = param.substr(strSummaryWarn.length()); + auto errorcode = findErrorCodeRDE(errortype); + if (errorcode > -1) { + try { + auto threshold = std::stoi(value); + ILOG(Info) << "Setting custom threshold in Histogram RawDataErrors Summary Warning: " << errortype << " <= " << threshold << ENDM; + mErrorCountThresholdRDESummary[errorcode][1] = threshold; + } catch (...) { + ILOG(Error) << "Thresholds for histogram RawDataErrors Summary Warning: Failure in decoding threshold value (" << value << ") for error type " << errortype << ENDM; + } + } else { + ILOG(Error) << "Thresholds for histogram RawDataErrors Summary Warning: Requested error type " << errortype << " not found" << ENDM; + } + } + + if (param.find(keyThreshPageError) == 0) { + auto errortype = param.substr(keyThreshPageError.length()); + auto errorcode = findErrorCodePE(errortype); + if (errorcode > -1) { + try { + auto threshold = std::stoi(value); + ILOG(Info) << "Setting custom threshold in Histogram PageErrors: " << errortype << " <= " << threshold << ENDM; + mErrorCountThresholdPE[errorcode] = threshold; + } catch (...) { + ILOG(Error) << "Thresholds for histogram PageErrors: Failure in decoding threshold value (" << value << ") for error type " << errortype << ENDM; + } + } else { + ILOG(Error) << "Thresholds for histogram PageErrors: Requested error type " << errortype << " not found" << ENDM; + } + } + + if (param.find(keyThreshMajorAltroError) == 0) { + auto errortype = param.substr(keyThreshMajorAltroError.length()); + auto errorcode = findErrorCodeMAAE(errortype); + if (errorcode > -1) { + try { + auto threshold = std::stoi(value); + ILOG(Info) << "Setting custom threshold in Histogram MajorAltroErrors: " << errortype << " <= " << threshold << ENDM; + mErrorCountThresholdMAAE[errorcode] = threshold; + } catch (...) { + ILOG(Error) << "Thresholds for histogram MajorAltroErrors: Failure in decoding threshold value (" << value << ") for error type " << errortype << ENDM; + } + } else { + ILOG(Error) << "Thresholds for histogram MajorAltroErrors: Requested error type " << errortype << " not found" << ENDM; + } + } + + if (param.find(keyThreshMinorAltroError) == 0) { + auto errortype = param.substr(keyThreshMinorAltroError.length()); + auto errorcode = findErrorCodeMIAE(errortype); + if (errorcode > -1) { + try { + auto threshold = std::stoi(value); + ILOG(Info) << "Setting custom threshold in Histogram MinorAltroError: " << errortype << " <= " << threshold << ENDM; + mErrorCountThresholdMAAE[errorcode] = threshold; + } catch (...) { + ILOG(Error) << "Thresholds for histogram MinorAltroError: Failure in decoding threshold value (" << value << ") for error type " << errortype << ENDM; + } + } else { + ILOG(Error) << "Thresholds for histogram MinorAltroError: Requested error type " << errortype << " not found" << ENDM; + } + } + + if (param.find(keyThresRawFitError) == 0) { + auto errortype = param.substr(keyThresRawFitError.length()); + auto errorcode = findErrorCodeRFE(errortype); + if (errorcode > -1) { + try { + auto threshold = std::stoi(value); + ILOG(Info) << "Setting custom threshold in Histogram RawFitError: " << errortype << " <= " << threshold << ENDM; + mErrorCountThresholdRFE[errorcode] = threshold; + } catch (...) { + ILOG(Error) << "Thresholds for histogram RawFitError: Failure in decoding threshold value (" << value << ") for error type " << errortype << ENDM; + } + } else { + ILOG(Error) << "Thresholds for histogram RawFitError: Requested error type " << errortype << " not found" << ENDM; + } + } + + if (param.find(keyThresholdGeometryError) == 0) { + auto errortype = param.substr(keyThresholdGeometryError.length()); + auto errorcode = findErrorCodeGEE(errortype); + if (errorcode > -1) { + try { + auto threshold = std::stoi(value); + ILOG(Info) << "Setting custom threshold in Histogram GeometryError: " << errortype << " <= " << threshold << ENDM; + mErrorCountThresholdGEE[errorcode] = threshold; + } catch (...) { + ILOG(Error) << "Thresholds for histogram GeometryError: Failure in decoding threshold value (" << value << ") for error type " << errortype << ENDM; + } + } else { + ILOG(Error) << "Thresholds for histogram GeometryError: Requested error type " << errortype << " not found" << ENDM; + } + } + + if (param.find(keyThresholdGainTypeError) == 0) { + auto errortype = param.substr(keyThresholdGainTypeError.length()); + auto errorcode = findErrorCodeGTE(errortype); + if (errorcode > -1) { + try { + auto threshold = std::stoi(value); + ILOG(Info) << "Setting custom threshold in Histogram GainTypeError: " << errortype << " <= " << threshold << ENDM; + mErrorCountThresholdGTE[errorcode] = threshold; + } catch (...) { + ILOG(Error) << "Thresholds for histogram GainTypeError: Failure in decoding threshold value (" << value << ") for error type " << errortype << ENDM; + } + } else { + ILOG(Error) << "Thresholds for histogram GainTypeError: Requested error type " << errortype << " not found" << ENDM; + } + } + } + } catch (std::out_of_range& e) { + // Nothing to be done, no parameter found. + ILOG(Debug) << "Error in parameter extraction" << ENDM; + } +} + +Quality RawErrorCheck::check(std::map>* moMap) +{ + std::array errorSummaryHists = { { "RawDataErrors" } }; + std::array errorhists = { { "PageErrors", "MajorAltroErrors", "MinorAltroError", "RawFitError", "GeometryError", "GainTypeError" } }; + std::array gainhists = { { "NoHGPerDDL", "NoLGPerDDL" } }; + std::array channelgainhists = { { "ChannelLGnoHG", "ChannelHGnoLG" } }; + Quality result = Quality::Good; + + std::map*> thresholdConfigErrorHists = { + { "RawDataErrors", &mErrorCountThresholdRDE }, + { "PageErrors", &mErrorCountThresholdPE }, + { "MajorAltroErrors", &mErrorCountThresholdMAAE }, + { "MinorAltroError", &mErrorCountThresholdMIAE }, + { "RawFitError", &mErrorCountThresholdRFE }, + { "GeometryError", &mErrorCountThresholdGEE }, + { "GainTypeError", &mErrorCountThresholdGTE } + }; + + for (auto& [moName, mo] : *moMap) { + if (std::find(errorSummaryHists.begin(), errorSummaryHists.end(), mo->getName()) != errorSummaryHists.end()) { + // Check for presence of error codes + auto* errorhist = dynamic_cast(mo->getObject()); + + for (int errorcode = 0; errorcode < errorhist->GetYaxis()->GetNbins(); errorcode++) { + // try to find a threshold for the number of errors per bin + int threshold = 0; + auto thresholdHandler = thresholdConfigErrorHists.find(mo->getName()); + if (thresholdHandler != thresholdConfigErrorHists.end()) { + auto thresholdFound = thresholdHandler->second->find(errorcode); + if (thresholdFound != thresholdHandler->second->end()) { + threshold = thresholdFound->second; + } + } + // try to find the threshold for the number of links from when on it is considered to be warning ot bad + int thresholdTotalErrBad = 0; + int thresholdTotalErrWarn = 0; + if (mErrorCountThresholdRDESummary.contains(errorcode)) { + thresholdTotalErrBad = mErrorCountThresholdRDESummary[errorcode][0]; + thresholdTotalErrWarn = mErrorCountThresholdRDESummary[errorcode][1]; + } + + int numErrors = 0; + for (int linkID = 0; linkID < errorhist->GetXaxis()->GetNbins(); linkID++) { + int nErr = errorhist->GetBinContent(linkID + 1, errorcode + 1); + if (nErr > threshold) { + numErrors++; + } + } + + if (numErrors > thresholdTotalErrBad) { // Number of raw error exceeds the threshold and is considered to be bad + if (result != Quality::Bad) { + result = Quality::Bad; + } + result.addFlag(FlagTypeFactory::Unknown(), "Raw error " + std::string(errorhist->GetYaxis()->GetBinLabel(errorcode + 1)) + " above critical threshold: Call oncall!"); + + } else if (numErrors > thresholdTotalErrWarn && result != Quality::Bad) { // Number of raw error exceeds the threshold but is considered to be okay. Error can be fixed at beam dump + if (result != Quality::Medium) { + result = Quality::Medium; + } + result.addFlag(FlagTypeFactory::Unknown(), "Raw error " + std::string(errorhist->GetYaxis()->GetBinLabel(errorcode + 1)) + " below critical threshold: Call oncall at beam dump"); + } + } + } else if (std::find(errorhists.begin(), errorhists.end(), mo->getName()) != errorhists.end()) { + // Check for presence of error codes + auto* errorhist = dynamic_cast(mo->getObject()); + + for (int errorcode = 0; errorcode < errorhist->GetYaxis()->GetNbins(); errorcode++) { + // try to find a threshold + int threshold = 0; + auto thresholdHandler = thresholdConfigErrorHists.find(mo->getName()); + if (thresholdHandler != thresholdConfigErrorHists.end()) { + auto thresholdFound = thresholdHandler->second->find(errorcode); + if (thresholdFound != thresholdHandler->second->end()) { + threshold = thresholdFound->second; + } + } + int numErrors = 0; + for (int linkID = 0; linkID < errorhist->GetXaxis()->GetNbins(); linkID++) { + numErrors += errorhist->GetBinContent(linkID + 1, errorcode + 1); + } + if (numErrors > threshold) { + // Found number of raw data errors is above threshold for bin + if (result != Quality::Bad) { + result = Quality::Bad; + } + result.addFlag(FlagTypeFactory::Unknown(), "Raw error " + std::string(errorhist->GetYaxis()->GetBinLabel(errorcode + 1)) + " above critical threshold: Call oncall!"); + } + } + } else if (std::find(gainhists.begin(), gainhists.end(), mo->GetName()) != gainhists.end()) { + // Find FEC with gain error + auto errorhist = dynamic_cast(mo->getObject()); + std::string errortype; + std::string_view histname = mo->GetName(); + if (histname == "NoHGPerDDL") { + errortype = "LGnoHG"; + } else { + errortype = "HGnoLG"; + } + for (int linkID = 0; linkID < errorhist->GetXaxis()->GetNbins(); linkID++) { + for (int fecID = 0; fecID < errorhist->GetYaxis()->GetNbins(); fecID++) { + if (errorhist->GetBinContent(linkID + 1, fecID + 1)) { + if (result != Quality::Bad) { + result = Quality::Bad; + } + result.addFlag(FlagTypeFactory::Unknown(), "Gain error " + errortype + " in FEC " + std::to_string(fecID) + " of DDL " + std::to_string(linkID)); + ILOG(Debug, Support) << "Detected " << errortype << " in FEC " << fecID << " of DDL " << linkID << ENDM; + } + } + } + } else if (std::find(channelgainhists.begin(), channelgainhists.end(), mo->GetName()) != channelgainhists.end()) { + auto errorhist = dynamic_cast(mo->getObject()); + std::string errortype; + std::string_view histname = mo->GetName(); + if (histname == "ChannelLGnoHG") { + errortype = "LGnoHG"; + } else { + errortype = "HGnoLG"; + } + for (int column = 0; column < 96; column++) { + for (int row = 0; row < 208; row++) { + if (errorhist->GetBinContent(column + 1, row + 1)) { + if (result != Quality::Bad) { + result = Quality::Bad; + } + // Get position in supermodule + auto [supermoduleID, rowSM, columnSM] = mGeometry->GetPositionInSupermoduleFromGlobalRowCol(row, column); + result.addFlag(FlagTypeFactory::Unknown(), "Gain error " + errortype + " in channel col " + std::to_string(column) + " row " + std::to_string(column) + " (SM " + std::to_string(supermoduleID) + ", row " + std::to_string(rowSM) + " col " + std::to_string(columnSM) + ")"); + ILOG(Debug, Support) << "Detected " << errortype << " in column " << column << " row " << row << " ( SM " << supermoduleID << ", " << columnSM << ", " << rowSM << ")" << ENDM; + } + } + } + } else { + ILOG(Error, Support) << "Unsupported histogram in check: " << mo->GetName() << ENDM; + continue; + } + } + return result; +} + +void RawErrorCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + auto* h = dynamic_cast(mo->getObject()); + if (checkResult == Quality::Good) { + TLatex* msg = new TLatex(0.2, 0.8, "#color[418]{No Error: OK}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + } else if (checkResult == Quality::Bad) { + TLatex* msg = new TLatex(0.2, 0.8, "#color[2]{Presence of Error Code: call EMCAL oncall}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + // Notify about found errors on the infoLogger: + if (mNotifyInfologger) { + for (const auto& flag : checkResult.getFlags()) { + ILOG(Warning, Devel) << "Raw Error in " << mo->GetName() << " found: " << flag.second << ENDM; + } + } + } else if (checkResult == Quality::Medium) { + TLatex* msg = new TLatex(0.2, 0.8, "#color[802]{Non-critical error codes present: call EMCAL oncall at beam dump}"); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + // Notify about found errors on the infoLogger: + if (mNotifyInfologger) { + for (const auto& flag : checkResult.getFlags()) { + ILOG(Warning, Devel) << "Non-critical raw Error in " << mo->GetName() << " found: " << flag.second << ENDM; + } + } + } + // SM grid + if (mo->getName().find("Channel") != std::string::npos) { + auto* h2D = dynamic_cast(mo->getObject()); + // orizontal + TLine* l1 = new TLine(-0.5, 24, 95.5, 24); + TLine* l2 = new TLine(-0.5, 48, 95.5, 48); + TLine* l3 = new TLine(-0.5, 72, 95.5, 72); + TLine* l4 = new TLine(-0.5, 96, 95.5, 96); + TLine* l5 = new TLine(-0.5, 120, 95.5, 120); + TLine* l6 = new TLine(-0.5, 128, 95.5, 128); + + TLine* l7 = new TLine(-0.5, 152, 31.5, 152); + TLine* l8 = new TLine(63.5, 152, 95.5, 152); + + TLine* l9 = new TLine(-0.5, 176, 31.5, 176); + TLine* l10 = new TLine(63.5, 176, 95.5, 176); + + TLine* l11 = new TLine(-0.5, 200, 95.5, 200); + TLine* l12 = new TLine(47.5, 200, 47.5, 207.5); + + // vertical + TLine* l13 = new TLine(47.5, -0.5, 47.5, 128); + TLine* l14 = new TLine(31.5, 128, 31.5, 200); + TLine* l15 = new TLine(63.5, 128, 63.5, 200); + + h2D->GetListOfFunctions()->Add(l1); + h2D->GetListOfFunctions()->Add(l2); + h2D->GetListOfFunctions()->Add(l3); + h2D->GetListOfFunctions()->Add(l4); + h2D->GetListOfFunctions()->Add(l5); + h2D->GetListOfFunctions()->Add(l6); + h2D->GetListOfFunctions()->Add(l7); + h2D->GetListOfFunctions()->Add(l8); + h2D->GetListOfFunctions()->Add(l9); + h2D->GetListOfFunctions()->Add(l10); + h2D->GetListOfFunctions()->Add(l11); + h2D->GetListOfFunctions()->Add(l12); + h2D->GetListOfFunctions()->Add(l13); + h2D->GetListOfFunctions()->Add(l14); + h2D->GetListOfFunctions()->Add(l15); + + l1->Draw(); + l2->Draw(); + l3->Draw(); + l4->Draw(); + l5->Draw(); + l6->Draw(); + l7->Draw(); + l8->Draw(); + l9->Draw(); + l10->Draw(); + l11->Draw(); + l12->Draw(); + l13->Draw(); + l14->Draw(); + l15->Draw(); + } +} + +bool RawErrorCheck::decodeBool(std::string value) const +{ + boost::algorithm::to_lower_copy(value); + if (value == "true") { + return true; + } + if (value == "false") { + return false; + } + throw std::runtime_error(fmt::format("Value {} not a boolean", value.data()).data()); +} + +int RawErrorCheck::findErrorCodeRDE(const std::string_view errorname) const +{ + int result = -1; + for (int error = 0; error < o2::emcal::ErrorTypeFEE::getNumberOfErrorTypes(); error++) { + if (std::string_view(o2::emcal::ErrorTypeFEE::getErrorTypeName(error)) == errorname) { + result = error; + break; + } + } + return result; +} + +int RawErrorCheck::findErrorCodePE(const std::string_view errorname) const +{ + int result = -1; + for (int error = 0; error < o2::emcal::RawDecodingError::getNumberOfErrorTypes(); error++) { + if (std::string_view(o2::emcal::RawDecodingError::getErrorCodeNames(error)) == errorname) { + result = error; + break; + } + } + return result; +} + +int RawErrorCheck::findErrorCodeMAAE(const std::string_view errorname) const +{ + int result = -1; + for (int error = 0; error < o2::emcal::AltroDecoderError::getNumberOfErrorTypes(); error++) { + if (std::string_view(o2::emcal::AltroDecoderError::getErrorTypeName(error)) == errorname) { + result = error; + break; + } + } + return result; +} + +int RawErrorCheck::findErrorCodeMIAE(const std::string_view errorname) const +{ + int result = -1; + for (int error = 0; error < o2::emcal::MinorAltroDecodingError::getNumberOfErrorTypes(); error++) { + if (std::string_view(o2::emcal::MinorAltroDecodingError::getErrorTypeName(error)) == errorname) { + result = error; + break; + } + } + return result; +} + +int RawErrorCheck::findErrorCodeRFE(const std::string_view errorname) const +{ + int result = -1; + for (int error = 0; error < o2::emcal::CaloRawFitter::getNumberOfErrorTypes(); error++) { + if (std::string_view(o2::emcal::CaloRawFitter::getErrorTypeName(error)) == errorname) { + result = error; + break; + } + } + return result; +} + +int RawErrorCheck::findErrorCodeGEE(const std::string_view errorname) const +{ + int result = -1; + for (int error = 0; error < o2::emcal::reconstructionerrors::getNumberOfGeometryErrorCodes(); error++) { + if (std::string_view(o2::emcal::reconstructionerrors::getGeometryErrorName(error)) == errorname) { + result = error; + break; + } + } + return result; +} + +int RawErrorCheck::findErrorCodeGTE(const std::string_view errorname) const +{ + int result = -1; + for (int error = 0; error < o2::emcal::reconstructionerrors::getNumberOfGainErrorCodes(); error++) { + if (std::string_view(o2::emcal::reconstructionerrors::getGainErrorName(error)) == errorname) { + result = error; + break; + } + } + return result; +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/RawErrorCheckAll.cxx b/Modules/EMCAL/src/RawErrorCheckAll.cxx new file mode 100644 index 0000000000..7367a8fe29 --- /dev/null +++ b/Modules/EMCAL/src/RawErrorCheckAll.cxx @@ -0,0 +1,121 @@ +#include "QualityControl/MonitorObject.h" +#include "EMCAL/RawErrorCheckAll.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Quality.h" + +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::emcal +{ +void RawErrorCheckAll::configure() +{ + + // configure threshold-based checkers + auto nBadThreshold = mCustomParameters.find("BadThreshold"); + if (nBadThreshold != mCustomParameters.end()) { + try { + mBadThreshold = std::stod(nBadThreshold->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nBadThreshold->second << " not a double" << ENDM; + } + } + + auto nPeriodMovAvg = mCustomParameters.find("PeriodMovAvg"); + if (nPeriodMovAvg != mCustomParameters.end()) { + try { + mPeriodMovAvg = std::stoi(nPeriodMovAvg->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nPeriodMovAvg->second << " not an int" << ENDM; + } + } +} + +Quality RawErrorCheckAll::check(std::map>* moMap) +{ + Quality result = Quality::Good; + + for (auto& [moName, mo] : *moMap) { + if (mo->getName() == "TrendRawDataError") { + auto* canvas_obj = dynamic_cast(mo->getObject()); + auto* list_name = canvas_obj->GetListOfPrimitives(); + for (auto trendgraph : TRangeDynCast(list_name)) { + if (!trendgraph) { + continue; + } + + // queue used to store list so that we get the average + std::queue Dataset; + double sum = 0; + double mean = 0; + + auto* yValues = trendgraph->GetY(); + auto numPoints = trendgraph->GetN(); + std::vector meanArray(numPoints); + for (int i = 0; i < trendgraph->GetN(); ++i) { + double y = yValues[i]; + sum += y; + Dataset.push(y); + if (Dataset.size() > mPeriodMovAvg) { + sum -= Dataset.front(); + Dataset.pop(); + } + double mean = sum / mPeriodMovAvg; + meanArray[i] = mean; + } + + for (decltype(meanArray.size()) i = 0; i < meanArray.size() - 1; ++i) { + if (meanArray[i] > mBadThreshold && meanArray[i + 1] > mBadThreshold) { + result = Quality::Bad; + break; + } + } + meanArray.clear(); + } + } + } + + return result; +} + +void RawErrorCheckAll::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "TrendRawDataError") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("Average raw error rate within threshold: OK!!!"); + msg->SetFillColor(kGreen); + // + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + msg->Clear(); + msg->AddText("Average raw error rate above threshold"); + msg->AddText("If NOT a technical run,"); + msg->AddText("call EMCAL on-call."); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange"; + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/RawErrorTask.cxx b/Modules/EMCAL/src/RawErrorTask.cxx new file mode 100644 index 0000000000..de07bd8b6c --- /dev/null +++ b/Modules/EMCAL/src/RawErrorTask.cxx @@ -0,0 +1,341 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "EMCAL/RawErrorTask.h" +#include "EMCALBase/Geometry.h" +#include "EMCALBase/Mapper.h" +#include "EMCALBase/TriggerMappingV2.h" +#include "EMCALReconstruction/AltroDecoder.h" +#include "EMCALReconstruction/CaloRawFitter.h" +#include "EMCALReconstruction/RawDecodingError.h" +#include "EMCALReconstruction/ReconstructionErrors.h" +#include "DataFormatsEMCAL/ErrorTypeFEE.h" +#include +#include + +namespace o2::quality_control_modules::emcal +{ + +RawErrorTask::~RawErrorTask() +{ + // delete mHistogram; + delete mErrorTypeAltro; + delete mErrorTypePage; + delete mErrorTypeMinAltro; + delete mErrorTypeFit; + delete mErrorTypeGeometry; + delete mErrorTypeGain; + delete mErrorTypeUnknown; + delete mErrorGainLow; + delete mErrorGainHigh; + delete mFecIdMinorAltroError; + delete mTRUErrorType; + delete mTRUErrorPosition; +} + +void RawErrorTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize RawErrorTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + auto get_bool = [](const std::string_view input) -> bool { + return input == "true"; + }; + mExcludeGainErrorsFromOverview = get_bool(getConfigValueLower("excludeGainErrorFromSummary")); + + // Set DDL indices + // SRU: 0-39 + // STU: 44-45 + static constexpr int NDDL_ALL = 46, NDDL_FEE = 40; + // FEC IDs: 0-39 + static constexpr int NFEC = 40; + + /********* + * Most reconstruction errors are specific to ALTRO decoding (i.e. ALTRO decoding errors, geometry, raw fit, ...). Consequently their range can be restricted to the + * DDL range of FEE DDLs. Some reconstruction errors like page decoding errors and link errors are so general that they can also affect the STU data (in case i.e. the + * entire page is corrupted). Consequently the range has to be enlarged to cover also the STU DDLs. + */ + constexpr float binshift = -0.5; // shift bin centers of error codes in order to avoid edge effects + mErrorTypeAll = new TH2F("RawDataErrors", "Raw data errors", NDDL_ALL, 0, NDDL_ALL, o2::emcal::ErrorTypeFEE::getNumberOfErrorTypes(), binshift, o2::emcal::ErrorTypeFEE::getNumberOfErrorTypes() + binshift); + mErrorTypeAll->GetXaxis()->SetTitle("Link"); + mErrorTypeAll->GetYaxis()->SetTitle("Error type"); + for (int ierror = 0; ierror < o2::emcal::ErrorTypeFEE::getNumberOfErrorTypes(); ierror++) { + mErrorTypeAll->GetYaxis()->SetBinLabel(ierror + 1, o2::emcal::ErrorTypeFEE::getErrorTypeTitle(ierror)); + } + mErrorTypeAll->SetStats(0); + getObjectsManager()->startPublishing(mErrorTypeAll); + + mErrorTypeAltro = new TH2F("MajorAltroErrors", "Major ALTRO decoding errors", NDDL_FEE, 0, NDDL_FEE, o2::emcal::AltroDecoderError::getNumberOfErrorTypes(), binshift, o2::emcal::AltroDecoderError::getNumberOfErrorTypes() + binshift); + mErrorTypeAltro->GetXaxis()->SetTitle("Link"); + mErrorTypeAltro->GetYaxis()->SetTitle("Error Type"); + for (auto ierror = 0; ierror < o2::emcal::AltroDecoderError::getNumberOfErrorTypes(); ierror++) { + mErrorTypeAltro->GetYaxis()->SetBinLabel(ierror + 1, o2::emcal::AltroDecoderError::getErrorTypeTitle(ierror)); + } + mErrorTypeAltro->SetStats(0); + getObjectsManager()->startPublishing(mErrorTypeAltro); + + mErrorTypePage = new TH2F("PageErrors", "DMA page decoding errors", NDDL_ALL, 0, NDDL_ALL, o2::emcal::RawDecodingError::getNumberOfErrorTypes(), binshift, o2::emcal::RawDecodingError::getNumberOfErrorTypes() + binshift); + mErrorTypePage->GetXaxis()->SetTitle("Link"); + mErrorTypePage->GetYaxis()->SetTitle("Page Error Type"); + for (int ierror = 0; ierror < o2::emcal::RawDecodingError::getNumberOfErrorTypes(); ierror++) { + mErrorTypePage->GetYaxis()->SetBinLabel(ierror + 1, o2::emcal::RawDecodingError::getErrorCodeTitles(ierror)); + } + mErrorTypePage->SetStats(0); + getObjectsManager()->startPublishing(mErrorTypePage); + + mErrorTypeMinAltro = new TH2F("MinorAltroError", "Minor ALTRO decoding error", NDDL_FEE, 0, NDDL_FEE, o2::emcal::MinorAltroDecodingError::getNumberOfErrorTypes(), binshift, o2::emcal::MinorAltroDecodingError::getNumberOfErrorTypes() + binshift); + mErrorTypeMinAltro->GetXaxis()->SetTitle("Link"); + mErrorTypeMinAltro->GetYaxis()->SetTitle("MinorAltro Error Type"); + for (int ierror = 0; ierror < o2::emcal::MinorAltroDecodingError::getNumberOfErrorTypes(); ierror++) { + mErrorTypeMinAltro->GetYaxis()->SetBinLabel(ierror + 1, o2::emcal::MinorAltroDecodingError::getErrorTypeTitle(ierror)); + } + mErrorTypeMinAltro->SetStats(0); + getObjectsManager()->startPublishing(mErrorTypeMinAltro); + + mErrorTypeFit = new TH2F("RawFitError", "Error in raw fitting ", NDDL_FEE, 0, NDDL_FEE, o2::emcal::CaloRawFitter::getNumberOfErrorTypes(), binshift, o2::emcal::CaloRawFitter::getNumberOfErrorTypes() + binshift); + mErrorTypeFit->GetXaxis()->SetTitle("Link"); + mErrorTypeFit->GetYaxis()->SetTitle("Fit Error Type"); + for (int ierror = 0; ierror < o2::emcal::CaloRawFitter::getNumberOfErrorTypes(); ierror++) { + mErrorTypeFit->GetYaxis()->SetBinLabel(ierror + 1, o2::emcal::CaloRawFitter::getErrorTypeTitle(ierror)); + } + mErrorTypeFit->SetStats(0); + getObjectsManager()->startPublishing(mErrorTypeFit); + + mErrorTypeGeometry = new TH2F("GeometryError", "Geometry error", NDDL_FEE, 0, NDDL_FEE, o2::emcal::reconstructionerrors::getNumberOfGeometryErrorCodes(), binshift, o2::emcal::reconstructionerrors::getNumberOfGeometryErrorCodes() + binshift); + mErrorTypeGeometry->GetXaxis()->SetTitle("Link"); + mErrorTypeGeometry->GetYaxis()->SetTitle("Geometry Error Type"); + for (int ierror = 0; ierror < o2::emcal::reconstructionerrors::getNumberOfGeometryErrorCodes(); ierror++) { + mErrorTypeGeometry->GetYaxis()->SetBinLabel(ierror + 1, o2::emcal::reconstructionerrors::getGeometryErrorTitle(ierror)); + } + mErrorTypeGeometry->SetStats(0); + getObjectsManager()->startPublishing(mErrorTypeGeometry); + + mErrorTypeGain = new TH2F("GainTypeError", "Gain type error", NDDL_FEE, 0, NDDL_FEE, o2::emcal::reconstructionerrors::getNumberOfGainErrorCodes(), binshift, o2::emcal::reconstructionerrors::getNumberOfGainErrorCodes() + binshift); + mErrorTypeGain->GetXaxis()->SetTitle("Link"); + mErrorTypeGain->GetYaxis()->SetTitle("Gain Error Type"); + for (int ierror = 0; ierror < o2::emcal::reconstructionerrors::getNumberOfGainErrorCodes(); ierror++) { + mErrorTypeGain->GetYaxis()->SetBinLabel(ierror + 1, o2::emcal::reconstructionerrors::getGainErrorTitle(ierror)); + } + mErrorTypeGain->SetStats(false); + getObjectsManager()->startPublishing(mErrorTypeGain); + + mErrorGainLow = new TH2F("NoHGPerDDL", "High Gain bunch missing", NFEC, 0, NFEC, NDDL_FEE, 0, NDDL_FEE); + mErrorGainLow->GetYaxis()->SetTitle("fecID"); + mErrorGainLow->GetXaxis()->SetTitle("Link"); + mErrorGainLow->SetStats(false); + getObjectsManager()->startPublishing(mErrorGainLow); + + mErrorGainHigh = new TH2F("NoLGPerDDL", "Low Gain bunch missing for saturated High Gain", NFEC, 0, NFEC, NDDL_FEE, 0, NDDL_FEE); + mErrorGainHigh->GetYaxis()->SetTitle("fecID"); + mErrorGainHigh->GetXaxis()->SetTitle("Link"); + mErrorGainHigh->SetStats(0); + getObjectsManager()->startPublishing(mErrorGainHigh); + + mFecIdMinorAltroError = new TH2F("FecIDMinorAltroError", "FecID Minor Altro Error", NFEC, 0, NFEC, NDDL_FEE, 0, NDDL_FEE); + mFecIdMinorAltroError->GetYaxis()->SetTitle("fecID"); + mFecIdMinorAltroError->GetXaxis()->SetTitle("Link"); + mFecIdMinorAltroError->SetStats(0); + getObjectsManager()->startPublishing(mFecIdMinorAltroError); + + mChannelGainLow = new TH2F("ChannelLGnoHG", "Channel with HG bunch missing", 96, -0.5, 95.5, 208, -0.5, 207.5); + mChannelGainLow->GetXaxis()->SetTitle("Column"); + mChannelGainLow->GetYaxis()->SetTitle("Row"); + mChannelGainLow->SetStats(false); + getObjectsManager()->startPublishing(mChannelGainLow); + + mChannelGainHigh = new TH2F("ChannelHGnoLG", "Channel with LG bunch missing", 96, -0.5, 95.5, 208, -0.5, 207.5); + mChannelGainHigh->GetXaxis()->SetTitle("Column"); + mChannelGainHigh->GetYaxis()->SetTitle("Row"); + mChannelGainHigh->SetStats(false); + getObjectsManager()->startPublishing(mChannelGainHigh); + + mTRUErrorType = new TH2F("TRUErrorType", "TRU decoding error (type)", NDDL_FEE, 0., NDDL_FEE, o2::emcal::reconstructionerrors::getNumberOfTRUErrorCodes(), 0, o2::emcal::reconstructionerrors::getNumberOfTRUErrorCodes()); + mTRUErrorType->GetXaxis()->SetTitle("Link"); + mTRUErrorType->GetYaxis()->SetTitle("Error type"); + mTRUErrorType->SetStats(false); + for (int ierror = 0; ierror < o2::emcal::reconstructionerrors::getNumberOfTRUErrorCodes(); ierror++) { + mTRUErrorType->GetYaxis()->SetBinLabel(ierror + 1, o2::emcal::reconstructionerrors::getTRUDecodingErrorName(ierror)); + } + getObjectsManager()->startPublishing(mTRUErrorType); + + mTRUErrorPosition = new TH2F("TRUErrorPosition", "TRU decoding error (position)", NDDL_FEE, 0., NDDL_FEE, o2::emcal::TriggerMappingV2::ALLTRUS, binshift, o2::emcal::TriggerMappingV2::ALLTRUS + binshift); + mTRUErrorPosition->GetXaxis()->SetTitle("Link"); + mTRUErrorPosition->GetYaxis()->SetTitle("TRU ID"); + mTRUErrorPosition->SetStats(false); + getObjectsManager()->startPublishing(mTRUErrorPosition); + + mErrorTypeUnknown = new TH1F("UnknownErrorType", "Unknown error types", NDDL_ALL, 0., NDDL_ALL); + mErrorTypeUnknown->GetXaxis()->SetTitle("Link"); + mErrorTypeUnknown->GetYaxis()->SetTitle("Number of errors"); + getObjectsManager()->startPublishing(mErrorTypeUnknown); + + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); + mMapper = std::make_unique(); +} + +void RawErrorTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + reset(); +} + +void RawErrorTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void RawErrorTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + constexpr auto originEMC = o2::header::gDataOriginEMC; + + std::vector filter{ { "filter", framework::ConcreteDataTypeMatcher(originEMC, "DECODERERR") } }; + int firstEntry = 0; + for (const auto& rawErrorData : framework::InputRecordWalker(ctx.inputs(), filter)) { + auto errorcont = o2::framework::DataRefUtils::as(rawErrorData); // read error message + ILOG(Debug, Devel) << "Received " << errorcont.size() << " errors" << ENDM; + for (auto& error : errorcont) { + auto feeid = error.getFEEID(); + if (error.getErrorType() != o2::emcal::ErrorTypeFEE::ErrorSource_t::GAIN_ERROR || !mExcludeGainErrorsFromOverview) + mErrorTypeAll->Fill(feeid, error.getErrorType()); + auto errorCode = error.getErrorCode(); + TH2* errorhist = nullptr; + switch (error.getErrorType()) { + case o2::emcal::ErrorTypeFEE::ErrorSource_t::PAGE_ERROR: + errorhist = mErrorTypePage; + break; + case o2::emcal::ErrorTypeFEE::ErrorSource_t::ALTRO_ERROR: + errorhist = mErrorTypeAltro; + break; + case o2::emcal::ErrorTypeFEE::ErrorSource_t::MINOR_ALTRO_ERROR: + errorhist = mErrorTypeMinAltro; + break; + case o2::emcal::ErrorTypeFEE::ErrorSource_t::FIT_ERROR: + errorhist = mErrorTypeFit; + break; + case o2::emcal::ErrorTypeFEE::ErrorSource_t::GEOMETRY_ERROR: + errorhist = mErrorTypeGeometry; + break; + case o2::emcal::ErrorTypeFEE::ErrorSource_t::GAIN_ERROR: + errorhist = mErrorTypeGain; + break; + default: + // Error type is unknown - this should never happen + // In order to monitor such messages fill a dedicated + // counter histogram + mErrorTypeUnknown->Fill(feeid); + break; + }; // switch errorCode + if (errorhist) { + errorhist->Fill(feeid, errorCode); + } + + if (error.getErrorType() == o2::emcal::ErrorTypeFEE::ErrorSource_t::GAIN_ERROR) { + // Fill Histogram with FEC ID + auto fecID = error.getSubspecification(); + auto gainerrortype = o2::emcal::reconstructionerrors::getGainErrorFromErrorCode(errorCode); + switch (gainerrortype) { + case o2::emcal::reconstructionerrors::GainError_t::LGNOHG: + mErrorGainLow->Fill(feeid, fecID); // LGnoHG + break; + case o2::emcal::reconstructionerrors::GainError_t::HGNOLG: + mErrorGainHigh->Fill(feeid, fecID); // HGnoLG + break; + default: + break; // Unknown gain error - not handel + } + // Fill histogram with tower position + if (error.getHarwareAddress() >= 0) { + auto supermoduleID = feeid / 2; + try { + auto& mapping = mMapper->getMappingForDDL(feeid); + auto colOnline = mapping.getColumn(error.getHarwareAddress()); + auto rowOnline = mapping.getRow(error.getHarwareAddress()); + auto [rowCorrected, colCorrected] = mGeometry->ShiftOnlineToOfflineCellIndexes(supermoduleID, rowOnline, colOnline); + auto cellID = mGeometry->GetAbsCellIdFromCellIndexes(supermoduleID, rowCorrected, colCorrected); + auto [globalRow, globalColumn] = mGeometry->GlobalRowColFromIndex(cellID); + if (errorCode == 0) { + mChannelGainLow->Fill(globalColumn, globalRow); // LGnoHG + } else { + mChannelGainHigh->Fill(globalColumn, globalRow); // HGnoLG + } + } catch (o2::emcal::MappingHandler::DDLInvalid& e) { + ILOG(Warning, Support) << e.what() << ENDM; + } catch (o2::emcal::Mapper::AddressNotFoundException& e) { + ILOG(Warning, Support) << e.what() << ENDM; + } catch (o2::emcal::InvalidCellIDException& e) { + ILOG(Warning, Support) << e.what() << ENDM; + } + } + } + if (error.getErrorType() == o2::emcal::ErrorTypeFEE::ErrorSource_t::MINOR_ALTRO_ERROR) { + auto fecID = error.getSubspecification(); // check: hardware address, or tower id (after markus implementation) + mFecIdMinorAltroError->Fill(feeid, fecID); + } + if (error.getErrorType() == o2::emcal::ErrorTypeFEE::ErrorSource_t::TRU_ERROR) { + auto truID = error.getSubspecification(); + mTRUErrorType->Fill(feeid, errorCode); + mTRUErrorPosition->Fill(feeid, truID); + } + } // end for error in errorcont + } // end of loop on raw error data +} // end of monitorData + +void RawErrorTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void RawErrorTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void RawErrorTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mErrorTypeAll->Reset(); + mErrorTypeAltro->Reset(); + mErrorTypePage->Reset(); + mErrorTypeMinAltro->Reset(); + mErrorTypeFit->Reset(); + mErrorTypeGeometry->Reset(); + mErrorTypeGain->Reset(); + mErrorTypeUnknown->Reset(); + mErrorGainLow->Reset(); + mErrorGainHigh->Reset(); + mFecIdMinorAltroError->Reset(); + mTRUErrorType->Reset(); + mTRUErrorPosition->Reset(); +} +std::string RawErrorTask::getConfigValue(const std::string_view key) +{ + std::string result; + if (auto param = mCustomParameters.find(key.data()); param != mCustomParameters.end()) { + result = param->second; + } + return result; +} + +std::string RawErrorTask::getConfigValueLower(const std::string_view key) +{ + auto input = getConfigValue(key); + std::string result; + if (input.length()) { + result = boost::algorithm::to_lower_copy(input); + } + return result; +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/RawTask.cxx b/Modules/EMCAL/src/RawTask.cxx new file mode 100644 index 0000000000..e74a649166 --- /dev/null +++ b/Modules/EMCAL/src/RawTask.cxx @@ -0,0 +1,764 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "DetectorsRaw/RawHeaderStream.h" +#include "DetectorsRaw/RDHUtils.h" +#include "EMCAL/RawTask.h" +#include "Headers/RAWDataHeader.h" +#include "EMCALBase/Geometry.h" +#include "EMCALReconstruction/AltroDecoder.h" +#include "EMCALReconstruction/RawDecodingError.h" +#include "EMCALReconstruction/RawReaderMemory.h" +#include +#include +#include +#include +#include +#include + +using namespace o2::emcal; + +namespace o2::quality_control_modules::emcal +{ + +RawTask::~RawTask() +{ + if (mPayloadSizePerDDL) { + delete mPayloadSizePerDDL; + } + if (mPayloadSizePerDDL_1D) { + delete mPayloadSizePerDDL_1D; + } + if (mPayloadSizeTFPerDDL) { + delete mPayloadSizeTFPerDDL; + } + if (mPayloadSizeTFPerDDL_1D) { + delete mPayloadSizeTFPerDDL_1D; + } + if (mMessageCounter) { + delete mMessageCounter; + } + if (mNumberOfPagesPerMessage) { + delete mNumberOfPagesPerMessage; + } + if (mNumberOfSuperpagesPerMessage) { + delete mNumberOfSuperpagesPerMessage; + } + if (mTotalDataVolume) { + delete mTotalDataVolume; + } + if (mErrorTypeAltro) { + delete mErrorTypeAltro; + } + if (mNbunchPerChan) { + delete mNbunchPerChan; + } + if (mNofADCsamples) { + delete mNofADCsamples; + } + if (mADCsize) { + delete mADCsize; + } + if (mFECmaxCountperSM) { + delete mFECmaxCountperSM; + } + if (mFECmaxIDperSM) { + delete mFECmaxIDperSM; + } + if (mTFerrorCounter) { + delete mTFerrorCounter; + } + + for (auto& histos : mRMSBunchADCRCFull) { + delete histos.second; + } + + for (auto& histos : mMeanBunchADCRCFull) { + delete histos.second; + } + + for (auto& histos : mMaxChannelADCRCFull) { + delete histos.second; + } + + for (auto& histos : mMinChannelADCRCFull) { + delete histos.second; + } + + for (auto& histos : mBunchMinRawAmpSM) { + delete histos.second; + } + for (auto& histos : mBunchMinRawAmpFEC) { + delete histos.second; + } + for (auto& histos : mBunchMaxRawAmpSM) { + delete histos.second; + } + for (auto& histos : mBunchMaxRawAmpFEC) { + delete histos.second; + } + for (auto& histos : mSMMinRawAmpSM) { + delete histos.second; + } + for (auto& histos : mSMMaxRawAmpSM) { + delete histos.second; + } +} + +void RawTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + using infoCONTEXT = AliceO2::InfoLogger::InfoLoggerContext; + infoCONTEXT context; + context.setField(infoCONTEXT::FieldName::Facility, "QC"); + context.setField(infoCONTEXT::FieldName::System, "QC"); + context.setField(infoCONTEXT::FieldName::Detector, "EMC"); + QcInfoLogger::GetInfoLogger().setContext(context); + ILOG(Debug, Devel) << "initialize RawTask" << ENDM; + + // initialize geometry + if (!mGeometry) + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); + + mMappings = std::unique_ptr(new o2::emcal::MappingHandler); // initialize the unique pointer to Mapper + + // Statistics histograms + mMessageCounter = new TH1F("NumberOfMessages", "Number of messages", 3, 0.5, 3.5); + // mMessageCounter->GetXaxis()->SetTitle("MonitorData"); + mMessageCounter->GetXaxis()->SetBinLabel(1, "Number of message/time"); + mMessageCounter->GetXaxis()->SetBinLabel(2, "Number of super pages/time"); + mMessageCounter->GetXaxis()->SetBinLabel(3, "Number of pages/time"); + mMessageCounter->GetYaxis()->SetTitle("Number of objects"); + getObjectsManager()->startPublishing(mMessageCounter); + + mTFerrorCounter = new TH1F("NumberOfTFerror", "Number of TFbuilder errors", 2, 0.5, 2.5); + mTFerrorCounter->GetYaxis()->SetTitle("Time Frame Builder Error"); + mTFerrorCounter->GetXaxis()->SetBinLabel(1, "empty"); + mTFerrorCounter->GetXaxis()->SetBinLabel(2, "filled"); + getObjectsManager()->startPublishing(mTFerrorCounter); + + mNumberOfSuperpagesPerMessage = new TH1F("NumberOfSuperpagesPerMessage", "Number of superpages per message", 40., 0., 40.); + mNumberOfSuperpagesPerMessage->GetXaxis()->SetTitle("Number of superpages"); + mNumberOfSuperpagesPerMessage->GetYaxis()->SetTitle("Number of messages"); + getObjectsManager()->startPublishing(mNumberOfSuperpagesPerMessage); + + mNumberOfPagesPerMessage = new TH1F("NumberOfPagesPerMessage", "Number of pages per message", 400, 0., 400.); + mNumberOfPagesPerMessage->GetXaxis()->SetTitle("Number of pages"); + mNumberOfPagesPerMessage->GetYaxis()->SetTitle("Number of messages"); + getObjectsManager()->startPublishing(mNumberOfPagesPerMessage); + + mTotalDataVolume = new TH1D("TotalDataVolume", "Total data volume", 1, 0.5, 1.5); + mTotalDataVolume->GetXaxis()->SetTitle("MonitorData"); + mTotalDataVolume->GetYaxis()->SetTitle("Total data volume (Byte)"); + getObjectsManager()->startPublishing(mTotalDataVolume); + + // EMCAL related histograms + mPayloadSizePerDDL = new TH2F("PayloadSizePerDDL", "Payload Size / Event", 40, 0, 40, 200, 0, 20); + mPayloadSizePerDDL->GetXaxis()->SetTitle("DDL"); + mPayloadSizePerDDL->GetYaxis()->SetTitle("Payload Size / Event (kB)"); + mPayloadSizePerDDL->SetStats(0); + getObjectsManager()->startPublishing(mPayloadSizePerDDL); + + mPayloadSizePerDDL_1D = new TH1F("PayloadSizePerDDL_1D", "Accumulated Payload Size / Event", 40, 0, 40); + mPayloadSizePerDDL_1D->GetXaxis()->SetTitle("DDL"); + mPayloadSizePerDDL_1D->GetYaxis()->SetTitle("Accumulated Payload Size / Event (kB)"); + mPayloadSizePerDDL_1D->SetStats(0); + getObjectsManager()->startPublishing(mPayloadSizePerDDL_1D); + + mPayloadSizeTFPerDDL = new TH2F("PayloadSizeTFPerDDL", "Payload Size / TF", 40, 0, 40, 100, 0, 100); + mPayloadSizeTFPerDDL->GetXaxis()->SetTitle("DDL"); + mPayloadSizeTFPerDDL->GetYaxis()->SetTitle("Payload Size / TF (kB)"); + mPayloadSizeTFPerDDL->SetStats(0); + getObjectsManager()->startPublishing(mPayloadSizeTFPerDDL); + + mPayloadSizeTFPerDDL_1D = new TH1F("PayloadSizeTFPerDDL_1D", "Accumulated Payload Size / TF ", 40, 0, 40); + mPayloadSizeTFPerDDL_1D->GetXaxis()->SetTitle("DDL"); + mPayloadSizeTFPerDDL_1D->GetYaxis()->SetTitle("Accumulated Payload Size / TF (kB)"); + mPayloadSizeTFPerDDL_1D->SetStats(0); + getObjectsManager()->startPublishing(mPayloadSizeTFPerDDL_1D); + + mErrorTypeAltro = new TH2F("ErrorTypePerSM", "ErrorTypeForSM", 40, 0, 40, 10, 0, 10); // optional? OFFLINE + mErrorTypeAltro->GetXaxis()->SetTitle("SM"); + mErrorTypeAltro->GetYaxis()->SetTitle("Error Type"); + mErrorTypeAltro->GetYaxis()->SetBinLabel(1, "RCU Trailer"); + mErrorTypeAltro->GetYaxis()->SetBinLabel(2, "RCU Version"); + mErrorTypeAltro->GetYaxis()->SetBinLabel(3, "RCU Trailer Size"); + mErrorTypeAltro->GetYaxis()->SetBinLabel(4, "ALTRO Bunch Header"); + mErrorTypeAltro->GetYaxis()->SetBinLabel(5, "ALTRO Bunch Length"); + mErrorTypeAltro->GetYaxis()->SetBinLabel(6, "ALTRO Payload"); + mErrorTypeAltro->GetYaxis()->SetBinLabel(7, "ALTRO Mapping"); + mErrorTypeAltro->GetYaxis()->SetBinLabel(8, "Channel"); + mErrorTypeAltro->GetYaxis()->SetBinLabel(9, "Mapper HWAddress"); + mErrorTypeAltro->GetYaxis()->SetBinLabel(10, "Geometry InvalidCell"); + mErrorTypeAltro->SetStats(0); + getObjectsManager() + ->startPublishing(mErrorTypeAltro); + + mNbunchPerChan = new TH1F("NumberBunchPerChannel", "Number of bunches per channel", 4, -0.5, 3.5); + mNbunchPerChan->GetXaxis()->SetTitle("# bunches per channels"); + mNbunchPerChan->SetStats(0); + getObjectsManager()->startPublishing(mNbunchPerChan); + + mNofADCsamples = new TH1F("NumberOfADCPerChannel", "Number od ADC samples per channel", 15, -0.5, 14.5); + mNofADCsamples->GetXaxis()->SetTitle("# of ADC sample per channels"); + mNofADCsamples->SetStats(0); + getObjectsManager()->startPublishing(mNofADCsamples); + + mADCsize = new TH1F("ADCsizePerBunch", "ADCsizePerBunch", 15, -0.5, 14.5); + mADCsize->GetXaxis()->SetTitle("ADC size per bunch"); + mADCsize->SetStats(0); + getObjectsManager()->startPublishing(mADCsize); + + mFECmaxCountperSM = new TH2F("NumberOfChWithInput_perSM", "NumberOfChWithInput_perSM", 20, 0, 20, 40, 0, 40); + mFECmaxCountperSM->GetXaxis()->SetTitle("SM"); + mFECmaxCountperSM->GetYaxis()->SetTitle("max channel count"); + mFECmaxCountperSM->SetStats(0); + getObjectsManager()->startPublishing(mFECmaxCountperSM); // work on it, keep them + + mFECmaxIDperSM = new TH2F("FECidMaxChWithInput_perSM", "FECidMaxChWithInput_perSM", 20, 0, 20, 40, 0, 40); + mFECmaxIDperSM->GetXaxis()->SetTitle("SM"); + mFECmaxIDperSM->GetYaxis()->SetTitle("FEC id"); + mFECmaxIDperSM->SetStats(0); + getObjectsManager()->startPublishing(mFECmaxIDperSM); // work on it, keep them + + // histos per SM and Trigger + EventType triggers[2] = { EventType::CAL_EVENT, EventType::PHYS_EVENT }; + TString histoStr[2] = { "CAL", "PHYS" }; + for (auto trg = 0; trg < 2; trg++) { + + TProfile2D* histosRawAmplRmsRC; // Filling EMCAL/DCAL + TProfile2D* histosRawAmplMeanRC; + TProfile2D* histosRawAmplMaxRC; + TProfile2D* histosRawAmplMinRC; + // EMCAL+DCAL histo + histosRawAmplRmsRC = new TProfile2D(Form("RMSADC_EMCAL_%s", histoStr[trg].Data()), Form("Bunch ADC RMS (%s)", histoStr[trg].Data()), 96, -0.5, 95.5, 208, -0.5, 207.5); + histosRawAmplRmsRC->GetXaxis()->SetTitle("col"); + histosRawAmplRmsRC->GetYaxis()->SetTitle("row"); + histosRawAmplRmsRC->SetStats(0); + getObjectsManager()->startPublishing(histosRawAmplRmsRC); // markus/martin full emcal not so big + + histosRawAmplMeanRC = new TProfile2D(Form("MeanADC_EMCAL_%s", histoStr[trg].Data()), Form("Bunch ADC mean (%s)", histoStr[trg].Data()), 96, -0.5, 95.5, 208, -0.5, 207.5); + histosRawAmplMeanRC->GetXaxis()->SetTitle("col"); + histosRawAmplMeanRC->GetYaxis()->SetTitle("row"); + getObjectsManager()->startPublishing(histosRawAmplMeanRC); // markus/martin full emcal not so big + + histosRawAmplMaxRC = new TProfile2D(Form("MaxADC_EMCAL_%s", histoStr[trg].Data()), Form("Channel ADC max (%s)", histoStr[trg].Data()), 96, -0.5, 95.5, 208, -0.5, 207.5); + histosRawAmplMaxRC->GetXaxis()->SetTitle("col"); + histosRawAmplMaxRC->GetYaxis()->SetTitle("row"); + histosRawAmplMaxRC->SetStats(0); + getObjectsManager()->startPublishing(histosRawAmplMaxRC); // markus/martin full emcal not so big + + histosRawAmplMinRC = new TProfile2D(Form("MinADC_EMCAL_%s", histoStr[trg].Data()), Form("Channel ADC min (%s)", histoStr[trg].Data()), 96, -0.5, 95.5, 208, -0.5, 207.5); + histosRawAmplMinRC->GetXaxis()->SetTitle("col"); + histosRawAmplMinRC->GetYaxis()->SetTitle("raw"); + histosRawAmplMinRC->SetStats(0); + getObjectsManager()->startPublishing(histosRawAmplMinRC); // markus/martin full emcal not so big + + TH2* histosBunchMinRawAmpSM = new TH2D(Form("BunchMinRawAmplitudeSM_%s", histoStr[trg].Data()), Form("Bunch min raw amplitude per supermodule (%s)", histoStr[trg].Data()), 100, 0., 100., 20, -0.5, 19.5); + histosBunchMinRawAmpSM->GetXaxis()->SetTitle("Min raw amplitude (ADC)"); + histosBunchMinRawAmpSM->GetYaxis()->SetTitle("Supermodule ID"); + histosBunchMinRawAmpSM->SetStats(0); + getObjectsManager()->startPublishing(histosBunchMinRawAmpSM); + + TH2* histosBunchMinRawAmpFEC = new TH2D(Form("BunchMinRawAmplitudeFEC_%s", histoStr[trg].Data()), Form("Bunch min raw amplitude per FEC (%s)", histoStr[trg].Data()), 100, 0., 100., 800, -0.5, 799.5); + histosBunchMinRawAmpFEC->GetXaxis()->SetTitle("Min raw amplitude (ADC)"); + histosBunchMinRawAmpFEC->GetYaxis()->SetTitle("FEC ID"); + histosBunchMinRawAmpFEC->SetStats(0); + getObjectsManager()->startPublishing(histosBunchMinRawAmpFEC); + + TH2* histosBunchMaxRawAmpSM = new TH2D(Form("BunchMaxRawAmplitudeSM_%s", histoStr[trg].Data()), Form("Bunch max raw amplitude per supermodule (%s)", histoStr[trg].Data()), 500, 0., 500., 20, -0.5, 19.5); + histosBunchMaxRawAmpSM->GetXaxis()->SetTitle("Max raw amplitude (ADC)"); + histosBunchMaxRawAmpSM->GetYaxis()->SetTitle("Supermodule ID"); + histosBunchMaxRawAmpSM->SetStats(0); + getObjectsManager()->startPublishing(histosBunchMaxRawAmpSM); + + TH2* histosBunchMaxRawAmpFEC = new TH2D(Form("BunchMaxRawAmplitudeFEC_%s", histoStr[trg].Data()), Form("Bunch max raw amplitude per FEC (%s)", histoStr[trg].Data()), 500, 0., 500., 800, -0.5, 799.5); + histosBunchMaxRawAmpFEC->GetXaxis()->SetTitle("Max raw amplitude (ADC)"); + histosBunchMaxRawAmpFEC->GetYaxis()->SetTitle("FEC ID"); + histosBunchMaxRawAmpFEC->SetStats(0); + getObjectsManager()->startPublishing(histosBunchMaxRawAmpFEC); + + TH2* histosSMMinRawAmpSM = new TH2D(Form("SMMinRawAmplitudeSM_%s", histoStr[trg].Data()), Form("Min SM raw amplitude per supermodule (%s)", histoStr[trg].Data()), 100, 0., 100., 20, -0.5, 19.5); + histosSMMinRawAmpSM->GetXaxis()->SetTitle("Min raw amplitude (ADC)"); + histosSMMinRawAmpSM->GetYaxis()->SetTitle("Supermodule ID"); + histosSMMinRawAmpSM->SetStats(0); + getObjectsManager()->startPublishing(histosSMMinRawAmpSM); + + TH2* histosSMMaxRawAmpSM = new TH2D(Form("SMMaxRawAmplitudeSM_%s", histoStr[trg].Data()), Form("Max SM raw amplitude per supermodule (%s)", histoStr[trg].Data()), 500, 0., 500., 20, -0.5, 19.5); + histosSMMaxRawAmpSM->GetXaxis()->SetTitle("Max raw amplitude (ADC)"); + histosSMMaxRawAmpSM->GetYaxis()->SetTitle("Supermodule ID"); + histosSMMaxRawAmpSM->SetStats(0); + getObjectsManager()->startPublishing(histosSMMaxRawAmpSM); + + mRMSBunchADCRCFull[triggers[trg]] = histosRawAmplRmsRC; + mMeanBunchADCRCFull[triggers[trg]] = histosRawAmplMeanRC; + mMaxChannelADCRCFull[triggers[trg]] = histosRawAmplMaxRC; + mMinChannelADCRCFull[triggers[trg]] = histosRawAmplMinRC; + + mBunchMinRawAmpSM[triggers[trg]] = histosBunchMinRawAmpSM; + mBunchMinRawAmpFEC[triggers[trg]] = histosBunchMinRawAmpFEC; + mBunchMaxRawAmpSM[triggers[trg]] = histosBunchMaxRawAmpSM; + mBunchMaxRawAmpFEC[triggers[trg]] = histosBunchMaxRawAmpFEC; + + mSMMinRawAmpSM[triggers[trg]] = histosSMMinRawAmpSM; + mSMMaxRawAmpSM[triggers[trg]] = histosSMMaxRawAmpSM; + + } // loop trigger case +} + +void RawTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + reset(); +} + +void RawTask::startOfCycle() +{ + ILOG(Debug, Support) << "startOfCycle" << ENDM; +} + +void RawTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + using CHTYP = o2::emcal::ChannelType_t; + + // The type DataOrigin allows only conversion of char arrays with size 4, not char *, therefore + // the origin string has to be converted manually to the char array and checked for length. + if (mDataOrigin.size() > 4) { + ILOG(Error, Support) << "No valid data origin" << mDataOrigin << ", cannot process" << ENDM; + return; + } + char dataOrigin[4]; + strcpy(dataOrigin, mDataOrigin.data()); + + if (isLostTimeframe(ctx)) { + mTFerrorCounter->Fill(1); + return; + } + mTFerrorCounter->Fill(2); + + Int_t nPagesMessage = 0, nSuperpagesMessage = 0; + ILOG(Debug, Support) << " Processing message " << mNumberOfMessages << ENDM; + mNumberOfMessages++; + mMessageCounter->Fill(0); // for expert fill bin 1 with number of messages + + const int NUMBERSM = 20; + const int NFEESM = 40; // number of fee per sm + + double thresholdMinADCocc = 3, + thresholdMaxADCocc = 15; + + std::unordered_map, RawEventTypeHash> maxADCSM, minADCSM; + std::unordered_map, NUMBERSM>, RawEventTypeHash> fecMaxPayload; + + // Accept only descriptor RAWDATA, discard FLP/SUBTIMEFRAME + auto posReadout = ctx.inputs().getPos("readout"); + auto nslots = ctx.inputs().getNofParts(posReadout); + for (decltype(nslots) islot = 0; islot < nslots; islot++) { + auto rawData = ctx.inputs().getByPos(posReadout, islot); + // get message header + if (rawData.header != nullptr && rawData.payload != nullptr) { + const auto* header = o2::framework::DataRefUtils::getHeader(rawData); + const auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(rawData); + // get payload of a specific input, which is a char array. + ILOG(Debug, Support) << "Processing superpage " << mNumberOfSuperpages << ENDM; + mNumberOfSuperpages++; + nSuperpagesMessage++; + mMessageCounter->Fill(1); // fill bin 2 with mSuperpageCounter + ILOG(Debug, Support) << " EMCAL Reading Payload size: " << payloadSize << " for " << header->dataOrigin << ENDM; + + // fill the histogram with payload sizes + mTotalDataVolume->Fill(1., payloadSize); // for expert + + // Skip SOX headers + auto rdhblock = reinterpret_cast(rawData.payload); // + if (o2::raw::RDHUtils::getHeaderSize(rdhblock) == static_cast(payloadSize)) { + continue; + } + mPayloadSizeTFPerDDL->Fill(o2::raw::RDHUtils::getFEEID(rdhblock), payloadSize / 1024.); // PayLoad size per TimeFrame for shifter + mPayloadSizeTFPerDDL_1D->Fill(o2::raw::RDHUtils::getFEEID(rdhblock), payloadSize / 1024.); + + // try decoding payload + o2::emcal::RawReaderMemory rawreader(ctx.inputs().get>(rawData)); + + while (rawreader.hasNext()) { + + ILOG(Debug, Support) << " Processing page " << mNumberOfPages << ENDM; + mNumberOfPages++; + nPagesMessage++; + mMessageCounter->Fill(2); // fill bin 3 with PageCounter + try { + rawreader.next(); + } catch (RawDecodingError& e) { + // Skip page in case of page decoding errors + // For corrupted RDHs the determination of the next page + // will fail, leading to an infinity loop + break; + } + auto rawSize = rawreader.getPayloadSize(); // payloadsize in byte; + + auto rdh = rawreader.getRawHeader(); + auto feeID = o2::raw::RDHUtils::getFEEID(rdh); + + if (feeID > 40) + continue; // skip STU ddl + + o2::InteractionRecord triggerIR{ o2::raw::RDHUtils::getTriggerBC(rdh), o2::raw::RDHUtils::getTriggerOrbit(rdh) }; + RawEventType evIndex{ triggerIR, o2::raw::RDHUtils::getTriggerType(rdh) }; + + // trigger type + auto triggertype = o2::raw::RDHUtils::getTriggerType(rdh); + bool isPhysTrigger = triggertype & o2::trigger::PhT, isCalibTrigger = triggertype & o2::trigger::Cal; + if (isPhysTrigger) { + mPayloadSizePerDDL->Fill(feeID, rawSize / 1024.); // for shifter + mPayloadSizePerDDL_1D->Fill(feeID, rawSize / 1024.); // for shifter + } + if (!(isPhysTrigger || isCalibTrigger)) { + ILOG(Error, Support) << " Unmonitored trigger class requested " << ENDM; + continue; + } + + // Needs separate maps for the two trigger classes + auto fecMaxChannelsEvent = fecMaxPayload.find(evIndex); + if (fecMaxChannelsEvent == fecMaxPayload.end()) { + std::array, NUMBERSM> fecMaxCh; + for (auto ism = 0; ism < NUMBERSM; ism++) { + std::fill(fecMaxCh[ism].begin(), fecMaxCh[ism].end(), 0); + } + fecMaxChannelsEvent = (fecMaxPayload.insert({ evIndex, fecMaxCh })).first; + } + + auto maxADCSMEvent = maxADCSM.find(evIndex); + if (maxADCSMEvent == maxADCSM.end()) { // No entry found for key triggerIR + std::array maxadc; + memset(maxadc.data(), 0, maxadc.size()); + maxADCSMEvent = (maxADCSM.insert({ evIndex, maxadc })).first; + } + auto minADCSMEvent = minADCSM.find(evIndex); + if (minADCSMEvent == minADCSM.end()) { // No entry found for key triggerIR + std::array minadc; + memset(minadc.data(), 0, minadc.size()); + for (auto ism = 0; ism < NUMBERSM; ism++) + minadc[ism] = SHRT_MAX; + minADCSMEvent = (minADCSM.insert({ evIndex, minadc })).first; + } + + o2::emcal::AltroDecoder decoder(rawreader); + // check the words of the payload exception in altrodecoder + try { + decoder.decode(); + } catch (AltroDecoderError& e) { + std::stringstream errormessage; + using AltroErrType = o2::emcal::AltroDecoderError::ErrorType_t; + int errornum = -1; + switch (e.getErrorType()) { + case AltroErrType::RCU_TRAILER_ERROR: + errornum = 0; + errormessage << " RCU Trailer Error "; + break; + case AltroErrType::RCU_VERSION_ERROR: + errornum = 1; + errormessage << " RCU Version Error "; + break; + case AltroErrType::RCU_TRAILER_SIZE_ERROR: + errornum = 2; + errormessage << " RCU Trailer Size Error "; + break; + case AltroErrType::ALTRO_BUNCH_HEADER_ERROR: + errornum = 3; + errormessage << " ALTRO Bunch Header Error "; + break; + case AltroErrType::ALTRO_BUNCH_LENGTH_ERROR: + errornum = 4; + errormessage << " ALTRO Bunch Length Error "; + break; + case AltroErrType::ALTRO_PAYLOAD_ERROR: + errornum = 5; + errormessage << " ALTRO Payload Error "; + break; + case AltroErrType::ALTRO_MAPPING_ERROR: + errornum = 6; + errormessage << " ALTRO Mapping Error "; + break; + case AltroErrType::CHANNEL_ERROR: + errornum = 7; + errormessage << " Channel Error "; + break; + default: + break; + } + errormessage << " in Supermodule " << feeID; + ILOG(Error, Support) << " EMCAL raw task: " << errormessage.str() << ENDM; + // fill histograms with error types + mErrorTypeAltro->Fill(feeID, errornum); // for shifter + continue; + } + int supermoduleID = feeID / 2; // SM id + auto& mapping = mMappings->getMappingForDDL(feeID); + + auto fecIndex = 0; + auto branchIndex = 0; + auto fecID = 0; + + for (auto& chan : decoder.getChannels()) { + // Row and column in online format, must be remapped to offline indexing, + // otherwise it leads to invalid cell IDs + int colOnline, rowOnline; + o2::emcal::ChannelType_t chType; + try { + colOnline = mapping.getColumn(chan.getHardwareAddress()); + rowOnline = mapping.getRow(chan.getHardwareAddress()); + chType = mapping.getChannelType(chan.getHardwareAddress()); + } catch (o2::emcal::Mapper::AddressNotFoundException& err) { + ILOG(Error, Support) << "DDL " << feeID << ": " << err.what() << ENDM; + mErrorTypeAltro->Fill(feeID, 8); + continue; + } + // exclude LED Mon, TRU + if (chType == CHTYP::LEDMON || chType == CHTYP::TRU) + continue; + + int globRow(-1), globCol(-1); + try { + auto [row, col] = mGeometry->ShiftOnlineToOfflineCellIndexes(supermoduleID, rowOnline, colOnline); + // tower absolute ID + auto cellID = mGeometry->GetAbsCellIdFromCellIndexes(supermoduleID, row, col); + if (cellID > 17664) { + mErrorTypeAltro->Fill(feeID, 9); + continue; + } + // position in the EMCAL + auto [globRowTmp, globColTmp] = mGeometry->GlobalRowColFromIndex(cellID); + globRow = globRowTmp; + globCol = globColTmp; + } catch (o2::emcal::InvalidCellIDException& e) { + mErrorTypeAltro->Fill(feeID, 9); + continue; + } + + fecIndex = chan.getFECIndex(); + branchIndex = chan.getBranchIndex(); + fecID = mMappings->getFEEForChannelInDDL(feeID, fecIndex, branchIndex); + auto globalFecID = supermoduleID * NFEESM + fecID; + fecMaxChannelsEvent->second[supermoduleID][fecID]++; + + Short_t maxADC = 0; + Short_t minADC = SHRT_MAX; + Double_t meanADC = 0; + Double_t rmsADC = 0; + EventType evtype = isPhysTrigger ? EventType::PHYS_EVENT : EventType::CAL_EVENT; + + mNbunchPerChan->Fill(chan.getBunches().size()); //(1 histo for EMCAL-526).//1, if high rate --> pile up. + + int numberOfADCsamples = 0; + for (auto& bunch : chan.getBunches()) { + const auto& adcs = bunch.getADC(); + numberOfADCsamples += adcs.size(); + mADCsize->Fill(adcs.size()); + + if (adcs.size() == 0) { + // protection against 0 bunch size (possible data corruption in bunch header) + ILOG(Debug, Support) << "Bunch with length 0 detected" << ENDM; + continue; + } + + auto maxADCbunch = *max_element(adcs.begin(), adcs.end()); + if (maxADCbunch > maxADC) + maxADC = maxADCbunch; + mBunchMaxRawAmpFEC[evtype]->Fill(maxADCbunch, globalFecID); + mBunchMaxRawAmpSM[evtype]->Fill(maxADCbunch, supermoduleID); // max for each cell --> for for expert only + + auto minADCbunch = *min_element(adcs.begin(), adcs.end()); + if (minADCbunch < minADC) + minADC = minADCbunch; + mBunchMinRawAmpFEC[evtype]->Fill(minADCbunch, globalFecID); + mBunchMinRawAmpSM[evtype]->Fill(minADCbunch, supermoduleID); // min for each cell --> for for expert only + + meanADC = TMath::Mean(adcs.begin(), adcs.end()); + rmsADC = TMath::RMS(adcs.begin(), adcs.end()); + + mRMSBunchADCRCFull[evtype]->Fill(globCol, globRow, rmsADC); // for shifter + // mRMSBunchADCRCSM[evtype][supermoduleID]->Fill(col, row, rmsADC); // no shifter + + mMeanBunchADCRCFull[evtype]->Fill(globCol, globRow, meanADC); // for shifter + // mMeanBunchADCRCSM[evtype][supermoduleID]->Fill(col, row, meanADC); // no shifter + } + mNofADCsamples->Fill(numberOfADCsamples); // number of bunches per channel + + if (maxADC > maxADCSMEvent->second[supermoduleID]) + maxADCSMEvent->second[supermoduleID] = maxADC; + + // if (maxADC > thresholdMaxADCocc) + // mMaxChannelADCRCSM[evtype][supermoduleID]->Fill(col, row, maxADC); //max col,row, per SM + if (maxADC > thresholdMaxADCocc) + mMaxChannelADCRCFull[evtype]->Fill(globCol, globRow, maxADC); // for shifter + + if (minADC < minADCSMEvent->second[supermoduleID]) { + minADCSMEvent->second[supermoduleID] = minADC; + } + // if (minADC > thresholdMinADCocc) + // mMinChannelADCRCSM[evtype][supermoduleID]->Fill(col, row, minADC); //min col,row, per SM + if (minADC > thresholdMinADCocc) + mMinChannelADCRCFull[evtype]->Fill(globCol, globRow, minADC); // for shifter + } // channels + } // new page + } // header + } // inputs + mNumberOfPagesPerMessage->Fill(nPagesMessage); // for experts + mNumberOfSuperpagesPerMessage->Fill(nSuperpagesMessage); + + // Fill histograms with cached values + for (const auto& maxfec : fecMaxPayload) { + auto triggertype = maxfec.first.mTrigger; + bool isPhysTrigger = triggertype & o2::trigger::PhT; + if (!isPhysTrigger) + continue; // Only select phys event for max FEC, in case of calibration events the whole EMCAL gets the FEC pulse, so the payload size is roughly equal + for (auto ism = 0; ism < NUMBERSM; ism++) { + // Find maximum FEC in array of FECs + int maxfecID(-1), maxfecCount(-1); + auto& fecsSM = maxfec.second[ism]; + for (int ifec = 0; ifec < NFEESM; ifec++) { + if (fecsSM[ifec] > maxfecCount) { + maxfecCount = fecsSM[ifec]; + maxfecID = ifec; + } + } + if (maxfecCount <= 0) + continue; // Reject links on different FLP + + mFECmaxIDperSM->Fill(ism, maxfecID); // filled as a funcion of SM (shifter) + mFECmaxCountperSM->Fill(ism, maxfecCount); // filled as a function of SM (shifter) + } + } + for (const auto& maxadc : maxADCSM) { + auto triggertype = maxadc.first.mTrigger; + bool isPhysTrigger = triggertype & o2::trigger::PhT; + EventType evtype = isPhysTrigger ? EventType::PHYS_EVENT : EventType::CAL_EVENT; + for (int ism = 0; ism < NUMBERSM; ism++) { + if (maxadc.second[ism] == 0) { + continue; + } + mSMMaxRawAmpSM[evtype]->Fill(maxadc.second[ism], ism); + } + } + + for (const auto& minadc : minADCSM) { + auto triggertype = minadc.first.mTrigger; + bool isPhysTrigger = triggertype & o2::trigger::PhT; + EventType evtype = isPhysTrigger ? EventType::PHYS_EVENT : EventType::CAL_EVENT; + for (int ism = 0; ism < NUMBERSM; ism++) { + auto smminadc = minadc.second[ism]; + if (smminadc == SHRT_MAX) { + continue; + } + mSMMinRawAmpSM[evtype]->Fill(smminadc, ism); + } + } + // Same for other cached values +} // function monitor data + +void RawTask::endOfCycle() +{ + ILOG(Debug, Support) << "endOfCycle" << ENDM; +} + +void RawTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; + ILOG(Info, Support) << "Total amount of messages: " << mNumberOfMessages << ENDM; + ILOG(Info, Support) << "Total amount of superpages: " << mNumberOfSuperpages << ", pages: " << mNumberOfPages << ENDM; +} + +void RawTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Support) << "Resetting the histogram" << ENDM; + EventType triggers[2] = { EventType::CAL_EVENT, EventType::PHYS_EVENT }; + + for (const auto& trg : triggers) { + mRMSBunchADCRCFull[trg]->Reset(); + mMeanBunchADCRCFull[trg]->Reset(); + mMaxChannelADCRCFull[trg]->Reset(); + mMinChannelADCRCFull[trg]->Reset(); + mBunchMinRawAmpSM[trg]->Reset(); + mBunchMinRawAmpFEC[trg]->Reset(); + mBunchMaxRawAmpSM[trg]->Reset(); + mBunchMaxRawAmpFEC[trg]->Reset(); + mSMMinRawAmpSM[trg]->Reset(); + mSMMaxRawAmpSM[trg]->Reset(); + } + mMessageCounter->Reset(); + mNumberOfSuperpagesPerMessage->Reset(); + mNumberOfPagesPerMessage->Reset(); + mPayloadSizePerDDL->Reset(); + mPayloadSizePerDDL_1D->Reset(); + mPayloadSizeTFPerDDL->Reset(); + mPayloadSizeTFPerDDL_1D->Reset(); + mErrorTypeAltro->Reset(); + mNbunchPerChan->Reset(); + mNofADCsamples->Reset(); + mADCsize->Reset(); + mFECmaxIDperSM->Reset(); + mFECmaxCountperSM->Reset(); + mTFerrorCounter->Reset(); + mTotalDataVolume->Reset(); +} + +bool RawTask::isLostTimeframe(framework::ProcessingContext& ctx) const +{ + // direct data + constexpr auto originEMC = header::gDataOriginEMC; + o2::framework::InputSpec dummy{ "dummy", + framework::ConcreteDataMatcher{ originEMC, + header::gDataDescriptionRawData, + 0xDEADBEEF } }; + for (const auto& ref : o2::framework::InputRecordWalker(ctx.inputs(), { dummy })) { + // auto posReadout = ctx.inputs().getPos("readout"); + // auto nslots = ctx.inputs().getNofParts(posReadout); + // for (decltype(nslots) islot = 0; islot < nslots; islot++) { + // const auto& ref = ctx.inputs().getByPos(posReadout, islot); + const auto dh = o2::framework::DataRefUtils::getHeader(ref); + const auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(ref); + // if (dh->subSpecification == 0xDEADBEEF) { + if (payloadSize == 0) { + return true; + // } + } + } + // sampled data + o2::framework::InputSpec dummyDS{ "dummyDS", + framework::ConcreteDataMatcher{ "DS", + "emcrawdata0", + 0xDEADBEEF } }; + for (const auto& ref : o2::framework::InputRecordWalker(ctx.inputs(), { dummyDS })) { + // auto posReadout = ctx.inputs().getPos("readout"); + // auto nslots = ctx.inputs().getNofParts(posReadout); + // for (decltype(nslots) islot = 0; islot < nslots; islot++) { + // const auto& ref = ctx.inputs().getByPos(posReadout, islot); + const auto dh = o2::framework::DataRefUtils::getHeader(ref); + const auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(ref); + // if (dh->subSpecification == 0xDEADBEEF) { + if (payloadSize == 0) { + return true; + // } + } + } + + return false; +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/SubdetectorProjectionReductor.cxx b/Modules/EMCAL/src/SubdetectorProjectionReductor.cxx new file mode 100644 index 0000000000..62b7e8a946 --- /dev/null +++ b/Modules/EMCAL/src/SubdetectorProjectionReductor.cxx @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include "EMCAL/SubdetectorProjectionReductor.h" +#include "TH2.h" + +using namespace o2::quality_control_modules::emcal; + +void* SubdetectorProjectionReductor::getBranchAddress() +{ + return &mStats; +} + +const char* SubdetectorProjectionReductor::getBranchLeafList() +{ + return "CountsTotal/D:CountsEMCAL:CountsDCAL:MeanTotal:MeanEMCAL:MeanDCAL:SigmaTotal:SigmaEMCAL:SigmaDCAL"; +} + +void SubdetectorProjectionReductor::update(TObject* obj) +{ + memset(&mStats, 0, sizeof(mStats)); + auto inputhist = dynamic_cast(obj); + for (auto ibin = 0; ibin < 3; ibin++) { + std::unique_ptr projectionSM(inputhist->ProjectionY("detprojection", ibin + 1, ibin + 1)); + auto counts = projectionSM->GetEntries(), + mean = projectionSM->GetMean(), + sigma = projectionSM->GetRMS(); + switch (ibin) { + case 0: + mStats.mCountsTotal = counts; + mStats.mMeanTotal = mean; + mStats.mSigmaTotal = sigma; + break; + case 1: + mStats.mCountsEMCAL = counts; + mStats.mMeanEMCAL = mean; + mStats.mSigmaEMCAL = sigma; + break; + case 2: + mStats.mCountsDCAL = counts; + mStats.mMeanDCAL = mean; + mStats.mSigmaDCAL = sigma; + }; + } +} \ No newline at end of file diff --git a/Modules/EMCAL/src/SupermoduleProjectionReductor.cxx b/Modules/EMCAL/src/SupermoduleProjectionReductor.cxx new file mode 100644 index 0000000000..8cbc6d72fd --- /dev/null +++ b/Modules/EMCAL/src/SupermoduleProjectionReductor.cxx @@ -0,0 +1,39 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include "EMCAL/SupermoduleProjectionReductor.h" +#include "TH2.h" + +using namespace o2::quality_control_modules::emcal; + +void* SupermoduleProjectionReductorBase::getBranchAddress() +{ + return &mStats; +} + +const char* SupermoduleProjectionReductorBase::getBranchLeafList() +{ + return "smCounts[20]/D:smMean[20]:smSigma[20]:smMax[20]"; +} + +void SupermoduleProjectionReductorBase::update(TObject* obj) +{ + memset(&mStats, 0, sizeof(mStats)); + auto inputhist = dynamic_cast(obj); + for (auto ism = 0; ism < 20; ism++) { + std::unique_ptr projectionSM(mSupermoduleAxisX ? inputhist->ProjectionY("smprojection", ism + 1, ism + 1) : inputhist->ProjectionX("smprojection", ism + 1, ism + 1)); + mStats.mCountSM[ism] = projectionSM->GetEntries(); + mStats.mMeanSM[ism] = projectionSM->GetMean(); + mStats.mSigmaSM[ism] = projectionSM->GetRMS(); + mStats.mMaxSM[ism] = projectionSM->GetXaxis()->GetBinCenter(projectionSM->GetMaximumBin()); + } +} \ No newline at end of file diff --git a/Modules/EMCAL/src/SupermoduleProjectorTask.cxx b/Modules/EMCAL/src/SupermoduleProjectorTask.cxx new file mode 100644 index 0000000000..65c2bbf708 --- /dev/null +++ b/Modules/EMCAL/src/SupermoduleProjectorTask.cxx @@ -0,0 +1,276 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "EMCAL/SupermoduleProjectorTask.h" +#include "QualityControl/DatabaseInterface.h" +#include + +#include + +// root includes +#include "TCanvas.h" +#include "TPaveText.h" +#include "TH1D.h" +#include "TH2D.h" +#include "TLatex.h" + +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::emcal +{ + +void SupermoduleProjectorTask::configure(const boost::property_tree::ptree& config) +{ + mDataSources = getDataSources(getID(), config); + mAttributeHandler = parseCustomizations(getID(), config); +} + +void SupermoduleProjectorTask::initialize(Trigger, framework::ServiceRegistryRef) +{ + ILOG(Debug, Devel) << "initialize SuperModuleProjectorTask" << ENDM; + // create canvas objects for each plot + for (const auto& datasource : mDataSources) { + std::string canvasname = "PerSM_" + datasource.name, + canvastitle = datasource.name + " per SM"; + auto plot = new TCanvas(canvasname.data(), canvastitle.data(), 1000, 800); + plot->Divide(4, 5); + getObjectsManager()->startPublishing(plot); + mCanvasHandler[datasource.name] = plot; + } + mIndicesConverter.Initialize(); +} + +void SupermoduleProjectorTask::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + for (auto& dataSource : mDataSources) { + auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name, t.timestamp, t.activity); + if (mo == nullptr) { + ILOG(Warning, Trace) << "Could not retrieve MO '" << dataSource.name << "', skipping this data source" << ENDM; + continue; + } + auto canvas = mCanvasHandler.find(dataSource.name); + if (canvas != mCanvasHandler.end()) { + PlotAttributes* plotCustomizations = nullptr; + auto customiztions = mAttributeHandler.find(dataSource.name); + if (customiztions != mAttributeHandler.end()) { + plotCustomizations = &(customiztions->second); + } + + // retrive associated quality object + std::shared_ptr qo; + if (plotCustomizations && plotCustomizations->qualityPath.length()) { + std::string qopath = dataSource.path; + ILOG(Debug, Support) << "Looking for associated quality: " << plotCustomizations->qualityPath << ENDM; + qo = qcdb.retrieveQO(plotCustomizations->qualityPath, t.timestamp, t.activity); + } + makeProjections(*mo, *(canvas->second), plotCustomizations, qo.get()); + } + } +} + +void SupermoduleProjectorTask::finalize(Trigger t, framework::ServiceRegistryRef) +{ + for (auto& [datasource, plot] : mCanvasHandler) { + getObjectsManager()->stopPublishing(plot); + delete plot; + plot = nullptr; + } +} + +void SupermoduleProjectorTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Support) << "Resetting the histogram" << ENDM; + +} // namespace o2::quality_control_modules::emcal + +std::vector SupermoduleProjectorTask::getDataSources(std::string name, const boost::property_tree::ptree& config) +{ + std::vector dataSources; + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".dataSources")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + sourceName.second.data() }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + name + ".dataSources'"); + } + } + return dataSources; +} + +std::map SupermoduleProjectorTask::parseCustomizations(std::string name, const boost::property_tree::ptree& config) +{ + auto decodeBool = [](const std::string_view& token) -> bool { + if (token == "true" || token == "True" || token == "TRUE") { + return true; + } + return false; + }; + std::map customizations; + for (const auto& customizationConfig : config.get_child("qc.postprocessing." + name + ".customizations")) { + std::string objectname = customizationConfig.second.get("name"); + // each setting is optional + customizations[objectname] = { + customizationConfig.second.get("xtitle", ""), + customizationConfig.second.get("ytitle", ""), + customizationConfig.second.get("xmin", DBL_MIN), + customizationConfig.second.get("xmax", DBL_MAX), + decodeBool(customizationConfig.second.get("logx", "false")), + decodeBool(customizationConfig.second.get("logy", "false")), + customizationConfig.second.get("qualityPath", ""), + }; + } + return customizations; +} + +std::map SupermoduleProjectorTask::parseQuality(const quality_control::core::QualityObject& qo) const +{ + std::map result; + auto globalQuality = qo.getQuality(); + if (globalQuality == Quality::Bad || globalQuality == Quality::Medium) { + auto flags = qo.getFlags(); + auto emflag = std::find_if(flags.begin(), flags.end(), [](const std::pair& testflag) -> bool { + bool found = testflag.first == quality_control::FlagTypeFactory::BadEMCalorimetry(); + return testflag.first == quality_control::FlagTypeFactory::BadEMCalorimetry(); + }); + if (emflag != flags.end()) { + std::stringstream lineparser(emflag->second); + std::string line; + while (std::getline(lineparser, line, '\n')) { + std::stringstream tokenizer(line); + std::string token; + bool found = false; + int smID = -1; + while (std::getline(tokenizer, token, ' ')) { + if (found) { + try { + smID = std::stoi(token); + } catch (...) { + } + break; + } + auto lowertoken = boost::algorithm::to_lower_copy(token); + if (lowertoken == "sm" || lowertoken == "supermodule") { + found = true; + } + } + if (smID >= 0 && smID < 20) { + result[smID] = line; + } + } + } + } + + return result; +} + +void SupermoduleProjectorTask::makeProjections(quality_control::core::MonitorObject& mo, TCanvas& plot, const PlotAttributes* customizations, const quality_control::core::QualityObject* qo) +{ + auto inputhist = dynamic_cast(mo.getObject()); + bool xAxisSM = inputhist->GetXaxis()->GetNbins() == 20; // for the moment assume that the axis with 20 bins is the SM axis, very unlikely that we have an observable with exactly 20 bins + if (!inputhist) { + ILOG(Error, Support) << "Monitoring object to be projected not of type TH2" << ENDM; + return; + } + std::map badSMs; + if (qo) { + badSMs = parseQuality(*qo); + } + plot.Clear(); + plot.Divide(4, 5); + for (int supermoduleID = 0; supermoduleID < 20; supermoduleID++) { + std::string histname = std::string(inputhist->GetName()) + "_SM" + std::to_string(supermoduleID), + histtitle = "Supermodule " + std::to_string(supermoduleID) + " (" + mIndicesConverter.GetOnlineSMIndex(supermoduleID) + ")"; + TH1* projection = nullptr; + if (xAxisSM) { + projection = inputhist->ProjectionY(histname.data(), supermoduleID + 1, supermoduleID + 1); + } else { + projection = inputhist->ProjectionX(histname.data(), supermoduleID + 1, supermoduleID + 1); + } + projection->SetStats(false); + projection->SetTitle(histtitle.data()); + projection->SetTitleSize(12); + projection->SetTitleFont(43); + bool logx = false; + bool logy = false; + if (customizations) { + if (customizations->titleX.length()) { + projection->SetXTitle(customizations->titleX.data()); + } + if (customizations->titleY.length()) { + projection->SetYTitle(customizations->titleY.data()); + } + bool hasXrange = false; + double xrangeMin = DBL_MIN; + double xrangeMax = DBL_MAX; + if (customizations->minX > DBL_MIN && customizations->minX >= projection->GetXaxis()->GetXmin()) { + xrangeMin = customizations->minX; + } else { + xrangeMin = projection->GetXaxis()->GetXmin(); + } + if (customizations->maxX < DBL_MAX && customizations->maxX <= projection->GetXaxis()->GetXmax()) { + xrangeMax = customizations->maxX; + } else { + xrangeMax = projection->GetXaxis()->GetXmax(); + } + if (hasXrange) { + projection->GetXaxis()->SetRangeUser(xrangeMin, xrangeMax); + } + logx = customizations->logx; + logy = customizations->logy; + } + plot.cd(supermoduleID + 1); + if (logx) { + gPad->SetLogx(); + } + if (logy) { + gPad->SetLogy(); + } + if (qo) { + auto bad_quality = badSMs.find(supermoduleID); + if (bad_quality != badSMs.end()) { + TLatex* msg = new TLatex(0.3, 0.8, Form("#color[2]{%s}", bad_quality->second.data())); + msg->SetNDC(); + msg->SetTextSize(8); + msg->SetTextFont(43); + projection->GetListOfFunctions()->Add(msg); + msg->Draw(); + projection->SetFillColor(kRed); + } else { + TLatex* msg = new TLatex(0.3, 0.8, "#color[418]{Data OK}"); + msg->SetNDC(); + msg->SetTextSize(8); + msg->SetTextFont(43); + projection->GetListOfFunctions()->Add(msg); + msg->Draw(); + projection->SetFillColor(kGreen); + } + } + projection->Draw(); + projection->SetBit(TObject::kCanDelete); + } +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/TimeCalibParamReductor.cxx b/Modules/EMCAL/src/TimeCalibParamReductor.cxx new file mode 100644 index 0000000000..c46557b39e --- /dev/null +++ b/Modules/EMCAL/src/TimeCalibParamReductor.cxx @@ -0,0 +1,96 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control_modules::emcal; + +TimeCalibParamReductor::TimeCalibParamReductor() : ReductorTObject(), mGeometry(nullptr), mStats() +{ + mGeometry = o2::emcal::Geometry::GetInstanceFromRunNumber(300000); +} + +void* TimeCalibParamReductor::getBranchAddress() +{ + return &mStats; +} + +const char* TimeCalibParamReductor::getBranchLeafList() +{ + return "FailedChannelsTotal/I:FailedChannelsEMCAL:FailedChannelsDCAL:FailedChannelsSM[20]:SupermoduleMaxFailed:FractionFailedTotal/D:FractionFailedEMCAL:FractionFaileDCAL:FractionFailedSupermodule[20]:MeanShift:SigmaShift"; +} + +void TimeCalibParamReductor::update(TObject* obj) +{ + const std::map channelsSMTYPE = { { o2::emcal::EMCAL_STANDARD, 1152 }, { o2::emcal::EMCAL_THIRD, 384 }, { o2::emcal::DCAL_STANDARD, 768 }, { o2::emcal::DCAL_EXT, 384 } }; + constexpr int CHANNELS_TOTAL = 17664, CHANNELS_EMC = 12288, CHANNELS_DCAL = CHANNELS_TOTAL - CHANNELS_EMC; + memset(&mStats, 0, sizeof(mStats)); + auto timeCalib = dynamic_cast(obj); + if (!timeCalib) { + ILOG(Error, Support) << "Object " << obj->GetName() << " not a proper time calib histogram, or does not exist. Not possible to analyse" << ENDM; + return; + } + std::vector tcpGood; + for (auto itower = 0; itower < timeCalib->GetXaxis()->GetNbins(); itower++) { + auto param = timeCalib->GetBinContent(itower + 1); + if (std::abs(param) > 100) { + // Consider fit failed if the time calibration param is larger than 100 + mStats.mFailedParams++; + try { + auto [sm, mod, modphi, modeta] = mGeometry->GetCellIndex(itower); + mStats.mFailedParamSM[sm]++; + if (sm >= 12) { + mStats.mFailedParamsDCAL++; + } else { + mStats.mFailedParamsEMCAL++; + } + } catch (o2::emcal::InvalidCellIDException& e) { + ILOG(Error, Support) << e.what() << ENDM; + } + } else { + tcpGood.emplace_back(param); + } + } + mStats.mFractionFailed = static_cast(mStats.mFailedParams) / static_cast(CHANNELS_TOTAL); + mStats.mFractionFailedEMCAL = static_cast(mStats.mFractionFailedEMCAL) / static_cast(CHANNELS_EMC); + mStats.mFractionFailedDCAL = static_cast(mStats.mFailedParamsDCAL) / static_cast(CHANNELS_DCAL); + int currentmax = -1; + for (int ism = 0; ism < 20; ism++) { + try { + auto smtype = mGeometry->GetSMType(ism); + auto nchannels = channelsSMTYPE.find(smtype); + if (nchannels == channelsSMTYPE.end()) { + ILOG(Error, Support) << "Unhandled Supermodule type" << ENDM; + continue; + } + mStats.mFractionFailedSM[ism] = static_cast(mStats.mFailedParamSM[ism]) / static_cast(nchannels->second); + if (mStats.mFailedParamSM[ism] > currentmax) { + currentmax = mStats.mFailedParamSM[ism]; + mStats.mSupermoduleMaxFailed = ism; + } + } catch (o2::emcal::SupermoduleIndexException& e) { + ILOG(Error, Support) << e.what() << ENDM; + } + } + mStats.mMeanShiftGood = TMath::Mean(tcpGood.begin(), tcpGood.end()); + mStats.mSigmaShiftGood = TMath::Mean(tcpGood.begin(), tcpGood.end()); +} + +bool TimeCalibParamReductor::isPHOSRegion(int column, int row) const +{ + return (column >= 32 && column < 64) && (row >= 128 && row < 200); +} \ No newline at end of file diff --git a/Modules/EMCAL/src/TrendGraphCheck.cxx b/Modules/EMCAL/src/TrendGraphCheck.cxx new file mode 100644 index 0000000000..302e88c1d1 --- /dev/null +++ b/Modules/EMCAL/src/TrendGraphCheck.cxx @@ -0,0 +1,159 @@ +#include "EMCAL/TrendGraphCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Quality.h" + +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::emcal +{ +void TrendGraphCheck::configure() +{ + + // configure threshold-based checkers + auto nBadThresholdLow = mCustomParameters.find("BadThresholdLow"); + if (nBadThresholdLow != mCustomParameters.end()) { + try { + mBadThresholdLow = std::stod(nBadThresholdLow->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nBadThresholdLow->second.data() + << " not a double" << ENDM; + } + } + + auto nBadThresholdHigh = mCustomParameters.find("BadThresholdHigh"); + if (nBadThresholdHigh != mCustomParameters.end()) { + try { + mBadThresholdHigh = std::stod(nBadThresholdHigh->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nBadThresholdHigh->second.data() + << " not a double" << ENDM; + } + } + + auto nBadDiff = mCustomParameters.find("BadDiff"); + if (nBadDiff != mCustomParameters.end()) { + try { + mBadDiff = std::stod(nBadDiff->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nBadDiff->second.data() << " not a double" << ENDM; + } + } + + auto nPeriodMovAvg = mCustomParameters.find("PeriodMovAvg"); + if (nPeriodMovAvg != mCustomParameters.end()) { + try { + mPeriodMovAvg = std::stoi(nPeriodMovAvg->second); + } catch (std::exception& e) { + ILOG(Error, Support) << "Value " << nPeriodMovAvg->second.data() + << " not an int" << ENDM; + } + } +} + +Quality TrendGraphCheck::check( + std::map>* moMap) +{ + Quality result = Quality::Good; + + for (auto& [moName, mo] : *moMap) { + auto* c = dynamic_cast(mo->getObject()); + TList* list_name = c->GetListOfPrimitives(); + double counts = -1; + for (auto trendgraph : TRangeDynCast(list_name)) { + if (!trendgraph) { + continue; + } + + // queue used to store list so that we get the average + std::queue Dataset; + double sum = 0; + double mean = 0; + auto* yValues = trendgraph->GetY(); + auto numPoints = trendgraph->GetN(); + std::vector meanArray(numPoints); + for (int i = 0; i < trendgraph->GetN(); ++i) { + double y = yValues[i]; + sum += y; + Dataset.push(y); + if (Dataset.size() > mPeriodMovAvg) { + sum -= Dataset.front(); + Dataset.pop(); + } + double mean = sum / mPeriodMovAvg; + meanArray[i] = mean; + } + + for (decltype(meanArray.size()) i = 0; i < meanArray.size() - 1; ++i) { + if (meanArray[i] < mBadThresholdLow || + meanArray[i] > mBadThresholdHigh) { + result = Quality::Medium; + } + } + + for (decltype(meanArray.size()) i = 0; i < meanArray.size() - 1; ++i) { + if (std::abs(meanArray[i] - meanArray[i + 1]) > mBadDiff) { + result = Quality::Bad; + break; + } + } + + meanArray.clear(); + } + } + return result; +} + +void TrendGraphCheck::beautify(std::shared_ptr mo, + Quality checkResult) +{ + + auto* c = dynamic_cast(mo->getObject()); + TList* list_name = c->GetListOfPrimitives(); + for (auto h : TRangeDynCast(list_name)) { + if (!h) { + continue; + } + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("Trend is as expected: OK!!!"); + msg->SetFillColor(kGreen); + msg->Draw(); + // + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red" << ENDM; + msg->Clear(); + msg->AddText("Consecutive trend rates very different: BAD!!!"); + msg->AddText("If NOT a technical run,"); + msg->AddText("call EMCAL on-call."); + msg->Draw(); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange" << ENDM; + msg->Clear(); + msg->AddText("Trend rate outside expected range. Keep monitoring."); + msg->Draw(); + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } +} +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/src/TriggerTask.cxx b/Modules/EMCAL/src/TriggerTask.cxx new file mode 100644 index 0000000000..c2aa02e458 --- /dev/null +++ b/Modules/EMCAL/src/TriggerTask.cxx @@ -0,0 +1,288 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +#include + +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "EMCAL/TriggerTask.h" +#include "DataFormatsEMCAL/CompressedTriggerData.h" +#include "DataFormatsEMCAL/TriggerRecord.h" +#include +#include + +namespace o2::quality_control_modules::emcal +{ + +TriggerTask::~TriggerTask() +{ + delete mTRUFired; + delete mFastORFired; + delete mPositionFasORFired; + delete mNumberOfTRUsPerEvent; + delete mNumberOfPatchesPerEvent; + delete mPatchEnergySpectrumPerEvent; + delete mLeadingPatchEnergySpectrumPerEvent; + delete mPatchEnergyTRU; + delete mLeadingPatchEnergyTRU; + delete mNumberOfPatchesPerTRU; + delete mPatchIndexFired; + delete mPatchIndexLeading; + delete mTRUTime; + delete mPatchTime; + delete mNumberTimesumsEvent; + delete mL0Timesums; + delete mL0TimesumsTRU; + delete mADCMaxTimesum; + delete mFastORIndexMaxTimesum; + delete mPositionMaxTimesum; + delete mIntegratedTimesums; + delete mAverageTimesum; +} + +void TriggerTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + mTRUFired = new TH1F("NumberOfFiredTRUs", "Number of triggers per TRU; TRU index; Number of triggers", o2::emcal::TriggerMappingV2::ALLTRUS, -0.5, o2::emcal::TriggerMappingV2::ALLTRUS - 0.5); + getObjectsManager()->startPublishing(mTRUFired); + mFastORFired = new TH1F("NumberOfPatchesWithFastOR", "Number of patches per FastOR; FastOR abs. ID; Number of patches", o2::emcal::TriggerMappingV2::ALLFASTORS, -0.5, o2::emcal::TriggerMappingV2::ALLFASTORS - 0.5); + getObjectsManager()->startPublishing(mFastORFired); + mPositionFasORFired = new TH2F("PositionFastOrInPatch", "Position of FastORs in patches; column; row", o2::emcal::TriggerMappingV2::FASTORSETA, -0.5, o2::emcal::TriggerMappingV2::FASTORSETA - 0.5, o2::emcal::TriggerMappingV2::FASTORSPHI, -0.5, o2::emcal::TriggerMappingV2::FASTORSPHI - 0.5); + getObjectsManager()->startPublishing(mPositionFasORFired); + mNumberOfTRUsPerEvent = new TH1F("NumberOfTRUsPerEvent", "Number of fired TRUs per event; Number of TRUs; Number of events", 20, -0.5, 19.5); + getObjectsManager()->startPublishing(mNumberOfTRUsPerEvent); + mNumberOfPatchesPerEvent = new TH1F("NumberOfPatchesPerEvent", "Number of patches per event; Number of L0 patches; Number of events", 100, -0.5, 99.5); + getObjectsManager()->startPublishing(mNumberOfPatchesPerEvent); + mPatchEnergySpectrumPerEvent = new TH1F("PatchEnergySpectrum", "Patch energy spectrum; Patch ADC; Yield", 2000, 0., 2000); + getObjectsManager()->startPublishing(mPatchEnergySpectrumPerEvent); + mLeadingPatchEnergySpectrumPerEvent = new TH1F("LeadingPatchEnergySpectrum", "Leading patch energy spectrum; Patch ADC; Yield", 2000, 0., 2000); + getObjectsManager()->startPublishing(mLeadingPatchEnergySpectrumPerEvent); + mPatchEnergyTRU = new TH2F("PatchEnergySpectrumTRU", "Patch energy spectrum; Patch ADC; TRU index", 2000, 0., 2000, o2::emcal::TriggerMappingV2::ALLTRUS, -0.5, o2::emcal::TriggerMappingV2::ALLTRUS - 0.5); + getObjectsManager()->startPublishing(mPatchEnergyTRU); + mLeadingPatchEnergyTRU = new TH2F("LeadingPatchEnergySpectrumTRU", "Leading patch energy spectrum; Patch ADC; TRU index", 2000, 0., 2000, o2::emcal::TriggerMappingV2::ALLTRUS, -0.5, o2::emcal::TriggerMappingV2::ALLTRUS - 0.5); + getObjectsManager()->startPublishing(mLeadingPatchEnergyTRU); + mNumberOfPatchesPerTRU = new TH2F("NumberOfPatchesPerTRU", "Number of trigger patches per TRU and event; Patch ADC; TRU index", 20, -0.5, 19.5, o2::emcal::TriggerMappingV2::ALLTRUS, -0.5, o2::emcal::TriggerMappingV2::ALLTRUS - 0.5); + getObjectsManager()->startPublishing(mNumberOfPatchesPerTRU); + mPatchIndexFired = new TH2F("PatchIndexFired", "Fired trigger patches; Patch index; TRU index", o2::emcal::TriggerMappingV2::PATCHESINTRU, -0.5, o2::emcal::TriggerMappingV2::PATCHESINTRU - 0.5, o2::emcal::TriggerMappingV2::ALLTRUS, -0.5, o2::emcal::TriggerMappingV2::ALLTRUS - 0.5); + getObjectsManager()->startPublishing(mPatchIndexFired); + mPatchIndexLeading = new TH2F("LeadingPatchIndexFired", "Leading trigger patches; Patch index; TRU index", o2::emcal::TriggerMappingV2::PATCHESINTRU, -0.5, o2::emcal::TriggerMappingV2::PATCHESINTRU - 0.5, o2::emcal::TriggerMappingV2::ALLTRUS, -0.5, o2::emcal::TriggerMappingV2::ALLTRUS - 0.5); + getObjectsManager()->startPublishing(mPatchIndexLeading); + mTRUTime = new TH2F("TRUTime", "TRU time; TRU time sample; TRU index", 12, -0.5, 11.5, o2::emcal::TriggerMappingV2::ALLTRUS, -0.5, o2::emcal::TriggerMappingV2::ALLTRUS - 0.5); + getObjectsManager()->startPublishing(mTRUTime); + mPatchTime = new TH2F("PatchTime", "Patch time; Patch time sample; TRU index", 12, -0.5, 11.5, o2::emcal::TriggerMappingV2::ALLTRUS, -0.5, o2::emcal::TriggerMappingV2::ALLTRUS - 0.5); + getObjectsManager()->startPublishing(mPatchTime); + mLeadingPatchTime = new TH2F("LeadingPatchTime", "Leading patch time; Patch time sample; TRU index", 12, -0.5, 11.5, o2::emcal::TriggerMappingV2::ALLTRUS, -0.5, o2::emcal::TriggerMappingV2::ALLTRUS - 0.5); + getObjectsManager()->startPublishing(mLeadingPatchTime); + mNumberTimesumsEvent = new TH1F("NumberL0TimesumsEvent", "Number of L0 timesums per event; Number of L0 timesums; Number of events", 1000, 0., 1000.); + getObjectsManager()->startPublishing(mNumberTimesumsEvent); + mL0Timesums = new TH1F("L0Timesums", "L0 timesums; L0 timesum (ADC counts); yield", 2048, 0., 2048); + getObjectsManager()->startPublishing(mL0Timesums); + mL0TimesumsTRU = new TH2F("L0TimesumsTRU", "L0 timesums per TRU; L0 timesum (ADC counts); TRU index", 2048, 0., 2048, o2::emcal::TriggerMappingV2::ALLTRUS, -0.5, o2::emcal::TriggerMappingV2::ALLTRUS - 0.5); + getObjectsManager()->startPublishing(mL0TimesumsTRU); + mADCMaxTimesum = new TH1F("MaxL0Timesum", "Max. L0 timesum per event; L0 timesum (ADC counts); Yield", 2048, 0., 2048); + getObjectsManager()->startPublishing(mADCMaxTimesum); + mFastORIndexMaxTimesum = new TH1F("FastORMaxTimesum", "FastOR ID of the max. L0 timesum; FastOR abs. ID; Yield", o2::emcal::TriggerMappingV2::ALLFASTORS, -0.5, o2::emcal::TriggerMappingV2::ALLFASTORS - 0.5); + getObjectsManager()->startPublishing(mFastORIndexMaxTimesum); + mPositionMaxTimesum = new TH2F("PositionMaxTimesum", "FastOR position of the max. L0 timesum; column; row", o2::emcal::TriggerMappingV2::FASTORSETA, -0.5, o2::emcal::TriggerMappingV2::FASTORSETA - 0.5, o2::emcal::TriggerMappingV2::FASTORSPHI, -0.5, o2::emcal::TriggerMappingV2::FASTORSPHI - 0.5); + getObjectsManager()->startPublishing(mPositionMaxTimesum); + mIntegratedTimesums = new TH2F("IntegratedTimesums", "Integrated L0 timesums per FastOR; column; row", o2::emcal::TriggerMappingV2::FASTORSETA, -0.5, o2::emcal::TriggerMappingV2::FASTORSETA - 0.5, o2::emcal::TriggerMappingV2::FASTORSPHI, -0.5, o2::emcal::TriggerMappingV2::FASTORSPHI - 0.5); + getObjectsManager()->startPublishing(mIntegratedTimesums); + mAverageTimesum = new TProfile2D("AverageTimesums", "Average L0 timesums per FastOR; column; row", o2::emcal::TriggerMappingV2::FASTORSETA, -0.5, o2::emcal::TriggerMappingV2::FASTORSETA - 0.5, o2::emcal::TriggerMappingV2::FASTORSPHI, -0.5, o2::emcal::TriggerMappingV2::FASTORSPHI - 0.5); + getObjectsManager()->startPublishing(mAverageTimesum); + + mTriggerMapping = std::make_unique(); +} + +void TriggerTask::startOfActivity(const Activity& activity) +{ + // THIS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + reset(); +} + +void TriggerTask::startOfCycle() +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TriggerTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto trus = ctx.inputs().get>("truinfo"); + auto trurecords = ctx.inputs().get>("trurecords"); + auto patches = ctx.inputs().get>("patchinfos"); + auto patchrecords = ctx.inputs().get>("patchrecords"); + auto timesums = ctx.inputs().get>("timesums"); + auto timesumrecords = ctx.inputs().get>("timesumrecords"); + ILOG(Debug, Devel) << "Found " << trurecords.size() << " TRU trigger records for " << trus.size() << " TRU info objects ..." << ENDM; + ILOG(Debug, Devel) << "Found " << patchrecords.size() << " patch trigger records for " << patches.size() << " patch objects ..." << ENDM; + ILOG(Debug, Devel) << "Found " << timesumrecords.size() << " timesum trigger records for " << timesums.size() << " L0 timesums ..." << ENDM; + + for (const auto& bc : getAllBCs(trurecords, patchrecords, timesumrecords)) { + gsl::span eventTRUs; + gsl::span eventPatches; + gsl::span eventTimesums; + auto recordfinder = [&bc](const o2::emcal::TriggerRecord& rec) { + return bc == rec.getBCData(); + }; + auto trurecfound = std::find_if(trurecords.begin(), trurecords.end(), recordfinder); + auto patchrecfound = std::find_if(patchrecords.begin(), patchrecords.end(), recordfinder); + auto timesumrecfound = std::find_if(timesumrecords.begin(), timesumrecords.end(), recordfinder); + if (trurecfound != trurecords.end()) { + eventTRUs = trus.subspan(trurecfound->getFirstEntry(), trurecfound->getNumberOfObjects()); + } + if (patchrecfound != patchrecords.end()) { + eventPatches = patches.subspan(patchrecfound->getFirstEntry(), patchrecfound->getNumberOfObjects()); + } + if (timesumrecfound != timesumrecords.end()) { + eventTimesums = timesums.subspan(timesumrecfound->getFirstEntry(), timesumrecfound->getNumberOfObjects()); + } + processEvent(eventTRUs, eventPatches, eventTimesums); + } +} + +void TriggerTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void TriggerTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TriggerTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mTRUFired->Reset(); + mFastORFired->Reset(); + mPositionFasORFired->Reset(); + mNumberOfTRUsPerEvent->Reset(); + mNumberOfPatchesPerEvent->Reset(); + mPatchEnergySpectrumPerEvent->Reset(); + mLeadingPatchEnergySpectrumPerEvent->Reset(); + mPatchEnergyTRU->Reset(); + mLeadingPatchEnergyTRU->Reset(); + mNumberOfPatchesPerTRU->Reset(); + mPatchIndexFired->Reset(); + mPatchIndexLeading->Reset(); + mTRUTime->Reset(); + mPatchTime->Reset(); + mLeadingPatchTime->Reset(); + mNumberTimesumsEvent->Reset(); + mL0Timesums->Reset(); + mL0TimesumsTRU->Reset(); + mADCMaxTimesum->Reset(); + mFastORIndexMaxTimesum->Reset(); + mPositionMaxTimesum->Reset(); + mIntegratedTimesums->Reset(); + mAverageTimesum->Reset(); +} + +std::vector TriggerTask::getAllBCs(const gsl::span trurecords, const gsl::span patchrecords, const gsl::span timesumrecords) const +{ + std::vector result; + auto add_to_result = [&result](const o2::emcal::TriggerRecord& rec) { + if (std::find(result.begin(), result.end(), rec.getBCData()) == result.end()) { + result.emplace_back(rec.getBCData()); + } + }; + for (const auto& trurecord : trurecords) { + result.emplace_back(trurecord.getBCData()); + } + for (const auto& patchrecord : patchrecords) { + add_to_result(patchrecord); + } + for (const auto& timesumrecord : timesumrecords) { + add_to_result(timesumrecord); + } + + std::sort(result.begin(), result.end(), std::less<>()); + return result; +} + +void TriggerTask::processEvent(const gsl::span trudata, const gsl::span triggerpatches, const gsl::span timesums) +{ + uint8_t numFiredTRU = 0; + for (const auto& tru : trudata) { + if (tru.mFired) { + mTRUFired->Fill(tru.mTRUIndex); + mTRUTime->Fill(tru.mTriggerTime, tru.mTRUIndex); + numFiredTRU++; + } + } + mNumberOfTRUsPerEvent->Fill(numFiredTRU); + mNumberOfPatchesPerEvent->Fill(triggerpatches.size()); + uint16_t leadingadc = 0; + uint8_t timeleading = UCHAR_MAX, + indexleading = UCHAR_MAX, + indexTRUleading = UCHAR_MAX; + std::array numpatches; + std::fill(numpatches.begin(), numpatches.end(), 0); + for (auto& patch : triggerpatches) { + numpatches[patch.mTRUIndex]++; + mPatchIndexFired->Fill(patch.mPatchIndexInTRU, patch.mTRUIndex); + mPatchEnergySpectrumPerEvent->Fill(patch.mADC); + mPatchEnergyTRU->Fill(patch.mADC, patch.mTRUIndex); + mPatchTime->Fill(patch.mTime, patch.mTRUIndex); + auto fastORs = mTriggerMapping->getFastORIndexFromL0Index(patch.mTRUIndex, patch.mPatchIndexInTRU, 4); + for (auto fastor : fastORs) { + mFastORFired->Fill(fastor); + auto [col, row] = mTriggerMapping->getPositionInEMCALFromAbsFastORIndex(fastor); + mPositionFasORFired->Fill(col, row); + } + if (patch.mADC > leadingadc) { + leadingadc = patch.mADC; + indexleading = patch.mPatchIndexInTRU; + indexTRUleading = patch.mTRUIndex; + timeleading = patch.mTime; + } + } + if (indexleading < UCHAR_MAX) { + mLeadingPatchEnergySpectrumPerEvent->Fill(leadingadc); + mLeadingPatchEnergyTRU->Fill(leadingadc, indexTRUleading); + mPatchIndexLeading->Fill(indexleading, indexTRUleading); + mLeadingPatchTime->Fill(timeleading, indexTRUleading); + } + for (auto itru = 0; itru < 52; itru++) { + if (numpatches[itru]) { + mNumberOfPatchesPerTRU->Fill(numpatches[itru], itru); + } + } + + uint16_t maxtimesum = 0, + indexMaxTimesum = USHRT_MAX; + mNumberTimesumsEvent->Fill(timesums.size()); + for (const auto& timesum : timesums) { + mL0Timesums->Fill(timesum.mTimesum); + auto [indexTRU, indexInTRU] = mTriggerMapping->getTRUFromAbsFastORIndex(timesum.mIndex); + mL0TimesumsTRU->Fill(timesum.mTimesum, indexTRU); + auto [col, row] = mTriggerMapping->getPositionInEMCALFromAbsFastORIndex(timesum.mIndex); + mIntegratedTimesums->Fill(col, row, timesum.mTimesum); + mAverageTimesum->Fill(col, row, timesum.mTimesum); + if (timesum.mTimesum > maxtimesum) { + maxtimesum = timesum.mTimesum; + indexMaxTimesum = timesum.mIndex; + } + } + if (indexMaxTimesum < USHRT_MAX) { + mADCMaxTimesum->Fill(maxtimesum); + mFastORIndexMaxTimesum->Fill(indexMaxTimesum); + auto [col, row] = mTriggerMapping->getPositionInEMCALFromAbsFastORIndex(indexMaxTimesum); + mPositionMaxTimesum->Fill(col, row); + } +} + +} // namespace o2::quality_control_modules::emcal diff --git a/Modules/EMCAL/test/testEMCAL.cxx b/Modules/EMCAL/test/testEMCAL.cxx new file mode 100644 index 0000000000..71e21b4d1f --- /dev/null +++ b/Modules/EMCAL/test/testEMCAL.cxx @@ -0,0 +1,36 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testEMCAL.cxx +/// \author +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2 +{ +namespace quality_control_modules +{ +namespace emcal +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace emcal +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/Example/CMakeLists.txt b/Modules/Example/CMakeLists.txt index 52dc958491..cc93eb287b 100644 --- a/Modules/Example/CMakeLists.txt +++ b/Modules/Example/CMakeLists.txt @@ -1,43 +1,38 @@ -set(MODULE_NAME "QcExample") - -# ---- Files ---- +# ---- Library ---- -set( - SRCS +add_library(O2QcExample src/BenchmarkTask.cxx + src/EveryObject.cxx src/ExampleTask.cxx src/FakeCheck.cxx -) - -set( - HEADERS - include/Example/BenchmarkTask.h - include/Example/ExampleTask.h - include/Example/FakeCheck.h -) - -# ---- Library ---- - -add_library(${MODULE_NAME} SHARED ${SRCS} ${MODULE_NAME}Dict.cxx) + src/ExampleCondition.cxx + src/CustomTH2F.cxx + ) target_include_directories( - ${MODULE_NAME} - PUBLIC $ $ - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src -) - -target_link_libraries(${MODULE_NAME} PUBLIC QualityControl) - -install( - TARGETS ${MODULE_NAME} + O2QcExample + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcExample PUBLIC O2QualityControl) + +add_root_dictionary(O2QcExample + HEADERS include/Example/BenchmarkTask.h + include/Example/EveryObject.h + include/Example/ExampleTask.h + include/Example/FakeCheck.h + include/Example/ExampleCondition.h + include/Example/CustomTH2F.h + LINKDEF include/Example/LinkDef.h) + +install(TARGETS O2QcExample LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) - -# ---- ROOT dictionary ---- + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -generate_root_dict(MODULE_NAME ${MODULE_NAME} LINKDEF "include/Example/LinkDef.h" DICT_CLASS "${MODULE_NAME}Dict") +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/Example + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") # ---- Tests ---- @@ -46,9 +41,14 @@ set(TEST_SRCS test/testFactory.cxx test/testQcExample.cxx) foreach(test ${TEST_SRCS}) get_filename_component(test_name ${test} NAME) string(REGEX REPLACE ".cxx" "" test_name ${test_name}) - add_executable(${test_name} ${test}) - target_link_libraries(${test_name} PRIVATE ${MODULE_NAME} Boost::unit_test_framework) + target_link_libraries(${test_name} + PRIVATE O2QcExample Boost::unit_test_framework) add_test(NAME ${test_name} COMMAND ${test_name}) - set_tests_properties(${test_name} PROPERTIES TIMEOUT 60) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) endforeach() + +target_sources(testFactory PRIVATE ${CMAKE_BINARY_DIR}/getTestDataDirectory.cxx) +target_include_directories(testFactory PRIVATE ${CMAKE_SOURCE_DIR}) diff --git a/Modules/Example/README.md b/Modules/Example/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Modules/Example/etc/every-object.json b/Modules/Example/etc/every-object.json new file mode 100644 index 0000000000..be6536ceda --- /dev/null +++ b/Modules/Example/etc/every-object.json @@ -0,0 +1,51 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE", + "periodName": "", "": "Period name - e.g. LHC22c, LHC22c1b_test", + "passName": "", "": "Pass type - e.g. spass, cpass1", + "provenance": "qc", "": "Provenance - qc or qc_mc depending whether it is normal data or monte carlo data" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", "": "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel": "21", "": "Message at this level or above are discarded (default: 21 - Trace)" + } + }, + "tasks": { + "EveryObject": { + "active": "true", + "className": "o2::quality_control_modules::example::EveryObject", + "moduleName": "QcExample", + "detectorName": "TST", + "cycleDurationSeconds": "10", "": "10 seconds minimum", + "": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "direct", + "query": "random:TST/RAWDATA/0" + } + } + }, + "checks": { + } + }, + "dataSamplingPolicies": [ + ] +} diff --git a/Modules/Example/include/Example/BenchmarkTask.h b/Modules/Example/include/Example/BenchmarkTask.h index 309ddab847..a454f65bd6 100644 --- a/Modules/Example/include/Example/BenchmarkTask.h +++ b/Modules/Example/include/Example/BenchmarkTask.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file BenchmarkTask.h /// \author Barthelemy von Haller @@ -8,8 +19,14 @@ #include "QualityControl/TaskInterface.h" -#include "Configuration/ConfigurationFactory.h" #include +#include +#include + +namespace o2::configuration +{ +class ConfigurationInterface; +} class TH1F; @@ -33,18 +50,18 @@ class BenchmarkTask : public TaskInterface // Definition of the methods for the template method pattern void initialize(o2::framework::InitContext& ctx) override; - void startOfActivity(Activity& activity) override; + void startOfActivity(const Activity& activity) override; void startOfCycle() override; void monitorData(o2::framework::ProcessingContext& ctx) override; void endOfCycle() override; - void endOfActivity(Activity& activity) override; + void endOfActivity(const Activity& activity) override; void reset() override; private: std::vector mHistos; std::unique_ptr mConfigFile; - int mNumberHistos; - int mNumberChecks; + size_t mNumberHistos; + size_t mNumberChecks; std::string mTypeOfChecks; std::string mModuleOfChecks; diff --git a/Modules/Example/include/Example/CustomTH2F.h b/Modules/Example/include/Example/CustomTH2F.h new file mode 100644 index 0000000000..1ecc98c2f0 --- /dev/null +++ b/Modules/Example/include/Example/CustomTH2F.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CustomTH2F.h +/// \author Barthelemy von Haller +/// + +#ifndef QC_MODULE_EXAMPLE_CUSTOMTH2F_H +#define QC_MODULE_EXAMPLE_CUSTOMTH2F_H + +#include "QualityControl/TaskInterface.h" +#include +#include "Mergers/MergeInterface.h" + +namespace o2::quality_control_modules::example +{ + +/// \brief Example of a custom class that inherits from a ROOT standard class. +/// It should be drawn by calling the standard TH2::Draw() method. In ROOT, it is transparent, +/// in QCG we state the equivalence via the member `mTreatMeAs`. +/// \author Barthelemy von Haller +class CustomTH2F : public TH2F, public o2::mergers::MergeInterface +{ + public: + /// \brief Constructor. + CustomTH2F(std::string name); + CustomTH2F() = default; + /// \brief Default destructor + ~CustomTH2F() override = default; + + const char* GetName() const override + { + return TH2F::GetName(); + } + + void merge(MergeInterface* const other) override + { + auto otherHisto = dynamic_cast(other); + if (otherHisto) { + this->Add(otherHisto); + } + } + + private: + std::string mTreatMeAs = "TH2F"; // the name of the class this object should be considered as when drawing in QCG. + + ClassDefOverride(CustomTH2F, 1); +}; + +} // namespace o2::quality_control_modules::example + +#endif //QC_MODULE_EXAMPLE_CUSTOMTH2F_H diff --git a/Modules/Example/include/Example/EveryObject.h b/Modules/Example/include/Example/EveryObject.h new file mode 100644 index 0000000000..82004a69da --- /dev/null +++ b/Modules/Example/include/Example/EveryObject.h @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file EveryObject.h +/// \author Piotr Konopka +/// + +#ifndef QC_MODULE_EXAMPLE_EXAMPLEEVERYOBJECT_H +#define QC_MODULE_EXAMPLE_EXAMPLEEVERYOBJECT_H + +#include "QualityControl/TaskInterface.h" +#include + +class TH1F; +class TH2F; +class TH3F; +class THnSparse; +class TCanvas; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::example +{ + +/// \brief Task which publishes (not exactly) every class object used as MO. Can be used to test memory leaks. +/// \author Piotr Konopka +class EveryObject final : public TaskInterface +{ + public: + /// \brief Constructor + EveryObject() = default; + /// Destructor + ~EveryObject() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + TH1F* mTH1F = nullptr; + TH2F* mTH2F = nullptr; + TH3F* mTH3F = nullptr; + THnSparse* mTHnSparseF = nullptr; + TCanvas* mTCanvas = nullptr; + std::array mTCanvasMembers = { nullptr }; +}; + +} // namespace o2::quality_control_modules::example + +#endif // QC_MODULE_EXAMPLE_EXAMPLEEVERYOBJECT_H diff --git a/Modules/Example/include/Example/ExampleCondition.h b/Modules/Example/include/Example/ExampleCondition.h new file mode 100644 index 0000000000..b5245aaa66 --- /dev/null +++ b/Modules/Example/include/Example/ExampleCondition.h @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ExampleCondition.h +/// \author Piotr Konopka +/// + +#ifndef QC_MODULE_EXAMPLE_EXAMPLECONDITION_H +#define QC_MODULE_EXAMPLE_EXAMPLECONDITION_H + +#include "QualityControl/TaskInterface.h" +#include + +namespace o2::quality_control_modules::example +{ + +/// \brief Example of custom Data Sampling Condition +/// \author Piotr Konopka + +/// \brief A DataSamplingCondition which approves messages which have their first byte in the payload higher than +/// specified value. +class ExampleCondition : public o2::utilities::DataSamplingCondition +{ + public: + /// \brief Constructor. + ExampleCondition() = default; + /// \brief Default destructor + ~ExampleCondition() override = default; + + /// \brief Reads 'threshold' + void configure(const boost::property_tree::ptree& config) override; + /// \brief Makes a positive decision if first byte is higher than 'threshold' + bool decide(const o2::framework::DataRef& dataRef) override; + + private: + uint8_t mThreshold = 0; +}; + +} // namespace o2::quality_control_modules::example + +#endif //QC_MODULE_EXAMPLE_EXAMPLECONDITION_H diff --git a/Modules/Example/include/Example/ExampleTask.h b/Modules/Example/include/Example/ExampleTask.h index fbe212b961..efc9f1b348 100644 --- a/Modules/Example/include/Example/ExampleTask.h +++ b/Modules/Example/include/Example/ExampleTask.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file ExampleTask.h /// \author Barthelemy von Haller @@ -15,10 +26,12 @@ using namespace o2::quality_control::core; namespace o2::quality_control_modules::example { +class CustomTH2F; + /// \brief Example Quality Control Task /// It is final because there is no reason to derive from it. Just remove it if needed. /// \author Barthelemy von Haller -class ExampleTask /*final*/ : public TaskInterface // todo add back the "final" when doxygen is fixed +class ExampleTask final : public TaskInterface { public: /// \brief Constructor @@ -28,11 +41,11 @@ class ExampleTask /*final*/ : public TaskInterface // todo add back the "final" // Definition of the methods for the template method pattern void initialize(o2::framework::InitContext& ctx) override; - void startOfActivity(Activity& activity) override; + void startOfActivity(const Activity& activity) override; void startOfCycle() override; void monitorData(o2::framework::ProcessingContext& ctx) override; void endOfCycle() override; - void endOfActivity(Activity& activity) override; + void endOfActivity(const Activity& activity) override; void reset() override; // Accessors @@ -43,6 +56,7 @@ class ExampleTask /*final*/ : public TaskInterface // todo add back the "final" int mNumberCycles; TH1F* mHistos[25]; void publishHisto(int i); + CustomTH2F* mCustomTH2F; }; } // namespace o2::quality_control_modules::example diff --git a/Modules/Example/include/Example/FakeCheck.h b/Modules/Example/include/Example/FakeCheck.h index 4c46ea0add..28c1fd9c73 100644 --- a/Modules/Example/include/Example/FakeCheck.h +++ b/Modules/Example/include/Example/FakeCheck.h @@ -1,3 +1,14 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file FakeCheck.h /// \author Barthelemy von Haller @@ -7,8 +18,6 @@ #define QC_MODULE_EXAMPLE_FAKECHECK_H #include "QualityControl/CheckInterface.h" -#include "QualityControl/MonitorObject.h" -#include "QualityControl/Quality.h" namespace o2::quality_control_modules::example { @@ -20,15 +29,13 @@ class FakeCheck : public o2::quality_control::checker::CheckInterface { public: /// Default constructor - FakeCheck(); + FakeCheck() = default; /// Destructor - ~FakeCheck() override; - - void configure(std::string name) override; - Quality check(const MonitorObject* mo) override; - void beautify(MonitorObject* mo, Quality checkResult = Quality::Null) override; - std::string getAcceptedType() override; + ~FakeCheck() override = default; + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; ClassDefOverride(FakeCheck, 1); }; diff --git a/Modules/Example/include/Example/LinkDef.h b/Modules/Example/include/Example/LinkDef.h index 492288491b..5e378ccb21 100644 --- a/Modules/Example/include/Example/LinkDef.h +++ b/Modules/Example/include/Example/LinkDef.h @@ -6,4 +6,7 @@ #pragma link C++ class o2::quality_control_modules::example::ExampleTask + ; #pragma link C++ class o2::quality_control_modules::example::FakeCheck + ; #pragma link C++ class o2::quality_control_modules::example::BenchmarkTask + ; +#pragma link C++ class o2::quality_control_modules::example::ExampleCondition + ; +#pragma link C++ class o2::quality_control_modules::example::CustomTH2F + ; +#pragma link C++ class o2::quality_control_modules::example::EveryObject+; #endif diff --git a/Modules/Example/src/BenchmarkTask.cxx b/Modules/Example/src/BenchmarkTask.cxx index dcfd71c56b..8268316909 100644 --- a/Modules/Example/src/BenchmarkTask.cxx +++ b/Modules/Example/src/BenchmarkTask.cxx @@ -1,9 +1,21 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file BenchmarkTask.cxx /// \author Barthelemy von Haller /// #include "Example/BenchmarkTask.h" +#include #include "QualityControl/QcInfoLogger.h" #include #include @@ -15,14 +27,16 @@ using namespace o2::configuration; namespace o2::quality_control_modules::example { -BenchmarkTask::BenchmarkTask() : TaskInterface() {} +// These two cannot be in the header, because ctors and dtors need to know how to construct and destruct +// ConfigurationInterface, which is only forward-declared in the header. +BenchmarkTask::BenchmarkTask() = default; -BenchmarkTask::~BenchmarkTask() {} +BenchmarkTask::~BenchmarkTask() = default; -void BenchmarkTask::initialize(o2::framework::InitContext& ctx) +void BenchmarkTask::initialize(o2::framework::InitContext& /*ctx*/) { - QcInfoLogger::GetInstance() << "initialize benchmarktask \"" << getName() << "\"" - << AliceO2::InfoLogger::InfoLogger::endm; + ILOG(Debug, Devel) << "initialize benchmarktask \"" << getName() << "\"" + << ENDM; mConfigFile = ConfigurationFactory::getConfiguration("file:./example.ini"); string prefix = "qc.tasks_config." + getName(); @@ -41,26 +55,21 @@ void BenchmarkTask::initialize(o2::framework::InitContext& ctx) stringstream name; name << "histogram_" << getName() << "_" << i; mHistos.push_back(new TH1F(name.str().c_str(), name.str().c_str(), 1000, -5, 5)); - getObjectsManager()->startPublishing(mHistos[i], name.str()); - - // Add the checks - for (size_t j = 0; j < mNumberChecks; j++) { - getObjectsManager()->addCheck(name.str(), "fakeCheck_" + std::to_string(j), mTypeOfChecks, mModuleOfChecks); - } + getObjectsManager()->startPublishing(mHistos[i], PublicationPolicy::Forever); } } -void BenchmarkTask::startOfActivity(Activity& activity) +void BenchmarkTask::startOfActivity(const Activity& /*activity*/) { - QcInfoLogger::GetInstance() << "startOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; + ILOG(Debug, Devel) << "startOfActivity" << ENDM; } void BenchmarkTask::startOfCycle() { - QcInfoLogger::GetInstance() << "startOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; + ILOG(Debug, Devel) << "startOfCycle" << ENDM; } -void BenchmarkTask::monitorData(o2::framework::ProcessingContext& ctx) +void BenchmarkTask::monitorData(o2::framework::ProcessingContext& /*ctx*/) { std::this_thread::sleep_for(std::chrono::milliseconds(100) /*100ms*/); } @@ -71,14 +80,14 @@ void BenchmarkTask::endOfCycle() histo->Reset(); histo->FillRandom("gaus", 1000); } - QcInfoLogger::GetInstance() << "endOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; + ILOG(Debug, Devel) << "endOfCycle" << ENDM; } -void BenchmarkTask::endOfActivity(Activity& activity) +void BenchmarkTask::endOfActivity(const Activity& /*activity*/) { - QcInfoLogger::GetInstance() << "endOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; + ILOG(Debug, Devel) << "endOfActivity" << ENDM; } -void BenchmarkTask::reset() { QcInfoLogger::GetInstance() << "Reset" << AliceO2::InfoLogger::InfoLogger::endm; } +void BenchmarkTask::reset() { ILOG(Info, Support) << "Reset" << ENDM; } } // namespace o2::quality_control_modules::example diff --git a/Modules/Example/src/CustomTH2F.cxx b/Modules/Example/src/CustomTH2F.cxx new file mode 100644 index 0000000000..7c32dc99f1 --- /dev/null +++ b/Modules/Example/src/CustomTH2F.cxx @@ -0,0 +1,27 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CustomTH2F.cxx +/// \author Barthelemy von Haller +/// + +#include "Example/CustomTH2F.h" +#include "QualityControl/QcInfoLogger.h" + +namespace o2::quality_control_modules::example +{ + +CustomTH2F::CustomTH2F(std::string name) : TH2F(name.c_str(), "Custom object inheriting from TH2F", 100, 0, 99, 100, 0, 99) +{ +} + +} // namespace o2::quality_control_modules::example diff --git a/Modules/Example/src/EveryObject.cxx b/Modules/Example/src/EveryObject.cxx new file mode 100644 index 0000000000..c8a198ee56 --- /dev/null +++ b/Modules/Example/src/EveryObject.cxx @@ -0,0 +1,164 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file EveryObject.cxx +/// \author Piotr Konopka +/// + +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "Example/EveryObject.h" +#include +#include +#include + +#include +#include +#include +#include +#include + +constexpr Double_t rangeLimiter = 16 * 64000; + +namespace o2::quality_control_modules::example +{ + +EveryObject::~EveryObject() +{ + delete mTH1F; + delete mTH2F; + delete mTH3F; + delete mTHnSparseF; + delete mTCanvas; // TCanvas should delete the contained plots, given that we set kCanDelete +} + +void EveryObject::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize EveryObject" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // todo: enable/disable with config flags (this is why objects are always accessed after checking if not null) + // todo: ttree, tefficiency, tprofile + + mTH1F = new TH1F("th1f", "th1f", 64000, 0, rangeLimiter); + getObjectsManager()->startPublishing(mTH1F, PublicationPolicy::Forever); + + mTH2F = new TH2F("th2f", "th2f", 250, 0, rangeLimiter, 250, 0, rangeLimiter); + getObjectsManager()->startPublishing(mTH2F, PublicationPolicy::Forever); + + mTH3F = new TH3F("th3f", "th3f", 40, 0, rangeLimiter, 40, 0, rangeLimiter, 40, 0, rangeLimiter); + getObjectsManager()->startPublishing(mTH3F, PublicationPolicy::Forever); + { + const size_t bins = 1000; + const size_t dim = 5; + const Double_t min = 0.0; + const Double_t max = rangeLimiter; + + const std::vector binsDims(dim, bins); + const std::vector mins(dim, min); + const std::vector maxs(dim, max); + mTHnSparseF = new THnSparseF("thnsparsef", "thnsparsef", dim, binsDims.data(), mins.data(), maxs.data()); + getObjectsManager()->startPublishing(mTHnSparseF, PublicationPolicy::Forever); + } + { + mTCanvas = new TCanvas("tcanvas", "tcanvas", 1000, 1000); + mTCanvas->Clear(); + mTCanvas->Divide(2, 2); + for (size_t i = 0; i < 4; i++) { + auto name = std::string("tcanvas_th2f_") + std::to_string(i); + mTCanvasMembers[i] = new TH2F(name.c_str(), name.c_str(), 250, 0, rangeLimiter, 250, 0, rangeLimiter); + + mTCanvas->cd(i + 1); + mTCanvasMembers[i]->Draw(); + mTCanvasMembers[i]->SetBit(TObject::kCanDelete); + } + getObjectsManager()->startPublishing(mTCanvas, PublicationPolicy::Forever); + } +} + +void EveryObject::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; +} + +void EveryObject::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void EveryObject::monitorData(o2::framework::ProcessingContext& ctx) +{ + for (auto&& input : framework::InputRecordWalker(ctx.inputs())) { + const auto* header = framework::DataRefUtils::getHeader(input); + const auto payloadSize = framework::DataRefUtils::getPayloadSize(input); + auto value1 = (Float_t)(payloadSize % (size_t)rangeLimiter); + auto value2 = (Float_t)((header->tfCounter + payloadSize) % (size_t)rangeLimiter); + auto value3 = (Float_t)((header->tfCounter * payloadSize) % (size_t)rangeLimiter); + + if (mTH1F) { + mTH1F->Fill(value1); + } + if (mTH2F) { + mTH2F->Fill(value1, value3); + } + if (mTH3F) { + mTH3F->Fill(value1, value2, value3); + } + if (mTHnSparseF) { + std::array values{ value1, value2, value3, value2 / (value1 + 1), value2 / (value3 + 1) }; + mTHnSparseF->Fill(values.data()); + } + if (mTCanvas) { + mTCanvasMembers[0]->Fill(value1, value3); + mTCanvasMembers[1]->Fill(value3, value1); + mTCanvasMembers[2]->Fill(value2, value3); + mTCanvasMembers[3]->Fill(value3, value2); + } + } +} + +void EveryObject::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void EveryObject::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void EveryObject::reset() +{ + ILOG(Debug, Devel) << "Resetting the objects" << ENDM; + if (mTH1F) { + mTH1F->Reset(); + } + if (mTH2F) { + mTH2F->Reset(); + } + if (mTH3F) { + mTH3F->Reset(); + } + if (mTHnSparseF) { + mTHnSparseF->Reset(); + } + if (mTCanvas) { + for (const auto member : mTCanvasMembers) { + if (member) { + member->Reset(); + } + } + } +} + +} // namespace o2::quality_control_modules::example diff --git a/Modules/Example/src/ExampleCondition.cxx b/Modules/Example/src/ExampleCondition.cxx new file mode 100644 index 0000000000..718b49eb7a --- /dev/null +++ b/Modules/Example/src/ExampleCondition.cxx @@ -0,0 +1,33 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ExampleCondition.cxx +/// \author Piotr Konopka +/// + +#include "Example/ExampleCondition.h" +#include + +namespace o2::quality_control_modules::example +{ + +void ExampleCondition::configure(const boost::property_tree::ptree& config) +{ + mThreshold = config.get("threshold"); +} + +bool ExampleCondition::decide(const o2::framework::DataRef& dataRef) +{ + return dataRef.payload != nullptr ? dataRef.payload[0] > mThreshold : false; +} + +} // namespace o2::quality_control_modules::example diff --git a/Modules/Example/src/ExampleTask.cxx b/Modules/Example/src/ExampleTask.cxx index fa17ce27a9..3799b6a71f 100644 --- a/Modules/Example/src/ExampleTask.cxx +++ b/Modules/Example/src/ExampleTask.cxx @@ -1,13 +1,26 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file ExampleTask.cxx /// \author Barthelemy von Haller /// #include "Example/ExampleTask.h" +#include +#include +#include #include "QualityControl/QcInfoLogger.h" -#include -#include #include +#include "Example/CustomTH2F.h" using namespace std; @@ -29,9 +42,9 @@ ExampleTask::~ExampleTask() } } -void ExampleTask::initialize(o2::framework::InitContext& ctx) +void ExampleTask::initialize(o2::framework::InitContext& /*ctx*/) { - QcInfoLogger::GetInstance() << "initialize ExampleTask" << AliceO2::InfoLogger::InfoLogger::endm; + ILOG(Debug, Devel) << "initialize ExampleTask" << ENDM; for (int i = 0; i < 24; i++) { publishHisto(i); @@ -40,13 +53,8 @@ void ExampleTask::initialize(o2::framework::InitContext& ctx) // Extendable axis mHistos[0]->SetCanExtend(TH1::kXaxis); - // Add checks (first by name, then by reference) - getObjectsManager()->addCheck("array-0", "checkMeanIsAbove", "o2::quality_control_modules::common::MeanIsAbove", - "QcCommon"); - getObjectsManager()->addCheck(mHistos[0], "checkNonEmpty", "o2::quality_control_modules::common::NonEmpty", - "QcCommon"); - getObjectsManager()->addCheck(mHistos[0], "checkFromExample", "o2::quality_control_modules::example::FakeCheck", - "QcExample"); + mCustomTH2F = new CustomTH2F("customTH2F"); + getObjectsManager()->startPublishing(mCustomTH2F, PublicationPolicy::Forever); } void ExampleTask::publishHisto(int i) @@ -54,12 +62,12 @@ void ExampleTask::publishHisto(int i) stringstream name; name << "array-" << i; mHistos[i] = new TH1F(name.str().c_str(), name.str().c_str(), 100, 0, 99); - getObjectsManager()->startPublishing(mHistos[i], name.str()); + getObjectsManager()->startPublishing(mHistos[i], PublicationPolicy::Forever); } -void ExampleTask::startOfActivity(Activity& activity) +void ExampleTask::startOfActivity(const Activity& activity) { - QcInfoLogger::GetInstance() << "startOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; + ILOG(Debug, Devel) << "startOfActivity : " << activity.mId << ENDM; for (auto& mHisto : mHistos) { if (mHisto) { mHisto->Reset(); @@ -69,23 +77,29 @@ void ExampleTask::startOfActivity(Activity& activity) void ExampleTask::startOfCycle() { - QcInfoLogger::GetInstance() << "startOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; + ILOG(Debug, Devel) << "startOfCycle" << ENDM; } void ExampleTask::monitorData(o2::framework::ProcessingContext& ctx) { - const auto* header = o2::header::get(ctx.inputs().getByPos(0).header); // header of first input - mHistos[0]->Fill(header->payloadSize); - for (auto& mHisto : mHistos) { - if (mHisto) { - mHisto->FillRandom("gaus", 1); + for (const auto& input : o2::framework::InputRecordWalker(ctx.inputs())) { + if (input.header != nullptr) { + const auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(input); + mHistos[0]->Fill(payloadSize); + mCustomTH2F->Fill(payloadSize % 100, payloadSize % 100); + for (auto& mHisto : mHistos) { + if (mHisto) { + mHisto->FillRandom("gaus", 1); + } + } + break; } } } void ExampleTask::endOfCycle() { - QcInfoLogger::GetInstance() << "endOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; + ILOG(Debug, Devel) << "endOfCycle" << ENDM; mNumberCycles++; // Add one more object just to show that we can do it @@ -94,11 +108,11 @@ void ExampleTask::endOfCycle() } } -void ExampleTask::endOfActivity(Activity& activity) +void ExampleTask::endOfActivity(const Activity& /*activity*/) { - QcInfoLogger::GetInstance() << "endOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; + ILOG(Debug, Devel) << "endOfActivity" << ENDM; } -void ExampleTask::reset() { QcInfoLogger::GetInstance() << "Reset" << AliceO2::InfoLogger::InfoLogger::endm; } +void ExampleTask::reset() { ILOG(Info, Support) << "Reset" << ENDM; } } // namespace o2::quality_control_modules::example diff --git a/Modules/Example/src/FakeCheck.cxx b/Modules/Example/src/FakeCheck.cxx index 936a0b6608..9bd5bbe34b 100644 --- a/Modules/Example/src/FakeCheck.cxx +++ b/Modules/Example/src/FakeCheck.cxx @@ -1,12 +1,22 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file FakeCheck.cxx /// \author Barthelemy von Haller /// #include "Example/FakeCheck.h" - -// ROOT -#include +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" using namespace std; @@ -15,22 +25,16 @@ ClassImp(o2::quality_control_modules::example::FakeCheck) namespace o2::quality_control_modules::example { - FakeCheck::FakeCheck() {} - - FakeCheck::~FakeCheck() {} + void FakeCheck::configure() {} - void FakeCheck::configure(std::string name) {} - - Quality FakeCheck::check(const MonitorObject* mo) + Quality FakeCheck::check(std::map> * /*moMap*/) { Quality result = Quality::Null; return result; } - std::string FakeCheck::getAcceptedType() { return "TH1"; } - - void FakeCheck::beautify(MonitorObject* mo, Quality checkResult) + void FakeCheck::beautify(std::shared_ptr /*mo*/, Quality /*checkResult*/) { // NOOP } diff --git a/Modules/Example/test/testFactory.cxx b/Modules/Example/test/testFactory.cxx index de3b6fae96..e211673e98 100644 --- a/Modules/Example/test/testFactory.cxx +++ b/Modules/Example/test/testFactory.cxx @@ -3,10 +3,9 @@ /// \author Barthelemy von Haller /// -#include "../include/Example/ExampleTask.h" -#include "Common/Exceptions.h" #include "QualityControl/ObjectsManager.h" #include "QualityControl/TaskFactory.h" +#include #include #include @@ -15,11 +14,10 @@ #define BOOST_TEST_DYN_LINK #include -#include - -#include using namespace std; +namespace utf = boost::unit_test; +using namespace o2::quality_control::core; namespace o2::quality_control_modules::example { @@ -27,14 +25,16 @@ namespace o2::quality_control_modules::example BOOST_AUTO_TEST_CASE(Task_Factory) { TaskFactory factory; - TaskConfig config; - config.taskName = "task"; + TaskRunnerConfig config; + config.name = "task"; config.moduleName = "QcCommon"; config.className = "o2::quality_control_modules::example::ExampleTask"; - auto manager = make_shared(config); + config.detectorName = "DAQ"; + config.ccdbUrl = "something"; + auto manager = make_shared(config.name, config.className, config.detectorName, 0); try { - gSystem->AddDynamicPath("lib:../../lib:../../../lib:.:"); // add local paths for the test - factory.create(config, manager); + gSystem->AddDynamicPath("lib:../../lib:../../../lib:.:"); // add local paths for the test + factory.create(config, manager); } catch (...) { BOOST_TEST_FAIL(boost::current_exception_diagnostic_information()); } @@ -42,23 +42,22 @@ BOOST_AUTO_TEST_CASE(Task_Factory) bool is_critical(AliceO2::Common::FatalException const&) { return true; } -BOOST_AUTO_TEST_CASE(Task_Factory_failures) +BOOST_AUTO_TEST_CASE(Task_Factory_failures, *utf::depends_on("Task_Factory") /* make sure we don't run both tests at the same time */) { TaskFactory factory; - TaskConfig config; - auto manager = make_shared(config); + TaskRunnerConfig config; + auto manager = make_shared(config.name, config.className, config.detectorName, 0); - config.taskName = "task"; + config.name = "task"; config.moduleName = "WRONGNAME"; config.className = "o2::quality_control_modules::example::ExampleTask"; - BOOST_CHECK_EXCEPTION(factory.create(config, manager), AliceO2::Common::FatalException, is_critical); + BOOST_CHECK_EXCEPTION(factory.create(config, manager), AliceO2::Common::FatalException, is_critical); - std::string addition = "lib:../../lib:../../../lib:"; - gSystem->Setenv("LD_LIBRARY_PATH", (addition + gSystem->Getenv("LD_LIBRARY_PATH")).c_str()); - config.taskName = "task"; + gSystem->AddDynamicPath("lib:../../lib:../../../lib:"); // add local paths for the test + config.name = "task"; config.moduleName = "QcCommon"; config.className = "WRONGCLASS"; - BOOST_CHECK_EXCEPTION(factory.create(config, manager), AliceO2::Common::FatalException, is_critical); + BOOST_CHECK_EXCEPTION(factory.create(config, manager), AliceO2::Common::FatalException, is_critical); } } // namespace o2::quality_control_modules::example diff --git a/Modules/Example/test/testQcExample.cxx b/Modules/Example/test/testQcExample.cxx index 0ad4b8bf85..0fa375a7f5 100644 --- a/Modules/Example/test/testQcExample.cxx +++ b/Modules/Example/test/testQcExample.cxx @@ -3,11 +3,11 @@ /// \author Barthelemy von Haller /// -#include "../include/Example/ExampleTask.h" +#include "Example/ExampleTask.h" #include "QualityControl/TaskFactory.h" #include -#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MODULE QcExample test #define BOOST_TEST_MAIN #define BOOST_TEST_DYN_LINK @@ -23,28 +23,31 @@ namespace o2::quality_control_modules::example BOOST_AUTO_TEST_CASE(insantiate_task) { ExampleTask task; - TaskConfig config; - auto manager = make_shared(config); + TaskRunnerConfig config; + config.consulUrl = ""; + config.name = "qcExampleTest"; + config.detectorName = "TST"; + auto manager = make_shared(config.name, "ExampleTask", config.detectorName, 0); task.setObjectsManager(manager); // task.initialize();// TODO -// BOOST_CHECK(task.getHisto1() != nullptr); -// BOOST_CHECK(task.getHisto2() != nullptr); + // BOOST_CHECK(task.getHisto1() != nullptr); + // BOOST_CHECK(task.getHisto2() != nullptr); Activity activity; task.startOfActivity(activity); -// task.startOfCycle(); + // task.startOfCycle(); // auto producer = AliceO2::DataSampling::DataBlockProducer(false, 1024); // DataSetReference dataSet = producer.getDataSet(); // task.monitorDataBlock(dataSet);// TODO -// BOOST_CHECK(task.getHisto1()->GetEntries() > 0); + // BOOST_CHECK(task.getHisto1()->GetEntries() > 0); -// task.endOfCycle(); + // task.endOfCycle(); task.endOfActivity(activity); task.startOfActivity(activity); -// BOOST_CHECK(task.getHisto1()->GetEntries() == 0); + // BOOST_CHECK(task.getHisto1()->GetEntries() == 0); task.endOfActivity(activity); } diff --git a/Modules/FIT/CMakeLists.txt b/Modules/FIT/CMakeLists.txt new file mode 100644 index 0000000000..44b8df0050 --- /dev/null +++ b/Modules/FIT/CMakeLists.txt @@ -0,0 +1,6 @@ +# ---- FIT directories ---- +add_subdirectory(Common) +add_subdirectory(FDD) +add_subdirectory(FIT) +add_subdirectory(FT0) +add_subdirectory(FV0) \ No newline at end of file diff --git a/Modules/FIT/Common/CMakeLists.txt b/Modules/FIT/Common/CMakeLists.txt new file mode 100644 index 0000000000..95cf1f5f58 --- /dev/null +++ b/Modules/FIT/Common/CMakeLists.txt @@ -0,0 +1,55 @@ +set(MODULE_NAME "O2QcFITCommon") + +# ---- Files ---- + +set(SRCS + src/HelperCommon.cxx + src/HelperFIT.cxx + src/HelperHist.cxx + src/HelperLUT.cxx + src/DigitSync.cxx + src/HelperGraph.cxx +) + +set(HEADERS + include/FITCommon/DetectorFIT.h + include/FITCommon/HelperCommon.h + include/FITCommon/HelperFIT.h + include/FITCommon/HelperHist.h + include/FITCommon/HelperLUT.h + include/FITCommon/DigitSync.h + include/FITCommon/PostProcHelper.h + include/FITCommon/HelperGraph.h +) + +# ---- Library ---- + +add_library(${MODULE_NAME} SHARED ${SRCS}) + +target_include_directories( + ${MODULE_NAME} + PUBLIC $ $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +target_link_libraries(${MODULE_NAME} PUBLIC O2QualityControl + ROOT::Core + ROOT::Hist + O2::CommonDataFormat + O2::DataFormatsFIT + O2QcCommon + O2::DataFormatsFDD + O2::DataFormatsFT0 + O2::DataFormatsFV0) + +set(CMAKE_REQUIRED_INCLUDES ${O2_INCLUDE_DIRS} ${ROOT_INCLUDE_DIRS}) + +install( + TARGETS ${MODULE_NAME} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/FITCommon + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") \ No newline at end of file diff --git a/Modules/FIT/Common/include/FITCommon/DetectorFIT.h b/Modules/FIT/Common/include/FITCommon/DetectorFIT.h new file mode 100644 index 0000000000..ee4b04dc7f --- /dev/null +++ b/Modules/FIT/Common/include/FITCommon/DetectorFIT.h @@ -0,0 +1,200 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DetectorFIT.h +/// \author Artur Furs afurs@cern.ch +/// \brief Helper class for FIT detectors + +#ifndef QC_MODULE_FIT_DETECTORFIT_H +#define QC_MODULE_FIT_DETECTORFIT_H + +#include "DataFormatsFDD/Digit.h" +#include "DataFormatsFDD/ChannelData.h" + +#include "DataFormatsFT0/Digit.h" +#include "DataFormatsFT0/ChannelData.h" + +#include "DataFormatsFV0/Digit.h" +#include "DataFormatsFV0/ChannelData.h" + +// #include "CommonDataFormat/InteractionRecord.h" + +#include +#include +#include +#include +#include +// #include "FITCommon/HelperFIT.h" +namespace o2::quality_control_modules::fit +{ +namespace detectorFIT +{ + +using TrgMap_t = std::map; + +enum EDetectorFIT { kFDD, + kFT0, + kFV0 }; +enum ESide { kNothing, + kSideA, + kSideC }; +#define CREATE_FIT_CHDATA_ACCESSOR(Name, Field) \ + template \ + constexpr auto Name(T&& channelData)->std::decay_t().Field)>&& \ + { \ + return std::forward().Field)>>(channelData.mChargeADC); \ + } + +// FT0 and FV0 has the same field names +CREATE_FIT_CHDATA_ACCESSOR(Amp, mChargeADC) +CREATE_FIT_CHDATA_ACCESSOR(Amp, QTCAmpl) + +CREATE_FIT_CHDATA_ACCESSOR(Time, mTime) +CREATE_FIT_CHDATA_ACCESSOR(Time, CFDTime) + +CREATE_FIT_CHDATA_ACCESSOR(BitsPM, mFEEBits) +CREATE_FIT_CHDATA_ACCESSOR(BitsPM, ChainQTC) + +CREATE_FIT_CHDATA_ACCESSOR(ChannelID, mPMNumber) +CREATE_FIT_CHDATA_ACCESSOR(ChannelID, ChId) + +#undef CREATE_FIT_CHDATA_ACCESSOR + +template +class BaseDetectorFIT +{ + public: + typedef std::map TrgMap_t; + + constexpr static EDetectorFIT sDetFIT_ID = DetID; + constexpr static int sNchannelsA = NchannelsA; + constexpr static int sNchannelsC = NchannelsC; + constexpr static int sNchannelsNone = NchannelsNone; + constexpr static int sNchannelsAC = sNchannelsA + sNchannelsC; + constexpr static int sNchannelsAll = sNchannelsAC + sNchannelsNone; + constexpr static bool sIsChIDdirectForSides = isChIDdirectForSides; + typedef DigitType Digit_t; + typedef ChannelDataType ChannelData_t; + typedef decltype(std::declval().mTriggers) Triggers_t; + constexpr static int getSide(int chID) + { + if (chID < sNchannelsA) { + return isChIDdirectForSides ? ESide::kSideA : ESide::kSideC; + } else if (chID < sNchannelsAC) { + return isChIDdirectForSides ? ESide::kSideC : ESide::kSideA; + } + return ESide::kNothing; + } + constexpr static bool isSideA(int side) { return side == ESide::kSideA; } + constexpr static bool isSideC(int side) { return side == ESide::kSideC; } + static TrgMap_t addTechTrgBits(const TrgMap_t& trgMap) + { + auto newTrgMap = trgMap; + newTrgMap.insert({ Triggers_t::bitLaser, "Laser" }); + newTrgMap.insert({ Triggers_t::bitOutputsAreBlocked, "OutputsAreBlocked" }); + newTrgMap.insert({ Triggers_t::bitDataIsValid, "DataIsValid" }); + return newTrgMap; + } + static TrgMap_t addBitsToMap(const TrgMap_t& trgMap, const std::vector& vecPairs) + { + auto newTrgMap = trgMap; + std::for_each(vecPairs.cbegin(), vecPairs.cend(), [&newTrgMap](const auto& entry) { newTrgMap.insert(entry); }); + return newTrgMap; + } + static const inline TrgMap_t sMapPMbits = { + { ChannelData_t::kNumberADC, "NumberADC" }, + { ChannelData_t::kIsDoubleEvent, "IsDoubleEvent" }, + { ChannelData_t::kIsTimeInfoNOTvalid, "IsTimeInfoNOTvalid" }, + { ChannelData_t::kIsCFDinADCgate, "IsCFDinADCgate" }, + { ChannelData_t::kIsTimeInfoLate, "IsTimeInfoLate" }, + { ChannelData_t::kIsAmpHigh, "IsAmpHigh" }, + { ChannelData_t::kIsEventInTVDC, "IsEventInTVDC" }, + { ChannelData_t::kIsTimeInfoLost, "IsTimeInfoLost" } + }; + + constexpr static std::array setChIDs2Side() + { + std::array chID2side{}; + for (int iCh = 0; iCh < chID2side.size(); iCh++) { + chID2side[iCh] = getSide(iCh); + } + return chID2side; + } + constexpr static std::array sArrChID2Side = setChIDs2Side(); + + constexpr static std::array, 256> decompose1Byte() + { + std::array, 256> arrBitPos{}; + for (int iByteValue = 0; iByteValue < arrBitPos.size(); iByteValue++) { + auto& vec = arrBitPos[iByteValue]; + for (int iBit = 0; iBit < 8; iBit++) { + if (iByteValue & (1 << iBit)) { + vec.push_back(iBit); + } + } + } + return arrBitPos; + } + static const inline std::array, 256> sArrDecomposed1Byte = decompose1Byte(); +}; + +template +class DetectorFIT : BaseDetectorFIT +{ +}; + +template <> +struct DetectorFIT : public BaseDetectorFIT { + static const inline TrgMap_t sMapTrgBits = { + { Triggers_t::bitA, "OrA" }, + { Triggers_t::bitC, "OrC" }, + { Triggers_t::bitVertex, "Vertex" }, + { Triggers_t::bitCen, "Central" }, + { Triggers_t::bitSCen, "SemiCentral" } + }; + static const inline TrgMap_t sMapTechTrgBits = addTechTrgBits(sMapTrgBits); +}; + +template <> +struct DetectorFIT : public BaseDetectorFIT { + static const inline TrgMap_t sMapTrgBits = { + { Triggers_t::bitA, "OrA" }, + { Triggers_t::bitC, "OrC" }, + { Triggers_t::bitVertex, "Vertex" }, + { Triggers_t::bitCen, "Central" }, + { Triggers_t::bitSCen, "SemiCentral" } + }; + static const inline TrgMap_t sMapTechTrgBits = addTechTrgBits(sMapTrgBits); + static const inline TrgMap_t sMapTechTrgBitsExtra = addBitsToMap(sMapTechTrgBits, { { Triggers_t::bitMinBias, "MinBias" } }); +}; + +template <> +struct DetectorFIT : public BaseDetectorFIT { + static const inline TrgMap_t sMapTrgBits = { + { o2::fit::Triggers::bitA, "OrA" }, + { o2::fit::Triggers::bitAOut, "OrAOut" }, + { o2::fit::Triggers::bitTrgNchan, "TrgNChan" }, + { o2::fit::Triggers::bitTrgCharge, "TrgCharge" }, + { o2::fit::Triggers::bitAIn, "OrAIn" } + + }; + static const inline TrgMap_t sMapTechTrgBits = addTechTrgBits(sMapTrgBits); +}; + +using DetectorFDD = DetectorFIT; +using DetectorFT0 = DetectorFIT; +using DetectorFV0 = DetectorFIT; + +} // namespace detectorFIT + +} // namespace o2::quality_control_modules::fit +#endif diff --git a/Modules/FIT/Common/include/FITCommon/DigitSync.h b/Modules/FIT/Common/include/FITCommon/DigitSync.h new file mode 100644 index 0000000000..25197680bf --- /dev/null +++ b/Modules/FIT/Common/include/FITCommon/DigitSync.h @@ -0,0 +1,138 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitSync.h +/// \author Artur Furs afurs@cern.ch +/// \brief Class for synchronization of FIT detector digits + +#ifndef QC_MODULE_FIT_DIGITSYNC_H +#define QC_MODULE_FIT_DIGITSYNC_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CommonDataFormat/InteractionRecord.h" + +namespace o2::quality_control_modules::fit +{ + +template +struct DigitSync { + public: + typedef DigitFDDtype DigitFDD; + typedef DigitFT0type DigitFT0; + typedef DigitFV0type DigitFV0; + using Index_t = uint32_t; + typedef std::map MapIR2Digits_t; + enum EDetectorBit { kFDD, + kFT0, + kFV0 + }; + constexpr static std::size_t sNdetectors = 3; + std::bitset mActiveDets; + std::array mDigitIndexes{}; + template + static auto getIR(DigitType&& digit) -> std::enable_if_t::getInteractionRecord)>, o2::InteractionRecord> + { + return (std::forward(digit)).getInteractionRecord(); + } + + template + static auto getIR(DigitType&& digit) -> std::enable_if_t::getIntRecord)>, o2::InteractionRecord> + { + return (std::forward(digit)).getIntRecord(); + } + + template + static void fillSyncMap(MapIR2Digits_t& mapIR2Digits, const ContType& vecDigits) + { + uint32_t index{ 0 }; + using DigitType = typename ContType::value_type; // span or vector + for (const auto& digit : vecDigits) { + const auto ir = getIR(digit); + auto pairInserted = mapIR2Digits.insert({ ir, {} }); + if constexpr (std::is_same::value) { + pairInserted.first->second.mActiveDets.set(EDetectorBit::kFDD); + pairInserted.first->second.mDigitIndexes[EDetectorBit::kFDD] = index; + } else if (std::is_same::value) { + pairInserted.first->second.mActiveDets.set(EDetectorBit::kFT0); + pairInserted.first->second.mDigitIndexes[EDetectorBit::kFT0] = index; + } else if (std::is_same::value) { + pairInserted.first->second.mActiveDets.set(EDetectorBit::kFV0); + pairInserted.first->second.mDigitIndexes[EDetectorBit::kFV0] = index; + } else { + constexpr bool breakCompilation = true; + static_assert(breakCompilation == true, "Unrecognized digit type!"); + } + index++; + } + } + template + static MapIR2Digits_t makeSyncMap(const DigitFDDcont& vecDigitsFDD, + const DigitFT0cont& vecDigitsFT0, + const DigitFV0cont& vecDigitsFV0) + { + MapIR2Digits_t mapIR2Digits{}; + fillSyncMap(mapIR2Digits, vecDigitsFDD); + fillSyncMap(mapIR2Digits, vecDigitsFT0); + fillSyncMap(mapIR2Digits, vecDigitsFV0); + return mapIR2Digits; + } + Index_t getIndexFDD() const { return mDigitIndexes[EDetectorBit::kFDD]; } + Index_t getIndexFT0() const { return mDigitIndexes[EDetectorBit::kFT0]; } + Index_t getIndexFV0() const { return mDigitIndexes[EDetectorBit::kFV0]; } + + bool isFDD() const { return mActiveDets.test(EDetectorBit::kFDD); } + bool isFT0() const { return mActiveDets.test(EDetectorBit::kFT0); } + bool isFV0() const { return mActiveDets.test(EDetectorBit::kFV0); } + + template + const DigitFDD& getDigitFDD(const VecDigitsFDDtype& vecDigitsFDD) const + { + return isFDD() ? vecDigitsFDD[getIndexFDD()] : sDummyDigitFDD; + } + template + const DigitFT0& getDigitFT0(const VecDigitsFT0type& vecDigitsFT0) const + { + return isFT0() ? vecDigitsFT0[getIndexFT0()] : sDummyDigitFT0; + } + template + const DigitFV0& getDigitFV0(const VecDigitsFV0type& vecDigitsFV0) const + { + return isFV0() ? vecDigitsFV0[getIndexFV0()] : sDummyDigitFV0; + } + + private: + static const DigitFDD sDummyDigitFDD; + static const DigitFT0 sDummyDigitFT0; + static const DigitFV0 sDummyDigitFV0; +}; +template +const typename DigitSync::DigitFDD + DigitSync::sDummyDigitFDD = typename DigitSync::DigitFDD{}; + +template +const typename DigitSync::DigitFT0 + DigitSync::sDummyDigitFT0 = typename DigitSync::DigitFT0{}; + +template +const typename DigitSync::DigitFV0 + DigitSync::sDummyDigitFV0 = typename DigitSync::DigitFV0{}; + +} // namespace o2::quality_control_modules::fit +#endif diff --git a/Modules/FIT/Common/include/FITCommon/HelperCommon.h b/Modules/FIT/Common/include/FITCommon/HelperCommon.h new file mode 100644 index 0000000000..ab95d045bd --- /dev/null +++ b/Modules/FIT/Common/include/FITCommon/HelperCommon.h @@ -0,0 +1,125 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HelperHist.h +/// \author Artur Furs afurs@cern.ch +/// \brief Histogram helper + +#ifndef QC_MODULE_FIT_HELPERCOMMON_H +#define QC_MODULE_FIT_HELPERCOMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +namespace o2::quality_control_modules::fit +{ + +namespace helper +{ + +template +inline ValueType getConfigFromPropertyTree(const boost::property_tree::ptree& config, const char* fieldName, ValueType value = {}) +{ + const auto& node = config.get_child_optional(fieldName); + if (node) { + value = node.get_ptr()->get_child("").get_value(); + ILOG(Debug, Support) << fieldName << ": " << value << "\"" << ENDM; + } else { + ILOG(Debug, Support) << "Default " << fieldName << ": " << value << ENDM; + } + return value; +} + +template ::value || + std::is_same::value || + std::is_integral::value>> +inline auto parseParameters(const std::string& param, const std::string& del) +{ + std::regex reg(del); + std::sregex_token_iterator first{ param.begin(), param.end(), reg, -1 }, last; + std::vector vecResult; + for (auto it = first; it != last; it++) { + if constexpr (std::is_integral::value && !std::is_same::value) { + if (!boost::algorithm::trim_copy(*it).empty()) { + vecResult.push_back(std::stoi(*it)); + } + } else if constexpr (std::is_floating_point::value) { + if (!boost::algorithm::trim_copy(*it).empty()) { + vecResult.push_back(std::stof(*it)); + } + } else if constexpr (std::is_same::value) { + vecResult.push_back(*it); + } else if constexpr (std::is_same::value) { + std::string trimmed = boost::algorithm::trim_copy(*it); + vecResult.push_back(boost::algorithm::to_lower_copy(trimmed) == "true" || trimmed == "1"); + } + } + return vecResult; +} + +// first entry - start BC, second - number of BCs in row +template +inline std::vector> getMapBCtrains(const BitSetBC& bitsetBC) +{ + std::vector> vecTrains{}; + int nBCs{ 0 }; + int firstBC{ -1 }; + for (int iBC = 0; iBC < bitsetBC.size(); iBC++) { + if (bitsetBC.test(iBC)) { + // BC in train + nBCs++; + if (firstBC == -1) { + // first BC in train + firstBC = iBC; + } + } else if (nBCs > 0) { + // Next after end of train BC + vecTrains.push_back({ firstBC, nBCs }); + firstBC = -1; + nBCs = 0; + } + } + if (nBCs > 0) { // last iteration + vecTrains.push_back({ firstBC, nBCs }); + } + return vecTrains; +} + +std::map multiplyMaps(const std::vector, std::string>>& vecPreffixMapSuffix, bool useMapSizeAsMultFactor = true); + +template +auto funcWithArgsAsTupleBase(std::function func, const std::tuple& tupleArgs, std::index_sequence) +{ + return func(std::get(tupleArgs)...); +} + +template +auto funcWithArgsAsTuple(std::function func, const std::tuple& tupleArgs) +{ + return funcWithArgsAsTupleBase(func, tupleArgs, std::make_index_sequence{}); +} + +} // namespace helper +} // namespace o2::quality_control_modules::fit +#endif \ No newline at end of file diff --git a/Modules/FIT/Common/include/FITCommon/HelperFIT.h b/Modules/FIT/Common/include/FITCommon/HelperFIT.h new file mode 100644 index 0000000000..238c33c362 --- /dev/null +++ b/Modules/FIT/Common/include/FITCommon/HelperFIT.h @@ -0,0 +1,207 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HelperHist.h +/// \author Artur Furs afurs@cern.ch +/// \brief Helper class for FIT detectors + +#ifndef QC_MODULE_FIT_FITHELPER_H +#define QC_MODULE_FIT_FITHELPER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TH1D.h" +#include "TH2F.h" + +#include +#include "CommonDataFormat/BunchFilling.h" +#include "QualityControl/CustomParameters.h" +#include "Common/Utils.h" + +#include +namespace o2::quality_control_modules::fit +{ + +template +struct DataTCM { + using Digit_t = DigitType; + typedef decltype(std::declval().mTriggers) Triggers_t; + // Copied from o2::fit::Triggers + // will be moved there soon + DataTCM(int32_t _amplA, int32_t _amplC, int sumTimeA, int sumTimeC, uint8_t _nChanA, uint8_t _nChanC, uint8_t _triggersignals = 0) : triggersignals(_triggersignals), nChanA(_nChanA), nChanC(_nChanC), amplA(_amplA), amplC(_amplC), timeA(divHW_TCM(sumTimeA, _nChanA)), timeC(divHW_TCM(sumTimeC, _nChanC)) + { + } + + uint8_t triggersignals{}; // FIT trigger signals + uint8_t nChanA{}; // number of fired channels A side + uint8_t nChanC{}; // number of fired channels A side + int32_t amplA{}; // sum amplitude A side + int32_t amplC{}; // sum amplitude C side + int16_t timeA{}; // average time A side (shouldn't be used if nChanA == 0) + int16_t timeC{}; // average time C side (shouldn't be used if nChanC == 0) + + template + void fillSideA(const AmpType& amp, const TimeType& time) + { + amplA += amp; + timeA += time; + nChanA++; + } + template + void fillSideC(const AmpType& amp, const TimeType& time) + { + amplC += amp; + timeC += time; + nChanA++; + } + + static int divHW_TCM(int sumTime, int nChannels) + { + constexpr std::array rom7x17{ 16383, 8192, 5461, 4096, 3277, 2731, 2341, 2048, 1820, 1638, 1489, 1365, 1260, 1170, 1092, 1024, 964, 910, 862, 819, 780, 745, 712, 683, 655, 630, 607, 585, 565, 546, 529, 512, 496, 482, 468, 455, 443, 431, 420, 410, 400, 390, 381, 372, 364, 356, 349, 341, 334, 328, 321, 315, 309, 303, 298, 293, 287, 282, 278, 273, 269, 264, 260, 256, 252, 248, 245, 241, 237, 234, 231, 228, 224, 221, 218, 216, 213, 210, 207, 205, 202, 200, 197, 195, 193, 191, 188, 186, 184, 182, 180, 178, 176, 174, 172, 171, 169, 167, 165, 164, 162, 161, 159, 158, 156, 155, 153, 152, 150, 149, 148, 146, 145, 144, 142, 141, 140, 139, 138, 137, 135, 134, 133, 132, 131, 130, 129 }; + return nChannels ? (sumTime * rom7x17[nChannels - 1]) >> 14 : 0; + } +}; +template +class TrgValidation +{ + public: + using Digit_t = DigitType; + typedef decltype(std::declval().mTriggers) Triggers_t; + using DataTCM_t = DataTCM; + void configure(const o2::quality_control::core::CustomParameters& customParameters) + { + const std::string trgModeThresholdVar = o2::quality_control_modules::common::getFromConfig(customParameters, "trgModeThresholdVar", "Ampl"); + const std::string trgModeSide = o2::quality_control_modules::common::getFromConfig(customParameters, "trgModeSide", "A+C"); + const auto itFunc = mMapTrgCalcFunctors.find({ trgModeThresholdVar, trgModeSide }); + assert(itFunc != mMapTrgCalcFunctors.end()); + mFunctorTrgCalc = itFunc->second; + + mTrgOrGate = o2::quality_control_modules::common::getFromConfig(customParameters, "trgOrGate", 153); + mTrgChargeLevelLow = o2::quality_control_modules::common::getFromConfig(customParameters, "trgChargeLevelLow", 0); + mTrgChargeLevelHigh = o2::quality_control_modules::common::getFromConfig(customParameters, "trgChargeLevelHigh", 4095); + mTrgThresholdTimeLow = o2::quality_control_modules::common::getFromConfig(customParameters, "trgThresholdTimeLow", -192); + mTrgThresholdTimeHigh = o2::quality_control_modules::common::getFromConfig(customParameters, "trgThresholdTimeHigh", 192); + + int thrshFactor = (trgModeThresholdVar == std::string{ "Ampl" }) ? 2 : 1; // 2 for Ampl, 1 for Nchannels. TODO: better to use mapping + mTrgThresholdCenA = thrshFactor * o2::quality_control_modules::common::getFromConfig(customParameters, "trgThresholdCenA", 20); + mTrgThresholdCenC = thrshFactor * o2::quality_control_modules::common::getFromConfig(customParameters, "trgThresholdCenC", 20); + mTrgThresholdSCenA = thrshFactor * o2::quality_control_modules::common::getFromConfig(customParameters, "trgThresholdSCenA", 10); + mTrgThresholdSCenC = thrshFactor * o2::quality_control_modules::common::getFromConfig(customParameters, "trgThresholdSCenC", 10); + } + + // - time window for vertex trigger + int mTrgThresholdTimeLow; + int mTrgThresholdTimeHigh; + // - parameters for (Semi)Central triggers + // same parameters re-used for both Ampl and Nchannels thresholds + int mTrgThresholdCenA; + int mTrgThresholdCenC; + int mTrgThresholdSCenA; + int mTrgThresholdSCenC; + int mTrgChargeLevelLow; + int mTrgChargeLevelHigh; + int mTrgOrGate; + + enum ETriggerValidation { // first bit - HW, second - SW + kBothOff = 0b00, + kOnlyHW = 0b01, + kOnlySW = 0b10, + kBothOn = 0b11 + }; + + static const inline std::map sMapTrgValidation = { + { ETriggerValidation::kBothOff, "Both off" }, + { ETriggerValidation::kOnlyHW, "Only HW" }, + { ETriggerValidation::kOnlySW, "Only SW" }, + { ETriggerValidation::kBothOn, "Both on" } + }; + // To get trigger validation status + static ETriggerValidation getTrgValidationStatus(uint8_t hwTrg, uint8_t swTrg, uint8_t trgBitPos) + { + // |SW bit|HW bit| + uint8_t status = (((hwTrg >> trgBitPos) & 1) | (((swTrg << 1) >> trgBitPos) & 0b10)); + return static_cast(status); + }; + + void emulateTimeTriggers(DataTCM_t& tcm) + { + const bool isOrA = tcm.nChanA > 0; + const bool isOrC = tcm.nChanC > 0; + tcm.triggersignals |= (isOrA << Triggers_t::bitA); + tcm.triggersignals |= (isOrC << Triggers_t::bitC); + const int meanTimeDiff = tcm.timeC - tcm.timeA; + const bool isVertexEmu = mTrgThresholdTimeLow < meanTimeDiff && meanTimeDiff < mTrgThresholdTimeHigh && isOrA && isOrC; + tcm.triggersignals |= (isVertexEmu << Triggers_t::bitVertex); + } + void emulateTriggers(DataTCM_t& tcm) + { + emulateTimeTriggers(tcm); // Time trigger emulation + mFunctorTrgCalc(tcm); // Amp and channel triggers + } + const std::map, std::function> mMapTrgCalcFunctors = { + { { "Ampl", "A+C" }, [this](DataTCM_t& tcm) { + const auto sumAmp = tcm.amplA + tcm.amplC; + const bool cent = sumAmp > mTrgThresholdCenA; + const bool scent = cent == false && sumAmp > mTrgThresholdSCenA; + // std::cout< mTrgThresholdCenA && tcm.amplC > mTrgThresholdCenC; + const bool scent = cent == false && tcm.amplA > mTrgThresholdSCenA && tcm.amplC > mTrgThresholdSCenC; + tcm.triggersignals |= ((cent << Triggers_t::bitCen) | (scent << Triggers_t::bitSCen)); + } }, + { { "Ampl", "A" }, [this](DataTCM_t& tcm) { + const bool cent = tcm.amplA > mTrgThresholdCenA; + const bool scent = cent == false && tcm.amplA > mTrgThresholdSCenA; + tcm.triggersignals |= ((cent << Triggers_t::bitCen) | (scent << Triggers_t::bitSCen)); + } }, + { { "Ampl", "C" }, [this](DataTCM_t& tcm) { + const bool cent = tcm.amplC > mTrgThresholdCenC; + const bool scent = cent == false && tcm.amplC > mTrgThresholdSCenC; + tcm.triggersignals |= ((cent << Triggers_t::bitCen) | (scent << Triggers_t::bitSCen)); + } }, + { { "Nchannels", "A+C" }, [this](DataTCM_t& tcm) { + const auto nChannels = tcm.nChanA + tcm.nChanC; + const bool cent = nChannels > mTrgThresholdCenA; + const bool scent = cent == false && nChannels > mTrgThresholdSCenA; + tcm.triggersignals |= ((cent << Triggers_t::bitCen) | (scent << Triggers_t::bitSCen)); + } }, + { { "Nchannels", "A&C" }, [this](DataTCM_t& tcm) { + const bool cent = tcm.nChanA > mTrgThresholdCenA && tcm.nChanC > mTrgThresholdCenC; + const bool scent = cent == false && tcm.nChanA > mTrgThresholdSCenA && tcm.nChanC > mTrgThresholdSCenC; + tcm.triggersignals |= ((cent << Triggers_t::bitCen) | (scent << Triggers_t::bitSCen)); + } }, + { { "Nchannels", "A" }, [this](DataTCM_t& tcm) { + const bool cent = tcm.nChanA > mTrgThresholdCenA; + const bool scent = cent == false && tcm.nChanA > mTrgThresholdSCenA; + tcm.triggersignals |= ((cent << Triggers_t::bitCen) | (scent << Triggers_t::bitSCen)); + } }, + { { "Nchannels", "C" }, [this](DataTCM_t& tcm) { + const bool cent = tcm.nChanC > mTrgThresholdCenC; + const bool scent = cent == false && tcm.nChanC > mTrgThresholdSCenC; + tcm.triggersignals |= ((cent << Triggers_t::bitCen) | (scent << Triggers_t::bitSCen)); + } } + }; + std::function mFunctorTrgCalc; +}; + +} // namespace o2::quality_control_modules::fit +#endif diff --git a/Modules/FIT/Common/include/FITCommon/HelperGraph.h b/Modules/FIT/Common/include/FITCommon/HelperGraph.h new file mode 100644 index 0000000000..485f53ae7e --- /dev/null +++ b/Modules/FIT/Common/include/FITCommon/HelperGraph.h @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HelperGraph.h +/// \author Jakub Muszyński jakub.milosz.muszynski@cern.ch +/// \brief Graph helper + +#ifndef QC_MODULE_FIT_FITHELPERGRAPH_H +#define QC_MODULE_FIT_FITHELPERGRAPH_H + +#include +#include +#include + +#include +#include "QualityControl/QcInfoLogger.h" + +namespace o2::quality_control_modules::fit::helper +{ + +/// \brief Factory that forwards ctor arguments to the chosen GraphType. +/// Example: +/// auto g = makeGraph("name","title",n,x,y,ex,ey); +/// \author Jakub Muszyński jakub.milosz.muszynski@cern.ch +template +inline auto makeGraph(const std::string& name, + const std::string& title, + CtorArgs&&... args) +{ + static_assert(std::is_base_of_v, + "GraphType must inherit from TObject / ROOT graph class"); + auto ptr = std::make_unique(std::forward(args)...); + ptr->SetNameTitle(name.c_str(), title.c_str()); + return ptr; +} + +/// \brief Publishes graph through the QC ObjectsManager. +/// \author Jakub Muszyński jakub.milosz.muszynski@cern.ch +template +inline auto registerGraph(ManagerType manager, + PublicationPolicyType publicationPolicy, + const std::string& defaultDrawOption, + const std::string& name, + const std::string& title, + CtorArgs&&... args) +{ + auto ptrGraph = makeGraph(name, title, + std::forward(args)...); + manager->startPublishing(ptrGraph.get(), publicationPolicy); + + if (!defaultDrawOption.empty()) { + manager->setDefaultDrawOptions(ptrGraph.get(), defaultDrawOption); + } + ILOG(Info, Support) << "Registered graph \"" << name + << "\" with publication policy " << int(publicationPolicy) + << ENDM; + return ptrGraph; +} + +} // namespace o2::quality_control_modules::fit::helper +#endif // QC_MODULE_FIT_FITHELPERGRAPH_H diff --git a/Modules/FIT/Common/include/FITCommon/HelperHist.h b/Modules/FIT/Common/include/FITCommon/HelperHist.h new file mode 100644 index 0000000000..c25a7c3080 --- /dev/null +++ b/Modules/FIT/Common/include/FITCommon/HelperHist.h @@ -0,0 +1,203 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HelperHist.h +/// \author Artur Furs afurs@cern.ch +/// \brief Histogram helper + +#ifndef QC_MODULE_FIT_FITHELPERHIST_H +#define QC_MODULE_FIT_FITHELPERHIST_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "TH1D.h" +#include "TAxis.h" + +#include "QualityControl/QcInfoLogger.h" + +#include +namespace o2::quality_control_modules::fit +{ + +namespace helper +{ + +// Preparation for map axis -> map with bin labels +template +inline void getMapBinLabelsPerAxis(std::map>& mapAxisToMapBinLabels, const TupleArgs& tupleArgs) +{ + if constexpr (NStep < std::tuple_size_v) { + using ElementType = typename std::tuple_element::type; + const auto& arg = std::get(tupleArgs); + constexpr bool isArgMapBinLabels = std::is_same_v, std::map>; + constexpr bool isArgTupleBins = std::is_same_v, std::tuple>; + if constexpr (isArgMapBinLabels && NArgLocal == 0) { // map with bin labels + mapAxisToMapBinLabels.insert({ NAxis, arg }); + getMapBinLabelsPerAxis(mapAxisToMapBinLabels, tupleArgs); + } else if constexpr (isArgTupleBins && NArgLocal == 0) { // tuple of bin params + getMapBinLabelsPerAxis(mapAxisToMapBinLabels, tupleArgs); + } else if constexpr (!isArgMapBinLabels && NArgLocal < 2) { // first or second bin argument + getMapBinLabelsPerAxis(mapAxisToMapBinLabels, tupleArgs); + } else if constexpr (!isArgMapBinLabels && NArgLocal == 2) { // third bin argument + getMapBinLabelsPerAxis(mapAxisToMapBinLabels, tupleArgs); + } else { + // error + // static_assert(); + } + } +} + +// For unpacking std::map into tuple of bin parameters +template +inline auto unpackHistArg(const Arg& arg) +{ + if constexpr (std::is_same_v, std::map>) { + return std::make_tuple(static_cast(arg.size()), float(0.0), static_cast(arg.size())); + } else if constexpr (std::is_same_v, std::tuple>) { + return arg; + } else { + return std::make_tuple(arg); + } +} + +// Makes hist with map bin->label(std::map) as bin parameter +// Example: +// auto h1 = makeHist("hist","hist",10,0,10,map1) +// auto h2 = makeHist("hist2","hist2",map1) + +template +inline auto makeHist(const std::string& name, const std::string& title, BinArgs&&... binArgs) +{ + + const auto tupleArgs = std::tuple_cat(std::make_tuple(name, title), unpackHistArg(std::forward(binArgs))...); + auto ptr = std::apply( + [](const std::string& name, const std::string& title, auto&&... args) { + return std::move(std::make_unique>(name.c_str(), title.c_str(), std::forward(args)...)); + }, + tupleArgs); + + std::map> mapAxisToMapBinLabels; + getMapBinLabelsPerAxis<0, 0, 0>(mapAxisToMapBinLabels, std::make_tuple(std::forward(binArgs)...)); + // Preparing labels + auto getAxis = [](std::unique_ptr>& ptrHist, unsigned int axis) -> TAxis* { + if (axis == 0) { + return ptrHist->GetXaxis(); + } else if (axis == 1) { + return ptrHist->GetYaxis(); + } else if (axis == 2) { + return ptrHist->GetZaxis(); + } + return nullptr; + }; + auto makeLabels = [](TAxis* axis, const std::map& mapBinLabels) { + for (const auto& entry : mapBinLabels) { + axis->SetBinLabel(entry.first + 1, entry.second.c_str()); + } + }; + for (const auto& axisToMapBinLabels : mapAxisToMapBinLabels) { + makeLabels(getAxis(ptr, axisToMapBinLabels.first), axisToMapBinLabels.second); + } + return std::move(ptr); +} + +template +inline auto registerHist(ManagerType manager, PublicationPolicyType publicationPolicy, const std::string& defaultDrawOption, const std::string& name, const std::string& title, BinArgs&&... binArgs) +{ + auto ptrHist = makeHist(name, title, std::forward(binArgs)...); + manager->startPublishing(ptrHist.get(), publicationPolicy); + if (defaultDrawOption.size() > 0) { + manager->setDefaultDrawOptions(ptrHist.get(), defaultDrawOption); + } + return std::move(ptrHist); +} + +template +auto makeProj(const HistSrcType* histSrc, + const std::string& name, + const std::string& title, + const std::pair& rangeProj, + int axis = 0 /*axis along which to do projection: 0 - xAxis; 1 - yAxis*/ + ) -> TH1D* +{ + if (axis != 0 || axis != 1) { + return nullptr; + } + const TAxis* axisObj = axis == 0 ? histSrc->GetXaxis() : histSrc->GetYaxis(); + const auto binMin = axisObj->FindFixBin(rangeProj.first); + const auto binMax = axisObj->FindFixBin(rangeProj.second); + TH1D* histProj = ((TH1D*)(axis == 0 ? histSrc->ProjectionX(name.c_str(), binMin, binMax) : histSrc->ProjectionX(name.c_str(), binMin, binMax))); + histProj->LabelsDeflate(); + histProj->SetTitle(title.c_str()); + return histProj; +} + +template +auto makePartialProj(const HistSrcType* histSrc, + const std::string& name, + const std::string& title, + const SetBinsType& setBinsToProj, // bins to participate in projection + const std::pair& rangeProj, + int axis = 0 // axis along which to do projection: 0 - xAxis; 1 - yAxis + ) -> TH1D* +{ + if (axis != 0 || axis != 1) { + return nullptr; + } + auto histTmp = std::unique_ptr((HistSrcType*)histSrc->Clone("histTmp")); + for (int iBinX = 0; iBinX < histSrc->GetXaxis()->GetNbins() + 2; iBinX++) { + for (int iBinY = 0; iBinY < histSrc->GetYaxis()->GetNbins() + 2; iBinY++) { + bool isBinOutOfSet = false; + if constexpr (std::is_same_v, std::set>>) { + isBinOutOfSet = (setBinsToProj.find(axis == 0 ? iBinX : iBinY) == setBinsToProj.end()); + } else if constexpr (std::is_same_v, std::set>>) { + isBinOutOfSet = (setBinsToProj.find({ iBinX, iBinY }) == setBinsToProj.end()); + } else { + // assertion, shouldn't be compiled + } + if (isBinOutOfSet) { + histTmp->SetBinContent(iBinX, iBinY, 0); + } + } + } + return makeProj(histTmp.get(), name, title, rangeProj, axis); +} + +template +inline auto getRatioHistFrom2D(const HistSrcType* hist, + const std::string& name, + const std::string& title, + std::function funcNom, + const std::tuple& argsNom, + std::function funcDen, + const std::tuple& argsDen) -> HistType* +{ + std::unique_ptr histNom(funcWithArgsAsTuple(funcNom, std::tuple_cat(std::tuple(hist), argsNom))); + std::unique_ptr histDen(funcWithArgsAsTuple(funcDen, std::tuple_cat(std::tuple(hist), argsDen))); + std::unique_ptr histResult((HistType*)histNom->Clone(name.c_str())); + histResult->SetTitle(title.c_str()); + histResult->Divide(histDen); + return std::move(histResult); +} + +} // namespace helper +} // namespace o2::quality_control_modules::fit +#endif \ No newline at end of file diff --git a/Modules/FIT/Common/include/FITCommon/HelperLUT.h b/Modules/FIT/Common/include/FITCommon/HelperLUT.h new file mode 100644 index 0000000000..49b738a7b7 --- /dev/null +++ b/Modules/FIT/Common/include/FITCommon/HelperLUT.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HelperHist.h +/// \author Artur Furs afurs@cern.ch +/// \brief Temporary helper class for LookupTable of FIT detectors, shoud be moved into O2 +#ifndef QC_MODULE_FIT_HELPERLUT_H +#define QC_MODULE_FIT_HELPERLUT_H + +#include +#include + +#include "Headers/DataHeader.h" + +namespace o2::quality_control_modules::fit +{ +namespace helperLUT +{ +//(epID,linkID)->moduleName mapping +using FEEID_t = std::pair; +using Fee2ModuleName_t = std::map; +template +inline FEEID_t getFEEID(EPID_t&& epID, LinkID_t&& linkID) +{ + return FEEID_t{ static_cast(std::forward(epID)), static_cast(std::forward(linkID)) }; +} + +template +inline Fee2ModuleName_t getMapFEE2ModuleName(const LUT& lut) +{ + Fee2ModuleName_t mapFEE2ModuleName{}; + const auto& vecFEE = lut.getVecMetadataFEE(); + for (const auto& entryFEE : vecFEE) { + const auto& entryCRU = entryFEE.mEntryCRU; + mapFEE2ModuleName.insert({ getFEEID(entryCRU.mEndPointID, entryCRU.mLinkID), entryFEE.mModuleName }); + } + return mapFEE2ModuleName; +} + +template +inline void obtainAllParamsLUT(const LUT& lut, Fee2ModuleName_t& mapFEE2ModuleName) +{ + // Put here all LUT params you would like to have + mapFEE2ModuleName = getMapFEE2ModuleName(lut); +} + +void obtainFromLUT(const o2::header::DataOrigin& det, Fee2ModuleName_t& mapFEE2ModuleName, long timestamp = -1); +} // namespace helperLUT + +} // namespace o2::quality_control_modules::fit +#endif diff --git a/Modules/FIT/Common/include/FITCommon/PostProcHelper.h b/Modules/FIT/Common/include/FITCommon/PostProcHelper.h new file mode 100644 index 0000000000..5d4c335fd9 --- /dev/null +++ b/Modules/FIT/Common/include/FITCommon/PostProcHelper.h @@ -0,0 +1,162 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcHelper.h +/// \author Artur Furs afurs@cern.ch +/// + +#ifndef QC_MODULE_FIT_POSTPROCHELPER_H +#define QC_MODULE_FIT_POSTPROCHELPER_H + +#include +#include + +#include "CCDB/CcdbApi.h" +#include "DataFormatsParameters/GRPLHCIFData.h" +#include "QualityControl/Triggers.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/PostProcessingInterface.h" + +#include "FITCommon/HelperHist.h" +#include "FITCommon/HelperCommon.h" + +namespace o2::quality_control_modules::fit +{ + +class PostProcHelper +{ + public: + using Trigger = o2::quality_control::postprocessing::Trigger; + void configure(const boost::property_tree::ptree& config, const char* configPath, const std::string& detName) + { + mCcdbUrl = config.get_child("qc.config.conditionDB.url").get_value(); + const char* configCustom = Form("%s.custom", configPath); + auto cfgPath = [&configCustom](const std::string& entry) { + return Form("%s.%s", configCustom, entry.c_str()); + }; + mPathGrpLhcIf = helper::getConfigFromPropertyTree(config, cfgPath("pathGrpLhcIf"), "GLO/Config/GRPLHCIF"); + mPathInputQcTask = helper::getConfigFromPropertyTree(config, cfgPath("pathDigitQcTask"), std::string{ detName + "/MO/DigitQcTask/" }); + + mNumOrbitsInTF = helper::getConfigFromPropertyTree(config, cfgPath("numOrbitsInTF"), 32); + mMetaAnchorInput = helper::getConfigFromPropertyTree(config, cfgPath("metaAnchorInput"), "CycleDurationNTF"); + mTimestampSource = helper::getConfigFromPropertyTree(config, cfgPath("timestampSource"), "metadata"); + mTimestampMetaField = helper::getConfigFromPropertyTree(config, cfgPath("timestampMetaField"), "timestampTF"); + } + + void initialize(o2::quality_control::postprocessing::Trigger trg, framework::ServiceRegistryRef services) + { + mDatabase = &services.get(); + mCcdbApi.init(mCcdbUrl); + } + // config fields + std::string mPathGrpLhcIf{ "GLO/Config/GRPLHCIF" }; + std::string mPathInputQcTask{ "" }; + std::string mCcdbUrl{ "" }; + int mNumOrbitsInTF{ 32 }; + std::string mMetaAnchorInput{ "CycleDurationNTF" }; + std::string mTimestampSource{ "trigger" }; + std::string mAsynchChannelLogic{ "standard" }; + std::string mTimestampMetaField{ "timestampTF" }; + int mLowTimeThreshold{ -192 }; + int mUpTimeThreshold{ -192 }; + + o2::quality_control::repository::DatabaseInterface* mDatabase = nullptr; + o2::ccdb::CcdbApi mCcdbApi; + o2::parameters::GRPLHCIFData mGRPLHCIFData{}; + Trigger mCurrTrigger = Trigger(o2::quality_control::postprocessing::TriggerType::No); + long long mTimestampAnchor{ -1 }; // timestamp for fetching entries from CCDB + long long mCurrSampleLengthTF{ 0 }; // incomming sample length in TF units + double mCurrSampleLengthSec{ 0. }; // incomming sample length in seconds + + void setTrigger(const o2::quality_control::postprocessing::Trigger& trg) { mCurrTrigger = trg; } + const o2::parameters::GRPLHCIFData getGRPLHCIFData() const { return mGRPLHCIFData; } + + template + auto getObject(const std::string& moName) const -> std::unique_ptr + { + auto mo = mDatabase->retrieveMO(mPathInputQcTask, moName, mCurrTrigger.timestamp, mCurrTrigger.activity); + ObjType* obj = mo ? dynamic_cast(mo->getObject()) : nullptr; + if (!obj) { + ILOG(Error) << "MO " << moName << " is NOT retrieved!" << ENDM; + } + return obj ? std::move(std::unique_ptr(dynamic_cast(obj->Clone(moName.c_str())))) : nullptr; + } + + void getMetadata() + { + const auto moMetadata = mDatabase->retrieveMO(mPathInputQcTask, mMetaAnchorInput, mCurrTrigger.timestamp, mCurrTrigger.activity); + const auto hMetadata = moMetadata ? dynamic_cast(moMetadata->getObject()) : nullptr; + // Getting sample length + if (hMetadata) { + mCurrSampleLengthTF = hMetadata->GetBinContent(1); + mCurrSampleLengthSec = mCurrSampleLengthTF * mNumOrbitsInTF * o2::constants::lhc::LHCOrbitNS * 1e-9; + mIsMetadataValid = true; + } else { + mCurrSampleLengthTF = 0; + mCurrSampleLengthSec = 0.; + mTimestampAnchor = -1; + ILOG(Error) << "Cannot get anchor hist " << mMetaAnchorInput << " with required metadata" << ENDM; + mIsMetadataValid = false; + } + if (mCurrSampleLengthTF) { + mIsNonEmptySample = true; + } else { + mIsNonEmptySample = false; + } + // Getting timestamp + mTimestampAnchor = -1; + if (mTimestampSource == "trigger") { + mTimestampAnchor = mCurrTrigger.timestamp; + } else if (mTimestampSource == "validUntil") { + mTimestampAnchor = mCurrTrigger.activity.mValidity.getMax(); + } else if (moMetadata && mTimestampSource == "metadata") { + const auto iterMetadata = moMetadata->getMetadataMap().find(mTimestampMetaField); + const bool isFound = iterMetadata != moMetadata->getMetadataMap().end(); + if (isFound) { + mTimestampAnchor = std::stoll(iterMetadata->second); + } else { + ILOG(Error) << "Cannot find timestamp metadata field " << mTimestampMetaField << " in hist " << mMetaAnchorInput << " . Setting timestamp to -1" << ENDM; + mTimestampAnchor = -1; + } + } else if (mTimestampSource == "current") { + mTimestampAnchor = -1; + } else { + ILOG(Error) << "Uknown timestamp source " << mTimestampSource << " . Setting timestamp to -1" << ENDM; + } + // Get BC pattern + std::map metadata; + std::map headers; + const auto ptrGRPLHCIFData = mCcdbApi.retrieveFromTFileAny(mPathGrpLhcIf, metadata, mTimestampAnchor, &headers); + if (ptrGRPLHCIFData) { + mGRPLHCIFData = *ptrGRPLHCIFData; + } else { + mGRPLHCIFData = o2::parameters::GRPLHCIFData(); + ILOG(Error) << "Cannot get GRPLHCIFData, setting default(zero) object"; + } + } + bool IsNonEmptySample() const { return mIsNonEmptySample; } + void update(Trigger trg, framework::ServiceRegistryRef serviceReg) + { + setTrigger(trg); + getMetadata(); + mIsFirstIter = false; + } + + private: + bool mIsMetadataValid{ false }; + bool mIsNonEmptySample{ false }; + bool mIsFirstIter{ true }; +}; + +} // namespace o2::quality_control_modules::fit + +#endif // QC_MODULE_FIT_POSTPROCHELPER_H diff --git a/Modules/FIT/Common/src/DigitSync.cxx b/Modules/FIT/Common/src/DigitSync.cxx new file mode 100644 index 0000000000..121d36785b --- /dev/null +++ b/Modules/FIT/Common/src/DigitSync.cxx @@ -0,0 +1,16 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitSync.cxx +/// \author Artur Furs afurs@cern.ch + +#include "FITCommon/DigitSync.h" diff --git a/Modules/FIT/Common/src/HelperCommon.cxx b/Modules/FIT/Common/src/HelperCommon.cxx new file mode 100644 index 0000000000..c036342e69 --- /dev/null +++ b/Modules/FIT/Common/src/HelperCommon.cxx @@ -0,0 +1,67 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HelperHist.cxx +/// \author Artur Furs afurs@cern.ch + +#include "FITCommon/HelperHist.h" + +namespace o2::quality_control_modules::fit +{ +namespace helper +{ + +std::map multiplyMaps(const std::vector, std::string>>& vecPreffixMapSuffix, bool useMapSizeAsMultFactor) +{ + + auto multiplyPairMaps = [](bool useMapSizeAsMultFactor, const std::tuple, std::string>& firstPreffixMapSuffix, + const std::tuple, std::string>& secondPreffixMapSuffix = {}) -> std::map { + std::map mapResult{}; + const auto& firstPreffix = std::get<0>(firstPreffixMapSuffix); + const auto& firstSuffix = std::get<2>(firstPreffixMapSuffix); + const auto& firstMap = std::get<1>(firstPreffixMapSuffix); + + const auto& secondPreffix = std::get<0>(secondPreffixMapSuffix); + const auto& secondSuffix = std::get<2>(secondPreffixMapSuffix); + const auto& secondMap = std::get<1>(secondPreffixMapSuffix); + + const unsigned int lastPosSecondMap = secondMap.size() > 0 ? (--secondMap.end())->first : 0; + const unsigned int multFactor = (useMapSizeAsMultFactor && secondMap.size() > 0) ? secondMap.size() : lastPosSecondMap + 1; + + for (const auto& entryFirst : firstMap) { + const std::string newEntrySecond_preffix = firstPreffix + entryFirst.second + firstSuffix; + const unsigned int startIndex = entryFirst.first * multFactor; + for (const auto& entrySecond : secondMap) { + const std::string newEntrySecond = newEntrySecond_preffix + secondPreffix + entrySecond.second + secondSuffix; + const unsigned int newEntryFirst = startIndex + entrySecond.first; + mapResult.insert({ newEntryFirst, newEntrySecond }); + } + if (secondMap.size() == 0) { + // Only add preffix and suffix + mapResult.insert({ startIndex, newEntrySecond_preffix }); + } + } + return mapResult; + }; + std::map mapResult{}; + if (vecPreffixMapSuffix.size() > 0) { + mapResult = multiplyPairMaps(useMapSizeAsMultFactor, vecPreffixMapSuffix[0]); + for (int iEntry = 1; iEntry < vecPreffixMapSuffix.size(); iEntry++) { + const std::string s1{ "" }, s2{ "" }; + mapResult = multiplyPairMaps(useMapSizeAsMultFactor, std::tie(s1, mapResult, s2), vecPreffixMapSuffix[iEntry]); + } + } + return mapResult; +} + +} // namespace helper +} // namespace o2::quality_control_modules::fit diff --git a/Modules/FIT/Common/src/HelperFIT.cxx b/Modules/FIT/Common/src/HelperFIT.cxx new file mode 100644 index 0000000000..3c0460814c --- /dev/null +++ b/Modules/FIT/Common/src/HelperFIT.cxx @@ -0,0 +1,20 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HelperFIT.cxx +/// \author Artur Furs afurs@cern.ch + +#include "FITCommon/HelperFIT.h" + +namespace o2::quality_control_modules::fit +{ +} // namespace o2::quality_control_modules::fit diff --git a/Modules/FIT/Common/src/HelperGraph.cxx b/Modules/FIT/Common/src/HelperGraph.cxx new file mode 100644 index 0000000000..2a0e639701 --- /dev/null +++ b/Modules/FIT/Common/src/HelperGraph.cxx @@ -0,0 +1,17 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HelperGraph.h +/// \author Jakub Muszynski jakub.milosz.muszynski@cern.ch +/// \brief Graph helper + +#include "FITCommon/HelperGraph.h" diff --git a/Modules/FIT/Common/src/HelperHist.cxx b/Modules/FIT/Common/src/HelperHist.cxx new file mode 100644 index 0000000000..3da5df8b0c --- /dev/null +++ b/Modules/FIT/Common/src/HelperHist.cxx @@ -0,0 +1,16 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HelperHist.cxx +/// \author Artur Furs afurs@cern.ch + +#include "FITCommon/HelperHist.h" diff --git a/Modules/FIT/Common/src/HelperLUT.cxx b/Modules/FIT/Common/src/HelperLUT.cxx new file mode 100644 index 0000000000..377c0687fb --- /dev/null +++ b/Modules/FIT/Common/src/HelperLUT.cxx @@ -0,0 +1,38 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HelperHist.cxx +/// \author Artur Furs afurs@cern.ch + +#include "FITCommon/HelperLUT.h" + +#include "DataFormatsFIT/LookUpTable.h" +#include "DataFormatsFDD/LookUpTable.h" +#include "DataFormatsFT0/LookUpTable.h" +#include "DataFormatsFV0/LookUpTable.h" +namespace o2::quality_control_modules::fit +{ +namespace helperLUT +{ +void obtainFromLUT(const o2::header::DataOrigin& det, Fee2ModuleName_t& mapFEE2ModuleName, long timestamp) +{ + if (det == o2::header::gDataOriginFDD) { + obtainAllParamsLUT(o2::fdd::SingleLUT::Instance(nullptr, timestamp), mapFEE2ModuleName); + } else if (det == o2::header::gDataOriginFT0) { + obtainAllParamsLUT(o2::ft0::SingleLUT::Instance(nullptr, timestamp), mapFEE2ModuleName); + } else if (det == o2::header::gDataOriginFV0) { + obtainAllParamsLUT(o2::fv0::SingleLUT::Instance(nullptr, timestamp), mapFEE2ModuleName); + } +} +} // namespace helperLUT + +} // namespace o2::quality_control_modules::fit diff --git a/Modules/FIT/FDD/CMakeLists.txt b/Modules/FIT/FDD/CMakeLists.txt new file mode 100644 index 0000000000..77ad250f18 --- /dev/null +++ b/Modules/FIT/FDD/CMakeLists.txt @@ -0,0 +1,66 @@ +# ---- Library ---- + +add_library(O2QcFDD) + +target_sources(O2QcFDD PRIVATE + src/DigitQcTaskLaser.cxx + src/DigitQcTask.cxx + src/GenericCheck.cxx + src/RecPointsQcTask.cxx + src/PostProcTask.cxx + src/CFDEffCheck.cxx + src/OutOfBunchCollCheck.cxx + src/TriggersSwVsTcmCheck.cxx + src/OutOfBunchCollFeeModulesCheck.cxx) + +target_include_directories( + O2QcFDD + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcFDD PUBLIC O2QualityControl O2QcCommon O2QcFITCommon) + +install(TARGETS O2QcFDD + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcFDD + HEADERS + include/FDD/DigitQcTaskLaser.h + include/FDD/DigitQcTask.h + include/FDD/GenericCheck.h + include/FDD/RecPointsQcTask.h + include/FDD/Helper.h + include/FDD/PostProcTask.h + include/FDD/CFDEffCheck.h + include/FDD/OutOfBunchCollCheck.h + include/FDD/TriggersSwVsTcmCheck.h + include/FDD/OutOfBunchCollFeeModulesCheck.h + LINKDEF include/FDD/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/FDD + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +install(FILES etc/fdd-digits.json + etc/fdd-post-processing.json + etc/fdd-recpoints.json + DESTINATION Modules/FIT/FDD/etc) + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcFDD.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcFDD Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() diff --git a/Modules/FIT/FDD/etc/fdd-digits.json b/Modules/FIT/FDD/etc/fdd-digits.json new file mode 100644 index 0000000000..9b6583526c --- /dev/null +++ b/Modules/FIT/FDD/etc/fdd-digits.json @@ -0,0 +1,55 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" + } + }, + "tasks": { + "Digits": { + "active": "true", + "className": "o2::quality_control_modules::fdd::DigitQcTask", + "moduleName": "QcFDD", + "detectorName": "FDD", + "cycleDurationSeconds": "60", + "resetAfterCycles": "1", + "dataSource": { + "type": "direct", + "query": "digits:FDD/DIGITSBC/0;channels:FDD/DIGITSCH/0" + }, + "taskParameters": { + "ChannelIDs": "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15", + "ChannelIDsAmpVsTime": "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15", + "trgThresholdTimeLow": "-250", + "trgThresholdTimeHigh": "250", + "trgModeSide": "A&C", + "trgModeThresholdVar": "Ampl", + "trgThresholdCenA": "40", + "trgThresholdSCenA": "20", + "trgThresholdCenC": "20", + "trgThresholdSCenC": "10", + "trgOrGate": "153", + "trgChargeLevelLow": "0", + "trgChargeLevelHigh": "4095" + } + } + } + } +} \ No newline at end of file diff --git a/Modules/FIT/FDD/etc/fdd-post-processing.json b/Modules/FIT/FDD/etc/fdd-post-processing.json new file mode 100644 index 0000000000..1a2e6252c5 --- /dev/null +++ b/Modules/FIT/FDD/etc/fdd-post-processing.json @@ -0,0 +1,377 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" + } + }, + "checks": { + "OutOfBunchCollCheck": { + "active": "true", + "className": "o2::quality_control_modules::fdd::OutOfBunchCollCheck", + "moduleName": "QcFDD", + "policy": "OnAny", + "detectorName": "FDD", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "OutOfBunchColl_BCvsTrg" + ] + } + ], + "checkParameters": { + "thresholdWarning": "2.5e-3", + "thresholdError": "5.0e-3" + } + }, + "OutOfBunchCollCheckFeeVtx": { + "active": "true", + "className": "o2::quality_control_modules::fdd::OutOfBunchCollFeeModulesCheck", + "moduleName": "QcFDD", + "policy": "OnAny", + "detectorName": "FDD", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "OutOfBunchColl_BCvsFeeModulesForVtxTrg" + ] + } + ], + "checkParameters": { + "#thresholdWarning": "TODO", + "#thresholdError": "TODO" + } + }, + "CFDinTimeGateCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FDD", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "TimeInWindowFraction" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.8", + "thresholdError": "0.6", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "TimeInWindowFraction", + "isInversedThresholds": "false", + "pathDeadChannelMap": "FDD/Calib/DeadChannelMap", + "binsToIgnore": "16,17,18,19" + } + }, + "CFDinADCgateCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FDD", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "CFD_efficiency" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.8", + "thresholdError": "0.6", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "CFD_efficiency", + "isInversedThresholds": "false", + "pathDeadChannelMap": "FDD/Calib/DeadChannelMap", + "binsToIgnore": "16,17,18,19" + } + }, + "TrgValidationCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FDD", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "TrgValidation" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.0005", + "thresholdError": "0.002", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "TrgValidation", + "isInversedThresholds": "true", + "pathDeadChannelMap": "", + "binsToIgnore": "" + } + }, + "ChargeSaturationCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FDD", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "AmpSaturation" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.8", + "thresholdError": "0.6", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "AmpSaturation", + "isInversedThresholds": "false", + "pathDeadChannelMap": "FDD/Calib/DeadChannelMap", + "binsToIgnore": "16,17,18,19" + } + } + }, + "aggregators": { + "GlobalQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "FDD", + "dataSource": [ + { + "type": "Check", + "name": "OutOfBunchCollCheck" + }, + { + "type": "Check", + "name": "CFDinTimeGateCheck" + }, + { + "type": "Check", + "name": "CFDinADCgateCheck" + }, + { + "type": "Check", + "name": "TrgValidationCheck" + }, + { + "type": "Check", + "name": "ChargeSaturationCheck" + } + ] + } + }, + "postprocessing": { + "Quality": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QcCommon", + "detectorName": "FDD", + "qualityGroups": [ + { + "name": "Global", + "title": "GLOBAL FDD QUALITY", + "path": "FDD/QO", + "ignoreQualitiesDetails": [ + "Null", + "Good", + "Medium", + "Bad" + ], + "inputObjects": [ + { + "name": "GlobalQuality/GlobalQuality", + "title": "FDD Quality", + "messageBad": "Inform the FIT on-call immediately", + "messageMedium": "Follow individual check instructions", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty, inform the FIT on-call immediately" + } + ] + }, + { + "name": "Details", + "title": "FDD DETAILS", + "path": "FDD/QO", + "ignoreQualitiesDetails": [], + "inputObjects": [ + { + "name": "OutOfBunchCollCheck", + "title": "Out of bunch collisions" + }, + { + "name": "CFDinTimeGateCheck", + "title": "CFD in time gate" + }, + { + "name": "CFDinADCgateCheck", + "title": "CFD in ADC gate" + }, + { + "name": "TrgValidationCheck", + "title": "Trigger validation" + }, + { + "name": "ChargeSaturationCheck", + "title": "Charge saturation" + } + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:FDD/QO/GlobalQuality/GlobalQuality" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "PostProc": { + "active": "true", + "className": "o2::quality_control_modules::fdd::PostProcTask", + "moduleName": "QcFDD", + "detectorName": "FDD", + "custom": { + "numOrbitsInTF": "32", + "cycleDurationMoName": "CycleDurationNTF", + "timestampSourceLhcIf": "metadata", + "pathDigitQcTask": "FDD/MO/Digits" + }, + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:FDD/MO/Digits/TriggersCorrelation" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "Trending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcFDD", + "detectorName": "FDD", + "dataSources": [ + { + "type": "repository", + "path": "FDD/MO/Digits", + "names": [ + "Amp_channel0", + "Time_channel0", + "Amp_channel1", + "Time_channel1", + "Amp_channel2", + "Time_channel2", + "Amp_channel3", + "Time_channel3", + "Amp_channel4", + "Time_channel4", + "Amp_channel5", + "Time_channel5", + "Amp_channel6", + "Time_channel6", + "Amp_channel7", + "Time_channel7", + "Amp_channel8", + "Time_channel8", + "Amp_channel9", + "Time_channel9", + "Amp_channel10", + "Time_channel10", + "Amp_channel11", + "Time_channel11", + "Amp_channel12", + "Time_channel12", + "Amp_channel13", + "Time_channel13", + "Amp_channel14", + "Time_channel14", + "Amp_channel15", + "Time_channel15", + "AverageTimeA", + "AverageTimeC" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "trend_mean_amp0", + "title": "Mean trend of the amplitude, channel0", + "varexp": "Amp_channel0.mean:time", + "selection": "", + "option": "*L" + }, + { + "name": "trend_mean_amp1", + "title": "Mean trend of the amplitude, channel1", + "varexp": "Amp_channel1.mean:time", + "selection": "", + "option": "*L" + }, + { + "name": "trend_mean_amp2", + "title": "Mean trend of the amplitude, channel2", + "varexp": "Amp_channel2.mean:time", + "selection": "", + "option": "*L" + }, + { + "name": "trend_time", + "title": "(TOA+TOC)/2 from TCM [ps]", + "varexp": "(AverageTimeA.mean+AverageTimeC.mean)/2 * 13.02:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:FDD/MO/Digits/TriggersCorrelation" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} \ No newline at end of file diff --git a/Modules/FIT/FDD/etc/fdd-recpoints.json b/Modules/FIT/FDD/etc/fdd-recpoints.json new file mode 100644 index 0000000000..7852f668a3 --- /dev/null +++ b/Modules/FIT/FDD/etc/fdd-recpoints.json @@ -0,0 +1,40 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" + } + }, + "tasks": { + "RecPoints": { + "active": "true", + "className": "o2::quality_control_modules::fdd::RecPointsQcTask", + "moduleName": "QcFDD", + "detectorName": "FDD", + "cycleDurationSeconds": "60", + "resetAfterCycles": "1", + "dataSource": { + "type": "direct", + "query": "recpoints:FDD/RECPOINTS/0;channels:FDD/RECCHDATA/0" + } + } + } + } +} \ No newline at end of file diff --git a/Modules/FIT/FDD/include/FDD/CFDEffCheck.h b/Modules/FIT/FDD/include/FDD/CFDEffCheck.h new file mode 100644 index 0000000000..de84e59dd1 --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/CFDEffCheck.h @@ -0,0 +1,81 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CFDEffCheck.h +/// \author Sebastian Bysiak sbysiak@cern.ch +/// LATEST modification for FDD on 25.04.2023 (akhuntia@cern.ch) + +#ifndef QC_MODULE_FDD_FDDCFDEFFCHECK_H +#define QC_MODULE_FDD_FDDCFDEFFCHECK_H + +#include +#include + +#include "QualityControl/CheckInterface.h" + +#include "FDDBase/Geometry.h" +#include "FDDBase/Constants.h" +#include "DataFormatsFIT/DeadChannelMap.h" + +namespace o2::quality_control_modules::fdd +{ + +/// \brief checks if CFD efficiency is below threshold +/// \author Sebastian Bysiak sbysiak@cern.ch +class CFDEffCheck : public o2::quality_control::checker::CheckInterface +{ + public: + CFDEffCheck() = default; + ~CFDEffCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(CFDEffCheck, 2); + + private: + template ::value || + std::is_same::value || (std::is_integral::value && !std::is_same::value)>::type> + auto parseParameters(const std::string& param, const std::string& del) + { + std::regex reg(del); + std::sregex_token_iterator first{ param.begin(), param.end(), reg, -1 }, last; + std::vector vecResult; + if (std::find_if(param.begin(), param.end(), ::isdigit) == param.end()) { + return vecResult; + } + for (auto it = first; it != last; it++) { + if constexpr (std::is_integral::value && !std::is_same::value) { + vecResult.push_back(std::stoi(*it)); + } else if constexpr (std::is_floating_point::value) { + vecResult.push_back(std::stod(*it)); + } else if constexpr (std::is_same::value) { + vecResult.push_back(*it); + } + } + return vecResult; + } + + constexpr static std::size_t sNCHANNELS = o2::fdd::Nchannels; + o2::fit::DeadChannelMap* mDeadChannelMap; + std::string mDeadChannelMapStr; + std::string mPathDeadChannelMap; + float mThreshWarning; + float mThreshError; + int mNumWarnings; + int mNumErrors; +}; + +} // namespace o2::quality_control_modules::fdd + +#endif // QC_MODULE_FDD_FDDCFDEFFCHECK_H diff --git a/Modules/FIT/FDD/include/FDD/DigitQcTask.h b/Modules/FIT/FDD/include/FDD/DigitQcTask.h new file mode 100644 index 0000000000..a44db62bca --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/DigitQcTask.h @@ -0,0 +1,251 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTask.h +/// \author Artur Furs afurs@cern.ch +/// LATEST modification for FDD on 25.04.2023 (akhuntia@cern.ch) + +#ifndef QC_MODULE_FDD_FDDDIGITQCTASK_H +#define QC_MODULE_FDD_FDDDIGITQCTASK_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TH1.h" +#include "TH2.h" +#include "TList.h" +#include "FDD/Helper.h" +#include "Rtypes.h" + +#include "CommonConstants/LHCConstants.h" + +#include +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsFDD/Digit.h" +#include "DataFormatsFDD/ChannelData.h" +#include "QualityControl/TaskInterface.h" +#include "FDDBase/Constants.h" +#include "FITCommon/DetectorFIT.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::fdd +{ +namespace ch_data = helper::channel_data; + +/// \brief Quality Control DPL Task for FDD's digit visualization +/// \author Artur Furs afurs@cern.ch +class DigitQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + DigitQcTask() : mHashedBitBinPos(fillHashedBitBinPos()), mHashedPairBitBinPos(fillHashedPairBitBinPos()){}; + /// Destructor + ~DigitQcTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + constexpr static std::size_t sNCHANNELS_PM = 20; + constexpr static std::size_t sNCHANNELS_Physics = 16; + constexpr static std::size_t sNCHANNELS_A = 8; + constexpr static std::size_t sNCHANNELS_C = 8; + constexpr static std::size_t sOrbitsPerTF = 256; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + constexpr static float sCFDChannel2NS = o2::fdd::timePerTDC; // CFD channel width in ns + constexpr static int cSideChOffSet = 8; + using Detector_t = o2::quality_control_modules::fit::detectorFIT::DetectorFDD; + + private: + // three ways of computing cycle duration: + // 1) number of time frames + // 2) time in ns from InteractionRecord: total range (totalMax - totalMin) + // 3) time in ns from InteractionRecord: sum of each TF duration + // later on choose the best and remove others + double mTimeMinNS = 0.; + double mTimeMaxNS = 0.; + double mTimeCurNS = 0.; + int mTfCounter = 0; + double mTimeSum = 0.; + long mTFcreationTime = 0; + int mMinTimeGate = -192; + int mMaxTimeGate = 192; + + template ::value || + std::is_same::value || (std::is_integral::value && !std::is_same::value)>::type> + auto parseParameters(const std::string& param, const std::string& del) + { + std::regex reg(del); + std::sregex_token_iterator first{ param.begin(), param.end(), reg, -1 }, last; + std::vector vecResult; + for (auto it = first; it != last; it++) { + if constexpr (std::is_integral::value && !std::is_same::value) { + vecResult.push_back(std::stoi(*it)); + } else if constexpr (std::is_floating_point::value) { + vecResult.push_back(std::stod(*it)); + } else if constexpr (std::is_same::value) { + vecResult.push_back(*it); + } + } + return vecResult; + } + + void rebinFromConfig(); + unsigned int getModeParameter(std::string, unsigned int, std::map); + int getNumericalParameter(std::string, int); + bool chIsVertexEvent(const o2::fdd::ChannelData, bool simpleCheck = false); + static int fpgaDivision(int numerator, int denominator); + + TList* mListHistGarbage; + std::set mSetAllowedChIDs; + std::set mSetAllowedChIDsAmpVsTime; + std::array mStateLastIR2Ch; + std::array mChID2PMhash; // map chID->hashed PM value + uint8_t mTCMhash; // hash value for TCM, and bin position in hist + std::map mMapPMhash2isAside; + + typename Detector_t::TrgMap_t mMapPMbits = Detector_t::sMapPMbits; + typename Detector_t::TrgMap_t mMapTechTrgBits = Detector_t::sMapTechTrgBits; + typename Detector_t::TrgMap_t mMapTrgBits = Detector_t::sMapTrgBits; + + std::unique_ptr mHistNumADC; + std::unique_ptr mHistNumCFD; + std::array ChVertexArray; + + std::map mMapTrgSoftware; + enum TrgModeSide { kAplusC, + kAandC, + kA, + kC + }; + enum TrgModeFDD { kNormal, + kCoincidence + }; + enum TrgModeThresholdVar { kAmpl, + kNchannels + }; + enum TrgComparisonResult { kSWonly, + kTCMonly, + kNone, + kBoth + }; + // trigger parameters: + // - modes + unsigned int mTrgModeThresholdVar; + unsigned int mTrgModeFDD; + unsigned int mTrgModeSide; + // - time window for vertex trigger + int mTrgThresholdTimeLow; + int mTrgThresholdTimeHigh; + // - parameters for (Semi)Central triggers + // same parameters re-used for both Ampl and Nchannels thresholds + int mTrgThresholdCenA; + int mTrgThresholdCenC; + int mTrgThresholdSCenA; + int mTrgThresholdSCenC; + int mTrgChargeLevelLow; + int mTrgChargeLevelHigh; + int mTrgOrGate; + int mBinMinADCSaturationCheck; + int mBinMaxADCSaturationCheck; + // Timestamp + std::string mMetaAnchorOutput{}; + std::string mTimestampMetaField{}; + + // Object which will be published + std::unique_ptr mHist2CorrTCMchAndPMch; + std::map mMapHistAmp1DCoincidence; + std::map mMapPmModuleBcOrbit; + std::unique_ptr mHistAmp2Ch; + std::unique_ptr mHistTime2Ch; + std::unique_ptr mHistEventDensity2Ch; + std::unique_ptr mHistChDataBits; + std::unique_ptr mHistOrbit2BC; + std::unique_ptr mHistBC; + std::unique_ptr mHistNchA; + std::unique_ptr mHistNchC; + std::unique_ptr mHistSumAmpA; + std::unique_ptr mHistSumAmpC; + std::unique_ptr mHistAverageTimeA; + std::unique_ptr mHistAverageTimeC; + std::unique_ptr mHistChannelID; + std::unique_ptr mHistCFDEff; + std::unique_ptr mHistGateTimeRatio2Ch; + std::unique_ptr mHistSaturationFraction; + std::unique_ptr mHistTimeSum2Diff; + std::unique_ptr mHistTriggersCorrelation; + std::unique_ptr mHistCycleDuration; + std::unique_ptr mHistCycleDurationNTF; + std::unique_ptr mHistCycleDurationRange; + std::map mMapHistAmpVsTime; + std::unique_ptr mHistBCvsTrg; + std::unique_ptr mHistBCvsFEEmodules; + std::unique_ptr mHistOrbitVsTrg; + std::unique_ptr mHistOrbitVsFEEmodules; + std::unique_ptr mHistPmTcmNchA; + std::unique_ptr mHistPmTcmSumAmpA; + std::unique_ptr mHistPmTcmAverageTimeA; + std::unique_ptr mHistPmTcmNchC; + std::unique_ptr mHistPmTcmSumAmpC; + std::unique_ptr mHistPmTcmAverageTimeC; + std::unique_ptr mHistTriggersSw; + std::unique_ptr mHistTriggersSoftwareVsTCM; + std::unique_ptr mHistBcVsFeeForVtxTrg; + // Hashed maps + static const size_t mapSize = 256; + const std::array, mapSize> mHashedBitBinPos; // map with bit position for 1 byte trg signal, for 1 Dim hists; + const std::array>, mapSize> mHashedPairBitBinPos; // map with paired bit position for 1 byte trg signal, for 1 Dim hists; + static std::array, mapSize> fillHashedBitBinPos() + { + std::array, mapSize> hashedBitBinPos{}; + for (int iByteValue = 0; iByteValue < hashedBitBinPos.size(); iByteValue++) { + auto& vec = hashedBitBinPos[iByteValue]; + for (int iBit = 0; iBit < 8; iBit++) { + if (iByteValue & (1 << iBit)) { + vec.push_back(iBit); + } + } + } + return hashedBitBinPos; + } + static std::array>, mapSize> fillHashedPairBitBinPos() + { + const std::array, mapSize> hashedBitBinPos = fillHashedBitBinPos(); + std::array>, mapSize> hashedPairBitBinPos{}; + for (int iByteValue = 0; iByteValue < hashedBitBinPos.size(); iByteValue++) { + const auto& vecBits = hashedBitBinPos[iByteValue]; + auto& vecPairBits = hashedPairBitBinPos[iByteValue]; + for (int iBitFirst = 0; iBitFirst < vecBits.size(); iBitFirst++) { + for (int iBitSecond = iBitFirst; iBitSecond < vecBits.size(); iBitSecond++) { + vecPairBits.push_back({ static_cast(vecBits[iBitFirst]), static_cast(vecBits[iBitSecond]) }); + } + } + } + return hashedPairBitBinPos; + } +}; + +} // namespace o2::quality_control_modules::fdd + +#endif // QC_MODULE_FDD_FDDDIGITQCTASK_H diff --git a/Modules/FIT/FDD/include/FDD/DigitQcTaskLaser.h b/Modules/FIT/FDD/include/FDD/DigitQcTaskLaser.h new file mode 100644 index 0000000000..ddb5e73c4f --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/DigitQcTaskLaser.h @@ -0,0 +1,123 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTaskLaser.h +/// \author Artur Furs afurs@cern.ch +/// modified by A Khuntia for FDD akhuntia@cern.ch + +#ifndef QC_MODULE_FDD_FDDDIGITQCTASKLASER_H +#define QC_MODULE_FDD_FDDDIGITQCTASKLASER_H + +#include "CommonConstants/LHCConstants.h" + +#include + +#include "QualityControl/QcInfoLogger.h" +#include "FDDBase/Constants.h" +#include "DataFormatsFDD/Digit.h" +#include "DataFormatsFDD/ChannelData.h" +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include +#include +#include +#include +#include "TH1.h" +#include "TH2.h" +#include "TList.h" +#include "Rtypes.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::fdd +{ + +class DigitQcTaskLaser final : public TaskInterface +{ + public: + /// \brief Constructor + DigitQcTaskLaser() = default; + /// Destructor + ~DigitQcTaskLaser() override; + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + constexpr static std::size_t sNCHANNELS_PM = 19; + constexpr static std::size_t sOrbitsPerTF = 256; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + + private: + // three ways of computing cycle duration: + // 1) number of time frames + // 2) time in ns from InteractionRecord: total range (totalMax - totalMin) + // 3) time in ns from InteractionRecord: sum of each TF duration + // later on choose the best and remove others + double mTimeMinNS = 0.; + double mTimeMaxNS = 0.; + double mTimeCurNS = 0.; + int mTfCounter = 0; + double mTimeSum = 0.; + + template ::value || + std::is_same::value || (std::is_integral::value && !std::is_same::value)>::type> + auto parseParameters(const std::string& param, const std::string& del) + { + std::regex reg(del); + std::sregex_token_iterator first{ param.begin(), param.end(), reg, -1 }, last; + std::vector vecResult; + for (auto it = first; it != last; it++) { + if constexpr (std::is_integral::value && !std::is_same::value) { + vecResult.push_back(std::stoi(*it)); + } else if constexpr (std::is_floating_point::value) { + vecResult.push_back(std::stod(*it)); + } else if constexpr (std::is_same::value) { + vecResult.push_back(*it); + } + } + return vecResult; + } + + void rebinFromConfig(); + + TList* mListHistGarbage; + std::set mSetAllowedChIDs; + std::set mSetRefPMTChIDs; + std::array mStateLastIR2Ch; + std::map mMapDigitTrgNames; + std::map mMapChTrgNames; + std::map> mMapPmModuleChannels; // PM name to its channels + std::unique_ptr mHistNumADC; + std::unique_ptr mHistNumCFD; + + // Objects which will be published + std::unique_ptr mHistAmp2Ch; + std::unique_ptr mHistTime2Ch; + std::unique_ptr mHistChDataBits; + std::unique_ptr mHistOrbit2BC; + std::unique_ptr mHistBC; + std::unique_ptr mHistCFDEff; + std::unique_ptr mHistCycleDuration; + std::map mMapHistAmpVsBC; + std::map mMapPmModuleBcOrbit; +}; + +} // namespace o2::quality_control_modules::fdd + +#endif // QC_MODULE_FDD_FDDDigitQcTaskLaser_H diff --git a/Modules/FIT/FDD/include/FDD/GenericCheck.h b/Modules/FIT/FDD/include/FDD/GenericCheck.h new file mode 100644 index 0000000000..c60f9e638e --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/GenericCheck.h @@ -0,0 +1,157 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GenericCheck.h +/// \author Sebastian Bysiak +/// LATEST modification for FDD on 25.04.2023 (akhuntia@cern.ch) + +#ifndef QC_MODULE_FDD_FDDGENERICCHECK_H +#define QC_MODULE_FDD_FDDGENERICCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include "DataFormatsFIT/DeadChannelMap.h" +#include +#include + +namespace o2::quality_control_modules::fdd +{ +/// \brief helper class to store acceptable limits for given quantity +/// \author Sebastian Bysiak +class SingleCheck +{ + public: + SingleCheck() = default; + ~SingleCheck() = default; + + SingleCheck(std::string name, float thresholdWarning, float thresholdError, bool shouldBeLower, bool isActive) + { + mCheckName = name; + mThresholdWarning = thresholdWarning; + mThresholdError = thresholdError; + mShouldBeLower = shouldBeLower; + mIsActive = isActive; + mBinNumberX = 0; + }; + bool isActive() { return mIsActive; }; + + void doCheck(Quality& result, float checkedValue) + { + if (!mIsActive) + return; + + std::string log = Form("%s : comparing value = %f with thresholds = %f, %f", mCheckName.c_str(), checkedValue, mThresholdWarning, mThresholdError); + std::string reason; + if (mShouldBeLower) { + if (checkedValue > mThresholdError) { + if (result.isBetterThan(Quality::Bad)) + result.set(Quality::Bad); + reason = Form("%.3f > %.3f (%s error limit)", checkedValue, mThresholdError, mCheckName.c_str()); + log += "-> Bad"; + } else if (checkedValue > mThresholdWarning) { + if (result.isBetterThan(Quality::Medium)) + result.set(Quality::Medium); + reason = Form("%.3f > %.3f (%s warning limit)", checkedValue, mThresholdWarning, mCheckName.c_str()); + log += "-> Medium"; + } else { + log += "-> OK"; + } + } else { + if (checkedValue < mThresholdError) { + if (result.isBetterThan(Quality::Bad)) + result.set(Quality::Bad); + reason = Form("%.3f < %.3f (%s error limit)", checkedValue, mThresholdError, mCheckName.c_str()); + log += "-> Bad"; + } else if (checkedValue < mThresholdWarning) { + if (result.isBetterThan(Quality::Medium)) + result.set(Quality::Medium); + reason = Form("%.3f < %.3f (%s warning limit)", checkedValue, mThresholdWarning, mCheckName.c_str()); + log += "-> Medium"; + } else { + log += "-> OK"; + } + } + if (reason.length()) { + if (mBinNumberX) { + reason += Form(" for channel %d", mBinNumberX); + } + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), reason); + } + + ILOG(Debug, Support) << log << ENDM; + } + float getThresholdWarning() + { + return mThresholdWarning; + } + float getThresholdError() + { + return mThresholdError; + } + + public: + int mBinNumberX; + + private: + std::string mCheckName; + float mThresholdWarning; + float mThresholdError; + bool mShouldBeLower; + bool mIsActive; +}; + +/// \brief checks multiple basic hist statistics +/// \author Sebastian Bysiak +class GenericCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + GenericCheck() = default; + /// Destructor + ~GenericCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(GenericCheck, 2); + + private: + SingleCheck getCheckFromConfig(std::string); + SingleCheck mCheckMinThresholdY; + SingleCheck mCheckMaxThresholdY; + SingleCheck mCheckMaxOverflowIntegralRatio; + + SingleCheck mCheckMinMeanX; + SingleCheck mCheckMaxMeanX; + SingleCheck mCheckMaxStddevX; + + SingleCheck mCheckMinMeanY; + SingleCheck mCheckMaxMeanY; + SingleCheck mCheckMaxStddevY; + + SingleCheck mCheckMinGraphLastPoint; + SingleCheck mCheckMaxGraphLastPoint; + + std::array mPositionMsgBox; + std::string mNameObjOnCanvas; + + constexpr static std::size_t sNCHANNELSPhy = 16; + o2::fit::DeadChannelMap* mDeadChannelMap; + std::string mDeadChannelMapStr; + std::string mPathDeadChannelMap; +}; + +} // namespace o2::quality_control_modules::fdd + +#endif // QC_MODULE_FDD_FDDGENERICCHECK_H diff --git a/Modules/FIT/FDD/include/FDD/Helper.h b/Modules/FIT/FDD/include/FDD/Helper.h new file mode 100644 index 0000000000..e456c4481c --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/Helper.h @@ -0,0 +1,124 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Helper.h +/// \author Artur Furs afurs@cern.ch +/// + +// \brief Temporary helper +/// \author Artur Furs afurs@cern.ch + +#include +#include +#ifndef QC_MODULE_FDD_HELPER_H +#define QC_MODULE_FDD_HELPER_H +namespace o2::quality_control_modules::fdd +{ +namespace helper +{ //helper for soft fixes in case of DataFormatsFDD's modifications +namespace channel_data +{ //ChannelData section +//ChannelID section + +//Current field +template +auto getChId(const T& digit) -> std::enable_if_t().mPMNumber), uint8_t>::value, const decltype(std::declval().mPMNumber)&> +{ + return digit.mPMNumber; +} +//In case of changes in field type +template +auto getChId(const T& digit) -> std::enable_if_t().mPMNumber), uint8_t>::value, const decltype(std::declval().mPMNumber)&> +{ + return digit.mPMNumber; +} +//Target changes +template +auto getChId(const T& digit) -> std::enable_if_t().chId), uint8_t>::value, const decltype(std::declval().chId)&> +{ + return digit.chId; +} +//Target changes in case of different target field type +template +auto getChId(const T& digit) -> std::enable_if_t().chId), uint8_t>::value, const decltype(std::declval().chId)&> +{ + return digit.chId; +} +//Time section +template +auto getTime(const T& digit) -> std::enable_if_t().mTime), int16_t>::value, const decltype(std::declval().mTime)&> +{ + return digit.mTime; +} +template +auto getTime(const T& digit) -> std::enable_if_t().mTime), int16_t>::value, const decltype(std::declval().mTime)&> +{ + return digit.mTime; +} +template +auto getTime(const T& digit) -> std::enable_if_t().time), int16_t>::value, const decltype(std::declval().time)&> +{ + return digit.time; +} +template +auto getTime(const T& digit) -> std::enable_if_t().time), int16_t>::value, const decltype(std::declval().time)&> +{ + return digit.time; +} +//Amplitude section +template +auto getCharge(const T& digit) -> std::enable_if_t().mChargeADC), int16_t>::value, const decltype(std::declval().mChargeADC)&> +{ + return digit.mChargeADC; +} +template +auto getCharge(const T& digit) -> std::enable_if_t().mChargeADC), int16_t>::value, const decltype(std::declval().mChargeADC)&> +{ + return digit.mChargeADC; +} +template +auto getCharge(const T& digit) -> std::enable_if_t().charge), uint16_t>::value, const decltype(std::declval().charge)&> +{ + return digit.charge; +} +template +auto getCharge(const T& digit) -> std::enable_if_t().charge), uint16_t>::value, const decltype(std::declval().charge)&> +{ + return digit.charge; +} + +//PM Bits section +template +auto getPMbits(const T& digit) -> std::enable_if_t().mFEEBits), int16_t>::value, const decltype(std::declval().mFEEBits)&> +{ + return digit.mFEEBits; +} +template +auto getPMbits(const T& digit) -> std::enable_if_t().mFEEBits), int16_t>::value, const decltype(std::declval().mFEEBits)&> +{ + return digit.mFEEBits; +} +template +auto getPMbits(const T& digit) -> std::enable_if_t().pmBits), int16_t>::value, const decltype(std::declval().pmBits)&> +{ + return digit.pmBits; +} +template +auto getPMbits(const T& digit) -> std::enable_if_t().pmBits), int16_t>::value, const decltype(std::declval().pmBits)&> +{ + return digit.pmBits; +} +} //namespace channel_data +} //namespace helper +} //namespace o2::quality_control_modules::fdd + +#endif // QC_MODULE_FDD_HELPER_H diff --git a/Modules/FIT/FDD/include/FDD/LinkDef.h b/Modules/FIT/FDD/include/FDD/LinkDef.h new file mode 100644 index 0000000000..7c36fe4199 --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/LinkDef.h @@ -0,0 +1,15 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::fdd::DigitQcTask+; +#pragma link C++ class o2::quality_control_modules::fdd::DigitQcTaskLaser + ; +#pragma link C++ class o2::quality_control_modules::fdd::GenericCheck + ; +#pragma link C++ class o2::quality_control_modules::fdd::RecPointsQcTask + ; +#pragma link C++ class o2::quality_control_modules::fdd::PostProcTask + ; +#pragma link C++ class o2::quality_control_modules::fdd::CFDEffCheck + ; +#pragma link C++ class o2::quality_control_modules::fdd::OutOfBunchCollCheck + ; +#pragma link C++ class o2::quality_control_modules::fdd::TriggersSwVsTcmCheck + ; +#pragma link C++ class o2::quality_control_modules::fdd::OutOfBunchCollFeeModulesCheck + ; +#endif diff --git a/Modules/FIT/FDD/include/FDD/OutOfBunchCollCheck.h b/Modules/FIT/FDD/include/FDD/OutOfBunchCollCheck.h new file mode 100644 index 0000000000..6550ee45c1 --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/OutOfBunchCollCheck.h @@ -0,0 +1,56 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file OutOfBunchCollCheck.h +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#ifndef QC_MODULE_FDD_FDDOUTOFBUNCHCOLLCHECK_H +#define QC_MODULE_FDD_FDDOUTOFBUNCHCOLLCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "CommonConstants/LHCConstants.h" + +namespace o2::quality_control_modules::fdd +{ + +/// \brief Checks what fraction of collisions is out of bunch +/// \author Sebastian Bysiak sbysiak@cern.ch +class OutOfBunchCollCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + OutOfBunchCollCheck() = default; + /// Destructor + ~OutOfBunchCollCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + + ClassDefOverride(OutOfBunchCollCheck, 2); + + private: + float mFractionOutOfBunchColl; + int mNumNonEmptyBins; + float mThreshWarning; + float mThreshError; + std::string mTrgName; + int mBinPos; + bool mEnableMessage{ true }; +}; + +} // namespace o2::quality_control_modules::fdd + +#endif // QC_MODULE_FDD_FDDOUTOFBUNCHCOLLCHECK_H diff --git a/Modules/FIT/FDD/include/FDD/OutOfBunchCollFeeModulesCheck.h b/Modules/FIT/FDD/include/FDD/OutOfBunchCollFeeModulesCheck.h new file mode 100644 index 0000000000..8a66d1bcba --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/OutOfBunchCollFeeModulesCheck.h @@ -0,0 +1,45 @@ +// Copyright 2023 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_FDD_OUTOFBUNCHCOLLFEEMODULESCHECK_H +#define QC_MODULE_FDD_OUTOFBUNCHCOLLFEEMODULESCHECK_H + +#include "QualityControl/CheckInterface.h" + +#include "FDDBase/Constants.h" +#include "CommonConstants/LHCConstants.h" + +#include + +namespace o2::quality_control_modules::fdd +{ + +class OutOfBunchCollFeeModulesCheck : public o2::quality_control::checker::CheckInterface +{ + public: + OutOfBunchCollFeeModulesCheck() = default; + ~OutOfBunchCollFeeModulesCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(OutOfBunchCollFeeModulesCheck, 1); + + private: + float mThreshWarning; + float mThreshError; + float mFractionOutOfBunchColl = 0; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; +}; + +} // namespace o2::quality_control_modules::fdd + +#endif // QC_MODULE_FDD_OUTOFBUNCHCOLLFEEMODULESCHECK_H \ No newline at end of file diff --git a/Modules/FIT/FDD/include/FDD/PostProcTask.h b/Modules/FIT/FDD/include/FDD/PostProcTask.h new file mode 100644 index 0000000000..8941a50f5f --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/PostProcTask.h @@ -0,0 +1,120 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcTask.h +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#ifndef QC_MODULE_FDD_POSTPROCTASK_H +#define QC_MODULE_FDD_POSTPROCTASK_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/DatabaseInterface.h" +#include "FITCommon/DetectorFIT.h" + +#include "CCDB/CcdbApi.h" +#include "CommonConstants/LHCConstants.h" +#include "DataFormatsFDD/LookUpTable.h" + +#include "FDDBase/Constants.h" +#include "DataFormatsFDD/ChannelData.h" +#include "DataFormatsFDD/Digit.h" + +#include +#include +#include + +class TH1F; +class TCanvas; +class TLegend; +class TProfile; + +namespace o2::quality_control_modules::fdd +{ + +/// \brief Basic Postprocessing Task for FDD, computes among others the trigger rates +/// \author Sebastian Bysiak sbysiak@cern.ch +class PostProcTask final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + PostProcTask() = default; + ~PostProcTask() override; + void configure(const boost::property_tree::ptree&) override; + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + constexpr static std::size_t sNCHANNELS_PM = 20; + using Detector_t = o2::quality_control_modules::fit::detectorFIT::DetectorFDD; + + private: + std::string mPathGrpLhcIf; + std::string mPathDigitQcTask; + std::string mCycleDurationMoName; + std::string mCcdbUrl; + std::string mTimestampSourceLhcIf; + int mNumOrbitsInTF; + const unsigned int mNumTriggers = 5; + + typename Detector_t::TrgMap_t mMapPMbits = Detector_t::sMapPMbits; + typename Detector_t::TrgMap_t mMapTechTrgBits = Detector_t::sMapTechTrgBits; + typename Detector_t::TrgMap_t mMapTrgBits = Detector_t::sMapTrgBits; + o2::quality_control::repository::DatabaseInterface* mDatabase = nullptr; + o2::ccdb::CcdbApi mCcdbApi; + + std::unique_ptr mRateOrA; + std::unique_ptr mRateOrC; + std::unique_ptr mRateVertex; + std::unique_ptr mRateCentral; + std::unique_ptr mRateSemiCentral; + std::unique_ptr mHistChDataNegBits; + std::unique_ptr mHistTriggers; + + std::unique_ptr mHistTimeInWindow; + std::unique_ptr mHistCFDEff; + std::unique_ptr mHistTrgValidation; + std::unique_ptr mHistAmpSaturation; + + std::unique_ptr mRatesCanv; + TProfile* mAmpl = nullptr; + TProfile* mTime = nullptr; + + // if storage size matters it can be replaced with TH1 + // and TH2 can be created based on it on the fly, but only TH1 would be stored + std::unique_ptr mHistBcPattern; + std::unique_ptr mHistBcPatternFee; + std::unique_ptr mHistBcTrgOutOfBunchColl; + std::unique_ptr mHistBcFeeOutOfBunchCollForVtxTrg; + std::map mMapTrgHistBC; + + std::array mChID2PMhash; // map chID->hashed PM value + std::map mMapFEE2hash; + int mLowTimeThreshold{ -192 }; + int mUpTimeThreshold{ 192 }; + double mLowAmpSat; + double mUpAmpSat; + std::string mTimestampMetaField{ "timestampTF" }; + // + void setTimestampToMOs(long long timestamp); + // TO REMOVE + std::vector mVecChannelIDs{}; + std::vector mVecHistsToDecompose{}; + using HistDecomposed_t = TH1D; + using MapHistsDecomposed_t = std::map>>; + MapHistsDecomposed_t mMapHistsToDecompose{}; + void decomposeHists(quality_control::postprocessing::Trigger trg); +}; + +} // namespace o2::quality_control_modules::fdd + +#endif // QC_MODULE_FDD_POSTPROCTASK_H diff --git a/Modules/FIT/FDD/include/FDD/RecPointsQcTask.h b/Modules/FIT/FDD/include/FDD/RecPointsQcTask.h new file mode 100644 index 0000000000..47599760e1 --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/RecPointsQcTask.h @@ -0,0 +1,127 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RecPointsQcTask.h +/// \author Artur Furs afurs@cern.ch +/// developed by A Khuntia for FDD akhuntia@cern.ch + +#ifndef QC_MODULE_FDD_FDDRECOQCTASK_H +#define QC_MODULE_FDD_FDDRECOQCTASK_H + +#include "CommonConstants/LHCConstants.h" + +#include + +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::fdd +{ + +class RecPointsQcTask final : public TaskInterface +{ + static constexpr int NCHANNELS = 19; + + public: + /// \brief Constructor + RecPointsQcTask() = default; + /// Destructor + ~RecPointsQcTask() override; + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + constexpr static std::size_t sOrbitsPerTF = 256; + constexpr static uint8_t sDataIsValidBitPos = 7; + constexpr static std::size_t sNCHANNELS_PM = 19; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + + private: + // three ways of computing cycle duration: + // 1) number of time frames + // 2) time in ns from InteractionRecord: total range (totalMax - totalMin) + // 3) time in ns from InteractionRecord: sum of each TF duration + // later on choose the best and remove others + double mTimeMinNS = 0.; + double mTimeMaxNS = 0.; + double mTimeCurNS = 0.; + int mTfCounter = 0; + double mTimeSum = 0.; + const float mCFDChannel2NS = 0.01302; // CFD channel width in ns + + template ::value || + std::is_same::value || (std::is_integral::value && !std::is_same::value)>::type> + auto parseParameters(const std::string& param, const std::string& del) + { + std::regex reg(del); + std::sregex_token_iterator first{ param.begin(), param.end(), reg, -1 }, last; + std::vector vecResult; + for (auto it = first; it != last; it++) { + if constexpr (std::is_integral::value && !std::is_same::value) { + vecResult.push_back(std::stoi(*it)); + } else if constexpr (std::is_floating_point::value) { + vecResult.push_back(std::stod(*it)); + } else if constexpr (std::is_same::value) { + vecResult.push_back(*it); + } + } + return vecResult; + } + + void rebinFromConfig(); + + TList* mListHistGarbage; + std::set mSetAllowedChIDs; + std::array mStateLastIR2Ch; + + // Objects which will be published + std::unique_ptr mHistAmp2Ch; + std::unique_ptr mHistTime2Ch; + std::unique_ptr mVertexVsCollTimeAllBC; + std::unique_ptr mVertexNsVsCollTimeAllBC; + std::unique_ptr mVertexVsCollTimeVertexTrigger; + std::unique_ptr mVertexNsVsCollTimeVertexTrigger; + std::unique_ptr mTimeAvsTimeC; + std::unique_ptr mHistCollTimeAC; + std::unique_ptr mHistCollTimeA; + std::unique_ptr mHistCollTimeC; + std::unique_ptr mHistBC; + std::unique_ptr mHistBCVetex; + std::unique_ptr mHistBCorA; + std::unique_ptr mHistBCorC; + std::map mMapHistAmpVsTime; +}; + +} // namespace o2::quality_control_modules::fdd + +#endif // QC_MODULE_FDD_FDDRecoQcTask_H diff --git a/Modules/FIT/FDD/include/FDD/TriggersSwVsTcmCheck.h b/Modules/FIT/FDD/include/FDD/TriggersSwVsTcmCheck.h new file mode 100644 index 0000000000..1f8b59e3c6 --- /dev/null +++ b/Modules/FIT/FDD/include/FDD/TriggersSwVsTcmCheck.h @@ -0,0 +1,42 @@ +// Copyright 2023 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TriggersSwVsTcmCheck.h +/// \author Dawid Skora dawid.mateusz.skora@cern.ch +/// \Modification for FDD by arvind.khuntia@cern.ch (25.04.2023) + +#ifndef QC_MODULE_FDD_TRIGGERSSWVSTCMCHECK_H +#define QC_MODULE_FDD_TRIGGERSSWVSTCMCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "FV0Base/Constants.h" + +namespace o2::quality_control_modules::fdd +{ +class TriggersSwVsTcmCheck : public o2::quality_control::checker::CheckInterface +{ + public: + TriggersSwVsTcmCheck() = default; + ~TriggersSwVsTcmCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(TriggersSwVsTcmCheck, 2); + + private: + std::array mPositionMsgBox; +}; + +} // namespace o2::quality_control_modules::fdd + +#endif // QC_MODULE_FDD_TRIGGERSSWVSTCMCHECK_H diff --git a/Modules/FIT/FDD/src/CFDEffCheck.cxx b/Modules/FIT/FDD/src/CFDEffCheck.cxx new file mode 100644 index 0000000000..9b74665f1e --- /dev/null +++ b/Modules/FIT/FDD/src/CFDEffCheck.cxx @@ -0,0 +1,195 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CFDEffCheck.cxx +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#include "FDD/CFDEffCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fdd +{ + +void CFDEffCheck::configure() +{ + if (auto param = mCustomParameters.find("thresholdWarning"); param != mCustomParameters.end()) { + mThreshWarning = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdWarning = " << mThreshWarning << ENDM; + } else { + mThreshWarning = 0.9; + ILOG(Debug, Support) << "configure() : using default thresholdWarning = " << mThreshWarning << ENDM; + } + + if (auto param = mCustomParameters.find("thresholdError"); param != mCustomParameters.end()) { + mThreshError = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdError = " << mThreshError << ENDM; + } else { + mThreshError = 0.8; + ILOG(Debug, Support) << "configure() : using default thresholdError = " << mThreshError << ENDM; + } + + if (auto param = mCustomParameters.find("deadChannelMap"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + std::vector deadChannelVec = parseParameters(chIDs, del); + + mDeadChannelMap = new o2::fit::DeadChannelMap(); + for (uint8_t chId = 0; chId < sNCHANNELS; ++chId) { + if (std::find(deadChannelVec.begin(), deadChannelVec.end(), chId) != deadChannelVec.end()) + mDeadChannelMap->setChannelAlive(chId, 0); + else + mDeadChannelMap->setChannelAlive(chId, 1); + } + ILOG(Warning, Support) << "configure() : using deadChannelMap from config (superseding the one from CCDB)" << ENDM; + } else { + if (auto param = mCustomParameters.find("ccdbUrl"); param != mCustomParameters.end()) { + setCcdbUrl(param->second); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, configured url = " << param->second << ENDM; + } else { + setCcdbUrl("o2-ccdb.internal"); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, default url = " + << "o2-ccdb.internal" << ENDM; + } + if (auto param = mCustomParameters.find("pathDeadChannelMap"); param != mCustomParameters.end()) { + mPathDeadChannelMap = param->second; + ILOG(Debug, Support) << "configure() : using pathDeadChannelMap: " << mPathDeadChannelMap << ENDM; + } else { + mPathDeadChannelMap = "FDD/Calib/DeadChannelMap"; + ILOG(Debug, Support) << "configure() : using default pathDeadChannelMap: " << mPathDeadChannelMap << ENDM; + } + + // WARNING: always uses last available dead channel map + // supply deadChannelMap by hand when running offline + mDeadChannelMap = retrieveConditionAny(mPathDeadChannelMap); + if (!mDeadChannelMap || !mDeadChannelMap->map.size()) { + ILOG(Error, Support) << "object \"" << mPathDeadChannelMap << "\" NOT retrieved (or empty). All channels assumed to be alive!" << ENDM; + mDeadChannelMap = new o2::fit::DeadChannelMap(); + for (uint8_t chId = 0; chId < sNCHANNELS; ++chId) { + mDeadChannelMap->setChannelAlive(chId, 1); + } + } + } + mDeadChannelMapStr = ""; + for (unsigned chId = 0; chId < mDeadChannelMap->map.size(); chId++) { + if (!mDeadChannelMap->isChannelAlive(chId)) { + mDeadChannelMapStr += (mDeadChannelMapStr.empty() ? "" : ",") + std::to_string(chId); + } + } + if (mDeadChannelMapStr.empty()) { + mDeadChannelMapStr = "EMPTY"; + } + ILOG(Debug, Support) << "Loaded dead channel map: " << mDeadChannelMapStr << ENDM; +} + +Quality CFDEffCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "CFD_efficiency") { + auto* h = dynamic_cast(mo->getObject()); + + result = Quality::Good; + mNumErrors = 0; + mNumWarnings = 0; + for (uint8_t chId = 0; chId < h->GetNbinsX(); chId++) { + if (chId >= sNCHANNELS) + continue; + if (!mDeadChannelMap->isChannelAlive(chId)) + continue; + if (h->GetBinContent(chId + 1) < mThreshError) { + if (result.isBetterThan(Quality::Bad)) + // result = Quality::Bad; // setting quality like this clears reasons + result.set(Quality::Bad); + mNumErrors++; + result.addFlag(FlagTypeFactory::Unknown(), + "CFD eff. < \"Error\" threshold in channel " + std::to_string(chId)); + // no need to check medium threshold + // but don't `break` because we want to add other reasons + continue; + } else if (h->GetBinContent(chId + 1) < mThreshWarning) { + if (result.isBetterThan(Quality::Medium)) + result.set(Quality::Medium); + mNumWarnings++; + result.addFlag(FlagTypeFactory::Unknown(), + "CFD eff. < \"Warning\" threshold in channel " + std::to_string(chId)); + } + } + } + } + result.addMetadata("nErrors", std::to_string(mNumErrors)); + result.addMetadata("nWarnings", std::to_string(mNumWarnings)); + return result; +} + +void CFDEffCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "CFD_efficiency") { + auto* h = dynamic_cast(mo->getObject()); + + TPaveText* msg = new TPaveText(0.15, 0.2, 0.85, 0.45, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + if (mDeadChannelMapStr != "EMPTY") + msg->AddText(("Dead channel IDs: " + mDeadChannelMapStr).c_str()); + msg->AddText(Form("N channels with warning (< %.3f) = %d", mThreshWarning, mNumWarnings)); + msg->AddText(Form("N channels with error (< %.3f) = %d", mThreshError, mNumErrors)); + + if (checkResult == Quality::Good) { + msg->AddText(">> Quality::Good <<"); + msg->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + auto flags = checkResult.getFlags(); + msg->SetFillColor(kRed); + msg->AddText(">> Quality::Bad <<"); + } else if (checkResult == Quality::Medium) { + auto flags = checkResult.getFlags(); + msg->SetFillColor(kOrange); + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Null) { + msg->AddText(">> Quality::Null <<"); + msg->SetFillColor(kGray); + } + // add threshold lines + Double_t xMin = h->GetXaxis()->GetXmin(); + Double_t xMax = h->GetXaxis()->GetXmax(); + auto* lineError = new TLine(xMin, mThreshError, xMax, mThreshError); + auto* lineWarning = new TLine(xMin, mThreshWarning, xMax, mThreshWarning); + lineError->SetLineWidth(3); + lineWarning->SetLineWidth(3); + lineError->SetLineStyle(kDashed); + lineWarning->SetLineStyle(kDashed); + lineError->SetLineColor(kRed); + lineWarning->SetLineColor(kOrange); + h->GetListOfFunctions()->Add(lineError); + h->GetListOfFunctions()->Add(lineWarning); + h->SetStats(1); + } +} + +} // namespace o2::quality_control_modules::fdd diff --git a/Modules/FIT/FDD/src/DigitQcTask.cxx b/Modules/FIT/FDD/src/DigitQcTask.cxx new file mode 100644 index 0000000000..3f864558c4 --- /dev/null +++ b/Modules/FIT/FDD/src/DigitQcTask.cxx @@ -0,0 +1,929 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTask.cxx +/// \author Artur Furs afurs@cern.ch +/// LATEST modification for FDD on 25.04.2023 (akhuntia@cern.ch) + +#include "FDD/DigitQcTask.h" +#include +#include "TCanvas.h" +#include "TROOT.h" + +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsFIT/Triggers.h" +#include "Framework/InputRecord.h" +#include "Framework/InputRecordWalker.h" +#include "Framework/TimingInfo.h" +#include "DataFormatsFDD/LookUpTable.h" +#include "DataFormatsFDD/ChannelData.h" +#include "DataFormatsFDD/Digit.h" +#include "Common/Utils.h" + +#include "FITCommon/HelperHist.h" +#include "FITCommon/HelperCommon.h" + +namespace o2::quality_control_modules::fdd +{ +using namespace o2::quality_control_modules::fit; +DigitQcTask::~DigitQcTask() +{ + delete mListHistGarbage; +} + +void DigitQcTask::rebinFromConfig() +{ + /* Examples: + "binning_SumAmpC": "100, 0, 100" + "binning_BcOrbitMap_TrgOrA": "25, 0, 256, 10, 0, 3564" + hashtag = all channel IDs (mSetAllowedChIDs), e.g. + "binning_Amp_channel#": "5,-10,90" + is equivalent to: + "binning_Amp_channel0": "5,-10,90" + "binning_Amp_channel1": "5,-10,90" + "binning_Amp_channel2": "5,-10,90" ... + */ + auto rebinHisto = [](std::string hName, std::string binning) { + if (!gROOT->FindObject(hName.data())) { + ILOG(Warning) << "config: histogram named \"" << hName << "\" not found" << ENDM; + return; + } + std::vector tokenizedBinning; + boost::split(tokenizedBinning, binning, boost::is_any_of(",")); + if (tokenizedBinning.size() == 3) { // TH1 + ILOG(Debug) << "config: rebinning TH1 " << hName << " -> " << binning << ENDM; + auto htmp = (TH1F*)gROOT->FindObject(hName.data()); + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str())); + } else if (tokenizedBinning.size() == 6) { // TH2 + auto htmp = (TH2F*)gROOT->FindObject(hName.data()); + ILOG(Debug) << "config: rebinning TH2 " << hName << " -> " << binning << ENDM; + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str()), + std::atof(tokenizedBinning[3].c_str()), std::atof(tokenizedBinning[4].c_str()), std::atof(tokenizedBinning[5].c_str())); + } else { + ILOG(Warning) << "config: invalid binning parameter: " << hName << " -> " << binning << ENDM; + } + }; + + const std::string rebinKeyword = "binning"; + const char* channelIdPlaceholder = "#"; + try { + for (auto& param : mCustomParameters.getAllDefaults()) { + if (param.first.rfind(rebinKeyword, 0) != 0) + continue; + std::string hName = param.first.substr(rebinKeyword.length() + 1); + std::string binning = param.second.c_str(); + if (hName.find(channelIdPlaceholder) != std::string::npos) { + for (const auto& chID : mSetAllowedChIDs) { + std::string hNameCur = hName.substr(0, hName.find(channelIdPlaceholder)) + std::to_string(chID) + hName.substr(hName.find(channelIdPlaceholder) + 1); + rebinHisto(hNameCur, binning); + } + } else { + rebinHisto(hName, binning); + } + } + } catch (std::out_of_range& oor) { + ILOG(Error) << "Cannot access the default custom parameters : " << oor.what() << ENDM; + } +} + +unsigned int DigitQcTask::getModeParameter(std::string paramName, unsigned int defaultVal, std::map choices) +{ + if (auto param = mCustomParameters.find(paramName); param != mCustomParameters.end()) { + // if parameter was provided check which option was chosen + for (const auto& choice : choices) { + if (param->second == choice.second) { + ILOG(Debug, Support) << "setting \"" << paramName << "\" to: \"" << choice.second << "\"" << ENDM; + return choice.first; + } + } + // param value not allowed - use default but with warning + std::string allowedValues; + for (const auto& choice : choices) { + allowedValues += "\""; + allowedValues += choice.second; + allowedValues += "\", "; + } + ILOG(Warning, Support) << "Provided value (\"" << param->second << "\") for parameter \"" << paramName << "\" is not allowed. Allowed values are: " << allowedValues << " setting \"" << paramName << "\" to default value: \"" << choices[defaultVal] << "\"" << ENDM; + return defaultVal; + } else { + // param not provided - use default + ILOG(Debug, Support) << "Setting \"" << paramName << "\" to default value: \"" << choices[defaultVal] << "\"" << ENDM; + return defaultVal; + } +} + +int DigitQcTask::getNumericalParameter(std::string paramName, int defaultVal) +{ + if (auto param = mCustomParameters.find(paramName); param != mCustomParameters.end()) { + float val = stoi(param->second); + ILOG(Debug, Support) << "Setting \"" << paramName << "\" to: " << val << ENDM; + return val; + } else { + ILOG(Debug, Support) << "Setting \"" << paramName << "\" to default value: " << defaultVal << ENDM; + return defaultVal; + } +} + +bool DigitQcTask::chIsVertexEvent(const o2::fdd::ChannelData chd, bool simpleCheck) +{ + if (simpleCheck) { + return chd.getFlag(o2::fdd::ChannelData::kIsEventInTVDC); + } else { + return (chd.getFlag(o2::fdd::ChannelData::kIsCFDinADCgate) && + !(chd.getFlag(o2::fdd::ChannelData::kIsTimeInfoNOTvalid) || chd.getFlag(o2::fdd::ChannelData::kIsTimeInfoLate) || + chd.getFlag(o2::fdd::ChannelData::kIsTimeInfoLost) || chd.getFlag(o2::fdd::ChannelData::kIsDoubleEvent) || chd.getFlag(o2::fdd::ChannelData::kIsAmpHigh)) && + std::abs(static_cast(chd.mTime)) < mTrgOrGate && static_cast(chd.mChargeADC) > mTrgChargeLevelLow && + static_cast(chd.mChargeADC) < mTrgChargeLevelHigh); + } +} + +int DigitQcTask::fpgaDivision(int numerator, int denominator) +{ + // content of the ROM + int rom7x17[] = { 16383, 8192, 5461, 4096, 3277, 2731, 2341, 2048, 1820, 1638, 1489, 1365, 1260, 1170, 1092, 1024, 964, 910, 862, 819, 780, 745, 712, 683, 655, 630, 607, 585, 565, 546, 529, 512, 496, 482, 468, 455, 443, 431, 420, 410, 400, 390, 381, 372, 364, 356, 349, 341, 334, 328, 321, 315, 309, 303, 298, 293, 287, 282, 278, 273, 269, 264, 260, 256, 252, 248, 245, 241, 237, 234, 231, 228, 224, 221, 218, 216, 213, 210, 207, 205, 202, 200, 197, 195, 193, 191, 188, 186, 184, 182, 180, 178, 176, 174, 172, 171, 169, 167, 165, 164, 162, 161, 159, 158, 156, 155, 153, 152, 150, 149, 148, 146, 145, 144, 142, 141, 140, 139, 138, 137, 135, 134, 133, 132, 131, 130, 129 }; + + // return result of the operation (numerator / denominator) + return denominator ? (numerator * rom7x17[denominator - 1]) >> 14 : 0; +} + +void DigitQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize DigitQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + mStateLastIR2Ch = {}; + + mMapTrgSoftware.insert({ o2::fit::Triggers::bitA, false }); + mMapTrgSoftware.insert({ o2::fit::Triggers::bitC, false }); + mMapTrgSoftware.insert({ o2::fit::Triggers::bitVertex, false }); + mMapTrgSoftware.insert({ o2::fit::Triggers::bitCen, false }); + mMapTrgSoftware.insert({ o2::fit::Triggers::bitSCen, false }); + + mTrgModeThresholdVar = getModeParameter("trgModeThresholdVar", + TrgModeThresholdVar::kAmpl, + { { TrgModeThresholdVar::kAmpl, "Ampl" }, + { TrgModeThresholdVar::kNchannels, "Nchannels" } }); + + mTrgModeFDD = getModeParameter("trgModeFDD", + TrgModeFDD::kCoincidence, + { { TrgModeFDD::kNormal, "normal" }, + { TrgModeFDD::kCoincidence, "coincidence" } }); + mTrgModeSide = getModeParameter("trgModeSide", + TrgModeSide::kAplusC, + { { TrgModeSide::kAplusC, "A+C" }, + { TrgModeSide::kAandC, "A&C" }, + { TrgModeSide::kA, "A" }, + { TrgModeSide::kC, "C" } }); + mTrgOrGate = getNumericalParameter("trgOrGate", 153); + mTrgChargeLevelLow = getNumericalParameter("trgChargeLevelLow", 0); + mTrgChargeLevelHigh = getNumericalParameter("trgChargeLevelHigh", 4095); + mTrgThresholdTimeLow = getNumericalParameter("trgThresholdTimeLow", -192); + mTrgThresholdTimeHigh = getNumericalParameter("trgThresholdTimeHigh", 192); + mBinMinADCSaturationCheck = getNumericalParameter("BinMinADCSaturationCheck", 1); + mBinMaxADCSaturationCheck = getNumericalParameter("BinMaxADCSaturationCheck", 3600); + mMinTimeGate = getNumericalParameter("minGateTimeForRatioHistogram", -192); + mMaxTimeGate = getNumericalParameter("maxGateTimeForRatioHistogram", 192); + if (mTrgModeSide == TrgModeSide::kAplusC) { + mTrgThresholdCenA = getNumericalParameter("trgThresholdCenA", 20); + mTrgThresholdSCenA = getNumericalParameter("trgThresholdSCenA", 10); + } else if (mTrgModeSide == TrgModeSide::kAandC) { + mTrgThresholdCenA = getNumericalParameter("trgThresholdCenA", 20); + mTrgThresholdCenC = getNumericalParameter("trgThresholdCenC", 20); + mTrgThresholdSCenA = getNumericalParameter("trgThresholdSCenA", 10); + mTrgThresholdSCenC = getNumericalParameter("trgThresholdSCenC", 10); + } else if (mTrgModeSide == TrgModeSide::kA) { + mTrgThresholdCenA = getNumericalParameter("trgThresholdCenA", 20); + mTrgThresholdSCenA = getNumericalParameter("trgThresholdSCenA", 10); + } else if (mTrgModeSide == TrgModeSide::kC) { + mTrgThresholdCenC = getNumericalParameter("trgThresholdCenC", 20); + mTrgThresholdSCenC = getNumericalParameter("trgThresholdSCenC", 10); + } + + mHistTime2Ch = std::make_unique("TimePerChannel", "Time vs Channel;Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + mHistTime2Ch->SetOption("colz"); + mHistAmp2Ch = std::make_unique("AmpPerChannel", "Amplitude vs Channel;Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + mHistAmp2Ch->SetOption("colz"); + mHistBC = std::make_unique("BC", "BC;BC;counts;", sBCperOrbit, 0, sBCperOrbit); + mHistChDataBits = std::make_unique("ChannelDataBits", "ChannelData bits per ChannelID;Channel;Bit", sNCHANNELS_PM, 0, sNCHANNELS_PM, mMapPMbits.size(), 0, mMapPMbits.size()); + mHistChDataBits->SetOption("colz"); + for (const auto& entry : mMapPMbits) { + mHistChDataBits->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + } + mHistOrbitVsTrg = std::make_unique("OrbitVsTriggers", "Orbit vs Triggers;Orbit;Trg", sOrbitsPerTF, 0, sOrbitsPerTF, mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + mHistOrbitVsTrg->SetOption("colz"); + mHistOrbit2BC = std::make_unique("OrbitPerBC", "BC-Orbit map;Orbit;BC;", sOrbitsPerTF, 0, sOrbitsPerTF, sBCperOrbit, 0, sBCperOrbit); + mHistOrbit2BC->SetOption("colz"); + mHistEventDensity2Ch = std::make_unique("EventDensityPerChannel", "Event density(in BC) per Channel;Channel;BC;", sNCHANNELS_PM, 0, sNCHANNELS_PM, 10000, 0, 1e5); + mHistEventDensity2Ch->SetOption("colz"); + mHistTriggersCorrelation = std::make_unique("TriggersCorrelation", "Correlation of triggers from TCM", mMapTechTrgBits.size(), 0, mMapTechTrgBits.size(), mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + mHistTriggersCorrelation->SetOption("colz"); + mHistBCvsTrg = std::make_unique("BCvsTriggers", "BC vs Triggers;BC;Trg", sBCperOrbit, 0, sBCperOrbit, mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + mHistBCvsTrg->SetOption("colz"); + mHistPmTcmNchA = std::make_unique("PmTcmNumChannelsA", "Comparison of num. channels A from PM and TCM;Number of channels(TCM), side A;PM - TCM", sNCHANNELS_A + 2, 0, sNCHANNELS_A + 2, 2 * sNCHANNELS_A + 1, -int(sNCHANNELS_A) - 0.5, int(sNCHANNELS_A) + 0.5); + mHistPmTcmSumAmpA = std::make_unique("PmTcmSumAmpA", "Comparison of sum of amplitudes A from PM and TCM;Sum of amplitudes(TCM), side A;PM - TCM", 2e2, 0, 1e3, 2e3, -1e3 - 0.5, 1e3 - 0.5); + mHistPmTcmAverageTimeA = std::make_unique("PmTcmAverageTimeA", "Comparison of average time A from PM and TCM;Average time(TCM), side A;PM - TCM", 410, -2050, 2050, 820, -410 - 0.5, 410 - 0.5); + mHistPmTcmNchC = std::make_unique("PmTcmNumChannelsC", "Comparison of num. channels C from PM and TCM;Number of channels(TCM), side C;PM - TCM", sNCHANNELS_C + 2, 0, sNCHANNELS_C + 2, 2 * sNCHANNELS_C + 1, -int(sNCHANNELS_C) - 0.5, int(sNCHANNELS_C) + 0.5); + mHistPmTcmSumAmpC = std::make_unique("PmTcmSumAmpC", "Comparison of sum of amplitudes C from PM and TCM;Sum of amplitudes(TCM), side C;PM - TCM", 2e2, 0, 1e3, 2e3, -1e3 - 0.5, 1e3 - 0.5); + mHistPmTcmAverageTimeC = std::make_unique("PmTcmAverageTimeC", "Comparison of average time C from PM and TCM;Average time(TCM), side C;PM - TCM", 410, -2050, 2050, 820, -410 - 0.5, 410 - 0.5); + mHistTriggersSw = std::make_unique("TriggersSoftware", "Triggers from software", mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + + const std::map mapTrgValidationStatus = { + { TrgComparisonResult::kSWonly, "Sw only" }, + { TrgComparisonResult::kTCMonly, "TCM only" }, + { TrgComparisonResult::kNone, "neither TCM nor Sw" }, + { TrgComparisonResult::kBoth, "both TCM and Sw" } + }; + mHistTriggersSoftwareVsTCM = o2::quality_control_modules::fit::helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "TriggersSoftwareVsTCM", "Comparison of triggers from software and TCM;;Trigger name", mMapTrgBits, mapTrgValidationStatus); + + for (const auto& entry : mMapTechTrgBits) { + mHistOrbitVsTrg->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistTriggersCorrelation->GetXaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistTriggersCorrelation->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistBCvsTrg->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistTriggersSw->GetXaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + } + mHistTriggersSw->GetXaxis()->SetRange(1, 5); + + mListHistGarbage = new TList(); + mListHistGarbage->SetOwner(kTRUE); + + /// ak1 + mHist2CorrTCMchAndPMch = std::make_unique("CorrTCMchAndPMch", "TCM charge - (PM totalCh/8);TCM charge;TCM - PM/8 totalCh;", 1100, 0, 6600, 301, -150.5, 150.5); + mHist2CorrTCMchAndPMch->GetYaxis()->SetRangeUser(-8, 8); + + std::map mapFEE2hash; + const auto& lut = o2::fdd::SingleLUT::Instance().getVecMetadataFEE(); + auto lutSorted = lut; + std::sort(lutSorted.begin(), lutSorted.end(), [](const auto& first, const auto& second) { return first.mModuleName < second.mModuleName; }); + uint8_t binPos{ 0 }; + for (const auto& lutEntry : lutSorted) { + const auto& moduleName = lutEntry.mModuleName; + const auto& moduleType = lutEntry.mModuleType; + const auto& strChID = lutEntry.mChannelID; + const auto& pairIt = mapFEE2hash.insert({ moduleName, binPos }); + if (pairIt.second) { + if (moduleName.find("PMA") != std::string::npos) + mMapPMhash2isAside.insert({ binPos, true }); + else if (moduleName.find("PMC") != std::string::npos) + mMapPMhash2isAside.insert({ binPos, false }); + binPos++; + } + if (std::regex_match(strChID, std::regex("[[\\d]{1,3}"))) { + int chID = std::stoi(strChID); + if (chID < sNCHANNELS_PM) { + mChID2PMhash[chID] = mapFEE2hash[moduleName]; + } else { + LOG(error) << "Incorrect LUT entry: chID " << strChID << " | " << moduleName; + } + } else if (moduleType != "TCM") { + LOG(error) << "Non-TCM module w/o numerical chID: chID " << strChID << " | " << moduleName; + } else if (moduleType == "TCM") { + mTCMhash = mapFEE2hash[moduleName]; + } + } + + mHistBCvsFEEmodules = std::make_unique("BCvsFEEmodules", "BC vs FEE module;BC;FEE", sBCperOrbit, 0, sBCperOrbit, mapFEE2hash.size(), 0, mapFEE2hash.size()); + mHistOrbitVsFEEmodules = std::make_unique("OrbitVsFEEmodules", "Orbit vs FEE module;Orbit;FEE", sOrbitsPerTF, 0, sOrbitsPerTF, mapFEE2hash.size(), 0, mapFEE2hash.size()); + mHistBcVsFeeForVtxTrg = std::make_unique("BCvsFEEmodulesForVtxTrg", "BC vs FEE module for Vertex trigger;BC;FEE", sBCperOrbit, 0, sBCperOrbit, mapFEE2hash.size(), 0, mapFEE2hash.size()); + for (const auto& entry : mapFEE2hash) { + mHistBCvsFEEmodules->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistOrbitVsFEEmodules->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcVsFeeForVtxTrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + } + /// ak1 + mHistTimeSum2Diff = std::make_unique("timeSumVsDiff", "time A/C side: sum VS diff;(TOC-TOA)/2 [ns];(TOA+TOC)/2 [ns]", 2000, -52.08, 52.08, 2000, -52.08, 52.08); // range of 52.08 ns = 4000*13.02ps = 4000 channels + mHistTimeSum2Diff->GetXaxis()->SetRangeUser(-5, 5); + mHistTimeSum2Diff->GetYaxis()->SetRangeUser(-5, 5); + mHistNumADC = std::make_unique("HistNumADC", "HistNumADC", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistNumCFD = std::make_unique("HistNumCFD", "HistNumCFD", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistCFDEff = std::make_unique("CFD_efficiency", "Fraction of events with CFD in ADC gate vs ChannelID;ChannelID;Event fraction with CFD in ADC gate", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistSaturationFraction = std::make_unique("ADCChargeFractionInRange", Form("Fraction of charge in [%d, %d] ADC;Channel ID;Event fraction in [%d, %d] ADC", mBinMinADCSaturationCheck, mBinMaxADCSaturationCheck, mBinMinADCSaturationCheck, mBinMaxADCSaturationCheck), sNCHANNELS_PM, 0, sNCHANNELS_PM); + std::string gateTimeRatioTitle = "Ratio of events between time " + std::to_string(mMinTimeGate) + " and " + std::to_string(mMaxTimeGate); + mHistGateTimeRatio2Ch = std::make_unique("EventsInGateTime", gateTimeRatioTitle.c_str(), sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistNchA = std::make_unique("NumChannelsA", "Number of channels(TCM), side A;Nch", sNCHANNELS_A, 0, sNCHANNELS_A); + mHistNchC = std::make_unique("NumChannelsC", "Number of channels(TCM), side C;Nch", sNCHANNELS_C, 0, sNCHANNELS_C); + mHistSumAmpA = std::make_unique("SumAmpA", "Sum of amplitudes(TCM), side A;", 5e3, 0, 5e3); + mHistSumAmpC = std::make_unique("SumAmpC", "Sum of amplitudes(TCM), side C;", 5e3, 0, 5e3); + mHistAverageTimeA = std::make_unique("AverageTimeA", "Average time(TCM), side A", 4100, -2050, 2050); + mHistAverageTimeC = std::make_unique("AverageTimeC", "Average time(TCM), side C", 4100, -2050, 2050); + mHistChannelID = std::make_unique("StatChannelID", "ChannelID statistics;ChannelID", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistCycleDuration = std::make_unique("CycleDuration", "Cycle Duration;;time [ns]", 1, 0, 2); + mHistCycleDurationNTF = std::make_unique("CycleDurationNTF", "Cycle Duration;;time [TimeFrames]", 1, 0, 2); + mHistCycleDurationRange = std::make_unique("CycleDurationRange", "Cycle Duration (total cycle range);;time [ns]", 1, 0, 2); + + std::vector vecChannelIDs; + if (auto param = mCustomParameters.find("ChannelIDs"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDs = parseParameters(chIDs, del); + } + for (const auto& entry : vecChannelIDs) { + mSetAllowedChIDs.insert(entry); + } + std::vector vecChannelIDsAmpVsTime; + if (auto param = mCustomParameters.find("ChannelIDsAmpVsTime"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDsAmpVsTime = parseParameters(chIDs, del); + } + for (const auto& entry : vecChannelIDsAmpVsTime) { + mSetAllowedChIDsAmpVsTime.insert(entry); + } + + for (const auto& chID : mSetAllowedChIDs) { + auto pairHistAmpCoincidence = mMapHistAmp1DCoincidence.insert({ chID, new TH1F(Form("Amp_channelCoincidence%i", chID), Form("AmplitudeCoincidence, channel %i", chID), 4200, -100, 4100) }); + if (pairHistAmpCoincidence.second) { + getObjectsManager()->startPublishing(pairHistAmpCoincidence.first->second); + mListHistGarbage->Add(pairHistAmpCoincidence.first->second); + } + } + for (const auto& chID : mSetAllowedChIDsAmpVsTime) { + auto pairHistAmpVsTime = mMapHistAmpVsTime.insert({ chID, new TH2F(Form("Amp_vs_time_channel%i", chID), Form("Amplitude vs time, channel %i;Amp;Time", chID), 420, -100, 4100, 410, -2050, 2050) }); + if (pairHistAmpVsTime.second) { + mListHistGarbage->Add(pairHistAmpVsTime.first->second); + getObjectsManager()->startPublishing(pairHistAmpVsTime.first->second); + } + } + + rebinFromConfig(); // after all histos are created + // 1-dim hists + getObjectsManager()->startPublishing(mHistCFDEff.get()); + getObjectsManager()->startPublishing(mHistSaturationFraction.get()); + getObjectsManager()->startPublishing(mHistGateTimeRatio2Ch.get()); + getObjectsManager()->startPublishing(mHistBC.get()); + getObjectsManager()->startPublishing(mHistNchA.get()); + getObjectsManager()->startPublishing(mHistNchC.get()); + getObjectsManager()->startPublishing(mHistSumAmpA.get()); + getObjectsManager()->startPublishing(mHistSumAmpC.get()); + getObjectsManager()->startPublishing(mHistAverageTimeA.get()); + getObjectsManager()->startPublishing(mHistAverageTimeC.get()); + getObjectsManager()->startPublishing(mHistChannelID.get()); + getObjectsManager()->startPublishing(mHistCycleDuration.get()); + getObjectsManager()->startPublishing(mHistCycleDurationNTF.get()); + getObjectsManager()->startPublishing(mHistCycleDurationRange.get()); + getObjectsManager()->startPublishing(mHistTriggersSw.get()); + /// 2d hists + getObjectsManager()->startPublishing(mHist2CorrTCMchAndPMch.get()); + getObjectsManager()->setDefaultDrawOptions(mHist2CorrTCMchAndPMch.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistTime2Ch.get()); + getObjectsManager()->setDefaultDrawOptions(mHistTime2Ch.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistAmp2Ch.get()); + getObjectsManager()->setDefaultDrawOptions(mHistAmp2Ch.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBCvsFEEmodules.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBCvsFEEmodules.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcVsFeeForVtxTrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBcVsFeeForVtxTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistOrbitVsTrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistOrbitVsTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistOrbitVsFEEmodules.get()); + getObjectsManager()->setDefaultDrawOptions(mHistOrbitVsFEEmodules.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistChDataBits.get()); + getObjectsManager()->setDefaultDrawOptions(mHistChDataBits.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistTimeSum2Diff.get()); + getObjectsManager()->setDefaultDrawOptions(mHistTimeSum2Diff.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistOrbit2BC.get()); + getObjectsManager()->setDefaultDrawOptions(mHistOrbit2BC.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBCvsTrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBCvsTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistEventDensity2Ch.get()); + getObjectsManager()->setDefaultDrawOptions(mHistEventDensity2Ch.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistPmTcmNchA.get()); + getObjectsManager()->setDefaultDrawOptions(mHistPmTcmNchA.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistPmTcmSumAmpA.get()); + getObjectsManager()->setDefaultDrawOptions(mHistPmTcmSumAmpA.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistPmTcmAverageTimeA.get()); + getObjectsManager()->setDefaultDrawOptions(mHistPmTcmAverageTimeA.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistPmTcmNchC.get()); + getObjectsManager()->setDefaultDrawOptions(mHistPmTcmNchC.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistPmTcmSumAmpC.get()); + getObjectsManager()->setDefaultDrawOptions(mHistPmTcmSumAmpC.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistPmTcmAverageTimeC.get()); + getObjectsManager()->setDefaultDrawOptions(mHistPmTcmAverageTimeC.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistTriggersCorrelation.get()); + getObjectsManager()->setDefaultDrawOptions(mHistTriggersCorrelation.get(), "COLZ"); + + for (int i = 0; i < getObjectsManager()->getNumberPublishedObjects(); i++) { + TH1* obj = dynamic_cast(getObjectsManager()->getMonitorObject(i)->getObject()); + obj->SetTitle((std::string("FDD ") + obj->GetTitle()).c_str()); + } + // Timestamp + mMetaAnchorOutput = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "metaAnchorOutput", "CycleDurationNTF"); + mTimestampMetaField = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "timestampMetaField", "timestampTF"); +} + +void DigitQcTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + mHist2CorrTCMchAndPMch->Reset(); + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistBC->Reset(); + mHistChDataBits->Reset(); + mHistCFDEff->Reset(); + mHistSaturationFraction->Reset(); + mHistGateTimeRatio2Ch->Reset(); + mHistNumADC->Reset(); + mHistNumCFD->Reset(); + mHistTimeSum2Diff->Reset(); + mHistBCvsFEEmodules->Reset(); + mHistOrbitVsTrg->Reset(); + mHistOrbitVsFEEmodules->Reset(); + mHistBcVsFeeForVtxTrg->Reset(); + mHistTriggersCorrelation->Reset(); + mHistCycleDuration->Reset(); + mHistCycleDurationNTF->Reset(); + mHistCycleDurationRange->Reset(); + mHistBCvsTrg->Reset(); + mHistOrbit2BC->Reset(); + mHistEventDensity2Ch->Reset(); + mHistNchA->Reset(); + mHistNchC->Reset(); + mHistSumAmpA->Reset(); + mHistSumAmpC->Reset(); + mHistAverageTimeA->Reset(); + mHistAverageTimeC->Reset(); + mHistChannelID->Reset(); + mHistPmTcmNchA->Reset(); + mHistPmTcmSumAmpA->Reset(); + mHistPmTcmAverageTimeA->Reset(); + mHistPmTcmNchC->Reset(); + mHistPmTcmSumAmpC->Reset(); + mHistPmTcmAverageTimeC->Reset(); + mHistTriggersSw->Reset(); + mHistTriggersSoftwareVsTCM->Reset(); + for (auto& entry : mMapHistAmp1DCoincidence) { + entry.second->Reset(); + } + for (auto& entry : mMapHistAmpVsTime) { + entry.second->Reset(); + } +} + +void DigitQcTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + mTimeMinNS = -1; + mTimeMaxNS = 0.; + mTimeCurNS = 0.; + mTfCounter = 0; + mTimeSum = 0.; +} + +void DigitQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + mTFcreationTime = ctx.services().get().creation; + mTfCounter++; + auto channels = ctx.inputs().get>("channels"); + auto digits = ctx.inputs().get>("digits"); + if (digits.size() > 0) { + // Considering that Digit container is already sorted by IR + const o2::InteractionRecord& firstIR = digits[0].getIntRecord(); + const o2::InteractionRecord& lastIR = digits[digits.size() - 1].getIntRecord(); + auto timeMinNS = firstIR.bc2ns(); + auto timeMaxNS = lastIR.bc2ns(); + mTimeMinNS = std::min(mTimeMinNS, timeMinNS); // to be sure if somehow TFID is unordered + mTimeMaxNS = std::max(mTimeMaxNS, timeMaxNS); + mTimeSum += timeMaxNS - timeMinNS; + } + int mPMChargeTotalAside, mPMChargeTotalCside; + for (auto& digit : digits) { + // Exclude all BCs, in which laser signals are expected (and trigger outputs are blocked) + if (digit.mTriggers.getOutputsAreBlocked()) { + continue; + } + mPMChargeTotalAside = 0; + mPMChargeTotalCside = 0; + const auto& vecChData = digit.getBunchChannelData(channels); + bool isTCM = true; + if (digit.mTriggers.getTimeA() == o2::fit::Triggers::DEFAULT_TIME && digit.mTriggers.getTimeC() == o2::fit::Triggers::DEFAULT_TIME) { + isTCM = false; + } + mHistOrbit2BC->Fill(digit.getIntRecord().orbit % sOrbitsPerTF, digit.getIntRecord().bc); + mHistBC->Fill(digit.getBC()); + + // Fill the amplitude, if there is a coincidence of the signals in in the front or back layers + bool hasData[16] = { 0 }; + for (const auto& chData : vecChData) { + if (mSetAllowedChIDs.find(static_cast(chData.mPMNumber)) != mSetAllowedChIDs.end()) { + if (static_cast(chData.mPMNumber) < 16) { + hasData[static_cast(chData.mPMNumber)] = 1; + } + } + } // ak + + std::set setFEEmodules{}; + // reset triggers + for (auto& entry : mMapTrgSoftware) { + mMapTrgSoftware[entry.first] = false; + } + + Int_t pmSumAmplA = 0; + Int_t pmSumAmplC = 0; + Int_t pmNChanA = 0; + Int_t pmNChanC = 0; + Int_t pmNCoincidenceChanA = 0; + Int_t pmNCoincidenceChanC = 0; + Int_t pmSumTimeA = 0; + Int_t pmSumTimeC = 0; + Int_t pmAverTimeA = 0; + Int_t pmAverTimeC = 0; + Int_t vtxPos = -999; + // Initialize array elements to zero using std::fill + std::fill(ChVertexArray.begin(), ChVertexArray.end(), 0); + + std::map mapPMhash2sumAmpl; + for (const auto& entry : mMapPMhash2isAside) { + mapPMhash2sumAmpl.insert({ entry.first, 0 }); + } + for (const auto& chData : vecChData) { + if (static_cast(chData.mPMNumber) < sNCHANNELS_C) + mPMChargeTotalCside += chData.mChargeADC; + else + mPMChargeTotalAside += chData.mChargeADC; + + mHistTime2Ch->Fill(static_cast(chData.mPMNumber), static_cast(chData.mTime)); + mHistAmp2Ch->Fill(static_cast(chData.mPMNumber), static_cast(chData.mChargeADC)); + mHistEventDensity2Ch->Fill(static_cast(chData.mPMNumber), static_cast(digit.mIntRecord.differenceInBC(mStateLastIR2Ch[chData.mPMNumber]))); + mStateLastIR2Ch[chData.mPMNumber] = digit.mIntRecord; + mHistChannelID->Fill(chData.mPMNumber); + if (chData.mChargeADC > 0) { + mHistNumADC->Fill(chData.mPMNumber); + } + mHistNumCFD->Fill(chData.mPMNumber); + if (mSetAllowedChIDs.size() != 0 && mSetAllowedChIDs.find(static_cast(chData.mPMNumber)) != mSetAllowedChIDs.end()) { + if (static_cast(chData.mPMNumber) == 0 && hasData[4]) { + mMapHistAmp1DCoincidence[0]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 1 && hasData[5]) { + mMapHistAmp1DCoincidence[1]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 2 && hasData[6]) { + mMapHistAmp1DCoincidence[2]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 3 && hasData[7]) { + mMapHistAmp1DCoincidence[3]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 4 && hasData[0]) { + mMapHistAmp1DCoincidence[4]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 5 && hasData[1]) { + mMapHistAmp1DCoincidence[5]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 6 && hasData[2]) { + mMapHistAmp1DCoincidence[6]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 7 && hasData[3]) { + mMapHistAmp1DCoincidence[7]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 8 && hasData[12]) { + mMapHistAmp1DCoincidence[8]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 9 && hasData[13]) { + mMapHistAmp1DCoincidence[9]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 10 && hasData[14]) { + mMapHistAmp1DCoincidence[10]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 11 && hasData[15]) { + mMapHistAmp1DCoincidence[11]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 12 && hasData[8]) { + mMapHistAmp1DCoincidence[12]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 13 && hasData[9]) { + mMapHistAmp1DCoincidence[13]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 14 && hasData[10]) { + mMapHistAmp1DCoincidence[14]->Fill(chData.mChargeADC); + } + if (static_cast(chData.mPMNumber) == 15 && hasData[11]) { + mMapHistAmp1DCoincidence[15]->Fill(chData.mChargeADC); + } + } + if (mSetAllowedChIDsAmpVsTime.size() != 0 && mSetAllowedChIDsAmpVsTime.find(static_cast(chData.mPMNumber)) != mSetAllowedChIDsAmpVsTime.end()) { + mMapHistAmpVsTime[chData.mPMNumber]->Fill(chData.mChargeADC, chData.mTime); + } + for (const auto& binPos : mHashedBitBinPos[chData.mFEEBits]) { + mHistChDataBits->Fill(chData.mPMNumber, binPos); + } + + setFEEmodules.insert(mChID2PMhash[chData.mPMNumber]); + + if (chIsVertexEvent(chData, true)) { + if (!mMapPMhash2isAside[mChID2PMhash[static_cast(chData.mPMNumber)]]) { + pmSumTimeC += chData.mTime; + pmNChanC++; + if ((int)chData.mPMNumber < sNCHANNELS_Physics) + ChVertexArray[chData.mPMNumber] = 1; + } else if (mMapPMhash2isAside[mChID2PMhash[static_cast(chData.mPMNumber)]]) { + pmSumTimeA += chData.mTime; + pmNChanA++; + if ((int)chData.mPMNumber < sNCHANNELS_Physics) + ChVertexArray[chData.mPMNumber] = 1; + } + } + + /// now check the coincidence for the vertex trigger + for (int i = 0; i < 4; ++i) { + if (ChVertexArray[i] == 1 && ChVertexArray[i + 4] == 1) + pmNCoincidenceChanC++; + if (ChVertexArray[i + cSideChOffSet] == 1 && ChVertexArray[i + 4 + cSideChOffSet] == 1) + pmNCoincidenceChanA++; + } + + if (chData.getFlag(o2::fdd::ChannelData::kIsCFDinADCgate)) { + mapPMhash2sumAmpl[mChID2PMhash[static_cast(chData.mPMNumber)]] += static_cast(chData.mChargeADC); + } + } + + for (const auto& entry : mapPMhash2sumAmpl) { + if (mMapPMhash2isAside[entry.first]) + pmSumAmplA += static_cast(entry.second >> 3); + else + pmSumAmplC += static_cast(entry.second >> 3); + } + auto pmNChan = pmNChanA + pmNChanC; + auto pmSumAmpl = pmSumAmplA + pmSumAmplC; + if (isTCM) { + pmAverTimeA = fpgaDivision(pmSumTimeA, pmNChanA); + pmAverTimeC = fpgaDivision(pmSumTimeC, pmNChanC); + } else { + pmAverTimeA = o2::fit::Triggers::DEFAULT_TIME; + pmAverTimeC = o2::fit::Triggers::DEFAULT_TIME; + } + + if (mTrgModeFDD == TrgModeFDD::kNormal) + vtxPos = (pmNChanA && pmNChanC) ? (pmAverTimeC - pmAverTimeA) / 2 : 0; + else if (mTrgModeFDD == TrgModeFDD::kCoincidence) + vtxPos = (pmNCoincidenceChanA && pmNCoincidenceChanC) ? (pmAverTimeC - pmAverTimeA) / 2 : 0; + + /// PM charge is scaled by 8 to compare with TCM charge + // mPMChargeTotalAside = std::lround(static_cast(mPMChargeTotalAside / 8)); + mPMChargeTotalAside = static_cast(mPMChargeTotalAside >> 3); + // mPMChargeTotalCside = std::lround(static_cast(mPMChargeTotalCside / 8)); + mPMChargeTotalCside = static_cast(mPMChargeTotalCside >> 3); + + if (isTCM) { + setFEEmodules.insert(mTCMhash); + mHist2CorrTCMchAndPMch->Fill(digit.mTriggers.getAmplA() + digit.mTriggers.getAmplC(), (digit.mTriggers.getAmplA() + digit.mTriggers.getAmplC()) - (mPMChargeTotalAside + mPMChargeTotalCside)); + } + for (const auto& feeHash : setFEEmodules) { + mHistBCvsFEEmodules->Fill(static_cast(digit.getIntRecord().bc), static_cast(feeHash)); + mHistOrbitVsFEEmodules->Fill(static_cast(digit.getIntRecord().orbit % sOrbitsPerTF), static_cast(feeHash)); + if (digit.mTriggers.getVertex()) + mHistBcVsFeeForVtxTrg->Fill(static_cast(digit.getIntRecord().bc), static_cast(feeHash)); + } + + if (isTCM && digit.mTriggers.getDataIsValid() && !digit.mTriggers.getOutputsAreBlocked()) { + if (digit.mTriggers.getNChanA() > 0) { + mHistNchA->Fill(digit.mTriggers.getNChanA()); + mHistSumAmpA->Fill(digit.mTriggers.getAmplA()); + mHistAverageTimeA->Fill(digit.mTriggers.getTimeA()); + } + if (digit.mTriggers.getNChanC() > 0) { + mHistNchC->Fill(digit.mTriggers.getNChanC()); + mHistSumAmpC->Fill(digit.mTriggers.getAmplC()); + mHistAverageTimeC->Fill(digit.mTriggers.getTimeC()); + } + mHistPmTcmNchA->Fill(digit.mTriggers.getNChanA(), pmNChanA - digit.mTriggers.getNChanA()); + mHistPmTcmSumAmpA->Fill(digit.mTriggers.getAmplA(), pmSumAmplA - digit.mTriggers.getAmplA()); + mHistPmTcmAverageTimeA->Fill(digit.mTriggers.getTimeA(), pmAverTimeA - digit.mTriggers.getTimeA()); + mHistPmTcmNchC->Fill(digit.mTriggers.getNChanC(), pmNChanC - digit.mTriggers.getNChanC()); + mHistPmTcmSumAmpC->Fill(digit.mTriggers.getAmplC(), pmSumAmplC - digit.mTriggers.getAmplC()); + mHistPmTcmAverageTimeC->Fill(digit.mTriggers.getTimeC(), pmAverTimeC - digit.mTriggers.getTimeC()); + + mHistTimeSum2Diff->Fill((digit.mTriggers.getTimeC() - digit.mTriggers.getTimeA()) * sCFDChannel2NS / 2, (digit.mTriggers.getTimeC() + digit.mTriggers.getTimeA()) * sCFDChannel2NS / 2); + for (const auto& binPos : mHashedPairBitBinPos[digit.mTriggers.getTriggersignals()]) { + mHistTriggersCorrelation->Fill(binPos.first, binPos.second); + } + for (const auto& binPos : mHashedBitBinPos[digit.mTriggers.getTriggersignals()]) { + mHistBCvsTrg->Fill(digit.getIntRecord().bc, binPos); + mHistOrbitVsTrg->Fill(digit.getIntRecord().orbit % sOrbitsPerTF, binPos); + } + } + + // triggers re-computation + switch (mTrgModeFDD) { + case TrgModeFDD::kNormal: + mMapTrgSoftware[o2::fdd::Triggers::bitA] = pmNChanA > 0; + mMapTrgSoftware[o2::fdd::Triggers::bitC] = pmNChanC > 0; + if (mTrgThresholdTimeLow < vtxPos && vtxPos < mTrgThresholdTimeHigh && pmNChanA > 0 && pmNChanC > 0) + mMapTrgSoftware[o2::fdd::Triggers::bitVertex] = true; + break; + case TrgModeFDD::kCoincidence: + mMapTrgSoftware[o2::fdd::Triggers::bitA] = pmNCoincidenceChanA > 0; + mMapTrgSoftware[o2::fdd::Triggers::bitC] = pmNCoincidenceChanC > 0; + if (mTrgThresholdTimeLow < vtxPos && vtxPos < mTrgThresholdTimeHigh && pmNCoincidenceChanA > 0 && pmNCoincidenceChanC > 0) + mMapTrgSoftware[o2::fdd::Triggers::bitVertex] = true; + break; + } + + // Central/SemiCentral logic + switch (mTrgModeSide) { + case TrgModeSide::kAplusC: + if (mTrgModeThresholdVar == TrgModeThresholdVar::kAmpl) { + if (pmSumAmplA + pmSumAmplC >= 2 * mTrgThresholdCenA) + mMapTrgSoftware[o2::fdd::Triggers::bitCen] = true; + else if (pmSumAmplA + pmSumAmplC >= 2 * mTrgThresholdSCenA) + mMapTrgSoftware[o2::fdd::Triggers::bitSCen] = true; + } else if (mTrgModeThresholdVar == TrgModeThresholdVar::kNchannels) { + if (pmNChanA + pmNChanC >= mTrgThresholdCenA) + mMapTrgSoftware[o2::fdd::Triggers::bitCen] = true; + else if (pmNChanA + pmNChanC >= mTrgThresholdSCenA) + mMapTrgSoftware[o2::fdd::Triggers::bitSCen] = true; + } + break; + + case TrgModeSide::kAandC: + if (mTrgModeThresholdVar == TrgModeThresholdVar::kAmpl) { + if (pmSumAmplA > 2 * mTrgThresholdCenA && pmSumAmplC > 2 * mTrgThresholdCenC) + mMapTrgSoftware[o2::fdd::Triggers::bitCen] = true; + else if (pmSumAmplA > 2 * mTrgThresholdSCenA && pmSumAmplC > 2 * mTrgThresholdSCenC && !mMapTrgSoftware[o2::fdd::Triggers::bitCen]) { + mMapTrgSoftware[o2::fdd::Triggers::bitSCen] = true; + } + } else if (mTrgModeThresholdVar == TrgModeThresholdVar::kNchannels) { + if (pmNChanA >= mTrgThresholdCenA && pmNChanC >= mTrgThresholdCenC) + mMapTrgSoftware[o2::fdd::Triggers::bitCen] = true; + else if (pmNChanA >= mTrgThresholdSCenA && pmNChanC >= mTrgThresholdSCenC) + mMapTrgSoftware[o2::fdd::Triggers::bitSCen] = true; + } + break; + + case TrgModeSide::kA: + if (mTrgModeThresholdVar == TrgModeThresholdVar::kAmpl) { + if (pmSumAmplA >= 2 * mTrgThresholdCenA) + mMapTrgSoftware[o2::fdd::Triggers::bitCen] = true; + else if (pmSumAmplA >= 2 * mTrgThresholdSCenA) + mMapTrgSoftware[o2::fdd::Triggers::bitSCen] = true; + } else if (mTrgModeThresholdVar == TrgModeThresholdVar::kNchannels) { + if (pmNChanA >= 2 * mTrgThresholdCenA) + mMapTrgSoftware[o2::fdd::Triggers::bitCen] = true; + else if (pmNChanA >= 2 * mTrgThresholdSCenA) + mMapTrgSoftware[o2::fdd::Triggers::bitSCen] = true; + } + break; + + case TrgModeSide::kC: + if (mTrgModeThresholdVar == TrgModeThresholdVar::kAmpl) { + if (pmSumAmplC >= mTrgThresholdCenC) + mMapTrgSoftware[o2::fdd::Triggers::bitCen] = true; + else if (pmSumAmplC >= mTrgThresholdSCenC) + mMapTrgSoftware[o2::fdd::Triggers::bitSCen] = true; + } else if (mTrgModeThresholdVar == TrgModeThresholdVar::kNchannels) { + if (pmNChanC >= mTrgThresholdCenC) + mMapTrgSoftware[o2::fdd::Triggers::bitCen] = true; + else if (pmNChanC >= mTrgThresholdSCenC) + mMapTrgSoftware[o2::fdd::Triggers::bitSCen] = true; + } + break; + } + + for (const auto& entry : mMapTrgSoftware) { + if (entry.second) + mHistTriggersSw->Fill(entry.first); + bool isTCMFired = digit.mTriggers.getTriggersignals() & (1 << entry.first); + bool isSwFired = entry.second; + if (!isTCMFired && isSwFired) { + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kSWonly); + + } else if (isTCMFired && !isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kTCMonly); + else if (!isTCMFired && !isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kNone); + else if (isTCMFired && isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kBoth); + + if (isTCMFired != isSwFired) { + auto msg = Form( + "Software does not reproduce TCM decision! \n \ + trigger name: %s\n \ + TCM / SW: \n \ + hasFired = %d / %d \n \ + nChannelsA = %d / %d \n \ + nChannelsC = %d / %d \n \ + sumAmplA = %d / %d \n \ + sumAmplC = %d / %d \n \ + timeA = %d / %d \n \ + timeC = %d / %d \n \ + vertexPos = -- / %d \n \ + TCM bits = %d / --", + mMapTechTrgBits[entry.first].c_str(), + isTCMFired, isSwFired, + digit.mTriggers.getNChanA(), pmNChanA, + digit.mTriggers.getNChanC(), pmNChanC, + digit.mTriggers.getAmplA(), pmSumAmplA, + digit.mTriggers.getAmplC(), pmSumAmplC, + digit.mTriggers.getTimeA(), pmAverTimeA, + digit.mTriggers.getTimeC(), pmAverTimeC, vtxPos, + digit.mTriggers.getTriggersignals()); + ILOG(Debug, Support) << msg << ENDM; + } + } + // end of triggers re-computation + } +} + +void DigitQcTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + // add TF creation time for further match with filling scheme in PP in case of offline running + ILOG(Debug, Support) << "adding last TF creation time: " << mTFcreationTime << ENDM; + getObjectsManager()->getMonitorObject(mMetaAnchorOutput)->addOrUpdateMetadata(mTimestampMetaField, std::to_string(mTFcreationTime)); + + // one has to set num. of entries manually because + // default TH1Reductor gets only mean,stddev and entries (no integral) + mHistCFDEff->Divide(mHistNumADC.get(), mHistNumCFD.get()); + for (int iPM = 0; iPM < sNCHANNELS_PM; iPM++) { + double intNumerator = mHistAmp2Ch->ProjectionY("yNum", iPM + 1, iPM + 1)->Integral(mBinMinADCSaturationCheck, mBinMaxADCSaturationCheck); + double intDenominator = mHistAmp2Ch->ProjectionY("yDen", iPM + 1, iPM + 1)->Integral(mBinMinADCSaturationCheck, mHistAmp2Ch->GetNbinsY()); + if (intDenominator) + mHistSaturationFraction->SetBinContent(iPM + 1, intNumerator / intDenominator); + } + + for (int channel = 0; channel <= sNCHANNELS_PM; channel++) { + float events_in_range = 0; + float events_per_channel = 0; + for (int bin_on_y_axis = 1; bin_on_y_axis <= mHistTime2Ch->GetNbinsY(); bin_on_y_axis++) { + if (mHistTime2Ch->GetYaxis()->GetBinLowEdge(bin_on_y_axis) > mMinTimeGate && mHistTime2Ch->GetYaxis()->GetBinLowEdge(bin_on_y_axis) < mMaxTimeGate) { + events_in_range += mHistTime2Ch->GetBinContent(channel + 1, bin_on_y_axis); + } + events_per_channel += mHistTime2Ch->GetBinContent(channel + 1, bin_on_y_axis); + } + if (events_per_channel) { + mHistGateTimeRatio2Ch->SetBinContent(channel + 1, events_in_range / events_per_channel); + } else { + mHistGateTimeRatio2Ch->SetBinContent(channel + 1, 0); + } + } + mHistSaturationFraction->GetYaxis()->SetRangeUser(0, 1.1); + mHistCycleDurationRange->SetBinContent(1., mTimeMaxNS - mTimeMinNS); + mHistCycleDurationRange->SetEntries(mTimeMaxNS - mTimeMinNS); + mHistCycleDurationNTF->SetBinContent(1., mTfCounter); + mHistCycleDurationNTF->SetEntries(mTfCounter); + mHistCycleDuration->SetBinContent(1., mTimeSum); + mHistCycleDuration->SetEntries(mTimeSum); + ILOG(Debug, Support) << "Cycle duration: NTF=" << mTfCounter << ", range = " << (mTimeMaxNS - mTimeMinNS) / 1e6 / mTfCounter << " ms/TF, sum = " << mTimeSum / 1e6 / mTfCounter << " ms/TF" << ENDM; +} + +void DigitQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void DigitQcTask::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mHist2CorrTCMchAndPMch->Reset(); + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistBC->Reset(); + mHistChDataBits->Reset(); + mHistCFDEff->Reset(); + mHistSaturationFraction->Reset(); + mHistGateTimeRatio2Ch->Reset(); + mHistNumADC->Reset(); + mHistNumCFD->Reset(); + mHistTimeSum2Diff->Reset(); + mHistOrbit2BC->Reset(); + mHistEventDensity2Ch->Reset(); + mHistNchA->Reset(); + mHistNchC->Reset(); + mHistSumAmpA->Reset(); + mHistSumAmpC->Reset(); + mHistAverageTimeA->Reset(); + mHistAverageTimeC->Reset(); + mHistChannelID->Reset(); + mHistTriggersCorrelation->Reset(); + mHistCycleDuration->Reset(); + mHistCycleDurationNTF->Reset(); + mHistCycleDurationRange->Reset(); + mHistBCvsTrg->Reset(); + mHistBCvsFEEmodules->Reset(); + mHistBcVsFeeForVtxTrg->Reset(); + mHistOrbitVsTrg->Reset(); + mHistOrbitVsFEEmodules->Reset(); + mHistPmTcmNchA->Reset(); + mHistPmTcmSumAmpA->Reset(); + mHistPmTcmAverageTimeA->Reset(); + mHistPmTcmNchC->Reset(); + mHistPmTcmSumAmpC->Reset(); + mHistPmTcmAverageTimeC->Reset(); + mHistTriggersSw->Reset(); + mHistTriggersSoftwareVsTCM->Reset(); + for (auto& entry : mMapHistAmp1DCoincidence) { + entry.second->Reset(); + } + for (auto& entry : mMapHistAmpVsTime) { + entry.second->Reset(); + } +} + +} // namespace o2::quality_control_modules::fdd diff --git a/Modules/FIT/FDD/src/DigitQcTaskLaser.cxx b/Modules/FIT/FDD/src/DigitQcTaskLaser.cxx new file mode 100644 index 0000000000..cc21f7b354 --- /dev/null +++ b/Modules/FIT/FDD/src/DigitQcTaskLaser.cxx @@ -0,0 +1,356 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTaskLaser.cxx +/// \author Artur Furs afurs@cern.ch +/// modified by A Khuntia for FDD akhuntia@cern.ch + +#include "TCanvas.h" +#include "TH1.h" +#include "TH2.h" +#include "TROOT.h" + +#include "QualityControl/QcInfoLogger.h" +#include "FDD/DigitQcTaskLaser.h" +#include "DataFormatsFDD/Digit.h" +#include "DataFormatsFDD/ChannelData.h" +#include "Framework/InputRecord.h" + +#include +#include + +namespace o2::quality_control_modules::fdd +{ + +DigitQcTaskLaser::~DigitQcTaskLaser() +{ + delete mListHistGarbage; +} + +void DigitQcTaskLaser::rebinFromConfig() +{ + /* Examples: + "binning_SumAmpC": "100, 0, 100" + "binning_BcOrbitMap_TrgOrA": "25, 0, 256, 10, 0, 3564" + hashtag = all channel IDs (mSetAllowedChIDs), e.g. + "binning_Amp_channel#": "5,-10,90" + is equivalent to: + "binning_Amp_channel0": "5,-10,90" + "binning_Amp_channel1": "5,-10,90" + "binning_Amp_channel2": "5,-10,90" ... + */ + auto rebinHisto = [](std::string hName, std::string binning) { + if (!gROOT->FindObject(hName.data())) { + ILOG(Warning) << "config: histogram named \"" << hName << "\" not found" << ENDM; + return; + } + std::vector tokenizedBinning; + boost::split(tokenizedBinning, binning, boost::is_any_of(",")); + if (tokenizedBinning.size() == 3) { // TH1 + ILOG(Debug) << "config: rebinning TH1 " << hName << " -> " << binning << ENDM; + auto htmp = (TH1F*)gROOT->FindObject(hName.data()); + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str())); + } else if (tokenizedBinning.size() == 6) { // TH2 + auto htmp = (TH2F*)gROOT->FindObject(hName.data()); + ILOG(Debug) << "config: rebinning TH2 " << hName << " -> " << binning << ENDM; + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str()), + std::atof(tokenizedBinning[3].c_str()), std::atof(tokenizedBinning[4].c_str()), std::atof(tokenizedBinning[5].c_str())); + } else { + ILOG(Warning) << "config: invalid binning parameter: " << hName << " -> " << binning << ENDM; + } + }; + + const std::string rebinKeyword = "binning"; + const char* channelIdPlaceholder = "#"; + try { + for (auto& param : mCustomParameters.getAllDefaults()) { + if (param.first.rfind(rebinKeyword, 0) != 0) + continue; + std::string hName = param.first.substr(rebinKeyword.length() + 1); + std::string binning = param.second.c_str(); + if (hName.find(channelIdPlaceholder) != std::string::npos) { + for (const auto& chID : mSetAllowedChIDs) { + std::string hNameCur = hName.substr(0, hName.find(channelIdPlaceholder)) + std::to_string(chID) + hName.substr(hName.find(channelIdPlaceholder) + 1); + rebinHisto(hNameCur, binning); + } + for (const auto& chID : mSetRefPMTChIDs) { + std::string hNameCur = hName.substr(0, hName.find(channelIdPlaceholder)) + std::to_string(chID) + hName.substr(hName.find(channelIdPlaceholder) + 1); + rebinHisto(hNameCur, binning); + } + } else { + rebinHisto(hName, binning); + } + } + } catch (std::out_of_range& oor) { + ILOG(Error) << "Cannot access the default custom parameters : " << oor.what() << ENDM; + } +} + +void DigitQcTaskLaser::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize DigitQcTaskLaser" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + mStateLastIR2Ch = {}; + mMapChTrgNames.insert({ o2::fdd::ChannelData::kNumberADC, "NumberADC" }); + mMapChTrgNames.insert({ o2::fdd::ChannelData::kIsDoubleEvent, "IsDoubleEvent" }); + mMapChTrgNames.insert({ o2::fdd::ChannelData::kIsTimeInfoNOTvalid, "IsTimeInfoNOTvalid" }); + mMapChTrgNames.insert({ o2::fdd::ChannelData::kIsCFDinADCgate, "IsCFDinADCgate" }); + mMapChTrgNames.insert({ o2::fdd::ChannelData::kIsTimeInfoLate, "IsTimeInfoLate" }); + mMapChTrgNames.insert({ o2::fdd::ChannelData::kIsAmpHigh, "IsAmpHigh" }); + mMapChTrgNames.insert({ o2::fdd::ChannelData::kIsEventInTVDC, "IsEventInTVDC" }); + mMapChTrgNames.insert({ o2::fdd::ChannelData::kIsTimeInfoLost, "IsTimeInfoLost" }); + + mMapDigitTrgNames.insert({ o2::fdd::Triggers::bitA, "OrA" }); + mMapDigitTrgNames.insert({ o2::fdd::Triggers::bitC, "OrC" }); + mMapDigitTrgNames.insert({ o2::fdd::Triggers::bitVertex, "Vertex" }); + mMapDigitTrgNames.insert({ o2::fdd::Triggers::bitCen, "Central" }); + mMapDigitTrgNames.insert({ o2::fdd::Triggers::bitSCen, "SemiCentral" }); + mMapDigitTrgNames.insert({ o2::fdd::Triggers::bitLaser, "Laser" }); + mMapDigitTrgNames.insert({ o2::fdd::Triggers::bitOutputsAreBlocked, "OutputsAreBlocked" }); + mMapDigitTrgNames.insert({ o2::fdd::Triggers::bitDataIsValid, "DataIsValid" }); + + mHistTime2Ch = std::make_unique("TimePerChannel", "Time vs Channel;Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + mHistTime2Ch->SetOption("colz"); + mHistAmp2Ch = std::make_unique("AmpPerChannel", "Amplitude vs Channel;Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + mHistAmp2Ch->SetOption("colz"); + mHistOrbit2BC = std::make_unique("OrbitPerBC", "BC-Orbit map;Orbit;BC;", 256, 0, 256, sBCperOrbit, 0, sBCperOrbit); + mHistOrbit2BC->SetOption("colz"); + mHistBC = std::make_unique("BC", "BC;BC;counts;", sBCperOrbit, 0, sBCperOrbit); + + mHistChDataBits = std::make_unique("ChannelDataBits", "ChannelData bits per ChannelID;Channel;Bit", sNCHANNELS_PM, 0, sNCHANNELS_PM, mMapChTrgNames.size(), 0, mMapChTrgNames.size()); + mHistChDataBits->SetOption("colz"); + + for (const auto& entry : mMapChTrgNames) { + mHistChDataBits->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + } + + mListHistGarbage = new TList(); + mListHistGarbage->SetOwner(kTRUE); + + char* p; + for (const auto& lutEntry : o2::fdd::SingleLUT::Instance().getVecMetadataFEE()) { + int chId = std::strtol(lutEntry.mChannelID.c_str(), &p, 10); + if (*p) { + // lutEntry.mChannelID is not a number + continue; + } + auto moduleName = lutEntry.mModuleName; + if (moduleName == "TCM") { + if (mMapPmModuleChannels.find(moduleName) == mMapPmModuleChannels.end()) { + mMapPmModuleChannels.insert({ moduleName, std::vector{} }); + } + continue; + } + if (mMapPmModuleChannels.find(moduleName) != mMapPmModuleChannels.end()) { + mMapPmModuleChannels[moduleName].push_back(chId); + } else { + std::vector vChId = { + chId, + }; + mMapPmModuleChannels.insert({ moduleName, vChId }); + } + } + + for (const auto& entry : mMapPmModuleChannels) { + auto pairModuleBcOrbit = mMapPmModuleBcOrbit.insert({ entry.first, new TH2F(Form("BcOrbitMap_%s", entry.first.c_str()), Form("BC-orbit map for %s;Orbit;BC", entry.first.c_str()), 256, 0, 256, sBCperOrbit, 0, sBCperOrbit) }); + } + + mHistNumADC = std::make_unique("HistNumADC", "HistNumADC", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistNumCFD = std::make_unique("HistNumCFD", "HistNumCFD", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistCFDEff = std::make_unique("CFD_efficiency", "CFD efficiency;ChannelID;efficiency", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistCycleDuration = std::make_unique("CycleDuration", "Cycle Duration;;time [ns]", 1, 0, 2); + + std::vector vecChannelIDs; + std::vector vecRefPMTChannelIDs; + if (auto param = mCustomParameters.find("ChannelIDs"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDs = parseParameters(chIDs, del); + } else { + for (unsigned int iCh = 0; iCh < sNCHANNELS_PM; iCh++) + vecChannelIDs.push_back(iCh); + } + if (auto param = mCustomParameters.find("RefPMTChannelIDs"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecRefPMTChannelIDs = parseParameters(chIDs, del); + } + for (const auto& entry : vecChannelIDs) { + mSetAllowedChIDs.insert(entry); + } + for (const auto& entry : vecRefPMTChannelIDs) { + mSetRefPMTChIDs.insert(entry); + } + + for (const auto& RefPMTChID : mSetRefPMTChIDs) { + auto pairHistAmpVsBC = mMapHistAmpVsBC.insert({ RefPMTChID, new TH2F(Form("Amp_vs_BC_channel%i", RefPMTChID), Form("Amplitude vs BC, channel %i;Amp;BC", RefPMTChID), 1000, 0, 1000, 1000, 0, 1000) }); + if (pairHistAmpVsBC.second) { + mListHistGarbage->Add(pairHistAmpVsBC.first->second); + getObjectsManager()->startPublishing(pairHistAmpVsBC.first->second); + } + } + + rebinFromConfig(); // after all histos are created + getObjectsManager()->startPublishing(mHistTime2Ch.get()); + getObjectsManager()->startPublishing(mHistAmp2Ch.get()); + getObjectsManager()->startPublishing(mHistOrbit2BC.get()); + getObjectsManager()->startPublishing(mHistBC.get()); + getObjectsManager()->startPublishing(mHistCFDEff.get()); + getObjectsManager()->startPublishing(mHistCycleDuration.get()); + + for (int i = 0; i < getObjectsManager()->getNumberPublishedObjects(); i++) { + TH1* obj = dynamic_cast(getObjectsManager()->getMonitorObject(i)->getObject()); + obj->SetTitle((std::string("FDD Laser ") + obj->GetTitle()).c_str()); + } +} + +void DigitQcTaskLaser::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity" << activity.mId << ENDM; + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistOrbit2BC->Reset(); + mHistBC->Reset(); + mHistCFDEff->Reset(); + mHistNumADC->Reset(); + mHistNumCFD->Reset(); + mHistCycleDuration->Reset(); + for (auto& entry : mMapHistAmpVsBC) { + entry.second->Reset(); + } + for (auto& entry : mMapPmModuleBcOrbit) { + entry.second->Reset(); + } +} + +void DigitQcTaskLaser::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + mTimeMinNS = -1; + mTimeMaxNS = 0.; + mTimeCurNS = 0.; + mTfCounter = 0; + mTimeSum = 0.; +} + +void DigitQcTaskLaser::monitorData(o2::framework::ProcessingContext& ctx) +{ + double curTfTimeMin = -1; + double curTfTimeMax = 0; + mTfCounter++; + auto channels = ctx.inputs().get>("channels"); + auto digits = ctx.inputs().get>("digits"); + bool isFirst = true; + uint32_t firstOrbit; + + for (auto& digit : digits) { + // Exclude all BCs, in which laser signals are NOT expected (and trigger outputs are NOT blocked) + if (!digit.mTriggers.getOutputsAreBlocked()) { + continue; + } + const auto& vecChData = digit.getBunchChannelData(channels); + bool isTCM = true; + mTimeCurNS = o2::InteractionRecord::bc2ns(digit.getBC(), digit.getOrbit()); + if (mTimeMinNS < 0) + mTimeMinNS = mTimeCurNS; + if (isFirst == true) { + firstOrbit = digit.getOrbit(); + isFirst = false; + } + if (mTimeCurNS < mTimeMinNS) + mTimeMinNS = mTimeCurNS; + if (mTimeCurNS > mTimeMaxNS) + mTimeMaxNS = mTimeCurNS; + + if (curTfTimeMin < 0) + curTfTimeMin = mTimeCurNS; + if (mTimeCurNS < curTfTimeMin) + curTfTimeMin = mTimeCurNS; + if (mTimeCurNS > curTfTimeMax) + curTfTimeMax = mTimeCurNS; + + if (digit.mTriggers.getTimeA() == o2::fit::Triggers::DEFAULT_TIME && digit.mTriggers.getTimeC() == o2::fit::Triggers::DEFAULT_TIME) { + isTCM = false; + } + mHistOrbit2BC->Fill(digit.getIntRecord().orbit % sOrbitsPerTF, digit.getIntRecord().bc); + mHistBC->Fill(digit.getBC()); + + for (auto& entry : mMapPmModuleChannels) { + for (const auto& chData : vecChData) { + if (std::find(entry.second.begin(), entry.second.end(), chData.mPMNumber) != entry.second.end()) { + mMapPmModuleBcOrbit[entry.first]->Fill(digit.getOrbit() % sOrbitsPerTF, digit.getBC()); + break; + } + } + if (entry.first == "TCM" && isTCM && (digit.mTriggers.getDataIsValid())) { + mMapPmModuleBcOrbit[entry.first]->Fill(digit.getOrbit() % sOrbitsPerTF, digit.getBC()); + } + } + + for (const auto& chData : vecChData) { + mHistTime2Ch->Fill(static_cast(chData.mPMNumber), static_cast(chData.mTime)); + mHistAmp2Ch->Fill(static_cast(chData.mPMNumber), static_cast(chData.mChargeADC)); + mStateLastIR2Ch[chData.mPMNumber] = digit.mIntRecord; + + if (chData.mChargeADC > 0) + mHistNumADC->Fill(chData.mPMNumber); + mHistNumCFD->Fill(chData.mPMNumber); + + if (mSetRefPMTChIDs.find(static_cast(chData.mPMNumber)) != mSetRefPMTChIDs.end()) { + mMapHistAmpVsBC[chData.mPMNumber]->Fill(chData.mChargeADC, digit.getIntRecord().bc); + } + + for (const auto& entry : mMapChTrgNames) { + if ((chData.mFEEBits & (1 << entry.first))) { + mHistChDataBits->Fill(chData.mPMNumber, entry.first); + } + } + } + mHistCFDEff->Reset(); + mHistCFDEff->Divide(mHistNumADC.get(), mHistNumCFD.get()); + } + mTimeSum += curTfTimeMax - curTfTimeMin; +} + +void DigitQcTaskLaser::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + mHistCycleDuration->SetBinContent(1., mTimeSum); + mHistCycleDuration->SetEntries(mTimeSum); + ILOG(Debug) << "Cycle duration: NTF=" << mTfCounter << ", range = " << (mTimeMaxNS - mTimeMinNS) / 1e6 / mTfCounter << " ms/TF, sum = " << mTimeSum / 1e6 / mTfCounter << " ms/TF" << ENDM; +} + +void DigitQcTaskLaser::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void DigitQcTaskLaser::reset() +{ + // clean all the monitor objects here + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistOrbit2BC->Reset(); + mHistBC->Reset(); + mHistChDataBits->Reset(); + mHistCFDEff->Reset(); + mHistNumADC->Reset(); + mHistNumCFD->Reset(); + mHistCycleDuration->Reset(); + for (auto& entry : mMapHistAmpVsBC) { + entry.second->Reset(); + } + for (auto& entry : mMapPmModuleBcOrbit) { + entry.second->Reset(); + } +} + +} // namespace o2::quality_control_modules::fdd diff --git a/Modules/FIT/FDD/src/GenericCheck.cxx b/Modules/FIT/FDD/src/GenericCheck.cxx new file mode 100644 index 0000000000..26d67bc695 --- /dev/null +++ b/Modules/FIT/FDD/src/GenericCheck.cxx @@ -0,0 +1,340 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GenericCheck.cxx +/// \author Sebastian Bysiak +/// LATEST modification for FDD on 25.04.2023 (akhuntia@cern.ch) + +#include "FDD/GenericCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fdd +{ + +SingleCheck GenericCheck::getCheckFromConfig(std::string paramName) +{ + bool isActive = false; + float thresholdWarning = TMath::SignalingNaN(), thresholdError = TMath::SignalingNaN(); + bool shouldBeLower = true; + if (paramName.find("Max") != string::npos || paramName.find("max") != string::npos) { + shouldBeLower = true; + } else if (paramName.find("Min") != string::npos || paramName.find("min") != string::npos) { + shouldBeLower = false; + } + + if (auto paramWarning = mCustomParameters.find(string("thresholdWarning") + paramName), paramError = mCustomParameters.find(string("thresholdError") + paramName); paramWarning != mCustomParameters.end() || paramError != mCustomParameters.end()) { + if (paramWarning != mCustomParameters.end() && paramError != mCustomParameters.end()) { + thresholdWarning = stof(paramWarning->second); + thresholdError = stof(paramError->second); + isActive = true; + if ((shouldBeLower && thresholdWarning > thresholdError) || (!shouldBeLower && thresholdWarning < thresholdError)) { + ILOG(Warning, Support) << "configure(): warning more strict than error -> swapping values!" << ENDM; + std::swap(thresholdWarning, thresholdError); + } + ILOG(Debug, Support) << "configure() : using thresholdWarning" << paramName.c_str() << " = " << thresholdWarning << " , thresholdError" << paramName << " = " << thresholdError << ENDM; + } else { + ILOG(Warning, Support) << "configure() : only one threshold (warning/error) was provided for " << paramName.c_str() << " -> this parameter will not be used!" << ENDM; + } + } + return SingleCheck(paramName, thresholdWarning, thresholdError, shouldBeLower, isActive); +} + +void GenericCheck::configure() +{ + mCheckMaxThresholdY = getCheckFromConfig("MaxThresholdY"); + mCheckMinThresholdY = getCheckFromConfig("MinThresholdY"); + + mCheckMaxOverflowIntegralRatio = getCheckFromConfig("MaxOverflowIntegralRatio"); + mCheckMinMeanX = getCheckFromConfig("MinMeanX"); + mCheckMaxMeanX = getCheckFromConfig("MaxMeanX"); + mCheckMaxStddevX = getCheckFromConfig("MaxStddevX"); + + mCheckMinMeanY = getCheckFromConfig("MinMeanY"); + mCheckMaxMeanY = getCheckFromConfig("MaxMeanY"); + mCheckMaxStddevY = getCheckFromConfig("MaxStddevY"); + + mCheckMinGraphLastPoint = getCheckFromConfig("MinGraphLastPoint"); + mCheckMaxGraphLastPoint = getCheckFromConfig("MaxGraphLastPoint"); + + // Set path to ccdb to get DeadChannelMap + if (auto param = mCustomParameters.find("ccdbUrl"); param != mCustomParameters.end()) { + setCcdbUrl(param->second); + ILOG(Info, Support) << "configure() : using deadChannelMap from CCDB, configured url = " << param->second << ENDM; + } else { + setCcdbUrl("alice-ccdb.cern.ch"); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, default url = " + << "alice-ccdb.cern.ch" << ENDM; + } + + // Set internal path to DeadChannelMap + if (auto param = mCustomParameters.find("pathDeadChannelMap"); param != mCustomParameters.end()) { + mPathDeadChannelMap = param->second; + ILOG(Debug, Support) << "configure() : using pathDeadChannelMap: " << mPathDeadChannelMap << ENDM; + } else { + mPathDeadChannelMap = "FDD/Calib/DeadChannelMap"; + ILOG(Debug, Support) << "configure() : using default pathDeadChannelMap: " << mPathDeadChannelMap << ENDM; + } + + // Align mDeadChannelMap with downloaded one + mDeadChannelMap = retrieveConditionAny(mPathDeadChannelMap); + if (!mDeadChannelMap || !mDeadChannelMap->map.size()) { + ILOG(Error, Support) << "object \"" << mPathDeadChannelMap << "\" NOT retrieved (or empty). All channels assumed to be alive!" << ENDM; + mDeadChannelMap = new o2::fit::DeadChannelMap(); + for (uint8_t chId = 0; chId < sNCHANNELSPhy; ++chId) { + mDeadChannelMap->setChannelAlive(chId, 1); + } + } + + // Print DeadChannelMap + mDeadChannelMapStr = ""; + for (unsigned chId = 0; chId < mDeadChannelMap->map.size(); chId++) { + if (!mDeadChannelMap->isChannelAlive(chId)) { + mDeadChannelMapStr += (mDeadChannelMapStr.empty() ? "" : ",") + std::to_string(chId); + } + } + if (mDeadChannelMapStr.empty()) { + mDeadChannelMapStr = "EMPTY"; + } + ILOG(Info, Support) << "Loaded dead channel map: " << mDeadChannelMapStr << ENDM; + + mPositionMsgBox = { 0.15, 0.75, 0.85, 0.9 }; + if (auto param = mCustomParameters.find("positionMsgBox"); param != mCustomParameters.end()) { + stringstream ss(param->second); + int i = 0; + while (ss.good()) { + if (i > 4) { + ILOG(Info, Support) << "Skipping next values provided for positionMsgBox" << ENDM; + break; + } + string substr; + getline(ss, substr, ','); + mPositionMsgBox[i] = std::stod(substr); + i++; + } + float minHeight = 0.09, minWidth = 0.19; + if (mPositionMsgBox[2] - mPositionMsgBox[0] < minWidth || mPositionMsgBox[3] - mPositionMsgBox[1] < minHeight) { + mPositionMsgBox = { 0.15, 0.75, 0.85, 0.9 }; + ILOG(Info, Support) << "MsgBox too small: returning to default" << ENDM; + } + } + + if (auto param = mCustomParameters.find("nameObjOnCanvas"); param != mCustomParameters.end()) { + mNameObjOnCanvas = param->second; + ILOG(Info, Support) << "nameObjOnCanvas set to " << mNameObjOnCanvas << ENDM; + } else { + mNameObjOnCanvas = "unspecified"; + } +} + +Quality GenericCheck::check(std::map>* moMap) +{ + Quality result = Quality::Good; + for (auto& [moName, mo] : *moMap) { + if (!mo) { + result.set(Quality::Null); + ILOG(Error, Support) << "MO " << moName << " not found" << ENDM; + continue; + } + if (std::strcmp(mo->getObject()->ClassName(), "TCanvas") == 0) { + auto g = (TGraph*)((TCanvas*)mo->getObject())->GetListOfPrimitives()->FindObject(mNameObjOnCanvas.c_str()); + + if (!g) { + result.set(Quality::Null); + ILOG(Error, Support) << "Object " << mNameObjOnCanvas << " inside MO " << moName << " not found" << ENDM; + continue; + } + + if (mCheckMinGraphLastPoint.isActive()) + mCheckMinGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + if (mCheckMaxGraphLastPoint.isActive()) + mCheckMaxGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + } + if (std::strcmp(mo->getObject()->ClassName(), "TGraph") == 0) { + auto g = dynamic_cast(mo->getObject()); + + if (!g) { + result.set(Quality::Null); + ILOG(Error, Support) << "Object inside MO " << moName << " not found" << ENDM; + continue; + } + + if (mCheckMinGraphLastPoint.isActive()) + mCheckMinGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + if (mCheckMaxGraphLastPoint.isActive()) + mCheckMaxGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + + } else { + auto h = dynamic_cast(mo->getObject()); + if (!h) { + result.set(Quality::Null); + ILOG(Error, Support) << "Object inside MO " << moName << " not found" << ENDM; + continue; + } + + if (mCheckMinThresholdY.isActive()) { + float minValue = h->GetBinContent(1); + for (int channel = 1; channel < h->GetNbinsX(); ++channel) { + if (channel >= sNCHANNELSPhy || !mDeadChannelMap->isChannelAlive(channel)) { + continue; + } + if (minValue > h->GetBinContent(channel)) { + minValue = h->GetBinContent(channel); + mCheckMinThresholdY.mBinNumberX = channel; + } + } + mCheckMinThresholdY.doCheck(result, minValue); + } + + if (mCheckMaxThresholdY.isActive()) { + if (mDeadChannelMap->isChannelAlive(h->GetMaximumBin())) { + mCheckMaxThresholdY.mBinNumberX = h->GetMaximumBin(); + mCheckMaxThresholdY.doCheck(result, h->GetBinContent(mCheckMaxThresholdY.mBinNumberX)); + } else { + float maxValue = 0; + for (int channel = 1; channel < h->GetNbinsX(); ++channel) { + if (channel >= sNCHANNELSPhy || !mDeadChannelMap->isChannelAlive(channel)) { + continue; + } + if (maxValue < h->GetBinContent(channel)) { + maxValue = h->GetBinContent(channel); + mCheckMaxThresholdY.mBinNumberX = channel; + } + } + mCheckMaxThresholdY.doCheck(result, maxValue); + } + } + + if (mCheckMinMeanX.isActive()) + mCheckMinMeanX.doCheck(result, h->GetMean()); + if (mCheckMaxMeanX.isActive()) + mCheckMaxMeanX.doCheck(result, h->GetMean()); + if (mCheckMaxStddevX.isActive()) + mCheckMaxStddevX.doCheck(result, h->GetStdDev()); + + if (mCheckMinMeanY.isActive()) + mCheckMinMeanY.doCheck(result, h->GetMean(2)); + if (mCheckMaxMeanY.isActive()) + mCheckMaxMeanY.doCheck(result, h->GetMean(2)); + if (mCheckMaxStddevY.isActive()) + mCheckMaxStddevY.doCheck(result, h->GetStdDev(2)); + + if (mCheckMaxOverflowIntegralRatio.isActive()) { + float integralWithoutOverflow = 0., overflow = 0.; + if (h->GetDimension() == 1) { + integralWithoutOverflow = h->Integral(); + overflow = h->GetBinContent(h->GetNbinsX() + 1); + } else if (h->GetDimension() == 2) { + // for 2D include these overflows: (over,over), (over,in-range), (in-range, over) + TH2* h2 = (TH2*)h->Clone(); // TH2 needed for Integral() with both axis specified + integralWithoutOverflow = h2->Integral(); + float integralWithOverflow = h2->Integral(1, h2->GetNbinsX() + 1, 1, h2->GetNbinsY() + 1); + overflow = integralWithOverflow - integralWithoutOverflow; + } + mCheckMaxOverflowIntegralRatio.doCheck(result, overflow / integralWithoutOverflow); + } + } + } + return result; +} + +void GenericCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (!mo) { + ILOG(Error, Support) << "beautify(): MO not found" << ENDM; + return; + } + + TPaveText* msg = new TPaveText(mPositionMsgBox[0], mPositionMsgBox[1], mPositionMsgBox[2], mPositionMsgBox[3], "NDC"); + if (std::strcmp(mo->getObject()->ClassName(), "TCanvas") == 0) { + auto g = (TGraph*)((TCanvas*)mo->getObject())->GetListOfPrimitives()->FindObject(mNameObjOnCanvas.c_str()); + if (!g) { + ILOG(Error, Support) << "beautify(): Object " << mNameObjOnCanvas << " inside MO " << mo->GetName() << " not found" << ENDM; + return; + } + g->GetListOfFunctions()->Add(msg); + } else if (std::strcmp(mo->getObject()->ClassName(), "TGraph") == 0) { + auto g = dynamic_cast(mo->getObject()); + if (!g) { + ILOG(Error, Support) << "beautify(): Object inside MO " << mo->GetName() << " not found" << ENDM; + return; + } + g->GetListOfFunctions()->Add(msg); + } else { + auto h = dynamic_cast(mo->getObject()); + if (!h) { + ILOG(Error, Support) << "beautify(): Object inside MO " << mo->GetName() << " not found" << ENDM; + return; + } + h->GetListOfFunctions()->Add(msg); + // add threshold lines + if (mCheckMinThresholdY.isActive()) { + Double_t xMin = h->GetXaxis()->GetXmin(); + Double_t xMax = h->GetXaxis()->GetXmax(); + auto* lineMinError = new TLine(xMin, mCheckMinThresholdY.getThresholdError(), xMax, mCheckMinThresholdY.getThresholdError()); + auto* lineMinWarning = new TLine(xMin, mCheckMinThresholdY.getThresholdWarning(), xMax, mCheckMinThresholdY.getThresholdWarning()); + lineMinError->SetLineWidth(3); + lineMinWarning->SetLineWidth(3); + lineMinError->SetLineStyle(kDashed); + lineMinWarning->SetLineStyle(kDashed); + lineMinError->SetLineColor(kRed); + lineMinWarning->SetLineColor(kOrange); + h->GetListOfFunctions()->Add(lineMinError); + h->GetListOfFunctions()->Add(lineMinWarning); + } + } + + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + auto flags = checkResult.getFlags(); + for (int i = 0; i < int(flags.size()); i++) { + msg->AddText(flags[i].second.c_str()); + if (i > 4) { + msg->AddText("et al ... "); + break; + } + } + int color = kBlack; + if (checkResult == Quality::Good) { + color = kGreen + 1; + msg->AddText(">> Quality::Good <<"); + } else if (checkResult == Quality::Medium) { + color = kOrange - 1; + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Bad) { + color = kRed; + msg->AddText(">> Quality::Bad <<"); + } + msg->SetFillStyle(1); + msg->SetLineWidth(3); + msg->SetLineColor(color); + msg->SetShadowColor(color); + msg->SetTextColor(color); + msg->SetMargin(0.0); +} + +} // namespace o2::quality_control_modules::fdd diff --git a/Modules/FIT/FDD/src/OutOfBunchCollCheck.cxx b/Modules/FIT/FDD/src/OutOfBunchCollCheck.cxx new file mode 100644 index 0000000000..9b377f2f97 --- /dev/null +++ b/Modules/FIT/FDD/src/OutOfBunchCollCheck.cxx @@ -0,0 +1,163 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file OutOfBunchCollCheck.cxx +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#include "FDD/OutOfBunchCollCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsFIT/Triggers.h" +// ROOT +#include +#include +#include +#include + +#include +#include +#include "Common/Utils.h" + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fdd +{ + +void OutOfBunchCollCheck::configure() +{ + if (auto param = mCustomParameters.find("thresholdWarning"); param != mCustomParameters.end()) { + mThreshWarning = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdWarning = " << mThreshWarning << ENDM; + } else { + mThreshWarning = 1e-3; + ILOG(Debug, Support) << "configure() : using default thresholdWarning = " << mThreshWarning << ENDM; + } + + if (auto param = mCustomParameters.find("thresholdError"); param != mCustomParameters.end()) { + mThreshError = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdError = " << mThreshError << ENDM; + } else { + mThreshError = 0.1; + ILOG(Debug, Support) << "configure() : using default thresholdError = " << mThreshError << ENDM; + } + + if (auto param = mCustomParameters.find("binPos"); param != mCustomParameters.end()) { + mBinPos = stoi(param->second); + ILOG(Debug, Support) << "configure() : using binPos = " << mBinPos << ENDM; + } else { + mBinPos = int(o2::fit::Triggers::bitVertex) + 1; + ILOG(Debug, Support) << "configure() : using default binPos = " << mBinPos << ENDM; + } + mEnableMessage = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "enableMessage", true); +} + +Quality OutOfBunchCollCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + TH2F* hOutOfBunchColl = 0; + float integralBcOrbitMap = 0; + float integralOutOfBunchColl = 0; + bool metadataFound = false; + const std::string metadataKey = "BcVsTrgIntegralBin" + std::to_string(mBinPos); + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName().find("OutOfBunchColl") != std::string::npos) { + hOutOfBunchColl = dynamic_cast(mo->getObject()); + for (auto metainfo : mo->getMetadataMap()) { + if (metainfo.first == metadataKey) { + integralBcOrbitMap = std::stof(metainfo.second); + metadataFound = true; + } + } + } + } + std::string reason = ""; + if (!integralBcOrbitMap) + reason = Form("Cannot compute quality due to zero integ in BcOrbitMap"); + if (!metadataFound) + reason = Form("Cannot compute quality due to missing metadata: %s", metadataKey.c_str()); + if (!hOutOfBunchColl) + reason = Form("Cannot compute quality due to problem with retieving MO"); + if (reason != "") { + result.set(Quality::Null); + result.addFlag(FlagTypeFactory::Unknown(), reason); + ILOG(Warning) << reason << ENDM; + return result; + } + + result = Quality::Good; + mFractionOutOfBunchColl = 0; + mNumNonEmptyBins = 0; + + integralOutOfBunchColl = hOutOfBunchColl->Integral(1, sBCperOrbit, mBinPos, mBinPos); + mFractionOutOfBunchColl = integralOutOfBunchColl / integralBcOrbitMap; + + ILOG(Debug, Support) << "in checker: integralBcOrbitMap:" << integralBcOrbitMap << " integralOutOfBunchColl: " << integralOutOfBunchColl << " -> fraction: " << mFractionOutOfBunchColl << ENDM; + + if (mFractionOutOfBunchColl > mThreshError) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::Unknown(), + Form("fraction of out of bunch collisions (%.2e) is above \"Error\" threshold (%.2e)", + mFractionOutOfBunchColl, mThreshError)); + } else if (mFractionOutOfBunchColl > mThreshWarning) { + result.set(Quality::Medium); + result.addFlag(FlagTypeFactory::Unknown(), + Form("fraction of out of bunch collisions (%.2e) is above \"Warning\" threshold (%.2e)", + mFractionOutOfBunchColl, mThreshWarning)); + } + + for (int i = 1; i < hOutOfBunchColl->GetNbinsX() + 1; i++) { + for (int j = 1; j < hOutOfBunchColl->GetNbinsY() + 1; j++) { + if (hOutOfBunchColl->GetBinContent(i, j)) + mNumNonEmptyBins++; + } + } + result.addMetadata("numNonEmptyBins", std::to_string(mNumNonEmptyBins)); + return result; +} + +void OutOfBunchCollCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH2F*, skipping" << ENDM; + return; + } + if (!mEnableMessage) { + return; + } + TPaveText* msg = new TPaveText(0.1, 0.9, 0.9, 0.95, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + msg->SetTextAlign(12); + std::string prefix = Form("Fraction of out of bunch collisions = %.2e (Warning > %.2e, Error > %.2e) ", mFractionOutOfBunchColl, mThreshWarning, mThreshError); + + if (checkResult == Quality::Good) { + msg->SetFillColor(kGreen); + msg->AddText((prefix + ">> Quality::Good <<").c_str()); + } else if (checkResult == Quality::Bad) { + msg->SetFillColor(kRed); + msg->AddText((prefix + ">> Quality::Bad <<").c_str()); + } else if (checkResult == Quality::Medium) { + msg->SetFillColor(kOrange); + msg->AddText((prefix + ">> Quality::Medium <<").c_str()); + } else if (checkResult == Quality::Null) { + msg->SetFillColor(kGray); + msg->AddText((prefix + ">> Quality::Null <<").c_str()); + } +} + +} // namespace o2::quality_control_modules::fdd diff --git a/Modules/FIT/FDD/src/OutOfBunchCollFeeModulesCheck.cxx b/Modules/FIT/FDD/src/OutOfBunchCollFeeModulesCheck.cxx new file mode 100644 index 0000000000..4e924f6e31 --- /dev/null +++ b/Modules/FIT/FDD/src/OutOfBunchCollFeeModulesCheck.cxx @@ -0,0 +1,155 @@ +// Copyright 2023 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "FDD/OutOfBunchCollFeeModulesCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsParameters/GRPLHCIFData.h" + +// ROOT +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fdd +{ + +void OutOfBunchCollFeeModulesCheck::configure() +{ + if (auto param = mCustomParameters.find("thresholdWarning"); param != mCustomParameters.end()) { + mThreshWarning = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdWarning = " << mThreshWarning << ENDM; + } else { + mThreshWarning = 1e-3; + ILOG(Debug, Support) << "configure() : using default thresholdWarning = " << mThreshWarning << ENDM; + } + + if (auto param = mCustomParameters.find("thresholdError"); param != mCustomParameters.end()) { + mThreshError = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdError = " << mThreshError << ENDM; + } else { + mThreshError = 0.1; + ILOG(Debug, Support) << "configure() : using default thresholdError = " << mThreshError << ENDM; + } + + if (mThreshError < mThreshWarning) { + ILOG(Debug, Support) << "thresholdError lower than thresholdWarning. Swaping values." << ENDM; + std::swap(mThreshError, mThreshWarning); + } +} + +Quality OutOfBunchCollFeeModulesCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName().find("OutOfBunchColl_BCvsFeeModules") != std::string::npos) { + auto* histogram = dynamic_cast(mo->getObject()); + + if (!histogram) { + ILOG(Error, Support) << "check(): MO " << mo->getName() << " not found" << ENDM; + result.addFlag(FlagTypeFactory::Unknown(), "MO " + mo->getName() + " not found"); + result.set(Quality::Null); + return result; + } + + std::vector allCollPerFeeModule(mo->getMetadataMap().size() + 1, 0); + for (auto metainfo : mo->getMetadataMap()) { + int bin = 0; + float value = 0; + try { + bin = std::stoi(metainfo.first); + value = std::stof(metainfo.second); + allCollPerFeeModule[bin] = value; + } catch (const std::invalid_argument& e) { + ILOG(Warning, Support) << "Could not get value for key " << metainfo.first << ENDM; + continue; + } + } + + // Calculate out-of-bunch-coll fraction for Fee Modules + for (int binY = 1; binY <= histogram->GetNbinsY(); binY++) { + auto outOfBcCollisions = histogram->Integral(1, sBCperOrbit, binY, binY); + float fraction = 0; + if (allCollPerFeeModule[binY]) { + fraction = outOfBcCollisions / allCollPerFeeModule[binY]; + } + + if (fraction > mFractionOutOfBunchColl) { + mFractionOutOfBunchColl = fraction; + } + } + + // Check the biggest fraction of out-of-bunch-coll + if (mFractionOutOfBunchColl > mThreshError) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::Unknown(), + Form("Fraction of out of bunch collisions (%.2e) is above \"Error\" threshold (%.2e)", + mFractionOutOfBunchColl, mThreshError)); + } else if (mFractionOutOfBunchColl > mThreshWarning) { + result.set(Quality::Medium); + result.addFlag(FlagTypeFactory::Unknown(), + Form("Fraction of out of bunch collisions (%.2e) is above \"Warning\" threshold (%.2e)", + mFractionOutOfBunchColl, mThreshWarning)); + } else { + result.set(Quality::Good); + } + } + } + + return result; +} + +void OutOfBunchCollFeeModulesCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName().find("OutOfBunchColl_BCvsFeeModules") != std::string::npos) { + auto* histogram = dynamic_cast(mo->getObject()); + + if (!histogram) { + ILOG(Error, Support) << "beautify(): MO " << mo->getName() << " not found" << ENDM; + return; + } + + TPaveText* msg = new TPaveText(0.1, 0.85, 0.9, 0.9, "NDC"); + histogram->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + if (checkResult.isWorseThan(Quality::Good)) { + msg->AddText(checkResult.getFlags()[0].second.c_str()); + } + int color = kWhite; + if (checkResult == Quality::Good) { + color = kGreen + 1; + msg->AddText(">> Quality::Good <<"); + } else if (checkResult == Quality::Medium) { + color = kOrange - 1; + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Bad) { + color = kRed; + msg->AddText(">> Quality::Bad <<"); + } + msg->SetFillColor(color); + } +} + +} // namespace o2::quality_control_modules::fdd \ No newline at end of file diff --git a/Modules/FIT/FDD/src/PostProcTask.cxx b/Modules/FIT/FDD/src/PostProcTask.cxx new file mode 100644 index 0000000000..f6f07cb6c7 --- /dev/null +++ b/Modules/FIT/FDD/src/PostProcTask.cxx @@ -0,0 +1,603 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcTask.cxx +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#include "FDD/PostProcTask.h" +#include "QualityControl/QcInfoLogger.h" +#include "CommonConstants/LHCConstants.h" +#include "DataFormatsParameters/GRPLHCIFData.h" +#include "Common/Utils.h" + +#include "FITCommon/HelperHist.h" +#include "FITCommon/HelperCommon.h" + +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::fit; + +namespace o2::quality_control_modules::fdd +{ + +PostProcTask::~PostProcTask() +{ + delete mAmpl; + delete mTime; + for (auto& [_, histo] : mMapTrgHistBC) { + delete histo; + histo = nullptr; + } +} + +void PostProcTask::configure(const boost::property_tree::ptree& config) +{ + mCcdbUrl = config.get_child("qc.config.conditionDB.url").get_value(); + + const char* configPath = Form("qc.postprocessing.%s", getID().c_str()); + const char* configCustom = Form("%s.custom", configPath); + ILOG(Info, Support) << "configPath = " << configPath << ENDM; + auto cfgPath = [&configCustom](const std::string& entry) { + return Form("%s.%s", configCustom, entry.c_str()); + }; + + auto node = config.get_child_optional(Form("%s.custom.pathGrpLhcIf", configPath)); + if (node) { + mPathGrpLhcIf = node.get_ptr()->get_child("").get_value(); + ILOG(Debug, Support) << "configure() : using pathBunchFilling = \"" << mPathGrpLhcIf << "\"" << ENDM; + } else { + mPathGrpLhcIf = "GLO/Config/GRPLHCIF"; + ILOG(Debug, Support) << "configure() : using default pathBunchFilling = \"" << mPathGrpLhcIf << "\"" << ENDM; + } + + node = config.get_child_optional(Form("%s.custom.numOrbitsInTF", configPath)); + if (node) { + mNumOrbitsInTF = std::stoi(node.get_ptr()->get_child("").get_value()); + ILOG(Debug, Support) << "configure() : using numOrbitsInTF = " << mNumOrbitsInTF << ENDM; + } else { + mNumOrbitsInTF = 256; + ILOG(Debug, Support) << "configure() : using default numOrbitsInTF = " << mNumOrbitsInTF << ENDM; + } + + node = config.get_child_optional(Form("%s.custom.cycleDurationMoName", configPath)); + if (node) { + mCycleDurationMoName = node.get_ptr()->get_child("").get_value(); + ILOG(Debug, Support) << "configure() : using cycleDurationMoName = \"" << mCycleDurationMoName << "\"" << ENDM; + } else { + mCycleDurationMoName = "CycleDurationNTF"; + ILOG(Debug, Support) << "configure() : using default cycleDurationMoName = \"" << mCycleDurationMoName << "\"" << ENDM; + } + + node = config.get_child_optional(Form("%s.custom.pathDigitQcTask", configPath)); + if (node) { + mPathDigitQcTask = node.get_ptr()->get_child("").get_value(); + ILOG(Debug, Support) << "configure() : using pathDigitQcTask = \"" << mPathDigitQcTask << "\"" << ENDM; + } else { + mPathDigitQcTask = "FDD/MO/DigitQcTask/"; + ILOG(Debug, Support) << "configure() : using default pathDigitQcTask = \"" << mPathDigitQcTask << "\"" << ENDM; + } + + node = config.get_child_optional(Form("%s.custom.timestampSourceLhcIf", configPath)); + if (node) { + mTimestampSourceLhcIf = node.get_ptr()->get_child("").get_value(); + if (mTimestampSourceLhcIf == "last" || mTimestampSourceLhcIf == "trigger" || mTimestampSourceLhcIf == "metadata" || mTimestampSourceLhcIf == "validUntil") { + ILOG(Debug, Support) << "configure() : using timestampSourceLhcIf = \"" << mTimestampSourceLhcIf << "\"" << ENDM; + } else { + auto prev = mTimestampSourceLhcIf; + mTimestampSourceLhcIf = "trigger"; + ILOG(Warning, Support) << "configure() : invalid value for timestampSourceLhcIf = \"" << prev + << "\"\n available options are \"last\", \"trigger\" or \"metadata\"" + << "\n fallback to default: \"" << mTimestampSourceLhcIf << "\"" << ENDM; + } + } else { + mTimestampSourceLhcIf = "trigger"; + ILOG(Debug, Support) << "configure() : using default timestampSourceLhcIf = \"" << mTimestampSourceLhcIf << "\"" << ENDM; + } + mLowTimeThreshold = helper::getConfigFromPropertyTree(config, Form("%s.lowTimeThreshold", configCustom), -192); + mUpTimeThreshold = helper::getConfigFromPropertyTree(config, Form("%s.upTimeThreshold", configCustom), 192); + mLowAmpSat = helper::getConfigFromPropertyTree(config, Form("%s.lowAmpSat", configCustom), 1.); + mUpAmpSat = helper::getConfigFromPropertyTree(config, Form("%s.upAmpSat", configCustom), 3600.); + mTimestampMetaField = helper::getConfigFromPropertyTree(config, cfgPath("timestampMetaField"), "timestampTF"); + + // TO REMOVE + // VERY BAD SOLUTION, YOU SHOULDN'T USE IT + const std::string del = ","; + const std::string strChannelIDs = helper::getConfigFromPropertyTree(config, cfgPath("channelIDs"), ""); + const std::string strHistsToDecompose = helper::getConfigFromPropertyTree(config, cfgPath("histsToDecompose"), ""); + if (strChannelIDs.size() > 0 && strHistsToDecompose.size() > 0) { + mVecChannelIDs = helper::parseParameters(strChannelIDs, del); + mVecHistsToDecompose = helper::parseParameters(strHistsToDecompose, del); + } +} + +void PostProcTask::initialize(Trigger, framework::ServiceRegistryRef services) +{ + // delete any objects from previous runs + mRateOrA.reset(); + mRateOrC.reset(); + mRateVertex.reset(); + mRateCentral.reset(); + mRateSemiCentral.reset(); + mHistChDataNegBits.reset(); + mHistTriggers.reset(); + + mHistTimeInWindow.reset(); + mHistCFDEff.reset(); + mHistTrgValidation.reset(); + mHistAmpSaturation.reset(); + mRatesCanv.reset(); + + delete mAmpl; + mAmpl = nullptr; + delete mTime; + mTime = nullptr; + + mHistBcPattern.reset(); + mHistBcPatternFee.reset(); + mHistBcTrgOutOfBunchColl.reset(); + mHistBcFeeOutOfBunchCollForVtxTrg.reset(); + + for (auto& [_, histo] : mMapTrgHistBC) { + delete histo; + histo = nullptr; + } + mMapTrgHistBC.clear(); + + // start initialization + mDatabase = &services.get(); + mCcdbApi.init(mCcdbUrl); + + mRateOrA = std::make_unique(0); + mRateOrC = std::make_unique(0); + mRateVertex = std::make_unique(0); + mRateCentral = std::make_unique(0); + mRateSemiCentral = std::make_unique(0); + // mRatesCanv = std::make_unique("cRates", "trigger rates"); + mAmpl = new TProfile("MeanAmplPerChannel", "mean ampl per channel;Channel;Ampl #mu #pm #sigma", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mTime = new TProfile("MeanTimePerChannel", "mean time per channel;Channel;Time #mu #pm #sigma", sNCHANNELS_PM, 0, sNCHANNELS_PM); + + mRateOrA->SetNameTitle("rateOrA", "trg rate: OrA;cycle;rate [kHz]"); + mRateOrC->SetNameTitle("rateOrC", "trg rate: OrC;cycle;rate [kHz]"); + mRateVertex->SetNameTitle("rateVertex", "trg rate: Vertex;cycle;rate [kHz]"); + mRateCentral->SetNameTitle("rateCentral", "trg rate: Central;cycle;rate [kHz]"); + mRateSemiCentral->SetNameTitle("rateSemiCentral", "trg rate: SemiCentral;cycle;rate [kHz]"); + + mRateOrA->SetMarkerStyle(24); + mRateOrC->SetMarkerStyle(25); + mRateVertex->SetMarkerStyle(26); + mRateCentral->SetMarkerStyle(27); + mRateSemiCentral->SetMarkerStyle(28); + mRateOrA->SetMarkerColor(kOrange); + mRateOrC->SetMarkerColor(kMagenta); + mRateVertex->SetMarkerColor(kBlack); + mRateCentral->SetMarkerColor(kBlue); + mRateSemiCentral->SetMarkerColor(kOrange); + mRateOrA->SetLineColor(kOrange); + mRateOrC->SetLineColor(kMagenta); + mRateVertex->SetLineColor(kBlack); + mRateCentral->SetLineColor(kBlue); + mRateSemiCentral->SetLineColor(kOrange); + + mHistChDataNegBits = std::make_unique("ChannelDataNegBits", "ChannelData negative bits per ChannelID;Channel;Negative bit", sNCHANNELS_PM, 0, sNCHANNELS_PM, mMapPMbits.size(), 0, mMapPMbits.size()); + for (const auto& entry : mMapPMbits) { + std::string stBitName = "! " + entry.second; + mHistChDataNegBits->GetYaxis()->SetBinLabel(entry.first + 1, stBitName.c_str()); + } + getObjectsManager()->startPublishing(mHistChDataNegBits.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistChDataNegBits.get(), "COLZ"); + + mHistTriggers = std::make_unique("Triggers", "Triggers from TCM", mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + mHistBcPattern = std::make_unique("bcPattern", "BC pattern", sBCperOrbit, 0, sBCperOrbit, mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + mHistBcTrgOutOfBunchColl = std::make_unique("OutOfBunchColl_BCvsTrg", "BC vs Triggers for out-of-bunch collisions;BC;Triggers", sBCperOrbit, 0, sBCperOrbit, mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + for (const auto& entry : mMapTechTrgBits) { + mHistTriggers->GetXaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistBcPattern->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistBcTrgOutOfBunchColl->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + + // depends on triggers set to bits 0-N + if (entry.first >= mNumTriggers) + continue; + auto pairHistBC = mMapTrgHistBC.insert({ entry.first, new TH1D(Form("BC_%s", entry.second.c_str()), Form("BC for %s trigger;BC;counts;", entry.second.c_str()), sBCperOrbit, 0, sBCperOrbit) }); + if (pairHistBC.second) { + getObjectsManager()->startPublishing(pairHistBC.first->second, quality_control::core::PublicationPolicy::ThroughStop); + } + } + const auto& lut = o2::fdd::SingleLUT::Instance().getVecMetadataFEE(); + auto lutSorted = lut; + std::sort(lutSorted.begin(), lutSorted.end(), [](const auto& first, const auto& second) { return first.mModuleName < second.mModuleName; }); + uint8_t binPos{ 0 }; + for (const auto& lutEntry : lutSorted) { + const auto& moduleName = lutEntry.mModuleName; + const auto& moduleType = lutEntry.mModuleType; + const auto& strChID = lutEntry.mChannelID; + const auto& pairIt = mMapFEE2hash.insert({ moduleName, binPos }); + if (pairIt.second) { + binPos++; + } + if (std::regex_match(strChID, std::regex("[[\\d]{1,3}"))) { + int chID = std::stoi(strChID); + if (chID < sNCHANNELS_PM) { + mChID2PMhash[chID] = mMapFEE2hash[moduleName]; + } else { + ILOG(Error, Support) << "Incorrect LUT entry: chID " << strChID << " | " << moduleName << ENDM; + } + } else if (moduleType != "TCM") { + ILOG(Error, Support) << "Non-TCM module w/o numerical chID: chID " << strChID << " | " << moduleName << ENDM; + } else if (moduleType == "TCM") { + uint8_t mTCMhash = mMapFEE2hash[moduleName]; + } + } + mHistBcFeeOutOfBunchCollForVtxTrg = std::make_unique("OutOfBunchColl_BCvsFeeModulesForVtxTrg", "BC vs FEE Modules for out-of-bunch collisions for Vertex trg;BC;FEE Modules", sBCperOrbit, 0, sBCperOrbit, mMapFEE2hash.size(), 0, mMapFEE2hash.size()); + mHistBcPatternFee = std::make_unique("bcPatternForFeeModules", "BC pattern", sBCperOrbit, 0, sBCperOrbit, mMapFEE2hash.size(), 0, mMapFEE2hash.size()); + for (const auto& entry : mMapFEE2hash) { + // ILOG(Warning, Support) << "============= mMapFEE2hash.second + 1: " << entry.second + 1 + // << " mMapFEE2hash.first.c_str(): " << entry.first.c_str() << ENDM; + + mHistBcPatternFee->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcFeeOutOfBunchCollForVtxTrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + } + + getObjectsManager()->startPublishing(mHistBcFeeOutOfBunchCollForVtxTrg.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcFeeOutOfBunchCollForVtxTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcPatternFee.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcPatternFee.get(), "COLZ"); + + getObjectsManager()->startPublishing(mHistTriggers.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mHistBcPattern.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcPattern.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcTrgOutOfBunchColl.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcTrgOutOfBunchColl.get(), "COLZ"); + + getObjectsManager()->startPublishing(mRateOrA.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mRateOrC.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mRateVertex.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mRateCentral.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mRateSemiCentral.get(), quality_control::core::PublicationPolicy::ThroughStop); + // getObjectsManager()->startPublishing(mRatesCanv.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mAmpl, quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mTime, quality_control::core::PublicationPolicy::ThroughStop); + + for (int i = 0; i < getObjectsManager()->getNumberPublishedObjects(); i++) { + TH1* obj = dynamic_cast(getObjectsManager()->getMonitorObject(i)->getObject()); + if (obj != nullptr) { + obj->SetTitle((std::string("FDD ") + obj->GetTitle()).c_str()); + } + } + + mHistTrgValidation = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "TrgValidation", "FDD SW + HW only to validated triggers fraction", mMapTrgBits); + mHistTimeInWindow = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "TimeInWindowFraction", Form("FDD Fraction of events with CFD in time gate(%i,%i) vs ChannelID;ChannelID;Event fraction with CFD in time gate", mLowTimeThreshold, mUpTimeThreshold), sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistCFDEff = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "CFD_efficiency", "FDD Fraction of events with CFD in ADC gate vs ChannelID;ChannelID;Event fraction with CFD in ADC gate;", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistAmpSaturation = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "AmpSaturation", Form("FDD Fraction of charge in [%d, %d] ADC;ChannelID;Fraction", static_cast(mLowAmpSat), static_cast(mUpAmpSat)), sNCHANNELS_PM, 0, sNCHANNELS_PM); +} + +void PostProcTask::update(Trigger t, framework::ServiceRegistryRef) +{ + auto mo = mDatabase->retrieveMO(mPathDigitQcTask, "TriggersCorrelation", t.timestamp, t.activity); + auto hTrgCorr = mo ? dynamic_cast(mo->getObject()) : nullptr; + mHistTriggers->Reset(); + auto getBinContent2Ddiag = [](TH2* hist, const std::string& binName) { + return hist->GetBinContent(hist->GetXaxis()->FindBin(binName.c_str()), hist->GetYaxis()->FindBin(binName.c_str())); + }; + if (!hTrgCorr) { + ILOG(Error) << "MO \"TriggersCorrelation\" NOT retrieved!!!" << ENDM; + } else { + double totalStat{ 0 }; + for (int iBin = 1; iBin < mHistTriggers->GetXaxis()->GetNbins() + 1; iBin++) { + std::string binName{ mHistTriggers->GetXaxis()->GetBinLabel(iBin) }; + const auto binContent = getBinContent2Ddiag(hTrgCorr, binName); + mHistTriggers->SetBinContent(iBin, getBinContent2Ddiag(hTrgCorr, binName)); + totalStat += binContent; + } + mHistChDataNegBits->SetEntries(totalStat); + } + + auto moChDataBits = mDatabase->retrieveMO(mPathDigitQcTask, "ChannelDataBits", t.timestamp, t.activity); + auto hChDataBits = moChDataBits ? dynamic_cast(moChDataBits->getObject()) : nullptr; + if (!hChDataBits) { + ILOG(Error) << "MO \"ChannelDataBits\" NOT retrieved!!!" << ENDM; + } + auto moStatChannelID = mDatabase->retrieveMO(mPathDigitQcTask, "StatChannelID", t.timestamp, t.activity); + auto hStatChannelID = moStatChannelID ? dynamic_cast(moStatChannelID->getObject()) : nullptr; + if (!hStatChannelID) { + ILOG(Error) << "MO \"StatChannelID\" NOT retrieved!!!" << ENDM; + } + mHistChDataNegBits->Reset(); + if (hChDataBits != nullptr && hStatChannelID != nullptr) { + double totalStat{ 0 }; + for (int iBinX = 1; iBinX < hChDataBits->GetXaxis()->GetNbins() + 1; iBinX++) { + for (int iBinY = 1; iBinY < hChDataBits->GetYaxis()->GetNbins() + 1; iBinY++) { + const double nStatTotal = hStatChannelID->GetBinContent(iBinX); + const double nStatPMbit = hChDataBits->GetBinContent(iBinX, iBinY); + const double nStatNegPMbit = nStatTotal - nStatPMbit; + totalStat += nStatNegPMbit; + mHistChDataNegBits->SetBinContent(iBinX, iBinY, nStatNegPMbit); + } + } + mHistChDataNegBits->SetEntries(totalStat); + } + + auto mo2 = mDatabase->retrieveMO(mPathDigitQcTask, mCycleDurationMoName, t.timestamp, t.activity); + auto hCycleDuration = mo2 ? dynamic_cast(mo2->getObject()) : nullptr; + if (!hCycleDuration) { + ILOG(Error) << "MO \"" << mCycleDurationMoName << "\" NOT retrieved!!!" << ENDM; + } + + if (hTrgCorr && hCycleDuration) { + double cycleDurationMS = 0; + if (mCycleDurationMoName == "CycleDuration" || mCycleDurationMoName == "CycleDurationRange") + // assume MO stores cycle duration in ns + cycleDurationMS = hCycleDuration->GetBinContent(1) / 1e6; // ns -> ms + else if (mCycleDurationMoName == "CycleDurationNTF") + // assume MO stores cycle duration in number of TF + cycleDurationMS = hCycleDuration->GetBinContent(1) * mNumOrbitsInTF * o2::constants::lhc::LHCOrbitNS / 1e6; // ns ->ms + + int n = mRateOrA->GetN(); + + double eps = 1e-8; + if (cycleDurationMS < eps) { + ILOG(Warning) << "cycle duration = " << cycleDurationMS << " ms, almost zero - cannot compute trigger rates!" << ENDM; + } else { + mRateOrA->SetPoint(n, n, getBinContent2Ddiag(hTrgCorr, "OrA") / cycleDurationMS); + mRateOrC->SetPoint(n, n, getBinContent2Ddiag(hTrgCorr, "OrC") / cycleDurationMS); + mRateVertex->SetPoint(n, n, getBinContent2Ddiag(hTrgCorr, "Vertex") / cycleDurationMS); + mRateCentral->SetPoint(n, n, getBinContent2Ddiag(hTrgCorr, "Central") / cycleDurationMS); + mRateSemiCentral->SetPoint(n, n, getBinContent2Ddiag(hTrgCorr, "SemiCentral") / cycleDurationMS); + } + /* + mRatesCanv->cd(); + float vmin = std::min({ mRateOrA->GetYaxis()->GetXmin(), mRateOrC->GetYaxis()->GetXmin(), mRateVertex->GetYaxis()->GetXmin(), mRateCentral->GetYaxis()->GetXmin(), mRateSemiCentral->GetYaxis()->GetXmin() }); + float vmax = std::max({ mRateOrA->GetYaxis()->GetXmax(), mRateOrC->GetYaxis()->GetXmax(), mRateVertex->GetYaxis()->GetXmax(), mRateCentral->GetYaxis()->GetXmax(), mRateSemiCentral->GetYaxis()->GetXmax() }); + + auto hAxis = mRateOrA->GetHistogram(); + hAxis->GetYaxis()->SetTitleOffset(1.4); + hAxis->SetMinimum(vmin); + hAxis->SetMaximum(vmax * 1.1); + hAxis->SetTitle("FDD trigger rates"); + hAxis->SetLineWidth(0); + hAxis->Draw("AXIS"); + + mRateOrA->Draw("PL,SAME"); + mRateOrC->Draw("PL,SAME"); + mRateVertex->Draw("PL,SAME"); + mRateCentral->Draw("PL,SAME"); + mRateSemiCentral->Draw("PL,SAME"); + TLegend* leg = gPad->BuildLegend(); + leg->SetFillStyle(1); + mRatesCanv->Modified(); + mRatesCanv->Update(); + */ + } + + auto mo3 = mDatabase->retrieveMO(mPathDigitQcTask, "AmpPerChannel", t.timestamp, t.activity); + auto hAmpPerChannel = mo3 ? dynamic_cast(mo3->getObject()) : nullptr; + if (!hAmpPerChannel) { + ILOG(Error) << "MO \"AmpPerChannel\" NOT retrieved!!!" + << ENDM; + } else { + { + std::unique_ptr projNom(hAmpPerChannel->ProjectionX("projNom", hAmpPerChannel->GetYaxis()->FindBin(1.0), -1)); + std::unique_ptr projDen(hAmpPerChannel->ProjectionX("projDen")); + mHistCFDEff->Divide(projNom.get(), projDen.get()); + } + { + std::unique_ptr projNom(hAmpPerChannel->ProjectionX("projNom", hAmpPerChannel->GetYaxis()->FindBin(mLowAmpSat), hAmpPerChannel->GetYaxis()->FindBin(mUpAmpSat))); + std::unique_ptr projDen(hAmpPerChannel->ProjectionX("projDen", hAmpPerChannel->GetYaxis()->FindBin(mLowAmpSat), hAmpPerChannel->GetNbinsY())); + mHistAmpSaturation->Divide(projNom.get(), projDen.get()); + } + } + auto mo4 = mDatabase->retrieveMO(mPathDigitQcTask, "TimePerChannel", t.timestamp, t.activity); + auto hTimePerChannel = mo4 ? dynamic_cast(mo4->getObject()) : nullptr; + if (!hTimePerChannel) { + ILOG(Error) << "MO \"TimePerChannel\" NOT retrieved!!!" + << ENDM; + } else { + std::unique_ptr projInWindow(hTimePerChannel->ProjectionX("projInWindow", hTimePerChannel->GetYaxis()->FindBin(mLowTimeThreshold), hTimePerChannel->GetYaxis()->FindBin(mUpTimeThreshold))); + std::unique_ptr projFull(hTimePerChannel->ProjectionX("projFull")); + mHistTimeInWindow->Divide(projInWindow.get(), projFull.get()); + } + + if (hAmpPerChannel && hTimePerChannel) { + mAmpl = hAmpPerChannel->ProfileX("MeanAmplPerChannel"); + mTime = hTimePerChannel->ProfileX("MeanTimePerChannel"); + mAmpl->SetErrorOption("s"); + mTime->SetErrorOption("s"); + // for some reason the styling is not preserved after assigning result of ProfileX/Y() to already existing object + mAmpl->SetMarkerStyle(8); + mTime->SetMarkerStyle(8); + mAmpl->SetLineColor(kBlack); + mTime->SetLineColor(kBlack); + mAmpl->SetDrawOption("P"); + mTime->SetDrawOption("P"); + mAmpl->GetXaxis()->SetTitleOffset(1); + mTime->GetXaxis()->SetTitleOffset(1); + mAmpl->GetYaxis()->SetTitleOffset(1); + mTime->GetYaxis()->SetTitleOffset(1); + } + + auto moBCvsTriggers = mDatabase->retrieveMO(mPathDigitQcTask, "BCvsTriggers", t.timestamp, t.activity); + auto hBcVsTrg = moBCvsTriggers ? dynamic_cast(moBCvsTriggers->getObject()) : nullptr; + if (!hBcVsTrg) { + ILOG(Error, Support) << "MO \"BCvsTriggers\" NOT retrieved!!!" << ENDM; + return; + } + + for (const auto& entry : mMapTrgHistBC) { + hBcVsTrg->ProjectionX(entry.second->GetName(), entry.first + 1, entry.first + 1); + } + + long ts = 999; + if (mTimestampSourceLhcIf == "last") { + ts = -1; + } else if (mTimestampSourceLhcIf == "trigger") { + ts = t.timestamp; + } else if (mTimestampSourceLhcIf == "validUntil") { + ts = t.activity.mValidity.getMax(); + } else if (mTimestampSourceLhcIf == "metadata") { + for (auto metainfo : moBCvsTriggers->getMetadataMap()) { + if (metainfo.first == "TFcreationTime") + ts = std::stol(metainfo.second); + } + if (ts > 1651500000000 && ts < 1651700000000) + ILOG(Warning, Support) << "timestamp (read from TF via metadata) points to 02-04 May 2022" + " - make sure this is the data we are processing and not the default timestamp " + "(it may appear when running on digits w/o providing \"--hbfutils-config o2_tfidinfo.root\")" + << ENDM; + if (ts == 999) { + ILOG(Error) << "\"TFcreationTime\" not found in metadata, fallback to ts from trigger " << ENDM; + ts = t.timestamp; + } + } + + std::map metadata; + std::map headers; + auto* lhcIf = mCcdbApi.retrieveFromTFileAny(mPathGrpLhcIf, metadata, ts, &headers); + if (!lhcIf) { + ILOG(Error, Support) << "object \"" << mPathGrpLhcIf << "\" NOT retrieved. OutOfBunchColTask will not produce valid QC plots." << ENDM; + return; + } + const std::string bcName = lhcIf->getInjectionScheme(); + if (bcName.size() == 8) { + if (bcName.compare("no_value")) { + ILOG(Error, Support) << "Filling scheme not set. OutOfBunchColTask will not produce valid QC plots." << ENDM; + } + } else { + ILOG(Info, Support) << "Filling scheme: " << bcName.c_str() << ENDM; + } + auto bcPattern = lhcIf->getBunchFilling(); + + mHistBcPattern->Reset(); + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapTechTrgBits.size() + 1; j++) { + mHistBcPattern->SetBinContent(i + 1, j + 1, bcPattern.testBC(i)); + } + } + + // Create histogram with bc pattern for FEE modules + mHistBcPatternFee->Reset(); + for (int i = 0; i < sBCperOrbit; i++) { + for (int j = 0; j < mMapFEE2hash.size(); j++) { + mHistBcPatternFee->SetBinContent(i + 1, j + 1, bcPattern.testBC(i)); + } + } + + mHistBcTrgOutOfBunchColl->Reset(); + float vmax = hBcVsTrg->GetBinContent(hBcVsTrg->GetMaximumBin()); + mHistBcTrgOutOfBunchColl->Add(hBcVsTrg, mHistBcPattern.get(), 1, -1 * vmax); + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapTechTrgBits.size() + 1; j++) { + if (mHistBcTrgOutOfBunchColl->GetBinContent(i + 1, j + 1) < 0) { + mHistBcTrgOutOfBunchColl->SetBinContent(i + 1, j + 1, 0); // is it too slow? + } + } + } + mHistBcTrgOutOfBunchColl->SetEntries(mHistBcTrgOutOfBunchColl->Integral(1, sBCperOrbit, 1, mMapTechTrgBits.size())); + for (int iBin = 1; iBin < mMapTechTrgBits.size() + 1; iBin++) { + const std::string metadataKey = "BcVsTrgIntegralBin" + std::to_string(iBin); + const std::string metadataValue = std::to_string(hBcVsTrg->Integral(1, sBCperOrbit, iBin, iBin)); + getObjectsManager()->getMonitorObject(mHistBcTrgOutOfBunchColl->GetName())->addOrUpdateMetadata(metadataKey, metadataValue); + ILOG(Info, Support) << metadataKey << ":" << metadataValue << ENDM; + } + + auto moTriggersSoftwareVsTCM = mDatabase->retrieveMO(mPathDigitQcTask, "TriggersSoftwareVsTCM", t.timestamp, t.activity); + auto hTriggersSoftwareVsTCM = moTriggersSoftwareVsTCM ? dynamic_cast(moTriggersSoftwareVsTCM->getObject()) : nullptr; + if (hTriggersSoftwareVsTCM != nullptr) { + std::unique_ptr projOnlyHWorSW(hTriggersSoftwareVsTCM->ProjectionX("projOnlyHWorSW", 1, 2)); + std::unique_ptr projValidatedSWandHW(hTriggersSoftwareVsTCM->ProjectionX("projValidatedSWandHW", 4, 4)); + projOnlyHWorSW->LabelsDeflate(); + projValidatedSWandHW->LabelsDeflate(); + mHistTrgValidation->Divide(projOnlyHWorSW.get(), projValidatedSWandHW.get()); + } + // Download histogram BCvsFEEmodulesForVtxTrg from database + auto moBcVsFeeModulesForVtxTrg = mDatabase->retrieveMO(mPathDigitQcTask, "BCvsFEEmodulesForVtxTrg", t.timestamp, t.activity); + auto hBcVsFeeModulesForVtxTrg = moBcVsFeeModulesForVtxTrg ? dynamic_cast(moBcVsFeeModulesForVtxTrg->getObject()) : nullptr; + if (!hBcVsFeeModulesForVtxTrg) { + ILOG(Error, Support) << "MO \"BCvsFEEmodulesForVtxTrg\" NOT retrieved!!!" << ENDM; + return; + } else { + mHistBcFeeOutOfBunchCollForVtxTrg->Reset(); + float vmax = hBcVsFeeModulesForVtxTrg->GetBinContent(hBcVsFeeModulesForVtxTrg->GetMaximumBin()); + mHistBcFeeOutOfBunchCollForVtxTrg->Add(hBcVsFeeModulesForVtxTrg, mHistBcPatternFee.get(), 1, -1 * vmax); + for (int i = 0; i < sBCperOrbit; i++) { + for (int j = 0; j < mMapFEE2hash.size(); j++) { + if (mHistBcFeeOutOfBunchCollForVtxTrg->GetBinContent(i + 1, j + 1) < 0) { + mHistBcFeeOutOfBunchCollForVtxTrg->SetBinContent(i + 1, j + 1, 0); + } + } + } + // Add metadata to histogram OutOfBunchColl_BCvsFeeModulesForOrATrg + mHistBcFeeOutOfBunchCollForVtxTrg->SetEntries(mHistBcFeeOutOfBunchCollForVtxTrg->Integral(1, sBCperOrbit, 1, mMapFEE2hash.size())); + for (int iBin = 1; iBin <= mMapFEE2hash.size(); iBin++) { + const std::string metadataKey = std::to_string(iBin); + const std::string metadataValue = std::to_string(hBcVsFeeModulesForVtxTrg->Integral(1, sBCperOrbit, iBin, iBin)); + getObjectsManager()->getMonitorObject(mHistBcFeeOutOfBunchCollForVtxTrg->GetName())->addOrUpdateMetadata(metadataKey, metadataValue); + } + } + decomposeHists(t); + setTimestampToMOs(ts); +} +void PostProcTask::decomposeHists(Trigger trg) +{ + for (const auto& histName : mVecHistsToDecompose) { + auto insertedMap = mMapHistsToDecompose.insert({ histName, {} }); + auto& mapHists = insertedMap.first->second; + + auto mo = mDatabase->retrieveMO(mPathDigitQcTask, histName, trg.timestamp, trg.activity); + auto histSrcPtr = mo ? dynamic_cast(mo->getObject()) : nullptr; + + if (histSrcPtr == nullptr) { + continue; + } + const auto bins = histSrcPtr->GetYaxis()->GetNbins(); + const auto binLow = histSrcPtr->GetYaxis()->GetXmin(); + const auto binUp = histSrcPtr->GetYaxis()->GetXmax(); + + for (const auto& chID : mVecChannelIDs) { + auto insertedHistDst = mapHists.insert({ chID, nullptr }); + auto& histDstPtr = insertedHistDst.first->second; + auto isInserted = insertedHistDst.second; + if (isInserted == true) { + // creation in first iter + const std::string suffix = std::string{ Form("%03i", chID) }; + const std::string newHistName = histName + std::string{ "_" } + suffix; + const std::string newHistTitle = histSrcPtr->GetTitle() + std::string{ " " } + suffix; + histDstPtr = std::make_shared(newHistName.c_str(), newHistTitle.c_str(), bins, binLow, binUp); + getObjectsManager()->startPublishing(histDstPtr.get()); + } + histDstPtr->Reset(); + // making projection + const auto binPos = chID + 1; + const std::unique_ptr proj(histSrcPtr->ProjectionY("proj", binPos, binPos)); + histDstPtr->Add(proj.get()); + } + } +} + +void PostProcTask::setTimestampToMOs(long long timestamp) +{ + for (int iObj = 0; iObj < getObjectsManager()->getNumberPublishedObjects(); iObj++) { + auto mo = getObjectsManager()->getMonitorObject(iObj); + mo->addOrUpdateMetadata(mTimestampMetaField, std::to_string(timestamp)); + } +} + +void PostProcTask::finalize(Trigger t, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::fdd diff --git a/Modules/FIT/FDD/src/RecPointsQcTask.cxx b/Modules/FIT/FDD/src/RecPointsQcTask.cxx new file mode 100644 index 0000000000..7d7b014ad4 --- /dev/null +++ b/Modules/FIT/FDD/src/RecPointsQcTask.cxx @@ -0,0 +1,273 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RecPointsQcTask.cxx +/// \author Artur Furs afurs@cern.ch +/// developed by A Khuntia for FDD akhuntia@cern.ch + +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include "CommonConstants/PhysicsConstants.h" + +namespace o2::quality_control_modules::fdd +{ + +RecPointsQcTask::~RecPointsQcTask() +{ + delete mListHistGarbage; +} + +void RecPointsQcTask::rebinFromConfig() +{ + /* Examples: + "binning_SumAmpC": "100, 0, 100" + "binning_BcOrbitMap_TrgOrA": "25, 0, 256, 10, 0, 3564" + hashtag = all channel IDs (mSetAllowedChIDs), e.g. + "binning_Amp_channel#": "5,-10,90" + is equivalent to: + "binning_Amp_channel0": "5,-10,90" + "binning_Amp_channel1": "5,-10,90" + "binning_Amp_channel2": "5,-10,90" ... + */ + auto rebinHisto = [](std::string hName, std::string binning) { + std::vector tokenizedBinning; + boost::split(tokenizedBinning, binning, boost::is_any_of(",")); + if (tokenizedBinning.size() == 3) { // TH1 + ILOG(Debug) << "config: rebinning TH1 " << hName << " -> " << binning << ENDM; + auto htmp = (TH1F*)gROOT->FindObject(hName.data()); + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str())); + } else if (tokenizedBinning.size() == 6) { // TH2 + auto htmp = (TH2F*)gROOT->FindObject(hName.data()); + ILOG(Debug) << "config: rebinning TH2 " << hName << " -> " << binning << ENDM; + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str()), + std::atof(tokenizedBinning[3].c_str()), std::atof(tokenizedBinning[4].c_str()), std::atof(tokenizedBinning[5].c_str())); + } else { + ILOG(Warning) << "config: invalid binning parameter: " << hName << " -> " << binning << ENDM; + } + }; + const std::string rebinKeyword = "binning"; + const char* channelIdPlaceholder = "#"; + try { + for (auto& param : mCustomParameters.getAllDefaults()) { + if (param.first.rfind(rebinKeyword, 0) != 0) + continue; + std::string hName = param.first.substr(rebinKeyword.length() + 1); + std::string binning = param.second.c_str(); + if (hName.find(channelIdPlaceholder) != std::string::npos) { + for (const auto& chID : mSetAllowedChIDs) { + std::string hNameCur = hName.substr(0, hName.find(channelIdPlaceholder)) + std::to_string(chID) + hName.substr(hName.find(channelIdPlaceholder) + 1); + rebinHisto(hNameCur, binning); + } + } else if (!gROOT->FindObject(hName.data())) { + ILOG(Warning) << "config: histogram named \"" << hName << "\" not found" << ENDM; + continue; + } else { + rebinHisto(hName, binning); + } + } + } catch (std::out_of_range& oor) { + ILOG(Error) << "Cannot access the default custom parameters : " << oor.what() << ENDM; + } +} + +void RecPointsQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Info, Support) << "@@@@initialize RecoQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + mStateLastIR2Ch = {}; + + mHistTime2Ch = std::make_unique("TimePerChannel", "Time vs Channel;Channel;Time [ns]", NCHANNELS, 0, NCHANNELS, 420, -10.50, 10.50); + mVertexVsCollTimeAllBC = std::make_unique("VertexVsCollTimeAllBC", "FDD vertex vs Collision time;FDD vertex (cm); Collision Time [ns]", 200, -100.5, 100.5, 200, -20.5, 20.5); + mVertexVsCollTimeVertexTrigger = std::make_unique("VertexVsCollTimeVertexTrigger", "FDD vertex vs Collision time (Vertex trigger);FDD vertex (cm); Collision Time [ns]", 200, -100.5, 100.5, 200, -20.5, 20.5); + mVertexNsVsCollTimeAllBC = std::make_unique("VertexNsVsCollTimeAllBC", "FDD vertex vs Collision time;FDD vertex (ns); Collision Time [ns]", 200, -20.5, 20.5, 200, -20.5, 20.5); + mVertexNsVsCollTimeVertexTrigger = std::make_unique("VertexNsVsCollTimeVertexTrigger", "FDD vertex vs Collision time (Vertex trigger);FDD vertex (ns); Collision Time [ns]", 200, -20.5, 20.5, 200, -20.5, 20.5); + mTimeAvsTimeC = std::make_unique("TimeAvsTimeC", "FDD time A vs time C;time A (ns);time C (ns)", 200, -80.5, 80.5, 200, -80.5, 80.5); + mHistAmp2Ch = std::make_unique("AmpPerChannel", "Amplitude vs Channel;Channel;Amp [#ADC channels]", NCHANNELS, 0, NCHANNELS, 2200, -100, 4100); + mHistCollTimeA = std::make_unique("CollTimeA", "T0A;Time [ns]", 4100, -20.5, 20.5); + mHistCollTimeC = std::make_unique("CollTimeC", "T0C;Time [ns]", 4100, -20.5, 20.5); + mHistBC = std::make_unique("BC", "BC;BC;counts;", sBCperOrbit, 0, sBCperOrbit); + mHistBCVetex = std::make_unique("BCVetex", "BC Vertex trigger;BC;counts;", sBCperOrbit, 0, sBCperOrbit); + mHistBCorA = std::make_unique("BCorA", "BC orA;BC;counts;", sBCperOrbit, 0, sBCperOrbit); + mHistBCorC = std::make_unique("BCorC", "BC orC;BC;counts;", sBCperOrbit, 0, sBCperOrbit); + mListHistGarbage = new TList(); + mListHistGarbage->SetOwner(kTRUE); + + std::vector vecChannelIDs; + if (auto param = mCustomParameters.find("ChannelIDs"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDs = parseParameters(chIDs, del); + } else { + for (unsigned int iCh = 0; iCh < sNCHANNELS_PM; iCh++) + vecChannelIDs.push_back(iCh); + } + for (const auto& entry : vecChannelIDs) { + mSetAllowedChIDs.insert(entry); + } + getObjectsManager()->startPublishing(mHistTime2Ch.get()); + getObjectsManager()->setDefaultDrawOptions(mHistTime2Ch.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistAmp2Ch.get()); + getObjectsManager()->setDefaultDrawOptions(mHistAmp2Ch.get(), "COLZ"); + getObjectsManager()->startPublishing(mVertexVsCollTimeAllBC.get()); + getObjectsManager()->setDefaultDrawOptions(mVertexVsCollTimeAllBC.get(), "COLZ"); + getObjectsManager()->startPublishing(mVertexVsCollTimeVertexTrigger.get()); + getObjectsManager()->setDefaultDrawOptions(mVertexVsCollTimeVertexTrigger.get(), "COLZ"); + getObjectsManager()->startPublishing(mVertexNsVsCollTimeAllBC.get()); + getObjectsManager()->setDefaultDrawOptions(mVertexNsVsCollTimeAllBC.get(), "COLZ"); + getObjectsManager()->startPublishing(mVertexNsVsCollTimeVertexTrigger.get()); + getObjectsManager()->setDefaultDrawOptions(mVertexNsVsCollTimeVertexTrigger.get(), "COLZ"); + getObjectsManager()->startPublishing(mTimeAvsTimeC.get()); + getObjectsManager()->setDefaultDrawOptions(mTimeAvsTimeC.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistCollTimeA.get()); + getObjectsManager()->startPublishing(mHistCollTimeC.get()); + getObjectsManager()->startPublishing(mHistBC.get()); + getObjectsManager()->startPublishing(mHistBCVetex.get()); + getObjectsManager()->startPublishing(mHistBCorA.get()); + getObjectsManager()->startPublishing(mHistBCorC.get()); + + for (const auto& chID : mSetAllowedChIDs) { + auto pairHistAmpVsTime = mMapHistAmpVsTime.insert({ chID, new TH2F(Form("Amp_vs_time_channel%i", chID), Form("Amplitude vs time, channel %i;Amp;Time (ns)", chID), 1000, -100, 4100, 100, -20.5, 20.5) }); + if (pairHistAmpVsTime.second) { + mListHistGarbage->Add(pairHistAmpVsTime.first->second); + getObjectsManager()->startPublishing(pairHistAmpVsTime.first->second); + getObjectsManager()->setDefaultDrawOptions(pairHistAmpVsTime.first->second, "COLZ"); + } + } + + ILOG(Info, Support) << "@@@ histos created" << ENDM; + rebinFromConfig(); // after all histos are created +} + +void RecPointsQcTask::startOfActivity(const Activity& activity) +{ + ILOG(Info, Support) << "@@@@ startOfActivity" << activity.mId << ENDM; + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mVertexVsCollTimeAllBC->Reset(); + mVertexVsCollTimeVertexTrigger->Reset(); + mVertexNsVsCollTimeAllBC->Reset(); + mVertexNsVsCollTimeVertexTrigger->Reset(); + mTimeAvsTimeC->Reset(); + mHistCollTimeA->Reset(); + mHistCollTimeC->Reset(); + mHistBC->Reset(); + mHistBCVetex->Reset(); + mHistBCorA->Reset(); + mHistBCorC->Reset(); + for (auto& entry : mMapHistAmpVsTime) { + entry.second->Reset(); + } +} + +void RecPointsQcTask::startOfCycle() +{ + mTimeMinNS = -1; + mTimeMaxNS = 0.; + mTimeCurNS = 0.; + mTfCounter = 0; + mTimeSum = 0.; +} + +void RecPointsQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + double curTfTimeMin = -1; + double curTfTimeMax = 0; + mTfCounter++; + auto chan = ctx.inputs().get>("channels"); + auto recpoints = ctx.inputs().get>("recpoints"); + bool isFirst = true; + uint32_t firstOrbit; + bool isTCM = true; + for (auto& recpoint : recpoints) { + if (recpoint.getTrigger().getTimeA() == fit::Triggers::DEFAULT_TIME && recpoint.getTrigger().getTimeC() == fit::Triggers::DEFAULT_TIME) { + isTCM = false; + } + o2::fdd::Triggers triggersignals = recpoint.getTrigger(); + + auto channels = recpoint.getBunchChannelData(chan); + mHistBC->Fill(recpoint.getInteractionRecord().bc); + + if (isTCM) { + int bc = recpoint.getInteractionRecord().bc; + bool vertexTrigger = triggersignals.getVertex(); + double collisionTimeA = static_cast(recpoint.getCollisionTimeA() * 1.e-3); // time ps-->ns + double collisionTimeC = static_cast(recpoint.getCollisionTimeC() * 1.e-3); // time ps-->ns + mHistCollTimeA->Fill(collisionTimeA); // time ps-->ns + mHistCollTimeC->Fill(collisionTimeC); // time ps-->ns + mTimeAvsTimeC->Fill(collisionTimeA, collisionTimeC); + if ((collisionTimeA > -15 && collisionTimeA < 15) && (collisionTimeC > -15 && collisionTimeC < 15)) { // avoid dummytime + mVertexVsCollTimeAllBC->Fill(((collisionTimeA - collisionTimeC) / 2) * o2::constants::physics::LightSpeedCm2NS, (collisionTimeA + collisionTimeC) / 2); + mVertexNsVsCollTimeAllBC->Fill((collisionTimeA - collisionTimeC) / 2., (collisionTimeA + collisionTimeC) / 2.); + } + if (vertexTrigger) { + mHistBCVetex->Fill(bc); + mVertexVsCollTimeVertexTrigger->Fill(((collisionTimeA - collisionTimeC) / 2) * o2::constants::physics::LightSpeedCm2NS, (collisionTimeA + collisionTimeC) / 2); + mVertexNsVsCollTimeVertexTrigger->Fill((collisionTimeA - collisionTimeC) / 2., (collisionTimeA + collisionTimeC) / 2.); + } + if (triggersignals.getOrA()) { + mHistBCorA->Fill(bc); + } + if (triggersignals.getOrC()) { + mHistBCorC->Fill(bc); + } + } /// TCM + for (const auto& chData : channels) { + mHistTime2Ch->Fill(static_cast(chData.mPMNumber), static_cast(chData.mTime)); + mHistAmp2Ch->Fill(static_cast(chData.mPMNumber), static_cast(chData.mChargeADC)); + if (mSetAllowedChIDs.find(static_cast(chData.mPMNumber)) != mSetAllowedChIDs.end()) { + mMapHistAmpVsTime[chData.mPMNumber]->Fill(chData.mChargeADC, chData.mTime); + } + } + } + mTimeSum += curTfTimeMax - curTfTimeMin; +} + +void RecPointsQcTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + // one has to set num. of entries manually because + // default TH1Reductor gets only mean,stddev and entries (no integral) +} + +void RecPointsQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void RecPointsQcTask::reset() +{ + // clean all the monitor objects here + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mVertexVsCollTimeAllBC->Reset(); + mVertexVsCollTimeVertexTrigger->Reset(); + mVertexNsVsCollTimeAllBC->Reset(); + mVertexNsVsCollTimeVertexTrigger->Reset(); + mTimeAvsTimeC->Reset(); + mHistCollTimeA->Reset(); + mHistCollTimeC->Reset(); + mHistBC->Reset(); + mHistBCVetex->Reset(); + mHistBCorA->Reset(); + mHistBCorC->Reset(); + for (auto& entry : mMapHistAmpVsTime) { + entry.second->Reset(); + } +} +} // namespace o2::quality_control_modules::fdd diff --git a/Modules/FIT/FDD/src/TriggersSwVsTcmCheck.cxx b/Modules/FIT/FDD/src/TriggersSwVsTcmCheck.cxx new file mode 100644 index 0000000000..da8b9045a5 --- /dev/null +++ b/Modules/FIT/FDD/src/TriggersSwVsTcmCheck.cxx @@ -0,0 +1,149 @@ +// Copyright 2023 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TriggersSwVsTcmCheck.cxx +/// \author Dawid Skora dawid.mateusz.skora@cern.ch (FV0) +/// \Modification for FDD by arvind.khuntia@cern.ch (25.04.2023) + +#include "FDD/TriggersSwVsTcmCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fdd +{ + +constexpr int kBinSwOnly = 1; +constexpr int kBinTcmOnly = 2; + +void TriggersSwVsTcmCheck::configure() +{ + if (auto param = mCustomParameters.find("ccdbUrl"); param != mCustomParameters.end()) { + setCcdbUrl(param->second); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, configured url = " << param->second << ENDM; + } else { + setCcdbUrl("o2-ccdb.internal"); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, default url = " + << "o2-ccdb.internal" << ENDM; + } + + mPositionMsgBox = { 0.15, 0.75, 0.85, 0.9 }; + if (auto param = mCustomParameters.find("positionMsgBox"); param != mCustomParameters.end()) { + stringstream ss(param->second); + int i = 0; + while (ss.good()) { + if (i > 4) { + ILOG(Info, Support) << "Skipping next values provided for positionMsgBox" << ENDM; + break; + } + string substr; + getline(ss, substr, ','); + mPositionMsgBox[i] = std::stod(substr); + i++; + } + float minHeight = 0.09, minWidth = 0.19; + if (mPositionMsgBox[2] - mPositionMsgBox[0] < minWidth || mPositionMsgBox[3] - mPositionMsgBox[1] < minHeight) { + mPositionMsgBox = { 0.15, 0.75, 0.85, 0.9 }; + ILOG(Info, Support) << "MsgBox too small: returning to default" << ENDM; + } + } +} + +Quality TriggersSwVsTcmCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + int mNumErrors = 0; + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "TriggersSoftwareVsTCM") { + auto* histogram = dynamic_cast(mo->getObject()); + + if (!histogram) { + ILOG(Error, Support) << "check(): MO TriggersSoftwareVsTCM not found" << ENDM; + result.addFlag(FlagTypeFactory::Unknown(), "MO TriggersSoftwareVsTCM not found"); + result.set(Quality::Null); + return result; + } + + result = Quality::Good; + int numberOfBinsX = histogram->GetNbinsX(); + for (int binId = 1; binId <= numberOfBinsX; binId++) { + if ((bool)(histogram->GetBinContent(binId, kBinSwOnly)) || (bool)(histogram->GetBinContent(binId, kBinTcmOnly))) { + mNumErrors++; + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + result.addFlag(FlagTypeFactory::Unknown(), "Only SW or TCM trigger was activated"); + } + } + } + } + result.addMetadata("nErrors", std::to_string(mNumErrors)); + return result; +} + +void TriggersSwVsTcmCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (!mo) { + ILOG(Error, Support) << "beautify(): MO NULL pointer" << ENDM; + return; + } + + if (mo->getName() == "TriggersSoftwareVsTCM") { + auto* histogram = dynamic_cast(mo->getObject()); + + if (!histogram) { + ILOG(Error, Support) << "beautify(): MO TriggersSoftwareVsTCM not found" << ENDM; + return; + } + + TPaveText* msg = new TPaveText(mPositionMsgBox[0], mPositionMsgBox[1], mPositionMsgBox[2], mPositionMsgBox[3], "NDC"); + histogram->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + auto flags = checkResult.getFlags(); + for (int i = 0; i < int(flags.size()); i++) { + msg->AddText(flags[i].second.c_str()); + } + int color = kBlack; + if (checkResult == Quality::Good) { + color = kGreen + 1; + msg->AddText(">> Quality::Good <<"); + } else if (checkResult == Quality::Medium) { + color = kOrange - 1; + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Bad) { + color = kRed; + msg->AddText(">> Quality::Bad <<"); + } + msg->SetFillStyle(1); + msg->SetLineWidth(3); + msg->SetLineColor(color); + msg->SetShadowColor(color); + msg->SetTextColor(color); + msg->SetMargin(0.0); + } +} + +} // namespace o2::quality_control_modules::fdd diff --git a/Modules/FIT/FDD/test/testQcFDD.cxx b/Modules/FIT/FDD/test/testQcFDD.cxx new file mode 100644 index 0000000000..8f468abda1 --- /dev/null +++ b/Modules/FIT/FDD/test/testQcFDD.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testFDD.cxx +/// \author My Name +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::fdd +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::fdd diff --git a/Modules/FIT/FIT/CMakeLists.txt b/Modules/FIT/FIT/CMakeLists.txt new file mode 100644 index 0000000000..866045ac49 --- /dev/null +++ b/Modules/FIT/FIT/CMakeLists.txt @@ -0,0 +1,90 @@ +# ---- Library ---- +set(MODULE_NAME "O2QcFIT") + +add_library(${MODULE_NAME}) + +target_sources(${MODULE_NAME} PRIVATE src/LevelCheck.cxx) +target_sources(${MODULE_NAME} PRIVATE src/MIPCheck.cxx) +target_sources(${MODULE_NAME} PRIVATE src/RawDataMetricTask.cxx) +target_sources(${MODULE_NAME} PRIVATE src/RecoFITQcTask.cxx) + +target_include_directories( + O2QcFIT + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(${MODULE_NAME} PUBLIC O2QualityControl + O2::DataFormatsFDD + O2::DataFormatsFT0 + O2::DataFormatsFV0 + O2::CommonDataFormat + O2QcCommon + O2QcFITCommon) + +install(TARGETS ${MODULE_NAME} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(${MODULE_NAME} + HEADERS include/FIT/LevelCheck.h + include/FIT/MIPCheck.h + include/FIT/RawDataMetricTask.h + include/FIT/RecoFITQcTask.h + LINKDEF include/FIT/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/FIT + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Executables ---- +# keep commented as an example + +#set(EXE_SRCS +# src/runDataProducer.cxx ) +#set(EXE_NAMES +# o2-qc-ft0-data-producer) +# +#list(LENGTH EXE_SRCS count) +#math(EXPR count "${count}-1") +#foreach(i RANGE ${count}) +# list(GET EXE_SRCS ${i} src) +# list(GET EXE_NAMES ${i} name) +# add_executable(${name} ${src}) +# target_link_libraries(${name} PRIVATE O2QualityControl O2QcFT0 O2::DataFormatsFT0 O2::FITCalibration) +#endforeach() + +#install( +# TARGETS o2-qc-ft0-data-producer +# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +#) + + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcFT0.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcFIT Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + +# ----- Configs ------- +#install(FILES +# ft0-reconstruction-config.json +# DESTINATION etc) + +get_property(dirs + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + PROPERTY INCLUDE_DIRECTORIES) +foreach(dir ${dirs}) + message(STATUS "dir='${dir}'") +endforeach() diff --git a/Modules/FIT/FIT/README.md b/Modules/FIT/FIT/README.md new file mode 100644 index 0000000000..f6cc9d9f10 --- /dev/null +++ b/Modules/FIT/FIT/README.md @@ -0,0 +1,46 @@ +# FIT quality control + +## Checks + +### MIPCheck + +The `MIPCheck` checks peaks in channel amplitude spectra. Essentially it is a check on parameters of Gaussian fits of the MIP peaks. + +#### Check parameters + +The parameters of the check are listed in the table below. For all parameters that take a list as argument, the items in the list corresponds to the different peaks; the first item is for the 1 MIP peak and so on. +If a threshold argument is omitted, that threshold will not be checked. This way, it is also possible to disable checks for a threshold for only some of the peaks. To disable a check for the first peak, but keep it for subsequent peaks, a negative value will also disable a check. + +| Name | Type | Default value | Comments | +|------------------------|------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `nameObjectToCheck` | `string` | `""` | Name of the MO to check | +| `nPeaksToFit` | `integer` | `2` | Number of MIP peaks to fit. The resulting fit funcion is a sum of `nPeaksToFit` Gaussians. | +| `gausParamsMeans` | list of `float` | `""` | Initial fit paramemters for the gaussian means. If omitted, the 1 MIP peak will default to the amplitude at the ampltude histogram max value. Other peaks will default to multiples of the initial 1 MIP peak mean (2 MIP peak mean = 2 * 1 MIP peak mean, and so on). The values are used as initial guesses and are not fixed. | +| `gausParamsSigma` | list of `float` | `"3.0, 7.0"` | The initial guesses for the MIP peak sigmas. Defaults to 5.0 for omitted peaks. | +| `fitRangeLow` | `float` | `11.0` | Lower limit of the fit | +| `fitRangeHigh` | `float` | `35.0` | Upper limit of the fit | +| `meanWarningsLow` | list of `float` | `""` | Lower warning thresholds for the MIP peak means | +| `meanWarningsHigh` | list of `float` | `""` | Upper warning thresholds for the MIP peak means | +| `meanErrorsLow` | list of `float` | `""` | Lower error thresholds for the MIP peak means | +| `meanErrorsHigh` | list of `float` | `""` | Upper error thresholds for the MIP peak means | +| `sigmaWarnings` | list of `float` | `""` | Sigma warning thresholds | +| `sigmaErrors` | list of `float` | `""` | Sigma error thresholds | +| `drawMeanWarningsLow` | list of `bool` | `"false, false"` | Whether to draw the lower warning thresholds for the MIP peak means | +| `drawMeanWarningsHigh` | list of `bool` | `"false, false"` | Whether to draw the upper warning thresholds for the MIP peak means | +| `drawMeanErrorsLow` | list of `bool` | `"true, true"` | Whether to draw the lower error thresholds for the MIP peak | +| `drawMeanErrorsHigh` | list of `bool` | `"true, true"` | Whether to draw the upper error thresholds for the MIP peak means | +| `drawSigmaWarnings` | list of `bool` | `"false, false"` | Whether to draw the sigma warning thresholds | +| `drawSigmaErrors` | list of `bool` | `"false, false"` | Whether to draw the sigma error thresholds | +| `labelPos` | list of `double` | `"0.15, 0.2, 0.85, 0.45"` | Position of the check label | + +#### Check logic + +The check produces the worst quality it finds among the listed checks below. For each failed check, a quality flag is added to the quality that explains the problem. + +For each peak (pseudo code): + +- if the fit fails -> `quality = BAD` +- `if not (meanWarningLow < mean < meanWarningHigh)` -> `quality = MEDIUM` +- `if not (meanErrorLow < mean < meanErrorHigh)` -> `quality = BAD` +- `if not sigma < sigmaWarning` -> `quality = WARNING` +- `if not sigma < sigmaError` -> `quality = BAD` diff --git a/Modules/FIT/FIT/etc/fit_reco.json b/Modules/FIT/FIT/etc/fit_reco.json new file mode 100644 index 0000000000..1e2f60ecd7 --- /dev/null +++ b/Modules/FIT/FIT/etc/fit_reco.json @@ -0,0 +1,44 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch", + "host": "alice-ccdb.cern.ch" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", "": "Set to 1 to discard debug and trace messages (default: false)", + "filterDiscardLevel": "20", "": "Message at this level or above are discarded (default: 21 - Trace)" + } + }, + "tasks": { + "FITRecPoints": { + "active": "true", + "className": "o2::quality_control_modules::fit::RecoFITQcTask", + "moduleName": "QcFIT", + "taskName": "RecPoints", + "detectorName": "FIT", + "cycleDurationSeconds": "60", + "resetAfterCycles": "1", + "dataSource": { + "type": "direct", + "query": "recPointsFDD:FDD/RECPOINTS/0;channelsFDD:FDD/RECCHDATA/0;recPointsFT0:FT0/RECPOINTS/0;channelsFT0:FT0/RECCHDATA/0;recPointsFV0:FV0/RECPOINTS/0;channelsFV0:FV0/RECCHDATA/0" + }, + "taskParameters": { + } + } + } + } +} diff --git a/Modules/FIT/FIT/include/FIT/LevelCheck.h b/Modules/FIT/FIT/include/FIT/LevelCheck.h new file mode 100644 index 0000000000..423efd8edf --- /dev/null +++ b/Modules/FIT/FIT/include/FIT/LevelCheck.h @@ -0,0 +1,70 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RatioCheck.h +/// \author Artur Furs afurs@cern.ch +/// + +#ifndef QC_MODULE_FIT_LEVELCHECK_H +#define QC_MODULE_FIT_LEVELCHECK_H + +#include +#include +#include + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::fit +{ + +class LevelCheck : public o2::quality_control::checker::CheckInterface +{ + public: + LevelCheck() = default; + ~LevelCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void startOfActivity(const Activity& activity) override; + + private: + void updateBinsToIgnoreWithDCM(); + void setTimestamp(const std::shared_ptr& moMetadata); + + std::string mBinsToIgnoreAsStr{ "" }; + std::string mPathDeadChannelMap{ "" }; + std::string mUrlCCDB{ "" }; + std::string mNameObjectToCheck{ "" }; + std::string mMessagePrefixError{ "" }; + std::string mMessagePrefixWarning{ "" }; + std::string mTimestampMetaField{ "timestampMetaField" }; + std::string mTimestampSource{ "" }; + int mNelementsPerLine{ 20 }; + bool mUseBinLabels{ false }; + bool mUseBinError{ false }; + long long mTimestamp{ -1 }; // For fetching CCDB + std::set mBinsToIgnore{}; + bool mIsInvertedThrsh; // check if values should be upper + std::string mSignCheck{ "" }; + float mThreshWarning; + float mThreshError; + int mNumWarnings; + int mNumErrors; + std::vector mVecLabelPos{ 0.15, 0.2, 0.85, 0.45 }; + bool mIsFirstIter{ true }; + ClassDefOverride(LevelCheck, 1); +}; + +} // namespace o2::quality_control_modules::fit + +#endif // QC_MODULE_FIT_LEVELCHECK_H diff --git a/Modules/FIT/FIT/include/FIT/LinkDef.h b/Modules/FIT/FIT/include/FIT/LinkDef.h new file mode 100644 index 0000000000..fb2dbdc537 --- /dev/null +++ b/Modules/FIT/FIT/include/FIT/LinkDef.h @@ -0,0 +1,11 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::fit::LevelCheck + ; +#pragma link C++ class o2::quality_control_modules::fit::MIPCheck + ; +#pragma link C++ class o2::quality_control_modules::fit::RawDataMetricTask + ; +#pragma link C++ class o2::quality_control_modules::fit::RecoFITQcTask + ; + +#endif diff --git a/Modules/FIT/FIT/include/FIT/MIPCheck.h b/Modules/FIT/FIT/include/FIT/MIPCheck.h new file mode 100644 index 0000000000..b5f5a99c23 --- /dev/null +++ b/Modules/FIT/FIT/include/FIT/MIPCheck.h @@ -0,0 +1,100 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MIPCheck.h +/// \author andreas.molander@cern.ch +/// + +#ifndef QC_MODULE_FIT_FITMIPCHECK_H +#define QC_MODULE_FIT_FITMIPCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::fit +{ + +/// \brief QC check on MIP peaks in channel amplitude spectra. Essentially it is a check on +/// parameters of Gaussian fits of the MIP peaks. +/// \author andreas.molander@cern.ch +class MIPCheck : public o2::quality_control::checker::CheckInterface +{ + public: + MIPCheck() = default; + ~MIPCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void startOfActivity(const Activity& activity) override; + + private: + std::shared_ptr mActivity; + + std::string mNameObjectToCheck = ""; //< Name of the MO to check + + /// Number of MIP peaks to fit. The resulting fit funcion is a sum of `mNPeaksToFit` Gaussians. + int mNPeaksToFit = 2; + + /// Initial fit paramemters for the gaussian means. + /// If omitted, the 1 MIP peak will default to the amplitude at the ampltude histogram max value. + /// Other peaks will default to multiples of the initial 1 MIP peak mean (2 MIP peak mean = 2 * 1 MIP peak mean, and so on). + /// The values are used as initial guesses and are not fixed. + std::vector mGausParamsMeans; + + /// The initial fit parameters for the MIP peak sigmas. The values are used as initial guesses and are not fixed. + std::vector mGausParamsSigmas; + + float mFitRangeLow = 11.0; //< Lower limit of the fit + + float mFitRangeHigh = 35.0; //< Upper limit of the fit + + /// Lower warning thresholds for the MIP peak means. + /// The first element is for the first peak and so on. + std::vector mMeanWarningsLow; + + /// Upper warning thresholds for the MIP peak means. + /// The first element is for the first peak and so on. + std::vector mMeanWarningsHigh; + + /// Lower error thresholds for the MIP peak means. + /// The first element is for the first peak and so on. + std::vector mMeanErrorsLow; + + /// Upper error thresholds for the MIP peak means. + /// The first element is for the first peak and so on. + std::vector mMeanErrorsHigh; + + /// Sigma warning thresholds. + /// The first element is for the first peak and so on. + std::vector mSigmaWarnings; + + /// Sigma error thresholds. + /// The first element is for the first peak and so on. + std::vector mSigmaErrors; + + /// Whether to draw the threhold lines. + /// The first element is for the first peak and so on. + std::vector mDrawMeanWarningsLow; + std::vector mDrawMeanWarningsHigh; + std::vector mDrawMeanErrorsLow; + std::vector mDrawMeanErrorsHigh; + std::vector mDrawSigmaWarnings; + std::vector mDrawSigmaErrors; + + std::vector mVecLabelPos{ 0.15, 0.2, 0.85, 0.45 }; //< Position of the check label + + ClassDefOverride(MIPCheck, 3); +}; + +} // namespace o2::quality_control_modules::fit + +#endif // QC_MODULE_FIT_FITMIPCHECK_H diff --git a/Modules/FIT/FIT/include/FIT/RawDataMetricTask.h b/Modules/FIT/FIT/include/FIT/RawDataMetricTask.h new file mode 100644 index 0000000000..209c5f89f2 --- /dev/null +++ b/Modules/FIT/FIT/include/FIT/RawDataMetricTask.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawDataMetricTask.h +/// \author Artur Furs afurs@cern.ch +/// QC task for RawDataMetric QC proccessing at FIT detectors + +#ifndef QC_MODULE_FIT_RAWDATAMETRICTASK_H +#define QC_MODULE_FIT_RAWDATAMETRICTASK_H + +#include +#include + +#include + +#include "Headers/DataHeader.h" +#include "QualityControl/TaskInterface.h" +#include +#include +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::fit +{ + +class RawDataMetricTask final : public TaskInterface +{ + public: + /// \brief Constructor + RawDataMetricTask() = default; + /// Destructor + ~RawDataMetricTask() override; + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + // Objects which will be published + std::string mDetName{ "" }; // detector name + unsigned int mBinPosUnknown; // position for uknown FEE; + std::unique_ptr mHistRawDataMetrics; + std::map mMapFEE2binPos{}; //(epID,linkID)->bin position, alphabet sorted by module name + static const std::set sSetOfAllowedDets; // set of allowed detectors: FDD, FT, FV0 +}; + +} // namespace o2::quality_control_modules::fit + +#endif // QC_MODULE_FIT_RAWDATAMETRICTASK_H diff --git a/Modules/FIT/FIT/include/FIT/RecoFITQcTask.h b/Modules/FIT/FIT/include/FIT/RecoFITQcTask.h new file mode 100644 index 0000000000..21e45733a1 --- /dev/null +++ b/Modules/FIT/FIT/include/FIT/RecoFITQcTask.h @@ -0,0 +1,103 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTask.h +/// \author Artur Furs afurs@cern.ch +/// RecoQC Task for FT0 detector + +#ifndef QC_MODULE_FIT_RECOFITQCTASK_H +#define QC_MODULE_FIT_RECOFITQCTASK_H + +#include + +#include "QualityControl/QcInfoLogger.h" + +#include +#include +#include +#include "CommonConstants/LHCConstants.h" + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FITCommon/DigitSync.h" +#include "FITCommon/DetectorFIT.h" +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::fit +{ + +class RecoFITQcTask final : public TaskInterface +{ + public: + using DigitSyncFIT = DigitSync; + /// \brief Constructor + RecoFITQcTask() = default; + /// Destructor + ~RecoFITQcTask() override; + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + constexpr static int sNBC = o2::constants::lhc::LHCMaxBunches; // 3564 BCs + + bool mIsFDD{ false }; + bool mIsFT0{ false }; + bool mIsFV0{ false }; + + const uint16_t mNTrgBitsFDD = detectorFIT::DetectorFDD::sMapTrgBits.size(); + const uint16_t mNTrgBitsFT0 = detectorFIT::DetectorFT0::sMapTrgBits.size(); + const uint16_t mNTrgBitsFV0 = detectorFIT::DetectorFV0::sMapTrgBits.size(); + const uint16_t mNTrgBitsFT0_FV0 = mNTrgBitsFT0 * mNTrgBitsFV0; + + const float mCFDChannel2NS = 0.01302; // CFD channel width in ns + + std::function mFuncTrgStatusFDD_FT0 = [&](uint8_t trgBitFDD, uint8_t trgBitFT0) { + return static_cast(trgBitFDD) * mNTrgBitsFT0 + static_cast(trgBitFT0); + }; + std::function mFuncTrgStatusFDD_FV0 = [&](uint8_t trgBitFDD, uint8_t trgBitFV0) { + return static_cast(trgBitFDD) * mNTrgBitsFV0 + static_cast(trgBitFV0); + }; + std::function mFuncTrgStatusFT0_FV0 = [&](uint8_t trgBitFT0, uint8_t trgBitFV0) { + return static_cast(trgBitFT0) * mNTrgBitsFV0 + static_cast(trgBitFV0); + }; + std::function mFuncTrgStatusFDD_FT0_FV0 = [&](uint8_t trgBitFDD, uint8_t trgBitFT0, uint8_t trgBitFV0) { + return static_cast(trgBitFDD) * mNTrgBitsFT0_FV0 + static_cast(trgBitFT0) * mNTrgBitsFV0 + static_cast(trgBitFV0); + }; + + // Objects which will be published + std::unique_ptr mHistTrgCorrelationFDD_FT0; + std::unique_ptr mHistTrgCorrelationFDD_FV0; + std::unique_ptr mHistTrgCorrelationFT0_FV0; + // std::unique_ptr mHistTrgCorrelationFDD_FT0_FV0; + std::array, 5> mHistTrgCorrelationFT0_FDD_FV0; +}; + +} // namespace o2::quality_control_modules::fit + +#endif // QC_MODULE_FT0_FT0RecoQcTask_H diff --git a/Modules/FIT/FIT/src/LevelCheck.cxx b/Modules/FIT/FIT/src/LevelCheck.cxx new file mode 100644 index 0000000000..469b0deaa1 --- /dev/null +++ b/Modules/FIT/FIT/src/LevelCheck.cxx @@ -0,0 +1,251 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file LevelCheck.cxx +/// \author Artur Furs afurs@cern.ch +/// + +#include +#include +#include "DataFormatsFIT/DeadChannelMap.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include + +#include +#include +#include "Common/Utils.h" +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fit +{ + +void LevelCheck::updateBinsToIgnoreWithDCM() +{ + if (mPathDeadChannelMap.size() > 0) { + const auto deadChannelMap = retrieveConditionAny(mPathDeadChannelMap, {}, mTimestamp); + for (unsigned chID = 0; chID < deadChannelMap->map.size(); chID++) { + if (!deadChannelMap->isChannelAlive(chID)) { + mBinsToIgnore.insert(chID); + } + } + } +} + +void LevelCheck::startOfActivity(const Activity&) +{ + mIsFirstIter = true; // for SSS +} + +void LevelCheck::configure() +{ + mIsFirstIter = true; + mMessagePrefixWarning = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "messagePrefixWarning", "Warning in bin idxs: "); + mMessagePrefixError = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "messagePrefixError", "Error in bin idxs: "); + mTimestampMetaField = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "timestampMetaField", "timestampTF"); + mTimestampSource = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "timestampSource", "metadata"); + + mThreshWarning = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "thresholdWarning", 0.9); + mThreshError = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "thresholdError", 0.8); + mNameObjectToCheck = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nameObjectToCheck", "CFD_efficiency"); + + mNelementsPerLine = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nElementsPerLine", 20); + mUseBinLabels = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "useBinLabels", false); + mUseBinError = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "useBinError", false); + + mIsInvertedThrsh = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "isInversedThresholds", false); + mSignCheck = mIsInvertedThrsh ? std::string{ ">" } : std::string{ "<" }; + + mPathDeadChannelMap = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "pathDeadChannelMap", ""); + mUrlCCDB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "ccdbUrl", "o2-ccdb.internal"); + setCcdbUrl(mUrlCCDB); + + const std::string labelPos = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "labelPos", "0.15, 0.2, 0.85, 0.45"); + mVecLabelPos = helper::parseParameters(labelPos, ","); + if (mVecLabelPos.size() != 4) { + ILOG(Error, Devel) << "Incorrect label coordinates! Setting default." << ENDM; + mVecLabelPos = { 0.15, 0.2, 0.85, 0.45 }; + } + + const std::string binsToIgnore = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "binsToIgnore", ""); + // Getting bins to ignore directly from config, + // they will be combined with automatic bin selection + if (binsToIgnore.size() > 0) { + const auto vecParams = helper::parseParameters(binsToIgnore, ","); + for (const auto& chId : vecParams) { + mBinsToIgnore.insert(chId); + } + } + // Automatic selection of bins to ignore + // Dead channel map + mBinsToIgnoreAsStr = std::to_string(mBinsToIgnore.size()); +} + +void LevelCheck::setTimestamp(const std::shared_ptr& moMetadata) +{ + // Getting timestamp + if (mTimestampSource == "metadata") { + const auto iterMetadata = moMetadata->getMetadataMap().find(mTimestampMetaField); + const bool isFound = iterMetadata != moMetadata->getMetadataMap().end(); + if (isFound) { + mTimestamp = std::stoll(iterMetadata->second); + } else { + ILOG(Error) << "Cannot find timestamp metadata field " << mTimestampMetaField << " . Setting timestamp to -1" << ENDM; + mTimestamp = -1; + } + } else if (mTimestampSource == "current") { + mTimestamp = -1; + } else { + mTimestamp = -1; + ILOG(Error) << "Uknown timestamp source " << mTimestampSource << " . Setting timestamp to -1" << ENDM; + } +} + +Quality LevelCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + for (const auto& entry : *moMap) { + const auto& mo = entry.second; + if (mo->getName() == mNameObjectToCheck) { + auto hist = dynamic_cast(mo->getObject()); + if (hist == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1* => Quality::Bad" << ENDM; + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), "Cannot get TH1 object from DB"); + continue; + } + if (mIsFirstIter) { + setTimestamp(mo); + // Automatic selection of bins to ignore + // Dead channel map + updateBinsToIgnoreWithDCM(); + mBinsToIgnoreAsStr = std::to_string(mBinsToIgnore.size()); + mIsFirstIter = false; + } + + result = Quality::Good; + std::vector vecWarnings{}; + std::vector vecErrors{}; + for (int binIdx = 0; binIdx < hist->GetNbinsX(); binIdx++) { + if (mBinsToIgnore.find(binIdx) != mBinsToIgnore.end()) { + continue; + } + const auto bin = binIdx + 1; + const auto val = hist->GetBinContent(bin); + const bool hasError = hist->GetBinError(bin) > 0; // for checking denomenator, if denominator == 0 error will be also zero + const bool isError = (val < mThreshError && !mIsInvertedThrsh) || (val > mThreshError && mIsInvertedThrsh) || (!hasError && mUseBinError); + const bool isWarning = (val < mThreshWarning && !mIsInvertedThrsh) || (val > mThreshWarning && mIsInvertedThrsh); + const std::string binAsStr = mUseBinLabels ? hist->GetXaxis()->GetBinLabel(bin) : std::to_string(binIdx); + if (isError) { + vecErrors.push_back(binAsStr); + continue; + } else if (isWarning) { + vecWarnings.push_back(binAsStr); + } + } + mNumErrors = vecErrors.size(); + mNumWarnings = vecWarnings.size(); + auto funcInt2Str = [](std::string accum, std::string bin) { return std::move(accum) + ", " + bin; }; + auto addFlag = [&funcInt2Str, &result](auto&& vec, auto&& messagePrefix) -> void { + if (vec.size() == 0) { + return; + } + const auto idxLine = vec.size() > 0 ? std::accumulate(std::next(vec.begin()), vec.end(), vec[0], funcInt2Str) : ""; + const std::string message = messagePrefix + idxLine; + result.addFlag(FlagTypeFactory::Unknown(), message); + }; + auto addMultipleFlags = [&addFlag, this](auto&& vec, auto&& messagePrefix) -> void { + if (vec.size() == 0) { + return; + } + auto it = vec.begin(); + int distance{ 0 }; + do { + const std::string msg = distance == 0 ? messagePrefix : ""; // first iteration, message prefix will be used only in first line + const int extraNelements = distance == 0 ? 0 : mNelementsPerLine; + const auto itBegin = it; + std::advance(it, mNelementsPerLine + extraNelements); + distance = std::distance(it, vec.end()); + const auto itEnd = distance > 0 ? it : vec.end(); + addFlag(std::vector(itBegin, itEnd), msg); + } while (distance > 0); + }; + if (mNumErrors > 0) { + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + addMultipleFlags(vecErrors, mMessagePrefixError); + } + if (mNumWarnings > 0) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + addMultipleFlags(vecWarnings, mMessagePrefixWarning); + } + } + } + result.addMetadata("nErrors", std::to_string(mNumErrors)); + result.addMetadata("nWarnings", std::to_string(mNumWarnings)); + return result; +} + +void LevelCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == mNameObjectToCheck) { + auto* h = dynamic_cast(mo->getObject()); + + TPaveText* msg = new TPaveText(mVecLabelPos[0], mVecLabelPos[1], mVecLabelPos[2], mVecLabelPos[3], "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + msg->AddText(Form("N ignored elements: %s", mBinsToIgnoreAsStr.c_str())); + msg->AddText(Form("N elements with warning (%s %.3f) = %d", mSignCheck.c_str(), mThreshWarning, mNumWarnings)); + msg->AddText(Form("N elements with error (%s %.3f) = %d", mSignCheck.c_str(), mThreshError, mNumErrors)); + if (checkResult == Quality::Good) { + msg->AddText(">> Quality::Good <<"); + msg->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + auto flags = checkResult.getFlags(); + msg->SetFillColor(kRed); + msg->AddText(">> Quality::Bad <<"); + } else if (checkResult == Quality::Medium) { + auto flags = checkResult.getFlags(); + msg->SetFillColor(kOrange); + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Null) { + msg->AddText(">> Quality::Null <<"); + msg->SetFillColor(kGray); + } + // add threshold lines + Double_t xMin = h->GetXaxis()->GetXmin(); + Double_t xMax = h->GetXaxis()->GetXmax(); + auto* lineError = new TLine(xMin, mThreshError, xMax, mThreshError); + auto* lineWarning = new TLine(xMin, mThreshWarning, xMax, mThreshWarning); + lineError->SetLineWidth(3); + lineWarning->SetLineWidth(3); + lineError->SetLineStyle(kDashed); + lineWarning->SetLineStyle(kDashed); + lineError->SetLineColor(kRed); + lineWarning->SetLineColor(kOrange); + h->GetListOfFunctions()->Add(lineError); + h->GetListOfFunctions()->Add(lineWarning); + h->SetStats(0); + } +} + +} // namespace o2::quality_control_modules::fit diff --git a/Modules/FIT/FIT/src/MIPCheck.cxx b/Modules/FIT/FIT/src/MIPCheck.cxx new file mode 100644 index 0000000000..5983b97dd1 --- /dev/null +++ b/Modules/FIT/FIT/src/MIPCheck.cxx @@ -0,0 +1,376 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MIPCheck.cxx +/// \author andreas.molander@cern.ch +/// + +#include "FIT/MIPCheck.h" +#include "FITCommon/HelperCommon.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" + +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fit +{ + +void MIPCheck::configure() +{ + // Read configuration parameters + + mNameObjectToCheck = mCustomParameters.atOrDefaultValue("nameObjectToCheck", ""); + if (mNameObjectToCheck.empty()) { + ILOG(Error, Support) << "No MO name provided to check. The check will not do anything. Please provide a valid MO name." << ENDM; + } + mNPeaksToFit = std::stoi(mCustomParameters.atOrDefaultValue("nPeaksToFit", "2")); + mFitRangeLow = std::stof(mCustomParameters.atOrDefaultValue("fitRangeLow", "11.0")); + mFitRangeHigh = std::stof(mCustomParameters.atOrDefaultValue("fitRangeHigh", "35.0")); + + std::string parseableParam; + + parseableParam = mCustomParameters.atOrDefaultValue("gausParamsMean", ""); + mGausParamsMeans = helper::parseParameters(parseableParam, ","); + if (mGausParamsMeans.size() != mNPeaksToFit) { + ILOG(Warning, Support) << "Initial fit arguments for gaussian means not provided for all MIP peaks. Trying to estimate them..." << ENDM; + } + + parseableParam = mCustomParameters.atOrDefaultValue("gausParamsSigma", "3.0, 7.0"); + mGausParamsSigmas = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("meanWarningsLow", ""); + mMeanWarningsLow = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("meanWarningsHigh", ""); + mMeanWarningsHigh = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("meanErrorsLow", ""); + mMeanErrorsLow = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("meanErrorsHigh", ""); + mMeanErrorsHigh = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("sigmaWarnings", ""); + mSigmaWarnings = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("sigmaErrors", ""); + mSigmaErrors = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("drawMeanWarningsLow", "false, false"); + mDrawMeanWarningsLow = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("drawMeanWarningsHigh", "false, false"); + mDrawMeanWarningsHigh = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("drawMeanErrorsLow", "true, true"); + mDrawMeanErrorsLow = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("drawMeanErrorsHigh", "true, true"); + mDrawMeanErrorsHigh = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("drawSigmaWarnings", "false, false"); + mDrawSigmaWarnings = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("drawSigmaErrors", "false, false"); + mDrawSigmaErrors = helper::parseParameters(parseableParam, ","); + + parseableParam = mCustomParameters.atOrDefaultValue("labelPos", "0.15, 0.2, 0.85, 0.45"); + mVecLabelPos = helper::parseParameters(parseableParam, ","); + if (mVecLabelPos.size() != 4) { + ILOG(Error, Devel) << "Incorrect label coordinates! Setting default." << ENDM; + mVecLabelPos = { 0.15, 0.2, 0.85, 0.45 }; + } +} + +Quality MIPCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + if (mo->getName() == mNameObjectToCheck) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "Could not cast " << mNameObjectToCheck << " to TH1*, skipping" << ENDM; + continue; + } + // unless we find issues, we assume the quality is good + result = Quality::Good; + + // Fit the histogram to find the MIP peaks + + // Gaussian fit function + auto multiGausLambda = [this](Double_t* x, Double_t* par) -> Double_t { + Double_t sum = 0; + for (int i = 0; i < mNPeaksToFit; i++) { + // a = par[i * 3] + // mean = par[i * 3 + 1] + // sigma = par[i * 3 + 2]; + sum += par[i * 3] * TMath::Gaus(x[0], par[i * 3 + 1], par[i * 3 + 2]); + } + return sum; + }; + + // Construct a TF1 using the lambda + TF1 fitFunc("fitFunc", multiGausLambda, mFitRangeLow, mFitRangeHigh, 3 * mNPeaksToFit); + + // Provide initial guesses for the fit parameters + double mean1 = -1; + if (mGausParamsMeans.size() > 0) { + mean1 = mGausParamsMeans.at(0); + } else { + mean1 = h->GetBinCenter(h->GetMaximumBin()); + ILOG(Info, Devel) << Form("No initial guess for the 1 MIP peak mean provided. Estimated: %f", mean1) << ENDM; + } + + for (int i = 0; i < mNPeaksToFit; i++) { + double mean = -1; + if (i == 0) { + mean = mean1; + } else if (mGausParamsMeans.size() > i) { + mean = mGausParamsMeans.at(i); + } else { + mean = mean1 * (i + 1); + ILOG(Info, Devel) << Form("No initial guess for the %i MIP peak mean provided. Estimated: %f", i + 1, mean) << ENDM; + } + + int meanBin = h->FindBin(mean); + double a = h->GetBinContent(meanBin); + double sigma = mGausParamsSigmas.size() > i ? mGausParamsSigmas.at(i) : 5.0; // Use 5.0 for sigmas not provided + + ILOG(Info, Devel) << "Peak " << i << " initial fit parameters: a: " << a << ", mean: " << mean << ", sigma: " << sigma << ENDM; + + fitFunc.SetParameter(i * 3, a); + fitFunc.SetParameter(i * 3 + 1, mean); + fitFunc.SetParameter(i * 3 + 2, sigma); + } + + TFitResultPtr fitResult = h->Fit(&fitFunc, "SR"); + + // Check if the fit was successful + if (fitResult->Status() != 0) { + ILOG(Error, Devel) << "Fit failed for histogram " << moName << ", status: " << fitResult->Status() << ENDM; + result = Quality::Bad; + result.addFlag(FlagTypeFactory::UnknownQuality(), "Fit failed"); + break; // Don't bother checking the thresholds if the fit failed + } + + // Check the threholds, if some are not specified in the config, simply don't check them + for (int i = 0; i < mNPeaksToFit; i++) { + double mean = fitFunc.GetParameter(i * 3 + 1); + double sigma = fitFunc.GetParameter(i * 3 + 2); + + bool meanBad = false; + bool sigmaBad = false; + + // Check the error threholds + + // Mean + if (mMeanErrorsHigh.size() > i && mMeanErrorsHigh.at(i) >= 0 && mean > mMeanErrorsHigh.at(i)) { + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), Form("%i MIP peak mean (%.2f) above upper error threshold (%.2f)", i + 1, mean, mMeanErrorsHigh.at(i))); + meanBad = true; + } else if (mMeanErrorsLow.size() > i && mMeanErrorsLow.at(i) >= 0 && mean < mMeanErrorsLow.at(i)) { + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), Form("%i MIP peak mean (%.2f) below lower error threshold (%.2f)", i + 1, mean, mMeanErrorsLow.at(i))); + meanBad = true; + } + + // Sigma + if (mSigmaErrors.size() > i && mSigmaErrors.at(i) >= 0 && sigma > mSigmaErrors.at(i)) { + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), Form("%i MIP peak sigma (%.2f) larger than error threshold (%.2f)", i + 1, sigma, mSigmaErrors.at(i))); + sigmaBad = true; + } + + // Check the warning thresholds + + // Mean (no need to check in case the mean already crossed an error threshold) + if (!meanBad && mMeanWarningsHigh.size() > i && mMeanWarningsHigh.at(i) >= 0 && mean > mMeanWarningsHigh.at(i)) { + if (result.isBetterThan(Quality::Medium)) { // Don't make the quality better than it was + result.set(Quality::Medium); + } + // Add a flag even if the quality was bad because of another peak + result.addFlag(FlagTypeFactory::Unknown(), Form("%i MIP peak mean (%.2f) above upper warning threshold (%.2f)", i + 1, mean, mMeanWarningsHigh.at(i))); + } else if (!meanBad && mMeanWarningsLow.size() > i && mMeanWarningsLow.at(i) >= 0 && mean < mMeanWarningsLow.at(i)) { + if (result.isBetterThan(Quality::Medium)) { // Don't make the quality better than it was + result.set(Quality::Medium); + } + // Add a flag even if the quality was bad because of another peak + result.addFlag(FlagTypeFactory::Unknown(), Form("%i MIP peak mean (%.2f) below lower warning threshold (%.2f)", i + 1, mean, mMeanWarningsLow.at(i))); + } + + // Sigma (no need to check in case the sigma already crossed an error threshold) + if (!sigmaBad && mSigmaWarnings.size() > i && mSigmaWarnings.at(i) >= 0 && sigma > mSigmaWarnings.at(i)) { + if (result.isBetterThan(Quality::Medium)) { // Don't make the quality better than it was + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("%i MIP peak sigma (%.2f) larger than warning threshold (%.2f)", i + 1, sigma, mSigmaWarnings.at(i))); + } + } + + beautify(mo, result); + } + } + + return result; +} + +void MIPCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == mNameObjectToCheck) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "Could not cast `example` to TH1*, skipping" << ENDM; + return; + } + + TPaveText* msg = new TPaveText(mVecLabelPos[0], mVecLabelPos[1], mVecLabelPos[2], mVecLabelPos[3], "NDC"); + + std::vector meanValues; + std::vector sigmaValues; + + auto* func = h->GetFunction("fitFunc"); + if (func == nullptr) { + ILOG(Error, Support) << "Could not find fit function in histogram " << mo->getName() << ENDM; + msg->AddText("Gaussian fit not found!"); + } else { + msg->AddText("Gaussian fit"); + func->SetLineColor(kBlack); + + for (int i = 0; i < mNPeaksToFit; i++) { + double mean = func->GetParameter(i * 3 + 1); + double sigma = func->GetParameter(i * 3 + 2); + meanValues.push_back(mean); + sigmaValues.push_back(sigma); + + msg->AddText(Form("%i MIP: #mu = %.2f, #sigma = %.2f", i + 1, mean, sigma)); + } + } + + if (checkResult == Quality::Good) { + msg->AddText(">> Quality::Good <<"); + msg->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + msg->SetFillColor(kRed); + msg->AddText(">> Quality::Bad <<"); + } else if (checkResult == Quality::Medium) { + msg->SetFillColor(kOrange); + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Null) { + msg->AddText(">> Quality::Null <<"); + msg->SetFillColor(kGray); + } + + for (const auto& [flag, comment] : checkResult.getFlags()) { + if (comment.empty()) { + msg->AddText(Form("#rightarrow Flag: %s", flag.getName().c_str())); + } else { + msg->AddText(Form("#rightarrow Flag: %s: %s", flag.getName().c_str(), comment.c_str())); + } + } + + h->GetListOfFunctions()->Add(msg); + + // Add threshold lines + + for (int i = 0; i < mNPeaksToFit; i++) { + // Warning low + if (mMeanWarningsLow.size() > i && mMeanWarningsLow.at(i) >= 0 && mDrawMeanWarningsLow.size() > i && mDrawMeanWarningsLow.at(i)) { + double meanWarningLow = mMeanWarningsLow.at(i); + auto* line = new TLine(meanWarningLow, 0, meanWarningLow, h->GetMaximum()); + line->SetLineColor(kOrange); + line->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(line); + } + + // Warning high + if (mMeanWarningsHigh.size() > i && mMeanWarningsHigh.at(i) >= 0 && mDrawMeanWarningsHigh.size() > i && mDrawMeanWarningsHigh.at(i)) { + double meanWarningHigh = mMeanWarningsHigh.at(i); + auto* line = new TLine(meanWarningHigh, 0, meanWarningHigh, h->GetMaximum()); + line->SetLineColor(kOrange); + line->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(line); + } + + // Error low + if (mMeanErrorsLow.size() > i && mMeanErrorsLow.at(i) >= 0 && mDrawMeanErrorsLow.size() > i && mDrawMeanErrorsLow.at(i)) { + double meanErrorLow = mMeanErrorsLow.at(i); + auto* line = new TLine(meanErrorLow, 0, meanErrorLow, h->GetMaximum()); + line->SetLineColor(kRed); + line->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(line); + } + + // Error high + if (mMeanErrorsHigh.size() > i && mMeanErrorsHigh.at(i) >= 0 && mDrawMeanErrorsHigh.size() > i && mDrawMeanErrorsHigh.at(i)) { + double meanErrorHigh = mMeanErrorsHigh.at(i); + auto* line = new TLine(meanErrorHigh, 0, meanErrorHigh, h->GetMaximum()); + line->SetLineColor(kRed); + line->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(line); + } + + // Sigma warning + if (mSigmaWarnings.size() > i && mSigmaWarnings.at(i) >= 0 && mDrawSigmaWarnings.size() > i && mDrawSigmaWarnings.at(i) && meanValues.size() > i) { + double sigmaWarningLow = meanValues.at(i) - mSigmaWarnings.at(i); + auto* lineLow = new TLine(sigmaWarningLow, 0, sigmaWarningLow, h->GetMaximum()); + lineLow->SetLineColor(kOrange); + lineLow->SetLineStyle(kDotted); + h->GetListOfFunctions()->Add(lineLow); + double sigmaWarningHigh = meanValues.at(i) + mSigmaWarnings.at(i); + auto* lineHigh = new TLine(sigmaWarningHigh, 0, sigmaWarningHigh, h->GetMaximum()); + lineLow->SetLineColor(kOrange); + lineLow->SetLineStyle(kDotted); + h->GetListOfFunctions()->Add(lineLow); + } + + // Sigma error + if (mSigmaErrors.size() > i && mSigmaErrors.at(i) >= 0 && mDrawSigmaErrors.size() > i && mDrawSigmaErrors.at(i) && meanValues.size() > i) { + double sigmaErrorLow = meanValues.at(i) - mSigmaErrors.at(i); + auto* lineLow = new TLine(sigmaErrorLow, 0, sigmaErrorLow, h->GetMaximum()); + lineLow->SetLineColor(kRed); + lineLow->SetLineStyle(kDotted); + h->GetListOfFunctions()->Add(lineLow); + double sigmaErrorHigh = meanValues.at(i) + mSigmaErrors.at(i); + auto* lineHigh = new TLine(sigmaErrorHigh, 0, sigmaErrorHigh, h->GetMaximum()); + lineHigh->SetLineColor(kRed); + lineHigh->SetLineStyle(kDotted); + h->GetListOfFunctions()->Add(lineHigh); + } + } + } +} + +void MIPCheck::startOfActivity(const Activity& activity) +{ + mActivity = make_shared(activity); +} + +} // namespace o2::quality_control_modules::fit diff --git a/Modules/FIT/FIT/src/RawDataMetricTask.cxx b/Modules/FIT/FIT/src/RawDataMetricTask.cxx new file mode 100644 index 0000000000..b432cfc3e8 --- /dev/null +++ b/Modules/FIT/FIT/src/RawDataMetricTask.cxx @@ -0,0 +1,134 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawDataMetricTask.cxx +/// \author Artur Furs afurs@cern.ch +/// \brief Task for checking FIT RawDataMetric + +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" +#include +#include + +#include + +#include +#include "QualityControl/QcInfoLogger.h" +#include "Framework/ProcessingContext.h" +#include "Framework/InputRecord.h" +#include "Framework/DeviceSpec.h" +#include "Framework/DataSpecUtils.h" + +namespace o2::quality_control_modules::fit +{ +const std::set RawDataMetricTask::sSetOfAllowedDets = { o2::header::gDataOriginFDD, o2::header::gDataOriginFT0, o2::header::gDataOriginFV0 }; +RawDataMetricTask::~RawDataMetricTask() +{ +} + +void RawDataMetricTask::initialize(o2::framework::InitContext& ctx) +{ + // Obtaining data origin, i.e. detector ID + const auto& inputRouts = ctx.services().get().inputs; + std::set setOfOrigins{}; // should be only one + header::DataOrigin det; + for (const auto& route : inputRouts) { + for (const auto& allowedDet : sSetOfAllowedDets) { + if (o2::framework::DataSpecUtils::partialMatch(route.matcher, allowedDet)) { + setOfOrigins.insert(allowedDet); + } + } + } + + if (setOfOrigins.size() == 1) { + det = *setOfOrigins.begin(); + mDetName = std::string{ det.str }; + } else { + ILOG(Error, Devel) << "Cannot recognize detector!" << ENDM; + + return; + // assertion + } + { + const auto useModuleNamesForBins = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "useModuleNamesForBins", false); + std::map mapBinPos2FeeLabel{}; + helperLUT::Fee2ModuleName_t mapFEE2ModuleName{}; + helperLUT::obtainFromLUT(det, mapFEE2ModuleName); + mMapFEE2binPos.clear(); + unsigned int binPos = 0; + for (const auto& entry : mapFEE2ModuleName) { + const auto& entryFEE = entry.first; // (epID,linkID) + const auto& epID = entryFEE.first; + const auto& linkID = entryFEE.second; + const auto& moduleName = entry.second; + std::string binLabel = useModuleNamesForBins ? moduleName : Form("%i/%i", epID, linkID); + mapBinPos2FeeLabel.insert({ binPos, binLabel }); + mMapFEE2binPos.insert({ entryFEE, binPos }); + binPos++; + } + mBinPosUnknown = mMapFEE2binPos.size(); + mapBinPos2FeeLabel.insert({ mBinPosUnknown, "Unknown" }); + const std::string titleAxisX = useModuleNamesForBins ? "FEE module" : "EPID/LinkID"; + mHistRawDataMetrics = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "RawDataMetric", Form("%s raw data metric statistics;%s;Raw data metric bits", mDetName.c_str(), titleAxisX.c_str()), mapBinPos2FeeLabel, o2::fit::RawDataMetric::sMapBitsToNames); + } + // +} + +void RawDataMetricTask::startOfActivity(const Activity& activity) +{ + mHistRawDataMetrics->Reset(); +} + +void RawDataMetricTask::startOfCycle() +{ +} + +void RawDataMetricTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + const auto& vecRawDataMetric = ctx.inputs().get>("rawDataMetric"); + for (const auto& rawDataMetric : vecRawDataMetric) { + const auto& itBinPos = mMapFEE2binPos.find(helperLUT::getFEEID(rawDataMetric.mEPID, rawDataMetric.mLinkID)); + if (itBinPos != mMapFEE2binPos.end()) { // registered FEE + const auto& binPos = itBinPos->second; + for (int iPosY = 0; iPosY < rawDataMetric.mBitStats.size(); iPosY++) { + mHistRawDataMetrics->Fill(binPos, iPosY, rawDataMetric.mBitStats[iPosY]); + } + } else { // non-registered FEE => unknown, info will be in IL + for (int iPosY = 0; iPosY < rawDataMetric.mBitStats.size(); iPosY++) { + mHistRawDataMetrics->Fill(mBinPosUnknown, iPosY, rawDataMetric.mBitStats[iPosY]); + } + } + } +} + +void RawDataMetricTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + // one has to set num. of entries manually because + // default TH1Reductor gets only mean,stddev and entries (no integral) +} + +void RawDataMetricTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void RawDataMetricTask::reset() +{ + // clean all the monitor objects here + mHistRawDataMetrics->Reset(); +} + +} // namespace o2::quality_control_modules::fit diff --git a/Modules/FIT/FIT/src/RecoFITQcTask.cxx b/Modules/FIT/FIT/src/RecoFITQcTask.cxx new file mode 100644 index 0000000000..a2072b7794 --- /dev/null +++ b/Modules/FIT/FIT/src/RecoFITQcTask.cxx @@ -0,0 +1,162 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RecoFITQcTask.cxx +/// \author Artur Furs afurs@cern.ch + +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" +#include +#include +#include +#include +#include +#include + +#include "Framework/ProcessingContext.h" +#include "Framework/InputRecord.h" +#include "Framework/ConcreteDataMatcher.h" +#include "Framework/InitContext.h" +#include "Framework/DeviceSpec.h" +#include +#include +#include + +using namespace o2::quality_control_modules::fit::detectorFIT; +using namespace o2::quality_control_modules::fit; + +RecoFITQcTask::~RecoFITQcTask() +{ +} + +void RecoFITQcTask::initialize(o2::framework::InitContext& ctx) +{ + const auto& inputRouts = ctx.services().get().inputs; + std::set setOfBindings{}; + for (const auto& route : inputRouts) { + setOfBindings.insert(route.matcher.binding); + } + if (setOfBindings.find("recPointsFDD") != setOfBindings.end() && setOfBindings.find("channelsFDD") != setOfBindings.end()) { + mIsFDD = true; + LOG(debug) << "FDD DPL channel is detected"; + } + if (setOfBindings.find("recPointsFT0") != setOfBindings.end() && setOfBindings.find("channelsFT0") != setOfBindings.end()) { + mIsFT0 = true; + LOG(debug) << "FT0 DPL channel is detected"; + } + if (setOfBindings.find("recPointsFV0") != setOfBindings.end() && setOfBindings.find("channelsFV0") != setOfBindings.end()) { + mIsFV0 = true; + LOG(debug) << "FV0 DPL channel is detected"; + } + const auto mapFDD_FT0 = helper::multiplyMaps({ { "FDD ", DetectorFDD::sMapTrgBits, " && " }, { "FT0 ", DetectorFT0::sMapTrgBits, "" } }); + const auto mapFDD_FV0 = helper::multiplyMaps({ { "FDD ", DetectorFDD::sMapTrgBits, " && " }, { "FV0 ", DetectorFV0::sMapTrgBits, "" } }); + const auto mapFT0_FV0 = helper::multiplyMaps({ { "FT0 ", DetectorFT0::sMapTrgBits, " && " }, { "FV0 ", DetectorFV0::sMapTrgBits, "" } }); + + const auto mapFDD_FT0_FV0 = helper::multiplyMaps({ { "FDD ", DetectorFDD::sMapTrgBits, " && " }, { "FT0 ", DetectorFT0::sMapTrgBits, " && " }, { "FV0 ", DetectorFV0::sMapTrgBits, "" } }); + std::tuple axisBC{ static_cast(sNBC), 0., sNBC }; + mHistTrgCorrelationFDD_FT0 = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "TrgCorrelationFDD_FT0", "Correlation between trigger signals: FDD & FT0;BC;Triggers", axisBC, mapFDD_FT0); + mHistTrgCorrelationFDD_FV0 = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "TrgCorrelationFDD_FV0", "Correlation between trigger signals: FDD & FV0;BC;Triggers", axisBC, mapFDD_FV0); + mHistTrgCorrelationFT0_FV0 = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "TrgCorrelationFT0_FV0", "Correlation between trigger signals: FT0 & FV0;BC;Triggers", axisBC, mapFT0_FV0); + + // mHistTrgCorrelationFDD_FT0_FV0 = helper::registerHist(getObjectsManager(), "COLZ", "TrgCorrelationFDD_FT0_FV0","Correlation between trigger signals: FDD & FT0 & FV0;BC;Triggers",sNBC,0,sNBC,mapFDD_FT0_FV0); + for (const auto& enTrgFT0 : DetectorFT0::sMapTrgBits) { + const std::string histName = "TrgCorrelation_FT0" + enTrgFT0.second + "_FDD_FV0"; + const std::string histTitle = "Correlation between trigger signals (FT0 " + enTrgFT0.second + "): FDD & FV0;BC;Triggers"; + mHistTrgCorrelationFT0_FDD_FV0[enTrgFT0.first] = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", histName, histTitle, sNBC, 0, sNBC, mapFDD_FV0); + } +} + +void RecoFITQcTask::startOfActivity(const Activity& activity) +{ +} + +void RecoFITQcTask::startOfCycle() +{ +} + +void RecoFITQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + const auto& vecChannelsFDD = mIsFDD ? ctx.inputs().get>("channelsFDD") : gsl::span{}; + const auto& vecRecPointsFDD = mIsFDD ? ctx.inputs().get>("recPointsFDD") : gsl::span{}; + + const auto& vecChannelsFT0 = mIsFT0 ? ctx.inputs().get>("channelsFT0") : gsl::span{}; + const auto& vecRecPointsFT0 = mIsFT0 ? ctx.inputs().get>("recPointsFT0") : gsl::span{}; + + const auto& vecChannelsFV0 = mIsFV0 ? ctx.inputs().get>("channelsFV0") : gsl::span{}; + const auto& vecRecPointsFV0 = mIsFV0 ? ctx.inputs().get>("recPointsFV0") : gsl::span{}; + + const auto& mapDigitSync = DigitSyncFIT::makeSyncMap(vecRecPointsFDD, vecRecPointsFT0, vecRecPointsFV0); + const auto start = std::chrono::high_resolution_clock::now(); + for (const auto& entry : mapDigitSync) { + const o2::InteractionRecord& ir = entry.first; + const auto& digitSync = entry.second; + const auto& recPointsFDD = digitSync.getDigitFDD(vecRecPointsFDD); // o2::fdd::RecPoint + const auto& recPointsFT0 = digitSync.getDigitFT0(vecRecPointsFT0); // o2::ft0::RecPoints + const auto& recPointsFV0 = digitSync.getDigitFV0(vecRecPointsFV0); // o2::fv0::RecPoints + constexpr uint8_t allowedTrgBits = 0b11111; // to suppress tech trg bits(laset, dataIsValid, etc) + const auto trgFDD = recPointsFDD.getTrigger().getTriggersignals() & allowedTrgBits; + const auto trgFT0 = recPointsFT0.getTrigger().getTriggersignals() & allowedTrgBits; + const auto trgFV0 = recPointsFV0.getTrigger().getTriggersignals() & allowedTrgBits; + // Too many small nested loops(max size = 5) , better to find another way for correlation calc? + for (const auto trgBitFDD : DetectorFDD::sArrDecomposed1Byte[trgFDD]) { + for (const auto trgBitFT0 : DetectorFT0::sArrDecomposed1Byte[trgFT0]) { + const auto trgCorrStatusFDD_FT0 = mFuncTrgStatusFDD_FT0(trgBitFDD, trgBitFT0); + mHistTrgCorrelationFDD_FT0->Fill(ir.bc, trgCorrStatusFDD_FT0); + } + for (const auto trgBitFV0 : DetectorFV0::sArrDecomposed1Byte[trgFV0]) { + const auto trgCorrStatusFDD_FV0 = mFuncTrgStatusFDD_FV0(trgBitFDD, trgBitFV0); + mHistTrgCorrelationFDD_FV0->Fill(ir.bc, trgCorrStatusFDD_FV0); + for (const auto trgBitFT0 : DetectorFT0::sArrDecomposed1Byte[trgFT0]) { + mHistTrgCorrelationFT0_FDD_FV0[trgBitFT0]->Fill(ir.bc, trgCorrStatusFDD_FV0); + } + } + } + for (const auto trgBitFT0 : DetectorFT0::sArrDecomposed1Byte[trgFT0]) { + for (const auto trgBitFV0 : DetectorFV0::sArrDecomposed1Byte[trgFV0]) { + const auto trgCorrStatusFT0_FV0 = mFuncTrgStatusFT0_FV0(trgBitFT0, trgBitFV0); + mHistTrgCorrelationFT0_FV0->Fill(ir.bc, trgCorrStatusFT0_FV0); + } + } + } + const auto stop = std::chrono::high_resolution_clock::now(); + const auto duration = std::chrono::duration_cast(stop - start); + LOG(debug) << "FIT reco QC delay: " << duration.count(); +} + +void RecoFITQcTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + // one has to set num. of entries manually because + // default TH1Reductor gets only mean,stddev and entries (no integral) +} + +void RecoFITQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void RecoFITQcTask::reset() +{ + // clean all the monitor objects here + // mHistTrgCorrelationFDD_FT0_FV0->Reset(); + mHistTrgCorrelationFDD_FT0->Reset(); + mHistTrgCorrelationFDD_FV0->Reset(); + mHistTrgCorrelationFT0_FV0->Reset(); + for (int iTrgBit = 0; iTrgBit < 5; iTrgBit++) { + mHistTrgCorrelationFT0_FDD_FV0[iTrgBit]->Reset(); + } +} diff --git a/Modules/FIT/FT0/CMakeLists.txt b/Modules/FIT/FT0/CMakeLists.txt new file mode 100644 index 0000000000..f27c414dab --- /dev/null +++ b/Modules/FIT/FT0/CMakeLists.txt @@ -0,0 +1,111 @@ +# ---- Library ---- + +add_library(O2QcFT0) + +target_sources(O2QcFT0 PRIVATE src/AgingLaserTask.cxx + src/AgingLaserPostProcTask.cxx + src/DigitQcTask.cxx + src/GenericCheck.cxx + src/CFDEffCheck.cxx + src/FractionCheck.cxx + src/PostProcTask.cxx + src/OutOfBunchCollCheck.cxx + src/MergedTreeCheck.cxx + src/RecPointsQcTask.cxx + src/ChannelGeometry.cxx + src/AmpTimeDistribution.cxx) + +target_include_directories( + O2QcFT0 + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcFT0 PUBLIC O2QualityControl + O2::DataFormatsFT0 + O2::FT0Base + O2::DataFormatsParameters + O2QcCommon + O2QcFITCommon) + +install(TARGETS O2QcFT0 + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcFT0 + HEADERS include/FT0/AgingLaserTask.h + include/FT0/AgingLaserPostProcTask.h + include/FT0/DigitQcTask.h + include/FT0/PostProcTask.h + include/FT0/CFDEffCheck.h + include/FT0/FractionCheck.h + include/FT0/OutOfBunchCollCheck.h + include/FT0/GenericCheck.h + include/FT0/Utilities.h + include/FT0/MergedTreeCheck.h + include/FT0/RecPointsQcTask.h + include/FT0/ChannelGeometry.h + include/FT0/AmpTimeDistribution.h + LINKDEF include/FT0/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/FT0 + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +install(FILES etc/ft0-digits.json + etc/ft0-post-processing.json + etc/ft0-recpoints.json + etc/FT0_LUT.csv + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/Modules/FIT/FT0/etc) + +# ---- Executables ---- +# keep commented as an example + +#set(EXE_SRCS +# src/runDataProducer.cxx ) +#set(EXE_NAMES +# o2-qc-ft0-data-producer) +# +#list(LENGTH EXE_SRCS count) +#math(EXPR count "${count}-1") +#foreach(i RANGE ${count}) +# list(GET EXE_SRCS ${i} src) +# list(GET EXE_NAMES ${i} name) +# add_executable(${name} ${src}) +# target_link_libraries(${name} PRIVATE O2QualityControl O2QcFT0 O2::DataFormatsFT0 O2::FITCalibration) +#endforeach() + +#install( +# TARGETS o2-qc-ft0-data-producer +# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +#) + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcFT0.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcFT0 Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + +# ----- Configs ------- +install(FILES + ft0-reconstruction-config.json + etc/ft0-aging-laser.json + DESTINATION etc) + +get_property(dirs + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + PROPERTY INCLUDE_DIRECTORIES) +foreach(dir ${dirs}) + message(STATUS "dir='${dir}'") +endforeach() diff --git a/Modules/FIT/FT0/README.md b/Modules/FIT/FT0/README.md new file mode 100644 index 0000000000..da1fda55c1 --- /dev/null +++ b/Modules/FIT/FT0/README.md @@ -0,0 +1,169 @@ +# FT0 quality control + +# Aging monitoring + +_The following documentation consern FT0 aging **monitoring**. Software to deduce the aging **correction** will come later._ + +The aging monitoring of FT0 is performed by 1 minute long laser runs that are launched after each beam dump. + +Dedicated QC tasks analyze the data: + +- `o2::quality_control_modules::ft0::AgingLaserTask` - raw collection of the data +- `o2::quality_control_modules::ft0::AgingLaserPostProc` - post processing of the data +- `o2::quality_control::postprocessing::SliceTrendingTask` - trending of the post processing data + +At the moment, the QC task is adapted to the FT0 laser calibration system (LCS) and the monitoring of the FT0 aging. If needed, the task can be generalized to work with other FIT detectors. + +## Monitoring principles + +The schematics of the LCS is shown below. Per laser pulse, there will be two signals in each reference channel and one signal in each detector channel. The signals are separated in time by well defined delays, so one can identify them by BC ID. + + + +The basic idea is to monitor the amplitudes seen in the detector during the laser runs. The reference channels don't age and the amplitudes in these are used as a normalization factor for the detector channel amplitudes. + +More information about the LCS and the hardware side of the aging monitoring can be found [here](https://indico.cern.ch/event/1229241/contributions/5172798/attachments/2561719/4420583/Ageing-related%20tasks.pdf). + +--- + +## Aging monitoring QC tasks + +### AgingLaserTask + +The `AgingLaserTask` task collects the raw data from the laser runs. + +**Procedure:** + +* Selects laser events with specific BC IDs +* Separately identifies: + * detector channel laser signals + * reference channel signals, separating the two signals per laser pulse +* Fills per-channel **amplitude** and **time** histograms, both ADCs together as well as separated by ADC +* (Optional) produces a rich set of debug histograms + +This task is the *producer* of the raw per-channel spectra used later in post-processing. + +#### Input + +* Digits and channel streams, specified in the config as + + ```json + "dataSource": { + "type": "direct", + "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" + }, + ``` + +#### Configuration + +An example configuration can be found in [etc/ft0-aging-laser.json](https://github.com/AliceO2Group/QualityControl/blob/master/Modules/FIT/FT0/etc/ft0-aging-laser.json). The task parameters are listed in the table below. + +| Key | Type | Default | Meaning | +| ------------------------ | -----------: | ----------------: | --------------------------------------------------------------- | +| `detectorChannelIDs` | list `uint8` | | Detector channels to monitor, omit to use all | +| `referenceChannelIDs` | list `uint8` | `208,209,210,211` | Reference channels to monitor | +| `detectorAmpCut` | int | `0` | Minimum amplitude for detector channels (**currently not applied**) | +| `referenceAmpCut` | int | `100` | Minimum amplitude for reference channels (suppress noise cross-talk) | +| `laserTriggerBCs` | list `int` | `0,1783` | BCs where the laser is fired | +| `detectorBCdelay` | int | `131` | BC delay from laser pulse to detector signal | +| `referencePeak1BCdelays` | list `int` | `115,115,115,115` | BC delay from laser pulse to the first reference channel signal | +| `referencePeak2BCdelays` | list `int` | `136,142,135,141` | BC delay from laser pulse to the second reference channel signal | +| `debug` | bool | `false` | Enable extra (heavy) debug histograms. | + +> The BC delays and channel ID ranges are stable and set by hardware; adjust only if the LCS changes. + +#### Output + +| Name | Type | Description | +|--------------------------|------|------------------------------------------------------------------| +| `AmpPerChannel` | TH2I | Amplitude distribution per channel (both ADCs) | +| `AmpPerChannelADC0` | TH2I | Amplitude distribution per channel for ADC0 | +| `AmpPerChannelADC1` | TH2I | Amplitude distribution per channel for ADC1 | +| `AmpPerChannelPeak1ADC0` | TH2I | Amplitude distribution per channel for the first peak with ADC0 | +| `AmpPerChannelPeak1ADC1` | TH2I | Amplitude distribution per channel for the first peak with ADC1 | +| `AmpPerChannelPeak2ADC0` | TH2I | Amplitude distribution per channel for the second peak with ADC0 | +| `AmpPerChannelPeak2ADC1` | TH2I | Amplitude distribution per channel for the second peak with ADC1 | +| `TimePerChannel` | TH2I | Time distribution per channel (both ADCs) | +| `TimePerChannelPeak1` | TH2I | Time distribution per channel for the first peak (both ADCs) | +| `TimePerChannelPeak2` | TH2I | Time distribution per channel for the second peak (both ADCs) | + +> A set of debug histograms can be set if the `debug` parameter is set to `true`. See the task header file for the definition of these histograms. + +#### TODO + +- The MO's should be distinguished by: + - Gain setting (number of ADC channels per MIP) + - In the future we will fetch this from CCDB, but at the moment we pass the value via the QC config. + - The gain could be stored in the MO metadata. This can then be used as we wish in the post processing. + - B field -> We decided not to care about this (???) + - Can be fetched from GRP in CCDB (?) +- Should we have some out-of-bunch checking as part of a LCS sanity check? + +### AgingLaserPostProcTask + +The `AgingLaserPostProc` task reads the output from the `AgingLaserTask` and produces output suitable for aging monitoring. + +#### Input + +* From the QC repository path `FT0/MO/AgingLaser`: + * `AmpPerChannel` (TH2): amplitude (ADC) vs channel + +#### Algorithm + +1. **Reference normalization** + * For each configured reference channel: + * Project its slice amplitude distribtion `AmpPerChannel` → `TH1` + * Find the maximum `x_max` + * Fit a Gaussian in `[(1−fracWindowLow)·x_max, (1+fracWindowHigh)·x_max]` → mean `μ_ref`. + * `norm = average(μ_ref)` over all successful fits. +2. **Per-channel value** + * For **every** detector channel: + * Find the global maximum `x_max`. + * In the same fractional window around `x_max`, compute **weighted mean** + `⟨x⟩ = Σ w_i x_i / Σ w_i` with weights `w_i = bin content`. + * Store `value = ⟨x⟩ / norm`. + * Store `value_corrected = value / value_after_last_aging_correction` +3. **Publish four TH1F MOs** + * `AmpPerChannelNormWeightedMeanA`: 96 bins, channels 0–95. + * `AmpPerChannelNormWeightedMeanC`: 112 bins, channels 96–207. + * `AmpPerChannelNormWeightedMeanCorrectedA`: 96 bins, channels 0–95. Normalized with amplitudes from the last aging correction. + * `AmpPerChannelNormWeightedMeanCorrectedC`: 112 bins, channels 96–207. Normalized with amplitudes from the last aging correction. +4. **In case of a "reset" run - publish two more TH1F MOs** + * `AmpPerChannelNormWeightedMeanAfterLastCorrectionA`: 96 bins, channels 0–95. Used as normalization to deduce relative aging since last aging correction. + * `AmpPerChannelNormWeightedMeanAfterLastCorrectionC`: 112 bins, channels 96–207. Used as normalization to deduce relative aging since last aging correction. + +> Tip: when booking the C-side histogram use upper edge **208** (exclusive) with 112 bins to avoid off-by-one bin widths. + +#### Configuration + +| Key | Type | Default | Meaning | +|--------------------------|--------------:|----------------------------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `reset` | bool | false | To be set true (only) on the scans following aging corrections. The amplitudes are stored in an additional path, and are used for normalization in following laser scans. | +| `useDeadChannelMap` | bool | true | If true, channels marked dead in the dead channel map are not processed | +| `ignoreDetectorChannels` | list `uint_8` | | Detector channels to ignore | +| `ignoreRefChannels` | list `uint8` | | Reference channels to ignore | +| `fracWindowLow` | double | `0.25` | Low fractional window for the Gaussian fits | +| `fracWindowHigh` | double | `0.25` | High fractional window for the Gaussian fits | +| `agingLaserTaskPath` | string | `FT0/MO/AgingLaser` | Path to the AgingLaser task output in QCDB | +| `agingLaserPostProcPath` | string | `FT0/MO/AgingLaserPostProc` | Path to the AgingLaserPostProc task output in QCDB | + +#### Output + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `AmpPerChannelNormWeightedMeanA` | TH1F | Weighted means of A-side amplitudes normalized with reference channel amplitudes | +| `AmpPerChannelNormWeightedMeanC` | TH1F | Weighted means of C-side amplitudes normalized with reference channel amplitudes | +| `AmpPerChannelNormWeightedMeanCorrectedA` | TH1F | AmpPerChannelNormWeightedMeanA normalized with the same values from last aging correction | +| `AmpPerChannelNormWeightedMeanCorrectedC` | TH1F | AmpPerChannelNormWeightedMeanC normalized with the same values from last aging correction | +| `AmpPerChannelNormWeightedMeanAfterLastCorrectionA` | TH1F | AmpPerChannelNormWeightedMeanA from the last aging correction | +| `AmpPerChannelNormWeightedMeanAfterLastCorrectionC` | TH1F | AmpPerChannelNormWeightedMeanC from the last aging correction | + +#### Trending + +Having four output MOs from the `AgingLaserPostProcTask`, allows us to create four time series, one per side. An example QC configuration that accomplishes this is in [`etc/ft0-aging-laser-postproc.json`](https://github.com/AliceO2Group/QualityControl/blob/master/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json). + +#### Notes & gotchas + +* **Off-by-one binning (C side)**: for channels `96–207` you need **112 bins** and an **exclusive** upper edge at `208`. Using `207` with 112 bins yields non-unit bin width and will trip histogram helpers. +* **Reference fits**: if all Gaussian fits fail, the post-processing update exits early and publishes nothing for that cycle. + diff --git a/Modules/FIT/FT0/etc/FT0_LUT.csv b/Modules/FIT/FT0/etc/FT0_LUT.csv new file mode 100644 index 0000000000..29094d347c --- /dev/null +++ b/Modules/FIT/FT0/etc/FT0_LUT.csv @@ -0,0 +1,209 @@ +channel #;FEE address;group;Cell;coordinate X in mm;coordinate Y in mm;coordinate Z in mm;HV board;HV board channel;PMT S/N;Module Type;FEE module;PM channel;DIM position;FEE ID;Link ID;EP ID;CRU ID;orientation;Signal channel;Detector quarter;Long signal cable #;Signal PP line #;Signal PP column #;I-V curve offset (a);I-V curve slope (b);Long HV cable #;Splitter box #;Splitter box output;Local attenuator (dB);Splitter box output intensity;Patch-cord label (C34->PM) +0;PMA5/Ch02;A-side Inner ring;A-B3;59.75;-13.25;3400;1;7;2180;PM;A5;2;26;64165;5;0;1106;2;FTA-B3-4;FTA-I;32;3;14;-4.47;0.21;8;OC-205;38;2;1.26;A32/3.14/5 02 +1;PMA5/Ch04;A-side Inner ring;A-B3;86.25;-13.25;3400;1;7;2180;PM;A5;4;66;64165;5;0;1106;2;FTA-B3-1;FTA-I;29;3;13;-4.47;0.21;8;OC-205;18;2;0.71;A29/3.13/5 04 +2;PMA5/Ch03;A-side Inner ring;A-B3;86.25;13.25;3400;1;7;2180;PM;A5;3;46;64165;5;0;1106;2;FTA-B3-2;FTA-I;30;1;11;-4.47;0.21;8;OC-205;35;0;0.98;A30/1.11/5 03 +3;PMA5/Ch01;A-side Inner ring;A-B3;59.75;13.25;3400;1;7;2180;PM;A5;1;6;64165;5;0;1106;2;FTA-B3-3;FTA-I;31;2;13;-4.47;0.21;8;OC-205;14;3;1.79;A31/2.13/5 01 +4;PMA6/Ch03;A-side Inner ring;A-B2;74.25;47.75;3400;1;6;2185;PM;A6;3;47;64166;6;0;1106;3;FTA-B2-2;FTA-I;26;2;11;-5.71;0.21;7;OC-205;60;0;0.9;A26/2.11/6 03 +5;PMA6/Ch01;A-side Inner ring;A-B2;47.75;47.75;3400;1;6;2185;PM;A6;1;7;64166;6;0;1106;3;FTA-B2-1;FTA-I;25;1;12;-5.71;0.21;7;OC-205;57;2;1.35;A25/1.12/6 01 +6;PMA6/Ch04;A-side Inner ring;A-B2;74.25;74.25;3400;1;6;2185;PM;A6;4;67;64166;6;0;1106;3;FTA-B2-3;FTA-I;27;1;13;-5.71;0.21;7;OC-205;6;0;0.9;A27/1.13/6 04 +7;PMA6/Ch02;A-side Inner ring;A-B2;47.75;74.25;3400;1;6;2185;PM;A6;2;27;64166;6;0;1106;3;FTA-B2-4;FTA-I;28;4;14;-5.71;0.21;7;OC-205;42;0;0.9;A28/4.14/6 02 +8;PMA7/Ch02;A-side Inner ring;A-C2;13.25;60.75;3400;1;11;2163;PM;A7;2;28;64167;7;0;1106;2;FTA-C2-1;FTA-I;45;4;15;-9.4;0.23;12;OC-205;28;5;1.56;A45/4.15/7 02 +9;PMA7/Ch04;A-side Inner ring;A-C2;13.25;87.25;3400;1;11;2163;PM;A7;4;68;64167;7;0;1106;2;FTA-C2-2;FTA-I;46;2;14;-9.4;0.23;12;OC-205;5;0;0.74;A46/2.14/7 04 +10;PMA7/Ch03;A-side Inner ring;A-C2;-13.25;87.25;3400;1;11;2163;PM;A7;3;48;64167;7;0;1106;2;FTA-C2-3;FTA-I;47;2;15;-9.4;0.23;12;OC-205;54;0;0.82;A47/2.15/7 03 +11;PMA7/Ch01;A-side Inner ring;A-C2;-13.25;60.75;3400;1;11;2163;PM;A7;1;8;64167;7;0;1106;2;FTA-C2-4;FTA-I;48;4;17;-9.4;0.23;12;OC-205;64;2;1.35;A48/4.17/7 01 +12;PMA0/Ch03;A-side Inner ring;A-D2;-47.75;74.25;3400;0;3;2186;PM;A0;3;41;64160;0;0;1106;2;FTA-D2-2;FTA-O;62;4;20;-3.64;0.2;16;OC-206;36;0;0.69;A62/4.20/0 03 +13;PMA0/Ch01;A-side Inner ring;A-D2;-47.75;47.75;3400;0;3;2186;PM;A0;1;1;64160;0;0;1106;2;FTA-D2-1;FTA-O;61;2;19;-3.64;0.2;16;OC-206;52;6;1.08;A61/2.19/0 01 +14;PMA0/Ch04;A-side Inner ring;A-D2;-74.25;74.25;3400;0;3;2186;PM;A0;4;61;64160;0;0;1106;2;FTA-D2-3;FTA-O;63;5;20;-3.64;0.2;16;OC-206;27;2;0.8;A63/5.20/0 04 +15;PMA0/Ch02;A-side Inner ring;A-D2;-74.25;47.75;3400;0;3;2186;PM;A0;2;21;64160;0;0;1106;2;FTA-D2-4;FTA-O;64;3;19;-3.64;0.2;16;OC-206;38;7;1.67;A64/3.19/0 02 +16;PMA1/Ch02;A-side Inner ring;A-D3;-59.75;13.25;3400;0;4;2184;PM;A1;2;22;64161;1;0;1106;2;FTA-D3-2;FTA-O;66;3;5;-7.76;0.23;17;OC-206;59;0;0.53;A66/3.5/1 02 +17;PMA1/Ch04;A-side Inner ring;A-D3;-86.25;13.25;3400;0;4;2184;PM;A1;4;62;64161;1;0;1106;2;FTA-D3-3;FTA-O;67;5;5;-7.76;0.23;17;OC-206;61;0;0.55;A67/5.5/1 04 +18;PMA1/Ch03;A-side Inner ring;A-D3;-86.25;-13.25;3400;0;4;2184;PM;A1;3;42;64161;1;0;1106;2;FTA-D3-4;FTA-O;68;3;4;-7.76;0.23;17;OC-206;19;2;0.96;A68/3.4/1 03 +19;PMA1/Ch01;A-side Inner ring;A-D3;-59.75;-13.25;3400;0;4;2184;PM;A1;1;2;64161;1;0;1106;2;FTA-D3-1;FTA-O;65;4;4;-7.76;0.23;17;OC-206;3;8;1.56;A65/4.4/1 01 +20;PMA2/Ch03;A-side Inner ring;A-D4;-74.25;-47.75;3400;0;5;2164;PM;A2;3;43;64162;2;0;1106;1;FTA-D4-2;FTA-O;70;2;5;-5.22;0.21;18;OC-206;40;7;3.48;A70/2.5/2 03 +21;PMA2/Ch01;A-side Inner ring;A-D4;-47.75;-47.75;3400;0;5;2164;PM;A2;1;3;64162;2;0;1106;1;FTA-D4-1;FTA-O;69;4;5;-5.22;0.21;18;OC-206;28;6;1.1;A69/4.5/2 01 +22;PMA2/Ch04;A-side Inner ring;A-D4;-74.25;-74.25;3400;0;5;2164;PM;A2;4;63;64162;2;0;1106;1;FTA-D4-3;FTA-O;71;2;4;-5.22;0.21;18;OC-206;9;0;0.54;A71/2.4/2 04 +23;PMA2/Ch02;A-side Inner ring;A-D4;-47.75;-74.25;3400;0;5;2164;PM;A2;2;23;64162;2;0;1106;1;FTA-D4-4;FTA-O;72;1;4;-5.22;0.21;18;OC-206;44;0;0.62;A72/1.4/2 02 +24;PMA3/Ch02;A-side Inner ring;A-C4;-13.25;-60.75;3400;0;0;2132;PM;A3;2;24;64163;3;0;1106;1;FTA-C4-2;FTA-O;50;3;20;-5.12;0.21;13;OC-206;63;6;1.08;A50/3.20/3 02 +25;PMA3/Ch04;A-side Inner ring;A-C4;-13.25;-87.25;3400;0;0;2132;PM;A3;4;64;64163;3;0;1106;1;FTA-C4-3;FTA-O;51;2;20;-5.12;0.21;13;OC-206;21;3;0.8;A51/2.20/3 04 +26;PMA3/Ch03;A-side Inner ring;A-C4;13.25;-87.25;3400;0;0;2132;PM;A3;3;44;64163;3;0;1106;1;FTA-C4-4;FTA-O;52;1;19;-5.12;0.21;13;OC-206;43;0;0.55;A52/1.19/3 03 +27;PMA3/Ch01;A-side Inner ring;A-C4;13.25;-60.75;3400;0;0;2132;PM;A3;1;4;64163;3;0;1106;1;FTA-C4-1;FTA-O;49;1;17;-5.12;0.21;13;OC-206;39;6;1.26;A49/1.17/3 01 +28;PMA4/Ch03;A-side Inner ring;A-B4;47.75;-74.25;3400;1;8;2183;PM;A4;3;45;64164;4;0;1106;4;FTA-B4-2;FTA-I;34;3;17;-6.65;0.22;9;OC-205;59;3;1.26;A34/3.17/4 03 +29;PMA4/Ch01;A-side Inner ring;A-B4;47.75;-47.75;3400;1;8;2183;PM;A4;1;5;64164;4;0;1106;4;FTA-B4-1;FTA-I;33;1;16;-6.65;0.22;9;OC-205;45;1;1.15;A33/1.16/4 01 +30;PMA4/Ch04;A-side Inner ring;A-B4;74.25;-74.25;3400;1;8;2183;PM;A4;4;65;64164;4;0;1106;4;FTA-B4-3;FTA-I;35;5;15;-6.65;0.22;9;OC-205;21;3;1.35;A35/5.15/4 04 +31;PMA4/Ch02;A-side Inner ring;A-B4;74.25;-47.75;3400;1;8;2183;PM;A4;2;25;64164;4;0;1106;4;FTA-B4-4;FTA-I;36;4;16;-6.65;0.22;9;OC-205;16;2;0.71;A36/4.16/4 02 +32;PMA5/Ch10;A-side Outer ring;A-A3;120.75;-13.25;3400;1;2;2174;PM;A5;10;186;64165;5;0;1106;4;FTA-A3-2;FTA-I;10;2;1;-4.23;0.21;3;OC-205;56;0;0.73;A10/2.1/5 10 +33;PMA5/Ch12;A-side Outer ring;A-A3;147.25;-13.25;3400;1;2;2174;PM;A5;12;226;64165;5;0;1106;4;FTA-A3-3;FTA-I;11;3;3;-4.23;0.21;3;OC-205;9;3;0.76;A11/3.3/5 12 +34;PMA5/Ch11;A-side Outer ring;A-A3;147.25;13.25;3400;1;2;2174;PM;A5;11;206;64165;5;0;1106;4;FTA-A3-4;FTA-I;12;1;2;-4.23;0.21;3;OC-205;52;2;1.13;A12/1.2/5 11 +35;PMA5/Ch09;A-side Outer ring;A-A3;120.75;13.25;3400;1;2;2174;PM;A5;9;166;64165;5;0;1106;4;FTA-A3-1;FTA-I;9;2;2;-4.23;0.21;3;OC-205;48;4;3.03;A9/2.2/5 09 +36;PMA6/Ch07;A-side Outer ring;A-A2;135.25;47.75;3400;1;1;2181;PM;A6;7;127;64166;6;0;1106;3;FTA-A2-2;FTA-I;6;5;1;-7.22;0.22;2;OC-205;24;0;0.74;A6/5.1/6 07 +37;PMA6/Ch05;A-side Outer ring;A-A2;108.75;47.75;3400;1;1;2181;PM;A6;5;87;64166;6;0;1106;3;FTA-A2-1;FTA-I;5;3;2;-7.22;0.22;2;OC-205;63;5;2;A5/3.2/6 05 +38;PMA6/Ch08;A-side Outer ring;A-A2;135.25;74.25;3400;1;1;2181;PM;A6;8;147;64166;6;0;1106;3;FTA-A2-3;FTA-I;7;5;2;-7.22;0.22;2;OC-205;50;0;0.76;A7/5.2/6 08 +39;PMA6/Ch06;A-side Outer ring;A-A2;108.75;74.25;3400;1;1;2181;PM;A6;6;107;64166;6;0;1106;3;FTA-A2-4;FTA-I;8;4;2;-7.22;0.22;2;OC-205;53;4;1.56;A8/4.2/6 06 +40;PMA6/Ch11;A-side Outer ring;A-A1;135.25;108.75;3400;1;0;2173;PM;A6;11;207;64166;6;0;1106;3;FTA-A1-2;FTA-I;2;5;4;-3.52;0.2;1;OC-205;27;1;0.87;A2/5.4/6 11 +41;PMA6/Ch09;A-side Outer ring;A-A1;108.75;108.75;3400;1;0;2173;PM;A6;9;167;64166;6;0;1106;3;FTA-A1-1;FTA-I;1;5;3;-3.52;0.2;1;OC-205;41;3;1.32;A1/5.3/6 09 +42;PMA6/Ch12;A-side Outer ring;A-A1;135.25;135.25;3400;1;0;2173;PM;A6;12;227;64166;6;0;1106;3;FTA-A1-3;FTA-I;3;4;1;-3.52;0.2;1;OC-205;36;0;0.9;A3/4.1/6 12 +43;PMA6/Ch10;A-side Outer ring;A-A1;108.75;135.25;3400;1;0;2173;PM;A6;10;187;64166;6;0;1106;3;FTA-A1-4;FTA-I;4;4;3;-3.52;0.2;1;OC-205;7;1;1.05;A4/4.3/6 10 +44;PMA7/Ch06;A-side Outer ring;A-B1;74.25;108.75;3400;1;5;2182;PM;A7;6;108;64167;7;0;1106;2;FTA-B1-1;FTA-I;21;3;11;-3.8;0.2;6;OC-205;13;6;2.47;A21/3.11/7 06 +45;PMA7/Ch08;A-side Outer ring;A-B1;74.25;135.25;3400;1;5;2182;PM;A7;8;148;64167;7;0;1106;2;FTA-B1-2;FTA-I;22;3;12;-3.8;0.2;6;OC-205;49;0;0.8;A22/3.12/7 08 +46;PMA7/Ch05;A-side Outer ring;A-B1;47.75;108.75;3400;1;5;2182;PM;A7;5;88;64167;7;0;1106;2;FTA-B1-4;FTA-I;24;4;13;-3.8;0.2;6;OC-205;58;8;3.25;A24/4.13/7 05 +47;PMA7/Ch07;A-side Outer ring;A-B1;47.75;135.25;3400;1;5;2182;PM;A7;7;128;64167;7;0;1106;2;FTA-B1-3;FTA-I;23;2;12;-3.8;0.2;6;OC-205;55;0;0.66;A23/2.12/7 07 +48;PMA7/Ch10;A-side Outer ring;A-C1;13.25;121.75;3400;1;10;2187;PM;A7;10;188;64167;7;0;1106;4;FTA-C1-3;FTA-I;43;1;15;-3.13;0.2;11;OC-205;25;4;1.42;A43/1.15/7 10 +49;PMA7/Ch12;A-side Outer ring;A-C1;13.25;148.25;3400;1;10;2187;PM;A7;12;228;64167;7;0;1106;4;FTA-C1-4;FTA-I;44;5;16;-3.13;0.2;11;OC-205;15;2;1.1;A44/5.16/7 12 +50;PMA7/Ch11;A-side Outer ring;A-C1;-13.25;148.25;3400;1;10;2187;PM;A7;11;208;64167;7;0;1106;4;FTA-C1-1;FTA-I;41;5;17;-3.13;0.2;11;OC-205;19;0;0.68;A41/5.17/7 11 +51;PMA7/Ch09;A-side Outer ring;A-C1;-13.25;121.75;3400;1;10;2187;PM;A7;9;168;64167;7;0;1106;4;FTA-C1-2;FTA-I;42;3;16;-3.13;0.2;11;OC-205;17;5;1.32;A42/3.16/7 09 +52;PMA0/Ch07;A-side Outer ring;A-D1;-47.75;135.25;3400;0;2;2117;PM;A0;7;121;64160;0;0;1106;4;FTA-D1-4;FTA-O;60;3;18;-6.48;0.22;15;OC-206;23;1;0.54;A60/3.18/0 07 +53;PMA0/Ch05;A-side Outer ring;A-D1;-47.75;108.75;3400;0;2;2117;PM;A0;5;81;64160;0;0;1106;4;FTA-D1-3;FTA-O;59;4;19;-6.48;0.22;15;OC-206;64;8;1.56;A59/4.19/0 05 +54;PMA0/Ch08;A-side Outer ring;A-D1;-74.25;135.25;3400;0;2;2117;PM;A0;8;141;64160;0;0;1106;4;FTA-D1-1;FTA-O;57;5;18;-6.48;0.22;15;OC-206;57;2;0.58;A57/5.18/0 08 +55;PMA0/Ch06;A-side Outer ring;A-D1;-74.25;108.75;3400;0;2;2117;PM;A0;6;101;64160;0;0;1106;4;FTA-D1-2;FTA-O;58;4;18;-6.48;0.22;15;OC-206;17;7;2.1;A58/4.18/0 06 +56;PMA0/Ch11;A-side Outer ring;A-E1;-108.75;135.25;3400;0;7;2170;PM;A0;11;201;64160;0;0;1106;2;FTA-E1-2;FTA-O;78;5;6;-4.67;0.21;20;OC-206;60;4;0.94;A78/5.6/0 11 +57;PMA0/Ch09;A-side Outer ring;A-E1;-108.75;108.75;3400;0;7;2170;PM;A0;9;161;64160;0;0;1106;2;FTA-E1-1;FTA-O;77;1;6;-4.67;0.21;20;OC-206;22;8;1.52;A77/1.6/0 09 +58;PMA0/Ch12;A-side Outer ring;A-E1;-135.25;135.25;3400;0;7;2170;PM;A0;12;221;64160;0;0;1106;2;FTA-E1-3;FTA-O;79;1;5;-4.67;0.21;20;OC-206;62;2;0.68;A79/1.5/0 12 +59;PMA0/Ch10;A-side Outer ring;A-E1;-135.25;108.75;3400;0;7;2170;PM;A0;10;181;64160;0;0;1106;2;FTA-E1-4;FTA-O;80;2;6;-4.67;0.21;20;OC-206;8;3;0.82;A80/2.6/0 10 +60;PMA1/Ch06;A-side Outer ring;A-E2;-108.75;74.25;3400;0;8;2154;PM;A1;6;102;64161;1;0;1106;1;FTA-E2-1;FTA-O;81;5;8;-7.06;0.22;21;OC-206;25;3;0.84;A81/5.8/1 06 +61;PMA1/Ch08;A-side Outer ring;A-E2;-135.25;74.25;3400;0;8;2154;PM;A1;8;142;64161;1;0;1106;1;FTA-E2-2;FTA-O;82;3;7;-7.06;0.22;21;OC-206;53;8;1.45;A82/3.7/1 08 +62;PMA1/Ch05;A-side Outer ring;A-E2;-108.75;47.75;3400;0;8;2154;PM;A1;5;82;64161;1;0;1106;1;FTA-E2-4;FTA-O;84;4;8;-7.06;0.22;21;OC-206;50;0;0.53;A84/4.8/1 05 +63;PMA1/Ch07;A-side Outer ring;A-E2;-135.25;47.75;3400;0;8;2154;PM;A1;7;122;64161;1;0;1106;1;FTA-E2-3;FTA-O;83;2;8;-7.06;0.22;21;OC-206;55;4;0.98;A83/2.8/1 07 +64;PMA1/Ch10;A-side Outer ring;A-E3;-120.75;13.25;3400;0;9;2124;PM;A1;10;182;64161;1;0;1106;2;FTA-E3-2;FTA-O;86;1;8;-7.57;0.23;22;OC-206;2;4;0.92;A86/1.8/1 10 +65;PMA1/Ch12;A-side Outer ring;A-E3;-147.25;13.25;3400;0;9;2124;PM;A1;12;222;64161;1;0;1106;2;FTA-E3-3;FTA-O;87;2;7;-7.57;0.23;22;OC-206;20;1;0.61;A87/2.7/1 12 +66;PMA1/Ch11;A-side Outer ring;A-E3;-147.25;-13.25;3400;0;9;2124;PM;A1;11;202;64161;1;0;1106;2;FTA-E3-4;FTA-O;88;1;7;-7.57;0.23;22;OC-206;1;6;1.05;A88/1.7/1 11 +67;PMA1/Ch09;A-side Outer ring;A-E3;-120.75;-13.25;3400;0;9;2124;PM;A1;9;162;64161;1;0;1106;2;FTA-E3-1;FTA-O;85;3;8;-7.57;0.23;22;OC-206;56;7;1.42;A85/3.8/1 09 +68;PMA2/Ch07;A-side Outer ring;A-E4;-135.25;-47.75;3400;0;10;2135;PM;A2;7;123;64162;2;0;1106;2;FTA-E4-3;FTA-O;91;1;10;-8.88;0.23;23;OC-206;37;7;1.18;A91/1.10/2 07 +69;PMA2/Ch05;A-side Outer ring;A-E4;-108.75;-47.75;3400;0;10;2135;PM;A2;5;83;64162;2;0;1106;2;FTA-E4-2;FTA-O;90;4;10;-8.88;0.23;23;OC-206;18;6;1.26;A90/4.10/2 05 +70;PMA2/Ch08;A-side Outer ring;A-E4;-135.25;-74.25;3400;0;10;2135;PM;A2;8;143;64162;2;0;1106;2;FTA-E4-4;FTA-O;92;2;10;-8.88;0.23;23;OC-206;58;0;0.48;A92/2.10/2 08 +71;PMA2/Ch06;A-side Outer ring;A-E4;-108.75;-74.25;3400;0;10;2135;PM;A2;6;103;64162;2;0;1106;2;FTA-E4-1;FTA-O;89;5;11;-8.88;0.23;23;OC-206;24;2;0.66;A89/5.11/2 06 +72;PMA2/Ch11;A-side Outer ring;A-E5;-135.25;-108.8;3400;0;11;2160;PM;A2;11;203;64162;2;0;1106;1;FTA-E5-2;FTA-O;94;1;9;-7.61;0.23;24;OC-206;41;6;1.32;A94/1.9/2 11 +73;PMA2/Ch09;A-side Outer ring;A-E5;-108.75;-108.8;3400;0;11;2160;PM;A2;9;163;64162;2;0;1106;1;FTA-E5-1;FTA-O;93;5;10;-7.61;0.23;24;OC-206;54;4;2.3;A93/5.10/2 09 +74;PMA2/Ch12;A-side Outer ring;A-E5;-135.25;-135.3;3400;0;11;2160;PM;A2;12;223;64162;2;0;1106;1;FTA-E5-3;FTA-O;95;3;10;-7.61;0.23;24;OC-206;34;1;0.58;A95/3.10/2 12 +75;PMA2/Ch10;A-side Outer ring;A-E5;-108.75;-135.3;3400;0;11;2160;PM;A2;10;183;64162;2;0;1106;1;FTA-E5-4;FTA-O;96;4;11;-7.61;0.23;24;OC-206;7;2;0.65;A96/4.11/2 10 +76;PMA3/Ch06;A-side Outer ring;A-D5;-74.25;-108.8;3400;0;6;2171;PM;A3;6;104;64163;3;0;1106;3;FTA-D5-4;FTA-O;76;5;7;-7.07;0.22;19;OC-206;42;5;2.47;A76/5.7/3 06 +77;PMA3/Ch08;A-side Outer ring;A-D5;-74.25;-135.3;3400;0;6;2171;PM;A3;8;144;64163;3;0;1106;3;FTA-D5-1;FTA-O;73;4;7;-7.07;0.22;19;OC-206;45;2;0.54;A73/4.7/3 08 +78;PMA3/Ch05;A-side Outer ring;A-D5;-47.75;-108.8;3400;0;6;2171;PM;A3;5;84;64163;3;0;1106;3;FTA-D5-3;FTA-O;75;4;6;-7.07;0.22;19;OC-206;26;11;2.41;A75/4.6/3 05 +79;PMA3/Ch07;A-side Outer ring;A-D5;-47.75;-135.3;3400;0;6;2171;PM;A3;7;124;64163;3;0;1106;3;FTA-D5-2;FTA-O;74;3;6;-7.07;0.22;19;OC-206;49;2;0.61;A74/3.6/3 07 +80;PMA3/Ch10;A-side Outer ring;A-C5;-13.25;-121.8;3400;0;1;2153;PM;A3;10;184;64163;3;0;1106;3;FTA-C5-4;FTA-O;56;1;18;-4.05;0.2;14;OC-206;51;10;1.42;A56/1.18/3 10 +81;PMA3/Ch12;A-side Outer ring;A-C5;-13.25;-148.3;3400;0;1;2153;PM;A3;12;224;64163;3;0;1106;3;FTA-C5-1;FTA-O;53;5;19;-4.05;0.2;14;OC-206;35;4;1;A53/5.19/3 12 +82;PMA3/Ch11;A-side Outer ring;A-C5;13.25;-148.3;3400;0;1;2153;PM;A3;11;204;64163;3;0;1106;3;FTA-C5-2;FTA-O;54;1;20;-4.05;0.2;14;OC-206;33;3;0.69;A54/1.20/3 11 +83;PMA3/Ch09;A-side Outer ring;A-C5;13.25;-121.8;3400;0;1;2153;PM;A3;9;164;64163;3;0;1106;3;FTA-C5-3;FTA-O;55;2;18;-4.05;0.2;14;OC-206;46;3;0.71;A55/2.18/3 09 +84;PMA4/Ch07;A-side Outer ring;A-B5;47.75;-135.3;3400;1;9;2176;PM;A4;7;125;64164;4;0;1106;4;FTA-B5-2;FTA-I;38;2;16;-5.27;0.21;10;OC-205;1;0;0.84;A38/2.16/4 07 +85;PMA4/Ch05;A-side Outer ring;A-B5;47.75;-108.8;3400;1;9;2176;PM;A4;5;85;64164;4;0;1106;4;FTA-B5-1;FTA-I;S01;2;9;-5.27;0.21;10;OC-205;37;6;1.91;AS01/2.9/4 05 +86;PMA4/Ch08;A-side Outer ring;A-B5;74.25;-135.3;3400;1;9;2176;PM;A4;8;145;64164;4;0;1106;4;FTA-B5-3;FTA-I;39;3;15;-5.27;0.21;10;OC-205;20;0;0.74;A39/3.15/4 08 +87;PMA4/Ch06;A-side Outer ring;A-B5;74.25;-108.8;3400;1;9;2176;PM;A4;6;105;64164;4;0;1106;4;FTA-B5-4;FTA-I;40;1;14;-5.27;0.21;10;OC-205;46;2;1.29;A40/1.14/4 06 +88;PMA4/Ch11;A-side Outer ring;A-A5;108.75;-135.3;3400;1;4;2169;PM;A4;11;205;64164;4;0;1106;2;FTA-A5-4;FTA-I;20;5;14;-6.39;0.22;5;OC-205;51;3;0.96;A20/5.14/4 11 +89;PMA4/Ch09;A-side Outer ring;A-A5;108.75;-108.8;3400;1;4;2169;PM;A4;9;165;64164;4;0;1106;2;FTA-A5-3;FTA-I;19;4;12;-6.39;0.22;5;OC-205;23;4;1.03;A19/4.12/4 09 +90;PMA4/Ch12;A-side Outer ring;A-A5;135.25;-135.3;3400;1;4;2169;PM;A4;12;225;64164;4;0;1106;2;FTA-A5-1;FTA-I;17;5;13;-6.39;0.22;5;OC-205;8;1;0.8;A17/5.13/4 12 +91;PMA4/Ch10;A-side Outer ring;A-A5;135.25;-108.8;3400;1;4;2169;PM;A4;10;185;64164;4;0;1106;2;FTA-A5-2;FTA-I;18;5;12;-6.39;0.22;5;OC-205;40;5;1.29;A18/5.12/4 10 +92;PMA5/Ch06;A-side Outer ring;A-A4;108.75;-74.25;3400;1;3;2179;PM;A5;6;106;64165;5;0;1106;4;FTA-A4-2;FTA-I;14;3;1;-7.01;0.22;4;OC-205;2;3;1.21;A14/3.1/5 06 +93;PMA5/Ch08;A-side Outer ring;A-A4;135.25;-74.25;3400;1;3;2179;PM;A5;8;146;64165;5;0;1106;4;FTA-A4-3;FTA-I;15;2;3;-7.01;0.22;4;OC-205;39;2;0.78;A15/2.3/5 08 +94;PMA5/Ch05;A-side Outer ring;A-A4;108.75;-47.75;3400;1;3;2179;PM;A5;5;86;64165;5;0;1106;4;FTA-A4-1;FTA-I;13;1;1;-7.01;0.22;4;OC-205;26;3;1.91;A13/1.1/5 05 +95;PMA5/Ch07;A-side Outer ring;A-A4;135.25;-47.75;3400;1;3;2179;PM;A5;7;126;64165;5;0;1106;4;FTA-A4-4;FTA-I;16;1;3;-7.01;0.22;4;OC-205;22;2;0.78;A16/1.3/5 07 +96;PMC3/Ch03;C-side Inner ring;C-E3;103.2;17.8;-831.8;2;9;2130;PM;C3;3;54;64243;3;1;1106;2;FTC-E3-4;FTC-T;20;4;7;-4.95;0.21;5;OC-201;26;1;1.08;C20/4.7/3 03 +97;PMC3/Ch01;C-side Inner ring;C-E3;76.9;17.8;-834.6;2;9;2130;PM;C3;1;14;64243;3;1;1106;2;FTC-E3-1;FTC-T;17;3;8;-4.95;0.21;5;OC-201;42;3;1.39;C17/3.8/3 01 +98;PMC3/Ch04;C-side Inner ring;C-E3;103.1;44.2;-830.8;2;9;2130;PM;C3;4;74;64243;3;1;1106;2;FTC-E3-3;FTC-T;19;1;10;-4.95;0.21;5;OC-201;18;0;0.82;C19/1.10/3 04 +99;PMC3/Ch02;C-side Inner ring;C-E3;76.8;44.2;-833.6;2;9;2130;PM;C3;2;34;64243;3;1;1106;2;FTC-E3-2;FTC-T;18;5;7;-4.95;0.21;5;OC-201;23;0;0.82;C18/5.7/3 02 +100;PMC8/Ch07;C-side Inner ring;C-E2;103.2;78.7;-828.7;4;1;2133;PM;C8;7;139;64248;8;1;1106;2;FTC-E2-4;FTC-T;16;2;9;-7.18;0.21;4;OC-201;56;0;0.9;C16/2.9/8 07 +101;PMC8/Ch05;C-side Inner ring;C-E2;76.8;79;-831.6;4;1;2133;PM;C8;5;99;64248;8;1;1106;2;FTC-E2-1;FTC-T;13;5;9;-7.18;0.21;4;OC-201;54;4;1.53;C13/5.9/8 05 +102;PMC8/Ch08;C-side Inner ring;C-E2;103.2;105;-825.8;4;1;2133;PM;C8;8;159;64248;8;1;1106;2;FTC-E2-3;FTC-T;15;3;9;-7.18;0.21;4;OC-201;21;0;0.82;C15/3.9/8 08 +103;PMC8/Ch06;C-side Inner ring;C-E2;76.8;105.3;-828.7;4;1;2133;PM;C8;6;119;64248;8;1;1106;2;FTC-E2-2;FTC-T;14;4;9;-7.18;0.21;4;OC-201;40;0;0.92;C14/4.9/8 06 +104;PMC2/Ch02;C-side Inner ring;C-D2;43.2;78.8;-833.7;2;7;2128;PM;C2;2;33;64242;2;1;1106;1;FTC-D2-3;FTC-T;7;6;9;-5.04;0.21;2;OC-201;11;0;1.03;C7/6.9/2 02 +105;PMC2/Ch04;C-side Inner ring;C-D2;43.2;105.1;-830.8;2;7;2128;PM;C2;4;73;64242;2;1;1106;1;FTC-D2-2;FTC-T;6;1;11;-5.04;0.21;2;OC-201;4;0;0.84;C6/1.11/2 04 +106;PMC2/Ch01;C-side Inner ring;C-D2;16.8;78.9;-834.6;2;7;2128;PM;C2;1;13;64242;2;1;1106;1;FTC-D2-4;FTC-T;8;6;8;-5.04;0.21;2;OC-201;13;0;1.13;C8/6.8/2 01 +107;PMC2/Ch03;C-side Inner ring;C-D2;16.8;105.2;-831.7;2;7;2128;PM;C2;3;53;64242;2;1;1106;1;FTC-D2-1;FTC-T;5;1;12;-5.04;0.21;2;OC-201;27;2;1.42;C5/1.12/2 03 +108;PMC1/Ch03;C-side Inner ring;C-C2;-16.8;105.2;-831.7;2;5;2123;PM;C1;3;52;64241;1;1;1106;3;FTC-C2-4;FTC-T;56;4;3;-7.38;0.23;14;OC-201;7;2;0.98;C56/4.3/1 03 +109;PMC1/Ch01;C-side Inner ring;C-C2;-16.8;78.9;-834.6;2;5;2123;PM;C1;1;12;64241;1;1;1106;3;FTC-C2-1;FTC-T;53;3;3;-7.38;0.23;14;OC-201;58;4;1.08;C53/3.3/1 01 +110;PMC1/Ch04;C-side Inner ring;C-C2;-43.2;105.1;-830.8;2;5;2123;PM;C1;4;72;64241;1;1;1106;3;FTC-C2-3;FTC-T;55;6;1;-7.38;0.23;14;OC-201;6;1;0.82;C55/6.1/1 04 +111;PMC1/Ch02;C-side Inner ring;C-C2;-43.2;78.8;-833.7;2;5;2123;PM;C1;2;32;64241;1;1;1106;3;FTC-C2-2;FTC-T;54;3;2;-7.38;0.23;14;OC-201;49;2;1.06;C54/3.2/1 02 +112;PMC8/Ch03;C-side Inner ring;C-B2;-76.8;105.3;-828.7;4;0;2121;PM;C8;3;59;64248;8;1;1106;4;FTC-B2-1;FTC-T;41;6;2;-5.97;0.22;11;OC-201;14;3;1.16;C41/6.2/8 03 +113;PMC8/Ch01;C-side Inner ring;C-B2;-76.8;79;-831.6;4;0;2121;PM;C8;1;19;64248;8;1;1106;4;FTC-B2-2;FTC-T;42;4;4;-5.97;0.22;11;OC-201;60;0;0.84;C42/4.4/8 01 +114;PMC8/Ch04;C-side Inner ring;C-B2;-103.2;105;-825.8;4;0;2121;PM;C8;4;79;64248;8;1;1106;4;FTC-B2-4;FTC-T;44;5;1;-5.97;0.22;11;OC-201;43;1;1.06;C44/5.1/8 03 +115;PMC8/Ch02;C-side Inner ring;C-B2;-103.2;78.7;-828.7;4;0;2121;PM;C8;2;39;64248;8;1;1106;4;FTC-B2-3;FTC-T;43;5;3;-5.97;0.22;11;OC-201;37;1;0.9;C43/5.3/8 04 +116;PMC0/Ch02;C-side Inner ring;C-B3;-76.8;44.2;-833.6;2;3;2119;PM;C0;2;31;64240;0;1;1106;4;FTC-B3-1;FTC-T;45;4;2;-6.93;0.22;12;OC-201;55;2;1.24;C45/4.2/0 02 +117;PMC0/Ch04;C-side Inner ring;C-B3;-103.1;44.2;-830.8;2;3;2119;PM;C0;4;71;64240;0;1;1106;4;FTC-B3-4;FTC-T;48;3;4;-6.93;0.22;12;OC-201;38;2;1.06;C48/3.4/0 04 +118;PMC0/Ch01;C-side Inner ring;C-B3;-76.9;17.8;-834.6;2;3;2119;PM;C0;1;11;64240;0;1;1106;4;FTC-B3-2;FTC-T;46;1;4;-6.93;0.22;12;OC-201;44;0;0.9;C46/1.4/0 01 +119;PMC0/Ch03;C-side Inner ring;C-B3;-103.2;17.8;-831.8;2;3;2119;PM;C0;3;51;64240;0;1;1106;4;FTC-B3-3;FTC-T;47;2;4;-6.93;0.22;12;OC-201;46;0;0.82;C47/2.4/0 03 +120;PMC7/Ch03;C-side Inner ring;C-B4;-103.2;-17.8;-831.8;3;2;2136;PM;C7;3;58;64247;7;1;1106;4;FTC-B4-4;FTC-B;96;6;19;-4.4;0.21;24;OC-202;36;1;0.77;C96/6.19/7 03 +121;PMC7/Ch01;C-side Inner ring;C-B4;-76.9;-17.8;-834.6;3;2;2136;PM;C7;1;18;64247;7;1;1106;4;FTC-B4-1;FTC-B;93;5;20;-4.4;0.21;24;OC-202;22;0;0.59;C93/5.20/7 01 +122;PMC7/Ch04;C-side Inner ring;C-B4;-103.1;-44.2;-830.8;3;2;2136;PM;C7;4;78;64247;7;1;1106;4;FTC-B4-3;FTC-B;95;4;19;-4.4;0.21;24;OC-202;14;5;1.33;C95/4.19/7 04 +123;PMC7/Ch02;C-side Inner ring;C-B4;-76.8;-44.2;-833.6;3;2;2136;PM;C7;2;38;64247;7;1;1106;4;FTC-B4-2;FTC-B;94;6;20;-4.4;0.21;24;OC-202;58;3;0.88;C94/6.20/7 02 +124;PMC8/Ch11;C-side Inner ring;C-B5;-103.2;-78.7;-828.7;4;3;2143;PM;C8;11;219;64248;8;1;1106;4;FTC-B5-4;FTC-B;100;1;18;-6.15;0.22;25;OC-202;48;0;0.7;C100/1.18/8 11 +125;PMC8/Ch09;C-side Inner ring;C-B5;-76.8;-79;-831.6;4;3;2143;PM;C8;9;179;64248;8;1;1106;4;FTC-B5-1;FTC-B;97;4;16;-6.15;0.22;25;OC-202;59;3;0.99;C97/4.16/8 09 +126;PMC8/Ch12;C-side Inner ring;C-B5;-103.2;-105;-825.8;4;3;2143;PM;C8;12;239;64248;8;1;1106;4;FTC-B5-3;FTC-B;99;2;17;-6.15;0.22;25;OC-202;26;0;0.67;C99/2.17/8 12 +127;PMC8/Ch10;C-side Inner ring;C-B5;-76.8;-105.3;-828.7;4;3;2143;PM;C8;10;199;64248;8;1;1106;4;FTC-B5-2;FTC-B;98;1;19;-6.15;0.22;25;OC-202;11;4;1.33;C98/1.19/8 10 +128;PMC6/Ch02;C-side Inner ring;C-C5;-43.2;-78.8;-833.7;3;4;2148;PM;C6;2;37;64246;6;1;1106;3;FTC-C5-3;FTC-B;107;3;16;-3.91;0.21;27;OC-202;49;0;0.58;C107/3.16/6 02 +129;PMC6/Ch04;C-side Inner ring;C-C5;-43.2;-105.1;-830.8;3;4;2148;PM;C6;4;77;64246;6;1;1106;3;FTC-C5-2;FTC-B;106;3;17;-3.91;0.21;27;OC-202;25;3;0.92;C106/3.17/6 04 +130;PMC6/Ch01;C-side Inner ring;C-C5;-16.8;-78.9;-834.6;3;4;2148;PM;C6;1;17;64246;6;1;1106;3;FTC-C5-4;FTC-B;108;4;18;-3.91;0.21;27;OC-202;52;3;1.01;C108/4.18/6 01 +131;PMC6/Ch03;C-side Inner ring;C-C5;-16.8;-105.2;-831.7;3;4;2148;PM;C6;3;57;64246;6;1;1106;3;FTC-C5-1;FTC-B;105;5;17;-3.91;0.21;27;OC-202;44;3;0.94;C105/5.17/6 03 +132;PMC5/Ch03;C-side Inner ring;C-D5;16.8;-105.2;-831.7;3;6;2157;PM;C5;3;56;64245;5;1;1106;1;FTC-D5-4;FTC-B;60;3;14;-7.01;0.22;15;OC-202;19;0;0.58;C60/3.14/5 03 +133;PMC5/Ch01;C-side Inner ring;C-D5;16.8;-78.9;-834.6;3;6;2157;PM;C5;1;16;64245;5;1;1106;1;FTC-D5-1;FTC-B;57;1;16;-7.01;0.22;15;OC-202;35;7;1.33;C57/1.16/5 01 +134;PMC5/Ch04;C-side Inner ring;C-D5;43.2;-105.1;-830.8;3;6;2157;PM;C5;4;76;64245;5;1;1106;1;FTC-D5-3;FTC-B;59;5;15;-7.01;0.22;15;OC-202;60;2;0.67;C59/5.15/5 04 +135;PMC5/Ch02;C-side Inner ring;C-D5;43.2;-78.8;-833.7;3;6;2157;PM;C5;2;36;64245;5;1;1106;1;FTC-D5-2;FTC-B;58;1;17;-7.01;0.22;15;OC-202;57;4;1.16;C58/1.17/5 02 +136;PMC9/Ch03;C-side Inner ring;C-E5;76.8;-105.3;-828.7;4;2;2158;PM;C9;3;60;64249;9;1;1106;2;FTC-E5-1;FTC-B;69;6;13;-4.93;0.21;18;OC-202;47;2;0.88;C69/6.13/9 03 +137;PMC9/Ch01;C-side Inner ring;C-E5;76.8;-79;-831.6;4;2;2158;PM;C9;1;20;64249;9;1;1106;2;FTC-E5-2;FTC-B;70;4;14;-4.93;0.21;18;OC-202;38;1;0.73;C70/4.14/9 01 +138;PMC9/Ch04;C-side Inner ring;C-E5;103.2;-105;-825.8;4;2;2158;PM;C9;4;80;64249;9;1;1106;2;FTC-E5-4;FTC-B;72;2;15;-4.93;0.21;18;OC-202;8;2;0.82;C72/2.15/9 04 +139;PMC9/Ch02;C-side Inner ring;C-E5;103.2;-78.7;-828.7;4;2;2158;PM;C9;2;40;64249;9;1;1106;2;FTC-E5-3;FTC-B;71;4;13;-4.93;0.21;18;OC-202;15;3;0.96;C71/4.13/9 02 +140;PMC4/Ch02;C-side Inner ring;C-E4;76.8;-44.2;-833.6;3;8;2137;PM;C4;2;35;64244;4;1;1106;2;FTC-E4-1;FTC-B;65;5;14;-6.31;0.22;17;OC-202;23;1;0.84;C65/5.14/4 02 +141;PMC4/Ch04;C-side Inner ring;C-E4;103.1;-44.2;-830.8;3;8;2137;PM;C4;4;75;64244;4;1;1106;2;FTC-E4-4;FTC-B;68;3;15;-6.31;0.22;17;OC-202;34;2;1.03;C68/3.15/4 04 +142;PMC4/Ch01;C-side Inner ring;C-E4;76.9;-17.8;-834.6;3;8;2137;PM;C4;1;15;64244;4;1;1106;2;FTC-E4-2;FTC-B;66;3;13;-6.31;0.22;17;OC-202;24;0;0.7;C66/3.13/4 01 +143;PMC4/Ch03;C-side Inner ring;C-E4;103.2;-17.8;-831.8;3;8;2137;PM;C4;3;55;64244;4;1;1106;2;FTC-E4-3;FTC-B;67;1;13;-6.31;0.22;17;OC-202;4;0;0.84;C67/1.13/4 03 +144;PMC3/Ch07;C-side Outer ring;C-F3;163;18.7;-822.8;2;11;2149;PM;C3;7;134;64243;3;1;1106;2;FTC-F3-4;FTC-T;28;5;6;-4.26;0.21;7;OC-201;15;2;0.98;C28/5.6/3 07 +145;PMC3/Ch05;C-side Outer ring;C-F3;137;18.9;-827.6;2;11;2149;PM;C3;5;94;64243;3;1;1106;2;FTC-F3-1;FTC-T;25;3;6;-4.26;0.21;7;OC-201;59;2;0.8;C25/3.6/3 05 +146;PMC3/Ch08;C-side Outer ring;C-F3;163;45.2;-821.8;2;11;2149;PM;C3;8;154;64243;3;1;1106;2;FTC-F3-3;FTC-T;27;2;7;-4.26;0.21;7;OC-201;9;4;1.01;C27/2.7/3 08 +147;PMC3/Ch06;C-side Outer ring;C-F3;137;45.3;-826.6;2;11;2149;PM;C3;6;114;64243;3;1;1106;2;FTC-F3-2;FTC-T;26;6;5;-4.26;0.21;7;OC-201;19;2;0.94;C26/6.5/3 06 +148;PMC3/Ch11;C-side Outer ring;C-F2;163;78.6;-818.8;2;10;2134;PM;C3;11;214;64243;3;1;1106;2;FTC-F2-4;FTC-T;24;6;6;-4.56;0.21;6;OC-201;47;2;0.98;C24/6.6/3 11 +149;PMC3/Ch09;C-side Outer ring;C-F2;137;79.1;-823.6;2;10;2134;PM;C3;9;174;64243;3;1;1106;2;FTC-F2-1;FTC-T;21;3;7;-4.56;0.21;6;OC-201;35;3;0.98;C21/3.7/3 09 +150;PMC3/Ch12;C-side Outer ring;C-F2;163;104.9;-815.9;2;10;2134;PM;C3;12;234;64243;3;1;1106;2;FTC-F2-3;FTC-T;23;4;8;-4.56;0.21;6;OC-201;51;0;0.86;C23/4.8/3 12 +151;PMC3/Ch10;C-side Outer ring;C-F2;137;105.4;-820.6;2;10;2134;PM;C3;10;194;64243;3;1;1106;2;FTC-F2-2;FTC-T;22;2;8;-4.56;0.21;6;OC-201;45;2;0.76;C22/2.8/3 10 +152;PMC2/Ch10;C-side Outer ring;C-E1;103.4;138;-820.7;2;8;2125;PM;C2;10;193;64242;2;1;1106;3;FTC-E1-1;FTC-T;9;5;8;-5.03;0.22;3;OC-201;41;3;1.06;C9/5.8/2 10 +153;PMC2/Ch12;C-side Outer ring;C-E1;102.9;164;-815.9;2;8;2125;PM;C2;12;233;64242;2;1;1106;3;FTC-E1-4;FTC-T;12;1;9;-5.03;0.22;3;OC-201;5;2;0.98;C12/1.9/2 12 +154;PMC2/Ch09;C-side Outer ring;C-E1;77.1;138;-823.6;2;8;2125;PM;C2;9;173;64242;2;1;1106;3;FTC-E1-2;FTC-T;10;6;7;-5.03;0.22;3;OC-201;24;3;1.06;C10/6.7/2 09 +155;PMC2/Ch11;C-side Outer ring;C-E1;76.6;164;-818.7;2;8;2125;PM;C2;11;213;64242;2;1;1106;3;FTC-E1-3;FTC-T;11;2;10;-5.03;0.22;3;OC-201;57;3;1.53;C11/2.10/2 11 +156;PMC2/Ch06;C-side Outer ring;C-D1;43.3;139;-825.7;2;6;2145;PM;C2;6;113;64242;2;1;1106;1;FTC-D1-3;FTC-T;3;2;12;-6.33;0.22;1;OC-201;36;0;0.9;C3/2.12/2 06 +157;PMC2/Ch08;C-side Outer ring;C-D1;43.2;165;-820.8;2;6;2145;PM;C2;8;153;64242;2;1;1106;1;FTC-D1-2;FTC-T;2;3;10;-6.33;0.22;1;OC-201;61;0;0.78;C2/3.10/2 08 +158;PMC2/Ch05;C-side Outer ring;C-D1;16.9;139;-826.6;2;6;2145;PM;C2;5;93;64242;2;1;1106;1;FTC-D1-4;FTC-T;4;3;12;-6.33;0.22;1;OC-201;2;3;1.24;C4/3.12/2 05 +159;PMC2/Ch07;C-side Outer ring;C-D1;16.7;165;-821.7;2;6;2145;PM;C2;7;133;64242;2;1;1106;1;FTC-D1-1;FTC-T;1;2;11;-6.33;0.22;1;OC-201;50;1;1.33;C1/2.11/2 07 +160;PMC1/Ch07;C-side Outer ring;C-C1;-16.7;165;-821.7;2;4;2127;PM;C1;7;132;64241;1;1;1106;3;FTC-C1-4;FTC-T;52;1;2;-11.78;0.22;13;OC-201;8;1;0.88;C52/1.2/1 07 +161;PMC1/Ch05;C-side Outer ring;C-C1;-16.9;139;-826.6;2;4;2127;PM;C1;5;92;64241;1;1;1106;3;FTC-C1-1;FTC-T;49;5;2;-11.78;0.22;13;OC-201;28;2;1.13;C49/5.2/1 05 +162;PMC1/Ch08;C-side Outer ring;C-C1;-43.2;165;-820.8;2;4;2127;PM;C1;8;152;64241;1;1;1106;3;FTC-C1-3;FTC-T;51;2;2;-11.78;0.22;13;OC-201;12;1;0.9;C51/2.2/1 08 +163;PMC1/Ch06;C-side Outer ring;C-C1;-43.3;139;-825.7;2;4;2127;PM;C1;6;112;64241;1;1;1106;3;FTC-C1-2;FTC-T;50;4;5;-11.78;0.22;13;OC-201;16;2;1.03;C50/4.5/1 06 +164;PMC1/Ch11;C-side Outer ring;C-B1;-76.6;164;-818.7;2;2;2118;PM;C1;11;212;64241;1;1;1106;4;FTC-B1-1;FTC-T;37;3;5;-5.39;0.22;10;OC-201;39;3;1.24;C37/3.5/1 11 +165;PMC1/Ch09;C-side Outer ring;C-B1;-77.1;138;-823.6;2;2;2118;PM;C1;9;172;64241;1;1;1106;4;FTC-B1-2;FTC-T;38;5;4;-5.39;0.22;10;OC-201;33;0;0.86;C38/5.4/1 09 +166;PMC1/Ch12;C-side Outer ring;C-B1;-102.9;164;-815.9;2;2;2118;PM;C1;12;232;64241;1;1;1106;4;FTC-B1-4;FTC-T;40;6;3;-5.39;0.22;10;OC-201;62;3;1.27;C40/6.3/1 12 +167;PMC1/Ch10;C-side Outer ring;C-B1;-103.4;138;-820.7;2;2;2118;PM;C1;10;192;64241;1;1;1106;4;FTC-B1-3;FTC-T;39;1;3;-5.39;0.22;10;OC-201;52;0;0.78;C39/1.3/1 10 +168;PMC0/Ch10;C-side Outer ring;C-A2;-137;105.4;-820.6;2;0;2129;PM;C0;10;191;64240;0;1;1106;4;FTC-A2-1;FTC-T;29;5;5;-4.44;0.21;8;OC-201;17;3;1.03;C29/5.5/0 10 +169;PMC0/Ch12;C-side Outer ring;C-A2;-163;104.9;-815.9;2;0;2129;PM;C0;12;231;64240;0;1;1106;4;FTC-A2-4;FTC-T;32;1;7;-4.44;0.21;8;OC-201;20;4;1.16;C32/1.7/0 12 +170;PMC0/Ch09;C-side Outer ring;C-A2;-137;79.1;-823.6;2;0;2129;PM;C0;9;171;64240;0;1;1106;4;FTC-A2-2;FTC-T;30;4;6;-4.44;0.21;8;OC-201;48;3;0.88;C30/4.6/0 09 +171;PMC0/Ch11;C-side Outer ring;C-A2;-163;78.6;-818.8;2;0;2129;PM;C0;11;211;64240;0;1;1106;4;FTC-A2-3;FTC-T;31;2;6;-4.44;0.21;8;OC-201;34;3;0.9;C31/2.6/0 11 +172;PMC0/Ch06;C-side Outer ring;C-A3;-137;45.3;-826.6;2;1;2147;PM;C0;6;111;64240;0;1;1106;4;FTC-A3-1;FTC-T;33;2;5;-3.7;0.2;9;OC-201;10;3;1.27;C33/2.5/0 06 +173;PMC0/Ch08;C-side Outer ring;C-A3;-163;45.2;-821.8;2;1;2147;PM;C0;8;151;64240;0;1;1106;4;FTC-A3-4;FTC-T;36;6;4;-3.7;0.2;9;OC-201;53;2;1.18;C36/6.4/0 08 +174;PMC0/Ch05;C-side Outer ring;C-A3;-137;18.9;-827.6;2;1;2147;PM;C0;5;91;64240;0;1;1106;4;FTC-A3-2;FTC-T;34;1;8;-3.7;0.2;9;OC-201;25;0;0.8;C34/1.8/0 05 +175;PMC0/Ch07;C-side Outer ring;C-A3;-163;18.7;-822.8;2;1;2147;PM;C0;7;131;64240;0;1;1106;4;FTC-A3-3;FTC-T;35;1;5;-3.7;0.2;9;OC-201;1;0;0.86;C35/1.5/0 07 +176;PMC7/Ch07;C-side Outer ring;C-A4;-163;-18.7;-822.8;3;0;2151;PM;C7;7;138;64247;7;1;1106;4;FTC-A4-4;FTA-B;88;1;20;-7.29;0.21;22;OC-202;3;5;1.06;C88/1.20/7 07 +177;PMC7/Ch05;C-side Outer ring;C-A4;-137;-18.9;-827.6;3;0;2151;PM;C7;5;98;64247;7;1;1106;4;FTC-A4-1;FTA-B;85;3;19;-7.29;0.21;22;OC-202;6;8;1.6;C85/3.19/7 05 +178;PMC7/Ch08;C-side Outer ring;C-A4;-163;-45.2;-821.8;3;0;2151;PM;C7;8;158;64247;7;1;1106;4;FTC-A4-3;FTA-B;87;2;19;-7.29;0.21;22;OC-202;7;1;0.57;C87/2.19/7 08 +179;PMC7/Ch06;C-side Outer ring;C-A4;-137;-45.3;-826.6;3;0;2151;PM;C7;6;118;64247;7;1;1106;4;FTC-A4-2;FTA-B;86;5;19;-7.29;0.21;22;OC-202;1;2;0.7;C86/5.19/7 06 +180;PMC7/Ch11;C-side Outer ring;C-A5;-163;-78.6;-818.8;3;1;2141;PM;C7;11;218;64247;7;1;1106;4;FTC-A5-4;FTA-B;92;4;20;-6.46;0.22;23;OC-202;17;11;0.52;C92/4.20/7 11 +181;PMC7/Ch09;C-side Outer ring;C-A5;-137;-79.1;-823.6;3;1;2141;PM;C7;9;178;64247;7;1;1106;4;FTC-A5-1;FTA-B;89;2;20;-6.46;0.22;23;OC-202;55;6;3.34;C89/2.20/7 09 +182;PMC7/Ch12;C-side Outer ring;C-A5;-163;-104.9;-815.9;3;1;2141;PM;C7;12;238;64247;7;1;1106;4;FTC-A5-3;FTA-B;91;3;20;-6.46;0.22;23;OC-202;43;0;0.78;C91/3.20/7 12 +183;PMC7/Ch10;C-side Outer ring;C-A5;-137;-105.4;-820.6;3;1;2141;PM;C7;10;198;64247;7;1;1106;4;FTC-A5-2;FTA-B;90;2;18;-6.46;0.22;23;OC-202;42;7;3.19;C90/2.18/7 10 +184;PMC6/Ch10;C-side Outer ring;C-B6;-103.4;-138;-820.7;3;3;2144;PM;C6;10;197;64246;6;1;1106;1;FTC-B6-1;FTA-B;101;3;18;-5.95;0.22;26;OC-202;2;2;0.92;C101/3.18/6 10 +185;PMC6/Ch12;C-side Outer ring;C-B6;-102.9;-164;-815.9;3;3;2144;PM;C6;12;237;64246;6;1;1106;1;FTC-B6-4;FTA-B;104;6;17;-5.95;0.22;26;OC-202;33;3;0.9;C104/6.17/6 12 +186;PMC6/Ch09;C-side Outer ring;C-B6;-77.1;-138;-823.6;3;3;2144;PM;C6;9;177;64246;6;1;1106;1;FTC-B6-2;FTA-B;102;6;18;-5.95;0.22;26;OC-202;64;0;0.58;C102/6.18/6 09 +187;PMC6/Ch11;C-side Outer ring;C-B6;-76.6;-164;-818.7;3;3;2144;PM;C6;11;217;64246;6;1;1106;1;FTC-B6-3;FTA-B;103;5;18;-5.95;0.22;26;OC-202;40;5;1.16;C103/5.18/6 11 +188;PMC6/Ch06;C-side Outer ring;C-C6;-43.3;-139;-825.7;3;5;2162;PM;C6;6;117;64246;6;1;1106;3;FTC-C6-3;FTA-B;111;6;16;-4.75;0.21;28;OC-202;9;3;1.06;C111/6.16/6 06 +189;PMC6/Ch08;C-side Outer ring;C-C6;-43.2;-165;-820.8;3;5;2162;PM;C6;8;157;64246;6;1;1106;3;FTC-C6-2;FTA-B;110;4;17;-4.75;0.21;28;OC-202;61;0;0.55;C110/4.17/6 08 +190;PMC6/Ch05;C-side Outer ring;C-C6;-16.9;-139;-826.6;3;5;2162;PM;C6;5;97;64246;6;1;1106;3;FTC-C6-4;FTA-B;112;6;15;-4.75;0.21;28;OC-202;51;5;1.43;C112/6.15/6 05 +191;PMC6/Ch07;C-side Outer ring;C-C6;-16.7;-165;-821.7;3;5;2162;PM;C6;7;137;64246;6;1;1106;3;FTC-C6-1;FTA-B;109;2;16;-4.75;0.21;28;OC-202;45;2;0.77;C109/2.16/6 07 +192;PMC5/Ch07;C-side Outer ring;C-D6;16.7;-165;-821.7;3;7;2167;PM;C5;7;136;64245;5;1;1106;1;FTC-D6-4;FTA-B;64;4;15;-6.64;0.22;16;OC-202;46;1;0.64;C64/4.15/5 07 +193;PMC5/Ch05;C-side Outer ring;C-D6;16.9;-139;-826.6;3;7;2167;PM;C5;5;96;64245;5;1;1106;1;FTC-D6-1;FTA-B;61;6;14;-6.64;0.22;16;OC-202;50;0;0.62;C61/6.14/5 05 +194;PMC5/Ch08;C-side Outer ring;C-D6;43.2;-165;-820.8;3;7;2167;PM;C5;8;156;64245;5;1;1106;1;FTC-D6-3;FTA-B;63;5;13;-6.64;0.22;16;OC-202;53;10;2.48;C63/5.13/5 08 +195;PMC5/Ch06;C-side Outer ring;C-D6;43.3;-139;-825.7;3;7;2167;PM;C5;6;116;64245;5;1;1106;1;FTC-D6-2;FTA-B;62;2;14;-6.64;0.22;16;OC-202;13;2;0.75;C62/2.14/5 06 +196;PMC5/Ch11;C-side Outer ring;C-E6;76.6;-164;-818.7;3;9;2165;PM;C5;11;216;64245;5;1;1106;2;FTC-E6-1;FTA-B;73;1;15;-4.67;0.21;19;OC-202;31;10;2.85;C73/1.15/5 11 +197;PMC5/Ch09;C-side Outer ring;C-E6;77.1;-138;-823.6;3;9;2165;PM;C5;9;176;64245;5;1;1106;2;FTC-E6-2;FTA-B;74;1;14;-4.67;0.21;19;OC-202;28;0;0.7;C74/1.14/5 09 +198;PMC5/Ch12;C-side Outer ring;C-E6;102.9;-164;-815.9;3;9;2165;PM;C5;12;236;64245;5;1;1106;2;FTC-E6-4;FTA-B;76;5;12;-4.67;0.21;19;OC-202;27;2;0.86;C76/5.12/5 12 +199;PMC5/Ch10;C-side Outer ring;C-E6;103.4;-138;-820.7;3;9;2165;PM;C5;10;196;64245;5;1;1106;2;FTC-E6-3;FTA-B;75;2;13;-4.67;0.21;19;OC-202;37;0;0.54;C75/2.13/5 10 +200;PMC4/Ch10;C-side Outer ring;C-F5;137;-105.4;-820.6;3;11;2142;PM;C4;10;195;64244;4;1;1106;2;FTC-F5-1;FTA-B;81;5;11;-6.43;0.22;21;OC-202;16;4;1.13;C81/5.11/4 10 +201;PMC4/Ch12;C-side Outer ring;C-F5;163;-104.9;-815.9;3;11;2142;PM;C4;12;235;64244;4;1;1106;2;FTC-F5-4;FTA-B;84;4;12;-6.43;0.22;21;OC-202;54;4;1.03;C84/4.12/4 12 +202;PMC4/Ch09;C-side Outer ring;C-F5;137;-79.1;-823.6;3;11;2142;PM;C4;9;175;64244;4;1;1106;2;FTC-F5-2;FTA-B;82;6;10;-6.43;0.22;21;OC-202;62;0;0.77;C82/6.10/4 09 +203;PMC4/Ch11;C-side Outer ring;C-F5;163;-78.6;-818.8;3;11;2142;PM;C4;11;215;64244;4;1;1106;2;FTC-F5-3;FTA-B;83;3;11;-6.43;0.22;21;OC-202;18;0;0.61;C83/3.11/4 11 +204;PMC4/Ch06;C-side Outer ring;C-F4;137;-45.3;-826.6;3;10;2152;PM;C4;6;115;64244;4;1;1106;2;FTC-F4-1;FTA-B;77;6;11;-3.44;0.2;20;OC-202;10;5;1.39;C77/6.11/4 06 +205;PMC4/Ch08;C-side Outer ring;C-F4;163;-45.2;-821.8;3;10;2152;PM;C4;8;155;64244;4;1;1106;2;FTC-F4-4;FTA-B;80;6;12;-3.44;0.2;20;OC-202;21;5;0.99;C80/6.12/4 08 +206;PMC4/Ch05;C-side Outer ring;C-F4;137;-18.9;-827.6;3;10;2152;PM;C4;5;95;64244;4;1;1106;2;FTC-F4-2;FTA-B;78;5;10;-3.44;0.2;20;OC-202;39;0;0.7;C78/5.10/4 05 +207;PMC4/Ch07;C-side Outer ring;C-F4;163;-18.7;-822.8;3;10;2152;PM;C4;7;135;64244;4;1;1106;2;FTC-F4-3;FTA-B;79;4;10;-3.44;0.2;20;OC-202;41;0;0.64;C79/4.10/4 07 diff --git a/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json new file mode 100644 index 0000000000..3f1992e50f --- /dev/null +++ b/Modules/FIT/FT0/etc/ft0-aging-laser-postproc.json @@ -0,0 +1,256 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" + } + }, + "postprocessing": { + "AgingLaserPostProc": { + "active": "true", + "className": "o2::quality_control_modules::ft0::AgingLaserPostProcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "extendedTaskParameters": { + "default": { + "default": { + "agingLaserTaskPath": "FT0/MO/AgingLaser", + "agingLaserPostProcPath": "FT0/MO/AgingLaserPostProc", + "fracWindowLow": "0.30", + "fracWindowHigh": "0.30", + "ignoreDetectorChannels": "", + "useDeadChannelMap": "true", + "ignoreRefChannels": "209", + "reset": "false" + } + } + }, + "initTrigger": [ "userorcontrol" ], + "updateTrigger": [ "newobject:qcdb:FT0/MO/AgingLaser/AmpPerChannel" ], + "stopTrigger": [ "userorcontrol" ] + }, + "AgingLaserTrending": { + "active": "true", + "className": "o2::quality_control::postprocessing::SliceTrendingTask", + "moduleName": "QualityControl", + "detectorName": "FT0", + "resumeTrend": "true", + "producePlotsOnUpdate": "true", + "colorPalette": "1", + "dataSources": [ + { + "type": "repository", + "path": "FT0/MO/AgingLaserPostProc", + "names": [ "AmpPerChannelNormWeightedMeanA" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ "0","1","2","3","4","5","6","7","8","9", + "10","11","12","13","14","15","16","17","18","19", + "20","21","22","23","24","25","26","27","28","29", + "30","31","32","33","34","35","36","37","38","39", + "40","41","42","43","44","45","46","47","48","49", + "50","51","52","53","54","55","56","57","58","59", + "60","61","62","63","64","65","66","67","68","69", + "70","71","72","73","74","75","76","77","78","79", + "80","81","82","83","84","85","86","87","88","89", + "90","91","92","93","94","95", "96" + ] + ], + "sliceLabels": [ + [ "1","2","3","4","5","6","7","8","9","10", + "11","12","13","14","15","16","17","18","19","20", + "21","22","23","24","25","26","27","28","29","30", + "31","32","33","34","35","36","37","38","39","40", + "41","42","43","44","45","46","47","48","49","50", + "51","52","53","54","55","56","57","58","59","60", + "61","62","63","64","65","66","67","68","69","70", + "71","72","73","74","75","76","77","78","79","80", + "81","82","83","84","85","86","87","88","89","90", + "91","92","93","94","95", "96" + ] + ], + "moduleName": "QcCommon" + }, + { + "type": "repository", + "path": "FT0/MO/AgingLaserPostProc", + "names": [ "AmpPerChannelNormWeightedMeanC" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ "96","97","98","99","100","101","102","103","104","105", + "106","107","108","109","110","111","112","113","114","115", + "116","117","118","119","120","121","122","123","124","125", + "126","127","128","129","130","131","132","133","134","135", + "136","137","138","139","140","141","142","143","144","145", + "146","147","148","149","150","151","152","153","154","155", + "156","157","158","159","160","161","162","163","164","165", + "166","167","168","169","170","171","172","173","174","175", + "176","177","178","179","180","181","182","183","184","185", + "186","187","188","189","190","191","192","193","194","195", + "196","197","198","199","200","201","202","203","204","205", + "206","207", "208" + ] + ], + "sliceLabels": [ + [ "97","98","99","100","101","102","103","104","105","106", + "107","108","109","110","111","112","113","114","115","116", + "117","118","119","120","121","122","123","124","125","126", + "127","128","129","130","131","132","133","134","135","136", + "137","138","139","140","141","142","143","144","145","146", + "147","148","149","150","151","152","153","154","155","156", + "157","158","159","160","161","162","163","164","165","166", + "167","168","169","170","171","172","173","174","175","176", + "177","178","179","180","181","182","183","184","185","186", + "187","188","189","190","191","192","193","194","195","196", + "197","198","199","200","201","202","203","204","205","206", + "207","208" + ] + ], + "moduleName": "QcCommon" + }, + { + "type": "repository", + "path": "FT0/MO/AgingLaserPostProc", + "names": [ "AmpPerChannelNormWeightedMeanCorrectedA" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ "0","1","2","3","4","5","6","7","8","9", + "10","11","12","13","14","15","16","17","18","19", + "20","21","22","23","24","25","26","27","28","29", + "30","31","32","33","34","35","36","37","38","39", + "40","41","42","43","44","45","46","47","48","49", + "50","51","52","53","54","55","56","57","58","59", + "60","61","62","63","64","65","66","67","68","69", + "70","71","72","73","74","75","76","77","78","79", + "80","81","82","83","84","85","86","87","88","89", + "90","91","92","93","94","95", "96" + ] + ], + "sliceLabels": [ + [ "1","2","3","4","5","6","7","8","9","10", + "11","12","13","14","15","16","17","18","19","20", + "21","22","23","24","25","26","27","28","29","30", + "31","32","33","34","35","36","37","38","39","40", + "41","42","43","44","45","46","47","48","49","50", + "51","52","53","54","55","56","57","58","59","60", + "61","62","63","64","65","66","67","68","69","70", + "71","72","73","74","75","76","77","78","79","80", + "81","82","83","84","85","86","87","88","89","90", + "91","92","93","94","95", "96" + ] + ], + "moduleName": "QcCommon" + }, + { + "type": "repository", + "path": "FT0/MO/AgingLaserPostProc", + "names": [ "AmpPerChannelNormWeightedMeanCorrectedC" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ "96","97","98","99","100","101","102","103","104","105", + "106","107","108","109","110","111","112","113","114","115", + "116","117","118","119","120","121","122","123","124","125", + "126","127","128","129","130","131","132","133","134","135", + "136","137","138","139","140","141","142","143","144","145", + "146","147","148","149","150","151","152","153","154","155", + "156","157","158","159","160","161","162","163","164","165", + "166","167","168","169","170","171","172","173","174","175", + "176","177","178","179","180","181","182","183","184","185", + "186","187","188","189","190","191","192","193","194","195", + "196","197","198","199","200","201","202","203","204","205", + "206","207", "208" + ] + ], + "sliceLabels": [ + [ "97","98","99","100","101","102","103","104","105","106", + "107","108","109","110","111","112","113","114","115","116", + "117","118","119","120","121","122","123","124","125","126", + "127","128","129","130","131","132","133","134","135","136", + "137","138","139","140","141","142","143","144","145","146", + "147","148","149","150","151","152","153","154","155","156", + "157","158","159","160","161","162","163","164","165","166", + "167","168","169","170","171","172","173","174","175","176", + "177","178","179","180","181","182","183","184","185","186", + "187","188","189","190","191","192","193","194","195","196", + "197","198","199","200","201","202","203","204","205","206", + "207","208" + ] + ], + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "ft0_aging_laser_A", + "title": "FT0 Aging Monitoring: A-side (ch 1-96)", + "varexp": "AmpPerChannelNormWeightedMeanA.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphAxisLabel": ":", + "graphYRange": "0:1.5", + "legendNColums": "12", + "legendTextSize": "0.018", + "colorPalette": "1" + }, + { + "name": "ft0_aging_laser_C", + "title": "FT0 Aging Monitoring: C-side (ch 97-208)", + "varexp": "AmpPerChannelNormWeightedMeanC.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphAxisLabel": ":", + "graphYRange": "0:1.5", + "legendNColums": "14", + "legendTextSize": "0.018", + "colorPalette": "1" + }, + { + "name": "ft0_aging_laser_A_corrected", + "title": "FT0 Aging Monitoring: A-side (ch 1-96) - corrected", + "varexp": "AmpPerChannelNormWeightedMeanCorrectedA.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphAxisLabel": ":", + "graphYRange": "0:1.5", + "legendNColums": "12", + "legendTextSize": "0.018", + "colorPalette": "1" + }, + { + "name": "ft0_aging_laser_C_corrected", + "title": "FT0 Aging Monitoring: C-side (ch 97-208) - corrected", + "varexp": "AmpPerChannelNormWeightedMeanCorrectedC.meanY:multigraphtime", + "selection": "", + "option": "A*L PMC PLC", + "graphAxisLabel": ":", + "graphYRange": "0:1.5", + "legendNColums": "14", + "legendTextSize": "0.018", + "colorPalette": "1" + } + ], + + "initTrigger": [ "userorcontrol" ], + "updateTrigger": [ "newobject:qcdb:FT0/MO/AgingLaserPostProc/AmpPerChannelNormWeightedMeanA" ], + "stopTrigger": [ "userorcontrol" ] + } + } +} +} \ No newline at end of file diff --git a/Modules/FIT/FT0/etc/ft0-aging-laser.json b/Modules/FIT/FT0/etc/ft0-aging-laser.json new file mode 100644 index 0000000000..25b5652c65 --- /dev/null +++ b/Modules/FIT/FT0/etc/ft0-aging-laser.json @@ -0,0 +1,51 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" + } + }, + "tasks": { + "AgingLaser": { + "active": "true", + "className": "o2::quality_control_modules::ft0::AgingLaserTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "cycleDurationSeconds": "60", + "maxNumberCycles": "-1", + "dataSource": { + "type": "direct", + "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" + }, + "taskParameters": { + "#detectorChannelIDs": "omit to use all", + "referenceChannelIDs": "208, 209, 210, 211", + "detectorAmpCut": "0", + "referenceAmpCut": "100", + "laserTriggerBCs": "0, 1783", + "detectorBCdelay": "131", + "referencePeak1BCdelays": "115, 115, 115, 115", + "referencePeak2BCdelays": "136, 142, 135, 141", + "debug": "false" + } + } + } + } +} \ No newline at end of file diff --git a/Modules/FIT/FT0/etc/ft0-digits.json b/Modules/FIT/FT0/etc/ft0-digits.json new file mode 100644 index 0000000000..dc121a6ab3 --- /dev/null +++ b/Modules/FIT/FT0/etc/ft0-digits.json @@ -0,0 +1,53 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" + } + }, + "tasks": { + "Digits": { + "active": "true", + "className": "o2::quality_control_modules::ft0::DigitQcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "cycleDurationSeconds": "60", + "resetAfterCycles": "1", + "dataSource": { + "type": "direct", + "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" + }, + "taskParameters": { + "#ChannelIDs": "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215", + "ChannelIDsAmpVsTime": "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215", + "trgThresholdTimeLow": "-100", + "trgThresholdTimeHigh": "100", + "trgModeSide": "A+C", + "trgModeThresholdVar": "Ampl", + "trgThresholdSCenA": "20", + "trgThresholdCenA": "40", + "trgOrGate": "153", + "trgChargeLevelLow": "0", + "trgChargeLevelHigh": "4095" + } + } + } + } +} \ No newline at end of file diff --git a/Modules/FIT/FT0/etc/ft0-post-processing.json b/Modules/FIT/FT0/etc/ft0-post-processing.json new file mode 100644 index 0000000000..57e7a75498 --- /dev/null +++ b/Modules/FIT/FT0/etc/ft0-post-processing.json @@ -0,0 +1,538 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" + } + }, + "tasks": { + "Digits": { + "active": "true", + "className": "o2::quality_control_modules::ft0::DigitQcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "cycleDurationSeconds": "60", + "resetAfterCycles": "1", + "dataSource": { + "type": "direct", + "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" + }, + "taskParameters": { + "#ChannelIDs": "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215", + "ChannelIDsAmpVsTime": "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215", + "trgThresholdTimeLow": "-100", + "trgThresholdTimeHigh": "100", + "trgModeSide": "A+C", + "trgModeThresholdVar": "Ampl", + "trgThresholdSCenA": "20", + "trgThresholdCenA": "40", + "trgOrGate": "153", + "trgChargeLevelLow": "0", + "trgChargeLevelHigh": "4095" + } + } + }, + "checks": { + "ASideInnerMIPCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::MIPCheck", + "moduleName": "QcFT0", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "AmpAInner" + ] + } + ], + "checkParameters": { + "nameObjectToCheck": "AmpAInner", + "nPeaksToFit": "2", + "gausParamsMean": "14, 28", + "gausParamsSigma": "3, 7", + "fitRangeLow": "11", + "fitRangeHigh": "35", + "meanWarningsLow": "12, 26", + "meanWarningsHigh": "16, 30", + "meanErrorsLow": "10, 24", + "meanErrorsHigh": "18, 32", + "sigmaWarnings": "4, -1", + "sigmaErrors": "5, -1", + "drawMeanWarningsLow": "false, false", + "drawMeanWarningsHigh": "false, false", + "drawMeanErrorsLow": "true, true", + "drawMeanErrorsHigh": "true, true", + "drawSigmaWarnings": "false, false", + "drawSigmaErrors": "fasle, false", + "labelPos": "0.15, 0.2, 0.85, 0.45" + } + }, + "ASideOuterMIPCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::MIPCheck", + "moduleName": "QcFT0", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "AmpAOuter" + ] + } + ], + "checkParameters": { + "nameObjectToCheck": "AmpAOuter", + "nPeaksToFit": "2", + "gausParamsMean": "14, 28", + "gausParamsSigma": "3, 7", + "fitRangeLow": "11", + "fitRangeHigh": "35", + "meanWarningsLow": "12, 26", + "meanWarningsHigh": "16, 30", + "meanErrorsLow": "10, 24", + "meanErrorsHigh": "18, 32", + "sigmaWarnings": "4, -1", + "sigmaErrors": "5, -1", + "drawMeanWarningsLow": "false, false", + "drawMeanWarningsHigh": "false, false", + "drawMeanErrorsLow": "true, true", + "drawMeanErrorsHigh": "true, true", + "drawSigmaWarnings": "false, false", + "drawSigmaErrors": "fasle, false", + "labelPos": "0.15, 0.2, 0.85, 0.45" + } + }, + "CSideMIPCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::MIPCheck", + "moduleName": "QcFT0", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "AmpC" + ] + } + ], + "checkParameters": { + "nameObjectToCheck": "AmpC", + "nPeaksToFit": "2", + "gausParamsMean": "14, 28", + "gausParamsSigma": "3, 7", + "fitRangeLow": "11", + "fitRangeHigh": "35", + "meanWarningsLow": "12, 26", + "meanWarningsHigh": "16, 30", + "meanErrorsLow": "10, 24", + "meanErrorsHigh": "18, 32", + "sigmaWarnings": "4, -1", + "sigmaErrors": "5, -1", + "drawMeanWarningsLow": "false, false", + "drawMeanWarningsHigh": "false, false", + "drawMeanErrorsLow": "true, true", + "drawMeanErrorsHigh": "true, true", + "drawSigmaWarnings": "false, false", + "drawSigmaErrors": "fasle, false", + "labelPos": "0.15, 0.2, 0.85, 0.45" + } + }, + "AllChannelsMIPCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::MIPCheck", + "moduleName": "QcFT0", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "AmpNormPerChannel" + ] + } + ], + "checkParameters": { + "nameObjectToCheck": "AmpNormPerChannel", + "nPeaksToFit": "2", + "gausParamsMean": "14, 28", + "gausParamsSigma": "3, 7", + "fitRangeLow": "11", + "fitRangeHigh": "35", + "meanWarningsLow": "12, 26", + "meanWarningsHigh": "16, 30", + "meanErrorsLow": "10, 24", + "meanErrorsHigh": "18, 32", + "sigmaWarnings": "4, -1", + "sigmaErrors": "5, -1", + "drawMeanWarningsLow": "false, false", + "drawMeanWarningsHigh": "false, false", + "drawMeanErrorsLow": "true, true", + "drawMeanErrorsHigh": "true, true", + "drawSigmaWarnings": "false, false", + "drawSigmaErrors": "fasle, false", + "labelPos": "0.15, 0.2, 0.85, 0.45" + } + }, + "OutOfBunchCollCheck": { + "active": "true", + "className": "o2::quality_control_modules::ft0::OutOfBunchCollCheck", + "moduleName": "QcFT0", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "OutOfBunchColl_BCvsTrg" + ] + } + ], + "checkParameters": { + "thresholdWarning": "5.0e-3", + "thresholdError": "1.0e-2" + } + }, + "CFDinTimeGateCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "TimeInWindowFraction" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.75", + "thresholdError": "0.5", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "TimeInWindowFraction", + "isInversedThresholds": "false", + "pathDeadChannelMap": "FT0/Calib/DeadChannelMap", + "binsToIgnore": "208,209,210,211,212,213,214,215" + } + }, + "CFDinADCgateCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "CFD_efficiency" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.75", + "thresholdError": "0.5", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "CFD_efficiency", + "isInversedThresholds": "false", + "pathDeadChannelMap": "FT0/Calib/DeadChannelMap", + "binsToIgnore": "208,209,210,211,212,213,214,215" + } + }, + "ChannelOutOfBunchCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "ChannelID_outOfBC" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.4", + "thresholdError": "1.0", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "ChannelID_outOfBC", + "isInversedThresholds": "true", + "pathDeadChannelMap": "FT0/Calib/DeadChannelMap", + "binsToIgnore": "208,209,210,211,212,213,214,215" + } + }, + "TrgValidationCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "TrgValidation" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.0005", + "thresholdError": "0.0015", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "TrgValidation", + "isInversedThresholds": "true", + "pathDeadChannelMap": "", + "binsToIgnore": "" + } + } + }, + "aggregators": { + "GlobalQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "FT0", + "dataSource": [ + { + "type": "Check", + "name": "OutOfBunchCollCheck" + }, + { + "type": "Check", + "name": "CFDinTimeGateCheck" + }, + { + "type": "Check", + "name": "CFDinADCgateCheck" + }, + { + "type": "Check", + "name": "ChannelOutOfBunchCheck" + }, + { + "type": "Check", + "name": "TrgValidationCheck" + } + ] + } + }, + "postprocessing": { + "Quality": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QualityControl", + "detectorName": "FT0", + "qualityGroups": [ + { + "name": "Global", + "title": "GLOBAL FT0 QUALITY", + "path": "FT0/QO/GlobalQuality", + "ignoreQualitiesDetails": [ + "Null", + "Good", + "Medium", + "Bad" + ], + "inputObjects": [ + { + "name": "GlobalQuality", + "title": "Global FT0 Quality", + "messageBad": "Inform the FIT on-call immediately", + "messageMedium": "Follow individual check instructions", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty, inform the FIT on-call immediately" + } + ] + }, + { + "name": "Details", + "title": "FT0 DETAILS", + "path": "FT0/QO", + "ignoreQualitiesDetails": [], + "inputObjects": [ + { + "name": "OutOfBunchCollCheck", + "title": "Out of bunch collisions" + }, + { + "name": "CFDinTimeGateCheck", + "title": "CFD in time gate" + }, + { + "name": "CFDinADCgateCheck", + "title": "CDF in ADC gate" + }, + { + "name": "ChannelOutOfBunchCheck", + "title": "Channels out of bunch collisions" + }, + { + "name": "TrgValidationCheck", + "title": "Trigger validation" + } + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:FT0/QO/GlobalQuality/GlobalQuality" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "PostProc": { + "active": "true", + "className": "o2::quality_control_modules::ft0::PostProcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "custom": { + "numOrbitsInTF": "32", + "cycleDurationMoName": "CycleDurationNTF", + "timestampSourceLhcIf": "metadata", + "pathDigitQcTask": "FT0/MO/Digits" + }, + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:FT0/MO/Digits/TriggersCorrelation" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "Trending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "resumeTrend": "true", + "dataSources": [ + { + "type": "repository", + "path": "FT0/MO/Digits", + "names": [ + "CycleDuration", + "CycleDurationNTF", + "AverageTimeA", + "AverageTimeC" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "trend_cycle_duration_ntf_corr", + "title": "cycle duration: ns/TF;time;cycle duration [ns/TimeFrames]", + "legend": { "enabled": true, "x1": 0.70, "y1": 0.70, "x2": 0.93, "y2": 0.90, "nColumns": 1 }, + "graphs": [ + { + "title": "cycle duration [ns]", + "varexp": "CycleDuration.entries:time", + "selection": "", + "option": "*LP", + "style": { "lineColor": 38, "markerColor": 4, "markerStyle": 20, "lineWidth": 2 } + }, + { + "title": "cycle duration [TimeFrames]", + "varexp": "CycleDurationNTF.entries:time", + "selection": "", + "option": "*LP", + "style": { "lineColor": 8, "markerColor": 3, "markerStyle": 30, "lineWidth": 2 } + }, + { + "title": "cycle duration: ns/TF;time;cycle duration [ns/TimeFrames]", + "varexp": "CycleDuration.entries/CycleDurationNTF.entries:time", + "selection": "", + "option": "*LP", + "style": { "lineColor": 31, "markerColor": 30, "markerStyle": 23, "lineWidth": 2 } + } + ] + }, + { + "name": "trend_cycle_duration", + "title": "cycle duration [ns]", + "varexp": "CycleDuration.entries:time", + "selection": "", + "option": "*L" + }, + { + "name": "trend_cycle_duration_ntf", + "title": "cycle duration [TimeFrames]", + "varexp": "CycleDurationNTF.entries:time", + "selection": "", + "option": "*L" + }, + { + "name": "cycle_duration_corr", + "title": "cycle duration: ns/TF;time;cycle duration [ns/TimeFrames]", + "varexp": "CycleDuration.entries/CycleDurationNTF.entries:time", + "selection": "", + "option": "*L" + }, + { + "name": "cycle_duration_ntf_corr", + "title": "TF duration [ns];#TF;TF duration [ns]", + "varexp": "CycleDuration.entries/CycleDurationNTF.entries:CycleDurationNTF.entries", + "selection": "", + "option": "colz" + }, + { + "name": "trend_time", + "title": "(TOA+TOC)/2 from TCM [ps]", + "varexp": "(AverageTimeA.mean+AverageTimeC.mean)/2 * 13.02:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:FT0/MO/Digits/TriggersCorrelation" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} \ No newline at end of file diff --git a/Modules/FIT/FT0/etc/ft0-recpoints.json b/Modules/FIT/FT0/etc/ft0-recpoints.json new file mode 100644 index 0000000000..68d8077702 --- /dev/null +++ b/Modules/FIT/FT0/etc/ft0-recpoints.json @@ -0,0 +1,40 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" + } + }, + "tasks": { + "RecPoints": { + "active": "true", + "className": "o2::quality_control_modules::ft0::RecPointsQcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "cycleDurationSeconds": "60", + "resetAfterCycles": "1", + "dataSource": { + "type": "direct", + "query": "recpoints:FT0/RECPOINTS/0;channels:FT0/RECCHDATA/0" + } + } + } + } +} diff --git a/Modules/FIT/FT0/etc/ft0_prod_flp.json b/Modules/FIT/FT0/etc/ft0_prod_flp.json new file mode 100644 index 0000000000..d3ba0b11b4 --- /dev/null +++ b/Modules/FIT/FT0/etc/ft0_prod_flp.json @@ -0,0 +1,76 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch", + "host": "alice-ccdb.cern.ch" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", "": "Set to 1 to discard debug and trace messages (default: false)", + "filterDiscardLevel": "20", "": "Message at this level or above are discarded (default: 21 - Trace)" + } + }, + "tasks": { + "DigitQcTask": { + "active": "true", + "className": "o2::quality_control_modules::ft0::DigitQcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "cycleDurationSeconds": "60", + "resetAfterCycles": "1", + "dataSource": { + "type": "direct", + "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" + }, + "taskParameters": { + "trgThresholdTimeLow": "-50", + "trgThresholdTimeHigh": "50", + "trgModeSide": "A+C", + "trgModeThresholdVar": "Ampl", + "trgThresholdSCenA": "20", + "trgThresholdCenA": "40", + "trgOrGate": "153", + "trgChargeLevelLow": "0", + "trgChargeLevelHigh": "4095", + "goodPMbits_ChID":"72" + } + } + }, + "checks": { + "CFDEffCheck": { + "active": "true", + "className": "o2::quality_control_modules::ft0::CFDEffCheck", + "moduleName": "QcFT0", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [ + { + "type": "Task", + "name": "DigitQcTask", + "MOs": [ + "CFD_efficiency" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.9", + "thresholdError": "0.8", + "deadChannelMap": "139" + } + } + } + } +} diff --git a/Modules/FIT/FT0/etc/recpoints_monitoring.json b/Modules/FIT/FT0/etc/recpoints_monitoring.json new file mode 100644 index 0000000000..124a98eedd --- /dev/null +++ b/Modules/FIT/FT0/etc/recpoints_monitoring.json @@ -0,0 +1,63 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "http://j183.localdomain:8083", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?METRIC" + }, + "consul": { + "url": "http://j183.localdomain:8500" + }, + "conditionDB": { + "url": "http://j183.localdomain:8083" + } + }, + "tasks": { + "RecPointsQcTask": { + "active": "true", + "className": "o2::quality_control_modules::ft0::RecpointsQcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query": "recpoints:FT0/RECPOINTS/0;channels:FT0/RECCHDATA/0" + }, + "taskParameters": { + "ChannelIDs": "73,76,79,83,122,125,129,132" + }, + "location": "local", + "localMachines": [ + "j183.localdomain" + ], + "remoteMachine": "j183.localdomain", + "remotePort": "30132", + "mergingMode": "entire" + } + }, + "checks": { + "DummyAnalysisCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [{ + "type": "Task", + "name": "RecPointsQcTask", + "MOs": ["example"] + }] + } + } + } +} diff --git a/Modules/FIT/FT0/ft0-digits-qc.json b/Modules/FIT/FT0/ft0-digits-qc.json new file mode 100644 index 0000000000..ea7d9aaf36 --- /dev/null +++ b/Modules/FIT/FT0/ft0-digits-qc.json @@ -0,0 +1,63 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "http://localhost:8083", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?METRIC" + }, + "consul": { + "url": "http://localhost:8500" + }, + "conditionDB": { + "url": "http://localhost:8083" + } + }, + "tasks": { + "DigitQcTask": { + "active": "true", + "className": "o2::quality_control_modules::ft0::DigitQcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query": "digits:FT0/DIGITSBC/0;channels:FT0/DIGITSCH/0" + }, + "taskParameters": { + "ChannelIDs": "204,205" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "localhost", + "remotePort": "30132", + "mergingMode": "entire" + } + }, + "checks": { + "DummyAnalysisCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "FT0", + "dataSource": [{ + "type": "Task", + "name": "DigitQcTask", + "MOs": ["example"] + }] + } + } + } +} diff --git a/Modules/FIT/FT0/ft0-reconstruction-config.json b/Modules/FIT/FT0/ft0-reconstruction-config.json new file mode 100644 index 0000000000..ce2f2bb69e --- /dev/null +++ b/Modules/FIT/FT0/ft0-reconstruction-config.json @@ -0,0 +1,51 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE", + "provenance": "qc_mc", + "passName": "passMC", + "periodName": "SimChallenge" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "ccdb-test.cern.ch:8080" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "RecPointsQcTask": { + "active": "true", + "className": "o2::quality_control_modules::ft0::RecPointsQcTask", + "moduleName": "QcFT0", + "detectorName": "FT0", + "cycleDurationSeconds": "600", + "dataSource": { + "type": "direct", + "query": "recpoints:FT0/RECPOINTS/0;channels:FT0/RECCHDATA/0" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "localhost", + "remotePort": "30132", + "mergingMode": "entire", + "saveObjectsToFile":"qcrootobjects.root" + } + + } + } +} diff --git a/Modules/FIT/FT0/images/lcs.png b/Modules/FIT/FT0/images/lcs.png new file mode 100644 index 0000000000..90b5432a3c Binary files /dev/null and b/Modules/FIT/FT0/images/lcs.png differ diff --git a/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h new file mode 100644 index 0000000000..c2a04c9b6e --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/AgingLaserPostProcTask.h @@ -0,0 +1,80 @@ +// Copyright 2019-2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General +// Public License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file AgingLaserPostProcTask.h +/// \author Andreas Molander , Jakub Muszyński +/// \brief Post-processing task that derives a per-channel +/// weighted-mean amplitude, normalised to reference channels. + +#ifndef QC_MODULE_FT0_AGINGLASERPOSTPROC_H +#define QC_MODULE_FT0_AGINGLASERPOSTPROC_H + +#include "QualityControl/PostProcessingInterface.h" +#include "FT0Base/Constants.h" +#include "FITCommon/PostProcHelper.h" + +#include +#include +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::ft0 +{ + +/// \brief Post-processing task that derives a per-channel +/// weighted-mean amplitude, normalised to reference channels. +class AgingLaserPostProcTask final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + AgingLaserPostProcTask() = default; + ~AgingLaserPostProcTask() override; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + static constexpr std::size_t sNCHANNELS_PM = o2::ft0::Constants::sNCHANNELS_PM; + + /// Flag to indicate whether the aging correction has recently been performed. + /// If true, the trends should be reset. + /// If true, mAmpVsChNormWeightedMean[A/C]AfterLastCorr will be published to the database, and this will be used + /// as a normalization factor for the datapoints in the new trends. + bool mReset = false; + + std::string mAgingLaserPath = "FT0/MO/AgingLaser"; //< Path to the AgingLaser task output in QCDB + std::string mAgingLaserPostProcPath = "FT0/MO/AgingLaserPostProc"; //< Path to the AgingLaserPostProc task output in QCDB + + std::vector mDetectorChIDs; ///< Detector (target) channels + std::vector mReferenceChIDs; ///< Reference channels + + /// Flag to indicate whether to skip processing of channels marked dead in the dead channel map + bool mUseDeadChannelMap = true; + + double mFracWindowA = 0.25; ///< low fractional window parameter a + double mFracWindowB = 0.25; ///< high fractional window parameter b + + std::unique_ptr mAmpVsChNormWeightedMeanA; + std::unique_ptr mAmpVsChNormWeightedMeanC; + std::unique_ptr mAmpVsChNormWeightedMeanAfterLastCorrA; + std::unique_ptr mAmpVsChNormWeightedMeanAfterLastCorrC; + std::unique_ptr mAmpVsChNormWeightedMeanCorrectedA; + std::unique_ptr mAmpVsChNormWeightedMeanCorrectedC; + + o2::quality_control_modules::fit::PostProcHelper mPostProcHelper; +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_AGINGLASERPOSTPROC_H \ No newline at end of file diff --git a/Modules/FIT/FT0/include/FT0/AgingLaserTask.h b/Modules/FIT/FT0/include/FT0/AgingLaserTask.h new file mode 100644 index 0000000000..0bc2417284 --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/AgingLaserTask.h @@ -0,0 +1,171 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AgingLaserTask.h +/// \author Andreas Molander , Sandor Lokos , Edmundo Garcia-Solis +/// + +#ifndef QC_MODULE_FT0_AGINGLASERTASK_H +#define QC_MODULE_FT0_AGINGLASERTASK_H + +#include "QualityControl/TaskInterface.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::ft0 +{ + +class AgingLaserTask final : public TaskInterface +{ + public: + AgingLaserTask() = default; + ~AgingLaserTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + /// Check if the laser was triggered for this BC + /// \param bc BC to check + /// \param bcDelay Expected BC delay from trigger to signal + /// \return True if the laser was triggered + bool bcIsTrigger(int bc, int bcDelay) const; + + /// Check if a detector signal is expected for this BC + /// \param bc BC to check + /// \return True if a detector signal is expected + bool bcIsDetector(int bc) const; + + /// Check if the first reference signal is expected for this BC + /// \param bc BC to check + /// \param refChId Rerernce channel where signal is seen + /// \return True if the first reference signal is expected for this BC + bool bcIsPeak1(int bc, int refChId) const; + + /// Check if the second reference signal is expected for this BC + /// \param bc BC to check + /// \param refChId Rerernce channel where signal is seen + /// \return True if the second reference signal is expected for this BC + bool bcIsPeak2(int bc, int refChId) const; + + // Constants + constexpr static std::size_t sNCHANNELS_PM = o2::ft0::Constants::sNCHANNELS_PM; ///< Max number of FT0 channels + constexpr static std::size_t sMaxBC = o2::constants::lhc::LHCMaxBunches; ///< Max number of BCs + + // Task parameters + std::vector mDetectorChIDs; ///< Enabled detector channels + std::vector mReferenceChIDs; ///< Enabled reference channels + int mDetectorAmpCut; ///< Amplitude cut for detector channels + int mReferenceAmpCut; ///< Amplitude cut for reference channels + std::vector mLaserTriggerBCs; ///< BCs in which the laser is triggered + int mDetectorBCdelay; ///< BC delay for detector channels (same for all) + std::map mReferencePeak1BCdelays; ///< BC delays for reference channel peak 1 (per channel) + std::map mReferencePeak2BCdelays; ///< BC delays for reference channel peak 2 (per channel) + + bool mDebug = false; ///< Enable more histograms in debug mode + + // Histograms + + // Amplitude per channel + std::unique_ptr mHistAmpVsChADC0; ///< Amplitude per channel for ADC0 (detector + reference channels) + std::unique_ptr mHistAmpVsChADC1; ///< Amplitude per channel for ADC1 (detector + reference channels) + std::unique_ptr mHistAmpVsChPeak1ADC0; ///< Amplitude per channel for peak 1 for ADC0 (reference channels) + std::unique_ptr mHistAmpVsChPeak1ADC1; ///< Amplitude per channel for peak 1 for ADC1 (reference channels) + std::unique_ptr mHistAmpVsChPeak2ADC0; ///< Amplitude per channel for peak 2 for ADC0 (reference channels) + std::unique_ptr mHistAmpVsChPeak2ADC1; ///< Amplitude per channel for peak 2 for ADC1 (reference channels) + + // // Time per channel + std::unique_ptr mHistTimeVsCh; ///< Time per channel (detector + reference channels) + std::unique_ptr mHistTimeVsChPeak1; ///< Time per channel for peak 1 (reference channels, both ADCs) + std::unique_ptr mHistTimeVsChPeak2; ///< Time per channel for peak 2 (reference channels, both ADCs) + + // Debug histograms + + // Ampltiude per channel + std::unique_ptr mHistAmpVsCh; ///< Amplitude per channel (detector + reference channels) + + // Ampltidue histograms for reference channel peaks + std::map> mMapDebugHistAmp; ///< Amplitude (both ADCs and peaks) + std::map> mMapDebugHistAmpADC0; ///< Ampltidue for ADC0 + std::map> mMapDebugHistAmpADC1; ///< Ampltidue for ADC1 + std::map> mMapDebugHistAmpPeak1; ///< Amplitude for peak 1 + std::map> mMapDebugHistAmpPeak2; ///< Amplitude for peak 2 + std::map> mMapDebugHistAmpPeak1ADC0; ///< Amplitude for peak 1 for ADC0 + std::map> mMapDebugHistAmpPeak1ADC1; ///< Amplitude for peak 1 for ADC1 + std::map> mMapDebugHistAmpPeak2ADC0; ///< Amplitude for peak 2 for ADC0 + std::map> mMapDebugHistAmpPeak2ADC1; ///< Amplitude for peak 2 for ADC1 + + // Time per channel + std::unique_ptr mDebugHistTimeVsChADC0; ///< Time per channel for ADC0 (detector + reference channels) + std::unique_ptr mDebugHistTimeVsChADC1; ///< Time per channel for ADC1 (detector + reference channels) + std::unique_ptr mDebugHistTimeVsChPeak1ADC0; ///< Time per channel for peak 1 for ADC0 (reference channels) + std::unique_ptr mDebugHistTimeVsChPeak1ADC1; ///< Time per channel for peak 1 for ADC1 (reference channels) + std::unique_ptr mDebugHistTimeVsChPeak2ADC0; ///< Time per channel for peak 2 for ADC0 (reference channels) + std::unique_ptr mDebugHistTimeVsChPeak2ADC1; ///< Time per channel for peak 2 for ADC1 (reference channels) + + // Time histograms for reference channel peaks + // TODO: add mMapDebugHistTime, mMapDebugHistTimeADC0, mMapDebugHistTimeADC1 + std::map> mMapDebugHistTimePeak1; + std::map> mMapDebugHistTimePeak2; + std::map> mMapDebugHistTimePeak1ADC0; + std::map> mMapDebugHistTimePeak1ADC1; + std::map> mMapDebugHistTimePeak2ADC0; + std::map> mMapDebugHistTimePeak2ADC1; + + // BC + std::unique_ptr mDebugHistBC; ///< All BCs + std::unique_ptr mDebugHistBCDetector; ///< Detector channel BCs + std::unique_ptr mDebugHistBCReference; ///< Reference channel BCs + std::unique_ptr mDebugHistBCAmpCut; ///< All BCs with amplitude cut + std::unique_ptr mDebugHistBCAmpCutADC0; ///< All BCs with amplitude cut for ADC0 + std::unique_ptr mDebugHistBCAmpCutADC1; ///< All BCs with amplitude cut for ADC1 + std::unique_ptr mDebugHistBCDetectorAmpCut; ///< Detector channel BCs with amplitude cut + std::unique_ptr mDebugHistBCDetectorAmpCutADC0; ///< Detector channel BCs with amplitude cut for ADC0 + std::unique_ptr mDebugHistBCDetectorAmpCutADC1; ///< Detector channel BCs with amplitude cut for ADC1 + std::unique_ptr mDebugHistBCReferenceAmpCut; ///< Reference channel BCs with amplitude cut + std::unique_ptr mDebugHistBCReferenceAmpCutADC0; ///< Reference channel BCs with amplitude cut for ADC0 + std::unique_ptr mDebugHistBCReferenceAmpCutADC1; ///< Reference channel BCs with amplitude cut for ADC1 + + // Amplitude per BC for reference channels + std::map> mMapDebugHistAmpVsBC; ///< Amplitude vs BC (both ADCs and peaks = 4 peaks) + std::map> mMapDebugHistAmpVsBCADC0; ///< Amplitude vs BC for ADC0 (both peaks) + std::map> mMapDebugHistAmpVsBCADC1; ///< Amplitude vs BC for ADC1 (both peaks) + + // Including the histograms below will produce 'object of class TObjArray read too many bytes' in --local-batch mode + + // // Time per BC for reference channels + // std::map> mMapDebugHistTimeVsBC; ///< Time vs BC (both ADCs and peaks = 4 peaks) + // std::map> mMapDebugHistTimeVsBCADC0; ///< Time vs BC for ADC0 (both peaks) + // std::map> mMapDebugHistTimeVsBCADC1; ///< Time vs BC for ADC1 (both peaks) +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_AGINGLASERTASK_H diff --git a/Modules/FIT/FT0/include/FT0/AmpTimeDistribution.h b/Modules/FIT/FT0/include/FT0/AmpTimeDistribution.h new file mode 100644 index 0000000000..89c91e798f --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/AmpTimeDistribution.h @@ -0,0 +1,47 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef O2_FITAMPTIMEDISTRIBUTION_H +#define O2_FITAMPTIMEDISTRIBUTION_H + +#include "TH2F.h" + +#include +#include +#include +#include + +#include + +namespace o2::fit +{ + +struct AmpTimeDistribution { + AmpTimeDistribution() = default; + AmpTimeDistribution(const std::string& name, const std::string& title, int nBins, double minRange, double maxRange, int binsInStep = 50, int binMax = 4095, int axis = 0); + AmpTimeDistribution(const AmpTimeDistribution&); + AmpTimeDistribution(AmpTimeDistribution&&) noexcept; + AmpTimeDistribution& operator=(const AmpTimeDistribution&); + AmpTimeDistribution& operator=(AmpTimeDistribution&&) noexcept; + typedef float Content_t; // to template? + typedef TH2F Hist2F_t; + std::unique_ptr mHist = nullptr; + + void initHists(const std::string& name, const std::string& title, int nBins, double minRange, double maxRange, int binsInStep = 50, int binMax = 4095, int axis = 0); + static std::vector makeVaribleBins(const std::vector>& vecParams, int binMax = 4095); + static std::vector makeVaribleBins(int binsInStep = 50, int binMax = 4095); +}; + +template +using AmpTimeDistributionDetector = std::array, NADC>; + +} // namespace o2::fit +#endif \ No newline at end of file diff --git a/Modules/FIT/FT0/include/FT0/CFDEffCheck.h b/Modules/FIT/FT0/include/FT0/CFDEffCheck.h new file mode 100644 index 0000000000..ab92c0caa1 --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/CFDEffCheck.h @@ -0,0 +1,80 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CFDEffCheck.h +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#ifndef QC_MODULE_FT0_FT0CFDEFFCHECK_H +#define QC_MODULE_FT0_FT0CFDEFFCHECK_H + +#include +#include + +#include "QualityControl/CheckInterface.h" + +#include "FT0Base/Geometry.h" +#include "DataFormatsFIT/DeadChannelMap.h" + +namespace o2::quality_control_modules::ft0 +{ + +/// \brief checks if CFD efficiency is below threshold +/// \author Sebastian Bysiak sbysiak@cern.ch +class CFDEffCheck : public o2::quality_control::checker::CheckInterface +{ + public: + CFDEffCheck() = default; + ~CFDEffCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(CFDEffCheck, 2); + + private: + template ::value || + std::is_same::value || (std::is_integral::value && !std::is_same::value)>::type> + auto parseParameters(const std::string& param, const std::string& del) + { + std::regex reg(del); + std::sregex_token_iterator first{ param.begin(), param.end(), reg, -1 }, last; + std::vector vecResult; + if (std::find_if(param.begin(), param.end(), ::isdigit) == param.end()) { + return vecResult; + } + for (auto it = first; it != last; it++) { + if constexpr (std::is_integral::value && !std::is_same::value) { + vecResult.push_back(std::stoi(*it)); + } else if constexpr (std::is_floating_point::value) { + vecResult.push_back(std::stod(*it)); + } else if constexpr (std::is_same::value) { + vecResult.push_back(*it); + } + } + return vecResult; + } + + constexpr static std::size_t sNCHANNELS = o2::ft0::Geometry::Nchannels; + o2::fit::DeadChannelMap* mDeadChannelMap; + std::string mDeadChannelMapStr; + std::string mPathDeadChannelMap; + float mThreshWarning; + float mThreshError; + int mNumWarnings; + int mNumErrors; +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_FT0CFDEFFCHECK_H diff --git a/Modules/FIT/FT0/include/FT0/ChannelGeometry.h b/Modules/FIT/FT0/include/FT0/ChannelGeometry.h new file mode 100644 index 0000000000..2d8b80bb44 --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/ChannelGeometry.h @@ -0,0 +1,79 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ChannelGeometry.h +/// \author Artur Furs afurs@cern.ch +/// + +#ifndef QC_MODULE_FT0_CHANNELGEOMETRY_H +#define QC_MODULE_FT0_CHANNELGEOMETRY_H + +#include + +#include +#include +#include +namespace o2::quality_control_modules::ft0 +{ + +class ChannelGeometry +{ + public: + ChannelGeometry() = default; + ~ChannelGeometry() = default; + typedef TH2Poly Hist_t; + typedef std::map ChannelMap_t; // chID -> bin + typedef std::pair Point_t; // X/y coordinates + typedef std::map ChannelGeometryMap_t; + + ChannelGeometryMap_t mChannelGeometryMap{}; + ChannelMap_t mChannelMapA{}; // A-side + ChannelMap_t mChannelMapC{}; // C-side + double mMargin{ 10. }; // margin between channels + void parseChannelTable(const std::string& filepath, char delimeter = ';'); + void makeChannel(int chID, double x, double y); + void initHists(double xMin, double xMax, double yMin, double yMax); + void init(double xMin, double xMax, double yMin, double yMax, double margin); + void clear(); + + std::unique_ptr makeHistSideA(const std::string& histName, const std::string& histTitle); + std::unique_ptr makeHistSideC(const std::string& histName, const std::string& histTitle); + void setBinContent(Hist_t* histSideA, Hist_t* histSideC, int chID, double val); + + template + void convertHist1D(HistSrcType* histSrc, Hist_t* histSideA, Hist_t* histSideC) + { + for (int iBin = 0; iBin < histSrc->GetNbinsX(); iBin++) { + const auto val = histSrc->GetBinContent(iBin + 1); + setBinContent(histSideA, histSideC, iBin, val); + } + } + + static std::string getFilepath(const std::string& filename = "FT0_LUT.csv") + { + const auto pathEnv = std::getenv("QUALITYCONTROL_ROOT"); + const std::string subfilepath = "/etc/Modules/FIT/FT0/etc/" + filename; + if (pathEnv) { + return pathEnv + subfilepath; + } + return std::string{ "" }; + } + + private: + std::unique_ptr mHistSideA; //! hist template for A-side, use Clone() + std::unique_ptr mHistSideC; //! hist template for C-side, use Clone() + bool mIsOk{ true }; +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_CHANNELGEOMETRY_H diff --git a/Modules/FIT/FT0/include/FT0/DigitQcTask.h b/Modules/FIT/FT0/include/FT0/DigitQcTask.h new file mode 100644 index 0000000000..6556261e39 --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/DigitQcTask.h @@ -0,0 +1,184 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTask.h +/// \author Artur Furs afurs@cern.ch +/// \brief Quality Control DPL Task for FT0's digit visualization for non-laser events only + +#ifndef QC_MODULE_FT0_FT0DIGITQCTASK_H +#define QC_MODULE_FT0_FT0DIGITQCTASK_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TH1.h" +#include "TH2.h" +#include "TList.h" +#include "Rtypes.h" + +#include "CommonConstants/LHCConstants.h" + +#include "QualityControl/TaskInterface.h" +#include "QualityControl/QcInfoLogger.h" + +#include "FT0Base/Constants.h" +#include "FT0Base/Geometry.h" +#include "DataFormatsFT0/Digit.h" +#include "DataFormatsFT0/ChannelData.h" +#include "FITCommon/DetectorFIT.h" +#include "FITCommon/HelperFIT.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::ft0 +{ +class DigitQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + DigitQcTask() : mHashedBitBinPos(fillHashedBitBinPos()), mHashedPairBitBinPos(fillHashedPairBitBinPos()) {} + /// Destructor + ~DigitQcTask() override; + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + constexpr static std::size_t sNCHANNELS_PM = o2::ft0::Constants::sNCHANNELS_PM; + constexpr static std::size_t sNCHANNELS_A = o2::ft0::Geometry::NCellsA * 4; + constexpr static std::size_t sNCHANNELS_C = o2::ft0::Geometry::NCellsC * 4; + constexpr static std::size_t sOrbitsPerTF = 256; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + + constexpr static float sCFDChannel2NS = 0.01302; // CFD channel width in ns + using Detector_t = o2::quality_control_modules::fit::detectorFIT::DetectorFT0; + + private: + // three ways of computing cycle duration: + // 1) number of time frames + // 2) time in ns from InteractionRecord: total range (totalMax - totalMin) + // 3) time in ns from InteractionRecord: sum of each TF duration + // later on choose the best and remove others + double mTimeMinNS = 0.; + double mTimeMaxNS = 0.; + double mTimeCurNS = 0.; + int mTfCounter = 0; + double mTimeSum = 0.; + + long mTFcreationTime = 0; + + void rebinFromConfig(); + bool chIsVertexEvent(const o2::ft0::ChannelData); + + TList* mListHistGarbage; + std::set mSetAllowedChIDs; + std::set mSetAllowedChIDsAmpVsTime; + std::array mStateLastIR2Ch; + std::array mChID2PMhash; // map chID->hashed PM value + uint8_t mTCMhash; // hash value for TCM, and bin position in hist + std::map mMapPMhash2isAside; + typename Detector_t::TrgMap_t mMapPMbits = Detector_t::sMapPMbits; + typename Detector_t::TrgMap_t mMapTechTrgBitsExtra = Detector_t::sMapTechTrgBitsExtra; + typename Detector_t::TrgMap_t mMapTrgBits = Detector_t::sMapTrgBits; + using DataTCM_t = o2::quality_control_modules::fit::DataTCM; + using TrgValidation_t = o2::quality_control_modules::fit::TrgValidation; + TrgValidation_t mTrgValidation; + + int mGoodPMbits_ChID; + int mBadPMbits_ChID; + int mPMbitsToCheck_ChID; + int mLowTimeGate_ChID; + int mUpTimeGate_ChID; + // Timestamp + std::string mMetaAnchorOutput{}; + std::string mTimestampMetaField{}; + // Objects which will be published + std::unique_ptr mHistAmp2Ch; + std::unique_ptr mHistTime2Ch; + std::unique_ptr mHistChDataBits; + std::unique_ptr mHistOrbit2BC; + std::unique_ptr mHistBC; + std::unique_ptr mHistNchA; + std::unique_ptr mHistNchC; + std::unique_ptr mHistSumAmpA; + std::unique_ptr mHistSumAmpC; + std::unique_ptr mHistAverageTimeA; + std::unique_ptr mHistAverageTimeC; + std::unique_ptr mHistChannelID; + std::unique_ptr mHistChIDperBC; + std::unique_ptr mHistTimeSum2Diff; + std::unique_ptr mHistTriggersCorrelation; + std::unique_ptr mHistCycleDuration; + std::unique_ptr mHistCycleDurationNTF; + std::unique_ptr mHistCycleDurationRange; + std::map mMapHistAmp1D; + std::map mMapHistTime1D; + std::map mMapHistPMbits; + std::map mMapHistAmpVsTime; + std::unique_ptr mHistBCvsTrg; + std::unique_ptr mHistBCvsFEEmodules; + std::unique_ptr mHistOrbitVsTrg; + std::unique_ptr mHistOrbitVsFEEmodules; + std::unique_ptr mHistPmTcmNchA; + std::unique_ptr mHistPmTcmSumAmpA; + std::unique_ptr mHistPmTcmAverageTimeA; + std::unique_ptr mHistPmTcmNchC; + std::unique_ptr mHistPmTcmSumAmpC; + std::unique_ptr mHistPmTcmAverageTimeC; + std::unique_ptr mHistTriggersSoftwareVsTCM; + + // Hashed maps + static const size_t mapSize = 256; + const std::array, mapSize> mHashedBitBinPos; // map with bit position for 1 byte trg signal, for 1 Dim hists; + const std::array>, mapSize> mHashedPairBitBinPos; // map with paired bit position for 1 byte trg signal, for 1 Dim hists; + static std::array, mapSize> fillHashedBitBinPos() + { + std::array, mapSize> hashedBitBinPos{}; + for (int iByteValue = 0; iByteValue < hashedBitBinPos.size(); iByteValue++) { + auto& vec = hashedBitBinPos[iByteValue]; + for (int iBit = 0; iBit < 8; iBit++) { + if (iByteValue & (1 << iBit)) { + vec.push_back(iBit); + } + } + } + return hashedBitBinPos; + } + static std::array>, mapSize> fillHashedPairBitBinPos() + { + const std::array, mapSize> hashedBitBinPos = fillHashedBitBinPos(); + std::array>, mapSize> hashedPairBitBinPos{}; + for (int iByteValue = 0; iByteValue < hashedBitBinPos.size(); iByteValue++) { + const auto& vecBits = hashedBitBinPos[iByteValue]; + auto& vecPairBits = hashedPairBitBinPos[iByteValue]; + for (int iBitFirst = 0; iBitFirst < vecBits.size(); iBitFirst++) { + for (int iBitSecond = iBitFirst; iBitSecond < vecBits.size(); iBitSecond++) { + vecPairBits.push_back({ static_cast(vecBits[iBitFirst]), static_cast(vecBits[iBitSecond]) }); + } + } + } + return hashedPairBitBinPos; + } +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_FT0DIGITQCTASK_H diff --git a/Modules/FIT/FT0/include/FT0/FractionCheck.h b/Modules/FIT/FT0/include/FT0/FractionCheck.h new file mode 100644 index 0000000000..71608b088a --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/FractionCheck.h @@ -0,0 +1,85 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file FractionCheck.h +/// \author Artur Furs afurs@cern.ch +/// + +#ifndef QC_MODULE_FT0_FT0FRACTIONCHECK_H +#define QC_MODULE_FT0_FT0FRACTIONCHECK_H + +#include +#include +#include + +#include "QualityControl/CheckInterface.h" + +#include "FT0Base/Geometry.h" +#include "DataFormatsFIT/DeadChannelMap.h" + +namespace o2::quality_control_modules::ft0 +{ + +/// \brief checks if CFD efficiency is below threshold +/// \author Sebastian Bysiak sbysiak@cern.ch +class FractionCheck : public o2::quality_control::checker::CheckInterface +{ + public: + FractionCheck() = default; + ~FractionCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + template ::value || + std::is_same::value || (std::is_integral::value && !std::is_same::value)>::type> + auto parseParameters(const std::string& param, const std::string& del) + { + std::regex reg(del); + std::sregex_token_iterator first{ param.begin(), param.end(), reg, -1 }, last; + std::vector vecResult; + if (std::find_if(param.begin(), param.end(), ::isdigit) == param.end()) { + return vecResult; + } + for (auto it = first; it != last; it++) { + if constexpr (std::is_integral::value && !std::is_same::value) { + vecResult.push_back(std::stoi(*it)); + } else if constexpr (std::is_floating_point::value) { + vecResult.push_back(std::stod(*it)); + } else if constexpr (std::is_same::value) { + vecResult.push_back(*it); + } + } + return vecResult; + } + + constexpr static std::size_t sNCHANNELS = o2::ft0::Geometry::Nchannels; + o2::fit::DeadChannelMap* mDeadChannelMap; + std::string mDeadChannelMapStr; + std::string mPathDeadChannelMap; + std::string mNameObjectToCheck; + std::set mIgnoreBins{}; + bool mUseDeadChannelMap{ false }; + bool mIsInversedThresholds; // check if values should be upper + float mThreshWarning; + float mThreshError; + int mNumWarnings; + int mNumErrors; + ClassDefOverride(FractionCheck, 1); +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_FT0FRACTIONCHECK_H diff --git a/Modules/FIT/FT0/include/FT0/GenericCheck.h b/Modules/FIT/FT0/include/FT0/GenericCheck.h new file mode 100644 index 0000000000..14f8aa2aea --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/GenericCheck.h @@ -0,0 +1,130 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GenericCheck.h +/// \author Sebastian Bysiak +/// + +#ifndef QC_MODULE_FT0_FT0GENERICCHECK_H +#define QC_MODULE_FT0_FT0GENERICCHECK_H + +#include "QualityControl/CheckInterface.h" +#include +#include +#include "QualityControl/QcInfoLogger.h" + +namespace o2::quality_control_modules::ft0 +{ + +/// \brief helper class to store acceptable limits for given quantity +/// \author Sebastian Bysiak +class SingleCheck +{ + public: + SingleCheck() = default; + ~SingleCheck() = default; + + SingleCheck(std::string name, float thresholdWarning, float thresholdError, bool shouldBeLower, bool isActive) + { + mCheckName = name; + mThresholdWarning = thresholdWarning; + mThresholdError = thresholdError; + mShouldBeLower = shouldBeLower; + mIsActive = isActive; + }; + bool isActive() { return mIsActive; }; + + void doCheck(Quality& result, float checkedValue) + { + if (!mIsActive) + return; + + std::string log = Form("%s : comparing value = %f with thresholds = %f, %f", mCheckName.c_str(), checkedValue, mThresholdWarning, mThresholdError); + if (mShouldBeLower) { + if (checkedValue > mThresholdError) { + if (result.isBetterThan(Quality::Bad)) + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("%.3f > %.3f (%s error limit)", checkedValue, mThresholdError, mCheckName.c_str())); + log += "-> Bad"; + } else if (checkedValue > mThresholdWarning) { + if (result.isBetterThan(Quality::Medium)) + result.set(Quality::Medium); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("%.3f > %.3f (%s warning limit)", checkedValue, mThresholdWarning, mCheckName.c_str())); + log += "-> Medium"; + } else { + log += "-> OK"; + } + } else { + if (checkedValue < mThresholdError) { + if (result.isBetterThan(Quality::Bad)) + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("%.3f < %.3f (%s error limit)", checkedValue, mThresholdError, mCheckName.c_str())); + log += "-> Bad"; + } else if (checkedValue < mThresholdWarning) { + if (result.isBetterThan(Quality::Medium)) + result.set(Quality::Medium); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("%.3f < %.3f (%s warning limit)", checkedValue, mThresholdWarning, mCheckName.c_str())); + log += "-> Medium"; + } else { + log += "-> OK"; + } + } + ILOG(Debug, Support) << log << ENDM; + } + + private: + std::string mCheckName; + float mThresholdWarning; + float mThresholdError; + bool mShouldBeLower; + bool mIsActive; +}; + +/// \brief checks multiple basic hist statistics +/// \author Sebastian Bysiak +class GenericCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + GenericCheck() = default; + /// Destructor + ~GenericCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(GenericCheck, 2); + + private: + SingleCheck getCheckFromConfig(std::string); + + SingleCheck mCheckMaxOverflowIntegralRatio; + + SingleCheck mCheckMinMeanX; + SingleCheck mCheckMaxMeanX; + SingleCheck mCheckMaxStddevX; + + SingleCheck mCheckMinMeanY; + SingleCheck mCheckMaxMeanY; + SingleCheck mCheckMaxStddevY; + + SingleCheck mCheckMinGraphLastPoint; + SingleCheck mCheckMaxGraphLastPoint; + + std::array mPositionMsgBox; + std::string mNameObjOnCanvas; +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_FT0GENERICCHECK_H diff --git a/Modules/FIT/FT0/include/FT0/LinkDef.h b/Modules/FIT/FT0/include/FT0/LinkDef.h new file mode 100644 index 0000000000..2748953e33 --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/LinkDef.h @@ -0,0 +1,19 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::ft0::EventWithChannelData + ; +#pragma link C++ class o2::quality_control_modules::ft0::DigitQcTask + ; +#pragma link C++ class o2::quality_control_modules::ft0::MergedTreeCheck + ; +#pragma link C++ class o2::quality_control_modules::ft0::PostProcTask + ; +#pragma link C++ class o2::quality_control_modules::ft0::GenericCheck + ; +#pragma link C++ class o2::quality_control_modules::ft0::CFDEffCheck + ; +#pragma link C++ class o2::quality_control_modules::ft0::FractionCheck + ; +#pragma link C++ class o2::quality_control_modules::ft0::OutOfBunchCollCheck + ; +#pragma link C++ class o2::quality_control_modules::ft0::RecPointsQcTask + ; + +#pragma link C++ class o2::quality_control_modules::ft0::AgingLaserTask + ; +#pragma link C++ class o2::quality_control_modules::ft0::AgingLaserPostProcTask + ; + +#endif diff --git a/Modules/FIT/FT0/include/FT0/MergedTreeCheck.h b/Modules/FIT/FT0/include/FT0/MergedTreeCheck.h new file mode 100644 index 0000000000..ecae98349e --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/MergedTreeCheck.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MergedTreeCheck.h +/// \author Milosz Filus +/// Example of post processing check + +#ifndef QC_MODULE_FT0_FT0MergedTreeCheck_H +#define QC_MODULE_FT0_FT0MergedTreeCheck_H + +// Quality Control +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::ft0 +{ + +class MergedTreeCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + MergedTreeCheck() = default; + /// Destructor + ~MergedTreeCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(MergedTreeCheck, 2); +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif diff --git a/Modules/FIT/FT0/include/FT0/OutOfBunchCollCheck.h b/Modules/FIT/FT0/include/FT0/OutOfBunchCollCheck.h new file mode 100644 index 0000000000..4feae605c3 --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/OutOfBunchCollCheck.h @@ -0,0 +1,56 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file OutOfBunchCollCheck.h +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#ifndef QC_MODULE_FT0_FT0OUTOFBUNCHCOLLCHECK_H +#define QC_MODULE_FT0_FT0OUTOFBUNCHCOLLCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "CommonConstants/LHCConstants.h" + +namespace o2::quality_control_modules::ft0 +{ + +/// \brief Checks what fraction of collisions is out of bunch +/// \author Sebastian Bysiak sbysiak@cern.ch +class OutOfBunchCollCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + OutOfBunchCollCheck() = default; + /// Destructor + ~OutOfBunchCollCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + + ClassDefOverride(OutOfBunchCollCheck, 2); + + private: + float mFractionOutOfBunchColl; + int mNumNonEmptyBins; + float mThreshWarning; + float mThreshError; + std::string mTrgName; + int mBinPos; + bool mEnableMessage{ true }; +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_FT0OUTOFBUNCHCOLLCHECK_H diff --git a/Modules/FIT/FT0/include/FT0/PostProcTask.h b/Modules/FIT/FT0/include/FT0/PostProcTask.h new file mode 100644 index 0000000000..7a1b8b0fa3 --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/PostProcTask.h @@ -0,0 +1,125 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcTask.h +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#ifndef QC_MODULE_FT0_POSTPROCTASK_H +#define QC_MODULE_FT0_POSTPROCTASK_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/DatabaseInterface.h" +#include "FITCommon/PostProcHelper.h" +#include "FITCommon/DetectorFIT.h" +#include "FT0/ChannelGeometry.h" + +#include "CCDB/CcdbApi.h" +#include "CommonConstants/LHCConstants.h" +#include "FT0Base/Constants.h" +#include "DataFormatsFT0/ChannelData.h" +#include "DataFormatsFT0/Digit.h" + +#include +#include +#include +#include +#include + +#include +#include + +class TH1F; +class TCanvas; +class TLegend; +class TProfile; + +namespace o2::quality_control_modules::ft0 +{ + +/// \brief Basic Postprocessing Task for FT0, computes among others the trigger rates +/// \author Sebastian Bysiak sbysiak@cern.ch +class PostProcTask final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + PostProcTask() = default; + ~PostProcTask() override; + void configure(const boost::property_tree::ptree&) override; + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + constexpr static std::size_t sNCHANNELS_PM = o2::ft0::Constants::sNCHANNELS_PM; + using Detector_t = o2::quality_control_modules::fit::detectorFIT::DetectorFT0; + + private: + o2::quality_control_modules::fit::PostProcHelper mPostProcHelper; + bool mIsFirstIter{ true }; + typename Detector_t::TrgMap_t mMapPMbits = Detector_t::sMapPMbits; + typename Detector_t::TrgMap_t mMapTechTrgBitsExtra = Detector_t::sMapTechTrgBitsExtra; + typename Detector_t::TrgMap_t mMapTrgBits = Detector_t::sMapTrgBits; + // MOs + std::unique_ptr mHistChDataNOTbits; + std::unique_ptr mHistTriggers; + std::unique_ptr mHistTriggerRates; + std::unique_ptr mHistTimeInWindow; + std::unique_ptr mHistCFDEff; + std::unique_ptr mHistChannelID_outOfBC; + std::unique_ptr mHistTrg_outOfBC; + std::unique_ptr mHistTrgValidation; + std::unique_ptr mHistBcPattern; + std::unique_ptr mHistBcTrgOutOfBunchColl; + std::unique_ptr mAmpl; + std::unique_ptr mTime; + std::unique_ptr mHistStatsSideA; + std::unique_ptr mHistStatsSideC; + + /// Sum of count in bins vs amplitude for detector channels 0 to 31 (A-side inner). + /// In other words a projection of range 1-32 in the 2D histogram amplitude vs channel + /// from the DigitQcTask. + std::unique_ptr mHistAmpAInner; + + /// Sum of count in bins vs amplitude for detector channels 32 to 95 (A-side outer). + /// In other words a projection of range 33-96 in the 2D histogram amplitude vs channel + /// from the DigitQcTask. + std::unique_ptr mHistAmpAOuter; + + /// Sum of count in bins vs amplitude for detector channels 96 to 207 (C-side). + /// In other words a projection of range 97-208 in the 2D histogram amplitude vs channel + /// from the DigitQcTask. + std::unique_ptr mHistAmpC; + + /// Sum of normalized count in bins vs amplitude for all detector channels (0-207). + /// In other words the sum of Y projections for each bin in the 2D histogram amplitude vs channel + /// from the DigitQcTask, where each projection is scaled with 1/(counts in that projection) + std::unique_ptr mHistAmpNormPerChannel; + + ChannelGeometry mChannelGeometry; //! + // Configurations + int mLowTimeThreshold{ -192 }; + int mUpTimeThreshold{ 192 }; + std::string mAsynchChannelLogic{ "standard" }; + // + void setTimestampToMOs(); + // TO REMOVE + std::vector mVecChannelIDs{}; + std::vector mVecHistsToDecompose{}; + using HistDecomposed_t = TH1D; + using MapHistsDecomposed_t = std::map>>; + MapHistsDecomposed_t mMapHistsToDecompose{}; + void decomposeHists(); + void reset(); +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_POSTPROCTASK_H diff --git a/Modules/FIT/FT0/include/FT0/RecPointsQcTask.h b/Modules/FIT/FT0/include/FT0/RecPointsQcTask.h new file mode 100644 index 0000000000..95a89ca45a --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/RecPointsQcTask.h @@ -0,0 +1,93 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTask.h +/// \author Artur Furs afurs@cern.ch +/// modified by Sebastin Bysiak sbysiak@cern.ch +/// QC Task for FT0 detector, mostly for data visualisation during FEE tests + +#ifndef QC_MODULE_FT0_FT0RECOQCTASK_H +#define QC_MODULE_FT0_FT0RECOQCTASK_H + +#include +#include +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include "QualityControl/TaskInterface.h" +#include "FITCommon/DetectorFIT.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::ft0 +{ + +class RecPointsQcTask final : public TaskInterface +{ + static constexpr int sNCHANNELS = o2::ft0::Geometry::Nchannels; + + public: + /// \brief Constructor + RecPointsQcTask() = default; + /// Destructor + ~RecPointsQcTask() override; + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + using Detector_t = o2::quality_control_modules::fit::detectorFIT::DetectorFT0; + void initHists(); + + private: + std::array mArrAmpTimeDistribution; + TList* mListHistGarbage; + std::set mSetAllowedChIDs; + unsigned int mTrgPos_minBias; + unsigned int mTrgPos_allEvents; + // Objects which will be published + std::unique_ptr mHistAmp2Ch; + std::unique_ptr mHistTime2Ch; + std::unique_ptr mHistCollTimeAC; + std::unique_ptr mHistCollTimeA; + std::unique_ptr mHistCollTimeC; + std::unique_ptr mHistSumTimeAC_perTrg; + std::unique_ptr mHistDiffTimeAC_perTrg; + std::unique_ptr mHistTimeA_perTrg; + std::unique_ptr mHistTimeC_perTrg; + std::unique_ptr mHistBC_perTriggers; + std::unique_ptr mHistResCollTimeA; + std::unique_ptr mHistResCollTimeC; + std::map mMapHistAmpVsTime; + + typename Detector_t::TrgMap_t mMapTrgBits = Detector_t::sMapTrgBits; +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif // QC_MODULE_FT0_FT0RecoQcTask_H diff --git a/Modules/FIT/FT0/include/FT0/Utilities.h b/Modules/FIT/FT0/include/FT0/Utilities.h new file mode 100644 index 0000000000..e50835dac3 --- /dev/null +++ b/Modules/FIT/FT0/include/FT0/Utilities.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Utilities.h +/// \author Milosz Filus +// Custom class used to store values in ttree which will be published to ccdb as monitoring object +// probably will be modified as you want + +#ifndef __O2_QC_FT0_UTILITIES_H__ +#define __O2_QC_FT0_UTILITIES_H__ + +#include +#include "DataFormatsFT0/Digit.h" +#include "DataFormatsFT0/ChannelData.h" +#include "Rtypes.h" +#include "TObject.h" + +namespace o2::quality_control_modules::ft0 +{ + +struct EventWithChannelData { + + EventWithChannelData() = default; + EventWithChannelData(int pEventID, uint16_t pBC, uint32_t pOrbit, double pTimestamp, const std::vector& pChannels) + : eventID(pEventID), bc(pBC), orbit(pOrbit), timestampNS(pTimestamp), channels(pChannels) {} + + int eventID = -1; + uint16_t bc = o2::InteractionRecord::DummyBC; + uint32_t orbit = o2::InteractionRecord::DummyOrbit; + double timestampNS = 0; + std::vector channels; + + int getEventID() const { return eventID; } + uint16_t getBC() const { return bc; } + uint32_t getOrbit() const { return orbit; } + double getTimestamp() const { return timestampNS; } + const std::vector& getChannels() const { return channels; } + + ClassDefNV(EventWithChannelData, 2); +}; + +} // namespace o2::quality_control_modules::ft0 + +#endif \ No newline at end of file diff --git a/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx new file mode 100644 index 0000000000..c26db51335 --- /dev/null +++ b/Modules/FIT/FT0/src/AgingLaserPostProcTask.cxx @@ -0,0 +1,307 @@ +// Copyright 2019-2025 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General +// Public License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file AgingLaserPostProcTask.cxx +/// \author Andreas Molander , Jakub Muszyński + +#include "FT0/AgingLaserPostProcTask.h" + +#include "Common/Utils.h" +#include "DataFormatsFIT/DeadChannelMap.h" +#include "FITCommon/HelperHist.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include +#include + +#include +#include +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::fit; + +namespace o2::quality_control_modules::ft0 +{ + +AgingLaserPostProcTask::~AgingLaserPostProcTask() = default; + +void AgingLaserPostProcTask::configure(const boost::property_tree::ptree& cfg) +{ + mReset = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "reset", false); + ILOG(Info, Support) << "Is this a reset run: " << mReset << ENDM; + + mAgingLaserPath = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "agingLaserTaskPath", mAgingLaserPath); + mAgingLaserPostProcPath = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "agingLaserPostProcPath", mAgingLaserPostProcPath); + + mDetectorChIDs.resize(208); + std::iota(mDetectorChIDs.begin(), mDetectorChIDs.end(), 0); + const std::string detSkip = + o2::quality_control_modules::common::getFromConfig(mCustomParameters, "ignoreDetectorChannels", ""); + if (!detSkip.empty()) { + auto toSkip = fit::helper::parseParameters(detSkip, ","); + for (auto s : toSkip) { + mDetectorChIDs.erase(std::remove(mDetectorChIDs.begin(), mDetectorChIDs.end(), s), + mDetectorChIDs.end()); + } + } + mUseDeadChannelMap = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "useDeadChannelMap", true); + if (mUseDeadChannelMap) { + o2::fit::DeadChannelMap* dcm = retrieveConditionAny("FT0/Calib/DeadChannelMap"); + if (!dcm) { + ILOG(Error) << "Could not retrieve DeadChannelMap from CCDB!" << ENDM; + } else { + for (unsigned chId = 0; chId < dcm->map.size(); chId++) { + if (!dcm->isChannelAlive(chId)) { + mDetectorChIDs.erase(std::remove(mDetectorChIDs.begin(), mDetectorChIDs.end(), chId), mDetectorChIDs.end()); + } + } + } + } + + // Reference channels: default 208–210, then remove skipped ones + mReferenceChIDs.clear(); + for (uint8_t ch = 208; ch < 211; ++ch) { + mReferenceChIDs.push_back(ch); + } + const std::string refSkip = + o2::quality_control_modules::common::getFromConfig(mCustomParameters, "ignoreRefChannels", ""); + if (!refSkip.empty()) { + auto toSkip = fit::helper::parseParameters(refSkip, ","); + for (auto s : toSkip) { + mReferenceChIDs.erase(std::remove(mReferenceChIDs.begin(), mReferenceChIDs.end(), s), + mReferenceChIDs.end()); + } + } + + mFracWindowA = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "fracWindowLow", mFracWindowA); + mFracWindowB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "fracWindowHigh", mFracWindowB); +} + +void AgingLaserPostProcTask::initialize(Trigger, framework::ServiceRegistryRef) +{ + ILOG(Debug, Devel) << "initialize AgingLaserPostProcTask" << ENDM; + + ILOG(Debug, Devel) << "agingTaskSourcePath : " << mAgingLaserPath << ENDM; + ILOG(Debug, Devel) << "agingLaserPostProcPath : " << mAgingLaserPostProcPath << ENDM; + ILOG(Debug, Devel) << "fractional window : a=" << mFracWindowA << " b=" << mFracWindowB << ENDM; + + mAmpVsChNormWeightedMeanA = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanA", "AmpPerChannelNormWeightedMeanA", + 96, 0, 96); + + mAmpVsChNormWeightedMeanC = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanC", "AmpPerChannelNormWeightedMeanC", + 112, 96, 208); + + if (mReset) { + mAmpVsChNormWeightedMeanAfterLastCorrA = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanAfterLastCorrectionA", "AmpPerChannelNormWeightedMeanAfterLastCorrectionA", + 96, 0, 96); + + mAmpVsChNormWeightedMeanAfterLastCorrC = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanAfterLastCorrectionC", "AmpPerChannelNormWeightedMeanAfterLastCorrectionC", + 112, 96, 208); + } + + mAmpVsChNormWeightedMeanCorrectedA = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanCorrectedA", "AmpPerChannelNormWeightedMeanCorrectedA", + 96, 0, 96); + mAmpVsChNormWeightedMeanCorrectedC = fit::helper::registerHist( + getObjectsManager(), + quality_control::core::PublicationPolicy::ThroughStop, + "", "AmpPerChannelNormWeightedMeanCorrectedC", "AmpPerChannelNormWeightedMeanCorrectedC", + 112, 96, 208); +} + +void AgingLaserPostProcTask::update(Trigger t, framework::ServiceRegistryRef srv) +{ + mAmpVsChNormWeightedMeanA->Reset(); + mAmpVsChNormWeightedMeanC->Reset(); + if (mReset) { + mAmpVsChNormWeightedMeanAfterLastCorrA->Reset(); + mAmpVsChNormWeightedMeanAfterLastCorrC->Reset(); + } + mAmpVsChNormWeightedMeanCorrectedA->Reset(); + mAmpVsChNormWeightedMeanCorrectedC->Reset(); + + /* ---- fetch source histogram ---- */ + auto& qcdb = srv.get(); + + auto moAmpPerChannel = qcdb.retrieveMO(mAgingLaserPath, "AmpPerChannel", t.timestamp, t.activity); + auto moAmpPerChannelPeak1ADC0 = qcdb.retrieveMO(mAgingLaserPath, "AmpPerChannelPeak1ADC0", t.timestamp, t.activity); + auto moAmpPerChannelPeak1ADC1 = qcdb.retrieveMO(mAgingLaserPath, "AmpPerChannelPeak1ADC1", t.timestamp, t.activity); + + TH2* h2AmpPerChannel = moAmpPerChannel ? dynamic_cast(moAmpPerChannel->getObject()) : nullptr; + TH2* h2AmpPerChannelPeak1ADC0 = moAmpPerChannelPeak1ADC0 ? dynamic_cast(moAmpPerChannelPeak1ADC0->getObject()) : nullptr; + TH2* h2AmpPerChannelPeak1ADC1 = moAmpPerChannelPeak1ADC1 ? dynamic_cast(moAmpPerChannelPeak1ADC1->getObject()) : nullptr; + TH2* hAmpPerChannelPeak1 = nullptr; + + if (h2AmpPerChannelPeak1ADC0 && h2AmpPerChannelPeak1ADC1) { + hAmpPerChannelPeak1 = new TH2F("hAmpPerChannelPeak1", "hAmpPerChannelPeak1", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + hAmpPerChannelPeak1->Add(h2AmpPerChannelPeak1ADC0); + hAmpPerChannelPeak1->Add(h2AmpPerChannelPeak1ADC1); + } else { + ILOG(Fatal) << "Could not retrieve " << mAgingLaserPath << "/AmpPerChannelPeak1ADC0 or " << mAgingLaserPath << "/AmpPerChannelPeak1ADC1 for timestamp " + << t.timestamp << ENDM; + } + + if (!h2AmpPerChannel) { + ILOG(Fatal) << "Could not retrieve " << mAgingLaserPath << "/AmpPerChannel for timestamp " + << t.timestamp << ENDM; + } + + if (!hAmpPerChannelPeak1) { + ILOG(Fatal) << "Could not create merged histogram from " << mAgingLaserPath << "/AmpPerChannelPeak1ADC0 and " << mAgingLaserPath << "/AmpPerChannelPeak1ADC1 for timestamp " + << t.timestamp << ENDM; + } + + // Weighted mean of amplitude per detector channel, normalized by the average of the reference channel amplitudes, as stored after performing an aging correction. + // This is used as a normalization factor to get the relative aging in "non-reset" runs since the time of the last aging correction. + std::unique_ptr hAmpVsChWeightedMeanAfterLastCorrA; + std::unique_ptr hAmpVsChWeightedMeanAfterLastCorrC; + + if (!mReset) { + auto validity = qcdb.getLatestObjectValidity("qc/" + mAgingLaserPostProcPath + "/AmpPerChannelNormWeightedMeanAfterLastCorrectionA"); + long timestamp = validity.getMin(); + + ILOG(Info, Support) << "Retrieving normalization histograms from timestamp " << timestamp << ENDM; + + auto moAmpPerChannelNormWeightedMeanAfterLastCorrA = qcdb.retrieveMO( + mAgingLaserPostProcPath, "AmpPerChannelNormWeightedMeanAfterLastCorrectionA", timestamp); + if (!moAmpPerChannelNormWeightedMeanAfterLastCorrA) { + ILOG(Fatal) << "Failed to retrieve histogram " << mAgingLaserPostProcPath + "/AmpPerChannelNormWeightedMeanAfterLastCorrectionA" + << " for timestamp " << timestamp << "! This is not a 'resetting' run and this histogram is therefore needed." << ENDM; + } else { + hAmpVsChWeightedMeanAfterLastCorrA = std::unique_ptr( + dynamic_cast(moAmpPerChannelNormWeightedMeanAfterLastCorrA->getObject()->Clone())); + } + + auto moAmpPerChannelNormWeightedMeanAfterLastCorrC = qcdb.retrieveMO( + mAgingLaserPostProcPath, "AmpPerChannelNormWeightedMeanAfterLastCorrectionC", timestamp); + if (!moAmpPerChannelNormWeightedMeanAfterLastCorrC) { + ILOG(Fatal) << "Failed to retrieve histogram " << mAgingLaserPostProcPath + "/AmpPerChannelNormWeightedMeanAfterLastCorrectionC" + << " for timestamp " << timestamp << "! This is not a 'resetting' run and this histogram is therefore needed." + << " Please contact the FIT expert." << ENDM; + } else { + hAmpVsChWeightedMeanAfterLastCorrC = std::unique_ptr( + dynamic_cast(moAmpPerChannelNormWeightedMeanAfterLastCorrC->getObject()->Clone())); + } + + if (!hAmpVsChWeightedMeanAfterLastCorrA || !hAmpVsChWeightedMeanAfterLastCorrC) { + ILOG(Fatal) << "Failed to clone normalization histograms from " + << mAgingLaserPostProcPath + "/AmpPerChannelNormWeightedMeanAfterLastCorrectionA or " + << mAgingLaserPostProcPath + "/AmpPerChannelNormWeightedMeanAfterLastCorrectionC" + << "! This is not a 'resetting' run and these histograms are therefore needed." << ENDM; + } + } + + /* ---- 1. Reference-channel Gaussian fits ---- */ + std::vector refMus; + + for (auto chId : mReferenceChIDs) { + auto h1 = std::unique_ptr(hAmpPerChannelPeak1->ProjectionY( + Form("ref_%d", chId), chId + 1, chId + 1)); + + int binMax = h1->GetMaximumBin(); + const double xMax = h1->GetBinCenter(binMax); + const double winLo = TMath::Max(0., (1. - mFracWindowA) * xMax); + const double winHi = (1. + mFracWindowB) * xMax; + + TF1 g("g", "gaus", winLo, winHi); + if (h1->Fit(&g, "QNRS") == 0) { // 0 = fit OK + refMus.push_back(g.GetParameter(1)); + } else { + ILOG(Warning) << "Gaussian fit failed for reference channel " << int(chId) << ENDM; + } + } + + const unsigned nRef = refMus.size(); + if (nRef == 0) { + ILOG(Error) << "No successful reference fits – cannot normalise." << ENDM; + return; + } + const double norm = std::accumulate(refMus.begin(), refMus.end(), 0.) / nRef; + + /* ---- 2. Loop over ALL channels ---- */ + auto processChannel = [&](uint8_t chId) { + auto h1 = std::unique_ptr(h2AmpPerChannel->ProjectionY( + Form("proj_%d", chId), chId + 1, chId + 1)); + + // global maximum + const int binMax = h1->GetMaximumBin(); + const double xMax = h1->GetBinCenter(binMax); + const double winLo = TMath::Max(0., (1. - mFracWindowA) * xMax); + const double winHi = (1. + mFracWindowB) * xMax; + + double num = 0., den = 0.; + const int binLo = h1->FindBin(winLo); + const int binHi = h1->FindBin(winHi); + for (int b = binLo; b <= binHi; ++b) { + const double w = h1->GetBinContent(b); + const double x = h1->GetBinCenter(b); + num += w * x; + den += w; + } + + const double wMean = (den > 0.) ? num / den : 0.; + const double val = (norm > 0.) ? wMean / norm : 0.; + if (chId < 96) { + mAmpVsChNormWeightedMeanA->SetBinContent(chId + 1, val); + if (mReset) { + mAmpVsChNormWeightedMeanAfterLastCorrA->SetBinContent(chId + 1, mAmpVsChNormWeightedMeanA->GetBinContent(chId + 1)); + } + Float_t valCorr = (!mReset ? hAmpVsChWeightedMeanAfterLastCorrA->GetBinContent(chId + 1) : mAmpVsChNormWeightedMeanAfterLastCorrA->GetBinContent(chId + 1)); + if (valCorr == 0) { + ILOG(Error, Support) << "Normalization factor = 0 for channel " << chId + 1 << ". Skipping." << ENDM; + return; + } + mAmpVsChNormWeightedMeanCorrectedA->SetBinContent(chId + 1, val / valCorr); + } else if (chId >= 96 && chId <= 208) { + mAmpVsChNormWeightedMeanC->SetBinContent(chId - 95, val); + if (mReset) { + mAmpVsChNormWeightedMeanAfterLastCorrC->SetBinContent(chId - 95, mAmpVsChNormWeightedMeanC->GetBinContent(chId - 95)); + } + Float_t valCorr = (!mReset ? hAmpVsChWeightedMeanAfterLastCorrC->GetBinContent(chId - 95) : mAmpVsChNormWeightedMeanAfterLastCorrC->GetBinContent(chId - 95)); + if (valCorr == 0) { + ILOG(Error, Support) << "Normalization factor = 0 for channel " << chId + 1 << ". Skipping." << ENDM; + return; + } + mAmpVsChNormWeightedMeanCorrectedC->SetBinContent(chId - 95, val / valCorr); + } + }; + + for (auto ch : mDetectorChIDs) { + processChannel(ch); + } + + ILOG(Debug, Devel) << "update done – " << nRef << " reference fits, norm=" << norm << ENDM; +} + +void AgingLaserPostProcTask::finalize(Trigger, framework::ServiceRegistryRef) +{ + ILOG(Debug, Devel) << "finalize AgingLaserPostProcTask" << ENDM; +} + +} // namespace o2::quality_control_modules::ft0 \ No newline at end of file diff --git a/Modules/FIT/FT0/src/AgingLaserTask.cxx b/Modules/FIT/FT0/src/AgingLaserTask.cxx new file mode 100644 index 0000000000..0afd2b2c61 --- /dev/null +++ b/Modules/FIT/FT0/src/AgingLaserTask.cxx @@ -0,0 +1,595 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AgingLaserTask.cxx +/// \author Andreas Molander , Sandor Lokos , Edmundo Garcia-Solis +/// + +#include "FT0/AgingLaserTask.h" + +#include "Common/Utils.h" +#include "FITCommon/HelperCommon.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace o2::quality_control_modules::ft0 +{ + +AgingLaserTask::~AgingLaserTask() +{ +} + +void AgingLaserTask::initialize(o2::framework::InitContext&) +{ + // Read task parameters + + // Enabled detector channels + const std::string detectorChannels = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "detectorChannelIDs", ""); + if (detectorChannels.size()) { + mDetectorChIDs = fit::helper::parseParameters(detectorChannels, ","); + } else { + // Not specified, enable all + for (uint8_t chId = 0; chId < sNCHANNELS_PM; chId++) { + mDetectorChIDs.push_back(chId); + } + } + + // Enabled reference channels + const std::string referenceChannels = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "referenceChannelIDs", ""); + if (referenceChannels.size()) { + mReferenceChIDs = fit::helper::parseParameters(referenceChannels, ","); + } else { + // Not specified, enable all + // TODO: return with fatal if not specified, to avoid hard coded numbers? + for (uint8_t chId = 208; chId < 211; chId++) { + mReferenceChIDs.push_back(chId); + } + } + + mDetectorAmpCut = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "detectorAmpCut", 0); + mReferenceAmpCut = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "referenceAmpCut", 100); + + // BCs + + // Laser trigger BCs + const std::string laserTriggerBCs = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "laserTriggerBCs", ""); + if (laserTriggerBCs.size()) { + const auto vecParams = fit::helper::parseParameters(laserTriggerBCs, ","); + for (const int bc : vecParams) { + mLaserTriggerBCs.push_back(bc); + } + } + if (mLaserTriggerBCs.size() == 0) { + LOG(fatal) << "No laser trigger BCs specified in QC config!"; + } + + // BC delay for detector channels + mDetectorBCdelay = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "detectorBCdelay", -1); + if (mDetectorBCdelay < 0) { + LOG(fatal) << "No detector BC delay specified in QC config!"; + } + + // BC delay for reference channels peak1 + const std::string referencePeak1BCdelays = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "referencePeak1BCdelays", ""); + if (referencePeak1BCdelays.size()) { + const auto vecParams = fit::helper::parseParameters(referencePeak1BCdelays, ","); + if (vecParams.size() != mReferenceChIDs.size()) { + LOG(fatal) << "Number of reference channels and reference peak 1 BC delays do not match!"; + } + for (int i = 0; i < mReferenceChIDs.size(); i++) { + mReferencePeak1BCdelays.insert({ mReferenceChIDs.at(i), vecParams.at(i) }); + } + } else { + LOG(fatal) << "No reference peak 1 BC delays specified in QC config!"; + } + + // BC delay for reference channels peak2 + const std::string referencePeak2BCdelays = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "referencePeak2BCdelays", ""); + if (referencePeak2BCdelays.size()) { + const auto vecParams = fit::helper::parseParameters(referencePeak2BCdelays, ","); + if (vecParams.size() != mReferenceChIDs.size()) { + LOG(fatal) << "Number of reference channels and reference peak 2 BC delays do not match!"; + } + for (int i = 0; i < mReferenceChIDs.size(); i++) { + mReferencePeak2BCdelays.insert({ mReferenceChIDs.at(i), vecParams.at(i) }); + } + } else { + LOG(fatal) << "No reference peak 2 BC delays specified in QC config!"; + } + + mDebug = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "debug", false); + if (mDebug) { + LOG(warning) << "Running in debug mode!"; + } + + // Initialize histograms + + // Amplitude per channel + mHistAmpVsCh = std::make_unique("AmpPerChannel", "Amplitude vs channel;Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + mHistAmpVsChADC0 = std::make_unique("AmpPerChannelADC0", "Amplitude vs channel (ADC0);Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + mHistAmpVsChADC1 = std::make_unique("AmpPerChannelADC1", "Amplitude vs channel (ADC1);Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + mHistAmpVsChPeak1ADC0 = std::make_unique("AmpPerChannelPeak1ADC0", "Amplitude vs channel (peak 1, ADC0);Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + mHistAmpVsChPeak1ADC1 = std::make_unique("AmpPerChannelPeak1ADC1", "Amplitude vs channel (peak 1, ADC1);Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + mHistAmpVsChPeak2ADC0 = std::make_unique("AmpPerChannelPeak2ADC0", "Amplitude vs channel (peak 2, ADC0);Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + mHistAmpVsChPeak2ADC1 = std::make_unique("AmpPerChannelPeak2ADC1", "Amplitude vs channel (peak 2, ADC1);Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + getObjectsManager()->startPublishing(mHistAmpVsChADC0.get()); + getObjectsManager()->startPublishing(mHistAmpVsChADC1.get()); + getObjectsManager()->startPublishing(mHistAmpVsChPeak1ADC0.get()); + getObjectsManager()->startPublishing(mHistAmpVsChPeak1ADC1.get()); + getObjectsManager()->startPublishing(mHistAmpVsChPeak2ADC0.get()); + getObjectsManager()->startPublishing(mHistAmpVsChPeak2ADC1.get()); + getObjectsManager()->setDefaultDrawOptions(mHistAmpVsChADC0.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mHistAmpVsChADC1.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mHistAmpVsChPeak1ADC0.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mHistAmpVsChPeak1ADC1.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mHistAmpVsChPeak2ADC0.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mHistAmpVsChPeak2ADC1.get(), "COLZ"); + + // Time per channel + mHistTimeVsCh = std::make_unique("TimePerChannel", "Time vs channel;Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + mHistTimeVsChPeak1 = std::make_unique("TimePerChannelPeak1", "Time vs channel (peak 1);Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + mHistTimeVsChPeak2 = std::make_unique("TimePerChannelPeak2", "Time vs channel (peak 2);Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + getObjectsManager()->startPublishing(mHistTimeVsCh.get()); + getObjectsManager()->startPublishing(mHistTimeVsChPeak1.get()); + getObjectsManager()->startPublishing(mHistTimeVsChPeak2.get()); + getObjectsManager()->setDefaultDrawOptions(mHistTimeVsCh.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mHistTimeVsChPeak1.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mHistTimeVsChPeak2.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistAmpVsCh.get()); + getObjectsManager()->setDefaultDrawOptions(mHistAmpVsCh.get(), "COLZ"); + + // Debug histograms + + // Time per channel + mDebugHistTimeVsChADC0 = std::make_unique("TimePerChannelADC0", "Time vs channel (ADC0);Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + mDebugHistTimeVsChADC1 = std::make_unique("TimePerChannelADC1", "Time vs channel (ADC1);Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + mDebugHistTimeVsChPeak1ADC0 = std::make_unique("TimePerChannelPeak1ADC0", "Time vs channel (peak 1, ADC0);Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + mDebugHistTimeVsChPeak1ADC1 = std::make_unique("TimePerChannelPeak1ADC1", "Time vs channel (peak 1, ADC1);Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + mDebugHistTimeVsChPeak2ADC0 = std::make_unique("TimePerChannelPeak2ADC0", "Time vs channel (peak 2, ADC0);Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + mDebugHistTimeVsChPeak2ADC1 = std::make_unique("TimePerChannelPeak2ADC1", "Time vs channel (peak 2, ADC1);Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + + // BC + mDebugHistBC = std::make_unique("BC", "BC;BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCDetector = std::make_unique("BC_detector", "BC detector channels;BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCReference = std::make_unique("BC_reference", "BC reference channels;BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCAmpCut = std::make_unique("BC_ampcut", "BC (amp cut);BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCAmpCutADC0 = std::make_unique("BC_ampcut_ADC0", "BC (amp cut) ADC0;BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCAmpCutADC1 = std::make_unique("BC_ampcut_ADC1", "BC (amp cut) ADC1;BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCDetectorAmpCut = std::make_unique("BC_detector_ampcut", "BC detector channels (amp cut);BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCDetectorAmpCutADC0 = std::make_unique("BC_detector_ampcut_ADC0", "BC detector channels (amp cut) ADC0;BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCDetectorAmpCutADC1 = std::make_unique("BC_detector_ampcut_ADC1", "BC detector channels (amp cut) ADC1;BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCReferenceAmpCut = std::make_unique("BC_reference_ampcut", "BC reference channels (amp cut);BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCReferenceAmpCutADC0 = std::make_unique("BC_reference_ampcut_ADC0", "BC reference channels (amp cut) ADC0;BC;", sMaxBC, 0, sMaxBC); + mDebugHistBCReferenceAmpCutADC1 = std::make_unique("BC_reference_ampcut_ADC1", "BC reference channels (amp cut) ADC1;BC;", sMaxBC, 0, sMaxBC); + + // Reference channel histograms + for (const uint8_t refChId : mReferenceChIDs) { + // Amplitude histograms for reference channel peaks + mMapDebugHistAmp.insert({ refChId, std::make_unique(Form("AmpCh%i", refChId), Form("Amplitude, channel %i;Amp;", refChId), 4200, -100, 4100) }); + mMapDebugHistAmpADC0.insert({ refChId, std::make_unique(Form("AmpCh%iADC0", refChId), Form("Amplitude, channel %i, ADC0;Amp;", refChId), 4200, -100, 4100) }); + mMapDebugHistAmpADC1.insert({ refChId, std::make_unique(Form("AmpCh%iADC1", refChId), Form("Amplitude, channel %i, ADC1;Amp;", refChId), 4200, -100, 4100) }); + mMapDebugHistAmpPeak1.insert({ refChId, std::make_unique(Form("AmpCh%iPeak1", refChId), Form("Amplitude, channel %i, peak 1;Amp;", refChId), 4200, -100, 4100) }); + mMapDebugHistAmpPeak2.insert({ refChId, std::make_unique(Form("AmpCh%iPeak2", refChId), Form("Amplitude, channel %i, peak 2;Amp;", refChId), 4200, -100, 4100) }); + mMapDebugHistAmpPeak1ADC0.insert({ refChId, std::make_unique(Form("AmpCh%iPeak1ADC0", refChId), Form("Amplitude, channel %i, peak 1, ADC0;Amp;", refChId), 4200, -100, 4100) }); + mMapDebugHistAmpPeak1ADC1.insert({ refChId, std::make_unique(Form("AmpCh%iPeak1ADC1", refChId), Form("Amplitude, channel %i, peak 1, ADC1;Amp;", refChId), 4200, -100, 4100) }); + mMapDebugHistAmpPeak2ADC0.insert({ refChId, std::make_unique(Form("AmpCh%iPeak2ADC0", refChId), Form("Amplitude, channel %i, peak 2, ADC0;Amp;", refChId), 4200, -100, 4100) }); + mMapDebugHistAmpPeak2ADC1.insert({ refChId, std::make_unique(Form("AmpCh%iPeak2ADC1", refChId), Form("Amplitude, channel %i, peak 2, ADC1;Amp;", refChId), 4200, -100, 4100) }); + + // Time histograms for reference channel peaks + mMapDebugHistTimePeak1.insert({ refChId, std::make_unique(Form("TimeCh%iPeak1", refChId), Form("Time, channel %i, peak 1;Time;", refChId), 4100, -2050, 2050) }); + mMapDebugHistTimePeak2.insert({ refChId, std::make_unique(Form("TimeCh%iPeak2", refChId), Form("Time, channel %i, peak 2;Time;", refChId), 4100, -2050, 2050) }); + mMapDebugHistTimePeak1ADC0.insert({ refChId, std::make_unique(Form("TimeCh%iPeak1ADC0", refChId), Form("Time, channel %i, peak 1, ADC0;Time;", refChId), 4100, -2050, 2050) }); + mMapDebugHistTimePeak1ADC1.insert({ refChId, std::make_unique(Form("TimeCh%iPeak1ADC1", refChId), Form("Time, channel %i, peak 1, ADC1;Time;", refChId), 4100, -2050, 2050) }); + mMapDebugHistTimePeak2ADC0.insert({ refChId, std::make_unique(Form("TimeCh%iPeak2ADC0", refChId), Form("Time, channel %i, peak 2, ADC0;Time;", refChId), 4100, -2050, 2050) }); + mMapDebugHistTimePeak2ADC1.insert({ refChId, std::make_unique(Form("TimeCh%iPeak2ADC1", refChId), Form("Time, channel %i, peak 2, ADC1;Time;", refChId), 4100, -2050, 2050) }); + + // Amplitude per BC + mMapDebugHistAmpVsBC.insert({ refChId, std::make_unique(Form("AmpPerBC_ch%i", refChId), Form("Amplitude vs BC, channel %i;BC;Amp", refChId), sMaxBC, 0, sMaxBC, 4200, -100, 4200) }); + mMapDebugHistAmpVsBCADC0.insert({ refChId, std::make_unique(Form("AmpPerBC_ch%i_ADC0", refChId), Form("Amplitude vs BC, channel %i, ADC0;BC;Amp", refChId), sMaxBC, 0, sMaxBC, 4200, -100, 4200) }); + mMapDebugHistAmpVsBCADC1.insert({ refChId, std::make_unique(Form("AmpPerBC_ch%i_ADC1", refChId), Form("Amplitude vs BC, channel %i, ADC1;BC;Amp", refChId), sMaxBC, 0, sMaxBC, 4200, -100, 4200) }); + + // // Time per BC + // mMapDebugHistTimeVsBC.insert({ refChId, std::make_unique(Form("TimePerBC_ch%i", refChId), Form("Time vs BC, channel %i;BC;Time", refChId), sMaxBC, 0, sMaxBC, 4100, -2050, 2050) }); + // mMapDebugHistTimeVsBCADC0.insert({ refChId, std::make_unique(Form("TimePerBC_ch%i_ADC0", refChId), Form("Time vs BC, channel %i, ADC0;BC;Time", refChId), sMaxBC, 0, sMaxBC, 4100, -2050, 2050)}); + // mMapDebugHistTimeVsBCADC1.insert({ refChId, std::make_unique(Form("TimePerBC_ch%i_ADC1", refChId), Form("Time vs BC, channel %i, ADC1;BC;Time", refChId), sMaxBC, 0, sMaxBC, 4100, -2050, 2050)}); + } + + if (mDebug) { + // Time per channel + getObjectsManager()->startPublishing(mDebugHistTimeVsChADC0.get()); + getObjectsManager()->startPublishing(mDebugHistTimeVsChADC1.get()); + getObjectsManager()->startPublishing(mDebugHistTimeVsChPeak1ADC0.get()); + getObjectsManager()->startPublishing(mDebugHistTimeVsChPeak1ADC1.get()); + getObjectsManager()->startPublishing(mDebugHistTimeVsChPeak2ADC0.get()); + getObjectsManager()->startPublishing(mDebugHistTimeVsChPeak2ADC1.get()); + getObjectsManager()->setDefaultDrawOptions(mDebugHistTimeVsChADC0.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mDebugHistTimeVsChADC1.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mDebugHistTimeVsChPeak1ADC0.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mDebugHistTimeVsChPeak1ADC1.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mDebugHistTimeVsChPeak2ADC0.get(), "COLZ"); + getObjectsManager()->setDefaultDrawOptions(mDebugHistTimeVsChPeak2ADC1.get(), "COLZ"); + + // BC + getObjectsManager()->startPublishing(mDebugHistBC.get()); + getObjectsManager()->startPublishing(mDebugHistBCDetector.get()); + getObjectsManager()->startPublishing(mDebugHistBCReference.get()); + getObjectsManager()->startPublishing(mDebugHistBCAmpCut.get()); + getObjectsManager()->startPublishing(mDebugHistBCAmpCutADC0.get()); + getObjectsManager()->startPublishing(mDebugHistBCAmpCutADC1.get()); + getObjectsManager()->startPublishing(mDebugHistBCDetectorAmpCut.get()); + getObjectsManager()->startPublishing(mDebugHistBCDetectorAmpCutADC0.get()); + getObjectsManager()->startPublishing(mDebugHistBCDetectorAmpCutADC1.get()); + getObjectsManager()->startPublishing(mDebugHistBCReferenceAmpCut.get()); + getObjectsManager()->startPublishing(mDebugHistBCReferenceAmpCutADC0.get()); + getObjectsManager()->startPublishing(mDebugHistBCReferenceAmpCutADC1.get()); + + // Reference channel histograms + for (const uint8_t refChId : mReferenceChIDs) { + // Amplitude histograms for reference channel peaks + getObjectsManager()->startPublishing(mMapDebugHistAmp.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistAmpADC0.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistAmpADC1.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistAmpPeak1.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistAmpPeak2.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistAmpPeak1ADC0.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistAmpPeak1ADC1.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistAmpPeak2ADC0.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistAmpPeak2ADC1.at(refChId).get()); + + // Time histograms for reference channel peaks + getObjectsManager()->startPublishing(mMapDebugHistTimePeak1.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistTimePeak2.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistTimePeak1ADC0.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistTimePeak1ADC1.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistTimePeak2ADC0.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistTimePeak2ADC1.at(refChId).get()); + + // Amplitude per BC + getObjectsManager()->startPublishing(mMapDebugHistAmpVsBC.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistAmpVsBCADC0.at(refChId).get()); + getObjectsManager()->startPublishing(mMapDebugHistAmpVsBCADC1.at(refChId).get()); + + // // Time per BC + // getObjectsManager()->startPublishing(mMapDebugHistTimeVsBC.at(refChId).get()); + // getObjectsManager()->startPublishing(mMapDebugHistTimeVsBCADC0.at(refChId).get()); + // getObjectsManager()->startPublishing(mMapDebugHistTimeVsBCADC1.at(refChId).get()); + } + } +} + +void AgingLaserTask::startOfActivity(const Activity& activity) +{ + reset(); +} + +void AgingLaserTask::startOfCycle() +{ +} + +void AgingLaserTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto channels = ctx.inputs().get>("channels"); + auto digits = ctx.inputs().get>("digits"); + + // Loop over digits + for (const auto& digit : digits) { + const int bc = digit.getIntRecord().bc; + const auto& digitChannelData = digit.getBunchChannelData(channels); + + // Conditions wether to fill BC histograms for this BC. For debug use only + // 'AmpCut' means there was at least one channel with chAmp > mReferenceAmpCut + // 'ADCX' means there was at least one channel data with ADCX + // 'Detector' means there was at least one detector channel + // 'Reference' means there was at least one reference channel + bool bcHasAmpCut = false; + bool bcHasAmpCutADC0 = false; + bool bcHasAmpCutADC1 = false; + bool bcHasDetectorCh = false; + bool bcHasDetectorChAmpCut = false; + bool bcHasDetectorChAmpCutADC0 = false; + bool bcHasDetectorChAmpCutADC1 = false; + bool bcHasReferenceCh = false; + bool bcHasReferenceChAmpCut = false; + bool bcHasReferenceChAmpCutADC0 = false; + bool bcHasReferenceChAmpCutADC1 = false; + + // Fill all BCs + mDebugHistBC->Fill(bc); + + // Loop over channels + for (const auto& chData : digitChannelData) { + const int chId = chData.ChId; + const int chAmp = chData.QTCAmpl; + const int chTime = chData.CFDTime; + const bool isRef = std::find(mReferenceChIDs.begin(), mReferenceChIDs.end(), chId) != mReferenceChIDs.end(); // TODO: optimize + const bool isDet = !isRef; + const bool isADC0 = !chData.getFlag(o2::ft0::ChannelData::kNumberADC); + const bool isADC1 = !isADC0; + const bool isAmpCutOk = chAmp > mReferenceAmpCut; + const bool isDetAmpCutOk = chAmp > mDetectorAmpCut; // TODO: use this + const bool isRefAmpCutOk = chAmp > mReferenceAmpCut; + + // Use var = condition || var, so that var is never set to back to false if once true + bcHasAmpCut = isAmpCutOk || bcHasAmpCut; + bcHasAmpCutADC0 = (isAmpCutOk && isADC0) || bcHasAmpCutADC0; + bcHasAmpCutADC1 = (isAmpCutOk && isADC1) || bcHasAmpCutADC1; + bcHasDetectorCh = isDet || bcHasDetectorCh; + bcHasDetectorChAmpCut = (isDet && isAmpCutOk) || bcHasDetectorChAmpCut; + bcHasDetectorChAmpCutADC0 = (isDet && isAmpCutOk && isADC0) || bcHasDetectorChAmpCutADC0; + bcHasDetectorChAmpCutADC1 = (isDet && isAmpCutOk && isADC1) || bcHasDetectorChAmpCutADC1; + bcHasReferenceCh = isRef || bcHasReferenceCh; + bcHasReferenceChAmpCut = (isRef && isAmpCutOk) || bcHasReferenceChAmpCut; + bcHasReferenceChAmpCutADC0 = (isRef && isAmpCutOk && isADC0) || bcHasReferenceChAmpCutADC0; + bcHasReferenceChAmpCutADC1 = (isRef && isAmpCutOk && isADC1) || bcHasReferenceChAmpCutADC1; + + // Fill amplitude and time per channel histograms + mHistAmpVsCh->Fill(chId, chAmp); + mHistTimeVsCh->Fill(chId, chTime); + isADC0 ? mHistAmpVsChADC0->Fill(chId, chAmp) : mHistAmpVsChADC1->Fill(chId, chAmp); + isADC0 ? mDebugHistTimeVsChADC0->Fill(chId, chTime) : mDebugHistTimeVsChADC1->Fill(chId, chTime); + + // Fill amplitude per BC for reference channels + if (isRef && isRefAmpCutOk) { + mMapDebugHistAmpVsBC.at(chId)->Fill(bc, chAmp); + isADC0 ? mMapDebugHistAmpVsBCADC0.at(chId)->Fill(bc, chAmp) : mMapDebugHistAmpVsBCADC1.at(chId)->Fill(bc, chAmp); + // mMapDebugHistTimeVsBC.at(chId)->Fill(bc, chTime); + // isADC0 ? mMapDebugHistTimeVsBCADC0.at(chId)->Fill(bc, chTime) : mMapDebugHistTimeVsBCADC1.at(chId)->Fill(bc, chTime); + } + + // Fill reference channel ampltidude histograms + if (isRef) { + // Amplitude + mMapDebugHistAmp.at(chId)->Fill(chAmp); + isADC0 ? mMapDebugHistAmpADC0.at(chId)->Fill(chAmp) : mMapDebugHistAmpADC1.at(chId)->Fill(chAmp); + + // Ampltiude for the different peaks. The peaks are selected based on BC + if (isRefAmpCutOk) { + // Ampltiude and time for peak 1 + if (bcIsPeak1(bc, chId)) { + mHistTimeVsChPeak1->Fill(chId, chTime); + mMapDebugHistAmpPeak1.at(chId)->Fill(chAmp); + mMapDebugHistTimePeak1.at(chId)->Fill(chTime); + + if (isADC0) { + mHistAmpVsChPeak1ADC0->Fill(chId, chAmp); + mMapDebugHistAmpPeak1ADC0.at(chId)->Fill(chAmp); + mDebugHistTimeVsChPeak1ADC0->Fill(chId, chTime); + mMapDebugHistTimePeak1ADC0.at(chId)->Fill(chTime); + } else { + mHistAmpVsChPeak1ADC1->Fill(chId, chAmp); + mMapDebugHistAmpPeak1ADC1.at(chId)->Fill(chAmp); + mDebugHistTimeVsChPeak1ADC1->Fill(chId, chTime); + mMapDebugHistTimePeak1ADC1.at(chId)->Fill(chTime); + } + } // if bcIsPeak1 + + // Amplitude and time peak 2 + if (bcIsPeak2(bc, chId)) { + mHistTimeVsChPeak2->Fill(chId, chTime); + mMapDebugHistAmpPeak2.at(chId)->Fill(chAmp); + mMapDebugHistTimePeak2.at(chId)->Fill(chTime); + + if (isADC0) { + mHistAmpVsChPeak2ADC0->Fill(chId, chAmp); + mMapDebugHistAmpPeak2ADC0.at(chId)->Fill(chAmp); + mDebugHistTimeVsChPeak2ADC0->Fill(chId, chTime); + mMapDebugHistTimePeak2ADC0.at(chId)->Fill(chTime); + } else { + mHistAmpVsChPeak2ADC1->Fill(chId, chAmp); + mMapDebugHistAmpPeak2ADC1.at(chId)->Fill(chAmp); + mDebugHistTimeVsChPeak2ADC1->Fill(chId, chTime); + mMapDebugHistTimePeak2ADC1.at(chId)->Fill(chTime); + } + } // if bcIsPeak2 + } // if isRefAmpCutOK + } // if isRef + } // channel loop + + // Fill BCs + if (bcHasAmpCut) { + mDebugHistBCAmpCut->Fill(bc); + } + if (bcHasAmpCutADC0) { + mDebugHistBCAmpCutADC0->Fill(bc); + } + if (bcHasAmpCutADC1) { + mDebugHistBCAmpCutADC1->Fill(bc); + } + if (bcHasDetectorCh) { + mDebugHistBCDetector->Fill(bc); + } + if (bcHasDetectorChAmpCut) { + mDebugHistBCDetectorAmpCut->Fill(bc); + } + if (bcHasDetectorChAmpCutADC0) { + mDebugHistBCDetectorAmpCutADC0->Fill(bc); + } + if (bcHasDetectorChAmpCutADC1) { + mDebugHistBCDetectorAmpCutADC1->Fill(bc); + } + if (bcHasReferenceCh) { + mDebugHistBCReference->Fill(bc); + } + if (bcHasReferenceChAmpCut) { + mDebugHistBCReferenceAmpCut->Fill(bc); + } + if (bcHasReferenceChAmpCutADC0) { + mDebugHistBCReferenceAmpCutADC0->Fill(bc); + } + if (bcHasReferenceChAmpCutADC1) { + mDebugHistBCReferenceAmpCutADC1->Fill(bc); + } + } // digit loop +} // monitorData + +void AgingLaserTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void AgingLaserTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void AgingLaserTask::reset() +{ + // Reset histograms + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + + // Amplitude per channel + mHistAmpVsChADC0->Reset(); + mHistAmpVsChADC1->Reset(); + mHistAmpVsChPeak1ADC0->Reset(); + mHistAmpVsChPeak1ADC1->Reset(); + mHistAmpVsChPeak2ADC0->Reset(); + mHistAmpVsChPeak2ADC1->Reset(); + + // Time per channel + mHistTimeVsCh->Reset(); + mHistTimeVsChPeak1->Reset(); + mHistTimeVsChPeak2->Reset(); + + // Amplitude per channel + mHistAmpVsCh->Reset(); + + // Amplitude histograms for reference channel peaks + for (auto& entry : mMapDebugHistAmp) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistAmpADC0) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistAmpADC1) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistAmpPeak1) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistAmpPeak2) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistAmpPeak1ADC0) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistAmpPeak1ADC1) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistAmpPeak2ADC0) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistAmpPeak2ADC1) { + entry.second->Reset(); + } + + // Time per channel + mDebugHistTimeVsChADC0->Reset(); + mDebugHistTimeVsChADC1->Reset(); + mDebugHistTimeVsChPeak1ADC0->Reset(); + mDebugHistTimeVsChPeak1ADC1->Reset(); + mDebugHistTimeVsChPeak2ADC0->Reset(); + mDebugHistTimeVsChPeak2ADC1->Reset(); + + // Time histograms for reference channel peaks + for (auto& entry : mMapDebugHistTimePeak1) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistTimePeak2) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistTimePeak1ADC0) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistTimePeak1ADC1) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistTimePeak2ADC0) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistTimePeak2ADC1) { + entry.second->Reset(); + } + + // BC + mDebugHistBC->Reset(); + mDebugHistBCDetector->Reset(); + mDebugHistBCReference->Reset(); + mDebugHistBCAmpCut->Reset(); + mDebugHistBCAmpCutADC0->Reset(); + mDebugHistBCAmpCutADC1->Reset(); + mDebugHistBCDetectorAmpCut->Reset(); + mDebugHistBCDetectorAmpCutADC0->Reset(); + mDebugHistBCDetectorAmpCutADC1->Reset(); + mDebugHistBCReferenceAmpCut->Reset(); + mDebugHistBCReferenceAmpCutADC0->Reset(); + mDebugHistBCReferenceAmpCutADC1->Reset(); + + // Amplitude per BC for reference channels + for (auto& entry : mMapDebugHistAmpVsBC) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistAmpVsBCADC0) { + entry.second->Reset(); + } + for (auto& entry : mMapDebugHistAmpVsBCADC1) { + entry.second->Reset(); + } + + // // Time per BC for reference channels + // for (auto& entry : mMapDebugHistTimeVsBC) { + // entry.second->Reset(); + // } + // for (auto& entry : mMapDebugHistTimeVsBCADC0) { + // entry.second->Reset(); + // } + // for (auto& entry : mMapDebugHistTimeVsBCADC1) { + // entry.second->Reset(); + // } +} + +bool AgingLaserTask::bcIsTrigger(int bc, int bcDelay) const +{ + for (const int bcTrg : mLaserTriggerBCs) { + if (bc == bcTrg + bcDelay) { + return true; + } + } + return false; +} + +bool AgingLaserTask::bcIsDetector(int bc) const +{ + return bcIsTrigger(bc, mDetectorBCdelay); +} + +bool AgingLaserTask::bcIsPeak1(int bc, int refChId) const +{ + return bcIsTrigger(bc, mReferencePeak1BCdelays.at(refChId)); +} + +bool AgingLaserTask::bcIsPeak2(int bc, int refChId) const +{ + return bcIsTrigger(bc, mReferencePeak2BCdelays.at(refChId)); +} + +} // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FT0/src/AmpTimeDistribution.cxx b/Modules/FIT/FT0/src/AmpTimeDistribution.cxx new file mode 100644 index 0000000000..c415c9bdb6 --- /dev/null +++ b/Modules/FIT/FT0/src/AmpTimeDistribution.cxx @@ -0,0 +1,99 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include + +using namespace o2::fit; + +AmpTimeDistribution::AmpTimeDistribution(const std::string& name, const std::string& title, int nBins, double minRange, double maxRange, int binsInStep, int binMax, int axis) +{ + initHists(name, title, nBins, minRange, maxRange, binsInStep, binMax, axis); +} + +AmpTimeDistribution::AmpTimeDistribution(const AmpTimeDistribution& other) +{ + if (other.mHist) { + const std::string newName = std::string(other.mHist->GetName()) + "_Cloned"; + mHist = std::unique_ptr(dynamic_cast(other.mHist->Clone(newName.c_str()))); + } +} + +AmpTimeDistribution::AmpTimeDistribution(AmpTimeDistribution&& other) noexcept : mHist(std::move(other.mHist)) +{ +} + +AmpTimeDistribution& AmpTimeDistribution::operator=(const AmpTimeDistribution& other) +{ + if (this != &other) { + if (other.mHist) { + const std::string newName = std::string(other.mHist->GetName()) + "_Cloned"; + mHist = std::unique_ptr(dynamic_cast(other.mHist->Clone(newName.c_str()))); + } else { + mHist.reset(); + } + } + return *this; +} +AmpTimeDistribution& AmpTimeDistribution::operator=(AmpTimeDistribution&& other) noexcept +{ + if (this != &other) { + mHist = std::move(other.mHist); + } + return *this; +} + +void AmpTimeDistribution::initHists(const std::string& name, const std::string& title, int nBins, double minRange, double maxRange, int binsInStep, int binMax, int axis) +{ + const auto& varBins = makeVaribleBins(binsInStep, binMax); + const int varNbins = varBins.size() - 1; + if (axis == 0) { + mHist = std::make_unique(name.c_str(), title.c_str(), varNbins, varBins.data(), nBins, minRange, maxRange); + } else if (axis == 1) { + mHist = std::make_unique(name.c_str(), title.c_str(), nBins, minRange, maxRange, varNbins, varBins.data()); + } +} + +std::vector AmpTimeDistribution::makeVaribleBins(const std::vector>& vecParams, int binMax) +{ + std::vector vecLowEdgeBins{}; + int startBin{ 0 }; + auto makeBinRange = [&vec = vecLowEdgeBins, binMax](int startBin, int binWidth, int nBins) { + const int endBin = startBin + binWidth * nBins; + for (int iBin = startBin; iBin < endBin; iBin += binWidth) { + vec.emplace_back(static_cast(iBin)); + if (iBin > binMax) { + break; + } + } + return endBin; + }; + for (const auto& entry : vecParams) { + startBin = makeBinRange(startBin, entry.first, entry.second); + } + return vecLowEdgeBins; +} + +std::vector AmpTimeDistribution::makeVaribleBins(int binsInStep, int binMax) +{ + auto generateParams = [](int binsInStep, int binMax) { + std::vector> vecParams{}; + int endBin{ 0 }; + int binWidth = 1; + while (endBin < binMax) { + vecParams.push_back({ binWidth, binsInStep }); + endBin += (binWidth * binsInStep); + binWidth *= 2; + } + return vecParams; + }; + return makeVaribleBins(generateParams(binsInStep, binMax), binMax); +} diff --git a/Modules/FIT/FT0/src/CFDEffCheck.cxx b/Modules/FIT/FT0/src/CFDEffCheck.cxx new file mode 100644 index 0000000000..dbafb7bfda --- /dev/null +++ b/Modules/FIT/FT0/src/CFDEffCheck.cxx @@ -0,0 +1,199 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CFDEffCheck.cxx +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#include "FT0/CFDEffCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::ft0 +{ + +void CFDEffCheck::configure() +{ + if (auto param = mCustomParameters.find("thresholdWarning"); param != mCustomParameters.end()) { + mThreshWarning = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdWarning = " << mThreshWarning << ENDM; + } else { + mThreshWarning = 0.9; + ILOG(Debug, Support) << "configure() : using default thresholdWarning = " << mThreshWarning << ENDM; + } + + if (auto param = mCustomParameters.find("thresholdError"); param != mCustomParameters.end()) { + mThreshError = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdError = " << mThreshError << ENDM; + } else { + mThreshError = 0.8; + ILOG(Debug, Support) << "configure() : using default thresholdError = " << mThreshError << ENDM; + } + + if (auto param = mCustomParameters.find("deadChannelMap"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + std::vector deadChannelVec = parseParameters(chIDs, del); + + mDeadChannelMap = new o2::fit::DeadChannelMap(); + for (uint8_t chId = 0; chId < sNCHANNELS; ++chId) { + if (std::find(deadChannelVec.begin(), deadChannelVec.end(), chId) != deadChannelVec.end()) + mDeadChannelMap->setChannelAlive(chId, 0); + else + mDeadChannelMap->setChannelAlive(chId, 1); + } + ILOG(Warning, Support) << "configure() : using deadChannelMap from config (superseding the one from CCDB)" << ENDM; + } else { + if (auto param = mCustomParameters.find("ccdbUrl"); param != mCustomParameters.end()) { + setCcdbUrl(param->second); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, configured url = " << param->second << ENDM; + } else { + setCcdbUrl("o2-ccdb.internal"); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, default url = " + << "o2-ccdb.internal" << ENDM; + } + if (auto param = mCustomParameters.find("pathDeadChannelMap"); param != mCustomParameters.end()) { + mPathDeadChannelMap = param->second; + ILOG(Debug, Support) << "configure() : using pathDeadChannelMap: " << mPathDeadChannelMap << ENDM; + } else { + mPathDeadChannelMap = "FT0/Calib/DeadChannelMap"; + ILOG(Debug, Support) << "configure() : using default pathDeadChannelMap: " << mPathDeadChannelMap << ENDM; + } + + // WARNING: always uses last available dead channel map + // supply deadChannelMap by hand when running offline + mDeadChannelMap = retrieveConditionAny(mPathDeadChannelMap); + if (!mDeadChannelMap || !mDeadChannelMap->map.size()) { + ILOG(Error, Support) << "object \"" << mPathDeadChannelMap << "\" NOT retrieved (or empty). All channels assumed to be alive!" << ENDM; + mDeadChannelMap = new o2::fit::DeadChannelMap(); + for (uint8_t chId = 0; chId < sNCHANNELS; ++chId) { + mDeadChannelMap->setChannelAlive(chId, 1); + } + } + } + mDeadChannelMapStr = ""; + for (unsigned chId = 0; chId < mDeadChannelMap->map.size(); chId++) { + if (!mDeadChannelMap->isChannelAlive(chId)) { + mDeadChannelMapStr += (mDeadChannelMapStr.empty() ? "" : ",") + std::to_string(chId); + } + } + if (mDeadChannelMapStr.empty()) + mDeadChannelMapStr = "EMPTY"; + ILOG(Info, Support) << "Loaded dead channel map: " << mDeadChannelMapStr << ENDM; +} + +Quality CFDEffCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "CFD_efficiency") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F* => Quality::Bad" << ENDM; + result = Quality::Bad; + continue; + } + + result = Quality::Good; + mNumErrors = 0; + mNumWarnings = 0; + for (uint8_t chId = 0; chId < h->GetNbinsX(); chId++) { + if (chId >= sNCHANNELS) + continue; + if (!mDeadChannelMap->isChannelAlive(chId)) + continue; + if (h->GetBinContent(chId + 1) < mThreshError) { + if (result.isBetterThan(Quality::Bad)) + // result = Quality::Bad; // setting quality like this clears flags + result.set(Quality::Bad); + mNumErrors++; + result.addFlag(FlagTypeFactory::Unknown(), + "CFD eff. < \"Error\" threshold in channel " + std::to_string(chId)); + // no need to check medium threshold + // but don't `break` because we want to add other flags + continue; + } else if (h->GetBinContent(chId + 1) < mThreshWarning) { + if (result.isBetterThan(Quality::Medium)) + result.set(Quality::Medium); + mNumWarnings++; + result.addFlag(FlagTypeFactory::Unknown(), + "CFD eff. < \"Warning\" threshold in channel " + std::to_string(chId)); + } + } + } + } + result.addMetadata("nErrors", std::to_string(mNumErrors)); + result.addMetadata("nWarnings", std::to_string(mNumWarnings)); + return result; +} + +void CFDEffCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "CFD_efficiency") { + auto* h = dynamic_cast(mo->getObject()); + + TPaveText* msg = new TPaveText(0.15, 0.2, 0.85, 0.45, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + if (mDeadChannelMapStr != "EMPTY") + msg->AddText(("Dead channel IDs: " + mDeadChannelMapStr).c_str()); + msg->AddText(Form("N channels with warning (< %.3f) = %d", mThreshWarning, mNumWarnings)); + msg->AddText(Form("N channels with error (< %.3f) = %d", mThreshError, mNumErrors)); + + if (checkResult == Quality::Good) { + msg->AddText(">> Quality::Good <<"); + msg->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + auto flags = checkResult.getFlags(); + msg->SetFillColor(kRed); + msg->AddText(">> Quality::Bad <<"); + } else if (checkResult == Quality::Medium) { + auto flags = checkResult.getFlags(); + msg->SetFillColor(kOrange); + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Null) { + msg->AddText(">> Quality::Null <<"); + msg->SetFillColor(kGray); + } + // add threshold lines + Double_t xMin = h->GetXaxis()->GetXmin(); + Double_t xMax = h->GetXaxis()->GetXmax(); + auto* lineError = new TLine(xMin, mThreshError, xMax, mThreshError); + auto* lineWarning = new TLine(xMin, mThreshWarning, xMax, mThreshWarning); + lineError->SetLineWidth(3); + lineWarning->SetLineWidth(3); + lineError->SetLineStyle(kDashed); + lineWarning->SetLineStyle(kDashed); + lineError->SetLineColor(kRed); + lineWarning->SetLineColor(kOrange); + h->GetListOfFunctions()->Add(lineError); + h->GetListOfFunctions()->Add(lineWarning); + h->SetStats(0); + } +} + +} // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FT0/src/ChannelGeometry.cxx b/Modules/FIT/FT0/src/ChannelGeometry.cxx new file mode 100644 index 0000000000..c6d38a7d84 --- /dev/null +++ b/Modules/FIT/FT0/src/ChannelGeometry.cxx @@ -0,0 +1,114 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ChannelGeometryPlot.cxx +/// \author Artur Furs afurs@cern.ch +/// +#include "QualityControl/QcInfoLogger.h" +#include "FT0/ChannelGeometry.h" +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::ft0 +{ +void ChannelGeometry::parseChannelTable(const std::string& filepath, char delimiter) +{ + clear(); + try { + std::unordered_map colTypes = { { "Long signal cable #", 'T' } }; + auto dataframe = ROOT::RDF::FromCSV(filepath.c_str(), true, delimiter, -1LL, std::move(colTypes)); + dataframe.Foreach([&channelGeometryMap = this->mChannelGeometryMap](const Long64_t& chID, const double& x, const double& y) { + channelGeometryMap.insert({ static_cast(chID), { x, y } }); + }, + { "channel #", "coordinate X in mm", "coordinate Y in mm" }); + + for (const auto& [chID, point] : mChannelGeometryMap) { + // temporary hardcoded + const auto& x = point.first; + const auto& y = point.second; + makeChannel(chID, x, y); + } + } catch (std::exception const& e) { + mIsOk = false; + LOGP(error, "FT0 channel map arsing error: {}", e.what()); + } +} + +void ChannelGeometry::makeChannel(int chID, double x, double y) +{ + // For further development + std::array x_borders = { x - mMargin, x + mMargin, x + mMargin, x - mMargin }; + std::array y_borders = { y + mMargin, y + mMargin, y - mMargin, y - mMargin }; + // side temporary hardcoded for FT0, should be taken from csv + if (chID < 96) { + const auto bin = mHistSideA->AddBin(4, x_borders.data(), y_borders.data()); + mChannelMapA.insert({ chID, static_cast(bin) }); + } else if (chID < 208) { + const auto bin = mHistSideC->AddBin(4, x_borders.data(), y_borders.data()); + mChannelMapC.insert({ chID, static_cast(bin) }); + } +} + +void ChannelGeometry::initHists(double xMin, double xMax, double yMin, double yMax) +{ + mHistSideA = std::make_unique("hDummyGeometryFT0A", "hDummyGeometryFT0A", xMin, xMax, yMin, yMax); + mHistSideC = std::make_unique("hDummyGeometryFT0C", "hDummyGeometryFT0C", xMin, xMax, yMin, yMax); +} + +void ChannelGeometry::init(double xMin, double xMax, double yMin, double yMax, double margin) +{ + mMargin = margin; + initHists(xMin, xMax, yMin, yMax); + const auto& filepath = ChannelGeometry::getFilepath(); + parseChannelTable(filepath); +} + +std::unique_ptr ChannelGeometry::makeHistSideA(const std::string& histName, const std::string& histTitle) +{ + std::unique_ptr histPtr(dynamic_cast(mHistSideA->Clone(histName.c_str()))); + histPtr->SetTitle(histTitle.c_str()); + return std::move(histPtr); +} + +std::unique_ptr ChannelGeometry::makeHistSideC(const std::string& histName, const std::string& histTitle) +{ + std::unique_ptr histPtr(dynamic_cast(mHistSideC->Clone(histName.c_str()))); + histPtr->SetTitle(histTitle.c_str()); + return std::move(histPtr); +} +void ChannelGeometry::setBinContent(Hist_t* histSideA, Hist_t* histSideC, int chID, double val) +{ + const auto itSideA = mChannelMapA.find(chID); + const auto itSideC = mChannelMapC.find(chID); + if (histSideA && itSideA != mChannelMapA.end()) { + histSideA->SetBinContent(itSideA->second, val); + } else if (histSideC && itSideC != mChannelMapC.end()) { + histSideC->SetBinContent(itSideC->second, val); + } +} + +void ChannelGeometry::clear() +{ + mChannelGeometryMap.clear(); + mChannelMapA.clear(); + mChannelMapA.clear(); + mHistSideA->Reset(""); + mHistSideC->Reset(""); + mIsOk = true; +} + +} // namespace o2::quality_control_modules::ft0 \ No newline at end of file diff --git a/Modules/FIT/FT0/src/DigitQcTask.cxx b/Modules/FIT/FT0/src/DigitQcTask.cxx new file mode 100644 index 0000000000..8cb4368874 --- /dev/null +++ b/Modules/FIT/FT0/src/DigitQcTask.cxx @@ -0,0 +1,533 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTask.cxx +/// \author Artur Furs afurs@cern.ch +/// modified by Sebastin Bysiak sbysiak@cern.ch + +#include "FT0/DigitQcTask.h" +#include "TCanvas.h" +#include "TROOT.h" + +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsFIT/Triggers.h" +#include "Framework/InputRecord.h" +#include "Framework/TimingInfo.h" +#include "DataFormatsFT0/LookUpTable.h" +#include "Common/Utils.h" + +#include "FITCommon/HelperHist.h" +#include "FITCommon/HelperCommon.h" + +namespace o2::quality_control_modules::ft0 +{ +using namespace o2::quality_control_modules::fit; +DigitQcTask::~DigitQcTask() +{ + delete mListHistGarbage; +} + +void DigitQcTask::rebinFromConfig() +{ + /* Examples: + "binning_SumAmpC": "100, 0, 100" + "binning_BcOrbitMap_TrgOrA": "25, 0, 256, 10, 0, 3564" + hashtag = all channel IDs (mSetAllowedChIDs), e.g. + "binning_Amp_channel#": "5,-10,90" + is equivalent to: + "binning_Amp_channel0": "5,-10,90" + "binning_Amp_channel1": "5,-10,90" + "binning_Amp_channel2": "5,-10,90" ... + */ + auto rebinHisto = [](std::string hName, std::string binning) { + if (!gROOT->FindObject(hName.data())) { + ILOG(Warning) << "config: histogram named \"" << hName << "\" not found" << ENDM; + return; + } + std::vector tokenizedBinning; + boost::split(tokenizedBinning, binning, boost::is_any_of(",")); + if (tokenizedBinning.size() == 3) { // TH1 + ILOG(Debug) << "config: rebinning TH1 " << hName << " -> " << binning << ENDM; + auto htmp = (TH1F*)gROOT->FindObject(hName.data()); + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str())); + } else if (tokenizedBinning.size() == 6) { // TH2 + auto htmp = (TH2F*)gROOT->FindObject(hName.data()); + ILOG(Debug) << "config: rebinning TH2 " << hName << " -> " << binning << ENDM; + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str()), + std::atof(tokenizedBinning[3].c_str()), std::atof(tokenizedBinning[4].c_str()), std::atof(tokenizedBinning[5].c_str())); + } else { + ILOG(Warning) << "config: invalid binning parameter: " << hName << " -> " << binning << ENDM; + } + }; + + const std::string rebinKeyword = "binning"; + const char* channelIdPlaceholder = "#"; + try { + for (auto& param : mCustomParameters.getAllDefaults()) { + if (param.first.rfind(rebinKeyword, 0) != 0) + continue; + std::string hName = param.first.substr(rebinKeyword.length() + 1); + std::string binning = param.second.c_str(); + if (hName.find(channelIdPlaceholder) != std::string::npos) { + for (const auto& chID : mSetAllowedChIDs) { + std::string hNameCur = hName.substr(0, hName.find(channelIdPlaceholder)) + std::to_string(chID) + hName.substr(hName.find(channelIdPlaceholder) + 1); + rebinHisto(hNameCur, binning); + } + } else { + rebinHisto(hName, binning); + } + } + } catch (std::out_of_range& oor) { + ILOG(Error) << "Cannot access the default custom parameters : " << oor.what() << ENDM; + } +} +bool DigitQcTask::chIsVertexEvent(const o2::ft0::ChannelData chd) +{ + return (chd.getFlag(o2::ft0::ChannelData::kIsCFDinADCgate) && + !(chd.getFlag(o2::ft0::ChannelData::kIsTimeInfoNOTvalid) || chd.getFlag(o2::ft0::ChannelData::kIsTimeInfoLate) || chd.getFlag(o2::ft0::ChannelData::kIsTimeInfoLost)) && + std::abs(static_cast(chd.CFDTime)) < mTrgValidation.mTrgOrGate && + !chd.getFlag(o2::ft0::ChannelData::kIsAmpHigh)); +} + +void DigitQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize DigitQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + mStateLastIR2Ch = {}; + mTrgValidation.configure(mCustomParameters); + mHistTime2Ch = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "TimePerChannel", "Time vs Channel;Channel;Time", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4100, -2050, 2050); + mHistAmp2Ch = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "AmpPerChannel", "Amplitude vs Channel;Channel;Amp", sNCHANNELS_PM, 0, sNCHANNELS_PM, 4200, -100, 4100); + mHistBC = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "BC", "BC;BC;counts;", sBCperOrbit, 0, sBCperOrbit); + mHistChDataBits = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "ChannelDataBits", "ChannelData bits per ChannelID;Channel;Bit", sNCHANNELS_PM, 0, sNCHANNELS_PM, mMapPMbits); + + // Trg plots + mHistOrbitVsTrg = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "OrbitVsTriggers", "Orbit vs Triggers;Orbit;Trg", sOrbitsPerTF, 0, sOrbitsPerTF, mMapTechTrgBitsExtra); + mHistBCvsTrg = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "BCvsTriggers", "BC vs Triggers;BC;Trg", sBCperOrbit, 0, sBCperOrbit, mMapTechTrgBitsExtra); + mHistTriggersCorrelation = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "TriggersCorrelation", "Correlation of triggers from TCM", mMapTechTrgBitsExtra, mMapTechTrgBitsExtra); + + mHistOrbit2BC = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "OrbitPerBC", "BC-Orbit map;Orbit;BC;", sOrbitsPerTF, 0, sOrbitsPerTF, sBCperOrbit, 0, sBCperOrbit); + mHistPmTcmNchA = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "PmTcmNumChannelsA", "Comparison of num. channels A from PM and TCM;Number of channels(TCM), side A;PM - TCM", sNCHANNELS_A + 2, 0, sNCHANNELS_A + 2, 2 * sNCHANNELS_A + 1, -int(sNCHANNELS_A) - 0.5, int(sNCHANNELS_A) + 0.5); + mHistPmTcmSumAmpA = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "PmTcmSumAmpA", "Comparison of sum of amplitudes A from PM and TCM;Sum of amplitudes(TCM), side A;PM - TCM", 2e2, 0, 1e3, 2e3, -1e3 - 0.5, 1e3 - 0.5); + mHistPmTcmAverageTimeA = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "PmTcmAverageTimeA", "Comparison of average time A from PM and TCM;Average time(TCM), side A;PM - TCM", 410, -2050, 2050, 820, -410 - 0.5, 410 - 0.5); + mHistPmTcmNchC = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "PmTcmNumChannelsC", "Comparison of num. channels C from PM and TCM;Number of channels(TCM), side C;PM - TCM", sNCHANNELS_C + 2, 0, sNCHANNELS_C + 2, 2 * sNCHANNELS_C + 1, -int(sNCHANNELS_C) - 0.5, int(sNCHANNELS_C) + 0.5); + mHistPmTcmSumAmpC = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "PmTcmSumAmpC", "Comparison of sum of amplitudes C from PM and TCM;Sum of amplitudes(TCM), side C;PM - TCM", 2e2, 0, 1e3, 2e3, -1e3 - 0.5, 1e3 - 0.5); + mHistPmTcmAverageTimeC = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "PmTcmAverageTimeC", "Comparison of average time C from PM and TCM;Average time(TCM), side C;PM - TCM", 410, -2050, 2050, 820, -410 - 0.5, 410 - 0.5); + + mHistTriggersSoftwareVsTCM = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "TriggersSoftwareVsTCM", "Trigger validation", mMapTrgBits, mTrgValidation.sMapTrgValidation); + + mListHistGarbage = new TList(); + mListHistGarbage->SetOwner(kTRUE); + + std::map mapFEE2hash; + const auto& lut = o2::ft0::SingleLUT::Instance().getVecMetadataFEE(); + auto lutSorted = lut; + std::sort(lutSorted.begin(), lutSorted.end(), [](const auto& first, const auto& second) { return first.mModuleName < second.mModuleName; }); + uint8_t binPos{ 0 }; + for (const auto& lutEntry : lutSorted) { + const auto& moduleName = lutEntry.mModuleName; + const auto& moduleType = lutEntry.mModuleType; + const auto& strChID = lutEntry.mChannelID; + const auto& pairIt = mapFEE2hash.insert({ moduleName, binPos }); + if (pairIt.second) { + if (moduleName.find("PMA") != std::string::npos) + mMapPMhash2isAside.insert({ binPos, true }); + else if (moduleName.find("PMC") != std::string::npos) + mMapPMhash2isAside.insert({ binPos, false }); + binPos++; + } + if (std::regex_match(strChID, std::regex("[\\d]{1,3}"))) { + int chID = std::stoi(strChID); + if (chID < sNCHANNELS_PM) { + mChID2PMhash[chID] = mapFEE2hash[moduleName]; + } else { + LOG(error) << "Incorrect LUT entry: chID " << strChID << " | " << moduleName; + } + } else if (moduleType != "TCM") { + LOG(error) << "Non-TCM module w/o numerical chID: chID " << strChID << " | " << moduleName; + } else if (moduleType == "TCM") { + mTCMhash = mapFEE2hash[moduleName]; + } + } + + std::map mapBin2ModuleName; + std::transform(mapFEE2hash.begin(), mapFEE2hash.end(), std::inserter(mapBin2ModuleName, std::end(mapBin2ModuleName)), [](const auto& entry) { return std::pair{ static_cast(entry.second), entry.first }; }); + + mHistBCvsFEEmodules = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "BCvsFEEmodules", "BC vs FEE module;BC;FEE", sBCperOrbit, 0, sBCperOrbit, mapBin2ModuleName); + mHistOrbitVsFEEmodules = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "OrbitVsFEEmodules", "Orbit vs FEE module;Orbit;FEE", sOrbitsPerTF, 0, sOrbitsPerTF, mapBin2ModuleName); + + mHistTimeSum2Diff = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "timeSumVsDiff", "time A/C side: sum VS diff;(TOC-TOA)/2 [ns];(TOA+TOC)/2 [ns]", 2000, -52.08, 52.08, 2000, -52.08, 52.08); // range of 52.08 ns = 4000*13.02ps = 4000 channels + mHistTimeSum2Diff->GetXaxis()->SetRangeUser(-5, 5); + mHistTimeSum2Diff->GetYaxis()->SetRangeUser(-5, 5); + mHistNchA = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "NumChannelsA", "Number of channels(TCM), side A;Nch", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistNchC = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "NumChannelsC", "Number of channels(TCM), side C;Nch", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistSumAmpA = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "SumAmpA", "Sum of amplitudes(TCM), side A;", 1e4, 0, 1e4); + mHistSumAmpC = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "SumAmpC", "Sum of amplitudes(TCM), side C;", 1e4, 0, 1e4); + mHistAverageTimeA = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "AverageTimeA", "Average time(TCM), side A", 4100, -2050, 2050); + mHistAverageTimeC = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "AverageTimeC", "Average time(TCM), side C", 4100, -2050, 2050); + mHistChannelID = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "StatChannelID", "ChannelID statistics;ChannelID", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistCycleDuration = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "CycleDuration", "Cycle Duration;;time [ns]", 1, 0, 2); + mHistCycleDurationNTF = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "CycleDurationNTF", "Cycle Duration;;time [TimeFrames]", 1, 0, 2); + mHistCycleDurationRange = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "CycleDurationRange", "Cycle Duration (total cycle range);;time [ns]", 1, 0, 2); + + std::vector vecChannelIDs; + if (auto param = mCustomParameters.find("ChannelIDs"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDs = helper::parseParameters(chIDs, del); + } + for (const auto& entry : vecChannelIDs) { + mSetAllowedChIDs.insert(entry); + } + std::vector vecChannelIDsAmpVsTime; + if (auto param = mCustomParameters.find("ChannelIDsAmpVsTime"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDsAmpVsTime = helper::parseParameters(chIDs, del); + } + for (const auto& entry : vecChannelIDsAmpVsTime) { + mSetAllowedChIDsAmpVsTime.insert(entry); + } + + for (const auto& chID : mSetAllowedChIDs) { + auto pairHistAmp = mMapHistAmp1D.insert({ chID, new TH1F(Form("Amp_channel%i", chID), Form("Amplitude, channel %i", chID), 4200, -100, 4100) }); + auto pairHistTime = mMapHistTime1D.insert({ chID, new TH1F(Form("Time_channel%i", chID), Form("Time, channel %i", chID), 4100, -2050, 2050) }); + auto pairHistBits = mMapHistPMbits.insert({ chID, new TH1F(Form("Bits_channel%i", chID), Form("Bits, channel %i", chID), mMapPMbits.size(), 0, mMapPMbits.size()) }); + for (const auto& entry : mMapPMbits) { + pairHistBits.first->second->GetXaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + } + if (pairHistAmp.second) { + getObjectsManager()->startPublishing(pairHistAmp.first->second); + mListHistGarbage->Add(pairHistAmp.first->second); + } + if (pairHistTime.second) { + mListHistGarbage->Add(pairHistTime.first->second); + getObjectsManager()->startPublishing(pairHistTime.first->second); + } + if (pairHistBits.second) { + mListHistGarbage->Add(pairHistBits.first->second); + getObjectsManager()->startPublishing(pairHistBits.first->second); + } + } + for (const auto& chID : mSetAllowedChIDsAmpVsTime) { + auto pairHistAmpVsTime = mMapHistAmpVsTime.insert({ chID, new TH2F(Form("Amp_vs_time_channel%i", chID), Form("Amplitude vs time, channel %i;Amp;Time", chID), 420, -100, 4100, 410, -2050, 2050) }); + if (pairHistAmpVsTime.second) { + mListHistGarbage->Add(pairHistAmpVsTime.first->second); + getObjectsManager()->startPublishing(pairHistAmpVsTime.first->second); + } + } + + rebinFromConfig(); // after all histos are created + + for (int i = 0; i < getObjectsManager()->getNumberPublishedObjects(); i++) { + TH1* obj = dynamic_cast(getObjectsManager()->getMonitorObject(i)->getObject()); + obj->SetTitle((std::string("FT0 ") + obj->GetTitle()).c_str()); + } + // + mGoodPMbits_ChID = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "goodPMbits_ChID", 1 << o2::ft0::ChannelData::kIsCFDinADCgate); + mBadPMbits_ChID = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "badPMbits_ChID", (1 << o2::ft0::ChannelData::kIsTimeInfoNOTvalid) | (1 << o2::ft0::ChannelData::kIsTimeInfoLate) | (1 << o2::ft0::ChannelData::kIsAmpHigh) | (1 << o2::ft0::ChannelData::kIsTimeInfoLost)); + mPMbitsToCheck_ChID = mBadPMbits_ChID | mGoodPMbits_ChID; + mLowTimeGate_ChID = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "lowTimeGate_ChID", -192); + mUpTimeGate_ChID = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "upTimeGate_ChID", 192); + mHistChIDperBC = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "ChannelIDperBC", Form("FT0 ChannelID per BC, bad PM bit suppression %i, good PM checking %i, gate (%i,%i)", mBadPMbits_ChID, mGoodPMbits_ChID, mLowTimeGate_ChID, mUpTimeGate_ChID), sBCperOrbit, 0, sBCperOrbit, sNCHANNELS_PM, 0, sNCHANNELS_PM); + + // Timestamp + mMetaAnchorOutput = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "metaAnchorOutput", "CycleDurationNTF"); + mTimestampMetaField = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "timestampMetaField", "timestampTF"); +} + +void DigitQcTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity" << activity.mId << ENDM; + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistBC->Reset(); + mHistChDataBits->Reset(); + mHistTimeSum2Diff->Reset(); + mHistBCvsFEEmodules->Reset(); + mHistOrbitVsTrg->Reset(); + mHistOrbitVsFEEmodules->Reset(); + mHistTriggersCorrelation->Reset(); + mHistCycleDuration->Reset(); + mHistCycleDurationNTF->Reset(); + mHistCycleDurationRange->Reset(); + mHistBCvsTrg->Reset(); + mHistOrbit2BC->Reset(); + mHistNchA->Reset(); + mHistNchC->Reset(); + mHistSumAmpA->Reset(); + mHistSumAmpC->Reset(); + mHistAverageTimeA->Reset(); + mHistAverageTimeC->Reset(); + mHistChannelID->Reset(); + mHistPmTcmNchA->Reset(); + mHistPmTcmSumAmpA->Reset(); + mHistPmTcmAverageTimeA->Reset(); + mHistPmTcmNchC->Reset(); + mHistPmTcmSumAmpC->Reset(); + mHistPmTcmAverageTimeC->Reset(); + mHistTriggersSoftwareVsTCM->Reset(); + mHistChIDperBC->Reset(); + for (auto& entry : mMapHistAmp1D) { + entry.second->Reset(); + } + for (auto& entry : mMapHistTime1D) { + entry.second->Reset(); + } + for (auto& entry : mMapHistPMbits) { + entry.second->Reset(); + } + for (auto& entry : mMapHistAmpVsTime) { + entry.second->Reset(); + } +} + +void DigitQcTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + mTimeMinNS = -1; + mTimeMaxNS = 0.; + mTimeCurNS = 0.; + mTfCounter = 0; + mTimeSum = 0.; +} + +void DigitQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + mTFcreationTime = ctx.services().get().creation; + mTfCounter++; + auto channels = ctx.inputs().get>("channels"); + auto digits = ctx.inputs().get>("digits"); + if (digits.size() > 0) { + // Considering that Digit container is already sorted by IR + const o2::InteractionRecord& firstIR = digits[0].getIntRecord(); + const o2::InteractionRecord& lastIR = digits[digits.size() - 1].getIntRecord(); + auto timeMinNS = firstIR.bc2ns(); + auto timeMaxNS = lastIR.bc2ns(); + mTimeMinNS = std::min(mTimeMinNS, timeMinNS); // to be sure if somehow TFID is unordered + mTimeMaxNS = std::max(mTimeMaxNS, timeMaxNS); + mTimeSum += timeMaxNS - timeMinNS; + } + for (auto& digit : digits) { + // Exclude all BCs, in which laser signals are expected (and trigger outputs are blocked) + const auto& vecChData = digit.getBunchChannelData(channels); + bool isTCM = true; + if (digit.mTriggers.getTimeA() == o2::fit::Triggers::DEFAULT_TIME && digit.mTriggers.getTimeC() == o2::fit::Triggers::DEFAULT_TIME) { + isTCM = false; + } + mHistOrbit2BC->Fill(digit.getIntRecord().orbit % sOrbitsPerTF, digit.getIntRecord().bc); + mHistBC->Fill(digit.getBC()); + + std::set setFEEmodules{}; + + int32_t pmSumAmplA = 0; + int32_t pmSumAmplC = 0; + uint8_t pmNChanA{ 0 }; + uint8_t pmNChanC{ 0 }; + int pmSumTimeA{ 0 }; + int pmSumTimeC{ 0 }; + int pmAverTimeA{ 0 }; + int pmAverTimeC{ 0 }; + + std::map mapPMhash2sumAmpl; + for (const auto& entry : mMapPMhash2isAside) { + mapPMhash2sumAmpl.insert({ entry.first, 0 }); + } + for (const auto& chData : vecChData) { + mHistTime2Ch->Fill(static_cast(chData.ChId), static_cast(chData.CFDTime)); + mHistAmp2Ch->Fill(static_cast(chData.ChId), static_cast(chData.QTCAmpl)); + mStateLastIR2Ch[chData.ChId] = digit.mIntRecord; + mHistChannelID->Fill(chData.ChId); + if (mSetAllowedChIDs.size() != 0 && mSetAllowedChIDs.find(static_cast(chData.ChId)) != mSetAllowedChIDs.end()) { + mMapHistAmp1D[chData.ChId]->Fill(chData.QTCAmpl); + mMapHistTime1D[chData.ChId]->Fill(chData.CFDTime); + for (const auto& entry : mMapPMbits) { + if ((chData.ChainQTC & (1 << entry.first))) { + mMapHistPMbits[chData.ChId]->Fill(entry.first); + } + } + } + if (mSetAllowedChIDsAmpVsTime.size() != 0 && mSetAllowedChIDsAmpVsTime.find(static_cast(chData.ChId)) != mSetAllowedChIDsAmpVsTime.end()) { + mMapHistAmpVsTime[chData.ChId]->Fill(chData.QTCAmpl, chData.CFDTime); + } + for (const auto& binPos : mHashedBitBinPos[chData.ChainQTC]) { + mHistChDataBits->Fill(chData.ChId, binPos); + } + + setFEEmodules.insert(mChID2PMhash[chData.ChId]); + + if (((chData.ChainQTC & mPMbitsToCheck_ChID) == mGoodPMbits_ChID) && std::abs(static_cast(chData.CFDTime)) < mTrgValidation.mTrgOrGate) { + if (!mMapPMhash2isAside[mChID2PMhash[static_cast(chData.ChId)]]) { + pmSumTimeC += chData.CFDTime; + pmNChanC++; + } else if (mMapPMhash2isAside[mChID2PMhash[static_cast(chData.ChId)]]) { + pmSumTimeA += chData.CFDTime; + pmNChanA++; + } + mHistChIDperBC->Fill(digit.getBC(), chData.ChId); + } + if (chData.getFlag(o2::ft0::ChannelData::kIsCFDinADCgate)) { + mapPMhash2sumAmpl[mChID2PMhash[static_cast(chData.ChId)]] += static_cast(chData.QTCAmpl); + } + } + + for (const auto& entry : mapPMhash2sumAmpl) { + if (mMapPMhash2isAside[entry.first]) + pmSumAmplA += (entry.second >> 3); + else + pmSumAmplC += (entry.second >> 3); + } + + if (isTCM) { + if (pmNChanA > 1) { + pmAverTimeA = std::floor((float)pmSumTimeA / pmNChanA); + } else if (pmNChanA == 1) { + pmAverTimeA = pmSumTimeA; + } else { + pmAverTimeA = 0; + } + if (pmNChanC > 1) { + pmAverTimeC = std::floor((float)pmSumTimeC / pmNChanC); + } else if (pmNChanC == 1) { + pmAverTimeC = pmSumTimeC; + } else { + pmAverTimeC = 0; + } + setFEEmodules.insert(mTCMhash); + + } else { + pmAverTimeA = o2::fit::Triggers::DEFAULT_TIME; + pmAverTimeC = o2::fit::Triggers::DEFAULT_TIME; + } + auto vtxPos = (pmNChanA && pmNChanC) ? (pmAverTimeC - pmAverTimeA) / 2 : 0; + + for (const auto& feeHash : setFEEmodules) { + mHistBCvsFEEmodules->Fill(static_cast(digit.getIntRecord().bc), static_cast(feeHash)); + mHistOrbitVsFEEmodules->Fill(static_cast(digit.getIntRecord().orbit % sOrbitsPerTF), static_cast(feeHash)); + } + + if (isTCM && digit.mTriggers.getDataIsValid() && !digit.mTriggers.getOutputsAreBlocked()) { + if (digit.mTriggers.getNChanA() > 0) { + mHistNchA->Fill(digit.mTriggers.getNChanA()); + mHistSumAmpA->Fill(digit.mTriggers.getAmplA()); + mHistAverageTimeA->Fill(digit.mTriggers.getTimeA()); + } + if (digit.mTriggers.getNChanC() > 0) { + mHistNchC->Fill(digit.mTriggers.getNChanC()); + mHistSumAmpC->Fill(digit.mTriggers.getAmplC()); + mHistAverageTimeC->Fill(digit.mTriggers.getTimeC()); + } + mHistPmTcmNchA->Fill(digit.mTriggers.getNChanA(), pmNChanA - digit.mTriggers.getNChanA()); + mHistPmTcmSumAmpA->Fill(digit.mTriggers.getAmplA(), pmSumAmplA - digit.mTriggers.getAmplA()); + mHistPmTcmAverageTimeA->Fill(digit.mTriggers.getTimeA(), pmAverTimeA - digit.mTriggers.getTimeA()); + mHistPmTcmNchC->Fill(digit.mTriggers.getNChanC(), pmNChanC - digit.mTriggers.getNChanC()); + mHistPmTcmSumAmpC->Fill(digit.mTriggers.getAmplC(), pmSumAmplC - digit.mTriggers.getAmplC()); + mHistPmTcmAverageTimeC->Fill(digit.mTriggers.getTimeC(), pmAverTimeC - digit.mTriggers.getTimeC()); + + mHistTimeSum2Diff->Fill((digit.mTriggers.getTimeC() - digit.mTriggers.getTimeA()) * sCFDChannel2NS / 2, (digit.mTriggers.getTimeC() + digit.mTriggers.getTimeA()) * sCFDChannel2NS / 2); + } + if (isTCM) { + std::vector vecTrgWords{}; + const uint64_t trgWordExt = digit.mTriggers.getExtendedTrgWordFT0(); + for (const auto& entry : mMapTechTrgBitsExtra) { + const auto& trgBit = entry.first; + if (((1 << trgBit) & trgWordExt) > 0) { + mHistTriggersCorrelation->Fill(trgBit, trgBit); + for (const auto& prevTrgBit : vecTrgWords) { + mHistTriggersCorrelation->Fill(trgBit, prevTrgBit); + } + mHistBCvsTrg->Fill(digit.getIntRecord().bc, trgBit); + mHistOrbitVsTrg->Fill(digit.getIntRecord().orbit % sOrbitsPerTF, trgBit); + vecTrgWords.push_back(trgBit); + } + } + } + // trigger emulation + DataTCM_t tcmEmu(pmSumAmplA, pmSumAmplC, pmSumTimeA, pmSumTimeC, pmNChanA, pmNChanC); + mTrgValidation.emulateTriggers(tcmEmu); // emulate trigger + const auto& trg = digit.mTriggers.getTriggersignals(); + for (int iTrgBit = 0; iTrgBit < mMapTrgBits.size(); iTrgBit++) { + const auto status = mTrgValidation.getTrgValidationStatus(trg, tcmEmu.triggersignals, iTrgBit); + mHistTriggersSoftwareVsTCM->Fill(iTrgBit, status); + } + // end of triggers re-computation + } +} + +void DigitQcTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + // add TF creation time for further match with filling scheme in PP in case of offline running + ILOG(Debug, Support) << "adding last TF creation time: " << mTFcreationTime << ENDM; + getObjectsManager()->getMonitorObject(mMetaAnchorOutput)->addOrUpdateMetadata(mTimestampMetaField, std::to_string(mTFcreationTime)); + + // one has to set num. of entries manually because + // default TH1Reductor gets only mean,stddev and entries (no integral) + mHistCycleDurationRange->SetBinContent(1., mTimeMaxNS - mTimeMinNS); + mHistCycleDurationRange->SetEntries(mTimeMaxNS - mTimeMinNS); + mHistCycleDurationNTF->SetBinContent(1., mTfCounter); + mHistCycleDurationNTF->SetEntries(mTfCounter); + mHistCycleDuration->SetBinContent(1., mTimeSum); + mHistCycleDuration->SetEntries(mTimeSum); + ILOG(Debug) << "Cycle duration: NTF=" << mTfCounter << ", range = " << (mTimeMaxNS - mTimeMinNS) / 1e6 / mTfCounter << " ms/TF, sum = " << mTimeSum / 1e6 / mTfCounter << " ms/TF" << ENDM; +} + +void DigitQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void DigitQcTask::reset() +{ + // clean all the monitor objects here + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistBC->Reset(); + mHistChDataBits->Reset(); + mHistTimeSum2Diff->Reset(); + mHistOrbit2BC->Reset(); + mHistNchA->Reset(); + mHistNchC->Reset(); + mHistSumAmpA->Reset(); + mHistSumAmpC->Reset(); + mHistAverageTimeA->Reset(); + mHistAverageTimeC->Reset(); + mHistChannelID->Reset(); + mHistTriggersCorrelation->Reset(); + mHistCycleDuration->Reset(); + mHistCycleDurationNTF->Reset(); + mHistCycleDurationRange->Reset(); + mHistBCvsTrg->Reset(); + mHistBCvsFEEmodules->Reset(); + mHistOrbitVsTrg->Reset(); + mHistOrbitVsFEEmodules->Reset(); + mHistPmTcmNchA->Reset(); + mHistPmTcmSumAmpA->Reset(); + mHistPmTcmAverageTimeA->Reset(); + mHistPmTcmNchC->Reset(); + mHistPmTcmSumAmpC->Reset(); + mHistPmTcmAverageTimeC->Reset(); + mHistTriggersSoftwareVsTCM->Reset(); + mHistChIDperBC->Reset(); + for (auto& entry : mMapHistAmp1D) { + entry.second->Reset(); + } + for (auto& entry : mMapHistTime1D) { + entry.second->Reset(); + } + for (auto& entry : mMapHistPMbits) { + entry.second->Reset(); + } + for (auto& entry : mMapHistAmpVsTime) { + entry.second->Reset(); + } +} + +} // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FT0/src/FractionCheck.cxx b/Modules/FIT/FT0/src/FractionCheck.cxx new file mode 100644 index 0000000000..61c47088c0 --- /dev/null +++ b/Modules/FIT/FT0/src/FractionCheck.cxx @@ -0,0 +1,170 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file FractionCheck.cxx +/// \author Artur Furs afurs@cern.ch +/// + +#include "FT0/FractionCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include + +#include +#include +#include "Common/Utils.h" +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::ft0 +{ + +void FractionCheck::configure() +{ + mThreshWarning = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "thresholdWarning", 0.9); + mThreshError = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "thresholdError", 0.8); + mNameObjectToCheck = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nameObjectToCheck", "CFD_efficiency"); + mIsInversedThresholds = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "isInversedThresholds", false); + const std::string binsToIgnore = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "binsToIgnore", ""); + if (binsToIgnore.size() > 0) { + const auto vecParams = parseParameters(binsToIgnore, ","); + for (const auto& chId : vecParams) { + mIgnoreBins.insert(chId); + } + } + mUseDeadChannelMap = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "useDeadChannelMap", false); + if (mUseDeadChannelMap) { + const std::string ccdbUrl = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "ccdbUrl", "o2-ccdb.internal"); + setCcdbUrl(ccdbUrl); + const std::string mPathDeadChannelMap = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "pathDeadChannelMap", "FT0/Calib/DeadChannelMap"); + mDeadChannelMap = retrieveConditionAny(mPathDeadChannelMap); + for (unsigned chId = 0; chId < mDeadChannelMap->map.size(); chId++) { + if (!mDeadChannelMap->isChannelAlive(chId)) { + mIgnoreBins.insert(chId); + } + } + } + for (const auto chId : mIgnoreBins) { + mDeadChannelMapStr += (mDeadChannelMapStr.empty() ? "" : ",") + std::to_string(chId); + } + if (mIgnoreBins.size() == 0) { + mDeadChannelMapStr = "EMPTY"; + } +} + +Quality FractionCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == mNameObjectToCheck) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F* => Quality::Bad" << ENDM; + result = Quality::Bad; + continue; + } + + result = Quality::Good; + mNumErrors = 0; + mNumWarnings = 0; + for (int chId = 0; chId < h->GetNbinsX(); chId++) { + if (chId >= sNCHANNELS) { + continue; + } + if (mIgnoreBins.find(chId) != mIgnoreBins.end()) { + continue; + } + const bool isError = (h->GetBinContent(chId + 1) < mThreshError && !mIsInversedThresholds) || (h->GetBinContent(chId + 1) > mThreshError && mIsInversedThresholds); + const bool isWarning = ((h->GetBinContent(chId + 1) < mThreshWarning && !mIsInversedThresholds) || (h->GetBinContent(chId + 1) > mThreshWarning && mIsInversedThresholds)); + if (isError) { + if (result.isBetterThan(Quality::Bad)) + // result = Quality::Bad; // setting quality like this clears flags + result.set(Quality::Bad); + mNumErrors++; + result.addFlag(FlagTypeFactory::Unknown(), + "CFD eff. < \"Error\" threshold in channel " + std::to_string(chId)); + // no need to check medium threshold + // but don't `break` because we want to add other flags + continue; + } else if (isWarning) { + if (result.isBetterThan(Quality::Medium)) + result.set(Quality::Medium); + mNumWarnings++; + result.addFlag(FlagTypeFactory::Unknown(), + "CFD eff. < \"Warning\" threshold in channel " + std::to_string(chId)); + } + } + } + } + result.addMetadata("nErrors", std::to_string(mNumErrors)); + result.addMetadata("nWarnings", std::to_string(mNumWarnings)); + return result; +} + +void FractionCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == mNameObjectToCheck) { + auto* h = dynamic_cast(mo->getObject()); + + TPaveText* msg = new TPaveText(0.15, 0.2, 0.85, 0.45, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + if (mDeadChannelMapStr != "EMPTY") + msg->AddText(("Ignore bins(ChannelIDs): " + mDeadChannelMapStr).c_str()); + if (!mIsInversedThresholds) { + msg->AddText(Form("N channels with warning (< %.3f) = %d", mThreshWarning, mNumWarnings)); + msg->AddText(Form("N channels with error (< %.3f) = %d", mThreshError, mNumErrors)); + } else { + msg->AddText(Form("N channels with warning (> %.3f) = %d", mThreshWarning, mNumWarnings)); + msg->AddText(Form("N channels with error (> %.3f) = %d", mThreshError, mNumErrors)); + } + if (checkResult == Quality::Good) { + msg->AddText(">> Quality::Good <<"); + msg->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + auto flags = checkResult.getFlags(); + msg->SetFillColor(kRed); + msg->AddText(">> Quality::Bad <<"); + } else if (checkResult == Quality::Medium) { + auto flags = checkResult.getFlags(); + msg->SetFillColor(kOrange); + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Null) { + msg->AddText(">> Quality::Null <<"); + msg->SetFillColor(kGray); + } + // add threshold lines + Double_t xMin = h->GetXaxis()->GetXmin(); + Double_t xMax = h->GetXaxis()->GetXmax(); + auto* lineError = new TLine(xMin, mThreshError, xMax, mThreshError); + auto* lineWarning = new TLine(xMin, mThreshWarning, xMax, mThreshWarning); + lineError->SetLineWidth(3); + lineWarning->SetLineWidth(3); + lineError->SetLineStyle(kDashed); + lineWarning->SetLineStyle(kDashed); + lineError->SetLineColor(kRed); + lineWarning->SetLineColor(kOrange); + h->GetListOfFunctions()->Add(lineError); + h->GetListOfFunctions()->Add(lineWarning); + h->SetStats(0); + } +} + +} // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FT0/src/GenericCheck.cxx b/Modules/FIT/FT0/src/GenericCheck.cxx new file mode 100644 index 0000000000..15b60482c0 --- /dev/null +++ b/Modules/FIT/FT0/src/GenericCheck.cxx @@ -0,0 +1,250 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GenericCheck.cxx +/// \author Sebastian Bysiak +/// + +#include "FT0/GenericCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +// ROOT +#include +#include +#include +#include +#include +#include +// #include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::ft0 +{ + +SingleCheck GenericCheck::getCheckFromConfig(std::string paramName) +{ + bool isActive = false; + float thresholdWarning = TMath::SignalingNaN(), thresholdError = TMath::SignalingNaN(); + bool shouldBeLower = true; + if (paramName.find("Max") != string::npos || paramName.find("max") != string::npos) { + shouldBeLower = true; + } else if (paramName.find("Min") != string::npos || paramName.find("min") != string::npos) { + shouldBeLower = false; + } + + if (auto paramWarning = mCustomParameters.find(string("thresholdWarning") + paramName), paramError = mCustomParameters.find(string("thresholdError") + paramName); paramWarning != mCustomParameters.end() || paramError != mCustomParameters.end()) { + if (paramWarning != mCustomParameters.end() && paramError != mCustomParameters.end()) { + thresholdWarning = stof(paramWarning->second); + thresholdError = stof(paramError->second); + isActive = true; + if ((shouldBeLower && thresholdWarning > thresholdError) || (!shouldBeLower && thresholdWarning < thresholdError)) { + ILOG(Warning, Support) << "configure(): warning more strict than error -> swapping values!" << ENDM; + std::swap(thresholdWarning, thresholdError); + } + ILOG(Debug, Support) << "configure() : using thresholdWarning" << paramName.c_str() << " = " << thresholdWarning << " , thresholdError" << paramName << " = " << thresholdError << ENDM; + } else { + ILOG(Warning, Support) << "configure() : only one threshold (warning/error) was provided for " << paramName.c_str() << " -> this parameter will not be used!" << ENDM; + } + } + return SingleCheck(paramName, thresholdWarning, thresholdError, shouldBeLower, isActive); +} + +void GenericCheck::configure() +{ + + mCheckMaxOverflowIntegralRatio = getCheckFromConfig("MaxOverflowIntegralRatio"); + + mCheckMinMeanX = getCheckFromConfig("MinMeanX"); + mCheckMaxMeanX = getCheckFromConfig("MaxMeanX"); + mCheckMaxStddevX = getCheckFromConfig("MaxStddevX"); + + mCheckMinMeanY = getCheckFromConfig("MinMeanY"); + mCheckMaxMeanY = getCheckFromConfig("MaxMeanY"); + mCheckMaxStddevY = getCheckFromConfig("MaxStddevY"); + + mCheckMinGraphLastPoint = getCheckFromConfig("MinGraphLastPoint"); + mCheckMaxGraphLastPoint = getCheckFromConfig("MaxGraphLastPoint"); + + mPositionMsgBox = { 0.15, 0.75, 0.85, 0.9 }; + if (auto param = mCustomParameters.find("positionMsgBox"); param != mCustomParameters.end()) { + stringstream ss(param->second); + int i = 0; + while (ss.good()) { + if (i > 4) { + ILOG(Info, Support) << "Skipping next values provided for positionMsgBox" << ENDM; + break; + } + string substr; + getline(ss, substr, ','); + mPositionMsgBox[i] = std::stod(substr); + i++; + } + float minHeight = 0.09, minWidth = 0.19; + if (mPositionMsgBox[2] - mPositionMsgBox[0] < minWidth || mPositionMsgBox[3] - mPositionMsgBox[1] < minHeight) { + mPositionMsgBox = { 0.15, 0.75, 0.85, 0.9 }; + ILOG(Info, Support) << "MsgBox too small: returning to default" << ENDM; + } + } + + if (auto param = mCustomParameters.find("nameObjOnCanvas"); param != mCustomParameters.end()) { + mNameObjOnCanvas = param->second; + ILOG(Info, Support) << "nameObjOnCanvas set to " << mNameObjOnCanvas << ENDM; + } else { + mNameObjOnCanvas = "unspecified"; + } +} + +Quality GenericCheck::check(std::map>* moMap) +{ + Quality result = Quality::Good; + for (auto& [moName, mo] : *moMap) { + if (!mo) { + result.set(Quality::Null); + ILOG(Error, Support) << "MO " << moName << " not found" << ENDM; + continue; + } + if (std::strcmp(mo->getObject()->ClassName(), "TCanvas") == 0) { + auto g = (TGraph*)((TCanvas*)mo->getObject())->GetListOfPrimitives()->FindObject(mNameObjOnCanvas.c_str()); + + if (!g) { + result.set(Quality::Null); + ILOG(Error, Support) << "Object " << mNameObjOnCanvas << " inside MO " << moName << " not found" << ENDM; + continue; + } + + if (mCheckMinGraphLastPoint.isActive()) + mCheckMinGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + if (mCheckMaxGraphLastPoint.isActive()) + mCheckMaxGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + } + if (std::strcmp(mo->getObject()->ClassName(), "TGraph") == 0) { + auto g = dynamic_cast(mo->getObject()); + + if (!g) { + result.set(Quality::Null); + ILOG(Error, Support) << "Object inside MO " << moName << " not found" << ENDM; + continue; + } + + if (mCheckMinGraphLastPoint.isActive()) + mCheckMinGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + if (mCheckMaxGraphLastPoint.isActive()) + mCheckMaxGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + + } else { + auto h = dynamic_cast(mo->getObject()); + if (!h) { + result.set(Quality::Null); + ILOG(Error, Support) << "Object inside MO " << moName << " not found" << ENDM; + continue; + } + + if (mCheckMinMeanX.isActive()) + mCheckMinMeanX.doCheck(result, h->GetMean()); + if (mCheckMaxMeanX.isActive()) + mCheckMaxMeanX.doCheck(result, h->GetMean()); + if (mCheckMaxStddevX.isActive()) + mCheckMaxStddevX.doCheck(result, h->GetStdDev()); + + if (mCheckMinMeanY.isActive()) + mCheckMinMeanY.doCheck(result, h->GetMean(2)); + if (mCheckMaxMeanY.isActive()) + mCheckMaxMeanY.doCheck(result, h->GetMean(2)); + if (mCheckMaxStddevY.isActive()) + mCheckMaxStddevY.doCheck(result, h->GetStdDev(2)); + + if (mCheckMaxOverflowIntegralRatio.isActive()) { + float integralWithoutOverflow = 0., overflow = 0.; + if (h->GetDimension() == 1) { + integralWithoutOverflow = h->Integral(); + overflow = h->GetBinContent(h->GetNbinsX() + 1); + } else if (h->GetDimension() == 2) { + // for 2D include these overflows: (over,over), (over,in-range), (in-range, over) + TH2* h2 = (TH2*)h->Clone(); // TH2 needed for Integral() with both axis specified + integralWithoutOverflow = h2->Integral(); + float integralWithOverflow = h2->Integral(1, h2->GetNbinsX() + 1, 1, h2->GetNbinsY() + 1); + overflow = integralWithOverflow - integralWithoutOverflow; + } + mCheckMaxOverflowIntegralRatio.doCheck(result, overflow / integralWithoutOverflow); + } + } + } + return result; +} + +void GenericCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (!mo) { + ILOG(Error, Support) << "beautify(): MO not found" << ENDM; + return; + } + + TPaveText* msg = new TPaveText(mPositionMsgBox[0], mPositionMsgBox[1], mPositionMsgBox[2], mPositionMsgBox[3], "NDC"); + if (std::strcmp(mo->getObject()->ClassName(), "TCanvas") == 0) { + auto g = (TGraph*)((TCanvas*)mo->getObject())->GetListOfPrimitives()->FindObject(mNameObjOnCanvas.c_str()); + if (!g) { + ILOG(Error, Support) << "beautify(): Object " << mNameObjOnCanvas << " inside MO " << mo->GetName() << " not found" << ENDM; + return; + } + g->GetListOfFunctions()->Add(msg); + } else if (std::strcmp(mo->getObject()->ClassName(), "TGraph") == 0) { + auto g = dynamic_cast(mo->getObject()); + if (!g) { + ILOG(Error, Support) << "beautify(): Object inside MO " << mo->GetName() << " not found" << ENDM; + return; + } + g->GetListOfFunctions()->Add(msg); + } else { + auto h = dynamic_cast(mo->getObject()); + if (!h) { + ILOG(Error, Support) << "beautify(): Object inside MO " << mo->GetName() << " not found" << ENDM; + return; + } + h->GetListOfFunctions()->Add(msg); + } + + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + auto flags = checkResult.getFlags(); + for (int i = 0; i < int(flags.size()); i++) { + msg->AddText(flags[i].second.c_str()); + if (i > 4) { + msg->AddText("et al ... "); + break; + } + } + int color = kBlack; + if (checkResult == Quality::Good) { + color = kGreen + 1; + msg->AddText(">> Quality::Good <<"); + } else if (checkResult == Quality::Medium) { + color = kOrange - 1; + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Bad) { + color = kRed; + msg->AddText(">> Quality::Bad <<"); + } + msg->SetFillStyle(1); + msg->SetLineWidth(3); + msg->SetLineColor(color); + msg->SetShadowColor(color); + msg->SetTextColor(color); + msg->SetMargin(0.0); +} + +} // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FT0/src/MergedTreeCheck.cxx b/Modules/FIT/FT0/src/MergedTreeCheck.cxx new file mode 100644 index 0000000000..55834f7e10 --- /dev/null +++ b/Modules/FIT/FT0/src/MergedTreeCheck.cxx @@ -0,0 +1,63 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MergedTreeCheck.cxx +/// \author Milosz Filus +/// + +// Fair +#include +// ROOT +#include "TH1.h" +#include "TTree.h" +// Quality Control +#include "FT0/MergedTreeCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsFT0/Digit.h" +#include "DataFormatsFT0/ChannelData.h" +#include "FT0/Utilities.h" + +using namespace std; + +namespace o2::quality_control_modules::ft0 +{ + +Quality MergedTreeCheck::check(std::map>* moMap) +{ + for (auto [name, obj] : *moMap) { + + (void)name; + if (obj->getName() == "ChargeHistogram") { + TH1* histogram = dynamic_cast(obj->getObject()); + if (histogram == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << obj->getName() << " to TH1*, skipping" << ENDM; + continue; + } + auto entries = histogram->GetEntries(); + if (entries < 1000) { + return Quality::Bad; + } else { + return Quality::Good; + } + } + } + + return Quality::Bad; +} + +void MergedTreeCheck::beautify(std::shared_ptr, Quality) +{ +} + +} // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FT0/src/OutOfBunchCollCheck.cxx b/Modules/FIT/FT0/src/OutOfBunchCollCheck.cxx new file mode 100644 index 0000000000..6aeacb8958 --- /dev/null +++ b/Modules/FIT/FT0/src/OutOfBunchCollCheck.cxx @@ -0,0 +1,163 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file OutOfBunchCollCheck.cxx +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#include "FT0/OutOfBunchCollCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsFIT/Triggers.h" +// ROOT +#include +#include +#include +#include + +#include +#include +#include "Common/Utils.h" + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::ft0 +{ + +void OutOfBunchCollCheck::configure() +{ + if (auto param = mCustomParameters.find("thresholdWarning"); param != mCustomParameters.end()) { + mThreshWarning = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdWarning = " << mThreshWarning << ENDM; + } else { + mThreshWarning = 1e-3; + ILOG(Debug, Support) << "configure() : using default thresholdWarning = " << mThreshWarning << ENDM; + } + + if (auto param = mCustomParameters.find("thresholdError"); param != mCustomParameters.end()) { + mThreshError = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdError = " << mThreshError << ENDM; + } else { + mThreshError = 0.1; + ILOG(Debug, Support) << "configure() : using default thresholdError = " << mThreshError << ENDM; + } + + if (auto param = mCustomParameters.find("binPos"); param != mCustomParameters.end()) { + mBinPos = stoi(param->second); + ILOG(Debug, Support) << "configure() : using binPos = " << mBinPos << ENDM; + } else { + mBinPos = int(o2::fit::Triggers::bitVertex) + 1; + ILOG(Debug, Support) << "configure() : using default binPos = " << mBinPos << ENDM; + } + mEnableMessage = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "enableMessage", true); +} + +Quality OutOfBunchCollCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + TH2F* hOutOfBunchColl = 0; + float integralBcOrbitMap = 0; + float integralOutOfBunchColl = 0; + bool metadataFound = false; + const std::string metadataKey = "BcVsTrgIntegralBin" + std::to_string(mBinPos); + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName().find("OutOfBunchColl") != std::string::npos) { + hOutOfBunchColl = dynamic_cast(mo->getObject()); + for (auto metainfo : mo->getMetadataMap()) { + if (metainfo.first == metadataKey) { + integralBcOrbitMap = std::stof(metainfo.second); + metadataFound = true; + } + } + } + } + std::string reason = ""; + if (!integralBcOrbitMap) + reason = Form("Cannot compute quality due to zero integ in BcOrbitMap"); + if (!metadataFound) + reason = Form("Cannot compute quality due to missing metadata: %s", metadataKey.c_str()); + if (!hOutOfBunchColl) + reason = Form("Cannot compute quality due to problem with retieving MO"); + if (reason != "") { + result.set(Quality::Null); + result.addFlag(FlagTypeFactory::Unknown(), reason); + ILOG(Warning) << reason << ENDM; + return result; + } + + result = Quality::Good; + mFractionOutOfBunchColl = 0; + mNumNonEmptyBins = 0; + + integralOutOfBunchColl = hOutOfBunchColl->Integral(1, sBCperOrbit, mBinPos, mBinPos); + mFractionOutOfBunchColl = integralOutOfBunchColl / integralBcOrbitMap; + + ILOG(Debug, Support) << "in checker: integralBcOrbitMap:" << integralBcOrbitMap << " integralOutOfBunchColl: " << integralOutOfBunchColl << " -> fraction: " << mFractionOutOfBunchColl << ENDM; + + if (mFractionOutOfBunchColl > mThreshError) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::Unknown(), + Form("fraction of out of bunch collisions (%.2e) is above \"Error\" threshold (%.2e)", + mFractionOutOfBunchColl, mThreshError)); + } else if (mFractionOutOfBunchColl > mThreshWarning) { + result.set(Quality::Medium); + result.addFlag(FlagTypeFactory::Unknown(), + Form("fraction of out of bunch collisions (%.2e) is above \"Warning\" threshold (%.2e)", + mFractionOutOfBunchColl, mThreshWarning)); + } + + for (int i = 1; i < hOutOfBunchColl->GetNbinsX() + 1; i++) { + for (int j = 1; j < hOutOfBunchColl->GetNbinsY() + 1; j++) { + if (hOutOfBunchColl->GetBinContent(i, j)) + mNumNonEmptyBins++; + } + } + result.addMetadata("numNonEmptyBins", std::to_string(mNumNonEmptyBins)); + return result; +} + +void OutOfBunchCollCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH2F*, skipping" << ENDM; + return; + } + if (!mEnableMessage) { + return; + } + TPaveText* msg = new TPaveText(0.1, 0.9, 0.9, 0.95, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + msg->SetTextAlign(12); + std::string prefix = Form("Fraction of out of bunch collisions = %.2e (Warning > %.2e, Error > %.2e) ", mFractionOutOfBunchColl, mThreshWarning, mThreshError); + + if (checkResult == Quality::Good) { + msg->SetFillColor(kGreen); + msg->AddText((prefix + ">> Quality::Good <<").c_str()); + } else if (checkResult == Quality::Bad) { + msg->SetFillColor(kRed); + msg->AddText((prefix + ">> Quality::Bad <<").c_str()); + } else if (checkResult == Quality::Medium) { + msg->SetFillColor(kOrange); + msg->AddText((prefix + ">> Quality::Medium <<").c_str()); + } else if (checkResult == Quality::Null) { + msg->SetFillColor(kGray); + msg->AddText((prefix + ">> Quality::Null <<").c_str()); + } +} + +} // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FT0/src/PostProcTask.cxx b/Modules/FIT/FT0/src/PostProcTask.cxx new file mode 100644 index 0000000000..63571de6b6 --- /dev/null +++ b/Modules/FIT/FT0/src/PostProcTask.cxx @@ -0,0 +1,400 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcTask.cxx +/// \author Artur Furs afurs@cern.ch +/// + +#include "FT0/PostProcTask.h" +#include "QualityControl/QcInfoLogger.h" +#include "CommonConstants/LHCConstants.h" +#include "DataFormatsParameters/GRPLHCIFData.h" +#include "DataFormatsFT0/Digit.h" +#include "DataFormatsFT0/ChannelData.h" + +#include +#include +#include +#include +#include +#include + +#include "FITCommon/HelperHist.h" +#include "FITCommon/HelperCommon.h" + +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::fit; + +namespace o2::quality_control_modules::ft0 +{ + +PostProcTask::~PostProcTask() +{ +} + +void PostProcTask::configure(const boost::property_tree::ptree& config) +{ + const char* configPath = Form("qc.postprocessing.%s", getID().c_str()); + const char* configCustom = Form("%s.custom", configPath); + auto cfgPath = [&configCustom](const std::string& entry) { + return Form("%s.%s", configCustom, entry.c_str()); + }; + mPostProcHelper.configure(config, configPath, "FT0"); + + // Detector related configs + mLowTimeThreshold = helper::getConfigFromPropertyTree(config, cfgPath("lowTimeThreshold"), -192); + mUpTimeThreshold = helper::getConfigFromPropertyTree(config, cfgPath("upTimeThreshold"), 192); + mAsynchChannelLogic = helper::getConfigFromPropertyTree(config, cfgPath("asynchChannelLogic"), "standard"); + mIsFirstIter = true; // to be sure + + // TO REMOVE + // VERY BAD SOLUTION, YOU SHOULDN'T USE IT + const std::string del = ","; + const std::string strChannelIDs = helper::getConfigFromPropertyTree(config, cfgPath("channelIDs"), ""); + const std::string strHistsToDecompose = helper::getConfigFromPropertyTree(config, cfgPath("histsToDecompose"), ""); + if (strChannelIDs.size() > 0 && strHistsToDecompose.size() > 0) { + mVecChannelIDs = helper::parseParameters(strChannelIDs, del); + mVecHistsToDecompose = helper::parseParameters(strHistsToDecompose, del); + } +} + +void PostProcTask::reset() +{ + mHistChDataNOTbits.reset(); + mHistTriggers.reset(); + mHistTriggerRates.reset(); + mHistTimeInWindow.reset(); + mHistCFDEff.reset(); + mHistChannelID_outOfBC.reset(); + mHistTrg_outOfBC.reset(); + mHistTrgValidation.reset(); + mHistBcPattern.reset(); + mHistBcTrgOutOfBunchColl.reset(); + mAmpl.reset(); + mTime.reset(); + mHistAmpAInner.reset(); + mHistAmpAOuter.reset(); + mHistAmpC.reset(); + mHistAmpNormPerChannel.reset(); + mMapHistsToDecompose.clear(); +} + +void PostProcTask::initialize(Trigger trg, framework::ServiceRegistryRef services) +{ + reset(); // for SSS procedure + mPostProcHelper.initialize(trg, services); + mIsFirstIter = true; // to be sure + + mHistChDataNOTbits = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "COLZ", "ChannelDataNegBits", "ChannelData NOT PM bits per ChannelID;Channel;Negative bit", sNCHANNELS_PM, 0, sNCHANNELS_PM, mMapPMbits); + mHistTriggers = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "Triggers", "Triggers from TCM", mMapTechTrgBitsExtra); + mHistTriggerRates = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "HIST", "TriggerRates", "Trigger rates; Triggers; Rate [kHz]", mMapTechTrgBitsExtra); + mHistBcTrgOutOfBunchColl = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "COLZ", "OutOfBunchColl_BCvsTrg", "BC vs Triggers for out-of-bunch collisions;BC;Triggers", sBCperOrbit, 0, sBCperOrbit, mMapTechTrgBitsExtra); + mHistBcPattern = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "COLZ", "bcPattern", "BC pattern", sBCperOrbit, 0, sBCperOrbit, mMapTechTrgBitsExtra); + mHistTimeInWindow = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "TimeInWindowFraction", Form("Fraction of events with CFD in time gate(%i,%i) vs ChannelID;ChannelID;Event fraction with CFD in time gate", mLowTimeThreshold, mUpTimeThreshold), sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistCFDEff = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "CFD_efficiency", "Fraction of events with CFD in ADC gate vs ChannelID;ChannelID;Event fraction with CFD in ADC gate;", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistChannelID_outOfBC = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "ChannelID_outOfBC", "ChannelID, out of bunch", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistTrg_outOfBC = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "Triggers_outOfBC", "Trigger fraction, out of bunch", mMapTechTrgBitsExtra); + mHistTrgValidation = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "TrgValidation", "SW + HW only to validated triggers fraction", mMapTrgBits); + mAmpl = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "MeanAmplPerChannel", "mean ampl per channel;Channel;Ampl #mu #pm #sigma", o2::ft0::Constants::sNCHANNELS_PM, 0, o2::ft0::Constants::sNCHANNELS_PM); + mTime = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "MeanTimePerChannel", "mean time per channel;Channel;Time #mu #pm #sigma", o2::ft0::Constants::sNCHANNELS_PM, 0, o2::ft0::Constants::sNCHANNELS_PM); + mHistAmpAInner = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "AmpAInner", "FT0 A-side inner channel amplitudes (ch 0-31);Channel amplitude (ADC ch);Counts", 4200, -100, 4100); + mHistAmpAOuter = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "AmpAOuter", "FT0 A-side outer channel amplitudes (ch 32-95);Channel amplitude (ADC ch);Counts", 4200, -100, 4100); + mHistAmpC = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "AmpC", "FT0 C-side channel ampltitudes (ch 96-207);Channel amplitude (ADC ch);Counts", 4200, -100, 4100); + mHistAmpNormPerChannel = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "AmpNormPerChannel", "FT0 channel amplitudes normalized per channel (ch 0-207);Channel amplitude (ADC ch);#sum_{ch}AmpHist_{ch} #times (1/counts_{ch})", 4200, -100, 4100); + mHistAmpNormPerChannel->Sumw2(kFALSE); + mChannelGeometry.init(-200., 200., -200., 200., 10.); // values - borders for hist and margin + + mHistStatsSideA = mChannelGeometry.makeHistSideA("GeoChannelStatA", "Channel occupancy, side-A"); + mHistStatsSideC = mChannelGeometry.makeHistSideC("GeoChannelStatC", "Channel occupancy, side-C"); + getObjectsManager()->startPublishing(mHistStatsSideA.get()); + getObjectsManager()->setDefaultDrawOptions(mHistStatsSideA.get(), "TEXT COLZ L"); + getObjectsManager()->startPublishing(mHistStatsSideC.get()); + getObjectsManager()->setDefaultDrawOptions(mHistStatsSideC.get(), "TEXT COLZ L"); +} + +void PostProcTask::update(Trigger trg, framework::ServiceRegistryRef serviceReg) +{ + mPostProcHelper.update(trg, serviceReg); + + auto getBinContent2Ddiag = [](TH2F* hist, const std::string& binName) { + const auto xBin = hist->GetXaxis()->FindBin(binName.c_str()); + const auto yBin = hist->GetYaxis()->FindBin(binName.c_str()); + return hist->GetBinContent(xBin, yBin); + }; + + // Trigger correlation + auto hTrgCorr = mPostProcHelper.template getObject("TriggersCorrelation"); + mHistTriggers->Reset(); + + if (hTrgCorr) { + double totalStat{ 0 }; + for (int iBin = 1; iBin < mHistTriggers->GetXaxis()->GetNbins() + 1; iBin++) { + const std::string binName{ mHistTriggers->GetXaxis()->GetBinLabel(iBin) }; + const auto binContent = getBinContent2Ddiag(hTrgCorr.get(), binName); + mHistTriggers->SetBinContent(iBin, binContent); + totalStat += binContent; + } + mHistTriggers->SetEntries(totalStat); + } + + auto getVrtTrgCounters = [binPos = static_cast(o2::ft0::Triggers::bitVertex) + 1](const auto& histTrgCnts) { + return histTrgCnts->GetBinContent(binPos); + }; + const auto trgVrtCnts = getVrtTrgCounters(mHistTriggers); + // Trigger rates + mHistTriggerRates->Reset(); + if (mPostProcHelper.IsNonEmptySample()) { + mHistTriggerRates->Add(mHistTriggers.get()); + constexpr double factor = 1e3; // Hz -> kHz + const double samplePeriod = 1. / (factor * mPostProcHelper.mCurrSampleLengthSec); // in sec^-1 units + mHistTriggerRates->Scale(samplePeriod); + } + // PM bits + auto hChDataBits = mPostProcHelper.template getObject("ChannelDataBits"); + auto hStatChannelID = mPostProcHelper.template getObject("StatChannelID"); + mHistChDataNOTbits->Reset(); + if (hChDataBits && hStatChannelID) { + double totalStat{ 0 }; + for (int iBinX = 1; iBinX < hChDataBits->GetXaxis()->GetNbins() + 1; iBinX++) { + for (int iBinY = 1; iBinY < hChDataBits->GetYaxis()->GetNbins() + 1; iBinY++) { + const double nStatTotal = hStatChannelID->GetBinContent(iBinX); + const double nStatPMbit = hChDataBits->GetBinContent(iBinX, iBinY); + const double nStatNegPMbit = nStatTotal - nStatPMbit; + totalStat += nStatNegPMbit; + mHistChDataNOTbits->SetBinContent(iBinX, iBinY, nStatNegPMbit); + } + } + mHistChDataNOTbits->SetEntries(totalStat); + } + // Amplitudes + auto hAmpPerChannel = mPostProcHelper.template getObject("AmpPerChannel"); + if (hAmpPerChannel) { + std::unique_ptr projNom(hAmpPerChannel->ProjectionX("projNom", hAmpPerChannel->GetYaxis()->FindBin(1.0), -1)); + std::unique_ptr projDen(hAmpPerChannel->ProjectionX("projDen")); + mHistCFDEff->Divide(projNom.get(), projDen.get()); + + // Channel amplitudes for A-side inner, A-side outer, C-side and all channels + mHistAmpAInner->Reset(); + mHistAmpAOuter->Reset(); + mHistAmpC->Reset(); + mHistAmpNormPerChannel->Reset(); + + std::unique_ptr projAInner(hAmpPerChannel->ProjectionY("projAInner", 1, 32)); + std::unique_ptr projAOuter(hAmpPerChannel->ProjectionY("projAOuter", 33, 96)); + std::unique_ptr projC(hAmpPerChannel->ProjectionY("projC", 97, 208)); + + mHistAmpAInner->Add(projAInner.get()); + mHistAmpAOuter->Add(projAOuter.get()); + mHistAmpC->Add(projC.get()); + + // Iterate over channels + int entries = mHistAmpNormPerChannel->GetEntries(); + for (int iBin = 1; iBin < hAmpPerChannel->GetXaxis()->GetNbins() + 1; iBin++) { + std::unique_ptr ampForChannel(hAmpPerChannel->ProjectionY(Form("ampForChannel%i", iBin - 1), iBin, iBin)); + ampForChannel->Sumw2(kFALSE); + + // Here height of the bins of each detector channel must be divided by the sum of all bins (= number of events) in that channel. + // This is needed to equalize the weight of all channels independently of the load (central channels are loaded more). + const double integral = ampForChannel->Integral(); + const double scale = integral > 0. ? 1. / integral : 0.; + mHistAmpNormPerChannel->Add(ampForChannel.get(), scale); + entries += ampForChannel->GetEntries(); + } + mHistAmpNormPerChannel->SetEntries(entries); + } + // Times + auto hTimePerChannel = mPostProcHelper.template getObject("TimePerChannel"); + if (hTimePerChannel) { + std::unique_ptr projInWindow(hTimePerChannel->ProjectionX("projInWindow", hTimePerChannel->GetYaxis()->FindBin(mLowTimeThreshold), hTimePerChannel->GetYaxis()->FindBin(mUpTimeThreshold))); + std::unique_ptr projFull(hTimePerChannel->ProjectionX("projFull")); + mHistTimeInWindow->Divide(projInWindow.get(), projFull.get()); + // hist with channel variables -> geometrical lot with channel variables + const double scaleVrtTrg = trgVrtCnts > 0. ? 1. / trgVrtCnts : 0.0; + projInWindow->Scale(scaleVrtTrg); + mHistStatsSideA->Reset("content"); + mHistStatsSideC->Reset("content"); + mHistStatsSideA->SetStats(0); + mHistStatsSideC->SetStats(0); + mChannelGeometry.convertHist1D(projInWindow.get(), mHistStatsSideA.get(), mHistStatsSideC.get()); + } + + if (hAmpPerChannel && hTimePerChannel) { + hAmpPerChannel->ProfileX("MeanAmplPerChannel"); + hTimePerChannel->ProfileX("MeanTimePerChannel"); + mAmpl->SetErrorOption("s"); + mTime->SetErrorOption("s"); + // for some reason the styling is not preserved after assigning result of ProfileX/Y() to already existing object + mAmpl->SetMarkerStyle(8); + mTime->SetMarkerStyle(8); + mAmpl->SetLineColor(kBlack); + mTime->SetLineColor(kBlack); + mAmpl->SetDrawOption("P"); + mTime->SetDrawOption("P"); + mAmpl->GetXaxis()->SetTitleOffset(1); + mTime->GetXaxis()->SetTitleOffset(1); + mAmpl->GetYaxis()->SetTitleOffset(1); + mTime->GetYaxis()->SetTitleOffset(1); + } + + auto hBcVsTrg = mPostProcHelper.template getObject("BCvsTriggers"); + const auto& bcPattern = mPostProcHelper.getGRPLHCIFData().getBunchFilling(); + + // Should be removed + // BC pattern + mHistBcPattern->Reset(); + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapTechTrgBitsExtra.size() + 1; j++) { + mHistBcPattern->SetBinContent(i + 1, j + 1, bcPattern.testBC(i)); + } + } + // Should be removed + // Triggers in non-collision BCs + mHistBcTrgOutOfBunchColl->Reset(); + float vmax = hBcVsTrg->GetBinContent(hBcVsTrg->GetMaximumBin()); + mHistBcTrgOutOfBunchColl->Add(hBcVsTrg.get(), mHistBcPattern.get(), 1, -1 * vmax); + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapTechTrgBitsExtra.size() + 1; j++) { + if (mHistBcTrgOutOfBunchColl->GetBinContent(i + 1, j + 1) < 0) { + mHistBcTrgOutOfBunchColl->SetBinContent(i + 1, j + 1, 0); // is it too slow? + } + } + } + mHistBcTrgOutOfBunchColl->SetEntries(mHistBcTrgOutOfBunchColl->Integral(1, sBCperOrbit, 1, mMapTechTrgBitsExtra.size())); + for (int iBin = 1; iBin < mMapTechTrgBitsExtra.size() + 1; iBin++) { + const std::string metadataKey = "BcVsTrgIntegralBin" + std::to_string(iBin); + const std::string metadataValue = std::to_string(hBcVsTrg->Integral(1, sBCperOrbit, iBin, iBin)); + getObjectsManager()->getMonitorObject(mHistBcTrgOutOfBunchColl->GetName())->addOrUpdateMetadata(metadataKey, metadataValue); + ILOG(Info, Support) << metadataKey << ":" << metadataValue << ENDM; + } + // Functor for in/out colliding BC fraction calculation + auto calcFraction = [&pattern = bcPattern](const auto& histSrc, auto& histDst) { + for (int iBin = 0; iBin < histSrc->GetYaxis()->GetNbins(); iBin++) { + double cntInBC{ 0 }; + double cntOutOfBC{ 0 }; + const auto binPos = iBin + 1; + std::unique_ptr proj(histSrc->ProjectionX("proj", binPos, binPos)); + for (int iBC = 0; iBC < proj->GetXaxis()->GetNbins(); iBC++) { + if (pattern.testBC(iBC)) { + cntInBC += proj->GetBinContent(iBC + 1); + } else { + cntOutOfBC += proj->GetBinContent(iBC + 1); + } + } + const auto val = cntInBC > 0 ? cntOutOfBC / cntInBC : 0; + histDst->SetBinContent(binPos, val); + } + }; + + // New version for trigger fraction in non colliding BCa + mHistTrg_outOfBC->Reset(); + calcFraction(hBcVsTrg, mHistTrg_outOfBC); + + // Channel stats in non-collision BCs + auto hChIDvsBC = mPostProcHelper.template getObject("ChannelIDperBC"); + if (hChIDvsBC) { + auto hChID_InBC = std::make_unique("hChID_InBC", "hChID_InBC", hChIDvsBC->GetYaxis()->GetNbins(), 0, hChIDvsBC->GetYaxis()->GetNbins()); + auto hChID_OutOfBC = std::make_unique("hChID_OutOfBC", "hChID_OutOfBC", hChIDvsBC->GetYaxis()->GetNbins(), 0, hChIDvsBC->GetYaxis()->GetNbins()); + if (mAsynchChannelLogic == std::string{ "standard" }) { + // Standard logic, calculates outCollBC/inCollBC ratio + calcFraction(hChIDvsBC, mHistChannelID_outOfBC); + } else if (mAsynchChannelLogic == std::string{ "normalizedTrains" }) { + // Train normlization, normalizes events in trains and then calculates outCollBC/inCollBC ratio + const auto mapTrainBC = helper::getMapBCtrains(bcPattern.getBCPattern()); + for (int iChID = 0; iChID < hChIDvsBC->GetYaxis()->GetNbins(); iChID++) { + double cntOutOfBC{ 0 }; + double cntInBC{ 0 }; + for (const auto& train : mapTrainBC) { + double eventsTrain{ 0 }; + for (int iBC = train.first; iBC < train.first + train.second; iBC++) { + eventsTrain += hChIDvsBC->GetBinContent(iBC + 1, iChID + 1); + } + if (train.second > 0) { + cntInBC += eventsTrain / train.second; + } + } + for (int iBC = 0; iBC < hChIDvsBC->GetXaxis()->GetNbins(); iBC++) { + if (!bcPattern.testBC(iBC)) { + cntOutOfBC += hChIDvsBC->GetBinContent(iBC + 1, iChID + 1); + } + } + const auto val = cntInBC > 0 ? cntOutOfBC / cntInBC : 0; + mHistChannelID_outOfBC->SetBinContent(iChID + 1, val); + } + } + } + + // Fraction for trigger validation + auto hTriggersSoftwareVsTCM = mPostProcHelper.template getObject("TriggersSoftwareVsTCM"); + if (hTriggersSoftwareVsTCM) { + std::unique_ptr projOnlyHWorSW(hTriggersSoftwareVsTCM->ProjectionX("projOnlyHWorSW", 2, 3)); + std::unique_ptr projValidatedSWandHW(hTriggersSoftwareVsTCM->ProjectionX("projValidatedSWandHW", 4, 4)); + projOnlyHWorSW->LabelsDeflate(); + projValidatedSWandHW->LabelsDeflate(); + mHistTrgValidation->Divide(projOnlyHWorSW.get(), projValidatedSWandHW.get()); + } + decomposeHists(); + setTimestampToMOs(); + // Needed for first-iter init + mIsFirstIter = false; +} + +void PostProcTask::decomposeHists() +{ + for (const auto& histName : mVecHistsToDecompose) { + auto insertedMap = mMapHistsToDecompose.insert({ histName, {} }); + auto& mapHists = insertedMap.first->second; + + auto histSrcPtr = mPostProcHelper.template getObject(histName); + if (histSrcPtr == nullptr) { + continue; + } + const auto bins = histSrcPtr->GetYaxis()->GetNbins(); + const auto binLow = histSrcPtr->GetYaxis()->GetXmin(); + const auto binUp = histSrcPtr->GetYaxis()->GetXmax(); + + for (const auto& chID : mVecChannelIDs) { + auto insertedHistDst = mapHists.insert({ chID, nullptr }); + auto& histDstPtr = insertedHistDst.first->second; + auto isInserted = insertedHistDst.second; + if (isInserted == true) { + // creation in first iter + const std::string suffix = std::string{ Form("%03i", chID) }; + const std::string newHistName = histName + std::string{ "_" } + suffix; + const std::string newHistTitle = histSrcPtr->GetTitle() + std::string{ " " } + suffix; + histDstPtr = std::make_shared(newHistName.c_str(), newHistTitle.c_str(), bins, binLow, binUp); + getObjectsManager()->startPublishing(histDstPtr.get()); + } + histDstPtr->Reset(); + // making projection + const auto binPos = chID + 1; + const std::unique_ptr proj(histSrcPtr->ProjectionY("proj", binPos, binPos)); + histDstPtr->Add(proj.get()); + } + } +} + +void PostProcTask::setTimestampToMOs() +{ + for (int iObj = 0; iObj < getObjectsManager()->getNumberPublishedObjects(); iObj++) { + auto mo = getObjectsManager()->getMonitorObject(iObj); + mo->addOrUpdateMetadata(mPostProcHelper.mTimestampMetaField, std::to_string(mPostProcHelper.mTimestampAnchor)); + } +} + +void PostProcTask::finalize(Trigger t, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FT0/src/RecPointsQcTask.cxx b/Modules/FIT/FT0/src/RecPointsQcTask.cxx new file mode 100644 index 0000000000..40ed1b48c0 --- /dev/null +++ b/Modules/FIT/FT0/src/RecPointsQcTask.cxx @@ -0,0 +1,260 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTask.cxx +/// \author Artur Furs afurs@cern.ch +/// modified by Sebastin Bysiak sbysiak@cern.ch + +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include +#include "DataFormatsParameters/GRPLHCIFData.h" +#include "FITCommon/HelperHist.h" +#include "FITCommon/HelperCommon.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include "CommonDataFormat/BunchFilling.h" +namespace o2::quality_control_modules::ft0 +{ + +using namespace o2::quality_control_modules::fit; + +RecPointsQcTask::~RecPointsQcTask() +{ + delete mListHistGarbage; +} +void RecPointsQcTask::initHists() +{ + for (int iCh = 0; iCh < sNCHANNELS; iCh++) { + const std::string name = fmt::format("hAmpVsTime_ch{}", iCh); + const std::string title = fmt::format("Amp Vs Time channelID {}; Amp [ADC]; Time [ps]", iCh); + mArrAmpTimeDistribution[iCh] = o2::fit::AmpTimeDistribution(name, title, 200, -2000., 2000., 50, 4095, 0); // in total 315 bins along x-axis + getObjectsManager()->startPublishing(mArrAmpTimeDistribution[iCh].mHist.get()); + } +} + +void RecPointsQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Info, Support) << "@@@@initialize RecoQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + mHistTime2Ch = std::make_unique("TimePerChannel", "Time vs Channel;Channel;Time [ps]", sNCHANNELS, 0, sNCHANNELS, 500, -2050, 2050); + mHistTime2Ch->SetOption("colz"); + mHistAmp2Ch = std::make_unique("AmpPerChannel", "Amplitude vs Channel;Channel;Amp [#ADC channels]", sNCHANNELS, 0, sNCHANNELS, 200, 0, 1000); + mHistAmp2Ch->SetOption("colz"); + mHistCollTimeAC = std::make_unique("CollTimeAC", "(T0A+T0C)/2;ps", 100, -1000, 1000); + mHistCollTimeA = std::make_unique("CollTimeA", "T0A;ps", 100, -1000, 1000); + mHistCollTimeC = std::make_unique("CollTimeC", "T0C;ps", 100, -1000, 1000); + mHistResCollTimeA = std::make_unique("ResCollTimeA", "(T0Aup-T0Adown)/2;ps", 100, -500, 500); + mHistResCollTimeC = std::make_unique("ResCollTimeC", "(T0Cup-T0Cdown)/2;ps", 100, -500, 500); + mListHistGarbage = new TList(); + mListHistGarbage->SetOwner(kTRUE); + + std::vector vecChannelIDs; + if (auto param = mCustomParameters.find("ChannelIDs"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDs = helper::parseParameters(chIDs, del); + } else { + for (unsigned int iCh = 0; iCh < o2::ft0::Constants::sNCHANNELS_PM; iCh++) + vecChannelIDs.push_back(iCh); + } + for (const auto& entry : vecChannelIDs) { + mSetAllowedChIDs.insert(entry); + } + + getObjectsManager()->startPublishing(mHistTime2Ch.get()); + getObjectsManager()->startPublishing(mHistAmp2Ch.get()); + getObjectsManager()->startPublishing(mHistCollTimeAC.get()); + getObjectsManager()->startPublishing(mHistCollTimeA.get()); + getObjectsManager()->startPublishing(mHistCollTimeC.get()); + getObjectsManager()->startPublishing(mHistResCollTimeA.get()); + getObjectsManager()->startPublishing(mHistResCollTimeC.get()); + + const std::tuple binsTime{ 500, -2500, 2500 }; + const std::tuple binsBC{ 3564, 0, 3564 }; + mTrgPos_minBias = mMapTrgBits.size(); + mMapTrgBits.insert({ mTrgPos_minBias, "MinBias" }); + mTrgPos_allEvents = mMapTrgBits.size(); + mMapTrgBits.insert({ mTrgPos_allEvents, "AllEvents" }); + mHistSumTimeAC_perTrg = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "SumTimeAC_perTrg", "(T0A+T0C)/2 per Trigger;Time [ps]; Trigger", binsTime, mMapTrgBits); + mHistDiffTimeAC_perTrg = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "DiffTimeAC_perTrg", "(T0C-T0C)/2 per Trigger;Time [ps]; Trigger", binsTime, mMapTrgBits); + mHistTimeA_perTrg = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "TimeA_perTrg", "T0A per Trigger;Time [ps]; Trigger", binsTime, mMapTrgBits); + mHistTimeC_perTrg = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "TimeC_perTrg", "T0C per Trigger;Time [ps]; Trigger", binsTime, mMapTrgBits); + mHistBC_perTriggers = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "BC_perTriggers", "BC per Triggers;BC; Trigger", binsBC, mMapTrgBits); + /* + for (const auto& chID : mSetAllowedChIDs) { + auto pairHistAmpVsTime = mMapHistAmpVsTime.insert({ chID, new TH2F(Form("Amp_vs_time_channel%i", chID), Form("Amplitude vs time, channel %i;Amp;Time", chID), 1000, 0, 4000, 100, -1000, 1000) }); + if (pairHistAmpVsTime.second) { + mListHistGarbage->Add(pairHistAmpVsTime.first->second); + getObjectsManager()->startPublishing(pairHistAmpVsTime.first->second); + } + } + */ + initHists(); + ILOG(Info, Support) << "@@@ histos created" << ENDM; +} + +void RecPointsQcTask::startOfActivity(const Activity& activity) +{ + ILOG(Info, Support) << "@@@@ startOfActivity" << activity.mId << ENDM; + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistCollTimeAC->Reset(); + mHistCollTimeA->Reset(); + mHistCollTimeC->Reset(); + mHistResCollTimeA->Reset(); + mHistResCollTimeC->Reset(); + mHistSumTimeAC_perTrg->Reset(); + mHistDiffTimeAC_perTrg->Reset(); + mHistTimeA_perTrg->Reset(); + mHistTimeC_perTrg->Reset(); + mHistBC_perTriggers->Reset(); + for (int iCh = 0; iCh < sNCHANNELS; iCh++) { + mArrAmpTimeDistribution[iCh].mHist->Reset(); + } +} + +void RecPointsQcTask::startOfCycle() +{ +} + +void RecPointsQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto chan = ctx.inputs().get>("channels"); + auto recpoints = ctx.inputs().get>("recpoints"); + const auto& grplhcif = o2::base::GRPGeomHelper::instance().getGRPLHCIF(); + const auto& bcSchema = grplhcif ? grplhcif->getBunchFilling().getBCPattern() : o2::BunchFilling::Pattern{}; + const bool isPbPb = grplhcif ? grplhcif->getAtomicNumberB1() == 82 && grplhcif->getAtomicNumberB2() == 82 : false; + for (const auto& recpoint : recpoints) { + const auto bc = recpoint.getInteractionRecord().bc; + int time[o2::ft0::Constants::sNCHANNELS_PM] = { 0 }; + int amp[o2::ft0::Constants::sNCHANNELS_PM] = { 0 }; + o2::ft0::Triggers triggersignals = recpoint.getTrigger(); + bool vertexTrigger = triggersignals.getVertex(); + auto channels = recpoint.getBunchChannelData(chan); + mHistCollTimeAC->Fill(static_cast(recpoint.getCollisionTimeMean())); + mHistCollTimeA->Fill(static_cast(recpoint.getCollisionTimeA())); + mHistCollTimeC->Fill(static_cast(recpoint.getCollisionTimeC())); + const bool minBias = triggersignals.getVertex() && (triggersignals.getCen() || triggersignals.getSCen()); + // Preparing trigger word + uint64_t trgWord = triggersignals.getTriggersignals() & 0b11111; + trgWord |= (static_cast(minBias) << mTrgPos_minBias); + trgWord |= (1ull << mTrgPos_allEvents); + // Filling hists with trg bits + for (const auto& entry : mMapTrgBits) { + if ((trgWord & (1ull << entry.first)) > 0) { + mHistBC_perTriggers->Fill(static_cast(bc), static_cast(entry.first)); + const auto timeA = static_cast(recpoint.getCollisionTimeA()); + const auto timeC = static_cast(recpoint.getCollisionTimeC()); + mHistTimeA_perTrg->Fill(timeA, static_cast(entry.first)); + mHistTimeC_perTrg->Fill(timeC, static_cast(entry.first)); + mHistDiffTimeAC_perTrg->Fill(static_cast(recpoint.getVertex()), static_cast(entry.first)); + if (timeA < o2::ft0::RecPoints::sDummyCollissionTime && timeC < o2::ft0::RecPoints::sDummyCollissionTime) { + mHistSumTimeAC_perTrg->Fill(static_cast(recpoint.getCollisionTimeMean()), static_cast(entry.first)); + } + } + } + const bool isMinBiasEvent = (isPbPb && minBias) || (!isPbPb && triggersignals.getVertex()); // for pp only vertex, for PbPb vrt && (CENT || SCENT) + for (const auto& chData : channels) { + time[chData.ChId] = chData.CFDTime; + amp[chData.ChId] = chData.QTCAmpl; + mHistTime2Ch->Fill(static_cast(chData.ChId), static_cast(chData.CFDTime)); + mHistAmp2Ch->Fill(static_cast(chData.ChId), static_cast(chData.QTCAmpl)); + if (bcSchema.test(bc) && isMinBiasEvent) { // ampt-time dependency + mArrAmpTimeDistribution[chData.ChId].mHist->Fill(chData.QTCAmpl, chData.CFDTime); + } + } + if (vertexTrigger) { + int avtimeAup = 0, avtimeAdown = 0, naup = 0, nadown = 0; + int avtimeCup = 0, avtimeCdown = 0, ncup = 0, ncdown = 0; + for (int ich = 0; ich < 48; ich++) { + if (amp[ich] > 5 && std::abs(time[ich]) < 1000) { + avtimeAup += time[ich]; + naup++; + } + } + if (naup > 0) + avtimeAup /= naup; + for (int ich = 48; ich < 96; ich++) { + if (amp[ich] > 5 && std::abs(time[ich]) < 1000) { + avtimeAdown += time[ich]; + nadown++; + } + } + if (nadown > 0) + avtimeAdown /= nadown; + if (nadown > 0 && naup > 0) { + mHistResCollTimeA->Fill((avtimeAdown - avtimeAup) / 2); + } + for (int ich = 96; ich < 152; ich++) { + if (amp[ich] > 5 && std::abs(time[ich]) < 1000) { + avtimeCup += time[ich]; + ncup++; + } + } + if (ncup > 0) + avtimeCup /= ncup; + for (int ich = 152; ich < 208; ich++) { + if (amp[ich] > 14 && std::abs(time[ich]) < 1000) { + avtimeCdown += time[ich]; + ncdown++; + } + } + if (ncdown > 0) + avtimeCdown /= ncdown; + if (ncdown > 0 && ncup > 0) { + mHistResCollTimeC->Fill((avtimeCdown - avtimeCup) / 2); + } + } + } +} + +void RecPointsQcTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + // one has to set num. of entries manually because + // default TH1Reductor gets only mean,stddev and entries (no integral) +} + +void RecPointsQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void RecPointsQcTask::reset() +{ + // clean all the monitor objects here + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistCollTimeAC->Reset(); + mHistCollTimeA->Reset(); + mHistCollTimeC->Reset(); + mHistResCollTimeA->Reset(); + mHistResCollTimeC->Reset(); + mHistSumTimeAC_perTrg->Reset(); + mHistDiffTimeAC_perTrg->Reset(); + mHistTimeA_perTrg->Reset(); + mHistTimeC_perTrg->Reset(); + mHistBC_perTriggers->Reset(); + for (int iCh = 0; iCh < sNCHANNELS; iCh++) { + mArrAmpTimeDistribution[iCh].mHist->Reset(); + } +} + +} // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FT0/test/testQcFT0.cxx b/Modules/FIT/FT0/test/testQcFT0.cxx new file mode 100644 index 0000000000..fbc37c95d4 --- /dev/null +++ b/Modules/FIT/FT0/test/testQcFT0.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testFT0.cxx +/// \author Milosz Filus +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::ft0 +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::ft0 diff --git a/Modules/FIT/FV0/CMakeLists.txt b/Modules/FIT/FV0/CMakeLists.txt new file mode 100644 index 0000000000..f877af81fe --- /dev/null +++ b/Modules/FIT/FV0/CMakeLists.txt @@ -0,0 +1,67 @@ +# ---- Library ---- + +add_library(O2QcFV0) + +target_sources(O2QcFV0 PRIVATE src/TH1ReductorLaser.cxx + src/DigitQcTaskLaser.cxx + src/DigitQcTask.cxx + src/GenericCheck.cxx + src/CFDEffCheck.cxx + src/PostProcTask.cxx + src/OutOfBunchCollCheck.cxx + src/TriggersSwVsTcmCheck.cxx + src/OutOfBunchCollFeeModulesCheck.cxx) + +target_include_directories( + O2QcFV0 + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcFV0 PUBLIC O2QualityControl + O2::DataFormatsFV0 + O2::FV0Base + O2::DataFormatsParameters + O2QcCommon + O2QcFITCommon) + +install(TARGETS O2QcFV0 + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcFV0 + HEADERS include/FV0/TH1ReductorLaser.h + include/FV0/DigitQcTaskLaser.h + include/FV0/DigitQcTask.h + include/FV0/PostProcTask.h + include/FV0/CFDEffCheck.h + include/FV0/OutOfBunchCollCheck.h + include/FV0/GenericCheck.h + include/FV0/TriggersSwVsTcmCheck.h + include/FV0/OutOfBunchCollFeeModulesCheck.h + LINKDEF include/FV0/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/FV0 + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +install(FILES etc/fv0-digits.json + etc/fv0-post-processing.json + DESTINATION Modules/FIT/FV0/etc) + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcFV0.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcFV0 Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() diff --git a/Modules/FIT/FV0/etc/fv0-digits.json b/Modules/FIT/FV0/etc/fv0-digits.json new file mode 100644 index 0000000000..cc4791c895 --- /dev/null +++ b/Modules/FIT/FV0/etc/fv0-digits.json @@ -0,0 +1,53 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" + } + }, + "tasks": { + "Digits": { + "active": "true", + "className": "o2::quality_control_modules::fv0::DigitQcTask", + "moduleName": "QcFV0", + "detectorName": "FV0", + "cycleDurationSeconds": "60", + "resetAfterCycles": "1", + "dataSource": { + "type": "direct", + "query": "digits:FV0/DIGITSBC/0;channels:FV0/DIGITSCH/0" + }, + "taskParameters": { + "#ChannelIDs": "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48", + "ChannelIDsAmpVsTime": "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48", + "trgModeInnerOuterThresholdVar": "Ampl", + "trgThresholdNChannels": "2", + "trgThresholdCharge": "8", + "trgThresholdChargeInner": "4", + "trgThresholdChargeOuter": "4", + "trgChargeLevelLow": "6", + "trgChargeLevelHigh": "4095", + "minGateTimeForRatioHistogram": "-192", + "maxGateTimeForRatioHistogram": "192" + } + } + } + } +} \ No newline at end of file diff --git a/Modules/FIT/FV0/etc/fv0-post-processing.json b/Modules/FIT/FV0/etc/fv0-post-processing.json new file mode 100644 index 0000000000..863408d755 --- /dev/null +++ b/Modules/FIT/FV0/etc/fv0-post-processing.json @@ -0,0 +1,428 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "#host": "http://localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "bookkeeping": { + "url": "" + } + }, + "checks": { + "OutOfBunchCollCheck": { + "active": "true", + "className": "o2::quality_control_modules::fv0::OutOfBunchCollCheck", + "moduleName": "QcFV0", + "policy": "OnAny", + "detectorName": "FV0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "OutOfBunchColl_BCvsTrg" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.1", + "thresholdError": "0.15", + "binPos": "2" + } + }, + "OutOfBunchCollCheckFee": { + "active": "true", + "className": "o2::quality_control_modules::fv0::OutOfBunchCollFeeModulesCheck", + "moduleName": "QcFV0", + "policy": "OnAny", + "detectorName": "FV0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "OutOfBunchColl_BCvsFeeModules" + ] + } + ], + "checkParameters": { + "#thresholdWarning": "TODO", + "#thresholdError": "TODO" + } + }, + "OutOfBunchCollCheckFeeCharge": { + "active": "true", + "className": "o2::quality_control_modules::fv0::OutOfBunchCollFeeModulesCheck", + "moduleName": "QcFV0", + "policy": "OnAny", + "detectorName": "FV0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "OutOfBunchColl_BCvsFeeModulesForChargeTrg" + ] + } + ], + "checkParameters": { + "#thresholdWarning": "TODO", + "#thresholdError": "TODO" + } + }, + "OutOfBunchCollCheckFeeNChannel": { + "active": "true", + "className": "o2::quality_control_modules::fv0::OutOfBunchCollFeeModulesCheck", + "moduleName": "QcFV0", + "policy": "OnAny", + "detectorName": "FV0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "OutOfBunchColl_BCvsFeeModulesForNChanTrg" + ] + } + ], + "checkParameters": { + "#thresholdWarning": "TODO", + "#thresholdError": "TODO" + } + }, + "OutOfBunchCollCheckFeeOrA": { + "active": "true", + "className": "o2::quality_control_modules::fv0::OutOfBunchCollFeeModulesCheck", + "moduleName": "QcFV0", + "policy": "OnAny", + "detectorName": "FV0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "OutOfBunchColl_BCvsFeeModulesForOrATrg" + ] + } + ], + "checkParameters": { + "#thresholdWarning": "TODO", + "#thresholdError": "TODO" + } + }, + "OutOfBunchCollCheckFeeOrAIn": { + "active": "true", + "className": "o2::quality_control_modules::fv0::OutOfBunchCollFeeModulesCheck", + "moduleName": "QcFV0", + "policy": "OnAny", + "detectorName": "FV0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "OutOfBunchColl_BCvsFeeModulesForOrAInTrg" + ] + } + ], + "checkParameters": { + "#thresholdWarning": "TODO", + "#thresholdError": "TODO" + } + }, + "OutOfBunchCollCheckFeeOrAOut": { + "active": "true", + "className": "o2::quality_control_modules::fv0::OutOfBunchCollFeeModulesCheck", + "moduleName": "QcFV0", + "policy": "OnAny", + "detectorName": "FV0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "OutOfBunchColl_BCvsFeeModulesForOrAOutTrg" + ] + } + ], + "checkParameters": { + "#thresholdWarning": "TODO", + "#thresholdError": "TODO" + } + }, + "CFDinTimeGateCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FV0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "TimeInWindowFraction" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.8", + "thresholdError": "0.6", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "TimeInWindowFraction", + "isInversedThresholds": "false", + "pathDeadChannelMap": "FV0/Calib/DeadChannelMap", + "binsToIgnore": "48" + } + }, + "CFDinADCgateCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FV0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "CFD_efficiency" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.8", + "thresholdError": "0.6", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "CFD_efficiency", + "isInversedThresholds": "false", + "pathDeadChannelMap": "FV0/Calib/DeadChannelMap", + "binsToIgnore": "48" + } + }, + "TrgValidationCheck": { + "active": "true", + "className": "o2::quality_control_modules::fit::LevelCheck", + "moduleName": "QcFIT", + "policy": "OnAny", + "detectorName": "FV0", + "dataSource": [ + { + "type": "PostProcessing", + "name": "PostProc", + "MOs": [ + "TrgValidation" + ] + } + ], + "checkParameters": { + "thresholdWarning": "0.0005", + "thresholdError": "0.002", + "ccdbUrl": "alice-ccdb.cern.ch", + "nameObjectToCheck": "TrgValidation", + "isInversedThresholds": "true", + "pathDeadChannelMap": "", + "binsToIgnore": "" + } + } + }, + "aggregators": { + "GlobalQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "FV0", + "dataSource": [ + { + "type": "Check", + "name": "OutOfBunchCollCheck" + }, + { + "type": "Check", + "name": "CFDinTimeGateCheck" + }, + { + "type": "Check", + "name": "CFDinADCgateCheck" + }, + { + "type": "Check", + "name": "TrgValidationCheck" + } + ] + } + }, + "postprocessing": { + "Quality": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QualityControl", + "detectorName": "FV0", + "qualityGroups": [ + { + "name": "Global", + "title": "GLOBAL FV0 QUALITY", + "path": "FV0/QO/GlobalQuality", + "ignoreQualitiesDetails": [ + "Null", + "Good", + "Medium", + "Bad" + ], + "inputObjects": [ + { + "name": "GlobalQuality", + "title": "Global FV0 Quality", + "messageBad": "Inform the FIT on-call immediately", + "messageMedium": "Follow individual check instructions", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty, inform the FIT on-call immediately" + } + ] + }, + { + "name": "Details", + "title": "FV0 DETAILS", + "path": "FV0/QO", + "ignoreQualitiesDetails": [], + "inputObjects": [ + { + "name": "OutOfBunchCollCheck", + "title": "Out of bunch collisions" + }, + { + "name": "CFDinTimeGateCheck", + "title": "CFD in time gate" + }, + { + "name": "CFDinADCgateCheck", + "title": "CDF in ADC gate" + }, + { + "name": "TrgValidationCheck", + "title": "Trigger validation" + } + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:FV0/QO/GlobalQuality/GlobalQuality" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "PostProc": { + "active": "true", + "className": "o2::quality_control_modules::fv0::PostProcTask", + "moduleName": "QcFV0", + "detectorName": "FV0", + "custom": { + "numOrbitsInTF": "32", + "cycleDurationMoName": "CycleDurationNTF", + "timestampSourceLhcIf": "metadata", + "pathDigitQcTask": "FV0/MO/Digits" + }, + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:FV0/MO/Digits/TriggersCorrelation" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "Trending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcFV0", + "detectorName": "FV0", + "dataSources": [ + { + "type": "repository", + "path": "FV0/MO/Digits", + "names": [ + "CycleDuration", + "CycleDurationNTF", + "SumAmpA" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "trend_cycle_duration", + "title": "cycle duration [ns]", + "varexp": "CycleDuration.entries:time", + "selection": "", + "option": "*L" + }, + { + "name": "trend_cycle_duration_ntf", + "title": "cycle duration [TimeFrames]", + "varexp": "CycleDurationNTF.entries:time", + "selection": "", + "option": "*L" + }, + { + "name": "cycle_duration_corr", + "title": "cycle duration: ns/TF;time;cycle duration [ns/TimeFrames]", + "varexp": "CycleDuration.entries/CycleDurationNTF.entries:time", + "selection": "", + "option": "*L" + }, + { + "name": "cycle_duration_ntf_corr", + "title": "TF duration [ns];#TF;TF duration [ns]", + "varexp": "CycleDuration.entries/CycleDurationNTF.entries:CycleDurationNTF.entries", + "selection": "", + "option": "colz" + }, + { + "name": "trend_mean_sumAmpA", + "title": "Mean trend of the sum of amplitudes (TCM)", + "varexp": "SumAmpA.mean:time", + "selection": "", + "option": "*L" + }, + { + "name": "trend_stddev_sumAmpA", + "title": "Stddev trend of the sum of amplitudes (TCM)", + "varexp": "SumAmpA.stddev:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:FV0/MO/Digits/TriggersCorrelation" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} \ No newline at end of file diff --git a/Modules/FIT/FV0/include/FV0/CFDEffCheck.h b/Modules/FIT/FV0/include/FV0/CFDEffCheck.h new file mode 100644 index 0000000000..b7a794a9d7 --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/CFDEffCheck.h @@ -0,0 +1,80 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CFDEffCheck.h +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#ifndef QC_MODULE_FV0_FV0CFDEFFCHECK_H +#define QC_MODULE_FV0_FV0CFDEFFCHECK_H + +#include +#include + +#include "QualityControl/CheckInterface.h" + +#include "FV0Base/Constants.h" +#include "DataFormatsFIT/DeadChannelMap.h" + +namespace o2::quality_control_modules::fv0 +{ + +/// \brief checks if CFD efficiency is below threshold +/// \author Sebastian Bysiak sbysiak@cern.ch +class CFDEffCheck : public o2::quality_control::checker::CheckInterface +{ + public: + CFDEffCheck() = default; + ~CFDEffCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(CFDEffCheck, 2); + + private: + template ::value || + std::is_same::value || (std::is_integral::value && !std::is_same::value)>::type> + auto parseParameters(const std::string& param, const std::string& del) + { + std::regex reg(del); + std::sregex_token_iterator first{ param.begin(), param.end(), reg, -1 }, last; + std::vector vecResult; + if (std::find_if(param.begin(), param.end(), ::isdigit) == param.end()) { + return vecResult; + } + for (auto it = first; it != last; it++) { + if constexpr (std::is_integral::value && !std::is_same::value) { + vecResult.push_back(std::stoi(*it)); + } else if constexpr (std::is_floating_point::value) { + vecResult.push_back(std::stod(*it)); + } else if constexpr (std::is_same::value) { + vecResult.push_back(*it); + } + } + return vecResult; + } + + constexpr static std::size_t sNCHANNELS = o2::fv0::Constants::nFv0Channels; + o2::fit::DeadChannelMap* mDeadChannelMap; + std::string mDeadChannelMapStr; + std::string mPathDeadChannelMap; + float mThreshWarning; + float mThreshError; + int mNumWarnings; + int mNumErrors; +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif // QC_MODULE_FV0_FV0CFDEFFCHECK_H diff --git a/Modules/FIT/FV0/include/FV0/CalibrationTask.h b/Modules/FIT/FV0/include/FV0/CalibrationTask.h new file mode 100644 index 0000000000..186c2b7afb --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/CalibrationTask.h @@ -0,0 +1,67 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibrationTask.h +/// \author Milosz Filus + +#ifndef QUALITYCONTROL_FV0_CALIBRATIONTASK_H +#define QUALITYCONTROL_FV0_CALIBRATIONTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include "TH1.h" +#include "TH2.h" +#include "TTree.h" +#include "TFile.h" +#include "TGraph.h" +#include "TMultiGraph.h" +#include "Rtypes.h" +#include "FV0Calibration/FV0ChannelTimeCalibrationObject.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::fv0 +{ + +class CalibrationTask final : public TaskInterface +{ + public: + /// \brief Constructor + CalibrationTask() = default; + /// Destructor + ~CalibrationTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + static constexpr int CHANNEL_TIME_HISTOGRAM_RANGE = 200; + static constexpr const char* CCDB_PARAM_KEY = "CCDBUrl"; + + // Object which will be published + std::unique_ptr mNotCalibratedChannelTimeHistogram; + std::unique_ptr mCalibratedChannelTimeHistogram; + std::unique_ptr mChannelTimeCalibrationObjectGraph; + std::unique_ptr mCalibratedTimePerChannelHistogram; + std::unique_ptr mNotCalibratedTimePerChannelHistogram; + o2::fv0::FV0ChannelTimeCalibrationObject* mCurrentChannelTimeCalibrationObject; +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif // QUALITYCONTROL_FV0_CALIBRATIONTASK_H diff --git a/Modules/FIT/FV0/include/FV0/ChannelTimeCalibrationCheck.h b/Modules/FIT/FV0/include/FV0/ChannelTimeCalibrationCheck.h new file mode 100644 index 0000000000..ccec910b41 --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/ChannelTimeCalibrationCheck.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ChannelTimeCalibrationCheck.h +/// \author Milosz Filus + +#ifndef QC_MODULE_FV0_FV0CalibrationCheck_H +#define QC_MODULE_FV0_FV0CalibrationCheck_H + +// Quality Control +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::fv0 +{ + +/// \brief +/// +class ChannelTimeCalibrationCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + ChannelTimeCalibrationCheck() = default; + /// Destructor + ~ChannelTimeCalibrationCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + ClassDefOverride(ChannelTimeCalibrationCheck, 2); + + private: + static constexpr const char* MEAN_WARNING_KEY = "MeanWarning"; + static constexpr const char* MEAN_ERROR_KEY = "MeanError"; + static constexpr const char* RMS_WARNING_KEY = "RMSWarning"; + static constexpr const char* RMS_ERROR_KEY = "RMSError"; + static constexpr const char* MIN_ENTRIES_KEY = "MinEntries"; + + private: + double mMeanWarning; + double mMeanError; + double mRMSWarning; + double mRMSError; + int mMinEntries; +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif diff --git a/Modules/FIT/FV0/include/FV0/DigitQcTask.h b/Modules/FIT/FV0/include/FV0/DigitQcTask.h new file mode 100644 index 0000000000..091c78ec72 --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/DigitQcTask.h @@ -0,0 +1,231 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTask.h +/// \author Artur Furs afurs@cern.ch +/// \brief Quality Control DPL Task for FV0's digit visualization + +#ifndef QC_MODULE_FV0_FV0DIGITQCTASK_H +#define QC_MODULE_FV0_FV0DIGITQCTASK_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TH1.h" +#include "TH2.h" +#include "TList.h" +#include "Rtypes.h" + +#include "CommonConstants/LHCConstants.h" + +#include "QualityControl/TaskInterface.h" +#include "QualityControl/QcInfoLogger.h" + +#include "FV0Base/Constants.h" +#include "DataFormatsFV0/Digit.h" +#include "DataFormatsFV0/ChannelData.h" + +#include "FITCommon/DetectorFIT.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::fv0 +{ +class DigitQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + DigitQcTask() : mHashedBitBinPos(fillHashedBitBinPos()), mHashedPairBitBinPos(fillHashedPairBitBinPos()) {} + /// Destructor + ~DigitQcTask() override; + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + constexpr static std::size_t sNCHANNELS_FV0 = o2::fv0::Constants::nFv0Channels; + constexpr static std::size_t sNCHANNELS_FV0_PLUSREF = o2::fv0::Constants::nFv0ChannelsPlusRef; + constexpr static std::size_t sNCHANNELS_FV0_INNER = 24; // "Inner" = 3 inner rings = first 24 channels + constexpr static std::size_t sOrbitsPerTF = 256; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + + constexpr static float sCFDChannel2NS = 0.01302; // CFD channel width in ns + using Detector_t = o2::quality_control_modules::fit::detectorFIT::DetectorFV0; + + private: + // three ways of computing cycle duration: + // 1) number of time frames + // 2) time in ns from InteractionRecord: total range (totalMax - totalMin) + // 3) time in ns from InteractionRecord: sum of each TF duration + // later on choose the best and remove others + double mTimeMinNS = 0.; + double mTimeMaxNS = 0.; + double mTimeCurNS = 0.; + int mTfCounter = 0; + double mTimeSum = 0.; + + long mTFcreationTime = 0; + + int mMinTimeGate = -192; + int mMaxTimeGate = 192; + + template ::value || + std::is_same::value || (std::is_integral::value && !std::is_same::value)>::type> + auto parseParameters(const std::string& param, const std::string& del) + { + std::regex reg(del); + std::sregex_token_iterator first{ param.begin(), param.end(), reg, -1 }, last; + std::vector vecResult; + for (auto it = first; it != last; it++) { + if constexpr (std::is_integral::value && !std::is_same::value) { + vecResult.push_back(std::stoi(*it)); + } else if constexpr (std::is_floating_point::value) { + vecResult.push_back(std::stod(*it)); + } else if constexpr (std::is_same::value) { + vecResult.push_back(*it); + } + } + return vecResult; + } + + void rebinFromConfig(); + unsigned int getModeParameter(std::string, unsigned int, std::map); + int getNumericalParameter(std::string, int); + bool chIsVertexEvent(const o2::fv0::ChannelData, bool simpleCheck = false) const; + static int fpgaDivision(int numerator, int denominator); + + TList* mListHistGarbage; + std::set mSetAllowedChIDs; + std::set mSetAllowedChIDsAmpVsTime; + std::array mStateLastIR2Ch; + std::array mChID2PMhash; // map chID->hashed PM value + uint8_t mTCMhash; // hash value for TCM, and bin position in hist + std::map mMapPMhash2isInner; + + typename Detector_t::TrgMap_t mMapPMbits = Detector_t::sMapPMbits; + typename Detector_t::TrgMap_t mMapTechTrgBits = Detector_t::sMapTechTrgBits; + typename Detector_t::TrgMap_t mMapTrgBits = Detector_t::sMapTrgBits; + + std::unique_ptr mHistNumADC; + std::unique_ptr mHistNumCFD; + + std::map mMapTrgSoftware; + // only for Inner/Outer trigger + enum TrgModeThresholdVar { kAmpl, + kNchannels + }; + enum TrgComparisonResult { kSWonly, + kTCMonly, + kNone, + kBoth + }; + + unsigned int mTrgModeInnerOuterThresholdVar; + // full set of possible parameters: + // to be eliminated after decision about Inner/Outer trigger type + int mTrgThresholdCharge; + int mTrgThresholdChargeOuter; + int mTrgThresholdChargeInner; + int mTrgThresholdNChannels; + int mTrgThresholdNChannelsOuter; + int mTrgThresholdNChannelsInner; + int mTrgChargeLevelLow; + int mTrgChargeLevelHigh; + int mTrgOrGate; + // Timestamp + std::string mMetaAnchorOutput{}; + std::string mTimestampMetaField{}; + + // Objects which will be published + std::unique_ptr mHistAmp2Ch; + std::unique_ptr mHistTime2Ch; + std::unique_ptr mHistEventDensity2Ch; + std::unique_ptr mHistChDataBits; + std::unique_ptr mHistOrbit2BC; + std::unique_ptr mHistBC; + std::unique_ptr mHistNchA; + std::unique_ptr mHistNchC; + std::unique_ptr mHistSumAmpA; + std::unique_ptr mHistSumAmpC; + std::unique_ptr mHistAverageTimeA; + std::unique_ptr mHistAverageTimeC; + std::unique_ptr mHistChannelID; + std::unique_ptr mHistCFDEff; + std::unique_ptr mHistGateTimeRatio2Ch; + // std::unique_ptr mHistTimeSum2Diff; + std::unique_ptr mHistTriggersCorrelation; + std::unique_ptr mHistCycleDuration; + std::unique_ptr mHistCycleDurationNTF; + std::unique_ptr mHistCycleDurationRange; + std::map mMapHistAmpVsTime; + std::unique_ptr mHistBCvsTrg; + std::unique_ptr mHistBCvsFEEmodules; + std::unique_ptr mHistBcVsFeeForOrATrg; + std::unique_ptr mHistBcVsFeeForOrAOutTrg; + std::unique_ptr mHistBcVsFeeForNChanTrg; + std::unique_ptr mHistBcVsFeeForChargeTrg; + std::unique_ptr mHistBcVsFeeForOrAInTrg; + std::unique_ptr mHistOrbitVsTrg; + std::unique_ptr mHistOrbitVsFEEmodules; + std::unique_ptr mHistPmTcmNchA; + std::unique_ptr mHistPmTcmSumAmpA; + std::unique_ptr mHistPmTcmAverageTimeA; + std::unique_ptr mHistTriggersSw; + std::unique_ptr mHistTriggersSoftwareVsTCM; + + // Hashed maps + static const size_t mapSize = 256; + const std::array, mapSize> mHashedBitBinPos; // map with bit position for 1 byte trg signal, for 1 Dim hists; + const std::array>, mapSize> mHashedPairBitBinPos; // map with paired bit position for 1 byte trg signal, for 1 Dim hists; + static std::array, mapSize> fillHashedBitBinPos() + { + std::array, mapSize> hashedBitBinPos{}; + for (int iByteValue = 0; iByteValue < hashedBitBinPos.size(); iByteValue++) { + auto& vec = hashedBitBinPos[iByteValue]; + for (int iBit = 0; iBit < 8; iBit++) { + if (iByteValue & (1 << iBit)) { + vec.push_back(iBit); + } + } + } + return hashedBitBinPos; + } + static std::array>, mapSize> fillHashedPairBitBinPos() + { + const std::array, mapSize> hashedBitBinPos = fillHashedBitBinPos(); + std::array>, mapSize> hashedPairBitBinPos{}; + for (int iByteValue = 0; iByteValue < hashedBitBinPos.size(); iByteValue++) { + const auto& vecBits = hashedBitBinPos[iByteValue]; + auto& vecPairBits = hashedPairBitBinPos[iByteValue]; + for (int iBitFirst = 0; iBitFirst < vecBits.size(); iBitFirst++) { + for (int iBitSecond = iBitFirst; iBitSecond < vecBits.size(); iBitSecond++) { + vecPairBits.push_back({ static_cast(vecBits[iBitFirst]), static_cast(vecBits[iBitSecond]) }); + } + } + } + return hashedPairBitBinPos; + } +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif // QC_MODULE_FV0_FV0DIGITQCTASK_H diff --git a/Modules/FIT/FV0/include/FV0/DigitQcTaskLaser.h b/Modules/FIT/FV0/include/FV0/DigitQcTaskLaser.h new file mode 100644 index 0000000000..46f551008f --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/DigitQcTaskLaser.h @@ -0,0 +1,165 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTaskLaser.h +/// \author Artur Furs afurs@cern.ch, developed further for laser QC by Sandor Lokos (sandor.lokos@cern.ch) +/// \brief Quality Control DPL Task for FT0's digit visualization for laser events only + +#ifndef QC_MODULE_FV0_FV0DIGITQCTASKLASER_H +#define QC_MODULE_FV0_FV0DIGITQCTASKLASER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TH1.h" +#include "TH2.h" +#include "TList.h" +#include "Rtypes.h" + +#include "CommonConstants/LHCConstants.h" + +#include "QualityControl/TaskInterface.h" +#include "QualityControl/QcInfoLogger.h" + +#include "FV0Base/Constants.h" +#include "DataFormatsFV0/Digit.h" +#include "DataFormatsFV0/ChannelData.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::fv0 +{ +class DigitQcTaskLaser final : public TaskInterface +{ + public: + /// \brief Constructor + DigitQcTaskLaser() : mHashedBitBinPos(fillHashedBitBinPos()) {} + /// Destructor + ~DigitQcTaskLaser() override; + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + constexpr static std::size_t sNCHANNELS_FV0 = o2::fv0::Constants::nFv0Channels; + constexpr static std::size_t sNCHANNELS_FV0_PLUSREF = o2::fv0::Constants::nFv0ChannelsPlusRef; + constexpr static std::size_t sNCHANNELS_FV0_INNER = 24; // "Inner" = 3 inner rings = first 24 channels + constexpr static std::size_t sOrbitsPerTF = 256; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + + constexpr static float sCFDChannel2NS = 0.01302; // CFD channel width in ns + + private: + template ::value || + std::is_same::value || + (std::is_integral::value && !std::is_same::value)>::type> + auto parseParameters(const std::string& param, const std::string& del) + { + std::regex reg(del); + std::sregex_token_iterator first{ param.begin(), param.end(), reg, -1 }, last; + std::vector vecResult; + for (auto it = first; it != last; it++) { + if constexpr (std::is_integral::value && !std::is_same::value) { + vecResult.push_back(std::stoi(*it)); + } else if constexpr (std::is_floating_point::value) { + vecResult.push_back(std::stod(*it)); + } else if constexpr (std::is_same::value) { + vecResult.push_back(*it); + } + } + return vecResult; + } + + void rebinFromConfig(); + unsigned int getModeParameter(std::string, unsigned int, std::map); + int getNumericalParameter(std::string, int); + + TList* mListHistGarbage; + std::set mSetAllowedChIDs; + std::set mSetAllowedChIDsAmpVsTime; + std::array mStateLastIR2Ch; + std::array mChID2PMhash; // map chID->hashed PM value + uint8_t mTCMhash; // hash value for TCM, and bin position in hist + std::map mMapDigitTrgNames; + std::map mMapChTrgNames; + std::unique_ptr mHistNumADC; + std::unique_ptr mHistNumCFD; + + std::map mMapTrgSoftware; + // only for Inner/Outer trigger + enum TrgModeThresholdVar { kAmpl, + kNchannels + }; + enum TrgComparisonResult { kSWonly, + kTCMonly, + kNone, + kBoth + }; + + unsigned int mTrgModeInnerOuterThresholdVar; + // full set of possible parameters: + // to be eliminated after decision about Inner/Outer trigger type + int mTrgThresholdCharge; + int mTrgThresholdChargeOuter; + int mTrgThresholdChargeInner; + int mTrgThresholdNChannels; + int mTrgThresholdNChannelsOuter; + int mTrgThresholdNChannelsInner; + + // Objects which will be published + std::unique_ptr mHistAmp2Ch; + std::unique_ptr mHistTime2Ch; + std::unique_ptr mHistChDataBits; + std::unique_ptr mHistBC; + std::unique_ptr mHistCFDEff; + // std::unique_ptr mHistTimeSum2Diff; + std::map mMapHistAmp1D; + std::map mMapHistTime1D; + std::map mMapHistPMbits; + std::map mMapHistAmpVsTime; + std::unique_ptr mHistBCvsFEEmodules; + std::unique_ptr mHistOrbitVsTrg; + std::unique_ptr mHistOrbitVsFEEmodules; + std::unique_ptr mHistTriggersSw; + std::unique_ptr mHistTriggersSoftwareVsTCM; + + // Hashed maps + static const size_t mapSize = 256; + const std::array, mapSize> mHashedBitBinPos; // map with bit position for 1 byte trg signal, for 1 Dim hists; + static std::array, mapSize> fillHashedBitBinPos() + { + std::array, mapSize> hashedBitBinPos{}; + for (int iByteValue = 0; iByteValue < hashedBitBinPos.size(); iByteValue++) { + auto& vec = hashedBitBinPos[iByteValue]; + for (int iBit = 0; iBit < 8; iBit++) { + if (iByteValue & (1 << iBit)) { + vec.push_back(iBit); + } + } + } + return hashedBitBinPos; + } +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif // QC_MODULE_FV0_FV0DIGITQCTASKLASER_H diff --git a/Modules/FIT/FV0/include/FV0/GenericCheck.h b/Modules/FIT/FV0/include/FV0/GenericCheck.h new file mode 100644 index 0000000000..00e3a131e0 --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/GenericCheck.h @@ -0,0 +1,163 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GenericCheck.h +/// \author Sebastian Bysiak +/// + +#ifndef QC_MODULE_FV0_FV0GENERICCHECK_H +#define QC_MODULE_FV0_FV0GENERICCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "QualityControl/QcInfoLogger.h" +#include "FV0Base/Constants.h" +#include "DataFormatsFIT/DeadChannelMap.h" +#include +#include + +namespace o2::quality_control_modules::fv0 +{ + +/// \brief helper class to store acceptable limits for given quantity +/// \author Sebastian Bysiak +class SingleCheck +{ + public: + SingleCheck() = default; + ~SingleCheck() = default; + + SingleCheck(std::string name, float thresholdWarning, float thresholdError, bool shouldBeLower, bool isActive) + { + mCheckName = name; + mThresholdWarning = thresholdWarning; + mThresholdError = thresholdError; + mShouldBeLower = shouldBeLower; + mIsActive = isActive; + mBinNumberX = 0; + }; + bool isActive() { return mIsActive; }; + + void doCheck(Quality& result, float checkedValue) + { + if (!mIsActive) + return; + + std::string log = Form("%s : comparing value = %f with thresholds = %f, %f", mCheckName.c_str(), checkedValue, mThresholdWarning, mThresholdError); + std::string reason; + if (mShouldBeLower) { + if (checkedValue > mThresholdError) { + if (result.isBetterThan(Quality::Bad)) + result.set(Quality::Bad); + reason = Form("%.3f > %.3f (%s error limit)", checkedValue, mThresholdError, mCheckName.c_str()); + log += "-> Bad"; + } else if (checkedValue > mThresholdWarning) { + if (result.isBetterThan(Quality::Medium)) + result.set(Quality::Medium); + reason = Form("%.3f > %.3f (%s warning limit)", checkedValue, mThresholdWarning, mCheckName.c_str()); + log += "-> Medium"; + } else { + log += "-> OK"; + } + } else { + if (checkedValue < mThresholdError) { + if (result.isBetterThan(Quality::Bad)) + result.set(Quality::Bad); + reason = Form("%.3f < %.3f (%s error limit)", checkedValue, mThresholdError, mCheckName.c_str()); + log += "-> Bad"; + } else if (checkedValue < mThresholdWarning) { + if (result.isBetterThan(Quality::Medium)) + result.set(Quality::Medium); + reason = Form("%.3f < %.3f (%s warning limit)", checkedValue, mThresholdWarning, mCheckName.c_str()); + log += "-> Medium"; + } else { + log += "-> OK"; + } + } + + if (reason.length()) { + if (mBinNumberX) { + reason += Form(" for channel %d", mBinNumberX); + } + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), reason); + } + + ILOG(Debug, Support) << log << ENDM; + } + + float getThresholdWarning() + { + return mThresholdWarning; + } + + float getThresholdError() + { + return mThresholdError; + } + + public: + int mBinNumberX; + + private: + std::string mCheckName; + float mThresholdWarning; + float mThresholdError; + bool mShouldBeLower; + bool mIsActive; +}; + +/// \brief checks multiple basic hist statistics +/// \author Sebastian Bysiak +class GenericCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + GenericCheck() = default; + /// Destructor + ~GenericCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(GenericCheck, 2); + + private: + SingleCheck getCheckFromConfig(std::string); + + SingleCheck mCheckMaxThresholdY; + SingleCheck mCheckMinThresholdY; + + SingleCheck mCheckMaxOverflowIntegralRatio; + + SingleCheck mCheckMinMeanX; + SingleCheck mCheckMaxMeanX; + SingleCheck mCheckMaxStddevX; + + SingleCheck mCheckMinMeanY; + SingleCheck mCheckMaxMeanY; + SingleCheck mCheckMaxStddevY; + + SingleCheck mCheckMinGraphLastPoint; + SingleCheck mCheckMaxGraphLastPoint; + + std::array mPositionMsgBox; + std::string mNameObjOnCanvas; + + constexpr static std::size_t sNCHANNELS = o2::fv0::Constants::nFv0Channels; + o2::fit::DeadChannelMap* mDeadChannelMap; + std::string mDeadChannelMapStr; + std::string mPathDeadChannelMap; +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif // QC_MODULE_FV0_FV0GENERICCHECK_H diff --git a/Modules/FIT/FV0/include/FV0/LinkDef.h b/Modules/FIT/FV0/include/FV0/LinkDef.h new file mode 100644 index 0000000000..69751a9930 --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/LinkDef.h @@ -0,0 +1,18 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::fv0::DigitQcTask + ; +#pragma link C++ class o2::quality_control_modules::fv0::PostProcTask + ; +#pragma link C++ class o2::quality_control_modules::fv0::CFDEffCheck + ; +#pragma link C++ class o2::quality_control_modules::fv0::OutOfBunchCollCheck + ; +#pragma link C++ class o2::quality_control_modules::fv0::GenericCheck + ; +#pragma link C++ class o2::quality_control_modules::fv0::TriggersSwVsTcmCheck + ; +#pragma link C++ class o2::quality_control_modules::fv0::OutOfBunchCollFeeModulesCheck + ; +//#pragma link C++ class o2::quality_control_modules::fv0::CalibrationTask + ; +//#pragma link C++ class o2::quality_control_modules::fv0::ChannelTimeCalibrationCheck + ; + +#pragma link C++ class o2::quality_control_modules::fv0::DigitQcTaskLaser + ; +#pragma link C++ class o2::quality_control_modules::fv0::TH1ReductorLaser + ; +#endif diff --git a/Modules/FIT/FV0/include/FV0/OutOfBunchCollCheck.h b/Modules/FIT/FV0/include/FV0/OutOfBunchCollCheck.h new file mode 100644 index 0000000000..46787b2985 --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/OutOfBunchCollCheck.h @@ -0,0 +1,56 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file OutOfBunchCollCheck.h +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#ifndef QC_MODULE_FV0_FV0OUTOFBUNCHCOLLCHECK_H +#define QC_MODULE_FV0_FV0OUTOFBUNCHCOLLCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "CommonConstants/LHCConstants.h" + +namespace o2::quality_control_modules::fv0 +{ + +/// \brief Checks what fraction of collisions is out of bunch +/// \author Sebastian Bysiak sbysiak@cern.ch +class OutOfBunchCollCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + OutOfBunchCollCheck() = default; + /// Destructor + ~OutOfBunchCollCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + + ClassDefOverride(OutOfBunchCollCheck, 2); + + private: + float mFractionOutOfBunchColl; + int mNumNonEmptyBins; + float mThreshWarning; + float mThreshError; + int mBinPos; + std::string mTrgName; + bool mEnableMessage{ true }; +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif // QC_MODULE_FV0_FV0OUTOFBUNCHCOLLCHECK_H diff --git a/Modules/FIT/FV0/include/FV0/OutOfBunchCollFeeModulesCheck.h b/Modules/FIT/FV0/include/FV0/OutOfBunchCollFeeModulesCheck.h new file mode 100644 index 0000000000..e2c1c4eba8 --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/OutOfBunchCollFeeModulesCheck.h @@ -0,0 +1,50 @@ +// Copyright 2023 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file OutOfBunchCollFeeModulesCheck.h +/// \author Dawid Skora dawid.mateusz.skora@cern.ch +/// + +#ifndef QC_MODULE_FV0_OUTOFBUNCHCOLLFEEMODULESCHECK_H +#define QC_MODULE_FV0_OUTOFBUNCHCOLLFEEMODULESCHECK_H + +#include "QualityControl/CheckInterface.h" + +#include "FV0Base/Constants.h" +#include "CommonConstants/LHCConstants.h" + +#include + +namespace o2::quality_control_modules::fv0 +{ + +class OutOfBunchCollFeeModulesCheck : public o2::quality_control::checker::CheckInterface +{ + public: + OutOfBunchCollFeeModulesCheck() = default; + ~OutOfBunchCollFeeModulesCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(OutOfBunchCollFeeModulesCheck, 2); + + private: + float mThreshWarning; + float mThreshError; + float mFractionOutOfBunchColl = 0; + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif // QC_MODULE_FV0_OUTOFBUNCHCOLLFEEMODULESCHECK_H diff --git a/Modules/FIT/FV0/include/FV0/PostProcTask.h b/Modules/FIT/FV0/include/FV0/PostProcTask.h new file mode 100644 index 0000000000..cb2c48d9a6 --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/PostProcTask.h @@ -0,0 +1,127 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcTask.h +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#ifndef QC_MODULE_FV0_POSTPROCTASK_H +#define QC_MODULE_FV0_POSTPROCTASK_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/DatabaseInterface.h" +#include "CCDB/CcdbApi.h" +#include "CommonConstants/LHCConstants.h" + +#include "FV0Base/Constants.h" +#include "DataFormatsFV0/ChannelData.h" +#include "DataFormatsFV0/Digit.h" + +#include "FITCommon/DetectorFIT.h" + +#include +#include +#include +#include +#include + +class TH1F; +class TCanvas; +class TLegend; +class TProfile; + +namespace o2::quality_control_modules::fv0 +{ + +/// \brief Basic Postprocessing Task for FV0, computes among others the trigger rates +/// \author Sebastian Bysiak sbysiak@cern.ch +class PostProcTask final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + PostProcTask() = default; + ~PostProcTask() override; + void configure(const boost::property_tree::ptree&) override; + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + constexpr static std::size_t sBCperOrbit = o2::constants::lhc::LHCMaxBunches; + constexpr static std::size_t sNCHANNELS_PM = o2::fv0::Constants::nFv0ChannelsPlusRef; + using Detector_t = o2::quality_control_modules::fit::detectorFIT::DetectorFV0; + + private: + std::string mPathGrpLhcIf; + std::string mPathDigitQcTask; + std::string mCycleDurationMoName; + std::string mCcdbUrl; + std::string mTimestampSourceLhcIf; + int mNumOrbitsInTF; + const unsigned int mNumTriggers = 5; + + typename Detector_t::TrgMap_t mMapPMbits = Detector_t::sMapPMbits; + typename Detector_t::TrgMap_t mMapTechTrgBits = Detector_t::sMapTechTrgBits; + typename Detector_t::TrgMap_t mMapTrgBits = Detector_t::sMapTrgBits; + + o2::quality_control::repository::DatabaseInterface* mDatabase = nullptr; + o2::ccdb::CcdbApi mCcdbApi; + + std::unique_ptr mRateOrA; + std::unique_ptr mRateOrAout; + std::unique_ptr mRateOrAin; + std::unique_ptr mRateTrgCharge; + std::unique_ptr mRateTrgNchan; + std::unique_ptr mHistChDataNegBits; + std::unique_ptr mHistTriggers; + + std::unique_ptr mHistTimeInWindow; + std::unique_ptr mHistCFDEff; + std::unique_ptr mHistTrgValidation; + + std::unique_ptr mRatesCanv; + TProfile* mAmpl = nullptr; + TProfile* mTime = nullptr; + + // if storage size matters it can be replaced with TH1 + // and TH2 can be created based on it on the fly, but only TH1 would be stored + std::unique_ptr mHistBcPattern; + std::unique_ptr mHistBcPatternFee; + std::unique_ptr mHistBcTrgOutOfBunchColl; + std::unique_ptr mHistBcFeeOutOfBunchColl; + std::unique_ptr mHistBcFeeOutOfBunchCollForOrATrg; + std::unique_ptr mHistBcFeeOutOfBunchCollForOrAOutTrg; + std::unique_ptr mHistBcFeeOutOfBunchCollForNChanTrg; + std::unique_ptr mHistBcFeeOutOfBunchCollForChargeTrg; + std::unique_ptr mHistBcFeeOutOfBunchCollForOrAInTrg; + + uint8_t mTCMhash; + std::array mChID2PMhash; // map chID->hashed PM value + std::map mMapPMhash2isInner; + std::map mMapTrgHistBC; + std::map mMapFEE2hash; + + int mLowTimeThreshold{ -192 }; + int mUpTimeThreshold{ 192 }; + std::string mTimestampMetaField{ "timestampTF" }; + // + void setTimestampToMOs(long long timestamp); + // TO REMOVE + std::vector mVecChannelIDs{}; + std::vector mVecHistsToDecompose{}; + using HistDecomposed_t = TH1D; + using MapHistsDecomposed_t = std::map>>; + MapHistsDecomposed_t mMapHistsToDecompose{}; + void decomposeHists(quality_control::postprocessing::Trigger trg); +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif // QC_MODULE_FV0_POSTPROCTASK_H diff --git a/Modules/FIT/FV0/include/FV0/TH1ReductorLaser.h b/Modules/FIT/FV0/include/FV0/TH1ReductorLaser.h new file mode 100644 index 0000000000..2a15030910 --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/TH1ReductorLaser.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright +// holders. All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1ReductorLaser.h +/// \author Piotr Konopka, developed to laser QC by Sandor Lokos +/// (sandor.lokos@cern.ch) +/// +#ifndef QUALITYCONTROL_TH1REDUCTORLASER_H +#define QUALITYCONTROL_TH1REDUCTORLASER_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::fv0 +{ + +/// \brief A Reductor which obtains the most popular characteristics of TH1. +/// +/// A Reductor which obtains the most popular characteristics of TH1. +/// It produces a branch in the format: "mean/D:stddev:entries" +class TH1ReductorLaser : public quality_control::postprocessing::ReductorTObject +{ + public: + TH1ReductorLaser() = default; + ~TH1ReductorLaser() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + Double_t mean; + Double_t mean1fit; + Double_t mean2fit; + Double_t stddev; + Double_t stddev1fit; + Double_t stddev2fit; + Double_t entries; + } mStats; +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif // QUALITYCONTROL_TH1REDUCTORLASER_H \ No newline at end of file diff --git a/Modules/FIT/FV0/include/FV0/TriggersSwVsTcmCheck.h b/Modules/FIT/FV0/include/FV0/TriggersSwVsTcmCheck.h new file mode 100644 index 0000000000..4ceb0a70bd --- /dev/null +++ b/Modules/FIT/FV0/include/FV0/TriggersSwVsTcmCheck.h @@ -0,0 +1,46 @@ +// Copyright 2023 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TriggersSwVsTcmCheck.h +/// \author Dawid Skora dawid.mateusz.skora@cern.ch +/// + +#ifndef QC_MODULE_FV0_TRIGGERSSWVSTCMCHECK_H +#define QC_MODULE_FV0_TRIGGERSSWVSTCMCHECK_H + +#include "QualityControl/CheckInterface.h" + +#include "FV0Base/Constants.h" + +namespace o2::quality_control_modules::fv0 +{ + +/// \brief This check provide xnor (exclusive nor) operation on SW and HW triggers +/// \author Dawid Skora dawid.mateusz.skora@cern.ch +class TriggersSwVsTcmCheck : public o2::quality_control::checker::CheckInterface +{ + public: + TriggersSwVsTcmCheck() = default; + ~TriggersSwVsTcmCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(TriggersSwVsTcmCheck, 2); + + private: + std::array mPositionMsgBox; +}; + +} // namespace o2::quality_control_modules::fv0 + +#endif // QC_MODULE_FV0_TRIGGERSSWVSTCMCHECK_H diff --git a/Modules/FIT/FV0/src/CFDEffCheck.cxx b/Modules/FIT/FV0/src/CFDEffCheck.cxx new file mode 100644 index 0000000000..04a986d3b0 --- /dev/null +++ b/Modules/FIT/FV0/src/CFDEffCheck.cxx @@ -0,0 +1,194 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CFDEffCheck.cxx +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#include "FV0/CFDEffCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fv0 +{ + +void CFDEffCheck::configure() +{ + if (auto param = mCustomParameters.find("thresholdWarning"); param != mCustomParameters.end()) { + mThreshWarning = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdWarning = " << mThreshWarning << ENDM; + } else { + mThreshWarning = 0.9; + ILOG(Debug, Support) << "configure() : using default thresholdWarning = " << mThreshWarning << ENDM; + } + + if (auto param = mCustomParameters.find("thresholdError"); param != mCustomParameters.end()) { + mThreshError = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdError = " << mThreshError << ENDM; + } else { + mThreshError = 0.8; + ILOG(Debug, Support) << "configure() : using default thresholdError = " << mThreshError << ENDM; + } + + if (auto param = mCustomParameters.find("deadChannelMap"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + std::vector deadChannelVec = parseParameters(chIDs, del); + + mDeadChannelMap = new o2::fit::DeadChannelMap(); + for (uint8_t chId = 0; chId < sNCHANNELS; ++chId) { + if (std::find(deadChannelVec.begin(), deadChannelVec.end(), chId) != deadChannelVec.end()) + mDeadChannelMap->setChannelAlive(chId, 0); + else + mDeadChannelMap->setChannelAlive(chId, 1); + } + ILOG(Warning, Support) << "configure() : using deadChannelMap from config (superseding the one from CCDB)" << ENDM; + } else { + if (auto param = mCustomParameters.find("ccdbUrl"); param != mCustomParameters.end()) { + setCcdbUrl(param->second); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, configured url = " << param->second << ENDM; + } else { + setCcdbUrl("o2-ccdb.internal"); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, default url = " + << "o2-ccdb.internal" << ENDM; + } + if (auto param = mCustomParameters.find("pathDeadChannelMap"); param != mCustomParameters.end()) { + mPathDeadChannelMap = param->second; + ILOG(Debug, Support) << "configure() : using pathDeadChannelMap: " << mPathDeadChannelMap << ENDM; + } else { + mPathDeadChannelMap = "FV0/Calib/DeadChannelMap"; + ILOG(Debug, Support) << "configure() : using default pathDeadChannelMap: " << mPathDeadChannelMap << ENDM; + } + + // WARNING: always uses last available dead channel map + // supply deadChannelMap by hand when running offline + mDeadChannelMap = retrieveConditionAny(mPathDeadChannelMap); + if (!mDeadChannelMap || !mDeadChannelMap->map.size()) { + ILOG(Error, Support) << "object \"" << mPathDeadChannelMap << "\" NOT retrieved (or empty). All channels assumed to be alive!" << ENDM; + mDeadChannelMap = new o2::fit::DeadChannelMap(); + for (uint8_t chId = 0; chId < sNCHANNELS; ++chId) { + mDeadChannelMap->setChannelAlive(chId, 1); + } + } + } + mDeadChannelMapStr = ""; + for (unsigned chId = 0; chId < mDeadChannelMap->map.size(); chId++) { + if (!mDeadChannelMap->isChannelAlive(chId)) { + mDeadChannelMapStr += (mDeadChannelMapStr.empty() ? "" : ",") + std::to_string(chId); + } + } + if (mDeadChannelMapStr.empty()) + mDeadChannelMapStr = "EMPTY"; + ILOG(Info, Support) << "Loaded dead channel map: " << mDeadChannelMapStr << ENDM; +} + +Quality CFDEffCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "CFD_efficiency") { + auto* h = dynamic_cast(mo->getObject()); + + result = Quality::Good; + mNumErrors = 0; + mNumWarnings = 0; + for (uint8_t chId = 0; chId < h->GetNbinsX(); chId++) { + if (chId >= sNCHANNELS) + continue; + if (!mDeadChannelMap->isChannelAlive(chId)) + continue; + if (h->GetBinContent(chId + 1) < mThreshError) { + if (result.isBetterThan(Quality::Bad)) + // result = Quality::Bad; // setting quality like this clears reasons + result.set(Quality::Bad); + mNumErrors++; + result.addFlag(FlagTypeFactory::Unknown(), + "CFD eff. < \"Error\" threshold in channel " + std::to_string(chId)); + // no need to check medium threshold + // but don't `break` because we want to add other reasons + continue; + } else if (h->GetBinContent(chId + 1) < mThreshWarning) { + if (result.isBetterThan(Quality::Medium)) + result.set(Quality::Medium); + mNumWarnings++; + result.addFlag(FlagTypeFactory::Unknown(), + "CFD eff. < \"Warning\" threshold in channel " + std::to_string(chId)); + } + } + } + } + result.addMetadata("nErrors", std::to_string(mNumErrors)); + result.addMetadata("nWarnings", std::to_string(mNumWarnings)); + return result; +} + +void CFDEffCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "CFD_efficiency") { + auto* h = dynamic_cast(mo->getObject()); + + TPaveText* msg = new TPaveText(0.15, 0.2, 0.85, 0.45, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + if (mDeadChannelMapStr != "EMPTY") + msg->AddText(("Dead channel IDs: " + mDeadChannelMapStr).c_str()); + msg->AddText(Form("N channels with warning (< %.3f) = %d", mThreshWarning, mNumWarnings)); + msg->AddText(Form("N channels with error (< %.3f) = %d", mThreshError, mNumErrors)); + + if (checkResult == Quality::Good) { + msg->AddText(">> Quality::Good <<"); + msg->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + auto reasons = checkResult.getFlags(); + msg->SetFillColor(kRed); + msg->AddText(">> Quality::Bad <<"); + } else if (checkResult == Quality::Medium) { + auto reasons = checkResult.getFlags(); + msg->SetFillColor(kOrange); + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Null) { + msg->AddText(">> Quality::Null <<"); + msg->SetFillColor(kGray); + } + // add threshold lines + Double_t xMin = h->GetXaxis()->GetXmin(); + Double_t xMax = h->GetXaxis()->GetXmax(); + auto* lineError = new TLine(xMin, mThreshError, xMax, mThreshError); + auto* lineWarning = new TLine(xMin, mThreshWarning, xMax, mThreshWarning); + lineError->SetLineWidth(3); + lineWarning->SetLineWidth(3); + lineError->SetLineStyle(kDashed); + lineWarning->SetLineStyle(kDashed); + lineError->SetLineColor(kRed); + lineWarning->SetLineColor(kOrange); + h->GetListOfFunctions()->Add(lineError); + h->GetListOfFunctions()->Add(lineWarning); + h->SetStats(1); + } +} + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/src/CalibrationTask.cxx b/Modules/FIT/FV0/src/CalibrationTask.cxx new file mode 100644 index 0000000000..45d28edd49 --- /dev/null +++ b/Modules/FIT/FV0/src/CalibrationTask.cxx @@ -0,0 +1,128 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibrationTask.cxx +/// \author Milosz Filus +/// + +#include "TCanvas.h" +#include "TH1.h" +#include "TGraph.h" + +#include "QualityControl/QcInfoLogger.h" +#include "FV0/CalibrationTask.h" +#include "DataFormatsFV0/Digit.h" +#include "DataFormatsFV0/ChannelData.h" +#include "FV0Base/Constants.h" +#include +#include "DetectorsCalibration/Utils.h" +#include "CCDB/BasicCCDBManager.h" +#include "FITCalibration/FITCalibrationApi.h" + +namespace o2::quality_control_modules::fv0 +{ + +CalibrationTask::~CalibrationTask() +{ +} + +void CalibrationTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize CalibrationTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + constexpr std::size_t Nchannels_FV0 = o2::fv0::Constants::nFv0Channels; // 48 // hsharma + mNotCalibratedChannelTimeHistogram = std::make_unique("Not_calibrated_time", "Not_calibrated_time", 2 * CHANNEL_TIME_HISTOGRAM_RANGE, -CHANNEL_TIME_HISTOGRAM_RANGE, CHANNEL_TIME_HISTOGRAM_RANGE); + mCalibratedChannelTimeHistogram = std::make_unique("Calibrated_time", "Calibrated_time", 2 * CHANNEL_TIME_HISTOGRAM_RANGE, -CHANNEL_TIME_HISTOGRAM_RANGE, CHANNEL_TIME_HISTOGRAM_RANGE); + mCalibratedTimePerChannelHistogram = std::make_unique("Calibrated_time_per_channel", "Calibrated_time_per_channel", Nchannels_FV0, 0, Nchannels_FV0, 2 * CHANNEL_TIME_HISTOGRAM_RANGE, -CHANNEL_TIME_HISTOGRAM_RANGE, CHANNEL_TIME_HISTOGRAM_RANGE); + mNotCalibratedTimePerChannelHistogram = std::make_unique("Not_calibrated_time_per_channel", "Not_calibrated_time_per_channel", Nchannels_FV0, 0, Nchannels_FV0, 2 * CHANNEL_TIME_HISTOGRAM_RANGE, -CHANNEL_TIME_HISTOGRAM_RANGE, CHANNEL_TIME_HISTOGRAM_RANGE); + mChannelTimeCalibrationObjectGraph = std::make_unique(Nchannels_FV0); + mChannelTimeCalibrationObjectGraph->SetName("Channel_time_calibration_object"); + mChannelTimeCalibrationObjectGraph->SetTitle("Channel_time_calibration_object"); + mChannelTimeCalibrationObjectGraph->SetMarkerStyle(20); + mChannelTimeCalibrationObjectGraph->SetLineColor(kWhite); + mChannelTimeCalibrationObjectGraph->SetFillColor(kBlack); + getObjectsManager()->startPublishing(mNotCalibratedChannelTimeHistogram.get()); + getObjectsManager()->startPublishing(mCalibratedChannelTimeHistogram.get()); + getObjectsManager()->startPublishing(mCalibratedTimePerChannelHistogram.get()); + getObjectsManager()->startPublishing(mNotCalibratedTimePerChannelHistogram.get()); + getObjectsManager()->startPublishing(mChannelTimeCalibrationObjectGraph.get()); + ccdb::BasicCCDBManager::instance().setURL(mCustomParameters.at(CCDB_PARAM_KEY)); +} + +void CalibrationTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity" << activity.mId << ENDM; + mNotCalibratedChannelTimeHistogram->Reset(); + mCalibratedChannelTimeHistogram->Reset(); + mCalibratedTimePerChannelHistogram->Reset(); + mNotCalibratedTimePerChannelHistogram->Reset(); +} + +void CalibrationTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + mNotCalibratedChannelTimeHistogram->Reset(); + mCalibratedChannelTimeHistogram->Reset(); + mCalibratedTimePerChannelHistogram->Reset(); + mNotCalibratedTimePerChannelHistogram->Reset(); + mCurrentChannelTimeCalibrationObject = ccdb::BasicCCDBManager::instance().get(o2::fit::FITCalibrationApi::getObjectPath()); + for (std::size_t chID = 0; chID < o2::fv0::Constants::nFv0Channels; ++chID) { + if (mCurrentChannelTimeCalibrationObject) { + mChannelTimeCalibrationObjectGraph->SetPoint(chID, chID, mCurrentChannelTimeCalibrationObject->mTimeOffsets[chID]); + } else { + mChannelTimeCalibrationObjectGraph->SetPoint(chID, chID, 0); + } + } +} + +void CalibrationTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + + auto channels = ctx.inputs().get>("channels"); + + for (auto& channel : channels) { + if (mCurrentChannelTimeCalibrationObject) { + // mCalibratedChannelTimeHistogram->Fill(channel.time + mCurrentChannelTimeCalibrationObject->mTimeOffsets[channel.getChannelID()]); + // mCalibratedTimePerChannelHistogram->Fill(channel.getChannelID(), channel.time + mCurrentChannelTimeCalibrationObject->mTimeOffsets[channel.getChannelID()]); + mCalibratedChannelTimeHistogram->Fill(channel.CFDTime - mCurrentChannelTimeCalibrationObject->mTimeOffsets[channel.getChannelID()]); + mCalibratedTimePerChannelHistogram->Fill(channel.getChannelID(), channel.CFDTime - mCurrentChannelTimeCalibrationObject->mTimeOffsets[channel.getChannelID()]); + } else { + mCalibratedChannelTimeHistogram->Fill(channel.CFDTime); + mCalibratedTimePerChannelHistogram->Fill(channel.getChannelID(), channel.CFDTime); + } + mNotCalibratedChannelTimeHistogram->Fill(channel.CFDTime); + mNotCalibratedTimePerChannelHistogram->Fill(channel.getChannelID(), channel.CFDTime); + // std::cout << "-------++++++++++++++++++++++----------++++++++++++++------------_+++++++++++++++++ " << std::endl; + // std::cout << channel.time << std::endl; + // std::cout << "-------++++++++++++++++++++++----------++++++++++++++------------_+++++++++++++++++ " << std::endl; + } +} + +void CalibrationTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void CalibrationTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void CalibrationTask::reset() +{ + // clean all the monitor objects here + mNotCalibratedChannelTimeHistogram->Reset(); + mCalibratedChannelTimeHistogram->Reset(); + mCalibratedTimePerChannelHistogram->Reset(); + mNotCalibratedTimePerChannelHistogram->Reset(); +} + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/src/ChannelTimeCalibrationCheck.cxx b/Modules/FIT/FV0/src/ChannelTimeCalibrationCheck.cxx new file mode 100644 index 0000000000..24a1c9a7ee --- /dev/null +++ b/Modules/FIT/FV0/src/ChannelTimeCalibrationCheck.cxx @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ChannelTimeCalibrationCheck.cxx +/// \author Milosz Filus +/// + +#include +#include +#include +#include "FV0/ChannelTimeCalibrationCheck.h" +#include "FV0Base/Constants.h" + +namespace o2::quality_control_modules::fv0 +{ + +void ChannelTimeCalibrationCheck::configure() +{ + mMeanWarning = std::stod(mCustomParameters.at(MEAN_WARNING_KEY)); + mMeanError = std::stod(mCustomParameters.at(MEAN_ERROR_KEY)); + mRMSWarning = std::stod(mCustomParameters.at(RMS_WARNING_KEY)); + mRMSError = std::stod(mCustomParameters.at(RMS_ERROR_KEY)); + mMinEntries = std::stoi(mCustomParameters.at(MIN_ENTRIES_KEY)); +} + +Quality ChannelTimeCalibrationCheck::check(std::map>* moMap) +{ + Quality currentQuality = Quality::Bad; + for (auto [name, obj] : *moMap) { + (void)name; + if (obj->getName() == "Calibrated_time_per_channel") { + currentQuality = Quality::Good; + auto histogram = dynamic_cast(obj->getObject()); + for (size_t chID = 0; chID < o2::fv0::Constants::nFv0Channels; ++chID) { + auto channelProjection = histogram->ProjectionY(("Times per channel: " + std::to_string(chID)).c_str(), chID, chID); + if (channelProjection->GetEntries() < mMinEntries) { + return Quality::Bad; + } + if (std::fabs(channelProjection->GetMean()) > mMeanError) { + return Quality::Bad; + } else if (std::fabs(channelProjection->GetMean()) > mMeanWarning) { + currentQuality = Quality::Medium; + } + if (channelProjection->GetRMS() > mRMSError) { + return Quality::Bad; + } else if (channelProjection->GetRMS() > mRMSWarning) { + currentQuality = Quality::Medium; + } + } + } + } + return currentQuality; +} + +void ChannelTimeCalibrationCheck::beautify(std::shared_ptr mo, Quality quality) +{ + auto* h = dynamic_cast(mo->getObject()); + auto* tInfo = new TText(); + + if (Quality::Good == quality) { + tInfo->SetText(0.2, 0.8, "Calibration Quality = GOOD"); + tInfo->SetTextColor(kGreen); + } else if (Quality::Medium == quality) { + tInfo->SetText(0.2, 0.8, "Calibration Quality = MEDIUM"); + tInfo->SetTextColor(kYellow); + } else if (Quality::Bad == quality) { + tInfo->SetText(0.2, 0.8, "Calibration Quality = BAD"); + tInfo->SetTextColor(kRed); + } + tInfo->SetTextSize(23); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo); +} + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/src/DigitQcTask.cxx b/Modules/FIT/FV0/src/DigitQcTask.cxx new file mode 100644 index 0000000000..26ef96b4b4 --- /dev/null +++ b/Modules/FIT/FV0/src/DigitQcTask.cxx @@ -0,0 +1,715 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTask.cxx +/// \author Artur Furs afurs@cern.ch +/// modified by Sebastin Bysiak sbysiak@cern.ch + +#include "FV0/DigitQcTask.h" + +#include "TCanvas.h" +#include "TROOT.h" + +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsFIT/Triggers.h" +#include "Framework/InputRecord.h" +#include "Framework/TimingInfo.h" +#include "DataFormatsFV0/LookUpTable.h" +#include "Common/Utils.h" + +#include "FITCommon/HelperHist.h" +#include "FITCommon/HelperCommon.h" + +namespace o2::quality_control_modules::fv0 +{ +using namespace o2::quality_control_modules::fit; +DigitQcTask::~DigitQcTask() +{ + delete mListHistGarbage; +} + +void DigitQcTask::rebinFromConfig() +{ + /* Examples: + "binning_SumAmpC": "100, 0, 100" + "binning_BcOrbitMap_TrgOrA": "25, 0, 256, 10, 0, 3564" + hashtag = all channel IDs (mSetAllowedChIDs), e.g. + "binning_Amp_channel#": "5,-10,90" + is equivalent to: + "binning_Amp_channel0": "5,-10,90" + "binning_Amp_channel1": "5,-10,90" + "binning_Amp_channel2": "5,-10,90" ... + */ + auto rebinHisto = [](std::string hName, std::string binning) { + if (!gROOT->FindObject(hName.data())) { + ILOG(Warning) << "config: histogram named \"" << hName << "\" not found" << ENDM; + return; + } + std::vector tokenizedBinning; + boost::split(tokenizedBinning, binning, boost::is_any_of(",")); + if (tokenizedBinning.size() == 3) { // TH1 + ILOG(Debug) << "config: rebinning TH1 " << hName << " -> " << binning << ENDM; + auto htmp = (TH1F*)gROOT->FindObject(hName.data()); + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str())); + } else if (tokenizedBinning.size() == 6) { // TH2 + auto htmp = (TH2F*)gROOT->FindObject(hName.data()); + ILOG(Debug) << "config: rebinning TH2 " << hName << " -> " << binning << ENDM; + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str()), + std::atof(tokenizedBinning[3].c_str()), std::atof(tokenizedBinning[4].c_str()), std::atof(tokenizedBinning[5].c_str())); + } else { + ILOG(Warning) << "config: invalid binning parameter: " << hName << " -> " << binning << ENDM; + } + }; + + const std::string rebinKeyword = "binning"; + const char* channelIdPlaceholder = "#"; + try { + for (auto& param : mCustomParameters.getAllDefaults()) { + if (param.first.rfind(rebinKeyword, 0) != 0) + continue; + std::string hName = param.first.substr(rebinKeyword.length() + 1); + std::string binning = param.second.c_str(); + if (hName.find(channelIdPlaceholder) != std::string::npos) { + for (const auto& chID : mSetAllowedChIDs) { + std::string hNameCur = hName.substr(0, hName.find(channelIdPlaceholder)) + std::to_string(chID) + hName.substr(hName.find(channelIdPlaceholder) + 1); + rebinHisto(hNameCur, binning); + } + } else { + rebinHisto(hName, binning); + } + } + } catch (std::out_of_range& oor) { + ILOG(Error) << "Cannot access the default custom parameters : " << oor.what() << ENDM; + } +} + +unsigned int DigitQcTask::getModeParameter(std::string paramName, unsigned int defaultVal, std::map choices) +{ + if (auto param = mCustomParameters.find(paramName); param != mCustomParameters.end()) { + // if parameter was provided check which option was chosen + for (const auto& choice : choices) { + if (param->second == choice.second) { + ILOG(Debug, Support) << "setting \"" << paramName << "\" to: \"" << choice.second << "\"" << ENDM; + return choice.first; + } + } + // param value not allowed - use default but with warning + std::string allowedValues; + for (const auto& choice : choices) { + allowedValues += "\""; + allowedValues += choice.second; + allowedValues += "\", "; + } + ILOG(Warning, Support) << "Provided value (\"" << param->second << "\") for parameter \"" << paramName << "\" is not allowed. Allowed values are: " << allowedValues << " setting \"" << paramName << "\" to default value: \"" << choices[defaultVal] << "\"" << ENDM; + return defaultVal; + } else { + // param not provided - use default + ILOG(Debug, Support) << "Setting \"" << paramName << "\" to default value: \"" << choices[defaultVal] << "\"" << ENDM; + return defaultVal; + } +} + +int DigitQcTask::getNumericalParameter(std::string paramName, int defaultVal) +{ + if (auto param = mCustomParameters.find(paramName); param != mCustomParameters.end()) { + float val = stoi(param->second); + ILOG(Debug, Support) << "Setting \"" << paramName << "\" to: " << val << ENDM; + return val; + } else { + ILOG(Debug, Support) << "Setting \"" << paramName << "\" to default value: " << defaultVal << ENDM; + return defaultVal; + } +} + +bool DigitQcTask::chIsVertexEvent(const o2::fv0::ChannelData chd, bool simpleCheck) const +{ + if (simpleCheck) { + return chd.getFlag(o2::fv0::ChannelData::kIsEventInTVDC); + } else { + return (chd.getFlag(o2::fv0::ChannelData::kIsCFDinADCgate) && + !(chd.getFlag(o2::fv0::ChannelData::kIsTimeInfoNOTvalid) || chd.getFlag(o2::fv0::ChannelData::kIsTimeInfoLate) || chd.getFlag(o2::fv0::ChannelData::kIsTimeInfoLost)) && + std::abs(static_cast(chd.CFDTime)) < mTrgOrGate && + static_cast(chd.QTCAmpl) > mTrgChargeLevelLow && + static_cast(chd.QTCAmpl) < mTrgChargeLevelHigh); + } +} + +int DigitQcTask::fpgaDivision(int numerator, int denominator) +{ + // content of the ROM + int rom7x17[] = { 16383, 8192, 5461, 4096, 3277, 2731, 2341, 2048, 1820, 1638, 1489, 1365, 1260, 1170, 1092, 1024, 964, 910, 862, 819, 780, 745, 712, 683, 655, 630, 607, 585, 565, 546, 529, 512, 496, 482, 468, 455, 443, 431, 420, 410, 400, 390, 381, 372, 364, 356, 349, 341, 334, 328, 321, 315, 309, 303, 298, 293, 287, 282, 278, 273, 269, 264, 260, 256, 252, 248, 245, 241, 237, 234, 231, 228, 224, 221, 218, 216, 213, 210, 207, 205, 202, 200, 197, 195, 193, 191, 188, 186, 184, 182, 180, 178, 176, 174, 172, 171, 169, 167, 165, 164, 162, 161, 159, 158, 156, 155, 153, 152, 150, 149, 148, 146, 145, 144, 142, 141, 140, 139, 138, 137, 135, 134, 133, 132, 131, 130, 129 }; + + // return result of the operation (numerator / denominator) + return denominator ? (numerator * rom7x17[denominator - 1]) >> 14 : 0; +} + +void DigitQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize DigitQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + mStateLastIR2Ch = {}; + mMapTrgSoftware.insert({ o2::fit::Triggers::bitA, false }); + mMapTrgSoftware.insert({ o2::fit::Triggers::bitAOut, false }); + mMapTrgSoftware.insert({ o2::fit::Triggers::bitTrgNchan, false }); + mMapTrgSoftware.insert({ o2::fit::Triggers::bitTrgCharge, false }); + mMapTrgSoftware.insert({ o2::fit::Triggers::bitAIn, false }); + + mTrgModeInnerOuterThresholdVar = getModeParameter("trgModeInnerOuterThresholdVar", + TrgModeThresholdVar::kNchannels, + { { TrgModeThresholdVar::kAmpl, "Ampl" }, + { TrgModeThresholdVar::kNchannels, "Nchannels" } }); + + mTrgOrGate = getNumericalParameter("trgOrGate", 153); + mTrgChargeLevelLow = getNumericalParameter("trgChargeLevelLow", 0); + mTrgChargeLevelHigh = getNumericalParameter("trgChargeLevelHigh", 4095); + mTrgThresholdCharge = getNumericalParameter("trgThresholdCharge", 1); + mTrgThresholdNChannels = getNumericalParameter("trgThresholdNChannels", 1); + mMinTimeGate = getNumericalParameter("minGateTimeForRatioHistogram", -192); + mMaxTimeGate = getNumericalParameter("maxGateTimeForRatioHistogram", 192); + + if (mTrgModeInnerOuterThresholdVar == TrgModeThresholdVar::kAmpl) { + mTrgThresholdChargeInner = getNumericalParameter("trgThresholdChargeInner", 1); + mTrgThresholdChargeOuter = getNumericalParameter("trgThresholdChargeOuter", 1); + } else if (mTrgModeInnerOuterThresholdVar == TrgModeThresholdVar::kNchannels) { + mTrgThresholdNChannelsInner = getNumericalParameter("trgThresholdNChannelsInner", 1); + mTrgThresholdNChannelsOuter = getNumericalParameter("trgThresholdNChannelsOuter", 1); + } + + mHistTime2Ch = std::make_unique("TimePerChannel", "Time vs Channel;Channel;Time", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF, 4100, -2050, 2050); + mHistTime2Ch->SetOption("colz"); + mHistAmp2Ch = std::make_unique("AmpPerChannel", "Amplitude vs Channel;Channel;Amp", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF, 4200, -100, 4100); + mHistAmp2Ch->SetOption("colz"); + mHistBC = std::make_unique("BC", "BC;BC;counts;", sBCperOrbit, 0, sBCperOrbit); + mHistChDataBits = std::make_unique("ChannelDataBits", "ChannelData bits per ChannelID;Channel;Bit", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF, mMapPMbits.size(), 0, mMapPMbits.size()); + mHistChDataBits->SetOption("colz"); + for (const auto& entry : mMapPMbits) { + mHistChDataBits->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + } + mHistOrbitVsTrg = std::make_unique("OrbitVsTriggers", "Orbit vs Triggers;Orbit;Trg", sOrbitsPerTF, 0, sOrbitsPerTF, mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + mHistOrbitVsTrg->SetOption("colz"); + mHistOrbit2BC = std::make_unique("OrbitPerBC", "BC-Orbit map;Orbit;BC;", sOrbitsPerTF, 0, sOrbitsPerTF, sBCperOrbit, 0, sBCperOrbit); + mHistOrbit2BC->SetOption("colz"); + mHistEventDensity2Ch = std::make_unique("EventDensityPerChannel", "Event density(in BC) per Channel;Channel;BC;", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF, 10000, 0, 1e5); + mHistEventDensity2Ch->SetOption("colz"); + mHistTriggersCorrelation = std::make_unique("TriggersCorrelation", "Correlation of triggers from TCM", mMapTechTrgBits.size(), 0, mMapTechTrgBits.size(), mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + mHistTriggersCorrelation->SetOption("colz"); + mHistBCvsTrg = std::make_unique("BCvsTriggers", "BC vs Triggers;BC;Trg", sBCperOrbit, 0, sBCperOrbit, mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + mHistBCvsTrg->SetOption("colz"); + mHistPmTcmNchA = std::make_unique("PmTcmNumChannelsA", "Comparison of num. channels A from PM and TCM;Number of channels(TCM), side A;PM - TCM", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF, 2 * sNCHANNELS_FV0_PLUSREF + 1, -int(sNCHANNELS_FV0_PLUSREF) - 0.5, int(sNCHANNELS_FV0_PLUSREF) + 0.5); + mHistPmTcmSumAmpA = std::make_unique("PmTcmSumAmpA", "Comparison of sum of amplitudes A from PM and TCM;Sum of amplitudes(TCM), side A;PM - TCM", 2e2, 0, 1e4, 2e3, -1e3 - 0.5, 1e3 - 0.5); + mHistPmTcmAverageTimeA = std::make_unique("PmTcmAverageTimeA", "Comparison of average time A from PM and TCM;Average time(TCM), side A;PM - TCM", 410, -2050, 2050, 820, -410 - 0.5, 410 - 0.5); + mHistTriggersSw = std::make_unique("TriggersSoftware", "Triggers from software", mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + + const std::map mapTrgValidationStatus = { + { TrgComparisonResult::kSWonly, "Sw only" }, + { TrgComparisonResult::kTCMonly, "TCM only" }, + { TrgComparisonResult::kNone, "neither TCM nor Sw" }, + { TrgComparisonResult::kBoth, "both TCM and Sw" } + }; + mHistTriggersSoftwareVsTCM = helper::registerHist(getObjectsManager(), PublicationPolicy::Forever, "COLZ", "TriggersSoftwareVsTCM", "Comparison of triggers from software and TCM;;Trigger name", mMapTrgBits, mapTrgValidationStatus); + for (const auto& entry : mMapTechTrgBits) { + mHistOrbitVsTrg->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistTriggersCorrelation->GetXaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistTriggersCorrelation->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistBCvsTrg->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistTriggersSw->GetXaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + } + mHistTriggersSw->GetXaxis()->SetRange(1, 5); + + mListHistGarbage = new TList(); + mListHistGarbage->SetOwner(kTRUE); + + std::map mapFEE2hash; + const auto& lut = o2::fv0::SingleLUT::Instance().getVecMetadataFEE(); + auto lutSorted = lut; + std::sort(lutSorted.begin(), lutSorted.end(), [](const auto& first, const auto& second) { return first.mModuleName < second.mModuleName; }); + uint8_t binPos{ 0 }; + for (const auto& lutEntry : lutSorted) { + const auto& moduleName = lutEntry.mModuleName; + const auto& moduleType = lutEntry.mModuleType; + const auto& strChID = lutEntry.mChannelID; + const auto& pairIt = mapFEE2hash.insert({ moduleName, binPos }); + if (pairIt.second) { + binPos++; + } + if (std::regex_match(strChID, std::regex("[[\\d]{1,3}"))) { + int chID = std::stoi(strChID); + if (chID < sNCHANNELS_FV0_PLUSREF) { + mChID2PMhash[chID] = mapFEE2hash[moduleName]; + mMapPMhash2isInner.insert({ mapFEE2hash[moduleName], chID < sNCHANNELS_FV0_INNER }); + } else { + LOG(error) << "Incorrect LUT entry: chID " << strChID << " | " << moduleName; + } + } else if (moduleType != "TCM") { + LOG(error) << "Non-TCM module w/o numerical chID: chID " << strChID << " | " << moduleName; + } else if (moduleType == "TCM") { + mTCMhash = mapFEE2hash[moduleName]; + } + } + + mHistBCvsFEEmodules = std::make_unique("BCvsFEEmodules", "BC vs FEE module;BC;FEE", sBCperOrbit, 0, sBCperOrbit, mapFEE2hash.size(), 0, mapFEE2hash.size()); + mHistBcVsFeeForOrATrg = std::make_unique("BCvsFEEmodulesForOrATrg", "BC vs FEE module for OrA trigger;BC;FEE", sBCperOrbit, 0, sBCperOrbit, mapFEE2hash.size(), 0, mapFEE2hash.size()); + mHistBcVsFeeForOrAOutTrg = std::make_unique("BCvsFEEmodulesForOrAOutTrg", "BC vs FEE module for OrAOut trigger;BC;FEE", sBCperOrbit, 0, sBCperOrbit, mapFEE2hash.size(), 0, mapFEE2hash.size()); + mHistBcVsFeeForNChanTrg = std::make_unique("BCvsFEEmodulesForNChanTrg", "BC vs FEE module for NChan trigger;BC;FEE", sBCperOrbit, 0, sBCperOrbit, mapFEE2hash.size(), 0, mapFEE2hash.size()); + mHistBcVsFeeForChargeTrg = std::make_unique("BCvsFEEmodulesForChargeTrg", "BC vs FEE module for Charge trigger;BC;FEE", sBCperOrbit, 0, sBCperOrbit, mapFEE2hash.size(), 0, mapFEE2hash.size()); + mHistBcVsFeeForOrAInTrg = std::make_unique("BCvsFEEmodulesForOrAInTrg", "BC vs FEE module for OrAIn trigger;BC;FEE", sBCperOrbit, 0, sBCperOrbit, mapFEE2hash.size(), 0, mapFEE2hash.size()); + mHistOrbitVsFEEmodules = std::make_unique("OrbitVsFEEmodules", "Orbit vs FEE module;Orbit;FEE", sOrbitsPerTF, 0, sOrbitsPerTF, mapFEE2hash.size(), 0, mapFEE2hash.size()); + for (const auto& entry : mapFEE2hash) { + mHistBCvsFEEmodules->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcVsFeeForOrATrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcVsFeeForOrAOutTrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcVsFeeForNChanTrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcVsFeeForChargeTrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcVsFeeForOrAInTrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistOrbitVsFEEmodules->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + } + // mHistTimeSum2Diff = std::make_unique("timeSumVsDiff", "time A/C side: sum VS diff;(TOC-TOA)/2 [ns];(TOA+TOC)/2 [ns]", 400, -52.08, 52.08, 400, -52.08, 52.08); // range of 52.08 ns = 4000*13.02ps = 4000 channels + mHistNumADC = std::make_unique("HistNumADC", "HistNumADC", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF); + mHistNumCFD = std::make_unique("HistNumCFD", "HistNumCFD", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF); + mHistCFDEff = std::make_unique("CFD_efficiency", "Fraction of events with CFD in ADC gate vs ChannelID;ChannelID;Event fraction with CFD in ADC gate", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF); + mHistNchA = std::make_unique("NumChannelsA", "Number of channels(TCM), side A;Nch", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF); + // mHistNchC = std::make_unique("NumChannelsC", "Number of channels(TCM), side C;Nch", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF); + mHistSumAmpA = std::make_unique("SumAmpA", "Sum of amplitudes(TCM), side A;", 1e4, 0, 1e4); + // mHistSumAmpC = std::make_unique("SumAmpC", "Sum of amplitudes(TCM), side C;", 1e4, 0, 1e4); + mHistAverageTimeA = std::make_unique("AverageTimeA", "Average time(TCM), side A", 4100, -2050, 2050); + // mHistAverageTimeC = std::make_unique("AverageTimeC", "Average time(TCM), side C", 4100, -2050, 2050); + mHistChannelID = std::make_unique("StatChannelID", "ChannelID statistics;ChannelID", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF); + mHistCycleDuration = std::make_unique("CycleDuration", "Cycle Duration;;time [ns]", 1, 0, 2); + mHistCycleDurationNTF = std::make_unique("CycleDurationNTF", "Cycle Duration;;time [TimeFrames]", 1, 0, 2); + mHistCycleDurationRange = std::make_unique("CycleDurationRange", "Cycle Duration (total cycle range);;time [ns]", 1, 0, 2); + mHistGateTimeRatio2Ch = std::make_unique("EventsInGateTime", "Fraction of events with CFD in time gate vs ChannelID;ChannelID;Event fraction with CFD in time gate", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF); + + std::vector vecChannelIDs; + if (auto param = mCustomParameters.find("ChannelIDs"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDs = parseParameters(chIDs, del); + } + for (const auto& entry : vecChannelIDs) { + mSetAllowedChIDs.insert(entry); + } + std::vector vecChannelIDsAmpVsTime; + if (auto param = mCustomParameters.find("ChannelIDsAmpVsTime"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDsAmpVsTime = parseParameters(chIDs, del); + } + for (const auto& entry : vecChannelIDsAmpVsTime) { + mSetAllowedChIDsAmpVsTime.insert(entry); + } + + for (const auto& chID : mSetAllowedChIDsAmpVsTime) { + auto pairHistAmpVsTime = mMapHistAmpVsTime.insert({ chID, new TH2F(Form("Amp_vs_time_channel%i", chID), Form("Amplitude vs time, channel %i;Amp;Time", chID), 420, -100, 4100, 410, -2050, 2050) }); + if (pairHistAmpVsTime.second) { + mListHistGarbage->Add(pairHistAmpVsTime.first->second); + getObjectsManager()->startPublishing(pairHistAmpVsTime.first->second); + } + } + + rebinFromConfig(); // after all histos are created + // 1-dim hists + getObjectsManager()->startPublishing(mHistGateTimeRatio2Ch.get()); + getObjectsManager()->startPublishing(mHistCFDEff.get()); + getObjectsManager()->startPublishing(mHistBC.get()); + getObjectsManager()->startPublishing(mHistNchA.get()); + // getObjectsManager()->startPublishing(mHistNchC.get()); + getObjectsManager()->startPublishing(mHistSumAmpA.get()); + // getObjectsManager()->startPublishing(mHistSumAmpC.get()); + getObjectsManager()->startPublishing(mHistAverageTimeA.get()); + // getObjectsManager()->startPublishing(mHistAverageTimeC.get()); + getObjectsManager()->startPublishing(mHistChannelID.get()); + getObjectsManager()->startPublishing(mHistCycleDuration.get()); + getObjectsManager()->startPublishing(mHistCycleDurationNTF.get()); + getObjectsManager()->startPublishing(mHistCycleDurationRange.get()); + getObjectsManager()->startPublishing(mHistTriggersSw.get()); + // 2-dim hists + getObjectsManager()->startPublishing(mHistTime2Ch.get()); + getObjectsManager()->setDefaultDrawOptions(mHistTime2Ch.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistAmp2Ch.get()); + getObjectsManager()->setDefaultDrawOptions(mHistAmp2Ch.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBCvsFEEmodules.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBCvsFEEmodules.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcVsFeeForOrATrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBcVsFeeForOrATrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcVsFeeForOrAOutTrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBcVsFeeForOrAOutTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcVsFeeForNChanTrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBcVsFeeForNChanTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcVsFeeForChargeTrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBcVsFeeForChargeTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcVsFeeForOrAInTrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBcVsFeeForOrAInTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistOrbitVsTrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistOrbitVsTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistOrbitVsFEEmodules.get()); + getObjectsManager()->setDefaultDrawOptions(mHistOrbitVsFEEmodules.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistChDataBits.get()); + getObjectsManager()->setDefaultDrawOptions(mHistChDataBits.get(), "COLZ"); + // getObjectsManager()->startPublishing(mHistTimeSum2Diff.get()); + // getObjectsManager()->setDefaultDrawOptions(mHistTimeSum2Diff.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistOrbit2BC.get()); + getObjectsManager()->setDefaultDrawOptions(mHistOrbit2BC.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBCvsTrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBCvsTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistEventDensity2Ch.get()); + getObjectsManager()->setDefaultDrawOptions(mHistEventDensity2Ch.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistPmTcmNchA.get()); + getObjectsManager()->setDefaultDrawOptions(mHistPmTcmNchA.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistPmTcmSumAmpA.get()); + getObjectsManager()->setDefaultDrawOptions(mHistPmTcmSumAmpA.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistPmTcmAverageTimeA.get()); + getObjectsManager()->setDefaultDrawOptions(mHistPmTcmAverageTimeA.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistTriggersCorrelation.get()); + getObjectsManager()->setDefaultDrawOptions(mHistTriggersCorrelation.get(), "COLZ"); + + for (int i = 0; i < getObjectsManager()->getNumberPublishedObjects(); i++) { + TH1* obj = dynamic_cast(getObjectsManager()->getMonitorObject(i)->getObject()); + obj->SetTitle((std::string("FV0 ") + obj->GetTitle()).c_str()); + } + // Timestamp + mMetaAnchorOutput = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "metaAnchorOutput", "CycleDurationNTF"); + mTimestampMetaField = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "timestampMetaField", "timestampTF"); +} + +void DigitQcTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity" << activity.mId << ENDM; + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistBC->Reset(); + mHistChDataBits->Reset(); + mHistGateTimeRatio2Ch->Reset(); + mHistCFDEff->Reset(); + mHistNumADC->Reset(); + mHistNumCFD->Reset(); + // mHistTimeSum2Diff->Reset(); + mHistBCvsFEEmodules->Reset(); + mHistBcVsFeeForOrATrg->Reset(); + mHistBcVsFeeForOrAOutTrg->Reset(); + mHistBcVsFeeForNChanTrg->Reset(); + mHistBcVsFeeForChargeTrg->Reset(); + mHistBcVsFeeForOrAInTrg->Reset(); + mHistOrbitVsTrg->Reset(); + mHistOrbitVsFEEmodules->Reset(); + mHistTriggersCorrelation->Reset(); + mHistCycleDuration->Reset(); + mHistCycleDurationNTF->Reset(); + mHistCycleDurationRange->Reset(); + mHistBCvsTrg->Reset(); + mHistOrbit2BC->Reset(); + mHistEventDensity2Ch->Reset(); + mHistNchA->Reset(); + // mHistNchC->Reset(); + mHistSumAmpA->Reset(); + // mHistSumAmpC->Reset(); + mHistAverageTimeA->Reset(); + // mHistAverageTimeC->Reset(); + mHistChannelID->Reset(); + mHistPmTcmNchA->Reset(); + mHistPmTcmSumAmpA->Reset(); + mHistPmTcmAverageTimeA->Reset(); + mHistTriggersSw->Reset(); + mHistTriggersSoftwareVsTCM->Reset(); + for (auto& entry : mMapHistAmpVsTime) { + entry.second->Reset(); + } +} + +void DigitQcTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + mTimeMinNS = -1; + mTimeMaxNS = 0.; + mTimeCurNS = 0.; + mTfCounter = 0; + mTimeSum = 0.; +} + +void DigitQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + mTFcreationTime = ctx.services().get().creation; + + mTfCounter++; + auto channels = ctx.inputs().get>("channels"); + auto digits = ctx.inputs().get>("digits"); + if (digits.size() > 0) { + // Considering that Digit container is already sorted by IR + const o2::InteractionRecord& firstIR = digits[0].getIntRecord(); + const o2::InteractionRecord& lastIR = digits[digits.size() - 1].getIntRecord(); + auto timeMinNS = firstIR.bc2ns(); + auto timeMaxNS = lastIR.bc2ns(); + mTimeMinNS = std::min(mTimeMinNS, timeMinNS); // to be sure if somehow TFID is unordered + mTimeMaxNS = std::max(mTimeMaxNS, timeMaxNS); + mTimeSum += timeMaxNS - timeMinNS; + } + for (auto& digit : digits) { + // Exclude all BCs, in which laser signals are expected (and trigger outputs are blocked) + if (digit.mTriggers.getOutputsAreBlocked()) { + continue; + } + const auto& vecChData = digit.getBunchChannelData(channels); + bool isTCM = true; + if (digit.mTriggers.getTimeA() == o2::fit::Triggers::DEFAULT_TIME && digit.mTriggers.getTimeC() == o2::fit::Triggers::DEFAULT_TIME) { + isTCM = false; + } + mHistOrbit2BC->Fill(digit.getIntRecord().orbit % sOrbitsPerTF, digit.getIntRecord().bc); + mHistBC->Fill(digit.getBC()); + + std::set setFEEmodules{}; + // reset triggers + for (auto& entry : mMapTrgSoftware) { + mMapTrgSoftware[entry.first] = false; + } + Int_t pmSumAmplIn = 0; + Int_t pmSumAmplOut = 0; + Int_t pmNChanIn = 0; + Int_t pmNChanOut = 0; + Int_t pmSumTime = 0; + Int_t pmAverTime = 0; + + std::map mapPMhash2sumAmpl; + for (const auto& entry : mMapPMhash2isInner) { + mapPMhash2sumAmpl.insert({ entry.first, 0 }); + } + + for (const auto& chData : vecChData) { + mHistTime2Ch->Fill(static_cast(chData.ChId), static_cast(chData.CFDTime)); + mHistAmp2Ch->Fill(static_cast(chData.ChId), static_cast(chData.QTCAmpl)); + mHistEventDensity2Ch->Fill(static_cast(chData.ChId), static_cast(digit.mIntRecord.differenceInBC(mStateLastIR2Ch[chData.ChId]))); + mStateLastIR2Ch[chData.ChId] = digit.mIntRecord; + mHistChannelID->Fill(chData.ChId); + if (chData.QTCAmpl > 0) { + mHistNumADC->Fill(chData.ChId); + } + mHistNumCFD->Fill(chData.ChId); + if (mSetAllowedChIDsAmpVsTime.size() != 0 && mSetAllowedChIDsAmpVsTime.find(static_cast(chData.ChId)) != mSetAllowedChIDsAmpVsTime.end()) { + mMapHistAmpVsTime[chData.ChId]->Fill(chData.QTCAmpl, chData.CFDTime); + } + for (const auto& binPos : mHashedBitBinPos[chData.ChainQTC]) { + mHistChDataBits->Fill(chData.ChId, binPos); + } + + setFEEmodules.insert(mChID2PMhash[chData.ChId]); + + if (chData.ChId >= sNCHANNELS_FV0) { // skip reference PMT + continue; + } + + if (chIsVertexEvent(chData, true)) { + pmSumTime += chData.CFDTime; + if (chData.ChId < sNCHANNELS_FV0_INNER) { + pmNChanIn++; + } else { + pmNChanOut++; + } + } + if (chData.getFlag(o2::fv0::ChannelData::kIsCFDinADCgate)) { + mapPMhash2sumAmpl[mChID2PMhash[static_cast(chData.ChId)]] += static_cast(chData.QTCAmpl); + } + } // channel data loop + + for (const auto& entry : mapPMhash2sumAmpl) { + if (mMapPMhash2isInner[entry.first]) + pmSumAmplIn += static_cast(entry.second >> 3); + else + pmSumAmplOut += static_cast(entry.second >> 3); + } + + auto pmNChan = pmNChanIn + pmNChanOut; + auto pmSumAmpl = pmSumAmplIn + pmSumAmplOut; + if (isTCM) { + pmAverTime = fpgaDivision(pmSumTime, pmNChan); + } else { + pmAverTime = o2::fit::Triggers::DEFAULT_TIME; + } + + if (isTCM) { + setFEEmodules.insert(mTCMhash); + } + for (const auto& feeHash : setFEEmodules) { + mHistBCvsFEEmodules->Fill(static_cast(digit.getIntRecord().bc), static_cast(feeHash)); + if (digit.mTriggers.getOrA()) + mHistBcVsFeeForOrATrg->Fill(static_cast(digit.getIntRecord().bc), static_cast(feeHash)); + if (digit.mTriggers.getOrAOut()) + mHistBcVsFeeForOrAOutTrg->Fill(static_cast(digit.getIntRecord().bc), static_cast(feeHash)); + if (digit.mTriggers.getTrgNChan()) + mHistBcVsFeeForNChanTrg->Fill(static_cast(digit.getIntRecord().bc), static_cast(feeHash)); + if (digit.mTriggers.getTrgCharge()) + mHistBcVsFeeForChargeTrg->Fill(static_cast(digit.getIntRecord().bc), static_cast(feeHash)); + if (digit.mTriggers.getOrAIn()) + mHistBcVsFeeForOrAInTrg->Fill(static_cast(digit.getIntRecord().bc), static_cast(feeHash)); + mHistOrbitVsFEEmodules->Fill(static_cast(digit.getIntRecord().orbit % sOrbitsPerTF), static_cast(feeHash)); + } + + if (isTCM && digit.mTriggers.getDataIsValid() && !digit.mTriggers.getOutputsAreBlocked()) { + if (digit.mTriggers.getNChanA() > 0) { + mHistNchA->Fill(digit.mTriggers.getNChanA()); + mHistSumAmpA->Fill(digit.mTriggers.getAmplA()); + mHistAverageTimeA->Fill(digit.mTriggers.getTimeA()); + } + mHistPmTcmNchA->Fill(digit.mTriggers.getNChanA(), pmNChan - digit.mTriggers.getNChanA()); + mHistPmTcmSumAmpA->Fill(digit.mTriggers.getAmplA(), pmSumAmpl - digit.mTriggers.getAmplA()); + mHistPmTcmAverageTimeA->Fill(digit.mTriggers.getTimeA(), pmAverTime - digit.mTriggers.getTimeA()); + + for (const auto& binPos : mHashedPairBitBinPos[digit.mTriggers.getTriggersignals()]) { + mHistTriggersCorrelation->Fill(binPos.first, binPos.second); + } + for (const auto& binPos : mHashedBitBinPos[digit.mTriggers.getTriggersignals()]) { + mHistBCvsTrg->Fill(digit.getIntRecord().bc, binPos); + mHistOrbitVsTrg->Fill(digit.getIntRecord().orbit % sOrbitsPerTF, binPos); + } + + // triggers re-computation + mMapTrgSoftware[o2::fit::Triggers::bitA] = pmNChan > 0; + mMapTrgSoftware[o2::fit::Triggers::bitTrgNchan] = pmNChan > mTrgThresholdNChannels; + mMapTrgSoftware[o2::fit::Triggers::bitTrgCharge] = pmSumAmpl > 2 * mTrgThresholdCharge; + + if (mTrgModeInnerOuterThresholdVar == TrgModeThresholdVar::kAmpl) { + mMapTrgSoftware[o2::fit::Triggers::bitAIn] = pmSumAmplIn > 2 * mTrgThresholdChargeInner; + mMapTrgSoftware[o2::fit::Triggers::bitAOut] = pmSumAmplOut > 2 * mTrgThresholdChargeOuter; + } else if (mTrgModeInnerOuterThresholdVar == TrgModeThresholdVar::kNchannels) { + mMapTrgSoftware[o2::fit::Triggers::bitAIn] = pmNChanIn > mTrgThresholdNChannelsInner; + mMapTrgSoftware[o2::fit::Triggers::bitAOut] = pmNChanOut > mTrgThresholdNChannelsOuter; + } + + for (const auto& entry : mMapTrgSoftware) { + if (entry.second) + mHistTriggersSw->Fill(entry.first); + bool isTCMFired = digit.mTriggers.getTriggersignals() & (1 << entry.first); + bool isSwFired = entry.second; + if (!isTCMFired && isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kSWonly); + else if (isTCMFired && !isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kTCMonly); + else if (!isTCMFired && !isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kNone); + else if (isTCMFired && isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kBoth); + + if (isTCMFired != isSwFired) { + auto msg = Form( + "Software does not reproduce TCM decision! \n \ + trigger name: %s\n \ + TCM / SW: \n \ + hasFired = %d / %d \n \ + nChannelsA = %d / %d \n \ + nChannelsInner = -- / %d \n \ + nChannelsOuter = -- / %d \n \ + timeA = %d / %d \n \ + sumTimeA = -- / %d \n \ + sumAmplA = %d / %d \n \ + sumAmplIn = %d / %d \n \ + sumAmplOut = %d / %d \n \ + TCM bits = %d / -- \n", + mMapTechTrgBits[entry.first].c_str(), + isTCMFired, isSwFired, + digit.mTriggers.getNChanA(), pmNChan, + pmNChanIn, + pmNChanOut, + digit.mTriggers.getTimeA(), pmAverTime, + pmSumTime, + digit.mTriggers.getAmplA(), pmSumAmpl, + digit.mTriggers.getAmplC(), pmSumAmplIn, + digit.mTriggers.getAmplA() - digit.mTriggers.getAmplC(), pmSumAmplOut, + digit.mTriggers.getTriggersignals()); + ILOG(Debug, Support) << msg << ENDM; + } + } + // end of triggers re-computation + } + } // digit loop +} + +void DigitQcTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + // add TF creation time for further match with filling scheme in PP in case of offline running + ILOG(Debug, Support) << "adding last TF creation time: " << mTFcreationTime << ENDM; + getObjectsManager()->getMonitorObject(mMetaAnchorOutput)->addOrUpdateMetadata(mTimestampMetaField, std::to_string(mTFcreationTime)); + + for (int channel = 1; channel <= sNCHANNELS_FV0_PLUSREF - 1; channel++) { + float events_in_range = 0; + float events_per_channel = 0; + for (int bin_on_y_axis = 1; bin_on_y_axis <= mHistTime2Ch->GetNbinsY(); bin_on_y_axis++) { + if (mHistTime2Ch->GetYaxis()->GetBinLowEdge(bin_on_y_axis) > mMinTimeGate && mHistTime2Ch->GetYaxis()->GetBinLowEdge(bin_on_y_axis) < mMaxTimeGate) { + events_in_range += mHistTime2Ch->GetBinContent(channel, bin_on_y_axis); + } + events_per_channel += mHistTime2Ch->GetBinContent(channel, bin_on_y_axis); + } + if (events_per_channel) { + mHistGateTimeRatio2Ch->SetBinContent(channel, events_in_range / events_per_channel); + } else { + mHistGateTimeRatio2Ch->SetBinContent(channel, 0); + } + } + + // one has to set num. of entries manually because + // default TH1Reductor gets only mean,stddev and entries (no integral) + mHistCFDEff->Divide(mHistNumADC.get(), mHistNumCFD.get()); + mHistCycleDurationRange->SetBinContent(1., mTimeMaxNS - mTimeMinNS); + mHistCycleDurationRange->SetEntries(mTimeMaxNS - mTimeMinNS); + mHistCycleDurationNTF->SetBinContent(1., mTfCounter); + mHistCycleDurationNTF->SetEntries(mTfCounter); + mHistCycleDuration->SetBinContent(1., mTimeSum); + mHistCycleDuration->SetEntries(mTimeSum); + ILOG(Debug) << "Cycle duration: NTF=" << mTfCounter << ", range = " << (mTimeMaxNS - mTimeMinNS) / 1e6 / mTfCounter << " ms/TF, sum = " << mTimeSum / 1e6 / mTfCounter << " ms/TF" << ENDM; +} + +void DigitQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void DigitQcTask::reset() +{ + // clean all the monitor objects here + mHistGateTimeRatio2Ch->Reset(); + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistBC->Reset(); + mHistChDataBits->Reset(); + mHistCFDEff->Reset(); + mHistNumADC->Reset(); + mHistNumCFD->Reset(); + // mHistTimeSum2Diff->Reset(); + mHistOrbit2BC->Reset(); + mHistEventDensity2Ch->Reset(); + mHistNchA->Reset(); + // mHistNchC->Reset(); + mHistSumAmpA->Reset(); + // mHistSumAmpC->Reset(); + mHistAverageTimeA->Reset(); + // mHistAverageTimeC->Reset(); + mHistChannelID->Reset(); + mHistTriggersCorrelation->Reset(); + mHistCycleDuration->Reset(); + mHistCycleDurationNTF->Reset(); + mHistCycleDurationRange->Reset(); + mHistBCvsTrg->Reset(); + mHistBCvsFEEmodules->Reset(); + mHistBcVsFeeForOrATrg->Reset(); + mHistBcVsFeeForOrAOutTrg->Reset(); + mHistBcVsFeeForNChanTrg->Reset(); + mHistBcVsFeeForChargeTrg->Reset(); + mHistBcVsFeeForOrAInTrg->Reset(); + mHistOrbitVsTrg->Reset(); + mHistOrbitVsFEEmodules->Reset(); + mHistPmTcmNchA->Reset(); + mHistPmTcmSumAmpA->Reset(); + mHistPmTcmAverageTimeA->Reset(); + mHistTriggersSw->Reset(); + mHistTriggersSoftwareVsTCM->Reset(); + for (auto& entry : mMapHistAmpVsTime) { + entry.second->Reset(); + } +} + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/src/DigitQcTaskLaser.cxx b/Modules/FIT/FV0/src/DigitQcTaskLaser.cxx new file mode 100644 index 0000000000..accad6b878 --- /dev/null +++ b/Modules/FIT/FV0/src/DigitQcTaskLaser.cxx @@ -0,0 +1,520 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitQcTaskLaser.cxx +/// \brief Based on the existing DigitQcTask +/// \author Sandor Lokos sandor.lokos@cern.ch + +#include "FV0/DigitQcTaskLaser.h" + +#include "TCanvas.h" +#include "TROOT.h" + +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsFIT/Triggers.h" +#include "Framework/InputRecord.h" +#include "DataFormatsFV0/LookUpTable.h" + +namespace o2::quality_control_modules::fv0 +{ + +DigitQcTaskLaser::~DigitQcTaskLaser() +{ + delete mListHistGarbage; +} + +void DigitQcTaskLaser::rebinFromConfig() +{ + /* Examples: + "binning_SumAmpC": "100, 0, 100" + "binning_BcOrbitMap_TrgOrA": "25, 0, 256, 10, 0, 3564" + hashtag = all channel IDs (mSetAllowedChIDs), e.g. + "binning_Amp_channel#": "5,-10,90" + is equivalent to: + "binning_Amp_channel0": "5,-10,90" + "binning_Amp_channel1": "5,-10,90" + "binning_Amp_channel2": "5,-10,90" ... + */ + auto rebinHisto = [](std::string hName, std::string binning) { + if (!gROOT->FindObject(hName.data())) { + ILOG(Warning) << "config: histogram named \"" << hName << "\" not found" << ENDM; + return; + } + std::vector tokenizedBinning; + boost::split(tokenizedBinning, binning, boost::is_any_of(",")); + if (tokenizedBinning.size() == 3) { // TH1 + ILOG(Debug) << "config: rebinning TH1 " << hName << " -> " << binning << ENDM; + auto htmp = (TH1F*)gROOT->FindObject(hName.data()); + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str())); + } else if (tokenizedBinning.size() == 6) { // TH2 + auto htmp = (TH2F*)gROOT->FindObject(hName.data()); + ILOG(Debug) << "config: rebinning TH2 " << hName << " -> " << binning << ENDM; + htmp->SetBins(std::atof(tokenizedBinning[0].c_str()), std::atof(tokenizedBinning[1].c_str()), std::atof(tokenizedBinning[2].c_str()), + std::atof(tokenizedBinning[3].c_str()), std::atof(tokenizedBinning[4].c_str()), std::atof(tokenizedBinning[5].c_str())); + } else { + ILOG(Warning) << "config: invalid binning parameter: " << hName << " -> " << binning << ENDM; + } + }; + + const std::string rebinKeyword = "binning"; + const char* channelIdPlaceholder = "#"; + try { + for (auto& param : mCustomParameters.getAllDefaults()) { + if (param.first.rfind(rebinKeyword, 0) != 0) + continue; + std::string hName = param.first.substr(rebinKeyword.length() + 1); + std::string binning = param.second.c_str(); + if (hName.find(channelIdPlaceholder) != std::string::npos) { + for (const auto& chID : mSetAllowedChIDs) { + std::string hNameCur = hName.substr(0, hName.find(channelIdPlaceholder)) + std::to_string(chID) + hName.substr(hName.find(channelIdPlaceholder) + 1); + rebinHisto(hNameCur, binning); + } + } else { + rebinHisto(hName, binning); + } + } + } catch (std::out_of_range& oor) { + ILOG(Error) << "Cannot access the default custom parameters : " << oor.what() << ENDM; + } +} + +unsigned int DigitQcTaskLaser::getModeParameter(std::string paramName, unsigned int defaultVal, std::map choices) +{ + if (auto param = mCustomParameters.find(paramName); param != mCustomParameters.end()) { + // if parameter was provided check which option was chosen + for (const auto& choice : choices) { + if (param->second == choice.second) { + ILOG(Debug, Support) << "setting \"" << paramName << "\" to: \"" << choice.second << "\"" << ENDM; + return choice.first; + } + } + // param value not allowed - use default but with warning + std::string allowedValues; + for (const auto& choice : choices) { + allowedValues += "\""; + allowedValues += choice.second; + allowedValues += "\", "; + } + ILOG(Warning, Support) << "Provided value (\"" << param->second << "\") for parameter \"" << paramName << "\" is not allowed. Allowed values are: " << allowedValues << " setting \"" << paramName << "\" to default value: \"" << choices[defaultVal] << "\"" << ENDM; + return defaultVal; + } else { + // param not provided - use default + ILOG(Debug, Support) << "Setting \"" << paramName << "\" to default value: \"" << choices[defaultVal] << "\"" << ENDM; + return defaultVal; + } +} + +int DigitQcTaskLaser::getNumericalParameter(std::string paramName, int defaultVal) +{ + if (auto param = mCustomParameters.find(paramName); param != mCustomParameters.end()) { + float val = stoi(param->second); + ILOG(Debug, Support) << "Setting \"" << paramName << "\" to: " << val << ENDM; + return val; + } else { + ILOG(Debug, Support) << "Setting \"" << paramName << "\" to default value: " << defaultVal << ENDM; + return defaultVal; + } +} + +void DigitQcTaskLaser::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize DigitQcTaskLaser" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + mStateLastIR2Ch = {}; + mMapChTrgNames.insert({ o2::fv0::ChannelData::kNumberADC, "NumberADC" }); + mMapChTrgNames.insert({ o2::fv0::ChannelData::kIsDoubleEvent, "IsDoubleEvent" }); + mMapChTrgNames.insert({ o2::fv0::ChannelData::kIsTimeInfoNOTvalid, "IsTimeInfoNOTvalid" }); + mMapChTrgNames.insert({ o2::fv0::ChannelData::kIsCFDinADCgate, "IsCFDinADCgate" }); + mMapChTrgNames.insert({ o2::fv0::ChannelData::kIsTimeInfoLate, "IsTimeInfoLate" }); + mMapChTrgNames.insert({ o2::fv0::ChannelData::kIsAmpHigh, "IsAmpHigh" }); + mMapChTrgNames.insert({ o2::fv0::ChannelData::kIsEventInTVDC, "IsEventInTVDC" }); + mMapChTrgNames.insert({ o2::fv0::ChannelData::kIsTimeInfoLost, "IsTimeInfoLost" }); + + mMapDigitTrgNames.insert({ o2::fit::Triggers::bitA, "OrA" }); + mMapDigitTrgNames.insert({ o2::fit::Triggers::bitAOut, "OrAOut" }); + mMapDigitTrgNames.insert({ o2::fit::Triggers::bitTrgNchan, "TrgNChan" }); + mMapDigitTrgNames.insert({ o2::fit::Triggers::bitTrgCharge, "TrgCharge" }); + mMapDigitTrgNames.insert({ o2::fit::Triggers::bitAIn, "OrAIn" }); + mMapDigitTrgNames.insert({ o2::fit::Triggers::bitLaser, "Laser" }); + mMapDigitTrgNames.insert({ o2::fit::Triggers::bitOutputsAreBlocked, "OutputsAreBlocked" }); + mMapDigitTrgNames.insert({ o2::fit::Triggers::bitDataIsValid, "DataIsValid" }); + + mTrgModeInnerOuterThresholdVar = getModeParameter("trgModeInnerOuterThresholdVar", + TrgModeThresholdVar::kNchannels, + { { TrgModeThresholdVar::kAmpl, "Ampl" }, + { TrgModeThresholdVar::kNchannels, "Nchannels" } }); + + mTrgThresholdCharge = getNumericalParameter("trgThresholdCharge", 498 * 3); + mTrgThresholdNChannels = getNumericalParameter("trgThresholdNChannels", 10); + + if (mTrgModeInnerOuterThresholdVar == TrgModeThresholdVar::kAmpl) { + mTrgThresholdChargeInner = getNumericalParameter("trgThresholdChargeInner", 50); + mTrgThresholdChargeOuter = getNumericalParameter("trgThresholdChargeOuter", 50); + } else if (mTrgModeInnerOuterThresholdVar == TrgModeThresholdVar::kNchannels) { + mTrgThresholdNChannelsInner = getNumericalParameter("trgThresholdNChannelsInner", 1); + mTrgThresholdNChannelsOuter = getNumericalParameter("trgThresholdNChannelsOuter", 1); + } + + mHistTime2Ch = std::make_unique("TimePerChannel", "Time vs Channel;Channel;Time", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF, 4100, -2050, 2050); + mHistTime2Ch->SetOption("colz"); + mHistAmp2Ch = std::make_unique("AmpPerChannel", "Amplitude vs Channel;Channel;Amp", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF, 4200, -100, 4100); + mHistAmp2Ch->SetOption("colz"); + mHistBC = std::make_unique("BC", "BC;BC;counts;", sBCperOrbit, 0, sBCperOrbit); + mHistChDataBits = std::make_unique("ChannelDataBits", "ChannelData bits per ChannelID;Channel;Bit", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF, mMapChTrgNames.size(), 0, mMapChTrgNames.size()); + mHistChDataBits->SetOption("colz"); + for (const auto& entry : mMapChTrgNames) { + mHistChDataBits->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + } + mHistOrbitVsTrg = std::make_unique("OrbitVsTriggers", "Orbit vs Triggers;Orbit;Trg", sOrbitsPerTF, 0, sOrbitsPerTF, mMapDigitTrgNames.size(), 0, mMapDigitTrgNames.size()); + mHistOrbitVsTrg->SetOption("colz"); + mHistTriggersSw = std::make_unique("TriggersSoftware", "Triggers from software", mMapDigitTrgNames.size(), 0, mMapDigitTrgNames.size()); + mHistTriggersSoftwareVsTCM = std::make_unique("TriggersSoftwareVsTCM", "Comparison of triggers from software and TCM;;Trigger name", mMapDigitTrgNames.size(), 0, mMapDigitTrgNames.size(), 4, 0, 4); + mHistTriggersSoftwareVsTCM->SetOption("colz"); + mHistTriggersSoftwareVsTCM->SetStats(0); + for (const auto& entry : mMapDigitTrgNames) { + mHistOrbitVsTrg->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistTriggersSw->GetXaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistTriggersSoftwareVsTCM->GetXaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + } + mHistTriggersSw->GetXaxis()->SetRange(1, 5); + mHistTriggersSoftwareVsTCM->GetXaxis()->SetRange(1, 5); + mHistTriggersSoftwareVsTCM->GetYaxis()->SetBinLabel(TrgComparisonResult::kSWonly + 1, "Sw only"); + mHistTriggersSoftwareVsTCM->GetYaxis()->SetBinLabel(TrgComparisonResult::kTCMonly + 1, "TCM only"); + mHistTriggersSoftwareVsTCM->GetYaxis()->SetBinLabel(TrgComparisonResult::kNone + 1, "neither TCM nor Sw"); + mHistTriggersSoftwareVsTCM->GetYaxis()->SetBinLabel(TrgComparisonResult::kBoth + 1, "both TCM and Sw"); + + mListHistGarbage = new TList(); + mListHistGarbage->SetOwner(kTRUE); + + std::map mapFEE2hash; + const auto& lut = o2::fv0::SingleLUT::Instance().getVecMetadataFEE(); + auto lutSorted = lut; + std::sort(lutSorted.begin(), lutSorted.end(), [](const auto& first, const auto& second) { return first.mModuleName < second.mModuleName; }); + uint8_t binPos{ 0 }; + for (const auto& lutEntry : lutSorted) { + const auto& moduleName = lutEntry.mModuleName; + const auto& moduleType = lutEntry.mModuleType; + const auto& strChID = lutEntry.mChannelID; + const auto& pairIt = mapFEE2hash.insert({ moduleName, binPos }); + if (pairIt.second) { + binPos++; + } + if (std::regex_match(strChID, std::regex("[[\\d]{1,3}"))) { + int chID = std::stoi(strChID); + if (chID < sNCHANNELS_FV0_PLUSREF) { + mChID2PMhash[chID] = mapFEE2hash[moduleName]; + } else { + LOG(error) << "Incorrect LUT entry: chID " << strChID << " | " << moduleName; + } + } else if (moduleType != "TCM") { + LOG(error) << "Non-TCM module w/o numerical chID: chID " << strChID << " | " << moduleName; + } else if (moduleType == "TCM") { + mTCMhash = mapFEE2hash[moduleName]; + } + } + mHistBCvsFEEmodules = std::make_unique("BCvsFEEmodules", "BC vs FEE module;BC;FEE", sBCperOrbit, 0, sBCperOrbit, mapFEE2hash.size(), 0, mapFEE2hash.size()); + mHistOrbitVsFEEmodules = std::make_unique("OrbitVsFEEmodules", "Orbit vs FEE module;Orbit;FEE", sOrbitsPerTF, 0, sOrbitsPerTF, mapFEE2hash.size(), 0, mapFEE2hash.size()); + for (const auto& entry : mapFEE2hash) { + mHistBCvsFEEmodules->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistOrbitVsFEEmodules->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + } + + // mHistTimeSum2Diff = std::make_unique("timeSumVsDiff", "time A/C side: sum VS diff;(TOC-TOA)/2 [ns];(TOA+TOC)/2 [ns]", 400, -52.08, 52.08, 400, -52.08, 52.08); // range of 52.08 ns = 4000*13.02ps = 4000 channels + mHistNumADC = std::make_unique("HistNumADC", "HistNumADC", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF); + mHistNumCFD = std::make_unique("HistNumCFD", "HistNumCFD", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF); + mHistCFDEff = std::make_unique("CFD_efficiency", "CFD efficiency;ChannelID;efficiency", sNCHANNELS_FV0_PLUSREF, 0, sNCHANNELS_FV0_PLUSREF); + + std::vector vecChannelIDs; + if (auto param = mCustomParameters.find("ChannelIDs"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDs = parseParameters(chIDs, del); + } + for (const auto& entry : vecChannelIDs) { + mSetAllowedChIDs.insert(entry); + } + std::vector vecChannelIDsAmpVsTime; + if (auto param = mCustomParameters.find("ChannelIDsAmpVsTime"); param != mCustomParameters.end()) { + const auto chIDs = param->second; + const std::string del = ","; + vecChannelIDsAmpVsTime = parseParameters(chIDs, del); + } + for (const auto& entry : vecChannelIDsAmpVsTime) { + mSetAllowedChIDsAmpVsTime.insert(entry); + } + + for (const auto& chID : mSetAllowedChIDs) { + auto pairHistAmp = mMapHistAmp1D.insert({ chID, new TH1F(Form("Amp_channel%i", chID), Form("Amplitude, channel %i", chID), 4200, -100, 4100) }); + auto pairHistTime = mMapHistTime1D.insert({ chID, new TH1F(Form("Time_channel%i", chID), Form("Time, channel %i", chID), 4100, -2050, 2050) }); + auto pairHistBits = mMapHistPMbits.insert({ chID, new TH1F(Form("Bits_channel%i", chID), Form("Bits, channel %i", chID), mMapChTrgNames.size(), 0, mMapChTrgNames.size()) }); + for (const auto& entry : mMapChTrgNames) { + pairHistBits.first->second->GetXaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + } + if (pairHistAmp.second) { + getObjectsManager()->startPublishing(pairHistAmp.first->second); + mListHistGarbage->Add(pairHistAmp.first->second); + } + if (pairHistTime.second) { + mListHistGarbage->Add(pairHistTime.first->second); + getObjectsManager()->startPublishing(pairHistTime.first->second); + } + if (pairHistBits.second) { + mListHistGarbage->Add(pairHistBits.first->second); + getObjectsManager()->startPublishing(pairHistBits.first->second); + } + } + for (const auto& chID : mSetAllowedChIDsAmpVsTime) { + auto pairHistAmpVsTime = mMapHistAmpVsTime.insert({ chID, new TH2F(Form("Amp_vs_time_channel%i", chID), Form("Amplitude vs time, channel %i;Amp;Time", chID), 420, -100, 4100, 410, -2050, 2050) }); + if (pairHistAmpVsTime.second) { + mListHistGarbage->Add(pairHistAmpVsTime.first->second); + getObjectsManager()->startPublishing(pairHistAmpVsTime.first->second); + } + } + + rebinFromConfig(); // after all histos are created + // 1-dim hists + getObjectsManager()->startPublishing(mHistCFDEff.get()); + getObjectsManager()->startPublishing(mHistBC.get()); + getObjectsManager()->startPublishing(mHistTriggersSw.get()); + // 2-dim hists + getObjectsManager()->startPublishing(mHistTime2Ch.get()); + getObjectsManager()->setDefaultDrawOptions(mHistTime2Ch.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistAmp2Ch.get()); + getObjectsManager()->setDefaultDrawOptions(mHistAmp2Ch.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBCvsFEEmodules.get()); + getObjectsManager()->setDefaultDrawOptions(mHistBCvsFEEmodules.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistOrbitVsTrg.get()); + getObjectsManager()->setDefaultDrawOptions(mHistOrbitVsTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistOrbitVsFEEmodules.get()); + getObjectsManager()->setDefaultDrawOptions(mHistOrbitVsFEEmodules.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistChDataBits.get()); + getObjectsManager()->setDefaultDrawOptions(mHistChDataBits.get(), "COLZ"); + // getObjectsManager()->startPublishing(mHistTimeSum2Diff.get()); + // getObjectsManager()->setDefaultDrawOptions(mHistTimeSum2Diff.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistTriggersSoftwareVsTCM.get()); + getObjectsManager()->setDefaultDrawOptions(mHistTriggersSoftwareVsTCM.get(), "COLZ"); + + for (int i = 0; i < getObjectsManager()->getNumberPublishedObjects(); i++) { + TH1* obj = dynamic_cast(getObjectsManager()->getMonitorObject(i)->getObject()); + obj->SetTitle((std::string("FV0 Laser ") + obj->GetTitle()).c_str()); + } +} + +void DigitQcTaskLaser::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity" << activity.mId << ENDM; + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistBC->Reset(); + mHistChDataBits->Reset(); + mHistCFDEff->Reset(); + mHistNumADC->Reset(); + mHistNumCFD->Reset(); + // mHistTimeSum2Diff->Reset(); + mHistBCvsFEEmodules->Reset(); + mHistOrbitVsTrg->Reset(); + mHistOrbitVsFEEmodules->Reset(); + mHistTriggersSw->Reset(); + mHistTriggersSoftwareVsTCM->Reset(); + for (auto& entry : mMapHistAmp1D) { + entry.second->Reset(); + } + for (auto& entry : mMapHistTime1D) { + entry.second->Reset(); + } + for (auto& entry : mMapHistPMbits) { + entry.second->Reset(); + } + for (auto& entry : mMapHistAmpVsTime) { + entry.second->Reset(); + } +} + +void DigitQcTaskLaser::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void DigitQcTaskLaser::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto channels = ctx.inputs().get>("channels"); + auto digits = ctx.inputs().get>("digits"); + for (auto& digit : digits) { + // Exclude all BCs, in which laser signals are NOT expected (and trigger outputs are NOT blocked) + if (!digit.mTriggers.getOutputsAreBlocked()) { + continue; + } + const auto& vecChData = digit.getBunchChannelData(channels); + bool isTCM = true; + if (digit.mTriggers.getTimeA() == o2::fit::Triggers::DEFAULT_TIME && digit.mTriggers.getTimeC() == o2::fit::Triggers::DEFAULT_TIME) { + isTCM = false; + } + mHistBC->Fill(digit.getBC()); + if (isTCM && digit.mTriggers.getDataIsValid() && digit.mTriggers.getOutputsAreBlocked()) { + // mHistTimeSum2Diff->Fill((digit.mTriggers.getTimeC() - digit.mTriggers.getTimeA()) * sCFDChannel2NS / 2, (digit.mTriggers.getTimeC() + digit.mTriggers.getTimeA()) * sCFDChannel2NS / 2); + for (const auto& binPos : mHashedBitBinPos[digit.mTriggers.getTriggersignals()]) { + mHistOrbitVsTrg->Fill(digit.getIntRecord().orbit % sOrbitsPerTF, binPos); + } + } + std::set setFEEmodules{}; + + // reset triggers + for (auto& entry : mMapTrgSoftware) { + mMapTrgSoftware[entry.first] = false; + } + float sumAmplInner = 0; + float sumAmplOuter = 0; + int nFiredChannelsInner = 0; + int nFiredChannelsOuter = 0; + for (const auto& chData : vecChData) { + mHistTime2Ch->Fill(static_cast(chData.ChId), static_cast(chData.CFDTime)); + mHistAmp2Ch->Fill(static_cast(chData.ChId), static_cast(chData.QTCAmpl)); + mStateLastIR2Ch[chData.ChId] = digit.mIntRecord; + if (chData.QTCAmpl > 0) { + mHistNumADC->Fill(chData.ChId); + } + mHistNumCFD->Fill(chData.ChId); + if (mSetAllowedChIDs.size() != 0 && mSetAllowedChIDs.find(static_cast(chData.ChId)) != mSetAllowedChIDs.end()) { + mMapHistAmp1D[chData.ChId]->Fill(chData.QTCAmpl); + mMapHistTime1D[chData.ChId]->Fill(chData.CFDTime); + for (const auto& entry : mMapChTrgNames) { + if ((chData.ChainQTC & (1 << entry.first))) { + mMapHistPMbits[chData.ChId]->Fill(entry.first); + } + } + } + if (mSetAllowedChIDsAmpVsTime.size() != 0 && mSetAllowedChIDsAmpVsTime.find(static_cast(chData.ChId)) != mSetAllowedChIDsAmpVsTime.end()) { + mMapHistAmpVsTime[chData.ChId]->Fill(chData.QTCAmpl, chData.CFDTime); + } + for (const auto& binPos : mHashedBitBinPos[chData.ChainQTC]) { + mHistChDataBits->Fill(chData.ChId, binPos); + } + + setFEEmodules.insert(mChID2PMhash[chData.ChId]); + + if (chData.ChId >= sNCHANNELS_FV0) { // skip reference PMT + continue; + } + + if (chData.ChId < sNCHANNELS_FV0_INNER) { + sumAmplInner += chData.QTCAmpl; + nFiredChannelsInner++; + } else { + sumAmplOuter += chData.QTCAmpl; + nFiredChannelsOuter++; + } + } + if (isTCM) { + setFEEmodules.insert(mTCMhash); + } + for (const auto& feeHash : setFEEmodules) { + mHistBCvsFEEmodules->Fill(static_cast(digit.getIntRecord().bc), static_cast(feeHash)); + mHistOrbitVsFEEmodules->Fill(static_cast(digit.getIntRecord().orbit % sOrbitsPerTF), static_cast(feeHash)); + } + + // triggers re-computation + mMapTrgSoftware[o2::fit::Triggers::bitA] = nFiredChannelsInner + nFiredChannelsOuter >= 1; + mMapTrgSoftware[o2::fit::Triggers::bitTrgNchan] = nFiredChannelsInner + nFiredChannelsOuter > mTrgThresholdNChannels; + mMapTrgSoftware[o2::fit::Triggers::bitTrgCharge] = sumAmplInner + sumAmplOuter > mTrgThresholdCharge; + + if (mTrgModeInnerOuterThresholdVar == TrgModeThresholdVar::kAmpl) { + mMapTrgSoftware[o2::fit::Triggers::bitAIn] = sumAmplInner >= mTrgThresholdChargeInner; + mMapTrgSoftware[o2::fit::Triggers::bitAOut] = sumAmplOuter >= mTrgThresholdChargeOuter; + } else if (mTrgModeInnerOuterThresholdVar == TrgModeThresholdVar::kNchannels) { + mMapTrgSoftware[o2::fit::Triggers::bitAIn] = nFiredChannelsInner >= mTrgThresholdNChannelsInner; + mMapTrgSoftware[o2::fit::Triggers::bitAOut] = nFiredChannelsOuter >= mTrgThresholdNChannelsOuter; + } + + for (const auto& entry : mMapTrgSoftware) { + if (entry.second) + mHistTriggersSw->Fill(entry.first); + bool isTCMFired = digit.mTriggers.getTriggersignals() & (1 << entry.first); + bool isSwFired = entry.second; + if (!isTCMFired && isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kSWonly); + else if (isTCMFired && !isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kTCMonly); + else if (!isTCMFired && !isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kNone); + else if (isTCMFired && isSwFired) + mHistTriggersSoftwareVsTCM->Fill(entry.first, TrgComparisonResult::kBoth); + + if (isTCMFired != isSwFired) { + auto msg = Form( + "Software does not reproduce TCM decision! \n \ + trigger name: %s\n \ + TCM / SW: \n \ + hasFired = %d / %d \n \ + nChannelsA = %d / %d \n \ + nChannelsInner = -- / %d \n \ + nChannelsOuter = -- / %d \n \ + sumAmplA = %d / %d \n \ + sumAmplInner = -- / %d \n \ + sumAmplOuter = -- / %d \n", + mMapDigitTrgNames[entry.first].c_str(), + isTCMFired, isSwFired, + digit.mTriggers.getNChanA(), nFiredChannelsInner + nFiredChannelsOuter, + nFiredChannelsInner, + nFiredChannelsOuter, + digit.mTriggers.getAmplA(), int(sumAmplInner + sumAmplOuter), + int(sumAmplInner), + int(sumAmplOuter)); + ILOG(Debug, Support) << msg << ENDM; + } + } + // end of triggers re-computation + } +} + +void DigitQcTaskLaser::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + // one has to set num. of entries manually because + // default TH1Reductor gets only mean,stddev and entries (no integral) + mHistCFDEff->Divide(mHistNumADC.get(), mHistNumCFD.get()); +} + +void DigitQcTaskLaser::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void DigitQcTaskLaser::reset() +{ + // clean all the monitor objects here + mHistTime2Ch->Reset(); + mHistAmp2Ch->Reset(); + mHistBC->Reset(); + mHistChDataBits->Reset(); + mHistCFDEff->Reset(); + mHistNumADC->Reset(); + mHistNumCFD->Reset(); + // mHistTimeSum2Diff->Reset(); + mHistBCvsFEEmodules->Reset(); + mHistOrbitVsTrg->Reset(); + mHistOrbitVsFEEmodules->Reset(); + mHistTriggersSw->Reset(); + mHistTriggersSoftwareVsTCM->Reset(); + for (auto& entry : mMapHistAmp1D) { + entry.second->Reset(); + } + for (auto& entry : mMapHistTime1D) { + entry.second->Reset(); + } + for (auto& entry : mMapHistPMbits) { + entry.second->Reset(); + } + for (auto& entry : mMapHistAmpVsTime) { + entry.second->Reset(); + } +} + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/src/GenericCheck.cxx b/Modules/FIT/FV0/src/GenericCheck.cxx new file mode 100644 index 0000000000..596771c446 --- /dev/null +++ b/Modules/FIT/FV0/src/GenericCheck.cxx @@ -0,0 +1,344 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GenericCheck.cxx +/// \author Sebastian Bysiak +/// + +#include "FV0/GenericCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fv0 +{ + +SingleCheck GenericCheck::getCheckFromConfig(std::string paramName) +{ + bool isActive = false; + float thresholdWarning = TMath::SignalingNaN(), thresholdError = TMath::SignalingNaN(); + bool shouldBeLower = true; + if (paramName.find("Max") != string::npos || paramName.find("max") != string::npos) { + shouldBeLower = true; + } else if (paramName.find("Min") != string::npos || paramName.find("min") != string::npos) { + shouldBeLower = false; + } + + if (auto paramWarning = mCustomParameters.find(string("thresholdWarning") + paramName), paramError = mCustomParameters.find(string("thresholdError") + paramName); paramWarning != mCustomParameters.end() || paramError != mCustomParameters.end()) { + if (paramWarning != mCustomParameters.end() && paramError != mCustomParameters.end()) { + thresholdWarning = stof(paramWarning->second); + thresholdError = stof(paramError->second); + isActive = true; + if ((shouldBeLower && thresholdWarning > thresholdError) || (!shouldBeLower && thresholdWarning < thresholdError)) { + ILOG(Warning, Support) << "configure(): warning more strict than error -> swapping values!" << ENDM; + std::swap(thresholdWarning, thresholdError); + } + ILOG(Debug, Support) << "configure() : using thresholdWarning" << paramName.c_str() << " = " << thresholdWarning << " , thresholdError" << paramName << " = " << thresholdError << ENDM; + } else { + ILOG(Warning, Support) << "configure() : only one threshold (warning/error) was provided for " << paramName.c_str() << " -> this parameter will not be used!" << ENDM; + } + } + return SingleCheck(paramName, thresholdWarning, thresholdError, shouldBeLower, isActive); +} + +void GenericCheck::configure() +{ + mCheckMaxThresholdY = getCheckFromConfig("MaxThresholdY"); + mCheckMinThresholdY = getCheckFromConfig("MinThresholdY"); + + mCheckMaxOverflowIntegralRatio = getCheckFromConfig("MaxOverflowIntegralRatio"); + + mCheckMinMeanX = getCheckFromConfig("MinMeanX"); + mCheckMaxMeanX = getCheckFromConfig("MaxMeanX"); + mCheckMaxStddevX = getCheckFromConfig("MaxStddevX"); + + mCheckMinMeanY = getCheckFromConfig("MinMeanY"); + mCheckMaxMeanY = getCheckFromConfig("MaxMeanY"); + mCheckMaxStddevY = getCheckFromConfig("MaxStddevY"); + + mCheckMinGraphLastPoint = getCheckFromConfig("MinGraphLastPoint"); + mCheckMaxGraphLastPoint = getCheckFromConfig("MaxGraphLastPoint"); + + // Set path to ccdb to get DeadChannelMap + if (auto param = mCustomParameters.find("ccdbUrl"); param != mCustomParameters.end()) { + setCcdbUrl(param->second); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, configured url = " << param->second << ENDM; + } else { + setCcdbUrl("alice-ccdb.cern.ch"); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, default url = " + << "alice-ccdb.cern.ch" << ENDM; + } + + // Set internal path to DeadChannelMap + if (auto param = mCustomParameters.find("pathDeadChannelMap"); param != mCustomParameters.end()) { + mPathDeadChannelMap = param->second; + ILOG(Debug, Support) << "configure() : using pathDeadChannelMap: " << mPathDeadChannelMap << ENDM; + } else { + mPathDeadChannelMap = "FV0/Calib/DeadChannelMap"; + ILOG(Debug, Support) << "configure() : using default pathDeadChannelMap: " << mPathDeadChannelMap << ENDM; + } + + // Align mDeadChannelMap with downloaded one + mDeadChannelMap = retrieveConditionAny(mPathDeadChannelMap); + if (!mDeadChannelMap || !mDeadChannelMap->map.size()) { + ILOG(Error, Support) << "object \"" << mPathDeadChannelMap << "\" NOT retrieved (or empty). All channels assumed to be alive!" << ENDM; + mDeadChannelMap = new o2::fit::DeadChannelMap(); + for (uint8_t chId = 0; chId < sNCHANNELS; ++chId) { + mDeadChannelMap->setChannelAlive(chId, 1); + } + } + + // Print DeadChannelMap + mDeadChannelMapStr = ""; + for (unsigned chId = 0; chId < mDeadChannelMap->map.size(); chId++) { + if (!mDeadChannelMap->isChannelAlive(chId)) { + mDeadChannelMapStr += (mDeadChannelMapStr.empty() ? "" : ",") + std::to_string(chId); + } + } + if (mDeadChannelMapStr.empty()) { + mDeadChannelMapStr = "EMPTY"; + } + ILOG(Info, Support) << "Loaded dead channel map: " << mDeadChannelMapStr << ENDM; + + mPositionMsgBox = { 0.15, 0.75, 0.85, 0.9 }; + if (auto param = mCustomParameters.find("positionMsgBox"); param != mCustomParameters.end()) { + stringstream ss(param->second); + int i = 0; + while (ss.good()) { + if (i > 4) { + ILOG(Info, Support) << "Skipping next values provided for positionMsgBox" << ENDM; + break; + } + string substr; + getline(ss, substr, ','); + mPositionMsgBox[i] = std::stod(substr); + i++; + } + float minHeight = 0.09, minWidth = 0.19; + if (mPositionMsgBox[2] - mPositionMsgBox[0] < minWidth || mPositionMsgBox[3] - mPositionMsgBox[1] < minHeight) { + mPositionMsgBox = { 0.15, 0.75, 0.85, 0.9 }; + ILOG(Info, Support) << "MsgBox too small: returning to default" << ENDM; + } + } + + if (auto param = mCustomParameters.find("nameObjOnCanvas"); param != mCustomParameters.end()) { + mNameObjOnCanvas = param->second; + ILOG(Info, Support) << "nameObjOnCanvas set to " << mNameObjOnCanvas << ENDM; + } else { + mNameObjOnCanvas = "unspecified"; + } +} + +Quality GenericCheck::check(std::map>* moMap) +{ + Quality result = Quality::Good; + for (auto& [moName, mo] : *moMap) { + if (!mo) { + result.set(Quality::Null); + ILOG(Error, Support) << "MO " << moName << " not found" << ENDM; + continue; + } + + if (std::strcmp(mo->getObject()->ClassName(), "TCanvas") == 0) { + auto g = (TGraph*)((TCanvas*)mo->getObject())->GetListOfPrimitives()->FindObject(mNameObjOnCanvas.c_str()); + + if (!g) { + result.set(Quality::Null); + ILOG(Error, Support) << "Object " << mNameObjOnCanvas << " inside MO " << moName << " not found" << ENDM; + continue; + } + + if (mCheckMinGraphLastPoint.isActive()) + mCheckMinGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + if (mCheckMaxGraphLastPoint.isActive()) + mCheckMaxGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + } + if (std::strcmp(mo->getObject()->ClassName(), "TGraph") == 0) { + auto g = dynamic_cast(mo->getObject()); + + if (!g) { + result.set(Quality::Null); + ILOG(Error, Support) << "Object inside MO " << moName << " not found" << ENDM; + continue; + } + + if (mCheckMinGraphLastPoint.isActive()) + mCheckMinGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + if (mCheckMaxGraphLastPoint.isActive()) + mCheckMaxGraphLastPoint.doCheck(result, g->GetPointY(g->GetN() - 1)); + + } else { + auto h = dynamic_cast(mo->getObject()); + if (!h) { + result.set(Quality::Null); + ILOG(Error, Support) << "Object inside MO " << moName << " not found" << ENDM; + continue; + } + + if (mCheckMinThresholdY.isActive()) { + float minValue = h->GetBinContent(1); + for (int channel = 1; channel < h->GetNbinsX(); ++channel) { + if (channel >= sNCHANNELS || !mDeadChannelMap->isChannelAlive(channel)) { + continue; + } + if (minValue > h->GetBinContent(channel)) { + minValue = h->GetBinContent(channel); + mCheckMinThresholdY.mBinNumberX = channel; + } + } + mCheckMinThresholdY.doCheck(result, minValue); + } + + if (mCheckMaxThresholdY.isActive()) { + if (mDeadChannelMap->isChannelAlive(h->GetMaximumBin())) { + mCheckMaxThresholdY.mBinNumberX = h->GetMaximumBin(); + mCheckMaxThresholdY.doCheck(result, h->GetBinContent(mCheckMaxThresholdY.mBinNumberX)); + } else { + float maxValue = 0; + for (int channel = 1; channel < h->GetNbinsX(); ++channel) { + if (channel >= sNCHANNELS || !mDeadChannelMap->isChannelAlive(channel)) { + continue; + } + if (maxValue < h->GetBinContent(channel)) { + maxValue = h->GetBinContent(channel); + mCheckMaxThresholdY.mBinNumberX = channel; + } + } + mCheckMaxThresholdY.doCheck(result, maxValue); + } + } + + if (mCheckMinMeanX.isActive()) + mCheckMinMeanX.doCheck(result, h->GetMean()); + if (mCheckMaxMeanX.isActive()) + mCheckMaxMeanX.doCheck(result, h->GetMean()); + if (mCheckMaxStddevX.isActive()) + mCheckMaxStddevX.doCheck(result, h->GetStdDev()); + + if (mCheckMinMeanY.isActive()) + mCheckMinMeanY.doCheck(result, h->GetMean(2)); + if (mCheckMaxMeanY.isActive()) + mCheckMaxMeanY.doCheck(result, h->GetMean(2)); + if (mCheckMaxStddevY.isActive()) + mCheckMaxStddevY.doCheck(result, h->GetStdDev(2)); + + if (mCheckMaxOverflowIntegralRatio.isActive()) { + float integralWithoutOverflow = 0., overflow = 0.; + if (h->GetDimension() == 1) { + integralWithoutOverflow = h->Integral(); + overflow = h->GetBinContent(h->GetNbinsX() + 1); + } else if (h->GetDimension() == 2) { + // for 2D include these overflows: (over,over), (over,in-range), (in-range, over) + TH2* h2 = (TH2*)h->Clone(); // TH2 needed for Integral() with both axis specified + integralWithoutOverflow = h2->Integral(); + float integralWithOverflow = h2->Integral(1, h2->GetNbinsX() + 1, 1, h2->GetNbinsY() + 1); + overflow = integralWithOverflow - integralWithoutOverflow; + } + mCheckMaxOverflowIntegralRatio.doCheck(result, overflow / integralWithoutOverflow); + } + } + } + return result; +} + +void GenericCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (!mo) { + ILOG(Error, Support) << "beautify(): MO not found" << ENDM; + return; + } + + TPaveText* msg = new TPaveText(mPositionMsgBox[0], mPositionMsgBox[1], mPositionMsgBox[2], mPositionMsgBox[3], "NDC"); + if (std::strcmp(mo->getObject()->ClassName(), "TCanvas") == 0) { + auto g = (TGraph*)((TCanvas*)mo->getObject())->GetListOfPrimitives()->FindObject(mNameObjOnCanvas.c_str()); + if (!g) { + ILOG(Error, Support) << "beautify(): Object " << mNameObjOnCanvas << " inside MO " << mo->GetName() << " not found" << ENDM; + return; + } + g->GetListOfFunctions()->Add(msg); + } else if (std::strcmp(mo->getObject()->ClassName(), "TGraph") == 0) { + auto g = dynamic_cast(mo->getObject()); + if (!g) { + ILOG(Error, Support) << "beautify(): Object inside MO " << mo->GetName() << " not found" << ENDM; + return; + } + g->GetListOfFunctions()->Add(msg); + } else { + auto h = dynamic_cast(mo->getObject()); + if (!h) { + ILOG(Error, Support) << "beautify(): Object inside MO " << mo->GetName() << " not found" << ENDM; + return; + } + h->GetListOfFunctions()->Add(msg); + + // add threshold lines + if (mCheckMinThresholdY.isActive()) { + Double_t xMin = h->GetXaxis()->GetXmin(); + Double_t xMax = h->GetXaxis()->GetXmax(); + auto* lineMinError = new TLine(xMin, mCheckMinThresholdY.getThresholdError(), xMax, mCheckMinThresholdY.getThresholdError()); + auto* lineMinWarning = new TLine(xMin, mCheckMinThresholdY.getThresholdWarning(), xMax, mCheckMinThresholdY.getThresholdWarning()); + lineMinError->SetLineWidth(3); + lineMinWarning->SetLineWidth(3); + lineMinError->SetLineStyle(kDashed); + lineMinWarning->SetLineStyle(kDashed); + lineMinError->SetLineColor(kRed); + lineMinWarning->SetLineColor(kOrange); + h->GetListOfFunctions()->Add(lineMinError); + h->GetListOfFunctions()->Add(lineMinWarning); + } + } + + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + auto flags = checkResult.getFlags(); + for (int i = 0; i < int(flags.size()); i++) { + msg->AddText(flags[i].second.c_str()); + if (i > 4) { + msg->AddText("et al ... "); + break; + } + } + int color = kBlack; + if (checkResult == Quality::Good) { + color = kGreen + 1; + msg->AddText(">> Quality::Good <<"); + } else if (checkResult == Quality::Medium) { + color = kOrange - 1; + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Bad) { + color = kRed; + msg->AddText(">> Quality::Bad <<"); + } + msg->SetFillStyle(1); + msg->SetLineWidth(3); + msg->SetLineColor(color); + msg->SetShadowColor(color); + msg->SetTextColor(color); + msg->SetMargin(0.0); +} + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/src/OutOfBunchCollCheck.cxx b/Modules/FIT/FV0/src/OutOfBunchCollCheck.cxx new file mode 100644 index 0000000000..d0e439858e --- /dev/null +++ b/Modules/FIT/FV0/src/OutOfBunchCollCheck.cxx @@ -0,0 +1,165 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file OutOfBunchCollCheck.cxx +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#include "FV0/OutOfBunchCollCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" + +#include "DataFormatsFIT/Triggers.h" +// ROOT +#include +#include +#include +#include + +#include +#include +#include "Common/Utils.h" + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fv0 +{ + +void OutOfBunchCollCheck::configure() +{ + if (auto param = mCustomParameters.find("thresholdWarning"); param != mCustomParameters.end()) { + mThreshWarning = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdWarning = " << mThreshWarning << ENDM; + } else { + mThreshWarning = 1e-3; + ILOG(Debug, Support) << "configure() : using default thresholdWarning = " << mThreshWarning << ENDM; + } + + if (auto param = mCustomParameters.find("thresholdError"); param != mCustomParameters.end()) { + mThreshError = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdError = " << mThreshError << ENDM; + } else { + mThreshError = 0.1; + ILOG(Debug, Support) << "configure() : using default thresholdError = " << mThreshError << ENDM; + } + + if (auto param = mCustomParameters.find("binPos"); param != mCustomParameters.end()) { + mBinPos = stoi(param->second); + ILOG(Debug, Support) << "configure() : using binPos = " << mBinPos << ENDM; + } else { + mBinPos = int(o2::fit::Triggers::bitA) + 1; + ILOG(Debug, Support) << "configure() : using default binPos = " << mBinPos << ENDM; + } + mEnableMessage = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "enableMessage", true); +} + +Quality OutOfBunchCollCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + TH2F* hOutOfBunchColl = 0; + float integralBcOrbitMap = 0; + float integralOutOfBunchColl = 0; + bool metadataFound = false; + const std::string metadataKey = "BcVsTrgIntegralBin" + std::to_string(mBinPos); + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName().find("OutOfBunchColl") != std::string::npos) { + hOutOfBunchColl = dynamic_cast(mo->getObject()); + for (auto metainfo : mo->getMetadataMap()) { + if (metainfo.first == metadataKey) { + integralBcOrbitMap = std::stof(metainfo.second); + metadataFound = true; + } + } + } + } + std::string reason = ""; + if (!integralBcOrbitMap) + reason = Form("Cannot compute quality due to zero integ in BcOrbitMap"); + if (!metadataFound) + reason = Form("Cannot compute quality due to missing metadata: %s", metadataKey.c_str()); + if (!hOutOfBunchColl) + reason = Form("Cannot compute quality due to problem with retieving MO"); + if (reason != "") { + result.set(Quality::Null); + result.addFlag(FlagTypeFactory::Unknown(), reason); + ILOG(Warning) << reason << ENDM; + return result; + } + + result = Quality::Good; + mFractionOutOfBunchColl = 0; + mNumNonEmptyBins = 0; + + integralOutOfBunchColl = hOutOfBunchColl->Integral(1, sBCperOrbit, mBinPos, mBinPos); + mFractionOutOfBunchColl = integralOutOfBunchColl / integralBcOrbitMap; + + ILOG(Debug, Support) << "in checker: integralBcOrbitMap:" << integralBcOrbitMap << " integralOutOfBunchColl: " << integralOutOfBunchColl << " -> fraction: " << mFractionOutOfBunchColl << ENDM; + + if (mFractionOutOfBunchColl > mThreshError) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::Unknown(), + Form("fraction of out of bunch collisions (%.2e) is above \"Error\" threshold (%.2e)", + mFractionOutOfBunchColl, mThreshError)); + } else if (mFractionOutOfBunchColl > mThreshWarning) { + result.set(Quality::Medium); + result.addFlag(FlagTypeFactory::Unknown(), + Form("fraction of out of bunch collisions (%.2e) is above \"Warning\" threshold (%.2e)", + mFractionOutOfBunchColl, mThreshWarning)); + } + + for (int i = 1; i < hOutOfBunchColl->GetNbinsX() + 1; i++) { + for (int j = 1; j < hOutOfBunchColl->GetNbinsY() + 1; j++) { + if (hOutOfBunchColl->GetBinContent(i, j)) + mNumNonEmptyBins++; + } + } + result.addMetadata("numNonEmptyBins", std::to_string(mNumNonEmptyBins)); + return result; +} + +void OutOfBunchCollCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH2F*, skipping" << ENDM; + return; + } + if (!mEnableMessage) { + return; + } + TPaveText* msg = new TPaveText(0.1, 0.9, 0.9, 0.95, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + msg->SetTextAlign(12); + std::string prefix = Form("Fraction of out of bunch collisions = %.2e (Warning > %.2e, Error > %.2e) ", mFractionOutOfBunchColl, mThreshWarning, mThreshError); + + if (checkResult == Quality::Good) { + msg->SetFillColor(kGreen); + msg->AddText((prefix + ">> Quality::Good <<").c_str()); + } else if (checkResult == Quality::Bad) { + msg->SetFillColor(kRed); + msg->AddText((prefix + ">> Quality::Bad <<").c_str()); + } else if (checkResult == Quality::Medium) { + msg->SetFillColor(kOrange); + msg->AddText((prefix + ">> Quality::Medium <<").c_str()); + } else if (checkResult == Quality::Null) { + msg->SetFillColor(kGray); + msg->AddText((prefix + ">> Quality::Null <<").c_str()); + } +} + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/src/OutOfBunchCollFeeModulesCheck.cxx b/Modules/FIT/FV0/src/OutOfBunchCollFeeModulesCheck.cxx new file mode 100644 index 0000000000..9db128ace4 --- /dev/null +++ b/Modules/FIT/FV0/src/OutOfBunchCollFeeModulesCheck.cxx @@ -0,0 +1,160 @@ +// Copyright 2023 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file OutOfBunchCollFeeModulesCheck.cxx +/// \author Dawid Skora dawid.mateusz.skora@cern.ch +/// + +#include "FV0/OutOfBunchCollFeeModulesCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsParameters/GRPLHCIFData.h" + +// ROOT +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fv0 +{ + +void OutOfBunchCollFeeModulesCheck::configure() +{ + if (auto param = mCustomParameters.find("thresholdWarning"); param != mCustomParameters.end()) { + mThreshWarning = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdWarning = " << mThreshWarning << ENDM; + } else { + mThreshWarning = 1e-3; + ILOG(Debug, Support) << "configure() : using default thresholdWarning = " << mThreshWarning << ENDM; + } + + if (auto param = mCustomParameters.find("thresholdError"); param != mCustomParameters.end()) { + mThreshError = stof(param->second); + ILOG(Debug, Support) << "configure() : using thresholdError = " << mThreshError << ENDM; + } else { + mThreshError = 0.1; + ILOG(Debug, Support) << "configure() : using default thresholdError = " << mThreshError << ENDM; + } + + if (mThreshError < mThreshWarning) { + ILOG(Debug, Support) << "thresholdError lower than thresholdWarning. Swaping values." << ENDM; + std::swap(mThreshError, mThreshWarning); + } +} + +Quality OutOfBunchCollFeeModulesCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName().find("OutOfBunchColl_BCvsFeeModules") != std::string::npos) { + auto* histogram = dynamic_cast(mo->getObject()); + + if (!histogram) { + ILOG(Error, Support) << "check(): MO " << mo->getName() << " not found" << ENDM; + result.addFlag(FlagTypeFactory::Unknown(), "MO " + mo->getName() + " not found"); + result.set(Quality::Null); + return result; + } + + std::vector allCollPerFeeModule(mo->getMetadataMap().size() + 1, 0); + for (auto metainfo : mo->getMetadataMap()) { + int bin = 0; + float value = 0; + try { + bin = std::stoi(metainfo.first); + value = std::stof(metainfo.second); + allCollPerFeeModule[bin] = value; + } catch (const std::invalid_argument& e) { + ILOG(Warning, Support) << "Could not get value for key " << metainfo.first << ENDM; + continue; + } + } + + // Calculate out-of-bunch-coll fraction for Fee Modules + for (int binY = 1; binY <= histogram->GetNbinsY(); binY++) { + auto outOfBcCollisions = histogram->Integral(1, sBCperOrbit, binY, binY); + float fraction = 0; + if (allCollPerFeeModule[binY]) { + fraction = outOfBcCollisions / allCollPerFeeModule[binY]; + } + + if (fraction > mFractionOutOfBunchColl) { + mFractionOutOfBunchColl = fraction; + } + } + + // Check the biggest fraction of out-of-bunch-coll + if (mFractionOutOfBunchColl > mThreshError) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::Unknown(), + Form("Fraction of out of bunch collisions (%.2e) is above \"Error\" threshold (%.2e)", + mFractionOutOfBunchColl, mThreshError)); + } else if (mFractionOutOfBunchColl > mThreshWarning) { + result.set(Quality::Medium); + result.addFlag(FlagTypeFactory::Unknown(), + Form("Fraction of out of bunch collisions (%.2e) is above \"Warning\" threshold (%.2e)", + mFractionOutOfBunchColl, mThreshWarning)); + } else { + result.set(Quality::Good); + } + } + } + + return result; +} + +void OutOfBunchCollFeeModulesCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName().find("OutOfBunchColl_BCvsFeeModules") != std::string::npos) { + auto* histogram = dynamic_cast(mo->getObject()); + + if (!histogram) { + ILOG(Error, Support) << "beautify(): MO " << mo->getName() << " not found" << ENDM; + return; + } + + TPaveText* msg = new TPaveText(0.1, 0.85, 0.9, 0.9, "NDC"); + histogram->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + if (checkResult.isWorseThan(Quality::Good)) { + msg->AddText(checkResult.getFlags()[0].second.c_str()); + } + int color = kWhite; + if (checkResult == Quality::Good) { + color = kGreen + 1; + msg->AddText(">> Quality::Good <<"); + } else if (checkResult == Quality::Medium) { + color = kOrange - 1; + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Bad) { + color = kRed; + msg->AddText(">> Quality::Bad <<"); + } + msg->SetFillColor(color); + } +} + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/src/PostProcTask.cxx b/Modules/FIT/FV0/src/PostProcTask.cxx new file mode 100644 index 0000000000..e6aeb1be1b --- /dev/null +++ b/Modules/FIT/FV0/src/PostProcTask.cxx @@ -0,0 +1,775 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcTask.cxx +/// \author Sebastian Bysiak sbysiak@cern.ch +/// + +#include "FV0/PostProcTask.h" +#include "QualityControl/QcInfoLogger.h" +#include "CommonConstants/LHCConstants.h" +#include "DataFormatsParameters/GRPLHCIFData.h" +#include "DataFormatsFV0/LookUpTable.h" +#include "Common/Utils.h" + +#include "FITCommon/HelperHist.h" +#include "FITCommon/HelperCommon.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::fit; + +namespace o2::quality_control_modules::fv0 +{ + +PostProcTask::~PostProcTask() +{ + delete mAmpl; + delete mTime; + for (auto& [_, histo] : mMapTrgHistBC) { + delete histo; + histo = nullptr; + } +} + +void PostProcTask::configure(const boost::property_tree::ptree& config) +{ + mCcdbUrl = config.get_child("qc.config.conditionDB.url").get_value(); + + const char* configPath = Form("qc.postprocessing.%s", getID().c_str()); + const char* configCustom = Form("%s.custom", configPath); + ILOG(Info, Support) << "configPath = " << configPath << ENDM; + auto cfgPath = [&configCustom](const std::string& entry) { + return Form("%s.%s", configCustom, entry.c_str()); + }; + + auto node = config.get_child_optional(Form("%s.custom.pathGrpLhcIf", configPath)); + if (node) { + mPathGrpLhcIf = node.get_ptr()->get_child("").get_value(); + ILOG(Debug, Support) << "configure() : using pathBunchFilling = \"" << mPathGrpLhcIf << "\"" << ENDM; + } else { + mPathGrpLhcIf = "GLO/Config/GRPLHCIF"; + ILOG(Debug, Support) << "configure() : using default pathBunchFilling = \"" << mPathGrpLhcIf << "\"" << ENDM; + } + + node = config.get_child_optional(Form("%s.custom.numOrbitsInTF", configPath)); + if (node) { + mNumOrbitsInTF = std::stoi(node.get_ptr()->get_child("").get_value()); + ILOG(Debug, Support) << "configure() : using numOrbitsInTF = " << mNumOrbitsInTF << ENDM; + } else { + mNumOrbitsInTF = 256; + ILOG(Debug, Support) << "configure() : using default numOrbitsInTF = " << mNumOrbitsInTF << ENDM; + } + + node = config.get_child_optional(Form("%s.custom.cycleDurationMoName", configPath)); + if (node) { + mCycleDurationMoName = node.get_ptr()->get_child("").get_value(); + ILOG(Debug, Support) << "configure() : using cycleDurationMoName = \"" << mCycleDurationMoName << "\"" << ENDM; + } else { + mCycleDurationMoName = "CycleDurationNTF"; + ILOG(Debug, Support) << "configure() : using default cycleDurationMoName = \"" << mCycleDurationMoName << "\"" << ENDM; + } + + node = config.get_child_optional(Form("%s.custom.pathDigitQcTask", configPath)); + if (node) { + mPathDigitQcTask = node.get_ptr()->get_child("").get_value(); + ILOG(Debug, Support) << "configure() : using pathDigitQcTask = \"" << mPathDigitQcTask << "\"" << ENDM; + } else { + mPathDigitQcTask = "FV0/MO/DigitQcTask/"; + ILOG(Debug, Support) << "configure() : using default pathDigitQcTask = \"" << mPathDigitQcTask << "\"" << ENDM; + } + + node = config.get_child_optional(Form("%s.custom.timestampSourceLhcIf", configPath)); + if (node) { + mTimestampSourceLhcIf = node.get_ptr()->get_child("").get_value(); + if (mTimestampSourceLhcIf == "last" || mTimestampSourceLhcIf == "trigger" || mTimestampSourceLhcIf == "metadata" || mTimestampSourceLhcIf == "validUntil") { + ILOG(Debug, Support) << "configure() : using timestampSourceLhcIf = \"" << mTimestampSourceLhcIf << "\"" << ENDM; + } else { + auto prev = mTimestampSourceLhcIf; + mTimestampSourceLhcIf = "trigger"; + ILOG(Warning, Support) << "configure() : invalid value for timestampSourceLhcIf = \"" << prev + << "\"\n available options are \"last\", \"trigger\" or \"metadata\"" + << "\n fallback to default: \"" << mTimestampSourceLhcIf << "\"" << ENDM; + } + } else { + mTimestampSourceLhcIf = "trigger"; + ILOG(Debug, Support) << "configure() : using default timestampSourceLhcIf = \"" << mTimestampSourceLhcIf << "\"" << ENDM; + } + + mLowTimeThreshold = helper::getConfigFromPropertyTree(config, Form("%s.lowTimeThreshold", configCustom), -192); + mUpTimeThreshold = helper::getConfigFromPropertyTree(config, Form("%s.upTimeThreshold", configCustom), 192); + mTimestampMetaField = helper::getConfigFromPropertyTree(config, cfgPath("timestampMetaField"), "timestampTF"); + + // TO REMOVE + // VERY BAD SOLUTION, YOU SHOULDN'T USE IT + const std::string del = ","; + const std::string strChannelIDs = helper::getConfigFromPropertyTree(config, cfgPath("channelIDs"), ""); + const std::string strHistsToDecompose = helper::getConfigFromPropertyTree(config, cfgPath("histsToDecompose"), ""); + if (strChannelIDs.size() > 0 && strHistsToDecompose.size() > 0) { + mVecChannelIDs = helper::parseParameters(strChannelIDs, del); + mVecHistsToDecompose = helper::parseParameters(strHistsToDecompose, del); + } +} + +void PostProcTask::initialize(Trigger, framework::ServiceRegistryRef services) +{ + // delete any objects from previous runs + mRateOrA.reset(); + mRateOrAout.reset(); + mRateOrAin.reset(); + mRateTrgCharge.reset(); + mRateTrgNchan.reset(); + mHistChDataNegBits.reset(); + mHistTriggers.reset(); + + mHistTimeInWindow.reset(); + mHistCFDEff.reset(); + mHistTrgValidation.reset(); + + mRatesCanv.reset(); + delete mAmpl; + mAmpl = nullptr; + delete mTime; + mTime = nullptr; + + mHistBcPattern.reset(); + mHistBcPatternFee.reset(); + mHistBcTrgOutOfBunchColl.reset(); + mHistBcFeeOutOfBunchColl.reset(); + mHistBcFeeOutOfBunchCollForOrATrg.reset(); + mHistBcFeeOutOfBunchCollForOrAOutTrg.reset(); + mHistBcFeeOutOfBunchCollForNChanTrg.reset(); + mHistBcFeeOutOfBunchCollForChargeTrg.reset(); + mHistBcFeeOutOfBunchCollForOrAInTrg.reset(); + + for (auto& [_, histo] : mMapTrgHistBC) { + delete histo; + histo = nullptr; + } + mMapTrgHistBC.clear(); + + // start initialization + mDatabase = &services.get(); + mCcdbApi.init(mCcdbUrl); + + mRateOrA = std::make_unique(0); + mRateOrAout = std::make_unique(0); + mRateOrAin = std::make_unique(0); + mRateTrgCharge = std::make_unique(0); + mRateTrgNchan = std::make_unique(0); + // mRatesCanv = std::make_unique("cRates", "trigger rates"); + mAmpl = new TProfile("MeanAmplPerChannel", "mean ampl per channel;Channel;Ampl #mu #pm #sigma", sNCHANNELS_PM, 0, sNCHANNELS_PM); + mTime = new TProfile("MeanTimePerChannel", "mean time per channel;Channel;Time #mu #pm #sigma", sNCHANNELS_PM, 0, sNCHANNELS_PM); + + mRateOrA->SetNameTitle("rateOrA", "trg rate: OrA;cycle;rate [kHz]"); + mRateOrAout->SetNameTitle("rateOrAout", "trg rate: OrAout;cycle;rate [kHz]"); + mRateOrAin->SetNameTitle("rateOrAin", "trg rate: OrAin;cycle;rate [kHz]"); + mRateTrgCharge->SetNameTitle("rateTrgCharge", "trg rate: TrgCharge;cycle;rate [kHz]"); + mRateTrgNchan->SetNameTitle("rateTrgNchan", "trg rate: TrgNchan;cycle;rate [kHz]"); + + mRateOrA->SetMarkerStyle(24); + mRateOrAout->SetMarkerStyle(25); + mRateOrAin->SetMarkerStyle(26); + mRateTrgCharge->SetMarkerStyle(27); + mRateTrgNchan->SetMarkerStyle(28); + mRateOrA->SetMarkerColor(kOrange); + mRateOrAout->SetMarkerColor(kMagenta); + mRateOrAin->SetMarkerColor(kBlack); + mRateTrgCharge->SetMarkerColor(kBlue); + mRateTrgNchan->SetMarkerColor(kOrange); + mRateOrA->SetLineColor(kOrange); + mRateOrAout->SetLineColor(kMagenta); + mRateOrAin->SetLineColor(kBlack); + mRateTrgCharge->SetLineColor(kBlue); + mRateTrgNchan->SetLineColor(kOrange); + + mHistChDataNegBits = std::make_unique("ChannelDataNegBits", "ChannelData negative bits per ChannelID;Channel;Negative bit", sNCHANNELS_PM, 0, sNCHANNELS_PM, mMapPMbits.size(), 0, mMapPMbits.size()); + for (const auto& entry : mMapPMbits) { + std::string stBitName = "! " + entry.second; + mHistChDataNegBits->GetYaxis()->SetBinLabel(entry.first + 1, stBitName.c_str()); + } + getObjectsManager()->startPublishing(mHistChDataNegBits.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistChDataNegBits.get(), "COLZ"); + + mHistTriggers = std::make_unique("Triggers", "Triggers from TCM", mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + mHistBcPattern = std::make_unique("bcPattern", "BC pattern", sBCperOrbit, 0, sBCperOrbit, mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + mHistBcTrgOutOfBunchColl = std::make_unique("OutOfBunchColl_BCvsTrg", "BC vs Triggers for out-of-bunch collisions;BC;Triggers", sBCperOrbit, 0, sBCperOrbit, mMapTechTrgBits.size(), 0, mMapTechTrgBits.size()); + for (const auto& entry : mMapTechTrgBits) { + mHistTriggers->GetXaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistBcPattern->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + mHistBcTrgOutOfBunchColl->GetYaxis()->SetBinLabel(entry.first + 1, entry.second.c_str()); + + // Depends on triggers set to bits 0-N + if (entry.first >= mNumTriggers) + continue; + auto pairHistBC = mMapTrgHistBC.insert({ entry.first, new TH1D(Form("BC_%s", entry.second.c_str()), Form("BC for %s trigger;BC;counts;", entry.second.c_str()), sBCperOrbit, 0, sBCperOrbit) }); + if (pairHistBC.second) { + getObjectsManager()->startPublishing(pairHistBC.first->second, quality_control::core::PublicationPolicy::ThroughStop); + } + } + + const auto& lut = o2::fv0::SingleLUT::Instance().getVecMetadataFEE(); + auto lutSorted = lut; + std::sort(lutSorted.begin(), lutSorted.end(), [](const auto& first, const auto& second) { return first.mModuleName < second.mModuleName; }); + uint8_t binPos{ 0 }; + for (const auto& lutEntry : lutSorted) { + const auto& moduleName = lutEntry.mModuleName; + const auto& moduleType = lutEntry.mModuleType; + const auto& strChID = lutEntry.mChannelID; + const auto& pairIt = mMapFEE2hash.insert({ moduleName, binPos }); + if (pairIt.second) { + binPos++; + } + if (std::regex_match(strChID, std::regex("[[\\d]{1,3}"))) { + int chID = std::stoi(strChID); + if (chID < sNCHANNELS_PM) { + mChID2PMhash[chID] = mMapFEE2hash[moduleName]; + } else { + ILOG(Error, Support) << "Incorrect LUT entry: chID " << strChID << " | " << moduleName << ENDM; + } + } else if (moduleType != "TCM") { + ILOG(Error, Support) << "Non-TCM module w/o numerical chID: chID " << strChID << " | " << moduleName << ENDM; + } else if (moduleType == "TCM") { + uint8_t mTCMhash = mMapFEE2hash[moduleName]; + } + } + + mHistBcPatternFee = std::make_unique("bcPatternForFeeModules", "BC pattern", sBCperOrbit, 0, sBCperOrbit, mMapFEE2hash.size(), 0, mMapFEE2hash.size()); + mHistBcFeeOutOfBunchColl = std::make_unique("OutOfBunchColl_BCvsFeeModules", "BC vs FEE Modules for out-of-bunch collisions;BC;FEE Modules", sBCperOrbit, 0, sBCperOrbit, mMapFEE2hash.size(), 0, mMapFEE2hash.size()); + mHistBcFeeOutOfBunchCollForOrATrg = std::make_unique("OutOfBunchColl_BCvsFeeModulesForOrATrg", "BC vs FEE Modules for out-of-bunch collisions for OrA trg;BC;FEE Modules", sBCperOrbit, 0, sBCperOrbit, mMapFEE2hash.size(), 0, mMapFEE2hash.size()); + mHistBcFeeOutOfBunchCollForOrAOutTrg = std::make_unique("OutOfBunchColl_BCvsFeeModulesForOrAOutTrg", "BC vs FEE Modules for out-of-bunch collisions for OrAOut trg;BC;FEE Modules", sBCperOrbit, 0, sBCperOrbit, mMapFEE2hash.size(), 0, mMapFEE2hash.size()); + mHistBcFeeOutOfBunchCollForNChanTrg = std::make_unique("OutOfBunchColl_BCvsFeeModulesForNChanTrg", "BC vs FEE Modules for out-of-bunch collisions for NChan trg;BC;FEE Modules", sBCperOrbit, 0, sBCperOrbit, mMapFEE2hash.size(), 0, mMapFEE2hash.size()); + mHistBcFeeOutOfBunchCollForChargeTrg = std::make_unique("OutOfBunchColl_BCvsFeeModulesForChargeTrg", "BC vs FEE Modules for out-of-bunch collisions for Charge trg;BC;FEE Modules", sBCperOrbit, 0, sBCperOrbit, mMapFEE2hash.size(), 0, mMapFEE2hash.size()); + mHistBcFeeOutOfBunchCollForOrAInTrg = std::make_unique("OutOfBunchColl_BCvsFeeModulesForOrAInTrg", "BC vs FEE Modules for out-of-bunch collisions for OrAIn trg;BC;FEE Modules", sBCperOrbit, 0, sBCperOrbit, mMapFEE2hash.size(), 0, mMapFEE2hash.size()); + + for (const auto& entry : mMapFEE2hash) { + // ILOG(Warning, Support) << "============= mMapFEE2hash.second + 1: " << entry.second + 1 + // << " mMapFEE2hash.first.c_str(): " << entry.first.c_str() << ENDM; + + mHistBcPatternFee->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcFeeOutOfBunchColl->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcFeeOutOfBunchCollForOrATrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcFeeOutOfBunchCollForOrAOutTrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcFeeOutOfBunchCollForNChanTrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcFeeOutOfBunchCollForChargeTrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + mHistBcFeeOutOfBunchCollForOrAInTrg->GetYaxis()->SetBinLabel(entry.second + 1, entry.first.c_str()); + } + getObjectsManager()->startPublishing(mHistTriggers.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mHistBcPattern.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcPattern.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcTrgOutOfBunchColl.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcTrgOutOfBunchColl.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcPatternFee.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcPatternFee.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcFeeOutOfBunchColl.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcFeeOutOfBunchColl.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcFeeOutOfBunchCollForOrATrg.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcFeeOutOfBunchCollForOrATrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcFeeOutOfBunchCollForOrAOutTrg.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcFeeOutOfBunchCollForOrAOutTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcFeeOutOfBunchCollForNChanTrg.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcFeeOutOfBunchCollForNChanTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcFeeOutOfBunchCollForChargeTrg.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcFeeOutOfBunchCollForChargeTrg.get(), "COLZ"); + getObjectsManager()->startPublishing(mHistBcFeeOutOfBunchCollForOrAInTrg.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistBcFeeOutOfBunchCollForOrAInTrg.get(), "COLZ"); + + getObjectsManager()->startPublishing(mRateOrA.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mRateOrAout.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mRateOrAin.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mRateTrgCharge.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mRateTrgNchan.get(), quality_control::core::PublicationPolicy::ThroughStop); + // getObjectsManager()->startPublishing(mRatesCanv.get(), quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mAmpl, quality_control::core::PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mTime, quality_control::core::PublicationPolicy::ThroughStop); + + for (int i = 0; i < getObjectsManager()->getNumberPublishedObjects(); i++) { + TH1* obj = dynamic_cast(getObjectsManager()->getMonitorObject(i)->getObject()); + if (obj != nullptr) { + obj->SetTitle((std::string("FV0 ") + obj->GetTitle()).c_str()); + } + } + + mHistTrgValidation = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "TrgValidation", "FV0 SW + HW only to validated triggers fraction", mMapTrgBits); + mHistTimeInWindow = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "TimeInWindowFraction", Form("FV0 Fraction of events with CFD in time gate(%i,%i) vs ChannelID;ChannelID;Event fraction with CFD in time gate", mLowTimeThreshold, mUpTimeThreshold), sNCHANNELS_PM, 0, sNCHANNELS_PM); + mHistCFDEff = helper::registerHist(getObjectsManager(), quality_control::core::PublicationPolicy::ThroughStop, "", "CFD_efficiency", "FV0 Fraction of events with CFD in ADC gate vs ChannelID;ChannelID;Event fraction with CFD in ADC gate;", sNCHANNELS_PM, 0, sNCHANNELS_PM); +} + +void PostProcTask::update(Trigger t, framework::ServiceRegistryRef) +{ + auto mo = mDatabase->retrieveMO(mPathDigitQcTask, "TriggersCorrelation", t.timestamp, t.activity); + auto hTrgCorr = mo ? dynamic_cast(mo->getObject()) : nullptr; + mHistTriggers->Reset(); + auto getBinContent2Ddiag = [](TH2* hist, const std::string& binName) { + return hist->GetBinContent(hist->GetXaxis()->FindBin(binName.c_str()), hist->GetYaxis()->FindBin(binName.c_str())); + }; + if (!hTrgCorr) { + ILOG(Error) << "MO \"TriggersCorrelation\" NOT retrieved!!!" << ENDM; + } else { + double totalStat{ 0 }; + for (int iBin = 1; iBin < mHistTriggers->GetXaxis()->GetNbins() + 1; iBin++) { + std::string binName{ mHistTriggers->GetXaxis()->GetBinLabel(iBin) }; + const auto binContent = getBinContent2Ddiag(hTrgCorr, binName); + mHistTriggers->SetBinContent(iBin, getBinContent2Ddiag(hTrgCorr, binName)); + totalStat += binContent; + } + mHistChDataNegBits->SetEntries(totalStat); + } + + auto moChDataBits = mDatabase->retrieveMO(mPathDigitQcTask, "ChannelDataBits", t.timestamp, t.activity); + auto hChDataBits = moChDataBits ? dynamic_cast(moChDataBits->getObject()) : nullptr; + if (!hChDataBits) { + ILOG(Error) << "MO \"ChannelDataBits\" NOT retrieved!!!" << ENDM; + } + auto moStatChannelID = mDatabase->retrieveMO(mPathDigitQcTask, "StatChannelID", t.timestamp, t.activity); + auto hStatChannelID = moStatChannelID ? dynamic_cast(moStatChannelID->getObject()) : nullptr; + if (!hStatChannelID) { + ILOG(Error) << "MO \"StatChannelID\" NOT retrieved!!!" << ENDM; + } + mHistChDataNegBits->Reset(); + if (hChDataBits != nullptr && hStatChannelID != nullptr) { + double totalStat{ 0 }; + for (int iBinX = 1; iBinX < hChDataBits->GetXaxis()->GetNbins() + 1; iBinX++) { + for (int iBinY = 1; iBinY < hChDataBits->GetYaxis()->GetNbins() + 1; iBinY++) { + const double nStatTotal = hStatChannelID->GetBinContent(iBinX); + const double nStatPMbit = hChDataBits->GetBinContent(iBinX, iBinY); + const double nStatNegPMbit = nStatTotal - nStatPMbit; + totalStat += nStatNegPMbit; + mHistChDataNegBits->SetBinContent(iBinX, iBinY, nStatNegPMbit); + } + } + mHistChDataNegBits->SetEntries(totalStat); + } + + auto mo2 = mDatabase->retrieveMO(mPathDigitQcTask, mCycleDurationMoName, t.timestamp, t.activity); + auto hCycleDuration = mo2 ? dynamic_cast(mo2->getObject()) : nullptr; + if (!hCycleDuration) { + ILOG(Error) << "MO \"" << mCycleDurationMoName << "\" NOT retrieved!!!" << ENDM; + } + + if (hTrgCorr && hCycleDuration) { + double cycleDurationMS = 0; + if (mCycleDurationMoName == "CycleDuration" || mCycleDurationMoName == "CycleDurationRange") + // assume MO stores cycle duration in ns + cycleDurationMS = hCycleDuration->GetBinContent(1) / 1e6; // ns -> ms + else if (mCycleDurationMoName == "CycleDurationNTF") + // assume MO stores cycle duration in number of TF + cycleDurationMS = hCycleDuration->GetBinContent(1) * mNumOrbitsInTF * o2::constants::lhc::LHCOrbitNS / 1e6; // ns ->ms + + int n = mRateOrA->GetN(); + + double eps = 1e-8; + if (cycleDurationMS < eps) { + ILOG(Warning) << "cycle duration = " << cycleDurationMS << " ms, almost zero - cannot compute trigger rates!" << ENDM; + } else { + mRateOrA->SetPoint(n, n, getBinContent2Ddiag(hTrgCorr, "OrA") / cycleDurationMS); + mRateOrAout->SetPoint(n, n, getBinContent2Ddiag(hTrgCorr, "OrAOut") / cycleDurationMS); + mRateOrAin->SetPoint(n, n, getBinContent2Ddiag(hTrgCorr, "OrAIn") / cycleDurationMS); + mRateTrgCharge->SetPoint(n, n, getBinContent2Ddiag(hTrgCorr, "TrgCharge") / cycleDurationMS); + mRateTrgNchan->SetPoint(n, n, getBinContent2Ddiag(hTrgCorr, "TrgNChan") / cycleDurationMS); + } + /* + mRatesCanv->cd(); + float vmin = std::min({ mRateOrA->GetYaxis()->GetXmin(), mRateOrAout->GetYaxis()->GetXmin(), mRateOrAin->GetYaxis()->GetXmin(), mRateTrgCharge->GetYaxis()->GetXmin(), mRateTrgNchan->GetYaxis()->GetXmin() }); + float vmax = std::max({ mRateOrA->GetYaxis()->GetXmax(), mRateOrAout->GetYaxis()->GetXmax(), mRateOrAin->GetYaxis()->GetXmax(), mRateTrgCharge->GetYaxis()->GetXmax(), mRateTrgNchan->GetYaxis()->GetXmax() }); + + auto hAxis = mRateOrA->GetHistogram(); + hAxis->GetYaxis()->SetTitleOffset(1.4); + hAxis->SetMinimum(vmin); + hAxis->SetMaximum(vmax * 1.1); + hAxis->SetTitle("FV0 trigger rates"); + hAxis->SetLineWidth(0); + hAxis->Draw("AXIS"); + + mRateOrA->Draw("PL,SAME"); + mRateOrAout->Draw("PL,SAME"); + mRateOrAin->Draw("PL,SAME"); + mRateTrgCharge->Draw("PL,SAME"); + mRateTrgNchan->Draw("PL,SAME"); + TLegend* leg = gPad->BuildLegend(); + leg->SetFillStyle(1); + mRatesCanv->Modified(); + mRatesCanv->Update(); + */ + } + + auto mo3 = mDatabase->retrieveMO(mPathDigitQcTask, "AmpPerChannel", t.timestamp, t.activity); + auto hAmpPerChannel = mo3 ? dynamic_cast(mo3->getObject()) : nullptr; + if (!hAmpPerChannel) { + ILOG(Error) << "MO \"AmpPerChannel\" NOT retrieved!!!" + << ENDM; + } else { + std::unique_ptr projNom(hAmpPerChannel->ProjectionX("projNom", hAmpPerChannel->GetYaxis()->FindBin(1.0), -1)); + std::unique_ptr projDen(hAmpPerChannel->ProjectionX("projDen")); + mHistCFDEff->Divide(projNom.get(), projDen.get()); + } + auto mo4 = mDatabase->retrieveMO(mPathDigitQcTask, "TimePerChannel", t.timestamp, t.activity); + auto hTimePerChannel = mo4 ? dynamic_cast(mo4->getObject()) : nullptr; + if (!hTimePerChannel) { + ILOG(Error) << "MO \"TimePerChannel\" NOT retrieved!!!" + << ENDM; + } else { + std::unique_ptr projInWindow(hTimePerChannel->ProjectionX("projInWindow", hTimePerChannel->GetYaxis()->FindBin(mLowTimeThreshold), hTimePerChannel->GetYaxis()->FindBin(mUpTimeThreshold))); + std::unique_ptr projFull(hTimePerChannel->ProjectionX("projFull")); + mHistTimeInWindow->Divide(projInWindow.get(), projFull.get()); + } + + if (hAmpPerChannel && hTimePerChannel) { + mAmpl = hAmpPerChannel->ProfileX("MeanAmplPerChannel"); + mTime = hTimePerChannel->ProfileX("MeanTimePerChannel"); + mAmpl->SetErrorOption("s"); + mTime->SetErrorOption("s"); + // for some reason the styling is not preserved after assigning result of ProfileX/Y() to already existing object + mAmpl->SetMarkerStyle(8); + mTime->SetMarkerStyle(8); + mAmpl->SetLineColor(kBlack); + mTime->SetLineColor(kBlack); + mAmpl->SetDrawOption("P"); + mTime->SetDrawOption("P"); + mAmpl->GetXaxis()->SetTitleOffset(1); + mTime->GetXaxis()->SetTitleOffset(1); + mAmpl->GetYaxis()->SetTitleOffset(1); + mTime->GetYaxis()->SetTitleOffset(1); + } + + // Download BCvsTriggers + auto moBCvsTriggers = mDatabase->retrieveMO(mPathDigitQcTask, "BCvsTriggers", t.timestamp, t.activity); + auto hBcVsTrg = moBCvsTriggers ? dynamic_cast(moBCvsTriggers->getObject()) : nullptr; + if (!hBcVsTrg) { + ILOG(Error, Support) << "MO \"BCvsTriggers\" NOT retrieved!!!" << ENDM; + return; + } + for (const auto& entry : mMapTrgHistBC) { + hBcVsTrg->ProjectionX(entry.second->GetName(), entry.first + 1, entry.first + 1); + } + + long ts = 999; + if (mTimestampSourceLhcIf == "last") { + ts = -1; + } else if (mTimestampSourceLhcIf == "trigger") { + ts = t.timestamp; + } else if (mTimestampSourceLhcIf == "validUntil") { + ts = t.activity.mValidity.getMax(); + } else if (mTimestampSourceLhcIf == "metadata") { + for (auto metainfo : moBCvsTriggers->getMetadataMap()) { + if (metainfo.first == "TFcreationTime") + ts = std::stol(metainfo.second); + } + if (ts > 1651500000000 && ts < 1651700000000) + ILOG(Warning, Support) << "timestamp (read from TF via metadata) points to 02-04 May 2022" + " - make sure this is the data we are processing and not the default timestamp " + "(it may appear when running on digits w/o providing \"--hbfutils-config o2_tfidinfo.root\")" + << ENDM; + if (ts == 999) { + ILOG(Error) << "\"TFcreationTime\" not found in metadata, fallback to ts from trigger " << ENDM; + ts = t.timestamp; + } + } + + // Download bcPattern + std::map metadata; + std::map headers; + auto* lhcIf = mCcdbApi.retrieveFromTFileAny(mPathGrpLhcIf, metadata, ts, &headers); + if (!lhcIf) { + ILOG(Error, Support) << "object \"" << mPathGrpLhcIf << "\" NOT retrieved. OutOfBunchColTask will not produce valid QC plots." << ENDM; + return; + } + const std::string bcName = lhcIf->getInjectionScheme(); + if (bcName.size() == 8) { + if (bcName.compare("no_value")) { + ILOG(Error, Support) << "Filling scheme not set. OutOfBunchColTask will not produce valid QC plots." << ENDM; + } + } else { + ILOG(Info, Support) << "Filling scheme: " << bcName.c_str() << ENDM; + } + auto bcPattern = lhcIf->getBunchFilling(); + + // Download histogram BCvsFEEmodules from database + auto moBcVsFeeModules = mDatabase->retrieveMO(mPathDigitQcTask, "BCvsFEEmodules", t.timestamp, t.activity); + auto hBcVsFeeModules = moBcVsFeeModules ? dynamic_cast(moBcVsFeeModules->getObject()) : nullptr; + + // Create histogram with bc pattern for FEE modules + mHistBcPatternFee->Reset(); + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapFEE2hash.size() + 1; j++) { + mHistBcPatternFee->SetBinContent(i + 1, j + 1, bcPattern.testBC(i)); + } + } + + if (!hBcVsFeeModules) { + ILOG(Error, Support) << "MO \"BCvsFEEmodules\" NOT retrieved!!!" << ENDM; + return; + } else { + + mHistBcFeeOutOfBunchColl->Reset(); + float vmax = hBcVsFeeModules->GetBinContent(hBcVsFeeModules->GetMaximumBin()); + mHistBcFeeOutOfBunchColl->Add(hBcVsFeeModules, mHistBcPatternFee.get(), 1, -1 * vmax); + + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapFEE2hash.size() + 1; j++) { + if (mHistBcFeeOutOfBunchColl->GetBinContent(i + 1, j + 1) < 0) { + mHistBcFeeOutOfBunchColl->SetBinContent(i + 1, j + 1, 0); + } + } + } + + // Add metadata to histogram OutOfBunchColl_BCvsFeeModules + mHistBcFeeOutOfBunchColl->SetEntries(mHistBcFeeOutOfBunchColl->Integral(1, sBCperOrbit, 1, mMapFEE2hash.size())); + for (int iBin = 1; iBin <= mMapFEE2hash.size(); iBin++) { + const std::string metadataKey = std::to_string(iBin); + const std::string metadataValue = std::to_string(hBcVsFeeModules->Integral(1, sBCperOrbit, iBin, iBin)); + getObjectsManager()->getMonitorObject(mHistBcFeeOutOfBunchColl->GetName())->addOrUpdateMetadata(metadataKey, metadataValue); + } + } + + // Download histogram BCvsFEEmodulesForOrATrg from database + auto moBcVsFeeModulesForOrATrg = mDatabase->retrieveMO(mPathDigitQcTask, "BCvsFEEmodulesForOrATrg", t.timestamp, t.activity); + auto hBcVsFeeModulesForOrATrg = moBcVsFeeModulesForOrATrg ? dynamic_cast(moBcVsFeeModulesForOrATrg->getObject()) : nullptr; + + if (!hBcVsFeeModulesForOrATrg) { + ILOG(Error, Support) << "MO \"BCvsFEEmodulesForOrATrg\" NOT retrieved!!!" << ENDM; + return; + } else { + + mHistBcFeeOutOfBunchCollForOrATrg->Reset(); + float vmax = hBcVsFeeModulesForOrATrg->GetBinContent(hBcVsFeeModulesForOrATrg->GetMaximumBin()); + mHistBcFeeOutOfBunchCollForOrATrg->Add(hBcVsFeeModulesForOrATrg, mHistBcPatternFee.get(), 1, -1 * vmax); + + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapFEE2hash.size() + 1; j++) { + if (mHistBcFeeOutOfBunchCollForOrATrg->GetBinContent(i + 1, j + 1) < 0) { + mHistBcFeeOutOfBunchCollForOrATrg->SetBinContent(i + 1, j + 1, 0); + } + } + } + + // Add metadata to histogram OutOfBunchColl_BCvsFeeModulesForOrATrg + mHistBcFeeOutOfBunchCollForOrATrg->SetEntries(mHistBcFeeOutOfBunchCollForOrATrg->Integral(1, sBCperOrbit, 1, mMapFEE2hash.size())); + for (int iBin = 1; iBin <= mMapFEE2hash.size(); iBin++) { + const std::string metadataKey = std::to_string(iBin); + const std::string metadataValue = std::to_string(hBcVsFeeModulesForOrATrg->Integral(1, sBCperOrbit, iBin, iBin)); + getObjectsManager()->getMonitorObject(mHistBcFeeOutOfBunchCollForOrATrg->GetName())->addOrUpdateMetadata(metadataKey, metadataValue); + } + } + + // Download histogram BCvsFEEmodulesForOrAOutTrg from database + auto moBCvsFEEmodulesForOrAOutTrg = mDatabase->retrieveMO(mPathDigitQcTask, "BCvsFEEmodulesForOrAOutTrg", t.timestamp, t.activity); + auto hBCvsFEEmodulesForOrAOutTrg = moBCvsFEEmodulesForOrAOutTrg ? dynamic_cast(moBCvsFEEmodulesForOrAOutTrg->getObject()) : nullptr; + + if (!hBCvsFEEmodulesForOrAOutTrg) { + ILOG(Error, Support) << "MO \"hBCvsFEEmodulesForOrAOutTrg\" NOT retrieved!!!" << ENDM; + return; + } else { + mHistBcFeeOutOfBunchCollForOrAOutTrg->Reset(); + float vmax = hBCvsFEEmodulesForOrAOutTrg->GetBinContent(hBCvsFEEmodulesForOrAOutTrg->GetMaximumBin()); + mHistBcFeeOutOfBunchCollForOrAOutTrg->Add(hBCvsFEEmodulesForOrAOutTrg, mHistBcPatternFee.get(), 1, -1 * vmax); + + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapFEE2hash.size() + 1; j++) { + if (mHistBcFeeOutOfBunchCollForOrAOutTrg->GetBinContent(i + 1, j + 1) < 0) { + mHistBcFeeOutOfBunchCollForOrAOutTrg->SetBinContent(i + 1, j + 1, 0); + } + } + } + + // Add metadata to histogram OutOfBunchColl_BCvsFeeModulesForOrAOutTrg + mHistBcFeeOutOfBunchCollForOrAOutTrg->SetEntries(mHistBcFeeOutOfBunchCollForOrAOutTrg->Integral(1, sBCperOrbit, 1, mMapFEE2hash.size())); + for (int iBin = 1; iBin <= mMapFEE2hash.size(); iBin++) { + const std::string metadataKey = std::to_string(iBin); + const std::string metadataValue = std::to_string(hBCvsFEEmodulesForOrAOutTrg->Integral(1, sBCperOrbit, iBin, iBin)); + getObjectsManager()->getMonitorObject(mHistBcFeeOutOfBunchCollForOrAOutTrg->GetName())->addOrUpdateMetadata(metadataKey, metadataValue); + } + } + + // Download histogram BCvsFEEmodulesForNChanTrg from database + auto moBCvsFEEmodulesForNChanTrg = mDatabase->retrieveMO(mPathDigitQcTask, "BCvsFEEmodulesForNChanTrg", t.timestamp, t.activity); + auto hBCvsFEEmodulesForNChanTrg = moBCvsFEEmodulesForNChanTrg ? dynamic_cast(moBCvsFEEmodulesForNChanTrg->getObject()) : nullptr; + + if (!hBCvsFEEmodulesForNChanTrg) { + ILOG(Error, Support) << "MO \"BCvsFEEmodulesForNChanTrg\" NOT retrieved!!!" << ENDM; + return; + } else { + mHistBcFeeOutOfBunchCollForNChanTrg->Reset(); + float vmax = hBCvsFEEmodulesForNChanTrg->GetBinContent(hBCvsFEEmodulesForNChanTrg->GetMaximumBin()); + mHistBcFeeOutOfBunchCollForNChanTrg->Add(hBCvsFEEmodulesForNChanTrg, mHistBcPatternFee.get(), 1, -1 * vmax); + + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapFEE2hash.size() + 1; j++) { + if (mHistBcFeeOutOfBunchCollForNChanTrg->GetBinContent(i + 1, j + 1) < 0) { + mHistBcFeeOutOfBunchCollForNChanTrg->SetBinContent(i + 1, j + 1, 0); + } + } + } + + // Add metadata to histogram OutOfBunchColl_BCvsFeeModulesForNChanTrg + mHistBcFeeOutOfBunchCollForNChanTrg->SetEntries(mHistBcFeeOutOfBunchCollForNChanTrg->Integral(1, sBCperOrbit, 1, mMapFEE2hash.size())); + for (int iBin = 1; iBin <= mMapFEE2hash.size(); iBin++) { + const std::string metadataKey = std::to_string(iBin); + const std::string metadataValue = std::to_string(hBCvsFEEmodulesForNChanTrg->Integral(1, sBCperOrbit, iBin, iBin)); + getObjectsManager()->getMonitorObject(mHistBcFeeOutOfBunchCollForNChanTrg->GetName())->addOrUpdateMetadata(metadataKey, metadataValue); + } + } + + // Download histogram BCvsFEEmodulesForChargeTrg from database + auto moBCvsFEEmodulesForChargeTrg = mDatabase->retrieveMO(mPathDigitQcTask, "BCvsFEEmodulesForChargeTrg", t.timestamp, t.activity); + auto hBCvsFEEmodulesForChargeTrg = moBCvsFEEmodulesForChargeTrg ? dynamic_cast(moBCvsFEEmodulesForChargeTrg->getObject()) : nullptr; + + if (!hBCvsFEEmodulesForChargeTrg) { + ILOG(Error, Support) << "MO \"BCvsFEEmodulesForChargeTrg\" NOT retrieved!!!" << ENDM; + return; + } else { + mHistBcFeeOutOfBunchCollForChargeTrg->Reset(); + float vmax = hBCvsFEEmodulesForChargeTrg->GetBinContent(hBCvsFEEmodulesForChargeTrg->GetMaximumBin()); + mHistBcFeeOutOfBunchCollForChargeTrg->Add(hBCvsFEEmodulesForChargeTrg, mHistBcPatternFee.get(), 1, -1 * vmax); + + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapFEE2hash.size() + 1; j++) { + if (mHistBcFeeOutOfBunchCollForChargeTrg->GetBinContent(i + 1, j + 1) < 0) { + mHistBcFeeOutOfBunchCollForChargeTrg->SetBinContent(i + 1, j + 1, 0); + } + } + } + + // Add metadata to histogram OutOfBunchColl_BCvsFeeModulesForChargeTrg + mHistBcFeeOutOfBunchCollForChargeTrg->SetEntries(mHistBcFeeOutOfBunchCollForChargeTrg->Integral(1, sBCperOrbit, 1, mMapFEE2hash.size())); + for (int iBin = 1; iBin <= mMapFEE2hash.size(); iBin++) { + const std::string metadataKey = std::to_string(iBin); + const std::string metadataValue = std::to_string(hBCvsFEEmodulesForChargeTrg->Integral(1, sBCperOrbit, iBin, iBin)); + getObjectsManager()->getMonitorObject(mHistBcFeeOutOfBunchCollForChargeTrg->GetName())->addOrUpdateMetadata(metadataKey, metadataValue); + } + } + + // Download histogram BCvsFEEmodulesForOrAInTrg from database + auto moBCvsFEEmodulesForOrAInTrg = mDatabase->retrieveMO(mPathDigitQcTask, "BCvsFEEmodulesForOrAInTrg", t.timestamp, t.activity); + auto hBCvsFEEmodulesForOrAInTrg = moBCvsFEEmodulesForOrAInTrg ? dynamic_cast(moBCvsFEEmodulesForOrAInTrg->getObject()) : nullptr; + + if (!hBCvsFEEmodulesForOrAInTrg) { + ILOG(Error, Support) << "MO \"BCvsFEEmodulesForOrAInTrg\" NOT retrieved!!!" << ENDM; + return; + } else { + mHistBcFeeOutOfBunchCollForOrAInTrg->Reset(); + float vmax = hBCvsFEEmodulesForOrAInTrg->GetBinContent(hBCvsFEEmodulesForOrAInTrg->GetMaximumBin()); + mHistBcFeeOutOfBunchCollForOrAInTrg->Add(hBCvsFEEmodulesForOrAInTrg, mHistBcPatternFee.get(), 1, -1 * vmax); + + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapFEE2hash.size() + 1; j++) { + if (mHistBcFeeOutOfBunchCollForOrAInTrg->GetBinContent(i + 1, j + 1) < 0) { + mHistBcFeeOutOfBunchCollForOrAInTrg->SetBinContent(i + 1, j + 1, 0); + } + } + } + + // Add metadata to histogram OutOfBunchColl_BCvsFeeModulesOrAInTrg + mHistBcFeeOutOfBunchCollForOrAInTrg->SetEntries(mHistBcFeeOutOfBunchCollForOrAInTrg->Integral(1, sBCperOrbit, 1, mMapFEE2hash.size())); + for (int iBin = 1; iBin <= mMapFEE2hash.size(); iBin++) { + const std::string metadataKey = std::to_string(iBin); + const std::string metadataValue = std::to_string(hBCvsFEEmodulesForOrAInTrg->Integral(1, sBCperOrbit, iBin, iBin)); + getObjectsManager()->getMonitorObject(mHistBcFeeOutOfBunchCollForOrAInTrg->GetName())->addOrUpdateMetadata(metadataKey, metadataValue); + } + } + + mHistBcPattern->Reset(); + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapTechTrgBits.size() + 1; j++) { + mHistBcPattern->SetBinContent(i + 1, j + 1, bcPattern.testBC(i)); + } + } + + mHistBcTrgOutOfBunchColl->Reset(); + float vmax = hBcVsTrg->GetBinContent(hBcVsTrg->GetMaximumBin()); + mHistBcTrgOutOfBunchColl->Add(hBcVsTrg, mHistBcPattern.get(), 1, -1 * vmax); + for (int i = 0; i < sBCperOrbit + 1; i++) { + for (int j = 0; j < mMapTechTrgBits.size() + 1; j++) { + if (mHistBcTrgOutOfBunchColl->GetBinContent(i + 1, j + 1) < 0) { + mHistBcTrgOutOfBunchColl->SetBinContent(i + 1, j + 1, 0); + } + } + } + + mHistBcTrgOutOfBunchColl->SetEntries(mHistBcTrgOutOfBunchColl->Integral(1, sBCperOrbit, 1, mMapTechTrgBits.size())); + for (int iBin = 1; iBin < mMapTechTrgBits.size() + 1; iBin++) { + const std::string metadataKey = "BcVsTrgIntegralBin" + std::to_string(iBin); + const std::string metadataValue = std::to_string(hBcVsTrg->Integral(1, sBCperOrbit, iBin, iBin)); + getObjectsManager()->getMonitorObject(mHistBcTrgOutOfBunchColl->GetName())->addOrUpdateMetadata(metadataKey, metadataValue); + ILOG(Info, Support) << metadataKey << ":" << metadataValue << ENDM; + } + + auto moTriggersSoftwareVsTCM = mDatabase->retrieveMO(mPathDigitQcTask, "TriggersSoftwareVsTCM", t.timestamp, t.activity); + auto hTriggersSoftwareVsTCM = moTriggersSoftwareVsTCM ? dynamic_cast(moTriggersSoftwareVsTCM->getObject()) : nullptr; + if (hTriggersSoftwareVsTCM != nullptr) { + std::unique_ptr projOnlyHWorSW(hTriggersSoftwareVsTCM->ProjectionX("projOnlyHWorSW", 1, 2)); + std::unique_ptr projValidatedSWandHW(hTriggersSoftwareVsTCM->ProjectionX("projValidatedSWandHW", 4, 4)); + projOnlyHWorSW->LabelsDeflate(); + projValidatedSWandHW->LabelsDeflate(); + mHistTrgValidation->Divide(projOnlyHWorSW.get(), projValidatedSWandHW.get()); + } + decomposeHists(t); + setTimestampToMOs(ts); +} + +void PostProcTask::decomposeHists(Trigger trg) +{ + for (const auto& histName : mVecHistsToDecompose) { + auto insertedMap = mMapHistsToDecompose.insert({ histName, {} }); + auto& mapHists = insertedMap.first->second; + + auto mo = mDatabase->retrieveMO(mPathDigitQcTask, histName, trg.timestamp, trg.activity); + auto histSrcPtr = mo ? dynamic_cast(mo->getObject()) : nullptr; + + if (histSrcPtr == nullptr) { + continue; + } + const auto bins = histSrcPtr->GetYaxis()->GetNbins(); + const auto binLow = histSrcPtr->GetYaxis()->GetXmin(); + const auto binUp = histSrcPtr->GetYaxis()->GetXmax(); + + for (const auto& chID : mVecChannelIDs) { + auto insertedHistDst = mapHists.insert({ chID, nullptr }); + auto& histDstPtr = insertedHistDst.first->second; + auto isInserted = insertedHistDst.second; + if (isInserted == true) { + // creation in first iter + const std::string suffix = std::string{ Form("%03i", chID) }; + const std::string newHistName = histName + std::string{ "_" } + suffix; + const std::string newHistTitle = histSrcPtr->GetTitle() + std::string{ " " } + suffix; + histDstPtr = std::make_shared(newHistName.c_str(), newHistTitle.c_str(), bins, binLow, binUp); + getObjectsManager()->startPublishing(histDstPtr.get()); + } + histDstPtr->Reset(); + // making projection + const auto binPos = chID + 1; + const std::unique_ptr proj(histSrcPtr->ProjectionY("proj", binPos, binPos)); + histDstPtr->Add(proj.get()); + } + } +} +void PostProcTask::setTimestampToMOs(long long timestamp) +{ + for (int iObj = 0; iObj < getObjectsManager()->getNumberPublishedObjects(); iObj++) { + auto mo = getObjectsManager()->getMonitorObject(iObj); + mo->addOrUpdateMetadata(mTimestampMetaField, std::to_string(timestamp)); + } +} + +void PostProcTask::finalize(Trigger t, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/src/TH1ReductorLaser.cxx b/Modules/FIT/FV0/src/TH1ReductorLaser.cxx new file mode 100644 index 0000000000..1b45e782bb --- /dev/null +++ b/Modules/FIT/FV0/src/TH1ReductorLaser.cxx @@ -0,0 +1,107 @@ +/// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +/// See https://alice-o2.web.cern.ch/copyright for details of the copyright +/// holders. All rights not expressly granted are reserved. +// +/// This software is distributed under the terms of the GNU General Public +/// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +/// In applying this license CERN does not waive the privileges and immunities +/// granted to it by virtue of its status as an Intergovernmental Organization +/// or submit itself to any jurisdiction. + +/// +/// \file TH1ReductorLaser.cxx +/// \author Piotr Konopka, developed to laser QC by Sandor Lokos +/// (sandor.lokos@cern.ch) +/// + +#include "FV0/TH1ReductorLaser.h" +#include +#include +namespace o2::quality_control_modules::fv0 +{ +void* TH1ReductorLaser::getBranchAddress() { return &mStats; } +const char* TH1ReductorLaser::getBranchLeafList() +{ + return "mean/D:mean1fit/D:mean2fit/D:stddev:stddev1fit:stddev2fit:entries"; +} +double Gauss(double* x, double* par) +{ + double arg = 0; + if (par[2] != 0) { + arg = (x[0] - par[1]) / par[2]; + } + double fitval = par[0] * exp(-0.5 * arg * arg); + return fitval; +} + +void TH1ReductorLaser::update(TObject* obj) +{ + auto histo = dynamic_cast(obj); + if (histo) { + /// Store the mean and the stddev (makes sense if there is one peak) + if (histo->GetBinCenter(1) != 0) + histo->GetXaxis()->SetRangeUser(1, histo->GetEntries() - 1); + + double origMean = histo->GetMean(); + double origStddev = histo->GetStdDev(); + mStats.entries = histo->GetEntries(); + /// Set parameters for single peak + mStats.mean = origMean; + mStats.stddev = origStddev; + + /// Store the original xmin and xmax + double origLowerLimit = histo->GetBinCenter(1); + double origUpperLimit = histo->GetBinCenter(histo->GetEntries() - 1); + + /// Get the first peak and have a guess of its Gaussian parameters + /// The parameters from the fit cannot be too far from the initial guess + /// (checked with good_fit routine) After that the original limits of the + /// histo must be restored + histo->GetXaxis()->SetRangeUser(histo->GetMean() - 2. * histo->GetStdDev(), + histo->GetMean()); + double peak1Amp = histo->GetBin(histo->GetMean()); + double peak1Pos = histo->GetMean(); + double peak1Wid = histo->GetStdDev(); + + TF1* GaussFit1 = new TF1("gauss1", Gauss, -2. * histo->GetStdDev(), + 2. * histo->GetStdDev(), 3); + + bool goodFit1 = false; + int iteration1 = 0; + int maxIteration1 = 10; + + GaussFit1->SetParameter(0, peak1Amp); + GaussFit1->SetParameter(1, peak1Pos); + GaussFit1->SetParameter(2, peak1Wid); + histo->Fit("gauss1"); + + mStats.mean1fit = GaussFit1->GetParameter(1); + mStats.stddev1fit = GaussFit1->GetParameter(2); + + /// Restoring the original limits + histo->GetXaxis()->SetRangeUser(origLowerLimit, origUpperLimit); + + /// Get the second peak and have a guess of its Gaussian parameters + /// The parameters from the fit cannot be too far from the initial guess + /// (checked with good_fit routine) After that the original limits of the + /// histo must be restored + histo->GetXaxis()->SetRangeUser(histo->GetMean(), + histo->GetMean() + 2. * histo->GetStdDev()); + double peak2Amp = histo->GetBin(histo->GetMean()); + double peak2Pos = histo->GetMean(); + double peak2Wid = histo->GetStdDev(); + + TF1* GaussFit2 = new TF1("gauss2", Gauss, -2. * histo->GetStdDev(), + 2. * histo->GetStdDev(), 3); + + GaussFit2->SetParameter(0, peak2Amp); + GaussFit2->SetParameter(1, peak2Pos); + GaussFit2->SetParameter(2, peak2Wid); + histo->Fit("gauss2"); + + mStats.mean2fit = GaussFit2->GetParameter(1); + mStats.stddev2fit = GaussFit2->GetParameter(2); + } +} +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/src/TriggersSwVsTcmCheck.cxx b/Modules/FIT/FV0/src/TriggersSwVsTcmCheck.cxx new file mode 100644 index 0000000000..fb5e5f14b8 --- /dev/null +++ b/Modules/FIT/FV0/src/TriggersSwVsTcmCheck.cxx @@ -0,0 +1,152 @@ +// Copyright 2023 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TriggersSwVsTcmCheck.cxx +/// \author Dawid Skora dawid.mateusz.skora@cern.ch +/// + +#include "FV0/TriggersSwVsTcmCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::fv0 +{ + +constexpr int kBinSwOnly = 1; +constexpr int kBinTcmOnly = 2; + +void TriggersSwVsTcmCheck::configure() +{ + if (auto param = mCustomParameters.find("ccdbUrl"); param != mCustomParameters.end()) { + setCcdbUrl(param->second); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, configured url = " << param->second << ENDM; + } else { + setCcdbUrl("o2-ccdb.internal"); + ILOG(Debug, Support) << "configure() : using deadChannelMap from CCDB, default url = " + << "o2-ccdb.internal" << ENDM; + } + + mPositionMsgBox = { 0.15, 0.75, 0.85, 0.9 }; + if (auto param = mCustomParameters.find("positionMsgBox"); param != mCustomParameters.end()) { + stringstream ss(param->second); + int i = 0; + while (ss.good()) { + if (i > 4) { + ILOG(Info, Support) << "Skipping next values provided for positionMsgBox" << ENDM; + break; + } + string substr; + getline(ss, substr, ','); + mPositionMsgBox[i] = std::stod(substr); + i++; + } + float minHeight = 0.09, minWidth = 0.19; + if (mPositionMsgBox[2] - mPositionMsgBox[0] < minWidth || mPositionMsgBox[3] - mPositionMsgBox[1] < minHeight) { + mPositionMsgBox = { 0.15, 0.75, 0.85, 0.9 }; + ILOG(Info, Support) << "MsgBox too small: returning to default" << ENDM; + } + } +} + +Quality TriggersSwVsTcmCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + int mNumErrors = 0; + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "TriggersSoftwareVsTCM") { + auto* histogram = dynamic_cast(mo->getObject()); + + if (!histogram) { + ILOG(Error, Support) << "check(): MO TriggersSoftwareVsTCM not found" << ENDM; + result.addFlag(FlagTypeFactory::Unknown(), "MO TriggersSoftwareVsTCM not found"); + result.set(Quality::Null); + return result; + } + + result = Quality::Good; + int numberOfBinsX = histogram->GetNbinsX(); + for (int binId = 1; binId <= numberOfBinsX; binId++) { + if ((bool)(histogram->GetBinContent(binId, kBinSwOnly)) || (bool)(histogram->GetBinContent(binId, kBinTcmOnly))) { + mNumErrors++; + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + result.addFlag(FlagTypeFactory::Unknown(), "Only SW or TCM trigger was activated"); + } + } + } + } + result.addMetadata("nErrors", std::to_string(mNumErrors)); + return result; +} + +void TriggersSwVsTcmCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (!mo) { + ILOG(Error, Support) << "beautify(): MO NULL pointer" << ENDM; + return; + } + + if (mo->getName() == "TriggersSoftwareVsTCM") { + auto* histogram = dynamic_cast(mo->getObject()); + + if (!histogram) { + ILOG(Error, Support) << "beautify(): MO TriggersSoftwareVsTCM not found" << ENDM; + return; + } + + TPaveText* msg = new TPaveText(mPositionMsgBox[0], mPositionMsgBox[1], mPositionMsgBox[2], mPositionMsgBox[3], "NDC"); + histogram->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + auto flags = checkResult.getFlags(); + for (int i = 0; i < int(flags.size()); i++) { + if (i == 0) { + msg->AddText(flags[i].second.c_str()); + break; + } + } + int color = kBlack; + if (checkResult == Quality::Good) { + color = kGreen + 1; + msg->AddText(">> Quality::Good <<"); + } else if (checkResult == Quality::Medium) { + color = kOrange - 1; + msg->AddText(">> Quality::Medium <<"); + } else if (checkResult == Quality::Bad) { + color = kRed; + msg->AddText(">> Quality::Bad <<"); + } + msg->SetFillStyle(1); + msg->SetLineWidth(3); + msg->SetLineColor(color); + msg->SetShadowColor(color); + msg->SetTextColor(color); + msg->SetMargin(0.0); + } +} + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FIT/FV0/test/testQcFV0.cxx b/Modules/FIT/FV0/test/testQcFV0.cxx new file mode 100644 index 0000000000..b61e619ee7 --- /dev/null +++ b/Modules/FIT/FV0/test/testQcFV0.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testFV0.cxx +/// \author My Name +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::fv0 +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::fv0 diff --git a/Modules/FOCAL/CMakeLists.txt b/Modules/FOCAL/CMakeLists.txt new file mode 100644 index 0000000000..71ef948d9a --- /dev/null +++ b/Modules/FOCAL/CMakeLists.txt @@ -0,0 +1,48 @@ +# ---- Library ---- + +add_library(O2QcFOCAL) + +target_sources(O2QcFOCAL PRIVATE src/PedestalCalibTask.cxx src/TestbeamRawTask.cxx) + +target_include_directories( + O2QcFOCAL + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcFOCAL PUBLIC O2QualityControl O2::DetectorsRaw O2::Headers O2::ITSMFTReconstruction O2::DataFormatsFOCAL O2::FOCALReconstruction O2::FOCALCalib) + +install(TARGETS O2QcFOCAL + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcFOCAL + HEADERS + include/FOCAL/TestbeamRawTask.h + include/FOCAL/PedestalCalibTask.h + LINKDEF include/FOCAL/LinkDef.h) + +install( + DIRECTORY etc DESTINATION Modules/FOCAL +) + +# Install headers +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/FOCAL + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Test(s) ---- +#set(TEST_SRCS test/testQcFOCAL.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcFOCAL Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() diff --git a/Modules/FOCAL/etc/testbeam.json b/Modules/FOCAL/etc/testbeam.json new file mode 100644 index 0000000000..a36f042bb5 --- /dev/null +++ b/Modules/FOCAL/etc/testbeam.json @@ -0,0 +1,41 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "http://consul-test.cern.ch:8500" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "RawTaskFOCAL": { + "active": "true", + "className": "o2::quality_control_modules::focal::TestbeamRawTask", + "moduleName": "QcFOCAL", + "detectorName": "FOC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query" : "readout:FLP/RAWDATA" + }, + "location": "remote" + } + } + } + } + \ No newline at end of file diff --git a/Modules/FOCAL/include/FOCAL/LinkDef.h b/Modules/FOCAL/include/FOCAL/LinkDef.h new file mode 100644 index 0000000000..f34177948c --- /dev/null +++ b/Modules/FOCAL/include/FOCAL/LinkDef.h @@ -0,0 +1,10 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::focal::TestbeamRawTask + ; + +#pragma link C++ class o2::quality_control_modules::focal::PedestalCalibTask + ; + +#endif diff --git a/Modules/FOCAL/include/FOCAL/PedestalCalibTask.h b/Modules/FOCAL/include/FOCAL/PedestalCalibTask.h new file mode 100644 index 0000000000..74d2899580 --- /dev/null +++ b/Modules/FOCAL/include/FOCAL/PedestalCalibTask.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalCalibTask.h +/// \author My Name +/// + +#ifndef QC_MODULE_FOCAL_FOCALPEDESTALCALIBTASK_H +#define QC_MODULE_FOCAL_FOCALPEDESTALCALIBTASK_H + +#include +#include "DataFormatsFOCAL/Constants.h" +#include "FOCALReconstruction/PadMapper.h" +#include "QualityControl/TaskInterface.h" + +class TH1; +class TH2; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::focal +{ + +/// \brief Example Quality Control DPL Task +/// \author My Name +class PedestalCalibTask final : public TaskInterface +{ + public: + /// \brief Constructor + PedestalCalibTask(); + /// Destructor + ~PedestalCalibTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + o2::focal::PadMapper mPadMapper; + std::array mPedestalChannel; + std::array mPedestalPosition; + std::size_t mNumberObjectsFetched = 0; +}; + +} // namespace o2::quality_control_modules::focal + +#endif // QC_MODULE_FOCAL_FOCALPEDESTALCALIBTASK_H diff --git a/Modules/FOCAL/include/FOCAL/TestbeamRawTask.h b/Modules/FOCAL/include/FOCAL/TestbeamRawTask.h new file mode 100644 index 0000000000..7e187008c5 --- /dev/null +++ b/Modules/FOCAL/include/FOCAL/TestbeamRawTask.h @@ -0,0 +1,174 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TestbeamRawTask.h +/// \author My Name +/// + +#ifndef QC_MODULE_FOCAL_FOCALTESTBEAMRAWTASK_H +#define QC_MODULE_FOCAL_FOCALTESTBEAMRAWTASK_H + +#include +#include + +#include "QualityControl/TaskInterface.h" +#include "CommonDataFormat/InteractionRecord.h" +#include "ITSMFTReconstruction/GBTWord.h" +#include "FOCALReconstruction/PadWord.h" +#include "FOCALReconstruction/PadDecoder.h" +#include "FOCALReconstruction/PadMapper.h" +#include "FOCALReconstruction/PixelDecoder.h" +#include "FOCALReconstruction/PixelMapper.h" + +class TH1; +class TH2; +class TProfile2D; + +namespace o2::focal +{ +class PadPedestal; +class PadBadChannelMap; +} // namespace o2::focal + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::focal +{ + +/// \brief Example Quality Control DPL Task +/// \author My Name +class TestbeamRawTask final : public TaskInterface +{ + public: + /// \brief Constructor + TestbeamRawTask() = default; + /// Destructor + ~TestbeamRawTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + static constexpr int PAD_ASICS = 18, + PIXEL_ROWS_IB = 512, + PIXEL_COLS_IB = 1024, + PIXEL_ROW_SEGMENTSIZE_IB = 8, + PIXEL_COL_SEGMENSIZE_IB = 32, + PIXEL_ROWS_OB = 512, + PIXEL_COLS_OB = 1024, + PIXEL_ROW_SEGMENTSIZE_OB = 8, + PIXEL_COL_SEGMENSIZE_OB = 32; + struct PadChannelProjections { + PadChannelProjections() = default; + ~PadChannelProjections(); + std::unordered_map mHistos; + void init(const std::vector channels, int ASICid); + void startPublishing(o2::quality_control::core::ObjectsManager& manager); + void reset(); + }; + void default_init(); + bool isLostTimeframe(framework::ProcessingContext& ctx) const; + void processPadPayload(gsl::span gbtpayload); + void processPixelPayload(gsl::span gbtpayload, uint16_t feeID); + void processPadEvent(gsl::span gbtpayload); + std::pair getNumberOfPixelSegments(o2::focal::PixelMapper::MappingType_t mappingtype) const; + std::pair getPixelSegment(const o2::focal::PixelHit& hit, o2::focal::PixelMapper::MappingType_t mappingtype, const o2::focal::PixelMapper::ChipPosition& chipMapping) const; + + o2::focal::PadDecoder mPadDecoder; ///< Decoder for pad data + o2::focal::PadMapper mPadMapper; ///< Mapping for Pads + o2::focal::PixelDecoder mPixelDecoder; ///< Decoder for pixel data + o2::focal::PadPedestal* mPadPedestalHandler = nullptr; ///< Pedestal handler for pad pedestal subtraction + o2::focal::PadBadChannelMap* mPadBadChannelMap = nullptr; ///< Bad channel map for pads + std::unique_ptr mPixelMapper; ///< Testbeam mapping for pixels + std::unordered_map mPixelNHitsAll; ///< Number of hits / event all layers + std::array, 2> mPixelNHitsLayer; ///< Number of hits / event layer + std::vector mHitSegmentCounter; ///< Number of hits / segment + std::vector mChannelsPadProjections; ///< Channels selected for pad projections + int mPadTOTCutADC = 1; ///< Max TOT for ADC plot + bool mDebugMode = false; ///< Additional debug verbosity + bool mDisablePads = false; ///< Disable pads + bool mDisablePixels = false; ///< Disable pixels + bool mEnablePedestalSubtraction = false; ///< Enable pedestal subtraction pads + bool mEnableBadChannelMask = false; ///< Enable bad channel map for pads + + ///////////////////////////////////////////////////////////////////////////////////// + /// General histograms + ///////////////////////////////////////////////////////////////////////////////////// + TH1* mTFerrorCounter = nullptr; ///< Number of TF builder errors + TH1* mFEENumberHBF = nullptr; ///< Number of HBFs per FEE + TH1* mFEENumberTF = nullptr; ///< Number of TFs per FEE + TH1* mNumLinksTF = nullptr; ///< Number of links per timeframe + TH1* mNumHBFPerCRU = nullptr; ///< Number of HBFs per CRU + TH2* mCRUcounter = nullptr; ///< CRU counter + TH1* mPayloadSizeTF = nullptr; ///< Payload size per timeframe + + ///////////////////////////////////////////////////////////////////////////////////// + /// Pad histograms + ///////////////////////////////////////////////////////////////////////////////////// + TH1* mPayloadSizePadsGBT = nullptr; ///< Payload size GBT words of pad data + std::array mPadASICChannelADC; ///< ADC per channel for each ASIC + std::array mPadASICChannelTOA; ///< TOA per channel for each ASIC + std::array mPadASICChannelTOT; ///< TOT per channel for each ASIC + std::array mHitMapPadASIC; ///< Hitmap per ASIC + std::array mPadTOTSumASIC; ///< Sum of TOT per ASIC + std::array mPadADCSumASIC; ///< Sum of ADC per ASIC + std::array mPadTOTCorrASIC; ///< TOT correlation between ASICs + std::array mPadADCCorrASIC; ///< ADC correlation between ASICs + TH1* mPadTOTSumGlobal = nullptr; ///< Sum of TOT per event + TH1* mPadADCSumGlobal = nullptr; ///< Sum of ADC per event + std::array, PAD_ASICS> mPadChannelProjections; ///< ADC projections per ASIC channel + TH2* mPadTOAvsASIC = nullptr; ///< average TOA for each ASICs + TH2* mPadTOAvsASIC_Ch14 = nullptr; ///< (no average) TOA for each ASICs for channel 14 + TH2* mPadTOAvsASIC_Ch16 = nullptr; ///< (no average) TOA for each ASICs for channel 16 + TH2* mPadTOAvsASIC_Ch19 = nullptr; ///< (no average) TOA for each ASICs for channel 19 + TH2* mPadTOAvsASIC_Ch48 = nullptr; ///< (no average) TOA for each ASICs for channel 48 + TH2* mPadTOAvsASIC_Ch52 = nullptr; ///< (no average) TOA for each ASICs for channel 52 + TH2* mPadTOAvsASIC_Ch61 = nullptr; ///< (no average) TOA for each ASICs for channel 61 + + TH1* mPadGlobalMIPADC_Ch14_Asic0 = nullptr; /// ADC for channel 14 for asic 0 after cmn subtraction + TH1* mPadGlobalMIPADC_Ch16_Asic0 = nullptr; /// ADC for channel 16 for asic 0 after cmn subtraction + TH1* mPadGlobalMIPADC_Ch19_Asic0 = nullptr; /// ADC for channel 19 for asic 0 after cmn subtraction + TH1* mPadGlobalMIPADC_Ch48_Asic0 = nullptr; /// ADC for channel 48 for asic 0 after cmn subtraction + TH1* mPadGlobalMIPADC_Ch52_Asic0 = nullptr; /// ADC for channel 52 for asic 0 after cmn subtraction + TH1* mPadGlobalMIPADC_Ch61_Asic0 = nullptr; /// ADC for channel 61 for asic 0 after cmn subtraction + std::array mPadTRIGvsWindowASIC; ///< Number of triggers per window for each ASIC + TH2* mPadGlobalTOTvsADC = nullptr; ///< TOT vs ADC for all pad layers + TH2* mPadGlobalTOAvsADC = nullptr; ///< TOA vs ADC for all pad layers + ///////////////////////////////////////////////////////////////////////////////////// + /// Pixel histograms + ///////////////////////////////////////////////////////////////////////////////////// + TH1* mPayloadSizePixelsGBT = nullptr; ///< Payload size pixel data in GBT word + TH1* mLinksWithPayloadPixel = nullptr; ///< HBF with payload per link + TH2* mTriggersFeePixel = nullptr; ///< Nunber of triggers per HBF and FEE ID + TProfile2D* mAverageHitsChipPixel = nullptr; ///< Average number of hits / chip + TH1* mHitsChipPixel = nullptr; ///< Number of hits / chip + TH2* mPixelChipsIDsFound = nullptr; ///< Chip IDs vs FEE IDs + TH2* mPixelChipsIDsHits = nullptr; ///< Chip IDs with hits vs FEE IDs + std::array mPixelLaneIDChipIDFEE; ///< Lane ID vs. chip ID for each FEE + std::array mPixelChipHitProfileLayer; ///< Hit profile for pixel chips + std::array mPixelChipHitmapLayer; ///< Hit map for pixel chips + std::array mPixelSegmentHitProfileLayer; ///< Hit profile for pixel segments + std::array mPixelSegmentHitmapLayer; ///< Hit map for pixel segments + std::array mPixelHitDistribitionLayer; ///< Hit distribution per chip in layer + TH1* mPixelHitsTriggerAll = nullptr; ///< Number of pixel hits / trigger + std::array mPixelHitsTriggerLayer; ///< Number of pixel hits in layer / trigger +}; + +} // namespace o2::quality_control_modules::focal + +#endif // QC_MODULE_FOCAL_FOCALTESTBEAMRAWTASK_H diff --git a/Modules/FOCAL/src/PedestalCalibTask.cxx b/Modules/FOCAL/src/PedestalCalibTask.cxx new file mode 100644 index 0000000000..c4636d0529 --- /dev/null +++ b/Modules/FOCAL/src/PedestalCalibTask.cxx @@ -0,0 +1,135 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalCalibTask.cxx +/// \author My Name +/// + +#include +#include +#include + +#include "FOCALCalib/PadPedestal.h" + +#include "QualityControl/QcInfoLogger.h" +#include "FOCAL/PedestalCalibTask.h" +#include +#include +#include + +namespace o2::quality_control_modules::focal +{ + +PedestalCalibTask::PedestalCalibTask() +{ + std::fill(mPedestalChannel.begin(), mPedestalChannel.end(), nullptr); + std::fill(mPedestalPosition.begin(), mPedestalPosition.end(), nullptr); +} + +PedestalCalibTask::~PedestalCalibTask() +{ + for (auto& hist : mPedestalChannel) { + delete hist; + } + for (auto& hist : mPedestalPosition) { + delete hist; + } +} + +void PedestalCalibTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + for (auto layer = 0; layer < mPedestalChannel.size(); layer++) { + mPedestalChannel[layer] = new TH1D(Form("mPedestalChannelLayer%d", layer), Form("Pedestals in layer %d", layer), o2::focal::constants::PADLAYER_MODULE_NCHANNELS, -0.5, o2::focal::constants::PADLAYER_MODULE_NCHANNELS - 0.5); + mPedestalChannel[layer]->SetXTitle("Channel ID"); + mPedestalChannel[layer]->SetYTitle("Pedestal (ADC counts)"); + getObjectsManager()->startPublishing(mPedestalChannel[layer]); + mPedestalPosition[layer] = new TH2D(Form("mPedestalPositionLayer%d", layer), Form("Pedestals in layer %d", layer), o2::focal::PadMapper::NCOLUMN, -0.5, o2::focal::PadMapper::NCOLUMN - 0.5, o2::focal::PadMapper::NROW, -0.5, o2::focal::PadMapper::NROW - 0.5); + mPedestalPosition[layer]->SetXTitle("Column"); + mPedestalPosition[layer]->SetYTitle("Row"); + getObjectsManager()->startPublishing(mPedestalPosition[layer]); + } +} + +void PedestalCalibTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + reset(); +} + +void PedestalCalibTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void PedestalCalibTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + ILOG(Debug, Support) << "Start monitoring data" << ENDM; + bool hasPedestalsCCDB = false; + for (auto&& input : o2::framework::InputRecordWalker(ctx.inputs())) { + // get message header + if (input.header != nullptr && input.payload != nullptr) { + auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(input); + const auto* header = o2::framework::DataRefUtils::getHeader(input); + if (payloadSize) { + if ((strcmp(header->dataOrigin.str, "CLP") == 0) && (strcmp(header->dataDescription.str, "FOC_PADPEDESTALSCLP") == 0)) { + ILOG(Info, Support) << "Pedestals for Pads found" << ENDM; + hasPedestalsCCDB = true; + } + } + } + } + + if (hasPedestalsCCDB) { + auto pedestals = o2::framework::DataRefUtils::as>(ctx.inputs().get("peds")); + if (pedestals) { + mNumberObjectsFetched++; + LOG(info) << "PedestalCalibTask::monitorData() : Extracted FOCAL Pad Pedestals"; + for (auto layer = 0; layer < mPedestalChannel.size(); layer++) { + mPedestalChannel[layer]->Reset(); + mPedestalPosition[layer]->Reset(); + for (auto chan = 0; chan < o2::focal::constants::PADLAYER_MODULE_NCHANNELS; chan++) { + try { + auto channelPedestal = pedestals->getPedestal(layer, chan); + mPedestalChannel[layer]->SetBinContent(chan + 1, channelPedestal); + auto [col, row] = mPadMapper.getRowColFromChannelID(chan); + mPedestalPosition[layer]->SetBinContent(col + 1, row + 1, channelPedestal); + } catch (o2::focal::PadPedestal::InvalidChannelException& e) { + ILOG(Error, Support) << "Error in pedestal access: " << e.what() << ENDM; + } + } + } + } + ILOG(Info, Support) << "Number of CCDB fetches of pedestal objects: " << mNumberObjectsFetched << ENDM; + } +} + +void PedestalCalibTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void PedestalCalibTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void PedestalCalibTask::reset() +{ + for (auto& hist : mPedestalChannel) { + hist->Reset(); + } + for (auto& hist : mPedestalPosition) { + hist->Reset(); + } +} + +} // namespace o2::quality_control_modules::focal diff --git a/Modules/FOCAL/src/TestbeamRawTask.cxx b/Modules/FOCAL/src/TestbeamRawTask.cxx new file mode 100644 index 0000000000..cec1b0fb10 --- /dev/null +++ b/Modules/FOCAL/src/TestbeamRawTask.cxx @@ -0,0 +1,1219 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TestbeamRawTask.cxx +/// \author My Name +/// + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "FOCAL/TestbeamRawTask.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::focal +{ + +TestbeamRawTask::~TestbeamRawTask() +{ + if (mTFerrorCounter) { + delete mTFerrorCounter; + } + if (mFEENumberHBF) { + delete mFEENumberHBF; + } + if (mFEENumberTF) { + delete mFEENumberTF; + } + if (mNumLinksTF) { + delete mNumLinksTF; + } + if (mNumHBFPerCRU) { + delete mNumHBFPerCRU; + } + if (mCRUcounter) { + delete mCRUcounter; + } + for (auto& hist : mPadASICChannelADC) { + delete hist; + } + for (auto& hist : mPadASICChannelTOA) { + delete hist; + } + for (auto& hist : mPadASICChannelTOT) { + delete hist; + }; + if (mPayloadSizePadsGBT) { + delete mPayloadSizePadsGBT; + } + if (mPayloadSizePixelsGBT) { + delete mPayloadSizePixelsGBT; + } + if (mPayloadSizeTF) { + delete mPayloadSizeTF; + } + if (mLinksWithPayloadPixel) { + delete mLinksWithPayloadPixel; + } + if (mTriggersFeePixel) { + delete mTriggersFeePixel; + } + if (mAverageHitsChipPixel) { + delete mAverageHitsChipPixel; + } + if (mHitsChipPixel) { + delete mHitsChipPixel; + } + if (mPixelHitsTriggerAll) { + delete mPixelHitsTriggerAll; + } + if (mPixelChipsIDsFound) { + delete mPixelChipsIDsFound; + } + if (mPixelChipsIDsHits) { + delete mPixelChipsIDsHits; + } + for (auto& hist : mPixelLaneIDChipIDFEE) { + delete hist; + } + for (auto& hist : mPixelChipHitProfileLayer) { + delete hist; + } + for (auto& hist : mPixelChipHitmapLayer) { + delete hist; + } + for (auto& hist : mPixelSegmentHitProfileLayer) { + delete hist; + } + for (auto& hist : mPixelSegmentHitmapLayer) { + delete hist; + } + for (auto& hist : mPixelHitDistribitionLayer) { + delete hist; + } + for (auto& hist : mPixelHitsTriggerLayer) { + delete hist; + } +} + +void TestbeamRawTask::default_init() +{ + std::fill(mPadASICChannelADC.begin(), mPadASICChannelADC.end(), nullptr); + std::fill(mPadASICChannelTOA.begin(), mPadASICChannelTOA.end(), nullptr); + std::fill(mPadASICChannelTOT.begin(), mPadASICChannelTOT.end(), nullptr); + std::fill(mHitMapPadASIC.begin(), mHitMapPadASIC.end(), nullptr); + std::fill(mPadTOTSumASIC.begin(), mPadTOTSumASIC.end(), nullptr); + std::fill(mPadADCSumASIC.begin(), mPadADCSumASIC.end(), nullptr); + std::fill(mPadTOTCorrASIC.begin(), mPadTOTCorrASIC.end(), nullptr); + std::fill(mPadADCCorrASIC.begin(), mPadADCCorrASIC.end(), nullptr); + + std::fill(mPixelLaneIDChipIDFEE.begin(), mPixelLaneIDChipIDFEE.end(), nullptr); + std::fill(mPixelChipHitProfileLayer.begin(), mPixelChipHitProfileLayer.end(), nullptr); + std::fill(mPixelChipHitmapLayer.begin(), mPixelChipHitmapLayer.end(), nullptr); + std::fill(mPixelSegmentHitProfileLayer.begin(), mPixelSegmentHitProfileLayer.end(), nullptr); + std::fill(mPixelSegmentHitmapLayer.begin(), mPixelSegmentHitmapLayer.end(), nullptr); + std::fill(mPixelHitDistribitionLayer.begin(), mPixelHitDistribitionLayer.end(), nullptr); + std::fill(mPixelHitsTriggerLayer.begin(), mPixelHitsTriggerLayer.end(), nullptr); + std::fill(mPadTRIGvsWindowASIC.begin(), mPadTRIGvsWindowASIC.end(), nullptr); +} + +void TestbeamRawTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + auto get_bool = [](const std::string_view input) -> bool { + return input == "true"; + }; + int winDur = 20; + auto hasWinDur = mCustomParameters.find("WinDur"); + if (hasWinDur != mCustomParameters.end()) { + winDur = std::stoi(hasWinDur->second); + } + mPadDecoder.setTriggerWinDur(winDur); + + auto hasDebugParam = mCustomParameters.find("Debug"); + if (hasDebugParam != mCustomParameters.end()) { + mDebugMode = get_bool(hasDebugParam->second); + } + + auto hasDisablePads = mCustomParameters.find("DisablePads"); + if (hasDisablePads != mCustomParameters.end()) { + mDisablePads = get_bool(hasDisablePads->second); + } + auto hasDisablePixels = mCustomParameters.find("DisablePixels"); + if (hasDisablePixels != mCustomParameters.end()) { + mDisablePixels = get_bool(hasDisablePixels->second); + } + auto hasPadPedestalSubtraction = mCustomParameters.find("SubtractPadPedestals"); + if (hasPadPedestalSubtraction != mCustomParameters.end()) { + mEnablePedestalSubtraction = get_bool(hasPadPedestalSubtraction->second); + } + auto hasPadBadChannelMap = mCustomParameters.find("PadBadChannelMasking"); + if (hasPadBadChannelMap != mCustomParameters.end()) { + mEnableBadChannelMask = get_bool(hasPadBadChannelMap->second); + } + + mChannelsPadProjections = { 52, 16, 19, 46, 59, 14, 42 }; + if (!mDisablePads) { + auto hasChannelsPadProjections = mCustomParameters.find("ChannelsPadProjections"); + if (hasChannelsPadProjections != mCustomParameters.end()) { + std::stringstream parser(hasChannelsPadProjections->second); + std::string buffer; + while (std::getline(parser, buffer, ',')) { + mChannelsPadProjections.emplace_back(std::stoi(buffer)); + } + } + std::sort(mChannelsPadProjections.begin(), mChannelsPadProjections.end(), std::less()); + + auto hasCutTOT = mCustomParameters.find("PadCutTOT"); + if (hasCutTOT != mCustomParameters.end()) { + mPadTOTCutADC = std::stoi(hasCutTOT->second); + } + } + + default_init(); + + ILOG(Info, Support) << "Pad QC: " << (mDisablePads ? "disabled" : "enabled") << ENDM; + ILOG(Info, Support) << "Pixel QC: " << (mDisablePixels ? "disabled" : "enabled") << ENDM; + if (!mDisablePads) { + ILOG(Info, Support) << "Window dur: " << winDur << ENDM; + std::stringstream channelstring; + bool firstchannel = true; + for (auto chan : mChannelsPadProjections) { + if (firstchannel) { + firstchannel = false; + } else { + channelstring << ", "; + } + channelstring << chan; + } + ILOG(Info, Support) << "Selected channels for pad ADC projections: " << channelstring.str() << ENDM; + } + ILOG(Info, Support) << "Debug mode: " << (mDebugMode ? "yes" : "no") << ENDM; + + if (!mDisablePixels) { + o2::focal::PixelMapper::MappingType_t mappingtype = o2::focal::PixelMapper::MappingType_t::MAPPING_IB; + auto pixellayout = mCustomParameters.find("Pixellayout"); + if (pixellayout != mCustomParameters.end()) { + if (pixellayout->second == "IB") { + mappingtype = o2::focal::PixelMapper::MappingType_t::MAPPING_IB; + } else if (pixellayout->second == "OB") { + mappingtype = o2::focal::PixelMapper::MappingType_t::MAPPING_OB; + } else { + ILOG(Fatal, Support) << "Unknown pixel setup: " << pixellayout->second << ENDM; + } + } + switch (mappingtype) { + case o2::focal::PixelMapper::MappingType_t::MAPPING_IB: + ILOG(Info, Support) << "Using pixel layout: IB" << ENDM; + break; + + case o2::focal::PixelMapper::MappingType_t::MAPPING_OB: + ILOG(Info, Support) << "Using pixel layout: OB" << ENDM; + break; + default: + break; + } + std::string mappingfile; + auto mappingfilename = mCustomParameters.find("Pixelmapping"); + if (mappingfilename != mCustomParameters.end()) { + mappingfile = mappingfilename->second; + ILOG(Info, Support) << "Using pixel chip mapping from file: " << mappingfile << ENDM; + } + mPixelMapper = std::make_unique(mappingfile.length() ? o2::focal::PixelMapper::MappingType_t::MAPPING_UNKNOWN : mappingtype); + if (mappingfile.length()) { + try { + mPixelMapper->setMappingFile(mappingfile, mappingtype); + } catch (o2::focal::PixelMapper::MappingNotSetException& e) { + ILOG(Fatal, Support) << "Unable to initialize pixel chip mapping: " << e << ENDM; + } + } + } + + ///////////////////////////////////////////////////////////////// + /// general histograms + ///////////////////////////////////////////////////////////////// + mTFerrorCounter = new TH1F("NumberOfTFerror", "Number of TFbuilder errors", 2, 0.5, 2.5); + mTFerrorCounter->GetYaxis()->SetTitle("Time Frame Builder Error"); + mTFerrorCounter->GetXaxis()->SetBinLabel(1, "empty"); + mTFerrorCounter->GetXaxis()->SetBinLabel(2, "filled"); + getObjectsManager()->startPublishing(mTFerrorCounter); + + mFEENumberHBF = new TH1F("NumberOfHBFPerFEE", "Number of HBFs per FEE", 100001, -0.5, 100000.5); + mFEENumberHBF->GetXaxis()->SetTitle("FEE ID"); + mFEENumberHBF->GetYaxis()->SetTitle("Number of HBFs"); + getObjectsManager()->startPublishing(mFEENumberHBF); + + mFEENumberTF = new TH1F("NumberOfTFPerFEE", "Number of TFs per FEE", 100001, -0.5, 100000.5); + mFEENumberTF->GetXaxis()->SetTitle("FEE ID"); + mFEENumberTF->GetYaxis()->SetTitle("Number of TFs"); + getObjectsManager()->startPublishing(mFEENumberTF); + + mNumLinksTF = new TH1F("NumberOfLinksPerTF", "Number of Links / timeframe", 101, -0.5, 100.5); + mNumLinksTF->GetXaxis()->SetTitle("Number of links"); + mNumLinksTF->GetYaxis()->SetTitle("Number of TFs"); + getObjectsManager()->startPublishing(mNumLinksTF); + + mNumHBFPerCRU = new TH1F("NumberOfHBFPerCRU", "Number of HBFs / CRU", 10001, -0.5, 10000.5); + mNumHBFPerCRU->GetXaxis()->SetTitle("Endpoint ID"); + mNumHBFPerCRU->GetYaxis()->SetTitle("Link ID"); + getObjectsManager()->startPublishing(mNumHBFPerCRU); + + mCRUcounter = new TH2D("CRUcounter", "Number of HBFs per CRU link", 2, -0.5, 1.5, 21, -0.5, 20.5); + mCRUcounter->GetXaxis()->SetTitle("Endpoint ID"); + mCRUcounter->GetYaxis()->SetTitle("Link ID"); + getObjectsManager()->startPublishing(mCRUcounter); + + mPayloadSizeTF = new TH1D("PayloadSizeTF", "Payload size TF", 10000, 0., 10000.); + getObjectsManager()->startPublishing(mPayloadSizeTF); + + ///////////////////////////////////////////////////////////////// + /// PAD histograms + ///////////////////////////////////////////////////////////////// + + if (!mDisablePads) { + constexpr int PAD_CHANNELS = 76; + constexpr int RANGE_ADC = 1024; + constexpr int RANGE_TOA = 1024; + constexpr int RANGE_TOT = 4096; + + mPadTOTSumGlobal = new TH1D("PadTOTSumGlobal", "Sum of all TOTs in a time frame; TOT sum; counts", 250, 0., 50000); + mPadTOTSumGlobal->SetStats(false); + mPadADCSumGlobal = new TH1D("PadADCSumGlobal", "Sum of all ADCs in a time frame; ADC sum; counts", 250, 0., 200E3); + mPadADCSumGlobal->SetStats(false); + + getObjectsManager()->startPublishing(mPadTOTSumGlobal); + getObjectsManager()->startPublishing(mPadADCSumGlobal); + + for (int iasic = 0; iasic < PAD_ASICS; iasic++) { + mPadASICChannelADC[iasic] = new TH2D(Form("PadADC_ASIC_%d", iasic), Form("ADC vs. channel ID for ASIC %d; channel ID; ADC", iasic), PAD_CHANNELS, -0.5, PAD_CHANNELS - 0.5, RANGE_ADC, 0., RANGE_ADC); + mPadASICChannelADC[iasic]->SetStats(false); + mPadASICChannelTOA[iasic] = new TH2D(Form("PadTOA_ASIC_%d", iasic), Form("TOA vs. channel ID for ASIC %d; channel ID; TOA", iasic), PAD_CHANNELS, -0.5, PAD_CHANNELS - 0.5, RANGE_TOA, 0., RANGE_TOA); + mPadASICChannelTOA[iasic]->SetStats(false); + mPadASICChannelTOT[iasic] = new TH2D(Form("PadTOT_ASIC_%d", iasic), Form("TOT vs. channel ID for ASIC %d; channel ID; TOT", iasic), PAD_CHANNELS, -0.5, PAD_CHANNELS - 0.5, RANGE_TOT / 4, 0., RANGE_TOT); + mPadASICChannelTOT[iasic]->SetStats(false); + mHitMapPadASIC[iasic] = new TProfile2D(Form("HitmapPadASIC_%d", iasic), Form("Hitmap for ASIC %d; col; row", iasic), o2::focal::PadMapper::NROW, -0.5, o2::focal::PadMapper::NROW - 0.5, o2::focal::PadMapper::NCOLUMN, -0.5, o2::focal::PadMapper::NCOLUMN - 0.5); + mHitMapPadASIC[iasic]->SetStats(false); + + mPadTOTSumASIC[iasic] = new TH1D(Form("PadTOTSumASIC%d", iasic), Form("TOT sum for ASIC %d; TOT ASIC %d", iasic, iasic), 300, 0., 6000); + mPadTOTSumASIC[iasic]->SetStats(false); + + mPadADCSumASIC[iasic] = new TH1D(Form("PadADCSumASIC_%d", iasic), Form("ADC sum for ASIC %d; ADC ASIC %d", iasic, iasic), 400, 0., 16000); + mPadADCSumASIC[iasic]->SetStats(false); + + mPadTOTCorrASIC[iasic] = new TH2D(Form("PadTOTCorrASIC_%d_%d", iasic + 1, iasic), Form("TOT ASIC %d vs. ASIC %d; TOT ASIC %d; TOT ASIC %d", iasic, iasic + 1, iasic, iasic + 1), 300, 0., 6000, 300, 0., 6000); + mPadTOTCorrASIC[iasic]->SetStats(false); + + mPadADCCorrASIC[iasic] = new TH2D(Form("PadADCCorrASIC_%d_%d", iasic + 1, iasic), Form("ADC ASIC %d vs. ASIC %d; ADC ASIC %d; ADC ASIC %d", iasic, iasic + 1, iasic, iasic + 1), 400, 0., 16000, 400, 0., 16000); + mPadADCCorrASIC[iasic]->SetStats(false); + + mPadTRIGvsWindowASIC[iasic] = new TH2D(Form("PadTRIGvsWindowASIC_%d", iasic), Form("TRIG vs. Window ASIC %d; Window; Trig", iasic), 20, 0., 20., 128, 0., 128); + mPadTRIGvsWindowASIC[iasic]->SetStats(false); + + getObjectsManager()->startPublishing(mPadASICChannelADC[iasic]); + getObjectsManager()->startPublishing(mPadASICChannelTOA[iasic]); + getObjectsManager()->startPublishing(mPadASICChannelTOT[iasic]); + getObjectsManager()->startPublishing(mHitMapPadASIC[iasic]); + getObjectsManager()->startPublishing(mPadTOTSumASIC[iasic]); + getObjectsManager()->startPublishing(mPadADCSumASIC[iasic]); + getObjectsManager()->startPublishing(mPadTOTCorrASIC[iasic]); + getObjectsManager()->startPublishing(mPadADCCorrASIC[iasic]); + getObjectsManager()->startPublishing(mPadTRIGvsWindowASIC[iasic]); + + mPadChannelProjections[iasic] = std::make_unique(); + mPadChannelProjections[iasic]->init(mChannelsPadProjections, iasic); + mPadChannelProjections[iasic]->startPublishing(*getObjectsManager()); + } + + mPayloadSizePadsGBT = new TH1D("PayloadSizePadGBT", "Payload size GBT words", 10000, 0., 10000.); + getObjectsManager()->startPublishing(mPayloadSizePadsGBT); + + // Pad average TOA per ASIC for all channels with TOA>0 + mPadTOAvsASIC = new TH2D("PadTOAvsASIC", "average Pad TOA vs. ASIC (for TOA>0); TOA; ASIC NO.", 512, 0., 1024., PAD_ASICS, 0, PAD_ASICS); + mPadTOAvsASIC->SetStats(false); + getObjectsManager()->startPublishing(mPadTOAvsASIC); + + mPadTOAvsASIC_Ch14 = new TH2D("PadTOAvsASIC_Ch14", "TOA vs. ASIC for channel 14; TOA; ASIC NO.", 512, 0., 1024., PAD_ASICS, 0, PAD_ASICS); + mPadTOAvsASIC_Ch16 = new TH2D("PadTOAvsASIC_Ch16", "TOA vs. ASIC for channel 16; TOA; ASIC NO.", 512, 0., 1024., PAD_ASICS, 0, PAD_ASICS); + mPadTOAvsASIC_Ch19 = new TH2D("PadTOAvsASIC_Ch19", "TOA vs. ASIC for channel 19; TOA; ASIC NO.", 512, 0., 1024., PAD_ASICS, 0, PAD_ASICS); + mPadTOAvsASIC_Ch48 = new TH2D("PadTOAvsASIC_Ch48", "TOA vs. ASIC for channel 48; TOA; ASIC NO.", 512, 0., 1024., PAD_ASICS, 0, PAD_ASICS); + mPadTOAvsASIC_Ch52 = new TH2D("PadTOAvsASIC_Ch52", "TOA vs. ASIC for channel 52; TOA; ASIC NO.", 512, 0., 1024., PAD_ASICS, 0, PAD_ASICS); + mPadTOAvsASIC_Ch61 = new TH2D("PadTOAvsASIC_Ch61", "TOA vs. ASIC for channel 61; TOA; ASIC NO.", 512, 0., 1024., PAD_ASICS, 0, PAD_ASICS); + mPadTOAvsASIC_Ch14->SetStats(false); + mPadTOAvsASIC_Ch16->SetStats(false); + mPadTOAvsASIC_Ch19->SetStats(false); + mPadTOAvsASIC_Ch48->SetStats(false); + mPadTOAvsASIC_Ch52->SetStats(false); + mPadTOAvsASIC_Ch61->SetStats(false); + getObjectsManager()->startPublishing(mPadTOAvsASIC_Ch14); + getObjectsManager()->startPublishing(mPadTOAvsASIC_Ch16); + getObjectsManager()->startPublishing(mPadTOAvsASIC_Ch19); + getObjectsManager()->startPublishing(mPadTOAvsASIC_Ch48); + getObjectsManager()->startPublishing(mPadTOAvsASIC_Ch52); + getObjectsManager()->startPublishing(mPadTOAvsASIC_Ch61); + + mPadGlobalMIPADC_Ch14_Asic0 = new TH1D("PadGlobalMIPADC_Ch14_Asic0", "Pad global ADC after CMN sub for channel 14, ASIC 0; ADC", 130, -30., 100.); + mPadGlobalMIPADC_Ch16_Asic0 = new TH1D("PadGlobalMIPADC_Ch16_Asic0", "Pad global ADC after CMN sub for channel 16, ASIC 0; ADC", 130, -30., 100.); + mPadGlobalMIPADC_Ch19_Asic0 = new TH1D("PadGlobalMIPADC_Ch19_Asic0", "Pad global ADC after CMN sub for channel 19, ASIC 0; ADC", 130, -30., 100.); + mPadGlobalMIPADC_Ch48_Asic0 = new TH1D("PadGlobalMIPADC_Ch48_Asic0", "Pad global ADC after CMN sub for channel 48, ASIC 0; ADC", 130, -30., 100.); + mPadGlobalMIPADC_Ch52_Asic0 = new TH1D("PadGlobalMIPADC_Ch52_Asic0", "Pad global ADC after CMN sub for channel 52, ASIC 0; ADC", 130, -30., 100.); + mPadGlobalMIPADC_Ch61_Asic0 = new TH1D("PadGlobalMIPADC_Ch61_Asic0", "Pad global ADC after CMN sub for channel 61, ASIC 0; ADC", 130, -30., 100.); + mPadGlobalMIPADC_Ch14_Asic0->SetStats(false); + mPadGlobalMIPADC_Ch16_Asic0->SetStats(false); + mPadGlobalMIPADC_Ch19_Asic0->SetStats(false); + mPadGlobalMIPADC_Ch48_Asic0->SetStats(false); + mPadGlobalMIPADC_Ch52_Asic0->SetStats(false); + mPadGlobalMIPADC_Ch61_Asic0->SetStats(false); + getObjectsManager()->startPublishing(mPadGlobalMIPADC_Ch14_Asic0); + getObjectsManager()->startPublishing(mPadGlobalMIPADC_Ch16_Asic0); + getObjectsManager()->startPublishing(mPadGlobalMIPADC_Ch19_Asic0); + getObjectsManager()->startPublishing(mPadGlobalMIPADC_Ch48_Asic0); + getObjectsManager()->startPublishing(mPadGlobalMIPADC_Ch52_Asic0); + getObjectsManager()->startPublishing(mPadGlobalMIPADC_Ch61_Asic0); + + mPadGlobalTOAvsADC = new TH2D("PadGlobalTOAvsADC", "Pad average TOA vs. sum ADC; sum ADC; average TOA", 250, 0., 200E3, 512, 0., 1024.); + mPadGlobalTOAvsADC->SetStats(false); + getObjectsManager()->startPublishing(mPadGlobalTOAvsADC); + mPadGlobalTOTvsADC = new TH2D("PadGlobalTOTvsADC", "Pad sum TOT vs. sum ADC; sum ADC; sum TOT", 250, 0., 200E3, 250, 0., 50000); + mPadGlobalTOTvsADC->SetStats(false); + getObjectsManager()->startPublishing(mPadGlobalTOTvsADC); + } + + ///////////////////////////////////////////////////////////////// + /// Pixel histograms + ///////////////////////////////////////////////////////////////// + if (!mDisablePixels) { + constexpr int FEES = 30; + constexpr int MAX_CHIPS = 14; // For the moment leave completely open + constexpr int MAX_TRIGGERS = 10; // Number of triggers / HBF usually sparse + mLinksWithPayloadPixel = new TH1D("Pixel_PagesFee", "HBF vs. FEE ID; FEE ID; HBFs", FEES, -0.5, FEES - 0.5); + getObjectsManager()->startPublishing(mLinksWithPayloadPixel); + mTriggersFeePixel = new TH2D("Pixel_NumTriggerHBF", "Number of triggers per HBF and FEE; FEE ID; Number of triggers / HBF", FEES, -0.5, FEES - 0.5, MAX_TRIGGERS, -0.5, MAX_TRIGGERS - 0.5); + mTriggersFeePixel->SetStats(false); + getObjectsManager()->startPublishing(mTriggersFeePixel); + mAverageHitsChipPixel = new TProfile2D("Pixel_AverageNumberOfHitsChip", "Average number of hits / chip", FEES, -0.5, FEES - 0.5, MAX_CHIPS, -0.5, MAX_CHIPS - 0.5); + mAverageHitsChipPixel->SetStats(false); + getObjectsManager()->startPublishing(mAverageHitsChipPixel); + mHitsChipPixel = new TH1D("Pixel_NumberHits", "Number of hits / chip", 50, 0., 50); + mHitsChipPixel->SetStats(false); + getObjectsManager()->startPublishing(mHitsChipPixel); + mPixelHitsTriggerAll = new TH1D("Pixel_TotalNumberHitsTrigger", "Number of hits per trigger; Number of hits; Number of events", 1500, -0.5, 15000); + mPixelHitsTriggerAll->SetStats(false); + getObjectsManager()->startPublishing(mPixelHitsTriggerAll); + mPixelChipsIDsFound = new TH2D("Pixel_ChipIDsFEE", "Chip ID vs. FEE ID", FEES, -0.5, FEES - 0.5, MAX_CHIPS, -0.5, MAX_CHIPS - 0.5); + mPixelChipsIDsFound->SetDirectory(nullptr); + getObjectsManager()->startPublishing(mPixelChipsIDsFound); + mPixelChipsIDsHits = new TH2D("Pixel_ChipIDsFilledFEE", "Chip ID with hits vs. FEE ID", FEES, -0.5, FEES - 0.5, MAX_CHIPS, -0.5, MAX_CHIPS - 0.5); + mPixelChipsIDsHits->SetDirectory(nullptr); + getObjectsManager()->startPublishing(mPixelChipsIDsHits); + + mPayloadSizePixelsGBT = new TH1D("PayloadSizePixelsGBT", "Payload size GBT words", 10000, 0., 10000.); + getObjectsManager()->startPublishing(mPayloadSizePixelsGBT); + + for (int ifee = 0; ifee < 4; ifee++) { + mPixelLaneIDChipIDFEE[ifee] = new TH2D(Form("Pixel_LaneIDChipID_FEE%d", ifee), Form("Lane ID vs. Chip ID for FEE %d; Lane ID; ChipID", ifee), 57, -0.5, 56.5, 31, -0.5, 30.5); + mPixelLaneIDChipIDFEE[ifee]->SetDirectory(nullptr); + getObjectsManager()->startPublishing(mPixelLaneIDChipIDFEE[ifee]); + } + + constexpr int PIXEL_LAYERS = 2; + auto pixel_segments = getNumberOfPixelSegments(mPixelMapper->getMappingType()); + auto pixel_columns = mPixelMapper->getNumberOfColumns(), + pixel_rows = mPixelMapper->getNumberOfRows(), + pixel_chips = pixel_columns * pixel_rows, + segments_colums = pixel_columns * pixel_segments.first, + segments_rows = pixel_rows * pixel_segments.second; + ILOG(Info, Support) << "Pixel hitmap, segments per chip (" << pixel_segments.first << " columns, " << pixel_segments.second << " rows), segment columns " << segments_colums << ", rows " << segments_rows << ENDM; + std::array pixelLayerIndex = { { 10, 5 } }; + ILOG(Info, Support) << "Setup acceptance histograms " << pixel_columns << " colums and " << pixel_rows << " rows (" << pixel_chips << " chips)" << ENDM; + for (int ilayer = 0; ilayer < PIXEL_LAYERS; ilayer++) { + mPixelChipHitProfileLayer[ilayer] = new TProfile2D(Form("Pixel_Hitprofile_%d", ilayer), Form("Pixel hit profile in layer %d; X; Y", pixelLayerIndex[ilayer]), pixel_columns, -0.5, pixel_columns - 0.5, pixel_rows, -0.5, pixel_rows - 0.5); + mPixelChipHitProfileLayer[ilayer]->SetStats(false); + getObjectsManager()->startPublishing(mPixelChipHitProfileLayer[ilayer]); + mPixelChipHitmapLayer[ilayer] = new TH2D(Form("Pixel_Hitmap_%d", ilayer), Form("Pixel hitmap in layer %d; X; Y", pixelLayerIndex[ilayer]), pixel_columns, -0.5, pixel_columns - 0.5, pixel_rows, -0.5, pixel_rows - 0.5); + mPixelChipHitmapLayer[ilayer]->SetStats(false); + getObjectsManager()->startPublishing(mPixelChipHitmapLayer[ilayer]); + mPixelSegmentHitProfileLayer[ilayer] = new TProfile2D(Form("Pixel_Segment_Hitprofile_%d", ilayer), Form("Pixel hit profile in layer %d; X; Y", pixelLayerIndex[ilayer]), segments_colums, -0.5, segments_colums - 0.5, segments_rows, -0.5, segments_rows - 0.5); + mPixelSegmentHitProfileLayer[ilayer]->SetStats(false); + getObjectsManager()->startPublishing(mPixelSegmentHitProfileLayer[ilayer]); + mPixelSegmentHitmapLayer[ilayer] = new TH2D(Form("Pixel_Segment_Hitmap_%d", ilayer), Form("Pixel hitmap in layer %d; X; Y", pixelLayerIndex[ilayer]), segments_colums, -0.5, segments_colums - 0.5, segments_rows, -0.5, segments_rows - 0.5); + mPixelSegmentHitmapLayer[ilayer]->SetStats(false); + getObjectsManager()->startPublishing(mPixelSegmentHitmapLayer[ilayer]); + mPixelHitDistribitionLayer[ilayer] = new TH2D(Form("Pixel_Hitdist_%d", ilayer), Form("Pixel hit distribution in layer %d; Number of hits / chip; Number of events", pixelLayerIndex[ilayer]), pixel_chips, -0.5, pixel_chips - 0.5, 101, -0.5, 100.5); + mPixelHitDistribitionLayer[ilayer]->SetStats(false); + getObjectsManager()->startPublishing(mPixelHitDistribitionLayer[ilayer]); + mPixelHitsTriggerLayer[ilayer] = new TH1D(Form("Pixel_NumberHitsTrigger_%d", ilayer), Form("Number of hits per trigger in layer %d; Number of hits / layer; Number of events", pixelLayerIndex[ilayer]), 1500, 0, 15000.); + mPixelHitsTriggerLayer[ilayer]->SetStats(false); + getObjectsManager()->startPublishing(mPixelHitsTriggerLayer[ilayer]); + } + mHitSegmentCounter.resize(segments_colums * segments_rows); + } +} + +void TestbeamRawTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + reset(); + // Pedestal come from pedestal runs, usually not updated during the run + std::map metadata; + if (mEnablePedestalSubtraction) { + mPadPedestalHandler = retrieveConditionAny("FOC/Calib/PadPedestals", metadata); + if (mPadPedestalHandler) { + ILOG(Info, Support) << "Pedestal data found - pedestals will be subtracted for Pads" << ENDM; + } else { + ILOG(Error, Support) << "No pedestal data found - pedestal subtraction not possible" << ENDM; + } + } + if (mEnableBadChannelMask) { + mPadBadChannelMap = retrieveConditionAny("FOC/Calib/PadBadChannelMap", metadata); + if (mPadPedestalHandler) { + ILOG(Info, Support) << "Bad channel map for pads found - bad pad channels will be masked" << ENDM; + } else { + ILOG(Error, Support) << "No bad channel map found for pads - bad channel mask cannot be applied" << ENDM; + } + } +} + +void TestbeamRawTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TestbeamRawTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + ILOG(Info, Support) << "Called" << ENDM; + ILOG(Info, Support) << "Received " << ctx.inputs().size() << " inputs" << ENDM; + mPixelNHitsAll.clear(); + for (auto& hitcounterLayer : mPixelNHitsLayer) { + hitcounterLayer.clear(); + } + + if (isLostTimeframe(ctx)) { + mTFerrorCounter->Fill(1); + return; + } + mTFerrorCounter->Fill(2); + + std::unordered_set feeIDs; + + int inputs = 0; + std::vector rawbuffer; + uint16_t currentfee = 0; + for (const auto& rawData : framework::InputRecordWalker(ctx.inputs())) { + if (rawData.header != nullptr && rawData.payload != nullptr) { + const auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(rawData); + mPayloadSizeTF->Fill(payloadSize); + auto header = o2::framework::DataRefUtils::getHeader(rawData); + ILOG(Debug, Support) << "Channel " << header->dataOrigin.str << "/" << header->dataDescription.str << "/" << header->subSpecification << ENDM; + + gsl::span databuffer(rawData.payload, payloadSize); + ILOG(Debug, Support) << "Payload buffer has size " << databuffer.size() << ENDM; + int currentpos = 0; + while (currentpos < databuffer.size()) { + auto rdh = reinterpret_cast(databuffer.data() + currentpos); + if (mDebugMode) { + o2::raw::RDHUtils::printRDH(rdh); + } + + if (o2::raw::RDHUtils::getMemorySize(rdh) > o2::raw::RDHUtils::getHeaderSize(rdh)) { + // non-0 payload size: + auto payloadsize = o2::raw::RDHUtils::getMemorySize(rdh) - o2::raw::RDHUtils::getHeaderSize(rdh); + auto fee = static_cast(o2::raw::RDHUtils::getFEEID(rdh)); + ILOG(Debug, Support) << "Next RDH: " << ENDM; + std::stringstream ss; + ss << "Found fee 0x" << std::hex << fee << std::dec << " (System " << (fee == 0xcafe ? "Pads" : "Pixels") << ")"; + ILOG(Debug, Support) << ss.str() << ENDM; + ILOG(Debug, Support) << "Found trigger BC: " << o2::raw::RDHUtils::getTriggerBC(rdh) << ENDM; + ILOG(Debug, Support) << "Found trigger Oribt: " << o2::raw::RDHUtils::getTriggerOrbit(rdh) << ENDM; + ILOG(Debug, Support) << "Found payload size: " << payloadsize << ENDM; + ILOG(Debug, Support) << "Found offset to next: " << o2::raw::RDHUtils::getOffsetToNext(rdh) << ENDM; + ILOG(Debug, Support) << "Stop bit: " << (o2::raw::RDHUtils::getStop(rdh) ? "yes" : "no") << ENDM; + ILOG(Debug, Support) << "Number of GBT words: " << (payloadsize * sizeof(char) / (fee == 0xcafe ? sizeof(o2::focal::PadGBTWord) : sizeof(o2::itsmft::GBTWord))) << ENDM; + auto page_payload = databuffer.subspan(currentpos + o2::raw::RDHUtils::getHeaderSize(rdh), payloadsize); + std::copy(page_payload.begin(), page_payload.end(), std::back_inserter(rawbuffer)); + } + + auto trigger = o2::raw::RDHUtils::getTriggerType(rdh); + if (trigger & o2::trigger::SOT || trigger & o2::trigger::HB) { + if (o2::raw::RDHUtils::getStop(rdh)) { + ILOG(Debug, Support) << "Stop bit received - processing payload" << ENDM; + mFEENumberHBF->Fill(currentfee); + mNumHBFPerCRU->Fill(o2::raw::RDHUtils::getCRUID(rdh)); + mCRUcounter->Fill(o2::raw::RDHUtils::getEndPointID(rdh), o2::raw::RDHUtils::getLinkID(rdh)); + auto fee_found = feeIDs.find(currentfee); + if (fee_found == feeIDs.end()) { + feeIDs.insert(currentfee); + } + // Data ready + if (currentfee == 0xcafe) { // Use FEE ID 0xcafe for PAD data + // Pad data + if (!mDisablePads) { + ILOG(Debug, Support) << "Processing PAD data" << ENDM; + auto payloadsizeGBT = rawbuffer.size() * sizeof(char) / sizeof(o2::focal::PadGBTWord); + mPayloadSizePadsGBT->Fill(payloadsizeGBT); + ILOG(Debug) << "Found pixel payload of size " << rawbuffer.size() << " (" << payloadsizeGBT << " GBT words)" << ENDM; + processPadPayload(gsl::span(reinterpret_cast(rawbuffer.data()), payloadsizeGBT)); + } + } else { // All other FEEs are pixel FEEs + // Pixel data + if (!mDisablePixels) { + auto feeID = o2::raw::RDHUtils::getFEEID(rdh); + ILOG(Debug, Support) << "Processing Pixel data from FEE " << feeID << ENDM; + auto payloadsizeGBT = rawbuffer.size() * sizeof(char) / sizeof(o2::itsmft::GBTWord); + mPayloadSizePixelsGBT->Fill(payloadsizeGBT); + ILOG(Debug, Support) << "Found pixel payload of size " << rawbuffer.size() << " (" << payloadsizeGBT << " GBT words)" << ENDM; + processPixelPayload(gsl::span(reinterpret_cast(rawbuffer.data()), payloadsizeGBT), feeID); + } + } + rawbuffer.clear(); + } else { + ILOG(Debug, Support) << "New HBF or Timeframe" << ENDM; + currentfee = o2::raw::RDHUtils::getFEEID(rdh); + std::stringstream ss; + ss << "Using FEE ID: 0x" << std::hex << currentfee << std::dec; + ILOG(Debug, Support) << ss.str() << ENDM; + } + } + currentpos += o2::raw::RDHUtils::getOffsetToNext(rdh); + } + } else { + ILOG(Error, Support) << "Input " << inputs << ": Either header or payload is nullptr" << ENDM; + } + inputs++; + } + + // Fill number of hit/trigger histogram of the pixels + for (auto& [trg, nhits] : mPixelNHitsAll) { + mPixelHitsTriggerAll->Fill(nhits); + } + for (int ilayer = 0; ilayer < 2; ilayer++) { + for (auto& [trg, nhits] : mPixelNHitsLayer[ilayer]) { + mPixelHitsTriggerLayer[ilayer]->Fill(nhits); + } + } + mNumLinksTF->Fill(feeIDs.size()); + for (auto& fee : feeIDs) { + ILOG(Debug, Support) << "Found fee: " << fee << ENDM; + mFEENumberTF->Fill(fee); + } +} + +void TestbeamRawTask::processPadPayload(gsl::span padpayload) +{ + // processPadEvent(padpayload); + + constexpr std::size_t EVENTSIZEPADGBT = 1180; + int nevents = padpayload.size() / EVENTSIZEPADGBT; + for (int iev = 0; iev < nevents; iev++) { + processPadEvent(padpayload.subspan(iev * EVENTSIZEPADGBT, EVENTSIZEPADGBT)); + } +} + +void TestbeamRawTask::processPadEvent(gsl::span padpayload) +{ + // adc vs. TOT + // adc vs. TOA + // adc for MIP -30 to 100 in 130bin + mPadDecoder.reset(); + mPadDecoder.decodeEvent(padpayload); + const auto& eventdata = mPadDecoder.getData(); + double totglobalsum = 0; + double adcglobalsum = 0; + double toaglobalaverage = 0; // toa average of all layers + + std::array kTOTsum = { 0 }; + std::array kADCsum = { 0 }; + double cmn = 0; + std::array kADCForCMN = { 0 }; + for (int iasic = 0; iasic < PAD_ASICS; iasic++) { + const auto& asic = eventdata[iasic].getASIC(); + ILOG(Debug, Support) << "ASIC " << iasic << ", Header 0: " << asic.getFirstHeader() << ENDM; + ILOG(Debug, Support) << "ASIC " << iasic << ", Header 1: " << asic.getSecondHeader() << ENDM; + int currentchannel = 0; + double totsum = 0; + double adcsum = 0; + + double adc_ch14 = 0; + double adc_ch16 = 0; + double adc_ch19 = 0; + double adc_ch48 = 0; + double adc_ch52 = 0; + double adc_ch61 = 0; + + double asicAverageTOA = 0; // average TOA of all channels with TOA>0 + int nChWithTOA = 0; // number of ASICs with TOA>0 + int icmn = 0; + for (const auto& chan : asic.getChannels()) { + bool skipChannel = false; + if (mPadBadChannelMap) { + try { + skipChannel = (mPadBadChannelMap->getChannelStatus(iasic, currentchannel) != o2::focal::PadBadChannelMap::MaskType_t::GOOD_CHANNEL); + } catch (o2::focal::PadBadChannelMap::ChannelIndexException& e) { + ILOG(Error, Support) << "Error accessing channel status: " << e.what() << ENDM; + } + } + if (skipChannel) { + continue; + } + if (chan.getTOT() < mPadTOTCutADC) { + double adc = chan.getADC(); // must be converted to floating point number for pedestal subtraction + if (mPadPedestalHandler && iasic < 18) { + try { + double pedestal = mPadPedestalHandler->getPedestal(iasic, currentchannel); + adc -= pedestal; + } catch (o2::focal::PadPedestal::InvalidChannelException& e) { + ILOG(Error, Support) << e.what() << ENDM; + } + } + mPadASICChannelADC[iasic]->Fill(currentchannel, adc); + if ((chan.getTOT() == 0) && (chan.getTOT() != 4095)) { + adcsum += adc; + kADCsum[iasic] += adc; + } + auto [column, row] = mPadMapper.getRowColFromChannelID(currentchannel); + mHitMapPadASIC[iasic]->Fill(row, column, adc); + + // get average TOA of all channels in the ASIC + if (chan.getTOA() > 0) { + asicAverageTOA += chan.getTOA(); + nChWithTOA++; + } + + if (iasic == 0) { + if ((currentchannel != 14) && (currentchannel != 16) && (currentchannel != 19) && (currentchannel != 48) && (currentchannel != 52) && (currentchannel != 61)) { + kADCForCMN[icmn] += adc; + icmn++; + } + + if (currentchannel == 14) + adc_ch14 = adc; + if (currentchannel == 16) + adc_ch16 = adc; + if (currentchannel == 19) + adc_ch19 = adc; + if (currentchannel == 48) + adc_ch48 = adc; + if (currentchannel == 52) + adc_ch52 = adc; + if (currentchannel == 61) + adc_ch61 = adc; + } + } + mPadASICChannelTOA[iasic]->Fill(currentchannel, chan.getTOA()); + if (chan.getTOT()) { + mPadASICChannelTOT[iasic]->Fill(currentchannel, chan.getTOT()); + if (chan.getTOT() != 4095) { + totsum += chan.getTOT(); + kTOTsum[iasic] += chan.getTOT(); + } + } + if (std::find(mChannelsPadProjections.begin(), mChannelsPadProjections.end(), currentchannel) != mChannelsPadProjections.end()) { + auto hist = mPadChannelProjections[iasic]->mHistos.find(currentchannel); + if (hist != mPadChannelProjections[iasic]->mHistos.end()) { + hist->second->Fill(chan.getADC()); + } + } + if (currentchannel == 14) + mPadTOAvsASIC_Ch14->Fill(chan.getTOA(), iasic); + if (currentchannel == 16) + mPadTOAvsASIC_Ch16->Fill(chan.getTOA(), iasic); + if (currentchannel == 19) + mPadTOAvsASIC_Ch19->Fill(chan.getTOA(), iasic); + if (currentchannel == 48) + mPadTOAvsASIC_Ch48->Fill(chan.getTOA(), iasic); + if (currentchannel == 52) + mPadTOAvsASIC_Ch52->Fill(chan.getTOA(), iasic); + if (currentchannel == 61) + mPadTOAvsASIC_Ch61->Fill(chan.getTOA(), iasic); + + currentchannel++; + // calculate common noise + } + + asicAverageTOA /= nChWithTOA; + + toaglobalaverage += asicAverageTOA; + + // fill average TOA + mPadTOAvsASIC->Fill(asicAverageTOA, iasic); + + mPadTOTSumASIC[iasic]->Fill(totsum); + mPadADCSumASIC[iasic]->Fill(adcsum); + totglobalsum += totsum; + adcglobalsum += adcsum; + + if (iasic == 0) { + cmn = TMath::Median(icmn, kADCForCMN.data()); + mPadGlobalMIPADC_Ch14_Asic0->Fill(adc_ch14 - cmn); + mPadGlobalMIPADC_Ch16_Asic0->Fill(adc_ch16 - cmn); + mPadGlobalMIPADC_Ch19_Asic0->Fill(adc_ch19 - cmn); + mPadGlobalMIPADC_Ch48_Asic0->Fill(adc_ch48 - cmn); + mPadGlobalMIPADC_Ch52_Asic0->Fill(adc_ch52 - cmn); + mPadGlobalMIPADC_Ch61_Asic0->Fill(adc_ch61 - cmn); + } + + // find all trigger windows of asic (20 windows) + auto trigwindows = eventdata[iasic].getTriggerWords(); + + // loop over span of all trigger windows + int w = 0; + for (auto& trigwindow : trigwindows) { + // find largest value from all trigger regions + uint64_t maxTrig = 0; + if (trigwindow.mTrigger0 >= maxTrig) + maxTrig = trigwindow.mTrigger0; + if (trigwindow.mTrigger1 >= maxTrig) + maxTrig = trigwindow.mTrigger1; + if (trigwindow.mTrigger2 >= maxTrig) + maxTrig = trigwindow.mTrigger2; + if (trigwindow.mTrigger3 >= maxTrig) + maxTrig = trigwindow.mTrigger3; + if (trigwindow.mTrigger4 >= maxTrig) + maxTrig = trigwindow.mTrigger4; + if (trigwindow.mTrigger5 >= maxTrig) + maxTrig = trigwindow.mTrigger5; + if (trigwindow.mTrigger6 >= maxTrig) + maxTrig = trigwindow.mTrigger6; + if (trigwindow.mTrigger7 >= maxTrig) + maxTrig = trigwindow.mTrigger7; + mPadTRIGvsWindowASIC[iasic]->Fill(w, maxTrig); + w++; + } + // Fill CMN channels + if (asic.getFirstCMN().getTOT() < mPadTOTCutADC) { + mPadASICChannelADC[iasic]->Fill(currentchannel, asic.getFirstCMN().getADC()); + } + mPadASICChannelTOA[iasic]->Fill(currentchannel, asic.getFirstCMN().getTOA()); + if (asic.getFirstCMN().getTOT()) { + mPadASICChannelTOT[iasic]->Fill(currentchannel, asic.getFirstCMN().getTOT()); + } + currentchannel++; + if (asic.getSecondCMN().getTOT() < mPadTOTCutADC) { + mPadASICChannelADC[iasic]->Fill(currentchannel, asic.getSecondCMN().getADC()); + } + mPadASICChannelTOA[iasic]->Fill(currentchannel, asic.getSecondCMN().getTOA()); + if (asic.getSecondCMN().getTOT()) { + mPadASICChannelTOT[iasic]->Fill(currentchannel, asic.getSecondCMN().getTOT()); + } + currentchannel++; + // Fill Calib channels + if (asic.getFirstCalib().getTOT() < mPadTOTCutADC) { + mPadASICChannelADC[iasic]->Fill(currentchannel, asic.getFirstCalib().getADC()); + } + mPadASICChannelTOA[iasic]->Fill(currentchannel, asic.getFirstCalib().getTOA()); + if (asic.getFirstCalib().getTOT()) { + mPadASICChannelTOT[iasic]->Fill(currentchannel, asic.getFirstCalib().getTOT()); + } + currentchannel++; + if (asic.getSecondCalib().getTOT() < mPadTOTCutADC) { + mPadASICChannelADC[iasic]->Fill(currentchannel, asic.getSecondCalib().getADC()); + } + mPadASICChannelTOA[iasic]->Fill(currentchannel, asic.getSecondCalib().getTOA()); + if (asic.getSecondCalib().getTOT()) { + mPadASICChannelTOT[iasic]->Fill(currentchannel, asic.getSecondCalib().getTOT()); + } + currentchannel++; + } + + // fill global average TOA + toaglobalaverage /= PAD_ASICS; + + // loop over all TOT sums to fill correlations + for (int i = 0; i < PAD_ASICS - 1; i++) { + mPadTOTCorrASIC[i]->Fill(kTOTsum[i], kTOTsum[i + 1]); + mPadADCCorrASIC[i]->Fill(kADCsum[i], kADCsum[i + 1]); + } + + mPadTOTSumGlobal->Fill(totglobalsum); + mPadADCSumGlobal->Fill(adcglobalsum); + + mPadGlobalTOTvsADC->Fill(adcglobalsum, totglobalsum); + mPadGlobalTOAvsADC->Fill(adcglobalsum, toaglobalaverage); +} + +void TestbeamRawTask::processPixelPayload(gsl::span pixelpayload, uint16_t feeID) +{ + auto fee = feeID & 0x00FF, + branch = (feeID & 0x0F00) >> 8; + ILOG(Debug, Support) << "Decoded FEE ID " << feeID << " -> FEE " << fee << ", branch " << branch << ENDM; + auto useFEE = branch * 10 + fee; + mLinksWithPayloadPixel->Fill(useFEE); + int layer = -1; + + // Testbeam November 2022 + auto mappingtype = mPixelMapper->getMappingType(); + auto numbersegments = getNumberOfPixelSegments(mappingtype); + int totalsegmentsCol = numbersegments.first * mPixelMapper->getNumberOfColumns(), + totalsegmentsRow = numbersegments.second * mPixelMapper->getNumberOfRows(), + totalsegments = totalsegmentsCol * totalsegmentsRow; + + mPixelDecoder.reset(); + mPixelDecoder.decodeEvent(pixelpayload); + if (pixelpayload.size()) { + ILOG(Debug, Support) << "Found pixel payload of size: " << pixelpayload.size() << " -> " << mPixelDecoder.getChipData().size() << " chips" << ENDM; + } + + mTriggersFeePixel->Fill(useFEE, mPixelDecoder.getChipData().size()); + + for (const auto& [trigger, chips] : mPixelDecoder.getChipData()) { + int nhitsAll = 0; + std::unordered_set chipIDsFound, chipIDsHits; + memset(mHitSegmentCounter.data(), 0, sizeof(int) * mHitSegmentCounter.size()); + for (const auto& chip : chips) { + ILOG(Debug, Support) << "[In task] Chip " << static_cast(chip.mChipID) << " from lane " << static_cast(chip.mLaneID) << ", " << chip.mHits.size() << " hit(s) ..." << ENDM; + nhitsAll += chip.mHits.size(); + mHitsChipPixel->Fill(chip.mHits.size()); + mAverageHitsChipPixel->Fill(useFEE, chip.mChipID, chip.mHits.size()); + mPixelLaneIDChipIDFEE[fee]->Fill(chip.mLaneID, chip.mChipID); + chipIDsFound.insert(chip.mChipID); + if (chip.mHits.size()) { + chipIDsHits.insert(chip.mChipID); + } + try { + auto position = mPixelMapper->getPosition(feeID, chip); + if (layer < 0) { + layer = position.mLayer; + } + mPixelChipHitProfileLayer[position.mLayer]->Fill(position.mColumn, position.mRow, chip.mHits.size()); + mPixelChipHitmapLayer[position.mLayer]->Fill(position.mColumn, position.mRow, chip.mHits.size()); + int chipIndex = position.mRow * 7 + position.mColumn; + mPixelHitDistribitionLayer[position.mLayer]->Fill(chipIndex, chip.mHits.size()); + for (auto& hit : chip.mHits) { + auto segment = getPixelSegment(hit, mappingtype, position); + auto segment_col = position.mColumn * numbersegments.first + segment.first; + auto segment_row = position.mRow * numbersegments.second + segment.second; + mPixelSegmentHitmapLayer[position.mLayer]->Fill(segment_col, segment_row); + int segment_id = segment_row * totalsegmentsCol + segment_col; + mHitSegmentCounter[segment_id]++; + } + } catch (o2::focal::PixelMapper::InvalidChipException& e) { + ILOG(Error, Support) << "Error in chip index: " << e << ENDM; + } catch (o2::focal::PixelMapper::UninitException& e) { + ILOG(Error, Support) << e << ENDM; + } + } + for (auto chipID : chipIDsFound) { + mPixelChipsIDsFound->Fill(useFEE, chipID); + } + for (auto chipID : chipIDsHits) { + mPixelChipsIDsHits->Fill(useFEE, chipID); + } + auto triggerfoundAll = mPixelNHitsAll.find(trigger); + if (triggerfoundAll == mPixelNHitsAll.end()) { + mPixelNHitsAll[trigger] = nhitsAll; + } else { + triggerfoundAll->second += nhitsAll; + } + auto triggerfoundLayer = mPixelNHitsLayer[layer].find(trigger); + if (triggerfoundLayer == mPixelNHitsLayer[layer].end()) { + mPixelNHitsLayer[layer][trigger] = nhitsAll; + } else { + triggerfoundLayer->second += nhitsAll; + } + for (int segmentID = 0; segmentID < mHitSegmentCounter.size(); segmentID++) { + if (mHitSegmentCounter[segmentID]) { + int segment_row = segmentID / totalsegmentsCol, + segment_col = segmentID % totalsegmentsCol; + mPixelSegmentHitProfileLayer[layer]->Fill(segment_col, segment_row, mHitSegmentCounter[segmentID]); + } + } + } +} + +std::pair TestbeamRawTask::getPixelSegment(const o2::focal::PixelHit& hit, o2::focal::PixelMapper::MappingType_t mappingtype, const o2::focal::PixelMapper::ChipPosition& chipMapping) const +{ + int row = -1, col = -1, absColumn = -1, absRow = -1; + switch (mappingtype) { + case o2::focal::PixelMapper::MappingType_t::MAPPING_IB: + absColumn = chipMapping.mInvertColumn ? PIXEL_COLS_IB - hit.mColumn : hit.mColumn; + absRow = chipMapping.mInvertRow ? PIXEL_ROWS_IB - hit.mRow : hit.mRow; + col = absColumn / PIXEL_COL_SEGMENSIZE_IB; + row = absRow / PIXEL_ROW_SEGMENTSIZE_IB; + break; + case o2::focal::PixelMapper::MappingType_t::MAPPING_OB: + absColumn = chipMapping.mInvertColumn ? PIXEL_COLS_OB - hit.mColumn : hit.mColumn; + absRow = chipMapping.mInvertRow ? PIXEL_ROWS_OB - hit.mRow : hit.mRow; + col = absColumn / PIXEL_COL_SEGMENSIZE_OB; + row = absRow / PIXEL_ROW_SEGMENTSIZE_OB; + break; + default: + break; + }; + return { col, row }; +} + +std::pair TestbeamRawTask::getNumberOfPixelSegments(o2::focal::PixelMapper::MappingType_t mappingtype) const +{ + int rows = -1, cols = -1; + switch (mappingtype) { + case o2::focal::PixelMapper::MappingType_t::MAPPING_IB: + rows = PIXEL_ROWS_IB / PIXEL_ROW_SEGMENTSIZE_IB; + cols = PIXEL_COLS_IB / PIXEL_COL_SEGMENSIZE_IB; + break; + case o2::focal::PixelMapper::MappingType_t::MAPPING_OB: + rows = PIXEL_ROWS_OB / PIXEL_ROW_SEGMENTSIZE_OB; + cols = PIXEL_COLS_OB / PIXEL_COL_SEGMENSIZE_OB; + break; + + default: + break; + }; + return { cols, rows }; +} + +void TestbeamRawTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void TestbeamRawTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TestbeamRawTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mTFerrorCounter->Reset(); + mFEENumberHBF->Reset(); + mFEENumberTF->Reset(); + mNumLinksTF->Reset(); + mNumHBFPerCRU->Reset(); + mCRUcounter->Reset(); + mPayloadSizeTF->Reset(); + if (!mDisablePads) { + mPayloadSizePadsGBT->Reset(); + for (auto padadc : mPadASICChannelADC) { + if (padadc) { + padadc->Reset(); + } + } + for (auto padtoa : mPadASICChannelTOA) { + if (padtoa) { + padtoa->Reset(); + } + } + for (auto padtot : mPadASICChannelTOT) { + if (padtot) { + padtot->Reset(); + } + } + for (auto hitmap : mHitMapPadASIC) { + if (hitmap) { + hitmap->Reset(); + } + } + for (auto mtotsum : mPadTOTSumASIC) { + if (mtotsum) { + mtotsum->Reset(); + } + } + for (auto madcsum : mPadADCSumASIC) { + if (madcsum) { + madcsum->Reset(); + } + } + for (auto& projections : mPadChannelProjections) { + if (projections) { + projections->reset(); + } + } + + for (auto hist : mPadTOTCorrASIC) { + if (hist) { + hist->Reset(); + } + } + for (auto hist : mPadADCCorrASIC) { + if (hist) { + hist->Reset(); + } + } + + for (auto hist : mPadTRIGvsWindowASIC) { + if (hist) { + hist->Reset(); + } + } + mPadTOTSumGlobal->Reset(); + mPadADCSumGlobal->Reset(); + mPadGlobalTOTvsADC->Reset(); + mPadGlobalTOAvsADC->Reset(); + mPadTOAvsASIC->Reset(); + + mPadTOAvsASIC_Ch14->Reset(); + mPadTOAvsASIC_Ch16->Reset(); + mPadTOAvsASIC_Ch19->Reset(); + mPadTOAvsASIC_Ch48->Reset(); + mPadTOAvsASIC_Ch52->Reset(); + mPadTOAvsASIC_Ch61->Reset(); + + mPadGlobalMIPADC_Ch14_Asic0->Reset(); + mPadGlobalMIPADC_Ch16_Asic0->Reset(); + mPadGlobalMIPADC_Ch19_Asic0->Reset(); + mPadGlobalMIPADC_Ch48_Asic0->Reset(); + mPadGlobalMIPADC_Ch52_Asic0->Reset(); + mPadGlobalMIPADC_Ch61_Asic0->Reset(); + } + + if (!mDisablePixels) { + mPayloadSizePixelsGBT->Reset(); + if (mLinksWithPayloadPixel) { + mLinksWithPayloadPixel->Reset(); + } + if (mTriggersFeePixel) { + mTriggersFeePixel->Reset(); + } + if (mAverageHitsChipPixel) { + mAverageHitsChipPixel->Reset(); + } + if (mHitsChipPixel) { + mHitsChipPixel->Reset(); + } + if (mPixelHitsTriggerAll) { + mPixelHitsTriggerAll->Reset(); + } + if (mPixelChipsIDsFound) { + mPixelChipsIDsFound->Reset(); + } + + for (auto& hist : mPixelLaneIDChipIDFEE) { + if (hist) { + hist->Reset(); + } + } + + for (auto& hist : mPixelChipHitProfileLayer) { + if (hist) { + hist->Reset(); + } + } + for (auto& hist : mPixelChipHitmapLayer) { + if (hist) { + hist->Reset(); + } + } + for (auto& hist : mPixelSegmentHitProfileLayer) { + if (hist) { + hist->Reset(); + } + } + for (auto& hist : mPixelSegmentHitmapLayer) { + if (hist) { + hist->Reset(); + } + } + for (auto& hist : mPixelHitDistribitionLayer) { + if (hist) { + hist->Reset(); + } + } + for (auto& hist : mPixelHitsTriggerLayer) { + if (hist) { + hist->Reset(); + } + } + } +} + +TestbeamRawTask::PadChannelProjections::~PadChannelProjections() +{ + for (auto& [chan, hist] : mHistos) { + delete hist; + } +} + +void TestbeamRawTask::PadChannelProjections::init(const std::vector channels, int ASICid) +{ + for (auto chan : channels) { + auto hist = new TH1D(Form("Pad_ProjADC_ASIC%d_Chan%d", ASICid, chan), Form("Pad ADC projection ASIC %d channel %d", ASICid, chan), 101, -0.5, 100.5); + hist->SetStats(false); + mHistos.insert({ chan, hist }); + } +} + +void TestbeamRawTask::PadChannelProjections::startPublishing(o2::quality_control::core::ObjectsManager& mgr) +{ + for (auto& [chan, hist] : mHistos) { + mgr.startPublishing(hist); + } +} + +void TestbeamRawTask::PadChannelProjections::reset() +{ + for (auto& [chan, hist] : mHistos) { + hist->Reset(); + } +} + +bool TestbeamRawTask::isLostTimeframe(framework::ProcessingContext& ctx) const +{ + // direct data + constexpr auto originFOC = header::gDataOriginFOC; + o2::framework::InputSpec dummy{ "dummy", + framework::ConcreteDataMatcher{ originFOC, + header::gDataDescriptionRawData, + 0xDEADBEEF } }; + for (const auto& ref : o2::framework::InputRecordWalker(ctx.inputs(), { dummy })) { + // auto posReadout = ctx.inputs().getPos("readout"); + // auto nslots = ctx.inputs().getNofParts(posReadout); + // for (decltype(nslots) islot = 0; islot < nslots; islot++) { + // const auto& ref = ctx.inputs().getByPos(posReadout, islot); + const auto dh = o2::framework::DataRefUtils::getHeader(ref); + const auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(ref); + // if (dh->subSpecification == 0xDEADBEEF) { + if (payloadSize == 0) { + return true; + // } + } + } + // sampled data + o2::framework::InputSpec dummyDS{ "dummyDS", + framework::ConcreteDataMatcher{ "DS", + "focrawdata0", + 0xDEADBEEF } }; + for (const auto& ref : o2::framework::InputRecordWalker(ctx.inputs(), { dummyDS })) { + // auto posReadout = ctx.inputs().getPos("readout"); + // auto nslots = ctx.inputs().getNofParts(posReadout); + // for (decltype(nslots) islot = 0; islot < nslots; islot++) { + // const auto& ref = ctx.inputs().getByPos(posReadout, islot); + const auto dh = o2::framework::DataRefUtils::getHeader(ref); + const auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(ref); + // if (dh->subSpecification == 0xDEADBEEF) { + if (payloadSize == 0) { + return true; + // } + } + } + + return false; +} + +} // namespace o2::quality_control_modules::focal diff --git a/Modules/FOCAL/test/testQcFOCAL.cxx b/Modules/FOCAL/test/testQcFOCAL.cxx new file mode 100644 index 0000000000..226523b83e --- /dev/null +++ b/Modules/FOCAL/test/testQcFOCAL.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testFOCAL.cxx +/// \author My Name +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::focal +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::focal diff --git a/Modules/GLO/CMakeLists.txt b/Modules/GLO/CMakeLists.txt new file mode 100644 index 0000000000..5bcb337d41 --- /dev/null +++ b/Modules/GLO/CMakeLists.txt @@ -0,0 +1,80 @@ +# ---- Library ---- + +add_library(O2QcGLO) + +target_sources( + O2QcGLO + PRIVATE src/ITSTPCMatchingTask.cxx + src/ITSTPCmatchingCheck.cxx + src/Reductors.cxx + src/MeanVertexValidator.cxx + src/MeanVertexPostProcessing.cxx + src/MeanVertexCheck.cxx + src/VertexingQcTask.cxx + src/DataCompressionQcTask.cxx + src/CTFSizeTask.cxx) + +target_include_directories( + O2QcGLO + PUBLIC $ $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries( + O2QcGLO + PUBLIC O2QualityControl + O2::Steer + O2::DataFormatsGlobalTracking + O2::DataFormatsITS + O2::DataFormatsCalibration + O2::GlobalTracking + O2::GLOQC + O2QcCommon) + +install( + TARGETS O2QcGLO + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +# install json files +install( + FILES ITSTPCmatchedTracks_external.json + ITSTPCmatchedTracks.json + ITSTPCmatchedTracks_direct.json + vertexing-qc-direct.json + vertexing-qc-direct-mc.json + vertexing-qc.json + vertexing-qc-mc.json + dataCompression-qc.json + glo-mean-vtx-post-processing.json + DESTINATION etc) + +add_root_dictionary( + O2QcGLO + HEADERS include/GLO/MeanVertexValidator.h + include/GLO/MeanVertexPostProcessing.h + include/GLO/MeanVertexCheck.h + include/GLO/VertexingQcTask.h + include/GLO/DataCompressionQcTask.h + include/GLO/CTFSizeTask.h + include/GLO/ITSTPCMatchingTask.h + include/GLO/ITSTPCmatchingCheck.h + include/GLO/Reductors.h + LINKDEF include/GLO/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/GLO DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Test(s) ---- + +# set(TEST_SRCS test/testQcGLO.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} PRIVATE O2QcGLO Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() diff --git a/Modules/GLO/ITSTPCmatchedTracks.json b/Modules/GLO/ITSTPCmatchedTracks.json new file mode 100644 index 0000000000..2a68d4dc71 --- /dev/null +++ b/Modules/GLO/ITSTPCmatchedTracks.json @@ -0,0 +1,84 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MTCITSTPC" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::ITSTPCMatchingTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "3600", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "ITSTPCmatchSamp" + }, + "taskParameters" : { + "GID" : "ITS-TPC,ITS", + "verbose" : "false", + "minPtCut" : "0.1f", + "etaCut" : "1.4f", + "minNTPCClustersCut" : "60", + "minDCACut" : "100.f", + "minDCACutY" : "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "remote", + "saveObjectsToFile" : "ITSTPCmatched.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "ITSTPCmatchSamp", + "active" : "true", + "machines" : [], + "query_comment" : "checking every 10% matched track", + "query" : "trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS;trackTPCClRefs:TPC/CLUSREFS;trackITS:ITS/TRACKS/0;trackITSROF:ITS/ITSTrackROF/0;trackITSClIdx:ITS/TRACKCLSID/0;alpparITS:ITS/ALPIDEPARAM/0?lifetime=condition&ccdb-path=ITS/Config/AlpideParam", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.1", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/GLO/ITSTPCmatchedTracks_MC.json b/Modules/GLO/ITSTPCmatchedTracks_MC.json new file mode 100644 index 0000000000..3b6d8caef6 --- /dev/null +++ b/Modules/GLO/ITSTPCmatchedTracks_MC.json @@ -0,0 +1,99 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MatchedTracksITSTPC" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::ITSTPCMatchingTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "3600", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "ITSTPCmatchSamp" + }, + "taskParameters" : { + "GID" : "ITS-TPC,ITS", + "verbose" : "false", + "minPtCut" : "0.1f", + "etaCut" : "1.4f", + "minNTPCClustersCut" : "40", + "minDCACut" : "100.f", + "minDCACutY" : "10.f", + "isMC" : "true" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "remote", + "saveObjectsToFile" : "ITSTPCmatched.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "checks" : { + "QcCheck" : { + "active" : "false", + "className" : "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName" : "QcSkeleton", + "policy" : "OnAny", + "detectorName" : "TOF", + "dataSource" : [ { + "type" : "Task", + "name" : "QcTask", + "MOs" : ["example"] + } ] + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "ITSTPCmatchSamp", + "active" : "true", + "machines" : [], + "query_comment" : "checking every 10% matched track", + "query" : "trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS;trackTPCClRefs:TPC/CLUSREFS;trackITS:ITS/TRACKS/0;trackITSROF:ITS/ITSTrackROF/0;trackITSClIdx:ITS/TRACKCLSID/0;alpparITS:ITS/ALPIDEPARAM/0?lifetime=condition&ccdb-path=ITS/Config/AlpideParam;trackTPCMCTR:TPC/TRACKSMCLBL;trackITSTPCMCTR:GLO/TPCITS_MC;trackITSTPCABMCTR:GLO/TPCITSAB_MC;trackITSMCTR:ITS/TRACKSMCTR", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.1", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/GLO/ITSTPCmatchedTracks_direct.json b/Modules/GLO/ITSTPCmatchedTracks_direct.json new file mode 100644 index 0000000000..83067a6bae --- /dev/null +++ b/Modules/GLO/ITSTPCmatchedTracks_direct.json @@ -0,0 +1,70 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MTCITSTPC" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::ITSTPCMatchingTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "3600", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "direct", + "query_comment" : "checking every matched track", + "query" : "trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS;trackTPCClRefs:TPC/CLUSREFS;trackITS:ITS/TRACKS/0;trackITSROF:ITS/ITSTrackROF/0;trackITSClIdx:ITS/TRACKCLSID/0;alpparITS:ITS/ALPIDEPARAM/0?lifetime=condition&ccdb-path=ITS/Config/AlpideParam" + }, + "taskParameters" : { + "GID" : "ITS-TPC,ITS", + "verbose" : "false", + "minPtCut" : "0.1f", + "etaCut" : "1.4f", + "minNTPCClustersCut" : "60", + "minDCACut" : "100.f", + "minDCACutY" : "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "remote", + "saveObjectsToFile" : "ITSTPCmatched.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + } + }, + "dataSamplingPolicies" : [ + ] +} diff --git a/Modules/GLO/ITSTPCmatchedTracks_direct_MC.json b/Modules/GLO/ITSTPCmatchedTracks_direct_MC.json new file mode 100644 index 0000000000..a05381d7cb --- /dev/null +++ b/Modules/GLO/ITSTPCmatchedTracks_direct_MC.json @@ -0,0 +1,85 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MatchedTracksITSTPC" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::ITSTPCMatchingTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "3600", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "direct", + "query_comment" : "checking every matched track", + "query" : "trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS;trackTPCClRefs:TPC/CLUSREFS;trackITS:ITS/TRACKS/0;trackITSROF:ITS/ITSTrackROF/0;trackITSClIdx:ITS/TRACKCLSID/0;alpparITS:ITS/ALPIDEPARAM/0?lifetime=condition&ccdb-path=ITS/Config/AlpideParam;trackTPCMCTR:TPC/TRACKSMCLBL;trackITSTPCMCTR:GLO/TPCITS_MC;trackITSTPCABMCTR:GLO/TPCITSAB_MC;trackITSMCTR:ITS/TRACKSMCTR" + }, + "taskParameters" : { + "GID" : "ITS-TPC,ITS", + "verbose" : "false", + "minPtCut" : "0.1f", + "etaCut" : "1.4f", + "minNTPCClustersCut" : "40", + "minDCACut" : "100.f", + "minDCACutY" : "10.f", + "isMC" : "true" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "remote", + "saveObjectsToFile" : "ITSTPCmatched.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "checks" : { + "QcCheck" : { + "active" : "false", + "className" : "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName" : "QcSkeleton", + "policy" : "OnAny", + "detectorName" : "TOF", + "dataSource" : [ { + "type" : "Task", + "name" : "QcTask", + "MOs" : ["example"] + } ] + } + } + }, + "dataSamplingPolicies" : [ + ] +} diff --git a/Modules/GLO/ITSTPCmatchedTracks_external.json b/Modules/GLO/ITSTPCmatchedTracks_external.json new file mode 100644 index 0000000000..e90b671e3d --- /dev/null +++ b/Modules/GLO/ITSTPCmatchedTracks_external.json @@ -0,0 +1,42 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : {}, + "externalTasks" : { + "MTCITSTPC" : { + "active" : "true", + "query" : "MatchedTracksITSTPC:GLO/ITSTPCMATCHQC/0", + "" : "Use the task name as binding (encouraged)" + } + }, + "checks" : {} + }, + "dataSamplingPolicies" : [ + ] +} diff --git a/Modules/GLO/ITSTPCmatchedTracks_mnl.json b/Modules/GLO/ITSTPCmatchedTracks_mnl.json new file mode 100644 index 0000000000..b2bae5966f --- /dev/null +++ b/Modules/GLO/ITSTPCmatchedTracks_mnl.json @@ -0,0 +1,90 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MTCITSTPC" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::ITSTPCMatchingTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "3600", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "ITSTPCmSamp" + }, + "taskParameters" : { + "GID" : "ITS-TPC,ITS", + "verbose" : "false", + "minPtCut" : "0.1f", + "etaCut" : "1.4f", + "minNTPCClustersCut" : "60", + "minDCACut" : "100.f", + "minDCACutY" : "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "localhost", + "remotePort": "46001", + "localControl": "odc", + "saveObjectsToFile" : "ITSTPCmatched.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "ITSTPCmSamp", + "active" : "true", + "machines" : [], + "query_comment" : "checking every 10% matched track", + "query" : "trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS;trackTPCClRefs:TPC/CLUSREFS;trackITS:ITS/TRACKS/0;trackITSROF:ITS/ITSTrackROF/0;trackITSClIdx:ITS/TRACKCLSID/0;alpparITS:ITS/ALPIDEPARAM/0?lifetime=condition&ccdb-path=ITS/Config/AlpideParam", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.1", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/GLO/README.md b/Modules/GLO/README.md new file mode 100644 index 0000000000..5366fbfba2 --- /dev/null +++ b/Modules/GLO/README.md @@ -0,0 +1,91 @@ +# GLO +Documents the available task in the module and their parameters. +## ITS-TPC Matching Task +### Parameters +#### MC + - `isMC=false` produce additional MC plots + - `useTrkPID=false` propagate tracks with their pid hypothesis +#### ITS + - `minPtITSCut=0.1f` minimum ITS track momentum + - `etaITSCut=1.4f` maximum ITS track eta + - `minNITSClustersCut=0` minimum number of ITS clusters + - `maxChi2PerClusterITS=1e10f` maximum chi2/ITS cluster +#### TPC + - `minPtTPCCut=0.1f` minimum TPC track momentum + - `etaITSCut=1.4f` maximum ITS track eta + - `minTPCClustersCut=60` minimum number of TPC clusters + - `minDCACut=100.f` minimum TPC track DCA Z + - `minDCACutY=10.f` minimum TPC track DCA Y +#### ITS-TPC + - `minPtCut=0.1f` minimum ITS-TPC momentum + - `maxPtCut=20.f` maximum ITS-TPC momentum + - `etaCut=1.4f` maximum ITS-TPC track eta +#### Additional sync parameters + - `isSync=false` synchronous processing, needed for all following parameters +#### MTC ratios + - `doMTCRatios=false` produce MTC ratio plots +#### K0s + - `doK0QC=false` produce K0s plots + - `maxK0Eta=0.8f` maximum K0s eta + - `refitK0=false` refit K0 prongs + - `cutK0Mass=0.05f` cut around K0s peak + - `trackSourcesK0` SVertexer input sources + - `publishK0s3D=false` publish 3D cycle,integral histograms + - `splitK0sMassOccupancy=float` splitting point in TPC occupancy to define low and high region by default off + - `splitK0sMassPt=float` splitting point in pt to define low and high region by default off + + K0Fitter options +#### ITS-PV + - `doPVITSQC=false` produce ITS vs PV plots (not implemented yet) + +## ITS-TPC Matching Check +### Parameters +#### Pt + - `showPt=false` show check on MTC pt + - `thresholdPt=0.5f` threshold on MTC pt + - `minPt=1.0f` check range left + - `maxPt=1.999f` check range right +#### Phi + - `showPhi=false` show check on MTC phi + - `thresholdPhi=0.3f` threshold on MTC phi +#### Eta + - `showEta=false` show check on MTC eta + - `thresholdEta=0.4f` threshold on MTC eta + - `minEta=-0.8f` check range left + - `maxEta=0.8f` check range right +#### K0 + - `showK0s=false` show check on K0s mass + - `acceptableK0sRError=0.2f` acceptable relative error to pdg value + - `acceptableK0sUncertainty=0.2f` acceptable uncertainty to pdg value + + K0Fitter options +#### Other + - `limitRanges=5` maximum number of bad intervals shown + +## K0sFitReductor +Trends mean and sigma of fit. +### Output + - `mean` aggregated mass value + - `sigma` aggregated sigma value + +## MTCReductor +Trends MTC at given pt. +### Output + - `mtc` mtc efficiency +### Parameters + - `pt` take value at this pt + +## PVITSReductor +Trends constant + slope of straight line fit in given range. +### Output + - `pol0` constant + - `pol1` slope +### Parameters + - `r0` start fit range + - `r1` end fit range + +## K0Fitter +Does a `pol2 + Gaus` fit. +### Parameters + - `k0sBackgroundRejLeft=0.48` reject region in background fit from left side of mass peak + - `k0sBackgroundRejRight=0.51` reject region in background fit to right side of mass peak + - `k0sBackgroundRangeLeft=0.45` absolute left range to fit background + - `k0sBackgroundRangeRight=0.54` absolute right range to fit background diff --git a/Modules/GLO/dataCompression-qc.json b/Modules/GLO/dataCompression-qc.json new file mode 100644 index 0000000000..0779621a60 --- /dev/null +++ b/Modules/GLO/dataCompression-qc.json @@ -0,0 +1,63 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "DataCompression": { + "active": "true", + "className": "o2::quality_control_modules::glo::DataCompressionQcTask", + "moduleName": "QcGLO", + "detectorName": "GLO", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query" : "ctfEncRepTPC:TPC/CTFENCREP/0;ctfEncRepITS:ITS/CTFENCREP/0" + }, + "taskParameters": { + "mergeableOutput": "false", "": "Set this false for canvas output. Works only if the task runs remotely.", + "useAll": "0", "": "This detector config is just an example. Any number of detectors can be selected via 1, true, True or TRUE.", + "CPV": "0", "": "If 'useAll' is set true all detectors are selected and the indivisual choice is ignored.", + "CTP": "0", "": "Each detector that shall be active has the be put in the query as shown in the example above.", + "EMC": "0", + "FDD": "0", + "FT0": "0", + "FV0": "0", + "HMP": "0", + "ITS": "1", + "MFT": "0", + "MCH": "0", + "MID": "0", + "PHS": "0", + "TOF": "0", + "TPC": "1", + "TRD": "0", + "ZDC": "0", + "nBins": "100", + "xMin": "0", + "xMax": "1" + }, + "location": "remote", "": "It needs to be decided if it will run locally or remotely." + } + } + }, + "dataSamplingPolicies": [ + ] +} diff --git a/Modules/GLO/glo-itstpc-mtch-qcmn-epn.json b/Modules/GLO/glo-itstpc-mtch-qcmn-epn.json new file mode 100644 index 0000000000..3a18e5ce01 --- /dev/null +++ b/Modules/GLO/glo-itstpc-mtch-qcmn-epn.json @@ -0,0 +1,89 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ali-qcdb.cern.ch:8083", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "influxdb-unix:///tmp/telegraf.sock" + }, + "consul" : { + "url" : "http://ali-consul.cern.ch:8500" + }, + "conditionDB" : { + "url" : "http://localhost:8084" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "1", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MTCITSTPC" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::ITSTPCMatchingTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "600", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "ITSTPCmSamp" + }, + "taskParameters" : { + "GID" : "ITS-TPC,ITS", + "verbose" : "false", + "minPtCut" : "0.1f", + "etaCut" : "1.4f", + "minNTPCClustersCut" : "60", + "minDCACut" : "100.f", + "minDCACutY" : "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "local", + "localMachines": [ + "epn", + "localhost" + ], + "remoteMachine": "alio2-cr1-qc07.cern.ch", + "remotePort": "47761", + "localControl": "odc" + } + }, + }, + "dataSamplingPolicies" : [ + { + "id" : "ITSTPCmSamp", + "active" : "true", + "machines" : [], + "query_comment" : "checking every 10% matched track", + "query" : "trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS;trackTPCClRefs:TPC/CLUSREFS;trackITS:ITS/TRACKS/0;trackITSROF:ITS/ITSTrackROF/0;trackITSClIdx:ITS/TRACKCLSID/0;alpparITS:ITS/ALPIDEPARAM/0?lifetime=condition&ccdb-path=ITS/Config/AlpideParam", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.1", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/GLO/glo-itstpc-mtch-qcmn-test.json b/Modules/GLO/glo-itstpc-mtch-qcmn-test.json new file mode 100644 index 0000000000..21f836789b --- /dev/null +++ b/Modules/GLO/glo-itstpc-mtch-qcmn-test.json @@ -0,0 +1,323 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable", + "maxObjectSize": "20000000" + }, + "Activity": { + "number": "", + "type": "", + "beamType": "PbPb" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "infologger": { + "filterDiscardDebug": "false", + "filterDiscardLevel": "0" + }, + "postprocessing": { + "periodSeconds": 0.01, + "matchAnyRunNumber": "true" + } + }, + "tasks": { + "ITSTPCMatchingTask": { + "active": "true", + "className": "o2::quality_control_modules::glo::ITSTPCMatchingTask", + "moduleName": "QcGLO", + "detectorName": "GLO", + "cycleDurationSeconds": "1", + "maxNumberCycles": "-1", + "dataSource": { + "type": "direct", + "query_comment": "checking every matched track", + "query": "tofcluster:TOF/CLUSTERS/0;matchITSTPCTRDTOF:TOF/MTC_ITSTPCTRD/0;matchTPCTRDTOF/TOF/MTC_TPCTRD/0;matchITSTPCTOF:TOF/MTC_ITSTPC/0;matchTPCTOF:TOF/MTC_TPC/0;trackTPCTOF:TOF/TOFTRACKS_TPC/0;trigTPCTRD:TRD/TRGREC_TPC/0;trackTPCTRD:TRD/MATCH_TPC/0;trigITSTPCTRD:TRD/TRGREC_ITSTPC/0;trackITSTPCTRD:TRD/MATCH_ITSTPC/0;p2decay3body:GLO/PVTX_3BODYREFS/0;decay3body:GLO/DECAYS3BODY/0;decay3bodyIdx:GLO/DECAYS3BODY_IDX/0;p2cascs:GLO/PVTX_CASCREFS/0;cascs:GLO/CASCS/0;cascsIdx:GLO/CASCS_IDX/0;p2v0s:GLO/PVTX_V0REFS/0;v0s:GLO/V0S/0;v0sIdx:GLO/V0S_IDX/0;pvtx_tref:GLO/PVTX_TRMTCREFS/0;pvtx_trmtc:GLO/PVTX_TRMTC/0;pvtx:GLO/PVTX/0;trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS;trackTPCClRefs:TPC/CLUSREFS;trackITS:ITS/TRACKS/0;trackITSROF:ITS/ITSTrackROF/0;trackITSClIdx:ITS/TRACKCLSID/0;alpparITS:ITS/ALPIDEPARAM/0?lifetime=condition&ccdb-path=ITS/Config/AlpideParam;SVParam:GLO/SVPARAM/0?lifetime=condition&ccdb-path=GLO/Config/SVertexerParam;clusTPC:TPC/CLUSTERNATIVE;clusTPCshmap:TPC/CLSHAREDMAP/0;trigTPC:TPC/TRIGGERWORDS/0;clusTPCoccmap:TPC/TPCOCCUPANCYMAP/0" + }, + "taskParameters": { + "GID": "ITS-TPC,ITS", + "verbose": "false", + "doMTCRatios": "true", + "doK0QC": "true", + "doPVITSQC": "true", + "isSync": "true", + "trackSourcesK0": "ITS,TPC,ITS-TPC,ITS-TPC-TOF,TPC-TOF,TPC-TRD,ITS-TPC-TRD,TPC-TRD-TOF,ITS-TPC-TOF,ITS-TPC-TRD-TOF" + }, + "grpGeomRequest": { + "geomRequest": "None", + "askGRPECS": "true", + "askGRPLHCIF": "true", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "saveObjectsToFile": "ITSTPCmatched.root" + } + }, + "checks": { + "ITSTPCMatchingCheck": { + "active": "true", + "className": "o2::quality_control_modules::glo::ITSTPCmatchingCheck", + "moduleName": "QcGLO", + "policy": "OnAny", + "detectorName": "GLO", + "dataSource": [ + { + "type": "Task", + "name": "ITSTPCMatchingTask", + "MOs": [ + "mFractionITSTPCmatch_ITS_Hist", + "mFractionITSTPCmatchPhi_ITS_Hist", + "mFractionITSTPCmatchEta_ITS_Hist", + "mK0sMassVsPtVsOcc_Integral_pmass", + "mK0sMassVsPtVsOcc_Cycle_pmass", + "mK0sMassVsPtVsOcc_Cycle_pmass_lowOcc", + "mK0sMassVsPtVsOcc_Cycle_pmass_lowPt", + "mK0sMassVsPtVsOcc_Cycle_pmass_highOcc", + "mK0sMassVsPtVsOcc_Cycle_pmass_highPt" + ] + } + ], + "extendedCheckParameters": { + "default": { + "default": { + "showPt": "true", + "showEta": "true", + "showPhi": "true", + "showK0s": "true" + } + } + } + } + }, + "postprocessing": { + "K0sMassTrend": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcGLO", + "detectorName": "GLO", + "resumeTrend": "false", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:GLO/MO/ITSTPCMatchingTask/gloFitK0sMassSignal" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "GLO/MO/ITSTPCMatchingTask", + "names": [ + "gloFitK0sMassSignal" + ], + "reductorName": "o2::quality_control_modules::glo::K0sFitReductor", + "moduleName": "QcGLO" + } + ], + "plots": [ + { + "name": "glo_k0s_mass_trending", + "title": "Fitted K0s mass", + "varexp": "gloFitK0sMassSignal.mean:time", + "option": "*L", + "graphYRange": "0.45:0.55", + "graphAxisLabel": "K0s mass (GeV/c^{2}):time" + }, + { + "name": "glo_k0s_sigma_trending", + "title": "Fitted K0s sigma", + "varexp": "gloFitK0sMassSignal.sigma:time", + "option": "*L", + "graphYRange": "0:0.01", + "graphAxisLabel": "#sigma_{K0s mass (GeV/c^{2})}:time" + } + ] + }, + "K0sYieldTrend": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcGLO", + "detectorName": "GLO", + "resumeTrend": "false", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:GLO/MO/ITSTPCMatchingTask/gloFitK0sMassSignal" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "GLO/MO/ITSTPCMatchingTask", + "names": [ + "gloFitK0sMassSignal" + ], + "reductorName": "o2::quality_control_modules::glo::K0sFitReductor", + "moduleName": "QcGLO" + } + ], + "plots": [ + { + "name": "glo_k0s_yield_trending", + "title": "Fitted K0s yield", + "varexp": "gloFitK0sMassSignal.yield:time", + "option": "*L", + "graphAxisLabel": "K0s Yield:time" + } + ] + }, + "MTCTrend1": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcGLO", + "detectorName": "GLO", + "resumeTrend": "false", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:GLO/MO/ITSTPCMatchingTask/mFractionITSTPCmatch_ITS_Hist" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "GLO/MO/ITSTPCMatchingTask", + "names": [ + "mFractionITSTPCmatch_ITS_Hist" + ], + "reductorName": "o2::quality_control_modules::glo::MTCReductor", + "reductorParameters": { + "default": { + "default": { + "pt": "1.5" + } + } + }, + "moduleName": "QcGLO" + } + ], + "plots": [ + { + "name": "glo_mtc_pt15_trending", + "title": "MTC at #it{p}_{T}=1.5 trending", + "varexp": "mFractionITSTPCmatch_ITS_Hist.mtc:time", + "option": "*L", + "graphYRange": "0:1.02", + "graphAxisLabel": "MTC:time" + } + ] + }, + "MTCTrend2": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcGLO", + "detectorName": "GLO", + "resumeTrend": "false", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:GLO/MO/ITSTPCMatchingTask/mFractionITSTPCmatch_ITS_Hist" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "GLO/MO/ITSTPCMatchingTask", + "names": [ + "mFractionITSTPCmatch_ITS_Hist" + ], + "reductorName": "o2::quality_control_modules::glo::MTCReductor", + "reductorParameters": { + "default": { + "default": { + "pt": "0.5" + } + } + }, + "moduleName": "QcGLO" + } + ], + "plots": [ + { + "name": "glo_mtc_pt05_trending", + "title": "MTC at #it{p}_{T}=0.5 trending", + "varexp": "mFractionITSTPCmatch_ITS_Hist.mtc:time", + "option": "*L", + "graphYRange": "0:1.02", + "graphAxisLabel": "MTC:time" + } + ] + }, + "PVITSTrend": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcGLO", + "detectorName": "GLO", + "resumeTrend": "false", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:GLO/MO/ITSTPCMatchingTask/mPVNContVsITSTracks_Cycle_pfx" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "GLO/MO/ITSTPCMatchingTask", + "names": [ + "mPVNContVsITSTracks_Cycle_pfx" + ], + "reductorName": "o2::quality_control_modules::glo::PVITSReductor", + "reductorParameters": { + "default": { + "default": { + "r0": "1000", + "r1": "7000" + } + } + }, + "moduleName": "QcGLO" + } + ], + "plots": [ + { + "name": "glo_pvits_pol0_trending", + "title": "PV-ITS Fit pol0 trending", + "varexp": "mPVNContVsITSTracks_Cycle_pfx.pol0:time", + "option": "*L", + "graphAxisLabel": "pol0:time" + }, + { + "name": "glo_pvits_pol1_trending", + "title": "PV-ITS Fit pol1 trending", + "varexp": "mPVNContVsITSTracks_Cycle_pfx.pol1:time", + "option": "*L", + "graphAxisLabel": "pol1:time" + } + ] + } + } + } +} diff --git a/Modules/GLO/glo-mean-vtx-post-processing.json b/Modules/GLO/glo-mean-vtx-post-processing.json new file mode 100644 index 0000000000..81ba422cf5 --- /dev/null +++ b/Modules/GLO/glo-mean-vtx-post-processing.json @@ -0,0 +1,89 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "infologger": { + "filterDiscardDebug": "true", + "filterDiscardLevel": "11" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "http://localhost:8500" + }, + "conditionDB": { + "url": "http://ccdb-test.cern.ch:8080" + }, + "postprocessing": { + "matchAnyRunNumber": "true" + } + }, + "postprocessing": { + "MeanVertexCalib": { + "active": "true", + "className": "o2::quality_control_modules::glo::MeanVertexPostProcessing", + "moduleName": "QcGLO", + "detectorName": "GLO", + "customization": [ + { + "CcdbURL": "http://ccdb-test.cern.ch:8080" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:ccdb:GLO/Calib/MeanVertex" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks": { + "MeanVertexCheck": { + "active": "true", + "className": "o2::quality_control_modules::glo::MeanVertexCheck", + "moduleName": "QcGLO", + "detectorName": "GLO", + "policy": "OnAll", + "extendedCheckParameters": { + "default": { + "default": { + "nPointsToCheck": "10", + "range:MeanVtxXTrending": "-0.1,0.1", + "range:MeanVtxYTrending": "-0.1,0.1", + "range:MeanVtxZTrending": "-1.0,1.0", + "range:MeanVtxSigmaXTrending": "-0.1,0.1", + "range:MeanVtxSigmaYTrending": "-0.1,0.1", + "range:MeanVtxSigmaZTrending": "-10.0,10.0" + } + } + }, + "dataSource": [ + { + "type": "PostProcessing", + "name": "MeanVertexCalib", + "MOs" : [ + "MeanVtxXTrending", + "MeanVtxYTrending", + "MeanVtxZTrending", + "MeanVtxSigmaXTrending", + "MeanVtxSigmaYTrending", + "MeanVtxSigmaZTrending" + ] + } + ] + } + } + } +} diff --git a/Modules/GLO/glo-vtx-qcmn-epn.json b/Modules/GLO/glo-vtx-qcmn-epn.json new file mode 100644 index 0000000000..931dc1fb05 --- /dev/null +++ b/Modules/GLO/glo-vtx-qcmn-epn.json @@ -0,0 +1,72 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ali-qcdb.cern.ch:8083", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "influxdb-unix:///tmp/telegraf.sock" + }, + "consul" : { + "url" : "http://ali-consul.cern.ch:8500" + }, + "conditionDB" : { + "url" : "http://localhost:8084" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "1", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "Vertexing" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::VertexingQcTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "600", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "VtxSampling" + }, + "taskParameters" : { + "isMC": "false" + }, + "location" : "local", + "localMachines": [ + "epn", + "localhost" + ], + "remoteMachine": "alio2-cr1-qc07.cern.ch", + "remotePort": "47760", + "localControl": "odc" + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "VtxSampling", + "active" : "true", + "machines" : [], + "query" : "pvtx:GLO/PVTX/0", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.1", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/GLO/include/GLO/CTFSizeTask.h b/Modules/GLO/include/GLO/CTFSizeTask.h new file mode 100644 index 0000000000..ae1e61a6a3 --- /dev/null +++ b/Modules/GLO/include/GLO/CTFSizeTask.h @@ -0,0 +1,63 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CTFSizeTask.h +/// \author Ole Schmidt +/// + +#ifndef QC_MODULE_GLO_CTFSIZETASK_H +#define QC_MODULE_GLO_CTFSIZETASK_H + +#include "QualityControl/TaskInterface.h" +#include + +#include +#include +#include + +class TH1F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::glo +{ + +class CTFSize final : public TaskInterface +{ + public: + /// \brief Constructor + CTFSize() = default; + /// Destructor + ~CTFSize() override; + + std::tuple getBinningFromConfig(o2::detectors::DetID::ID iDet, const Activity& activity) const; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + std::array mHistSizes{ nullptr }; // CTF size per TF for each detector with different binning per detector + std::array mHistSizesLog{ nullptr }; // CTF size per TF with same axis scale for all detectors + std::array mDefaultBinning{ "" }; // default number of bins and limits for mHistSizes (customizable) + std::array mIsDetEnabled{ false }; // flag which detector is included in the run + bool mPublishingDone{ false }; +}; + +} // namespace o2::quality_control_modules::glo + +#endif // QC_MODULE_GLO_CTFSIZETASK_H diff --git a/Modules/GLO/include/GLO/DataCompressionQcTask.h b/Modules/GLO/include/GLO/DataCompressionQcTask.h new file mode 100644 index 0000000000..0985708628 --- /dev/null +++ b/Modules/GLO/include/GLO/DataCompressionQcTask.h @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DataCompressionQcTask.h +/// \author Thomas Klemenz +/// + +#ifndef QC_MODULE_GLO_DATACOMPRESSIONQCTASK_H +#define QC_MODULE_GLO_DATACOMPRESSIONQCTASK_H + +// O2 includes +#include "DetectorsCommonDataFormats/CTFIOSize.h" + +// QC includes +#include "QualityControl/TaskInterface.h" + +// root includes +#include +#include + +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::glo +{ + +/// \brief DataCompression QC Task +/// \author Thomas Klemenz +class DataCompressionQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + DataCompressionQcTask() = default; + /// Destructor + ~DataCompressionQcTask() = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + /// process the message for a single detector + void processMessage(const o2::ctf::CTFIOSize& ctfEncRep, const std::string detector); + + private: + bool mIsMergeable = false; // switch for canvas output: true->no canvas, false->canvas + std::unordered_map>> mCompressionHists; // contains two histograms for all active detector (as specified in the config) + std::unique_ptr mCompressionCanvas; // displays the compression histograms for all active detectors + std::unique_ptr mEntropyCompressionCanvas; // displays the entropy compression histograms for all active detectors +}; + +} // namespace o2::quality_control_modules::glo + +#endif // QC_MODULE_GLO_DATACOMPRESSION_H diff --git a/Modules/GLO/include/GLO/Helpers.h b/Modules/GLO/include/GLO/Helpers.h new file mode 100644 index 0000000000..18c1693cbf --- /dev/null +++ b/Modules/GLO/include/GLO/Helpers.h @@ -0,0 +1,150 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_GLO_HELPERS_H +#define QC_MODULE_GLO_HELPERS_H + +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/CustomParameters.h" +#include "QualityControl/Activity.h" +#include "Common/Utils.h" + +#include + +#include + +#include +#include +#include +#include + +namespace o2::quality_control_modules::glo::helpers +{ + +// Simple class to fit K0s mass +struct K0sFitter { + enum Parameters : uint8_t { + // Background + Pol0 = 0, + Pol1 = 1, + Pol2 = 2, + // K0s + Amplitude = 3, + Mass = 4, + Sigma = 5, + }; + static constexpr auto mMassK0s{ o2::constants::physics::MassK0Short }; + double mBackgroundRangeLeft{ 0.45 }; + double mBackgroundRangeRight{ 0.54 }; + struct FitBackground { + static constexpr int mNPar{ 3 }; + double mRejLeft{ 0.48 }; + double mRejRight{ 0.51 }; + double operator()(double* x, double* p) const + { + if (x[0] > mRejLeft && x[0] < mRejRight) { + TF1::RejectPoint(); + return 0; + } + return p[0] + (p[1] * x[0]) + (p[2] * x[0] * x[0]); + } + } mFitBackground; + std::unique_ptr mBackground; + std::unique_ptr mSignalAndBackground; + + void init(o2::quality_control::core::CustomParameters const& pars) + { + mFitBackground.mRejLeft = common::getFromConfig(pars, "k0sBackgroundRejLeft", 0.48); + mFitBackground.mRejRight = common::getFromConfig(pars, "k0sBackgroundRejRight", 0.51); + mBackgroundRangeLeft = common::getFromConfig(pars, "k0sBackgroundRangeLeft", 0.45); + mBackgroundRangeRight = common::getFromConfig(pars, "k0sBackgroundRangeRight", 0.54); + initImpl(); + } + + void init(o2::quality_control::core::CustomParameters const& pars, o2::quality_control::core::Activity const& activity) + { + mFitBackground.mRejLeft = common::getFromExtendedConfig(activity, pars, "k0sBackgroundRejLeft", 0.48); + mFitBackground.mRejRight = common::getFromExtendedConfig(activity, pars, "k0sBackgroundRejRight", 0.51); + mBackgroundRangeLeft = common::getFromExtendedConfig(activity, pars, "k0sBackgroundRangeLeft", 0.45); + mBackgroundRangeRight = common::getFromExtendedConfig(activity, pars, "k0sBackgroundRangeRight", 0.54); + initImpl(); + } + + void initImpl() + { + mBackground.reset(new TF1("gloFitK0sMassBackground", mFitBackground, mBackgroundRangeLeft, mBackgroundRangeRight, mFitBackground.mNPar)); + mSignalAndBackground.reset(new TF1("gloFitK0sMassSignal", "[0] + [1] * x + [2] * x * x + gaus(3)", mBackgroundRangeLeft, mBackgroundRangeRight)); + } + + bool fit(TH1* h, bool add = false) + { + if (!h || h->GetEntries() == 0) { + ILOG(Warning, Devel) << "Cannot fit empty histogram: " + << (h ? h->GetName() : "") << ENDM; + return false; + } + + // --- First: background-only fit + auto bgResult = h->Fit(mBackground.get(), "RNQS"); + if (bgResult.Get() == nullptr || bgResult->Status() != 0) { + ILOG(Warning, Devel) << "Failed k0s background fit for histogram: " + << h->GetName() + << " (status=" << (bgResult.Get() ? bgResult->Status() : -1) << ")" + << ENDM; + return false; + } + + // --- Initialize signal+background from background fit + mSignalAndBackground->SetParameter(Parameters::Pol0, mBackground->GetParameter(Parameters::Pol0)); + mSignalAndBackground->SetParameter(Parameters::Pol1, mBackground->GetParameter(Parameters::Pol1)); + mSignalAndBackground->SetParameter(Parameters::Pol2, mBackground->GetParameter(Parameters::Pol2)); + mSignalAndBackground->SetParameter(Parameters::Amplitude, h->GetMaximum() - mBackground->Eval(mMassK0s)); + mSignalAndBackground->SetParameter(Parameters::Mass, mMassK0s); + mSignalAndBackground->SetParameter(Parameters::Sigma, 0.005); + mSignalAndBackground->SetParLimits(Parameters::Sigma, 1e-6, 1.0); + + // --- Fit signal+background + const std::string fitOpt = add ? "RMQS" : "RMQS0"; + auto sbResult = h->Fit(mSignalAndBackground.get(), fitOpt.c_str()); + if (sbResult.Get() == nullptr || sbResult->Status() != 0) { + ILOG(Warning, Devel) << "Failed k0s signal+background fit for histogram: " + << h->GetName() + << " (status=" << (sbResult ? sbResult->Status() : -1) << ")" + << ENDM; + return false; + } + return true; + } + + auto getMass() const noexcept + { + return mSignalAndBackground->GetParameter(4); + } + + auto getSigma() const noexcept + { + return mSignalAndBackground->GetParameter(5); + } + + auto getUncertainty() const noexcept + { + return std::abs(mMassK0s - getMass()) / getSigma(); + } + + auto getRelativeError() const noexcept + { + return std::abs(mMassK0s - getMass()) / mMassK0s; + } +}; + +} // namespace o2::quality_control_modules::glo::helpers + +#endif diff --git a/Modules/GLO/include/GLO/ITSTPCMatchingTask.h b/Modules/GLO/include/GLO/ITSTPCMatchingTask.h new file mode 100644 index 0000000000..af148c5aac --- /dev/null +++ b/Modules/GLO/include/GLO/ITSTPCMatchingTask.h @@ -0,0 +1,81 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSTPCMatchingTask.h +/// \author Chiara Zampolli +/// + +#ifndef QC_MODULE_GLO_ITSTPCMATCHINGTASK_H +#define QC_MODULE_GLO_ITSTPCMATCHINGTASK_H + +#include "QualityControl/TaskInterface.h" +#include "GLOQC/MatchITSTPCQC.h" +#include "Common/TH1Ratio.h" +#include "GLO/Helpers.h" +#include "GLO/Reductors.h" + +#include + +#include +#include + +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::glo +{ + +/// \brief ITS-TPC Matching QC task +/// \author Chiara Zampolli +class ITSTPCMatchingTask final : public TaskInterface +{ + public: + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + template + static constexpr T OptValue = std::numeric_limits::max(); + + o2::gloqc::MatchITSTPCQC mMatchITSTPCQC; + + bool mIsSync{ false }; + bool mIsPbPb{ false }; + + bool mDoMTCRatios{ false }; + std::unique_ptr mEffPt; + std::unique_ptr mEffEta; + std::unique_ptr mEffPhi; + + bool mDoK0s{ false }; + bool mPublishK0s3D{ false }; + float mSplitTPCOccupancy{ OptValue }; + float mSplitPt{ OptValue }; + std::unique_ptr mK0sCycle; + std::unique_ptr mK0sIntegral; + helpers::K0sFitter mK0sFitter; + + bool mDoPVITS{ false }; + std::unique_ptr mPVITSCycle; + std::unique_ptr mPVITSIntegral; +}; + +} // namespace o2::quality_control_modules::glo + +#endif // QC_MODULE_GLO_ITSTPCMATCHINGTASK_H diff --git a/Modules/GLO/include/GLO/ITSTPCmatchingCheck.h b/Modules/GLO/include/GLO/ITSTPCmatchingCheck.h new file mode 100644 index 0000000000..98090b46a5 --- /dev/null +++ b/Modules/GLO/include/GLO/ITSTPCmatchingCheck.h @@ -0,0 +1,74 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file ITSTPCmatchingCheck.h +/// \brief Check for ITS-TPC sync. matching efficiency +/// \author felix.schlepper@cern.ch + +#ifndef QC_MODULE_GLO_GLOITSTPCMATCHINGCHECK_H +#define QC_MODULE_GLO_GLOITSTPCMATCHINGCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "QualityControl/Quality.h" + +#include "GLO/Helpers.h" + +#include + +namespace o2::quality_control_modules::glo +{ + +/// \brief Check for ITS-TPC sync. matching efficiency +/// \author felix.schlepper@cern.ch +class ITSTPCmatchingCheck final : public o2::quality_control::checker::CheckInterface +{ + public: + Quality check(std::map>* moMap) final; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) final; + void startOfActivity(const Activity& activity) final; + + private: + static std::vector> findRanges(const std::vector& nums) noexcept; + + std::shared_ptr mActivity; + + // Pt + bool mShowPt{ false }; + float mMinPt{ 1. }; + float mMaxPt{ 1.999 }; + float mThresholdPt{ 0.5 }; + + // Phi + bool mShowPhi{ false }; + float mThresholdPhi{ 0.3 }; + + // Eta + bool mShowEta{ false }; + float mThresholdEta{ 0.4 }; + float mMinEta{ -0.8 }; + float mMaxEta{ 0.8 }; + + // K0s + bool mShowK0s{ false }; + float mAccRelError{ 0.02 }; + float mAccUncertainty{ 2 }; + helpers::K0sFitter mK0sFitter; + + // Other + int mLimitRange{ -1 }; + bool mIsPbPb{ false }; + + ClassDefOverride(ITSTPCmatchingCheck, 2); +}; + +} // namespace o2::quality_control_modules::glo + +#endif // QC_MODULE_GLO_GLOITSTPCMATCHINGCHECK_H diff --git a/Modules/GLO/include/GLO/LinkDef.h b/Modules/GLO/include/GLO/LinkDef.h new file mode 100644 index 0000000000..8abcb91cf0 --- /dev/null +++ b/Modules/GLO/include/GLO/LinkDef.h @@ -0,0 +1,20 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::glo::CTFSize + ; +#pragma link C++ class o2::quality_control_modules::glo::DataCompressionQcTask + ; + +#pragma link C++ class o2::quality_control_modules::glo::MeanVertexPostProcessing + ; +#pragma link C++ class o2::quality_control_modules::glo::MeanVertexCheck + ; +#pragma link C++ class o2::quality_control_modules::glo::MeanVertexValidator + ; +#pragma link C++ class o2::quality_control_modules::glo::VertexingQcTask + ; + +#pragma link C++ class o2::quality_control_modules::glo::ITSTPCMatchingTask + ; +#pragma link C++ class o2::quality_control_modules::glo::ITSTPCmatchingCheck + ; +#pragma link C++ class o2::quality_control_modules::glo::K0sFitReductor + ; +#pragma link C++ class o2::quality_control_modules::glo::MTCReductor + ; +#pragma link C++ class o2::quality_control_modules::glo::PVITSReductor + ; + +#endif diff --git a/Modules/GLO/include/GLO/MeanVertexCheck.h b/Modules/GLO/include/GLO/MeanVertexCheck.h new file mode 100644 index 0000000000..d8d955207b --- /dev/null +++ b/Modules/GLO/include/GLO/MeanVertexCheck.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MeanVertexCheck.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_GLO_MEANVERTEXCHECK_H +#define QC_MODULE_GLO_MEANVERTEXCHECK_H + +#include "QualityControl/CheckInterface.h" + +#include + +class TH1; +class TEfficiency; + +namespace o2::quality_control_modules::glo +{ + +/// \brief Check whether the matching efficiency is within some configurable limits +/// +/// \author Andrea Ferrero +class MeanVertexCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + MeanVertexCheck() = default; + /// Destructor + ~MeanVertexCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + ClassDefOverride(MeanVertexCheck, 1); + + private: + void initRange(std::string key); + std::optional> getRange(std::string key); + + Activity mActivity; + int mNPointsToCheck{ 1 }; + std::map> mRanges; + std::map mQualities; +}; + +} // namespace o2::quality_control_modules::glo + +#endif // QC_MODULE_GLO_MEANVERTEXCHECK_H diff --git a/Modules/GLO/include/GLO/MeanVertexPostProcessing.h b/Modules/GLO/include/GLO/MeanVertexPostProcessing.h new file mode 100644 index 0000000000..a182959ff1 --- /dev/null +++ b/Modules/GLO/include/GLO/MeanVertexPostProcessing.h @@ -0,0 +1,77 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MeanVertexPostProcessing.h +/// \author Chiara Zampolli, Andrea Ferrero +/// + +#ifndef QUALITYCONTROL_MEANVERTEXPOSTPROCESSING_H +#define QUALITYCONTROL_MEANVERTEXPOSTPROCESSING_H + +#include "QualityControl/PostProcessingInterface.h" +#include "CCDB/CcdbApi.h" + +namespace o2::quality_control_modules::glo +{ + +class TrendGraph; + +/// \brief Postprocessing Task for Mean Vertex calibration + +class MeanVertexPostProcessing final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + MeanVertexPostProcessing() = default; + /// \brief Destructor + ~MeanVertexPostProcessing() override = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + virtual void configure(const boost::property_tree::ptree& config) override; + + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + std::shared_ptr mGraphX; + std::shared_ptr mGraphY; + std::shared_ptr mGraphZ; + std::shared_ptr mGraphSigmaX; + std::shared_ptr mGraphSigmaY; + std::shared_ptr mGraphSigmaZ; + o2::ccdb::CcdbApi mCcdbApi; + std::string mCcdbUrl = "https://alice-ccdb.cern.ch"; +}; + +} // namespace o2::quality_control_modules::glo + +#endif // QUALITYCONTROL_MEANVERTEXPOSTPROCESSING_H diff --git a/Modules/GLO/include/GLO/MeanVertexValidator.h b/Modules/GLO/include/GLO/MeanVertexValidator.h new file mode 100644 index 0000000000..6c7b470c04 --- /dev/null +++ b/Modules/GLO/include/GLO/MeanVertexValidator.h @@ -0,0 +1,41 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MeanVertexValidator.h +/// \author Andrea Ferrero +/// +#ifndef QUALITYCONTROL_MEANVERTEX_VALIDATOR_H +#define QUALITYCONTROL_MEANVERTEX_VALIDATOR_H + +#include "Common/CcdbValidatorInterface.h" + +namespace o2::quality_control_modules::glo +{ + +/// \brief Inspect and validate the contents of MeanVertex calibration objects +class MeanVertexValidator : public o2::quality_control_modules::common::CcdbValidatorInterface +{ + public: + /// \brief Constructor + MeanVertexValidator() = default; + /// \brief Destructor + virtual ~MeanVertexValidator() override = default; + + const std::type_info& getTinfo() const override; + + /// \brief Fill the data structure with new data + /// \param An object to be reduced into a limited set of observables + bool validate(void* obj) override; +}; + +} // namespace o2::quality_control_modules::glo +#endif // QUALITYCONTROL_MEANVERTEX_VALIDATOR_H diff --git a/Modules/GLO/include/GLO/Reductors.h b/Modules/GLO/include/GLO/Reductors.h new file mode 100644 index 0000000000..0965268a91 --- /dev/null +++ b/Modules/GLO/include/GLO/Reductors.h @@ -0,0 +1,67 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QUALITYCONTROL_K0SFITREDUCTOR_H +#define QUALITYCONTROL_K0SFITREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +#include + +namespace o2::quality_control_modules::glo +{ + +class K0sFitReductor final : public quality_control::postprocessing::ReductorTObject +{ + void* getBranchAddress() final { return &mStats; }; + const char* getBranchLeafList() final { return "yield/F:mean/F:sigma/F"; }; + void update(TObject* obj) final; + + private: + struct { + Float_t yield{ -1. }; + Float_t mean{ -1. }; + Float_t sigma{ -1. }; + } mStats; +}; + +class MTCReductor final : public quality_control::postprocessing::ReductorTObject +{ + void* getBranchAddress() final { return &mStats; }; + const char* getBranchLeafList() final; + void update(TObject* obj) final; + + private: + struct { + Float_t mtc{ -1. }; + } mStats; + + Float_t mPt{ -1. }; +}; + +class PVITSReductor final : public quality_control::postprocessing::ReductorTObject +{ + void* getBranchAddress() final { return &mStats; }; + const char* getBranchLeafList() final; + void update(TObject* obj) final; + + private: + struct { + Float_t pol0{ 0. }; + Float_t pol1{ 0. }; + } mStats; + + Float_t mR0{ 0 }, mR1{ 0 }; +}; + +} // namespace o2::quality_control_modules::glo + +#endif diff --git a/Modules/GLO/include/GLO/VertexingQcTask.h b/Modules/GLO/include/GLO/VertexingQcTask.h new file mode 100644 index 0000000000..7971638061 --- /dev/null +++ b/Modules/GLO/include/GLO/VertexingQcTask.h @@ -0,0 +1,90 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file VertexingQcTask.h +/// \author Chiara Zampolli +/// + +#ifndef QC_MODULE_GLO_GLOVERTEXINGQCTASK_H +#define QC_MODULE_GLO_GLOVERTEXINGQCTASK_H + +#include "QualityControl/TaskInterface.h" +#include "Steer/MCKinematicsReader.h" + +#include "SimulationDataFormat/MCEventLabel.h" + +class TH1F; +class TH2F; +class TF1; +class TProfile; +class TEfficiency; + +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::glo +{ + +/// \brief Vertexing QC Task +/// \author Chiara Zampolli +class VertexingQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + VertexingQcTask() = default; + /// Destructor + ~VertexingQcTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + TH1F* mX = nullptr; // vertex X + TF1* fX = nullptr; // fit vertex X + TH1F* mY = nullptr; // vertex Y + TF1* fY = nullptr; // fit vertex Y + TH1F* mZ = nullptr; // vertex Z + TH1F* mNContributors = nullptr; // vertex N contributors + TProfile* mTimeUncVsNContrib = nullptr; // time uncertainty vs N contributors + + bool mVerbose = false; + + // MC related part + bool mUseMC = false; + o2::steer::MCKinematicsReader mMCReader; // MC reader + TProfile* mPurityVsMult = nullptr; // purity vs multiplicity + + std::unordered_map mMapEvIDSourceID; // unordered_map counting the number of vertices reconstructed per event and source (--> MCEventLabel) + TH1F* mNPrimaryMCEvWithVtx = nullptr; // event multiplicity for MC events with at least 1 vertex + TH1F* mNPrimaryMCGen = nullptr; // event multiplicity for all MC events + TH1F* mRatioNPrimaryMCEvWithVtxvsNPrimaryMCGen = nullptr; /* ratio event multiplicity for MC events with at least 1 vertex vs. event multiplicity for all MC events */ + TEfficiency* mVtxEffVsMult = nullptr; // for vertex efficiency + TProfile* mCloneFactorVsMult = nullptr; // clone factor vs multiplicity + TProfile* mVtxResXVsMult = nullptr; // vertex resolution in X + TProfile* mVtxResYVsMult = nullptr; // vertex resolution in Y + TProfile* mVtxResZVsMult = nullptr; // vertex resolution in Z + TProfile* mVtxPullsXVsMult = nullptr; // vertex pulls in X + TProfile* mVtxPullsYVsMult = nullptr; // vertex pulls in Y + TProfile* mVtxPullsZVsMult = nullptr; // vertex pulls in Z + TH2F* mBeamSpot = nullptr; // bean spot +}; + +} // namespace o2::quality_control_modules::glo + +#endif // QC_MODULE_GLO_GLOVERTEXINGQCTASK_H diff --git a/Modules/GLO/src/CTFSizeTask.cxx b/Modules/GLO/src/CTFSizeTask.cxx new file mode 100644 index 0000000000..4e7ca2199e --- /dev/null +++ b/Modules/GLO/src/CTFSizeTask.cxx @@ -0,0 +1,147 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CTFSizeTask.cxx +/// \author Ole Schmidt +/// + +#include +#include +#include +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" +#include "GLO/CTFSizeTask.h" +#include + +using namespace o2::detectors; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::glo +{ + +CTFSize::~CTFSize() +{ +} + +std::tuple CTFSize::getBinningFromConfig(o2::detectors::DetID::ID iDet, const Activity& activity) const +{ + std::string cfKey = "binning"; + cfKey += DetID::getName(iDet); + std::string binning = mCustomParameters.atOptional(cfKey, activity).value_or(mDefaultBinning[iDet]); + auto nBins = std::stoi(binning.substr(0, binning.find(","))); + binning.erase(0, binning.find(",") + 1); + auto xMin = std::stof(binning.substr(0, binning.find(","))); + binning.erase(0, binning.find(",") + 1); + auto xMax = std::stof(binning.substr(0, binning.find(","))); + return std::make_tuple(nBins, xMin, xMax); +} + +void CTFSize::initialize(o2::framework::InitContext& /*ctx*/) +{ + // default lower limit from pp run 549884 and upper limit from PbPb run 543918, each limit with some margin + mDefaultBinning[DetID::ITS] = "1000, 1e2, 1e5"; + mDefaultBinning[DetID::TPC] = "1000, 1e3, 1e6"; + mDefaultBinning[DetID::TRD] = "1000, 1e2, 1e5"; + mDefaultBinning[DetID::TOF] = "1000, 10, 1e4"; + mDefaultBinning[DetID::PHS] = "100, 10, 1e3"; + mDefaultBinning[DetID::CPV] = "100, 10, 3e4"; + mDefaultBinning[DetID::EMC] = "1000, 100, 5e5"; + mDefaultBinning[DetID::HMP] = "100, 1, 300"; + mDefaultBinning[DetID::MFT] = "1000, 1e2, 1e4"; + mDefaultBinning[DetID::MCH] = "100, 1e3, 5e4"; + mDefaultBinning[DetID::MID] = "100, 10, 500"; + mDefaultBinning[DetID::ZDC] = "100, 1e3, 1e4"; + mDefaultBinning[DetID::FT0] = "100, 1, 500"; + mDefaultBinning[DetID::FV0] = "100, 1, 400"; + mDefaultBinning[DetID::FDD] = "100, 1, 100"; + mDefaultBinning[DetID::CTP] = "100, 1, 100"; + mDefaultBinning[DetID::TST] = "1, 0, 1"; + + std::string detList = getFromConfig(mCustomParameters, "detectors", "all"); + auto detMask = DetID::getMask(detList); + + constexpr int nLogBins = 100; + float xBins[nLogBins + 1]; + float xBinLogMin = 0.f; + float xBinLogMax = 11.f; + float logBinWidth = (xBinLogMax - xBinLogMin) / nLogBins; + for (int iBin = 0; iBin <= nLogBins; ++iBin) { + xBins[iBin] = TMath::Power(10, xBinLogMin + iBin * logBinWidth); + } + for (int iDet = 0; iDet < DetID::CTP + 1; ++iDet) { + if (detMask[iDet]) { + mIsDetEnabled[iDet] = true; + mHistSizesLog[iDet] = new TH1F(Form("hSizeLog_%s", DetID::getName(iDet)), Form("%s CTF size per TF;Byte;counts", DetID::getName(iDet)), nLogBins, xBins); + getObjectsManager()->startPublishing(mHistSizesLog[iDet]); + getObjectsManager()->setDefaultDrawOptions(mHistSizesLog[iDet]->GetName(), "logx"); + } + } +} + +void CTFSize::startOfActivity(const Activity& activity) +{ + if (!mPublishingDone) { + for (int iDet = 0; iDet < DetID::CTP + 1; ++iDet) { + if (!mIsDetEnabled[iDet]) { + continue; + } + auto binning = getBinningFromConfig(iDet, activity); + std::string unit = (iDet == DetID::EMC || iDet == DetID::CPV) ? "B" : "kB"; + mHistSizes[iDet] = new TH1F(Form("hSize_%s", DetID::getName(iDet)), Form("%s CTF size per TF;%s;counts", DetID::getName(iDet), unit.c_str()), std::get<0>(binning), std::get<1>(binning), std::get<2>(binning)); + getObjectsManager()->startPublishing(mHistSizes[iDet]); + } + mPublishingDone = true; + } + reset(); +} + +void CTFSize::startOfCycle() +{ +} + +void CTFSize::monitorData(o2::framework::ProcessingContext& ctx) +{ + const auto sizes = ctx.inputs().get>("ctfSizes"); + for (int iDet = 0; iDet <= DetID::CTP; ++iDet) { + ILOG(Debug, Devel) << fmt::format("Det {} : is enabled {}, data size {}", DetID::getName(iDet), mIsDetEnabled[iDet], sizes[iDet]) << ENDM; + if (!mIsDetEnabled[iDet]) { + continue; + } + float conversion = (iDet == DetID::EMC || iDet == DetID::CPV) ? 1.f : 1024.f; // EMC and CPV can sent <1kB per CTF + mHistSizes[iDet]->Fill(sizes[iDet] / conversion); + mHistSizesLog[iDet]->Fill(sizes[iDet]); + } +} + +void CTFSize::endOfCycle() +{ +} + +void CTFSize::endOfActivity(const Activity& /*activity*/) +{ +} + +void CTFSize::reset() +{ + for (auto h : mHistSizes) { + if (h) { + h->Reset(); + } + } + for (auto h : mHistSizesLog) { + if (h) { + h->Reset(); + } + } +} + +} // namespace o2::quality_control_modules::glo diff --git a/Modules/GLO/src/DataCompressionQcTask.cxx b/Modules/GLO/src/DataCompressionQcTask.cxx new file mode 100644 index 0000000000..503a4f03f1 --- /dev/null +++ b/Modules/GLO/src/DataCompressionQcTask.cxx @@ -0,0 +1,152 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DataCompressionQcTask.cxx +/// \author Thomas Klemenz +/// + +// O2 includes +#include "Framework/ProcessingContext.h" +#include +#include "DetectorsCommonDataFormats/DetID.h" + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "GLO/DataCompressionQcTask.h" +#include "Common/Utils.h" + +const std::vector histVecNames{ "entropy_compression", "compression" }; + +namespace o2::quality_control_modules::glo +{ + +void DataCompressionQcTask::initialize(o2::framework::InitContext&) +{ + // ILOG(Debug, Devel) << "initialize DataCompression QC task" << ENDM; + + TH1::AddDirectory(false); + + mIsMergeable = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "mergeableOutput"); + const auto nBins = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nBins"); + const auto xMin = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "xMin"); + const auto xMax = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "xMax"); + + // initialize histograms for active detectors + bool useAll = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "useAll"); + for (int iDet = 0; iDet < o2::detectors::DetID::nDetectors; ++iDet) { + const auto detName = o2::detectors::DetID::getName(iDet); + const bool useDet = o2::quality_control_modules::common::getFromConfig(mCustomParameters, detName); + if (useDet || useAll) { + for (const auto& type : histVecNames) { + mCompressionHists[detName].emplace_back(std::make_unique(fmt::format("h_{}_{}", detName, type).data(), + fmt::format("{} of {} data", type, detName).data(), + nBins, xMin, xMax)); + } + } + } + + // register single histograms for publishing + for (const auto& pair : mCompressionHists) { + for (const auto& hist : pair.second) { + getObjectsManager()->startPublishing(hist.get()); + } + } + + if (!mIsMergeable) { + // initialize canvases and register them for publishing + // putting the histos to canvases makes the trending very easy because of the way the trending is implemented at the moment + mEntropyCompressionCanvas = std::make_unique("c_entropy_compression", "Entropy Compression Factor", 1000, 1000); + mCompressionCanvas = std::make_unique("c_compression", "Compression Factor", 1000, 1000); + + mEntropyCompressionCanvas->DivideSquare(mCompressionHists.size()); + mCompressionCanvas->DivideSquare(mCompressionHists.size()); + + getObjectsManager()->startPublishing(mEntropyCompressionCanvas.get()); + getObjectsManager()->startPublishing(mCompressionCanvas.get()); + } +} + +void DataCompressionQcTask::startOfActivity(const Activity&) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + + for (const auto& pair : mCompressionHists) { + for (const auto& hist : pair.second) { + hist->Reset(); + } + } +} + +void DataCompressionQcTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void DataCompressionQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + // loop over active detectors and process the data + for (const auto& det : mCompressionHists) { + auto ctfEncRep = ctx.inputs().get(fmt::format("ctfEncRep{}", det.first).data()); + processMessage(ctfEncRep, det.first); + } + + if (!mIsMergeable) { + // draw histograms to the canvases + size_t padIter = 1; + for (const auto& det : mCompressionHists) { + mEntropyCompressionCanvas->cd(padIter); + det.second[0]->Draw(); + mCompressionCanvas->cd(padIter); + det.second[1]->Draw(); + padIter++; + } + } +} + +void DataCompressionQcTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void DataCompressionQcTask::endOfActivity(const Activity&) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void DataCompressionQcTask::reset() +{ + ILOG(Debug, Devel) << "Resetting the histogramss" << ENDM; + + for (const auto& pair : mCompressionHists) { + for (const auto& hist : pair.second) { + hist->Reset(); + } + } +} + +void DataCompressionQcTask::processMessage(const o2::ctf::CTFIOSize& ctfEncRep, const std::string detector) +{ + float entropyCompression = 0; + float compression = 0; + + if (ctfEncRep.ctfIn != 0) { + entropyCompression = float(ctfEncRep.ctfIn - ctfEncRep.ctfOut) / float(ctfEncRep.ctfIn); + } + if (ctfEncRep.rawIn != 0) { + compression = float(ctfEncRep.rawIn - ctfEncRep.ctfOut) / float(ctfEncRep.rawIn); + } + + mCompressionHists[detector][0]->Fill(entropyCompression); + mCompressionHists[detector][1]->Fill(compression); +} + +} // namespace o2::quality_control_modules::glo diff --git a/Modules/GLO/src/ITSTPCMatchingTask.cxx b/Modules/GLO/src/ITSTPCMatchingTask.cxx new file mode 100644 index 0000000000..b9730292fc --- /dev/null +++ b/Modules/GLO/src/ITSTPCMatchingTask.cxx @@ -0,0 +1,250 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSTPCMatchingTask.cxx +/// \author Chiara Zampolli +/// + +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "GLO/ITSTPCMatchingTask.h" +#include "Common/Utils.h" + +#include +#include + +using matchType = o2::gloqc::MatchITSTPCQC::matchType; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::glo +{ + +void ITSTPCMatchingTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize ITSTPCMatchingTask" << ENDM; + + // MC + mMatchITSTPCQC.setUseMC(getFromConfig(mCustomParameters, "isMC", false)); + mMatchITSTPCQC.setUseTrkPID(getFromConfig(mCustomParameters, "useTrkPID", false)); + // ITS track + mMatchITSTPCQC.setMinPtITSCut(getFromConfig(mCustomParameters, "minPtITSCut", 0.1f)); + mMatchITSTPCQC.setEtaITSCut(getFromConfig(mCustomParameters, "etaITSCut", 1.4f)); + mMatchITSTPCQC.setMinNClustersITS(getFromConfig(mCustomParameters, "minNITSClustersCut", 0)); + mMatchITSTPCQC.setMaxChi2PerClusterITS(getFromConfig(mCustomParameters, "maxChi2PerClusterITS", 1e10f)); + // TPC track + mMatchITSTPCQC.setMinPtTPCCut(getFromConfig(mCustomParameters, "minPtTPCCut", 0.1f)); + mMatchITSTPCQC.setEtaTPCCut(getFromConfig(mCustomParameters, "etaTPCCut", 1.4f)); + mMatchITSTPCQC.setMinNTPCClustersCut(getFromConfig(mCustomParameters, "minNTPCClustersCut", 60)); + mMatchITSTPCQC.setMinDCAtoBeamPipeDistanceCut(getFromConfig(mCustomParameters, "minDCACut", 100.f)); + mMatchITSTPCQC.setMinDCAtoBeamPipeYCut(getFromConfig(mCustomParameters, "minDCACutY", 10.f)); + // ITS-TPC kinematics + mMatchITSTPCQC.setPtCut(getFromConfig(mCustomParameters, "minPtCut", 0.1f)); + mMatchITSTPCQC.setMaxPtCut(getFromConfig(mCustomParameters, "maxPtCut", 20.f)); + mMatchITSTPCQC.setEtaCut(getFromConfig(mCustomParameters, "etaCut", 1.4f)); + // Sync + mIsSync = common::getFromConfig(mCustomParameters, "isSync", false); + // MTC ratios + mDoMTCRatios = common::getFromConfig(mCustomParameters, "doMTCRatios", false); + // K0s + mMatchITSTPCQC.setDoK0QC((mDoK0s = getFromConfig(mCustomParameters, "doK0QC", false))); + if (mIsSync && mDoK0s) { + mMatchITSTPCQC.setMaxK0Eta(getFromConfig(mCustomParameters, "maxK0Eta", 0.8f)); + mMatchITSTPCQC.setRefitK0(getFromConfig(mCustomParameters, "refitK0", true)); + mMatchITSTPCQC.setCutK0Mass(getFromConfig(mCustomParameters, "cutK0Mass", 0.05f)); + if (auto param = mCustomParameters.find("trackSourcesK0"); param != mCustomParameters.end()) { + mMatchITSTPCQC.setTrkSources(o2::dataformats::GlobalTrackID::getSourcesMask(param->second)); + } + + mPublishK0s3D = getFromConfig(mCustomParameters, "publishK0s3D", false); + mSplitTPCOccupancy = getFromConfig(mCustomParameters, "splitK0sMassOccupancy", mSplitTPCOccupancy); + mSplitPt = getFromConfig(mCustomParameters, "splitK0sMassPt", mSplitPt); + mK0sFitter.init(mCustomParameters); + } + // PV + mDoPVITS = common::getFromConfig(mCustomParameters, "doPVITSQC", false); + + mMatchITSTPCQC.initDataRequest(); + mMatchITSTPCQC.init(); + mMatchITSTPCQC.publishHistograms(getObjectsManager()); +} + +void ITSTPCMatchingTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + mMatchITSTPCQC.reset(); + mIsPbPb = activity.mBeamType == "PbPb"; +} + +void ITSTPCMatchingTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ITSTPCMatchingTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + + ILOG(Debug) << "********** Starting monitoring" << ENDM; + mMatchITSTPCQC.run(ctx); +} + +void ITSTPCMatchingTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + mMatchITSTPCQC.finalize(); + + // Sync Mode + if (mIsSync) { + if (mDoMTCRatios) { + auto makeRatio = [](std::unique_ptr& ratio, TEfficiency* eff) { + if (ratio) { + ratio->Reset(); + } else { + std::string name = eff->GetName(); + name += "_Hist"; + ratio = std::make_unique(name.c_str(), eff->GetTitle(), + eff->GetPassedHistogram()->GetXaxis()->GetNbins(), + eff->GetPassedHistogram()->GetXaxis()->GetXmin(), + eff->GetPassedHistogram()->GetXaxis()->GetXmax()); + } + ratio->SetBit(TH1::EStatusBits::kNoStats); + if (!ratio->getNum()->Add(eff->GetPassedHistogram())) { + ILOG(Error) << "Add operation for numerator histogram of " << ratio->GetName() << " failed; efficiency will be skewed" << ENDM; + } + if (!ratio->getDen()->Add(eff->GetTotalHistogram())) { + ILOG(Error) << "Add operation for denominator histogram of " << ratio->GetName() << " failed; efficiency will be skewed" << ENDM; + } + ratio->GetXaxis()->SetTitle(eff->GetPassedHistogram()->GetXaxis()->GetTitle()); + ratio->GetYaxis()->SetTitle(eff->GetPassedHistogram()->GetYaxis()->GetTitle()); + ratio->Sumw2(); + ratio->setHasBinominalErrors(); + ratio->update(); + }; + + // Pt + makeRatio(mEffPt, mMatchITSTPCQC.getFractionITSTPCmatch(gloqc::MatchITSTPCQC::ITS)); + getObjectsManager()->startPublishing(mEffPt.get(), PublicationPolicy::Once); + getObjectsManager()->setDefaultDrawOptions(mEffPt->GetName(), "logx"); + + // Eta + makeRatio(mEffEta, mMatchITSTPCQC.getFractionITSTPCmatchEta(gloqc::MatchITSTPCQC::ITS)); + getObjectsManager()->startPublishing(mEffEta.get(), PublicationPolicy::Once); + + // Phi + makeRatio(mEffPhi, mMatchITSTPCQC.getFractionITSTPCmatchPhi(gloqc::MatchITSTPCQC::ITS)); + getObjectsManager()->startPublishing(mEffPhi.get(), PublicationPolicy::Once); + } + + if (mDoPVITS) { + // const auto* h = (mIsPbPb) ? mMatchITSTPCQC.getHistoPVNContVsITSTracksPbPb() : mMatchITSTPCQC.getHistoPVNContVsITSTracks(); + // if (!h) { + // ILOG(Fatal) << "Could not retrieve pv ITS histogram!" << ENDM; + // } + + // mPVITSCycle.reset(); + // mPVITSCycle.reset(dynamic_cast(h->Clone("mPVNContVsITSTracks_Cycle"))); + // if (!mPVITSCycle) { + // ILOG(Fatal) << "Could not retrieve pv ITS histogram for current cycle!" << ENDM; + // } + // if (!mPVITSIntegral) { + // mPVITSIntegral.reset(dynamic_cast(h->Clone("mPVNContVsITSTracks_Integral"))); + // if (!mPVITSIntegral) { + // ILOG(Fatal) << "Could not retrieve pv ITS histogram for integral!" << ENDM; + // } + // } + // if (mPVITSCycle->GetEntries() != h->GetEntries()) { + // mPVITSCycle->Reset(); + // mPVITSCycle->Add(h, mPVITSCycle.get(), 1., -1.); + // mPVITSIntegral->Reset(); + // mPVITSIntegral->Add(h); + + // getObjectsManager()->startPublishing(mPVITSCycle.get(), PublicationPolicy::Once); + // getObjectsManager()->startPublishing(mPVITSIntegral.get(), PublicationPolicy::Once); + // getObjectsManager()->startPublishing(mPVITSCycle->ProfileX(), PublicationPolicy::Once); + // getObjectsManager()->startPublishing(mPVITSIntegral->ProfileX(), PublicationPolicy::Once); + // } + } + + if (mDoK0s) { + const auto* k0s = (mIsPbPb) ? mMatchITSTPCQC.getHistoK0MassVsPtVsOccPbPb() : mMatchITSTPCQC.getHistoK0MassVsPtVsOccpp(); + if (!k0s) { + ILOG(Fatal) << "Could not retrieve k0s histogram for beam type: " << mIsPbPb << ENDM; + } + + mK0sCycle.reset(); + mK0sCycle.reset(dynamic_cast(k0s->Clone("mK0sMassVsPtVsOcc_Cycle"))); + if (!mK0sCycle) { + ILOG(Fatal) << "Could not retrieve k0s histogram for current cycle" << ENDM; + } + if (!mK0sIntegral) { + mK0sIntegral.reset(dynamic_cast(k0s->Clone("mK0sMassVsPtVsOcc_Integral"))); + if (!mK0sIntegral) { + ILOG(Fatal) << "Could not retrieve k0s histogram integral" << ENDM; + } + } + if (k0s->GetEntries() != mK0sIntegral->GetEntries()) { + mK0sCycle->Reset(); + mK0sCycle->Add(k0s, mK0sIntegral.get(), 1., -1.); + mK0sIntegral->Reset(); + mK0sIntegral->Add(k0s); + + if (mPublishK0s3D) { + getObjectsManager()->startPublishing(mK0sCycle.get(), PublicationPolicy::Once); + getObjectsManager()->startPublishing(mK0sIntegral.get(), PublicationPolicy::Once); + } + + TH1D* h{ nullptr }; + getObjectsManager()->startPublishing((h = mK0sCycle->ProjectionY("mK0sMassVsPtVsOcc_Cycle_pmass")), PublicationPolicy::Once); + + if (mSplitTPCOccupancy != OptValue) { + auto splitOccBin = mK0sCycle->GetZaxis()->FindBin(mSplitTPCOccupancy); + getObjectsManager()->startPublishing(mK0sCycle->ProjectionY("mK0sMassVsPtVsOcc_Cycle_pmass_lowOcc", 0, -1, 0, splitOccBin - 1), PublicationPolicy::Once); + getObjectsManager()->startPublishing(mK0sCycle->ProjectionY("mK0sMassVsPtVsOcc_Cycle_pmass_highOcc", 0, -1, splitOccBin), PublicationPolicy::Once); + } + + if (mSplitPt != OptValue) { + auto splitPtBin = mK0sCycle->GetXaxis()->FindBin(mSplitPt); + getObjectsManager()->startPublishing(mK0sCycle->ProjectionY("mK0sMassVsPtVsOcc_Cycle_pmass_lowPt", 0, splitPtBin - 1), PublicationPolicy::Once); + getObjectsManager()->startPublishing(mK0sCycle->ProjectionY("mK0sMassVsPtVsOcc_Cycle_pmass_highPt", splitPtBin), PublicationPolicy::Once); + } + + if (mK0sFitter.fit(h)) { + if (mDoPVITS && mPVITSCycle->GetEntries() != 0) { + mK0sFitter.mSignalAndBackground->SetParameter(helpers::K0sFitter::Parameters::Pol0, mPVITSCycle->GetEntries()); + } + getObjectsManager()->startPublishing(mK0sFitter.mSignalAndBackground.get(), PublicationPolicy::Once); + } + + getObjectsManager()->startPublishing((h = mK0sIntegral->ProjectionY("mK0sMassVsPtVsOcc_Integral_pmass")), PublicationPolicy::Once); + } + } + } +} + +void ITSTPCMatchingTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSTPCMatchingTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mMatchITSTPCQC.reset(); + mEffPt.reset(); + mEffPhi.reset(); + mEffEta.reset(); +} + +} // namespace o2::quality_control_modules::glo diff --git a/Modules/GLO/src/ITSTPCmatchingCheck.cxx b/Modules/GLO/src/ITSTPCmatchingCheck.cxx new file mode 100644 index 0000000000..6002d61f60 --- /dev/null +++ b/Modules/GLO/src/ITSTPCmatchingCheck.cxx @@ -0,0 +1,590 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file ITSTPCmatchingCheck.cxx +/// \author felix.schlepper@cern.ch + +#include "GLO/ITSTPCmatchingCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include +#include +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::glo +{ + +Quality ITSTPCmatchingCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + Quality ptQual = Quality::Good; + Quality phiQual = Quality::Good; + Quality etaQual = Quality::Good; + + for (auto& [_, mo] : *moMap) { + const std::string moName = mo->GetName(); + + if (mShowPt && moName == "mFractionITSTPCmatch_ITS_Hist") { + auto* eff = dynamic_cast(mo->getObject()); + if (eff == nullptr) { + ILOG(Error) << "Failed cast for ITSTPCmatch_ITS check!" << ENDM; + continue; + } + auto binLow = eff->FindFixBin(mMinPt), binUp = eff->FindFixBin(mMaxPt); + + ptQual = Quality::Good; + result.addMetadata("checkPtQuality", "good"); + result.addMetadata("checkPtBins", ""); + result.addMetadata("checkPt", "good"); + if (eff->GetEntries() == 0) { + ptQual = Quality::Null; + result.updateMetadata("checkPt", "null"); + } else { + std::vector badBins; + for (int iBin = binLow; iBin <= binUp; ++iBin) { + if (eff->GetBinContent(iBin) < mThresholdPt) { + ptQual = Quality::Bad; + badBins.push_back(iBin); + } + } + auto ranges = findRanges(badBins); + if (ptQual == Quality::Bad) { + result.addFlag(FlagTypeFactory::BadTracking(), "Check vdrift online calibration and ITS/TPC QC"); + result.updateMetadata("checkPtQuality", "bad"); + + std::string cBins{ "Bad matching efficiency in: " }; + if (mLimitRange >= 0 && ranges.size() > mLimitRange) { + result.updateMetadata("checkPtBins", "too many bad bins"); + } else { + for (const auto& [binLow, binUp] : ranges) { + float low = eff->GetXaxis()->GetBinLowEdge(binLow), up = eff->GetXaxis()->GetBinUpEdge(binUp); + cBins += std::format("{:.1f}-{:.1f},", low, up); + } + cBins.pop_back(); // remove last `,` + cBins += " (GeV/c)"; + result.updateMetadata("checkPtBins", cBins); + } + } + } + } else if (mShowPhi && moName == "mFractionITSTPCmatchPhi_ITS_Hist") { + auto* eff = dynamic_cast(mo->getObject()); + if (eff == nullptr) { + ILOG(Error) << "Failed cast for ITSTPCmatchPhi_ITS check!" << ENDM; + continue; + } + + phiQual = Quality::Good; + result.addMetadata("checkPhiQuality", "good"); + result.addMetadata("checkPhiBins", ""); + result.addMetadata("checkPhi", "good"); + if (eff->GetEntries() == 0) { + phiQual = Quality::Null; + result.updateMetadata("checkPhi", "null"); + } else { + std::vector badBins; + for (int iBin{ 1 }; iBin <= eff->GetNbinsX(); ++iBin) { + if (eff->GetBinContent(iBin) < mThresholdPhi) { + phiQual = Quality::Bad; + badBins.push_back(iBin); + } + } + auto ranges = findRanges(badBins); + if (phiQual == Quality::Bad) { + result.addFlag(FlagTypeFactory::BadTracking(), "Check TPC sectors or ITS staves QC!"); + result.updateMetadata("checkPhiQuality", "bad"); + + if (mLimitRange >= 0 && ranges.size() > mLimitRange) { + result.updateMetadata("checkPhiBins", "too many bad bins"); + } else { + std::string cBins{ "Bad matching efficiency in: " }; + for (const auto& [binLow, binUp] : ranges) { + float low = eff->GetXaxis()->GetBinLowEdge(binLow), up = eff->GetXaxis()->GetBinUpEdge(binUp); + cBins += std::format("{:.1f}-{:.1f},", low, up); + } + cBins.pop_back(); // remove last `,` + cBins += " (rad)"; + result.updateMetadata("checkPhiBins", cBins); + } + } + } + } else if (mShowEta && moName == "mFractionITSTPCmatchEta_ITS_Hist") { + auto* eff = dynamic_cast(mo->getObject()); + if (eff == nullptr) { + ILOG(Error) << "Failed cast for ITSTPCmatchEta_ITS check!" << ENDM; + continue; + } + auto binLow = eff->FindFixBin(mMinEta), binUp = eff->FindFixBin(mMaxEta); + + etaQual = Quality::Good; + result.addMetadata("checkEtaQuality", "good"); + result.addMetadata("checkEtaBins", ""); + result.addMetadata("checkEta", "good"); + if (eff->GetEntries() == 0) { + etaQual = Quality::Null; + result.updateMetadata("checkEta", "null"); + } else { + std::vector badBins; + for (int iBin = binLow; iBin <= binUp; ++iBin) { + if (eff->GetBinContent(iBin) < mThresholdEta) { + etaQual = Quality::Bad; + badBins.push_back(iBin); + } + } + auto ranges = findRanges(badBins); + if (etaQual == Quality::Bad) { + result.updateMetadata("checkEtaQuality", "bad"); + + if (mLimitRange >= 0 && ranges.size() > mLimitRange) { + result.updateMetadata("checkEtaBins", "too many bad bins"); + } else { + std::string cBins{ "Bad matching efficiency in: " }; + for (const auto& [binLow, binUp] : ranges) { + float low = eff->GetXaxis()->GetBinLowEdge(binLow), up = eff->GetXaxis()->GetBinUpEdge(binUp); + cBins += std::format("{:.1f}-{:.1f},", low, up); + } + cBins.pop_back(); // remove last `,` + result.updateMetadata("checkEtaBins", cBins); + } + } + } + } + } + + // Overall Quality + auto isWorse = [](Quality const& a, Quality const& b) { + return a.isWorseThan(b) ? a : b; + }; + // Somehow this is not accepted by the CI, although running git-clang-format locally accepts it + // clang-format off + auto findWorst = [&](const T& first, Args... args) { + return isWorse(first, isWorse(args...)); + }; + // clang-format on + result.set(findWorst(ptQual, etaQual, phiQual)); + + return result; +} + +void ITSTPCmatchingCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo == nullptr) { + ILOG(Error) << "Received nullptr from upstream, returning..." << ENDM; + return; + } + + const auto name = mo->getName(); + + if (mShowPt && name == "mFractionITSTPCmatch_ITS_Hist") { + auto* eff = dynamic_cast(mo->getObject()); + if (eff == nullptr) { + ILOG(Error) << "Failed cast for ITSTPCmatch_ITS_Hist beautify!" << ENDM; + return; + } + eff->GetYaxis()->SetRangeUser(0, 1.1); + eff->GetYaxis()->SetBit(TAxis::EStatusBits::kDecimals); + + // Draw threshold lines + float xmin = eff->GetXaxis()->GetXmin(); + float xmax = eff->GetXaxis()->GetXmax(); + float xminT = mMinPt; + float xmaxT = mMaxPt; + auto* l1 = new TLine(xmin, mThresholdPt, xmax, mThresholdPt); + l1->SetLineStyle(kDashed); + l1->SetLineWidth(4); + l1->SetLineColor(kCyan - 7); + auto* l2 = new TLine(xminT, 0, xminT, 1.1); + l2->SetLineStyle(kDashed); + l2->SetLineWidth(4); + l2->SetLineColor(kCyan - 7); + auto* l3 = new TLine(xmaxT, 0, xmaxT, 1.1); + l3->SetLineStyle(kDashed); + l3->SetLineWidth(4); + l3->SetLineColor(kCyan - 7); + auto* tt = new TText(xmaxT + 0.3, 1.02, "Checked Range"); + tt->SetTextSize(0.04); + auto* ttt = new TText(xmaxT + 0.3, mThresholdPt + 0.012, "Threshold"); + auto* aa = new TArrow(xminT + 0.01, 1.02, xmaxT - 0.01, 1.02, 0.02, "<|>"); + if (checkResult.getMetadata("checkPtQuality") == "good") { + l1->SetLineColor(kCyan - 7); + l2->SetLineColor(kCyan - 7); + l3->SetLineColor(kCyan - 7); + aa->SetFillColor(kCyan - 7); + aa->SetLineColor(kCyan - 7); + } else { + l1->SetLineColor(kRed - 4); + l2->SetLineColor(kRed - 4); + l3->SetLineColor(kRed - 4); + aa->SetFillColor(kRed - 4); + aa->SetLineColor(kRed - 4); + } + eff->GetListOfFunctions()->Add(l1); + eff->GetListOfFunctions()->Add(l2); + eff->GetListOfFunctions()->Add(l3); + eff->GetListOfFunctions()->Add(tt); + eff->GetListOfFunctions()->Add(ttt); + eff->GetListOfFunctions()->Add(aa); + + // Color Bins in Window + int cG{ 0 }, cB{ 0 }, cTot{ 0 }; + auto* good = new TPolyMarker(); + good->SetMarkerColor(kGreen); + good->SetMarkerStyle(25); + good->SetMarkerSize(1); + auto* bad = new TPolyMarker(); + bad->SetMarkerColor(kRed); + bad->SetMarkerStyle(25); + bad->SetMarkerSize(1); + auto binLow = eff->FindFixBin(mMinPt), binUp = eff->FindFixBin(mMaxPt); + for (int iBin = binLow; iBin <= binUp; ++iBin) { + auto x = eff->GetBinCenter(iBin); + auto y = eff->GetBinContent(iBin); + if (y < mThresholdPt) { + bad->SetPoint(cB++, x, y); + } else { + good->SetPoint(cG++, x, y); + } + } + cTot = cG + cB; + auto* leg = new TLegend(0.7, 0.13, 0.89, 0.3); + leg->SetHeader("Threshold Checks"); + leg->AddEntry(good, Form("Good %d / %d", cG, cTot), "P"); + leg->AddEntry(bad, Form("Bad %d / %d", cB, cTot), "P"); + eff->GetListOfFunctions()->Add(good); + eff->GetListOfFunctions()->Add(bad); + eff->GetListOfFunctions()->Add(leg); + + // Quality + auto* msg = new TPaveText(0.12, 0.12, 0.5, 0.3, "NDC;NB"); + if (checkResult.getMetadata("checkPtQuality") == "good") { + msg->AddText("Quality: Good"); + msg->SetFillColor(kGreen); + } else if (checkResult.getMetadata("checkPtQuality") == "bad") { + msg->AddText("Quality: Bad"); + msg->SetFillColor(kRed); + msg->AddText("Check vdrift online calibration and ITS/TPC QC"); + msg->AddText(checkResult.getMetadata("checkPtBins").c_str()); + msg->SetTextColor(kWhite); + } else if (checkResult.getMetadata("checkPtQuality") == "null") { + msg->SetFillColor(kWhite); + msg->AddText("Quality: Null"); + msg->AddText("No ITS tracks in denominator!"); + msg->AddText(checkResult.getMetadata("checkPtBins").c_str()); + msg->SetTextColor(kBlack); + } else { + msg->AddText("Quality: Undefined"); + msg->AddText("Not-handled Quality flag, don't panic..."); + } + eff->GetListOfFunctions()->Add(msg); + } else if (mShowPhi && name == "mFractionITSTPCmatchPhi_ITS_Hist") { + auto* eff = dynamic_cast(mo->getObject()); + if (eff == nullptr) { + ILOG(Error) << "Failed cast for ITSTPCmatchPhi_ITS_Hist beautify!" << ENDM; + return; + } + eff->GetYaxis()->SetRangeUser(0, 1.1); + eff->GetYaxis()->SetBit(TAxis::EStatusBits::kDecimals); + + // Draw threshold lines + auto* l1 = new TLine(0, mThresholdPhi, 2 * TMath::Pi(), mThresholdPhi); + l1->SetLineStyle(kDashed); + l1->SetLineWidth(4); + l1->SetLineColor(kCyan - 7); + auto* tt = new TText(TMath::Pi() - 1., 1.05, "Checked Range"); + tt->SetTextSize(0.04); + auto* ttt = new TText(2 * TMath::Pi() - 2, mThresholdPhi + 0.012, "Threshold"); + auto* aa = new TArrow(0.01, 1.02, 2 * TMath::Pi() - 0.01, 1.02, 0.02, "<|>"); + if (checkResult.getMetadata("checkPhiQuality") == "good") { + l1->SetLineColor(kCyan - 7); + aa->SetFillColor(kCyan - 7); + aa->SetLineColor(kCyan - 7); + } else { + l1->SetLineColor(kRed - 4); + aa->SetFillColor(kRed - 4); + aa->SetLineColor(kRed - 4); + } + eff->GetListOfFunctions()->Add(l1); + eff->GetListOfFunctions()->Add(tt); + eff->GetListOfFunctions()->Add(ttt); + eff->GetListOfFunctions()->Add(aa); + + // Color Bins in Window + int cG{ 0 }, cB{ 0 }, cTot{ 0 }; + auto* good = new TPolyMarker(); + good->SetMarkerColor(kGreen); + good->SetMarkerStyle(25); + good->SetMarkerSize(1); + auto* bad = new TPolyMarker(); + bad->SetMarkerColor(kRed); + bad->SetMarkerStyle(25); + bad->SetMarkerSize(1); + for (int iBin{ 1 }; iBin <= eff->GetNbinsX(); ++iBin) { + auto x = eff->GetBinCenter(iBin); + auto y = eff->GetBinContent(iBin); + if (y < mThresholdPhi) { + bad->SetPoint(cB++, x, y); + } else { + good->SetPoint(cG++, x, y); + } + } + cTot = cG + cB; + auto* leg = new TLegend(0.7, 0.13, 0.89, 0.3); + leg->SetHeader("Threshold Checks"); + leg->AddEntry(good, Form("Good %d / %d", cG, cTot), "P"); + leg->AddEntry(bad, Form("Bad %d / %d", cB, cTot), "P"); + eff->GetListOfFunctions()->Add(good); + eff->GetListOfFunctions()->Add(bad); + eff->GetListOfFunctions()->Add(leg); + + // Quality + auto* msg = new TPaveText(0.12, 0.12, 0.5, 0.3, "NDC;NB"); + if (checkResult.getMetadata("checkPhiQuality") == "good") { + msg->AddText("Quality: Good"); + msg->SetFillColor(kGreen); + } else if (checkResult.getMetadata("checkPhiQuality") == "bad") { + msg->AddText("Quality: Bad"); + msg->SetFillColor(kRed); + msg->AddText("Check TPC sector or ITS staves!"); + msg->AddText(checkResult.getMetadata("checkPhiBins").c_str()); + msg->SetTextColor(kWhite); + } else if (checkResult.getMetadata("checkPhiQuality") == "null") { + msg->SetFillColor(kWhite); + msg->AddText("Quality: Null"); + msg->AddText("No ITS tracks in denominator!"); + msg->SetTextColor(kBlack); + } else { + msg->AddText("Quality: Undefined"); + msg->AddText("Not-handled Quality flag, don't panic..."); + } + eff->GetListOfFunctions()->Add(msg); + } else if (mShowEta && name == "mFractionITSTPCmatchEta_ITS_Hist") { + auto* eff = dynamic_cast(mo->getObject()); + if (eff == nullptr) { + ILOG(Error) << "Failed cast for ITSTPCmatchEta_ITS_Hist beautify!" << ENDM; + return; + } + eff->GetYaxis()->SetRangeUser(0, 1.1); + eff->GetYaxis()->SetBit(TAxis::EStatusBits::kDecimals); + + // Draw threshold lines + float xmin = eff->GetXaxis()->GetXmin(); + float xmax = eff->GetXaxis()->GetXmax(); + float xminT = mMinEta; + float xmaxT = mMaxEta; + auto* l1 = new TLine(xmin, mThresholdEta, xmax, mThresholdEta); + l1->SetLineStyle(kDashed); + l1->SetLineWidth(4); + l1->SetLineColor(kCyan - 7); + auto* l2 = new TLine(xminT - 0.02, 0, xminT - 0.02, 1.1); + l2->SetLineStyle(kDashed); + l2->SetLineWidth(4); + l2->SetLineColor(kCyan - 7); + auto* l3 = new TLine(xmaxT + 0.02, 0, xmaxT + 0.02, 1.1); + l3->SetLineStyle(kDashed); + l3->SetLineWidth(4); + l3->SetLineColor(kCyan - 7); + auto* tt = new TText(xminT + 0.1, 1.05, "Checked Range"); + tt->SetTextSize(0.04); + auto* ttt = new TText(xmaxT + 0.2, mThresholdEta + 0.012, "Threshold"); + auto* aa = new TArrow(xminT + 0.01, 1.02, xmaxT - 0.01, 1.02, 0.02, "<|>"); + if (checkResult.getMetadata("checkEtaQuality") == "good") { + l1->SetLineColor(kCyan - 7); + l2->SetLineColor(kCyan - 7); + l3->SetLineColor(kCyan - 7); + aa->SetFillColor(kCyan - 7); + aa->SetLineColor(kCyan - 7); + } else { + l1->SetLineColor(kRed - 4); + l2->SetLineColor(kRed - 4); + l3->SetLineColor(kRed - 4); + aa->SetFillColor(kRed - 4); + aa->SetLineColor(kRed - 4); + } + eff->GetListOfFunctions()->Add(l1); + eff->GetListOfFunctions()->Add(l2); + eff->GetListOfFunctions()->Add(l3); + eff->GetListOfFunctions()->Add(tt); + eff->GetListOfFunctions()->Add(ttt); + eff->GetListOfFunctions()->Add(aa); + + // Color Bins in Window + int cG{ 0 }, cB{ 0 }, cTot{ 0 }; + auto* good = new TPolyMarker(); + good->SetMarkerColor(kGreen); + good->SetMarkerStyle(25); + good->SetMarkerSize(1); + auto* bad = new TPolyMarker(); + bad->SetMarkerColor(kRed); + bad->SetMarkerStyle(25); + bad->SetMarkerSize(1); + auto binLow = eff->FindFixBin(mMinEta), binUp = eff->FindFixBin(mMaxEta); + for (int iBin = binLow; iBin <= binUp; ++iBin) { + auto x = eff->GetBinCenter(iBin); + auto y = eff->GetBinContent(iBin); + if (y < mThresholdEta) { + bad->SetPoint(cB++, x, y); + } else { + good->SetPoint(cG++, x, y); + } + } + cTot = cG + cB; + auto* leg = new TLegend(0.7, 0.13, 0.89, 0.3); + leg->SetHeader("Threshold Checks"); + leg->AddEntry(good, Form("Good %d / %d", cG, cTot), "P"); + leg->AddEntry(bad, Form("Bad %d / %d", cB, cTot), "P"); + eff->GetListOfFunctions()->Add(good); + eff->GetListOfFunctions()->Add(bad); + eff->GetListOfFunctions()->Add(leg); + + // Quality + auto* msg = new TPaveText(0.12, 0.12, 0.5, 0.3, "NDC;NB"); + if (checkResult.getMetadata("checkEtaQuality") == "good") { + msg->AddText("Quality: Good"); + msg->SetFillColor(kGreen); + } else if (checkResult.getMetadata("checkEtaQuality") == "bad") { + msg->AddText("Quality: Bad"); + msg->SetFillColor(kRed); + msg->AddText(checkResult.getMetadata("checkEtaBins").c_str()); + msg->SetTextColor(kWhite); + } else if (checkResult.getMetadata("checkEtaQuality") == "null") { + msg->SetFillColor(kWhite); + msg->AddText("Quality: Null"); + msg->AddText("No ITS tracks in denominator!"); + msg->SetTextColor(kBlack); + } else { + msg->AddText("Quality: Undefined"); + msg->AddText("Not-handled Quality flag, don't panic..."); + } + eff->GetListOfFunctions()->Add(msg); + } else if (mShowK0s && (name.starts_with("mK0sMassVsPtVsOcc_Cycle_pmass") || name.starts_with("mK0sMassVsPtVsOcc_Integral_pmass"))) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + ILOG(Error) << "Failed cast for " << name << " beautify!" << ENDM; + return; + } + auto isCycle = name.find("Cycle") != std::string::npos; + auto isHigh = name.find("high") != std::string::npos; + auto isLow = name.find("low") != std::string::npos; + auto isOcc = name.ends_with("Occ"); + auto isPt = name.ends_with("Pt"); + auto msg = new TPaveText(0.6, 0.6, 0.88, 0.88, "NDC;NB"); + if (!isLow && !isHigh) { + h->SetTitle(Form("K0s invariant mass (integrated over #it{p}_{T} and occupancy, %s);K0s mass (GeV/c^{2});entries", (isCycle) ? "last cycle" : "integrated")); + } else { + h->SetTitle(Form("K0s invariant mass (integrated over%s#it{p}_{T} and%soccupancy, last cycle);K0s mass (GeV/c^{2});entries", + (isPt) ? ((isLow) ? " low " : " high ") : " ", + (isOcc) ? ((isLow) ? " low " : " high ") : " ")); + } + if (!mK0sFitter.fit(h, true)) { + msg->AddText("Fit: Failed"); + msg->SetFillColor(kRed); + msg->SetTextColor(kWhite); + } else { + auto unc = mK0sFitter.getUncertainty(); + auto rerr = mK0sFitter.getRelativeError(); + auto max = h->GetMaximum(), min = h->GetMinimum(), textp{ (max - min) * 0.1 }; + auto l = new TLine(mK0sFitter.mMassK0s, 0, mK0sFitter.mMassK0s, max); + l->SetLineStyle(kDotted); + h->GetListOfFunctions()->Add(l); + auto t = new TText(mK0sFitter.mMassK0s - 0.025, textp, "PDG K0s"); + h->GetListOfFunctions()->Add(t); + if (unc > mAccUncertainty || rerr > mAccRelError) { + msg->AddText("Fit: BAD"); + msg->AddText("Not converged"); + msg->AddText(Form("Discrepant %.2f", unc)); + msg->AddText(Form("RError %.2f%%", rerr * 1e2)); + msg->SetFillColor(kRed); + msg->SetTextColor(kWhite); + } else { + msg->AddText("Fit: GOOD"); + msg->AddText(Form("Mass %.1f #pm %.1f (MeV)", mK0sFitter.getMass() * 1e3, mK0sFitter.getSigma() * 1e3)); + msg->AddText(Form("Consistent %.2f", unc)); + msg->AddText(Form("RError %.2f%%", rerr * 1e2)); + msg->SetFillColor(kGreen); + } + if (TF1* fit = h->GetFunction("gloFitK0sMassSignal"); fit != nullptr) { + h->GetListOfFunctions()->Add(fit); + } + } + h->GetListOfFunctions()->Add(msg); + } +} + +void ITSTPCmatchingCheck::startOfActivity(const Activity& activity) +{ + mActivity = make_shared(activity); + + if ((mShowPt = common::getFromExtendedConfig(activity, mCustomParameters, "showPt", false))) { + mThresholdPt = common::getFromExtendedConfig(activity, mCustomParameters, "thresholdPt", 0.5f); + mMinPt = common::getFromExtendedConfig(activity, mCustomParameters, "minPt", 1.0f); + mMaxPt = common::getFromExtendedConfig(activity, mCustomParameters, "maxPt", 1.999f); + } + + if ((mShowPhi = common::getFromExtendedConfig(activity, mCustomParameters, "showPhi", false))) { + mThresholdPhi = common::getFromExtendedConfig(activity, mCustomParameters, "thresholdPhi", 0.3f); + } + + if ((mShowEta = common::getFromExtendedConfig(activity, mCustomParameters, "showEta", false))) { + mThresholdEta = common::getFromExtendedConfig(activity, mCustomParameters, "thresholdEta", 0.4f); + mMinEta = common::getFromExtendedConfig(activity, mCustomParameters, "minEta", -0.8f); + mMaxEta = common::getFromExtendedConfig(activity, mCustomParameters, "maxEta", 0.8f); + } + + if ((mShowK0s = common::getFromExtendedConfig(activity, mCustomParameters, "showK0s", false))) { + mAccRelError = common::getFromExtendedConfig(activity, mCustomParameters, "acceptableK0sRError", 0.2f); + mAccUncertainty = common::getFromExtendedConfig(activity, mCustomParameters, "acceptableK0sUncertainty", 2.f); + mK0sFitter.init(mCustomParameters, activity); + } + + mLimitRange = common::getFromExtendedConfig(activity, mCustomParameters, "limitRanges", 5); + mIsPbPb = activity.mBeamType == "Pb-Pb"; +} + +std::vector> ITSTPCmatchingCheck::findRanges(const std::vector& nums) noexcept +{ + std::vector> ranges; + if (nums.empty()) { + return ranges; + } + + int start = nums[0]; + int end = start; + + for (size_t i = 1; i < nums.size(); ++i) { + if (nums[i] == end + 1) { + end = nums[i]; + } else { + ranges.emplace_back(start, end); + start = end = nums[i]; + } + } + ranges.emplace_back(start, end); // Add the last range + return ranges; +} + +} // namespace o2::quality_control_modules::glo diff --git a/Modules/GLO/src/MeanVertexCheck.cxx b/Modules/GLO/src/MeanVertexCheck.cxx new file mode 100644 index 0000000000..7742d3928e --- /dev/null +++ b/Modules/GLO/src/MeanVertexCheck.cxx @@ -0,0 +1,225 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MeanVertexCheck.cxx +/// \author Andrea Ferrero +/// + +#include "GLO/MeanVertexCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::glo +{ +void MeanVertexCheck::configure() {} + +void MeanVertexCheck::startOfActivity(const Activity& activity) +{ + mActivity = activity; + + std::string parKey = std::string("nPointsToCheck"); + // search in extended parameters first + auto parOpt = mCustomParameters.atOptional(parKey, mActivity); + if (parOpt.has_value()) { + if (parOpt.value() == "all") { + mNPointsToCheck = 0; + } else { + mNPointsToCheck = std::stoi(parOpt.value()); + } + } else { + // search in standard parameters if key not found in extended ones + parOpt = mCustomParameters.atOptional(parKey); + if (parOpt.value() == "all") { + mNPointsToCheck = 0; + } else { + mNPointsToCheck = std::stoi(parOpt.value()); + } + } + ILOG(Info, Support) << "MeanVertexCheck: checking at most " << mNPointsToCheck << " points " << ENDM; +} + +void MeanVertexCheck::endOfActivity(const Activity& activity) +{ + mActivity = Activity{}; + mRanges.clear(); + mQualities.clear(); +} + +static std::string getBaseName(std::string name) +{ + auto pos = name.rfind("/"); + return ((pos < std::string::npos) ? name.substr(pos + 1) : name); +} + +void MeanVertexCheck::initRange(std::string key) +{ + // Get acceptable range for this histogram + auto iter = mRanges.find(key); + if (iter != mRanges.end()) { + return; + } + + // get configuration parameter associated to the key + std::string parKey = std::string("range:") + key; + std::string parValue; + // search in extended parameters first + auto parOpt = mCustomParameters.atOptional(parKey, mActivity); + if (parOpt.has_value()) { + parValue = parOpt.value(); + } else { + // search in standard parameters if key not found in extended ones + parOpt = mCustomParameters.atOptional(parKey); + if (parOpt.has_value()) { + parValue = parOpt.value(); + } + } + + // extract the minimum and maximum values from the configurable parameter + auto range = o2::utils::Str::tokenize(parValue, ',', false, true); + if (range.size() == 2) { + double min = std::stod(range[0]); + double max = std::stod(range[1]); + mRanges.insert(std::pair{ key, std::make_pair(min, max) }); + ILOG(Info, Support) << "MeanVertexCheck: range for " << key << " set to [" << min << "," << max << "]" << ENDM; + } +} + +std::optional> MeanVertexCheck::getRange(std::string key) +{ + std::optional> result; + + // Get acceptable range for this histogram + auto iter = mRanges.find(key); + if (iter != mRanges.end()) { + result = iter->second; + } + + return result; +} + +Quality MeanVertexCheck::check(std::map>* moMap) +{ + for (auto& [moKey, mo] : *moMap) { + + auto moName = mo->getName(); + auto key = getBaseName(moName); + + // get acceptable range for the current plot + initRange(key); + auto range = getRange(key); + if (!range) { + continue; + } + + // check that the object is a TGraph + TGraph* graph = dynamic_cast(mo->getObject()); + if (!graph) { + continue; + } + + // Quality is good by default, unless one of the points is outside the acceptable range + mQualities[moName] = Quality::Good; + + // loop over points in reverse order + int nChecked = 0; + int nPoints = graph->GetN(); + for (int p = nPoints - 1; p >= 0; p--) { + double value = graph->GetPointY(p); + // check that the value is within the acceptable range + if (value < range->first || value > range->second) { + mQualities[moName] = Quality::Bad; + break; + } + + nChecked += 1; + // stop if we already checked enough points + if (mNPointsToCheck > 0 && nChecked >= mNPointsToCheck) { + break; + } + } + } + + // compute overall quality + Quality result = mQualities.empty() ? Quality::Null : Quality::Good; + for (auto& [key, q] : mQualities) { + if (q.isWorseThan(result)) { + result = q; + } + } + + return result; +} + +void MeanVertexCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + auto moName = mo->getName(); + Quality quality = mQualities[moName]; + + // get acceptable range for plot, to draw the horizontal lines + auto key = getBaseName(moName); + auto range = getRange(key); + if (!range) { + return; + } + + // check that the object is a TGraph + auto graph = dynamic_cast(mo->getObject()); + if (!graph || graph->GetN() < 1) { + return; + } + + // draw the graph in red if the quality is Bad + if (quality == Quality::Bad) { + graph->SetLineColor(kRed); + graph->SetMarkerColor(kRed); + } + + // Compute vertical axis range + double min = TMath::Min(range->first, TMath::MinElement(graph->GetN(), graph->GetY())); + double max = TMath::Max(range->second, TMath::MaxElement(graph->GetN(), graph->GetY())); + // put 10% of margin above and below the limits + auto delta = max - min; + min -= 0.1 * delta; + max += 0.1 * delta; + + graph->SetMinimum(min); + graph->SetMaximum(max); + + // add horizontal lines delimiting the acceptable range + double xmin = graph->GetXaxis()->GetXmin(); + double xmax = graph->GetXaxis()->GetXmax(); + TLine* l1 = new TLine(xmin, range->first, xmax, range->first); + TLine* l2 = new TLine(xmin, range->second, xmax, range->second); + + l1->SetLineStyle(kDotted); + l2->SetLineStyle(kDotted); + + if (quality == Quality::Bad) { + l1->SetLineColor(kRed); + l2->SetLineColor(kRed); + } else { + l1->SetLineColor(kBlue); + l2->SetLineColor(kBlue); + } + + graph->GetListOfFunctions()->Add(l1); + graph->GetListOfFunctions()->Add(l2); +} + +} // namespace o2::quality_control_modules::glo diff --git a/Modules/GLO/src/MeanVertexPostProcessing.cxx b/Modules/GLO/src/MeanVertexPostProcessing.cxx new file mode 100644 index 0000000000..8677f2fb1d --- /dev/null +++ b/Modules/GLO/src/MeanVertexPostProcessing.cxx @@ -0,0 +1,142 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MeanVertexPostProcessing.cxx +/// \author Chiara Zampolli, Andrea Ferrero +/// + +#include "GLO/MeanVertexPostProcessing.h" +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsCalibration/MeanVertexObject.h" +#include "CCDB/CCDBTimeStampUtils.h" + +// ROOT +#include +#include +#include + +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::glo +{ + +class TrendGraph : public TGraph +{ + public: + TrendGraph(std::string name, std::string title, std::string label) + : TGraph(0), + mAxisLabel(label) + { + SetMarkerStyle(kCircle); + SetNameTitle(name.c_str(), title.c_str()); + } + + ~TrendGraph() override = default; + + void update(uint64_t time, float val) + { + AddPoint(time, val); + Draw("APL"); + GetYaxis()->SetTitle(mAxisLabel.c_str()); + GetXaxis()->SetTimeDisplay(1); + GetXaxis()->SetNdivisions(505); + GetXaxis()->SetTimeOffset(0.0); + GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + + private: + std::string mAxisLabel; + std::unique_ptr mGraph; +}; + +//_________________________________________________________________________________________ + +void MeanVertexPostProcessing::configure(const boost::property_tree::ptree& config) +{ + if (const auto& customConfigs = config.get_child_optional("qc.postprocessing." + getID() + ".customization"); customConfigs.has_value()) { + for (const auto& customConfig : customConfigs.value()) { + if (const auto& customNames = customConfig.second.get_child_optional("CcdbURL"); customNames.has_value()) { + ILOG(Info, Support) << "MeanVertexCalib post-processing: getting customized CCDB url" << ENDM; + mCcdbUrl = customConfig.second.get("CcdbURL"); + } + } + } + ILOG(Info, Support) << "MeanVertexCalib post-processing: CCDB url will be set to: " << mCcdbUrl << ENDM; + mCcdbApi.init(mCcdbUrl); + + // create the graphs + mGraphX = std::make_shared("MeanVtxXTrending", "Mean Vertex X", "cm"); + mGraphY = std::make_shared("MeanVtxYTrending", "Mean Vertex Y", "cm"); + mGraphZ = std::make_shared("MeanVtxZTrending", "Mean Vertex Z", "cm"); + + mGraphSigmaX = std::make_shared("MeanVtxSigmaXTrending", "Mean Vertex #sigma_{X}", "cm"); + mGraphSigmaY = std::make_shared("MeanVtxSigmaYTrending", "Mean Vertex #sigma_{Y}", "cm"); + mGraphSigmaZ = std::make_shared("MeanVtxSigmaZTrending", "Mean Vertex #sigma_{Z}", "cm"); +} + +void MeanVertexPostProcessing::initialize(Trigger, framework::ServiceRegistryRef) +{ + // publish the graphs + getObjectsManager()->startPublishing(mGraphX.get(), PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mGraphY.get(), PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mGraphZ.get(), PublicationPolicy::ThroughStop); + + getObjectsManager()->startPublishing(mGraphSigmaX.get(), PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mGraphSigmaY.get(), PublicationPolicy::ThroughStop); + getObjectsManager()->startPublishing(mGraphSigmaZ.get(), PublicationPolicy::ThroughStop); + + ILOG(Info, Support) << "MeanVertexCalib post-processing: Initialization done"; +} + +void MeanVertexPostProcessing::update(Trigger t, framework::ServiceRegistryRef) +{ + std::map md; + auto* meanVtx = mCcdbApi.retrieveFromTFileAny("GLO/Calib/MeanVertex", md, t.timestamp); + if (!meanVtx) { + ILOG(Info, Support) << "MeanVertexCalib post-processing: null object received for " << t.timestamp << ENDM; + return; + } + + // get values + auto x = meanVtx->getX(); + auto y = meanVtx->getY(); + auto z = meanVtx->getZ(); + auto sx = meanVtx->getSigmaX(); + auto sy = meanVtx->getSigmaY(); + auto sz = meanVtx->getSigmaZ(); + + // get time stamp + std::map headers; + headers = mCcdbApi.retrieveHeaders("GLO/Calib/MeanVertex", md, t.timestamp); + const auto validFrom = headers.find("Valid-From"); + long startVal = std::stol(validFrom->second); + ILOG(Debug, Support) << "MeanVertexCalib post-processing: startValidity = " << startVal << " X = " << x << " Y = " << y << " Z = " << z << ENDM; + + // ROOT expects time in seconds + startVal /= 1000; + + mGraphX->update(startVal, x); + mGraphY->update(startVal, y); + mGraphZ->update(startVal, z); + mGraphSigmaX->update(startVal, sx); + mGraphSigmaY->update(startVal, sy); + mGraphSigmaZ->update(startVal, sz); +} + +void MeanVertexPostProcessing::finalize(Trigger, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::glo diff --git a/Modules/GLO/src/MeanVertexValidator.cxx b/Modules/GLO/src/MeanVertexValidator.cxx new file mode 100644 index 0000000000..9278e197f1 --- /dev/null +++ b/Modules/GLO/src/MeanVertexValidator.cxx @@ -0,0 +1,65 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MeanVertexValidator.cxx +/// \author Andrea Ferrero +/// + +#include "GLO/MeanVertexValidator.h" +#include "DataFormatsCalibration/MeanVertexObject.h" + +#include + +#include + +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::glo +{ + +const std::type_info& MeanVertexValidator::getTinfo() const +{ + return typeid(o2::dataformats::MeanVertexObject); +} + +bool MeanVertexValidator::validate(void* obj) +{ + if (!obj) { + return false; + } + auto meanVtx = static_cast(obj); + auto x = meanVtx->getX(); + auto y = meanVtx->getY(); + auto z = meanVtx->getZ(); + auto sx = meanVtx->getSigmaX(); + auto sy = meanVtx->getSigmaY(); + auto sz = meanVtx->getSigmaZ(); + + // do some sanity check using conservative limits for the vertex parameters + // X-Y: 1 cm + // Z: 10 cm + // sX-sY: 1 cm + // sZ: 100 cm + // aigmas not negative + if (std::fabs(x) > 1 || std::fabs(y) > 1 || std::fabs(z) > 10) { + return false; + } + if (sx < 0 || sy < 0 || sz < 0) { + return false; + } + if (sx > 1 || sy > 1 || sz > 100) { + return false; + } + return true; +} + +} // namespace o2::quality_control_modules::glo diff --git a/Modules/GLO/src/Reductors.cxx b/Modules/GLO/src/Reductors.cxx new file mode 100644 index 0000000000..9d85cae250 --- /dev/null +++ b/Modules/GLO/src/Reductors.cxx @@ -0,0 +1,83 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "Common/Utils.h" + +#include "GLO/Reductors.h" +#include "GLO/Helpers.h" + +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::glo +{ + +void K0sFitReductor::update(TObject* obj) +{ + auto f = dynamic_cast(obj); + if (!f) { + return; + } + + mStats.mean = (Float_t)f->GetParameter(helpers::K0sFitter::Parameters::Mass); + mStats.sigma = (Float_t)f->GetParameter(helpers::K0sFitter::Parameters::Sigma); + if (auto ncol = (Float_t)f->GetParameter(helpers::K0sFitter::Parameters::Pol0); ncol > 0.) { + mStats.yield = (Float_t)f->GetParameter(helpers::K0sFitter::Parameters::Amplitude) * mStats.sigma / ncol; + } else { + mStats.yield = -1; + } +} + +const char* MTCReductor::getBranchLeafList() +{ + mPt = common::internal::stringToType(mCustomParameters.atOrDefaultValue("pt", "0")); + + return "mtc/F"; +}; + +void MTCReductor::update(TObject* obj) +{ + auto h = dynamic_cast(obj); + if (!h) { + return; + } + + mStats.mtc = (float)h->GetBinContent(h->FindBin(mPt)); +} + +const char* PVITSReductor::getBranchLeafList() +{ + mR0 = common::internal::stringToType(mCustomParameters.atOrDefaultValue("r0", "0")); + mR1 = common::internal::stringToType(mCustomParameters.atOrDefaultValue("r1", "0")); + + return "pol0/F:pol1/F"; +}; + +void PVITSReductor::update(TObject* obj) +{ + auto p = dynamic_cast(obj); + if (!p) { + return; + } + + auto res = p->Fit("pol1", "QSNC", "", mR0, mR1); + if ((Int_t)res != 0) { + return; + } + + mStats.pol0 = (Float_t)res->Parameter(0); + mStats.pol1 = (Float_t)res->Parameter(1); +} + +} // namespace o2::quality_control_modules::glo diff --git a/Modules/GLO/src/VertexingQcTask.cxx b/Modules/GLO/src/VertexingQcTask.cxx new file mode 100644 index 0000000000..bfc1fcc353 --- /dev/null +++ b/Modules/GLO/src/VertexingQcTask.cxx @@ -0,0 +1,291 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file VertexingQcTask.cxx +/// \author Chiara Zampolli +/// + +#include +#include +#include +#include +#include +#include + +#include "ReconstructionDataFormats/PrimaryVertex.h" + +#include "QualityControl/QcInfoLogger.h" +#include "GLO/VertexingQcTask.h" +#include + +namespace o2::quality_control_modules::glo +{ + +VertexingQcTask::~VertexingQcTask() +{ + delete mX; + delete mY; + delete mZ; + delete mNContributors; + delete mTimeUncVsNContrib; + delete mBeamSpot; + if (mUseMC) { + delete mPurityVsMult; + delete mNPrimaryMCEvWithVtx; + delete mNPrimaryMCGen; + delete mRatioNPrimaryMCEvWithVtxvsNPrimaryMCGen; + delete mVtxEffVsMult; + delete mCloneFactorVsMult; + delete mVtxResXVsMult; + delete mVtxResYVsMult; + delete mVtxResZVsMult; + delete mVtxPullsXVsMult; + delete mVtxPullsYVsMult; + delete mVtxPullsZVsMult; + } +} + +void VertexingQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize VertexingQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + if (auto param = mCustomParameters.find("verbose"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - verbose (= verbose printouts): " << param->second << ENDM; + if (param->second == "true" || param->second == "True" || param->second == "TRUE") { + mVerbose = true; + } + } + + if (auto param = mCustomParameters.find("isMC"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - isMC: " << param->second << ENDM; + if (param->second == "true" || param->second == "True" || param->second == "TRUE") { + mUseMC = true; + mMCReader.initFromDigitContext("collisioncontext.root"); + mPurityVsMult = new TProfile("purityVsMult", "purityVsMult; MC primary mult; vtx purity", 10000, -0.5, 9999.5, 0.f, 1.f); + mNPrimaryMCEvWithVtx = new TH1F("NPrimaryMCEvWithVtx", "NPrimaryMCEvWithVtx; MC primary mult; n. events", 10000, -0.5, 9999.5); + mNPrimaryMCEvWithVtx->Sumw2(); + mNPrimaryMCGen = new TH1F("NPrimaryMCGen", "NPrimaryMCGen; MC primary mult; n. events with vtx", 10000, -0.5, 9999.5); + mNPrimaryMCGen->Sumw2(); + mRatioNPrimaryMCEvWithVtxvsNPrimaryMCGen = new TH1F("RatioNPrimaryMCEvWithVtxvsNPrimaryMCGen", "Ratio NPrimaryMCEvWithVtx vs. NPrimaryMCGen", 10000, -0.5, 9999.5); + mRatioNPrimaryMCEvWithVtxvsNPrimaryMCGen->Sumw2(); + mVtxEffVsMult = new TEfficiency("vtxEffVsMult", "vtxEffVsMult; MC primary mult; vtx reco efficiency", 10000, -0.5, 9999.5); + mCloneFactorVsMult = new TProfile("cloneFactorVsMult", "cloneFactorVsMult; MC primary mult; n. cloned vertices", 100, -0.5, 9999.5, 0.f, 1.f); + mVtxResXVsMult = new TProfile("vtxResXVsMult", "vtxRes (X) vs mult; n. contributors; res on X (cm)", 100, -0.5, 9999.5, 0.f, 100.f); + mVtxResYVsMult = new TProfile("vtxResYVsMult", "vtxRes (Y) vs mult; n. conrtibutors; res on Y (cm)", 100, -0.5, 9999.5, 0.f, 100.f); + mVtxResZVsMult = new TProfile("vtxResZVsMult", "vtxRes (Z) vs mult; n. contriobutors; res on Z (cm)", 100, -0.5, 9999.5, 0.f, 100.f); + mVtxPullsXVsMult = new TProfile("vtxPullsXVsMult", "vtxPulls (X) vs mult; MC primary mult; pulls for X", 100, -0.5, 9999.5, 0.f, 100.f); + mVtxPullsYVsMult = new TProfile("vtxPullsYVsMult", "vtxPulls (Y) vs mult; MC primary mult; pulls for Y", 100, -0.5, 9999.5, 0.f, 100.f); + mVtxPullsZVsMult = new TProfile("vtxPullsZVsMult", "vtxPulls (Z) vs mult; MC primary mult; pulls for Z", 100, -0.5, 9999.5, 0.f, 100.f); + getObjectsManager()->startPublishing(mPurityVsMult); + mNPrimaryMCEvWithVtx->SetOption("logy"); + getObjectsManager()->startPublishing(mNPrimaryMCEvWithVtx); + mNPrimaryMCGen->SetOption("logy"); + getObjectsManager()->startPublishing(mNPrimaryMCGen); + getObjectsManager()->startPublishing(mRatioNPrimaryMCEvWithVtxvsNPrimaryMCGen); + getObjectsManager()->startPublishing(mVtxEffVsMult); + getObjectsManager()->startPublishing(mCloneFactorVsMult); + getObjectsManager()->startPublishing(mVtxResXVsMult); + getObjectsManager()->startPublishing(mVtxResYVsMult); + getObjectsManager()->startPublishing(mVtxResZVsMult); + getObjectsManager()->startPublishing(mVtxPullsXVsMult); + getObjectsManager()->startPublishing(mVtxPullsYVsMult); + getObjectsManager()->startPublishing(mVtxPullsZVsMult); + + mPurityVsMult->GetYaxis()->SetTitleOffset(1.4); + mNPrimaryMCEvWithVtx->GetYaxis()->SetTitleOffset(1.4); + mNPrimaryMCGen->GetYaxis()->SetTitleOffset(1.4); + mRatioNPrimaryMCEvWithVtxvsNPrimaryMCGen->GetYaxis()->SetTitleOffset(1.4); + mCloneFactorVsMult->GetYaxis()->SetTitleOffset(1.4); + mVtxResXVsMult->GetYaxis()->SetTitleOffset(1.4); + mVtxResYVsMult->GetYaxis()->SetTitleOffset(1.4); + mVtxResZVsMult->GetYaxis()->SetTitleOffset(1.4); + mVtxPullsXVsMult->GetYaxis()->SetTitleOffset(1.4); + mVtxPullsYVsMult->GetYaxis()->SetTitleOffset(1.4); + mVtxPullsZVsMult->GetYaxis()->SetTitleOffset(1.4); + } + } + + mX = new TH1F("vertex_X", "vertex_X; vtx_X (cm); entries", 300, -0.3, 0.3); + fX = new TF1("fX", "gaus"); + mY = new TH1F("vertex_Y", "vertex_Y; vtx_Y (cm); entries", 300, -0.3, 0.3); + fY = new TF1("fY", "gaus"); + mZ = new TH1F("vertex_Z", "vertex_Z; vtx_Z (cm);entries", 1000, -20, 20); + mNContributors = new TH1F("vertex_NContributors", "vertex_NContributors; n. contributors; entries", 1000, -0.5, 999.5); + mTimeUncVsNContrib = new TProfile("timeUncVsNContrib", "timeUncVsNContrib; n. contributors; time uncertainty (us)", 100, -0.5, 999.5, 0.f, 10.f); + mBeamSpot = new TH2F("beamSpot", "beam spot; vtx_X (cm); vtx_Y (cm)", 300, -0.3, 0.3, 300, -0.3, 0.3); + + mX->SetOption("logy"); + getObjectsManager()->startPublishing(mX); + mY->SetOption("logy"); + getObjectsManager()->startPublishing(mY); + mZ->SetOption("logy"); + getObjectsManager()->startPublishing(mZ); + mNContributors->SetOption("logy"); + getObjectsManager()->startPublishing(mNContributors); + mTimeUncVsNContrib->SetOption("logy"); + getObjectsManager()->startPublishing(mTimeUncVsNContrib); + mBeamSpot->SetOption("colz"); + getObjectsManager()->startPublishing(mBeamSpot); + + mX->GetYaxis()->SetTitleOffset(1.4); + mY->GetYaxis()->SetTitleOffset(1.4); + mZ->GetYaxis()->SetTitleOffset(1.4); + mNContributors->GetYaxis()->SetTitleOffset(1.4); + mTimeUncVsNContrib->GetYaxis()->SetTitleOffset(1.4); + mBeamSpot->GetYaxis()->SetTitleOffset(1.4); +} + +void VertexingQcTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + reset(); +} + +void VertexingQcTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void VertexingQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + const auto pvertices = ctx.inputs().get>("pvtx"); + gsl::span mcLbl; + if (mUseMC) { + mcLbl = ctx.inputs().get>("pvtxLbl"); + for (const auto& lbl : mcLbl) { + if (lbl.getSourceID() != 0) { // using only underlying event, which is source 0 + continue; + } + ILOG(Debug, Support) << "From source " << lbl.getSourceID() << ", event " << lbl.getEventID() << " has a vertex" << ENDM; + mMapEvIDSourceID[{ lbl.getEventID(), lbl.getSourceID() }]++; + if (mMapEvIDSourceID[{ lbl.getEventID(), lbl.getSourceID() }] == 1) { // filling numerator for efficiency + auto header = mMCReader.getMCEventHeader(lbl.getSourceID(), lbl.getEventID()); + auto mult = header.GetNPrim(); + ILOG(Debug, Support) << "Found vertex for event with mult = " << mult << ENDM; + mNPrimaryMCEvWithVtx->Fill(mult); + // mRatioNPrimaryMCEvWithVtxvsNPrimaryMCGen = (TH1F *)mNPrimaryMCEvWithVtx->Clone(); //did not work + mRatioNPrimaryMCEvWithVtxvsNPrimaryMCGen->Fill(mult); + } + } + for (const auto& lbl : mcLbl) { + if (lbl.getSourceID() != 0) { // using only underlying event, which is source 0 + continue; + } + auto header = mMCReader.getMCEventHeader(lbl.getSourceID(), lbl.getEventID()); + auto mult = header.GetNPrim(); + auto nVertices = mMapEvIDSourceID[{ lbl.getEventID(), lbl.getSourceID() }]; + if (nVertices == 1) { + ILOG(Debug, Support) << "Found " << nVertices << " vertex for event with mult = " << mult << ENDM; + } else { + ILOG(Debug, Support) << "Found " << nVertices << " vertices for event with mult = " << mult << ENDM; + } + mCloneFactorVsMult->Fill(mult, nVertices); + } + + for (size_t i = 0; i < mMCReader.getNEvents(0); ++i) { // we use the underlying event, which is source 0 + auto header = mMCReader.getMCEventHeader(0, i); // we use the underlying event, which is source 0 + auto mult = header.GetNPrim(); + ILOG(Debug, Support) << "Found Gen event with mult = " << mult << ENDM; + mNPrimaryMCGen->Fill(mult); + } + mRatioNPrimaryMCEvWithVtxvsNPrimaryMCGen->Divide(mNPrimaryMCGen); + } + + for (uint64_t i = 0; i < pvertices.size(); ++i) { + auto x = pvertices[i].getX(); + auto y = pvertices[i].getY(); + auto z = pvertices[i].getZ(); + auto nContr = pvertices[i].getNContributors(); + auto timeUnc = pvertices[i].getTimeStamp().getTimeStampError(); + ILOG(Debug, Support) << "x = " << x << ", y = " << y << ", z = " << z << ", nContributors = " << nContr << ", timeUnc = " << timeUnc << ENDM; + mX->Fill(x); + mY->Fill(y); + mZ->Fill(z); + mNContributors->Fill(nContr); + mTimeUncVsNContrib->Fill(nContr, timeUnc); + mBeamSpot->Fill(x, y); + + if (mUseMC && mcLbl[i].isSet()) { // make sure the label was set + auto header = mMCReader.getMCEventHeader(mcLbl[i].getSourceID(), mcLbl[i].getEventID()); + auto purity = mcLbl[i].getCorrWeight(); + auto mult = header.GetNPrim(); + ILOG(Debug, Support) << "purity = " << purity << ", mult = " << mult << ENDM; + mPurityVsMult->Fill(mult, purity); + TVector3 vtMC; + header.GetVertex(vtMC); + mVtxResXVsMult->Fill(mult, vtMC[0] - pvertices[i].getX()); + mVtxResYVsMult->Fill(mult, vtMC[1] - pvertices[i].getY()); + mVtxResZVsMult->Fill(mult, vtMC[2] - pvertices[i].getZ()); + mVtxPullsXVsMult->Fill(mult, (vtMC[0] - pvertices[i].getX()) / std::sqrt(pvertices[i].getSigmaX2())); + mVtxPullsYVsMult->Fill(mult, (vtMC[1] - pvertices[i].getY()) / std::sqrt(pvertices[i].getSigmaY2())); + mVtxPullsZVsMult->Fill(mult, (vtMC[2] - pvertices[i].getZ()) / std::sqrt(pvertices[i].getSigmaZ2())); + } + } + mX->Fit("fX", "Q", "", mX->GetMean() - mX->GetRMS(), mX->GetMean() + mX->GetRMS()); + mY->Fit("fY", "Q", "", mY->GetMean() - mY->GetRMS(), mY->GetMean() + mY->GetRMS()); +} + +void VertexingQcTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + + if (mUseMC) { + + if (!mVtxEffVsMult->SetTotalHistogram(*mNPrimaryMCGen, "f") || + !mVtxEffVsMult->SetPassedHistogram(*mNPrimaryMCEvWithVtx, "")) { + ILOG(Fatal, Support) << "Something went wrong in defining the efficiency histograms!!"; + } else { + if (mVerbose) { + for (int ibin = 0; ibin < mNPrimaryMCEvWithVtx->GetNbinsX(); ibin++) { + if (mNPrimaryMCEvWithVtx->GetBinContent(ibin + 1) != 0 && mNPrimaryMCGen->GetBinContent(ibin + 1) != 0) { + ILOG(Info, Support) << "ibin = " << ibin + 1 << ", mNPrimaryMCEvWithVtx->GetBinContent(ibin + 1) = " << mNPrimaryMCEvWithVtx->GetBinContent(ibin + 1) << ", mNPrimaryMCGen->GetBinContent(ibin + 1) = " << mNPrimaryMCGen->GetBinContent(ibin + 1) << ", efficiency = " << mVtxEffVsMult->GetEfficiency(ibin + 1) << ENDM; + ILOG(Info, Support) << "ibin = " << ibin + 1 << ", mNPrimaryMCEvWithVtx->GetBinError(ibin + 1) = " << mNPrimaryMCEvWithVtx->GetBinError(ibin + 1) << ", mNPrimaryMCGen->GetBinError(ibin + 1) = " << mNPrimaryMCGen->GetBinError(ibin + 1) << ", efficiency error low = " << mVtxEffVsMult->GetEfficiencyErrorLow(ibin + 1) << ", efficiency error up = " << mVtxEffVsMult->GetEfficiencyErrorUp(ibin + 1) << ENDM; + } + } + ILOG(Info, Support) << "mNPrimaryMCEvWithVtx entries = " << mNPrimaryMCEvWithVtx->GetEntries() << ", mNPrimaryMCGen entries = " << mNPrimaryMCGen->GetEntries() << ENDM; + } + } + } +} + +void VertexingQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void VertexingQcTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mX->Reset(); + mY->Reset(); + mZ->Reset(); + mNContributors->Reset(); + mBeamSpot->Reset(); + if (mUseMC) { + mPurityVsMult->Reset(); + mNPrimaryMCEvWithVtx->Reset(); + mNPrimaryMCGen->Reset(); + mRatioNPrimaryMCEvWithVtxvsNPrimaryMCGen->Reset(); + mCloneFactorVsMult->Reset(); + mVtxResXVsMult->Reset(); + mVtxResYVsMult->Reset(); + mVtxResZVsMult->Reset(); + mVtxPullsXVsMult->Reset(); + mVtxPullsYVsMult->Reset(); + mVtxPullsZVsMult->Reset(); + } +} + +} // namespace o2::quality_control_modules::glo diff --git a/Modules/GLO/test/testQcGLO.cxx b/Modules/GLO/test/testQcGLO.cxx new file mode 100644 index 0000000000..1352368115 --- /dev/null +++ b/Modules/GLO/test/testQcGLO.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testQcGLO.cxx +/// \author My Name +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::glo +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::glo diff --git a/Modules/GLO/vertexing-qc-direct-mc.json b/Modules/GLO/vertexing-qc-direct-mc.json new file mode 100644 index 0000000000..d76ee5e468 --- /dev/null +++ b/Modules/GLO/vertexing-qc-direct-mc.json @@ -0,0 +1,69 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "1", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "Vertexing_MC" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::VertexingQcTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "10", + "maxNumberCycles" : "-1", + "" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "direct", + "query_comment" : "checking every vertex, in MC", + "query" : "pvtx:GLO/PVTX/0;pvtxLbl:GLO/PVTX_MCTR/0" + }, + "taskParameters" : { + "isMC" : "true" + }, + "location" : "remote", + "saveObjectsToFile" : "testVertexingQC_MC.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "checks" : { + "QcCheck" : { + "active" : "false", + "className" : "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName" : "QcSkeleton", + "policy" : "OnAny", + "detectorName" : "GLO", + "dataSource" : [ { + "type" : "Task", + "name" : "Vertexing_MC", + "MOs" : ["example"] + } ] + } + } + }, + "dataSamplingPolicies" : [ + ] +} diff --git a/Modules/GLO/vertexing-qc-direct.json b/Modules/GLO/vertexing-qc-direct.json new file mode 100644 index 0000000000..a94ab2b17f --- /dev/null +++ b/Modules/GLO/vertexing-qc-direct.json @@ -0,0 +1,69 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "1", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "Vertexing" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::VertexingQcTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "10", + "maxNumberCycles" : "-1", + "" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "direct", + "query_comment" : "checking every vertex", + "query" : "pvtx:GLO/PVTX/0" + }, + "taskParameters" : { + "isMC" : "false" + }, + "location" : "remote", + "saveObjectsToFile" : "testVertexingQC.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "checks" : { + "QcCheck" : { + "active" : "false", + "className" : "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName" : "QcSkeleton", + "policy" : "OnAny", + "detectorName" : "GLO", + "dataSource" : [ { + "type" : "Task", + "name" : "Vertexing", + "MOs" : ["example"] + } ] + } + } + }, + "dataSamplingPolicies" : [ + ] +} diff --git a/Modules/GLO/vertexing-qc-mc.json b/Modules/GLO/vertexing-qc-mc.json new file mode 100644 index 0000000000..9d9ee3eaca --- /dev/null +++ b/Modules/GLO/vertexing-qc-mc.json @@ -0,0 +1,83 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "1", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "Vertexing_MC" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::VertexingQcTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "10", + "maxNumberCycles" : "-1", + "" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "vertexing-mc-datasampling" + }, + "taskParameters" : { + "isMC" : "true" + }, + "location" : "remote", + "saveObjectsToFile" : "testVertexingQC_MC.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "checks" : { + "QcCheck" : { + "active" : "false", + "className" : "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName" : "QcSkeleton", + "policy" : "OnAny", + "detectorName" : "GLO", + "dataSource" : [ { + "type" : "Task", + "name" : "Vertexing_MC", + "MOs" : ["example"] + } ] + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "vertexing-mc-datasampling", + "active" : "true", + "machines" : [], + "query_comment" : "checking every vertex, in MC", + "query" : "pvtx:GLO/PVTX/0;pvtxLbl:GLO/PVTX_MCTR/0" + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.1", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/GLO/vertexing-qc.json b/Modules/GLO/vertexing-qc.json new file mode 100644 index 0000000000..584eed0e3b --- /dev/null +++ b/Modules/GLO/vertexing-qc.json @@ -0,0 +1,82 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "1", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "Vertexing" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::VertexingQcTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "10", + "maxNumberCycles" : "-1", + "" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "tst-raw" + }, + "taskParameters" : { + "myOwnKey" : "myOwnValue" + }, + "location" : "remote", + "saveObjectsToFile" : "testVertexingQC.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "checks" : { + "QcCheck" : { + "active" : "false", + "className" : "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName" : "QcSkeleton", + "policy" : "OnAny", + "detectorName" : "GLO", + "dataSource" : [ { + "type" : "Task", + "name" : "Vertexing", + "MOs" : ["example"] + } ] + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "tst-raw", + "active" : "true", + "machines" : [], + "query" : "pvtx:GLO/PVTX/0", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.1", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/GLO/vertexing-qc_mnl.json b/Modules/GLO/vertexing-qc_mnl.json new file mode 100644 index 0000000000..679ecb26b5 --- /dev/null +++ b/Modules/GLO/vertexing-qc_mnl.json @@ -0,0 +1,72 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "Vertexing" : { + "active" : "true", + "className" : "o2::quality_control_modules::glo::VertexingQcTask", + "moduleName" : "QcGLO", + "detectorName" : "GLO", + "cycleDurationSeconds" : "60", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "VtxSampling" + }, + "taskParameters" : { + "isMC": "false" + }, + "location" : "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "localhost", + "remotePort": "46000", + "localControl": "odc", + "saveObjectsToFile" : "testVertexingQC.root" + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "VtxSampling", + "active" : "true", + "machines" : [], + "query" : "pvtx:GLO/PVTX/0", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.1", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/HMPID/CMakeLists.txt b/Modules/HMPID/CMakeLists.txt new file mode 100644 index 0000000000..af5c59eada --- /dev/null +++ b/Modules/HMPID/CMakeLists.txt @@ -0,0 +1,64 @@ +# ---- Library ---- + +add_library(O2QcHMPID) + +target_sources(O2QcHMPID PRIVATE src/HmpidTask.cxx + src/HmpidTaskDigits.cxx + src/HmpidTaskClusters.cxx + src/HmpidRawChecks.cxx + src/Helpers.cxx + src/HmpidTaskMatches.cxx + ) + +target_include_directories( + O2QcHMPID + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcHMPID PUBLIC O2QualityControl O2::HMPIDReconstruction + O2::DataFormatsHMP + O2::DataFormatsGlobalTracking + O2::ReconstructionDataFormats) + +get_target_property(O2_INCLUDE_DIRS O2::CommonDataFormat INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(ROOT_INCLUDE_DIRS ROOT::Core INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(HMPID_INCLUDE_DIRS O2::HMPIDReconstruction INTERFACE_INCLUDE_DIRECTORIES) + +set(CMAKE_REQUIRED_INCLUDES ${O2_INCLUDE_DIRS} ${ROOT_INCLUDE_DIRS} ${HMPID_INCLUDE_DIRS}) + +install(TARGETS O2QcHMPID + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcHMPID + HEADERS + include/HMPID/HmpidTask.h + include/HMPID/HmpidTaskDigits.h + include/HMPID/HmpidTaskClusters.h + include/HMPID/HmpidRawChecks.h + include/HMPID/Helpers.h + include/HMPID/HmpidTaskMatches.h + LINKDEF include/HMPID/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/HMPID + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcHMPID.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcHMPID Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + diff --git a/Modules/HMPID/include/HMPID/Helpers.h b/Modules/HMPID/include/HMPID/Helpers.h new file mode 100644 index 0000000000..1899a4d362 --- /dev/null +++ b/Modules/HMPID/include/HMPID/Helpers.h @@ -0,0 +1,75 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Helpers.h +/// \author Nicola Nicassio +/// + +#ifndef QC_MODULE_HMPID_HELPERS_H +#define QC_MODULE_HMPID_HELPERS_H + +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/Quality.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::hmpid +{ + +constexpr int getNumDDL() { return 14; } + +constexpr int getNumHV() { return 42; } + +bool matchHistName(std::string hist, std::string name); + +//_________________________________________________________________________________________ + +// check plots vs DDL +struct QualityCheckerDDL { + QualityCheckerDDL(); + + void resetDDL(); + void addCheckResultDDL(gsl::span result); + o2::quality_control::core::Quality getQualityDDL(); + std::array mQualityDDL; + int mMaxBadDDLForMedium; + int mMaxBadDDLForBad; +}; + +//_________________________________________________________________________________________ + +// check plots vs HV +struct QualityCheckerHV { + QualityCheckerHV(); + + void resetHV(); + void addCheckResultHV(gsl::span result); + o2::quality_control::core::Quality getQualityHV(); + std::array mQualityHV; + int mMaxBadHVForMedium; + int mMaxBadHVForBad; +}; + +//_________________________________________________________________________________________ + +} // namespace o2::quality_control_modules::hmpid + +#endif // QC_MODULE_HMPID_HELPERS_H \ No newline at end of file diff --git a/Modules/HMPID/include/HMPID/HmpidRawChecks.h b/Modules/HMPID/include/HMPID/HmpidRawChecks.h new file mode 100644 index 0000000000..7883662c18 --- /dev/null +++ b/Modules/HMPID/include/HMPID/HmpidRawChecks.h @@ -0,0 +1,103 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HmpidRawChecks.h +/// \author Nicola Nicassio +/// + +#ifndef QC_MODULE_HMPID_RAWCHECK_H +#define QC_MODULE_HMPID_RAWCHECK_H + +#include "HMPID/Helpers.h" +#include "QualityControl/CheckInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include +#include +#include +#include +#include +#include + +// using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::hmpid +{ + +/// \brief Check if the occupancy on each equipment is between the two specified values +/// +/// \author Nicola Nicassio +class HmpidRawChecks : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + HmpidRawChecks() = default; + /// Destructor + ~HmpidRawChecks() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + std::array check_hHmpBigMap(TProfile2D* h); // <-- Dimensione da chiarire + std::array check_hHmpHvSectorQ(TH2F* h); + std::array check_hHmpPadOccPrf(TProfile* h); + std::array check_hBusyTime(TProfile* h); + std::array check_hEventSize(TProfile* h); + + std::string m_hHmpBigMap_HistName{ "hHmpBigMap_profile" }; + std::string m_hHmpHvSectorQ_HistName{ "hHmpHvSectorQ" }; + std::string m_hHmpPadOccPrf_HistName{ "hHmpPadOccPrf" }; + std::string m_hBusyTime_HistName{ "hBusyTime" }; + std::string m_hEventSize_HistName{ "hEventSize" }; + std::string m_hCheckHV_HistName{ "hCheckHV" }; + + // Variables for min/max plot + + double mMinOccupancy{ 0. }; + double mMaxOccupancy{ 10 }; + + double mMinEventSize{ 0.2 }; + double mMaxEventSize{ 0.9 }; + + double mMinBusyTime{ 0.2 }; + double mMaxBusyTime{ 0.9 }; + + double mMinHVTotalEntriesToCheckQuality{ 1000 }; + double mFractionXBinsHVSingleModuleEntriesToLabelGoodBadQuality{ 0.005 }; + + // Checker identifiers + + double mMaxBadDDLForMedium{ 1 }; + double mMaxBadDDLForBad{ 3 }; + + double mMaxBadHVForMedium{ 7 }; + double mMaxBadHVForBad{ 10 }; + + std::vector mErrorMessages; + std::vector mErrorMessagesColor; + + Quality mQualityOccupancy; + Quality mQualityBusyTime; + Quality mQualityEventSize; + Quality mQualityBigMap; + Quality mQualityHvSectorQ; + std::array qualityHvSectorQ; + + ClassDefOverride(HmpidRawChecks, 1); +}; + +} // namespace o2::quality_control_modules::hmpid +//} // namespace o2::quality_control::core +#endif // QC_MODULE_HMPID_RAWCHECK_H diff --git a/Modules/HMPID/include/HMPID/HmpidTask.h b/Modules/HMPID/include/HMPID/HmpidTask.h new file mode 100644 index 0000000000..a9b13aff4c --- /dev/null +++ b/Modules/HMPID/include/HMPID/HmpidTask.h @@ -0,0 +1,77 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HmpidTask.h +/// \author Antonio Franco, Giacomo Volpe, Antonio Paz +/// \brief Class for quality control of HMPID detectors +/// \version 0.2.4 +/// \date 19/05/2022 +/// + +#ifndef QC_MODULE_HMPID_HMPIDHMPIDTASK_H +#define QC_MODULE_HMPID_HMPIDHMPIDTASK_H + +#include "QualityControl/TaskInterface.h" +#include "HMPIDReconstruction/HmpidDecoder2.h" + +class TH1F; +class TH2F; +class TProfile; +class TProfile2D; +class TCanvas; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::hmpid +{ + +/// \brief Example Quality Control DPL Task +/// \author My Name +class HmpidTask final : public TaskInterface +{ + public: + /// \brief Constructor + HmpidTask() = default; + /// Destructor + ~HmpidTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + static const Int_t numCham = 7; + TH1F* hPedestalMean = nullptr; + TH1F* hPedestalSigma = nullptr; + TProfile* hBusyTime = nullptr; + TProfile* hEventSize = nullptr; + TProfile* hEventNumber = nullptr; + TH2F* hModuleMap[numCham] = { nullptr }; + o2::hmpid::HmpidDecoder2* mDecoder = nullptr; + // TH2F *hHmpBigMap = nullptr; + TH2F* hHmpHvSectorQ = nullptr; + TProfile2D* hHmpBigMap_profile = nullptr; + // TProfile2D *hHmpHvSectorQ_profile = nullptr; + // TH2F *hHmpHvSectorQ_profile_temp = nullptr; + TProfile* hHmpPadOccPrf = nullptr; + TCanvas* CheckerMessages; + TH2F* hCheckHV; +}; + +} // namespace o2::quality_control_modules::hmpid + +#endif // QC_MODULE_HMPID_HMPIDHMPIDTASK_H diff --git a/Modules/HMPID/include/HMPID/HmpidTaskClusters.h b/Modules/HMPID/include/HMPID/HmpidTaskClusters.h new file mode 100644 index 0000000000..44948001da --- /dev/null +++ b/Modules/HMPID/include/HMPID/HmpidTaskClusters.h @@ -0,0 +1,66 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file HmpidTaskClusters.h +/// \author Annalisa Mastroserio, Giacomo Volpe +/// + +#ifndef QC_MODULE_HMPID_HMPIDTASKCLUSTERS_H +#define QC_MODULE_HMPID_HMPIDTASKCLUSTERS_H + +#include +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include + +class TH1F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::hmpid +{ + +class HmpidTaskClusters : public TaskInterface +{ + + public: + HmpidTaskClusters(); + ~HmpidTaskClusters() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void BookHistograms(); + void getJsonParameters(); + + // monitoring histos + TProfile* ThClusMult; + TH1F* hClusMultEv; + TH1F* hHMPIDchargeClus[7]; + TH1F* hHMPIDchargeMipClus[7]; + TH1F* hHMPIDclusX[7]; + TH1F* hHMPIDclusY[7]; + TH2F* hHMPIDpositionClus[7]; + + std::vector mPublishedObjects; +}; +} // namespace o2::quality_control_modules::hmpid + +#endif diff --git a/Modules/HMPID/include/HMPID/HmpidTaskDigits.h b/Modules/HMPID/include/HMPID/HmpidTaskDigits.h new file mode 100644 index 0000000000..5b2f2f88da --- /dev/null +++ b/Modules/HMPID/include/HMPID/HmpidTaskDigits.h @@ -0,0 +1,65 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HmpidTaskDigits.h +/// \author Antonio Paz, Giacomo Volpe +/// \brief Class to map data from HMPID detectors +/// \version 0.3.1 +/// \date 26/10/2021 +/// + +#ifndef QC_MODULE_HMPID_HMPIDHMPIDTASKDIGITS_H +#define QC_MODULE_HMPID_HMPIDHMPIDTASKDIGITS_H + +#include "QualityControl/TaskInterface.h" + +class TH1F; +class TH2F; +class TProfile; +// class TProfile2D; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::hmpid +{ + +class HmpidTaskDigits final : public TaskInterface +{ + public: + /// \brief Constructor + HmpidTaskDigits() = default; + /// Destructor + ~HmpidTaskDigits() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + static const Int_t numCham = 7; + static const Int_t numEquip = 14; + const Double_t factor = 11520.; //! ap 11520= 24*10*48 + + TH1F* hHMPIDchargeDist[numCham]; // histogram of charges per chamber + TH2F* hHMPIDdigitmapAvg[numCham]; // Map of hits per coordinate + + // TH1F* hOccupancyI; // histogram of occupancy per data link + TProfile* hOccupancyAvg; // average occupancy per data link +}; + +} // namespace o2::quality_control_modules::hmpid + +#endif // QC_MODULE_HMPID_HMPIDHMPIDTASK_H diff --git a/Modules/HMPID/include/HMPID/HmpidTaskMatches.h b/Modules/HMPID/include/HMPID/HmpidTaskMatches.h new file mode 100644 index 0000000000..bbb51c06c3 --- /dev/null +++ b/Modules/HMPID/include/HMPID/HmpidTaskMatches.h @@ -0,0 +1,75 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file HmpidTaskMatches.h +/// \author Nicola Nicassio, Giacomo Volpe +/// + +#ifndef QC_MODULE_HMPID_HMPIDTASKMATCHES_H +#define QC_MODULE_HMPID_HMPIDTASKMATCHES_H + +#include +#include "QualityControl/TaskInterface.h" + +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include "ReconstructionDataFormats/MatchInfoHMP.h" +#include "SimulationDataFormat/MCCompLabel.h" + +class TH1F; +class TH2F; + +using namespace o2::quality_control::core; +using GID = o2::dataformats::GlobalTrackID; + +namespace o2::quality_control_modules::hmpid +{ + +class HmpidTaskMatches : public TaskInterface +{ + + public: + HmpidTaskMatches(); + ~HmpidTaskMatches() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void BookHistograms(); + + std::shared_ptr mDataRequest; + o2::globaltracking::RecoContainer mRecoCont; + GID::mask_t mSrc = GID::getSourcesMask("HMP"); + // HMPID Maching Info + gsl::span mHMPMatches; + + TH1F* mMatchInfoResidualsXTrackMIP[7]; + TH1F* mMatchInfoResidualsYTrackMIP[7]; + TH1F* mMatchInfoChargeClusterMIP[7]; + TH1F* mMatchInfoChargeClusterPhotons[7]; + TH1F* mMatchInfoXMip[7]; + TH1F* mMatchInfoYMip[7]; + TH1F* mMatchInfoXTrack[7]; + TH1F* mMatchInfoYTrack[7]; + TH2F* mMatchInfoClusterMIPMap[7]; + TH2F* mMatchInfoThetaCherenkovVsMom[7]; + + std::vector mPublishedObjects; +}; +} // namespace o2::quality_control_modules::hmpid + +#endif diff --git a/Modules/HMPID/include/HMPID/LinkDef.h b/Modules/HMPID/include/HMPID/LinkDef.h new file mode 100644 index 0000000000..365f45a97d --- /dev/null +++ b/Modules/HMPID/include/HMPID/LinkDef.h @@ -0,0 +1,12 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::hmpid::HmpidTask+; +#pragma link C++ class o2::quality_control_modules::hmpid::HmpidTaskDigits+; +#pragma link C++ class o2::quality_control_modules::hmpid::HmpidTaskClusters+; +#pragma link C++ class o2::quality_control_modules::hmpid::HmpidTaskMatches+; +//#pragma link C++ class o2::quality_control_modules::hmpid::Helpers+; +#pragma link C++ class o2::quality_control_modules::hmpid::HmpidRawChecks+; +#endif diff --git a/Modules/HMPID/src/Helpers.cxx b/Modules/HMPID/src/Helpers.cxx new file mode 100644 index 0000000000..7dbf830165 --- /dev/null +++ b/Modules/HMPID/src/Helpers.cxx @@ -0,0 +1,163 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Helpers.cxx +/// \author Nicola Nicassio +/// + +#include "HMPID/Helpers.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QualityObject.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::hmpid +{ + +//_________________________________________________________________________________________ + +bool matchHistName(std::string hist, std::string name) +{ + if (name.empty()) { + return false; + } + + int64_t pos = hist.find(name); + int64_t histSize = hist.size(); + int64_t nameSize = name.size(); + int64_t diff = histSize - nameSize; + return ((pos >= 0) && (pos == diff)); +} + +//_________________________________________________________________________________________ + +QualityCheckerDDL::QualityCheckerDDL() +{ + resetDDL(); +} + +void QualityCheckerDDL::resetDDL() +{ + std::fill(mQualityDDL.begin(), mQualityDDL.end(), Quality::Null); +} + +void QualityCheckerDDL::addCheckResultDDL(gsl::span result) +{ + if (mQualityDDL.size() != result.size()) // <-- Original was < + { + return; + } + + for (int i = 0; i < mQualityDDL.size(); i++) { + if ((mQualityDDL[i] == Quality::Null) || (result[i] == Quality::Bad)) { + mQualityDDL[i] = result[i]; + } + } +} + +Quality QualityCheckerDDL::getQualityDDL() +{ + Quality result = Quality::Null; + + // It at least one entry is not Null, initialize to Good + for (int i = 0; i < mQualityDDL.size(); i++) { + if (mQualityDDL[i] != Quality::Null) { + result = Quality::Good; + } + } + // Count bad links + int bad_ddl_counter = 0; + for (int i = 0; i < mQualityDDL.size(); i++) { + if (mQualityDDL[i] == Quality::Bad) { + bad_ddl_counter++; + } + } + // Find quality + if (result == Quality::Good) { + if (bad_ddl_counter >= mMaxBadDDLForMedium) { + result = Quality::Medium; + if (bad_ddl_counter >= mMaxBadDDLForBad) { + result = Quality::Bad; + } + } + } + return result; +} + +//_________________________________________________________________________________________ + +QualityCheckerHV::QualityCheckerHV() +{ + resetHV(); +} + +void QualityCheckerHV::resetHV() +{ + std::fill(mQualityHV.begin(), mQualityHV.end(), Quality::Null); +} + +void QualityCheckerHV::addCheckResultHV(gsl::span result) +{ + if (mQualityHV.size() != result.size()) // <-- Original was < + { + return; + } + + for (int i = 0; i < mQualityHV.size(); i++) { + if ((mQualityHV[i] == Quality::Null) || (result[i] == Quality::Bad)) { + mQualityHV[i] = result[i]; + } + } +} + +Quality QualityCheckerHV::getQualityHV() +{ + Quality result = Quality::Null; + + // It at least one entry is not Null, initialize to Good + for (int i = 0; i < mQualityHV.size(); i++) { + if (mQualityHV[i] != Quality::Null) { + result = Quality::Good; + } + } + // Count bad links + int bad_hv_counter = 0; + for (int i = 0; i < mQualityHV.size(); i++) { + if (mQualityHV[i] == Quality::Bad) { + bad_hv_counter++; + } + } + // Find quality + if (result == Quality::Good) { + if (bad_hv_counter >= mMaxBadHVForMedium) { + result = Quality::Medium; + if (bad_hv_counter >= mMaxBadHVForBad) { + result = Quality::Bad; + } + } + } + return result; +} + +//_________________________________________________________________________________________ + +} // namespace o2::quality_control_modules::hmpid \ No newline at end of file diff --git a/Modules/HMPID/src/HmpidRawChecks.cxx b/Modules/HMPID/src/HmpidRawChecks.cxx new file mode 100644 index 0000000000..30a065322a --- /dev/null +++ b/Modules/HMPID/src/HmpidRawChecks.cxx @@ -0,0 +1,736 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HmpidRawChecks.cxx +/// \author Nicola Nicassio +/// + +#include "HMPID/HmpidRawChecks.h" + +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::hmpid +{ +void HmpidRawChecks::configure() +{ + + // Histo names + m_hHmpBigMap_HistName = mCustomParameters.atOrDefaultValue("m_hHmpBigMap_HistName"); + m_hHmpHvSectorQ_HistName = mCustomParameters.atOrDefaultValue("m_hHmpHvSectorQ_HistName"); + m_hHmpPadOccPrf_HistName = mCustomParameters.atOrDefaultValue("m_hHmpPadOccPrf_HistName"); + m_hBusyTime_HistName = mCustomParameters.atOrDefaultValue("m_hBusyTime_HistName"); + m_hEventSize_HistName = mCustomParameters.atOrDefaultValue("m_hEventSize_HistName"); + m_hCheckHV_HistName = mCustomParameters.atOrDefaultValue("m_hCheckHV_HistName"); + + // Histo limits + mMinOccupancy = std::stof(mCustomParameters.atOrDefaultValue("mMinOccupancy")); + mMaxOccupancy = std::stof(mCustomParameters.atOrDefaultValue("mMaxOccupancy")); + mMinEventSize = std::stof(mCustomParameters.atOrDefaultValue("mMinEventSize")); + mMaxEventSize = std::stof(mCustomParameters.atOrDefaultValue("mMaxEventSize")); + mMinBusyTime = std::stof(mCustomParameters.atOrDefaultValue("mMinBusyTime")); + mMaxBusyTime = std::stof(mCustomParameters.atOrDefaultValue("mMaxBusyTime")); + mMinHVTotalEntriesToCheckQuality = std::stof(mCustomParameters.atOrDefaultValue("mMinHVTotalEntriesToCheckQuality")); + mFractionXBinsHVSingleModuleEntriesToLabelGoodBadQuality = std::stof(mCustomParameters.atOrDefaultValue("mFractionXBinsHVSingleModuleEntriesToLabelGoodBadQuality")); + mMaxBadDDLForMedium = std::stof(mCustomParameters.atOrDefaultValue("mMaxBadDDLForMedium")); + mMaxBadDDLForBad = std::stof(mCustomParameters.atOrDefaultValue("mMaxBadDDLForBad")); + mMaxBadHVForMedium = std::stof(mCustomParameters.atOrDefaultValue("mMaxBadHVForMedium")); + mMaxBadHVForBad = std::stof(mCustomParameters.atOrDefaultValue("mMaxBadHVForBad")); +} + +template +std::array checkPlot(TProfile* h, Lambda check) +{ + std::array result; // size == number of links + std::fill(result.begin(), result.end(), Quality::Null); + for (int eqId = 0; eqId < 14; eqId++) // <-- Entries in histogram are already decoded in eqId + { + int bin = eqId + 1; + double val = h->GetBinContent(bin); + if (val == 0) { + result[eqId] = Quality::Null; + } else if (check(val)) { + result[eqId] = Quality::Good; + } else { + result[eqId] = Quality::Bad; + } + } + return result; +} + +std::array HmpidRawChecks::check_hHmpPadOccPrf(TProfile* h) +{ + return checkPlot(h, [&](double val) -> bool { return (val >= mMinOccupancy && val <= mMaxOccupancy); }); +} + +std::array HmpidRawChecks::check_hBusyTime(TProfile* h) +{ + return checkPlot(h, [&](double val) -> bool { return (val >= mMinBusyTime && val <= mMaxBusyTime); }); +} + +std::array HmpidRawChecks::check_hEventSize(TProfile* h) +{ + return checkPlot(h, [&](double val) -> bool { return (val >= mMinEventSize && val <= mMaxEventSize); }); +} + +template +std::array checkPlotHV(TH2F* h, Lambda check, double mMinHVTotalEntriesToCheckQuality) +{ + std::array result; // size == number of HV sectors + std::fill(result.begin(), result.end(), Quality::Null); + for (int HVId = 0; HVId < 42; HVId++) // <-- Entries in histogram are already decoded in eqId + { + int binY = HVId + 1; + double val = 0; + for (int binX = 1; binX <= h->GetNbinsX(); binX++) { + if (h->GetBinContent(binX, binY) != 0) { + val++; + } + } + if (h->GetEntries() < mMinHVTotalEntriesToCheckQuality) { + result[HVId] = Quality::Null; + } else if (check(val)) { + result[HVId] = Quality::Good; + } else { + result[HVId] = Quality::Bad; + } + } + return result; +} + +std::array HmpidRawChecks::check_hHmpHvSectorQ(TH2F* h) // <-- non 14 per 2D +{ + return checkPlotHV( + h, [&](double val) -> bool { return (val >= mFractionXBinsHVSingleModuleEntriesToLabelGoodBadQuality * h->GetNbinsX()); }, mMinHVTotalEntriesToCheckQuality); +} + +template +static T* getHisto(TCanvas* c, std::string hname) +{ + if (!c) { + return nullptr; + } + + T* h = dynamic_cast(c->GetPrimitive(hname.c_str())); + return h; +} + +template +static T* getHisto(std::shared_ptr mo) +{ + TObject* obj = dynamic_cast(mo->getObject()); + if (!obj) { + return nullptr; + } + + T* h{ nullptr }; + if (obj->InheritsFrom("TProfile") || obj->InheritsFrom("TH2F") || obj->InheritsFrom("TProfile2D")) { + h = dynamic_cast(obj); + } + + if (obj->InheritsFrom("TCanvas")) { + TCanvas* c = dynamic_cast(obj); + h = getHisto(c, mo->getName() + "Hist"); + } + + return h; +} + +Quality HmpidRawChecks::check(std::map>* moMap) +{ + + mQualityOccupancy = Quality::Null; + std::array qualityOccupancy; + QualityCheckerDDL qualityCheckerOccupancy; + qualityCheckerOccupancy.resetDDL(); + qualityCheckerOccupancy.mMaxBadDDLForMedium = mMaxBadDDLForMedium; + qualityCheckerOccupancy.mMaxBadDDLForBad = mMaxBadDDLForBad; + + mQualityBusyTime = Quality::Null; + std::array qualityBusyTime; + QualityCheckerDDL qualityCheckerBusyTime; + qualityCheckerBusyTime.resetDDL(); + qualityCheckerBusyTime.mMaxBadDDLForMedium = mMaxBadDDLForMedium; + qualityCheckerBusyTime.mMaxBadDDLForBad = mMaxBadDDLForBad; + + mQualityEventSize = Quality::Null; + std::array qualityEventSize; + QualityCheckerDDL qualityCheckerEventSize; + qualityCheckerEventSize.resetDDL(); + qualityCheckerEventSize.mMaxBadDDLForMedium = mMaxBadDDLForMedium; + qualityCheckerEventSize.mMaxBadDDLForBad = mMaxBadDDLForBad; + + mQualityHvSectorQ = Quality::Null; + QualityCheckerHV qualityCheckerHvSectorQ; + qualityCheckerHvSectorQ.resetHV(); + qualityCheckerHvSectorQ.mMaxBadHVForMedium = mMaxBadHVForMedium; + qualityCheckerHvSectorQ.mMaxBadHVForBad = mMaxBadHVForBad; + + mQualityBigMap = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + + if (matchHistName(mo->getName(), m_hHmpPadOccPrf_HistName)) { + TProfile* h = getHisto(mo); + if (h) { + auto q = check_hHmpPadOccPrf(h); + qualityCheckerOccupancy.addCheckResultDDL(q); + } + } + + if (matchHistName(mo->getName(), m_hBusyTime_HistName)) { + TProfile* h = getHisto(mo); + if (h && h->GetEntries() > 0) { + auto q = check_hBusyTime(h); + qualityCheckerBusyTime.addCheckResultDDL(q); + } + } + + if (matchHistName(mo->getName(), m_hEventSize_HistName)) { + TProfile* h = getHisto(mo); + if (h) { + auto q = check_hEventSize(h); + qualityCheckerEventSize.addCheckResultDDL(q); + } + } + + if (matchHistName(mo->getName(), m_hHmpHvSectorQ_HistName)) { + TH2F* h = getHisto(mo); + if (h && h->GetEntries() > 0) { + auto q = check_hHmpHvSectorQ(h); + qualityCheckerHvSectorQ.addCheckResultHV(q); + qualityHvSectorQ = q; + // Un-enable sectors we know are not working + qualityHvSectorQ[2] = Quality::Null; + qualityHvSectorQ[3] = Quality::Null; + qualityHvSectorQ[7] = Quality::Null; + qualityHvSectorQ[16] = Quality::Null; + qualityHvSectorQ[24] = Quality::Null; + qualityHvSectorQ[31] = Quality::Null; + qualityHvSectorQ[34] = Quality::Null; + } + } + } + + // compute the aggregated quality + qualityCheckerOccupancy.mQualityDDL[11] = Quality::Null; + mQualityOccupancy = qualityCheckerOccupancy.getQualityDDL(); + qualityCheckerBusyTime.mQualityDDL[11] = Quality::Null; + mQualityBusyTime = qualityCheckerBusyTime.getQualityDDL(); + qualityCheckerEventSize.mQualityDDL[11] = Quality::Null; + mQualityEventSize = qualityCheckerEventSize.getQualityDDL(); + qualityCheckerHvSectorQ.mQualityHV[2] = Quality::Null; + qualityCheckerHvSectorQ.mQualityHV[3] = Quality::Null; + qualityCheckerHvSectorQ.mQualityHV[7] = Quality::Null; + qualityCheckerHvSectorQ.mQualityHV[16] = Quality::Null; + qualityCheckerHvSectorQ.mQualityHV[24] = Quality::Null; + qualityCheckerHvSectorQ.mQualityHV[31] = Quality::Null; + qualityCheckerHvSectorQ.mQualityHV[34] = Quality::Null; + mQualityHvSectorQ = qualityCheckerHvSectorQ.getQualityHV(); + mQualityBigMap = mQualityHvSectorQ; // <-- reasonable assumption + + // Final quality result + Quality result = Quality::Null; + if (mQualityOccupancy == Quality::Bad || mQualityBusyTime == Quality::Bad || mQualityEventSize == Quality::Bad || mQualityHvSectorQ == Quality::Bad) { + result = Quality::Bad; + } else if (mQualityOccupancy == Quality::Medium || mQualityBusyTime == Quality::Medium || mQualityEventSize == Quality::Medium || mQualityHvSectorQ == Quality::Medium) { + result = Quality::Medium; + } else if (mQualityOccupancy == Quality::Good || mQualityBusyTime == Quality::Good || mQualityEventSize == Quality::Good || mQualityHvSectorQ == Quality::Good) { + result = Quality::Good; + } + + // update the error messages + mErrorMessages.clear(); + mErrorMessagesColor.clear(); + if (result == Quality::Good) { + mErrorMessages.emplace_back("Quality: GOOD"); + mErrorMessagesColor.emplace_back(kGreen + 2); + } else if (result == Quality::Medium) { + mErrorMessages.emplace_back("Quality: MEDIUM"); + mErrorMessagesColor.emplace_back(kOrange); + mErrorMessages.emplace_back("Add Logbook entry"); + mErrorMessagesColor.emplace_back(kOrange); + } else if (result == Quality::Bad) { + mErrorMessages.emplace_back("Quality: BAD"); + mErrorMessagesColor.emplace_back(kRed); + mErrorMessages.emplace_back("Contact HMPID on-call"); + mErrorMessagesColor.emplace_back(kRed); + } else if (result == Quality::Null) { + mErrorMessages.emplace_back("Quality: NULL\n"); + mErrorMessagesColor.emplace_back(kBlack); + } + + mErrorMessages.emplace_back("========================================\n"); + mErrorMessagesColor.emplace_back(kBlack); + + if (mQualityOccupancy == Quality::Null) { + mErrorMessages.emplace_back("Occuapncy: Empty DDLs, plot not filled"); + mErrorMessagesColor.emplace_back(kBlack); + } else if (mQualityOccupancy == Quality::Bad) { + mErrorMessages.emplace_back("Occupancy: Too large/small for many DDLs"); + mErrorMessagesColor.emplace_back(kRed); + } else if (mQualityOccupancy == Quality::Medium) { + mErrorMessages.emplace_back("Occupancy: Too large/small for some DDLs"); + mErrorMessagesColor.emplace_back(kOrange); + } else if (mQualityOccupancy == Quality::Good) { + mErrorMessages.emplace_back("Occupancy: Good"); + mErrorMessagesColor.emplace_back(kGreen + 2); + } + + if (mQualityEventSize == Quality::Null) { + mErrorMessages.emplace_back("Event size: Empty DDLs, plot not filled"); + mErrorMessagesColor.emplace_back(kBlack); + } else if (mQualityEventSize == Quality::Bad) { + mErrorMessages.emplace_back("Event size: Too large/small for many DDLs"); + mErrorMessagesColor.emplace_back(kRed); + } else if (mQualityEventSize == Quality::Medium) { + mErrorMessages.emplace_back("Event size: Too large/small for some DDLs"); + mErrorMessagesColor.emplace_back(kOrange); + } else if (mQualityEventSize == Quality::Good) { + mErrorMessages.emplace_back("Event size: Good"); + mErrorMessagesColor.emplace_back(kGreen + 2); + } + + if (mQualityBusyTime == Quality::Null) { + mErrorMessages.emplace_back("Busy time: Empty DDLs, plot not filled"); + mErrorMessagesColor.emplace_back(kBlack); + } else if (mQualityBusyTime == Quality::Bad) { + mErrorMessages.emplace_back("Busy time: Too large/small for many DDLs"); + mErrorMessagesColor.emplace_back(kRed); + } else if (mQualityBusyTime == Quality::Medium) { + mErrorMessages.emplace_back("Busy time: Too large/small for some DDLs"); + mErrorMessagesColor.emplace_back(kOrange); + } else if (mQualityBusyTime == Quality::Good) { + mErrorMessages.emplace_back("Busy time: Good"); + mErrorMessagesColor.emplace_back(kGreen + 2); + } + + if (mQualityHvSectorQ == Quality::Null) { + mErrorMessages.emplace_back("HV sectors: Waiting for more entries"); + mErrorMessagesColor.emplace_back(kBlack); + } else if (mQualityHvSectorQ == Quality::Bad) { + mErrorMessages.emplace_back("HV and Map: Too few many HV sectors"); + mErrorMessagesColor.emplace_back(kRed); + } else if (mQualityHvSectorQ == Quality::Medium) { + mErrorMessages.emplace_back("HV and Map: Too few some HV sectors"); + mErrorMessagesColor.emplace_back(kOrange); + } else if (mQualityHvSectorQ == Quality::Good) { + mErrorMessages.emplace_back("HV and Map: Good"); + mErrorMessagesColor.emplace_back(kGreen + 2); + } + + return result; +} + +void HmpidRawChecks::beautify(std::shared_ptr mo, Quality checkResult) +{ + + if (mo->getName().find("CheckerMessages") != std::string::npos) { + auto* canvas = dynamic_cast(mo->getObject()); + if (!canvas) { + return; + } + canvas->cd(); + + TPaveText* msg = new TPaveText(0.1, 0.1, 0.9, 0.9, "NDC"); + msg->SetTextSize(0.8); + msg->SetTextFont(22); + for (std::size_t i = 0; i < mErrorMessages.size(); ++i) { + msg->AddText(mErrorMessages[i].c_str()); + ((TText*)msg->GetListOfLines()->Last())->SetTextColor(mErrorMessagesColor[i]); + } + + msg->SetBorderSize(0); + msg->SetFillColor(kWhite); + msg->Draw(); + } + + // Occupancy + if (mo->getName().find("hHmpPadOccPrf") != std::string::npos) { + TProfile* h = getHisto(mo); + if (!h) { + return; + } + float scaleMin{ 0 }; + float scaleMax{ 0 }; + scaleMin = 0; // mMinOccupancy + scaleMax = mMaxOccupancy * 2.0; + h->SetMinimum(scaleMin); + h->SetMaximum(scaleMax); + + // draw horizontal limits + TGraph* line_max = new TGraph(2); + line_max->SetPoint(0, h->GetXaxis()->GetXmin(), mMaxOccupancy); + line_max->SetPoint(1, h->GetXaxis()->GetXmax(), mMaxOccupancy); + line_max->SetLineColor(kBlue); + line_max->SetLineStyle(kDashed); + line_max->SetDrawOption("L"); + h->GetListOfFunctions()->Add(line_max); + TGraph* line_min = new TGraph(2); + line_min->SetPoint(0, h->GetXaxis()->GetXmin(), mMinOccupancy); + line_min->SetPoint(1, h->GetXaxis()->GetXmax(), mMinOccupancy); + line_min->SetLineColor(kBlue); + line_min->SetLineStyle(kDashed); + line_min->SetDrawOption("L"); + h->GetListOfFunctions()->Add(line_min); + + if (mQualityOccupancy == Quality::Good) { // checkResult + h->SetFillColor(kGreen); + + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Good", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kGreen); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + + } else if (mQualityOccupancy == Quality::Bad) { // checkResult + h->SetFillColor(kRed); + + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Bad", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kRed); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + + } else if (mQualityOccupancy == Quality::Medium) { // checkResult + h->SetFillColor(kOrange); + + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Medium", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kOrange); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } + h->SetLineColor(kBlack); + } + + // Busy time + if (mo->getName().find("hBusyTime") != std::string::npos) { + TProfile* h = getHisto(mo); + if (!h) { + return; + } + float scaleMin{ 0 }; + float scaleMax{ 0 }; + scaleMin = 0; // mMinBusyTime + scaleMax = mMaxBusyTime * 2.0; + h->SetMinimum(scaleMin); + h->SetMaximum(scaleMax); + + // draw horizontal limits + TGraph* line_max = new TGraph(2); + line_max->SetPoint(0, h->GetXaxis()->GetXmin(), mMaxBusyTime); + line_max->SetPoint(1, h->GetXaxis()->GetXmax(), mMaxBusyTime); + line_max->SetLineColor(kBlue); + line_max->SetLineStyle(kDashed); + line_max->SetDrawOption("L"); + h->GetListOfFunctions()->Add(line_max); + TGraph* line_min = new TGraph(2); + line_min->SetPoint(0, h->GetXaxis()->GetXmin(), mMinBusyTime); + line_min->SetPoint(1, h->GetXaxis()->GetXmax(), mMinBusyTime); + line_min->SetLineColor(kBlue); + line_min->SetLineStyle(kDashed); + line_min->SetDrawOption("L"); + h->GetListOfFunctions()->Add(line_min); + + if (mQualityBusyTime == Quality::Good) { // checkResult + h->SetFillColor(kGreen); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Good", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kGreen); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } else if (mQualityBusyTime == Quality::Bad) { // checkResult + h->SetFillColor(kRed); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Bad", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kRed); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } else if (mQualityBusyTime == Quality::Medium) { // checkResult + h->SetFillColor(kOrange); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Medium", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kOrange); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } + h->SetLineColor(kBlack); + } + + // Event size + if (mo->getName().find("hEventSize") != std::string::npos) { + TProfile* h = getHisto(mo); + if (!h) { + return; + } + float scaleMin{ 0 }; + float scaleMax{ 0 }; + scaleMin = 0; // mMinEventSize + scaleMax = mMaxEventSize * 2.0; + h->SetMinimum(scaleMin); + h->SetMaximum(scaleMax); + + // draw horizontal limits + TGraph* line_max = new TGraph(2); + line_max->SetPoint(0, h->GetXaxis()->GetXmin(), mMaxEventSize); + line_max->SetPoint(1, h->GetXaxis()->GetXmax(), mMaxEventSize); + line_max->SetLineColor(kBlue); + line_max->SetLineStyle(kDashed); + line_max->SetDrawOption("L"); + h->GetListOfFunctions()->Add(line_max); + TGraph* line_min = new TGraph(2); + line_min->SetPoint(0, h->GetXaxis()->GetXmin(), mMinEventSize); + line_min->SetPoint(1, h->GetXaxis()->GetXmax(), mMinEventSize); + line_min->SetLineColor(kBlue); + line_min->SetLineStyle(kDashed); + line_min->SetDrawOption("L"); + h->GetListOfFunctions()->Add(line_min); + + if (mQualityEventSize == Quality::Good) { // checkResult + h->SetFillColor(kGreen); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Good", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kGreen); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } else if (mQualityEventSize == Quality::Bad) { // checkResult + h->SetFillColor(kRed); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Bad", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kRed); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } else if (mQualityEventSize == Quality::Medium) { // checkResult + h->SetFillColor(kOrange); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Medium", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kOrange); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } + h->SetLineColor(kBlack); + } + + if (mo->getName().find("hHmpHvSectorQ") != std::string::npos) { + TH2F* h = getHisto(mo); + if (!h) { + return; + } + if (mQualityHvSectorQ == Quality::Good) { // checkResult + h->SetFillColor(kGreen); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Good", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kGreen); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } else if (mQualityHvSectorQ == Quality::Bad) { // checkResult + h->SetFillColor(kRed); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Bad", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kRed); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } else if (mQualityHvSectorQ == Quality::Medium) { // checkResult + h->SetFillColor(kOrange); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Medium", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kOrange); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } + } + + if (mo->getName().find("hHmpBigMap_profile") != std::string::npos) { + TProfile2D* h = getHisto(mo); + if (!h) { + return; + } + if (mQualityBigMap == Quality::Good) { // checkResult + h->SetFillColor(kGreen); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Good", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kGreen); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } else if (mQualityBigMap == Quality::Bad) { // checkResult + h->SetFillColor(kRed); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Bad", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kRed); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } else if (mQualityBigMap == Quality::Medium) { // checkResult + h->SetFillColor(kOrange); + TLegend* pave = new TLegend(0.65, 0.75, 0.85, 0.85); + pave->AddEntry((TObject*)0, "Medium", ""); + pave->SetLineWidth(3); + pave->SetTextAlign(22); + pave->SetFillColor(kOrange); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } + } + + // Check HV + if (mo->getName().find("hCheckHV") != std::string::npos) { + TH2F* h = getHisto(mo); + if (!h) { + return; + } + TString report_warning = "Few entries:"; + TString report_good = "Good"; + TString report_null = "Null"; + int counter_bad = 0; + int counter_all = 0; + for (int i_module = 0; i_module < 42; i_module++) { + if (qualityHvSectorQ[i_module] == Quality::Good) { + h->SetBinContent(i_module + 1, h->GetYaxis()->FindBin("Good"), 1); + h->SetBinContent(i_module + 1, h->GetYaxis()->FindBin("Bad"), 0); + h->SetBinContent(i_module + 1, h->GetYaxis()->FindBin("Null"), -0.001); + counter_all++; + } else if (qualityHvSectorQ[i_module] == Quality::Bad) { + h->SetBinContent(i_module + 1, h->GetYaxis()->FindBin("Good"), 0); + h->SetBinContent(i_module + 1, h->GetYaxis()->FindBin("Bad"), 2); + h->SetBinContent(i_module + 1, h->GetYaxis()->FindBin("Null"), -0.001); + counter_all++; + counter_bad++; + report_warning += Form(" %d", i_module); + } + } + TLegend* pave = new TLegend(0.13, 0.75, 0.87, 0.85); + if (counter_bad == 0 && counter_all > 0) { + pave->SetFillColor(kGreen); + pave->AddEntry((TObject*)0, report_good, ""); + } else if (counter_bad > 0 && counter_all > 0) { + pave->SetFillColor(kOrange); + pave->AddEntry((TObject*)0, report_warning, ""); + } else { + pave->SetFillColor(kWhite); + pave->AddEntry((TObject*)0, report_null, ""); + } + pave->SetLineWidth(3); + pave->SetTextAlign(22); + // pave->SetFillColor(kRed); + pave->SetBorderSize(1); + pave->SetLineColor(kBlack); + pave->SetTextSize(0.07); + pave->SetTextFont(22); + pave->SetDrawOption("same"); + h->GetListOfFunctions()->Add(pave); + } +} + +} // namespace o2::quality_control_modules::hmpid diff --git a/Modules/HMPID/src/HmpidTask.cxx b/Modules/HMPID/src/HmpidTask.cxx new file mode 100644 index 0000000000..f22ada1ac7 --- /dev/null +++ b/Modules/HMPID/src/HmpidTask.cxx @@ -0,0 +1,345 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HmpidTask.cxx +/// \author Antonio Franco, Giacomo Volpe, Antonio Paz +/// \brief Class for quality control of HMPID detectors +/// \version 0.2.4 +/// \date 19/05/2022 +/// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "HMPID/HmpidTask.h" +#include "HMPIDReconstruction/HmpidEquipment.h" +#include "HMPIDReconstruction/HmpidDecoder2.h" +#include "DataFormatsHMP/Digit.h" +#include + +namespace o2::quality_control_modules::hmpid +{ + +HmpidTask::~HmpidTask() +{ + delete hPedestalMean; + delete hPedestalSigma; + delete hBusyTime; + delete hEventSize; + delete hEventNumber; + for (Int_t i = 0; i < numCham; ++i) { + delete hModuleMap[i]; + } + delete hHmpBigMap_profile; + delete hHmpHvSectorQ; + delete hHmpPadOccPrf; + delete CheckerMessages; + delete hCheckHV; +} + +Int_t NumCycles = 0; + +void HmpidTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize HmpidTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + if (auto param = mCustomParameters.find("myOwnKey"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - myOwnKey: " << param->second << ENDM; + } + + hPedestalMean = new TH1F("hPedestalMean", "Pedestal Mean", 2000, 0, 2000); + hPedestalMean->SetXTitle("Pedestal mean (ADC channel)"); + hPedestalMean->SetYTitle("Entries/1 ADC"); + + hPedestalSigma = new TH1F("hPedestalSigma", "Pedestal Sigma", 100, 0, 10); + hPedestalSigma->SetXTitle("Pedestal sigma (ADC channel)"); + hPedestalSigma->SetYTitle("Entries/0.1 ADC"); + + // TProfiles + hBusyTime = new TProfile("hBusyTime", "HMP Busy Time per DDL;DDL;Busy Time (#mus)", 14, 0.5, 14.5); + hBusyTime->Sumw2(); + hBusyTime->SetOption("histE"); + hBusyTime->SetMinimum(0); + hBusyTime->SetMarkerStyle(20); + hBusyTime->SetMarkerColor(kBlack); + hBusyTime->SetLineColor(kBlack); + hBusyTime->SetFillStyle(3004); + for (Int_t iddl = 0; iddl < 14; iddl++) + hBusyTime->GetXaxis()->SetBinLabel(iddl + 1, Form("%d", iddl + 1)); + hBusyTime->SetStats(0); + hBusyTime->GetXaxis()->SetLabelSize(0.025); + hBusyTime->GetYaxis()->SetLabelSize(0.025); + + hEventSize = new TProfile("hEventSize", "HMP Event Size per DDL;DDL;Event Size (kB)", 14, 0.5, 14.5); + hEventSize->Sumw2(); + hEventSize->SetOption("E"); // hist + hEventSize->SetMinimum(0); + hEventSize->SetMarkerStyle(20); + hEventSize->SetMarkerColor(kBlack); + hEventSize->SetLineColor(kBlack); + hEventSize->SetFillStyle(3004); + for (Int_t iddl = 0; iddl < 14; iddl++) + hEventSize->GetXaxis()->SetBinLabel(iddl + 1, Form("%d", iddl + 1)); + hEventSize->SetStats(0); + hEventSize->GetXaxis()->SetLabelSize(0.025); + hEventSize->GetYaxis()->SetLabelSize(0.025); + + hEventNumber = new TProfile("hEventNumber", "HMP Event Number per DDL;DDL;Event Number", 14, 0.5, 14.5); + hEventNumber->Sumw2(); + hEventNumber->SetOption("E"); // hist + hEventNumber->SetMinimum(0); + hEventNumber->SetMarkerStyle(20); + hEventNumber->SetMarkerColor(kBlack); + hEventNumber->SetLineColor(kBlack); + for (Int_t iddl = 0; iddl < 14; iddl++) + hEventNumber->GetXaxis()->SetBinLabel(iddl + 1, Form("%d", iddl + 1)); + hEventNumber->SetStats(0); + hEventNumber->GetXaxis()->SetLabelSize(0.025); + hEventNumber->GetYaxis()->SetLabelSize(0.025); + + for (Int_t i = 0; i < numCham; ++i) { + hModuleMap[i] = new TH2F(Form("hModuleMap%i", i), Form("Coordinates of hits in chamber %i", i), 160, 0, 160, 144, 0, 144); + hModuleMap[i]->SetXTitle("X coordinate"); + hModuleMap[i]->SetYTitle("Y coordinate"); + hModuleMap[i]->SetMarkerStyle(20); + + getObjectsManager()->startPublishing(hModuleMap[i]); + getObjectsManager()->setDefaultDrawOptions(hModuleMap[i], "colz"); + getObjectsManager()->setDisplayHint(hModuleMap[i], "colz"); + } + + hHmpBigMap_profile = new TProfile2D("hHmpBigMap_profile", "HMP Sum Q Maps Ch: 0-6", 160, 0, 160, 1008, 0, 1008); + hHmpBigMap_profile->SetXTitle("Ch 0-6: pad X"); + hHmpBigMap_profile->SetYTitle("Ch0, Ch1, Ch2, Ch3, Ch4, Ch5, Ch6 pad Y"); + hHmpBigMap_profile->SetZTitle("Sum Q / Nevt"); + hHmpBigMap_profile->SetMarkerStyle(20); + hHmpBigMap_profile->SetStats(0); + hHmpBigMap_profile->GetXaxis()->SetLabelSize(0.025); + hHmpBigMap_profile->GetYaxis()->SetLabelSize(0.025); + hHmpBigMap_profile->GetZaxis()->SetLabelSize(0.015); + hHmpBigMap_profile->GetXaxis()->SetTitleOffset(1.); + hHmpBigMap_profile->GetYaxis()->SetTitleOffset(1.4); + + hHmpHvSectorQ = new TH2F("hHmpHvSectorQ", "HMP HV Sector vs Q", 410, 1, 4101, 42, 0, 42); + hHmpHvSectorQ->SetXTitle("Q (ADC)"); + hHmpHvSectorQ->SetYTitle("HV Sector (Ch0-Sc0,Ch0-Sc1,...)"); + hHmpHvSectorQ->SetZTitle("Entries*Q/Nevt"); + hHmpHvSectorQ->SetMarkerStyle(20); + hHmpHvSectorQ->SetStats(0); + hHmpHvSectorQ->GetXaxis()->SetLabelSize(0.025); + hHmpHvSectorQ->GetYaxis()->SetLabelSize(0.025); + hHmpHvSectorQ->GetZaxis()->SetLabelSize(0.015); + hHmpHvSectorQ->GetXaxis()->SetTitleOffset(1.); + hHmpHvSectorQ->GetYaxis()->SetTitleOffset(1.4); + + hHmpPadOccPrf = new TProfile("hHmpPadOccPrf", "HMP Average pad occupancy per DDL;DDL;Pad occupancy (%)", 14, 0.5, 14.5); + hHmpPadOccPrf->Sumw2(); + hHmpPadOccPrf->SetOption("hist"); // E + hHmpPadOccPrf->SetMinimum(0); + hHmpPadOccPrf->SetMarkerStyle(20); + hHmpPadOccPrf->SetMarkerColor(kBlack); + hHmpPadOccPrf->SetLineColor(kBlack); + hHmpPadOccPrf->SetFillStyle(3004); + for (Int_t iddl = 0; iddl < 14; iddl++) + hHmpPadOccPrf->GetXaxis()->SetBinLabel(iddl + 1, Form("%d", iddl + 1)); + // hHmpPadOccPrf->GetXaxis()->SetLabelSize(0.02); + hHmpPadOccPrf->SetStats(0); + hHmpPadOccPrf->GetYaxis()->SetDecimals(1); + hHmpPadOccPrf->GetXaxis()->SetLabelSize(0.025); + hHmpPadOccPrf->GetYaxis()->SetLabelSize(0.025); + + getObjectsManager()->startPublishing(hPedestalMean); + + getObjectsManager()->startPublishing(hPedestalSigma); + + getObjectsManager()->startPublishing(hBusyTime); + getObjectsManager()->setDefaultDrawOptions(hBusyTime, "E"); // hist E + getObjectsManager()->setDisplayHint(hBusyTime, "E"); // hist E + + getObjectsManager()->startPublishing(hEventSize); + getObjectsManager()->setDefaultDrawOptions(hEventSize, "E"); // hist E + getObjectsManager()->setDisplayHint(hEventSize, "E"); // hist E + + getObjectsManager()->startPublishing(hEventNumber); + + getObjectsManager()->startPublishing(hHmpBigMap_profile); + getObjectsManager()->setDefaultDrawOptions(hHmpBigMap_profile, "colz"); + getObjectsManager()->setDisplayHint(hHmpBigMap_profile, "colz"); + + getObjectsManager()->startPublishing(hHmpHvSectorQ); + getObjectsManager()->setDefaultDrawOptions(hHmpHvSectorQ, "colz"); + getObjectsManager()->setDisplayHint(hHmpHvSectorQ, "colz"); + + getObjectsManager()->startPublishing(hHmpPadOccPrf); + getObjectsManager()->setDefaultDrawOptions(hHmpPadOccPrf, "E"); // hist E + getObjectsManager()->setDisplayHint(hHmpPadOccPrf, "E"); // hist E + + // Error messages + CheckerMessages = new TCanvas("CheckerMessages"); + getObjectsManager()->startPublishing(CheckerMessages); + + // TH2 to check HV + hCheckHV = new TH2F("hCheckHV", "hCheckHV", 42, -0.5, 41.5, 4, 0, 4); + hCheckHV->SetXTitle("HV Sector (Ch0-Sc0,Ch0-Sc1,...)"); + hCheckHV->SetYTitle("Quality"); + hCheckHV->SetMarkerStyle(20); + hCheckHV->SetStats(0); + hCheckHV->GetXaxis()->SetLabelSize(0.025); + hCheckHV->GetYaxis()->SetLabelSize(0.025); + hCheckHV->GetXaxis()->SetTitleOffset(1.); + hCheckHV->GetYaxis()->SetTitleOffset(1.4); + hCheckHV->SetMinimum(0); + hCheckHV->SetMaximum(2); + getObjectsManager()->startPublishing(hCheckHV); + getObjectsManager()->setDefaultDrawOptions(hCheckHV, "col"); + getObjectsManager()->setDisplayHint(hCheckHV, "col"); +} + +void HmpidTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + + HmpidTask::reset(); + + mDecoder = new o2::hmpid::HmpidDecoder2(14); + mDecoder->init(); + mDecoder->setVerbosity(2); // this is for Debug +} + +void HmpidTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void HmpidTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + mDecoder->init(); + mDecoder->setVerbosity(2); // this is for Debug + // static const Int_t numCham = 7; + static AliceO2::InfoLogger::InfoLogger::AutoMuteToken msgLimit(LogErrorDevel, 1, 600); // send it only every 10 minutes + + // for (auto&& input : ctx.inputs()) { + for (auto&& input : o2::framework::InputRecordWalker(ctx.inputs())) { + // get message header + if (input.header != nullptr && input.payload != nullptr) { + auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(input); + int32_t* ptrToPayload = (int32_t*)(input.payload); + if (payloadSize < 80) { + continue; + } + mDecoder->setUpStream(ptrToPayload, (long int)payloadSize); + if (!mDecoder->decodeBufferFast()) { + ILOG_INST.log(msgLimit, "Error decoding the Superpage !"); + break; + } + + for (Int_t eq = 0; eq < 14; eq++) { + int eqId = mDecoder->mTheEquipments[eq]->getEquipmentId(); + if (mDecoder->getAverageEventSize(eqId) > 0.) { + hEventSize->Fill(eqId + 1, mDecoder->getAverageEventSize(eqId) / 1000.); + } + if (mDecoder->getAverageBusyTime(eqId) > 0.) { + hBusyTime->Fill(eqId + 1, mDecoder->getAverageBusyTime(eqId) * 1000000); + } + if (mDecoder->mTheEquipments[eq]->mNumberOfEvents > 0) { + hHmpPadOccPrf->Fill(eqId + 1, (100. * mDecoder->mTheEquipments[eq]->mTotalPads) / (11520. * mDecoder->mTheEquipments[eq]->mNumberOfEvents)); + } + + hEventNumber->Fill(eqId + 1, mDecoder->mTheEquipments[eq]->mEventNumber); + + int module, x, y; + + for (Int_t column = 0; column < 24; column++) { + for (Int_t dilogic = 0; dilogic < 10; dilogic++) { + for (Int_t channel = 0; channel < 48; channel++) { + Int_t n_samp = mDecoder->mTheEquipments[eq]->mPadSamples[column][dilogic][channel]; + if (n_samp > 0) { + Float_t mean = mDecoder->getChannelSum(eqId, column, dilogic, channel) / n_samp; + Float_t sigma = TMath::Sqrt(mDecoder->getChannelSquare(eqId, column, dilogic, channel) / n_samp - mean * mean); + hPedestalMean->Fill(mean); + hPedestalSigma->Fill(sigma); + o2::hmpid::Digit::equipment2Absolute(eqId, column, dilogic, channel, &module, &x, &y); + hModuleMap[module]->Fill(x, y, mean); + hHmpBigMap_profile->Fill(x, module * 144 + y, mean); + if (mDecoder->mTheEquipments[eq]->mNumberOfEvents > 0) { + hHmpHvSectorQ->Fill(mean, module * 6 + y / 24, mean / Float_t(mDecoder->mTheEquipments[eq]->mNumberOfEvents)); + } + } + } + } + } + } + + /* Access the pads + uint16_t decoder.theEquipments[0..13]->padSamples[0..23][0..9][0..47] Number of samples + float decoder.theEquipments[0..13]->padSum[0..23][0..9][0..47] Sum of the charge of all samples + float decoder.theEquipments[0..13]->padSquares[0..23][0..9][0..47] Sum of the charge squares of all samples + uint16_t GetChannelSamples(int Equipment, int Column, int Dilogic, int Channel); + float GetChannelSum(int Equipment, int Column, int Dilogic, int Channel); + float GetChannelSquare(int Equipment, int Column, int Dilogic, int Channel); + uint16_t GetPadSamples(int Module, int Column, int Row); + float GetPadSum(int Module, int Column, int Row); + float GetPadSquares(int Module, int Column, int Row); + */ + } + } +} + +void HmpidTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + NumCycles++; + if (NumCycles > 15) { + HmpidTask::reset(); + NumCycles = 0; + } +} + +void HmpidTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void HmpidTask::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + hPedestalMean->Reset(); + hPedestalSigma->Reset(); + hBusyTime->Reset(); + hEventSize->Reset(); + hEventNumber->Reset(); + for (Int_t i = 0; i < numCham; ++i) { + hModuleMap[i]->Reset(); + } + hHmpBigMap_profile->Reset(); + hHmpHvSectorQ->Reset(); + hHmpPadOccPrf->Reset(); + hCheckHV->Reset(); + for (int i = 1; i <= hCheckHV->GetNbinsX(); i++) { + hCheckHV->SetBinContent(i, hCheckHV->GetYaxis()->FindBin("Null"), 0.001); + hCheckHV->SetBinContent(i, hCheckHV->GetYaxis()->FindBin("Good"), -0.001); + hCheckHV->SetBinContent(i, hCheckHV->GetYaxis()->FindBin("Bad"), -0.001); + hCheckHV->SetBinContent(i, hCheckHV->GetYaxis()->FindBin(""), -0.001); + } +} + +} // namespace o2::quality_control_modules::hmpid diff --git a/Modules/HMPID/src/HmpidTaskClusters.cxx b/Modules/HMPID/src/HmpidTaskClusters.cxx new file mode 100644 index 0000000000..1ded089c00 --- /dev/null +++ b/Modules/HMPID/src/HmpidTaskClusters.cxx @@ -0,0 +1,182 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file TaskClusters.cxx +/// \author Annalisa Mastroserio, Giacomo Volpe +/// + +#include "QualityControl/QcInfoLogger.h" +#include "DataFormatsHMP/Cluster.h" +#include "DataFormatsHMP/Trigger.h" +#include "HMPID/HmpidTaskClusters.h" + +#include +#include +#include + +#include + +#ifdef WITH_OPENMP +#include +#endif + +// using namespace o2::hmpid; + +namespace o2::quality_control_modules::hmpid +{ + +HmpidTaskClusters::HmpidTaskClusters() : TaskInterface() {} + +HmpidTaskClusters::~HmpidTaskClusters() +{ + ILOG(Info, Support) << "destructor called" << ENDM; + delete hClusMultEv; + delete ThClusMult; + for (Int_t iCh = 0; iCh < 7; iCh++) { + delete hHMPIDchargeClus[iCh]; + delete hHMPIDchargeMipClus[iCh]; + delete hHMPIDclusX[iCh]; + delete hHMPIDclusY[iCh]; + delete hHMPIDpositionClus[iCh]; + } +} + +void HmpidTaskClusters::initialize(o2::framework::InitContext& /*ctx*/) +{ + + ILOG(Debug, Devel) << "initialize TaskClusters" << ENDM; + + // getJsonParameters(); + + BookHistograms(); + + // publish histograms (Q: why publishing in initalize?) + getObjectsManager()->startPublishing(hClusMultEv); + getObjectsManager()->startPublishing(ThClusMult); + for (Int_t iCh = 0; iCh < 7; iCh++) { + getObjectsManager()->startPublishing(hHMPIDchargeClus[iCh]); + getObjectsManager()->startPublishing(hHMPIDchargeMipClus[iCh]); + getObjectsManager()->startPublishing(hHMPIDpositionClus[iCh]); + getObjectsManager()->startPublishing(hHMPIDclusX[iCh]); + getObjectsManager()->startPublishing(hHMPIDclusY[iCh]); + getObjectsManager()->setDefaultDrawOptions(hHMPIDpositionClus[iCh], "colz"); + getObjectsManager()->setDisplayHint(hHMPIDpositionClus[iCh], "colz"); + } + + ILOG(Info, Support) << "START DOING QC HMPID Cluster" << ENDM; + + // here do the QC +} + +void HmpidTaskClusters::startOfActivity(const Activity& /*activity*/) +{ + reset(); +} + +void HmpidTaskClusters::startOfCycle() +{ +} + +void HmpidTaskClusters::monitorData(o2::framework::ProcessingContext& ctx) +{ + ILOG(Info, Support) << "monitorData" << ENDM; + + // Get HMPID triggers + const auto triggers = ctx.inputs().get>("intrecord"); + + // Get HMPID clusters + auto clusters = ctx.inputs().get>("clusters"); + + int nEvents = triggers.size(); + + for (int i = 0; i < nEvents; i++) { // events loop + + double nClusters = triggers[i].getLastEntry() - triggers[i].getFirstEntry(); + hClusMultEv->Fill(nClusters); + + Int_t nClusCh[7] = { 0, 0, 0, 0, 0, 0, 0 }; + + for (int j = triggers[i].getFirstEntry(); j <= triggers[i].getLastEntry(); j++) { // cluster loop on the same event + + int chamber = clusters[j].ch(); + nClusCh[chamber]++; + + if (chamber <= 6 && chamber >= 0) { + hHMPIDchargeClus[chamber]->Fill(clusters[j].q()); + if (clusters[j].size() >= 3 && clusters[j].size() <= 7) { + hHMPIDchargeMipClus[chamber]->Fill(clusters[j].q()); + } + hHMPIDclusX[chamber]->Fill(clusters[j].x()); + hHMPIDclusY[chamber]->Fill(clusters[j].y()); + hHMPIDpositionClus[chamber]->Fill(clusters[j].x(), clusters[j].y()); + } + } // cluster loop + for (Int_t ich = 0; ich < 7; ich++) { + ThClusMult->Fill(ich, nClusCh[ich]); + } + } // events loop +} + +void HmpidTaskClusters::endOfCycle() +{ +} + +void HmpidTaskClusters::endOfActivity(const Activity& /*activity*/) +{ +} + +void HmpidTaskClusters::BookHistograms() +{ + hClusMultEv = new TH1F("ClusMultEve", "HMPID Cluster multiplicity per event", 1000, 0, 1000); + + ThClusMult = new TProfile("ClusMult", "HMPID Cluster multiplicity per chamber;Chamber Id;# of clusters/event", 7, 0., 7.); + ThClusMult->Sumw2(); + ThClusMult->SetOption("P"); + ThClusMult->SetMinimum(0); + ThClusMult->SetMarkerStyle(20); + ThClusMult->SetMarkerColor(kBlack); + ThClusMult->SetLineColor(kBlack); + ThClusMult->SetStats(0); + + for (Int_t iCh = 0; iCh < 7; iCh++) { + hHMPIDchargeClus[iCh] = new TH1F(Form("CluQ%i", iCh), Form("Cluster Charge in HMPID Chamber %i; Q (ADC); Entries/1 ADC", iCh), 2000, 0, 2000); + hHMPIDchargeMipClus[iCh] = new TH1F(Form("MipCluQ%i", iCh), Form("MIP Cluster Charge in HMPID Chamber %i; Q (ADC); Entries/1 ADC", iCh), 2000, 200, 2200); + hHMPIDclusX[iCh] = new TH1F(Form("ClusX%i", iCh), Form("X Cluster in HMPID Chamber %i; X (cm); Entries/1 cm", iCh), 133, 0, 133); + hHMPIDclusY[iCh] = new TH1F(Form("ClusY%i", iCh), Form("Y Cluster in HMPID Chamber %i; Y (cm); Entries/1 cm", iCh), 125, 0, 125); + hHMPIDpositionClus[iCh] = new TH2F(Form("ClusterPoistion%i", iCh), Form("Cluster Position in HMPID Chamber %i; X (cm); Y (cm)", iCh), 133, 0, 133, 125, 0, 125); // cmxcm + hHMPIDpositionClus[iCh]->SetStats(0); + } + // +} + +void HmpidTaskClusters::reset() +{ + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + + hClusMultEv->Reset(); + ThClusMult->Reset(); + + for (Int_t iCh = 0; iCh < 7; iCh++) { + hHMPIDchargeClus[iCh]->Reset(); + hHMPIDchargeMipClus[iCh]->Reset(); + hHMPIDclusX[iCh]->Reset(); + hHMPIDclusY[iCh]->Reset(); + hHMPIDpositionClus[iCh]->Reset(); + } +} + +void HmpidTaskClusters::getJsonParameters() +{ + ILOG(Info, Support) << "GetJsonParams" << ENDM; +} + +} // namespace o2::quality_control_modules::hmpid diff --git a/Modules/HMPID/src/HmpidTaskDigits.cxx b/Modules/HMPID/src/HmpidTaskDigits.cxx new file mode 100644 index 0000000000..d9f8ef62a3 --- /dev/null +++ b/Modules/HMPID/src/HmpidTaskDigits.cxx @@ -0,0 +1,174 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HmpidTaskDigits.cxx +/// \author Antonio Paz, Giacomo Volpe +/// \brief Class to map data from HMPID detectors +/// \version 0.3.1 +/// \date 26/10/2021 +/// + +//! ap Changes: +// - Histograms hHMPIDdigitmapAvg[numCham] back to TH2F +// - Histograms hHMPIDchargeDist[numCham] back to TH1F + +#include +#include +#include +#include + +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "HMPID/HmpidTaskDigits.h" +#include "DataFormatsHMP/Digit.h" +#include "DataFormatsHMP/Trigger.h" + +namespace o2::quality_control_modules::hmpid +{ + +HmpidTaskDigits::~HmpidTaskDigits() +{ + delete hOccupancyAvg; + + for (Int_t i = 0; i < numCham; ++i) { + delete hHMPIDchargeDist[i]; + delete hHMPIDdigitmapAvg[i]; + } +} + +void HmpidTaskDigits::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize HmpidTaskDigits" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + // if (auto param = mCustomParameters.find("myOwnKey"); param != mCustomParameters.end()) { + // ILOG(Info, Support) << "Custom parameter - myOwnKey: " << param->second << ENDM; + // } + + hOccupancyAvg = new TProfile("hOccupancyAvg", "Occupancy per DDL;;Occupancy (%)", 14, 0.5, 14.5); + hOccupancyAvg->Sumw2(); + hOccupancyAvg->SetOption("P"); + hOccupancyAvg->SetMinimum(0); + hOccupancyAvg->SetMarkerStyle(20); + hOccupancyAvg->SetMarkerColor(kBlack); + hOccupancyAvg->SetLineColor(kBlack); + for (Int_t iddl = 0; iddl < 14; iddl++) { + hOccupancyAvg->GetXaxis()->SetBinLabel(iddl + 1, Form("%d", iddl + 1)); + } + hOccupancyAvg->GetXaxis()->SetLabelSize(0.02); + hOccupancyAvg->SetStats(0); + + getObjectsManager()->startPublishing(hOccupancyAvg); + getObjectsManager()->addMetadata(hOccupancyAvg->GetName(), "custom", "34"); + + // histos for digit charge distributions + for (Int_t i = 0; i < numCham; ++i) { + + hHMPIDchargeDist[i] = new TH1F(Form("hHMPIDchargeDist%i", i + 1), Form("Distribution of charges in chamber %i", i + 1), 2000, 0, 2000); + hHMPIDchargeDist[i]->SetXTitle("Charge (ADC)"); + hHMPIDchargeDist[i]->SetYTitle("Entries/1 ADC"); + + hHMPIDdigitmapAvg[i] = new TH2F(Form("hHMPIDdigitmapAvg%i", i + 1), Form("Coordinates of hits in chamber %i", i + 1), 160, 0, 160, 144, 0, 144); + hHMPIDdigitmapAvg[i]->SetXTitle("padX"); + hHMPIDdigitmapAvg[i]->SetYTitle("padY"); + + getObjectsManager()->startPublishing(hHMPIDchargeDist[i]); + + getObjectsManager()->startPublishing(hHMPIDdigitmapAvg[i]); + } +} + +void HmpidTaskDigits::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + hOccupancyAvg->Reset(); + for (Int_t i = 0; i < numCham; ++i) { + hHMPIDchargeDist[i]->Reset(); + hHMPIDdigitmapAvg[i]->Reset(); + } +} + +void HmpidTaskDigits::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void HmpidTaskDigits::monitorData(o2::framework::ProcessingContext& ctx) +{ + // Get HMPID triggers + const auto triggers = ctx.inputs().get>("intrecord"); + + // Get HMPID digits + const auto digits = ctx.inputs().get>("digits"); + + int nEvents = triggers.size(); + + for (int i = 0; i < nEvents; i++) { // events loop + + double equipEntries[numEquip] = { 0x0 }; + + for (int j = triggers[i].getFirstEntry(); j <= triggers[i].getLastEntry(); j++) { // digits loop on the same event + + int padChX = 0, padChY = 0, module = 0; + + o2::hmpid::Digit::pad2Absolute(digits[j].getPadID(), &module, &padChX, &padChY); + + Double_t charge = digits[j].getCharge(); + + if (module <= 6 && module >= 0) { + hHMPIDchargeDist[module]->Fill(charge); + hHMPIDdigitmapAvg[module]->Fill(padChX, padChY); + } + + Int_t eqID = 0, col = 0, dlog = 0, gass = 0; + + o2::hmpid::Digit::absolute2Equipment(module, padChX, padChY, &eqID, &col, &dlog, &gass); + + if (eqID < (numEquip + 1)) { + //! ap A trap just in case eqID return wrong value + equipEntries[eqID]++; + } + + } // digits loop on the same event + + for (Int_t eq = 0; eq < numEquip; eq++) { + + hOccupancyAvg->SetBinContent(eq + 1, 100. * equipEntries[eq] / (nEvents * factor)); + } + } // events loop +} + +void HmpidTaskDigits::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void HmpidTaskDigits::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void HmpidTaskDigits::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histogram" << ENDM; + hOccupancyAvg->Reset(); + for (Int_t i = 0; i < numCham; ++i) { + hHMPIDchargeDist[i]->Reset(); + hHMPIDdigitmapAvg[i]->Reset(); + } +} + +} // namespace o2::quality_control_modules::hmpid diff --git a/Modules/HMPID/src/HmpidTaskMatches.cxx b/Modules/HMPID/src/HmpidTaskMatches.cxx new file mode 100644 index 0000000000..86f117e869 --- /dev/null +++ b/Modules/HMPID/src/HmpidTaskMatches.cxx @@ -0,0 +1,178 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file HmpidTaskMatches.cxx +/// \author Nicola Nicassio, Giacomo Volpe +/// + +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "HMPID/HmpidTaskMatches.h" +#include +#include +#include +#include "GlobalTrackingWorkflowHelpers/InputHelper.h" +#include "ReconstructionDataFormats/TrackTPCITS.h" +#include "DataFormatsTPC/TrackTPC.h" +#include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" +#include "ReconstructionDataFormats/TrackParametrization.h" +#include "DetectorsBase/Propagator.h" + +#include "DataFormatsHMP/Cluster.h" +#include "ReconstructionDataFormats/MatchInfoHMP.h" + +namespace o2::quality_control_modules::hmpid +{ + +HmpidTaskMatches::HmpidTaskMatches() : TaskInterface() {} + +HmpidTaskMatches::~HmpidTaskMatches() +{ + for (int iCh = 0; iCh < 7; iCh++) { + delete mMatchInfoResidualsXTrackMIP[iCh]; + delete mMatchInfoResidualsYTrackMIP[iCh]; + delete mMatchInfoChargeClusterMIP[iCh]; + delete mMatchInfoChargeClusterPhotons[iCh]; + delete mMatchInfoXMip[iCh]; + delete mMatchInfoYMip[iCh]; + delete mMatchInfoXTrack[iCh]; + delete mMatchInfoYTrack[iCh]; + delete mMatchInfoClusterMIPMap[iCh]; + delete mMatchInfoThetaCherenkovVsMom[iCh]; + } +} + +void HmpidTaskMatches::initialize(o2::framework::InitContext& /*ctx*/) +{ + mDataRequest = std::make_shared(); + mDataRequest->requestTracks(mSrc, false); + + BookHistograms(); + + for (int iCh = 0; iCh < 7; iCh++) { + + getObjectsManager()->startPublishing(mMatchInfoResidualsXTrackMIP[iCh]); + getObjectsManager()->startPublishing(mMatchInfoResidualsYTrackMIP[iCh]); + getObjectsManager()->startPublishing(mMatchInfoChargeClusterMIP[iCh]); + getObjectsManager()->startPublishing(mMatchInfoChargeClusterPhotons[iCh]); + getObjectsManager()->startPublishing(mMatchInfoClusterMIPMap[iCh]); + getObjectsManager()->setDefaultDrawOptions(mMatchInfoClusterMIPMap[iCh], "colz"); + getObjectsManager()->setDisplayHint(mMatchInfoClusterMIPMap[iCh], "colz"); + getObjectsManager()->startPublishing(mMatchInfoXMip[iCh]); + getObjectsManager()->startPublishing(mMatchInfoYMip[iCh]); + getObjectsManager()->startPublishing(mMatchInfoXTrack[iCh]); + getObjectsManager()->startPublishing(mMatchInfoXTrack[iCh]); + getObjectsManager()->startPublishing(mMatchInfoThetaCherenkovVsMom[iCh]); + } +} + +void HmpidTaskMatches::startOfActivity(const Activity& /*activity*/) +{ + reset(); +} + +void HmpidTaskMatches::startOfCycle() +{ +} + +void HmpidTaskMatches::monitorData(o2::framework::ProcessingContext& ctx) +{ + mRecoCont.collectData(ctx, *mDataRequest.get()); + + // HMP + mHMPMatches = mRecoCont.getHMPMatches(); + LOG(debug) << "We found " << mHMPMatches.size() << " Tracks matched to HMPID"; + + // loop over HMPID MatchInfo + for (const auto& matchHMP : mHMPMatches) { // match info loop + + int ch = matchHMP.getIdxHMPClus() / 1000000; + + float xMip, yMip, xTrk, yTrk, theta, phi; + int q, nPhot; + + matchHMP.getHMPIDtrk(xTrk, yTrk, theta, phi); + + matchHMP.getHMPIDmip(xMip, yMip, q, nPhot); + + auto vPhotCharge = matchHMP.getPhotCharge(); + + for (int i = 0; i < 10; i++) { // photon array loop + if (vPhotCharge[i] > 0) { + mMatchInfoChargeClusterPhotons[ch]->Fill(vPhotCharge[i]); + } + } // photon array loop + + mMatchInfoResidualsXTrackMIP[ch]->Fill(xTrk - xMip); + mMatchInfoResidualsYTrackMIP[ch]->Fill(yTrk - yMip); + if (matchHMP.getMipClusSize() < 20 && matchHMP.getMipClusSize() > 1) { + mMatchInfoChargeClusterMIP[ch]->Fill(q); + } + mMatchInfoXMip[ch]->Fill(xMip); + mMatchInfoYMip[ch]->Fill(yMip); + mMatchInfoXTrack[ch]->Fill(xTrk); + mMatchInfoYTrack[ch]->Fill(yTrk); + mMatchInfoClusterMIPMap[ch]->Fill(xMip, yMip); + mMatchInfoThetaCherenkovVsMom[ch]->Fill(TMath::Abs(matchHMP.getHmpMom()), matchHMP.getHMPsignal()); + } // match info loop +} + +void HmpidTaskMatches::endOfCycle() +{ +} + +void HmpidTaskMatches::endOfActivity(const Activity& /*activity*/) +{ +} + +void HmpidTaskMatches::BookHistograms() +{ + for (int iCh = 0; iCh < 7; iCh++) { + + mMatchInfoResidualsXTrackMIP[iCh] = new TH1F(Form("XResCham%i", iCh), Form("X Residuals chamber %i; X residuals (cm); Entries/1 cm", iCh), 100, -50., 50.); + mMatchInfoResidualsYTrackMIP[iCh] = new TH1F(Form("YRescham%i", iCh), Form("Y Residuals chamber %i; Y residuals (cm); Entries/1 cm", iCh), 100, -50., 50.); + mMatchInfoChargeClusterMIP[iCh] = new TH1F(Form("MipClusQch%i", iCh), Form("MIP Cluster Charge chamber %i; ADC charge; Entries/1 ADC", iCh), 2000, 200., 2200.); + mMatchInfoChargeClusterPhotons[iCh] = new TH1F(Form("PhelQch%i", iCh), Form("Photo-electron cluster charge chmaber %i; ADC charge; Counts/1 ADC", iCh), 180, 20., 200.); + mMatchInfoClusterMIPMap[iCh] = new TH2F(Form("MipClusMap%i", iCh), Form("MIP Cluster map chamber %i; X (cm); Y (cm)", iCh), 133, 0, 133, 125, 0, 125); + mMatchInfoClusterMIPMap[iCh]->SetStats(0); + mMatchInfoXMip[iCh] = new TH1F(Form("MipX%i", iCh), Form("X MIP in HMPID Chamber %i; X (cm); Entries/1 cm", iCh), 133, 0, 133); + mMatchInfoYMip[iCh] = new TH1F(Form("MipY%i", iCh), Form("Y MIP in HMPID Chamber %i; Y (cm); Entries/1 cm", iCh), 125, 0, 125); + mMatchInfoXTrack[iCh] = new TH1F(Form("TrackX%i", iCh), Form("X Track in HMPID Chamber %i; X (cm); Entries/1 cm", iCh), 133, 0, 133); + mMatchInfoYTrack[iCh] = new TH1F(Form("TrackY%i", iCh), Form("Y Track in HMPID Chamber %i; Y (cm); Entries/1 cm", iCh), 125, 0, 125); + mMatchInfoThetaCherenkovVsMom[iCh] = new TH2F(Form("ThetavsMom%i", iCh), Form("ThetaCherenkov Vs Mom chamber %i; #it{p} (GeV/#it{c}); #theta_{Ckov} (rad);", iCh), 100, 0., 10., 1000, 0., 1.); + } + // +} + +void HmpidTaskMatches::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + + for (int iCh = 0; iCh < 7; iCh++) { + mMatchInfoResidualsXTrackMIP[iCh]->Reset(); + mMatchInfoResidualsYTrackMIP[iCh]->Reset(); + mMatchInfoChargeClusterMIP[iCh]->Reset(); + mMatchInfoChargeClusterPhotons[iCh]->Reset(); + mMatchInfoClusterMIPMap[iCh]->Reset(); + mMatchInfoXMip[iCh]->Reset(); + mMatchInfoYMip[iCh]->Reset(); + mMatchInfoXTrack[iCh]->Reset(); + mMatchInfoYTrack[iCh]->Reset(); + mMatchInfoThetaCherenkovVsMom[iCh]->Reset(); + } +} +} // namespace o2::quality_control_modules::hmpid diff --git a/Modules/HMPID/src/clusters.json b/Modules/HMPID/src/clusters.json new file mode 100644 index 0000000000..46c79ea16f --- /dev/null +++ b/Modules/HMPID/src/clusters.json @@ -0,0 +1,59 @@ +{ + "qc":{ + "config":{ + "database":{ + "implementation":"CCDB", + "host":"ccdb-test.cern.ch:8080", + "username":"not_applicable", + "password":"not_applicable", + "name":"not_applicable" + }, + "Activity":{ + "number":"42", + "type":"2" + }, + "monitoring":{ + "url":"infologger:///debug?qc" + }, + "consul":{ + "url":"http://consul-test.cern.ch:8500" + }, + "conditionDB":{ + "url":"ccdb-test.cern.ch:8080" + } + }, + "tasks":{ + "TaskClusters":{ + "active":"true", + "className":"o2::quality_control_modules::hmpid::HmpidTaskClusters", + "moduleName":"QcHMPID", + "detectorName":"HMP", + "cycleDurationSeconds":"10", + "maxNumberCycles":"-1", + "dataSource":{ + "type":"dataSamplingPolicy", + "name":"hmp-clusters" + }, + "location":"remote" + } + } + }, + "dataSamplingPolicies":[ + { + "id":"hmp-clusters", + "active":"true", + "machines":[ + + ], + "query":"intrecord:HMP/INTRECORDS1/0;clusters:HMP/CLUSTERS/0", + "samplingConditions":[ + { + "condition":"random", + "fraction":"1.", + "seed":"1234" + } + ], + "blocking":"false" + } + ] +} diff --git a/Modules/HMPID/src/matchinfos.json b/Modules/HMPID/src/matchinfos.json new file mode 100644 index 0000000000..0c1c3deea7 --- /dev/null +++ b/Modules/HMPID/src/matchinfos.json @@ -0,0 +1,59 @@ +{ + "qc":{ + "config":{ + "database":{ + "implementation":"CCDB", + "host":"ccdb-test.cern.ch:8080", + "username":"not_applicable", + "password":"not_applicable", + "name":"not_applicable" + }, + "Activity":{ + "number":"42", + "type":"2" + }, + "monitoring":{ + "url":"infologger:///debug?qc" + }, + "consul":{ + "url":"http://consul-test.cern.ch:8500" + }, + "conditionDB":{ + "url":"ccdb-test.cern.ch:8080" + } + }, + "tasks":{ + "TaskMatches":{ + "active":"true", + "className":"o2::quality_control_modules::hmpid::HmpidTaskMatches", + "moduleName":"QcHMPID", + "detectorName":"HMP", + "cycleDurationSeconds":"10", + "maxNumberCycles":"-1", + "dataSource":{ + "type":"dataSamplingPolicy", + "name":"hmp-matches" + }, + "location":"remote" + } + } + }, + "dataSamplingPolicies":[ + { + "id":"hmp-matches", + "active":"true", + "machines":[ + + ], + "query":"matchHMP:HMP/MATCHES/0;clsHMP_GLO_MCTR:HMP/MCLABELS/0;hmpidcluster:HMP/CLUSTERS/0;hmpidtriggers:HMP/INTRECORDS1/0", + "samplingConditions":[ + { + "condition":"random", + "fraction":"1.", + "seed":"1234" + } + ], + "blocking":"false" + } + ] +} diff --git a/Modules/HMPID/src/readout.json b/Modules/HMPID/src/readout.json new file mode 100644 index 0000000000..3222a41750 --- /dev/null +++ b/Modules/HMPID/src/readout.json @@ -0,0 +1,92 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "daqTask": { + "active": "true", + "className": "o2::quality_control_modules::hmpid::HmpidTask", + "moduleName": "QcHMPID", + "detectorName": "HMP", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "readout" + }, + "location": "remote" + } + }, + "checks": { + "HmpidRawChecks": { + "active": "true", + "className": "o2::quality_control_modules::hmpid::HmpidRawChecks", + "moduleName": "QcHMPID", + "detectorName": "HMP", + "policy": "OnAll", + "checkParameters": { + "m_hHmpBigMap_HistName": "hHmpBigMap_profile", + "m_hHmpHvSectorQ_HistName": "hHmpHvSectorQ", + "m_hHmpPadOccPrf_HistName": "hHmpPadOccPrf", + "m_hBusyTime_HistName": "hBusyTime", + "m_hEventSize_HistName": "hEventSize", + "m_hCheckHV_HistName": "hCheckHV", + "mMinOccupancy": "0.001", + "mMaxOccupancy": "0.05", + "mMinEventSize": "5", + "mMaxEventSize": "100", + "mMinBusyTime": "5", + "mMaxBusyTime": "100", + "mMinHVTotalEntriesToCheckQuality": "5000", + "mFractionXBinsHVSingleModuleEntriesToLabelGoodBadQuality": "0.02", + "mMaxBadDDLForMedium": "2", + "mMaxBadDDLForBad": "4", + "mMaxBadHVForMedium": "7", + "mMaxBadHVForBad": "12" + }, + "dataSource": [ + { + "type": "Task", + "name": "daqTask", + "MOs": "all" + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "readout", + "active": "true", + "machines": [], + "query": "readout:ROUT/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/HMPID/test/testQcHMPID.cxx b/Modules/HMPID/test/testQcHMPID.cxx new file mode 100644 index 0000000000..0c7e50ca84 --- /dev/null +++ b/Modules/HMPID/test/testQcHMPID.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testHMPID.cxx +/// \author My Name +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::hmpid +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::hmpid diff --git a/Modules/ITS/CMakeLists.txt b/Modules/ITS/CMakeLists.txt new file mode 100644 index 0000000000..cfc81a9c40 --- /dev/null +++ b/Modules/ITS/CMakeLists.txt @@ -0,0 +1,156 @@ +# ---- Library ---- + +add_library(O2QcITS + src/ITSRawTask.cxx + src/TrendingTaskITSThr.cxx + src/TrendingTaskITSFhr.cxx + src/TrendingTaskITSFEE.cxx + src/TrendingTaskITSCluster.cxx + src/TrendingTaskITSTracks.cxx + src/TrendingTaskConfigITS.cxx + src/TrendingTaskITSError.cxx + src/TH2XlineReductor.cxx + src/ReductorBinContent.cxx + src/ReductorIntegralContent.cxx + src/ITSFhrTask.cxx + src/ITSFeeTask.cxx + src/ITSClusterTask.cxx + src/ITSNoisyPixelTask.cxx + src/ITSTrackTask.cxx + src/ITSThresholdCalibrationTask.cxx + src/ITSFhrCheck.cxx + src/ITSClusterCheck.cxx + src/ITSTrackCheck.cxx + src/ITSFeeCheck.cxx + src/ITSTrackSimTask.cxx + src/ITSThresholdCalibrationCheck.cxx + src/ITSDecodingErrorTask.cxx + src/ITSDecodingErrorCheck.cxx + ) + +target_sources(O2QcITS PRIVATE src/ITSChipStatusCheck.cxx src/ITSChipStatusTask.cxx + src/TH2XlineReductor.cxx + src/ReductorIntegralContent.cxx + src/ReductorBinContent.cxx) + +target_include_directories( + O2QcITS + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +include_directories(${O2_ROOT}/include/GPU) + +target_link_libraries(O2QcITS PUBLIC O2QualityControl O2::ITSBase O2::ITSMFTBase O2::ITSMFTReconstruction ROOT::Hist O2::DataFormatsITS O2::Steer O2QcCommon) + +if (OpenMP_CXX_FOUND) + target_compile_definitions(O2QcITS PRIVATE WITH_OPENMP) + target_link_libraries(O2QcITS PRIVATE OpenMP::OpenMP_CXX) +endif() + +install(TARGETS O2QcITS + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ITS + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- ROOT dictionary ---- + +add_root_dictionary(O2QcITS + HEADERS include/ITS/ITSRawTask.h + include/ITS/ITSChipStatusCheck.h + include/ITS/ITSChipStatusTask.h + include/ITS/TrendingTaskITSThr.h + include/ITS/TrendingTaskITSFhr.h + include/ITS/TrendingTaskITSFEE.h + include/ITS/TrendingTaskITSCluster.h + include/ITS/TrendingTaskITSTracks.h + include/ITS/TrendingTaskITSError.h + include/ITS/TH2XlineReductor.h + include/ITS/ReductorBinContent.h + include/ITS/ReductorIntegralContent.h + include/ITS/ITSFhrTask.h + include/ITS/ITSFeeTask.h + include/ITS/ITSClusterTask.h + include/ITS/ITSNoisyPixelTask.h + include/ITS/ITSTrackTask.h + include/ITS/ITSThresholdCalibrationTask.h + include/ITS/ITSFhrCheck.h + include/ITS/ITSClusterCheck.h + include/ITS/ITSTrackCheck.h + include/ITS/ITSFeeCheck.h + include/ITS/ITSTrackSimTask.h + include/ITS/ITSThresholdCalibrationCheck.h + include/ITS/ITSDecodingErrorTask.h + include/ITS/ITSDecodingErrorCheck.h + include/ITS/ITSHelpers.h + LINKDEF include/ITS/LinkDef.h) + +# ---- Test(s) ---- + +#add_executable(testQcITS test/testITS.cxx) # uncomment to reenable the test which was empty +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcCPV Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + +# ---- Executables ---- + +set(EXE_SRCS + src/runITS.cxx + src/runITSTracksRootFileReader.cxx + src/runITSClustersRootFileReader.cxx) + +set(EXE_NAMES + o2-qc-run-its + o2-qc-its-tracks-root-file-reader + o2-qc-its-clusters-root-file-reader) + +list(LENGTH EXE_SRCS count) +math(EXPR count "${count}-1") +foreach(i RANGE ${count}) + list(GET EXE_SRCS ${i} src) + list(GET EXE_NAMES ${i} name) + add_executable(${name} ${src}) + target_link_libraries(${name} PRIVATE O2QualityControl CURL::libcurl O2::ITSQCDataReaderWorkflow O2::DetectorsBase ROOT::Tree) +endforeach() + +install( + TARGETS ${EXE_NAMES} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +# ---- Extra scripts ---- + +install(FILES + its.json + itsQCTrendingThr.json + itsQCTrendingFhr.json + itsQCTrendingFEE.json + itsQCTrendingCluster.json + itsQCTrendingTracks.json + itsFhr.json + itsFee.json + itsCluster.json + itsTrack.json + itsNoisyPixel.json + itsTrackSim.json + itsThresholdCalibration.json + DESTINATION etc) + +get_property(dirs + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + PROPERTY INCLUDE_DIRECTORIES) +foreach(dir ${dirs}) + message(STATUS "dir='${dir}'") +endforeach() diff --git a/Modules/ITS/include/ITS/ITSChipStatusCheck.h b/Modules/ITS/include/ITS/ITSChipStatusCheck.h new file mode 100644 index 0000000000..82dfc21777 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSChipStatusCheck.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSChipStatusCheck.h +/// \auhtor Zhen Zhang +/// + +#ifndef QC_MODULE_ITS_ITSCHIPSTATUSCHECK_H +#define QC_MODULE_ITS_ITSCHIPSTATUSCHECK_H + +#include "QualityControl/CheckInterface.h" +#include +#include "ITS/ITSHelpers.h" +#include + +class TH1; +class TH2; + +namespace o2::quality_control_modules::its +{ + +/// \brief Check the FAULT flag for the lanes + +class ITSChipStatusCheck : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + ITSChipStatusCheck() = default; + /// Destructor + ~ITSChipStatusCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + std::vector vBadStaves; + + private: + int nCycle = 0; + // set timer + int TIME = 1; + ClassDefOverride(ITSChipStatusCheck, 1); + static const int NLayer = 7; + const int StaveBoundary[NLayer + 1] = { 0, 12, 28, 48, 72, 102, 144, 192 }; + std::shared_ptr tInfo; + const int FeeIDBoundaryVsBarrel[4] = { 0, 144, 252, 432 }; +}; + +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSChipStatusCheck_H diff --git a/Modules/ITS/include/ITS/ITSChipStatusTask.h b/Modules/ITS/include/ITS/ITSChipStatusTask.h new file mode 100644 index 0000000000..17e84e3037 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSChipStatusTask.h @@ -0,0 +1,120 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSChipStatusTask.h +/// \author My Name +/// + +#ifndef QC_MODULE_ITS_ITSITSCHIPSTATUSTASK_H +#define QC_MODULE_ITS_ITSITSCHIPSTATUSTASK_H + +#include "QualityControl/TaskInterface.h" +#include "Common/Utils.h" +#include "Common/TH2Ratio.h" +#include +#include +#include +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::its +{ + +class Stack +{ + private: + int size_max; + int current_element_id; + int nRotationType; + + public: + std::vector> stack; + Stack() : size_max(0), current_element_id(0), nRotationType(0) {} + Stack(int nsizex, int nsizey, int rotationType) : size_max(nsizex), current_element_id(0) + { + stack.resize(nsizex, std::vector(nsizey, 0)); + nRotationType = rotationType; + } + void push(const std::vector& element) + { + if (nRotationType == 0) { + + if (current_element_id < size_max) { + stack[current_element_id] = element; + current_element_id++; + } else { + std::rotate(stack.begin(), stack.begin() + 1, stack.end()); + stack.back() = element; + } + } else { + std::rotate(stack.begin(), stack.begin() + 1, stack.end()); + stack.back() = element; + } + } +}; + +class ITSChipStatusTask final : public TaskInterface +{ + public: + /// \brief Constructor + ITSChipStatusTask(); + /// Destructor + ~ITSChipStatusTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void getParameters(); // get Task parameters from json file + void setAxisTitle(TH1* object, const char* xTitle, const char* yTitle); + void Beautify(); + void getStavePoint(int layer, int stave, double* px, double* py); + int getFEEID(int barrel, int chipinbarrel); + + static constexpr int NLayer = 7; + static constexpr int NLayerIB = 3; + const int nHicPerStave[NLayer] = { 1, 1, 1, 8, 8, 14, 14 }; + const int nChipsPerHic[NLayer] = { 9, 9, 9, 14, 14, 14, 14 }; + const int nChipsPerFeeID[3] = { 3, 56, 98 }; // index is the barrel + const int nChipsPerLayer[NLayer] = { 108, 144, 180, 2688, 3360, 8232, 9408 }; + const int StaveBoundary[NLayer + 1] = { 0, 12, 28, 48, 72, 102, 144, 192 }; + const int ChipBoundary[NLayer + 1] = { 0, 108, 252, 432, 3120, 6480, 14712, 24120 }; + const int ChipsBoundaryLayers[8] = { 0, 108, 252, 432, 3120 - 432, 6480 - 432, 14712 - 6480, 24120 - 6480 }; // needed for drawing labels and lines for histogram + const float StartAngle[7] = { 16.997 / 360 * (TMath::Pi() * 2.), 17.504 / 360 * (TMath::Pi() * 2.), 17.337 / 360 * (TMath::Pi() * 2.), 8.75 / 360 * (TMath::Pi() * 2.), 7 / 360 * (TMath::Pi() * 2.), 5.27 / 360 * (TMath::Pi() * 2.), 4.61 / 360 * (TMath::Pi() * 2.) }; // start angle of first stave in each layer + const float MidPointRad[7] = { 23.49, 31.586, 39.341, 197.598, 246.944, 345.348, 394.883 }; + const int ChipsBoundaryBarrels[4] = { 0, 432, 6480, 24120 }; + const int NStaves[NLayer] = { 12, 16, 20, 24, 30, 42, 48 }; + static constexpr int NFees = 48 * 3 + 144 * 2; + TString LayerBinLabels[11] = { "L0", "L1", "L2", "L3B", "L3T", "L4B", "L4T", "L5B", "L5T", "L6B", "L6T" }; + std::shared_ptr DeadChips[3]; + Stack* ChipsStack[3]; + Stack* TFsStack[3]; + int nQCCycleToMonitor = 10; + TString BarrelNames[3] = { "IB", "ML", "OB" }; + TH2Poly* StaveOverview; + TH1D* FeeIDOverview; + int NCycleForOverview = 3; + int nRotationType = 1; + std::vector CurrentDeadChips[3]; // Vectors of Dead Chips in current QC cycle + std::vector CurrentTFs[3]; // Vectors of Dead Chips in current QC cycle +}; + +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSITSCHIPSTATUSTASK_H diff --git a/Modules/ITS/include/ITS/ITSClusterCheck.h b/Modules/ITS/include/ITS/ITSClusterCheck.h new file mode 100644 index 0000000000..ea06e6f779 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSClusterCheck.h @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSClusterCheck.h +/// \author Artem Isakov +/// \auhtor Liang Zhang +/// \author Jian Liu +/// + +#ifndef QC_MODULE_ITS_ITSCLUSTERCHECK_H +#define QC_MODULE_ITS_ITSCLUSTERCHECK_H + +#include "QualityControl/CheckInterface.h" +#include +#include +#include +#include "ITS/ITSHelpers.h" + +namespace o2::quality_control_modules::its +{ + +/// \brief Check the average cluster size + +class ITSClusterCheck : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + ITSClusterCheck() = default; + /// Destructor + ~ITSClusterCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + ClassDefOverride(ITSClusterCheck, 2); + + std::shared_ptr tInfoSummary; + std::shared_ptr tInfo; + std::shared_ptr tInfoLine; + std::shared_ptr msg; + std::shared_ptr text[14]; + std::shared_ptr text2[14]; + static constexpr int NLayer = 7; + const int mNStaves[NLayer] = { 12, 16, 20, 24, 30, 42, 48 }; + const int StaveBoundary[NLayer + 1] = { 0, 12, 28, 48, 72, 102, 144, 192 }; + float maxcluocc[NLayer] = { 5, 4, 3, 2, 1, 1, 1 }; + double MaxEmptyLaneFraction = 0.1; +}; + +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSClusterCheck_H diff --git a/Modules/ITS/include/ITS/ITSClusterTask.h b/Modules/ITS/include/ITS/ITSClusterTask.h new file mode 100644 index 0000000000..810084f830 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSClusterTask.h @@ -0,0 +1,172 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file ITSClusterTask.h +/// \author Artem Isakov +/// + +#ifndef QC_MODULE_ITS_ITSCLUSTERTASK_H +#define QC_MODULE_ITS_ITSCLUSTERTASK_H + +#include "QualityControl/TaskInterface.h" +#include "Common/TH2Ratio.h" + +#include +#include +#include + +class TH1; +class TH2; + +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::its +{ + +class ITSClusterTask : public TaskInterface +{ + + public: + ITSClusterTask(); + ~ITSClusterTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + // Fine checks + void setRphiBinningIB(std::vector bins = { -0.75, -0.60, -0.45, -0.30, -0.15, 0, 0.15, 0.30, 0.45, 0.60, 0.76 }); + void setZBinningIB(std::vector bins = { -1.5, -1.20, -0.9, -0.6, -0.3, 0, 0.3, 0.6, 0.9, 1.2, 1.51 }); + void setRphiBinningOB(std::vector bins = { -0.75, -0.35, 0, 0.35, 0.76 }); + void setZBinningOB(std::vector bins = { -1.5, -0.75, 0., 0.75, 1.51 }); + + private: + void publishHistos(); + template + void formatAxes(T* obj, const char* xTitle, const char* yTitle, float xOffset, float yOffset) + { + obj->GetXaxis()->SetTitle(xTitle); + obj->GetYaxis()->SetTitle(yTitle); + obj->GetXaxis()->SetTitleOffset(xOffset); + obj->GetYaxis()->SetTitleOffset(yOffset); + } + void addObject(TObject* aObject); + void getJsonParameters(); + void createAllHistos(); + void addLines(); + + float getHorizontalBin(float z, int chip, int layer, int lane = 0); + float getVerticalBin(float rphi, int stave, int layer); + + static constexpr int NLayer = 7; + static constexpr int NLayerIB = 3; + static constexpr int NStavesIB = 12 + 16 + 20; + static constexpr int NStavesOB = 24 + 30 + 42 + 48; + + std::vector mPublishedObjects; + + // Task + TH1D* hTFCounter = nullptr; + + // Inner barrel + TH1D* hClusterTopologySummaryIB[NLayer][48][9] = { { { nullptr } } }; + TH1D* hGroupedClusterSizeSummaryIB[NLayer][48][9] = { { { nullptr } } }; + TH1D* hClusterSizeSummaryIB[NLayer][48][9] = { { { nullptr } } }; + + std::shared_ptr hAverageClusterOccupancySummaryIB[NLayer]; + std::shared_ptr hAverageClusterSizeSummaryIB[NLayer]; + + // Outer barrel + TH1D* hGroupedClusterSizeSummaryOB[NLayer][48] = { { nullptr } }; + TH1D* hClusterSizeSummaryOB[NLayer][48] = { { nullptr } }; + TH1D* hClusterTopologySummaryOB[NLayer][48] = { { nullptr } }; + + std::shared_ptr hAverageClusterOccupancySummaryOB[NLayer]; + std::shared_ptr hAverageClusterSizeSummaryOB[NLayer]; + + // Layer summary + TH1L* hClusterSizeLayerSummary[NLayer] = { nullptr }; + TH1L* hClusterTopologyLayerSummary[NLayer] = { nullptr }; + TH1L* hGroupedClusterSizeLayerSummary[NLayer] = { nullptr }; + TH2D* hClusterOccupancyDistribution[NLayer] = { nullptr }; // number of clusters and hits per chip, per ROF. From clusters with npix > 2 + + // Anomalies plots + TH2D* hLongClustersPerChip[3] = { nullptr }; // IB layers + TH2D* hMultPerChipWhenLongClusters[3] = { nullptr }; + TH2D* hLongClustersPerStave[4] = { nullptr }; // OB layers + + // General + TH2D* hClusterVsBunchCrossing = nullptr; + std::unique_ptr mGeneralOccupancy = nullptr; + TH2D* hClusterCenterMap[3] = { nullptr }; // only IB + + // Fine checks + + std::shared_ptr hAverageClusterOccupancySummaryFine[NLayer]; + std::shared_ptr hAverageClusterSizeSummaryFine[NLayer]; + + std::shared_ptr hAverageClusterOccupancySummaryZPhi[NLayer]; + std::shared_ptr hAverageClusterSizeSummaryZPhi[NLayer]; + + TH1D* hEmptyLaneFractionGlobal; + + // Edges of space binning within chips (local frame coordinates) + std::vector vRphiBinsIB; + std::vector vZBinsIB; + std::vector vRphiBinsOB; + std::vector vZBinsOB; + + int nZBinsIB = 1; + int nRphiBinsIB = 1; + int nRphiBinsOB = 1; + int nZBinsOB = 1; + static constexpr int NFlags = 4; + + const int mOccUpdateFrequency = 100000; + const int mNLanes[4] = { 432, 864, 2520, 3816 }; // IB, ML, OL, TOTAL lane + int mDoPublish1DSummary = 0; + int mNThreads = 1; + int nBCbins = 103; + long int mTimestamp = -1; + TString xLabel; + std::string mLaneStatusFlag[NFlags] = { "IB", "ML", "OL", "Total" }; + int mDoPublishDetailedSummary = 0; + + int minColSpanLongCluster = 128; // driven by o2::itsmft::ClusterPattern::MaxColSpan = 128 + int maxRowSpanLongCluster = 29; + + static constexpr int mNStaves[NLayer] = { 12, 16, 20, 24, 30, 42, 48 }; + static constexpr int mNHicPerStave[NLayer] = { 1, 1, 1, 8, 8, 14, 14 }; + static constexpr int mNChipsPerHic[NLayer] = { 9, 9, 9, 14, 14, 14, 14 }; + static constexpr int mNChipsPerStave[NLayer] = { 9, 9, 9, 112, 112, 196, 196 }; + static constexpr int mNLanePerHic[NLayer] = { 3, 3, 3, 2, 2, 2, 2 }; + static constexpr int ChipBoundary[NLayer + 1] = { 0, 108, 252, 432, 3120, 6480, 14712, 24120 }; + static constexpr int StaveBoundary[NLayer + 1] = { 0, 12, 28, 48, 72, 102, 144, 192 }; + static constexpr float mLength[NLayer] = { 14., 14., 14., 43., 43., 74., 74. }; + const std::string mYlabels[NLayer * 2] = { "L6B(S24#rightarrow47)", "L5B(S21#rightarrow41)", "L4B(S15#rightarrow29)", "L3B(S12#rightarrow23)", "L2B(S10#rightarrow19)", "L1B(S08#rightarrow15)", "L0B(S06#rightarrow11)", "L0T(S00#rightarrow05)", "L1T(S00#rightarrow07)", "L2T(S00#rightarrow09)", "L3T(S00#rightarrow11)", "L4T(S00#rightarrow14)", "L5T(S00#rightarrow20)", "L6T(S00#rightarrow23)" }; + + int mEnableLayers[NLayer] = { 0 }; + + o2::itsmft::TopologyDictionary* mDict = nullptr; + o2::its::GeometryTGeo* mGeom = nullptr; + + const char* OBLabel34[16] = { "HIC1L_B0_ln7", "HIC1L_A8_ln6", "HIC2L_B0_ln8", "HIC2L_A8_ln5", "HIC3L_B0_ln9", "HIC3L_A8_ln4", "HIC4L_B0_ln10", "HIC4L_A8_ln3", "HIC1U_B0_ln21", "HIC1U_A8_ln20", "HIC2U_B0_ln22", "HIC2U_A8_ln19", "HIC3U_B0_ln23", "HIC3U_A8_ln18", "HIC4U_B0_ln24", "HIC4U_A8_ln17" }; + const char* OBLabel56[28] = { "HIC1L_B0_ln7", "HIC1L_A8_ln6", "HIC2L_B0_ln8", "HIC2L_A8_ln5", "HIC3L_B0_ln9", "HIC3L_A8_ln4", "HIC4L_B0_ln10", "HIC4L_A8_ln3", "HIC5L_B0_ln11", "HIC5L_A8_ln2", "HIC6L_B0_ln12", "HIC6L_A8_ln1", "HIC7L_B0_ln13", "HIC7L_A8_ln0", "HIC1U_B0_ln21", "HIC1U_A8_ln20", "HIC2U_B0_ln22", "HIC2U_A8_ln19", "HIC3U_B0_ln23", "HIC3U_A8_ln18", "HIC4U_B0_ln24", "HIC4U_A8_ln17", "HIC5U_B0_ln25", "HIC5U_A8_ln16", "HIC6U_B0_ln26", "HIC6U_A8_ln15", "HIC7U_B0_ln27", "HIC7U_A8_ln14" }; +}; +} // namespace o2::quality_control_modules::its + +#endif diff --git a/Modules/ITS/include/ITS/ITSDecodingErrorCheck.h b/Modules/ITS/include/ITS/ITSDecodingErrorCheck.h new file mode 100644 index 0000000000..0e7e267878 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSDecodingErrorCheck.h @@ -0,0 +1,63 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSDecodingErrorCheck.h +/// \auhtor Zhen Zhang +/// + +#ifndef QC_MODULE_ITS_ITSDECODINGERRORCHECK_H +#define QC_MODULE_ITS_ITSDECODINGERRORCHECK_H + +#include "QualityControl/CheckInterface.h" +#include +#include "ITSMFTReconstruction/DecodingStat.h" +#include "ITS/ITSHelpers.h" + +class TH1; +class TH2; + +namespace o2::quality_control_modules::its +{ + +/// \brief Check the FAULT flag for the lanes + +class ITSDecodingErrorCheck : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + ITSDecodingErrorCheck() = default; + /// Destructor + ~ITSDecodingErrorCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + std::vector vListErrorIdBad, vListErrorIdMedium; + bool doFlatCheck = false; + std::map> vAlreadyCheckedFeeIDs{}; // alreadychecked[fee] = + o2::itsmft::GBTLinkDecodingStat statistics; + + private: + int nCycle = 0; + // set timer + std::chrono::time_point start; + std::chrono::time_point end; + int TIME = 1; + ClassDefOverride(ITSDecodingErrorCheck, 1); + + std::shared_ptr tInfo; +}; + +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSDecodingErrorCheck_H diff --git a/Modules/ITS/include/ITS/ITSDecodingErrorTask.h b/Modules/ITS/include/ITS/ITSDecodingErrorTask.h new file mode 100644 index 0000000000..7f2fd3e553 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSDecodingErrorTask.h @@ -0,0 +1,81 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSDecodingErrorTask.h +/// \author Zhen Zhang +/// + +#ifndef QC_MODULE_ITS_ITSDECODINGERRORTASK_H +#define QC_MODULE_ITS_ITSDECODINGERRORTASK_H + +#include "QualityControl/TaskInterface.h" +#include "Common/TH1Ratio.h" + +#include +#include + +// class TH2D; +// class TH1D; + +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::its +{ + +/// \brief ITS FEE task aiming at 100% online data integrity checking +class ITSDecodingErrorTask final : public TaskInterface +{ + public: + /// \brief Constructor + ITSDecodingErrorTask(); + /// Destructor + ~ITSDecodingErrorTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + int mTFCount = 0; + float mBusyViolationLimit = 0.75; + void getParameters(); // get Task parameters from json file + void setAxisTitle(TH1* object, const char* xTitle, const char* yTitle); + void createDecodingPlots(); + void getStavePoint(int layer, int stave, double* px, double* py); // prepare for fill TH2Poly, get all point for add TH2Poly bin + void setPlotsFormat(); + void resetGeneralPlots(); + static constexpr int NLayer = 7; + static constexpr int NLayerIB = 3; + const int nChipsPerLayer[NLayer] = { 108, 144, 180, 2688, 3360, 8232, 9408 }; + const int StaveBoundary[NLayer + 1] = { 0, 12, 28, 48, 72, 102, 144, 192 }; + const int ChipBoundary[NLayer + 1] = { 0, 108, 252, 432, 3120, 6480, 14712, 24120 }; + static constexpr int NFees = 48 * 3 + 144 * 2; + TString LayerBinLabels[11] = { "L0", "L1", "L2", "L3B", "L3T", "L4B", "L4T", "L5B", "L5T", "L6B", "L6T" }; + std::unique_ptr hBusyFraction; + TH1D* hAlwaysBusy; + TH1D* mChipErrorPlots; + TH1D* mLinkErrorPlots; + bool isDoLinkErrorReset = true; + std::map> DecErr_lastCycle; + TH2D* mChipErrorVsChipid[7]; // chip ErrorVsChipid + TH2D* mLinkErrorVsFeeid; // link ErrorVsFeeid + TH2D* mChipErrorVsFeeid; // chip ErrorVsFeeid +}; + +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSDECODINGERRORTASK_H diff --git a/Modules/ITS/include/ITS/ITSFeeCheck.h b/Modules/ITS/include/ITS/ITSFeeCheck.h new file mode 100644 index 0000000000..b6b676719a --- /dev/null +++ b/Modules/ITS/include/ITS/ITSFeeCheck.h @@ -0,0 +1,80 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSFeeCheck.h +/// \auhtor Liang Zhang +/// \author Jian Liu +/// \author Antonio Palasciano +/// + +#ifndef QC_MODULE_ITS_ITSFEECHECK_H +#define QC_MODULE_ITS_ITSFEECHECK_H + +#include "QualityControl/CheckInterface.h" +#include +#include +#include "ITS/ITSHelpers.h" +namespace o2::quality_control_modules::its +{ + +/// \brief Check the FAULT flag for the lanes + +class ITSFeeCheck : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + ITSFeeCheck() = default; + /// Destructor + ~ITSFeeCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + bool checkReason(Quality checkResult, TString text); + + private: + ClassDefOverride(ITSFeeCheck, 2); + + static constexpr int NLayer = 7; + const int StaveBoundary[NLayer + 1] = { 0, 12, 28, 48, 72, 102, 144, 192 }; + const int NLanePerStaveLayer[NLayer] = { 9, 9, 9, 16, 16, 28, 28 }; + const int NStaves[NLayer] = { 12, 16, 20, 24, 30, 42, 48 }; + static constexpr int NFlags = 3; + static constexpr int NTrg = 13; + const double minTextPosY[NLayer] = { 0.45, 0.41, 0.37, 0.23, 0.20, 0.16, 0.13 }; // Text y coordinates in TH2Poly + std::string mLaneStatusFlag[NFlags] = { "WARNING", "ERROR", "FAULT" }; + const int laneMax[NLayer] = { 108, 144, 180, 384, 480, 1176, 1344 }; + + std::shared_ptr tInfo; + std::shared_ptr tInfoLayers[7]; + std::shared_ptr tInfoIB; + std::shared_ptr tInfoML; + std::shared_ptr tInfoOL; + std::shared_ptr tInfoPL[10]; + std::shared_ptr tInfoSummary; + std::shared_ptr tInfoTrg[13]; + + std::string skipbinstrg = ""; + std::string skipfeeids = ""; + int maxtfdifference = -1; + int minPayloadSize = 1400; + int maxbadchipsIB = 2; + int maxbadlanesML = 4; + int maxbadlanesOL = 7; + double maxfractionbadlanes = 0.1; + int expectedROFperOrbit = 18; +}; + +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSFeeCheck_H diff --git a/Modules/ITS/include/ITS/ITSFeeTask.h b/Modules/ITS/include/ITS/ITSFeeTask.h new file mode 100644 index 0000000000..666518c709 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSFeeTask.h @@ -0,0 +1,226 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSFeeTask.h +/// \author Jian Liu +/// \author Liang Zhang +/// \author Pietro Fecchio +/// \author Antonio Palasciano +/// + +#ifndef QC_MODULE_ITS_ITSFEETASK_H +#define QC_MODULE_ITS_ITSFEETASK_H + +#include "QualityControl/TaskInterface.h" +#include "Headers/RAWDataHeader.h" +#include "Headers/RDHAny.h" +#include "DetectorsRaw/RDHUtils.h" + +#include +#include +#include +#include + +class TH1; +class TH2; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::its +{ + +/// \brief ITS FEE task aiming at 100% online data integrity checking +class ITSFeeTask final : public TaskInterface +{ + struct GBTDiagnosticWord { // DDW GBT word + union { + uint64_t word0 = 0x0; + struct { + uint64_t laneStatus : 56; + uint16_t zero0 : 8; + } laneBits; + } laneWord; + union { + uint64_t word1 = 0x0; + struct { + uint8_t flag1 : 4; + uint8_t index : 4; + uint8_t id : 8; + uint64_t padding : 48; + } indexBits; + } indexWord; + }; + + // TODO: use proper casting and extend to full word + struct CalibrationWordUserField { // CDW GBT word user field + union { + uint16_t word0 = 0x0; + struct { + uint16_t rowid : 9; // Mask Stage / row + uint8_t runtype : 7; // Run type + } content; + } userField0; + union { + uint16_t word1 = 0x0; + struct { + uint16_t loopvalue : 16; // Loop value + } content; + } userField1; + union { + uint16_t word2 = 0x0; + struct { + uint16_t confdb : 13; // config version + uint8_t cdwver : 3; // CDW version + } content; + } userField2; + }; + + struct GBTITSHeaderWord { // IHW GBT word + union { + uint64_t word0 = 0x0; + struct { + uint32_t activeLanes : 28; // bit mask + uint64_t reserved : 36; // nothing + } laneBits; + } IHWcontent; + union { + uint64_t word1 = 0x0; + struct { + uint8_t reserved : 8; // nothing + uint8_t id : 8; + uint64_t padding : 48; + } indexBits; + } indexWord; + }; + + public: + /// \brief Constructor + ITSFeeTask(); + /// Destructor + ~ITSFeeTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + std::vector> mRDHDetField{ // + std::make_pair(0, "Missing data"), + std::make_pair(1, "Warning"), + std::make_pair(2, "Error"), + std::make_pair(3, "Fault"), + std::make_pair(4, "TriggerRamp"), + std::make_pair(5, "Recovery"), + std::make_pair(24, "TimebaseUnsyncEvt"), + std::make_pair(25, "TimebaseEvt"), + std::make_pair(26, "ClockEvt") + }; + + std::vector> mTriggerType{ // + std::make_pair(0, "ORBIT"), + std::make_pair(1, "HB"), + std::make_pair(2, "HBr"), + std::make_pair(3, "HC"), + std::make_pair(4, "PHYSICS"), + std::make_pair(5, "PP"), + std::make_pair(6, "CAL"), + std::make_pair(7, "SOT"), + std::make_pair(8, "EOT"), + std::make_pair(9, "SOC"), + std::make_pair(10, "EOC"), + std::make_pair(11, "TF"), + std::make_pair(12, "INT") + }; + + void getParameters(); // get Task parameters from json file + void setAxisTitle(TH1* object, const char* xTitle, const char* yTitle); + void createFeePlots(); + void getStavePoint(int layer, int stave, double* px, double* py); // prepare for fill TH2Poly, get all point for add TH2Poly bin + void setPlotsFormat(); + void drawLayerName(TH2* histo2D); + void resetGeneralPlots(); + void resetLanePlotsAndCounters(bool isFullReset = false); + static constexpr int NLayer = 7; + const int NStaves[NLayer] = { 12, 16, 20, 24, 30, 42, 48 }; + static constexpr int NLayerIB = 3; + static constexpr int NLanesMax = 28; + static constexpr int NFees = 48 * 3 + 144 * 2; + static constexpr int NFlags = 3; + const int StaveBoundary[NLayer + 1] = { 0, 12, 28, 48, 72, 102, 144, 192 }; + const int NLanePerStaveLayer[NLayer] = { 9, 9, 9, 16, 16, 28, 28 }; + const int LayerBoundaryFEE[NLayer - 1] = { 35, 83, 143, 191, 251, 335 }; + const float StartAngle[7] = { 16.997 / 360 * (TMath::Pi() * 2.), 17.504 / 360 * (TMath::Pi() * 2.), 17.337 / 360 * (TMath::Pi() * 2.), 8.75 / 360 * (TMath::Pi() * 2.), 7 / 360 * (TMath::Pi() * 2.), 5.27 / 360 * (TMath::Pi() * 2.), 4.61 / 360 * (TMath::Pi() * 2.) }; // start angle of first stave in each layer + const float MidPointRad[7] = { 23.49, 31.586, 39.341, 197.598, 246.944, 345.348, 394.883 }; + const int laneMax[NLayer] = { 108, 144, 180, 384, 480, 1176, 1344 }; + const int NLanesIB = 432, NLanesML = 864, NLanesOL = 2520, NLanesTotal = 3816; + const int lanesPerFeeId[NLayer] = { 3, 3, 3, 8, 8, 14, 14 }; + const int feePerLayer[NLayer] = { 36, 48, 60, 48, 60, 84, 96 }; + const int StavePerLayer[NLayer] = { 12, 16, 20, 24, 30, 42, 48 }; + const int feePerStave[NLayer] = { 3, 3, 3, 2, 2, 2, 2 }; + const int feeBoundary[NLayer] = { 0, 35, 83, 143, 191, 251, 335 }; + const int indexFeeLow[NLayer] = { 0, 3, 6, 3, 17, 0, 14 }; + const int indexFeeUp[NLayer] = { 3, 6, 9, 11, 25, 14, 28 }; + int mTimeFrameId = 0; + std::string mLaneStatusFlag[NFlags] = { "WARNING", "ERROR", "FAULT" }; // b00 OK, b01 WARNING, b10 ERROR, b11 FAULT + + int mStatusFlagNumber[7][48][28][3] = { { { 0 } } }; //[iLayer][iStave][iLane][iLaneStatusFlag] + int mStatusSummaryLayerNumber[7][3] = { { 0 } }; //[iLayer][iflag] + int mStatusSummaryNumber[4][3] = { { 0 } }; //[summary][iflag] ---> Global, IB, ML, OL + + // parameters taken from the .json + int mNPayloadSizeBins = 4096; + bool mResetLaneStatus = false; + bool mResetPayload = false; + bool precisePayload = false; + int mPayloadParseEvery_n_HBF_per_TF = 32; // -1 to disable, 1 to process all the HBFs + int mPayloadParseEvery_n_TF = 1; // Use >= 1 values + bool mEnableIHWReading = 0; + bool mDecodeCDW = 0; + int nResetCycle = 1; + int nCycleID = 0; + TH1I* mTFInfo; // count vs TF ID + TH2I* mTriggerVsFeeId; + TH2I* mTriggerVsFeeId_reset; + TH1I* mTrigger; + TH2I* mFlag1Check; // include transmission_timeout, packet_overflow, lane_starts_violation + TH2I* mDecodingCheck; // summary of errors during custom decoding of specific bytes (see plot description) + TH2I* mRDHSummary; + TH2I* mRDHSummaryCumulative; // RDH plot which does NOT reset at every QC cycle + TH2I* mLaneStatus[NFlags]; // 4 flags for each lane. 3/8/14 lane for each link. 3/2/2 link for each RU. + // Misconfiguration plot + TH2I* mTrailerCount; + TH2I* mTrailerCount_reset; + // Calibration plots + TH1I* mCalibrationWordCount; + TH2I* mCalibStage; + TH2I* mCalibLoop; + TH2I* mActiveLanes; + TH2I* mLaneStatusCumulative[NFlags]; + TH2Poly* mLaneStatusOverview[2] = { 0x0 }; // Warning and Error/Fatal + TH1I* mLaneStatusSummary[NLayer]; + TH1D* mLaneStatusSummaryIB; + TH1D* mLaneStatusSummaryML; + TH1D* mLaneStatusSummaryOL; + TH1D* mLaneStatusSummaryGlobal; + TH1I* mProcessingTime; + TH1D* mProcessingTime2; + TH2F* mPayloadSize; // average payload size vs linkID + // TH1D* mInfoCanvas;//TODO: default, not implemented yet +}; + +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSFEETASK_H diff --git a/Modules/ITS/include/ITS/ITSFhrCheck.h b/Modules/ITS/include/ITS/ITSFhrCheck.h new file mode 100644 index 0000000000..903cd0a13a --- /dev/null +++ b/Modules/ITS/include/ITS/ITSFhrCheck.h @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSFhrCheck.h +/// \author Liang Zhang +/// \author Jian Liu +/// + +#ifndef QC_MODULE_ITS_ITSFHRCHECK_H +#define QC_MODULE_ITS_ITSFHRCHECK_H + +#include "QualityControl/CheckInterface.h" +#include +#include "ITS/ITSHelpers.h" + +namespace o2::quality_control_modules::its +{ + +/// \brief Check the sensor occupancy and raw data formatting errors + +class ITSFhrCheck : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + ITSFhrCheck() = default; + /// Destructor + ~ITSFhrCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + bool checkReason(Quality checkResult, TString text); + + private: + int mNPixelPerStave[3] = { 4718592, 58720256, 102760448 }; // IB, ML, OL + std::shared_ptr tInfo[5]; + float fhrcutIB = 0.01; + float fhrcutOB = 0.0001; + ClassDefOverride(ITSFhrCheck, 2); +}; + +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSFhrCheck_H diff --git a/Modules/ITS/include/ITS/ITSFhrTask.h b/Modules/ITS/include/ITS/ITSFhrTask.h new file mode 100644 index 0000000000..b9ea47e161 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSFhrTask.h @@ -0,0 +1,151 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSFhrTask.h +/// \author Liang Zhang +/// \author Jian Liu +/// + +#ifndef QC_MODULE_ITS_ITSFHRTASK_H +#define QC_MODULE_ITS_ITSFHRTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include + +#include +#include +#include + +class TH1; +class TH2; + +using namespace o2::quality_control::core; +using PixelReader = o2::itsmft::PixelReader; + +namespace o2::quality_control_modules::its +{ + +/// \brief ITS Fake-hit rate real-time data processing task +/// Working with the chain of "Detector -> RU -> CRU -> Readout -> STFB -> o2-dpl-raw-proxy -> QC" +class ITSFhrTask final : public TaskInterface +{ + using ChipPixelData = o2::itsmft::ChipPixelData; + + public: + /// \brief Constructor + ITSFhrTask(); + /// Destructor + ~ITSFhrTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + int mTFCount = 0; + void setAxisTitle(TH1* object, const char* xTitle, const char* yTitle); + void createGeneralPlots(); // create General Plots depend mLayer which define by json file + void createErrorTriggerPlots(); + void createOccupancyPlots(); + void setPlotsFormat(); + void getParameters(); // get Task parameters from json file + void resetGeneralPlots(); + void resetOccupancyPlots(); + void resetObject(TH1* obj); + void getStavePoint(int layer, int stave, double* px, double* py); // prepare for fill TH2Poly, get all point for add TH2Poly bin + // detector information + static constexpr int NCols = 1024; // column number in Alpide chip + static constexpr int NRows = 512; // row number in Alpide chip + static constexpr int NLayer = 7; // layer number in ITS detector + static constexpr int NLayerIB = 3; + + static constexpr int NSubStave2[NLayer] = { 1, 1, 1, 2, 2, 2, 2 }; + const int NSubStave[NLayer] = { 1, 1, 1, 2, 2, 2, 2 }; + const int NStaves[NLayer] = { 12, 16, 20, 24, 30, 42, 48 }; + const int nHicPerStave[NLayer] = { 1, 1, 1, 8, 8, 14, 14 }; + const int nLanePerHic[NLayer] = { 3, 3, 3, 2, 2, 2, 2 }; + const int nChipsPerHic[NLayer] = { 9, 9, 9, 14, 14, 14, 14 }; + const int ChipBoundary[NLayer + 1] = { 0, 108, 252, 432, 3120, 6480, 14712, 24120 }; + const int StaveBoundary[NLayer + 1] = { 0, 12, 28, 48, 72, 102, 144, 192 }; + const char* OBLabel34[16] = { "HIC1L_B0_ln7", "HIC1L_A8_ln6", "HIC2L_B0_ln8", "HIC2L_A8_ln5", "HIC3L_B0_ln9", "HIC3L_A8_ln4", "HIC4L_B0_ln10", "HIC4L_A8_ln3", "HIC1U_B0_ln21", "HIC1U_A8_ln20", "HIC2U_B0_ln22", "HIC2U_A8_ln19", "HIC3U_B0_ln23", "HIC3U_A8_ln18", "HIC4U_B0_ln24", "HIC4U_A8_ln17" }; + const char* OBLabel56[28] = { "HIC1L_B0_ln7", "HIC1L_A8_ln6", "HIC2L_B0_ln8", "HIC2L_A8_ln5", "HIC3L_B0_ln9", "HIC3L_A8_ln4", "HIC4L_B0_ln10", "HIC4L_A8_ln3", "HIC5L_B0_ln11", "HIC5L_A8_ln2", "HIC6L_B0_ln12", "HIC6L_A8_ln1", "HIC7L_B0_ln13", "HIC7L_A8_ln0", "HIC1U_B0_ln21", "HIC1U_A8_ln20", "HIC2U_B0_ln22", "HIC2U_A8_ln19", "HIC3U_B0_ln23", "HIC3U_A8_ln18", "HIC4U_B0_ln24", "HIC4U_A8_ln17", "HIC5U_B0_ln25", "HIC5U_A8_ln16", "HIC6U_B0_ln26", "HIC6U_A8_ln15", "HIC7U_B0_ln27", "HIC7U_A8_ln14" }; + const int ReduceFraction = 1; + const float StartAngle[7] = { 16.997 / 360 * (TMath::Pi() * 2.), 17.504 / 360 * (TMath::Pi() * 2.), 17.337 / 360 * (TMath::Pi() * 2.), 8.75 / 360 * (TMath::Pi() * 2.), 7 / 360 * (TMath::Pi() * 2.), 5.27 / 360 * (TMath::Pi() * 2.), 4.61 / 360 * (TMath::Pi() * 2.) }; // start angle of first stave in each layer + const float MidPointRad[7] = { 23.49, 31.586, 39.341, 197.598, 246.944, 345.348, 394.883 }; // mid point radius + + int mNThreads = 1; + std::unordered_map mHitPixelID_Hash[7][48][2][14][14]; // layer, stave, substave, hic, chip + + o2::itsmft::RawPixelDecoder* mDecoder = nullptr; + ChipPixelData* mChipDataBuffer = nullptr; + std::vector mChipsBuffer; + + static constexpr int NTrigger = 13; + int mLayer = 0; + int mHitCutForCheck = 100; // Hit number cut for fired pixel check in a trigger + int mGetTFFromBinding = 0; + int mHitCutForNoisyPixel = 1024; // Hit number cut for noisy pixel, this number should be define according how many TF will be accumulated before reset(one can reference the cycle time) + float mOccupancyCutForNoisyPixel = 0.1; // Occupancy cut for noisy pixel. check if the hit/event value over this cut. similar with mHitCutForNoisyPixel + float mPhysicalOccupancyIB = 1.7e-3; + float mPhysicalOccupancyOB = 4.3e-5; + double mCutTFForSparse = 1; // cut to stop THnSparse filling after mCutTrgForSparse triggers + int mDoHitmapFilter = 1; // do filtering of noise pixel vector + std::unordered_map*** mHitPixelID_InStave = nullptr /* = new std::unordered_map**[NStaves[lay]]*/; + int** mHitnumberLane = nullptr /* = new int*[NStaves[lay]]*/; // IB : hitnumber[stave][chip]; OB : hitnumber[stave][lane] + double** mOccupancyLane /* = new double*[NStaves[lay]]*/; // IB : occupancy[stave][chip]; OB : occupancy[stave][Lane] + int*** mErrorCount = nullptr /* = new int**[NStaves[lay]]*/; // IB : errorcount[stave][FEE][errorid] + double** mChipPhi = nullptr /* = new double*[NStaves[lay]]*/; // IB/OB : mChipPhi[Stave][chip] + double** mChipZ = nullptr /* = new double*[NStaves[lay]]*/; // IB/OB : mChipZ[Stave][chip] + + int** mChipStat = nullptr /* = new double*[NStaves[lay]]*/; // IB/OB : mChipStat[Stave][chip] + int mNoisyPixelNumber[7][48] = { { 0 } }; + unsigned long nHitsTotal = 0; + int mMaxGeneralAxisRange = -3; // the range of TH2Poly plots z axis range, pow(10, mMinGeneralAxisRange) ~ pow(10, mMaxGeneralAxisRange) + int mMinGeneralAxisRange = -12; // + int mMaxGeneralNoisyAxisRange = 4000; + int mMinGeneralNoisyAxisRange = 0; + + int mEtabins = 130; + int mPhibins = 240; + + const float mLength[NLayer] = { 27.1, 27.1, 27.1, 84.3, 84.3, 147.5, 147.5 }; // Sensetive area length + + // General plots + TH1D* mErrorPlots = nullptr; + TH2I* mErrorVsFeeid = nullptr; + TH2Poly* mGeneralOccupancy = nullptr; // Max Occuapncy(chip/hic) in one stave + TH2Poly* mGeneralNoisyPixel = nullptr; // Noisy pixel number in one stave + + // Occupancy and hit-map + THnSparseI* mStaveHitmap[48] = {}; + TH2D* mDeadChipPos = nullptr; + TH2D* mAliveChipPos = nullptr; + TH2D* mTotalDeadChipPos = nullptr; + TH2D* mTotalAliveChipPos = nullptr; + TH2D* mChipStaveOccupancy = nullptr; + TH2I* mChipStaveEventHitCheck = nullptr; + TH1D* mOccupancyPlot = nullptr; + bool mIgnoreRampUpData = true; + // Geometry decoder + o2::its::GeometryTGeo* mGeom = nullptr; +}; +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSFHRTASK_H diff --git a/Modules/ITS/include/ITS/ITSHelpers.h b/Modules/ITS/include/ITS/ITSHelpers.h new file mode 100644 index 0000000000..8774d77dff --- /dev/null +++ b/Modules/ITS/include/ITS/ITSHelpers.h @@ -0,0 +1,38 @@ +#ifndef QC_MODULE_ITS_ITSHELPERS_H +#define QC_MODULE_ITS_ITSHELPERS_H + +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/Quality.h" +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::its +{ +template +std::vector convertToArray(std::string input) +{ + + std::istringstream ss{ input }; + + std::vector result; + std::string token; + while (std::getline(ss, token, ',')) { + + if constexpr (std::is_same_v) { + result.push_back(std::stoi(token)); + } else if constexpr (std::is_same_v) { + result.push_back(std::strtof(token.c_str(), NULL)); + } else if constexpr (std::is_same_v) { + result.push_back(token); + } + } + + return result; +} + +} // namespace o2::quality_control_modules::its +#endif diff --git a/Modules/ITS/include/ITS/ITSNoisyPixelTask.h b/Modules/ITS/include/ITS/ITSNoisyPixelTask.h new file mode 100644 index 0000000000..a2ca6a5dc2 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSNoisyPixelTask.h @@ -0,0 +1,110 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// ITSNoisyPixelTask.h +/// +/// + +#ifndef QC_MODULE_ITS_ITSNOISYPIXELTASK_H +#define QC_MODULE_ITS_ITSNOISYPIXELTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +class TH1D; +class TH2D; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::its +{ + +class ITSNoisyPixelTask : public TaskInterface +{ + + public: + ITSNoisyPixelTask(); + ~ITSNoisyPixelTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void publishHistos(); + void formatAxes(TH1* h, const char* xTitle, const char* yTitle, float xOffset = 1., float yOffset = 1.); + void addObject(TObject* aObject); + void getJsonParameters(); + void createAllHistos(); + void NormalizeOccupancyPlots(int n_cycle); + std::vector MapOverHIC(int col, int row, int chip); + + static constexpr int NLayer = 7; + static constexpr int NLayerIB = 3; + + int mROFcounter = 0; + int mROFcycle = 0; + std::unordered_map hashtable; + + std::vector mPublishedObjects; + + int nmostnoisy = 25; // number of bins for the following three histograms. It can be configured from config file. + TH1D* hOrderedHitsAddressIB; + TH1D* hOrderedHitsAddressML; + TH1D* hOrderedHitsAddressOL; + + TH2D* hOccupancyIB[3]; + TH2D* hOccupancyOB[4]; + + THnSparseD* hNoisyPixelMapIB[3][20]; + THnSparseD* hNoisyPixelMapOB[4][48]; + + enum QueryType { kUndefined, + kCluster, + kDigit }; + QueryType mQueryOption = kUndefined; + + int mOccUpdateFrequency = 10000; + bool mEnableOrderedHitsObject; + int mTotalTimeInQCTask; + int ChipIDprev = 0; + + const int mNStaves[7] = { 12, 16, 20, 24, 30, 42, 48 }; + const int mNHicPerStave[7] = { 1, 1, 1, 8, 8, 14, 14 }; + const int mNChipsPerHic[7] = { 9, 9, 9, 14, 14, 14, 14 }; + int mEnableLayers[7]; + o2::itsmft::TopologyDictionary* mDict; + o2::its::GeometryTGeo* mGeom; + std::string mGeomPath = "./"; + long int mTimestamp; + + int mLocalGeometryFile = 0; + std::string mGeoTimestamp = "1640991600000"; +}; +} // namespace o2::quality_control_modules::its + +#endif diff --git a/Modules/ITS/include/ITS/ITSRawTask.h b/Modules/ITS/include/ITS/ITSRawTask.h new file mode 100644 index 0000000000..bdfe6063f4 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSRawTask.h @@ -0,0 +1,188 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSRawTask.h +/// \author Zhaozhong Shi +/// \author Markus Keil +/// \author Mario Sitta +/// \brief ITS QC task for raw data analysis +/// + +#ifndef QC_MODULE_ITS_ITSRAWTASK_H +#define QC_MODULE_ITS_ITSRAWTASK_H + +#include "QualityControl/TaskInterface.h" + +#include +#include +#include +#include +#include +#include + +class TH1F; + +using namespace o2::quality_control::core; + +struct DigitEvent { + o2::itsmft::Digit Digits; + unsigned int NEvent; +}; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace its +{ + +class ITSRawTask : public TaskInterface // todo add back the "final" when doxygen is fixed +{ + + using ChipPixelData = o2::itsmft::ChipPixelData; + using PixelReader = o2::itsmft::PixelReader; + + public: + /// \brief Constructor + ITSRawTask(); + /// Destructor + ~ITSRawTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + void setNChips(int n) + { + mChips.resize(n); + mChipsOld.resize(n); + } + void ConfirmXAxis(TH1* h); + void ReverseYAxis(TH1* h); + + private: + void createHistos(); + void createGlobalHistos(); + // void createIBHistos(int aLayer); + void createLayerHistos(int aLayer); + void createStaveHistos(int aLayer, int aStave); + void createEtaPhiHitmap(int aLayer); + void createChipStaveOcc(int aLayer); + void createHicHistos(int aLayer, int aStave, int aHic); + void publishHistos(); + void addMetadata(int runID, int EpID, int fileID); + void formatAxes(TH1* h, const char* xTitle, const char* yTitle, float xOffset = 1., float yOffset = 1.); + void formatPaveText(TPaveText* aPT, float aTextSize, Color_t aTextColor, short aTextAlign, const char* aText); + void getHicCoordinates(int aLayer, int aChip, int aCol, int aRow, int& aHicRow, int& aHicCol); + void getProcessStatus(int aInfoFile, int& aFileFinish); + void updateFile(int aRunID, int aEpID, int aFileID); + void resetHitmaps(); + void resetOccupancyPlots(); + void updateOccupancyPlots(int nEvents); + void addObject(TObject* aObject, bool published = true); + void enableLayers(); + void formatStatistics(TH2* h); + void format2DZaxis(TH2* h); + + ChipPixelData* mChipData = nullptr; + std::vector mChips; + std::vector mChipsOld; + o2::itsmft::PixelReader* mReader = nullptr; + std::unique_ptr mReaderMC; + o2::itsmft::RawPixelReader mReaderRaw; + o2::itsmft::ChipInfo chipInfo; + UInt_t mCurrROF = o2::itsmft::PixelData::DummyROF; + int* mCurr; // pointer on the 1st row of currently processed mColumnsX + int* mPrev; // pointer on the 1st row of previously processed mColumnsX + static constexpr int NCols = 1024; + static constexpr int NRows = 512; + const int NColHis = 1024; + const int NRowHis = 512; + + int mSizeReduce = 4; + + const int occUpdateFrequency = 1000000; + + int mDivisionStep = 32; + static constexpr int NPixels = NRows * NCols; + static constexpr int NLayer = 7; + static constexpr int NLayerIB = 3; + + const int ChipBoundary[NLayer + 1] = { 0, 108, 252, 432, 3120, 6480, 14712, 24120 }; + const int NStaves[NLayer] = { 12, 16, 20, 24, 30, 42, 48 }; + const int nHicPerStave[NLayer] = { 1, 1, 1, 8, 8, 14, 14 }; + const int nChipsPerHic[NLayer] = { 9, 9, 9, 14, 14, 14, 14 }; + int mlayerEnable[NLayer] = { 0, 0, 0, 0, 0, 0, 0 }; + const float etaCoverage[NLayer] = { 2.5, 2.3, 2.0, 1.5, 1.4, 1.4, 1.3 }; + const double PhiMin = 0; + const double PhiMax = 3.284; //??? + + TH1D* hErrorPlots; + TH1D* hFileNameInfo; + TH2D* hErrorFile; + TH1D* hInfoCanvas; + + TH1D* hOccupancyPlot[NLayer]; + TH2I* hEtaPhiHitmap[NLayer]; + TH2D* hChipStaveOccupancy[NLayer]; + TH2I* hHicHitmap[7][48][14]; + TH2I* hChipHitmap[7][48][14][14]; + TH2I* hIBHitmap[3]; + const std::vector* mDigits = nullptr; + + o2::its::GeometryTGeo* gm = o2::its::GeometryTGeo::Instance(); + + static constexpr int NError = 11; + std::array mErrors; + std::array mErrorPre; + std::array mErrorPerFile; + + // unsigned int Error[NError]; + TPaveText* pt[NError]; + TPaveText* ptFileName; + TPaveText* ptNFile; + TPaveText* ptNEvent; + TPaveText* bulbGreen; + TPaveText* bulbRed; + TPaveText* bulbYellow; + + std::vector m_objects; + std::vector m_publishedObjects; + + TString ErrorType[NError] = { "Error ID 1: ErrPageCounterDiscontinuity", "Error ID 2: ErrRDHvsGBTHPageCnt", + "Error ID 3: ErrMissingGBTHeader", "Error ID 4: ErrMissingGBTTrailer", "Error ID 5: ErrNonZeroPageAfterStop", + "Error ID 6: ErrUnstoppedLanes", "Error ID 7: ErrDataForStoppedLane", "Error ID 8: ErrNoDataForActiveLane", + "Error ID 9: ErrIBChipLaneMismatch", "Error ID 10: ErrCableDataHeadWrong", + "Error ID 11: Jump in RDH_packetCounter" }; + const int NFiles = 24; + TEllipse* bulb; + + int mTotalDigits = 0; + int mNEvent; + int mNEventPre; + int mTotalFileDone; + // int FileRest; + + int mCounted; + int mTotalCounted = 10000; + int mYellowed; +}; + +} // namespace its +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_ITS_ITSRAWTASK_H diff --git a/Modules/ITS/include/ITS/ITSTaskVariables.h b/Modules/ITS/include/ITS/ITSTaskVariables.h new file mode 100644 index 0000000000..56a279d817 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSTaskVariables.h @@ -0,0 +1,19 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +int FileFinish; +int FileFinishPre; +double TotalHisTime; +int FileRest; +int ReallyDONE; +int colTask; +int rowTask; +int ChipIDTask; diff --git a/Modules/ITS/include/ITS/ITSThresholdCalibrationCheck.h b/Modules/ITS/include/ITS/ITSThresholdCalibrationCheck.h new file mode 100644 index 0000000000..771bb3a018 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSThresholdCalibrationCheck.h @@ -0,0 +1,46 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSThresholdCalibrationCheck.h +/// \auhtor Artem Isakov +/// \author Jian Liu +/// + +#ifndef QC_MODULE_ITS_ITSTHRESHOLDCALIBRATIONCHECK_H +#define QC_MODULE_ITS_ITSTHRESHOLDCALIBRATIONCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::its +{ + +/// \brief Check the clusters on track + +class ITSThresholdCalibrationCheck : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + ITSThresholdCalibrationCheck() = default; + /// Destructor + ~ITSThresholdCalibrationCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + ClassDefOverride(ITSThresholdCalibrationCheck, 2); +}; + +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSTHRESHOLDCALIBRATIONCHECK_H diff --git a/Modules/ITS/include/ITS/ITSThresholdCalibrationTask.h b/Modules/ITS/include/ITS/ITSThresholdCalibrationTask.h new file mode 100644 index 0000000000..1bb3387b92 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSThresholdCalibrationTask.h @@ -0,0 +1,145 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSThreshodCalibrationTask.h +/// \author Artem Isakov +/// + +#ifndef QC_MODULE_ITS_ITSTHRESHOLDCALIBRATIONTASK_H +#define QC_MODULE_ITS_ITSTHRESHOLDCALIBRATIONTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include "ITSMFTReconstruction/ChipMappingITS.h" +#include + +class TH1D; +class TH2D; + +using namespace o2::quality_control::core; + +enum { + VCASN, + ITHR, + THR, + pixel, + TOT +}; + +namespace o2::quality_control_modules::its +{ + +class ITSThresholdCalibrationTask : public TaskInterface +{ + + public: + ITSThresholdCalibrationTask(); + ~ITSThresholdCalibrationTask() override; + + struct CalibrationResStructTHR { + int Layer; + int Stave; + int Hs; + int HIC; + int ChipID; + float MainVal; // can be THR, ITHR, VCASN + float RMS; + float Noise; + float NoiseRMS; + float status; + float Tot; // time over threshold + float TotRms; // time over threshold rms + float ToA; // time of arrival + float ToARms; // time of arrival rms + }; + struct CalibrationResStructPixel { + int Layer; + int Stave; + int Hs; + int HIC; + int ChipID; + int Type; + int counts; + int Dcols; + }; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + std::vector splitString(std::string, std::string); + + private: + void publishHistos(); + void formatAxes(TH1* h, const char* xTitle, const char* yTitle, float xOffset = 1., float yOffset = 1.); + void formatLayers(TH2* h, Int_t iBarrel); + void addObject(TObject* aObject); + void createAllHistos(); + Int_t getBarrel(Int_t iLayer); + int getCurrentChip(int barrel, int chipid, int hic, int hs); + + void doAnalysisTHR(std::string inString, int iScan); + void doAnalysisPixel(std::string inString); + + CalibrationResStructTHR CalibrationParserTHR(std::string input); + CalibrationResStructPixel CalibrationParserPixel(std::string input); + + std::vector mPublishedObjects; + + static constexpr int NLayer = 7; // layer number in ITS detector + static constexpr int NLayerIB = 3; + + static constexpr int NSubStave2[NLayer] = { 1, 1, 1, 2, 2, 2, 2 }; + const int NSubStave[NLayer] = { 1, 1, 1, 2, 2, 2, 2 }; + const int NStaves[NLayer] = { 12, 16, 20, 24, 30, 42, 48 }; + const int nHicPerStave[NLayer] = { 1, 1, 1, 8, 8, 14, 14 }; + const int nChipsPerStave[NLayer] = { 9, 9, 9, 112, 112, 196, 196 }; + const int ChipBoundary[NLayer + 1] = { 0, 108, 252, 432, 3120, 6480, 14712, 24120 }; + const int StaveBoundary[NLayer] = { 0, 12, 28, 0, 24, 0, 42 }; + + TString sScanTypes[3] = { "VCASN", "ITHR", "THR" }; + TString sCalibrationType[3] = { "Noisy", "Dead", "Ineff" }; + TString sBarrelType[3] = { "IB", "ML", "OL" }; + int CalibType = 0; // THR + Int_t nChips[3] = { 9, 112, 196 }; + Int_t nStaves[3] = { 48, 54, 90 }; + Int_t nXmax[3] = { 130, 100, 450 }; + Int_t nZmax[3] = { 130, 110, 150 }; + Int_t nZmin[3] = { 20, 20, 30 }; + + TString sXtitles[3] = { "DAC", "DAC", "e" }; + + TH2F *hCalibrationChipDone[3], *hCalibrationChipAverage[3][3], *hCalibrationRMSChipAverage[3][3]; + TH2F *hCalibrationThrNoiseRMSChipAverage[3], *hCalibrationThrNoiseChipAverage[3]; + // TH2F *hCalibrationDeadColumns[3], *hCalibrationDeadPixels[3]; + TH2D* hCalibrationDColChipAverage[3]; + TH2D* hCalibrationPixel_dead[3]; + TH2D* hCalibrationPixel_noise[3]; + TH2D* hCalibrationPixel_inEff[3]; + + TH2F* hUnsuccess[3]; + TH1F *hCalibrationLayer[NLayer][3], *hCalibrationRMSLayer[NLayer][3]; + TH1F *hCalibrationThrNoiseLayer[NLayer], *hCalibrationThrNoiseRMSLayer[NLayer]; + + // Histograms for pulse-length scan + TH2F *hTimeOverThreshold[3], *hTimeOverThresholdRms[3], *hToA[3], *hToARms[3]; + TH1F *hTimeOverThresholdLayer[NLayer], *hTimeOverThresholdRmsLayer[NLayer], *hToALayer[NLayer], *hToARmsLayer[NLayer]; + + o2::itsmft::ChipMappingITS mp; +}; +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSTHRESHOLDCALIBRATIONTASK_H diff --git a/Modules/ITS/include/ITS/ITSTrackCheck.h b/Modules/ITS/include/ITS/ITSTrackCheck.h new file mode 100644 index 0000000000..377bc40ce8 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSTrackCheck.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSTrackCheck.h +/// \auhtor Artem Isakov +/// \author Jian Liu +/// + +#ifndef QC_MODULE_ITS_ITSTRACKCHECK_H +#define QC_MODULE_ITS_ITSTRACKCHECK_H + +#include "QualityControl/CheckInterface.h" +#include +#include "ITS/ITSHelpers.h" + +namespace o2::quality_control_modules::its +{ + +/// \brief Check the clusters on track + +class ITSTrackCheck : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + ITSTrackCheck() = default; + /// Destructor + ~ITSTrackCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + int getDigit(int number, int digit); + + private: + float mEtaRatio = 0.1, mPhiRatio = 0.1; + std::shared_ptr tInfo; + std::shared_ptr tMessage[10]; + ClassDefOverride(ITSTrackCheck, 2); +}; + +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSTrackCheck_H diff --git a/Modules/ITS/include/ITS/ITSTrackSimTask.h b/Modules/ITS/include/ITS/ITSTrackSimTask.h new file mode 100644 index 0000000000..6c20be44d6 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSTrackSimTask.h @@ -0,0 +1,108 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file ITSTrackSimTask.h +/// \author Artem Isakov +/// + +#ifndef QC_MODULE_ITS_ITSTRACKSIMTASK_H +#define QC_MODULE_ITS_ITSTRACKSIMTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include + +#include "SimulationDataFormat/MCTrack.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "Field/MagneticField.h" +#include "TGeoGlobalMagField.h" +#include "DataFormatsITS/TrackITS.h" +#include +class TH1D; +class TH2D; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::its +{ + +class ITSTrackSimTask : public TaskInterface +{ + + public: + ITSTrackSimTask(); + ~ITSTrackSimTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + struct InfoStruct { + unsigned short clusters = 0; + bool isFilled = 0; + int isReco = 0; + bool isPrimary = 0; + float r; + float pt; + float eta; + float phi; + float z; + }; + + private: + void publishHistos(); + void formatAxes(TH1* h, const char* xTitle, const char* yTitle, float xOffset = 1., float yOffset = 1.); + void addObject(TObject* aObject); + void createAllHistos(); + + std::vector> info; + std::vector mPublishedObjects; + + TEfficiency *hEfficiency_pt, *hEfficiency_phi, *hEfficiency_eta, *hEfficiency_z, *hEfficiency_r; + TEfficiency *hFakeTrack_pt[5], *hFakeTrack_phi[5], *hFakeTrack_eta[5], *hFakeTrack_z[5], *hFakeTrack_r[5], *hFakeTrack_QoverPt[5]; // idxs 0 to 3 -> 4 to 7 cluster tracks, idx 4 - total + TH1D *hNumRecoFake_pt[5], *hNumRecoFake_phi[5], *hNumRecoFake_eta[5], *hNumRecoFake_z[5], *hNumRecoFake_r[5], *hNumRecoFake_QoverPt[5]; + TH1D *hDenTrue_pt[5], *hDenTrue_phi[5], *hDenTrue_eta[5], *hDenTrue_z[5], *hDenTrue_r[5], *hDenTrue_QoverPt[5]; + + TH1D* hNumRecoValid_pt; + TH1D* hNumRecoValid_eta; + TH1D* hNumRecoValid_phi; + TH1D* hNumRecoValid_r; + TH1D* hNumRecoValid_z; + + TH1F* hTrackImpactTransvFake; + TH1F* hTrackImpactTransvValid; + + TH1D* hPrimaryReco_pt; + TH1D* hPrimaryGen_pt; + + TH2D* hAngularDistribution; + + TEfficiency *hDuplicate_pt, *hDuplicate_phi, *hDuplicate_eta, *hDuplicate_z, *hDuplicate_r; + TH1D *hNumDuplicate_pt, *hNumDuplicate_phi, *hNumDuplicate_eta, *hNumDuplicate_z, *hNumDuplicate_r; + + int mRunNumber = 0; + std::string mO2GrpPath; + std::string mCollisionsContextPath; + + o2::its::GeometryTGeo* mGeom; + + float bz; +}; +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSTRACKSIMTASK_H diff --git a/Modules/ITS/include/ITS/ITSTrackTask.h b/Modules/ITS/include/ITS/ITSTrackTask.h new file mode 100644 index 0000000000..383d079c85 --- /dev/null +++ b/Modules/ITS/include/ITS/ITSTrackTask.h @@ -0,0 +1,278 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSTrackTask.h +/// \author Artem Isakov +/// + +#ifndef QC_MODULE_ITS_ITSTRACKTASK_H +#define QC_MODULE_ITS_ITSTRACKTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include +#include +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" +#include +#include +#include +#include +#include "TMath.h" +#include +#include "ITS/ITSHelpers.h" + +class TH1; +class TH2; + +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::its +{ + +class ITSTrackTask : public TaskInterface +{ + + public: + ITSTrackTask(); + ~ITSTrackTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void publishHistos(); + void formatAxes(TH1* h, const char* xTitle, const char* yTitle, float xOffset = 1., float yOffset = 1.); + void addObject(TObject* aObject); + void createAllHistos(); + template + void formatAxes(T* obj, const char* xTitle, const char* yTitle, float xOffset, float yOffset) + { + obj->GetXaxis()->SetTitle(xTitle); + obj->GetYaxis()->SetTitle(yTitle); + obj->GetXaxis()->SetTitleOffset(xOffset); + obj->GetYaxis()->SetTitleOffset(yOffset); + } + + static constexpr int NLayer = 7; + static constexpr int NLayerIB = 3; + + const int ChipBoundary[NLayer + 1] = { 0, 108, 252, 432, 3120, 6480, 14712, 24120 }; + + std::vector mPublishedObjects; + std::unique_ptr hNClusters; + std::unique_ptr hTrackEta; + std::unique_ptr hTrackPhi; + TH1D* hVerticesRof; + std::unique_ptr hAngularDistribution; + TH2D* hVertexCoordinates; + TH2D* hVertexRvsZ; + TH2D* hVertexContvsZ; + TH1D* hVertexZ; + TH1D* hVertexContributors; + TH1D* hAssociatedClusterFraction; + TH1D* hNtracks; + std::unique_ptr hNClustersPerTrackEta; + std::unique_ptr hNClustersPerTrackPhi; + std::unique_ptr hNClustersPerTrackPt; + std::unique_ptr hHitFirstLayerPhiAll; + std::unique_ptr hHitFirstLayerPhi4cls; + std::unique_ptr hHitFirstLayerPhi5cls; + std::unique_ptr hHitFirstLayerPhi6cls; + std::unique_ptr hHitFirstLayerPhi7cls; + TH2D* hClusterVsBunchCrossing; + TH2D* hNClusterVsChipITS; + // Histograms for inv mass k0s, lambda + TH1D* hInvMassK0s; + TH1D* hInvMassLambda; + TH1D* hInvMassLambdaBar; + TH2D* hTrackPtVsEta; + TH2D* hTrackPtVsPhi; + float mPiInvMass = 0.14; + float mProtonInvMass = 0.938; + Int_t mInvMasses = 0; // switch for the V0 invariant mass computation, 1 (default) - on, 0 - off + + int mPublishMore = 1; + float mVertexXYsize = 0.5; + float mVertexZsize = 15.; + float mVertexRsize = 0.8; + Int_t mNtracksMAX = 100; + Int_t mDoTTree = 0; + // mDoNorm: 0 = no normalization, 1 = normalization by nVertices, 2 = normalization by nRofs + Int_t mDoNorm = 1; + Int_t mNRofs = 0; + int nBCbins = 103; + long int mTimestamp = -1; + int nVertices = 0; + double mChipBins[2125]; // x bins for cos(lambda) plot + double mCoslBins[25]; // y bins for cos(lambda) plot + double ptBins[141]; // pt bins + + o2::itsmft::TopologyDictionary* mDict; + + private: + // analysis for its-only residual + o2::its::GeometryTGeo* mGeom; + + std::vector FitStepSize{ 0.3, 1.0e-5, 1.0e-5, 1.0e-5 }; + + double FitTolerance = 1.0e-8; + double ITS_AbsBz = 0.5; // T + + double inputG_C[3 * NLayer]; + double FitparXY[6]; + double FitparDZ[2]; + ROOT::Fit::Fitter fitterA; + ROOT::Fit::Fitter fitterB; + Int_t mAlignmentMonitor = 0; + Int_t mDefaultMomResPar = 0; + Int_t mResMonNclMin = 0; + float mResMonTrackMinPt = 0; + + std::array, NLayer> hResidualXY{}; //[NLayer]; + std::array, NLayer> hResidualZD{}; //[NLayer]; + + void circleFitXY(double* input, double* par, double& MSEvalue, std::vector hitUpdate, int step = 0); + + // default setting + // function Object to be minimized + struct se_circlefitXY { + // the TGraph is a data member of the object + std::vector fHits; + double thetaR; + double Bz; + double sigma_meas[2][NLayer] = { { 45, 45, 45, 55, 55, 55, 55 }, { 40, 40, 40, 40, 40, 40, 40 } }; // um unit + double sigma_msc[2][NLayer] = { { 30, 30, 30, 110, 110, 110, 110 }, { 25, 25, 25, 75, 75, 75, 75 } }; // um unit + + se_circlefitXY() = default; + se_circlefitXY(std::vector& h, double tR, double bz) + { + fHits = h; + thetaR = tR; + Bz = bz; + }; + + void loadparameters(double arrpar_meas[][NLayer], double arrpar_msc[][NLayer]) + { + for (int a = 0; a < 2; a++) { + for (int l = 0; l < NLayer; l++) { + sigma_meas[a][l] = arrpar_meas[a][l]; + sigma_msc[a][l] = arrpar_msc[a][l]; + } + } + }; + + void init() + { + fHits.clear(); + thetaR = 0; + Bz = 0; + }; + + void set(std::vector& h, double tR, double bz) + { + fHits = h; + thetaR = tR; + Bz = bz; + }; + + double getsigma(double R, int L, double B, int axis) + { + // R : cm + // B : T + if (L < 0 || L >= NLayer) + return 1; + double aL = sigma_meas[axis][L] * 1e-4; // um -> cm + double bL = sigma_msc[axis][L] * 1e-4; // um -> cm + double Beff = 0.299792458 * B; + double sigma = std::sqrt(std::pow(aL, 2) + std::pow(bL, 2) / (std::pow(Beff, 2) * std::pow(R * 1e-2, 2))); + + return sigma; + }; + + // calculate distance line-point + double distance2(double x, double y, const double* p, double tR, int charge) + { + + double R = std::abs(1 / p[0]); + + double Xc = p[0] > 0 ? R * std::cos(p[1] + tR + 0.5 * TMath::Pi()) : R * std::cos(p[1] + tR - 0.5 * TMath::Pi()); + double Yc = p[0] > 0 ? R * std::sin(p[1] + tR + 0.5 * TMath::Pi()) : R * std::sin(p[1] + tR - 0.5 * TMath::Pi()); + + double dx = x - (Xc + p[2]); + double dy = y - (Yc + p[3]); + double dxy = R - std::sqrt(dx * dx + dy * dy); + + double d2 = dxy * dxy; + return d2; + }; + + // implementation of the function to be minimized + double operator()(const double* par) + { // const double -> double + assert(fHits.size() != 0); + + int nhits = fHits.size(); + double sum = 0; + + double charge = par[0] > 0 ? +1 : -1; + double RecRadius = std::abs(1 / par[0]); + + double Sigma_tot[NLayer]; + double sum_Sigma_tot = 0; + for (int l = 0; l < nhits; l++) { + Sigma_tot[l] = getsigma(RecRadius, fHits[l].Z(), Bz, 1); + sum_Sigma_tot += 1 / (std::pow(Sigma_tot[l], 2)); + } + + for (int l = 0; l < nhits; l++) { + double d = distance2(fHits[l].X(), fHits[l].Y(), par, thetaR, charge) / (std::pow(Sigma_tot[l], 2)); + sum += d; + } + sum = sum / sum_Sigma_tot; + + return sum; + }; + }; + + se_circlefitXY fitfuncXY; + + void lineFitDZ(double* zIn, double* betaIn, double* parz, double Radius, bool vertex, std::vector hitUpdate); + + double mSigmaMeas[2][NLayer] = { { 45, 45, 45, 55, 55, 55, 55 }, { 40, 40, 40, 40, 40, 40, 40 } }; // um unit + double mSigmaMsc[2][NLayer] = { { 30, 30, 30, 110, 110, 110, 110 }, { 25, 25, 25, 75, 75, 75, 75 } }; // um unit + + double getsigma(double R, int L, double B, int axis) + { + // R : cm + // B : T + if (L < 0 || L >= NLayer) + return 1; + double aL = mSigmaMeas[axis][L] * 1e-4; // um -> cm + double bL = mSigmaMsc[axis][L] * 1e-4; // um -> cm + double Beff = 0.299792458 * B; + double sigma = std::sqrt(std::pow(aL, 2) + std::pow(bL, 2) / (std::pow(Beff, 2) * std::pow(R * 1e-2, 2))); + + return sigma; + }; +}; +} // namespace o2::quality_control_modules::its + +#endif // QC_MODULE_ITS_ITSTRACKTASK_H diff --git a/Modules/ITS/include/ITS/LinkDef.h b/Modules/ITS/include/ITS/LinkDef.h new file mode 100644 index 0000000000..fd026d0766 --- /dev/null +++ b/Modules/ITS/include/ITS/LinkDef.h @@ -0,0 +1,32 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::its::ITSRawTask + ; +#pragma link C++ class o2::quality_control::postprocessing::TrendingTaskITSThr + ; +#pragma link C++ class o2::quality_control::postprocessing::TrendingTaskITSFhr + ; +#pragma link C++ class o2::quality_control::postprocessing::TrendingTaskITSFEE + ; +#pragma link C++ class o2::quality_control::postprocessing::TrendingTaskITSCluster + ; +#pragma link C++ class o2::quality_control::postprocessing::TrendingTaskITSTracks + ; +#pragma link C++ class o2::quality_control_modules::its::TH2XlineReductor + ; +#pragma link C++ class o2::quality_control_modules::its::ReductorBinContent + ; +#pragma link C++ class o2::quality_control_modules::its::ReductorIntegralContent + ; +#pragma link C++ class o2::quality_control_modules::its::ITSFhrTask + ; +#pragma link C++ class o2::quality_control_modules::its::ITSFeeTask + ; +#pragma link C++ class o2::quality_control_modules::its::ITSClusterTask + ; +#pragma link C++ class o2::quality_control_modules::its::ITSNoisyPixelTask + ; +#pragma link C++ class o2::quality_control_modules::its::ITSThresholdCalibrationTask + ; +#pragma link C++ class o2::quality_control_modules::its::ITSTrackTask + ; +#pragma link C++ class o2::quality_control_modules::its::ITSFhrCheck + ; +#pragma link C++ class o2::quality_control_modules::its::ITSClusterCheck + ; +#pragma link C++ class o2::quality_control_modules::its::ITSTrackCheck + ; +#pragma link C++ class o2::quality_control_modules::its::ITSFeeCheck + ; +#pragma link C++ class o2::quality_control_modules::its::ITSTrackSimTask + ; +#pragma link C++ class o2::quality_control_modules::its::ITSThresholdCalibrationCheck + ; +#pragma link C++ class o2::quality_control_modules::its::ITSDecodingErrorTask + ; +#pragma link C++ class o2::quality_control_modules::its::ITSDecodingErrorCheck + ; +#pragma link C++ class o2::quality_control::postprocessing::TrendingTaskITSError + ; +#pragma link C++ class o2::quality_control_modules::its::ITSChipStatusTask+; +#pragma link C++ class o2::quality_control_modules::its::ITSChipStatusCheck+; +#endif diff --git a/Modules/ITS/include/ITS/README.md b/Modules/ITS/include/ITS/README.md new file mode 100644 index 0000000000..ea3f3ce79c --- /dev/null +++ b/Modules/ITS/include/ITS/README.md @@ -0,0 +1,31 @@ +# Analysis for ITS-only residual + +We use the Weighted Least Square(WLS) fit and calculate the residuals from each cluster positions to the fitted trajectory. + +## Circular Fit in the XY plane : circleFitXY + +In the XY plane, the trajectory of particle under the magnetic field in z-axis can be fitted with circular model. +There are 4 fit parameters : radius(R), direction at origin($$\theta_R$$), estimated vertex X(vx), and estimated vertex Y(vy). + +$$ x_c = R \cdot cos(\theta_R + \frac{R}{|R|} \cdot \pi/2) $$ + +$$ y_c = R \cdot sin(\theta_R + \frac{R}{|R|} \cdot \pi/2) $$ + +$$ (x - (x_c + v_x))^2 + (y - (y_c + v_y))^2 = |R|^2 $$ + +For the fit stability, +1. A simple algorithm to determine seeds of fit parameters(radius and direction at origin) is applied. +2. Circular fit is performed for both (+) and (-) signs of the circular trajectory (even if we have approximated seeds from 1.), the one with the lower chi2(sum of weighted residuals) is finally adopted. This procedure ensures the stability in case of hard(high pT) tracks. + +## Linear Fit in the DZ plane : lineFitDZ + +After 4 fit parameters of circular trajectory is determined, we can describe the fit positions of associated clusters as well as the global positions. +This makes it possible to represent each cluster position in the poloar coordinates $$(r, \beta)$$, whose origin is $$(x_c, y_c)$$. + +One of the simplest residual in z direction can be calculated in the RZ plane, where $$r = \sqrt(x^2 + y^2)$$. +However, in this case, the fitting stability cannot be guaranteed if the collision position is not located in the same quadrant. + +On the other hand, another parameter beta determined above the equation of the circle determined (in the XY plane) corresponds to the distance of the particle moving(=D) in the XY plane. +In this case, the fitting stability can be guaranteed even if the collision position is not located in the same quadrant. + +So we proceed with the linear fit in the DZ plane. diff --git a/Modules/ITS/include/ITS/ReductorBinContent.h b/Modules/ITS/include/ITS/ReductorBinContent.h new file mode 100644 index 0000000000..4f98b1c69b --- /dev/null +++ b/Modules/ITS/include/ITS/ReductorBinContent.h @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReductorBinContent.h +/// \author Artem Kotliarov +/// +#ifndef QUALITYCONTROL_REDUCTORBINCONTENT_H +#define QUALITYCONTROL_REDUCTORBINCONTENT_H + +#include "QualityControl/ReductorTObject.h" +#include + +namespace o2::quality_control_modules::its +{ + +class ReductorBinContent : public quality_control::postprocessing::ReductorTObject +{ + public: + ReductorBinContent() = default; + virtual ~ReductorBinContent() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + void setParams(Int_t Bins) + { + nBins = Bins; + } + + private: + int nBins = 3; + + struct mystat { + // std::vector binContent; // Bin content in a specified slice + Double_t binContent[100]; + }; + + mystat mStats; +}; +} // namespace o2::quality_control_modules::its + +#endif // QUALITYCONTROL_REDUCTORBINCONTENT_H diff --git a/Modules/ITS/include/ITS/ReductorIntegralContent.h b/Modules/ITS/include/ITS/ReductorIntegralContent.h new file mode 100644 index 0000000000..bb4e182046 --- /dev/null +++ b/Modules/ITS/include/ITS/ReductorIntegralContent.h @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReductorIntegralContent.h +/// \author Artem Kotliarov +/// +#ifndef QUALITYCONTROL_REDUCTORINTEGRALCONTENT_H +#define QUALITYCONTROL_REDUCTORINTEGRALCONTENT_H + +// #include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include + +namespace o2::quality_control_modules::its +{ + +class ReductorIntegralContent : public quality_control::postprocessing::ReductorTObject + +{ + public: + ReductorIntegralContent() = default; + virtual ~ReductorIntegralContent() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + void setParams(Int_t Bins) + { + nBins = Bins; + } + + private: + int nBins = 13; + int FeeBoundary[10] = { 0, 35, 83, 143, 191, 251, 293, 335, 383, 431 }; + struct mystat { + // std::vector binContent; // Bin content in a specified slice + Double_t integral[100]; + }; + + mystat mStats; +}; +} // namespace o2::quality_control_modules::its + +#endif // QUALITYCONTROL_REDUCTORINTEGRALCONTENT_H diff --git a/Modules/ITS/include/ITS/TH2XlineReductor.h b/Modules/ITS/include/ITS/TH2XlineReductor.h new file mode 100755 index 0000000000..496cab13ec --- /dev/null +++ b/Modules/ITS/include/ITS/TH2XlineReductor.h @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2XlineReductor.h +/// \author Ivan Ravasenga on the model from Piotr Konopka +/// +#ifndef QUALITYCONTROL_TH2XLINEREDUCTOR_H +#define QUALITYCONTROL_TH2XLINEREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" +#include + +namespace o2::quality_control_modules::its +{ + +/// \brief A Reductor which obtains specific characteristics of TH2: mean and stddev of bin contents +/// for each y bin (each row of the TH2) +/// +/// A Reductor which obtains specific characteristics of TH2. +class TH2XlineReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + TH2XlineReductor() = default; + ~TH2XlineReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + static constexpr int NDIM = 90; // put it so we have more 48+42 :) + struct mystat { + Double_t mean[NDIM]; // mean of the bin contents of each row (1 value per row) + Double_t stddev[NDIM]; // stddev of the bin contents of each row (1 value per row) + Double_t entries[NDIM]; // entries of each row (1 value per row) + Double_t mean_scaled[NDIM]; // mean scaled with number of active pixels in a stave to get the occupancy + }; + mystat mStats; +}; + +} // namespace o2::quality_control_modules::its + +#endif // QUALITYCONTROL_TH2XLINEREDUCTOR_H diff --git a/Modules/ITS/include/ITS/TrendingTaskConfigITS.h b/Modules/ITS/include/ITS/TrendingTaskConfigITS.h new file mode 100644 index 0000000000..5dffd00dec --- /dev/null +++ b/Modules/ITS/include/ITS/TrendingTaskConfigITS.h @@ -0,0 +1,59 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskConfig.h +/// \author Ivan Ravasenga on the model from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKCONFIGITS_H +#define QUALITYCONTROL_TRENDINGTASKCONFIGITS_H + +#include "QualityControl/PostProcessingConfig.h" +#include +#include + +namespace o2::quality_control::postprocessing +{ + +// todo pretty print +/// \brief TrendingTask configuration structure +struct TrendingTaskConfigITS : PostProcessingConfig { + TrendingTaskConfigITS() = default; + TrendingTaskConfigITS(std::string id, + const boost::property_tree::ptree& config); + ~TrendingTaskConfigITS() = default; + + struct Plot { + std::string name; + std::string title; + std::string varexp; + std::string selection; + std::string option; + }; + + struct DataSource { + std::string type; + std::string path; + std::string name; + std::string reductorName; + std::string moduleName; + }; + + std::vector plots; + std::vector dataSources; + + uint64_t maxObjectTimeShiftMs = 0; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRENDINGTASKCONFIG_H diff --git a/Modules/ITS/include/ITS/TrendingTaskITSCluster.h b/Modules/ITS/include/ITS/TrendingTaskITSCluster.h new file mode 100644 index 0000000000..afe4a8611d --- /dev/null +++ b/Modules/ITS/include/ITS/TrendingTaskITSCluster.h @@ -0,0 +1,103 @@ + +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSCluster.h +/// \author Ivan Ravasenga on the structure from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKITSCLUSTER_H +#define QUALITYCONTROL_TRENDINGTASKITSCLUSTER_H + +#include "ITS/TrendingTaskConfigITS.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" + +#include +#include +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +/// \brief A post-processing task which trends values, stores them in a TTree +/// and produces plots. +/// +/// A post-processing task which trends objects inside QC database (QCDB). It +/// extracts some values of one or multiple +/// objects using the Reductor classes, then stores them inside a TTree. One can +/// generate plots out the TTree - the +/// class exposes the TTree::Draw interface to the user. The TTree and plots are +/// stored in the QCDB. The class is +/// configured with configuration files, see Framework/postprocessing.json as an +/// example. +/// +/// \author Ivan Ravasenga on the structure from Piotr Konopka +class TrendingTaskITSCluster : public PostProcessingInterface +{ + public: + TrendingTaskITSCluster() = default; + ~TrendingTaskITSCluster() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + // other functions (mainly style) + void SetLegendStyle(TLegend* leg); + void SetGraphStyle(TGraph* g, int col, int mkr); + void SetGraphNameAndAxes(TH1* g, std::string name, std::string title, + std::string xtitle, std::string ytitle, double ymin, + double ymax, std::vector runlist); + void PrepareLegend(TLegend* leg, int layer); + + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, repository::DatabaseInterface& qcdb); + void storePlots(repository::DatabaseInterface& qcdb); + void storeTrend(repository::DatabaseInterface& qcdb); + + TrendingTaskConfigITS mConfig; + MetaData mMetaData; + Int_t ntreeentries = 0; + UInt_t mTime; + std::vector runlist; + std::unique_ptr mTrend; + std::unordered_map> mReductors; + + const int col[7] = { 1, 2, 3, 4, 5, 6, 7 }; + const int mkr[7] = { 8, 29, 34, 21, 22, 33, 23 }; + static constexpr int NLAYERS = 7; + static constexpr int NTRENDSCLUSTER = 4; + const int nStaves[NLAYERS] = { 12, 16, 20, 24, 30, 42, 48 }; + // const int nStaves[NLAYERS] = {48}; + const std::string trendtitles[NTRENDSCLUSTER] = { "Cluster Size", + "Stddev Cluster", "Cluster Number of Active chips", "Cluster Occupancy" }; + const std::string trendnames[NTRENDSCLUSTER] = { "mean", "rms", "activechips", "occupancy" }; + const std::string ytitles[NTRENDSCLUSTER] = { + "Avg. Cluster Size (pixels)", "Stddev Cluster (/event/pixel)", "Cluster # Active chips", "Cluster Occupancy (/chip/event)" + }; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRENDINGTASKITSCLUSTER_H diff --git a/Modules/ITS/include/ITS/TrendingTaskITSError.h b/Modules/ITS/include/ITS/TrendingTaskITSError.h new file mode 100644 index 0000000000..c6b3209548 --- /dev/null +++ b/Modules/ITS/include/ITS/TrendingTaskITSError.h @@ -0,0 +1,103 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSError.h +/// \author Mariia Selina on the structure from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKITSError_H +#define QUALITYCONTROL_TRENDINGTASKITSError_H + +#include "ITS/TrendingTaskConfigITS.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" +#include "ITSMFTReconstruction/DecodingStat.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +/// \brief A post-processing task which trends values, stores them in a TTree +/// and produces plots. +/// +/// A post-processing task which trends objects inside QC database (QCDB). It +/// extracts some values of one or multiple +/// objects using the Reductor classes, then stores them inside a TTree. One can +/// generate plots out the TTree - the +/// class exposes the TTree::Draw interface to the user. The TTree and plots are +/// stored in the QCDB. The class is +/// configured with configuration files, see Framework/postprocessing.json as an +/// example. +/// + +class TrendingTaskITSError : public PostProcessingInterface +{ + public: + TrendingTaskITSError() = default; + ~TrendingTaskITSError() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + // other functions; mainly style of plots + void SetLegendStyle(TLegend* legend, const std::string& name, bool isRun); + void SetCanvasSettings(TCanvas* canvas); + void SetGraphStyle(TGraph* graph, const std::string& name, const std::string& title, int col, int mkr); + void SetGraphName(TMultiGraph* graph, const std::string& name, const std::string& title); + void SetGraphAxes(TMultiGraph* graph, const std::string& xtitle, + const std::string& ytitle, bool isTime); + void SetHistoAxes(TH1* hist, const std::vector& runlist, + const double& Ymin, const double& Ymax); + + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, repository::DatabaseInterface& qcdb); + void storePlots(repository::DatabaseInterface& qcdb); + void storeTrend(repository::DatabaseInterface& qcdb); + + TrendingTaskConfigITS mConfig; + MetaData mMetaData; + UInt_t mTime; + Int_t nEntries = 0; + + std::vector runlist; + std::unique_ptr mTrend; + std::unordered_map> mReductors; + + const int colors[30] = { 1, 46, kAzure + 3, 807, 797, 827, 417, 841, 868, 867, 860, 602, 921, 874, 600, 820, 400, 840, 920, 616, 632, 432, 880, 416, 29, 900, kMagenta - 9, kOrange + 4, kGreen - 5, kPink - 9 }; + const int markers[30] = { 8, 20, 21, 22, 23, 25, 26, 27, 29, 30, 32, 33, 34, 39, 41, 43, 45, 47, 48, 49, 105, 107, 112, 114, 116, 117, 118, 119, 120, 121 }; + + // const std::string trend_titles[o2::itsmft::GBTLinkDecodingStat::NErrorsDefined] = { "NoRDHAtStart", "PageNotStopped", "StopPageNotEmpty", "PageCounterDiscontinuity", "RDHvsGBTHPageCnt", "MissingGBTTrigger", "MissingGBTHeader", "MissingGBTTrailer", "NonZeroPageAfterStop", "DataForStoppedLane", "NoDataForActiveLane", "IBChipLaneMismatch", "CableDataHeadWrong", "InvalidActiveLanes", "PacketCounterJump", "PacketDoneMissing", "MissingDiagnosticWord", "GBTWordNotRecognized", "WrongeCableID", "WrongAlignmentWord", "MissingROF", "OldROF" }; +}; +} // namespace o2::quality_control::postprocessing +#endif // QUALITYCONTROL_TRENDINGTASKITSError_H diff --git a/Modules/ITS/include/ITS/TrendingTaskITSFEE.h b/Modules/ITS/include/ITS/TrendingTaskITSFEE.h new file mode 100644 index 0000000000..e4c54e0268 --- /dev/null +++ b/Modules/ITS/include/ITS/TrendingTaskITSFEE.h @@ -0,0 +1,104 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSFEE.h +/// \author Artem Kotliarov on the structure from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKITSFEE_H +#define QUALITYCONTROL_TRENDINGTASKITSFEE_H + +#include "ITS/TrendingTaskConfigITS.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +/// \brief A post-processing task which trends values, stores them in a TTree +/// and produces plots. +/// +/// A post-processing task which trends objects inside QC database (QCDB). It +/// extracts some values of one or multiple +/// objects using the Reductor classes, then stores them inside a TTree. One can +/// generate plots out the TTree - the +/// class exposes the TTree::Draw interface to the user. The TTree and plots are +/// stored in the QCDB. The class is +/// configured with configuration files, see Framework/postprocessing.json as an +/// example. +/// + +class TrendingTaskITSFEE : public PostProcessingInterface +{ + public: + TrendingTaskITSFEE() = default; + ~TrendingTaskITSFEE() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + // other functions; mainly style of plots + void SetLegendStyle(TLegend* legend, const std::string& name, bool isRun); + void SetCanvasSettings(TCanvas* canvas); + void SetGraphStyle(TGraph* graph, const std::string& name, const std::string& title, int col, int mkr); + void SetGraphName(TMultiGraph* graph, const std::string& name, const std::string& title); + void SetGraphAxes(TMultiGraph* graph, const std::string& xtitle, + const std::string& ytitle, bool isTime); + void SetHistoAxes(TH1* hist, const std::vector& runlist, + const double& Ymin, const double& Ymax); + + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, repository::DatabaseInterface& qcdb); + void storePlots(repository::DatabaseInterface& qcdb); + void storeTrend(repository::DatabaseInterface& qcdb); + + TrendingTaskConfigITS mConfig; + MetaData mMetaData; + UInt_t mTime; + Int_t nEntries = 0; + + std::vector runlist; + std::unique_ptr mTrend; + std::unordered_map> mReductors; + + const int colors[14] = { 1, 2, kAzure + 3, 807, 797, 827, 417, 841, 868, 867, 860, 602, 921, 874 }; + const int markers[14] = { 20, 21, 22, 24, 25, 26, 27, 29, 30, 32, 33, 34, 43, 47 }; + + static constexpr int nFlags = 3; + static constexpr int nITSparts = 4; + static constexpr int nTriggers = 13; + + const std::string mTriggerType[nTriggers] = { "ORBIT", "HB", "HBr", "HC", "PHYSICS", "PP", "CAL", "SOT", "EOT", "SOC", "EOC", "TF", "INT" }; + const std::string trend_titles[nFlags] = { "Warnings", "Errors", "Faults" }; + const std::string itsParts[nITSparts] = { "IB", "ML", "OL", "Global" }; +}; +} // namespace o2::quality_control::postprocessing +#endif // QUALITYCONTROL_TRENDINGTASKITSFEE_H diff --git a/Modules/ITS/include/ITS/TrendingTaskITSFhr.h b/Modules/ITS/include/ITS/TrendingTaskITSFhr.h new file mode 100644 index 0000000000..71231aa08f --- /dev/null +++ b/Modules/ITS/include/ITS/TrendingTaskITSFhr.h @@ -0,0 +1,109 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSFhr.h +/// \author Ivan Ravasenga on the structure from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKITSFHR_H +#define QUALITYCONTROL_TRENDINGTASKITSFHR_H + +#include "ITS/TrendingTaskConfigITS.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" + +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +/// \brief A post-processing task which trends values, stores them in a TTree +/// and produces plots. +/// +/// A post-processing task which trends objects inside QC database (QCDB). It +/// extracts some values of one or multiple +/// objects using the Reductor classes, then stores them inside a TTree. One can +/// generate plots out the TTree - the +/// class exposes the TTree::Draw interface to the user. The TTree and plots are +/// stored in the QCDB. The class is +/// configured with configuration files, see Framework/postprocessing.json as an +/// example. +/// +/// \author Ivan Ravasenga on the structure from Piotr Konopka +class TrendingTaskITSFhr : public PostProcessingInterface +{ + public: + TrendingTaskITSFhr() = default; + ~TrendingTaskITSFhr() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + // other functions (mainly style) + void SetLegendStyle(TLegend* leg); + void SetGraphStyle(TGraph* g, int col, int mkr); + void SetGraphNameAndAxes(TH1* g, std::string title, + std::string xtitle, std::string ytitle, double ymin, + double ymax, std::vector runlist); + void PrepareLegend(TLegend* leg, int layer); + + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, repository::DatabaseInterface& qcdb); + void storePlots(repository::DatabaseInterface& qcdb); + void storeTrend(repository::DatabaseInterface& qcdb); + + TrendingTaskConfigITS mConfig; + MetaData mMetaData; + Int_t ntreeentries = 0; + UInt_t mTime; + std::vector runlist; + std::unique_ptr mTrend; + std::unordered_map> mReductors; + + const int col[7] = { 1, 2, 3, 4, 5, 6, 7 }; + const int mkr[8] = { 20, 21, 22, 29, 24, 25, 26, 30 }; + static constexpr int NLAYERS = 7; + static constexpr int NTRENDSFHR = 4; + const int nStaves[NLAYERS] = { 12, 16, 20, 24, 30, 42, 48 }; + const std::string trendtitlesIB[NTRENDSFHR] = { "Fake-hit rate", + "Stddev Fake-hit rate", "Number of Active chips", "Occupancy" }; + const std::string trendtitlesOB[NTRENDSFHR] = { "Fake-hit rate", + "Stddev Fake-hit rate", "Number of Active lanes", "Occupancy" }; + const std::string trendnamesIB[NTRENDSFHR] = { "mean", "rms", "activechips", "occupancy" }; + const std::string trendnamesOB[NTRENDSFHR] = { "mean", "rms", "activelanes", "occupancy" }; + const std::string ytitlesIB[NTRENDSFHR] = { + "Fake-hit rate (/event/pixel)", "Stddev Fake-hit rate (/event/pixel)", "# Active chips", "Occupancy (/event)" + }; + const std::string ytitlesOB[NTRENDSFHR] = { + "Fake-hit rate (/event/pixel)", "Stddev Fake-hit rate (/event/pixel)", "# Active lanes", "Occupancy (/event)" + }; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRENDINGTASKITSFHR_H diff --git a/Modules/ITS/include/ITS/TrendingTaskITSThr.h b/Modules/ITS/include/ITS/TrendingTaskITSThr.h new file mode 100755 index 0000000000..2311a9ad28 --- /dev/null +++ b/Modules/ITS/include/ITS/TrendingTaskITSThr.h @@ -0,0 +1,105 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSThr.h +/// \author Ivan Ravasenga on the structure from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKITSTHR_H +#define QUALITYCONTROL_TRENDINGTASKITSTHR_H + +#include "ITS/TrendingTaskConfigITS.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +/// \brief A post-processing task which trends values, stores them in a TTree +/// and produces plots. +/// +/// A post-processing task which trends objects inside QC database (QCDB). It +/// extracts some values of one or multiple +/// objects using the Reductor classes, then stores them inside a TTree. One can +/// generate plots out the TTree - the +/// class exposes the TTree::Draw interface to the user. The TTree and plots are +/// stored in the QCDB. The class is +/// configured with configuration files, see Framework/postprocessing.json as an +/// example. +/// +/// \author Ivan Ravasenga on the structure from Piotr Konopka +class TrendingTaskITSThr : public PostProcessingInterface +{ + public: + TrendingTaskITSThr() = default; + ~TrendingTaskITSThr() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + // other functions (mainly style) + void SetLegendStyle(TLegend* leg); + void SetGraphStyle(TGraph* g, int col, int mkr); + void SetGraphNameAndAxes(TH1* g, std::string name, std::string title, + std::string xtitle, std::string ytitle, double ymin, + double ymax, std::vector runlist); + void PrepareLegend(TLegend* leg, int layer); + + private: + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, repository::DatabaseInterface& qcdb); + void storePlots(repository::DatabaseInterface& qcdb); + void storeTrend(repository::DatabaseInterface& qcdb); + + TrendingTaskConfigITS mConfig; + MetaData mMetaData; + UInt_t mTime; + std::unique_ptr mTrend; + std::vector runlist; + Int_t ntreeentries = 0; + std::unordered_map> mReductors; + + const int col[7] = { 1, 2, 3, 4, 5, 6, 7 }; + const int mkr[3] = { 8, 29, 34 }; + static constexpr int NLAYERS = 7; + static constexpr int NTRENDSTHR = 3; + const int nStaves[NLAYERS] = { 12, 16, 20, 24, 30, 42, 48 }; + const std::string trendtitles[NTRENDSTHR] = { "Threshold mean", + "Threshold rms", "Active Chips" }; + const std::string trendnames[NTRENDSTHR] = { "mean", "rms", "Nchips" }; + const std::string ytitles[NTRENDSTHR] = { + "Threshold mean (electrons)", "Threshold rms (electrons)", "# Active Chips" + }; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRENDINGTASKITSTHR_H diff --git a/Modules/ITS/include/ITS/TrendingTaskITSTracks.h b/Modules/ITS/include/ITS/TrendingTaskITSTracks.h new file mode 100644 index 0000000000..f9be9a14a0 --- /dev/null +++ b/Modules/ITS/include/ITS/TrendingTaskITSTracks.h @@ -0,0 +1,95 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSTracks.h +/// \author Ivan Ravasenga on the structure from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKITSTRACKS_H +#define QUALITYCONTROL_TRENDINGTASKITSTRACKS_H + +#include "ITS/TrendingTaskConfigITS.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" + +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +/// \brief A post-processing task which trends values, stores them in a TTree +/// and produces plots. +/// +/// A post-processing task which trends objects inside QC database (QCDB). It +/// extracts some values of one or multiple +/// objects using the Reductor classes, then stores them inside a TTree. One can +/// generate plots out the TTree - the +/// class exposes the TTree::Draw interface to the user. The TTree and plots are +/// stored in the QCDB. The class is +/// configured with configuration files, see Framework/postprocessing.json as an +/// example. +/// +/// \author Ivan Ravasenga on the structure from Piotr Konopka +class TrendingTaskITSTracks : public PostProcessingInterface +{ + public: + TrendingTaskITSTracks() = default; + ~TrendingTaskITSTracks() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + // other functions (mainly style) + void SetLegendStyle(TLegend* leg); + void SetGraphStyle(TGraph* g, int col, int mkr); + void SetGraphNameAndAxes(TGraph* g, std::string name, std::string title, + std::string xtitle, std::string ytitle, double ymin, + double ymax, std::vector runlist); + void PrepareLegend(TLegend* leg, int layer); + + private: + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, repository::DatabaseInterface& qcdb); + void storePlots(repository::DatabaseInterface& qcdb); + void storeTrend(repository::DatabaseInterface& qcdb); + + TrendingTaskConfigITS mConfig; + MetaData mMetaData; + UInt_t mTime; + std::unique_ptr mTrend; + std::vector runlist; + Int_t ntreeentries = 0; + std::unordered_map> mReductors; + + static constexpr int NTRENDSTRACKS = 9; + const int col[2] = { 1, 4 }; + const int mkr[2] = { 21, 21 }; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRENDINGTASKITSTRACKS_H diff --git a/Modules/ITS/its.json b/Modules/ITS/its.json new file mode 100644 index 0000000000..5844763626 --- /dev/null +++ b/Modules/ITS/its.json @@ -0,0 +1,58 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + } + }, + "tasks": { + "QcTest3": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSRawTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "60", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "its-raw" + }, + "taskParameters": { + "nothing": "rien" + }, + "location": "remote" + } + } + }, + "dataSamplingPolicies": [ + { + "id": "its-raw", + "active": "true", + "machines": [], + "query": "digits:ITS/DIGITS/0;in:ITS/TEST/0;Error:ITS/Error/0;Run:ITS/Run/0;File:ITS/File/0;Finish:ITS/Finish/0;EP:ITS/EP/0;Events:ITS/Events/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1.0", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} + diff --git a/Modules/ITS/itsChipStatus.json b/Modules/ITS/itsChipStatus.json new file mode 100644 index 0000000000..819ef27dcb --- /dev/null +++ b/Modules/ITS/itsChipStatus.json @@ -0,0 +1,66 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "ITSChipStatus": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSChipStatusTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "chipstatus:ITS/CHIPSSTATUS/0" + }, + "location": "local", + "taskParameters": { + "nQCCycleToMonitor": "10", + "nQCCycleToOverview": "1", + "nRotationType": "0" + } + } + }, + "checks": { + "ChipStatusCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSChipStatusCheck", + "moduleName": "QcITS", + "policy": "OnEachSeparately", + "detectorName": "ITS", + "checkParameters": { + "feeidlimitsIB": "1,1", + "feeidlimitsML": "1,0.87", + "feeidlimitsOL": "1,0.92", + "excludedfeeid": "" + }, + "dataSource": [{ + "type": "Task", + "name": "ITSChipStatus", + "MOs": ["StaveStatusOverview"] + }] + } + } + + } + } diff --git a/Modules/ITS/itsCluster.json b/Modules/ITS/itsCluster.json new file mode 100644 index 0000000000..5298554303 --- /dev/null +++ b/Modules/ITS/itsCluster.json @@ -0,0 +1,159 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "PHYSICS", + "beamType" : "PbPb", "": "Beam type: `pp`, `PbPb`, `pPb` " + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://alice-ccdb.cern.ch" + } + }, + "tasks": { + "ITSClusterTask": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSClusterTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "180", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "compclus" + }, + "location": "remote", + "taskParameters": { + "layer": "1111111", + "nThreads": "1", + "nBCbins": "103", + "dicttimestamp": "0", + "geomPath": "./", + "publishSummary1D": "0", + "publishDetailedSummary": "1" + } + } + }, + "checks": { + "ITSClusterCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSClusterCheck", + "moduleName": "QcITS", + "policy": "OnEachSeparately", + "detectorName": "ITS", + "checkParameters": { + "maxcluoccL0": "5", + "maxcluoccL1": "4", + "maxcluoccL2": "3", + "maxcluoccL3": "2", + "maxcluoccL4": "1", + "maxcluoccL5": "1", + "maxcluoccL6": "1", + "MaxEmptyLaneFraction": "0.1", + "skipxbinsoccupancy": "", + "skipybinsoccupancy": "", + "plotWithTextMessage": "", + "textMessage": "" + + }, + "extendedCheckParameters": { + "default": { + "default": { + "maxcluoccL0": "5", + "maxcluoccL1": "3", + "maxcluoccL2": "3", + "maxcluoccL3": "0.3", + "maxcluoccL4": "0.3", + "maxcluoccL5": "0.2", + "maxcluoccL6": "0.2" + } + }, + "PHYSICS": { + "default": { + "maxcluoccL0": "5", + "maxcluoccL1": "3", + "maxcluoccL2": "3", + "maxcluoccL3": "0.3", + "maxcluoccL4": "0.3", + "maxcluoccL5": "0.2", + "maxcluoccL6": "0.2" + }, + "pp": { + "maxcluoccL0": "5", + "maxcluoccL1": "3", + "maxcluoccL2": "3", + "maxcluoccL3": "0.3", + "maxcluoccL4": "0.3", + "maxcluoccL5": "0.2", + "maxcluoccL6": "0.2" + }, + "PbPb": { + "maxcluoccL0": "65", + "maxcluoccL1": "35", + "maxcluoccL2": "25", + "maxcluoccL3": "1", + "maxcluoccL4": "1", + "maxcluoccL5": "1", + "maxcluoccL6": "1" + } + }, + "COSMICS": { + "maxcluoccL0": "5", + "maxcluoccL1": "3", + "maxcluoccL2": "3", + "maxcluoccL3": "0.3", + "maxcluoccL4": "0.3", + "maxcluoccL5": "0.2", + "maxcluoccL6": "0.2" + } + }, + "dataSource": [ + { + "type": "Task", + "name": "ITSClusterTask", + "MOs": [ + "Layer0/AverageClusterSize", + "Layer1/AverageClusterSize", + "Layer2/AverageClusterSize", + "Layer3/AverageClusterSize", + "Layer4/AverageClusterSize", + "Layer5/AverageClusterSize", + "Layer6/AverageClusterSize", + "EmptyLaneFractionGlobal", + "General/General_Occupancy" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "compclus", + "active": "true", + "machines": [], + "query": "compclus:ITS/COMPCLUSTERS/0;clustersrof:ITS/CLUSTERSROF/0;patterns:ITS/PATTERNS/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/ITS/itsDecoding.json b/Modules/ITS/itsDecoding.json new file mode 100644 index 0000000000..fdd222a24b --- /dev/null +++ b/Modules/ITS/itsDecoding.json @@ -0,0 +1,83 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "ITSDECODING": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSDecodingErrorTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query": "linkerrors:ITS/LinkErrors/0;decerrors:ITS/ChipErrors/0" + }, + "location": "local", + "taskParameters": { + "isDoLinkErrorReset": 1, + "mBusyViolationLimit": "0.75" + } + } + }, + "checks": { + "DecodingErrorCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSDecodingErrorCheck", + "moduleName": "QcITS", + "policy": "OnEachSeparately", + "detectorName": "ITS", + "checkParameters": { + "checkfeeIDonlyonce": "true", + "DecLinkErrorLimits": "5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, -1", + "DecLinkErrorLimitsRatio": "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1", + "DecLinkErrorType": "0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0", + "plotWithTextMessage": "", + "textMessage": "" + }, + "dataSource": [{ + "type": "Task", + "name": "ITSDECODING", + "MOs": ["General/LinkErrorPlots","General/ChipErrorPlots"] + }] + } + } + + }, + "dataSamplingPolicies": [ + { + "id": "linkerrors", + "active": "true", + "machines": [], + "query": "linkerrors:ITS/LinkErrors/0;decerrors:ITS/ChipErrors/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/ITS/itsEPN.json b/Modules/ITS/itsEPN.json new file mode 100644 index 0000000000..719c971fb6 --- /dev/null +++ b/Modules/ITS/itsEPN.json @@ -0,0 +1,121 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "ITSClusterTask": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSClusterTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "180", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "compclus" + }, + "location": "remote", + "taskParameters": { + "layer": "1111111", + "clusterDictionaryPath": "/home/epn/odc/files/ITSdictionary.bin", + "runNumberPath": "/home/its/QC/workdir/infiles/RunNumber.dat", + "geomPath": "/home/epn/odc/files/o2sim_geometry-aligned.root", + "nThreads": "4" + } + }, + "ITSTrackTask": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSTrackTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "30", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tracks" + }, + "location": "remote" + } + }, + "checks": { + "ITSClusterCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSClusterCheck", + "moduleName": "QcITS", + "policy": "OnAny", + "detectorName": "ITS", + "dataSource": [ + { + "type": "Task", + "name": "ITSClusterTask", + "MOs": ["Layer0/AverageClusterSize","Layer1/AverageClusterSize","Layer2/AverageClusterSize","Layer3/AverageClusterSize","Layer4/AverageClusterSize","Layer5/AverageClusterSize","Layer6/AverageClusterSize", "Layer0/ClusterOccupation","Layer1/ClusterOccupation","Layer2/ClusterOccupation","Layer3/ClusterOccupation","Layer4/ClusterOccupation", "Layer5/ClusterOccupation", "Layer6/ClusterOccupation"] + } + ] + }, + "ITSTrackCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSTrackCheck", + "moduleName": "QcITS", + "policy": "OnAny", + "detectorName": "ITS", + "dataSource": [ + { + "type": "Task", + "name": "ITSTrackTask", + "MOs" : ["NClusters","PhiDistribution","AngularDistribution","ClusterUsage","EtaDistribution","VertexCoordinates","VertexRvsZ","VertexZ"] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "compclus", + "active": "true", + "machines": [], + "query": "compclus:ITS/COMPCLUSTERS/0;clustersrof:ITS/CLUSTERSROF/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.01", + "seed": "1441" + } + ], + "blocking": "false" + }, + { + "id": "tracks", + "active": "true", + "machines": [], + "query": "tracks:ITS/TRACKS/0;rofs:ITS/ITSTrackROF/0;compclus:ITS/COMPCLUSTERS/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.01", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/ITS/itsFLP.json b/Modules/ITS/itsFLP.json new file mode 100644 index 0000000000..49724de92b --- /dev/null +++ b/Modules/ITS/itsFLP.json @@ -0,0 +1,123 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "FHRTask": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFhrTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "RAWDATA" + }, + "location": "remote", + "taskParameters": { + "Layer": "2", + "HitNumberCut": "512", + "GetTFFromBinding": "0", + "decoderThreads": "8", + "geomPath": "/tmp/qc_tmp/o2sim", + "HitNumberCutForNoisyPixel": "0", + "OccupancyNumberCutForNoisyPixel": "0.000001", + "MaxGeneralAxisRange": "-2", + "MinGeneralAxisRange": "-12", + "MaxGeneralNoisyAxisRange": "5000", + "MinGeneralNoisyAxisRange": "0" + } + }, + "ITSFEE": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFeeTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "feedata" + }, + "location": "remote" + } + }, + "checks": { + "FHRCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFhrCheck", + "moduleName": "QcITS", + "policy": "OnAny", + "detectorName": "ITS", + "dataSource": [{ + "type": "Task", + "name": "FHRTask", + "MOs": ["General/ErrorPlots","General/General_Occupancy","General/Noisy_Pixel","Occupancy/Layer0/Layer0ChipStave","Occupancy/Layer1/Layer1ChipStave","Occupancy/Layer2/Layer2ChipStave","Occupancy/Layer3/Layer3ChipStave","Occupancy/Layer4/Layer4ChipStave","Occupancy/Layer5/Layer5ChipStave","Occupancy/Layer6/Layer6ChipStave"] + }] + }, + "ITSFeeCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFeeCheck", + "moduleName": "QcITS", + "policy": "OnAny", + "detectorName": "ITS", + "dataSource": [{ + "type": "Task", + "name": "ITSFEE", + "MOs": ["LaneStatus/laneStatusFlagFAULT","LaneStatus/laneStatusFlagERROR","LaneStatus/laneStatusFlagWARNING"] + }] + } + } + + }, + "dataSamplingPolicies": [ + { + "id": "RAWDATA", + "active": "true", + "machines": [], + "query": "filter:ITS/RAWDATA;G:FLP/DISTSUBTIMEFRAME/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1441" + } + ], + "blocking": "false" + }, + { + "id": "feedata", + "active": "true", + "machines": [], + "query": "filter:ITS/RAWDATA;G:FLP/DISTSUBTIMEFRAME/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} + diff --git a/Modules/ITS/itsFee-no-ds.json b/Modules/ITS/itsFee-no-ds.json new file mode 100644 index 0000000000..539128e5d1 --- /dev/null +++ b/Modules/ITS/itsFee-no-ds.json @@ -0,0 +1,65 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "ITSFee": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFeeTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "filter:ITS/RAWDATA;G:FLP/DISTSUBTIMEFRAME/0" + }, + "location": "remote", + "taskParameters": { + "NPayloadSizeBins": "4096" + } + } + }, + "checks": { + "ITSFeeCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFeeCheck", + "moduleName": "QcITS", + "policy": "OnAny", + "detectorName": "ITS", + "dataSource": [ + { + "type": "Task", + "name": "ITSFEE", + "MOs": [ + "LaneStatus/laneStatusFlagFAULT", + "LaneStatus/laneStatusFlagERROR", + "LaneStatus/laneStatusFlagWARNING" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [ + ] +} diff --git a/Modules/ITS/itsFee.json b/Modules/ITS/itsFee.json new file mode 100644 index 0000000000..a07981973b --- /dev/null +++ b/Modules/ITS/itsFee.json @@ -0,0 +1,116 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "PHYSICS", + "beamType" : "PbPb", "": "Beam type: `pp`, `PbPb`, `pPb` " + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "ITSFEE": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFeeTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "filter:ITS/RAWDATA" + }, + "location": "local", + "taskParameters": { + "NPayloadSizeBins": "4096", + "ResetLaneStatus": "1", + "ResetPayload": "0", + "EnableIHWReading": "1", + "PayloadParsingEvery_n_HBFperTF": "0", + "PayloadParsingEvery_n_TF": "1", + "DecodeCDW": "0", + "nResetCycle": "3", + "precisePayload": "1" + } + } + }, + "checks": { + "ITSFeeCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFeeCheck", + "moduleName": "QcITS", + "policy": "OnEachSeparately", + "detectorName": "ITS", + "checkParameters": { + "skipbinstrg": "", + "skipfeeids": "", + "maxTFdifferenceAllowed": "1000000000", + "maxbadchipsIB": "2", + "maxbadlanesML": "4", + "maxbadlanesOL": "7", + "minPayloadSize": "1400", + "maxfractionbadlanes": "0.1", + "expectedROFperOrbit": "18", + "plotWithTextMessage": "", + "textMessage": "" + }, + "extendedCheckParameters": { + "default": { + "default": { + "expectedROFperOrbit": "18" + } + }, + "PHYSICS": { + "default": { + "expectedROFperOrbit": "18" + }, + "pp": { + "expectedROFperOrbit": "18" + }, + "PbPb": { + "expectedROFperOrbit": "6" + } + }, + "COSMICS": { + "expectedROFperOrbit": "18" + } + }, + "dataSource": [ + { + "type": "Task", + "name": "ITSFEE", + "MOs": [ + "LaneStatus/laneStatusFlagFAULT", + "LaneStatus/laneStatusFlagERROR", + "LaneStatus/laneStatusFlagWARNING", + "LaneStatus/laneStatusOverviewFlagERROR", + "LaneStatus/laneStatusOverviewFlagWARNING", + "LaneStatusSummary/LaneStatusSummaryGlobal", + "RDHSummary", + "TriggerVsFeeid", + "TriggerVsFeeid_reset", + "PayloadSize", + "TrailerCount", + "TrailerCount_reset" + + ] + } + ] + } + } + } +} diff --git a/Modules/ITS/itsFhr.json b/Modules/ITS/itsFhr.json new file mode 100644 index 0000000000..d5ccdde3c0 --- /dev/null +++ b/Modules/ITS/itsFhr.json @@ -0,0 +1,99 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://alice-ccdb.cern.ch" + } + }, + "tasks": { + "FHRTask": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFhrTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "RAWDATA" + }, + "location": "remote", + "taskParameters": { + "Layer": "4", + "HitNumberCut": "0", + "decoderThreads": "1", + "HitNumberCutForNoisyPixel": "0", + "OccupancyNumberCutForNoisyPixel": "0.000001", + "MaxGeneralAxisRange": "-2", + "MinGeneralAxisRange": "-12", + "MaxGeneralNoisyAxisRange": "5000", + "MinGeneralNoisyAxisRange": "0", + "Etabins": "130", + "Phibins": "240", + "geomPath": "./", + "CutSparseTF": "1", + "DoHitmapFilter": "1", + "PhysicalOccupancyIB": "1.7e-3", + "PhysicalOccupancyOB": "4.3e-5", + "IgnoreRampUpData": "true" + } + } + }, + "checks": { + "FHRCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFhrCheck", + "moduleName": "QcITS", + "policy": "OnEachSeparately", + "detectorName": "ITS", + "checkParameters": { + "fhrcutIB": "0.01", + "fhrcutOB": "0.0001", + "skipbins": "", + "plotWithTextMessage": "", + "textMessage": "" + + }, + "dataSource": [{ + "type": "Task", + "name": "FHRTask", + "MOs": ["General/ErrorPlots","General/General_Occupancy","General/Noisy_Pixel","Occupancy/Layer0/Layer0ChipStave","Occupancy/Layer1/Layer1ChipStave","Occupancy/Layer2/Layer2ChipStave","Occupancy/Layer3/Layer3ChipStave","Occupancy/Layer4/Layer4ChipStave","Occupancy/Layer5/Layer5ChipStave","Occupancy/Layer6/Layer6ChipStave"] + }] + } + } + + }, + "dataSamplingPolicies": [ + { + "id": "RAWDATA", + "active": "true", + "machines": [], + "query": "filter:ITS/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} + diff --git a/Modules/ITS/itsNoisyPixel.json b/Modules/ITS/itsNoisyPixel.json new file mode 100644 index 0000000000..d521de548c --- /dev/null +++ b/Modules/ITS/itsNoisyPixel.json @@ -0,0 +1,82 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://alice-ccdb.cern.ch" + } + }, + "tasks": { + "ITSNoisyPixelTask": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSNoisyPixelTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "180", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "compclus" + }, + "location": "remote", + "taskParameters": { + "layer": "1111111", + "queryOpton_comments": "from-digit: to use with query digitsrof:ITS/DIGITSROF/0;digits:ITS/DIGITS/0; from-cluster: to use with query compclus:ITS/COMPCLUSTERS/0;clustersrof:ITS/CLUSTERSROF/0", + "queryOption": "from-digit", + "orderedPlots_comment": "The following are used for the OrderedHitsAddress objects only. If one of them is set to -1, the production of those objects is disabled", + "orderedPlotsUpdateFrequency": "10000", + "orderedPlotsBinNumber": "25", + "dicttimestamp": "0", + "geomPath": "./", + "isLocalGeometry": "0", + "geomstamp": "1640991600000" + + + } + + + } + } + + }, + + "dataSamplingPolicies": [ + { + "id": "compclus", + "active": "true", + "machines": [], + "query": "digitsrof:ITS/DIGITSROF/0;digits:ITS/DIGITS/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1", + "seed": "1441" + } + ], + + "blocking": "false" + } + ] + +} + + + + + diff --git a/Modules/ITS/itsQCQualitySummary.json b/Modules/ITS/itsQCQualitySummary.json new file mode 100644 index 0000000000..ae8816719a --- /dev/null +++ b/Modules/ITS/itsQCQualitySummary.json @@ -0,0 +1,398 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "ITSFEE": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFeeTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "filter:ITS/RAWDATA" + }, + "location": "local", + "taskParameters": { + "NPayloadSizeBins": "4096", + "ResetLaneStatus": "1", + "ResetPayload": "0" + } + }, + "FHRTask": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFhrTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "RAWDATA" + }, + "location": "remote", + "taskParameters": { + "Layer": "3", + "HitNumberCut": "0", + "decoderThreads": "8", + "HitNumberCutForNoisyPixel": "0", + "OccupancyNumberCutForNoisyPixel": "0.000001", + "MaxGeneralAxisRange": "-2", + "MinGeneralAxisRange": "-12", + "MaxGeneralNoisyAxisRange": "5000", + "MinGeneralNoisyAxisRange": "0", + "Etabins": "130", + "Phibins": "240", + "geomPath": "./", + "CutSparseTF": "1", + "DoHitmapFilter": "1", + "PhysicalOccupancyIB": "1.7e-3", + "PhysicalOccupancyOB": "4.3e-5" + } + }, + "ITSClusterTask": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSClusterTask", + "moduleName": "QcITS", + "detectorName": "ITS", + "cycleDurationSeconds": "180", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "compclus" + }, + "location": "remote", + "taskParameters": { + "layer": "1111111", + "nThreads": "1", + "nBCbins": "103", + "dicttimestamp": "0", + "geomPath": "./", + "publishSummary1D": "0", + "publishDetailedSummary": "0" + } + }, +"ITSTrackTask" : { + "active" : "true", + "className" : "o2::quality_control_modules::its::ITSTrackTask", + "moduleName" : "QcITS", + "detectorName" : "ITS", + "cycleDurationSeconds" : "30", + "maxNumberCycles" : "-1", + "dataSource_comment" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "tracks" + }, + "location" : "remote", + "taskParameters" : { + "vertexXYsize" : "0.5", + "vertexZsize": "15", + "vertexRsize": "0.8", + "NtracksMAX" : "100", + "doTTree": "0", + "nBCbins": "103", + "dicttimestamp" : "0", + "doNorm" : "1" + } + + } + }, + + "checks" : { + "ITSFeeCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFeeCheck", + "moduleName": "QcITS", + "policy": "OnEachSeparately", + "detectorName": "ITS", + "checkParameters": { + "skipbinstrg": "", + "skipfeeids": "", + "maxbadchipsIB": "2", + "maxbadlanesML": "4", + "maxbadlanesOL": "7", + "maxfractionbadlanes": "0.1" + }, + "dataSource": [ + { + "type": "Task", + "name": "ITSFEE", + "MOs": [ + "LaneStatus/laneStatusFlagFAULT", + "LaneStatus/laneStatusFlagERROR", + "LaneStatus/laneStatusFlagWARNING", + "LaneStatus/laneStatusOverviewFlagFAULT", + "LaneStatus/laneStatusOverviewFlagERROR", + "LaneStatus/laneStatusOverviewFlagWARNING", + "LaneStatusSummary/LaneStatusSummaryGlobal", + "RDHSummary", + "TriggerVsFeeid", + "PayloadSize" + ] + } + ] + }, + "FHRCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSFhrCheck", + "moduleName": "QcITS", + "policy": "OnEachSeparately", + "detectorName": "ITS", + "checkParameters": { + "fhrcutIB": "0.01", + "fhrcutOB": "0.0001", + "skipbins": "" + }, + "dataSource": [{ + "type": "Task", + "name": "FHRTask", + "MOs": ["General/ErrorPlots","General/General_Occupancy","General/Noisy_Pixel","Occupancy/Layer0/Layer0ChipStave","Occupancy/Layer1/Layer1ChipStave","Occupancy/Layer2/Layer2ChipStave","Occupancy/Layer3/Layer3ChipStave","Occupancy/Layer4/Layer4ChipStave","Occupancy/Layer5/Layer5ChipStave","Occupancy/Layer6/Layer6ChipStave"] + }] + }, + "ITSClusterCheck": { + "active": "true", + "className": "o2::quality_control_modules::its::ITSClusterCheck", + "moduleName": "QcITS", + "policy": "OnEachSeparately", + "detectorName": "ITS", + "checkParameters": { + "maxcluoccL0": "5", + "maxcluoccL1": "4", + "maxcluoccL2": "3", + "maxcluoccL3": "2", + "maxcluoccL4": "1", + "maxcluoccL5": "1", + "maxcluoccL6": "1", + "skipxbinsoccupancy": "", + "skipybinsoccupancy": "" + }, + "dataSource": [ + { + "type": "Task", + "name": "ITSClusterTask", + "MOs": [ + "Layer0/AverageClusterSize", + "Layer1/AverageClusterSize", + "Layer2/AverageClusterSize", + "Layer3/AverageClusterSize", + "Layer4/AverageClusterSize", + "Layer5/AverageClusterSize", + "Layer6/AverageClusterSize", + "General/General_Occupancy" + ] + } + ] + }, + + "ITSTrackCheck" : { + "active" : "true", + "className" : "o2::quality_control_modules::its::ITSTrackCheck", + "moduleName" : "QcITS", + "policy" : "OnEachSeparately", + "detectorName" : "ITS", + "dataSource" : [ { + "type" : "Task", + "name" : "ITSTrackTask", + "MOs" : ["NClusters", + "PhiDistribution", + "AngularDistribution", + "EtaDistribution", + "VertexCoordinates", + "VertexRvsZ", + "VertexZ" + ] + } ] + } + + + + }, + "postprocessing": { + "ITSQualityTask": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QualityControl", + "detectorName": "ITS", + "qualityGroups": [ + { + "name" : "global", + "title" : "GLOBAL ITS QUALITY", + "path": "ITS/QO", + "ignoreQualitiesDetails" : ["Null", "Good", "Medium", "Bad"], + "inputObjects": [ + { + "name" : "ITSQuality/ITSQuality", + "title" : "ITS Quality", + "messageBad" : "Inform on-call immediately", + "messageMedium": "Add bookkeeping entry", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty!!!" + } + ] + }, + + { + "name" : "details", + "title" : "ITS DETAILS", + "path": "ITS/QO", + "ignoreQualitiesDetails" : [], + "inputObjects": [ + { + "name" : "FHRCheck/FHRTask/General/General_Occupancy", + "title" : "Hit Occupancy" + }, + { + "name" : "FHRCheck/FHRTask/General/ErrorPlots", + "title" : "Decoding Errors" + }, + + { + "name" : "ITSFeeCheck/ITSFEE/LaneStatus/laneStatusOverviewFlagFAULT", + "title" : "Lanes into FAULT" + }, + { + "name" : "ITSFeeCheck/ITSFEE/LaneStatus/laneStatusOverviewFlagERROR", + "title" : "Lanes into ERROR" + }, + { + "name" : "ITSFeeCheck/ITSFEE/LaneStatus/laneStatusOverviewFlagWARNING", + "title" : "Lanes into WARNING" + }, + { + "name" : "ITSFeeCheck/ITSFEE/LaneStatusSummary/LaneStatusSummaryGlobal", + "title" : "Lane status summary" + }, + { + "name" : "ITSFeeCheck/ITSFEE/TriggerVsFeeid", + "title" : "Trigger count Vs FeeID" + }, + { + "name" : "ITSClusterCheck/ITSClusterTask/General/General_Occupancy", + "title" : "Cluster occupancy" + }, + { + "name" : "ITSTrackCheck/ITSTrackTask/AngularDistribution", + "title" : "Track angular distribution" + }, + { + "name" : "ITSTrackCheck/ITSTrackTask/NClusters", + "title" : "NClusters" + } + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol", "10 minutes" + ] + } + }, + "aggregators": { + "ITSQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QualityControl", + "policy": "OnAny", + "detectorName": "ITS", + "dataSource": [ + { + "type": "Check", + "name": "FHRCheck", + "QOs" : ["FHRTask/General/General_Occupancy", "FHRTask/General/ErrorPlots" ] + }, + { + "type": "Check", + "name": "ITSFeeCheck", + "QOs": ["ITSFEE/LaneStatus/laneStatusOverviewFlagFAULT", "ITSFEE/LaneStatus/laneStatusOverviewFlagERROR", "ITSFEE/LaneStatus/laneStatusOverviewFlagWarning", "ITSFEE/LaneStatusSummary/LaneStatusSummaryGlobal", "ITSFEE/TriggerVsFeeid" ] + }, + { + "type": "Check", + "name": "ITSClusterCheck", + "QOs" : ["ITSClusterTask/General/General_Occupancy"] + }, + + { + "type": "Check", + "name": "ITSTrackCheck", + "QOs" : ["ITSTrackTask/AngularDistribution", "ITSTrackTask/NClusters" ] + } + ] + } + } + + + }, + "dataSamplingPolicies" : [ + { + "id" : "tracks", + "active" : "true", + "machines" : [], + "query" : "Verticesrof:ITS/VERTICESROF/0;Vertices:ITS/VERTICES/0;tracks:ITS/TRACKS/0;rofs:ITS/ITSTrackROF/0;clustersrof:ITS/CLUSTERSROF/0;compclus:ITS/COMPCLUSTERS/0;patterns:ITS/PATTERNS/0;clusteridx:ITS/TRACKCLSID/0", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "1", + "seed" : "1441" + } + ], + + "blocking" : "false" + }, + { + "id": "RAWDATA", + "active": "true", + "machines": [], + "query": "filter:ITS/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1", + "seed": "1441" + } + ], + "blocking": "false" + }, + { + "id": "compclus", + "active": "true", + "machines": [], + "query": "compclus:ITS/COMPCLUSTERS/0;clustersrof:ITS/CLUSTERSROF/0;patterns:ITS/PATTERNS/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] + + +} diff --git a/Modules/ITS/itsQCTrendingCluster.json b/Modules/ITS/itsQCTrendingCluster.json new file mode 100644 index 0000000000..b3ec55935e --- /dev/null +++ b/Modules/ITS/itsQCTrendingCluster.json @@ -0,0 +1,1517 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "ITSqcTrendingCluster": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTaskITSCluster", + "moduleName": "QualityControl", + "detectorName": "ITS", + "dataSources": [ + { + "type": "repository", + "paths": ["ITS/MO/ITSClusterTask/Layer0/AverageClusterSize", + "ITS/MO/ITSClusterTask/Layer1/AverageClusterSize", + "ITS/MO/ITSClusterTask/Layer2/AverageClusterSize", + "ITS/MO/ITSClusterTask/Layer3/AverageClusterSize", + "ITS/MO/ITSClusterTask/Layer4/AverageClusterSize", + "ITS/MO/ITSClusterTask/Layer5/AverageClusterSize", + "ITS/MO/ITSClusterTask/Layer6/AverageClusterSize"], + "names": ["Cluster_l0", + "Cluster_l1", + "Cluster_l2", + "Cluster_l3", + "Cluster_l4", + "Cluster_l5", + "Cluster_l6"], + "reductorName": "o2::quality_control_modules::its::TH2XlineReductor", + "moduleName": "QcITS" + }, + { + "type": "repository", + "paths": ["ITS/MO/ITSClusterTask/Layer0/ClusterOccupation", + "ITS/MO/ITSClusterTask/Layer1/ClusterOccupation", + "ITS/MO/ITSClusterTask/Layer2/ClusterOccupation", + "ITS/MO/ITSClusterTask/Layer3/ClusterOccupation", + "ITS/MO/ITSClusterTask/Layer4/ClusterOccupation", + "ITS/MO/ITSClusterTask/Layer5/ClusterOccupation", + "ITS/MO/ITSClusterTask/Layer6/ClusterOccupation"], + "names": ["Occupancy_l0", + "Occupancy_l1", + "Occupancy_l2", + "Occupancy_l3", + "Occupancy_l4", + "Occupancy_l5", + "Occupancy_l6"], + "reductorName": "o2::quality_control_modules::its::TH2XlineReductor", + "moduleName": "QcITS" + }, + { + "type": "repository", + "paths": ["ITS/MO/ITSClusterTask/Layer0/AverageClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer1/AverageClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer2/AverageClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer3/AverageClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer4/AverageClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer5/AverageClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer6/AverageClusterSizeSummary"], + "names": ["Cluster_Summary_l0", + "Cluster_Summary_l1", + "Cluster_Summary_l2", + "Cluster_Summary_l3", + "Cluster_Summary_l4", + "Cluster_Summary_l5", + "Cluster_Summary_l6"], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcITS" + }, + { + "type": "repository", + "paths": ["ITS/MO/ITSClusterTask/Layer0/AverageGroupedClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer1/AverageGroupedClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer2/AverageGroupedClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer3/AverageGroupedClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer4/AverageGroupedClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer5/AverageGroupedClusterSizeSummary", + "ITS/MO/ITSClusterTask/Layer6/AverageGroupedClusterSizeSummary"], + "names": ["Cluster_Grouped_l0", + "Cluster_Grouped_l1", + "Cluster_Grouped_l2", + "Cluster_Grouped_l3", + "Cluster_Grouped_l4", + "Cluster_Grouped_l5", + "Cluster_Grouped_l6"], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcITS" + } + + ], + "plots": [ + { + "names": ["avg_grouped_mean_of_L0", "avg_grouped_stddev_of_L0", "avg_cluster_mean_of_L0", "avg_cluster_stddev_of_L0"], + "title": ["Avg. Grouped Cluster distribution: L0", "Avg. Stddev Grouped Cluster: L0", "Avg. Cluster distribution: L0", "Avg. Stddev Cluster: L0"], + "varexp": ["Cluster_Grouped_l0.mean[0]:ntreeentries", "Cluster_Grouped_l0.stddev[0]:ntreeentries", "Cluster_Summary_l0.mean[0]:ntreeentries", "Cluster_Summary_l0.stddev[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["avg_grouped_mean_of_L1", "avg_grouped_stddev_of_L1", "avg_cluster_mean_of_L1", "avg_cluster_stddev_of_L1"], + "title": ["Avg. Grouped Cluster distribution: L1", "Avg. Stddev Grouped Cluster: L1", "Avg. Cluster distribution: L1", "Avg. Stddev Cluster: L1"], + "varexp": ["Cluster_Grouped_l1.mean[1]:ntreeentries", "Cluster_Grouped_l1.stddev[1]:ntreeentries", "Cluster_Summary_l1.mean[1]:ntreeentries", "Cluster_Summary_l1.stddev[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["avg_grouped_mean_of_L2", "avg_grouped_stddev_of_L2", "avg_cluster_mean_of_L2", "avg_cluster_stddev_of_L2"], + "title": ["Avg. Grouped Cluster distribution: L2", "Avg. Stddev Grouped Cluster: L2", "Avg. Cluster distribution: L2", "Avg. Stddev Cluster: L2"], + "varexp": ["Cluster_Grouped_l2.mean[2]:ntreeentries", "Cluster_Grouped_l2.stddev[2]:ntreeentries", "Cluster_Summary_l2.mean[2]:ntreeentries", "Cluster_Summary_l2.stddev[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["avg_grouped_mean_of_L3", "avg_grouped_stddev_of_L3", "avg_cluster_mean_of_L3", "avg_cluster_stddev_of_L3"], + "title": ["Avg. Grouped Cluster distribution: L3", "Avg. Stddev Grouped Cluster: L3", "Avg. Cluster distribution: L3", "Avg. Stddev Cluster: L3"], + "varexp": ["Cluster_Grouped_l3.mean[3]:ntreeentries", "Cluster_Grouped_l3.stddev[3]:ntreeentries", "Cluster_Summary_l3.mean[3]:ntreeentries", "Cluster_Summary_l3.stddev[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["avg_grouped_mean_of_L4", "avg_grouped_stddev_of_L4", "avg_cluster_mean_of_L4", "avg_cluster_stddev_of_L4"], + "title": ["Avg. Grouped Cluster distribution: L4", "Avg. Stddev Grouped Cluster: L4", "Avg. Cluster distribution: L4", "Avg. Stddev Cluster: L4"], + "varexp": ["Cluster_Grouped_l4.mean[4]:ntreeentries", "Cluster_Grouped_l4.stddev[4]:ntreeentries", "Cluster_Summary_l4.mean[4]:ntreeentries", "Cluster_Summary_l4.stddev[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["avg_grouped_mean_of_L5", "avg_grouped_stddev_of_L5", "avg_cluster_mean_of_L5", "avg_cluster_stddev_of_L5"], + "title": ["Avg. Grouped Cluster distribution: L5", "Avg. Stddev Grouped Cluster: L5", "Avg. Cluster distribution: L5", "Avg. Stddev Cluster: L5"], + "varexp": ["Cluster_Grouped_l5.mean[5]:ntreeentries", "Cluster_Grouped_l5.stddev[5]:ntreeentries", "Cluster_Summary_l5.mean[5]:ntreeentries", "Cluster_Summary_l5.stddev[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["avg_grouped_mean_of_L6", "avg_grouped_stddev_of_L6", "avg_cluster_mean_of_L6", "avg_cluster_stddev_of_L6"], + "title": ["Avg. Grouped Cluster distribution: L6", "Avg. Stddev Grouped Cluster: L6", "Avg. Cluster distribution: L6", "Avg. Stddev Cluster: L6"], + "varexp": ["Cluster_Grouped_l6.mean[6]:ntreeentries", "Cluster_Grouped_l6.stddev[6]:ntreeentries", "Cluster_Summary_l6.mean[6]:ntreeentries", "Cluster_Summary_l6.stddev[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_00", "cluster_stddev_of_L0_00", "cluster_chips_of_L0_00", "cluster_occ_of_L0_00"], + "title": ["Average Cluster trend: L0_00", "Stddev Cluster trend: L0_00", "Active chips trend: L0_00", "Occupancy trend: L0_00"], + "varexp": ["Cluster_l0.mean[0]:ntreeentries", "Cluster_l0.stddev[0]:ntreeentries", "Cluster_l0.entries[0]:ntreeentries", "Occupancy_l0.mean[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_01", "cluster_stddev_of_L0_01", "cluster_chips_of_L0_01", "cluster_occ_of_L0_01"], + "title": ["Average Cluster trend: L0_01", "Stddev Cluster trend: L0_01", "Active chips trend: L0_01", "Occupancy trend: L0_01"], + "varexp": ["Cluster_l0.mean[1]:ntreeentries", "Cluster_l0.stddev[1]:ntreeentries", "Cluster_l0.entries[1]:ntreeentries", "Occupancy_l0.mean[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_02", "cluster_stddev_of_L0_02", "cluster_chips_of_L0_02", "cluster_occ_of_L0_02"], + "title": ["Average Cluster trend: L0_02", "Stddev Cluster trend: L0_02", "Active chips trend: L0_02", "Occupancy trend: L0_02"], + "varexp": ["Cluster_l0.mean[2]:ntreeentries", "Cluster_l0.stddev[2]:ntreeentries", "Cluster_l0.entries[2]:ntreeentries", "Occupancy_l0.mean[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_03", "cluster_stddev_of_L0_03", "cluster_chips_of_L0_03", "cluster_occ_of_L0_03"], + "title": ["Average Cluster trend: L0_03", "Stddev Cluster trend: L0_03", "Active chips trend: L0_03", "Occupancy trend: L0_03"], + "varexp": ["Cluster_l0.mean[3]:ntreeentries", "Cluster_l0.stddev[3]:ntreeentries", "Cluster_l0.entries[3]:ntreeentries", "Occupancy_l0.mean[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_04", "cluster_stddev_of_L0_04", "cluster_chips_of_L0_04", "cluster_occ_of_L0_04"], + "title": ["Average Cluster trend: L0_04", "Stddev Cluster trend: L0_04", "Active chips trend: L0_04", "Occupancy trend: L0_04"], + "varexp": ["Cluster_l0.mean[4]:ntreeentries", "Cluster_l0.stddev[4]:ntreeentries", "Cluster_l0.entries[4]:ntreeentries", "Occupancy_l0.mean[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_05", "cluster_stddev_of_L0_05", "cluster_chips_of_L0_05", "cluster_occ_of_L0_05"], + "title": ["Average Cluster trend: L0_05", "Stddev Cluster trend: L0_05", "Active chips trend: L0_05", "Occupancy trend: L0_05"], + "varexp": ["Cluster_l0.mean[5]:ntreeentries", "Cluster_l0.stddev[5]:ntreeentries", "Cluster_l0.entries[5]:ntreeentries", "Occupancy_l0.mean[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_06", "cluster_stddev_of_L0_06", "cluster_chips_of_L0_06", "cluster_occ_of_L0_06"], + "title": ["Average Cluster trend: L0_06", "Stddev Cluster trend: L0_06", "Active chips trend: L0_06", "Occupancy trend: L0_06"], + "varexp": ["Cluster_l0.mean[6]:ntreeentries", "Cluster_l0.stddev[6]:ntreeentries", "Cluster_l0.entries[6]:ntreeentries", "Occupancy_l0.mean[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_07", "cluster_stddev_of_L0_07", "cluster_chips_of_L0_07", "cluster_occ_of_L0_07"], + "title": ["Average Cluster trend: L0_07", "Stddev Cluster trend: L0_07", "Active chips trend: L0_07", "Occupancy trend: L0_07"], + "varexp": ["Cluster_l0.mean[7]:ntreeentries", "Cluster_l0.stddev[7]:ntreeentries", "Cluster_l0.entries[7]:ntreeentries", "Occupancy_l0.mean[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_08", "cluster_stddev_of_L0_08", "cluster_chips_of_L0_08", "cluster_occ_of_L0_08"], + "title": ["Average Cluster trend: L0_08", "Stddev Cluster trend: L0_08", "Active chips trend: L0_08", "Occupancy trend: L0_08"], + "varexp": ["Cluster_l0.mean[8]:ntreeentries", "Cluster_l0.stddev[8]:ntreeentries", "Cluster_l0.entries[8]:ntreeentries", "Occupancy_l0.mean[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_09", "cluster_stddev_of_L0_09", "cluster_chips_of_L0_09", "cluster_occ_of_L0_09"], + "title": ["Average Cluster trend: L0_09", "Stddev Cluster trend: L0_09", "Active chips trend: L0_09", "Occupancy trend: L0_09"], + "varexp": ["Cluster_l0.mean[9]:ntreeentries", "Cluster_l0.stddev[9]:ntreeentries", "Cluster_l0.entries[9]:ntreeentries", "Occupancy_l0.mean[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_10", "cluster_stddev_of_L0_10", "cluster_chips_of_L0_10", "cluster_occ_of_L0_10"], + "title": ["Average Cluster trend: L0_10", "Stddev Cluster trend: L0_10", "Active chips trend: L0_10", "Occupancy trend: L0_10"], + "varexp": ["Cluster_l0.mean[10]:ntreeentries", "Cluster_l0.stddev[10]:ntreeentries", "Cluster_l0.entries[10]:ntreeentries", "Occupancy_l0.mean[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L0_11", "cluster_stddev_of_L0_11", "cluster_chips_of_L0_11", "cluster_occ_of_L0_11"], + "title": ["Average Cluster trend: L0_11", "Stddev Cluster trend: L0_11", "Active chips trend: L0_11", "Occupancy trend: L0_11"], + "varexp": ["Cluster_l0.mean[11]:ntreeentries", "Cluster_l0.stddev[11]:ntreeentries", "Cluster_l0.entries[11]:ntreeentries", "Occupancy_l0.mean[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_00", "cluster_stddev_of_L1_00", "cluster_chips_of_L1_00", "cluster_occ_of_L1_00"], + "title": ["Average Cluster trend: L1_00", "Stddev Cluster trend: L1_00", "Active chips trend: L1_00", "Occupancy trend: L1_00"], + "varexp": ["Cluster_l1.mean[0]:ntreeentries", "Cluster_l1.stddev[0]:ntreeentries", "Cluster_l1.entries[0]:ntreeentries", "Occupancy_l1.mean[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_01", "cluster_stddev_of_L1_01", "cluster_chips_of_L1_01", "cluster_occ_of_L1_01"], + "title": ["Average Cluster trend: L1_01", "Stddev Cluster trend: L1_01", "Active chips trend: L1_01", "Occupancy trend: L1_01"], + "varexp": ["Cluster_l1.mean[1]:ntreeentries", "Cluster_l1.stddev[1]:ntreeentries", "Cluster_l1.entries[1]:ntreeentries", "Occupancy_l1.mean[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_02", "cluster_stddev_of_L1_02", "cluster_chips_of_L1_02", "cluster_occ_of_L1_02"], + "title": ["Average Cluster trend: L1_02", "Stddev Cluster trend: L1_02", "Active chips trend: L1_02", "Occupancy trend: L1_02"], + "varexp": ["Cluster_l1.mean[2]:ntreeentries", "Cluster_l1.stddev[2]:ntreeentries", "Cluster_l1.entries[2]:ntreeentries", "Occupancy_l1.mean[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_03", "cluster_stddev_of_L1_03", "cluster_chips_of_L1_03", "cluster_occ_of_L1_03"], + "title": ["Average Cluster trend: L1_03", "Stddev Cluster trend: L1_03", "Active chips trend: L1_03", "Occupancy trend: L1_03"], + "varexp": ["Cluster_l1.mean[3]:ntreeentries", "Cluster_l1.stddev[3]:ntreeentries", "Cluster_l1.entries[3]:ntreeentries", "Occupancy_l1.mean[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_04", "cluster_stddev_of_L1_04", "cluster_chips_of_L1_04", "cluster_occ_of_L1_04"], + "title": ["Average Cluster trend: L1_04", "Stddev Cluster trend: L1_04", "Active chips trend: L1_04", "Occupancy trend: L1_04"], + "varexp": ["Cluster_l1.mean[4]:ntreeentries", "Cluster_l1.stddev[4]:ntreeentries", "Cluster_l1.entries[4]:ntreeentries", "Occupancy_l1.mean[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_05", "cluster_stddev_of_L1_05", "cluster_chips_of_L1_05", "cluster_occ_of_L1_05"], + "title": ["Average Cluster trend: L1_05", "Stddev Cluster trend: L1_05", "Active chips trend: L1_05", "Occupancy trend: L1_05"], + "varexp": ["Cluster_l1.mean[5]:ntreeentries", "Cluster_l1.stddev[5]:ntreeentries", "Cluster_l1.entries[5]:ntreeentries", "Occupancy_l1.mean[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_06", "cluster_stddev_of_L1_06", "cluster_chips_of_L1_06", "cluster_occ_of_L1_06"], + "title": ["Average Cluster trend: L1_06", "Stddev Cluster trend: L1_06", "Active chips trend: L1_06", "Occupancy trend: L1_06"], + "varexp": ["Cluster_l1.mean[6]:ntreeentries", "Cluster_l1.stddev[6]:ntreeentries", "Cluster_l1.entries[6]:ntreeentries", "Occupancy_l1.mean[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_07", "cluster_stddev_of_L1_07", "cluster_chips_of_L1_07", "cluster_occ_of_L1_07"], + "title": ["Average Cluster trend: L1_07", "Stddev Cluster trend: L1_07", "Active chips trend: L1_07", "Occupancy trend: L1_07"], + "varexp": ["Cluster_l1.mean[7]:ntreeentries", "Cluster_l1.stddev[7]:ntreeentries", "Cluster_l1.entries[7]:ntreeentries", "Occupancy_l1.mean[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_08", "cluster_stddev_of_L1_08", "cluster_chips_of_L1_08", "cluster_occ_of_L1_08"], + "title": ["Average Cluster trend: L1_08", "Stddev Cluster trend: L1_08", "Active chips trend: L1_08", "Occupancy trend: L1_08"], + "varexp": ["Cluster_l1.mean[8]:ntreeentries", "Cluster_l1.stddev[8]:ntreeentries", "Cluster_l1.entries[8]:ntreeentries", "Occupancy_l1.mean[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_09", "cluster_stddev_of_L1_09", "cluster_chips_of_L1_09", "cluster_occ_of_L1_09"], + "title": ["Average Cluster trend: L1_09", "Stddev Cluster trend: L1_09", "Active chips trend: L1_09", "Occupancy trend: L1_09"], + "varexp": ["Cluster_l1.mean[9]:ntreeentries", "Cluster_l1.stddev[9]:ntreeentries", "Cluster_l1.entries[9]:ntreeentries", "Occupancy_l1.mean[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_10", "cluster_stddev_of_L1_10", "cluster_chips_of_L1_10", "cluster_occ_of_L1_10"], + "title": ["Average Cluster trend: L1_10", "Stddev Cluster trend: L1_10", "Active chips trend: L1_10", "Occupancy trend: L1_10"], + "varexp": ["Cluster_l1.mean[10]:ntreeentries", "Cluster_l1.stddev[10]:ntreeentries", "Cluster_l1.entries[10]:ntreeentries", "Occupancy_l1.mean[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_11", "cluster_stddev_of_L1_11", "cluster_chips_of_L1_11", "cluster_occ_of_L1_11"], + "title": ["Average Cluster trend: L1_11", "Stddev Cluster trend: L1_11", "Active chips trend: L1_11", "Occupancy trend: L1_11"], + "varexp": ["Cluster_l1.mean[11]:ntreeentries", "Cluster_l1.stddev[11]:ntreeentries", "Cluster_l1.entries[11]:ntreeentries", "Occupancy_l1.mean[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_12", "cluster_stddev_of_L1_12", "cluster_chips_of_L1_12", "cluster_occ_of_L1_12"], + "title": ["Average Cluster trend: L1_12", "Stddev Cluster trend: L1_12", "Active chips trend: L1_12", "Occupancy trend: L1_12"], + "varexp": ["Cluster_l1.mean[12]:ntreeentries", "Cluster_l1.stddev[12]:ntreeentries", "Cluster_l1.entries[12]:ntreeentries", "Occupancy_l1.mean[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_13", "cluster_stddev_of_L1_13", "cluster_chips_of_L1_13", "cluster_occ_of_L1_13"], + "title": ["Average Cluster trend: L1_13", "Stddev Cluster trend: L1_13", "Active chips trend: L1_13", "Occupancy trend: L1_13"], + "varexp": ["Cluster_l1.mean[13]:ntreeentries", "Cluster_l1.stddev[13]:ntreeentries", "Cluster_l1.entries[13]:ntreeentries", "Cluster_l1.mean_scaled[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_14", "cluster_stddev_of_L1_14", "cluster_chips_of_L1_14", "cluster_occ_of_L1_14"], + "title": ["Average Cluster trend: L1_14", "Stddev Cluster trend: L1_14", "Active chips trend: L1_14", "Occupancy trend: L1_14"], + "varexp": ["Cluster_l1.mean[14]:ntreeentries", "Cluster_l1.stddev[14]:ntreeentries", "Cluster_l1.entries[14]:ntreeentries", "Occupancy_l1.mean[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L1_15", "cluster_stddev_of_L1_15", "cluster_chips_of_L1_15", "cluster_occ_of_L1_15"], + "title": ["Average Cluster trend: L1_15", "Stddev Cluster trend: L1_15", "Active chips trend: L1_15", "Occupancy trend: L1_15"], + "varexp": ["Cluster_l1.mean[15]:ntreeentries", "Cluster_l1.stddev[15]:ntreeentries", "Cluster_l1.entries[15]:ntreeentries", "Occupancy_l1.mean[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_00", "cluster_stddev_of_L2_00", "cluster_chips_of_L2_00", "cluster_occ_of_L2_00"], + "title": ["Average Cluster trend: L2_00", "Stddev Cluster trend: L2_00", "Active chips trend: L2_00", "Occupancy trend: L2_00"], + "varexp": ["Cluster_l2.mean[0]:ntreeentries", "Cluster_l2.stddev[0]:ntreeentries", "Cluster_l2.entries[0]:ntreeentries", "Occupancy_l2.mean[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_01", "cluster_stddev_of_L2_01", "cluster_chips_of_L2_01", "cluster_occ_of_L2_01"], + "title": ["Average Cluster trend: L2_01", "Stddev Cluster trend: L2_01", "Active chips trend: L2_01", "Occupancy trend: L2_01"], + "varexp": ["Cluster_l2.mean[1]:ntreeentries", "Cluster_l2.stddev[1]:ntreeentries", "Cluster_l2.entries[1]:ntreeentries", "Occupancy_l2.mean[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_02", "cluster_stddev_of_L2_02", "cluster_chips_of_L2_02", "cluster_occ_of_L2_02"], + "title": ["Average Cluster trend: L2_02", "Stddev Cluster trend: L2_02", "Active chips trend: L2_02", "Occupancy trend: L2_02"], + "varexp": ["Cluster_l2.mean[2]:ntreeentries", "Cluster_l2.stddev[2]:ntreeentries", "Cluster_l2.entries[2]:ntreeentries", "Occupancy_l2.mean[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_03", "cluster_stddev_of_L2_03", "cluster_chips_of_L2_03", "cluster_occ_of_L2_03"], + "title": ["Average Cluster trend: L2_03", "Stddev Cluster trend: L2_03", "Active chips trend: L2_03", "Occupancy trend: L2_03"], + "varexp": ["Cluster_l2.mean[3]:ntreeentries", "Cluster_l2.stddev[3]:ntreeentries", "Cluster_l2.entries[3]:ntreeentries", "Occupancy_l2.mean[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_04", "cluster_stddev_of_L2_04", "cluster_chips_of_L2_04", "cluster_occ_of_L2_04"], + "title": ["Average Cluster trend: L2_04", "Stddev Cluster trend: L2_04", "Active chips trend: L2_04", "Occupancy trend: L2_04"], + "varexp": ["Cluster_l2.mean[4]:ntreeentries", "Cluster_l2.stddev[4]:ntreeentries", "Cluster_l2.entries[4]:ntreeentries", "Occupancy_l2.mean[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_05", "cluster_stddev_of_L2_05", "cluster_chips_of_L2_05", "cluster_occ_of_L2_05"], + "title": ["Average Cluster trend: L2_05", "Stddev Cluster trend: L2_05", "Active chips trend: L2_05", "Occupancy trend: L2_05"], + "varexp": ["Cluster_l2.mean[5]:ntreeentries", "Cluster_l2.stddev[5]:ntreeentries", "Cluster_l2.entries[5]:ntreeentries", "Occupancy_l2.mean[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_06", "cluster_stddev_of_L2_06", "cluster_chips_of_L2_06", "cluster_occ_of_L2_06"], + "title": ["Average Cluster trend: L2_06", "Stddev Cluster trend: L2_06", "Active chips trend: L2_06", "Occupancy trend: L2_06"], + "varexp": ["Cluster_l2.mean[6]:ntreeentries", "Cluster_l2.stddev[6]:ntreeentries", "Cluster_l2.entries[6]:ntreeentries", "Occupancy_l2.mean[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_07", "cluster_stddev_of_L2_07", "cluster_chips_of_L2_07", "cluster_occ_of_L2_07"], + "title": ["Average Cluster trend: L2_07", "Stddev Cluster trend: L2_07", "Active chips trend: L2_07", "Occupancy trend: L2_07"], + "varexp": ["Cluster_l2.mean[7]:ntreeentries", "Cluster_l2.stddev[7]:ntreeentries", "Cluster_l2.entries[7]:ntreeentries", "Occupancy_l2.mean[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_08", "cluster_stddev_of_L2_08", "cluster_chips_of_L2_08", "cluster_occ_of_L2_08"], + "title": ["Average Cluster trend: L2_08", "Stddev Cluster trend: L2_08", "Active chips trend: L2_08", "Occupancy trend: L2_08"], + "varexp": ["Cluster_l2.mean[8]:ntreeentries", "Cluster_l2.stddev[8]:ntreeentries", "Cluster_l2.entries[8]:ntreeentries", "Occupancy_l2.mean[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_09", "cluster_stddev_of_L2_09", "cluster_chips_of_L2_09", "cluster_occ_of_L2_09"], + "title": ["Average Cluster trend: L2_09", "Stddev Cluster trend: L2_09", "Active chips trend: L2_09", "Occupancy trend: L2_09"], + "varexp": ["Cluster_l2.mean[9]:ntreeentries", "Cluster_l2.stddev[9]:ntreeentries", "Cluster_l2.entries[9]:ntreeentries", "Occupancy_l2.mean[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_10", "cluster_stddev_of_L2_10", "cluster_chips_of_L2_10", "cluster_occ_of_L2_10"], + "title": ["Average Cluster trend: L2_10", "Stddev Cluster trend: L2_10", "Active chips trend: L2_10", "Occupancy trend: L2_10"], + "varexp": ["Cluster_l2.mean[10]:ntreeentries", "Cluster_l2.stddev[10]:ntreeentries", "Cluster_l2.entries[10]:ntreeentries", "Occupancy_l2.mean[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_11", "cluster_stddev_of_L2_11", "cluster_chips_of_L2_11", "cluster_occ_of_L2_11"], + "title": ["Average Cluster trend: L2_11", "Stddev Cluster trend: L2_11", "Active chips trend: L2_11", "Occupancy trend: L2_11"], + "varexp": ["Cluster_l2.mean[11]:ntreeentries", "Cluster_l2.stddev[11]:ntreeentries", "Cluster_l2.entries[11]:ntreeentries", "Occupancy_l2.mean[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_12", "cluster_stddev_of_L2_12", "cluster_chips_of_L2_12", "cluster_occ_of_L2_12"], + "title": ["Average Cluster trend: L2_12", "Stddev Cluster trend: L2_12", "Active chips trend: L2_12", "Occupancy trend: L2_12"], + "varexp": ["Cluster_l2.mean[12]:ntreeentries", "Cluster_l2.stddev[12]:ntreeentries", "Cluster_l2.entries[12]:ntreeentries", "Occupancy_l2.mean[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_13", "cluster_stddev_of_L2_13", "cluster_chips_of_L2_13", "cluster_occ_of_L2_13"], + "title": ["Average Cluster trend: L2_13", "Stddev Cluster trend: L2_13", "Active chips trend: L2_13", "Occupancy trend: L2_13"], + "varexp": ["Cluster_l2.mean[13]:ntreeentries", "Cluster_l2.stddev[13]:ntreeentries", "Cluster_l2.entries[13]:ntreeentries", "Occupancy_l2.mean[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_14", "cluster_stddev_of_L2_14", "cluster_chips_of_L2_14", "cluster_occ_of_L2_14"], + "title": ["Average Cluster trend: L2_14", "Stddev Cluster trend: L2_14", "Active chips trend: L2_14", "Occupancy trend: L2_14"], + "varexp": ["Cluster_l2.mean[14]:ntreeentries", "Cluster_l2.stddev[14]:ntreeentries", "Cluster_l2.entries[14]:ntreeentries", "Occupancy_l2.mean[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_15", "cluster_stddev_of_L2_15", "cluster_chips_of_L2_15", "cluster_occ_of_L2_15"], + "title": ["Average Cluster trend: L2_15", "Stddev Cluster trend: L2_15", "Active chips trend: L2_15", "Occupancy trend: L2_15"], + "varexp": ["Cluster_l2.mean[15]:ntreeentries", "Cluster_l2.stddev[15]:ntreeentries", "Cluster_l2.entries[15]:ntreeentries", "Occupancy_l2.mean[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_16", "cluster_stddev_of_L2_16", "cluster_chips_of_L2_16", "cluster_occ_of_L2_16"], + "title": ["Average Cluster trend: L2_16", "Stddev Cluster trend: L2_16", "Active chips trend: L2_16", "Occupancy trend: L2_16"], + "varexp": ["Cluster_l2.mean[16]:ntreeentries", "Cluster_l2.stddev[16]:ntreeentries", "Cluster_l2.entries[16]:ntreeentries", "Occupancy_l2.mean[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_17", "cluster_stddev_of_L2_17", "cluster_chips_of_L2_17", "cluster_occ_of_L2_17"], + "title": ["Average Cluster trend: L2_17", "Stddev Cluster trend: L2_17", "Active chips trend: L2_17", "Occupancy trend: L2_17"], + "varexp": ["Cluster_l2.mean[17]:ntreeentries", "Cluster_l2.stddev[17]:ntreeentries", "Cluster_l2.entries[17]:ntreeentries", "Occupancy_l2.mean[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_18", "cluster_stddev_of_L2_18", "cluster_chips_of_L2_18", "cluster_occ_of_L2_18"], + "title": ["Average Cluster trend: L2_18", "Stddev Cluster trend: L2_18", "Active chips trend: L2_18", "Occupancy trend: L2_18"], + "varexp": ["Cluster_l2.mean[18]:ntreeentries", "Cluster_l2.stddev[18]:ntreeentries", "Cluster_l2.entries[18]:ntreeentries", "Cluster_l2.mean_scaled[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L2_19", "cluster_stddev_of_L2_19", "cluster_chips_of_L2_19", "cluster_occ_of_L2_19"], + "title": ["Average Cluster trend: L2_19", "Stddev Cluster trend: L2_19", "Active chips trend: L2_19", "Occupancy trend: L2_19"], + "varexp": ["Cluster_l2.mean[19]:ntreeentries", "Cluster_l2.stddev[19]:ntreeentries", "Cluster_l2.entries[19]:ntreeentries", "Occupancy_l2.mean[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_00", "cluster_stddev_of_L3_00", "cluster_chips_of_L3_00", "cluster_occ_of_L3_00"], + "title": ["Average Cluster trend: L3_00", "Stddev Cluster trend: L3_00", "Active chips trend: L3_00", "Occupancy trend: L3_00"], + "varexp": ["Cluster_l3.mean[0]:ntreeentries", "Cluster_l3.stddev[0]:ntreeentries", "Cluster_l3.entries[0]:ntreeentries", "Occupancy_l3.mean[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_01", "cluster_stddev_of_L3_01", "cluster_chips_of_L3_01", "cluster_occ_of_L3_01"], + "title": ["Average Cluster trend: L3_01", "Stddev Cluster trend: L3_01", "Active chips trend: L3_01", "Occupancy trend: L3_01"], + "varexp": ["Cluster_l3.mean[1]:ntreeentries", "Cluster_l3.stddev[1]:ntreeentries", "Cluster_l3.entries[1]:ntreeentries", "Occupancy_l3.mean[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_02", "cluster_stddev_of_L3_02", "cluster_chips_of_L3_02", "cluster_occ_of_L3_02"], + "title": ["Average Cluster trend: L3_02", "Stddev Cluster trend: L3_02", "Active chips trend: L3_02", "Occupancy trend: L3_02"], + "varexp": ["Cluster_l3.mean[2]:ntreeentries", "Cluster_l3.stddev[2]:ntreeentries", "Cluster_l3.entries[2]:ntreeentries", "Occupancy_l3.mean[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_03", "cluster_stddev_of_L3_03", "cluster_chips_of_L3_03", "cluster_occ_of_L3_03"], + "title": ["Average Cluster trend: L3_03", "Stddev Cluster trend: L3_03", "Active chips trend: L3_03", "Occupancy trend: L3_03"], + "varexp": ["Cluster_l3.mean[3]:ntreeentries", "Cluster_l3.stddev[3]:ntreeentries", "Cluster_l3.entries[3]:ntreeentries", "Occupancy_l3.mean[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_04", "cluster_stddev_of_L3_04", "cluster_chips_of_L3_04", "cluster_occ_of_L3_04"], + "title": ["Average Cluster trend: L3_04", "Stddev Cluster trend: L3_04", "Active chips trend: L3_04", "Occupancy trend: L3_04"], + "varexp": ["Cluster_l3.mean[4]:ntreeentries", "Cluster_l3.stddev[4]:ntreeentries", "Cluster_l3.entries[4]:ntreeentries", "Occupancy_l3.mean[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_05", "cluster_stddev_of_L3_05", "cluster_chips_of_L3_05", "cluster_occ_of_L3_05"], + "title": ["Average Cluster trend: L3_05", "Stddev Cluster trend: L3_05", "Active chips trend: L3_05", "Occupancy trend: L3_05"], + "varexp": ["Cluster_l3.mean[5]:ntreeentries", "Cluster_l3.stddev[5]:ntreeentries", "Cluster_l3.entries[5]:ntreeentries", "Occupancy_l3.mean[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_06", "cluster_stddev_of_L3_06", "cluster_chips_of_L3_06", "cluster_occ_of_L3_06"], + "title": ["Average Cluster trend: L3_06", "Stddev Cluster trend: L3_06", "Active chips trend: L3_06", "Occupancy trend: L3_06"], + "varexp": ["Cluster_l3.mean[6]:ntreeentries", "Cluster_l3.stddev[6]:ntreeentries", "Cluster_l3.entries[6]:ntreeentries", "Occupancy_l3.mean[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_07", "cluster_stddev_of_L3_07", "cluster_chips_of_L3_07", "cluster_occ_of_L3_07"], + "title": ["Average Cluster trend: L3_07", "Stddev Cluster trend: L3_07", "Active chips trend: L3_07", "Occupancy trend: L3_07"], + "varexp": ["Cluster_l3.mean[7]:ntreeentries", "Cluster_l3.stddev[7]:ntreeentries", "Cluster_l3.entries[7]:ntreeentries", "Occupancy_l3.mean[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_08", "cluster_stddev_of_L3_08", "cluster_chips_of_L3_08", "cluster_occ_of_L3_08"], + "title": ["Average Cluster trend: L3_08", "Stddev Cluster trend: L3_08", "Active chips trend: L3_08", "Occupancy trend: L3_08"], + "varexp": ["Cluster_l3.mean[8]:ntreeentries", "Cluster_l3.stddev[8]:ntreeentries", "Cluster_l3.entries[8]:ntreeentries", "Occupancy_l3.mean[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_09", "cluster_stddev_of_L3_09", "cluster_chips_of_L3_09", "cluster_occ_of_L3_09"], + "title": ["Average Cluster trend: L3_09", "Stddev Cluster trend: L3_09", "Active chips trend: L3_09", "Occupancy trend: L3_09"], + "varexp": ["Cluster_l3.mean[9]:ntreeentries", "Cluster_l3.stddev[9]:ntreeentries", "Cluster_l3.entries[9]:ntreeentries", "Occupancy_l3.mean[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_10", "cluster_stddev_of_L0_10", "cluster_chips_of_L3_10", "cluster_occ_of_L3_10"], + "title": ["Average Cluster trend: L3_10", "Stddev Cluster trend: L3_10", "Active chips trend: L3_10", "Occupancy trend: L3_10"], + "varexp": ["Cluster_l3.mean[10]:ntreeentries", "Cluster_l3.stddev[10]:ntreeentries", "Cluster_l3.entries[10]:ntreeentries", "Occupancy_l3.mean[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_11", "cluster_stddev_of_L3_11", "cluster_chips_of_L3_11", "cluster_occ_of_L3_11"], + "title": ["Average Cluster trend: L3_11", "Stddev Cluster trend: L3_11", "Active chips trend: L3_11", "Occupancy trend: L3_11"], + "varexp": ["Cluster_l3.mean[11]:ntreeentries", "Cluster_l3.stddev[11]:ntreeentries", "Cluster_l3.entries[11]:ntreeentries", "Occupancy_l3.mean[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_12", "cluster_stddev_of_L3_12", "cluster_chips_of_L3_12", "cluster_occ_of_L3_12"], + "title": ["Average Cluster trend: L1_00", "Stddev Cluster trend: L1_00", "Active chips trend: L3_12", "Occupancy trend: L3_12"], + "varexp": ["Cluster_l3.mean[12]:ntreeentries", "Cluster_l3.stddev[12]:ntreeentries", "Cluster_l3.entries[12]:ntreeentries", "Occupancy_l3.mean[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_13", "cluster_stddev_of_L3_13", "cluster_chips_of_L3_13", "cluster_occ_of_L3_13"], + "title": ["Average Cluster trend: L3_13", "Stddev Cluster trend: L3_13", "Active chips trend: L3_13", "Occupancy trend: L3_13"], + "varexp": ["Cluster_l3.mean[13]:ntreeentries", "Cluster_l3.stddev[13]:ntreeentries", "Cluster_l3.entries[13]:ntreeentries", "Occupancy_l3.mean[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_14", "cluster_stddev_of_L3_14", "cluster_chips_of_L3_14", "cluster_occ_of_L3_14"], + "title": ["Average Cluster trend: L3_14", "Stddev Cluster trend: L3_14", "Active chips trend: L3_14", "Occupancy trend: L3_14"], + "varexp": ["Cluster_l3.mean[14]:ntreeentries", "Cluster_l3.stddev[14]:ntreeentries", "Cluster_l3.entries[14]:ntreeentries", "Occupancy_l3.mean[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_15", "cluster_stddev_of_L3_15", "cluster_chips_of_L3_15", "cluster_occ_of_L3_15"], + "title": ["Average Cluster trend: L3_15", "Stddev Cluster trend: L3_15", "Active chips trend: L3_15", "Occupancy trend: L3_15"], + "varexp": ["Cluster_l3.mean[15]:ntreeentries", "Cluster_l3.stddev[15]:ntreeentries", "Cluster_l3.entries[15]:ntreeentries", "Occupancy_l3.mean[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_16", "cluster_stddev_of_L3_16", "cluster_chips_of_L3_16", "cluster_occ_of_L3_16"], + "title": ["Average Cluster trend: L3_16", "Stddev Cluster trend: L3_16", "Active chips trend: L3_16", "Occupancy trend: L3_16"], + "varexp": ["Cluster_l3.mean[16]:ntreeentries", "Cluster_l3.stddev[16]:ntreeentries", "Cluster_l3.entries[16]:ntreeentries", "Occupancy_l3.mean[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_17", "cluster_stddev_of_L3_17", "cluster_chips_of_L3_17", "cluster_occ_of_L3_17"], + "title": ["Average Cluster trend: L3_17", "Stddev Cluster trend: L3_17", "Active chips trend: L3_17", "Occupancy trend: L3_17"], + "varexp": ["Cluster_l3.mean[17]:ntreeentries", "Cluster_l3.stddev[17]:ntreeentries", "Cluster_l3.entries[17]:ntreeentries", "Occupancy_l3.mean[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_18", "cluster_stddev_of_L3_18", "cluster_chips_of_L3_18", "cluster_occ_of_L3_18"], + "title": ["Average Cluster trend: L3_18", "Stddev Cluster trend: L3_18", "Active chips trend: L3_18", "Occupancy trend: L3_18"], + "varexp": ["Cluster_l3.mean[18]:ntreeentries", "Cluster_l3.stddev[18]:ntreeentries", "Cluster_l3.entries[18]:ntreeentries", "Occupancy_l3.mean[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_19", "cluster_stddev_of_L3_19", "cluster_chips_of_L3_19", "cluster_occ_of_L3_19"], + "title": ["Average Cluster trend: L3_19", "Stddev Cluster trend: L3_19", "Active chips trend: L3_19", "Occupancy trend: L3_19"], + "varexp": ["Cluster_l3.mean[19]:ntreeentries", "Cluster_l3.stddev[19]:ntreeentries", "Cluster_l3.entries[19]:ntreeentries", "Occupancy_l3.mean[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_20", "cluster_stddev_of_L3_20", "cluster_chips_of_L3_20", "cluster_occ_of_L3_20"], + "title": ["Average Cluster trend: L3_20", "Stddev Cluster trend: L3_20", "Active chips trend: L3_20", "Occupancy trend: L3_20"], + "varexp": ["Cluster_l3.mean[20]:ntreeentries", "Cluster_l3.stddev[20]:ntreeentries", "Cluster_l3.entries[20]:ntreeentries", "Occupancy_l3.mean[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_21", "cluster_stddev_of_L3_21", "cluster_chips_of_L3_21", "cluster_occ_of_L3_21"], + "title": ["Average Cluster trend: L3_21", "Stddev Cluster trend: L3_21", "Active chips trend: L3_21", "Occupancy trend: L3_21"], + "varexp": ["Cluster_l3.mean[21]:ntreeentries", "Cluster_l3.stddev[21]:ntreeentries", "Cluster_l3.entries[21]:ntreeentries", "Occupancy_l3.mean[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_22", "cluster_stddev_of_L3_22", "cluster_chips_of_L3_22", "cluster_occ_of_L3_22"], + "title": ["Average Cluster trend: L3_22", "Stddev Cluster trend: L3_22", "Active chips trend: L3_22", "Occupancy trend: L3_22"], + "varexp": ["Cluster_l3.mean[22]:ntreeentries", "Cluster_l3.stddev[22]:ntreeentries", "Cluster_l3.entries[22]:ntreeentries", "Occupancy_l3.mean[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L3_23", "cluster_stddev_of_L3_23", "cluster_chips_of_L3_23", "cluster_occ_of_L3_23"], + "title": ["Average Cluster trend: L3_23", "Stddev Cluster trend: L3_23", "Active chips trend: L3_23", "Occupancy trend: L3_23"], + "varexp": ["Cluster_l3.mean[23]:ntreeentries", "Cluster_l3.stddev[23]:ntreeentries", "Cluster_l3.entries[23]:ntreeentries", "Occupancy_l3.mean[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_00", "cluster_stddev_of_L4_00", "cluster_chips_of_L4_00", "cluster_occ_of_L4_00"], + "title": ["Average Cluster trend: L4_00", "Stddev Cluster trend: L4_00", "Active chips trend: L4_00", "Occupancy trend: L4_00"], + "varexp": ["Cluster_l4.mean[0]:ntreeentries", "Cluster_l4.stddev[0]:ntreeentries", "Cluster_l4.entries[0]:ntreeentries", "Occupancy_l4.mean[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_01", "cluster_stddev_of_L4_01", "cluster_chips_of_L4_01", "cluster_occ_of_L4_01"], + "title": ["Average Cluster trend: L4_01", "Stddev Cluster trend: L4_01", "Active chips trend: L4_01", "Occupancy trend: L4_01"], + "varexp": ["Cluster_l4.mean[1]:ntreeentries", "Cluster_l4.stddev[1]:ntreeentries", "Cluster_l4.entries[1]:ntreeentries", "Occupancy_l4.mean[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_02", "cluster_stddev_of_L4_02", "cluster_chips_of_L4_02", "cluster_occ_of_L4_02"], + "title": ["Average Cluster trend: L4_02", "Stddev Cluster trend: L4_02", "Active chips trend: L4_02", "Occupancy trend: L4_02"], + "varexp": ["Cluster_l4.mean[2]:ntreeentries", "Cluster_l4.stddev[2]:ntreeentries", "Cluster_l4.entries[2]:ntreeentries", "Occupancy_l4.mean[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_03", "cluster_stddev_of_L4_03", "cluster_chips_of_L4_03", "cluster_occ_of_L4_03"], + "title": ["Average Cluster trend: L4_03", "Stddev Cluster trend: L4_03", "Active chips trend: L4_03", "Occupancy trend: L4_03"], + "varexp": ["Cluster_l4.mean[3]:ntreeentries", "Cluster_l4.stddev[3]:ntreeentries", "Cluster_l4.entries[3]:ntreeentries", "Occupancy_l4.mean[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_04", "cluster_stddev_of_L4_04", "cluster_chips_of_L4_04", "cluster_occ_of_L4_04"], + "title": ["Average Cluster trend: L4_04", "Stddev Cluster trend: L4_04", "Active chips trend: L4_04", "Occupancy trend: L4_04"], + "varexp": ["Cluster_l4.mean[4]:ntreeentries", "Cluster_l4.stddev[4]:ntreeentries", "Cluster_l4.entries[4]:ntreeentries", "Occupancy_l4.mean[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_05", "cluster_stddev_of_L4_05", "cluster_chips_of_L4_05", "cluster_occ_of_L4_05"], + "title": ["Average Cluster trend: L4_05", "Stddev Cluster trend: L4_05", "Active chips trend: L4_05", "Occupancy trend: L4_05"], + "varexp": ["Cluster_l4.mean[5]:ntreeentries", "Cluster_l4.stddev[5]:ntreeentries", "Cluster_l4.entries[5]:ntreeentries", "Occupancy_l4.mean[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_06", "cluster_stddev_of_L4_06", "cluster_chips_of_L4_06", "cluster_occ_of_L4_06"], + "title": ["Average Cluster trend: L4_06", "Stddev Cluster trend: L4_06", "Active chips trend: L4_06", "Occupancy trend: L4_06"], + "varexp": ["Cluster_l4.mean[6]:ntreeentries", "Cluster_l4.stddev[6]:ntreeentries", "Cluster_l4.entries[6]:ntreeentries", "Occupancy_l4.mean[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_07", "cluster_stddev_of_L4_07", "cluster_chips_of_L4_07", "cluster_occ_of_L4_07"], + "title": ["Average Cluster trend: L4_07", "Stddev Cluster trend: L4_07", "Active chips trend: L4_07", "Occupancy trend: L4_07"], + "varexp": ["Cluster_l4.mean[7]:ntreeentries", "Cluster_l4.stddev[7]:ntreeentries", "Cluster_l4.entries[7]:ntreeentries", "Occupancy_l4.mean[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_08", "cluster_stddev_of_L4_08", "cluster_chips_of_L4_08", "cluster_occ_of_L4_08"], + "title": ["Average Cluster trend: L4_08", "Stddev Cluster trend: L4_08", "Active chips trend: L4_08", "Occupancy trend: L4_08"], + "varexp": ["Cluster_l4.mean[8]:ntreeentries", "Cluster_l4.stddev[8]:ntreeentries", "Cluster_l4.entries[8]:ntreeentries", "Occupancy_l4.mean[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_09", "cluster_stddev_of_L4_09", "cluster_chips_of_L4_09", "cluster_occ_of_L4_09"], + "title": ["Average Cluster trend: L4_09", "Stddev Cluster trend: L4_09", "Active chips trend: L4_09", "Occupancy trend: L4_09"], + "varexp": ["Cluster_l4.mean[9]:ntreeentries", "Cluster_l4.stddev[9]:ntreeentries", "Cluster_l4.entries[9]:ntreeentries", "Occupancy_l4.mean[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_10", "cluster_stddev_of_L0_10", "cluster_chips_of_L4_10", "cluster_occ_of_L4_10"], + "title": ["Average Cluster trend: L4_10", "Stddev Cluster trend: L4_10", "Active chips trend: L4_10", "Occupancy trend: L4_10"], + "varexp": ["Cluster_l4.mean[10]:ntreeentries", "Cluster_l4.stddev[10]:ntreeentries", "Cluster_l4.entries[10]:ntreeentries", "Occupancy_l4.mean[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_11", "cluster_stddev_of_L4_11", "cluster_chips_of_L4_11", "cluster_occ_of_L4_11"], + "title": ["Average Cluster trend: L4_11", "Stddev Cluster trend: L4_11", "Active chips trend: L4_11", "Occupancy trend: L4_11"], + "varexp": ["Cluster_l4.mean[11]:ntreeentries", "Cluster_l4.stddev[11]:ntreeentries", "Cluster_l4.entries[11]:ntreeentries", "Occupancy_l4.mean[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_12", "cluster_stddev_of_L4_12", "cluster_chips_of_L4_12", "cluster_occ_of_L4_12"], + "title": ["Average Cluster trend: L1_00", "Stddev Cluster trend: L1_00", "Active chips trend: L4_12", "Occupancy trend: L4_12"], + "varexp": ["Cluster_l4.mean[12]:ntreeentries", "Cluster_l4.stddev[12]:ntreeentries", "Cluster_l4.entries[12]:ntreeentries", "Occupancy_l4.mean[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_13", "cluster_stddev_of_L4_13", "cluster_chips_of_L4_13", "cluster_occ_of_L4_13"], + "title": ["Average Cluster trend: L4_13", "Stddev Cluster trend: L4_13", "Active chips trend: L4_13", "Occupancy trend: L4_13"], + "varexp": ["Cluster_l4.mean[13]:ntreeentries", "Cluster_l4.stddev[13]:ntreeentries", "Cluster_l4.entries[13]:ntreeentries", "Occupancy_l4.mean[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_14", "cluster_stddev_of_L4_14", "cluster_chips_of_L4_14", "cluster_occ_of_L4_14"], + "title": ["Average Cluster trend: L4_14", "Stddev Cluster trend: L4_14", "Active chips trend: L4_14", "Occupancy trend: L4_14"], + "varexp": ["Cluster_l4.mean[14]:ntreeentries", "Cluster_l4.stddev[14]:ntreeentries", "Cluster_l4.entries[14]:ntreeentries", "Occupancy_l4.mean[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_15", "cluster_stddev_of_L4_15", "cluster_chips_of_L4_15", "cluster_occ_of_L4_15"], + "title": ["Average Cluster trend: L4_15", "Stddev Cluster trend: L4_15", "Active chips trend: L4_15", "Occupancy trend: L4_15"], + "varexp": ["Cluster_l4.mean[15]:ntreeentries", "Cluster_l4.stddev[15]:ntreeentries", "Cluster_l4.entries[15]:ntreeentries", "Occupancy_l4.mean[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_16", "cluster_stddev_of_L4_16", "cluster_chips_of_L4_16", "cluster_occ_of_L4_16"], + "title": ["Average Cluster trend: L4_16", "Stddev Cluster trend: L4_16", "Active chips trend: L4_16", "Occupancy trend: L4_16"], + "varexp": ["Cluster_l4.mean[16]:ntreeentries", "Cluster_l4.stddev[16]:ntreeentries", "Cluster_l4.entries[16]:ntreeentries", "Occupancy_l4.mean[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_17", "cluster_stddev_of_L4_17", "cluster_chips_of_L4_17", "cluster_occ_of_L4_17"], + "title": ["Average Cluster trend: L4_17", "Stddev Cluster trend: L4_17", "Active chips trend: L4_17", "Occupancy trend: L4_17"], + "varexp": ["Cluster_l4.mean[17]:ntreeentries", "Cluster_l4.stddev[17]:ntreeentries", "Cluster_l4.entries[17]:ntreeentries", "Occupancy_l4.mean[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_18", "cluster_stddev_of_L4_18", "cluster_chips_of_L4_18", "cluster_occ_of_L4_18"], + "title": ["Average Cluster trend: L4_18", "Stddev Cluster trend: L4_18", "Active chips trend: L4_18", "Occupancy trend: L4_18"], + "varexp": ["Cluster_l4.mean[18]:ntreeentries", "Cluster_l4.stddev[18]:ntreeentries", "Cluster_l4.entries[18]:ntreeentries", "Occupancy_l4.mean[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_19", "cluster_stddev_of_L4_19", "cluster_chips_of_L4_19", "cluster_occ_of_L4_19"], + "title": ["Average Cluster trend: L4_19", "Stddev Cluster trend: L4_19", "Active chips trend: L4_19", "Occupancy trend: L4_19"], + "varexp": ["Cluster_l4.mean[19]:ntreeentries", "Cluster_l4.stddev[19]:ntreeentries", "Cluster_l4.entries[19]:ntreeentries", "Occupancy_l4.mean[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_20", "cluster_stddev_of_L4_20", "cluster_chips_of_L4_20", "cluster_occ_of_L4_20"], + "title": ["Average Cluster trend: L4_20", "Stddev Cluster trend: L4_20", "Active chips trend: L4_20", "Occupancy trend: L4_20"], + "varexp": ["Cluster_l4.mean[20]:ntreeentries", "Cluster_l4.stddev[20]:ntreeentries", "Cluster_l4.entries[20]:ntreeentries", "Occupancy_l4.mean[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_21", "cluster_stddev_of_L4_21", "cluster_chips_of_L4_21", "cluster_occ_of_L4_21"], + "title": ["Average Cluster trend: L4_20", "Stddev Cluster trend: L4_21", "Active chips trend: L4_21", "Occupancy trend: L4_21"], + "varexp": ["Cluster_l4.mean[20]:ntreeentries", "Cluster_l4.stddev[21]:ntreeentries", "Cluster_l4.entries[21]:ntreeentries", "Occupancy_l4.mean[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_22", "cluster_stddev_of_L4_22", "cluster_chips_of_L4_22", "cluster_occ_of_L4_22"], + "title": ["Average Cluster trend: L4_22", "Stddev Cluster trend: L4_22", "Active chips trend: L4_22", "Occupancy trend: L4_22"], + "varexp": ["Cluster_l4.mean[22]:ntreeentries", "Cluster_l4.stddev[22]:ntreeentries", "Cluster_l4.entries[22]:ntreeentries", "Occupancy_l4.mean[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_23", "cluster_stddev_of_L4_23", "cluster_chips_of_L4_23", "cluster_occ_of_L4_23"], + "title": ["Average Cluster trend: L4_23", "Stddev Cluster trend: L4_23", "Active chips trend: L4_23", "Occupancy trend: L4_23"], + "varexp": ["Cluster_l4.mean[23]:ntreeentries", "Cluster_l4.stddev[23]:ntreeentries", "Cluster_l4.entries[23]:ntreeentries", "Occupancy_l4.mean[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_24", "cluster_stddev_of_L4_24", "cluster_chips_of_L4_24", "cluster_occ_of_L4_24"], + "title": ["Average Cluster trend: L4_24", "Stddev Cluster trend: L4_24", "Active chips trend: L4_24", "Occupancy trend: L4_24"], + "varexp": ["Cluster_l4.mean[24]:ntreeentries", "Cluster_l4.stddev[24]:ntreeentries", "Cluster_l4.entries[24]:ntreeentries", "Occupancy_l4.mean[24]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_25", "cluster_stddev_of_L4_25", "cluster_chips_of_L4_25", "cluster_occ_of_L4_25"], + "title": ["Average Cluster trend: L4_25", "Stddev Cluster trend: L4_25", "Active chips trend: L4_25", "Occupancy trend: L4_25"], + "varexp": ["Cluster_l4.mean[25]:ntreeentries", "Cluster_l4.stddev[25]:ntreeentries", "Cluster_l4.entries[25]:ntreeentries", "Occupancy_l4.mean[25]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_26", "cluster_stddev_of_L4_26", "cluster_chips_of_L4_26", "cluster_occ_of_L4_26"], + "title": ["Average Cluster trend: L4_26", "Stddev Cluster trend: L4_26", "Active chips trend: L4_26", "Occupancy trend: L4_26"], + "varexp": ["Cluster_l4.mean[26]:ntreeentries", "Cluster_l4.stddev[26]:ntreeentries", "Cluster_l4.entries[26]:ntreeentries", "Occupancy_l4.mean[26]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_27", "cluster_stddev_of_L4_27", "cluster_chips_of_L4_27", "cluster_occ_of_L4_27"], + "title": ["Average Cluster trend: L4_19", "Stddev Cluster trend: L4_19", "Active chips trend: L4_19", "Occupancy trend: L4_27"], + "varexp": ["Cluster_l4.mean[27]:ntreeentries", "Cluster_l4.stddev[27]:ntreeentries", "Cluster_l4.entries[27]:ntreeentries", "Occupancy_l4.mean[27]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_28", "cluster_stddev_of_L4_28", "cluster_chips_of_L4_28", "cluster_occ_of_L4_28"], + "title": ["Average Cluster trend: L4_28", "Stddev Cluster trend: L4_28", "Active chips trend: L4_28", "Occupancy trend: L4_28"], + "varexp": ["Cluster_l4.mean[28]:ntreeentries", "Cluster_l4.stddev[28]:ntreeentries", "Cluster_l4.entries[28]:ntreeentries", "Occupancy_l4.mean[28]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L4_29", "cluster_stddev_of_L4_29", "cluster_chips_of_L4_29", "cluster_occ_of_L4_29"], + "title": ["Average Cluster trend: L4_29", "Stddev Cluster trend: L4_29", "Active chips trend: L4_29", "Occupancy trend: L4_29"], + "varexp": ["Cluster_l4.mean[29]:ntreeentries", "Cluster_l4.stddev[29]:ntreeentries", "Cluster_l4.entries[29]:ntreeentries", "Occupancy_l4.mean[29]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_00", "cluster_stddev_of_L5_00", "cluster_chips_of_L5_00", "cluster_occ_of_L5_00"], + "title": ["Average Cluster trend: L5_00", "Stddev Cluster trend: L5_00", "Active chips trend: L5_00", "Occupancy trend: L5_00"], + "varexp": ["Cluster_l5.mean[0]:ntreeentries", "Cluster_l5.stddev[0]:ntreeentries", "Cluster_l5.entries[0]:ntreeentries", "Occupancy_l5.mean[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_01", "cluster_stddev_of_L5_01", "cluster_chips_of_L5_01", "cluster_occ_of_L5_01"], + "title": ["Average Cluster trend: L5_01", "Stddev Cluster trend: L5_01", "Active chips trend: L5_01", "Occupancy trend: L5_01"], + "varexp": ["Cluster_l5.mean[1]:ntreeentries", "Cluster_l5.stddev[1]:ntreeentries", "Cluster_l5.entries[1]:ntreeentries", "Occupancy_l5.mean[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_02", "cluster_stddev_of_L5_02", "cluster_chips_of_L5_02", "cluster_occ_of_L5_02"], + "title": ["Average Cluster trend: L5_02", "Stddev Cluster trend: L5_02", "Active chips trend: L5_02", "Occupancy trend: L5_02"], + "varexp": ["Cluster_l5.mean[2]:ntreeentries", "Cluster_l5.stddev[2]:ntreeentries", "Cluster_l5.entries[2]:ntreeentries", "Occupancy_l5.mean[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_03", "cluster_stddev_of_L5_03", "cluster_chips_of_L5_03", "cluster_occ_of_L5_03"], + "title": ["Average Cluster trend: L5_03", "Stddev Cluster trend: L5_03", "Active chips trend: L5_03", "Occupancy trend: L5_03"], + "varexp": ["Cluster_l5.mean[3]:ntreeentries", "Cluster_l5.stddev[3]:ntreeentries", "Cluster_l5.entries[3]:ntreeentries", "Occupancy_l5.mean[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_04", "cluster_stddev_of_L5_04", "cluster_chips_of_L5_04", "cluster_occ_of_L5_04"], + "title": ["Average Cluster trend: L5_04", "Stddev Cluster trend: L5_04", "Active chips trend: L5_04", "Occupancy trend: L5_04"], + "varexp": ["Cluster_l5.mean[4]:ntreeentries", "Cluster_l5.stddev[4]:ntreeentries", "Cluster_l5.entries[4]:ntreeentries", "Occupancy_l5.mean[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_05", "cluster_stddev_of_L5_05", "cluster_chips_of_L5_05", "cluster_occ_of_L5_05"], + "title": ["Average Cluster trend: L5_05", "Stddev Cluster trend: L5_05", "Active chips trend: L5_05", "Occupancy trend: L5_05"], + "varexp": ["Cluster_l5.mean[5]:ntreeentries", "Cluster_l5.stddev[5]:ntreeentries", "Cluster_l5.entries[5]:ntreeentries", "Occupancy_l5.mean[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_06", "cluster_stddev_of_L5_06", "cluster_chips_of_L5_06", "cluster_occ_of_L5_06"], + "title": ["Average Cluster trend: L5_06", "Stddev Cluster trend: L5_06", "Active chips trend: L5_06", "Occupancy trend: L5_06"], + "varexp": ["Cluster_l5.mean[6]:ntreeentries", "Cluster_l5.stddev[6]:ntreeentries", "Cluster_l5.entries[6]:ntreeentries", "Occupancy_l5.mean[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_07", "cluster_stddev_of_L5_07", "cluster_chips_of_L5_07", "cluster_occ_of_L5_07"], + "title": ["Average Cluster trend: L5_07", "Stddev Cluster trend: L5_07", "Active chips trend: L5_07", "Occupancy trend: L5_07"], + "varexp": ["Cluster_l5.mean[7]:ntreeentries", "Cluster_l5.stddev[7]:ntreeentries", "Cluster_l5.entries[7]:ntreeentries", "Occupancy_l5.mean[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_08", "cluster_stddev_of_L5_08", "cluster_chips_of_L5_08", "cluster_occ_of_L5_08"], + "title": ["Average Cluster trend: L5_08", "Stddev Cluster trend: L5_08", "Active chips trend: L5_08", "Occupancy trend: L5_08"], + "varexp": ["Cluster_l5.mean[8]:ntreeentries", "Cluster_l5.stddev[8]:ntreeentries", "Cluster_l5.entries[8]:ntreeentries", "Occupancy_l5.mean[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_09", "cluster_stddev_of_L5_09", "cluster_chips_of_L5_09", "cluster_occ_of_L5_09"], + "title": ["Average Cluster trend: L5_09", "Stddev Cluster trend: L5_09", "Active chips trend: L5_09", "Occupancy trend: L5_09"], + "varexp": ["Cluster_l5.mean[9]:ntreeentries", "Cluster_l5.stddev[9]:ntreeentries", "Cluster_l5.entries[9]:ntreeentries", "Occupancy_l5.mean[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_10", "cluster_stddev_of_L0_10", "cluster_chips_of_L5_10", "cluster_occ_of_L5_10"], + "title": ["Average Cluster trend: L5_10", "Stddev Cluster trend: L5_10", "Active chips trend: L5_10", "Occupancy trend: L5_10"], + "varexp": ["Cluster_l5.mean[10]:ntreeentries", "Cluster_l5.stddev[10]:ntreeentries", "Cluster_l5.entries[10]:ntreeentries", "Occupancy_l5.mean[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_11", "cluster_stddev_of_L5_11", "cluster_chips_of_L5_11", "cluster_occ_of_L5_11"], + "title": ["Average Cluster trend: L5_11", "Stddev Cluster trend: L5_11", "Active chips trend: L5_11", "Occupancy trend: L5_11"], + "varexp": ["Cluster_l5.mean[11]:ntreeentries", "Cluster_l5.stddev[11]:ntreeentries", "Cluster_l5.entries[11]:ntreeentries", "Occupancy_l5.mean[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_12", "cluster_stddev_of_L5_12", "cluster_chips_of_L5_12", "cluster_occ_of_L5_12"], + "title": ["Average Cluster trend: L5_00", "Stddev Cluster trend: L5_00", "Active chips trend: L5_12", "Occupancy trend: L5_12"], + "varexp": ["Cluster_l5.mean[12]:ntreeentries", "Cluster_l5.stddev[12]:ntreeentries", "Cluster_l5.entries[12]:ntreeentries", "Occupancy_l5.mean[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_13", "cluster_stddev_of_L5_13", "cluster_chips_of_L5_13", "cluster_occ_of_L5_13"], + "title": ["Average Cluster trend: L5_13", "Stddev Cluster trend: L5_13", "Active chips trend: L5_13", "Occupancy trend: L5_13"], + "varexp": ["Cluster_l5.mean[13]:ntreeentries", "Cluster_l5.stddev[13]:ntreeentries", "Cluster_l5.entries[13]:ntreeentries", "Occupancy_l5.mean[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_14", "cluster_stddev_of_L5_14", "cluster_chips_of_L5_14", "cluster_occ_of_L5_14"], + "title": ["Average Cluster trend: L5_14", "Stddev Cluster trend: L5_14", "Active chips trend: L5_14", "Occupancy trend: L5_14"], + "varexp": ["Cluster_l5.mean[14]:ntreeentries", "Cluster_l5.stddev[14]:ntreeentries", "Cluster_l5.entries[14]:ntreeentries", "Occupancy_l5.mean[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_15", "cluster_stddev_of_L5_15", "cluster_chips_of_L5_15", "cluster_occ_of_L5_15"], + "title": ["Average Cluster trend: L5_15", "Stddev Cluster trend: L5_15", "Active chips trend: L5_15", "Occupancy trend: L5_15"], + "varexp": ["Cluster_l5.mean[15]:ntreeentries", "Cluster_l5.stddev[15]:ntreeentries", "Cluster_l5.entries[15]:ntreeentries", "Occupancy_l5.mean[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_16", "cluster_stddev_of_L5_16", "cluster_chips_of_L5_16", "cluster_occ_of_L5_16"], + "title": ["Average Cluster trend: L5_16", "Stddev Cluster trend: L5_16", "Active chips trend: L5_16", "Occupancy trend: L5_16"], + "varexp": ["Cluster_l5.mean[16]:ntreeentries", "Cluster_l5.stddev[16]:ntreeentries", "Cluster_l5.entries[16]:ntreeentries", "Occupancy_l5.mean[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_17", "cluster_stddev_of_L5_17", "cluster_chips_of_L5_17", "cluster_occ_of_L5_17"], + "title": ["Average Cluster trend: L5_17", "Stddev Cluster trend: L5_17", "Active chips trend: L5_17", "Occupancy trend: L5_17"], + "varexp": ["Cluster_l5.mean[17]:ntreeentries", "Cluster_l5.stddev[17]:ntreeentries", "Cluster_l5.entries[17]:ntreeentries", "Occupancy_l5.mean[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_18", "cluster_stddev_of_L5_18", "cluster_chips_of_L5_18", "cluster_occ_of_L5_18"], + "title": ["Average Cluster trend: L5_18", "Stddev Cluster trend: L5_18", "Active chips trend: L5_18", "Occupancy trend: L5_18"], + "varexp": ["Cluster_l5.mean[18]:ntreeentries", "Cluster_l5.stddev[18]:ntreeentries", "Cluster_l5.entries[18]:ntreeentries", "Occupancy_l5.mean[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_19", "cluster_stddev_of_L5_19", "cluster_chips_of_L5_19", "cluster_occ_of_L5_19"], + "title": ["Average Cluster trend: L5_19", "Stddev Cluster trend: L5_19", "Active chips trend: L5_19", "Occupancy trend: L5_19"], + "varexp": ["Cluster_l5.mean[19]:ntreeentries", "Cluster_l5.stddev[19]:ntreeentries", "Cluster_l5.entries[19]:ntreeentries", "Occupancy_l5.mean[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_20", "cluster_stddev_of_L5_20", "cluster_chips_of_L5_20", "cluster_occ_of_L5_20"], + "title": ["Average Cluster trend: L5_20", "Stddev Cluster trend: L5_20", "Active chips trend: L5_20", "Occupancy trend: L5_20"], + "varexp": ["Cluster_l5.mean[20]:ntreeentries", "Cluster_l5.stddev[20]:ntreeentries", "Cluster_l5.entries[20]:ntreeentries", "Occupancy_l5.mean[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_21", "cluster_stddev_of_L5_21", "cluster_chips_of_L5_21", "cluster_occ_of_L5_21"], + "title": ["Average Cluster trend: L5_21", "Stddev Cluster trend: L5_21", "Active chips trend: L5_21", "Occupancy trend: L5_21"], + "varexp": ["Cluster_l5.mean[21]:ntreeentries", "Cluster_l5.stddev[21]:ntreeentries", "Cluster_l5.entries[21]:ntreeentries", "Occupancy_l5.mean[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_22", "cluster_stddev_of_L5_22", "cluster_chips_of_L5_22", "cluster_occ_of_L5_22"], + "title": ["Average Cluster trend: L5_22", "Stddev Cluster trend: L5_22", "Active chips trend: L5_22", "Occupancy trend: L5_22"], + "varexp": ["Cluster_l5.mean[22]:ntreeentries", "Cluster_l5.stddev[22]:ntreeentries", "Cluster_l5.entries[22]:ntreeentries", "Occupancy_l5.mean[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_23", "cluster_stddev_of_L5_23", "cluster_chips_of_L5_23", "cluster_occ_of_L5_23"], + "title": ["Average Cluster trend: L5_23", "Stddev Cluster trend: L5_23", "Active chips trend: L5_23", "Occupancy trend: L5_23"], + "varexp": ["Cluster_l5.mean[23]:ntreeentries", "Cluster_l5.stddev[23]:ntreeentries", "Cluster_l5.entries[23]:ntreeentries", "Occupancy_l5.mean[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_24", "cluster_stddev_of_L5_24", "cluster_chips_of_L5_24", "cluster_occ_of_L5_24"], + "title": ["Average Cluster trend: L5_24", "Stddev Cluster trend: L5_24", "Active chips trend: L5_24", "Occupancy trend: L5_24"], + "varexp": ["Cluster_l5.mean[24]:ntreeentries", "Cluster_l5.stddev[24]:ntreeentries", "Cluster_l5.entries[24]:ntreeentries", "Occupancy_l5.mean[24]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_25", "cluster_stddev_of_L5_25", "cluster_chips_of_L5_25", "cluster_occ_of_L5_25"], + "title": ["Average Cluster trend: L5_25", "Stddev Cluster trend: L5_25", "Active chips trend: L5_25", "Occupancy trend: L5_25"], + "varexp": ["Cluster_l5.mean[25]:ntreeentries", "Cluster_l5.stddev[25]:ntreeentries", "Cluster_l5.entries[25]:ntreeentries", "Occupancy_l5.mean[25]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_26", "cluster_stddev_of_L5_26", "cluster_chips_of_L5_26", "cluster_occ_of_L5_26"], + "title": ["Average Cluster trend: L5_26", "Stddev Cluster trend: L5_26", "Active chips trend: L5_26", "Occupancy trend: L5_26"], + "varexp": ["Cluster_l5.mean[26]:ntreeentries", "Cluster_l5.stddev[26]:ntreeentries", "Cluster_l5.entries[26]:ntreeentries", "Occupancy_l5.mean[26]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_27", "cluster_stddev_of_L5_27", "cluster_chips_of_L5_27", "cluster_occ_of_L5_27"], + "title": ["Average Cluster trend: L5_27", "Stddev Cluster trend: L5_27", "Active chips trend: L5_27", "Occupancy trend: L5_27"], + "varexp": ["Cluster_l5.mean[27]:ntreeentries", "Cluster_l5.stddev[27]:ntreeentries", "Cluster_l5.entries[27]:ntreeentries", "Occupancy_l5.mean[27]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_28", "cluster_stddev_of_L5_28", "cluster_chips_of_L5_28", "cluster_occ_of_L5_28"], + "title": ["Average Cluster trend: L5_28", "Stddev Cluster trend: L5_28", "Active chips trend: L5_28", "Occupancy trend: L5_28"], + "varexp": ["Cluster_l5.mean[28]:ntreeentries", "Cluster_l5.stddev[28]:ntreeentries", "Cluster_l5.entries[28]:ntreeentries", "Occupancy_l5.mean[28]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_29", "cluster_stddev_of_L5_29", "cluster_chips_of_L5_29", "cluster_occ_of_L5_29"], + "title": ["Average Cluster trend: L5_29", "Stddev Cluster trend: L5_29", "Active chips trend: L5_29", "Occupancy trend: L5_29"], + "varexp": ["Cluster_l5.mean[29]:ntreeentries", "Cluster_l5.stddev[29]:ntreeentries", "Cluster_l5.entries[29]:ntreeentries", "Occupancy_l5.mean[29]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_30", "cluster_stddev_of_L5_30", "cluster_chips_of_L5_30", "cluster_occ_of_L5_30"], + "title": ["Average Cluster trend: L5_30", "Stddev Cluster trend: L5_30", "Active chips trend: L5_30", "Occupancy trend: L5_30"], + "varexp": ["Cluster_l5.mean[30]:ntreeentries", "Cluster_l5.stddev[30]:ntreeentries", "Cluster_l5.entries[30]:ntreeentries", "Occupancy_l5.mean[30]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_31", "cluster_stddev_of_L5_31", "cluster_chips_of_L5_31", "cluster_occ_of_L5_31"], + "title": ["Average Cluster trend: L5_31", "Stddev Cluster trend: L5_31", "Active chips trend: L5_31", "Occupancy trend: L5_31"], + "varexp": ["Cluster_l5.mean[31]:ntreeentries", "Cluster_l5.stddev[31]:ntreeentries", "Cluster_l5.entries[31]:ntreeentries", "Occupancy_l5.mean[31]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_32", "cluster_stddev_of_L5_32", "cluster_chips_of_L5_32", "cluster_occ_of_L5_32"], + "title": ["Average Cluster trend: L5_32", "Stddev Cluster trend: L5_32", "Active chips trend: L5_32", "Occupancy trend: L5_32"], + "varexp": ["Cluster_l5.mean[32]:ntreeentries", "Cluster_l5.stddev[32]:ntreeentries", "Cluster_l5.entries[32]:ntreeentries", "Occupancy_l5.mean[32]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_33", "cluster_stddev_of_L5_33", "cluster_chips_of_L5_33", "cluster_occ_of_L5_33"], + "title": ["Average Cluster trend: L5_33", "Stddev Cluster trend: L5_33", "Active chips trend: L5_33", "Occupancy trend: L5_33"], + "varexp": ["Cluster_l5.mean[33]:ntreeentries", "Cluster_l5.stddev[33]:ntreeentries", "Cluster_l5.entries[33]:ntreeentries", "Occupancy_l5.mean[33]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_34", "cluster_stddev_of_L5_34", "cluster_chips_of_L5_34", "cluster_occ_of_L5_34"], + "title": ["Average Cluster trend: L5_34", "Stddev Cluster trend: L5_34", "Active chips trend: L5_34", "Occupancy trend: L5_34"], + "varexp": ["Cluster_l5.mean[34]:ntreeentries", "Cluster_l5.stddev[34]:ntreeentries", "Cluster_l5.entries[34]:ntreeentries", "Occupancy_l5.mean[34]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_35", "cluster_stddev_of_L5_35", "cluster_chips_of_L5_35", "cluster_occ_of_L5_35"], + "title": ["Average Cluster trend: L5_35", "Stddev Cluster trend: L5_35", "Active chips trend: L5_35", "Occupancy trend: L5_35"], + "varexp": ["Cluster_l5.mean[35]:ntreeentries", "Cluster_l5.stddev[35]:ntreeentries", "Cluster_l5.entries[35]:ntreeentries", "Occupancy_l5.mean[35]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_36", "cluster_stddev_of_L5_36", "cluster_chips_of_L5_36", "cluster_occ_of_L5_36"], + "title": ["Average Cluster trend: L5_36", "Stddev Cluster trend: L5_36", "Active chips trend: L5_36", "Occupancy trend: L5_36"], + "varexp": ["Cluster_l5.mean[36]:ntreeentries", "Cluster_l5.stddev[36]:ntreeentries", "Cluster_l5.entries[36]:ntreeentries", "Occupancy_l5.mean[36]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_37", "cluster_stddev_of_L5_37", "cluster_chips_of_L5_37", "cluster_occ_of_L5_37"], + "title": ["Average Cluster trend: L5_37", "Stddev Cluster trend: L5_37", "Active chips trend: L5_37", "Occupancy trend: L5_37"], + "varexp": ["Cluster_l5.mean[37]:ntreeentries", "Cluster_l5.stddev[37]:ntreeentries", "Cluster_l5.entries[37]:ntreeentries", "Occupancy_l5.mean[37]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_38", "cluster_stddev_of_L5_38", "cluster_chips_of_L5_38", "cluster_occ_of_L5_38"], + "title": ["Average Cluster trend: L5_38", "Stddev Cluster trend: L5_38", "Active chips trend: L5_38", "Occupancy trend: L5_38"], + "varexp": ["Cluster_l5.mean[38]:ntreeentries", "Cluster_l5.stddev[38]:ntreeentries", "Cluster_l5.entries[38]:ntreeentries", "Occupancy_l5.mean[38]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_39", "cluster_stddev_of_L5_39", "cluster_chips_of_L5_39", "cluster_occ_of_L5_39"], + "title": ["Average Cluster trend: L5_39", "Stddev Cluster trend: L5_39", "Active chips trend: L5_39", "Occupancy trend: L5_39"], + "varexp": ["Cluster_l5.mean[39]:ntreeentries", "Cluster_l5.stddev[39]:ntreeentries", "Cluster_l5.entries[39]:ntreeentries", "Occupancy_l5.mean[39]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_40", "cluster_stddev_of_L5_40", "cluster_chips_of_L5_40", "cluster_occ_of_L5_40"], + "title": ["Average Cluster trend: L5_40", "Stddev Cluster trend: L5_40", "Active chips trend: L5_40", "Occupancy trend: L5_40"], + "varexp": ["Cluster_l5.mean[40]:ntreeentries", "Cluster_l5.stddev[40]:ntreeentries", "Cluster_l5.entries[40]:ntreeentries", "Occupancy_l5.mean[40]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_0f_L5_41", "cluster_stddev_of_L5_41", "cluster_chips_of_L5_41", "cluster_occ_of_L5_41"], + "title": ["Average Cluster trend: L5_41", "Stddev Cluster trend: L5_41", "Active chips trend: L5_41", "Occupancy trend: L5_41"], + "varexp": ["Cluster_l5.mean[41]:ntreeentries", "Cluster_l5.stddev[41]:ntreeentries", "Cluster_l5.entries[41]:ntreeentries", "Occupancy_l5.mean[41]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_00", "cluster_stddev_of_L6_00", "cluster_chips_of_L6_00", "cluster_occ_of_L6_00"], + "title": ["Average Cluster trend: L6_00", "Stddev Cluster trend: L6_00", "Active chips trend: L6_00", "Occupancy trend: L6_00"], + "varexp": ["Cluster_l6.mean[0]:ntreeentries", "Cluster_l6.stddev[0]:ntreeentries", "Cluster_l6.entries[0]:ntreeentries", "Occupancy_l6.mean[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_01", "cluster_stddev_of_L6_01", "cluster_chips_of_L6_01", "cluster_occ_of_L6_01"], + "title": ["Average Cluster trend: L6_01", "Stddev Cluster trend: L6_01", "Active chips trend: L6_01", "Occupancy trend: L6_01"], + "varexp": ["Cluster_l6.mean[1]:ntreeentries", "Cluster_l6.stddev[1]:ntreeentries", "Cluster_l6.entries[1]:ntreeentries", "Occupancy_l6.mean[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_02", "cluster_stddev_of_L6_02", "cluster_chips_of_L6_02", "cluster_occ_of_L6_02"], + "title": ["Average Cluster trend: L6_02", "Stddev Cluster trend: L6_02", "Active chips trend: L6_02", "Occupancy trend: L6_02"], + "varexp": ["Cluster_l6.mean[2]:ntreeentries", "Cluster_l6.stddev[2]:ntreeentries", "Cluster_l6.entries[2]:ntreeentries", "Occupancy_l6.mean[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_03", "cluster_stddev_of_L6_03", "cluster_chips_of_L6_03", "cluster_occ_of_L6_03"], + "title": ["Average Cluster trend: L6_03", "Stddev Cluster trend: L6_03", "Active chips trend: L6_03", "Occupancy trend: L6_03"], + "varexp": ["Cluster_l6.mean[3]:ntreeentries", "Cluster_l6.stddev[3]:ntreeentries", "Cluster_l6.entries[3]:ntreeentries", "Occupancy_l6.mean[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_04", "cluster_stddev_of_L6_04", "cluster_chips_of_L6_04", "cluster_occ_of_L6_04"], + "title": ["Average Cluster trend: L6_04", "Stddev Cluster trend: L6_04", "Active chips trend: L6_04", "Occupancy trend: L6_04"], + "varexp": ["Cluster_l6.mean[4]:ntreeentries", "Cluster_l6.stddev[4]:ntreeentries", "Cluster_l6.entries[4]:ntreeentries", "Occupancy_l6.mean[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_05", "cluster_stddev_of_L6_05", "cluster_chips_of_L6_05", "cluster_occ_of_L6_05"], + "title": ["Average Cluster trend: L6_05", "Stddev Cluster trend: L6_05", "Active chips trend: L6_05", "Occupancy trend: L6_05"], + "varexp": ["Cluster_l6.mean[5]:ntreeentries", "Cluster_l6.stddev[5]:ntreeentries", "Cluster_l6.entries[5]:ntreeentries", "Occupancy_l6.mean[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_06", "cluster_stddev_of_L6_06", "cluster_chips_of_L6_06", "cluster_occ_of_L6_06"], + "title": ["Average Cluster trend: L6_06", "Stddev Cluster trend: L6_06", "Active chips trend: L6_06", "Occupancy trend: L6_06"], + "varexp": ["Cluster_l6.mean[6]:ntreeentries", "Cluster_l6.stddev[6]:ntreeentries", "Cluster_l6.entries[6]:ntreeentries", "Occupancy_l6.mean[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_07", "cluster_stddev_of_L6_07", "cluster_chips_of_L6_07", "cluster_occ_of_L6_07"], + "title": ["Average Cluster trend: L6_07", "Stddev Cluster trend: L6_07", "Active chips trend: L6_07", "Occupancy trend: L6_07"], + "varexp": ["Cluster_l6.mean[7]:ntreeentries", "Cluster_l6.stddev[7]:ntreeentries", "Cluster_l6.entries[7]:ntreeentries", "Occupancy_l6.mean[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_08", "cluster_stddev_of_L6_08", "cluster_chips_of_L6_08", "cluster_occ_of_L6_08"], + "title": ["Average Cluster trend: L6_08", "Stddev Cluster trend: L6_08", "Active chips trend: L6_08", "Occupancy trend: L6_08"], + "varexp": ["Cluster_l6.mean[8]:ntreeentries", "Cluster_l6.stddev[8]:ntreeentries", "Cluster_l6.entries[8]:ntreeentries", "Occupancy_l6.mean[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_09", "cluster_stddev_of_L6_09", "cluster_chips_of_L6_09", "cluster_occ_of_L6_09"], + "title": ["Average Cluster trend: L6_09", "Stddev Cluster trend: L6_09", "Active chips trend: L6_09", "Occupancy trend: L6_09"], + "varexp": ["Cluster_l6.mean[9]:ntreeentries", "Cluster_l6.stddev[9]:ntreeentries", "Cluster_l6.entries[9]:ntreeentries", "Occupancy_l6.mean[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_10", "cluster_stddev_of_L0_10", "cluster_chips_of_L6_10", "cluster_occ_of_L6_10"], + "title": ["Average Cluster trend: L6_10", "Stddev Cluster trend: L6_10", "Active chips trend: L6_10", "Occupancy trend: L6_10"], + "varexp": ["Cluster_l6.mean[10]:ntreeentries", "Cluster_l6.stddev[10]:ntreeentries", "Cluster_l6.entries[10]:ntreeentries", "Occupancy_l6.mean[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_11", "cluster_stddev_of_L6_11", "cluster_chips_of_L6_11", "cluster_occ_of_L6_11"], + "title": ["Average Cluster trend: L6_11", "Stddev Cluster trend: L6_11", "Active chips trend: L6_11", "Occupancy trend: L6_11"], + "varexp": ["Cluster_l6.mean[11]:ntreeentries", "Cluster_l6.stddev[11]:ntreeentries", "Cluster_l6.entries[11]:ntreeentries", "Occupancy_l6.mean[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_12", "cluster_stddev_of_L6_12", "cluster_chips_of_L6_12", "cluster_occ_of_L6_12"], + "title": ["Average Cluster trend: L1_00", "Stddev Cluster trend: L1_00", "Active chips trend: L6_12", "Occupancy trend: L6_12"], + "varexp": ["Cluster_l6.mean[12]:ntreeentries", "Cluster_l6.stddev[12]:ntreeentries", "Cluster_l6.entries[12]:ntreeentries", "Occupancy_l6.mean[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_13", "cluster_stddev_of_L6_13", "cluster_chips_of_L6_13", "cluster_occ_of_L6_13"], + "title": ["Average Cluster trend: L6_13", "Stddev Cluster trend: L6_13", "Active chips trend: L6_13", "Occupancy trend: L6_13"], + "varexp": ["Cluster_l6.mean[13]:ntreeentries", "Cluster_l6.stddev[13]:ntreeentries", "Cluster_l6.entries[13]:ntreeentries", "Occupancy_l6.mean[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_14", "cluster_stddev_of_L6_14", "cluster_chips_of_L6_14", "cluster_occ_of_L6_14"], + "title": ["Average Cluster trend: L6_14", "Stddev Cluster trend: L6_14", "Active chips trend: L6_14", "Occupancy trend: L6_14"], + "varexp": ["Cluster_l6.mean[14]:ntreeentries", "Cluster_l6.stddev[14]:ntreeentries", "Cluster_l6.entries[14]:ntreeentries", "Occupancy_l6.mean[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_15", "cluster_stddev_of_L6_15", "cluster_chips_of_L6_15", "cluster_occ_of_L6_15"], + "title": ["Average Cluster trend: L6_15", "Stddev Cluster trend: L6_15", "Active chips trend: L6_15", "Occupancy trend: L6_15"], + "varexp": ["Cluster_l6.mean[15]:ntreeentries", "Cluster_l6.stddev[15]:ntreeentries", "Cluster_l6.entries[15]:ntreeentries", "Occupancy_l6.mean[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_16", "cluster_stddev_of_L6_16", "cluster_chips_of_L6_16", "cluster_occ_of_L6_16"], + "title": ["Average Cluster trend: L6_16", "Stddev Cluster trend: L6_16", "Active chips trend: L6_16", "Occupancy trend: L6_16"], + "varexp": ["Cluster_l6.mean[16]:ntreeentries", "Cluster_l6.stddev[16]:ntreeentries", "Cluster_l6.entries[16]:ntreeentries", "Occupancy_l6.mean[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_17", "cluster_stddev_of_L6_17", "cluster_chips_of_L6_17", "cluster_occ_of_L6_17"], + "title": ["Average Cluster trend: L6_17", "Stddev Cluster trend: L6_17", "Active chips trend: L6_17", "Occupancy trend: L6_17"], + "varexp": ["Cluster_l6.mean[17]:ntreeentries", "Cluster_l6.stddev[17]:ntreeentries", "Cluster_l6.entries[17]:ntreeentries", "Occupancy_l6.mean[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_18", "cluster_stddev_of_L6_18", "cluster_chips_of_L6_18", "cluster_occ_of_L6_18"], + "title": ["Average Cluster trend: L6_18", "Stddev Cluster trend: L6_18", "Active chips trend: L6_18", "Occupancy trend: L6_18"], + "varexp": ["Cluster_l6.mean[18]:ntreeentries", "Cluster_l6.stddev[18]:ntreeentries", "Cluster_l6.entries[18]:ntreeentries", "Occupancy_l6.mean[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_19", "cluster_stddev_of_L6_19", "cluster_chips_of_L6_19", "cluster_occ_of_L6_19"], + "title": ["Average Cluster trend: L6_19", "Stddev Cluster trend: L6_19", "Active chips trend: L6_19", "Occupancy trend: L6_19"], + "varexp": ["Cluster_l6.mean[19]:ntreeentries", "Cluster_l6.stddev[19]:ntreeentries", "Cluster_l6.entries[19]:ntreeentries", "Occupancy_l6.mean[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_20", "cluster_stddev_of_L6_20", "cluster_chips_of_L6_20", "cluster_occ_of_L6_20"], + "title": ["Average Cluster trend: L6_20", "Stddev Cluster trend: L6_20", "Active chips trend: L6_20", "Occupancy trend: L6_20"], + "varexp": ["Cluster_l6.mean[20]:ntreeentries", "Cluster_l6.stddev[20]:ntreeentries", "Cluster_l6.entries[20]:ntreeentries", "Occupancy_l6.mean[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_21", "cluster_stddev_of_L6_21", "cluster_chips_of_L6_21", "cluster_occ_of_L6_21"], + "title": ["Average Cluster trend: L6_21", "Stddev Cluster trend: L6_21", "Active chips trend: L6_21", "Occupancy trend: L6_21"], + "varexp": ["Cluster_l6.mean[21]:ntreeentries", "Cluster_l6.stddev[21]:ntreeentries", "Cluster_l6.entries[21]:ntreeentries", "Occupancy_l6.mean[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_22", "cluster_stddev_of_L6_22", "cluster_chips_of_L6_22", "cluster_occ_of_L6_22"], + "title": ["Average Cluster trend: L6_22", "Stddev Cluster trend: L6_22", "Active chips trend: L6_22", "Occupancy trend: L6_22"], + "varexp": ["Cluster_l6.mean[22]:ntreeentries", "Cluster_l6.stddev[22]:ntreeentries", "Cluster_l6.entries[22]:ntreeentries", "Occupancy_l6.mean[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_23", "cluster_stddev_of_L6_23", "cluster_chips_of_L6_23", "cluster_occ_of_L6_23"], + "title": ["Average Cluster trend: L6_23", "Stddev Cluster trend: L6_23", "Active chips trend: L6_23", "Occupancy trend: L6_23"], + "varexp": ["Cluster_l6.mean[23]:ntreeentries", "Cluster_l6.stddev[23]:ntreeentries", "Cluster_l6.entries[23]:ntreeentries", "Occupancy_l6.mean[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_24", "cluster_stddev_of_L6_24", "cluster_chips_of_L6_24", "cluster_occ_of_L6_24"], + "title": ["Average Cluster trend: L6_24", "Stddev Cluster trend: L6_24", "Active chips trend: L6_24", "Occupancy trend: L6_24"], + "varexp": ["Cluster_l6.mean[24]:ntreeentries", "Cluster_l6.stddev[24]:ntreeentries", "Cluster_l6.entries[24]:ntreeentries", "Occupancy_l6.mean[24]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_25", "cluster_stddev_of_L6_25", "cluster_chips_of_L6_25", "cluster_occ_of_L6_25"], + "title": ["Average Cluster trend: L6_25", "Stddev Cluster trend: L6_25", "Active chips trend: L6_25", "Occupancy trend: L6_25"], + "varexp": ["Cluster_l6.mean[25]:ntreeentries", "Cluster_l6.stddev[25]:ntreeentries", "Cluster_l6.entries[25]:ntreeentries", "Occupancy_l6.mean[25]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_26", "cluster_stddev_of_L6_26", "cluster_chips_of_L6_26", "cluster_occ_of_L6_26"], + "title": ["Average Cluster trend: L6_26", "Stddev Cluster trend: L6_26", "Active chips trend: L6_26", "Occupancy trend: L6_26"], + "varexp": ["Cluster_l6.mean[26]:ntreeentries", "Cluster_l6.stddev[26]:ntreeentries", "Cluster_l6.entries[26]:ntreeentries", "Occupancy_l6.mean[26]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_27", "cluster_stddev_of_L6_27", "cluster_chips_of_L6_27", "cluster_occ_of_L6_27"], + "title": ["Average Cluster trend: L6_27", "Stddev Cluster trend: L6_27", "Active chips trend: L6_27", "Occupancy trend: L6_27"], + "varexp": ["Cluster_l6.mean[27]:ntreeentries", "Cluster_l6.stddev[27]:ntreeentries", "Cluster_l6.entries[27]:ntreeentries", "Occupancy_l6.mean[27]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_28", "cluster_stddev_of_L6_28", "cluster_chips_of_L6_28", "cluster_occ_of_L6_28"], + "title": ["Average Cluster trend: L6_28", "Stddev Cluster trend: L6_28", "Active chips trend: L6_28", "Occupancy trend: L6_28"], + "varexp": ["Cluster_l6.mean[28]:ntreeentries", "Cluster_l6.stddev[28]:ntreeentries", "Cluster_l6.entries[28]:ntreeentries", "Occupancy_l6.mean[28]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_29", "cluster_stddev_of_L6_29", "cluster_chips_of_L6_29", "cluster_occ_of_L6_29"], + "title": ["Average Cluster trend: L6_29", "Stddev Cluster trend: L6_29", "Active chips trend: L6_29", "Occupancy trend: L6_29"], + "varexp": ["Cluster_l6.mean[29]:ntreeentries", "Cluster_l6.stddev[29]:ntreeentries", "Cluster_l6.entries[29]:ntreeentries", "Occupancy_l6.mean[29]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_30", "cluster_stddev_of_L6_30", "cluster_chips_of_L6_30", "cluster_occ_of_L6_30"], + "title": ["Average Cluster trend: L6_30", "Stddev Cluster trend: L6_30", "Active chips trend: L6_30", "Occupancy trend: L6_30"], + "varexp": ["Cluster_l6.mean[30]:ntreeentries", "Cluster_l6.stddev[30]:ntreeentries", "Cluster_l6.entries[30]:ntreeentries", "Occupancy_l6.mean[30]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_31", "cluster_stddev_of_L6_31", "cluster_chips_of_L6_31", "cluster_occ_of_L6_31"], + "title": ["Average Cluster trend: L6_31", "Stddev Cluster trend: L6_31", "Active chips trend: L6_31", "Occupancy trend: L6_31"], + "varexp": ["Cluster_l6.mean[31]:ntreeentries", "Cluster_l6.stddev[31]:ntreeentries", "Cluster_l6.entries[31]:ntreeentries", "Occupancy_l6.mean[31]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_32", "cluster_stddev_of_L6_32", "cluster_chips_of_L6_32", "cluster_occ_of_L6_32"], + "title": ["Average Cluster trend: L6_32", "Stddev Cluster trend: L6_32", "Active chips trend: L6_32", "Occupancy trend: L6_32"], + "varexp": ["Cluster_l6.mean[32]:ntreeentries", "Cluster_l6.stddev[32]:ntreeentries", "Cluster_l6.entries[32]:ntreeentries", "Occupancy_l6.mean[32]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_33", "cluster_stddev_of_L6_33", "cluster_chips_of_L6_33", "cluster_occ_of_L6_33"], + "title": ["Average Cluster trend: L6_33", "Stddev Cluster trend: L6_33", "Active chips trend: L6_33", "Occupancy trend: L6_33"], + "varexp": ["Cluster_l6.mean[33]:ntreeentries", "Cluster_l6.stddev[33]:ntreeentries", "Cluster_l6.entries[33]:ntreeentries", "Occupancy_l6.mean[33]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_34", "cluster_stddev_of_L6_34", "cluster_chips_of_L6_34", "cluster_occ_of_L6_34"], + "title": ["Average Cluster trend: L6_34", "Stddev Cluster trend: L6_34", "Active chips trend: L6_34", "Occupancy trend: L6_34"], + "varexp": ["Cluster_l6.mean[34]:ntreeentries", "Cluster_l6.stddev[34]:ntreeentries", "Cluster_l6.entries[34]:ntreeentries", "Occupancy_l6.mean[34]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_35", "cluster_stddev_of_L6_35", "cluster_chips_of_L6_35", "cluster_occ_of_L6_35"], + "title": ["Average Cluster trend: L6_35", "Stddev Cluster trend: L6_35", "Active chips trend: L6_35", "Occupancy trend: L6_35"], + "varexp": ["Cluster_l6.mean[35]:ntreeentries", "Cluster_l6.stddev[35]:ntreeentries", "Cluster_l6.entries[35]:ntreeentries", "Occupancy_l6.mean[35]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_36", "cluster_stddev_of_L6_36", "cluster_chips_of_L6_36", "cluster_occ_of_L6_36"], + "title": ["Average Cluster trend: L6_36", "Stddev Cluster trend: L6_36", "Active chips trend: L6_36", "Occupancy trend: L6_36"], + "varexp": ["Cluster_l6.mean[36]:ntreeentries", "Cluster_l6.stddev[36]:ntreeentries", "Cluster_l6.entries[36]:ntreeentries", "Occupancy_l6.mean[36]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_37", "cluster_stddev_of_L6_37", "cluster_chips_of_L6_37", "cluster_occ_of_L6_37"], + "title": ["Average Cluster trend: L6_37", "Stddev Cluster trend: L6_37", "Active chips trend: L6_37", "Occupancy trend: L6_37"], + "varexp": ["Cluster_l6.mean[37]:ntreeentries", "Cluster_l6.stddev[37]:ntreeentries", "Cluster_l6.entries[37]:ntreeentries", "Occupancy_l6.mean[37]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_38", "cluster_stddev_of_L6_38", "cluster_chips_of_L6_38", "cluster_occ_of_L6_38"], + "title": ["Average Cluster trend: L6_38", "Stddev Cluster trend: L6_38", "Active chips trend: L6_38", "Occupancy trend: L6_38"], + "varexp": ["Cluster_l6.mean[38]:ntreeentries", "Cluster_l6.stddev[38]:ntreeentries", "Cluster_l6.entries[38]:ntreeentries", "Occupancy_l6.mean[38]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_39", "cluster_stddev_of_L6_39", "cluster_chips_of_L6_39", "cluster_occ_of_L6_39"], + "title": ["Average Cluster trend: L6_39", "Stddev Cluster trend: L6_39", "Active chips trend: L6_39", "Occupancy trend: L6_39"], + "varexp": ["Cluster_l6.mean[39]:ntreeentries", "Cluster_l6.stddev[39]:ntreeentries", "Cluster_l6.entries[39]:ntreeentries", "Occupancy_l6.mean[39]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_40", "cluster_stddev_of_L6_40", "cluster_chips_of_L6_40", "cluster_occ_of_L6_40"], + "title": ["Average Cluster trend: L6_40", "Stddev Cluster trend: L6_40", "Active chips trend: L6_40", "Occupancy trend: L6_40"], + "varexp": ["Cluster_l6.mean[40]:ntreeentries", "Cluster_l6.stddev[40]:ntreeentries", "Cluster_l6.entries[40]:ntreeentries", "Occupancy_l6.mean[40]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_41", "cluster_stddev_of_L6_41", "cluster_chips_of_L6_41", "cluster_occ_of_L6_41"], + "title": ["Average Cluster trend: L6_41", "Stddev Cluster trend: L6_41", "Active chips trend: L6_41", "Occupancy trend: L6_41"], + "varexp": ["Cluster_l6.mean[41]:ntreeentries", "Cluster_l6.stddev[41]:ntreeentries", "Cluster_l6.entries[41]:ntreeentries", "Occupancy_l6.mean[41]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_42", "cluster_stddev_of_L6_42", "cluster_chips_of_L6_42", "cluster_occ_of_L6_42"], + "title": ["Average Cluster trend: L6_42", "Stddev Cluster trend: L6_42", "Active chips trend: L6_42", "Occupancy trend: L6_42"], + "varexp": ["Cluster_l6.mean[42]:ntreeentries", "Cluster_l6.stddev[42]:ntreeentries", "Cluster_l6.entries[42]:ntreeentries", "Occupancy_l6.mean[42]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_43", "cluster_stddev_of_L6_43", "cluster_chips_of_L6_43", "cluster_occ_of_L6_43"], + "title": ["Average Cluster trend: L6_43", "Stddev Cluster trend: L6_43", "Active chips trend: L6_43", "Occupancy trend: L6_43"], + "varexp": ["Cluster_l6.mean[43]:ntreeentries", "Cluster_l6.stddev[43]:ntreeentries", "Cluster_l6.entries[43]:ntreeentries", "Occupancy_l6.mean[43]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_44", "cluster_stddev_of_L6_44", "cluster_chips_of_L6_44", "cluster_occ_of_L6_44"], + "title": ["Average Cluster trend: L6_44", "Stddev Cluster trend: L6_44", "Active chips trend: L6_44", "Occupancy trend: L6_44"], + "varexp": ["Cluster_l6.mean[44]:ntreeentries", "Cluster_l6.stddev[44]:ntreeentries", "Cluster_l6.entries[44]:ntreeentries", "Occupancy_l6.mean[44]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_45", "cluster_stddev_of_L6_45", "cluster_chips_of_L6_45", "cluster_occ_of_L6_45"], + "title": ["Average Cluster trend: L6_45", "Stddev Cluster trend: L6_45", "Active chips trend: L6_45", "Occupancy trend: L6_45"], + "varexp": ["Cluster_l6.mean[45]:ntreeentries", "Cluster_l6.stddev[45]:ntreeentries", "Cluster_l6.entries[45]:ntreeentries", "Occupancy_l6.mean[45]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_46", "cluster_stddev_of_L6_46", "cluster_chips_of_L6_46", "cluster_occ_of_L6_46"], + "title": ["Average Cluster trend: L6_46", "Stddev Cluster trend: L6_46", "Active chips trend: L6_46", "Occupancy trend: L6_46"], + "varexp": ["Cluster_l6.mean[46]:ntreeentries", "Cluster_l6.stddev[46]:ntreeentries", "Cluster_l6.entries[46]:ntreeentries", "Occupancy_l6.mean[46]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["cluster_mean_of_L6_47", "cluster_stddev_of_L6_47", "cluster_chips_of_L6_47", "cluster_occ_of_L6_47"], + "title": ["Average Cluster trend: L6_47", "Stddev Cluster trend: L6_47", "Active chips trend: L6_47", "Occupancy trend: L6_47"], + "varexp": ["Cluster_l6.mean[47]:ntreeentries", "Cluster_l6.stddev[47]:ntreeentries", "Cluster_l6.entries[47]:ntreeentries", "Occupancy_l6.mean[47]:ntreeentries"], + "selection": "", + "option": "PL" + } + ], + "initTrigger": [ + "usercontrol" + ], + "updateTrigger": [ + "newobject:qcdb:ITS/MO/ITSClusterTask/EmptyLaneFractionGlobal" + ], + "stopTrigger": [ + "usercontrol" + ] + } + } + } +} diff --git a/Modules/ITS/itsQCTrendingError.json b/Modules/ITS/itsQCTrendingError.json new file mode 100644 index 0000000000..3c0d1618c0 --- /dev/null +++ b/Modules/ITS/itsQCTrendingError.json @@ -0,0 +1,544 @@ +{ + "qc": + { + "config" : { + "database" : { + "implementation" : + "CCDB", + "host" : + "ccdb-test.cern.ch:8080", + "username" : + "not_applicable", + "password" : + "not_applicable", + "name" : + "not_applicable" + }, + "Activity" : { + "number" : + "42", + "type" : + "NONE" + }, + "monitoring" : { + "url" : + "infologger:///debug?qc" + }, + "consul" : { + "url" : + "" + }, + "conditionDB" : { + "url" : + "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": + { + "ITSqcTrendingError": + { + "active" : + "true", + "className" : + "o2::quality_control::postprocessing::TrendingTaskITSError", + "moduleName" : + "QualityControl", + "detectorName" : + "ITS", + "dataSources" : [{ + "type" : + "repository", + "paths" : [ + "ITS/MO/ITSDECODING/General", + "ITS/MO/ITSDECODING/General"], + "names" : [ + "ChipErrorPlots", + "LinkErrorPlots"], + "reductorName" : + "o2::quality_control_modules::its::ReductorBinContent", + "moduleName" : + "QcITS" + }], + "plots" :[ + { + "names" : [ + "# Error ID 0"], + "title" : [ + "Number of errors with ID = 0"], + "varexp" : [ + "LinkErrorPlots.binContent[0]"], + "selection" : + "Time", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 1"], + "title" : [ + "Number of errors with ID = 1"], + "varexp" : [ + "LinkErrorPlots.binContent[1]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 2"], + "title" : [ + "Number of errors with ID = 2"], + "varexp" : [ + "LinkErrorPlots.binContent[2]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 3"], + "title" : [ + "Number of errors with ID = 3"], + "varexp" : [ + "LinkErrorPlots.binContent[3]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 4"], + "title" : [ + "Number of errors with ID = 4"], + "varexp" : [ + "LinkErrorPlots.binContent[4]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 5"], + "title" : [ + "Number of errors with ID = 5"], + "varexp" : [ + "LinkErrorPlots.binContent[5]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 6"], + "title" : [ + "Number of errors with ID = 6"], + "varexp" : [ + "LinkErrorPlots.binContent[6]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 7"], + "title" : [ + "Number of errors with ID = 7"], + "varexp" : [ + "LinkErrorPlots.binContent[7]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 8"], + "title" : [ + "Number of errors with ID = 8"], + "varexp" : [ + "LinkErrorPlots.binContent[8]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 9"], + "title" : [ + "Number of errors with ID = 9"], + "varexp" : [ + "LinkErrorPlots.binContent[9]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 10"], + "title" : [ + "Number of errors with ID = 10"], + "varexp" : [ + "LinkErrorPlots.binContent[10]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 11"], + "title" : [ + "Number of errors with ID = 11"], + "varexp" : [ + "LinkErrorPlots.binContent[11]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 12"], + "title" : [ + "Number of errors with ID = 12"], + "varexp" : [ + "LinkErrorPlots.binContent[12]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 13"], + "title" : [ + "Number of errors with ID = 13"], + "varexp" : [ + "LinkErrorPlots.binContent[13]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 14"], + "title" : [ + "Number of errors with ID = 14"], + "varexp" : [ + "LinkErrorPlots.binContent[14]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 15"], + "title" : [ + "Number of errors with ID = 15"], + "varexp" : [ + "LinkErrorPlots.binContent[15]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 16"], + "title" : [ + "Number of errors with ID = 16"], + "varexp" : [ + "LinkErrorPlots.binContent[16]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 17"], + "title" : [ + "Number of errors with ID = 17"], + "varexp" : [ + "LinkErrorPlots.binContent[17]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 18"], + "title" : [ + "Number of errors with ID = 18"], + "varexp" : [ + "LinkErrorPlots.binContent[18]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 19"], + "title" : [ + "Number of errors with ID = 19"], + "varexp" : [ + "LinkErrorPlots.binContent[19]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 20"], + "title" : [ + "Number of errors with ID = 20"], + "varexp" : [ + "LinkErrorPlots.binContent[20]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 21"], + "title" : [ + "Number of errors with ID = 21"], + "varexp" : [ + "LinkErrorPlots.binContent[21]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 22"], + "title" : [ + "Number of errors with ID = 22"], + "varexp" : [ + "LinkErrorPlots.binContent[22]"], + "selection" : + "", + "option" : + "PL" + },{ + "names" : [ + "# Error ID 23"], + "title" : [ + "Number of errors with ID = 23"], + "varexp" : [ + "LinkErrorPlots.binContent[23]"], + "selection" : + "", + "option" : + "PL" + },{ + "names" : ["# Chip Error ID 0"], + "title" : ["Number of chip errors with ID = 0"], + "varexp" : ["ChipErrorPlots.binContent[0]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 1"], + "title" : ["Number of chip errors with ID = 1"], + "varexp" : ["ChipErrorPlots.binContent[1]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 2"], + "title" : ["Number of chip errors with ID = 2"], + "varexp" : ["ChipErrorPlots.binContent[2]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 3"], + "title" : ["Number of chip errors with ID = 3"], + "varexp" : ["ChipErrorPlots.binContent[3]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 4"], + "title" : ["Number of chip errors with ID = 4"], + "varexp" : ["ChipErrorPlots.binContent[4]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 5"], + "title" : ["Number of chip errors with ID = 5"], + "varexp" : ["ChipErrorPlots.binContent[5]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 6"], + "title" : ["Number of chip errors with ID = 6"], + "varexp" : ["ChipErrorPlots.binContent[6]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 7"], + "title" : ["Number of chip errors with ID = 7"], + "varexp" : ["ChipErrorPlots.binContent[7]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 8"], + "title" : ["Number of chip errors with ID = 8"], + "varexp" : ["ChipErrorPlots.binContent[8]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 9"], + "title" : ["Number of chip errors with ID = 9"], + "varexp" : ["ChipErrorPlots.binContent[9]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 10"], + "title" : ["Number of chip errors with ID = 10"], + "varexp" : ["ChipErrorPlots.binContent[10]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 11"], + "title" : ["Number of chip errors with ID = 11"], + "varexp" : ["ChipErrorPlots.binContent[11]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 12"], + "title" : ["Number of chip errors with ID = 12"], + "varexp" : ["ChipErrorPlots.binContent[12]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 13"], + "title" : ["Number of chip errors with ID = 13"], + "varexp" : ["ChipErrorPlots.binContent[13]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 14"], + "title" : ["Number of chip errors with ID = 14"], + "varexp" : ["ChipErrorPlots.binContent[14]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 15"], + "title" : ["Number of chip errors with ID = 15"], + "varexp" : ["ChipErrorPlots.binContent[15]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 16"], + "title" : ["Number of chip errors with ID = 16"], + "varexp" : ["ChipErrorPlots.binContent[16]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 17"], + "title" : ["Number of chip errors with ID = 17"], + "varexp" : ["ChipErrorPlots.binContent[17]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 18"], + "title" : ["Number of chip errors with ID = 18"], + "varexp" : ["ChipErrorPlots.binContent[18]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 19"], + "title" : ["Number of chip errors with ID = 19"], + "varexp" : ["ChipErrorPlots.binContent[19]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 20"], + "title" : ["Number of chip errors with ID = 20"], + "varexp" : ["ChipErrorPlots.binContent[20]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 21"], + "title" : ["Number of chip errors with ID = 21"], + "varexp" : ["ChipErrorPlots.binContent[21]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 22"], + "title" : ["Number of chip errors with ID = 22"], + "varexp" : ["ChipErrorPlots.binContent[22]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 23"], + "title" : ["Number of chip errors with ID = 23"], + "varexp" : ["ChipErrorPlots.binContent[23]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 24"], + "title" : ["Number of chip errors with ID = 24"], + "varexp" : ["ChipErrorPlots.binContent[24]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 25"], + "title" : ["Number of chip errors with ID = 25"], + "varexp" : ["ChipErrorPlots.binContent[25]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 26"], + "title" : ["Number of chip errors with ID = 26"], + "varexp" : ["ChipErrorPlots.binContent[26]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 27"], + "title" : ["Number of chip errors with ID = 27"], + "varexp" : ["ChipErrorPlots.binContent[27]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 28"], + "title" : ["Number of chip errors with ID = 28"], + "varexp" : ["ChipErrorPlots.binContent[28]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 29"], + "title" : ["Number of chip errors with ID = 29"], + "varexp" : ["ChipErrorPlots.binContent[29]"], + "selection" : "Time", + "option" :"PL" + },{ + "names" : ["# Chip Error ID 30"], + "title" : ["Number of chip errors with ID = 30"], + "varexp" : ["ChipErrorPlots.binContent[30]"], + "selection" : "Time", + "option" :"PL" + } + + ], + "initTrigger" : ["userorcontrol"], + "updateTrigger" : ["newobject:qcdb:ITS/MO/ITSDECODING/General/LinkErrorPlots"], + "stopTrigger" : ["userorcontrol"] + } + } + } +} diff --git a/Modules/ITS/itsQCTrendingError_old.json b/Modules/ITS/itsQCTrendingError_old.json new file mode 100644 index 0000000000..2e53fdce15 --- /dev/null +++ b/Modules/ITS/itsQCTrendingError_old.json @@ -0,0 +1,356 @@ +{ + "qc": + { + "config" : { + "database" : { + "implementation" : + "CCDB", + "host" : + "ccdb-test.cern.ch:8080", + "username" : + "not_applicable", + "password" : + "not_applicable", + "name" : + "not_applicable" + }, + "Activity" : { + "number" : + "42", + "type" : + "NONE" + }, + "monitoring" : { + "url" : + "infologger:///debug?qc" + }, + "consul" : { + "url" : + "" + }, + "conditionDB" : { + "url" : + "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": + { + "ITSqcTrendingError": + { + "active" : + "true", + "className" : + "o2::quality_control::postprocessing::TrendingTaskITSError", + "moduleName" : + "QualityControl", + "detectorName" : + "ITS", + "dataSources" : [{ + "type" : + "repository", + "paths" : [ + "ITS/MO/ITSDECODING/General"], + "names" : [ + "LinkErrorPlots"], + "reductorName" : + "o2::quality_control_modules::its::ReductorBinContent", + "moduleName" : + "QcITS" + }], + "plots" :[ + { + "names" : [ + "# Error ID 0"], + "title" : [ + "Number of errors with ID = 0"], + "varexp" : [ + "LinkErrorPlots.binContent[0]"], + "selection" : + "Time", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 1"], + "title" : [ + "Number of errors with ID = 1"], + "varexp" : [ + "LinkErrorPlots.binContent[1]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 2"], + "title" : [ + "Number of errors with ID = 2"], + "varexp" : [ + "LinkErrorPlots.binContent[2]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 3"], + "title" : [ + "Number of errors with ID = 3"], + "varexp" : [ + "LinkErrorPlots.binContent[3]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 4"], + "title" : [ + "Number of errors with ID = 4"], + "varexp" : [ + "LinkErrorPlots.binContent[4]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 5"], + "title" : [ + "Number of errors with ID = 5"], + "varexp" : [ + "LinkErrorPlots.binContent[5]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 6"], + "title" : [ + "Number of errors with ID = 6"], + "varexp" : [ + "LinkErrorPlots.binContent[6]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 7"], + "title" : [ + "Number of errors with ID = 7"], + "varexp" : [ + "LinkErrorPlots.binContent[7]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 8"], + "title" : [ + "Number of errors with ID = 8"], + "varexp" : [ + "LinkErrorPlots.binContent[8]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 9"], + "title" : [ + "Number of errors with ID = 9"], + "varexp" : [ + "LinkErrorPlots.binContent[9]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 10"], + "title" : [ + "Number of errors with ID = 10"], + "varexp" : [ + "LinkErrorPlots.binContent[10]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 11"], + "title" : [ + "Number of errors with ID = 11"], + "varexp" : [ + "LinkErrorPlots.binContent[11]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 12"], + "title" : [ + "Number of errors with ID = 12"], + "varexp" : [ + "LinkErrorPlots.binContent[12]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 13"], + "title" : [ + "Number of errors with ID = 13"], + "varexp" : [ + "LinkErrorPlots.binContent[13]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 14"], + "title" : [ + "Number of errors with ID = 14"], + "varexp" : [ + "LinkErrorPlots.binContent[14]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 15"], + "title" : [ + "Number of errors with ID = 15"], + "varexp" : [ + "LinkErrorPlots.binContent[15]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 16"], + "title" : [ + "Number of errors with ID = 16"], + "varexp" : [ + "LinkErrorPlots.binContent[16]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 17"], + "title" : [ + "Number of errors with ID = 17"], + "varexp" : [ + "LinkErrorPlots.binContent[17]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 18"], + "title" : [ + "Number of errors with ID = 18"], + "varexp" : [ + "LinkErrorPlots.binContent[18]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 19"], + "title" : [ + "Number of errors with ID = 19"], + "varexp" : [ + "LinkErrorPlots.binContent[19]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 20"], + "title" : [ + "Number of errors with ID = 20"], + "varexp" : [ + "LinkErrorPlots.binContent[20]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 21"], + "title" : [ + "Number of errors with ID = 21"], + "varexp" : [ + "LinkErrorPlots.binContent[21]"], + "selection" : + "", + "option" : + "PL" + }, + { + "names" : [ + "# Error ID 22"], + "title" : [ + "Number of errors with ID = 22"], + "varexp" : [ + "LinkErrorPlots.binContent[22]"], + "selection" : + "", + "option" : + "PL" + },{ + "names" : [ + "# Error ID 23"], + "title" : [ + "Number of errors with ID = 23"], + "varexp" : [ + "LinkErrorPlots.binContent[23]"], + "selection" : + "", + "option" : + "PL" + } + + ], + "initTrigger" : ["userorcontrol"], + "updateTrigger" : ["10 seconds"], + "stopTrigger" : ["userorcontrol"] + } + } + } +} diff --git a/Modules/ITS/itsQCTrendingFEE.json b/Modules/ITS/itsQCTrendingFEE.json new file mode 100644 index 0000000000..98325c2ab1 --- /dev/null +++ b/Modules/ITS/itsQCTrendingFEE.json @@ -0,0 +1,242 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "ITSqcTrendingFEE": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTaskITSFEE", + "moduleName": "QualityControl", + "detectorName": "ITS", + "dataSources": [ + { + "type": "repository", + "paths": [ "ITS/MO/ITSFEE/LaneStatusSummary", + "ITS/MO/ITSFEE/LaneStatusSummary", + "ITS/MO/ITSFEE/LaneStatusSummary", + "ITS/MO/ITSFEE/LaneStatusSummary"], + "names": [ "LaneStatusSummaryIB", + "LaneStatusSummaryML", + "LaneStatusSummaryOL", + "LaneStatusSummaryGlobal"], + "reductorName": "o2::quality_control_modules::its::ReductorBinContent", + "moduleName": "QcITS" + }, + { + "type": "repository", + "paths": [ "ITS/MO/ITSFEE" ], + "names": ["TriggerVsFeeid" ], + "reductorName": "o2::quality_control_modules::its::ReductorIntegralContent", + "moduleName": "QcITS" + } + ], + "plots": [ + { + "names": [ "# of lanes in WARNING" ], + "title": [ "IB - Trend of number of lanes in WARNING" ], + "varexp": [ "LaneStatusSummaryIB.binContent[0]" ], + "selection": "Time", + "option": "PL" + }, + { + "names": [ "# of lanes in ERROR" ], + "title": [ "IB - Trend of number of lanes in ERROR" ], + "varexp": [ "LaneStatusSummaryIB.binContent[1]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "# of lanes in FAULT" ], + "title": [ "IB - Trend of number of lanes in FAULT" ], + "varexp": [ "LaneStatusSummaryIB.binContent[2]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "# of lanes in WARNING" ], + "title": [ "ML - Trend of number of lanes in WARNING" ], + "varexp": [ "LaneStatusSummaryML.binContent[0]" ], + "selection": "Time", + "option": "PL" + }, + { + "names": [ "# of lanes in ERROR" ], + "title": [ "ML - Trend of number of lanes in ERROR" ], + "varexp": [ "LaneStatusSummaryML.binContent[1]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "# of lanes in FAULT" ], + "title": [ "ML - Trend of number of lanes in FAULT" ], + "varexp": [ "LaneStatusSummaryML.binContent[2]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "# of lanes in WARNING" ], + "title": [ "OL - Trend of number of lanes in WARNING" ], + "varexp": [ "LaneStatusSummaryOL.binContent[0]" ], + "selection": "Time", + "option": "PL" + }, + { + "names": [ "# of lanes in ERROR" ], + "title": [ "OL - Trend of number of lanes in ERROR" ], + "varexp": [ "LaneStatusSummaryOL.binContent[1]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "# of lanes in FAULT" ], + "title": [ "OL - Trend of number of lanes in FAULT" ], + "varexp": [ "LaneStatusSummaryOL.binContent[2]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "# of lanes in WARNING" ], + "title": [ "Global - Trend of number of lanes in WARNING" ], + "varexp": [ "LaneStatusSummaryGlobal.binContent[0]" ], + "selection": "Time", + "option": "PL" + }, + { + "names": [ "# of lanes in ERROR" ], + "title": [ "Global - Trend of number of lanes in ERROR" ], + "varexp": [ "LaneStatusSummaryGlobal.binContent[1]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "# of lanes in FAULT" ], + "title": [ "Global - Trend of number of lanes in FAULT" ], + "varexp": [ "LaneStatusSummaryGlobal.binContent[2]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. ORBIT" ], + "title": [ "Trig. \"ORBIT\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[0]" ], + "selection": "Time", + "option": "PL" + }, + { + "names": [ "Trig. HB" ], + "title": [ "Trig. \"HB\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[1]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. HBr" ], + "title": [ "Trig. \"HBr\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[2]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. HC" ], + "title": [ "Trig. \"HC\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[3]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. PHYSICS" ], + "title": [ "Trig. \"PHYSICS\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[4]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. PP" ], + "title": [ "Trig. \"PP\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[5]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. CAL" ], + "title": [ "Trig. \"CAL\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[6]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. SOT" ], + "title": [ "Trig. \"SOT\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[7]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. EOT" ], + "title": [ "Trig. \"EOT\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[8]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. SOC" ], + "title": [ "Trig. \"SOC\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[9]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. EOC" ], + "title": [ "Trig. \"EOC\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[10]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. TF" ], + "title": [ "Trig. \"TF\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[11]" ], + "selection": "", + "option": "PL" + }, + { + "names": [ "Trig. INT" ], + "title": [ "Trig. \"INT\" count through all FEE" ], + "varexp": [ "TriggerVsFeeid.integral[12]" ], + "selection": "", + "option": "PL" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:ITS/MO/ITSFEE/TriggerVsFeeid" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/ITS/itsQCTrendingFhr.json b/Modules/ITS/itsQCTrendingFhr.json new file mode 100644 index 0000000000..5ea03fb4ce --- /dev/null +++ b/Modules/ITS/itsQCTrendingFhr.json @@ -0,0 +1,1411 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "ITSqcTrendingFhr": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTaskITSFhr", + "moduleName": "QualityControl", + "detectorName": "ITS", + "dataSources": [ + { + "type": "repository", + "paths": ["ITS/MO/FHRTask/Occupancy/Layer0/Layer0ChipStave", + "ITS/MO/FHRTask/Occupancy/Layer1/Layer1ChipStave", + "ITS/MO/FHRTask/Occupancy/Layer2/Layer2ChipStave", + "ITS/MO/FHRTask/Occupancy/Layer3/Layer3ChipStave", + "ITS/MO/FHRTask/Occupancy/Layer4/Layer4ChipStave", + "ITS/MO/FHRTask/Occupancy/Layer5/Layer5ChipStave", + "ITS/MO/FHRTask/Occupancy/Layer6/Layer6ChipStave"], + "names": ["Fhr_l0", + "Fhr_l1", + "Fhr_l2", + "Fhr_l3", + "Fhr_l4", + "Fhr_l5", + "Fhr_l6"], + "reductorName": "o2::quality_control_modules::its::TH2XlineReductor", + "moduleName": "QcITS" + } + ], + "plots": [ + { + "names": ["fhr_mean_of_L0_00", "fhr_stddev_of_L0_00", "fhr_chips_of_L0_00", "fhr_occ_of_L0_00"], + "title": ["Average FHR trend: L0_00", "Stddev FHR trend: L0_00", "Active chips trend: L0_00", "Occupancy trend: L0_00"], + "varexp": ["Fhr_l0.mean[0]:ntreeentries", "Fhr_l0.stddev[0]:ntreeentries", "Fhr_l0.entries[0]:ntreeentries", "Fhr_l0.mean_scaled[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_01", "fhr_stddev_of_L0_01", "fhr_chips_of_L0_01", "fhr_occ_of_L0_01"], + "title": ["Average FHR trend: L0_01", "Stddev FHR trend: L0_01", "Active chips trend: L0_01", "Occupancy trend: L0_01"], + "varexp": ["Fhr_l0.mean[1]:ntreeentries", "Fhr_l0.stddev[1]:ntreeentries", "Fhr_l0.entries[1]:ntreeentries", "Fhr_l0.mean_scaled[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_02", "fhr_stddev_of_L0_02", "fhr_chips_of_L0_02", "fhr_occ_of_L0_02"], + "title": ["Average FHR trend: L0_02", "Stddev FHR trend: L0_02", "Active chips trend: L0_02", "Occupancy trend: L0_02"], + "varexp": ["Fhr_l0.mean[2]:ntreeentries", "Fhr_l0.stddev[2]:ntreeentries", "Fhr_l0.entries[2]:ntreeentries", "Fhr_l0.mean_scaled[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_03", "fhr_stddev_of_L0_03", "fhr_chips_of_L0_03", "fhr_occ_of_L0_03"], + "title": ["Average FHR trend: L0_03", "Stddev FHR trend: L0_03", "Active chips trend: L0_03", "Occupancy trend: L0_03"], + "varexp": ["Fhr_l0.mean[3]:ntreeentries", "Fhr_l0.stddev[3]:ntreeentries", "Fhr_l0.entries[3]:ntreeentries", "Fhr_l0.mean_scaled[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_04", "fhr_stddev_of_L0_04", "fhr_chips_of_L0_04", "fhr_occ_of_L0_04"], + "title": ["Average FHR trend: L0_04", "Stddev FHR trend: L0_04", "Active chips trend: L0_04", "Occupancy trend: L0_04"], + "varexp": ["Fhr_l0.mean[4]:ntreeentries", "Fhr_l0.stddev[4]:ntreeentries", "Fhr_l0.entries[4]:ntreeentries", "Fhr_l0.mean_scaled[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_05", "fhr_stddev_of_L0_05", "fhr_chips_of_L0_05", "fhr_occ_of_L0_05"], + "title": ["Average FHR trend: L0_05", "Stddev FHR trend: L0_05", "Active chips trend: L0_05", "Occupancy trend: L0_05"], + "varexp": ["Fhr_l0.mean[5]:ntreeentries", "Fhr_l0.stddev[5]:ntreeentries", "Fhr_l0.entries[5]:ntreeentries", "Fhr_l0.mean_scaled[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_06", "fhr_stddev_of_L0_06", "fhr_chips_of_L0_06", "fhr_occ_of_L0_06"], + "title": ["Average FHR trend: L0_06", "Stddev FHR trend: L0_06", "Active chips trend: L0_06", "Occupancy trend: L0_06"], + "varexp": ["Fhr_l0.mean[6]:ntreeentries", "Fhr_l0.stddev[6]:ntreeentries", "Fhr_l0.entries[6]:ntreeentries", "Fhr_l0.mean_scaled[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_07", "fhr_stddev_of_L0_07", "fhr_chips_of_L0_07", "fhr_occ_of_L0_07"], + "title": ["Average FHR trend: L0_07", "Stddev FHR trend: L0_07", "Active chips trend: L0_07", "Occupancy trend: L0_07"], + "varexp": ["Fhr_l0.mean[7]:ntreeentries", "Fhr_l0.stddev[7]:ntreeentries", "Fhr_l0.entries[7]:ntreeentries", "Fhr_l0.mean_scaled[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_08", "fhr_stddev_of_L0_08", "fhr_chips_of_L0_08", "fhr_occ_of_L0_08"], + "title": ["Average FHR trend: L0_08", "Stddev FHR trend: L0_08", "Active chips trend: L0_08", "Occupancy trend: L0_08"], + "varexp": ["Fhr_l0.mean[8]:ntreeentries", "Fhr_l0.stddev[8]:ntreeentries", "Fhr_l0.entries[8]:ntreeentries", "Fhr_l0.mean_scaled[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_09", "fhr_stddev_of_L0_09", "fhr_chips_of_L0_09", "fhr_occ_of_L0_09"], + "title": ["Average FHR trend: L0_09", "Stddev FHR trend: L0_09", "Active chips trend: L0_09", "Occupancy trend: L0_09"], + "varexp": ["Fhr_l0.mean[9]:ntreeentries", "Fhr_l0.stddev[9]:ntreeentries", "Fhr_l0.entries[9]:ntreeentries", "Fhr_l0.mean_scaled[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_10", "fhr_stddev_of_L0_10", "fhr_chips_of_L0_10", "fhr_occ_of_L0_10"], + "title": ["Average FHR trend: L0_10", "Stddev FHR trend: L0_10", "Active chips trend: L0_10", "Occupancy trend: L0_10"], + "varexp": ["Fhr_l0.mean[10]:ntreeentries", "Fhr_l0.stddev[10]:ntreeentries", "Fhr_l0.entries[10]:ntreeentries", "Fhr_l0.mean_scaled[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L0_11", "fhr_stddev_of_L0_11", "fhr_chips_of_L0_11", "fhr_occ_of_L0_11"], + "title": ["Average FHR trend: L0_11", "Stddev FHR trend: L0_11", "Active chips trend: L0_11", "Occupancy trend: L0_11"], + "varexp": ["Fhr_l0.mean[11]:ntreeentries", "Fhr_l0.stddev[11]:ntreeentries", "Fhr_l0.entries[11]:ntreeentries", "Fhr_l0.mean_scaled[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_00", "fhr_stddev_of_L1_00", "fhr_chips_of_L1_00", "fhr_occ_of_L1_00"], + "title": ["Average FHR trend: L1_00", "Stddev FHR trend: L1_00", "Active chips trend: L1_00", "Occupancy trend: L1_00"], + "varexp": ["Fhr_l1.mean[0]:ntreeentries", "Fhr_l1.stddev[0]:ntreeentries", "Fhr_l1.entries[0]:ntreeentries", "Fhr_l1.mean_scaled[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_01", "fhr_stddev_of_L1_01", "fhr_chips_of_L1_01", "fhr_occ_of_L1_01"], + "title": ["Average FHR trend: L1_01", "Stddev FHR trend: L1_01", "Active chips trend: L1_01", "Occupancy trend: L1_01"], + "varexp": ["Fhr_l1.mean[1]:ntreeentries", "Fhr_l1.stddev[1]:ntreeentries", "Fhr_l1.entries[1]:ntreeentries", "Fhr_l1.mean_scaled[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_02", "fhr_stddev_of_L1_02", "fhr_chips_of_L1_02", "fhr_occ_of_L1_02"], + "title": ["Average FHR trend: L1_02", "Stddev FHR trend: L1_02", "Active chips trend: L1_02", "Occupancy trend: L1_02"], + "varexp": ["Fhr_l1.mean[2]:ntreeentries", "Fhr_l1.stddev[2]:ntreeentries", "Fhr_l1.entries[2]:ntreeentries", "Fhr_l1.mean_scaled[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_03", "fhr_stddev_of_L1_03", "fhr_chips_of_L1_03", "fhr_occ_of_L1_03"], + "title": ["Average FHR trend: L1_03", "Stddev FHR trend: L1_03", "Active chips trend: L1_03", "Occupancy trend: L1_03"], + "varexp": ["Fhr_l1.mean[3]:ntreeentries", "Fhr_l1.stddev[3]:ntreeentries", "Fhr_l1.entries[3]:ntreeentries", "Fhr_l1.mean_scaled[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_04", "fhr_stddev_of_L1_04", "fhr_chips_of_L1_04", "fhr_occ_of_L1_04"], + "title": ["Average FHR trend: L1_04", "Stddev FHR trend: L1_04", "Active chips trend: L1_04", "Occupancy trend: L1_04"], + "varexp": ["Fhr_l1.mean[4]:ntreeentries", "Fhr_l1.stddev[4]:ntreeentries", "Fhr_l1.entries[4]:ntreeentries", "Fhr_l1.mean_scaled[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_05", "fhr_stddev_of_L1_05", "fhr_chips_of_L1_05", "fhr_occ_of_L1_05"], + "title": ["Average FHR trend: L1_05", "Stddev FHR trend: L1_05", "Active chips trend: L1_05", "Occupancy trend: L1_05"], + "varexp": ["Fhr_l1.mean[5]:ntreeentries", "Fhr_l1.stddev[5]:ntreeentries", "Fhr_l1.entries[5]:ntreeentries", "Fhr_l1.mean_scaled[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_06", "fhr_stddev_of_L1_06", "fhr_chips_of_L1_06", "fhr_occ_of_L1_06"], + "title": ["Average FHR trend: L1_06", "Stddev FHR trend: L1_06", "Active chips trend: L1_06", "Occupancy trend: L1_06"], + "varexp": ["Fhr_l1.mean[6]:ntreeentries", "Fhr_l1.stddev[6]:ntreeentries", "Fhr_l1.entries[6]:ntreeentries", "Fhr_l1.mean_scaled[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_07", "fhr_stddev_of_L1_07", "fhr_chips_of_L1_07", "fhr_occ_of_L1_07"], + "title": ["Average FHR trend: L1_07", "Stddev FHR trend: L1_07", "Active chips trend: L1_07", "Occupancy trend: L1_07"], + "varexp": ["Fhr_l1.mean[7]:ntreeentries", "Fhr_l1.stddev[7]:ntreeentries", "Fhr_l1.entries[7]:ntreeentries", "Fhr_l1.mean_scaled[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_08", "fhr_stddev_of_L1_08", "fhr_chips_of_L1_08", "fhr_occ_of_L1_08"], + "title": ["Average FHR trend: L1_08", "Stddev FHR trend: L1_08", "Active chips trend: L1_08", "Occupancy trend: L1_08"], + "varexp": ["Fhr_l1.mean[8]:ntreeentries", "Fhr_l1.stddev[8]:ntreeentries", "Fhr_l1.entries[8]:ntreeentries", "Fhr_l1.mean_scaled[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_09", "fhr_stddev_of_L1_09", "fhr_chips_of_L1_09", "fhr_occ_of_L1_09"], + "title": ["Average FHR trend: L1_09", "Stddev FHR trend: L1_09", "Active chips trend: L1_09", "Occupancy trend: L1_09"], + "varexp": ["Fhr_l1.mean[9]:ntreeentries", "Fhr_l1.stddev[9]:ntreeentries", "Fhr_l1.entries[9]:ntreeentries", "Fhr_l1.mean_scaled[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_10", "fhr_stddev_of_L1_10", "fhr_chips_of_L1_10", "fhr_occ_of_L1_10"], + "title": ["Average FHR trend: L1_10", "Stddev FHR trend: L1_10", "Active chips trend: L1_10", "Occupancy trend: L1_10"], + "varexp": ["Fhr_l1.mean[10]:ntreeentries", "Fhr_l1.stddev[10]:ntreeentries", "Fhr_l1.entries[10]:ntreeentries", "Fhr_l1.mean_scaled[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_11", "fhr_stddev_of_L1_11", "fhr_chips_of_L1_11", "fhr_occ_of_L1_11"], + "title": ["Average FHR trend: L1_11", "Stddev FHR trend: L1_11", "Active chips trend: L1_11", "Occupancy trend: L1_11"], + "varexp": ["Fhr_l1.mean[11]:ntreeentries", "Fhr_l1.stddev[11]:ntreeentries", "Fhr_l1.entries[11]:ntreeentries", "Fhr_l1.mean_scaled[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_12", "fhr_stddev_of_L1_12", "fhr_chips_of_L1_12", "fhr_occ_of_L1_12"], + "title": ["Average FHR trend: L1_12", "Stddev FHR trend: L1_12", "Active chips trend: L1_12", "Occupancy trend: L1_12"], + "varexp": ["Fhr_l1.mean[12]:ntreeentries", "Fhr_l1.stddev[12]:ntreeentries", "Fhr_l1.entries[12]:ntreeentries", "Fhr_l1.mean_scaled[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_13", "fhr_stddev_of_L1_13", "fhr_chips_of_L1_13", "fhr_occ_of_L1_13"], + "title": ["Average FHR trend: L1_13", "Stddev FHR trend: L1_13", "Active chips trend: L1_13", "Occupancy trend: L1_13"], + "varexp": ["Fhr_l1.mean[13]:ntreeentries", "Fhr_l1.stddev[13]:ntreeentries", "Fhr_l1.entries[13]:ntreeentries", "Fhr_l1.mean_scaled[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_14", "fhr_stddev_of_L1_14", "fhr_chips_of_L1_14", "fhr_occ_of_L1_14"], + "title": ["Average FHR trend: L1_14", "Stddev FHR trend: L1_14", "Active chips trend: L1_14", "Occupancy trend: L1_14"], + "varexp": ["Fhr_l1.mean[14]:ntreeentries", "Fhr_l1.stddev[14]:ntreeentries", "Fhr_l1.entries[14]:ntreeentries", "Fhr_l1.mean_scaled[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L1_15", "fhr_stddev_of_L1_15", "fhr_chips_of_L1_15", "fhr_occ_of_L1_15"], + "title": ["Average FHR trend: L1_15", "Stddev FHR trend: L1_15", "Active chips trend: L1_15", "Occupancy trend: L1_15"], + "varexp": ["Fhr_l1.mean[15]:ntreeentries", "Fhr_l1.stddev[15]:ntreeentries", "Fhr_l1.entries[15]:ntreeentries", "Fhr_l1.mean_scaled[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_00", "fhr_stddev_of_L2_00", "fhr_chips_of_L2_00", "fhr_occ_of_L2_00"], + "title": ["Average FHR trend: L2_00", "Stddev FHR trend: L2_00", "Active chips trend: L2_00", "Occupancy trend: L2_00"], + "varexp": ["Fhr_l2.mean[0]:ntreeentries", "Fhr_l2.stddev[0]:ntreeentries", "Fhr_l2.entries[0]:ntreeentries", "Fhr_l2.mean_scaled[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_01", "fhr_stddev_of_L2_01", "fhr_chips_of_L2_01", "fhr_occ_of_L2_01"], + "title": ["Average FHR trend: L2_01", "Stddev FHR trend: L2_01", "Active chips trend: L2_01", "Occupancy trend: L2_01"], + "varexp": ["Fhr_l2.mean[1]:ntreeentries", "Fhr_l2.stddev[1]:ntreeentries", "Fhr_l2.entries[1]:ntreeentries", "Fhr_l2.mean_scaled[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_02", "fhr_stddev_of_L2_02", "fhr_chips_of_L2_02", "fhr_occ_of_L2_02"], + "title": ["Average FHR trend: L2_02", "Stddev FHR trend: L2_02", "Active chips trend: L2_02", "Occupancy trend: L2_02"], + "varexp": ["Fhr_l2.mean[2]:ntreeentries", "Fhr_l2.stddev[2]:ntreeentries", "Fhr_l2.entries[2]:ntreeentries", "Fhr_l2.mean_scaled[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_03", "fhr_stddev_of_L2_03", "fhr_chips_of_L2_03", "fhr_occ_of_L2_03"], + "title": ["Average FHR trend: L2_03", "Stddev FHR trend: L2_03", "Active chips trend: L2_03", "Occupancy trend: L2_03"], + "varexp": ["Fhr_l2.mean[3]:ntreeentries", "Fhr_l2.stddev[3]:ntreeentries", "Fhr_l2.entries[3]:ntreeentries", "Fhr_l2.mean_scaled[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_04", "fhr_stddev_of_L2_04", "fhr_chips_of_L2_04", "fhr_occ_of_L2_04"], + "title": ["Average FHR trend: L2_04", "Stddev FHR trend: L2_04", "Active chips trend: L2_04", "Occupancy trend: L2_04"], + "varexp": ["Fhr_l2.mean[4]:ntreeentries", "Fhr_l2.stddev[4]:ntreeentries", "Fhr_l2.entries[4]:ntreeentries", "Fhr_l2.mean_scaled[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_05", "fhr_stddev_of_L2_05", "fhr_chips_of_L2_05", "fhr_occ_of_L2_05"], + "title": ["Average FHR trend: L2_05", "Stddev FHR trend: L2_05", "Active chips trend: L2_05", "Occupancy trend: L2_05"], + "varexp": ["Fhr_l2.mean[5]:ntreeentries", "Fhr_l2.stddev[5]:ntreeentries", "Fhr_l2.entries[5]:ntreeentries", "Fhr_l2.mean_scaled[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_06", "fhr_stddev_of_L2_06", "fhr_chips_of_L2_06", "fhr_occ_of_L2_06"], + "title": ["Average FHR trend: L2_06", "Stddev FHR trend: L2_06", "Active chips trend: L2_06", "Occupancy trend: L2_06"], + "varexp": ["Fhr_l2.mean[6]:ntreeentries", "Fhr_l2.stddev[6]:ntreeentries", "Fhr_l2.entries[6]:ntreeentries", "Fhr_l2.mean_scaled[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_07", "fhr_stddev_of_L2_07", "fhr_chips_of_L2_07", "fhr_occ_of_L2_07"], + "title": ["Average FHR trend: L2_07", "Stddev FHR trend: L2_07", "Active chips trend: L2_07", "Occupancy trend: L2_07"], + "varexp": ["Fhr_l2.mean[7]:ntreeentries", "Fhr_l2.stddev[7]:ntreeentries", "Fhr_l2.entries[7]:ntreeentries", "Fhr_l2.mean_scaled[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_08", "fhr_stddev_of_L2_08", "fhr_chips_of_L2_08", "fhr_occ_of_L2_08"], + "title": ["Average FHR trend: L2_08", "Stddev FHR trend: L2_08", "Active chips trend: L2_08", "Occupancy trend: L2_08"], + "varexp": ["Fhr_l2.mean[8]:ntreeentries", "Fhr_l2.stddev[8]:ntreeentries", "Fhr_l2.entries[8]:ntreeentries", "Fhr_l2.mean_scaled[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_09", "fhr_stddev_of_L2_09", "fhr_chips_of_L2_09", "fhr_occ_of_L2_09"], + "title": ["Average FHR trend: L2_09", "Stddev FHR trend: L2_09", "Active chips trend: L2_09", "Occupancy trend: L2_09"], + "varexp": ["Fhr_l2.mean[9]:ntreeentries", "Fhr_l2.stddev[9]:ntreeentries", "Fhr_l2.entries[9]:ntreeentries", "Fhr_l2.mean_scaled[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_10", "fhr_stddev_of_L2_10", "fhr_chips_of_L2_10", "fhr_occ_of_L2_10"], + "title": ["Average FHR trend: L2_10", "Stddev FHR trend: L2_10", "Active chips trend: L2_10", "Occupancy trend: L2_10"], + "varexp": ["Fhr_l2.mean[10]:ntreeentries", "Fhr_l2.stddev[10]:ntreeentries", "Fhr_l2.entries[10]:ntreeentries", "Fhr_l2.mean_scaled[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_11", "fhr_stddev_of_L2_11", "fhr_chips_of_L2_11", "fhr_occ_of_L2_11"], + "title": ["Average FHR trend: L2_11", "Stddev FHR trend: L2_11", "Active chips trend: L2_11", "Occupancy trend: L2_11"], + "varexp": ["Fhr_l2.mean[11]:ntreeentries", "Fhr_l2.stddev[11]:ntreeentries", "Fhr_l2.entries[11]:ntreeentries", "Fhr_l2.mean_scaled[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_12", "fhr_stddev_of_L2_12", "fhr_chips_of_L2_12", "fhr_occ_of_L2_12"], + "title": ["Average FHR trend: L2_12", "Stddev FHR trend: L2_12", "Active chips trend: L2_12", "Occupancy trend: L2_12"], + "varexp": ["Fhr_l2.mean[12]:ntreeentries", "Fhr_l2.stddev[12]:ntreeentries", "Fhr_l2.entries[12]:ntreeentries", "Fhr_l2.mean_scaled[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_13", "fhr_stddev_of_L2_13", "fhr_chips_of_L2_13", "fhr_occ_of_L2_13"], + "title": ["Average FHR trend: L2_13", "Stddev FHR trend: L2_13", "Active chips trend: L2_13", "Occupancy trend: L2_13"], + "varexp": ["Fhr_l2.mean[13]:ntreeentries", "Fhr_l2.stddev[13]:ntreeentries", "Fhr_l2.entries[13]:ntreeentries", "Fhr_l2.mean_scaled[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_14", "fhr_stddev_of_L2_14", "fhr_chips_of_L2_14", "fhr_occ_of_L2_14"], + "title": ["Average FHR trend: L2_14", "Stddev FHR trend: L2_14", "Active chips trend: L2_14", "Occupancy trend: L2_14"], + "varexp": ["Fhr_l2.mean[14]:ntreeentries", "Fhr_l2.stddev[14]:ntreeentries", "Fhr_l2.entries[14]:ntreeentries", "Fhr_l2.mean_scaled[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_15", "fhr_stddev_of_L2_15", "fhr_chips_of_L2_15", "fhr_occ_of_L2_15"], + "title": ["Average FHR trend: L2_15", "Stddev FHR trend: L2_15", "Active chips trend: L2_15", "Occupancy trend: L2_15"], + "varexp": ["Fhr_l2.mean[15]:ntreeentries", "Fhr_l2.stddev[15]:ntreeentries", "Fhr_l2.entries[15]:ntreeentries", "Fhr_l2.mean_scaled[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_16", "fhr_stddev_of_L2_16", "fhr_chips_of_L2_16", "fhr_occ_of_L2_16"], + "title": ["Average FHR trend: L2_16", "Stddev FHR trend: L2_16", "Active chips trend: L2_16", "Occupancy trend: L2_16"], + "varexp": ["Fhr_l2.mean[16]:ntreeentries", "Fhr_l2.stddev[16]:ntreeentries", "Fhr_l2.entries[16]:ntreeentries", "Fhr_l2.mean_scaled[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_17", "fhr_stddev_of_L2_17", "fhr_chips_of_L2_17", "fhr_occ_of_L2_17"], + "title": ["Average FHR trend: L2_17", "Stddev FHR trend: L2_17", "Active chips trend: L2_17", "Occupancy trend: L2_17"], + "varexp": ["Fhr_l2.mean[17]:ntreeentries", "Fhr_l2.stddev[17]:ntreeentries", "Fhr_l2.entries[17]:ntreeentries", "Fhr_l2.mean_scaled[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_18", "fhr_stddev_of_L2_18", "fhr_chips_of_L2_18", "fhr_occ_of_L2_18"], + "title": ["Average FHR trend: L2_18", "Stddev FHR trend: L2_18", "Active chips trend: L2_18", "Occupancy trend: L2_18"], + "varexp": ["Fhr_l2.mean[18]:ntreeentries", "Fhr_l2.stddev[18]:ntreeentries", "Fhr_l2.entries[18]:ntreeentries", "Fhr_l2.mean_scaled[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L2_19", "fhr_stddev_of_L2_19", "fhr_chips_of_L2_19", "fhr_occ_of_L2_19"], + "title": ["Average FHR trend: L2_19", "Stddev FHR trend: L2_19", "Active chips trend: L2_19", "Occupancy trend: L2_19"], + "varexp": ["Fhr_l2.mean[19]:ntreeentries", "Fhr_l2.stddev[19]:ntreeentries", "Fhr_l2.entries[19]:ntreeentries", "Fhr_l2.mean_scaled[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_00", "fhr_stddev_of_L3_00", "fhr_lanes_of_L3_00", "fhr_occ_of_L3_00"], + "title": ["Average FHR trend: L3_00", "Stddev FHR trend: L3_00", "Active lanes trend: L3_00", "Occupancy trend: L3_00"], + "varexp": ["Fhr_l3.mean[0]:ntreeentries", "Fhr_l3.stddev[0]:ntreeentries", "Fhr_l3.entries[0]:ntreeentries", "Fhr_l3.mean_scaled[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_01", "fhr_stddev_of_L3_01", "fhr_lanes_of_L3_01", "fhr_occ_of_L3_01"], + "title": ["Average FHR trend: L3_01", "Stddev FHR trend: L3_01", "Active lanes trend: L3_01", "Occupancy trend: L3_01"], + "varexp": ["Fhr_l3.mean[1]:ntreeentries", "Fhr_l3.stddev[1]:ntreeentries", "Fhr_l3.entries[1]:ntreeentries", "Fhr_l3.mean_scaled[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_02", "fhr_stddev_of_L3_02", "fhr_lanes_of_L3_02", "fhr_occ_of_L3_02"], + "title": ["Average FHR trend: L3_02", "Stddev FHR trend: L3_02", "Active lanes trend: L3_02", "Occupancy trend: L3_02"], + "varexp": ["Fhr_l3.mean[2]:ntreeentries", "Fhr_l3.stddev[2]:ntreeentries", "Fhr_l3.entries[2]:ntreeentries", "Fhr_l3.mean_scaled[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, +{ + "names": ["fhr_mean_of_L3_03", "fhr_stddev_of_L3_03", "fhr_lanes_of_L3_03", "fhr_occ_of_L3_03"], + "title": ["Average FHR trend: L3_03", "Stddev FHR trend: L3_03", "Active lanes trend: L3_03", "Occupancy trend: L3_03"], + "varexp": ["Fhr_l3.mean[3]:ntreeentries", "Fhr_l3.stddev[3]:ntreeentries", "Fhr_l3.entries[3]:ntreeentries", "Fhr_l3.mean_scaled[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_04", "fhr_stddev_of_L3_04", "fhr_lanes_of_L3_04", "fhr_occ_of_L3_04"], + "title": ["Average FHR trend: L3_04", "Stddev FHR trend: L3_04", "Active lanes trend: L3_04", "Occupancy trend: L3_04"], + "varexp": ["Fhr_l3.mean[4]:ntreeentries", "Fhr_l3.stddev[4]:ntreeentries", "Fhr_l3.entries[4]:ntreeentries", "Fhr_l3.mean_scaled[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_05", "fhr_stddev_of_L3_05", "fhr_lanes_of_L3_05", "fhr_occ_of_L3_05"], + "title": ["Average FHR trend: L3_05", "Stddev FHR trend: L3_05", "Active lanes trend: L3_05", "Occupancy trend: L3_05"], + "varexp": ["Fhr_l3.mean[5]:ntreeentries", "Fhr_l3.stddev[5]:ntreeentries", "Fhr_l3.entries[5]:ntreeentries", "Fhr_l3.mean_scaled[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_06", "fhr_stddev_of_L3_06", "fhr_lanes_of_L3_06", "fhr_occ_of_L3_06"], + "title": ["Average FHR trend: L3_06", "Stddev FHR trend: L3_06", "Active lanes trend: L3_06", "Occupancy trend: L3_06"], + "varexp": ["Fhr_l3.mean[6]:ntreeentries", "Fhr_l3.stddev[6]:ntreeentries", "Fhr_l3.entries[6]:ntreeentries", "Fhr_l3.mean_scaled[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_07", "fhr_stddev_of_L3_07", "fhr_lanes_of_L3_07", "fhr_occ_of_L3_07"], + "title": ["Average FHR trend: L3_07", "Stddev FHR trend: L3_07", "Active lanes trend: L3_07", "Occupancy trend: L3_07"], + "varexp": ["Fhr_l3.mean[7]:ntreeentries", "Fhr_l3.stddev[7]:ntreeentries", "Fhr_l3.entries[7]:ntreeentries", "Fhr_l3.mean_scaled[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_08", "fhr_stddev_of_L3_08", "fhr_lanes_of_L3_08", "fhr_occ_of_L3_08"], + "title": ["Average FHR trend: L3_08", "Stddev FHR trend: L3_08", "Active lanes trend: L3_08", "Occupancy trend: L3_08"], + "varexp": ["Fhr_l3.mean[8]:ntreeentries", "Fhr_l3.stddev[8]:ntreeentries", "Fhr_l3.entries[8]:ntreeentries", "Fhr_l3.mean_scaled[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_09", "fhr_stddev_of_L3_09", "fhr_lanes_of_L3_09", "fhr_occ_of_L3_09"], + "title": ["Average FHR trend: L3_09", "Stddev FHR trend: L3_09", "Active lanes trend: L3_09", "Occupancy trend: L3_09"], + "varexp": ["Fhr_l3.mean[9]:ntreeentries", "Fhr_l3.stddev[9]:ntreeentries", "Fhr_l3.entries[9]:ntreeentries", "Fhr_l3.mean_scaled[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_10", "fhr_stddev_of_L3_10", "fhr_lanes_of_L3_10", "fhr_occ_of_L3_10"], + "title": ["Average FHR trend: L3_10", "Stddev FHR trend: L3_10", "Active lanes trend: L3_10", "Occupancy trend: L3_10"], + "varexp": ["Fhr_l3.mean[10]:ntreeentries", "Fhr_l3.stddev[10]:ntreeentries", "Fhr_l3.entries[10]:ntreeentries", "Fhr_l3.mean_scaled[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_11", "fhr_stddev_of_L3_11", "fhr_lanes_of_L3_11", "fhr_occ_of_L3_11"], + "title": ["Average FHR trend: L3_11", "Stddev FHR trend: L3_11", "Active lanes trend: L3_11", "Occupancy trend: L3_11"], + "varexp": ["Fhr_l3.mean[11]:ntreeentries", "Fhr_l3.stddev[11]:ntreeentries", "Fhr_l3.entries[11]:ntreeentries", "Fhr_l3.mean_scaled[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_12", "fhr_stddev_of_L3_12", "fhr_lanes_of_L3_12", "fhr_occ_of_L3_12"], + "title": ["Average FHR trend: L3_12", "Stddev FHR trend: L3_12", "Active lanes trend: L3_12", "Occupancy trend: L3_12"], + "varexp": ["Fhr_l3.mean[12]:ntreeentries", "Fhr_l3.stddev[12]:ntreeentries", "Fhr_l3.entries[12]:ntreeentries", "Fhr_l3.mean_scaled[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_13", "fhr_stddev_of_L3_13", "fhr_lanes_of_L3_13", "fhr_occ_of_L3_13"], + "title": ["Average FHR trend: L3_13", "Stddev FHR trend: L3_13", "Active lanes trend: L3_13", "Occupancy trend: L3_13"], + "varexp": ["Fhr_l3.mean[13]:ntreeentries", "Fhr_l3.stddev[13]:ntreeentries", "Fhr_l3.entries[13]:ntreeentries", "Fhr_l3.mean_scaled[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_14", "fhr_stddev_of_L3_14", "fhr_lanes_of_L3_14", "fhr_occ_of_L3_14"], + "title": ["Average FHR trend: L3_14", "Stddev FHR trend: L3_14", "Active lanes trend: L3_14", "Occupancy trend: L3_14"], + "varexp": ["Fhr_l3.mean[14]:ntreeentries", "Fhr_l3.stddev[14]:ntreeentries", "Fhr_l3.entries[14]:ntreeentries", "Fhr_l3.mean_scaled[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_15", "fhr_stddev_of_L3_15", "fhr_lanes_of_L3_15", "fhr_occ_of_L3_15"], + "title": ["Average FHR trend: L3_15", "Stddev FHR trend: L3_15", "Active lanes trend: L3_15", "Occupancy trend: L3_15"], + "varexp": ["Fhr_l3.mean[15]:ntreeentries", "Fhr_l3.stddev[15]:ntreeentries", "Fhr_l3.entries[15]:ntreeentries", "Fhr_l3.mean_scaled[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_16", "fhr_stddev_of_L3_16", "fhr_lanes_of_L3_16", "fhr_occ_of_L3_16"], + "title": ["Average FHR trend: L3_16", "Stddev FHR trend: L3_16", "Active lanes trend: L3_16", "Occupancy trend: L3_16"], + "varexp": ["Fhr_l3.mean[16]:ntreeentries", "Fhr_l3.stddev[16]:ntreeentries", "Fhr_l3.entries[16]:ntreeentries", "Fhr_l3.mean_scaled[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_17", "fhr_stddev_of_L3_17", "fhr_lanes_of_L3_17", "fhr_occ_of_L3_17"], + "title": ["Average FHR trend: L3_17", "Stddev FHR trend: L3_17", "Active lanes trend: L3_17", "Occupancy trend: L3_17"], + "varexp": ["Fhr_l3.mean[17]:ntreeentries", "Fhr_l3.stddev[17]:ntreeentries", "Fhr_l3.entries[17]:ntreeentries", "Fhr_l3.mean_scaled[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_18", "fhr_stddev_of_L3_18", "fhr_lanes_of_L3_18", "fhr_occ_of_L3_18"], + "title": ["Average FHR trend: L3_18", "Stddev FHR trend: L3_18", "Active lanes trend: L3_18", "Occupancy trend: L3_18"], + "varexp": ["Fhr_l3.mean[18]:ntreeentries", "Fhr_l3.stddev[18]:ntreeentries", "Fhr_l3.entries[18]:ntreeentries", "Fhr_l3.mean_scaled[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_19", "fhr_stddev_of_L3_19", "fhr_lanes_of_L3_19", "fhr_occ_of_L3_19"], + "title": ["Average FHR trend: L3_19", "Stddev FHR trend: L3_19", "Active lanes trend: L3_19", "Occupancy trend: L3_19"], + "varexp": ["Fhr_l3.mean[19]:ntreeentries", "Fhr_l3.stddev[19]:ntreeentries", "Fhr_l3.entries[19]:ntreeentries", "Fhr_l3.mean_scaled[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_20", "fhr_stddev_of_L3_20", "fhr_lanes_of_L3_20", "fhr_occ_of_L3_20"], + "title": ["Average FHR trend: L3_20", "Stddev FHR trend: L3_20", "Active lanes trend: L3_20", "Occupancy trend: L3_20"], + "varexp": ["Fhr_l3.mean[20]:ntreeentries", "Fhr_l3.stddev[20]:ntreeentries", "Fhr_l3.entries[20]:ntreeentries", "Fhr_l3.mean_scaled[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_21", "fhr_stddev_of_L3_21", "fhr_lanes_of_L3_21", "fhr_occ_of_L3_21"], + "title": ["Average FHR trend: L3_21", "Stddev FHR trend: L3_21", "Active lanes trend: L3_21", "Occupancy trend: L3_21"], + "varexp": ["Fhr_l3.mean[21]:ntreeentries", "Fhr_l3.stddev[21]:ntreeentries", "Fhr_l3.entries[21]:ntreeentries", "Fhr_l3.mean_scaled[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_22", "fhr_stddev_of_L3_22", "fhr_lanes_of_L3_22", "fhr_occ_of_L3_22"], + "title": ["Average FHR trend: L3_22", "Stddev FHR trend: L3_22", "Active lanes trend: L3_22", "Occupancy trend: L3_22"], + "varexp": ["Fhr_l3.mean[22]:ntreeentries", "Fhr_l3.stddev[22]:ntreeentries", "Fhr_l3.entries[22]:ntreeentries", "Fhr_l3.mean_scaled[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L3_23", "fhr_stddev_of_L3_23", "fhr_lanes_of_L3_23", "fhr_occ_of_L3_23"], + "title": ["Average FHR trend: L3_23", "Stddev FHR trend: L3_23", "Active lanes trend: L3_23", "Occupancy trend: L3_23"], + "varexp": ["Fhr_l3.mean[23]:ntreeentries", "Fhr_l3.stddev[23]:ntreeentries", "Fhr_l3.entries[23]:ntreeentries", "Fhr_l3.mean_scaled[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_00", "fhr_stddev_of_L4_00", "fhr_lanes_of_L4_00", "fhr_occ_of_L4_00"], + "title": ["Average FHR trend: L4_00", "Stddev FHR trend: L4_00", "Active lanes trend: L4_00", "Occupancy trend: L4_00"], + "varexp": ["Fhr_l4.mean[0]:ntreeentries", "Fhr_l4.stddev[0]:ntreeentries", "Fhr_l4.entries[0]:ntreeentries", "Fhr_l4.mean_scaled[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_01", "fhr_stddev_of_L4_01", "fhr_lanes_of_L4_01", "fhr_occ_of_L4_01"], + "title": ["Average FHR trend: L4_01", "Stddev FHR trend: L4_01", "Active lanes trend: L4_01", "Occupancy trend: L4_01"], + "varexp": ["Fhr_l4.mean[1]:ntreeentries", "Fhr_l4.stddev[1]:ntreeentries", "Fhr_l4.entries[1]:ntreeentries", "Fhr_l4.mean_scaled[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_02", "fhr_stddev_of_L4_02", "fhr_lanes_of_L4_02", "fhr_occ_of_L4_02"], + "title": ["Average FHR trend: L4_02", "Stddev FHR trend: L4_02", "Active lanes trend: L4_02", "Occupancy trend: L4_02"], + "varexp": ["Fhr_l4.mean[2]:ntreeentries", "Fhr_l4.stddev[2]:ntreeentries", "Fhr_l4.entries[2]:ntreeentries", "Fhr_l4.mean_scaled[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_03", "fhr_stddev_of_L4_03", "fhr_lanes_of_L4_03", "fhr_occ_of_L4_03"], + "title": ["Average FHR trend: L4_03", "Stddev FHR trend: L4_03", "Active lanes trend: L4_03", "Occupancy trend: L4_03"], + "varexp": ["Fhr_l4.mean[3]:ntreeentries", "Fhr_l4.stddev[3]:ntreeentries", "Fhr_l4.entries[3]:ntreeentries", "Fhr_l4.mean_scaled[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_04", "fhr_stddev_of_L4_04", "fhr_lanes_of_L4_04", "fhr_occ_of_L4_04"], + "title": ["Average FHR trend: L4_04", "Stddev FHR trend: L4_04", "Active lanes trend: L4_04", "Occupancy trend: L4_04"], + "varexp": ["Fhr_l4.mean[4]:ntreeentries", "Fhr_l4.stddev[4]:ntreeentries", "Fhr_l4.entries[4]:ntreeentries", "Fhr_l4.mean_scaled[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_05", "fhr_stddev_of_L4_05", "fhr_lanes_of_L4_05", "fhr_occ_of_L4_05"], + "title": ["Average FHR trend: L4_05", "Stddev FHR trend: L4_05", "Active lanes trend: L4_05", "Occupancy trend: L4_05"], + "varexp": ["Fhr_l4.mean[5]:ntreeentries", "Fhr_l4.stddev[5]:ntreeentries", "Fhr_l4.entries[5]:ntreeentries", "Fhr_l4.mean_scaled[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_06", "fhr_stddev_of_L4_06", "fhr_lanes_of_L4_06", "fhr_occ_of_L4_06"], + "title": ["Average FHR trend: L4_06", "Stddev FHR trend: L4_06", "Active lanes trend: L4_06", "Occupancy trend: L4_06"], + "varexp": ["Fhr_l4.mean[6]:ntreeentries", "Fhr_l4.stddev[6]:ntreeentries", "Fhr_l4.entries[6]:ntreeentries", "Fhr_l4.mean_scaled[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_07", "fhr_stddev_of_L4_07", "fhr_lanes_of_L4_07", "fhr_occ_of_L4_07"], + "title": ["Average FHR trend: L4_07", "Stddev FHR trend: L4_07", "Active lanes trend: L4_07", "Occupancy trend: L4_07"], + "varexp": ["Fhr_l4.mean[7]:ntreeentries", "Fhr_l4.stddev[7]:ntreeentries", "Fhr_l4.entries[7]:ntreeentries", "Fhr_l4.mean_scaled[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_08", "fhr_stddev_of_L4_08", "fhr_lanes_of_L4_08", "fhr_occ_of_L4_08"], + "title": ["Average FHR trend: L4_08", "Stddev FHR trend: L4_08", "Active lanes trend: L4_08", "Occupancy trend: L4_08"], + "varexp": ["Fhr_l4.mean[8]:ntreeentries", "Fhr_l4.stddev[8]:ntreeentries", "Fhr_l4.entries[8]:ntreeentries", "Fhr_l4.mean_scaled[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_09", "fhr_stddev_of_L4_09", "fhr_lanes_of_L4_09", "fhr_occ_of_L4_09"], + "title": ["Average FHR trend: L4_09", "Stddev FHR trend: L4_09", "Active lanes trend: L4_09", "Occupancy trend: L4_09"], + "varexp": ["Fhr_l4.mean[9]:ntreeentries", "Fhr_l4.stddev[9]:ntreeentries", "Fhr_l4.entries[9]:ntreeentries", "Fhr_l4.mean_scaled[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_10", "fhr_stddev_of_L4_10", "fhr_lanes_of_L4_10", "fhr_occ_of_L4_10"], + "title": ["Average FHR trend: L4_10", "Stddev FHR trend: L4_10", "Active lanes trend: L4_10", "Occupancy trend: L4_10"], + "varexp": ["Fhr_l4.mean[10]:ntreeentries", "Fhr_l4.stddev[10]:ntreeentries", "Fhr_l4.entries[10]:ntreeentries", "Fhr_l4.mean_scaled[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_11", "fhr_stddev_of_L4_11", "fhr_lanes_of_L4_11", "fhr_occ_of_L4_11"], + "title": ["Average FHR trend: L4_11", "Stddev FHR trend: L4_11", "Active lanes trend: L4_11", "Occupancy trend: L4_11"], + "varexp": ["Fhr_l4.mean[11]:ntreeentries", "Fhr_l4.stddev[11]:ntreeentries", "Fhr_l4.entries[11]:ntreeentries", "Fhr_l4.mean_scaled[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_12", "fhr_stddev_of_L4_12", "fhr_lanes_of_L4_12", "fhr_occ_of_L4_12"], + "title": ["Average FHR trend: L4_12", "Stddev FHR trend: L4_12", "Active lanes trend: L4_12", "Occupancy trend: L4_12"], + "varexp": ["Fhr_l4.mean[12]:ntreeentries", "Fhr_l4.stddev[12]:ntreeentries", "Fhr_l4.entries[12]:ntreeentries", "Fhr_l4.mean_scaled[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_13", "fhr_stddev_of_L4_13", "fhr_lanes_of_L4_13", "fhr_occ_of_L4_13"], + "title": ["Average FHR trend: L4_13", "Stddev FHR trend: L4_13", "Active lanes trend: L4_13", "Occupancy trend: L4_13"], + "varexp": ["Fhr_l4.mean[13]:ntreeentries", "Fhr_l4.stddev[13]:ntreeentries", "Fhr_l4.entries[13]:ntreeentries", "Fhr_l4.mean_scaled[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_14", "fhr_stddev_of_L4_14", "fhr_lanes_of_L4_14", "fhr_occ_of_L4_14"], + "title": ["Average FHR trend: L4_14", "Stddev FHR trend: L4_14", "Active lanes trend: L4_14", "Occupancy trend: L4_14"], + "varexp": ["Fhr_l4.mean[14]:ntreeentries", "Fhr_l4.stddev[14]:ntreeentries", "Fhr_l4.entries[14]:ntreeentries", "Fhr_l4.mean_scaled[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_15", "fhr_stddev_of_L4_15", "fhr_lanes_of_L4_15", "fhr_occ_of_L4_15"], + "title": ["Average FHR trend: L4_15", "Stddev FHR trend: L4_15", "Active lanes trend: L4_15", "Occupancy trend: L4_15"], + "varexp": ["Fhr_l4.mean[15]:ntreeentries", "Fhr_l4.stddev[15]:ntreeentries", "Fhr_l4.entries[15]:ntreeentries", "Fhr_l4.mean_scaled[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_16", "fhr_stddev_of_L4_16", "fhr_lanes_of_L4_16", "fhr_occ_of_L4_16"], + "title": ["Average FHR trend: L4_16", "Stddev FHR trend: L4_16", "Active lanes trend: L4_16", "Occupancy trend: L4_16"], + "varexp": ["Fhr_l4.mean[16]:ntreeentries", "Fhr_l4.stddev[16]:ntreeentries", "Fhr_l4.entries[16]:ntreeentries", "Fhr_l4.mean_scaled[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_17", "fhr_stddev_of_L4_17", "fhr_lanes_of_L4_17", "fhr_occ_of_L4_17"], + "title": ["Average FHR trend: L4_17", "Stddev FHR trend: L4_17", "Active lanes trend: L4_17", "Occupancy trend: L4_17"], + "varexp": ["Fhr_l4.mean[17]:ntreeentries", "Fhr_l4.stddev[17]:ntreeentries", "Fhr_l4.entries[17]:ntreeentries", "Fhr_l4.mean_scaled[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_18", "fhr_stddev_of_L4_18", "fhr_lanes_of_L4_18", "fhr_occ_of_L4_18"], + "title": ["Average FHR trend: L4_18", "Stddev FHR trend: L4_18", "Active lanes trend: L4_18", "Occupancy trend: L4_18"], + "varexp": ["Fhr_l4.mean[18]:ntreeentries", "Fhr_l4.stddev[18]:ntreeentries", "Fhr_l4.entries[18]:ntreeentries", "Fhr_l4.mean_scaled[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_19", "fhr_stddev_of_L4_19", "fhr_lanes_of_L4_19", "fhr_occ_of_L4_19"], + "title": ["Average FHR trend: L4_19", "Stddev FHR trend: L4_19", "Active lanes trend: L4_19", "Occupancy trend: L4_19"], + "varexp": ["Fhr_l4.mean[19]:ntreeentries", "Fhr_l4.stddev[19]:ntreeentries", "Fhr_l4.entries[19]:ntreeentries", "Fhr_l4.mean_scaled[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_20", "fhr_stddev_of_L4_20", "fhr_lanes_of_L4_20", "fhr_occ_of_L4_20"], + "title": ["Average FHR trend: L4_20", "Stddev FHR trend: L4_20", "Active lanes trend: L4_20", "Occupancy trend: L4_20"], + "varexp": ["Fhr_l4.mean[20]:ntreeentries", "Fhr_l4.stddev[20]:ntreeentries", "Fhr_l4.entries[20]:ntreeentries", "Fhr_l4.mean_scaled[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_21", "fhr_stddev_of_L4_21", "fhr_lanes_of_L4_21", "fhr_occ_of_L4_21"], + "title": ["Average FHR trend: L4_21", "Stddev FHR trend: L4_21", "Active lanes trend: L4_21", "Occupancy trend: L4_21"], + "varexp": ["Fhr_l4.mean[21]:ntreeentries", "Fhr_l4.stddev[21]:ntreeentries", "Fhr_l4.entries[21]:ntreeentries", "Fhr_l4.mean_scaled[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_22", "fhr_stddev_of_L4_22", "fhr_lanes_of_L4_22", "fhr_occ_of_L4_22"], + "title": ["Average FHR trend: L4_22", "Stddev FHR trend: L4_22", "Active lanes trend: L4_22", "Occupancy trend: L4_22"], + "varexp": ["Fhr_l4.mean[22]:ntreeentries", "Fhr_l4.stddev[22]:ntreeentries", "Fhr_l4.entries[22]:ntreeentries", "Fhr_l4.mean_scaled[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_23", "fhr_stddev_of_L4_23", "fhr_lanes_of_L4_23", "fhr_occ_of_L4_23"], + "title": ["Average FHR trend: L4_23", "Stddev FHR trend: L4_23", "Active lanes trend: L4_23", "Occupancy trend: L4_23"], + "varexp": ["Fhr_l4.mean[23]:ntreeentries", "Fhr_l4.stddev[23]:ntreeentries", "Fhr_l4.entries[23]:ntreeentries", "Fhr_l4.mean_scaled[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_24", "fhr_stddev_of_L4_24", "fhr_lanes_of_L4_24", "fhr_occ_of_L4_24"], + "title": ["Average FHR trend: L4_24", "Stddev FHR trend: L4_24", "Active lanes trend: L4_24", "Occupancy trend: L4_24"], + "varexp": ["Fhr_l4.mean[24]:ntreeentries", "Fhr_l4.stddev[24]:ntreeentries", "Fhr_l4.entries[24]:ntreeentries", "Fhr_l4.mean_scaled[24]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_25", "fhr_stddev_of_L4_25", "fhr_lanes_of_L4_25", "fhr_occ_of_L4_25"], + "title": ["Average FHR trend: L4_25", "Stddev FHR trend: L4_25", "Active lanes trend: L4_25", "Occupancy trend: L4_25"], + "varexp": ["Fhr_l4.mean[25]:ntreeentries", "Fhr_l4.stddev[25]:ntreeentries", "Fhr_l4.entries[25]:ntreeentries", "Fhr_l4.mean_scaled[25]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_26", "fhr_stddev_of_L4_26", "fhr_lanes_of_L4_26", "fhr_occ_of_L4_26"], + "title": ["Average FHR trend: L4_26", "Stddev FHR trend: L4_26", "Active lanes trend: L4_26", "Occupancy trend: L4_26"], + "varexp": ["Fhr_l4.mean[26]:ntreeentries", "Fhr_l4.stddev[26]:ntreeentries", "Fhr_l4.entries[26]:ntreeentries", "Fhr_l4.mean_scaled[26]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_27", "fhr_stddev_of_L4_27", "fhr_lanes_of_L4_27", "fhr_occ_of_L4_27"], + "title": ["Average FHR trend: L4_27", "Stddev FHR trend: L4_27", "Active lanes trend: L4_27", "Occupancy trend: L4_27"], + "varexp": ["Fhr_l4.mean[27]:ntreeentries", "Fhr_l4.stddev[27]:ntreeentries", "Fhr_l4.entries[27]:ntreeentries", "Fhr_l4.mean_scaled[27]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_28", "fhr_stddev_of_L4_28", "fhr_lanes_of_L4_28", "fhr_occ_of_L4_28"], + "title": ["Average FHR trend: L4_28", "Stddev FHR trend: L4_28", "Active lanes trend: L4_28", "Occupancy trend: L4_28"], + "varexp": ["Fhr_l4.mean[28]:ntreeentries", "Fhr_l4.stddev[28]:ntreeentries", "Fhr_l4.entries[28]:ntreeentries", "Fhr_l4.mean_scaled[28]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L4_29", "fhr_stddev_of_L4_29", "fhr_lanes_of_L4_29", "fhr_occ_of_L4_29"], + "title": ["Average FHR trend: L4_29", "Stddev FHR trend: L4_29", "Active lanes trend: L4_29", "Occupancy trend: L4_29"], + "varexp": ["Fhr_l4.mean[29]:ntreeentries", "Fhr_l4.stddev[29]:ntreeentries", "Fhr_l4.entries[29]:ntreeentries", "Fhr_l4.mean_scaled[29]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_00", "fhr_stddev_of_L5_00", "fhr_lanes_of_L5_00", "fhr_occ_of_L5_00"], + "title": ["Average FHR trend: L5_00", "Stddev FHR trend: L5_00", "Active lanes trend: L5_00", "Occupancy trend: L5_00"], + "varexp": ["Fhr_l5.mean[0]:ntreeentries", "Fhr_l5.stddev[0]:ntreeentries", "Fhr_l5.entries[0]:ntreeentries", "Fhr_l5.mean_scaled[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_01", "fhr_stddev_of_L5_01", "fhr_lanes_of_L5_01", "fhr_occ_of_L5_01"], + "title": ["Average FHR trend: L5_01", "Stddev FHR trend: L5_01", "Active lanes trend: L5_01", "Occupancy trend: L5_01"], + "varexp": ["Fhr_l5.mean[1]:ntreeentries", "Fhr_l5.stddev[1]:ntreeentries", "Fhr_l5.entries[1]:ntreeentries", "Fhr_l5.mean_scaled[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_02", "fhr_stddev_of_L5_02", "fhr_lanes_of_L5_02", "fhr_occ_of_L5_02"], + "title": ["Average FHR trend: L5_02", "Stddev FHR trend: L5_02", "Active lanes trend: L5_02", "Occupancy trend: L5_02"], + "varexp": ["Fhr_l5.mean[2]:ntreeentries", "Fhr_l5.stddev[2]:ntreeentries", "Fhr_l5.entries[2]:ntreeentries", "Fhr_l5.mean_scaled[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_03", "fhr_stddev_of_L5_03", "fhr_lanes_of_L5_03", "fhr_occ_of_L5_03"], + "title": ["Average FHR trend: L5_03", "Stddev FHR trend: L5_03", "Active lanes trend: L5_03", "Occupancy trend: L5_03"], + "varexp": ["Fhr_l5.mean[3]:ntreeentries", "Fhr_l5.stddev[3]:ntreeentries", "Fhr_l5.entries[3]:ntreeentries", "Fhr_l5.mean_scaled[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_04", "fhr_stddev_of_L5_04", "fhr_lanes_of_L5_04", "fhr_occ_of_L5_04"], + "title": ["Average FHR trend: L5_04", "Stddev FHR trend: L5_04", "Active lanes trend: L5_04", "Occupancy trend: L5_04"], + "varexp": ["Fhr_l5.mean[4]:ntreeentries", "Fhr_l5.stddev[4]:ntreeentries", "Fhr_l5.entries[4]:ntreeentries", "Fhr_l5.mean_scaled[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_05", "fhr_stddev_of_L5_05", "fhr_lanes_of_L5_05", "fhr_occ_of_L5_05"], + "title": ["Average FHR trend: L5_05", "Stddev FHR trend: L5_05", "Active lanes trend: L5_05", "Occupancy trend: L5_05"], + "varexp": ["Fhr_l5.mean[5]:ntreeentries", "Fhr_l5.stddev[5]:ntreeentries", "Fhr_l5.entries[5]:ntreeentries", "Fhr_l5.mean_scaled[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_06", "fhr_stddev_of_L5_06", "fhr_lanes_of_L5_06", "fhr_occ_of_L5_06"], + "title": ["Average FHR trend: L5_06", "Stddev FHR trend: L5_06", "Active lanes trend: L5_06", "Occupancy trend: L5_06"], + "varexp": ["Fhr_l5.mean[6]:ntreeentries", "Fhr_l5.stddev[6]:ntreeentries", "Fhr_l5.entries[6]:ntreeentries", "Fhr_l5.mean_scaled[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_07", "fhr_stddev_of_L5_07", "fhr_lanes_of_L5_07", "fhr_occ_of_L5_07"], + "title": ["Average FHR trend: L5_07", "Stddev FHR trend: L5_07", "Active lanes trend: L5_07", "Occupancy trend: L5_07"], + "varexp": ["Fhr_l5.mean[7]:ntreeentries", "Fhr_l5.stddev[7]:ntreeentries", "Fhr_l5.entries[7]:ntreeentries", "Fhr_l5.mean_scaled[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_08", "fhr_stddev_of_L5_08", "fhr_lanes_of_L5_08", "fhr_occ_of_L5_08"], + "title": ["Average FHR trend: L5_08", "Stddev FHR trend: L5_08", "Active lanes trend: L5_08", "Occupancy trend: L5_08"], + "varexp": ["Fhr_l5.mean[8]:ntreeentries", "Fhr_l5.stddev[8]:ntreeentries", "Fhr_l5.entries[8]:ntreeentries", "Fhr_l5.mean_scaled[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_09", "fhr_stddev_of_L5_09", "fhr_lanes_of_L5_09", "fhr_occ_of_L5_09"], + "title": ["Average FHR trend: L5_09", "Stddev FHR trend: L5_09", "Active lanes trend: L5_09", "Occupancy trend: L5_09"], + "varexp": ["Fhr_l5.mean[9]:ntreeentries", "Fhr_l5.stddev[9]:ntreeentries", "Fhr_l5.entries[9]:ntreeentries", "Fhr_l5.mean_scaled[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_10", "fhr_stddev_of_L5_10", "fhr_lanes_of_L5_10", "fhr_occ_of_L5_10"], + "title": ["Average FHR trend: L5_10", "Stddev FHR trend: L5_10", "Active lanes trend: L5_10", "Occupancy trend: L5_10"], + "varexp": ["Fhr_l5.mean[10]:ntreeentries", "Fhr_l5.stddev[10]:ntreeentries", "Fhr_l5.entries[10]:ntreeentries", "Fhr_l5.mean_scaled[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_11", "fhr_stddev_of_L5_11", "fhr_lanes_of_L5_11", "fhr_occ_of_L5_11"], + "title": ["Average FHR trend: L5_11", "Stddev FHR trend: L5_11", "Active lanes trend: L5_11", "Occupancy trend: L5_11"], + "varexp": ["Fhr_l5.mean[11]:ntreeentries", "Fhr_l5.stddev[11]:ntreeentries", "Fhr_l5.entries[11]:ntreeentries", "Fhr_l5.mean_scaled[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_12", "fhr_stddev_of_L5_12", "fhr_lanes_of_L5_12", "fhr_occ_of_L5_12"], + "title": ["Average FHR trend: L5_12", "Stddev FHR trend: L5_12", "Active lanes trend: L5_12", "Occupancy trend: L5_12"], + "varexp": ["Fhr_l5.mean[12]:ntreeentries", "Fhr_l5.stddev[12]:ntreeentries", "Fhr_l5.entries[12]:ntreeentries", "Fhr_l5.mean_scaled[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_13", "fhr_stddev_of_L5_13", "fhr_lanes_of_L5_13", "fhr_occ_of_L5_13"], + "title": ["Average FHR trend: L5_13", "Stddev FHR trend: L5_13", "Active lanes trend: L5_13", "Occupancy trend: L5_13"], + "varexp": ["Fhr_l5.mean[13]:ntreeentries", "Fhr_l5.stddev[13]:ntreeentries", "Fhr_l5.entries[13]:ntreeentries", "Fhr_l5.mean_scaled[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_14", "fhr_stddev_of_L5_14", "fhr_lanes_of_L5_14", "fhr_occ_of_L5_14"], + "title": ["Average FHR trend: L5_14", "Stddev FHR trend: L5_14", "Active lanes trend: L5_14", "Occupancy trend: L5_14"], + "varexp": ["Fhr_l5.mean[14]:ntreeentries", "Fhr_l5.stddev[14]:ntreeentries", "Fhr_l5.entries[14]:ntreeentries", "Fhr_l5.mean_scaled[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_15", "fhr_stddev_of_L5_15", "fhr_lanes_of_L5_15", "fhr_occ_of_L5_15"], + "title": ["Average FHR trend: L5_15", "Stddev FHR trend: L5_15", "Active lanes trend: L5_15", "Occupancy trend: L5_15"], + "varexp": ["Fhr_l5.mean[15]:ntreeentries", "Fhr_l5.stddev[15]:ntreeentries", "Fhr_l5.entries[15]:ntreeentries", "Fhr_l5.mean_scaled[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_16", "fhr_stddev_of_L5_16", "fhr_lanes_of_L5_16", "fhr_occ_of_L5_16"], + "title": ["Average FHR trend: L5_16", "Stddev FHR trend: L5_16", "Active lanes trend: L5_16", "Occupancy trend: L5_16"], + "varexp": ["Fhr_l5.mean[16]:ntreeentries", "Fhr_l5.stddev[16]:ntreeentries", "Fhr_l5.entries[16]:ntreeentries", "Fhr_l5.mean_scaled[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_17", "fhr_stddev_of_L5_17", "fhr_lanes_of_L5_17", "fhr_occ_of_L5_17"], + "title": ["Average FHR trend: L5_17", "Stddev FHR trend: L5_17", "Active lanes trend: L5_17", "Occupancy trend: L5_17"], + "varexp": ["Fhr_l5.mean[17]:ntreeentries", "Fhr_l5.stddev[17]:ntreeentries", "Fhr_l5.entries[17]:ntreeentries", "Fhr_l5.mean_scaled[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_18", "fhr_stddev_of_L5_18", "fhr_lanes_of_L5_18", "fhr_occ_of_L5_18"], + "title": ["Average FHR trend: L5_18", "Stddev FHR trend: L5_18", "Active lanes trend: L5_18", "Occupancy trend: L5_18"], + "varexp": ["Fhr_l5.mean[18]:ntreeentries", "Fhr_l5.stddev[18]:ntreeentries", "Fhr_l5.entries[18]:ntreeentries", "Fhr_l5.mean_scaled[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_19", "fhr_stddev_of_L5_19", "fhr_lanes_of_L5_19", "fhr_occ_of_L5_19"], + "title": ["Average FHR trend: L5_19", "Stddev FHR trend: L5_19", "Active lanes trend: L5_19", "Occupancy trend: L5_19"], + "varexp": ["Fhr_l5.mean[19]:ntreeentries", "Fhr_l5.stddev[19]:ntreeentries", "Fhr_l5.entries[19]:ntreeentries", "Fhr_l5.mean_scaled[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_20", "fhr_stddev_of_L5_20", "fhr_lanes_of_L5_20", "fhr_occ_of_L5_20"], + "title": ["Average FHR trend: L5_20", "Stddev FHR trend: L5_20", "Active lanes trend: L5_20", "Occupancy trend: L5_20"], + "varexp": ["Fhr_l5.mean[20]:ntreeentries", "Fhr_l5.stddev[20]:ntreeentries", "Fhr_l5.entries[20]:ntreeentries", "Fhr_l5.mean_scaled[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_21", "fhr_stddev_of_L5_21", "fhr_lanes_of_L5_21", "fhr_occ_of_L5_21"], + "title": ["Average FHR trend: L5_21", "Stddev FHR trend: L5_21", "Active lanes trend: L5_21", "Occupancy trend: L5_21"], + "varexp": ["Fhr_l5.mean[21]:ntreeentries", "Fhr_l5.stddev[21]:ntreeentries", "Fhr_l5.entries[21]:ntreeentries", "Fhr_l5.mean_scaled[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_22", "fhr_stddev_of_L5_22", "fhr_lanes_of_L5_22", "fhr_occ_of_L5_22"], + "title": ["Average FHR trend: L5_22", "Stddev FHR trend: L5_22", "Active lanes trend: L5_22", "Occupancy trend: L5_22"], + "varexp": ["Fhr_l5.mean[22]:ntreeentries", "Fhr_l5.stddev[22]:ntreeentries", "Fhr_l5.entries[22]:ntreeentries", "Fhr_l5.mean_scaled[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_23", "fhr_stddev_of_L5_23", "fhr_lanes_of_L5_23", "fhr_occ_of_L5_23"], + "title": ["Average FHR trend: L5_23", "Stddev FHR trend: L5_23", "Active lanes trend: L5_23", "Occupancy trend: L5_23"], + "varexp": ["Fhr_l5.mean[23]:ntreeentries", "Fhr_l5.stddev[23]:ntreeentries", "Fhr_l5.entries[23]:ntreeentries", "Fhr_l5.mean_scaled[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_24", "fhr_stddev_of_L5_24", "fhr_lanes_of_L5_24", "fhr_occ_of_L5_24"], + "title": ["Average FHR trend: L5_24", "Stddev FHR trend: L5_24", "Active lanes trend: L5_24", "Occupancy trend: L5_24"], + "varexp": ["Fhr_l5.mean[24]:ntreeentries", "Fhr_l5.stddev[24]:ntreeentries", "Fhr_l5.entries[24]:ntreeentries", "Fhr_l5.mean_scaled[24]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_25", "fhr_stddev_of_L5_25", "fhr_lanes_of_L5_25", "fhr_occ_of_L5_25"], + "title": ["Average FHR trend: L5_25", "Stddev FHR trend: L5_25", "Active lanes trend: L5_25", "Occupancy trend: L5_25"], + "varexp": ["Fhr_l5.mean[25]:ntreeentries", "Fhr_l5.stddev[25]:ntreeentries", "Fhr_l5.entries[25]:ntreeentries", "Fhr_l5.mean_scaled[25]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_26", "fhr_stddev_of_L5_26", "fhr_lanes_of_L5_26", "fhr_occ_of_L5_26"], + "title": ["Average FHR trend: L5_26", "Stddev FHR trend: L5_26", "Active lanes trend: L5_26", "Occupancy trend: L5_26"], + "varexp": ["Fhr_l5.mean[26]:ntreeentries", "Fhr_l5.stddev[26]:ntreeentries", "Fhr_l5.entries[26]:ntreeentries", "Fhr_l5.mean_scaled[26]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_27", "fhr_stddev_of_L5_27", "fhr_lanes_of_L5_27", "fhr_occ_of_L5_27"], + "title": ["Average FHR trend: L5_27", "Stddev FHR trend: L5_27", "Active lanes trend: L5_27", "Occupancy trend: L5_27"], + "varexp": ["Fhr_l5.mean[27]:ntreeentries", "Fhr_l5.stddev[27]:ntreeentries", "Fhr_l5.entries[27]:ntreeentries", "Fhr_l5.mean_scaled[27]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_28", "fhr_stddev_of_L5_28", "fhr_lanes_of_L5_28", "fhr_occ_of_L5_28"], + "title": ["Average FHR trend: L5_28", "Stddev FHR trend: L5_28", "Active lanes trend: L5_28", "Occupancy trend: L5_28"], + "varexp": ["Fhr_l5.mean[28]:ntreeentries", "Fhr_l5.stddev[28]:ntreeentries", "Fhr_l5.entries[28]:ntreeentries", "Fhr_l5.mean_scaled[28]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_29", "fhr_stddev_of_L5_29", "fhr_lanes_of_L5_29", "fhr_occ_of_L5_29"], + "title": ["Average FHR trend: L5_29", "Stddev FHR trend: L5_29", "Active lanes trend: L5_29", "Occupancy trend: L5_29"], + "varexp": ["Fhr_l5.mean[29]:ntreeentries", "Fhr_l5.stddev[29]:ntreeentries", "Fhr_l5.entries[29]:ntreeentries", "Fhr_l5.mean_scaled[29]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_30", "fhr_stddev_of_L5_30", "fhr_lanes_of_L5_30", "fhr_occ_of_L5_30"], + "title": ["Average FHR trend: L5_30", "Stddev FHR trend: L5_30", "Active lanes trend: L5_30", "Occupancy trend: L5_30"], + "varexp": ["Fhr_l5.mean[30]:ntreeentries", "Fhr_l5.stddev[30]:ntreeentries", "Fhr_l5.entries[30]:ntreeentries", "Fhr_l5.mean_scaled[30]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_31", "fhr_stddev_of_L5_31", "fhr_lanes_of_L5_31", "fhr_occ_of_L5_31"], + "title": ["Average FHR trend: L5_31", "Stddev FHR trend: L5_31", "Active lanes trend: L5_31", "Occupancy trend: L5_31"], + "varexp": ["Fhr_l5.mean[31]:ntreeentries", "Fhr_l5.stddev[31]:ntreeentries", "Fhr_l5.entries[31]:ntreeentries", "Fhr_l5.mean_scaled[31]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_32", "fhr_stddev_of_L5_32", "fhr_lanes_of_L5_32", "fhr_occ_of_L5_32"], + "title": ["Average FHR trend: L5_32", "Stddev FHR trend: L5_32", "Active lanes trend: L5_32", "Occupancy trend: L5_32"], + "varexp": ["Fhr_l5.mean[32]:ntreeentries", "Fhr_l5.stddev[32]:ntreeentries", "Fhr_l5.entries[32]:ntreeentries", "Fhr_l5.mean_scaled[32]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_33", "fhr_stddev_of_L5_33", "fhr_lanes_of_L5_33", "fhr_occ_of_L5_33"], + "title": ["Average FHR trend: L5_33", "Stddev FHR trend: L5_33", "Active lanes trend: L5_33", "Occupancy trend: L5_33"], + "varexp": ["Fhr_l5.mean[33]:ntreeentries", "Fhr_l5.stddev[33]:ntreeentries", "Fhr_l5.entries[33]:ntreeentries", "Fhr_l5.mean_scaled[33]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_34", "fhr_stddev_of_L5_34", "fhr_lanes_of_L5_34", "fhr_occ_of_L5_34"], + "title": ["Average FHR trend: L5_34", "Stddev FHR trend: L5_34", "Active lanes trend: L5_34", "Occupancy trend: L5_34"], + "varexp": ["Fhr_l5.mean[34]:ntreeentries", "Fhr_l5.stddev[34]:ntreeentries", "Fhr_l5.entries[34]:ntreeentries", "Fhr_l5.mean_scaled[34]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_35", "fhr_stddev_of_L5_35", "fhr_lanes_of_L5_35", "fhr_occ_of_L5_35"], + "title": ["Average FHR trend: L5_35", "Stddev FHR trend: L5_35", "Active lanes trend: L5_35", "Occupancy trend: L5_35"], + "varexp": ["Fhr_l5.mean[35]:ntreeentries", "Fhr_l5.stddev[35]:ntreeentries", "Fhr_l5.entries[35]:ntreeentries", "Fhr_l5.mean_scaled[35]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_36", "fhr_stddev_of_L5_36", "fhr_lanes_of_L5_36", "fhr_occ_of_L5_36"], + "title": ["Average FHR trend: L5_36", "Stddev FHR trend: L5_36", "Active lanes trend: L5_36", "Occupancy trend: L5_36"], + "varexp": ["Fhr_l5.mean[36]:ntreeentries", "Fhr_l5.stddev[36]:ntreeentries", "Fhr_l5.entries[36]:ntreeentries", "Fhr_l5.mean_scaled[36]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_37", "fhr_stddev_of_L5_37", "fhr_lanes_of_L5_37", "fhr_occ_of_L5_37"], + "title": ["Average FHR trend: L5_37", "Stddev FHR trend: L5_37", "Active lanes trend: L5_37", "Occupancy trend: L5_37"], + "varexp": ["Fhr_l5.mean[37]:ntreeentries", "Fhr_l5.stddev[37]:ntreeentries", "Fhr_l5.entries[37]:ntreeentries", "Fhr_l5.mean_scaled[37]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_38", "fhr_stddev_of_L5_38", "fhr_lanes_of_L5_38", "fhr_occ_of_L5_38"], + "title": ["Average FHR trend: L5_38", "Stddev FHR trend: L5_38", "Active lanes trend: L5_38", "Occupancy trend: L5_38"], + "varexp": ["Fhr_l5.mean[38]:ntreeentries", "Fhr_l5.stddev[38]:ntreeentries", "Fhr_l5.entries[38]:ntreeentries", "Fhr_l5.mean_scaled[38]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_39", "fhr_stddev_of_L5_39", "fhr_lanes_of_L5_39", "fhr_occ_of_L5_39"], + "title": ["Average FHR trend: L5_39", "Stddev FHR trend: L5_39", "Active lanes trend: L5_39", "Occupancy trend: L5_39"], + "varexp": ["Fhr_l5.mean[39]:ntreeentries", "Fhr_l5.stddev[39]:ntreeentries", "Fhr_l5.entries[39]:ntreeentries", "Fhr_l5.mean_scaled[39]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_40", "fhr_stddev_of_L5_40", "fhr_lanes_of_L5_40", "fhr_occ_of_L5_40"], + "title": ["Average FHR trend: L5_40", "Stddev FHR trend: L5_40", "Active lanes trend: L5_40", "Occupancy trend: L5_40"], + "varexp": ["Fhr_l5.mean[40]:ntreeentries", "Fhr_l5.stddev[40]:ntreeentries", "Fhr_l5.entries[40]:ntreeentries", "Fhr_l5.mean_scaled[40]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L5_41", "fhr_stddev_of_L5_41", "fhr_lanes_of_L5_41", "fhr_occ_of_L5_41"], + "title": ["Average FHR trend: L5_41", "Stddev FHR trend: L5_41", "Active lanes trend: L5_41", "Occupancy trend: L5_41"], + "varexp": ["Fhr_l5.mean[41]:ntreeentries", "Fhr_l5.stddev[41]:ntreeentries", "Fhr_l5.entries[41]:ntreeentries", "Fhr_l5.mean_scaled[41]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_00", "fhr_stddev_of_L6_00", "fhr_lanes_of_L6_00", "fhr_occ_of_L6_00"], + "title": ["Average FHR trend: L6_00", "Stddev FHR trend: L6_00", "Active lanes trend: L6_00", "Occupancy trend: L6_00"], + "varexp": ["Fhr_l6.mean[0]:ntreeentries", "Fhr_l6.stddev[0]:ntreeentries", "Fhr_l6.entries[0]:ntreeentries", "Fhr_l6.mean_scaled[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_01", "fhr_stddev_of_L6_01", "fhr_lanes_of_L6_01", "fhr_occ_of_L6_01"], + "title": ["Average FHR trend: L6_01", "Stddev FHR trend: L6_01", "Active lanes trend: L6_01", "Occupancy trend: L6_01"], + "varexp": ["Fhr_l6.mean[1]:ntreeentries", "Fhr_l6.stddev[1]:ntreeentries", "Fhr_l6.entries[1]:ntreeentries", "Fhr_l6.mean_scaled[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_02", "fhr_stddev_of_L6_02", "fhr_lanes_of_L6_02", "fhr_occ_of_L6_02"], + "title": ["Average FHR trend: L6_02", "Stddev FHR trend: L6_02", "Active lanes trend: L6_02", "Occupancy trend: L6_02"], + "varexp": ["Fhr_l6.mean[2]:ntreeentries", "Fhr_l6.stddev[2]:ntreeentries", "Fhr_l6.entries[2]:ntreeentries", "Fhr_l6.mean_scaled[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_03", "fhr_stddev_of_L6_03", "fhr_lanes_of_L6_03", "fhr_occ_of_L6_03"], + "title": ["Average FHR trend: L6_03", "Stddev FHR trend: L6_03", "Active lanes trend: L6_03", "Occupancy trend: L6_03"], + "varexp": ["Fhr_l6.mean[3]:ntreeentries", "Fhr_l6.stddev[3]:ntreeentries", "Fhr_l6.entries[3]:ntreeentries", "Fhr_l6.mean_scaled[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_04", "fhr_stddev_of_L6_04", "fhr_lanes_of_L6_04", "fhr_occ_of_L6_04"], + "title": ["Average FHR trend: L6_04", "Stddev FHR trend: L6_04", "Active lanes trend: L6_04", "Occupancy trend: L6_04"], + "varexp": ["Fhr_l6.mean[4]:ntreeentries", "Fhr_l6.stddev[4]:ntreeentries", "Fhr_l6.entries[4]:ntreeentries", "Fhr_l6.mean_scaled[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_05", "fhr_stddev_of_L6_05", "fhr_lanes_of_L6_05", "fhr_occ_of_L6_05"], + "title": ["Average FHR trend: L6_05", "Stddev FHR trend: L6_05", "Active lanes trend: L6_05", "Occupancy trend: L6_05"], + "varexp": ["Fhr_l6.mean[5]:ntreeentries", "Fhr_l6.stddev[5]:ntreeentries", "Fhr_l6.entries[5]:ntreeentries", "Fhr_l6.mean_scaled[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_06", "fhr_stddev_of_L6_06", "fhr_lanes_of_L6_06", "fhr_occ_of_L6_06"], + "title": ["Average FHR trend: L6_06", "Stddev FHR trend: L6_06", "Active lanes trend: L6_06", "Occupancy trend: L6_06"], + "varexp": ["Fhr_l6.mean[6]:ntreeentries", "Fhr_l6.stddev[6]:ntreeentries", "Fhr_l6.entries[6]:ntreeentries", "Fhr_l6.mean_scaled[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_07", "fhr_stddev_of_L6_07", "fhr_lanes_of_L6_07", "fhr_occ_of_L6_07"], + "title": ["Average FHR trend: L6_07", "Stddev FHR trend: L6_07", "Active lanes trend: L6_07", "Occupancy trend: L6_07"], + "varexp": ["Fhr_l6.mean[7]:ntreeentries", "Fhr_l6.stddev[7]:ntreeentries", "Fhr_l6.entries[7]:ntreeentries", "Fhr_l6.mean_scaled[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_08", "fhr_stddev_of_L6_08", "fhr_lanes_of_L6_08", "fhr_occ_of_L6_08"], + "title": ["Average FHR trend: L6_08", "Stddev FHR trend: L6_08", "Active lanes trend: L6_08", "Occupancy trend: L6_08"], + "varexp": ["Fhr_l6.mean[8]:ntreeentries", "Fhr_l6.stddev[8]:ntreeentries", "Fhr_l6.entries[8]:ntreeentries", "Fhr_l6.mean_scaled[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_09", "fhr_stddev_of_L6_09", "fhr_lanes_of_L6_09", "fhr_occ_of_L6_09"], + "title": ["Average FHR trend: L6_09", "Stddev FHR trend: L6_09", "Active lanes trend: L6_09", "Occupancy trend: L6_09"], + "varexp": ["Fhr_l6.mean[9]:ntreeentries", "Fhr_l6.stddev[9]:ntreeentries", "Fhr_l6.entries[9]:ntreeentries", "Fhr_l6.mean_scaled[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_10", "fhr_stddev_of_L6_10", "fhr_lanes_of_L6_10", "fhr_occ_of_L6_10"], + "title": ["Average FHR trend: L6_10", "Stddev FHR trend: L6_10", "Active lanes trend: L6_10", "Occupancy trend: L6_10"], + "varexp": ["Fhr_l6.mean[10]:ntreeentries", "Fhr_l6.stddev[10]:ntreeentries", "Fhr_l6.entries[10]:ntreeentries", "Fhr_l6.mean_scaled[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_11", "fhr_stddev_of_L6_11", "fhr_lanes_of_L6_11", "fhr_occ_of_L6_11"], + "title": ["Average FHR trend: L6_11", "Stddev FHR trend: L6_11", "Active lanes trend: L6_11", "Occupancy trend: L6_11"], + "varexp": ["Fhr_l6.mean[11]:ntreeentries", "Fhr_l6.stddev[11]:ntreeentries", "Fhr_l6.entries[11]:ntreeentries", "Fhr_l6.mean_scaled[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_12", "fhr_stddev_of_L6_12", "fhr_lanes_of_L6_12", "fhr_occ_of_L6_12"], + "title": ["Average FHR trend: L6_12", "Stddev FHR trend: L6_12", "Active lanes trend: L6_12", "Occupancy trend: L6_12"], + "varexp": ["Fhr_l6.mean[12]:ntreeentries", "Fhr_l6.stddev[12]:ntreeentries", "Fhr_l6.entries[12]:ntreeentries", "Fhr_l6.mean_scaled[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_13", "fhr_stddev_of_L6_13", "fhr_lanes_of_L6_13", "fhr_occ_of_L6_13"], + "title": ["Average FHR trend: L6_13", "Stddev FHR trend: L6_13", "Active lanes trend: L6_13", "Occupancy trend: L6_13"], + "varexp": ["Fhr_l6.mean[13]:ntreeentries", "Fhr_l6.stddev[13]:ntreeentries", "Fhr_l6.entries[13]:ntreeentries", "Fhr_l6.mean_scaled[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_14", "fhr_stddev_of_L6_14", "fhr_lanes_of_L6_14", "fhr_occ_of_L6_14"], + "title": ["Average FHR trend: L6_14", "Stddev FHR trend: L6_14", "Active lanes trend: L6_14", "Occupancy trend: L6_14"], + "varexp": ["Fhr_l6.mean[14]:ntreeentries", "Fhr_l6.stddev[14]:ntreeentries", "Fhr_l6.entries[14]:ntreeentries", "Fhr_l6.mean_scaled[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_15", "fhr_stddev_of_L6_15", "fhr_lanes_of_L6_15", "fhr_occ_of_L6_15"], + "title": ["Average FHR trend: L6_15", "Stddev FHR trend: L6_15", "Active lanes trend: L6_15", "Occupancy trend: L6_15"], + "varexp": ["Fhr_l6.mean[15]:ntreeentries", "Fhr_l6.stddev[15]:ntreeentries", "Fhr_l6.entries[15]:ntreeentries", "Fhr_l6.mean_scaled[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_16", "fhr_stddev_of_L6_16", "fhr_lanes_of_L6_16", "fhr_occ_of_L6_16"], + "title": ["Average FHR trend: L6_16", "Stddev FHR trend: L6_16", "Active lanes trend: L6_16", "Occupancy trend: L6_16"], + "varexp": ["Fhr_l6.mean[16]:ntreeentries", "Fhr_l6.stddev[16]:ntreeentries", "Fhr_l6.entries[16]:ntreeentries", "Fhr_l6.mean_scaled[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_17", "fhr_stddev_of_L6_17", "fhr_lanes_of_L6_17", "fhr_occ_of_L6_17"], + "title": ["Average FHR trend: L6_17", "Stddev FHR trend: L6_17", "Active lanes trend: L6_17", "Occupancy trend: L6_17"], + "varexp": ["Fhr_l6.mean[17]:ntreeentries", "Fhr_l6.stddev[17]:ntreeentries", "Fhr_l6.entries[17]:ntreeentries", "Fhr_l6.mean_scaled[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_18", "fhr_stddev_of_L6_18", "fhr_lanes_of_L6_18", "fhr_occ_of_L6_18"], + "title": ["Average FHR trend: L6_18", "Stddev FHR trend: L6_18", "Active lanes trend: L6_18", "Occupancy trend: L6_18"], + "varexp": ["Fhr_l6.mean[18]:ntreeentries", "Fhr_l6.stddev[18]:ntreeentries", "Fhr_l6.entries[18]:ntreeentries", "Fhr_l6.mean_scaled[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_19", "fhr_stddev_of_L6_19", "fhr_lanes_of_L6_19", "fhr_occ_of_L6_19"], + "title": ["Average FHR trend: L6_19", "Stddev FHR trend: L6_19", "Active lanes trend: L6_19", "Occupancy trend: L6_19"], + "varexp": ["Fhr_l6.mean[19]:ntreeentries", "Fhr_l6.stddev[19]:ntreeentries", "Fhr_l6.entries[19]:ntreeentries", "Fhr_l6.mean_scaled[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_20", "fhr_stddev_of_L6_20", "fhr_lanes_of_L6_20", "fhr_occ_of_L6_20"], + "title": ["Average FHR trend: L6_20", "Stddev FHR trend: L6_20", "Active lanes trend: L6_20", "Occupancy trend: L6_20"], + "varexp": ["Fhr_l6.mean[20]:ntreeentries", "Fhr_l6.stddev[20]:ntreeentries", "Fhr_l6.entries[20]:ntreeentries", "Fhr_l6.mean_scaled[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_21", "fhr_stddev_of_L6_21", "fhr_lanes_of_L6_21", "fhr_occ_of_L6_21"], + "title": ["Average FHR trend: L6_21", "Stddev FHR trend: L6_21", "Active lanes trend: L6_21", "Occupancy trend: L6_21"], + "varexp": ["Fhr_l6.mean[21]:ntreeentries", "Fhr_l6.stddev[21]:ntreeentries", "Fhr_l6.entries[21]:ntreeentries", "Fhr_l6.mean_scaled[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_22", "fhr_stddev_of_L6_22", "fhr_lanes_of_L6_22", "fhr_occ_of_L6_22"], + "title": ["Average FHR trend: L6_22", "Stddev FHR trend: L6_22", "Active lanes trend: L6_22", "Occupancy trend: L6_22"], + "varexp": ["Fhr_l6.mean[22]:ntreeentries", "Fhr_l6.stddev[22]:ntreeentries", "Fhr_l6.entries[22]:ntreeentries", "Fhr_l6.mean_scaled[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_23", "fhr_stddev_of_L6_23", "fhr_lanes_of_L6_23", "fhr_occ_of_L6_23"], + "title": ["Average FHR trend: L6_23", "Stddev FHR trend: L6_23", "Active lanes trend: L6_23", "Occupancy trend: L6_23"], + "varexp": ["Fhr_l6.mean[23]:ntreeentries", "Fhr_l6.stddev[23]:ntreeentries", "Fhr_l6.entries[23]:ntreeentries", "Fhr_l6.mean_scaled[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_24", "fhr_stddev_of_L6_24", "fhr_lanes_of_L6_24", "fhr_occ_of_L6_24"], + "title": ["Average FHR trend: L6_24", "Stddev FHR trend: L6_24", "Active lanes trend: L6_24", "Occupancy trend: L6_24"], + "varexp": ["Fhr_l6.mean[24]:ntreeentries", "Fhr_l6.stddev[24]:ntreeentries", "Fhr_l6.entries[24]:ntreeentries", "Fhr_l6.mean_scaled[24]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_25", "fhr_stddev_of_L6_25", "fhr_lanes_of_L6_25", "fhr_occ_of_L6_25"], + "title": ["Average FHR trend: L6_25", "Stddev FHR trend: L6_25", "Active lanes trend: L6_25", "Occupancy trend: L6_25"], + "varexp": ["Fhr_l6.mean[25]:ntreeentries", "Fhr_l6.stddev[25]:ntreeentries", "Fhr_l6.entries[25]:ntreeentries", "Fhr_l6.mean_scaled[25]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_26", "fhr_stddev_of_L6_26", "fhr_lanes_of_L6_26", "fhr_occ_of_L6_26"], + "title": ["Average FHR trend: L6_26", "Stddev FHR trend: L6_26", "Active lanes trend: L6_26", "Occupancy trend: L6_26"], + "varexp": ["Fhr_l6.mean[26]:ntreeentries", "Fhr_l6.stddev[26]:ntreeentries", "Fhr_l6.entries[26]:ntreeentries", "Fhr_l6.mean_scaled[26]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_27", "fhr_stddev_of_L6_27", "fhr_lanes_of_L6_27", "fhr_occ_of_L6_27"], + "title": ["Average FHR trend: L6_27", "Stddev FHR trend: L6_27", "Active lanes trend: L6_27", "Occupancy trend: L6_27"], + "varexp": ["Fhr_l6.mean[27]:ntreeentries", "Fhr_l6.stddev[27]:ntreeentries", "Fhr_l6.entries[27]:ntreeentries", "Fhr_l6.mean_scaled[27]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_28", "fhr_stddev_of_L6_28", "fhr_lanes_of_L6_28", "fhr_occ_of_L6_28"], + "title": ["Average FHR trend: L6_28", "Stddev FHR trend: L6_28", "Active lanes trend: L6_28", "Occupancy trend: L6_28"], + "varexp": ["Fhr_l6.mean[28]:ntreeentries", "Fhr_l6.stddev[28]:ntreeentries", "Fhr_l6.entries[28]:ntreeentries", "Fhr_l6.mean_scaled[28]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_29", "fhr_stddev_of_L6_29", "fhr_lanes_of_L6_29", "fhr_occ_of_L6_29"], + "title": ["Average FHR trend: L6_29", "Stddev FHR trend: L6_29", "Active lanes trend: L6_29", "Occupancy trend: L6_29"], + "varexp": ["Fhr_l6.mean[29]:ntreeentries", "Fhr_l6.stddev[29]:ntreeentries", "Fhr_l6.entries[29]:ntreeentries", "Fhr_l6.mean_scaled[29]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_30", "fhr_stddev_of_L6_30", "fhr_lanes_of_L6_30", "fhr_occ_of_L6_30"], + "title": ["Average FHR trend: L6_30", "Stddev FHR trend: L6_30", "Active lanes trend: L6_30", "Occupancy trend: L6_30"], + "varexp": ["Fhr_l6.mean[30]:ntreeentries", "Fhr_l6.stddev[30]:ntreeentries", "Fhr_l6.entries[30]:ntreeentries", "Fhr_l6.mean_scaled[30]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_31", "fhr_stddev_of_L6_31", "fhr_lanes_of_L6_31", "fhr_occ_of_L6_31"], + "title": ["Average FHR trend: L6_31", "Stddev FHR trend: L6_31", "Active lanes trend: L6_31", "Occupancy trend: L6_31"], + "varexp": ["Fhr_l6.mean[31]:ntreeentries", "Fhr_l6.stddev[31]:ntreeentries", "Fhr_l6.entries[31]:ntreeentries", "Fhr_l6.mean_scaled[31]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_32", "fhr_stddev_of_L6_32", "fhr_lanes_of_L6_32", "fhr_occ_of_L6_32"], + "title": ["Average FHR trend: L6_32", "Stddev FHR trend: L6_32", "Active lanes trend: L6_32", "Occupancy trend: L6_32"], + "varexp": ["Fhr_l6.mean[32]:ntreeentries", "Fhr_l6.stddev[32]:ntreeentries", "Fhr_l6.entries[32]:ntreeentries", "Fhr_l6.mean_scaled[32]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_33", "fhr_stddev_of_L6_33", "fhr_lanes_of_L6_33", "fhr_occ_of_L6_33"], + "title": ["Average FHR trend: L6_33", "Stddev FHR trend: L6_33", "Active lanes trend: L6_33", "Occupancy trend: L6_33"], + "varexp": ["Fhr_l6.mean[33]:ntreeentries", "Fhr_l6.stddev[33]:ntreeentries", "Fhr_l6.entries[33]:ntreeentries", "Fhr_l6.mean_scaled[33]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_34", "fhr_stddev_of_L6_34", "fhr_lanes_of_L6_34", "fhr_occ_of_L6_34"], + "title": ["Average FHR trend: L6_34", "Stddev FHR trend: L6_34", "Active lanes trend: L6_34", "Occupancy trend: L6_34"], + "varexp": ["Fhr_l6.mean[34]:ntreeentries", "Fhr_l6.stddev[34]:ntreeentries", "Fhr_l6.entries[34]:ntreeentries", "Fhr_l6.mean_scaled[34]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_35", "fhr_stddev_of_L6_35", "fhr_lanes_of_L6_35", "fhr_occ_of_L6_35"], + "title": ["Average FHR trend: L6_35", "Stddev FHR trend: L6_35", "Active lanes trend: L6_35", "Occupancy trend: L6_35"], + "varexp": ["Fhr_l6.mean[35]:ntreeentries", "Fhr_l6.stddev[35]:ntreeentries", "Fhr_l6.entries[35]:ntreeentries", "Fhr_l6.mean_scaled[35]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_36", "fhr_stddev_of_L6_36", "fhr_lanes_of_L6_36", "fhr_occ_of_L6_36"], + "title": ["Average FHR trend: L6_36", "Stddev FHR trend: L6_36", "Active lanes trend: L6_36", "Occupancy trend: L6_36"], + "varexp": ["Fhr_l6.mean[36]:ntreeentries", "Fhr_l6.stddev[36]:ntreeentries", "Fhr_l6.entries[36]:ntreeentries", "Fhr_l6.mean_scaled[36]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_37", "fhr_stddev_of_L6_37", "fhr_lanes_of_L6_37", "fhr_occ_of_L6_37"], + "title": ["Average FHR trend: L6_37", "Stddev FHR trend: L6_37", "Active lanes trend: L6_37", "Occupancy trend: L6_37"], + "varexp": ["Fhr_l6.mean[37]:ntreeentries", "Fhr_l6.stddev[37]:ntreeentries", "Fhr_l6.entries[37]:ntreeentries", "Fhr_l6.mean_scaled[37]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_38", "fhr_stddev_of_L6_38", "fhr_lanes_of_L6_38", "fhr_occ_of_L6_38"], + "title": ["Average FHR trend: L6_38", "Stddev FHR trend: L6_38", "Active lanes trend: L6_38", "Occupancy trend: L6_38"], + "varexp": ["Fhr_l6.mean[38]:ntreeentries", "Fhr_l6.stddev[38]:ntreeentries", "Fhr_l6.entries[38]:ntreeentries", "Fhr_l6.mean_scaled[38]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_39", "fhr_stddev_of_L6_39", "fhr_lanes_of_L6_39", "fhr_occ_of_L6_39"], + "title": ["Average FHR trend: L6_39", "Stddev FHR trend: L6_39", "Active lanes trend: L6_39", "Occupancy trend: L6_39"], + "varexp": ["Fhr_l6.mean[39]:ntreeentries", "Fhr_l6.stddev[39]:ntreeentries", "Fhr_l6.entries[39]:ntreeentries", "Fhr_l6.mean_scaled[39]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_40", "fhr_stddev_of_L6_40", "fhr_lanes_of_L6_40", "fhr_occ_of_L6_40"], + "title": ["Average FHR trend: L6_40", "Stddev FHR trend: L6_40", "Active lanes trend: L6_40", "Occupancy trend: L6_40"], + "varexp": ["Fhr_l6.mean[40]:ntreeentries", "Fhr_l6.stddev[40]:ntreeentries", "Fhr_l6.entries[40]:ntreeentries", "Fhr_l6.mean_scaled[40]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_41", "fhr_stddev_of_L6_41", "fhr_lanes_of_L6_41", "fhr_occ_of_L6_41"], + "title": ["Average FHR trend: L6_41", "Stddev FHR trend: L6_41", "Active lanes trend: L6_41", "Occupancy trend: L6_41"], + "varexp": ["Fhr_l6.mean[41]:ntreeentries", "Fhr_l6.stddev[41]:ntreeentries", "Fhr_l6.entries[41]:ntreeentries", "Fhr_l6.mean_scaled[41]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_42", "fhr_stddev_of_L6_42", "fhr_lanes_of_L6_42", "fhr_occ_of_L6_42"], + "title": ["Average FHR trend: L6_42", "Stddev FHR trend: L6_42", "Active lanes trend: L6_42", "Occupancy trend: L6_42"], + "varexp": ["Fhr_l6.mean[42]:ntreeentries", "Fhr_l6.stddev[42]:ntreeentries", "Fhr_l6.entries[42]:ntreeentries", "Fhr_l6.mean_scaled[42]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_43", "fhr_stddev_of_L6_43", "fhr_lanes_of_L6_43", "fhr_occ_of_L6_43"], + "title": ["Average FHR trend: L6_43", "Stddev FHR trend: L6_43", "Active lanes trend: L6_43", "Occupancy trend: L6_43"], + "varexp": ["Fhr_l6.mean[43]:ntreeentries", "Fhr_l6.stddev[43]:ntreeentries", "Fhr_l6.entries[43]:ntreeentries", "Fhr_l6.mean_scaled[43]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_44", "fhr_stddev_of_L6_44", "fhr_lanes_of_L6_44", "fhr_occ_of_L6_44"], + "title": ["Average FHR trend: L6_44", "Stddev FHR trend: L6_44", "Active lanes trend: L6_44", "Occupancy trend: L6_44"], + "varexp": ["Fhr_l6.mean[44]:ntreeentries", "Fhr_l6.stddev[44]:ntreeentries", "Fhr_l6.entries[44]:ntreeentries", "Fhr_l6.mean_scaled[44]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_45", "fhr_stddev_of_L6_45", "fhr_lanes_of_L6_45", "fhr_occ_of_L6_45"], + "title": ["Average FHR trend: L6_45", "Stddev FHR trend: L6_45", "Active lanes trend: L6_45", "Occupancy trend: L6_45"], + "varexp": ["Fhr_l6.mean[45]:ntreeentries", "Fhr_l6.stddev[45]:ntreeentries", "Fhr_l6.entries[45]:ntreeentries", "Fhr_l6.mean_scaled[45]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_46", "fhr_stddev_of_L6_46", "fhr_lanes_of_L6_46", "fhr_occ_of_L6_46"], + "title": ["Average FHR trend: L6_46", "Stddev FHR trend: L6_46", "Active lanes trend: L6_46", "Occupancy trend: L6_46"], + "varexp": ["Fhr_l6.mean[46]:ntreeentries", "Fhr_l6.stddev[46]:ntreeentries", "Fhr_l6.entries[46]:ntreeentries", "Fhr_l6.mean_scaled[46]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["fhr_mean_of_L6_47", "fhr_stddev_of_L6_47", "fhr_lanes_of_L6_47", "fhr_occ_of_L6_47"], + "title": ["Average FHR trend: L6_47", "Stddev FHR trend: L6_47", "Active lanes trend: L6_47", "Occupancy trend: L6_47"], + "varexp": ["Fhr_l6.mean[47]:ntreeentries", "Fhr_l6.stddev[47]:ntreeentries", "Fhr_l6.entries[47]:ntreeentries", "Fhr_l6.mean_scaled[47]:ntreeentries"], + "selection": "", + "option": "PL" + } + ], + + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:ITS/MO/FHRTask/General/General_Occupancy" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/ITS/itsQCTrendingThr.json b/Modules/ITS/itsQCTrendingThr.json new file mode 100755 index 0000000000..8811041b6d --- /dev/null +++ b/Modules/ITS/itsQCTrendingThr.json @@ -0,0 +1,1443 @@ +{ + "qc": { + "config": { + "postprocessing": { + "periodSeconds": 10.0 + }, + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "0" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "ITSqcTrendingThr": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTaskITSThr", + "moduleName": "QualityControl", + "detectorName": "ITS", + "dataSources": [ + { + "type": "repository", + "paths": ["ITS/MO/ITSThresholdCalibrationTask/THRChipAverageIB","ITS/MO/ITSThresholdCalibrationTask/THRChipAverageML","ITS/MO/ITSThresholdCalibrationTask/THRChipAverageOL"], + "names": ["Threshold_IB","Threshold_ML","Threshold_OL"], + "reductorName": "o2::quality_control_modules::its::TH2XlineReductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "names": ["mean_of_Threshold_L0_00", "rms_of_Threshold_L0_00", "ActiveChips_of_L0_00"], + "title": ["Mean trend of Threshold: L0_00", "RMS trend of Threshold: L0_00", "Active_Chip trend: L0_00"], + "varexp": ["Threshold_IB.mean[0]:ntreeentries", "Threshold_IB.stddev[0]:ntreeentries", "Threshold_IB.entries[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_01", "rms_of_Threshold_L0_01", "ActiveChips_of_L0_01"], + "title": ["Mean trend of Threshold: L0_01", "RMS trend of Threshold: L0_01", "Active_Chip trend: L0_01"], + "varexp": ["Threshold_IB.mean[1]:ntreeentries", "Threshold_IB.stddev[1]:ntreeentries", "Threshold_IB.entries[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_02", "rms_of_Threshold_L0_02", "ActiveChips_of_L0_02"], + "title": ["Mean trend of Threshold: L0_02", "RMS trend of Threshold: L0_02", "Active_Chip trend: L0_02"], + "varexp": ["Threshold_IB.mean[2]:ntreeentries", "Threshold_IB.stddev[2]:ntreeentries", "Threshold_IB.entries[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_03", "rms_of_Threshold_L0_03", "ActiveChips_of_L0_03"], + "title": ["Mean trend of Threshold: L0_03", "RMS trend of Threshold: L0_03", "Active_Chip trend: L0_03"], + "varexp": ["Threshold_IB.mean[3]:ntreeentries", "Threshold_IB.stddev[3]:ntreeentries", "Threshold_IB.entries[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_04", "rms_of_Threshold_L0_04", "ActiveChips_of_L0_04"], + "title": ["Mean trend of Threshold: L0_04", "RMS trend of Threshold: L0_04", "Active_Chip trend: L0_04"], + "varexp": ["Threshold_IB.mean[4]:ntreeentries", "Threshold_IB.stddev[4]:ntreeentries", "Threshold_IB.entries[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_05", "rms_of_Threshold_L0_05", "ActiveChips_of_L0_05"], + "title": ["Mean trend of Threshold: L0_05", "RMS trend of Threshold: L0_05", "Active_Chip trend: L0_05"], + "varexp": ["Threshold_IB.mean[5]:ntreeentries", "Threshold_IB.stddev[5]:ntreeentries", "Threshold_IB.entries[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_06", "rms_of_Threshold_L0_06", "ActiveChips_of_L0_06"], + "title": ["Mean trend of Threshold: L0_06", "RMS trend of Threshold: L0_06", "Active_Chip trend: L0_06"], + "varexp": ["Threshold_IB.mean[6]:ntreeentries", "Threshold_IB.stddev[6]:ntreeentries", "Threshold_IB.entries[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_07", "rms_of_Threshold_L0_07", "ActiveChips_of_L0_07"], + "title": ["Mean trend of Threshold: L0_07", "RMS trend of Threshold: L0_07", "Active_Chip trend: L0_07"], + "varexp": ["Threshold_IB.mean[7]:ntreeentries", "Threshold_IB.stddev[7]:ntreeentries", "Threshold_IB.entries[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_08", "rms_of_Threshold_L0_08", "ActiveChips_of_L0_08"], + "title": ["Mean trend of Threshold: L0_08", "RMS trend of Threshold: L0_08", "Active_Chip trend: L0_08"], + "varexp": ["Threshold_IB.mean[8]:ntreeentries", "Threshold_IB.stddev[8]:ntreeentries", "Threshold_IB.entries[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_09", "rms_of_Threshold_L0_09", "ActiveChips_of_L0_09"], + "title": ["Mean trend of Threshold: L0_09", "RMS trend of Threshold: L0_09", "Active_Chip trend: L0_09"], + "varexp": ["Threshold_IB.mean[9]:ntreeentries", "Threshold_IB.stddev[9]:ntreeentries", "Threshold_IB.entries[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_10", "rms_of_Threshold_L0_10", "ActiveChips_of_L0_10"], + "title": ["Mean trend of Threshold: L0_10", "RMS trend of Threshold: L0_10", "Active_Chip trend: L0_10"], + "varexp": ["Threshold_IB.mean[10]:ntreeentries", "Threshold_IB.stddev[10]:ntreeentries", "Threshold_IB.entries[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L0_11", "rms_of_Threshold_L0_11", "ActiveChips_of_L0_11"], + "title": ["Mean trend of Threshold: L0_11", "RMS trend of Threshold: L0_11", "Active_Chip trend: L0_11"], + "varexp": ["Threshold_IB.mean[11]:ntreeentries", "Threshold_IB.stddev[11]:ntreeentries", "Threshold_IB.entries[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + + + + + + + { + "names": ["mean_of_Threshold_L1_00", "rms_of_Threshold_L1_00", "ActiveChips_of_L1_00"], + "title": ["Mean trend of Threshold: L1_00", "RMS trend of Threshold: L1_00", "Active_Chip trend: L1_00"], + "varexp": ["Threshold_IB.mean[12]:ntreeentries", "Threshold_IB.stddev[12]:ntreeentries", "Threshold_IB.entries[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_01", "rms_of_Threshold_L1_01", "ActiveChips_of_L1_01"], + "title": ["Mean trend of Threshold: L1_02", "RMS trend of Threshold: L1_02", "Active_Chip trend: L1_02"], + "varexp": ["Threshold_IB.mean[13]:ntreeentries", "Threshold_IB.stddev[13]:ntreeentries", "Threshold_IB.entries[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_02", "rms_of_Threshold_L1_02", "ActiveChips_of_L1_02"], + "title": ["Mean trend of Threshold: L1_02", "RMS trend of Threshold: L1_02", "Active_Chip trend: L1_02"], + "varexp": ["Threshold_IB.mean[14]:ntreeentries", "Threshold_IB.stddev[14]:ntreeentries", "Threshold_IB.entries[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_03", "rms_of_Threshold_L1_03", "ActiveChips_of_L1_03"], + "title": ["Mean trend of Threshold: L1_03", "RMS trend of Threshold: L1_03", "Active_Chip trend: L1_03"], + "varexp": ["Threshold_IB.mean[15]:ntreeentries", "Threshold_IB.stddev[15]:ntreeentries", "Threshold_IB.entries[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_04", "rms_of_Threshold_L1_04", "ActiveChips_of_L1_04"], + "title": ["Mean trend of Threshold: L1_04", "RMS trend of Threshold: L1_04", "Active_Chip trend: L1_04"], + "varexp": ["Threshold_IB.mean[16]:ntreeentries", "Threshold_IB.stddev[16]:ntreeentries", "Threshold_IB.entries[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_05", "rms_of_Threshold_L1_05", "ActiveChips_of_L1_05"], + "title": ["Mean trend of Threshold: L1_05", "RMS trend of Threshold: L1_05", "Active_Chip trend: L1_05"], + "varexp": ["Threshold_IB.mean[17]:ntreeentries", "Threshold_IB.stddev[17]:ntreeentries", "Threshold_IB.entries[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_06", "rms_of_Threshold_L1_06", "ActiveChips_of_L1_06"], + "title": ["Mean trend of Threshold: L1_06", "RMS trend of Threshold: L1_06", "Active_Chip trend: L1_06"], + "varexp": ["Threshold_IB.mean[18]:ntreeentries", "Threshold_IB.stddev[18]:ntreeentries", "Threshold_IB.entries[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_07", "rms_of_Threshold_L1_07", "ActiveChips_of_L1_07"], + "title": ["Mean trend of Threshold: L1_07", "RMS trend of Threshold: L1_07", "Active_Chip trend: L1_07"], + "varexp": ["Threshold_IB.mean[19]:ntreeentries", "Threshold_IB.stddev[19]:ntreeentries", "Threshold_IB.entries[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_08", "rms_of_Threshold_L1_08", "ActiveChips_of_L1_08"], + "title": ["Mean trend of Threshold: L1_08", "RMS trend of Threshold: L1_08", "Active_Chip trend: L1_08"], + "varexp": ["Threshold_IB.mean[20]:ntreeentries", "Threshold_IB.stddev[20]:ntreeentries", "Threshold_IB.entries[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_09", "rms_of_Threshold_L1_09", "ActiveChips_of_L1_09"], + "title": ["Mean trend of Threshold: L1_09", "RMS trend of Threshold: L1_09", "Active_Chip trend: L1_09"], + "varexp": ["Threshold_IB.mean[21]:ntreeentries", "Threshold_IB.stddev[21]:ntreeentries", "Threshold_IB.entries[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_10", "rms_of_Threshold_L1_10", "ActiveChips_of_L1_10"], + "title": ["Mean trend of Threshold: L1_10", "RMS trend of Threshold: L1_10", "Active_Chip trend: L1_10"], + "varexp": ["Threshold_IB.mean[22]:ntreeentries", "Threshold_IB.stddev[22]:ntreeentries", "Threshold_IB.entries[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_11", "rms_of_Threshold_L1_11", "ActiveChips_of_L1_11"], + "title": ["Mean trend of Threshold: L1_11", "RMS trend of Threshold: L1_11", "Active_Chip trend: L1_11"], + "varexp": ["Threshold_IB.mean[23]:ntreeentries", "Threshold_IB.stddev[23]:ntreeentries", "Threshold_IB.entries[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_12", "rms_of_Threshold_L1_12", "ActiveChips_of_L1_12"], + "title": ["Mean trend of Threshold: L1_12", "RMS trend of Threshold: L1_12", "Active_Chip trend: L1_12"], + "varexp": ["Threshold_IB.mean[24]:ntreeentries", "Threshold_IB.stddev[24]:ntreeentries", "Threshold_IB.entries[24]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_13", "rms_of_Threshold_L1_13", "ActiveChips_of_L1_13"], + "title": ["Mean trend of Threshold: L1_13", "RMS trend of Threshold: L1_13", "Active_Chip trend: L1_13"], + "varexp": ["Threshold_IB.mean[25]:ntreeentries", "Threshold_IB.stddev[25]:ntreeentries", "Threshold_IB.entries[25]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_14", "rms_of_Threshold_L1_14", "ActiveChips_of_L1_14"], + "title": ["Mean trend of Threshold: L1_14", "RMS trend of Threshold: L1_14", "Active_Chip trend: L1_14"], + "varexp": ["Threshold_IB.mean[26]:ntreeentries", "Threshold_IB.stddev[26]:ntreeentries", "Threshold_IB.entries[26]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L1_15", "rms_of_Threshold_L1_15", "ActiveChips_of_L1_15"], + "title": ["Mean trend of Threshold: L1_15", "RMS trend of Threshold: L1_15", "Active_Chip trend: L1_15"], + "varexp": ["Threshold_IB.mean[27]:ntreeentries", "Threshold_IB.stddev[27]:ntreeentries", "Threshold_IB.entries[27]:ntreeentries"], + "selection": "", + "option": "PL" + }, + + + + + { + "names": ["mean_of_Threshold_L2_00", "rms_of_Threshold_L2_00", "ActiveChips_of_L2_00"], + "title": ["Mean trend of Threshold: L2_00", "RMS trend of Threshold: L2_00", "Active_Chip trend: L2_00"], + "varexp": ["Threshold_IB.mean[28]:ntreeentries", "Threshold_IB.stddev[28]:ntreeentries", "Threshold_IB.entries[28]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_01", "rms_of_Threshold_L2_01", "ActiveChips_of_L2_01"], + "title": ["Mean trend of Threshold: L2_01", "RMS trend of Threshold: L2_01", "Active_Chip trend: L2_01"], + "varexp": ["Threshold_IB.mean[29]:ntreeentries", "Threshold_IB.stddev[29]:ntreeentries", "Threshold_IB.entries[29]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_02", "rms_of_Threshold_L2_02", "ActiveChips_of_L2_02"], + "title": ["Mean trend of Threshold: L2_02", "RMS trend of Threshold: L2_02", "Active_Chip trend: L2_02"], + "varexp": ["Threshold_IB.mean[30]:ntreeentries", "Threshold_IB.stddev[30]:ntreeentries", "Threshold_IB.entries[30]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_03", "rms_of_Threshold_L2_03", "ActiveChips_of_L2_03"], + "title": ["Mean trend of Threshold: L2_03", "RMS trend of Threshold: L2_03", "Active_Chip trend: L2_03"], + "varexp": ["Threshold_IB.mean[31]:ntreeentries", "Threshold_IB.stddev[31]:ntreeentries", "Threshold_IB.entries[31]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_04", "rms_of_Threshold_L2_04", "ActiveChips_of_L2_04"], + "title": ["Mean trend of Threshold: L2_04", "RMS trend of Threshold: L2_04", "Active_Chip trend: L2_04"], + "varexp": ["Threshold_IB.mean[32]:ntreeentries", "Threshold_IB.stddev[32]:ntreeentries", "Threshold_IB.entries[32]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_05", "rms_of_Threshold_L2_05", "ActiveChips_of_L2_05"], + "title": ["Mean trend of Threshold: L2_05", "RMS trend of Threshold: L2_05", "Active_Chip trend: L2_05"], + "varexp": ["Threshold_IB.mean[33]:ntreeentries", "Threshold_IB.stddev[33]:ntreeentries", "Threshold_IB.entries[33]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_06", "rms_of_Threshold_L2_06", "ActiveChips_of_L2_06"], + "title": ["Mean trend of Threshold: L2_06", "RMS trend of Threshold: L2_06", "Active_Chip trend: L2_06"], + "varexp": ["Threshold_IB.mean[34]:ntreeentries", "Threshold_IB.stddev[34]:ntreeentries", "Threshold_IB.entries[34]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_07", "rms_of_Threshold_L2_07", "ActiveChips_of_L2_07"], + "title": ["Mean trend of Threshold: L2_07", "RMS trend of Threshold: L2_07", "Active_Chip trend: L2_07"], + "varexp": ["Threshold_IB.mean[35]:ntreeentries", "Threshold_IB.stddev[35]:ntreeentries", "Threshold_IB.entries[35]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_08", "rms_of_Threshold_L2_08", "ActiveChips_of_L2_08"], + "title": ["Mean trend of Threshold: L2_08", "RMS trend of Threshold: L2_08", "Active_Chip trend: L2_08"], + "varexp": ["Threshold_IB.mean[36]:ntreeentries", "Threshold_IB.stddev[36]:ntreeentries", "Threshold_IB.entries[36]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_09", "rms_of_Threshold_L2_09", "ActiveChips_of_L2_09"], + "title": ["Mean trend of Threshold: L2_09", "RMS trend of Threshold: L2_09", "Active_Chip trend: L2_09"], + "varexp": ["Threshold_IB.mean[37]:ntreeentries", "Threshold_IB.stddev[37]:ntreeentries", "Threshold_IB.entries[37]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_10", "rms_of_Threshold_L2_10", "ActiveChips_of_L2_10"], + "title": ["Mean trend of Threshold: L2_10", "RMS trend of Threshold: L2_10", "Active_Chip trend: L2_10"], + "varexp": ["Threshold_IB.mean[38]:ntreeentries", "Threshold_IB.stddev[38]:ntreeentries", "Threshold_IB.entries[38]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_11", "rms_of_Threshold_L2_11", "ActiveChips_of_L2_11"], + "title": ["Mean trend of Threshold: L2_11", "RMS trend of Threshold: L2_11", "Active_Chip trend: L2_11"], + "varexp": ["Threshold_IB.mean[39]:ntreeentries", "Threshold_IB.stddev[39]:ntreeentries", "Threshold_IB.entries[39]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_12", "rms_of_Threshold_L2_12", "ActiveChips_of_L2_12"], + "title": ["Mean trend of Threshold: L2_12", "RMS trend of Threshold: L2_12", "Active_Chip trend: L2_12"], + "varexp": ["Threshold_IB.mean[40]:ntreeentries", "Threshold_IB.stddev[40]:ntreeentries", "Threshold_IB.entries[40]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_13", "rms_of_Threshold_L2_13", "ActiveChips_of_L2_13"], + "title": ["Mean trend of Threshold: L2_13", "RMS trend of Threshold: L2_13", "Active_Chip trend: L2_13"], + "varexp": ["Threshold_IB.mean[41]:ntreeentries", "Threshold_IB.stddev[41]:ntreeentries", "Threshold_IB.entries[41]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_14", "rms_of_Threshold_L2_14", "ActiveChips_of_L2_14"], + "title": ["Mean trend of Threshold: L2_14", "RMS trend of Threshold: L2_14", "Active_Chip trend: L2_14"], + "varexp": ["Threshold_IB.mean[42]:ntreeentries", "Threshold_IB.stddev[42]:ntreeentries", "Threshold_IB.entries[42]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_15", "rms_of_Threshold_L2_15", "ActiveChips_of_L2_15"], + "title": ["Mean trend of Threshold: L2_15", "RMS trend of Threshold: L2_15", "Active_Chip trend: L2_15"], + "varexp": ["Threshold_IB.mean[43]:ntreeentries", "Threshold_IB.stddev[43]:ntreeentries", "Threshold_IB.entries[43]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_16", "rms_of_Threshold_L2_16", "ActiveChips_of_L2_16"], + "title": ["Mean trend of Threshold: L2_16", "RMS trend of Threshold: L2_16", "Active_Chip trend: L2_16"], + "varexp": ["Threshold_IB.mean[44]:ntreeentries", "Threshold_IB.stddev[44]:ntreeentries", "Threshold_IB.entries[44]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_17", "rms_of_Threshold_L2_17", "ActiveChips_of_L2_17"], + "title": ["Mean trend of Threshold: L2_17", "RMS trend of Threshold: L2_17", "Active_Chip trend: L2_17"], + "varexp": ["Threshold_IB.mean[45]:ntreeentries", "Threshold_IB.stddev[45]:ntreeentries", "Threshold_IB.entries[45]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_18", "rms_of_Threshold_L2_18", "ActiveChips_of_L2_18"], + "title": ["Mean trend of Threshold: L2_18", "RMS trend of Threshold: L2_18", "Active_Chip trend: L2_18"], + "varexp": ["Threshold_IB.mean[46]:ntreeentries", "Threshold_IB.stddev[46]:ntreeentries", "Threshold_IB.entries[46]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L2_19", "rms_of_Threshold_L2_19", "ActiveChips_of_L2_19"], + "title": ["Mean trend of Threshold: L2_19", "RMS trend of Threshold: L2_19", "Active_Chip trend: L2_19"], + "varexp": ["Threshold_IB.mean[47]:ntreeentries", "Threshold_IB.stddev[47]:ntreeentries", "Threshold_IB.entries[47]:ntreeentries"], + "selection": "", + "option": "PL" + }, + + + + + + + + + + + + + + { + "names": ["mean_of_Threshold_L3_00", "rms_of_Threshold_L3_00", "ActiveChips_of_L3_00"], + "title": ["Mean trend of Threshold: L3_00", "RMS trend of Threshold: L3_00", "Active_Chip trend: L3_00"], + "varexp": ["Threshold_ML.mean[0]:ntreeentries", "Threshold_ML.stddev[0]:ntreeentries", "Threshold_ML.entries[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_01", "rms_of_Threshold_L3_01", "ActiveChips_of_L3_01"], + "title": ["Mean trend of Threshold: L3_01", "RMS trend of Threshold: L3_01", "Active_Chip trend: L3_01"], + "varexp": ["Threshold_ML.mean[1]:ntreeentries", "Threshold_ML.stddev[1]:ntreeentries", "Threshold_ML.entries[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_02", "rms_of_Threshold_L3_02", "ActiveChips_of_L3_02"], + "title": ["Mean trend of Threshold: L3_02", "RMS trend of Threshold: L3_02", "Active_Chip trend: L3_02"], + "varexp": ["Threshold_ML.mean[2]:ntreeentries", "Threshold_ML.stddev[2]:ntreeentries", "Threshold_ML.entries[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_03", "rms_of_Threshold_L3_03", "ActiveChips_of_L3_03"], + "title": ["Mean trend of Threshold: L3_03", "RMS trend of Threshold: L3_03", "Active_Chip trend: L3_03"], + "varexp": ["Threshold_ML.mean[3]:ntreeentries", "Threshold_ML.stddev[3]:ntreeentries", "Threshold_ML.entries[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_04", "rms_of_Threshold_L3_04", "ActiveChips_of_L3_04"], + "title": ["Mean trend of Threshold: L3_04", "RMS trend of Threshold: L3_04", "Active_Chip trend: L3_04"], + "varexp": ["Threshold_ML.mean[4]:ntreeentries", "Threshold_ML.stddev[4]:ntreeentries", "Threshold_ML.entries[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_05", "rms_of_Threshold_L3_05", "ActiveChips_of_L3_05"], + "title": ["Mean trend of Threshold: L3_05", "RMS trend of Threshold: L3_05", "Active_Chip trend: L3_05"], + "varexp": ["Threshold_ML.mean[5]:ntreeentries", "Threshold_ML.stddev[5]:ntreeentries", "Threshold_ML.entries[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_06", "rms_of_Threshold_L3_06", "ActiveChips_of_L3_06"], + "title": ["Mean trend of Threshold: L3_07", "RMS trend of Threshold: L3_07", "Active_Chip trend: L3_07"], + "varexp": ["Threshold_ML.mean[6]:ntreeentries", "Threshold_ML.stddev[6]:ntreeentries", "Threshold_ML.entries[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_07", "rms_of_Threshold_L3_07", "ActiveChips_of_L3_07"], + "title": ["Mean trend of Threshold: L3_07", "RMS trend of Threshold: L3_07", "Active_Chip trend: L3_07"], + "varexp": ["Threshold_ML.mean[7]:ntreeentries", "Threshold_ML.stddev[7]:ntreeentries", "Threshold_ML.entries[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_08", "rms_of_Threshold_L3_08", "ActiveChips_of_L3_08"], + "title": ["Mean trend of Threshold: L3_08", "RMS trend of Threshold: L3_08", "Active_Chip trend: L3_08"], + "varexp": ["Threshold_ML.mean[8]:ntreeentries", "Threshold_ML.stddev[8]:ntreeentries", "Threshold_ML.entries[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_09", "rms_of_Threshold_L3_09", "ActiveChips_of_L3_09"], + "title": ["Mean trend of Threshold: L3_09", "RMS trend of Threshold: L3_09", "Active_Chip trend: L3_09"], + "varexp": ["Threshold_ML.mean[9]:ntreeentries", "Threshold_ML.stddev[9]:ntreeentries", "Threshold_ML.entries[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_10", "rms_of_Threshold_L3_10", "ActiveChips_of_L3_10"], + "title": ["Mean trend of Threshold: L3_10", "RMS trend of Threshold: L3_10", "Active_Chip trend: L3_10"], + "varexp": ["Threshold_ML.mean[10]:ntreeentries", "Threshold_ML.stddev[10]:ntreeentries", "Threshold_ML.entries[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_11", "rms_of_Threshold_L3_11", "ActiveChips_of_L3_11"], + "title": ["Mean trend of Threshold: L3_11", "RMS trend of Threshold: L3_11", "Active_Chip trend: L3_11"], + "varexp": ["Threshold_ML.mean[11]:ntreeentries", "Threshold_ML.stddev[11]:ntreeentries", "Threshold_ML.entries[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_12", "rms_of_Threshold_L3_12", "ActiveChips_of_L3_12"], + "title": ["Mean trend of Threshold: L3_12", "RMS trend of Threshold: L3_12", "Active_Chip trend: L3_12"], + "varexp": ["Threshold_ML.mean[12]:ntreeentries", "Threshold_ML.stddev[12]:ntreeentries", "Threshold_ML.entries[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_13", "rms_of_Threshold_L3_13", "ActiveChips_of_L3_13"], + "title": ["Mean trend of Threshold: L3_13", "RMS trend of Threshold: L3_13", "Active_Chip trend: L3_13"], + "varexp": ["Threshold_ML.mean[13]:ntreeentries", "Threshold_ML.stddev[13]:ntreeentries", "Threshold_ML.entries[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_14", "rms_of_Threshold_L3_14", "ActiveChips_of_L3_14"], + "title": ["Mean trend of Threshold: L3_14", "RMS trend of Threshold: L3_14", "Active_Chip trend: L3_14"], + "varexp": ["Threshold_ML.mean[14]:ntreeentries", "Threshold_ML.stddev[14]:ntreeentries", "Threshold_ML.entries[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_15", "rms_of_Threshold_L3_15", "ActiveChips_of_L3_15"], + "title": ["Mean trend of Threshold: L3_15", "RMS trend of Threshold: L3_15", "Active_Chip trend: L3_15"], + "varexp": ["Threshold_ML.mean[15]:ntreeentries", "Threshold_ML.stddev[15]:ntreeentries", "Threshold_ML.entries[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_16", "rms_of_Threshold_L3_16", "ActiveChips_of_L3_16"], + "title": ["Mean trend of Threshold: L3_16", "RMS trend of Threshold: L3_16", "Active_Chip trend: L3_16"], + "varexp": ["Threshold_ML.mean[16]:ntreeentries", "Threshold_ML.stddev[16]:ntreeentries", "Threshold_ML.entries[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_17", "rms_of_Threshold_L3_17", "ActiveChips_of_L3_17"], + "title": ["Mean trend of Threshold: L3_17", "RMS trend of Threshold: L3_17", "Active_Chip trend: L3_17"], + "varexp": ["Threshold_ML.mean[17]:ntreeentries", "Threshold_ML.stddev[17]:ntreeentries", "Threshold_ML.entries[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_18", "rms_of_Threshold_L3_18", "ActiveChips_of_L3_18"], + "title": ["Mean trend of Threshold: L3_18", "RMS trend of Threshold: L3_18", "Active_Chip trend: L3_18"], + "varexp": ["Threshold_ML.mean[18]:ntreeentries", "Threshold_ML.stddev[18]:ntreeentries", "Threshold_ML.entries[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_19", "rms_of_Threshold_L3_19", "ActiveChips_of_L3_19"], + "title": ["Mean trend of Threshold: L3_19", "RMS trend of Threshold: L3_19", "Active_Chip trend: L3_19"], + "varexp": ["Threshold_ML.mean[19]:ntreeentries", "Threshold_ML.stddev[19]:ntreeentries", "Threshold_ML.entries[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_20", "rms_of_Threshold_L3_20", "ActiveChips_of_L3_20"], + "title": ["Mean trend of Threshold: L3_20", "RMS trend of Threshold: L3_20", "Active_Chip trend: L3_20"], + "varexp": ["Threshold_ML.mean[20]:ntreeentries", "Threshold_ML.stddev[20]:ntreeentries", "Threshold_ML.entries[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_21", "rms_of_Threshold_L3_21", "ActiveChips_of_L3_21"], + "title": ["Mean trend of Threshold: L3_21", "RMS trend of Threshold: L3_21", "Active_Chip trend: L3_21"], + "varexp": ["Threshold_ML.mean[21]:ntreeentries", "Threshold_ML.stddev[21]:ntreeentries", "Threshold_ML.entries[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_22", "rms_of_Threshold_L3_22", "ActiveChips_of_L3_22"], + "title": ["Mean trend of Threshold: L3_22", "RMS trend of Threshold: L3_22", "Active_Chip trend: L3_22"], + "varexp": ["Threshold_ML.mean[22]:ntreeentries", "Threshold_ML.stddev[22]:ntreeentries", "Threshold_ML.entries[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L3_23", "rms_of_Threshold_L3_23", "ActiveChips_of_L3_23"], + "title": ["Mean trend of Threshold: L3_23", "RMS trend of Threshold: L3_23", "Active_Chip trend: L3_23"], + "varexp": ["Threshold_ML.mean[23]:ntreeentries", "Threshold_ML.stddev[23]:ntreeentries", "Threshold_ML.entries[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + + + + + + + + + { + "names": ["mean_of_Threshold_L4_00", "rms_of_Threshold_L4_00", "ActiveChips_of_L4_00"], + "title": ["Mean trend of Threshold: L4_00", "RMS trend of Threshold: L4_00", "Active_Chip trend: L4_0"], + "varexp": ["Threshold_ML.mean[24]:ntreeentries", "Threshold_ML.stddev[24]:ntreeentries", "Threshold_ML.entries[24]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_01", "rms_of_Threshold_L4_01", "ActiveChips_of_L4_01"], + "title": ["Mean trend of Threshold: L4_01", "RMS trend of Threshold: L4_01", "Active_Chip trend: L4_01"], + "varexp": ["Threshold_ML.mean[25]:ntreeentries", "Threshold_ML.stddev[25]:ntreeentries", "Threshold_ML.entries[25]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_02", "rms_of_Threshold_L4_02", "ActiveChips_of_L4_02"], + "title": ["Mean trend of Threshold: L4_02", "RMS trend of Threshold: L4_02", "Active_Chip trend: L4_02"], + "varexp": ["Threshold_ML.mean[26]:ntreeentries", "Threshold_ML.stddev[26]:ntreeentries", "Threshold_ML.entries[26]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_03", "rms_of_Threshold_L4_03", "ActiveChips_of_L4_03"], + "title": ["Mean trend of Threshold: L4_03", "RMS trend of Threshold: L4_03", "Active_Chip trend: L4_03"], + "varexp": ["Threshold_ML.mean[27]:ntreeentries", "Threshold_ML.stddev[27]:ntreeentries", "Threshold_ML.entries[27]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_04", "rms_of_Threshold_L4_04", "ActiveChips_of_L4_04"], + "title": ["Mean trend of Threshold: L4_04", "RMS trend of Threshold: L4_04", "Active_Chip trend: L4_04"], + "varexp": ["Threshold_ML.mean[28]:ntreeentries", "Threshold_ML.stddev[28]:ntreeentries", "Threshold_ML.entries[28]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_05", "rms_of_Threshold_L4_05", "ActiveChips_of_L4_05"], + "title": ["Mean trend of Threshold: L4_05", "RMS trend of Threshold: L4_05", "Active_Chip trend: L4_05"], + "varexp": ["Threshold_ML.mean[29]:ntreeentries", "Threshold_ML.stddev[29]:ntreeentries", "Threshold_ML.entries[29]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_06", "rms_of_Threshold_L4_06", "ActiveChips_of_L4_06"], + "title": ["Mean trend of Threshold: L4_06", "RMS trend of Threshold: L4_06", "Active_Chip trend: L4_06"], + "varexp": ["Threshold_ML.mean[30]:ntreeentries", "Threshold_ML.stddev[30]:ntreeentries", "Threshold_ML.entries[30]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_07", "rms_of_Threshold_L4_07", "ActiveChips_of_L4_07"], + "title": ["Mean trend of Threshold: L4_07", "RMS trend of Threshold: L4_07", "Active_Chip trend: L4_07"], + "varexp": ["Threshold_ML.mean[31]:ntreeentries", "Threshold_ML.stddev[31]:ntreeentries", "Threshold_ML.entries[31]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_08", "rms_of_Threshold_L4_08", "ActiveChips_of_L4_08"], + "title": ["Mean trend of Threshold: L4_08", "RMS trend of Threshold: L4_08", "Active_Chip trend: L4_08"], + "varexp": ["Threshold_ML.mean[32]:ntreeentries", "Threshold_ML.stddev[32]:ntreeentries", "Threshold_ML.entries[32]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_09", "rms_of_Threshold_L4_09", "ActiveChips_of_L4_09"], + "title": ["Mean trend of Threshold: L4_09", "RMS trend of Threshold: L4_09", "Active_Chip trend: L4_09"], + "varexp": ["Threshold_ML.mean[33]:ntreeentries", "Threshold_ML.stddev[33]:ntreeentries", "Threshold_ML.entries[33]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_10", "rms_of_Threshold_L4_10", "ActiveChips_of_L4_10"], + "title": ["Mean trend of Threshold: L4_10", "RMS trend of Threshold: L4_10", "Active_Chip trend: L4_10"], + "varexp": ["Threshold_ML.mean[34]:ntreeentries", "Threshold_ML.stddev[34]:ntreeentries", "Threshold_ML.entries[34]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_11", "rms_of_Threshold_L4_11", "ActiveChips_of_L4_11"], + "title": ["Mean trend of Threshold: L4_11", "RMS trend of Threshold: L4_11", "Active_Chip trend: L4_11"], + "varexp": ["Threshold_ML.mean[35]:ntreeentries", "Threshold_ML.stddev[35]:ntreeentries", "Threshold_ML.entries[35]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_12", "rms_of_Threshold_L4_12", "ActiveChips_of_L4_12"], + "title": ["Mean trend of Threshold: L4_12", "RMS trend of Threshold: L4_12", "Active_Chip trend: L4_12"], + "varexp": ["Threshold_ML.mean[36]:ntreeentries", "Threshold_ML.stddev[36]:ntreeentries", "Threshold_ML.entries[36]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_13", "rms_of_Threshold_L4_13", "ActiveChips_of_L4_13"], + "title": ["Mean trend of Threshold: L4_13", "RMS trend of Threshold: L4_13", "Active_Chip trend: L4_13"], + "varexp": ["Threshold_ML.mean[37]:ntreeentries", "Threshold_ML.stddev[37]:ntreeentries", "Threshold_ML.entries[37]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_14", "rms_of_Threshold_L4_14", "ActiveChips_of_L4_14"], + "title": ["Mean trend of Threshold: L4_14", "RMS trend of Threshold: L4_14", "Active_Chip trend: L4_14"], + "varexp": ["Threshold_ML.mean[38]:ntreeentries", "Threshold_ML.stddev[38]:ntreeentries", "Threshold_ML.entries[38]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_15", "rms_of_Threshold_L4_15", "ActiveChips_of_L4_15"], + "title": ["Mean trend of Threshold: L4_15", "RMS trend of Threshold: L4_15", "Active_Chip trend: L4_15"], + "varexp": ["Threshold_ML.mean[39]:ntreeentries", "Threshold_ML.stddev[39]:ntreeentries", "Threshold_ML.entries[39]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_16", "rms_of_Threshold_L4_16", "ActiveChips_of_L4_16"], + "title": ["Mean trend of Threshold: L4_16", "RMS trend of Threshold: L4_16", "Active_Chip trend: L4_16"], + "varexp": ["Threshold_ML.mean[40]:ntreeentries", "Threshold_ML.stddev[40]:ntreeentries", "Threshold_ML.entries[40]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_17", "rms_of_Threshold_L4_17", "ActiveChips_of_L4_17"], + "title": ["Mean trend of Threshold: L4_17", "RMS trend of Threshold: L4_17", "Active_Chip trend: L4_17"], + "varexp": ["Threshold_ML.mean[41]:ntreeentries", "Threshold_ML.stddev[41]:ntreeentries", "Threshold_ML.entries[41]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_18", "rms_of_Threshold_L4_18", "ActiveChips_of_L4_18"], + "title": ["Mean trend of Threshold: L4_18", "RMS trend of Threshold: L4_18", "Active_Chip trend: L4_18"], + "varexp": ["Threshold_ML.mean[42]:ntreeentries", "Threshold_ML.stddev[42]:ntreeentries", "Threshold_ML.entries[42]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_19", "rms_of_Threshold_L4_19", "ActiveChips_of_L4_19"], + "title": ["Mean trend of Threshold: L4_19", "RMS trend of Threshold: L4_19", "Active_Chip trend: L4_19"], + "varexp": ["Threshold_ML.mean[43]:ntreeentries", "Threshold_ML.stddev[43]:ntreeentries", "Threshold_ML.entries[43]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_20", "rms_of_Threshold_L4_20", "ActiveChips_of_L4_20"], + "title": ["Mean trend of Threshold: L4_20", "RMS trend of Threshold: L4_20", "Active_Chip trend: L4_20"], + "varexp": ["Threshold_ML.mean[44]:ntreeentries", "Threshold_ML.stddev[44]:ntreeentries", "Threshold_ML.entries[44]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_21", "rms_of_Threshold_L4_21", "ActiveChips_of_L4_21"], + "title": ["Mean trend of Threshold: L4_21", "RMS trend of Threshold: L4_21", "Active_Chip trend: L4_21"], + "varexp": ["Threshold_ML.mean[45]:ntreeentries", "Threshold_ML.stddev[45]:ntreeentries", "Threshold_ML.entries[45]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_22", "rms_of_Threshold_L4_22", "ActiveChips_of_L4_22"], + "title": ["Mean trend of Threshold: L4_22", "RMS trend of Threshold: L4_22", "Active_Chip trend: L4_22"], + "varexp": ["Threshold_ML.mean[46]:ntreeentries", "Threshold_ML.stddev[46]:ntreeentries", "Threshold_ML.entries[46]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_23", "rms_of_Threshold_L4_23", "ActiveChips_of_L4_23"], + "title": ["Mean trend of Threshold: L4_23", "RMS trend of Threshold: L4_23", "Active_Chip trend: L4_23"], + "varexp": ["Threshold_ML.mean[47]:ntreeentries", "Threshold_ML.stddev[47]:ntreeentries", "Threshold_ML.entries[47]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_24", "rms_of_Threshold_L4_24", "ActiveChips_of_L4_24"], + "title": ["Mean trend of Threshold: L4_24", "RMS trend of Threshold: L4_24", "Active_Chip trend: L4_24"], + "varexp": ["Threshold_ML.mean[48]:ntreeentries", "Threshold_ML.stddev[48]:ntreeentries", "Threshold_ML.entries[48]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_25", "rms_of_Threshold_L4_25", "ActiveChips_of_L4_25"], + "title": ["Mean trend of Threshold: L4_25", "RMS trend of Threshold: L4_25", "Active_Chip trend: L4_25"], + "varexp": ["Threshold_ML.mean[49]:ntreeentries", "Threshold_ML.stddev[49]:ntreeentries", "Threshold_ML.entries[49]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_26", "rms_of_Threshold_L4_26", "ActiveChips_of_L4_26"], + "title": ["Mean trend of Threshold: L4_26", "RMS trend of Threshold: L4_26", "Active_Chip trend: L4_26"], + "varexp": ["Threshold_ML.mean[50]:ntreeentries", "Threshold_ML.stddev[50]:ntreeentries", "Threshold_ML.entries[50]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_27", "rms_of_Threshold_L4_27", "ActiveChips_of_L4_27"], + "title": ["Mean trend of Threshold: L4_27", "RMS trend of Threshold: L4_27", "Active_Chip trend: L4_27"], + "varexp": ["Threshold_ML.mean[51]:ntreeentries", "Threshold_ML.stddev[51]:ntreeentries", "Threshold_ML.entries[51]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_28", "rms_of_Threshold_L4_28", "ActiveChips_of_L4_28"], + "title": ["Mean trend of Threshold: L4_28", "RMS trend of Threshold: L4_28", "Active_Chip trend: L4_28"], + "varexp": ["Threshold_ML.mean[52]:ntreeentries", "Threshold_ML.stddev[52]:ntreeentries", "Threshold_ML.entries[52]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L4_29", "rms_of_Threshold_L4_29", "ActiveChips_of_L4_29"], + "title": ["Mean trend of Threshold: L4_29", "RMS trend of Threshold: L4_29", "Active_Chip trend: L4_29"], + "varexp": ["Threshold_ML.mean[53]:ntreeentries", "Threshold_ML.stddev[53]:ntreeentries", "Threshold_ML.entries[53]:ntreeentries"], + "selection": "", + "option": "PL" + }, + + + + + + { + "names": ["mean_of_Threshold_L5_00", "rms_of_Threshold_L5_00", "ActiveChips_of_L5_00"], + "title": ["Mean trend of Threshold: L5_00", "RMS trend of Threshold: L5_00", "Active_Chip trend: L5_00"], + "varexp": ["Threshold_OL.mean[0]:ntreeentries", "Threshold_OL.stddev[0]:ntreeentries", "Threshold_OL.entries[0]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_01", "rms_of_Threshold_L5_01", "ActiveChips_of_L5_01"], + "title": ["Mean trend of Threshold: L5_01", "RMS trend of Threshold: L5_01", "Active_Chip trend: L5_01"], + "varexp": ["Threshold_OL.mean[1]:ntreeentries", "Threshold_OL.stddev[1]:ntreeentries", "Threshold_OL.entries[1]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_02", "rms_of_Threshold_L5_02", "ActiveChips_of_L5_02"], + "title": ["Mean trend of Threshold: L5_02", "RMS trend of Threshold: L5_02", "Active_Chip trend: L5_02"], + "varexp": ["Threshold_OL.mean[2]:ntreeentries", "Threshold_OL.stddev[2]:ntreeentries", "Threshold_OL.entries[2]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_03", "rms_of_Threshold_L5_03", "ActiveChips_of_L5_03"], + "title": ["Mean trend of Threshold: L5_03", "RMS trend of Threshold: L5_03", "Active_Chip trend: L5_03"], + "varexp": ["Threshold_OL.mean[3]:ntreeentries", "Threshold_OL.stddev[3]:ntreeentries", "Threshold_OL.entries[3]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_04", "rms_of_Threshold_L5_04", "ActiveChips_of_L5_04"], + "title": ["Mean trend of Threshold: L5_04", "RMS trend of Threshold: L5_04", "Active_Chip trend: L5_04"], + "varexp": ["Threshold_OL.mean[4]:ntreeentries", "Threshold_OL.stddev[4]:ntreeentries", "Threshold_OL.entries[4]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_05", "rms_of_Threshold_L5_05", "ActiveChips_of_L5_05"], + "title": ["Mean trend of Threshold: L5_05", "RMS trend of Threshold: L5_05", "Active_Chip trend: L5_05"], + "varexp": ["Threshold_OL.mean[5]:ntreeentries", "Threshold_OL.stddev[5]:ntreeentries", "Threshold_OL.entries[5]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_06", "rms_of_Threshold_L5_06", "ActiveChips_of_L5_06"], + "title": ["Mean trend of Threshold: L5_06", "RMS trend of Threshold: L5_06", "Active_Chip trend: L5_06"], + "varexp": ["Threshold_OL.mean[6]:ntreeentries", "Threshold_OL.stddev[6]:ntreeentries", "Threshold_OL.entries[6]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_07", "rms_of_Threshold_L5_07", "ActiveChips_of_L5_07"], + "title": ["Mean trend of Threshold: L5_07", "RMS trend of Threshold: L5_07", "Active_Chip trend: L5_07"], + "varexp": ["Threshold_OL.mean[7]:ntreeentries", "Threshold_OL.stddev[7]:ntreeentries", "Threshold_OL.entries[7]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_08", "rms_of_Threshold_L5_08", "ActiveChips_of_L5_08"], + "title": ["Mean trend of Threshold: L5_08", "RMS trend of Threshold: L5_08", "Active_Chip trend: L5_08"], + "varexp": ["Threshold_OL.mean[8]:ntreeentries", "Threshold_OL.stddev[8]:ntreeentries", "Threshold_OL.entries[8]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_09", "rms_of_Threshold_L5_09", "ActiveChips_of_L5_09"], + "title": ["Mean trend of Threshold: L5_09", "RMS trend of Threshold: L5_09", "Active_Chip trend: L5_09"], + "varexp": ["Threshold_OL.mean[9]:ntreeentries", "Threshold_OL.stddev[9]:ntreeentries", "Threshold_OL.entries[9]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_10", "rms_of_Threshold_L5_10", "ActiveChips_of_L5_10"], + "title": ["Mean trend of Threshold: L5_10", "RMS trend of Threshold: L5_10", "Active_Chip trend: L5_10"], + "varexp": ["Threshold_OL.mean[10]:ntreeentries", "Threshold_OL.stddev[10]:ntreeentries", "Threshold_OL.entries[10]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_11", "rms_of_Threshold_L5_11", "ActiveChips_of_L5_11"], + "title": ["Mean trend of Threshold: L5_11", "RMS trend of Threshold: L5_11", "Active_Chip trend: L5_11"], + "varexp": ["Threshold_OL.mean[11]:ntreeentries", "Threshold_OL.stddev[11]:ntreeentries", "Threshold_OL.entries[11]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_12", "rms_of_Threshold_L5_12", "ActiveChips_of_L5_12"], + "title": ["Mean trend of Threshold: L5_12", "RMS trend of Threshold: L5_12", "Active_Chip trend: L5_12"], + "varexp": ["Threshold_OL.mean[12]:ntreeentries", "Threshold_OL.stddev[12]:ntreeentries", "Threshold_OL.entries[12]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_13", "rms_of_Threshold_L5_13", "ActiveChips_of_L5_13"], + "title": ["Mean trend of Threshold: L5_13", "RMS trend of Threshold: L5_13", "Active_Chip trend: L5_13"], + "varexp": ["Threshold_OL.mean[13]:ntreeentries", "Threshold_OL.stddev[13]:ntreeentries", "Threshold_OL.entries[13]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_14", "rms_of_Threshold_L5_14", "ActiveChips_of_L5_14"], + "title": ["Mean trend of Threshold: L5_14", "RMS trend of Threshold: L5_14", "Active_Chip trend: L5_14"], + "varexp": ["Threshold_OL.mean[14]:ntreeentries", "Threshold_OL.stddev[14]:ntreeentries", "Threshold_OL.entries[14]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_15", "rms_of_Threshold_L5_15", "ActiveChips_of_L5_15"], + "title": ["Mean trend of Threshold: L5_15", "RMS trend of Threshold: L5_15", "Active_Chip trend: L5_15"], + "varexp": ["Threshold_OL.mean[15]:ntreeentries", "Threshold_OL.stddev[15]:ntreeentries", "Threshold_OL.entries[15]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_16", "rms_of_Threshold_L5_16", "ActiveChips_of_L5_16"], + "title": ["Mean trend of Threshold: L5_16", "RMS trend of Threshold: L5_16", "Active_Chip trend: L5_16"], + "varexp": ["Threshold_OL.mean[16]:ntreeentries", "Threshold_OL.stddev[16]:ntreeentries", "Threshold_OL.entries[16]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_17", "rms_of_Threshold_L5_17", "ActiveChips_of_L5_17"], + "title": ["Mean trend of Threshold: L5_17", "RMS trend of Threshold: L5_17", "Active_Chip trend: L5_17"], + "varexp": ["Threshold_OL.mean[17]:ntreeentries", "Threshold_OL.stddev[17]:ntreeentries", "Threshold_OL.entries[17]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_18", "rms_of_Threshold_L5_18", "ActiveChips_of_L5_18"], + "title": ["Mean trend of Threshold: L5_18", "RMS trend of Threshold: L5_18", "Active_Chip trend: L5_18"], + "varexp": ["Threshold_OL.mean[18]:ntreeentries", "Threshold_OL.stddev[18]:ntreeentries", "Threshold_OL.entries[18]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_19", "rms_of_Threshold_L5_19", "ActiveChips_of_L5_19"], + "title": ["Mean trend of Threshold: L5_19", "RMS trend of Threshold: L5_19", "Active_Chip trend: L5_19"], + "varexp": ["Threshold_OL.mean[19]:ntreeentries", "Threshold_OL.stddev[19]:ntreeentries", "Threshold_OL.entries[19]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_20", "rms_of_Threshold_L5_20", "ActiveChips_of_L5_20"], + "title": ["Mean trend of Threshold: L5_20", "RMS trend of Threshold: L5_20", "Active_Chip trend: L5_20"], + "varexp": ["Threshold_OL.mean[20]:ntreeentries", "Threshold_OL.stddev[20]:ntreeentries", "Threshold_OL.entries[20]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_21", "rms_of_Threshold_L5_21", "ActiveChips_of_L5_21"], + "title": ["Mean trend of Threshold: L5_21", "RMS trend of Threshold: L5_21", "Active_Chip trend: L5_21"], + "varexp": ["Threshold_OL.mean[21]:ntreeentries", "Threshold_OL.stddev[21]:ntreeentries", "Threshold_OL.entries[21]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_22", "rms_of_Threshold_L5_22", "ActiveChips_of_L5_22"], + "title": ["Mean trend of Threshold: L5_22", "RMS trend of Threshold: L5_22", "Active_Chip trend: L5_22"], + "varexp": ["Threshold_OL.mean[22]:ntreeentries", "Threshold_OL.stddev[22]:ntreeentries", "Threshold_OL.entries[22]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_23", "rms_of_Threshold_L5_23", "ActiveChips_of_L5_23"], + "title": ["Mean trend of Threshold: L5_23", "RMS trend of Threshold: L5_23", "Active_Chip trend: L5_23"], + "varexp": ["Threshold_OL.mean[23]:ntreeentries", "Threshold_OL.stddev[23]:ntreeentries", "Threshold_OL.entries[23]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_24", "rms_of_Threshold_L5_24", "ActiveChips_of_L5_24"], + "title": ["Mean trend of Threshold: L5_24", "RMS trend of Threshold: L5_24", "Active_Chip trend: L5_24"], + "varexp": ["Threshold_OL.mean[24]:ntreeentries", "Threshold_OL.stddev[24]:ntreeentries", "Threshold_OL.entries[24]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_25", "rms_of_Threshold_L5_25", "ActiveChips_of_L5_25"], + "title": ["Mean trend of Threshold: L5_25", "RMS trend of Threshold: L5_25", "Active_Chip trend: L5_25"], + "varexp": ["Threshold_OL.mean[25]:ntreeentries", "Threshold_OL.stddev[25]:ntreeentries", "Threshold_OL.entries[25]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_26", "rms_of_Threshold_L5_26", "ActiveChips_of_L5_26"], + "title": ["Mean trend of Threshold: L5_26", "RMS trend of Threshold: L5_26", "Active_Chip trend: L5_26"], + "varexp": ["Threshold_OL.mean[26]:ntreeentries", "Threshold_OL.stddev[26]:ntreeentries", "Threshold_OL.entries[26]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_27", "rms_of_Threshold_L5_27", "ActiveChips_of_L5_27"], + "title": ["Mean trend of Threshold: L5_27", "RMS trend of Threshold: L5_27", "Active_Chip trend: L5_27"], + "varexp": ["Threshold_OL.mean[27]:ntreeentries", "Threshold_OL.stddev[27]:ntreeentries", "Threshold_OL.entries[27]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_28", "rms_of_Threshold_L5_28", "ActiveChips_of_L5_28"], + "title": ["Mean trend of Threshold: L5_28", "RMS trend of Threshold: L5_28", "Active_Chip trend: L5_28"], + "varexp": ["Threshold_OL.mean[28]:ntreeentries", "Threshold_OL.stddev[28]:ntreeentries", "Threshold_OL.entries[28]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_29", "rms_of_Threshold_L5_29", "ActiveChips_of_L5_29"], + "title": ["Mean trend of Threshold: L5_29", "RMS trend of Threshold: L5_29", "Active_Chip trend: L5_29"], + "varexp": ["Threshold_OL.mean[29]:ntreeentries", "Threshold_OL.stddev[29]:ntreeentries", "Threshold_OL.entries[29]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_30", "rms_of_Threshold_L5_30", "ActiveChips_of_L5_30"], + "title": ["Mean trend of Threshold: L5_30", "RMS trend of Threshold: L5_30", "Active_Chip trend: L5_30"], + "varexp": ["Threshold_OL.mean[30]:ntreeentries", "Threshold_OL.stddev[30]:ntreeentries", "Threshold_OL.entries[30]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_31", "rms_of_Threshold_L5_31", "ActiveChips_of_L5_31"], + "title": ["Mean trend of Threshold: L5_31", "RMS trend of Threshold: L5_31", "Active_Chip trend: L5_31"], + "varexp": ["Threshold_OL.mean[31]:ntreeentries", "Threshold_OL.stddev[31]:ntreeentries", "Threshold_OL.entries[31]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_32", "rms_of_Threshold_L5_32", "ActiveChips_of_L5_32"], + "title": ["Mean trend of Threshold: L5_32", "RMS trend of Threshold: L5_32", "Active_Chip trend: L5_32"], + "varexp": ["Threshold_OL.mean[32]:ntreeentries", "Threshold_OL.stddev[32]:ntreeentries", "Threshold_OL.entries[32]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_33", "rms_of_Threshold_L5_33", "ActiveChips_of_L5_33"], + "title": ["Mean trend of Threshold: L5_33", "RMS trend of Threshold: L5_33", "Active_Chip trend: L5_33"], + "varexp": ["Threshold_OL.mean[33]:ntreeentries", "Threshold_OL.stddev[33]:ntreeentries", "Threshold_OL.entries[33]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_34", "rms_of_Threshold_L5_34", "ActiveChips_of_L5_34"], + "title": ["Mean trend of Threshold: L5_34", "RMS trend of Threshold: L5_34", "Active_Chip trend: L5_34"], + "varexp": ["Threshold_OL.mean[34]:ntreeentries", "Threshold_OL.stddev[34]:ntreeentries", "Threshold_OL.entries[34]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_35", "rms_of_Threshold_L5_35", "ActiveChips_of_L5_35"], + "title": ["Mean trend of Threshold: L5_35", "RMS trend of Threshold: L5_35", "Active_Chip trend: L5_35"], + "varexp": ["Threshold_OL.mean[35]:ntreeentries", "Threshold_OL.stddev[35]:ntreeentries", "Threshold_OL.entries[35]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_36", "rms_of_Threshold_L5_36", "ActiveChips_of_L5_36"], + "title": ["Mean trend of Threshold: L5_36", "RMS trend of Threshold: L5_36", "Active_Chip trend: L5_36"], + "varexp": ["Threshold_OL.mean[36]:ntreeentries", "Threshold_OL.stddev[36]:ntreeentries", "Threshold_OL.entries[36]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_37", "rms_of_Threshold_L5_37", "ActiveChips_of_L5_37"], + "title": ["Mean trend of Threshold: L5_37", "RMS trend of Threshold: L5_37", "Active_Chip trend: L5_37"], + "varexp": ["Threshold_OL.mean[37]:ntreeentries", "Threshold_OL.stddev[37]:ntreeentries", "Threshold_OL.entries[37]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_38", "rms_of_Threshold_L5_38", "ActiveChips_of_L5_38"], + "title": ["Mean trend of Threshold: L5_38", "RMS trend of Threshold: L5_38", "Active_Chip trend: L5_38"], + "varexp": ["Threshold_OL.mean[38]:ntreeentries", "Threshold_OL.stddev[38]:ntreeentries", "Threshold_OL.entries[38]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_39", "rms_of_Threshold_L5_39", "ActiveChips_of_L5_39"], + "title": ["Mean trend of Threshold: L5_39", "RMS trend of Threshold: L5_39", "Active_Chip trend: L5_39"], + "varexp": ["Threshold_OL.mean[39]:ntreeentries", "Threshold_OL.stddev[39]:ntreeentries", "Threshold_OL.entries[39]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_40", "rms_of_Threshold_L5_40", "ActiveChips_of_L5_40"], + "title": ["Mean trend of Threshold: L5_40", "RMS trend of Threshold: L5_40", "Active_Chip trend: L5_40"], + "varexp": ["Threshold_OL.mean[40]:ntreeentries", "Threshold_OL.stddev[40]:ntreeentries", "Threshold_OL.entries[40]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L5_41", "rms_of_Threshold_L5_41", "ActiveChips_of_L5_41"], + "title": ["Mean trend of Threshold: L5_41", "RMS trend of Threshold: L5_41", "Active_Chip trend: L5_41"], + "varexp": ["Threshold_OL.mean[41]:ntreeentries", "Threshold_OL.stddev[41]:ntreeentries", "Threshold_OL.entries[41]:ntreeentries"], + "selection": "", + "option": "PL" + }, + + + + + + + { + "names": ["mean_of_Threshold_L6_00", "rms_of_Threshold_L6_00", "ActiveChips_of_L6_00"], + "title": ["Mean trend of Threshold: L6_00", "RMS trend of Threshold: L6_00", "Active_Chip trend: L6_00"], + "varexp": ["Threshold_OL.mean[42]:ntreeentries", "Threshold_OL.stddev[42]:ntreeentries", "Threshold_OL.entries[42]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_01", "rms_of_Threshold_L6_01", "ActiveChips_of_L6_01"], + "title": ["Mean trend of Threshold: L6_01", "RMS trend of Threshold: L6_01", "Active_Chip trend: L6_01"], + "varexp": ["Threshold_OL.mean[43]:ntreeentries", "Threshold_OL.stddev[43]:ntreeentries", "Threshold_OL.entries[43]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_02", "rms_of_Threshold_L6_02", "ActiveChips_of_L6_02"], + "title": ["Mean trend of Threshold: L6_02", "RMS trend of Threshold: L6_02", "Active_Chip trend: L6_02"], + "varexp": ["Threshold_OL.mean[44]:ntreeentries", "Threshold_OL.stddev[44]:ntreeentries", "Threshold_OL.entries[44]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_03", "rms_of_Threshold_L6_03", "ActiveChips_of_L6_03"], + "title": ["Mean trend of Threshold: L6_03", "RMS trend of Threshold: L6_03", "Active_Chip trend: L6_03"], + "varexp": ["Threshold_OL.mean[45]:ntreeentries", "Threshold_OL.stddev[45]:ntreeentries", "Threshold_OL.entries[45]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_04", "rms_of_Threshold_L6_04", "ActiveChips_of_L6_04"], + "title": ["Mean trend of Threshold: L6_04", "RMS trend of Threshold: L6_04", "Active_Chip trend: L6_04"], + "varexp": ["Threshold_OL.mean[46]:ntreeentries", "Threshold_OL.stddev[46]:ntreeentries", "Threshold_OL.entries[46]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_05", "rms_of_Threshold_L6_05", "ActiveChips_of_L6_05"], + "title": ["Mean trend of Threshold: L6_05", "RMS trend of Threshold: L6_05", "Active_Chip trend: L6_05"], + "varexp": ["Threshold_OL.mean[47]:ntreeentries", "Threshold_OL.stddev[47]:ntreeentries", "Threshold_OL.entries[47]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_06", "rms_of_Threshold_L6_06", "ActiveChips_of_L6_06"], + "title": ["Mean trend of Threshold: L6_06", "RMS trend of Threshold: L6_06", "Active_Chip trend: L6_06"], + "varexp": ["Threshold_OL.mean[48]:ntreeentries", "Threshold_OL.stddev[48]:ntreeentries", "Threshold_OL.entries[48]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_07", "rms_of_Threshold_L6_07", "ActiveChips_of_L6_07"], + "title": ["Mean trend of Threshold: L6_07", "RMS trend of Threshold: L6_07", "Active_Chip trend: L6_07"], + "varexp": ["Threshold_OL.mean[49]:ntreeentries", "Threshold_OL.stddev[49]:ntreeentries", "Threshold_OL.entries[49]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_08", "rms_of_Threshold_L6_08", "ActiveChips_of_L6_08"], + "title": ["Mean trend of Threshold: L6_08", "RMS trend of Threshold: L6_08", "Active_Chip trend: L6_08"], + "varexp": ["Threshold_OL.mean[50]:ntreeentries", "Threshold_OL.stddev[50]:ntreeentries", "Threshold_OL.entries[50]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_09", "rms_of_Threshold_L6_09", "ActiveChips_of_L6_09"], + "title": ["Mean trend of Threshold: L6_09", "RMS trend of Threshold: L6_09", "Active_Chip trend: L6_09"], + "varexp": ["Threshold_OL.mean[51]:ntreeentries", "Threshold_OL.stddev[51]:ntreeentries", "Threshold_OL.entries[51]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_10", "rms_of_Threshold_L6_10", "ActiveChips_of_L6_10"], + "title": ["Mean trend of Threshold: L6_10", "RMS trend of Threshold: L6_10", "Active_Chip trend: L6_10"], + "varexp": ["Threshold_OL.mean[52]:ntreeentries", "Threshold_OL.stddev[52]:ntreeentries", "Threshold_OL.entries[52]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_11", "rms_of_Threshold_L6_11", "ActiveChips_of_L6_11"], + "title": ["Mean trend of Threshold: L6_11", "RMS trend of Threshold: L6_11", "Active_Chip trend: L6_11"], + "varexp": ["Threshold_OL.mean[53]:ntreeentries", "Threshold_OL.stddev[53]:ntreeentries", "Threshold_OL.entries[53]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_12", "rms_of_Threshold_L6_12", "ActiveChips_of_L6_12"], + "title": ["Mean trend of Threshold: L6_12", "RMS trend of Threshold: L6_12", "Active_Chip trend: L6_12"], + "varexp": ["Threshold_OL.mean[54]:ntreeentries", "Threshold_OL.stddev[54]:ntreeentries", "Threshold_OL.entries[54]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_13", "rms_of_Threshold_L6_13", "ActiveChips_of_L6_13"], + "title": ["Mean trend of Threshold: L6_13", "RMS trend of Threshold: L6_13", "Active_Chip trend: L6_13"], + "varexp": ["Threshold_OL.mean[55]:ntreeentries", "Threshold_OL.stddev[55]:ntreeentries", "Threshold_OL.entries[55]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_14", "rms_of_Threshold_L6_14", "ActiveChips_of_L6_14"], + "title": ["Mean trend of Threshold: L6_14", "RMS trend of Threshold: L6_14", "Active_Chip trend: L6_14"], + "varexp": ["Threshold_OL.mean[56]:ntreeentries", "Threshold_OL.stddev[56]:ntreeentries", "Threshold_OL.entries[56]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_15", "rms_of_Threshold_L6_15", "ActiveChips_of_L6_15"], + "title": ["Mean trend of Threshold: L6_15", "RMS trend of Threshold: L6_15", "Active_Chip trend: L6_15"], + "varexp": ["Threshold_OL.mean[57]:ntreeentries", "Threshold_OL.stddev[57]:ntreeentries", "Threshold_OL.entries[57]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_16", "rms_of_Threshold_L6_16", "ActiveChips_of_L6_16"], + "title": ["Mean trend of Threshold: L6_16", "RMS trend of Threshold: L6_16", "Active_Chip trend: L6_16"], + "varexp": ["Threshold_OL.mean[58]:ntreeentries", "Threshold_OL.stddev[58]:ntreeentries", "Threshold_OL.entries[58]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_17", "rms_of_Threshold_L6_17", "ActiveChips_of_L6_17"], + "title": ["Mean trend of Threshold: L6_17", "RMS trend of Threshold: L6_17", "Active_Chip trend: L6_17"], + "varexp": ["Threshold_OL.mean[59]:ntreeentries", "Threshold_OL.stddev[59]:ntreeentries", "Threshold_OL.entries[59]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_18", "rms_of_Threshold_L6_18", "ActiveChips_of_L6_18"], + "title": ["Mean trend of Threshold: L6_18", "RMS trend of Threshold: L6_18", "Active_Chip trend: L6_18"], + "varexp": ["Threshold_OL.mean[60]:ntreeentries", "Threshold_OL.stddev[60]:ntreeentries", "Threshold_OL.entries[60]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_19", "rms_of_Threshold_L6_19", "ActiveChips_of_L6_19"], + "title": ["Mean trend of Threshold: L6_19", "RMS trend of Threshold: L6_19", "Active_Chip trend: L6_19"], + "varexp": ["Threshold_OL.mean[61]:ntreeentries", "Threshold_OL.stddev[61]:ntreeentries", "Threshold_OL.entries[61]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_20", "rms_of_Threshold_L6_20", "ActiveChips_of_L6_20"], + "title": ["Mean trend of Threshold: L6_20", "RMS trend of Threshold: L6_20", "Active_Chip trend: L6_20"], + "varexp": ["Threshold_OL.mean[62]:ntreeentries", "Threshold_OL.stddev[62]:ntreeentries", "Threshold_OL.entries[62]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_21", "rms_of_Threshold_L6_21", "ActiveChips_of_L6_21"], + "title": ["Mean trend of Threshold: L6_21", "RMS trend of Threshold: L6_21", "Active_Chip trend: L6_21"], + "varexp": ["Threshold_OL.mean[63]:ntreeentries", "Threshold_OL.stddev[63]:ntreeentries", "Threshold_OL.entries[63]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_22", "rms_of_Threshold_L6_22", "ActiveChips_of_L6_22"], + "title": ["Mean trend of Threshold: L6_22", "RMS trend of Threshold: L6_22", "Active_Chip trend: L6_22"], + "varexp": ["Threshold_OL.mean[64]:ntreeentries", "Threshold_OL.stddev[64]:ntreeentries", "Threshold_OL.entries[64]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_23", "rms_of_Threshold_L6_23", "ActiveChips_of_L6_23"], + "title": ["Mean trend of Threshold: L6_23", "RMS trend of Threshold: L6_23", "Active_Chip trend: L6_23"], + "varexp": ["Threshold_OL.mean[65]:ntreeentries", "Threshold_OL.stddev[65]:ntreeentries", "Threshold_OL.entries[65]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_24", "rms_of_Threshold_L6_24", "ActiveChips_of_L6_24"], + "title": ["Mean trend of Threshold: L6_24", "RMS trend of Threshold: L6_24", "Active_Chip trend: L6_24"], + "varexp": ["Threshold_OL.mean[66]:ntreeentries", "Threshold_OL.stddev[66]:ntreeentries", "Threshold_OL.entries[66]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_25", "rms_of_Threshold_L6_25", "ActiveChips_of_L6_25"], + "title": ["Mean trend of Threshold: L6_25", "RMS trend of Threshold: L6_25", "Active_Chip trend: L6_25"], + "varexp": ["Threshold_OL.mean[67]:ntreeentries", "Threshold_OL.stddev[67]:ntreeentries", "Threshold_OL.entries[67]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_26", "rms_of_Threshold_L6_26", "ActiveChips_of_L6_26"], + "title": ["Mean trend of Threshold: L6_26", "RMS trend of Threshold: L6_26", "Active_Chip trend: L6_26"], + "varexp": ["Threshold_OL.mean[68]:ntreeentries", "Threshold_OL.stddev[68]:ntreeentries", "Threshold_OL.entries[68]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_27", "rms_of_Threshold_L6_27", "ActiveChips_of_L6_27"], + "title": ["Mean trend of Threshold: L6_27", "RMS trend of Threshold: L6_27", "Active_Chip trend: L6_27"], + "varexp": ["Threshold_OL.mean[69]:ntreeentries", "Threshold_OL.stddev[69]:ntreeentries", "Threshold_OL.entries[69]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_28", "rms_of_Threshold_L6_28", "ActiveChips_of_L6_28"], + "title": ["Mean trend of Threshold: L6_28", "RMS trend of Threshold: L6_28", "Active_Chip trend: L6_28"], + "varexp": ["Threshold_OL.mean[70]:ntreeentries", "Threshold_OL.stddev[70]:ntreeentries", "Threshold_OL.entries[70]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_29", "rms_of_Threshold_L6_29", "ActiveChips_of_L6_29"], + "title": ["Mean trend of Threshold: L6_29", "RMS trend of Threshold: L6_29", "Active_Chip trend: L6_29"], + "varexp": ["Threshold_OL.mean[71]:ntreeentries", "Threshold_OL.stddev[71]:ntreeentries", "Threshold_OL.entries[71]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_30", "rms_of_Threshold_L6_30", "ActiveChips_of_L6_30"], + "title": ["Mean trend of Threshold: L6_30", "RMS trend of Threshold: L6_30", "Active_Chip trend: L6_30"], + "varexp": ["Threshold_OL.mean[72]:ntreeentries", "Threshold_OL.stddev[72]:ntreeentries", "Threshold_OL.entries[72]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_31", "rms_of_Threshold_L6_31", "ActiveChips_of_L6_31"], + "title": ["Mean trend of Threshold: L6_31", "RMS trend of Threshold: L6_31", "Active_Chip trend: L6_31"], + "varexp": ["Threshold_OL.mean[73]:ntreeentries", "Threshold_OL.stddev[73]:ntreeentries", "Threshold_OL.entries[73]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_32", "rms_of_Threshold_L6_32", "ActiveChips_of_L6_32"], + "title": ["Mean trend of Threshold: L6_32", "RMS trend of Threshold: L6_32", "Active_Chip trend: L6_32"], + "varexp": ["Threshold_OL.mean[74]:ntreeentries", "Threshold_OL.stddev[74]:ntreeentries", "Threshold_OL.entries[74]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_33", "rms_of_Threshold_L6_33", "ActiveChips_of_L6_33"], + "title": ["Mean trend of Threshold: L6_33", "RMS trend of Threshold: L6_33", "Active_Chip trend: L6_33"], + "varexp": ["Threshold_OL.mean[75]:ntreeentries", "Threshold_OL.stddev[75]:ntreeentries", "Threshold_OL.entries[75]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_34", "rms_of_Threshold_L6_34", "ActiveChips_of_L6_34"], + "title": ["Mean trend of Threshold: L6_34", "RMS trend of Threshold: L6_34", "Active_Chip trend: L6_34"], + "varexp": ["Threshold_OL.mean[76]:ntreeentries", "Threshold_OL.stddev[76]:ntreeentries", "Threshold_OL.entries[76]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_35", "rms_of_Threshold_L6_35", "ActiveChips_of_L6_35"], + "title": ["Mean trend of Threshold: L6_35", "RMS trend of Threshold: L6_35", "Active_Chip trend: L6_35"], + "varexp": ["Threshold_OL.mean[77]:ntreeentries", "Threshold_OL.stddev[77]:ntreeentries", "Threshold_OL.entries[77]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_36", "rms_of_Threshold_L6_36", "ActiveChips_of_L6_36"], + "title": ["Mean trend of Threshold: L6_36", "RMS trend of Threshold: L6_36", "Active_Chip trend: L6_36"], + "varexp": ["Threshold_OL.mean[78]:ntreeentries", "Threshold_OL.stddev[78]:ntreeentries", "Threshold_OL.entries[78]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_37", "rms_of_Threshold_L6_37", "ActiveChips_of_L6_37"], + "title": ["Mean trend of Threshold: L6_37", "RMS trend of Threshold: L6_37", "Active_Chip trend: L6_37"], + "varexp": ["Threshold_OL.mean[79]:ntreeentries", "Threshold_OL.stddev[79]:ntreeentries", "Threshold_OL.entries[79]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_38", "rms_of_Threshold_L6_38", "ActiveChips_of_L6_38"], + "title": ["Mean trend of Threshold: L6_38", "RMS trend of Threshold: L6_38", "Active_Chip trend: L6_38"], + "varexp": ["Threshold_OL.mean[80]:ntreeentries", "Threshold_OL.stddev[80]:ntreeentries", "Threshold_OL.entries[80]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_39", "rms_of_Threshold_L6_39", "ActiveChips_of_L6_39"], + "title": ["Mean trend of Threshold: L6_39", "RMS trend of Threshold: L6_39", "Active_Chip trend: L6_39"], + "varexp": ["Threshold_OL.mean[81]:ntreeentries", "Threshold_OL.stddev[81]:ntreeentries", "Threshold_OL.entries[81]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_40", "rms_of_Threshold_L6_40", "ActiveChips_of_L6_40"], + "title": ["Mean trend of Threshold: L6_40", "RMS trend of Threshold: L6_40", "Active_Chip trend: L6_40"], + "varexp": ["Threshold_OL.mean[82]:ntreeentries", "Threshold_OL.stddev[82]:ntreeentries", "Threshold_OL.entries[82]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_41", "rms_of_Threshold_L6_41", "ActiveChips_of_L6_41"], + "title": ["Mean trend of Threshold: L6_41", "RMS trend of Threshold: L6_41", "Active_Chip trend: L6_41"], + "varexp": ["Threshold_OL.mean[83]:ntreeentries", "Threshold_OL.stddev[83]:ntreeentries", "Threshold_OL.entries[83]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_42", "rms_of_Threshold_L6_42", "ActiveChips_of_L6_42"], + "title": ["Mean trend of Threshold: L6_42", "RMS trend of Threshold: L6_42", "Active_Chip trend: L6_42"], + "varexp": ["Threshold_OL.mean[84]:ntreeentries", "Threshold_OL.stddev[84]:ntreeentries", "Threshold_OL.entries[84]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_43", "rms_of_Threshold_L6_43", "ActiveChips_of_L6_43"], + "title": ["Mean trend of Threshold: L6_43", "RMS trend of Threshold: L6_43", "Active_Chip trend: L6_43"], + "varexp": ["Threshold_OL.mean[85]:ntreeentries", "Threshold_OL.stddev[85]:ntreeentries", "Threshold_OL.entries[85]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_44", "rms_of_Threshold_L6_44", "ActiveChips_of_L6_44"], + "title": ["Mean trend of Threshold: L6_44", "RMS trend of Threshold: L6_44", "Active_Chip trend: L6_44"], + "varexp": ["Threshold_OL.mean[86]:ntreeentries", "Threshold_OL.stddev[86]:ntreeentries", "Threshold_OL.entries[86]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_45", "rms_of_Threshold_L6_45", "ActiveChips_of_L6_45"], + "title": ["Mean trend of Threshold: L6_45", "RMS trend of Threshold: L6_45", "Active_Chip trend: L6_45"], + "varexp": ["Threshold_OL.mean[87]:ntreeentries", "Threshold_OL.stddev[87]:ntreeentries", "Threshold_OL.entries[87]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_46", "rms_of_Threshold_L6_46", "ActiveChips_of_L6_46"], + "title": ["Mean trend of Threshold: L6_46", "RMS trend of Threshold: L6_46", "Active_Chip trend: L6_46"], + "varexp": ["Threshold_OL.mean[88]:ntreeentries", "Threshold_OL.stddev[88]:ntreeentries", "Threshold_OL.entries[88]:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["mean_of_Threshold_L6_47", "rms_of_Threshold_L6_47", "ActiveChips_of_L6_47"], + "title": ["Mean trend of Threshold: L6_47", "RMS trend of Threshold: L6_47", "Active_Chip trend: L6_47"], + "varexp": ["Threshold_OL.mean[89]:ntreeentries", "Threshold_OL.stddev[89]:ntreeentries", "Threshold_OL.entries[89]:ntreeentries"], + "selection": "", + "option": "PL" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "foreachlatest:qcdb:ITS/MO/ITSThresholdCalibrationTask/THRChipAverageOL" + ], + "stopTrigger": [ + "userorcontrol", "10 minutes" + ] + } + } + } +} diff --git a/Modules/ITS/itsQCTrendingTracks.json b/Modules/ITS/itsQCTrendingTracks.json new file mode 100644 index 0000000000..8f2b3b6dc7 --- /dev/null +++ b/Modules/ITS/itsQCTrendingTracks.json @@ -0,0 +1,135 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "ITSqcTracks": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTaskITSTracks", + "moduleName": "QualityControl", + "detectorName": "ITS", + "dataSources": [ + { + "type": "repository", + "paths": ["ITS/MO/ITSTrackTask/NClustersReset", + "ITS/MO/ITSTrackTask/EtaDistribution", + "ITS/MO/ITSTrackTask/PhiDistribution", + "ITS/MO/ITSTrackTask/VertexZ", + "ITS/MO/ITSTrackTask/NVertexContributors", + "ITS/MO/ITSTrackTask/AssociatedClusterFraction", + "ITS/MO/ITSTrackTask/NtracksReset" + ], + "names": ["NClusters", + "EtaDistribution", + "PhiDistribution", + "VertexZ", + "NVertexContributors", + "AssociatedClusterFraction", + "Ntracks" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcITS" + }, + { + "type": "repository", + "paths": [ + "ITS/MO/ITSTrackTask/VertexCoordinates" + ], + "names": [ + "VertexXY" + ], + "reductorName": "o2::quality_control_modules::common::TH2Reductor", + "moduleName": "QcITS" + } + ], + "plots": [ + { + "names": ["NClusters_mean", "NClusters_stddev"], + "title": ["Average NClusters per Cycle", "Stddev NClusters per Cycle"], + "varexp": ["NClusters.mean:ntreeentries", "NClusters.stddev:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["EtaDistribution_mean", "EtaDistribution_stddev"], + "title": ["Average EtaDistribution", "Stddev EtaDistribution"], + "varexp": ["EtaDistribution.mean:ntreeentries", "EtaDistribution.stddev:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["PhiDistribution_mean", "PhiDistribution_stddev"], + "title": ["Average PhiDistribution", "Stddev PhiDistribution"], + "varexp": ["PhiDistribution.mean:ntreeentries", "PhiDistribution.stddev:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["VertexZ_mean", "VertexZ_stddev"], + "title": ["Average VertexZ", "Stddev VertexZ"], + "varexp": ["VertexZ.mean:ntreeentries", "VertexZ.stddev:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["NVertexContributors_mean", "NVertexContributors_stddev"], + "title": ["Average NVertexContributors", "Stddev NVertexContributors"], + "varexp": ["NVertexContributors.mean:ntreeentries", "NVertexContributors.stddev:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["AssociatedClusterFraction_mean", "AssociatedClusterFraction_stddev"], + "title": ["Average fraction of clusters per track", "Stddev of fraction of clusters per track"], + "varexp": ["AssociatedClusterFraction.mean:ntreeentries", "AssociatedClusterFraction.stddev:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["Ntracks_mean", "Ntracks_stddev"], + "title": ["Average NTracks per event per cycle", "Stddev of NTracks per event per cycle"], + "varexp": ["Ntracks.mean:ntreeentries", "Ntracks.stddev:ntreeentries"], + "selection": "", + "option": "PL" + }, + { + "names": ["VertexX_mean", "VertexX_stddev", "VertexY_mean", "VertexY_stddev"], + "title": ["Average VertexX", "Stddev VertexX", "Average VertexY", "Stddev VertexY"], + "varexp": ["VertexXY.sumwx/VertexXY.sumw:ntreeentries", "TMath::Sqrt((VertexXY.sumwx2/VertexXY.sumw)-(VertexXY.sumwx/VertexXY.sumw)*(VertexXY.sumwx/VertexXY.sumw)):ntreeentries", "VertexXY.sumwy/VertexXY.sumw:ntreeentries", "TMath::Sqrt((VertexXY.sumwy2/VertexXY.sumw)-(VertexXY.sumwy/VertexXY.sumw)*(VertexXY.sumwy/VertexXY.sumw)):ntreeentries"], + "selection": "", + "option": "PL" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:ITS/MO/ITSTrackTask/AngularDistribution" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/ITS/itsThresholdCalibration.json b/Modules/ITS/itsThresholdCalibration.json new file mode 100644 index 0000000000..1209268623 --- /dev/null +++ b/Modules/ITS/itsThresholdCalibration.json @@ -0,0 +1,50 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + } + }, + "tasks" : { + "ITSThresholdCalibrationTask" : { + "active" : "true", + "className" : "o2::quality_control_modules::its::ITSThresholdCalibrationTask", + "moduleName" : "QcITS", + "detectorName" : "ITS", + "cycleDurationSeconds" : "60", + "maxNumberCycles" : "-1", + "dataSource_comment" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "direct", + "query" : "tunestring:ITS/TSTR;runtype:ITS/RUNT;fittype:ITS/FITT;scantype:ITS/SCANT;chipdonestring:ITS/QCSTR;confdbv:ITS/CONFDBV;PixTypString:ITS/PIXTYP" + }, + "location" : "remote", + "taskParameters" : { + "CalibrationType": "THR" + } + } + } + + + + }, + + "dataSamplingPolicies" : [] +} diff --git a/Modules/ITS/itsTrack.json b/Modules/ITS/itsTrack.json new file mode 100644 index 0000000000..969ab6b4eb --- /dev/null +++ b/Modules/ITS/itsTrack.json @@ -0,0 +1,106 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://alice-ccdb.cern.ch" + } + }, + "tasks" : { + "ITSTrackTask" : { + "active" : "true", + "className" : "o2::quality_control_modules::its::ITSTrackTask", + "moduleName" : "QcITS", + "detectorName" : "ITS", + "cycleDurationSeconds" : "30", + "maxNumberCycles" : "-1", + "dataSource_comment" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "tracks" + }, + "location" : "remote", + "taskParameters" : { + "vertexXYsize" : "0.5", + "vertexZsize": "15", + "vertexRsize": "0.8", + "NtracksMAX" : "100", + "doTTree": "0", + "nBCbins": "103", + "dicttimestamp" : "0", + "doNorm" : "1", + "InvMasses" : "0", + "doAlignmentMonitor" : "1", + "UseDefaultMomResPar" : "0", + "MomResParMEAS1": "45, 45, 45, 55, 55, 55, 55", + "MomResParMEAS2": "40, 40, 40, 40, 40, 40, 40", + "MomResParMSC1": "30, 30, 30, 110, 110, 110, 110", + "MomResParMSC2": "25, 25, 25, 75, 75, 75, 75", + "ResidualMonitorNclMin" : "5", + "ResidualMonitorTrackMinPt" : "0.00" + } + } + }, + "checks" : { + "ITSTrackCheck" : { + "active" : "true", + "className" : "o2::quality_control_modules::its::ITSTrackCheck", + "moduleName" : "QcITS", + "policy" : "OnEachSeparately", + "detectorName" : "ITS", + "checkParameters": { + "plotWithTextMessage": "", + "textMessage": "", + "EtaRatio": "0.3", + "PhiRatio": "0.3" + }, + "dataSource" : [ { + "type" : "Task", + "name" : "ITSTrackTask", + "MOs" : ["NClusters", + "PhiDistribution", + "AngularDistribution", + "EtaDistribution", + "VertexCoordinates", + "VertexRvsZ", + "VertexZ" + ] + } ] + } + } + }, + + "dataSamplingPolicies" : [ + { + "id" : "tracks", + "active" : "true", + "machines" : [], + "query" : "Verticesrof:ITS/VERTICESROF/0;Vertices:ITS/VERTICES/0;tracks:ITS/TRACKS/0;rofs:ITS/ITSTrackROF/0;clustersrof:ITS/CLUSTERSROF/0;compclus:ITS/COMPCLUSTERS/0;patterns:ITS/PATTERNS/0;clusteridx:ITS/TRACKCLSID/0", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "1", + "seed" : "1441" + } + ], + + "blocking" : "false" + } + ] +} diff --git a/Modules/ITS/itsTrackSim.json b/Modules/ITS/itsTrackSim.json new file mode 100644 index 0000000000..aa955e491e --- /dev/null +++ b/Modules/ITS/itsTrackSim.json @@ -0,0 +1,65 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + } + }, + "tasks" : { + "ITSTrackSimTask" : { + "active" : "true", + "className" : "o2::quality_control_modules::its::ITSTrackSimTask", + "moduleName" : "QcITS", + "detectorName" : "ITS", + "cycleDurationSeconds" : "30", + "maxNumberCycles" : "-1", + "dataSource_comment" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "tracksim" + }, + "location" : "remote", + "taskParameters" : { + "O2GrpPath" : "./o2sim_grp.root", + "collisionsContextPath": "./collisioncontext.root" + } + + } + } + }, + + "dataSamplingPolicies" : [ + { + "id" : "tracksim", + "active" : "true", + "machines" : [], + "query" : "tracks:ITS/TRACKS/0;mstruth:ITS/TRACKSMCTR/0;compclus:ITS/COMPCLUSTERS/0;mcclustruth:ITS/CLUSTERSMCTR/0", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "1", + "seed" : "1441" + } + ], + + "blocking" : "false" + } + ] +} diff --git a/Modules/ITS/o2-qc-module-configurator.sh b/Modules/ITS/o2-qc-module-configurator.sh new file mode 100755 index 0000000000..1abd3006b0 --- /dev/null +++ b/Modules/ITS/o2-qc-module-configurator.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +set -e # exit on error +set -u # exit when using undeclared variable +#set -x ;# debugging + +DONOR=Skeleton +DONOR_LC=skeleton + +OS=`uname` + +function inplace_sed() { + sed -ibck "$1" $2 && rm $2bck +} + +# Checks if current pwd matches the location of this script, exits if it is not +function check_pwd() { + DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + if [[ $DIR != $PWD ]]; then + echo 'Please execute this script while being in its directory' + exit 1 + fi +} + +# Create new empty module +# \param 1 : module_name +function create_module() { + if [ -d $1 ]; then + echo 'Module '$1' already exists.' + else + echo 'Module '$1' does not exist, generating...' + if [ ! -d ${DONOR} ]; then + echo '> Donor template '${DONOR}' does not exist, exiting...' + exit 1 + fi + + MODULE_LC=$(echo $1 | tr A-Z a-z) + # prepare folder structure + mkdir $1 $1/src $1/include/ $1/include/$1 $1/test + + # prepare CMakeLists.txt + sed 's/'${DONOR}'/'$1'/' ${DONOR}/.CMakeListsEmpty.txt >$1/CMakeLists.txt + # prepare LinkDef.h + sed '/#pragma link C++ class o2::quality_control_modules::'${DONOR_LC}'::/ d' ${DONOR}/include/${DONOR}/LinkDef.h >$1/include/$1/LinkDef.h + # prepare test + sed 's/.testEmpty/test'$1'/; s/'${DONOR_LC}'/'${MODULE_LC}'/' ${DONOR}/test/.testEmpty.cxx >$1'/test/testQc'$1'.cxx' + + inplace_sed '/testQcSkeleton/s/Skeleton/'$1'/g' $1/CMakeLists.txt + + # add new module to the project + if ! grep -c $1 CMakeLists.txt; then + echo 'add_subdirectory('$1')' >>CMakeLists.txt + fi + + echo '> Module created.' + fi +} + +function cmake_format() { + if command cmake-format >/dev/null 2>&1; then + cmake-format -i $1 -c ../.cmake-format.py + fi +} + +# Return the name of the include guard +function include_guard() { + local modulename=$1 + local classname=$2 + echo QC_MODULE_$(echo $modulename | tr a-z A-Z)_$(echo $modulename$classname | tr a-z A-Z)_H +} + +# Return the name of the include file +function include_file() { + local modulename=$1 + local classname=$2 + echo $modulename'/include/'$modulename'/'$classname'.h' +} + +# Create new Task or Check in specified module +function create_class() { + + local modulename=$1 + local classname=$2 + local typename=$3 + + if [ "$typename" != "Task" ] && [ "$typename" != "Check" ] && [ "$typename" != "PostProcessing" ] && [ "$typename" != "Aggregator" ]; then + echo "3rd parameter can only be Task, Check, Aggregator or PostProcessing" + return + fi + + INCLUDE_FILENAME=$(include_file $modulename $classname) + + echo 'Creating '$typename' '$classname' in module '$modulename'.' + if [ -f $INCLUDE_FILENAME ]; then + echo '> '$typename' '$classname' already exists, returning...' + return + fi + + MODULE_LC=$(echo $modulename | tr A-Z a-z) + DONOR_INCLUDE_GUARD=$(include_guard $DONOR $typename) + INCLUDE_GUARD=$(include_guard $modulename $classname) + DONOR_INCLUDE_FILENAME=$(include_file $DONOR ${DONOR}${typename}) + + # add header + sed ' + s/'${DONOR}${typename}'/'${classname}'/g; + s/'${DONOR_LC}'/'${MODULE_LC}'/g; + s/'${DONOR}'/'${MODULE}'/g; + s/'${DONOR_INCLUDE_GUARD}'/'${INCLUDE_GUARD}'/g; + ' $DONOR_INCLUDE_FILENAME >$INCLUDE_FILENAME + + # add LinkDef.h + if [[ $OS == Linux ]] ; then + sed -i '/#endif/ i #pragma link C++ class o2::quality_control_modules::'${MODULE_LC}'::'$classname'+;' \ + $modulename/include/$modulename/LinkDef.h + sed -i '/HEADERS/ a \ \ include/'$modulename'/'$classname'.h' $modulename/CMakeLists.txt + else #Darwin/BSD + inplace_sed '/#endif/ i\ + #pragma link C++ class o2::quality_control_modules::'${MODULE_LC}'::'$classname'+;\ + \ + ' $modulename/include/$modulename/LinkDef.h + inplace_sed '/LINKDEF include/ i\ + include/'$modulename'/'$classname'.h\ + ' $modulename/CMakeLists.txt + fi + + # add src + sed ' + s/'${DONOR}${typename}'/'${classname}'/g; + s/'${DONOR_LC}'/'${MODULE_LC}'/g; + s/'${DONOR}'/'${MODULE}'/g; + ' ${DONOR}'/src/'${DONOR}${typename}'.cxx' >$modulename'/src/'$classname'.cxx' + + # the sources are on the same line as the PRIVATE + inplace_sed '/target_sources(O2Qc'$modulename' PRIVATE/ s_PRIVATE_PRIVATE src/'$classname'.cxx _' $modulename/CMakeLists.txt + + # the PRIVATE is on its own line (because there are more sources than + # what fits on a single line) + inplace_sed '/target_sources(O2Qc'$modulename'$/ { + N + /PRIVATE/ { + s_PRIVATE_PRIVATE src/'$classname'.cxx _ + } + }' $modulename/CMakeLists.txt + + echo '> '$typename' created.' + + cmake_format $modulename/CMakeLists.txt +} + +function print_usage() { + echo "Usage: ./o2-qc-module-configurator.sh -m MODULE_NAME [OPTION] + +Generate template QC module and/or tasks, checks, aggregators and postprocessing. +If a module with specified name already exists, new tasks, checks, aggregators and postprocessing are inserted to the existing module. +Please follow UpperCamelCase convention for modules', tasks' and checks' names. + +Example: +# create new module and some task +./o2-qc-module-configurator.sh -m MyModule -t SuperTask +# add one task and two checks +./o2-qc-module-configurator.sh -m MyModule -t EvenBetterTask -c HistoUniformityCheck -c MeanTest + +Options: + -h print this message + -m MODULE_NAME create a module named MODULE_NAME or add there some task/checker + -t TASK_NAME create a task named TASK_NAME + -c CHECK_NAME create a check named CHECK_NAME + -p PP_NAME create a postprocessing task named PP_NAME + -a AGG_NAME create an aggregator named AGG_NAME +" +} + +MODULE= +while getopts 'hm:t:c:p:a:' option; do + case "${option}" in + \?) + print_usage + exit 1 + ;; + h) + print_usage + exit 0 + ;; + m) + check_pwd + create_module ${OPTARG} + MODULE=${OPTARG} + ;; + t) + if [ -z ${MODULE} ]; then + echo 'Cannot add a task, module name not specified, exiting...' + exit 1 + fi + create_class ${MODULE} ${OPTARG} Task + ;; + c) + if [ -z ${MODULE} ]; then + echo 'Cannot add a check, module name not specified, exiting...' + exit 1 + fi + create_class ${MODULE} ${OPTARG} Check + ;; + p) + if [ -z ${MODULE} ]; then + echo 'Cannot add a postprocessing task, module name not specified, exiting...' + exit 1 + fi + create_class ${MODULE} ${OPTARG} PostProcessing + ;; + a) + if [ -z ${MODULE} ]; then + echo 'Cannot add an aggregator, module name not specified, exiting...' + exit 1 + fi + create_class ${MODULE} ${OPTARG} Aggregator + ;; + esac +done + +# If no options are specified +if [ ${OPTIND} -eq 1 ]; then + print_usage +fi diff --git a/Modules/ITS/src/ITSChipStatusCheck.cxx b/Modules/ITS/src/ITSChipStatusCheck.cxx new file mode 100644 index 0000000000..869e4d733b --- /dev/null +++ b/Modules/ITS/src/ITSChipStatusCheck.cxx @@ -0,0 +1,177 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSChipStatusCheck.cxx +/// \author Zhen Zhang +/// + +#include "ITS/ITSChipStatusCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "ITSMFTReconstruction/DecodingStat.h" +#include + +#include +#include "Common/Utils.h" + +namespace o2::quality_control_modules::its +{ + +Quality ITSChipStatusCheck::check(std::map>* moMap) +{ + + // limits to be used as "X,Y" --> BAD if at least X FFEIDs have at least Y chips each into error + std::vector feeidlimitsIB = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "feeidlimitsIB", "")); + std::vector feeidlimitsML = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "feeidlimitsML", "")); + std::vector feeidlimitsOL = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "feeidlimitsOL", "")); + std::vector excludedfeeid = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "excludedfeeid", "")); + + if (feeidlimitsIB.size() != 2) { + ILOG(Error, Support) << "Incorrect setting for feeidlimitsIB, check .json. Using default 1,1" << ENDM; + feeidlimitsIB.clear(); + feeidlimitsIB = std::vector{ 1, 1. }; + } + if (feeidlimitsML.size() != 2) { + ILOG(Error, Support) << "Incorrect setting for feeidlimitsML, check .json. Using default 1,1" << ENDM; + feeidlimitsML.clear(); + feeidlimitsML = std::vector{ 1, 1. }; + } + if (feeidlimitsOL.size() != 2) { + ILOG(Error, Support) << "Incorrect setting for feeidlimitsOL, check .json. Using default 1,1" << ENDM; + feeidlimitsOL.clear(); + feeidlimitsOL = std::vector{ 1, 1. }; + } + + Quality result = Quality::Null; + for (auto& [moName, mo] : *moMap) { + + if (mo->getName() == "StaveStatusOverview") { + result = Quality::Good; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast StaveStatusOverview plot from ChipError to TH2Poly" << ENDM; + continue; + } + for (int ilayer = 0; ilayer < NLayer; ilayer++) { + for (int ibin = StaveBoundary[ilayer] + 1; ibin <= StaveBoundary[ilayer + 1]; ++ibin) { + if (abs(h->GetBinContent(ibin) - 1) < 0.01) { + result = Quality::Bad; + TString text = Form("BAD: At least one stave is without data"); + vBadStaves.push_back(Form("L%d_%d", ilayer, ibin - StaveBoundary[ilayer] - 1)); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), text.Data()); + } + } + } + } + + if (mo->getName() == "FEEIDOverview") { + result = Quality::Good; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast FEEIDoverview plot from ChipStatus to TH1D" << ENDM; + continue; + } + + int nBadIB = 0; + int nBadML = 0; + int nBadOL = 0; + for (int ifee = 0; ifee < h->GetNbinsX(); ifee++) { + + if (excludedfeeid.size() > 0 && std::find(excludedfeeid.begin(), excludedfeeid.end(), ifee) != excludedfeeid.end()) { + continue; + } + + if (ifee >= FeeIDBoundaryVsBarrel[0] && ifee < FeeIDBoundaryVsBarrel[1] && h->GetBinContent(ifee + 1) >= feeidlimitsIB[1]) { + nBadIB++; + } + if (ifee >= FeeIDBoundaryVsBarrel[1] && ifee < FeeIDBoundaryVsBarrel[2] && h->GetBinContent(ifee + 1) >= feeidlimitsML[1]) { + nBadML++; + } + if (ifee >= FeeIDBoundaryVsBarrel[2] && ifee < FeeIDBoundaryVsBarrel[3] && h->GetBinContent(ifee + 1) >= feeidlimitsOL[1]) { + nBadOL++; + } + } + + if (nBadIB >= feeidlimitsIB[0] || nBadML >= feeidlimitsML[0] || nBadOL >= feeidlimitsOL[0]) { + result = Quality::Bad; + TString text = "At least one FEEid with large number of missing chips"; + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), text.Data()); + } + } + } + return result; +} + +void ITSChipStatusCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + TString status; + int textColor; + + if ((std::string)mo->GetName() == "FEEIDOverview") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast FEEIDOverview to TH1D*" << ENDM; + return; + } + + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else if (checkResult == Quality::Bad) { + status = "Quality::BAD (call expert)"; + textColor = kRed + 2; + } + + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.06); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + } + + if ((std::string)mo->getName() == "StaveStatusOverview") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast StaveStatusOverview to TH2Poly*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else { + + if (checkResult == Quality::Bad) { + status = "Quality::BAD (call expert)"; + for (int id = 0; id < vBadStaves.size(); id++) { + tInfo = std::make_shared(0.12, 0.835 - 0.04 * (id + 1), Form("BAD: stave %s is empty", vBadStaves[id].Data())); + tInfo->SetTextColor(kRed + 2); + tInfo->SetTextSize(0.04); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + } + textColor = kRed + 2; + } + } + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.06); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + } + vBadStaves.clear(); +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSChipStatusTask.cxx b/Modules/ITS/src/ITSChipStatusTask.cxx new file mode 100644 index 0000000000..048e53a34c --- /dev/null +++ b/Modules/ITS/src/ITSChipStatusTask.cxx @@ -0,0 +1,295 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSChipStatusTask.cxx +/// \author My Name +/// + +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "ITS/ITSChipStatusTask.h" +#include +#include +#include "TLine.h" +#include "TLatex.h" + +namespace o2::quality_control_modules::its +{ +ITSChipStatusTask::ITSChipStatusTask() + : TaskInterface() +{ +} + +ITSChipStatusTask::~ITSChipStatusTask() +{ + for (int i = 0; i < 3; i++) { + delete ChipsStack[i]; + } +} + +void ITSChipStatusTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initializing the ITSChipStatusTask" << ENDM; + getParameters(); + + for (int i = 0; i < 3; i++) { + ChipsStack[i] = new Stack(nQCCycleToMonitor, ChipsBoundaryBarrels[i + 1] - ChipsBoundaryBarrels[i], nRotationType); + TFsStack[i] = new Stack(nQCCycleToMonitor, ChipsBoundaryBarrels[i + 1] - ChipsBoundaryBarrels[i], nRotationType); + + DeadChips[i] = std::make_shared(Form("DeadChips%s", BarrelNames[i].Data()), Form("Time fraction without data from %s chips", BarrelNames[i].Data()), nQCCycleToMonitor, 0, nQCCycleToMonitor, ChipsBoundaryBarrels[i + 1] - ChipsBoundaryBarrels[i], 0, ChipsBoundaryBarrels[i + 1] - ChipsBoundaryBarrels[i], false); + setAxisTitle(DeadChips[i].get(), Form("Last %d QC Cycles", nQCCycleToMonitor), "Chip ID"); + + getObjectsManager()->startPublishing(DeadChips[i].get()); + } + + StaveOverview = new TH2Poly(); + StaveOverview->SetName("StaveStatusOverview"); + TString title = "QC cycles when stave is without data"; + title += ";mm (IB 3x);mm (IB 3x)"; + StaveOverview->SetTitle(title); + StaveOverview->SetStats(0); + StaveOverview->SetOption("lcolz"); + StaveOverview->SetMinimum(0); + StaveOverview->SetMaximum(1); + for (int ilayer = 0; ilayer < 7; ilayer++) { + for (int istave = 0; istave < NStaves[ilayer]; istave++) { + double* px = new double[4]; + double* py = new double[4]; + getStavePoint(ilayer, istave, px, py); + if (ilayer < 3) { + for (int icoo = 0; icoo < 4; icoo++) { + px[icoo] *= 3.; + py[icoo] *= 3.; + } + } + StaveOverview->AddBin(4, px, py); + } + } + getObjectsManager()->startPublishing(StaveOverview); + + FeeIDOverview = new TH1D("FEEIDOverview", Form("Fraction of missing chips in the last %d cycles;QC FEEid;fraction of missing chips", NCycleForOverview), NFees, 0, NFees); + getObjectsManager()->startPublishing(FeeIDOverview); +} + +void ITSChipStatusTask::setAxisTitle(TH1* object, const char* xTitle, const char* yTitle) +{ + object->GetXaxis()->SetTitle(xTitle); + object->GetYaxis()->SetTitle(yTitle); +} + +void ITSChipStatusTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity : " << activity.mId << ENDM; +} + +void ITSChipStatusTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + + for (int i = 0; i < 3; i++) { + CurrentDeadChips[i].clear(); + CurrentDeadChips[i].resize(ChipsBoundaryBarrels[i + 1] - ChipsBoundaryBarrels[i], 0); + CurrentTFs[i].clear(); + CurrentTFs[i].resize(ChipsBoundaryBarrels[i + 1] - ChipsBoundaryBarrels[i], 0); + } +} + +void ITSChipStatusTask::getStavePoint(int layer, int stave, double* px, double* py) +{ + float stepAngle = TMath::Pi() * 2 / NStaves[layer]; // the angle between to stave + float midAngle = StartAngle[layer] + (stave * stepAngle); // mid point angle + float staveRotateAngle = TMath::Pi() / 2 - (stave * stepAngle); // how many angle this stave rotate(compare with first stave) + px[1] = MidPointRad[layer] * TMath::Cos(midAngle); // there are 4 point to decide this TH2Poly bin + // 0:left point in this stave; + // 1:mid point in this stave; + // 2:right point in this stave; + // 3:higher point int this stave; + py[1] = MidPointRad[layer] * TMath::Sin(midAngle); // 4 point calculated accord the blueprint + // roughly calculate + if (layer < NLayerIB) { + px[0] = 7.7 * TMath::Cos(staveRotateAngle) + px[1]; + py[0] = -7.7 * TMath::Sin(staveRotateAngle) + py[1]; + px[2] = -7.7 * TMath::Cos(staveRotateAngle) + px[1]; + py[2] = 7.7 * TMath::Sin(staveRotateAngle) + py[1]; + px[3] = 5.623 * TMath::Sin(staveRotateAngle) + px[1]; + py[3] = 5.623 * TMath::Cos(staveRotateAngle) + py[1]; + } else { + px[0] = 21 * TMath::Cos(staveRotateAngle) + px[1]; + py[0] = -21 * TMath::Sin(staveRotateAngle) + py[1]; + px[2] = -21 * TMath::Cos(staveRotateAngle) + px[1]; + py[2] = 21 * TMath::Sin(staveRotateAngle) + py[1]; + px[3] = 40 * TMath::Sin(staveRotateAngle) + px[1]; + py[3] = 40 * TMath::Cos(staveRotateAngle) + py[1]; + } +} + +int ITSChipStatusTask::getFEEID(int barrel, int chipinbarrel) +{ + if (barrel == 0) { + return chipinbarrel / nChipsPerFeeID[0]; + } + if (barrel == 1) { + return ChipsBoundaryBarrels[1] / nChipsPerFeeID[0] + chipinbarrel / nChipsPerFeeID[1]; + } + if (barrel == 2) { + return (ChipsBoundaryBarrels[1] / nChipsPerFeeID[0]) + + ((ChipsBoundaryBarrels[2] - ChipsBoundaryBarrels[1]) / nChipsPerFeeID[1]) + + chipinbarrel / nChipsPerFeeID[2]; + } + return -1; +} + +void ITSChipStatusTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto aliveChips = ctx.inputs().get>("chipstatus"); + + int id = 0; + for (auto chipID : aliveChips) { + int iBarrel = id < ChipsBoundaryBarrels[1] ? 0 : id < ChipsBoundaryBarrels[2] ? 1 + : 2; + if ((int)chipID != 1) { + int ChipID_fill = id - ChipsBoundaryBarrels[iBarrel]; + if ((ChipID_fill < 0) || (ChipID_fill >= CurrentDeadChips[iBarrel].size())) { + ILOG(Warning, Devel) << " prolematic ID for chip: " << ChipID_fill << ENDM; + } else { + CurrentDeadChips[iBarrel][ChipID_fill]++; + } + CurrentTFs[iBarrel][ChipID_fill]++; + } + id++; + } +} + +void ITSChipStatusTask::getParameters() +{ + + nQCCycleToMonitor = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nQCCycleToMonitor", nQCCycleToMonitor); + NCycleForOverview = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nQCCycleToOverview", NCycleForOverview); + nRotationType = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nRotationType", nRotationType); +} + +void ITSChipStatusTask::endOfCycle() +{ + + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + for (int iBarrel = 0; iBarrel < 3; iBarrel++) { + ChipsStack[iBarrel]->push(CurrentDeadChips[iBarrel]); + TFsStack[iBarrel]->push(CurrentTFs[iBarrel]); + for (int ix = 1; ix <= DeadChips[iBarrel]->GetNbinsX(); ix++) + for (int iy = 1; iy <= DeadChips[iBarrel]->GetNbinsY(); iy++) { + DeadChips[iBarrel]->getNum()->SetBinContent(ix, iy, ChipsStack[iBarrel]->stack[ix - 1][iy - 1]); + DeadChips[iBarrel]->getDen()->SetBinContent(ix, iy, TFsStack[iBarrel]->stack[ix - 1][iy - 1]); + } + + DeadChips[iBarrel]->update(); + } + + Beautify(); + //---------- Shifter plot + + FeeIDOverview->Reset(); + for (int iBarrel = 0; iBarrel < 3; iBarrel++) { + TH1D* hBarProj = DeadChips[iBarrel]->ProjectionY("temp", TMath::Max(1, nQCCycleToMonitor - NCycleForOverview + 1), nQCCycleToMonitor); + + for (int ic = 0; ic < hBarProj->GetNbinsX(); ic++) { + if (hBarProj->GetBinContent(ic + 1) < NCycleForOverview) { // report only chips dead for N consecutive cycles + continue; + } + FeeIDOverview->Fill(getFEEID(iBarrel, ic), 1. / nChipsPerFeeID[iBarrel]); + } + } + FeeIDOverview->Sumw2(kFALSE); + FeeIDOverview->SetMinimum(0); + FeeIDOverview->SetMaximum(1.1); + + int iLayer = 0; + int iStave = 0; + StaveOverview->Reset("content"); + for (int iBarrel = 0; iBarrel < 3; iBarrel++) { + + for (int iSlice = 0; iSlice < NCycleForOverview; iSlice++) { + iLayer = iBarrel == 0 ? 0 : iBarrel == 1 ? 3 + : 5; + TH1D* hBarrelProj = DeadChips[iBarrel]->getNum()->ProjectionY("dummy", nQCCycleToMonitor - iSlice, nQCCycleToMonitor - iSlice); + iStave = 0; + int nEmptyChips = 0; + + for (int iChip = 1; iChip <= hBarrelProj->GetNbinsX(); iChip++) { + + if (hBarrelProj->GetBinContent(iChip) > 0) + nEmptyChips++; + + if ((iChip) % (nChipsPerHic[iLayer] * nHicPerStave[iLayer]) == 0) { + + if (nEmptyChips == nChipsPerHic[iLayer] * nHicPerStave[iLayer]) { // all the chips of the stave are dead + + int binContent = StaveOverview->GetBinContent(iStave + 1 + StaveBoundary[iLayer]) * NCycleForOverview + 1; + StaveOverview->SetBinContent(iStave + 1 + StaveBoundary[iLayer], 1. * binContent / NCycleForOverview); + } + nEmptyChips = 0; + iStave++; + if ((iChip) == ChipsBoundaryLayers[iLayer + 1]) { + iLayer++; + iStave = 0; + } + + } // end of ((iChip) % (nChipsPerHic[iLayer] * nHicPerStave[iLayer]) == 0) + } + } + } +} + +void ITSChipStatusTask::Beautify() +{ + + int Layer_Draw = 0; + for (int i = 0; i < 3; i++) { + + int iLayerBegin, iLayerEnd; + if (i == 0) { + iLayerBegin = 0; + iLayerEnd = 2; + } else if (i == 1) { + iLayerBegin = 3; + iLayerEnd = 4; + } else { + iLayerBegin = 5; + iLayerEnd = 6; + } + + for (Int_t iLayer = iLayerBegin; iLayer <= iLayerEnd; iLayer++) { + + TLatex* msg = new TLatex(5, ChipsBoundaryLayers[iLayer + 1] - 15 * (1 + i * 30), Form("#bf{L%d}", iLayer)); + msg->SetTextSize(20); + msg->SetTextFont(43); + DeadChips[i]->GetListOfFunctions()->Add(msg); + if (iLayer < iLayerEnd) { + auto l = new TLine(0, ChipsBoundaryLayers[iLayer + 1] - 0.5, nQCCycleToMonitor, ChipsBoundaryLayers[iLayer + 1] - 0.5); + DeadChips[i]->GetListOfFunctions()->Add(l); + } + } + } +} + +void ITSChipStatusTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSChipStatusTask::reset() +{ + ILOG(Debug, Devel) << "Reset" << ENDM; +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSClusterCheck.cxx b/Modules/ITS/src/ITSClusterCheck.cxx new file mode 100644 index 0000000000..bd50c2daa1 --- /dev/null +++ b/Modules/ITS/src/ITSClusterCheck.cxx @@ -0,0 +1,286 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSClusterCheck.cxx +/// \author Artem Isakov +/// \author LiAng Zhang +/// \author Jian Liu +/// + +#include "ITS/ITSClusterCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include + +#include +#include +#include "Common/Utils.h" + +namespace o2::quality_control_modules::its +{ + +Quality ITSClusterCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + double averageClusterSizeLimit[NLayer] = { 5, 5, 5, 5, 5, 5, 5 }; + + std::map>::iterator iter; + for (iter = moMap->begin(); iter != moMap->end(); ++iter) { + result = Quality::Good; + + if (iter->second->getName().find("AverageClusterSize") != std::string::npos) { + auto* h = dynamic_cast(iter->second->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast AverageClusteSize to TH2D*" << ENDM; + continue; + } + for (int ilayer = 0; ilayer < NLayer; ilayer++) { + result.addMetadata(Form("Layer%d", ilayer), "good"); + if (iter->second->getName().find(Form("Layer%d", ilayer)) != std::string::npos && h->GetMaximum() > averageClusterSizeLimit[ilayer]) { + result.updateMetadata(Form("Layer%d", ilayer), "medium"); + result.set(Quality::Medium); + } + } + } + + if (iter->second->getName().find("EmptyLaneFractionGlobal") != std::string::npos) { + auto* h = dynamic_cast(iter->second->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast EmptyLaneFractionGlobal to TH1D*" << ENDM; + continue; + } + result.addMetadata("EmptyLaneFractionGlobal", "good"); + MaxEmptyLaneFraction = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "MaxEmptyLaneFraction", MaxEmptyLaneFraction); + if (h->GetBinContent(1) + h->GetBinContent(2) + h->GetBinContent(3) > MaxEmptyLaneFraction) { + result.updateMetadata("EmptyLaneFractionGlobal", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("BAD:>%.0f %% of the lanes are empty", (h->GetBinContent(1) + h->GetBinContent(2) + h->GetBinContent(3)) * 100)); + } + } // end summary loop + + if (iter->second->getName().find("General_Occupancy") != std::string::npos) { + auto* hp = dynamic_cast(iter->second->getObject()); + auto activity = iter->second->getActivity(); + if (hp == nullptr) { + ILOG(Error, Support) << "could not cast general occupancy to TH2D*" << ENDM; + continue; + } + std::vector skipxbins = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "skipxbinsoccupancy", "")); + std::vector skipybins = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "skipybinsoccupancy", "")); + std::vector> xypairs; + for (int i = 0; i < (int)skipxbins.size(); i++) { + xypairs.push_back(std::make_pair(skipxbins[i], skipybins[i])); + } + result.set(Quality::Good); + for (int iy = 1; iy <= hp->GetNbinsY(); iy++) { + int ilayer = iy <= hp->GetNbinsY() / 2 ? hp->GetNbinsY() / 2 - iy : iy - hp->GetNbinsY() / 2 - 1; + std::string tb = iy <= hp->GetNbinsY() / 2 ? "B" : "T"; + result.addMetadata(Form("Layer%d%s", ilayer, tb.c_str()), "good"); + bool mediumHalfLayer = false; + for (int ix = 1; ix <= hp->GetNbinsX(); ix++) { // loop on staves + if (std::find(xypairs.begin(), xypairs.end(), std::make_pair(ix, iy)) != xypairs.end()) { + continue; + } + + // maxcluocc[ilayer] = o2::quality_control_modules::common::getFromConfig(mCustomParameters, Form("maxcluoccL%d", ilayer), maxcluocc[ilayer]); + + maxcluocc[ilayer] = stof(mCustomParameters.at(Form("maxcluoccL%d", ilayer), activity)); + if (hp->GetBinContent(ix, iy) > maxcluocc[ilayer]) { + result.set(Quality::Medium); + result.updateMetadata(Form("Layer%d%s", ilayer, tb.c_str()), "medium"); + mediumHalfLayer = true; + } + } + if (mediumHalfLayer) + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("Medium: Layer%d%s has high cluster occupancy;", ilayer, tb.c_str())); + + // check for empty bins (empty staves) + result.addMetadata(Form("Layer%d%s_empty", ilayer, tb.c_str()), "good"); + bool badHalfLayer = false; + for (int ix = 12; ix > 12 - mNStaves[ilayer] / 4; ix--) { + if (std::find(xypairs.begin(), xypairs.end(), std::make_pair(ix, iy)) != xypairs.end()) { + continue; + } + if (hp->GetBinContent(ix, iy) < 1e-15) { + result.updateMetadata(Form("Layer%d%s_empty", ilayer, tb.c_str()), "bad"); + result.set(Quality::Bad); + badHalfLayer = true; + ILOG(Debug, Devel) << "************************ " << Form("Layer%d%s_empty", ilayer, tb.c_str()) << ENDM; + } + } + int stop = (iy == 2 || iy == 13) ? 13 + mNStaves[ilayer] / 4 + 1 : 13 + mNStaves[ilayer] / 4; // for L5 + for (int ix = 13; ix < stop; ix++) { + if (std::find(xypairs.begin(), xypairs.end(), std::make_pair(ix, iy)) != xypairs.end()) { + continue; + } + if (hp->GetBinContent(ix, iy) < 1e-15) { + result.updateMetadata(Form("Layer%d%s_empty", ilayer, tb.c_str()), "bad"); + result.set(Quality::Bad); + badHalfLayer = true; + } + } + if (badHalfLayer) + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("BAD: Layer%d%s has empty stave;", ilayer, tb.c_str())); + } + } // end GeneralOccupancy + } + return result; +} // end check + +void ITSClusterCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + std::vector vPlotWithTextMessage = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "plotWithTextMessage", "")); + std::vector vTextMessage = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "textMessage", "")); + std::map ShifterInfoText; + + if ((int)vTextMessage.size() == (int)vPlotWithTextMessage.size()) { + for (int i = 0; i < (int)vTextMessage.size(); i++) { + ShifterInfoText[vPlotWithTextMessage[i]] = vTextMessage[i]; + } + } else + ILOG(Warning, Support) << "Bad list of plot with TextMessages for shifter, check .json" << ENDM; + + std::shared_ptr tShifterInfo = std::make_shared(0.005, 0.006, Form("#bf{%s}", TString(ShifterInfoText[mo->getName()]).Data())); + tShifterInfo->SetTextSize(0.04); + tShifterInfo->SetTextFont(43); + tShifterInfo->SetNDC(); + + TString status; + int textColor; + Double_t positionX, positionY; + + if (mo->getName().find("AverageClusterSize") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast AverageClusterSize to TH2D*" << ENDM; + return; + } + std::string histoName = mo->getName(); + int iLayer = histoName[histoName.find("Layer") + 5] - 48; // Searching for position of "Layer" in the name of the file, then +5 is the NUMBER of the layer, -48 is conversion to int + + if (checkResult == Quality::Medium) { + status = "INFO: large clusters - do not call expert"; + textColor = kYellow; + positionX = 0.15; + positionY = 0.8; + } else { + status = "Quality::GOOD"; + textColor = kGreen; + positionX = 0.02; + positionY = 0.91; + } + + msg = std::make_shared(positionX, positionY, Form("#bf{%s}", status.Data())); + msg->SetTextColor(textColor); + msg->SetTextSize(0.06); + msg->SetTextFont(43); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + + if (mo->getName().find("EmptyLaneFractionGlobal") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast EmptyLaneFractionGlobal to TH1D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + positionX = 0.05; + positionY = 0.91; + } else if (checkResult == Quality::Bad) { + status = "Quality::BAD (call expert)"; + textColor = kRed; + if (strcmp(checkResult.getMetadata("EmptyLaneFractionGlobal").c_str(), "bad") == 0) { + MaxEmptyLaneFraction = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "MaxEmptyLaneFraction", MaxEmptyLaneFraction); + tInfoSummary = std::make_shared(0.12, 0.5, Form(">%.0f %% of the lanes are empty", MaxEmptyLaneFraction * 100)); + tInfoSummary->SetTextColor(kRed); + tInfoSummary->SetTextSize(0.05); + tInfoSummary->SetTextFont(43); + tInfoSummary->SetNDC(); + h->GetListOfFunctions()->Add(tInfoSummary->Clone()); + } + } + tInfo = std::make_shared(0.1, 0.11, Form("#bf{%s}", "Threshold value")); + tInfo->SetTextColor(kRed); + tInfo->SetTextSize(0.05); + tInfo->SetTextFont(43); + h->GetListOfFunctions()->Add(tInfo->Clone()); + tInfoLine = std::make_shared(0, 0.1, 4, 0.1); + tInfoLine->SetLineColor(kRed); + tInfoLine->SetLineStyle(9); + h->GetListOfFunctions()->Add(tInfoLine->Clone()); + msg = std::make_shared(positionX, positionY, Form("#bf{%s}", status.Data())); + msg->SetTextColor(textColor); + msg->SetTextSize(0.06); + msg->SetTextFont(43); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + if (mo->getName().find("General_Occupancy") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast General_Occupancy to TH2D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + positionX = 0.05; + positionY = 0.95; + } else { + for (int il = 0; il < 14; il++) { + std::string tb = il < 7 ? "T" : "B"; + if (strcmp(checkResult.getMetadata(Form("Layer%d%s", il % 7, tb.c_str())).c_str(), "medium") == 0) { + text[il] = std::make_shared(0.3, 0.1 + il * 0.05, Form("L%d%s has large cluster occupancy", il % 7, tb.c_str())); + text[il]->SetTextFont(43); + text[il]->SetTextSize(0.03); + text[il]->SetTextColor(kOrange); + text[il]->SetNDC(); + h->GetListOfFunctions()->Add(text[il]->Clone()); + status = "Quality::Medium, create log entry"; + textColor = kOrange; + positionX = 0.05; + positionY = 0.95; + } + + if (strcmp(checkResult.getMetadata(Form("Layer%d%s_empty", il % 7, tb.c_str())).c_str(), "bad") == 0) { + text2[il] = std::make_shared(0.7, 0.1 + il * 0.05, Form("L%d%s has empty staves", il % 7, tb.c_str())); + text2[il]->SetTextFont(43); + text2[il]->SetTextSize(0.03); + text2[il]->SetTextColor(kRed); + text2[il]->SetNDC(); + h->GetListOfFunctions()->Add(text2[il]->Clone()); + status = "Quality::Bad, call expert"; + textColor = kRed; + positionX = 0.05; + positionY = 0.95; + } + } + } + msg = std::make_shared(positionX, positionY, Form("#bf{%s}", status.Data())); + msg->SetTextColor(textColor); + msg->SetTextSize(0.06); + msg->SetTextFont(43); + msg->SetNDC(); + h->GetListOfFunctions()->Add(msg->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSClusterTask.cxx b/Modules/ITS/src/ITSClusterTask.cxx new file mode 100644 index 0000000000..41c320205b --- /dev/null +++ b/Modules/ITS/src/ITSClusterTask.cxx @@ -0,0 +1,813 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file ITSClusterTask.cxx +/// \author Artem Isakov +/// \author Antonio Palasciano +/// + +#include "QualityControl/QcInfoLogger.h" +#include "ITS/ITSClusterTask.h" +#include "Common/Utils.h" + +#include +#include +#include +#include +#include + +#ifdef WITH_OPENMP +#include +#endif + +using o2::itsmft::Digit; +using namespace o2::itsmft; +using namespace o2::its; + +namespace o2::quality_control_modules::its +{ + +ITSClusterTask::ITSClusterTask() : TaskInterface() {} + +ITSClusterTask::~ITSClusterTask() +{ + delete hTFCounter; + delete hEmptyLaneFractionGlobal; + delete hClusterVsBunchCrossing; + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + + if (!mEnableLayers[iLayer]) + continue; + + if (iLayer < NLayerIB) { + delete hClusterCenterMap[iLayer]; + delete hLongClustersPerChip[iLayer]; + delete hMultPerChipWhenLongClusters[iLayer]; + } + + else { + delete hLongClustersPerStave[iLayer - NLayerIB]; + } + delete hClusterSizeLayerSummary[iLayer]; + delete hClusterTopologyLayerSummary[iLayer]; + delete hGroupedClusterSizeLayerSummary[iLayer]; + delete hClusterOccupancyDistribution[iLayer]; + + if (mDoPublish1DSummary == 1) { + if (iLayer < NLayerIB) { + for (int iStave = 0; iStave < mNStaves[iLayer]; iStave++) { + for (int iChip = 0; iChip < mNChipsPerHic[iLayer]; iChip++) { + + delete hClusterTopologySummaryIB[iLayer][iStave][iChip]; + delete hGroupedClusterSizeSummaryIB[iLayer][iStave][iChip]; + delete hClusterSizeSummaryIB[iLayer][iStave][iChip]; + } + } + } else { + + for (int iStave = 0; iStave < mNStaves[iLayer]; iStave++) { + + delete hClusterSizeSummaryOB[iLayer][iStave]; + delete hClusterTopologySummaryOB[iLayer][iStave]; + delete hGroupedClusterSizeSummaryOB[iLayer][iStave]; + } + } + } + } +} + +void ITSClusterTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + + ILOG(Debug, Devel) << "initialize ITSClusterTask" << ENDM; + + getJsonParameters(); + + // Create binning for fine checks + setRphiBinningIB(); + setZBinningIB(); + setRphiBinningOB(); + setZBinningOB(); + + createAllHistos(); + mGeneralOccupancy = std::make_unique("General/General_Occupancy", "General Cluster Occupancy (max n_clusters/event/chip)", 24, -12, 12, 14, 0, 14, true); + + addObject(mGeneralOccupancy.get()); + mGeneralOccupancy->SetStats(0); + for (int iy = 1; iy <= mGeneralOccupancy->GetNbinsY(); iy++) { + mGeneralOccupancy->GetYaxis()->SetBinLabel(iy, mYlabels[iy - 1].c_str()); + } + mGeneralOccupancy->GetXaxis()->SetLabelOffset(999); + mGeneralOccupancy->GetXaxis()->SetLabelSize(0); + mGeneralOccupancy->GetZaxis()->SetTitle("Max Avg Cluster occ (clusters/event/chip)"); + + publishHistos(); +} + +void ITSClusterTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + reset(); +} + +void ITSClusterTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ITSClusterTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + + if (mTimestamp == -1) { // get dict from ccdb + mTimestamp = std::stol(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "dicttimestamp", "0")); + long int ts = mTimestamp ? mTimestamp : ctx.services().get().creation; + ILOG(Debug, Devel) << "Getting dictionary from ccdb - timestamp: " << ts << ENDM; + std::map metadata; + mDict = TaskInterface::retrieveConditionAny("ITS/Calib/ClusterDictionary", metadata, ts); + ILOG(Debug, Devel) << "Dictionary size: " << mDict->getSize() << ENDM; + o2::its::GeometryTGeo::adopt(TaskInterface::retrieveConditionAny("ITS/Config/Geometry", metadata, ts)); + mGeom = o2::its::GeometryTGeo::Instance(); + ILOG(Debug, Devel) << "Loaded new instance of mGeom" << ENDM; + } + + std::chrono::time_point start; + std::chrono::time_point end; + int difference; + start = std::chrono::high_resolution_clock::now(); + ILOG(Debug, Devel) << "START DOING QC General" << ENDM; + auto clusArr = ctx.inputs().get>("compclus"); + auto clusRofArr = ctx.inputs().get>("clustersrof"); + auto clusPatternArr = ctx.inputs().get>("patterns"); + auto pattIt = clusPatternArr.begin(); + + // Reset this histo to have the latest picture + hEmptyLaneFractionGlobal->Reset("ICES"); + +#ifdef WITH_OPENMP + omp_set_num_threads(mNThreads); +#pragma omp parallel for schedule(dynamic) +#endif + + // Filling cluster histogram for each ROF by open_mp + for (unsigned int iROF = 0; iROF < clusRofArr.size(); iROF++) { + + const auto& ROF = clusRofArr[iROF]; + const auto bcdata = ROF.getBCData(); + int nDigits3pixLay[7] = { 0 }; + int nClusters3pixLay[7] = { 0 }; + int nClusters3pix = 0; + int nLongClusters[ChipBoundary[NLayerIB]] = {}; // for IB + int nHitsFromClusters[ChipBoundary[NLayerIB]] = {}; // only IB is implemented at the moment + int nLongClustersStave[4][mNStaves[6]] = { {} }; // for OB. nLongClustersStave[n][m] means stave L<3+n>_ + + for (int icl = ROF.getFirstEntry(); icl < ROF.getFirstEntry() + ROF.getNEntries(); icl++) { + + auto& cluster = clusArr[icl]; + auto ChipID = cluster.getSensorID(); + int ClusterID = cluster.getPatternID(); // used for normal (frequent) cluster shapes + int lay, sta, ssta, mod, chip, lane; + + // TODO: avoid call Geom if ChipID is the same as previous cluster + mGeom->getChipId(ChipID, lay, sta, ssta, mod, chip); + mod = mod + (ssta * (mNHicPerStave[lay] / 2)); + int chipIdLocal = (ChipID - ChipBoundary[lay]) % (14 * mNHicPerStave[lay]); + lane = (chipIdLocal % (14 * mNHicPerStave[lay])) / (14 / 2); + + int npix = -1; + int colspan = -1; + int rowspan = -1; + int isGrouped = -1; + + o2::math_utils::Point3D locC; // local coordinates + + if (ClusterID != o2::itsmft::CompCluster::InvalidPatternID) { // Normal (frequent) cluster shapes + if (!mDict->isGroup(ClusterID)) { + npix = mDict->getNpixels(ClusterID); + + // TODO: is there way other than calling the pattern? + colspan = mDict->getPattern(ClusterID).getColumnSpan(); + rowspan = mDict->getPattern(ClusterID).getRowSpan(); + + if (mDoPublishDetailedSummary == 1) { + locC = mDict->getClusterCoordinates(cluster); + } + isGrouped = 0; + } else { + o2::itsmft::ClusterPattern patt(pattIt); + npix = patt.getNPixels(); + colspan = patt.getColumnSpan(); + rowspan = patt.getRowSpan(); + if (mDoPublishDetailedSummary == 1) { + locC = mDict->getClusterCoordinates(cluster, patt, true); + } + isGrouped = 1; + } + + } else { // invalid pattern + o2::itsmft::ClusterPattern patt(pattIt); + npix = patt.getNPixels(); + colspan = patt.getColumnSpan(); + rowspan = patt.getRowSpan(); + isGrouped = 0; + if (mDoPublishDetailedSummary == 1) { + locC = mDict->getClusterCoordinates(cluster, patt, false); + } + } + + if (npix > 2) { + nClusters3pixLay[lay]++; + nClusters3pix++; + nDigits3pixLay[lay] += npix; + } + + if (lay < NLayerIB) { + nHitsFromClusters[ChipID] += npix; + } + + if (colspan >= minColSpanLongCluster && rowspan <= maxRowSpanLongCluster) { + // definition of long cluster + if (lay < NLayerIB) { + nLongClusters[ChipID]++; + } else { + nLongClustersStave[lay - NLayerIB][sta]++; + } + } + + if (lay < NLayerIB) { + hAverageClusterOccupancySummaryIB[lay]->getNum()->Fill(chip, sta); + hAverageClusterSizeSummaryIB[lay]->getNum()->Fill(chip, sta, (double)npix); + hAverageClusterSizeSummaryIB[lay]->getDen()->Fill(chip, sta, 1.); + hClusterCenterMap[lay]->Fill(cluster.getCol(), cluster.getRow()); + if (mDoPublish1DSummary == 1) { + hClusterTopologySummaryIB[lay][sta][chip]->Fill(ClusterID); + } + + hClusterSizeLayerSummary[lay]->Fill(npix); + hClusterTopologyLayerSummary[lay]->Fill(ClusterID); + + if (mDoPublish1DSummary) { + hClusterSizeSummaryIB[lay][sta][chip]->Fill(npix); + } + + if (isGrouped) { + if (mDoPublish1DSummary == 1) { + hGroupedClusterSizeSummaryIB[lay][sta][chip]->Fill(npix); + } + hGroupedClusterSizeLayerSummary[lay]->Fill(npix); + } + } else { + hAverageClusterOccupancySummaryOB[lay]->getNum()->Fill(lane, sta, 1. / (mNChipsPerHic[lay] / mNLanePerHic[lay])); // 14 To have occupation per chip -> 7 because we're considering lanes + hAverageClusterSizeSummaryOB[lay]->getNum()->Fill(lane, sta, (double)npix); + hAverageClusterSizeSummaryOB[lay]->getDen()->Fill(lane, sta, 1); + if (mDoPublish1DSummary == 1) { + hClusterTopologySummaryOB[lay][sta]->Fill(ClusterID); + hClusterSizeSummaryOB[lay][sta]->Fill(npix); + } + hClusterSizeLayerSummary[lay]->Fill(npix); + hClusterTopologyLayerSummary[lay]->Fill(ClusterID); + if (isGrouped) { + if (mDoPublish1DSummary == 1) { + hGroupedClusterSizeSummaryOB[lay][sta]->Fill(npix); + } + hGroupedClusterSizeLayerSummary[lay]->Fill(npix); + } + } + + // Transformation to the local --> global + if (mDoPublishDetailedSummary == 1) { + auto gloC = mGeom->getMatrixL2G(ChipID) * locC; + float phi = (float)TMath::ATan2(gloC.Y(), gloC.X()); + + phi = (float)(phi * 180 / TMath::Pi()); + hAverageClusterOccupancySummaryZPhi[lay]->getNum()->Fill(gloC.Z(), phi); + hAverageClusterSizeSummaryZPhi[lay]->getNum()->Fill(gloC.Z(), phi, (float)npix); + + hAverageClusterOccupancySummaryFine[lay]->getNum()->Fill(getHorizontalBin(locC.Z(), chip, lay, lane), getVerticalBin(locC.X(), sta, lay)); + hAverageClusterSizeSummaryFine[lay]->getNum()->Fill(getHorizontalBin(locC.Z(), chip, lay, lane), getVerticalBin(locC.X(), sta, lay), (float)npix); + } + } + hClusterVsBunchCrossing->Fill(bcdata.bc, nClusters3pix); // we count only the number of clusters, not their sizes + for (int lay = 0; lay < 7; lay++) { + if (nClusters3pixLay[lay] > 0) { + int nchips = mNStaves[lay] * mNHicPerStave[lay] * mNChipsPerHic[lay]; + hClusterOccupancyDistribution[lay]->Fill(1. * nClusters3pixLay[lay] / nchips, 1. * nDigits3pixLay[lay] / nchips); + } + } + + // filling these anomaly plots once per ROF, ignoring chips w/o long clusters -- IB + for (int ichip = 0; ichip < ChipBoundary[NLayerIB]; ichip++) { + + int nLong = TMath::Min(nLongClusters[ichip], 40); + if (nLong < 1) { + continue; + } + int ilayer = -1; + while (ichip >= ChipBoundary[ilayer + 1]) { + ilayer++; + } + hLongClustersPerChip[ilayer]->Fill(ichip, nLong); + hMultPerChipWhenLongClusters[ilayer]->Fill(ichip, nHitsFromClusters[ichip]); + } + // filling anomaly plots once per ROF, ignoring staves w/o long clusters -- OB + for (int ilay = 3; ilay < 7; ilay++) { + for (int ist = 0; ist < mNStaves[ilay]; ist++) { + + int nLong = TMath::Min(nLongClustersStave[ilay - NLayerIB][ist], 40); + if (nLong < 1) { + continue; + } + hLongClustersPerStave[ilay - NLayerIB]->Fill(ist, nLong); + } + } + } + + if ((int)clusRofArr.size() > 0) { + + mGeneralOccupancy->getDen()->Fill(0., 0., (double)(clusRofArr.size())); + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + + if (!mEnableLayers[iLayer]) { + continue; + } + + if (iLayer < NLayerIB) { + hAverageClusterOccupancySummaryIB[iLayer]->getDen()->Fill(0., 0., (double)(clusRofArr.size())); + } else { + hAverageClusterOccupancySummaryOB[iLayer]->getDen()->Fill(0., 0., (double)(clusRofArr.size())); + } + + for (int iStave = 0; iStave < mNStaves[iLayer]; iStave++) { + + if (iLayer < NLayerIB) { + float max = -1.; + for (int iChip = 0; iChip < mNChipsPerHic[iLayer]; iChip++) { + // find the chip with the max occupancy stave by stave + if (hAverageClusterOccupancySummaryIB[iLayer]->getNum()->GetBinContent(iChip + 1, iStave + 1) > max) { + max = hAverageClusterOccupancySummaryIB[iLayer]->getNum()->GetBinContent(iChip + 1, iStave + 1); + } + if (hAverageClusterOccupancySummaryIB[iLayer]->getNum()->GetBinContent(iChip + 1, iStave + 1) < 1e-10) { + hEmptyLaneFractionGlobal->Fill(0., 1. / mNLanes[0]); + hEmptyLaneFractionGlobal->Fill(3., 1. / mNLanes[3]); + } + } + int ybin = iStave < (mNStaves[iLayer] / 2) ? 7 + iLayer + 1 : 7 - iLayer; + int xbin = 12 - mNStaves[iLayer] / 4 + 1 + (iStave % (mNStaves[iLayer] / 2)); + mGeneralOccupancy->getNum()->SetBinContent(xbin, ybin, max); + } else { + float max = -1.; + for (int iLane = 0; iLane < mNLanePerHic[iLayer] * mNHicPerStave[iLayer]; iLane++) { + if (hAverageClusterOccupancySummaryOB[iLayer]->getNum()->GetBinContent(iLane + 1, iStave + 1) > max) { + max = hAverageClusterOccupancySummaryOB[iLayer]->getNum()->GetBinContent(iLane + 1, iStave + 1); + } + if (hAverageClusterOccupancySummaryOB[iLayer]->getNum()->GetBinContent(iLane + 1, iStave + 1) < 1e-10) { + hEmptyLaneFractionGlobal->Fill((iLayer < 5 ? 1. : 2.), (1. / mNLanes[iLayer < 5 ? 1 : 2])); + hEmptyLaneFractionGlobal->Fill(3., (1. / mNLanes[3])); + } + } + int ybin = iStave < (mNStaves[iLayer] / 2) ? 7 + iLayer + 1 : 7 - iLayer; + int xbin = 12 - mNStaves[iLayer] / 4 + 1 + (iStave % (mNStaves[iLayer] / 2)); + mGeneralOccupancy->getNum()->SetBinContent(xbin, ybin, max); + } + } + + if (mDoPublishDetailedSummary == 1) { + + for (int ix = 1; ix <= hAverageClusterSizeSummaryZPhi[iLayer]->GetNbinsX(); ix++) + for (int iy = 1; iy <= hAverageClusterSizeSummaryZPhi[iLayer]->GetNbinsY(); iy++) { + hAverageClusterSizeSummaryZPhi[iLayer]->getDen()->SetBinContent(ix, iy, hAverageClusterOccupancySummaryZPhi[iLayer]->getNum()->GetBinContent(ix, iy)); + } + hAverageClusterOccupancySummaryZPhi[iLayer]->getDen()->Fill(0., 0., (double)(clusRofArr.size())); + + for (int ix = 1; ix <= hAverageClusterSizeSummaryFine[iLayer]->GetNbinsX(); ix++) + for (int iy = 1; iy <= hAverageClusterSizeSummaryFine[iLayer]->GetNbinsY(); iy++) { + hAverageClusterSizeSummaryFine[iLayer]->getDen()->SetBinContent(ix, iy, hAverageClusterOccupancySummaryFine[iLayer]->getNum()->GetBinContent(ix, iy)); + } + hAverageClusterOccupancySummaryFine[iLayer]->getDen()->Fill(0., 0., (double)(clusRofArr.size())); + } + } + } + + hTFCounter->Fill(0); + + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - start).count(); + ILOG(Debug, Devel) << "Time in QC Cluster Task: " << difference << ENDM; +} + +void ITSClusterTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + if (mDoPublishDetailedSummary == 1) { + hAverageClusterSizeSummaryZPhi[iLayer]->update(); + hAverageClusterOccupancySummaryZPhi[iLayer]->update(); + hAverageClusterOccupancySummaryFine[iLayer]->update(); + hAverageClusterSizeSummaryFine[iLayer]->update(); + } + if (iLayer < NLayerIB) { + hAverageClusterOccupancySummaryIB[iLayer]->update(); + hAverageClusterSizeSummaryIB[iLayer]->update(); + } else { + hAverageClusterOccupancySummaryOB[iLayer]->update(); + hAverageClusterSizeSummaryOB[iLayer]->update(); + } + } + mGeneralOccupancy->update(); +} + +void ITSClusterTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSClusterTask::reset() +{ + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + hTFCounter->Reset(); + hClusterVsBunchCrossing->Reset(); + hEmptyLaneFractionGlobal->Reset("ICES"); + mGeneralOccupancy->Reset(); + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + if (!mEnableLayers[iLayer]) + continue; + + hClusterOccupancyDistribution[iLayer]->Reset(); + hClusterSizeLayerSummary[iLayer]->Reset(); + hGroupedClusterSizeLayerSummary[iLayer]->Reset(); + hClusterTopologyLayerSummary[iLayer]->Reset(); + + if (iLayer < NLayerIB) { + hClusterCenterMap[iLayer]->Reset(); + hLongClustersPerChip[iLayer]->Reset(); + hMultPerChipWhenLongClusters[iLayer]->Reset(); + hAverageClusterOccupancySummaryIB[iLayer]->Reset(); + hAverageClusterSizeSummaryIB[iLayer]->Reset(); + if (mDoPublish1DSummary == 1) { + for (int iStave = 0; iStave < mNStaves[iLayer]; iStave++) { + for (int iChip = 0; iChip < mNChipsPerHic[iLayer]; iChip++) { + hClusterTopologySummaryIB[iLayer][iStave][iChip]->Reset(); + hGroupedClusterSizeSummaryIB[iLayer][iStave][iChip]->Reset(); + hClusterSizeSummaryIB[iLayer][iStave][iChip]->Reset(); + } + } + } + } else { + hLongClustersPerStave[iLayer - NLayerIB]->Reset(); + hAverageClusterOccupancySummaryOB[iLayer]->Reset(); + hAverageClusterSizeSummaryOB[iLayer]->Reset(); + if (mDoPublish1DSummary == 1) { + for (int iStave = 0; iStave < mNStaves[iLayer]; iStave++) { + hClusterTopologySummaryOB[iLayer][iStave]->Reset(); + hGroupedClusterSizeSummaryOB[iLayer][iStave]->Reset(); + hClusterSizeSummaryOB[iLayer][iStave]->Reset(); + } + } + } + if (mDoPublishDetailedSummary == 1) { + hAverageClusterOccupancySummaryZPhi[iLayer]->Reset(); + hAverageClusterSizeSummaryZPhi[iLayer]->Reset(); + hAverageClusterOccupancySummaryFine[iLayer]->Reset(); + hAverageClusterSizeSummaryFine[iLayer]->Reset(); + } + } +} + +void ITSClusterTask::createAllHistos() +{ + hTFCounter = new TH1D("TFcounter", "TFcounter", 1, 0, 1); + hTFCounter->SetTitle("TF counter"); + addObject(hTFCounter); + formatAxes(hTFCounter, "", "TF", 1, 1.10); + + hClusterVsBunchCrossing = new TH2D("BunchCrossingIDvsClusters", "BunchCrossingIDvsClusters", nBCbins, 0, 4095, 150, 0, 3000); + hClusterVsBunchCrossing->SetTitle("#clusters vs BC id for clusters with npix > 2"); + addObject(hClusterVsBunchCrossing); + formatAxes(hClusterVsBunchCrossing, "Bunch Crossing ID", "Number of clusters with npix > 2 in ROF", 1, 1.10); + hClusterVsBunchCrossing->SetStats(0); + + hEmptyLaneFractionGlobal = new TH1D("EmptyLaneFractionGlobal", "Empty Lane Fraction Global", 4, 0, 4); + hEmptyLaneFractionGlobal->SetTitle("Empty Lane /All Lane "); + addObject(hEmptyLaneFractionGlobal); + formatAxes(hEmptyLaneFractionGlobal, "", "Fraction of empty lane", 1, 1.10); + for (int i = 0; i < NFlags; i++) { + hEmptyLaneFractionGlobal->GetXaxis()->SetBinLabel(i + 1, mLaneStatusFlag[i].c_str()); + } + hEmptyLaneFractionGlobal->GetXaxis()->CenterLabels(); + hEmptyLaneFractionGlobal->SetMaximum(1); + hEmptyLaneFractionGlobal->SetMinimum(0); + hEmptyLaneFractionGlobal->SetBit(TH1::kIsAverage); + hEmptyLaneFractionGlobal->SetStats(0); + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + if (!mEnableLayers[iLayer]) + continue; + + if (iLayer < NLayerIB) { + hClusterCenterMap[iLayer] = new TH2D(Form("ClusterCenterMapL%d", iLayer), Form("Stacked map of cluster centers for all L%d chips;Column;Row", iLayer), 1024, -0.5, 1023.5, 512, -0.5, 511.5); + addObject(hClusterCenterMap[iLayer]); + formatAxes(hClusterCenterMap[iLayer], "Column", "Row", 1, 1.10); + hClusterCenterMap[iLayer]->SetStats(0); + + hLongClustersPerChip[iLayer] = new TH2D(Form("Anomalies/Layer%d/LongClusters", iLayer), Form("Layer%d/LongClusters", iLayer), ChipBoundary[iLayer + 1] - ChipBoundary[iLayer], ChipBoundary[iLayer], ChipBoundary[iLayer + 1], 41, 0, 41); + hMultPerChipWhenLongClusters[iLayer] = new TH2D(Form("Anomalies/Layer%d/HitsWhenLongClusters", iLayer), Form("Layer%d/HitsWhenLongClusters", iLayer), ChipBoundary[iLayer + 1] - ChipBoundary[iLayer], ChipBoundary[iLayer], ChipBoundary[iLayer + 1], 250, 0, 40000); + addObject(hLongClustersPerChip[iLayer]); + formatAxes(hLongClustersPerChip[iLayer], "Chip ID", "number of long clusters", 1, 1.10); + hLongClustersPerChip[iLayer]->SetStats(0); + addObject(hMultPerChipWhenLongClusters[iLayer]); + formatAxes(hMultPerChipWhenLongClusters[iLayer], "Chip ID", "Sum of clusters size (events w/ long clus)", 1, 1.10); + hMultPerChipWhenLongClusters[iLayer]->SetStats(0); + } + + else { + + hLongClustersPerStave[iLayer - NLayerIB] = new TH2D(Form("Anomalies/Layer%d/LongClusters", iLayer), Form("Layer%d/LongClusters", iLayer), mNStaves[iLayer], 0, mNStaves[iLayer], 41, 0, 41); + addObject(hLongClustersPerStave[iLayer - NLayerIB]); + formatAxes(hLongClustersPerStave[iLayer - NLayerIB], "Stave", "number of long clusters", 1, 1.10); + hLongClustersPerStave[iLayer - NLayerIB]->SetStats(0); + } + + hClusterSizeLayerSummary[iLayer] = new TH1L(Form("Layer%d/AverageClusterSizeSummary", iLayer), Form("Layer%dAverageClusterSizeSummary", iLayer), 128 * 128, 0, 128 * 128); + hClusterSizeLayerSummary[iLayer]->SetTitle(Form("Cluster size summary for Layer %d", iLayer)); + addObject(hClusterSizeLayerSummary[iLayer]); + formatAxes(hClusterSizeLayerSummary[iLayer], "Cluster Size (pixels)", "counts", 1, 1.10); + hClusterSizeLayerSummary[iLayer]->SetStats(0); + + double dynbin[7][4] = { { 300, 100, 500, 1000 }, { 300, 100, 500, 1000 }, { 300, 100, 500, 1000 }, // IB + { 300, 5, 500, 50 }, + { 300, 5, 500, 50 }, // ML + { 300, 1.5, 500, 15 }, + { 300, 1.5, 500, 15 } }; // OL + hClusterOccupancyDistribution[iLayer] = new TH2D(Form("Layer%d/OccupancyPerChipPerEvt", iLayer), Form("Layer%d/OccupancyPerChipPerEvt", iLayer), (int)dynbin[iLayer][0], 0, dynbin[iLayer][1], (int)dynbin[iLayer][2], 0, dynbin[iLayer][3]); + hClusterOccupancyDistribution[iLayer]->SetTitle(Form("hits/chip/evt, form clusters with npix>2 - Layer%d", iLayer)); + addObject(hClusterOccupancyDistribution[iLayer]); + formatAxes(hClusterOccupancyDistribution[iLayer], "N clus", "N hit", 1, 1.10); + hClusterOccupancyDistribution[iLayer]->SetStats(0); + + hGroupedClusterSizeLayerSummary[iLayer] = new TH1L(Form("Layer%d/AverageGroupedClusterSizeSummary", iLayer), Form("Layer%dAverageGroupedClusterSizeSummary", iLayer), 128 * 128, 0, 128 * 128); + hGroupedClusterSizeLayerSummary[iLayer]->SetTitle(Form("Cluster size summary for Layer %d", iLayer)); + addObject(hGroupedClusterSizeLayerSummary[iLayer]); + formatAxes(hGroupedClusterSizeLayerSummary[iLayer], "Grouped Cluster Size (pixels)", "counts", 1, 1.10); + hGroupedClusterSizeLayerSummary[iLayer]->SetStats(0); + + hClusterTopologyLayerSummary[iLayer] = new TH1L(Form("Layer%d/ClusterTopologySummary", iLayer), Form("Layer%dClusterTopologySummary", iLayer), 300, 0, 300); + hClusterTopologyLayerSummary[iLayer]->SetTitle(Form("Cluster topology summary for Layer %d", iLayer)); + addObject(hClusterTopologyLayerSummary[iLayer]); + formatAxes(hClusterTopologyLayerSummary[iLayer], "Cluster Topology (ID)", "counts", 1, 1.10); + hClusterTopologyLayerSummary[iLayer]->SetStats(0); + + if (mDoPublishDetailedSummary == 1) { + hAverageClusterOccupancySummaryZPhi[iLayer] = std::make_shared(Form("Layer%d/ClusterOccupancyZPhi", iLayer), Form("Cluster occupancy on Layer %d;z (cm);#phi (degree);", iLayer), 2 * (int)mLength[iLayer], -1 * mLength[iLayer], mLength[iLayer], 360, -180., 180., true); + hAverageClusterOccupancySummaryZPhi[iLayer]->SetStats(0); + hAverageClusterOccupancySummaryZPhi[iLayer]->GetYaxis()->SetLabelSize(0.02); + hAverageClusterOccupancySummaryZPhi[iLayer]->GetXaxis()->SetLabelSize(0.02); + + addObject(hAverageClusterOccupancySummaryZPhi[iLayer].get()); + + hAverageClusterSizeSummaryZPhi[iLayer] = std::make_shared(Form("Layer%d/ClusterSizeZPhi", iLayer), Form("Cluster size on Layer %d;z (cm);#phi (degree);", iLayer), 2 * (int)mLength[iLayer], -1 * mLength[iLayer], mLength[iLayer], 360, -180., 180., false); + hAverageClusterSizeSummaryZPhi[iLayer]->SetStats(0); + hAverageClusterSizeSummaryZPhi[iLayer]->GetYaxis()->SetLabelSize(0.02); + hAverageClusterSizeSummaryZPhi[iLayer]->GetXaxis()->SetLabelSize(0.02); + addObject(hAverageClusterSizeSummaryZPhi[iLayer].get()); + } + + if (iLayer < NLayerIB) { + hAverageClusterOccupancySummaryIB[iLayer] = std::make_shared(Form("Layer%d/ClusterOccupation", iLayer), Form("Layer%dClusterOccupancy", iLayer), mNChipsPerHic[iLayer], 0, mNChipsPerHic[iLayer], mNStaves[iLayer], 0, mNStaves[iLayer], true); + hAverageClusterOccupancySummaryIB[iLayer]->SetTitle(Form("Cluster Occupancy on Layer %d", iLayer)); + addObject(hAverageClusterOccupancySummaryIB[iLayer].get()); + formatAxes(hAverageClusterOccupancySummaryIB[iLayer].get(), "Chip Number", "Stave Number", 1, 1.10); + hAverageClusterOccupancySummaryIB[iLayer]->SetStats(0); + hAverageClusterOccupancySummaryIB[iLayer]->GetYaxis()->SetLabelSize(0.02); + hAverageClusterOccupancySummaryIB[iLayer]->GetXaxis()->SetLabelSize(0.02); + + hAverageClusterSizeSummaryIB[iLayer] = std::make_shared(Form("Layer%d/AverageClusterSize", iLayer), Form("Layer%dAverageClusterSize", iLayer), mNChipsPerHic[iLayer], 0, mNChipsPerHic[iLayer], mNStaves[iLayer], 0, mNStaves[iLayer], false); + hAverageClusterSizeSummaryIB[iLayer]->SetTitle(Form("Average Cluster Size on Layer %d", iLayer)); + addObject(hAverageClusterSizeSummaryIB[iLayer].get()); + formatAxes(hAverageClusterSizeSummaryIB[iLayer].get(), "Chip Number", "Stave Number", 1, 1.10); + hAverageClusterSizeSummaryIB[iLayer]->SetStats(0); + hAverageClusterSizeSummaryIB[iLayer]->GetYaxis()->SetLabelSize(0.02); + hAverageClusterSizeSummaryIB[iLayer]->GetXaxis()->SetLabelSize(0.02); + // Fine check + if (mDoPublishDetailedSummary == 1) { + hAverageClusterOccupancySummaryFine[iLayer] = std::make_shared(Form("Layer%d/ClusterOccupancyFine", iLayer), Form("Cluster occupancy on Layer %d; Pixel group (column direction); Pixel group (row direction)", iLayer), mNChipsPerHic[iLayer] * mNHicPerStave[iLayer] * nZBinsIB, -0.5, (mNChipsPerHic[iLayer] * mNHicPerStave[iLayer] * nZBinsIB) - 0.5, mNStaves[iLayer] * nRphiBinsIB, -0.5, mNStaves[iLayer] * nRphiBinsIB - 0.5, true); + hAverageClusterOccupancySummaryFine[iLayer]->SetStats(0); + addObject(hAverageClusterOccupancySummaryFine[iLayer].get()); + + hAverageClusterSizeSummaryFine[iLayer] = std::make_shared(Form("Layer%d/ClusterSizeFine", iLayer), Form("Cluster size on Layer %d; Pixel group (column direction); Pixel group (row direction)", iLayer), mNChipsPerHic[iLayer] * mNHicPerStave[iLayer] * nZBinsIB, -0.5, (mNChipsPerHic[iLayer] * mNHicPerStave[iLayer] * nZBinsIB) - 0.5, mNStaves[iLayer] * nRphiBinsIB, -0.5, mNStaves[iLayer] * nRphiBinsIB - 0.5, false); + hAverageClusterSizeSummaryFine[iLayer]->SetStats(0); + addObject(hAverageClusterSizeSummaryFine[iLayer].get()); + } + + for (int iStave = 0; iStave < mNStaves[iLayer]; iStave++) { + hAverageClusterSizeSummaryIB[iLayer]->GetYaxis()->SetBinLabel(iStave + 1, Form("Stave %i", iStave)); + hAverageClusterSizeSummaryIB[iLayer]->getDen()->GetYaxis()->SetBinLabel(iStave + 1, Form("Stave %i", iStave)); + hAverageClusterSizeSummaryIB[iLayer]->getNum()->GetYaxis()->SetBinLabel(iStave + 1, Form("Stave %i", iStave)); + hAverageClusterOccupancySummaryIB[iLayer]->GetYaxis()->SetBinLabel(iStave + 1, Form("Stave %i", iStave)); + + for (int iChip = 0; iChip < mNChipsPerHic[iLayer]; iChip++) { + + if (mDoPublish1DSummary == 1) { + hGroupedClusterSizeSummaryIB[iLayer][iStave][iChip] = new TH1D(Form("Layer%d/Stave%d/CHIP%d/ClusterSizeGrouped", iLayer, iStave, iChip), Form("Layer%dStave%dCHIP%dClusterSizeGroped", iLayer, iStave, iChip), 100, 0, 100); + hGroupedClusterSizeSummaryIB[iLayer][iStave][iChip]->SetTitle(Form("Cluster Size for grouped topologies on Layer %d Stave %d Chip %d", iLayer, iStave, iChip)); + addObject(hGroupedClusterSizeSummaryIB[iLayer][iStave][iChip]); + formatAxes(hGroupedClusterSizeSummaryIB[iLayer][iStave][iChip], "Cluster size (Pixel)", "Counts", 1, 1.10); + + hClusterSizeSummaryIB[iLayer][iStave][iChip] = new TH1D(Form("Layer%d/Stave%d/CHIP%d/ClusterSize", iLayer, iStave, iChip), Form("Layer%dStave%dCHIP%dClusterSize", iLayer, iStave, iChip), 100, 0, 100); + hClusterSizeSummaryIB[iLayer][iStave][iChip]->SetTitle(Form("Cluster Size for Layer %d Stave %d Chip %d", iLayer, iStave, iChip)); + addObject(hClusterSizeSummaryIB[iLayer][iStave][iChip]); + formatAxes(hClusterSizeSummaryIB[iLayer][iStave][iChip], "Cluster size (Pixel)", "Counts", 1, 1.10); + + hClusterTopologySummaryIB[iLayer][iStave][iChip] = new TH1D(Form("Layer%d/Stave%d/CHIP%d/ClusterTopology", iLayer, iStave, iChip), Form("Layer%dStave%dCHIP%dClusterTopology", iLayer, iStave, iChip), 300, 0, 300); + hClusterTopologySummaryIB[iLayer][iStave][iChip]->SetTitle(Form("Cluster Topology on Layer %d Stave %d Chip %d", iLayer, iStave, iChip)); + addObject(hClusterTopologySummaryIB[iLayer][iStave][iChip]); + formatAxes(hClusterTopologySummaryIB[iLayer][iStave][iChip], "Cluster topology (ID)", "Counts", 1, 1.10); + } + + hAverageClusterSizeSummaryIB[iLayer]->GetXaxis()->SetBinLabel(iChip + 1, Form("Chip %i", iChip)); + hAverageClusterSizeSummaryIB[iLayer]->getDen()->GetXaxis()->SetBinLabel(iChip + 1, Form("Chip %i", iChip)); + hAverageClusterSizeSummaryIB[iLayer]->getNum()->GetXaxis()->SetBinLabel(iChip + 1, Form("Chip %i", iChip)); + hAverageClusterOccupancySummaryIB[iLayer]->GetXaxis()->SetBinLabel(iChip + 1, Form("Chip %i", iChip)); + } + } + } else { + hAverageClusterOccupancySummaryOB[iLayer] = std::make_shared(Form("Layer%d/ClusterOccupation", iLayer), Form("Layer%dClusterOccupancy", iLayer), mNLanePerHic[iLayer] * mNHicPerStave[iLayer], 0, mNLanePerHic[iLayer] * mNHicPerStave[iLayer], mNStaves[iLayer], 0, mNStaves[iLayer], true); + hAverageClusterOccupancySummaryOB[iLayer]->SetTitle(Form("Cluster Occupancy on Layer %d", iLayer)); + addObject(hAverageClusterOccupancySummaryOB[iLayer].get()); + formatAxes(hAverageClusterOccupancySummaryOB[iLayer].get(), "", "Stave Number", 1, 1.10); + hAverageClusterOccupancySummaryOB[iLayer]->SetStats(0); + hAverageClusterOccupancySummaryOB[iLayer]->GetYaxis()->SetLabelSize(0.02); + hAverageClusterOccupancySummaryOB[iLayer]->GetXaxis()->SetLabelSize(0.02); + hAverageClusterOccupancySummaryOB[iLayer]->GetXaxis()->SetTitleOffset(1.2); + + hAverageClusterSizeSummaryOB[iLayer] = std::make_shared(Form("Layer%d/AverageClusterSize", iLayer), Form("Layer%dAverageClusterSize", iLayer), mNLanePerHic[iLayer] * mNHicPerStave[iLayer], 0, mNLanePerHic[iLayer] * mNHicPerStave[iLayer], mNStaves[iLayer], 0, mNStaves[iLayer]); + hAverageClusterSizeSummaryOB[iLayer]->SetTitle(Form("Average Cluster Size on Layer %d", iLayer)); + addObject(hAverageClusterSizeSummaryOB[iLayer].get()); + formatAxes(hAverageClusterSizeSummaryOB[iLayer].get(), "", "Stave Number", 1, 1.10); + hAverageClusterSizeSummaryOB[iLayer]->SetStats(0); + hAverageClusterSizeSummaryOB[iLayer]->SetOption("colz"); + hAverageClusterSizeSummaryOB[iLayer]->GetYaxis()->SetLabelSize(0.02); + hAverageClusterSizeSummaryOB[iLayer]->GetXaxis()->SetLabelSize(0.02); + hAverageClusterSizeSummaryOB[iLayer]->GetXaxis()->SetTitleOffset(1.2); + + // Fine check + if (mDoPublishDetailedSummary == 1) { + hAverageClusterOccupancySummaryFine[iLayer] = std::make_shared(Form("Layer%d/ClusterOccupancyFine", iLayer), Form("Cluster occupancy on Layer %d; Pixel group (column direction); Pixel group (row direction)", iLayer), mNChipsPerHic[iLayer] * mNHicPerStave[iLayer] * nZBinsOB, -0.5, (mNChipsPerHic[iLayer] * mNHicPerStave[iLayer] * nZBinsOB) - 0.5, mNStaves[iLayer] * nRphiBinsOB, -0.5, mNStaves[iLayer] * nRphiBinsOB - 0.5, true); + hAverageClusterOccupancySummaryFine[iLayer]->SetStats(0); + addObject(hAverageClusterOccupancySummaryFine[iLayer].get()); + + hAverageClusterSizeSummaryFine[iLayer] = std::make_shared(Form("Layer%d/ClusterSizeFine", iLayer), Form("Cluster size on Layer %d; Pixel group (column direction); Pixel group (row direction)", iLayer), mNChipsPerHic[iLayer] * mNHicPerStave[iLayer] * nZBinsOB, -0.5, (mNChipsPerHic[iLayer] * mNHicPerStave[iLayer] * nZBinsOB) - 0.5, mNStaves[iLayer] * nRphiBinsOB, -0.5, mNStaves[iLayer] * nRphiBinsOB - 0.5, false); + hAverageClusterSizeSummaryFine[iLayer]->SetStats(0); + addObject(hAverageClusterSizeSummaryFine[iLayer].get()); + } + + for (int iStave = 0; iStave < mNStaves[iLayer]; iStave++) { + if (mDoPublish1DSummary == 1) { + hClusterSizeSummaryOB[iLayer][iStave] = new TH1D(Form("Layer%d/Stave%d/ClusterSize", iLayer, iStave), Form("Layer%dStave%dClusterSize", iLayer, iStave), 100, 0, 100); + hClusterSizeSummaryOB[iLayer][iStave]->SetTitle(Form("Cluster Size summary for Layer %d Stave %d", iLayer, iStave)); + addObject(hClusterSizeSummaryOB[iLayer][iStave]); + formatAxes(hClusterSizeSummaryOB[iLayer][iStave], "Cluster size (Pixel)", "Counts", 1, 1.10); + + hGroupedClusterSizeSummaryOB[iLayer][iStave] = new TH1D(Form("Layer%d/Stave%d/GroupedClusterSize", iLayer, iStave), Form("Layer%dStave%dGroupedClusterSize", iLayer, iStave), 100, 0, 100); + hGroupedClusterSizeSummaryOB[iLayer][iStave]->SetTitle(Form("Grouped Cluster Size summary for Layer %d Stave %d", iLayer, iStave)); + addObject(hGroupedClusterSizeSummaryOB[iLayer][iStave]); + formatAxes(hGroupedClusterSizeSummaryOB[iLayer][iStave], "Cluster size (Pixel)", "Counts", 1, 1.10); + + hClusterTopologySummaryOB[iLayer][iStave] = new TH1D(Form("Layer%d/Stave%d/ClusterTopology", iLayer, iStave), Form("Layer%dStave%dClusterTopology", iLayer, iStave), 300, 0, 300); + hClusterTopologySummaryOB[iLayer][iStave]->SetTitle(Form("Cluster Toplogy summary for Layer %d Stave %d", iLayer, iStave)); + addObject(hClusterTopologySummaryOB[iLayer][iStave]); + formatAxes(hClusterTopologySummaryOB[iLayer][iStave], "Cluster toplogy (ID)", "Counts", 1, 1.10); + } + + hAverageClusterSizeSummaryOB[iLayer]->GetYaxis()->SetBinLabel(iStave + 1, Form("Stave %i", iStave)); + hAverageClusterSizeSummaryOB[iLayer]->getDen()->GetYaxis()->SetBinLabel(iStave + 1, Form("Stave %i", iStave)); + hAverageClusterSizeSummaryOB[iLayer]->getNum()->GetYaxis()->SetBinLabel(iStave + 1, Form("Stave %i", iStave)); + hAverageClusterOccupancySummaryOB[iLayer]->GetYaxis()->SetBinLabel(iStave + 1, Form("Stave %i", iStave)); + for (int iLane = 0; iLane < mNLanePerHic[iLayer] * mNHicPerStave[iLayer]; iLane++) { // are used in TH2 construction, no need to keep on ccdb + (iLayer < 5) ? (xLabel = Form("%s", OBLabel34[iLane])) : (xLabel = Form("%s", OBLabel56[iLane])); + hAverageClusterSizeSummaryOB[iLayer]->GetXaxis()->SetBinLabel(iLane + 1, xLabel); + hAverageClusterSizeSummaryOB[iLayer]->getDen()->GetXaxis()->SetBinLabel(iLane + 1, xLabel); + hAverageClusterSizeSummaryOB[iLayer]->getNum()->GetXaxis()->SetBinLabel(iLane + 1, xLabel); + hAverageClusterOccupancySummaryOB[iLayer]->GetXaxis()->SetBinLabel(iLane + 1, xLabel); + } + } + } + } +} + +void ITSClusterTask::getJsonParameters() +{ + mNThreads = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nThreads", mNThreads); + nBCbins = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nBCbins", nBCbins); + mDoPublish1DSummary = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "publishSummary1D", mDoPublish1DSummary); + std::string LayerConfig = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "layer", "0000000"); + mDoPublishDetailedSummary = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "publishDetailedSummary", mDoPublishDetailedSummary); + + for (int ilayer = 0; ilayer < NLayer; ilayer++) { + if (LayerConfig[ilayer] != '0') { + mEnableLayers[ilayer] = 1; + ILOG(Debug, Devel) << "enable layer : " << ilayer << ENDM; + } else { + mEnableLayers[ilayer] = 0; + } + } +} + +void ITSClusterTask::addObject(TObject* aObject) +{ + if (!aObject) { + ILOG(Debug, Devel) << " ERROR: trying to add non-existent object " << ENDM; + return; + } else + mPublishedObjects.push_back(aObject); +} + +void ITSClusterTask::publishHistos() +{ + for (unsigned int iObj = 0; iObj < mPublishedObjects.size(); iObj++) { + getObjectsManager()->startPublishing(mPublishedObjects.at(iObj)); + } +} + +void ITSClusterTask::setRphiBinningIB(std::vector bins) +{ + vRphiBinsIB = bins; + nRphiBinsIB = (int)vRphiBinsIB.size() - 1; +} + +void ITSClusterTask::setZBinningIB(std::vector bins) +{ + vZBinsIB = bins; + nZBinsIB = (int)vZBinsIB.size() - 1; +} + +void ITSClusterTask::setRphiBinningOB(std::vector bins) +{ + vRphiBinsOB = bins; + nRphiBinsOB = (int)vRphiBinsOB.size() - 1; +} + +void ITSClusterTask::setZBinningOB(std::vector bins) +{ + vZBinsOB = bins; + nZBinsOB = (int)vZBinsOB.size() - 1; +} + +float ITSClusterTask::getHorizontalBin(float z, int chip, int layer, int lane) +{ + std::vector::iterator iter_z; + int index_z, chip_index; + float return_index; + if (layer < NLayerIB) { + if (z < vZBinsIB[0] || z > vZBinsIB[nZBinsIB]) + return -999.; + iter_z = std::upper_bound(vZBinsIB.begin(), vZBinsIB.end(), z, + [](const float& comp1, const float& comp2) { return comp1 < comp2; }); + index_z = std::distance(vZBinsIB.begin(), iter_z) - 1; + chip_index = chip; + return_index = (double)(nZBinsIB * chip_index + index_z); + } else { + if (z < vZBinsOB[0] || z > vZBinsOB[nZBinsOB]) + return -999.; + iter_z = std::upper_bound(vZBinsOB.begin(), vZBinsOB.end(), z, + [](const float& comp1, const float& comp2) { return comp1 < comp2; }); + index_z = std::distance(vZBinsOB.begin(), iter_z) - 1; + chip_index = chip % (mNChipsPerHic[layer] / mNLanePerHic[layer]) + (lane * (mNChipsPerHic[layer] / mNLanePerHic[layer])); + return_index = (float)(nZBinsOB * chip_index + index_z); + } + return return_index; +} + +float ITSClusterTask::getVerticalBin(float rphi, int stave, int layer) +{ + std::vector::iterator iter_rphi; + int index_rphi; + float return_index; + if (layer < 3) { + if (rphi < vRphiBinsIB[0] || rphi > vRphiBinsIB[nRphiBinsIB]) + return -999.; + iter_rphi = std::upper_bound(vRphiBinsIB.begin(), vRphiBinsIB.end(), rphi, + [](const float& comp1, const float& comp2) { return comp1 < comp2; }); + index_rphi = std::distance(vRphiBinsIB.begin(), iter_rphi) - 1; + return_index = (double)(stave * nRphiBinsIB + index_rphi); + } else { + if (rphi < vRphiBinsOB[0] || rphi > vRphiBinsOB[nRphiBinsOB]) + return -999.; + iter_rphi = std::upper_bound(vRphiBinsOB.begin(), vRphiBinsOB.end(), rphi, + [](const float& comp1, const float& comp2) { return comp1 < comp2; }); + index_rphi = std::distance(vRphiBinsOB.begin(), iter_rphi) - 1; + return_index = (float)(stave * nRphiBinsOB + index_rphi); + } + return return_index; +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSDecodingErrorCheck.cxx b/Modules/ITS/src/ITSDecodingErrorCheck.cxx new file mode 100644 index 0000000000..dbb1006eec --- /dev/null +++ b/Modules/ITS/src/ITSDecodingErrorCheck.cxx @@ -0,0 +1,263 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSDecodingErrorCheck.cxx +/// \author Zhen Zhang +/// + +#include "ITS/ITSDecodingErrorCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "ITSMFTReconstruction/DecodingStat.h" +#include + +#include +#include "Common/Utils.h" + +namespace o2::quality_control_modules::its +{ + +Quality ITSDecodingErrorCheck::check(std::map>* moMap) +{ + // set timer + if (nCycle == 0) { + start = std::chrono::high_resolution_clock::now(); + nCycle++; + } else { + end = std::chrono::high_resolution_clock::now(); + TIME = std::chrono::duration_cast(end - start).count(); + } + std::vector vDecErrorLimits = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "DecLinkErrorLimits", "")); + if (vDecErrorLimits.size() != o2::itsmft::GBTLinkDecodingStat::NErrorsDefined) { + ILOG(Error, Support) << "Incorrect vector with DecodingError limits, check .json" << ENDM; + doFlatCheck = true; + } + std::vector vDecErrorLimitsRatio = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "DecLinkErrorLimitsRatio", "")); + if (vDecErrorLimitsRatio.size() != o2::itsmft::GBTLinkDecodingStat::NErrorsDefined) { + ILOG(Error, Support) << "Incorrect vector with DecodingError limits Ratio, check .json" << ENDM; + doFlatCheck = true; + } + std::vector vDecErrorType = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "DecLinkErrorType", "")); + if (vDecErrorType.size() != o2::itsmft::GBTLinkDecodingStat::NErrorsDefined) { + ILOG(Error, Support) << "Incorrect vector with DecodingError Type, check .json" << ENDM; + doFlatCheck = true; + } + + bool checkFeeIDOnce = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "checkfeeIDonlyonce", ""); + + Quality result = Quality::Null; + for (auto& [moName, mo] : *moMap) { + (void)moName; + + if ((std::string)mo->getName() == "General/ChipErrorPlots") { + result = Quality::Good; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast ChipError plots to TH1D*" << ENDM; + continue; + } + if (h->GetMaximum() > 200) + result.set(Quality::Bad); + } + + if ((std::string)mo->GetName() == "General/LinkErrorVsFeeid") { + + result = Quality::Good; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast LinkErrorVsFeeid plot to TH2D*" << ENDM; + continue; + } + for (int ifee = 0; ifee < h->GetNbinsX(); ifee++) { + for (int ierr = 0; ierr < h->GetNbinsY(); ierr++) { + + if (doFlatCheck && h->GetBinContent(ifee + 1, ierr + 1) < 200) { // ok if below threshold + continue; + } + if (!doFlatCheck && (vDecErrorLimits[ierr] < 0 || h->GetBinContent(ifee + 1, ierr + 1) < vDecErrorLimits[ierr])) { // ok if below threshold or if threshold is -1 + continue; + } + + if (vAlreadyCheckedFeeIDs.count(ifee) > 0) { + std::vector flagskip = vAlreadyCheckedFeeIDs[ifee]; + if (std::find(flagskip.begin(), flagskip.end(), ierr) != flagskip.end()) { + continue; // bin to be skipped + } + } + + // if here, quality is BAD + + if (checkFeeIDOnce) { // gives instruction to exclude chech on this bin from now on + if (vAlreadyCheckedFeeIDs.count(ifee) > 0) { + vAlreadyCheckedFeeIDs[ifee].push_back(ierr); + } else { + vAlreadyCheckedFeeIDs[ifee] = std::vector{ ierr }; + } + } + + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("BAD: ID = %d, %s", ierr, std::string(statistics.ErrNames[ierr]).c_str())); + + } // end of y axis loop + } // end of x axis loop + } // end of check on General/LinkErrorVsFeeid + + if (((std::string)mo->getName()).find("General/LinkErrorPlots") != std::string::npos) { + result = Quality::Good; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast LinkErrorPlots to TH1D*" << ENDM; + continue; + } + + if (doFlatCheck) { + if (h->GetMaximum() > 200) + result.set(Quality::Bad); + + } else { + for (int iBin = 1; iBin <= h->GetNbinsX(); iBin++) { + + if (vDecErrorLimits[iBin - 1] < 0) + continue; // skipping bin + + if (vDecErrorType[iBin - 1] == 1 && TIME != 0) { + if (vDecErrorLimitsRatio[iBin - 1] <= h->GetBinContent(iBin) / TIME) { + vListErrorIdBad.push_back(iBin - 1); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("BAD: ID = %d, %s", iBin - 1, std::string(statistics.ErrNames[iBin - 1]).c_str())); + } else if (vDecErrorLimitsRatio[iBin - 1] / 2 < h->GetBinContent(iBin) / TIME) { + vListErrorIdMedium.push_back(iBin - 1); + if (result != Quality::Bad) { + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("Medium: ID = %d, %s", iBin - 1, std::string(statistics.ErrNames[iBin - 1]).c_str())); + result.set(Quality::Medium); + } + } + } else { // normal check, as we have in the code now + if (vDecErrorLimits[iBin - 1] <= h->GetBinContent(iBin)) { + vListErrorIdBad.push_back(iBin - 1); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("BAD: ID = %d, %s", iBin - 1, std::string(statistics.ErrNames[iBin - 1]).c_str())); + } else if (vDecErrorLimits[iBin - 1] / 2 < h->GetBinContent(iBin)) { + vListErrorIdMedium.push_back(iBin - 1); + if (result != Quality::Bad) { + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("Medium: ID = %d, %s", iBin - 1, std::string(statistics.ErrNames[iBin - 1]).c_str())); + result.set(Quality::Medium); + } + } + } + } + } + } + } + return result; +} + +void ITSDecodingErrorCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + std::vector vPlotWithTextMessage = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "plotWithTextMessage", "")); + std::vector vTextMessage = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "textMessage", "")); + std::map ShifterInfoText; + + if ((int)vTextMessage.size() == (int)vPlotWithTextMessage.size()) { + for (int i = 0; i < (int)vTextMessage.size(); i++) { + ShifterInfoText[vPlotWithTextMessage[i]] = vTextMessage[i]; + } + } else + ILOG(Warning, Support) << "Bad list of plot with TextMessages for shifter, check .json" << ENDM; + + std::shared_ptr tShifterInfo = std::make_shared(0.005, 0.006, Form("#bf{%s}", TString(ShifterInfoText[mo->getName()]).Data())); + tShifterInfo->SetTextSize(0.04); + tShifterInfo->SetTextFont(43); + tShifterInfo->SetNDC(); + + TString status; + int textColor; + + if (((std::string)mo->GetName()).find("General/LinkErrorVsFeeid") != std::string::npos) { + + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast LinkErrorVsFeeid plot to TH2D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else if (checkResult == Quality::Bad) { + status = "Quality::BAD (call expert)"; + textColor = kRed + 2; + } + + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.06); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + + } // end of beautify LinkErrorVsFeeid + + if ((((std::string)mo->getName()).find("General/LinkErrorPlots") != std::string::npos) || (mo->getName() == "General/ChipErrorPlots")) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast LinkErrorPlots to TH1D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else { + + if (checkResult == Quality::Bad) { + status = "Quality::BAD (call expert)"; + for (int id = 0; id < vListErrorIdBad.size(); id++) { + int currentError = vListErrorIdBad[id]; + tInfo = std::make_shared(0.12, 0.835 - 0.04 * (id + 1), Form("BAD: ID = %d, %s", currentError, std::string(statistics.ErrNames[currentError]).c_str())); + tInfo->SetTextColor(kRed + 2); + tInfo->SetTextSize(0.04); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + } + textColor = kRed + 2; + } + if (vListErrorIdMedium.size() > 0) { + if (checkResult == Quality::Medium) { + status = "Quality::Medium"; + textColor = kOrange; + } + for (int id = 0; id < vListErrorIdMedium.size(); id++) { + int currentError = vListErrorIdMedium[id]; + tInfo = std::make_shared(0.12, 0.6 - 0.04 * (id + 1), Form("Medium: ID = %d, %s", currentError, std::string(statistics.ErrNames[currentError]).c_str())); + tInfo->SetTextColor(kOrange + 1); + tInfo->SetTextSize(0.04); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + } + } + } + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.06); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + vListErrorIdBad.clear(); + vListErrorIdMedium.clear(); +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSDecodingErrorTask.cxx b/Modules/ITS/src/ITSDecodingErrorTask.cxx new file mode 100644 index 0000000000..578c7341da --- /dev/null +++ b/Modules/ITS/src/ITSDecodingErrorTask.cxx @@ -0,0 +1,254 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSDecodingErrorTask.cxx +/// \author Zhen Zhang +/// + +#include "ITS/ITSDecodingErrorTask.h" +#include "ITSMFTReconstruction/DecodingStat.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include "Common/TH1Ratio.h" +#include "Common/Utils.h" + +using namespace o2::framework; +using namespace o2::itsmft; +using namespace o2::header; + +namespace o2::quality_control_modules::its +{ + +ITSDecodingErrorTask::ITSDecodingErrorTask() + : TaskInterface() +{ +} + +ITSDecodingErrorTask::~ITSDecodingErrorTask() +{ + delete mLinkErrorPlots; + delete mChipErrorPlots; + delete mLinkErrorVsFeeid; + delete mChipErrorVsFeeid; + for (int ilayer = 0; ilayer < NLayer; ilayer++) { + delete mChipErrorVsChipid[ilayer]; + } +} + +void ITSDecodingErrorTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initializing the ITSDecodingErrorTask" << ENDM; + getParameters(); + createDecodingPlots(); + setPlotsFormat(); + + for (int iFEE = 0; iFEE < NFees; iFEE++) { + DecErr_lastCycle[iFEE] = std::vector(o2::itsmft::GBTLinkDecodingStat::NErrorsDefined, 0); + } +} + +void ITSDecodingErrorTask::createDecodingPlots() +{ + mLinkErrorVsFeeid = new TH2D("General/LinkErrorVsFeeid", "GBTLink errors per FeeId", NFees, 0, NFees, o2::itsmft::GBTLinkDecodingStat::NErrorsDefined, 0.5, (float)o2::itsmft::GBTLinkDecodingStat::NErrorsDefined + 0.5); + mLinkErrorVsFeeid->SetMinimum(0); + mLinkErrorVsFeeid->SetStats(0); + getObjectsManager()->startPublishing(mLinkErrorVsFeeid); + for (int ilayer = 0; ilayer < 7; ilayer++) { + mChipErrorVsChipid[ilayer] = new TH2D(Form("General/Layer%dChipErrorVsChipid", ilayer), Form("Layer%d Chip errors per FeeId", ilayer), ChipBoundary[ilayer + 1] - ChipBoundary[ilayer], 0, ChipBoundary[ilayer + 1] - ChipBoundary[ilayer], o2::itsmft::ChipStat::NErrorsDefined, 0.5, (float)o2::itsmft::ChipStat::NErrorsDefined + 0.5); + mChipErrorVsChipid[ilayer]->SetMinimum(0); + mChipErrorVsChipid[ilayer]->SetStats(0); + getObjectsManager()->startPublishing(mChipErrorVsChipid[ilayer]); + } + mChipErrorVsFeeid = new TH2D("General/ChipErrorVsFeeid", "Chip decoding errors per FeeId", NFees, 0, NFees, o2::itsmft::ChipStat::NErrorsDefined, 0.5, (float)o2::itsmft::ChipStat::NErrorsDefined + 0.5); + mChipErrorVsFeeid->SetMinimum(0); + mChipErrorVsFeeid->SetStats(0); + getObjectsManager()->startPublishing(mChipErrorVsFeeid); + mLinkErrorPlots = new TH1D("General/LinkErrorPlots", "GBTLink decoding Errors", o2::itsmft::GBTLinkDecodingStat::NErrorsDefined, 0.5, (float)o2::itsmft::GBTLinkDecodingStat::NErrorsDefined + 0.5); + mLinkErrorPlots->SetMinimum(0); + mLinkErrorPlots->SetStats(0); + mLinkErrorPlots->SetFillColor(kOrange); + getObjectsManager()->startPublishing(mLinkErrorPlots); // mLinkErrorPlots + mChipErrorPlots = new TH1D("General/ChipErrorPlots", "Chip Decoding Errors", o2::itsmft::ChipStat::NErrorsDefined, 0.5, (float)o2::itsmft::ChipStat::NErrorsDefined + 0.5); + mChipErrorPlots->SetMinimum(0); + mChipErrorPlots->SetStats(0); + mChipErrorPlots->SetFillColor(kOrange); + getObjectsManager()->startPublishing(mChipErrorPlots); // mChipErrorPlots + + hAlwaysBusy = new TH1D("AlwaysBusyChips", "Number of Chips always in BUSY state", 11, 0, 11); + setAxisTitle(hAlwaysBusy, "Layer", "Counts"); + getObjectsManager()->startPublishing(hAlwaysBusy); + + hBusyFraction = std::make_unique("FractionOfBusyChips", "Fraction of chips in BUSY, excluding permanent", 11, 0, 11, false); + setAxisTitle(hBusyFraction.get(), "Layer", "BusyViolations / TF / NChips"); + getObjectsManager()->startPublishing(hBusyFraction.get()); + + for (int iBin = 1; iBin <= 11; iBin++) { + hAlwaysBusy->GetXaxis()->SetBinLabel(iBin, LayerBinLabels[iBin - 1]); + hBusyFraction->GetXaxis()->SetBinLabel(iBin, LayerBinLabels[iBin - 1]); + } +} + +void ITSDecodingErrorTask::setAxisTitle(TH1* object, const char* xTitle, const char* yTitle) +{ + object->GetXaxis()->SetTitle(xTitle); + object->GetYaxis()->SetTitle(yTitle); +} + +void ITSDecodingErrorTask::setPlotsFormat() +{ + if (mLinkErrorVsFeeid) { + setAxisTitle(mLinkErrorVsFeeid, "FeeID", "Error ID"); + } + if (mChipErrorVsFeeid) { + setAxisTitle(mChipErrorVsFeeid, "FeeID", "Error ID"); + } + for (int ilayer = 0; ilayer < 7; ilayer++) { + if (mChipErrorVsChipid[ilayer]) { + setAxisTitle(mChipErrorVsChipid[ilayer], "ChipID", "Error ID"); + } + } + if (mLinkErrorPlots) { + setAxisTitle(mLinkErrorPlots, "LinkError ID", "Counts"); + } + if (mChipErrorPlots) { + setAxisTitle(mChipErrorPlots, "ChipError ID", "Counts"); + } +} + +void ITSDecodingErrorTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity : " << activity.mId << ENDM; +} + +void ITSDecodingErrorTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ITSDecodingErrorTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto linkErrors = ctx.inputs().get>("linkerrors"); + auto decErrors = ctx.inputs().get>("decerrors"); + + // multiply Error distributions before re-filling + for (const auto& le : linkErrors) { + int istave = (int)(le.feeID & 0x00ff); + int ilink = (int)((le.feeID & 0x0f00) >> 8); + int ilayer = (int)((le.feeID & 0xf000) >> 12); + int ifee = 3 * StaveBoundary[ilayer] - (StaveBoundary[ilayer] - StaveBoundary[NLayerIB]) * (ilayer >= NLayerIB) + istave * (3 - (ilayer >= NLayerIB)) + ilink; + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + if (le.errorCounts[ierror] <= 0) { + continue; + } + if (isDoLinkErrorReset) + mLinkErrorVsFeeid->SetBinContent(ifee + 1, ierror + 1, le.errorCounts[ierror] - DecErr_lastCycle[ifee][ierror]); + else + mLinkErrorVsFeeid->SetBinContent(ifee + 1, ierror + 1, le.errorCounts[ierror]); + } + } + + for (const auto& de : decErrors) { + int istave = (int)(de.getFEEID() & 0x00ff); + int ilink = (int)((de.getFEEID() & 0x0f00) >> 8); + int ilayer = (int)((de.getFEEID() & 0xf000) >> 12); + int ifee = 3 * StaveBoundary[ilayer] - (StaveBoundary[ilayer] - StaveBoundary[NLayerIB]) * (ilayer >= NLayerIB) + istave * (3 - (ilayer >= NLayerIB)) + ilink; + int ichip = de.getChipID() - ChipBoundary[ilayer]; + for (int ierror = 0; ierror < o2::itsmft::ChipStat::NErrorsDefined; ierror++) { + if ((de.errors >> ierror) % 2) { + if (de.getChipID() == -1) { + continue; + } + mChipErrorVsFeeid->Fill(ifee, ierror + 1); + mChipErrorVsChipid[ilayer]->Fill(ichip, ierror + 1); + } + } + } + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + int feeLinkError = mLinkErrorVsFeeid->Integral(1, mLinkErrorVsFeeid->GetXaxis()->GetNbins(), ierror + 1, ierror + 1); + mLinkErrorPlots->SetBinContent(ierror + 1, feeLinkError); + } + for (int ierror = 0; ierror < o2::itsmft::ChipStat::NErrorsDefined; ierror++) { + int feeChipError = mChipErrorVsFeeid->Integral(1, mChipErrorVsFeeid->GetXaxis()->GetNbins(), ierror + 1, ierror + 1); + mChipErrorPlots->SetBinContent(ierror + 1, feeChipError); + } + mTFCount++; +} + +void ITSDecodingErrorTask::getParameters() +{ + mBusyViolationLimit = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "mBusyViolationLimit", mBusyViolationLimit); + isDoLinkErrorReset = (bool)o2::quality_control_modules::common::getFromConfig(mCustomParameters, "isDoLinkErrorReset", isDoLinkErrorReset); +} + +void ITSDecodingErrorTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + hBusyFraction->Reset(); + hAlwaysBusy->Reset(); + + int binIterator = 0; + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + for (int iChip = 1; iChip <= nChipsPerLayer[iLayer]; iChip++) { + + if (iLayer > 2 && iChip == nChipsPerLayer[iLayer] / 2) + binIterator++; // to account for bot/top barrel + + int nBusyViolations = mChipErrorVsChipid[iLayer]->GetBinContent(iChip, 1); + if (1. * nBusyViolations / mTFCount > mBusyViolationLimit) { + hAlwaysBusy->Fill(binIterator); + continue; + } + if (nBusyViolations > 0) { + hBusyFraction->getNum()->Fill(binIterator, nBusyViolations); + } + } + hBusyFraction->getDen()->SetBinContent(binIterator + 1, mTFCount * nChipsPerLayer[iLayer]); + binIterator++; + } + hBusyFraction->update(); +} + +void ITSDecodingErrorTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSDecodingErrorTask::resetGeneralPlots() +{ + mTFCount++; + mChipErrorPlots->Reset(); + + if (isDoLinkErrorReset) { + for (int iFEE = 1; iFEE <= mLinkErrorVsFeeid->GetNbinsY(); iFEE++) { + for (int iError = 1; iError <= mLinkErrorVsFeeid->GetNbinsX(); iError++) { + DecErr_lastCycle[iFEE - 1][iError - 1] += mLinkErrorVsFeeid->GetBinContent(iFEE, iError); + } + } + } + mLinkErrorVsFeeid->Reset(); + mLinkErrorPlots->Reset(); + for (int ilayer = 0; ilayer < 7; ilayer++) { + mChipErrorVsChipid[ilayer]->Reset(); + } + mTFCount = 0; + hBusyFraction->Reset(); + hAlwaysBusy->Reset(); +} + +void ITSDecodingErrorTask::reset() +{ + resetGeneralPlots(); + ILOG(Debug, Devel) << "Reset" << ENDM; +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSFeeCheck.cxx b/Modules/ITS/src/ITSFeeCheck.cxx new file mode 100644 index 0000000000..7b48d56627 --- /dev/null +++ b/Modules/ITS/src/ITSFeeCheck.cxx @@ -0,0 +1,564 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSFeeCheck.cxx +/// \author LiAng Zhang +/// \author Jian Liu +/// \author Antonio Palasciano +/// + +#include "ITS/ITSFeeCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include + +#include +#include +#include "Common/Utils.h" + +namespace o2::quality_control_modules::its +{ + +Quality ITSFeeCheck::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + bool badStaveCount, badStaveIB, badStaveML, badStaveOL; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + for (int iflag = 0; iflag < NFlags; iflag++) { + if (mo->getName() == Form("LaneStatus/laneStatusFlag%s", mLaneStatusFlag[iflag].c_str())) { + result = Quality::Good; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast LaneStatusFlag to TH2I*" << ENDM; + continue; + } + if (h->GetMaximum() > 0) { + result.set(Quality::Bad); + } + } + if (mo->getName() == Form("LaneStatus/laneStatusOverviewFlag%s", mLaneStatusFlag[iflag].c_str())) { + result.set(Quality::Good); + auto* hp = dynamic_cast(mo->getObject()); + if (hp == nullptr) { + ILOG(Error, Support) << "could not cast laneStatusOverview to THPollyF*" << ENDM; + continue; + } + badStaveIB = false; + badStaveML = false; + badStaveOL = false; + // Initialization of metaData for IB, ML, OL + result.addMetadata("IB", "good"); + result.addMetadata("ML", "good"); + result.addMetadata("OL", "good"); + for (int ilayer = 0; ilayer < NLayer; ilayer++) { + int countStave = 0; + badStaveCount = false; + for (int ibin = StaveBoundary[ilayer] + 1; ibin <= StaveBoundary[ilayer + 1]; ++ibin) { + if (ibin <= StaveBoundary[3]) { + // Check if there are staves in the IB with lane in Bad (bins are filled with %) + maxbadchipsIB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxbadchipsIB", maxbadchipsIB); + if (hp->GetBinContent(ibin) > maxbadchipsIB / 9.) { + badStaveIB = true; + result.updateMetadata("IB", "medium"); + countStave++; + TString text = "Medium:IB stave has many NOK chips;"; + if (!checkReason(result, text)) + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), text.Data()); + } + } else if (ibin <= StaveBoundary[5]) { + // Check if there are staves in the MLs with at least 4 lanes in Bad (bins are filled with %) + maxbadlanesML = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxbadlanesML", maxbadlanesML); + if (hp->GetBinContent(ibin) > maxbadlanesML / (1. * NLanePerStaveLayer[ilayer])) { + badStaveML = true; + result.updateMetadata("ML", "medium"); + countStave++; + TString text = "Medium:ML stave has many NOK chips;"; + if (!checkReason(result, text)) + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), text.Data()); + } + } else if (ibin <= StaveBoundary[7]) { + // Check if there are staves in the OLs with at least 7 lanes in Bad (bins are filled with %) + maxbadlanesOL = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxbadlanesOL", maxbadlanesOL); + if (hp->GetBinContent(ibin) > maxbadlanesOL / (1. * NLanePerStaveLayer[ilayer])) { + badStaveOL = true; + result.updateMetadata("OL", "medium"); + countStave++; + TString text = "Medium:OL stave has many NOK chips;"; + if (!checkReason(result, text)) + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), text.Data()); + } + } + } // end loop bins (staves) + // Initialize metadata for the 7 layers + result.addMetadata(Form("Layer%d", ilayer), "good"); + // Check if there are more than 25% staves in Bad per layer + if (countStave > 0.25 * NStaves[ilayer]) { + badStaveCount = true; + result.updateMetadata(Form("Layer%d", ilayer), "bad"); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("BAD:Layer%d has many NOK staves;", ilayer)); + } + } // end loop over layers + if (badStaveIB || badStaveML || badStaveOL) { + result.set(Quality::Medium); + } + if (badStaveCount) { + result.set(Quality::Bad); + } + } // end lanestatusOverview + } + // Adding summary Plots Checks (General) + if (mo->getName() == "LaneStatusSummary/LaneStatusSummaryGlobal") { + result = Quality::Good; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast LaneStatusSummaryGlobal to TH1D*" << ENDM; + continue; + } + result.addMetadata("SummaryGlobal", "good"); + maxfractionbadlanes = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxfractionbadlanes", maxfractionbadlanes); + if (h->GetBinContent(1) + h->GetBinContent(2) + h->GetBinContent(3) > maxfractionbadlanes) { + result.updateMetadata("SummaryGlobal", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("BAD:>%.0f %% of the lanes are bad", (h->GetBinContent(1) + h->GetBinContent(2) + h->GetBinContent(3)) * 100)); + } + } // end summary loop + if (mo->getName() == Form("RDHSummary")) { + result = Quality::Good; + auto* h = dynamic_cast(mo->getObject()); + if (h->GetMaximum() > 0) { + result.set(Quality::Bad); + } + } + + if (((std::string)mo->getName()).find("TriggerVsFeeid") != std::string::npos) { + result.set(Quality::Good); + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast TriggerVsFeeid to TH2I*" << ENDM; + continue; + } + + std::vector skipbins = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "skipbinstrg", skipbinstrg)); + std::vector skipfeeid = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "skipfeeids", skipfeeids)); + maxtfdifference = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxTFdifferenceAllowed", maxtfdifference); + + for (int itrg = 1; itrg <= h->GetNbinsY(); itrg++) { + result.addMetadata(h->GetYaxis()->GetBinLabel(itrg), "good"); + } + + TString TrgAtLeastOne = "ORBIT HB PHYSICS TF"; + TString TrgExactlyOne = "SOC"; + // The others are requested to have no entries + + int min_n_tf = INT_MAX, max_n_tf = 0; + + for (int itrg = 0; itrg < h->GetNbinsY(); itrg++) { + if (std::find(skipbins.begin(), skipbins.end(), itrg + 1) != skipbins.end()) { + continue; + } + + bool badTrigger = false; + + TString trgname = (TString)(h->GetYaxis()->GetBinLabel(itrg + 1)); + + for (int ifee = 1; ifee <= h->GetNbinsX(); ifee++) { + + if (std::find(skipfeeid.begin(), skipfeeid.end(), ifee) != skipfeeid.end()) + continue; + + int bincontent = (int)(h->GetBinContent(ifee, itrg + 1)); + + // checking trigger flags supposed to have at least one entry + if (TrgAtLeastOne.Contains(trgname)) { + if (bincontent < 1) { + badTrigger = true; + break; + } + } + // checking trigger flags supposed to have exactly one entry + else if (TrgExactlyOne.Contains(trgname)) { + if (bincontent != 1) { + badTrigger = true; + break; + } + } + // checking trigger flags supposed to have no entries + else { + if (bincontent > 0) { + badTrigger = true; + break; + } + } + + if (trgname == "TF" && maxtfdifference > 0) { + min_n_tf = std::min(min_n_tf, bincontent); + max_n_tf = std::max(max_n_tf, bincontent); + } + } + + if (trgname == "TF" && maxtfdifference > 0 && (max_n_tf - min_n_tf > maxtfdifference)) + badTrigger = true; + + if (badTrigger) { + result.updateMetadata(h->GetYaxis()->GetBinLabel(itrg + 1), "bad"); + result.set(Quality::Bad); + std::string extraText = (!strcmp(h->GetYaxis()->GetBinLabel(itrg + 1), "PHYSICS")) ? "(OK if it's COSMICS/SYNTHETIC)" : ""; + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("BAD:Trigger flag %s of bad quality %s", h->GetYaxis()->GetBinLabel(itrg + 1), extraText.c_str())); + } + } + } + + if (mo->getName() == "PayloadSize") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast PayloadSize to TH2F*" << ENDM; + continue; + } + result.set(Quality::Good); + result.addMetadata("CheckTechnicals", "good"); + result.addMetadata("CheckTechnicalsFeeid", "good"); + std::vector skipfeeid = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "skipfeeids", skipfeeids)); + minPayloadSize = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "minPayloadSize", minPayloadSize); + if (h->Integral(1, 432, h->GetYaxis()->FindBin(minPayloadSize), h->GetYaxis()->GetLast()) > 0) { + result.set(Quality::Bad); + result.updateMetadata("CheckTechnicals", "bad"); + } + int countFeeids = 0; + for (int ix = 1; ix <= h->GetNbinsX(); ix++) { + for (int iy = 1; iy <= h->GetNbinsY(); iy++) { + if (h->GetBinContent(ix, iy) > 0) { + countFeeids++; + break; + } + } + } + if (countFeeids < 432 - (int)skipfeeid.size()) { + result.set(Quality::Bad); + result.updateMetadata("CheckTechnicalsFeeid", "bad"); + } + } + + if (((std::string)mo->getName()).find("TrailerCount") != std::string::npos) { + + auto activity = mo->getActivity(); + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast TrailerCount to TH2I*" << ENDM; + continue; + } + result.set(Quality::Good); + result.addMetadata("CheckROFRate", "good"); + expectedROFperOrbit = stoi(mCustomParameters.at("expectedROFperOrbit", activity)); + if (h->Integral(1, 432, 1, h->GetYaxis()->FindBin(expectedROFperOrbit) - 1) > 0 || h->Integral(1, 432, h->GetYaxis()->FindBin(expectedROFperOrbit) + 1, h->GetYaxis()->GetLast()) > 0) { + result.set(Quality::Bad); + result.updateMetadata("expectedROFperOrbit", "bad"); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "ITS seems to be misconfigured"); + } + } + } // end loop on MOs + return result; +} // end check + +void ITSFeeCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + std::vector vPlotWithTextMessage = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "plotWithTextMessage", "")); + std::vector vTextMessage = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "textMessage", "")); + std::map ShifterInfoText; + + if ((int)vTextMessage.size() == (int)vPlotWithTextMessage.size()) { + for (int i = 0; i < (int)vTextMessage.size(); i++) { + ShifterInfoText[vPlotWithTextMessage[i]] = vTextMessage[i]; + } + } else + + ILOG(Warning, Support) << "Bad list of plot with TextMessages for shifter, check .json" << ENDM; + + std::shared_ptr tShifterInfo = std::make_shared(0.005, 0.006, Form("#bf{%s}", TString(ShifterInfoText[mo->getName()]).Data())); + tShifterInfo->SetTextSize(0.04); + tShifterInfo->SetTextFont(43); + tShifterInfo->SetNDC(); + + TString status; + int textColor; + + for (int iflag = 0; iflag < NFlags; iflag++) { + if (mo->getName() == Form("LaneStatus/laneStatusFlag%s", mLaneStatusFlag[iflag].c_str())) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast LaneSatusFlag to TH2I*" << ENDM; + continue; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else if (checkResult == Quality::Bad) { + status = "Quality::BAD (call expert)"; + textColor = kRed; + } + tInfo = std::make_shared(0.12, 0.835, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.04); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + if (mo->getName() == Form("LaneStatus/laneStatusOverviewFlag%s", mLaneStatusFlag[iflag].c_str())) { + auto* hp = dynamic_cast(mo->getObject()); + if (hp == nullptr) { + ILOG(Error, Support) << "could not cast LaneSatusOverview to THPolly*" << ENDM; + continue; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else if (checkResult == Quality::Bad || checkResult == Quality::Medium) { + status = "Quality::BAD (call expert)"; + textColor = kRed; + if (strcmp(checkResult.getMetadata("IB").c_str(), "medium") == 0) { + status = "Quality::Medium (do not call, inform expert on MM)"; + textColor = kOrange; + maxbadchipsIB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxbadchipsIB", maxbadchipsIB); + tInfoIB = std::make_shared(0.40, 0.55, Form("Inner Barrel has stave(s) with >%d chips in %s", maxbadchipsIB, mLaneStatusFlag[iflag].c_str())); + tInfoIB->SetTextColor(kOrange); + tInfoIB->SetTextSize(0.03); + tInfoIB->SetTextFont(43); + tInfoIB->SetNDC(); + hp->GetListOfFunctions()->Add(tInfoIB->Clone()); + } + if (strcmp(checkResult.getMetadata("ML").c_str(), "medium") == 0) { + status = "Quality::Medium (do not call, inform expert on MM)"; + textColor = kOrange; + maxbadlanesML = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxbadlanesML", maxbadlanesML); + tInfoML = std::make_shared(0.42, 0.62, Form("ML have stave(s) with >%d lanes in %s", maxbadlanesML, mLaneStatusFlag[iflag].c_str())); + tInfoML->SetTextColor(kOrange); + tInfoML->SetTextSize(0.03); + tInfoML->SetTextFont(43); + tInfoML->SetNDC(); + hp->GetListOfFunctions()->Add(tInfoML->Clone()); + } + if (strcmp(checkResult.getMetadata("OL").c_str(), "medium") == 0) { + status = "Quality::Medium (do not call, inform expert on MM)"; + textColor = kOrange; + maxbadlanesOL = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxbadlanesOL", maxbadlanesOL); + tInfoOL = std::make_shared(0.415, 0.78, Form("OL have staves with >%d lanes in %s", maxbadlanesOL, mLaneStatusFlag[iflag].c_str())); + tInfoOL->SetTextColor(kOrange); + tInfoOL->SetTextSize(0.03); + tInfoOL->SetTextFont(43); + tInfoOL->SetNDC(); + hp->GetListOfFunctions()->Add(tInfoOL->Clone()); + } + for (int ilayer = 0; ilayer < NLayer; ilayer++) { + if (strcmp(checkResult.getMetadata(Form("Layer%d", ilayer)).c_str(), "bad") == 0) { + status = "Quality::Bad (call expert)"; + textColor = kRed; + maxbadchipsIB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxbadchipsIB", maxbadchipsIB); + maxbadlanesML = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxbadlanesML", maxbadlanesML); + maxbadlanesOL = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxbadlanesOL", maxbadlanesOL); + + int cut = ilayer < 3 ? maxbadchipsIB : ilayer < 5 ? maxbadlanesML + : maxbadlanesOL; + tInfoLayers[ilayer] = std::make_shared(0.37, minTextPosY[ilayer], Form("Layer %d has > 25%% staves with >%d %s in %s", ilayer, cut, ilayer < 3 ? "chips" : "lanes", mLaneStatusFlag[iflag].c_str())); + tInfoLayers[ilayer]->SetTextColor(kRed); + tInfoLayers[ilayer]->SetTextSize(0.03); + tInfoLayers[ilayer]->SetTextFont(43); + tInfoLayers[ilayer]->SetNDC(); + hp->GetListOfFunctions()->Add(tInfoLayers[ilayer]->Clone()); + } // end check result over layer + } // end of loop over layers + } + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.06); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + hp->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + hp->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + } // end flags + if (mo->getName() == "LaneStatusSummary/LaneStatusSummaryGlobal") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast LaneSatutsSummaryGlobal to TH1D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else if (checkResult == Quality::Bad) { + status = "Quality::BAD (call expert)"; + textColor = kRed; + if (strcmp(checkResult.getMetadata("SummaryGlobal").c_str(), "bad") == 0) { + maxfractionbadlanes = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "maxfractionbadlanes", maxfractionbadlanes); + tInfoSummary = std::make_shared(0.12, 0.5, Form(">%.0f %% of the lanes are bad", maxfractionbadlanes * 100)); + tInfoSummary->SetTextColor(kRed); + tInfoSummary->SetTextSize(0.05); + tInfoSummary->SetTextFont(43); + tInfoSummary->SetNDC(); + h->GetListOfFunctions()->Add(tInfoSummary->Clone()); + } + } + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.06); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + if (mo->getName() == Form("RDHSummary")) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast RDHSummary to TH2I*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else if (checkResult == Quality::Bad) { + status = "Quality::BAD (call expert)"; + textColor = kRed; + } + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.06); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + + // trigger plot + if (((std::string)mo->getName()).find("TriggerVsFeeid") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast TriggerVsFeeId to TH2I*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else if (checkResult == Quality::Bad) { + status = "Quality::BAD (call expert)"; + textColor = kRed; + for (int itrg = 1; itrg <= h->GetNbinsY(); itrg++) { + ILOG(Debug, Devel) << checkResult.getMetadata(h->GetYaxis()->GetBinLabel(itrg)).c_str() << ENDM; + if (strcmp(checkResult.getMetadata(h->GetYaxis()->GetBinLabel(itrg)).c_str(), "bad") == 0) { + std::string extraText = (!strcmp(h->GetYaxis()->GetBinLabel(itrg), "PHYSICS")) ? "(OK if it's COSMICS/SYNTHETIC)" : ""; + tInfoTrg[itrg - 1] = std::make_shared(0.3, 0.1 + 0.05 * (itrg - 1), Form("Trigger flag %s of bad quality %s", h->GetYaxis()->GetBinLabel(itrg), extraText.c_str())); + tInfoTrg[itrg - 1]->SetTextColor(kRed); + tInfoTrg[itrg - 1]->SetTextSize(0.03); + tInfoTrg[itrg - 1]->SetTextFont(43); + tInfoTrg[itrg - 1]->SetNDC(); + h->GetListOfFunctions()->Add(tInfoTrg[itrg - 1]->Clone()); + } + } + } + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.06); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + + // payload size plot + if (mo->getName() == "PayloadSize") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast PayloadSize to TH2F*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else { + status = "Quality::Bad (call expert)"; + textColor = kRed; + if (strcmp(checkResult.getMetadata("CheckTechnicals").c_str(), "bad") == 0) { + tInfoPL[0] = std::make_shared(0.3, 0.6, "Payload size too large for technical runs"); + tInfoPL[0]->SetNDC(); + tInfoPL[0]->SetTextFont(43); + tInfoPL[0]->SetTextSize(0.04); + tInfoPL[0]->SetTextColor(kRed); + h->GetListOfFunctions()->Add(tInfoPL[0]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckTechnicalsFeeid").c_str(), "bad") == 0) { + tInfoPL[1] = std::make_shared(0.3, 0.55, "Payload size is missing for some FeeIDs"); + tInfoPL[1]->SetNDC(); + tInfoPL[1]->SetTextFont(43); + tInfoPL[1]->SetTextSize(0.04); + tInfoPL[1]->SetTextColor(kRed); + h->GetListOfFunctions()->Add(tInfoPL[1]->Clone()); + } + } + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.06); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + + if (((std::string)mo->getName()).find("TrailerCount") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast TrailerCount to TH2F*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else { + status = "Quality::Bad (call expert)"; + textColor = kRed; + tInfoPL[1] = std::make_shared(0.3, 0.55, "MISCONFIGURATION. CALL EXPERTS."); + tInfoPL[1]->SetNDC(); + tInfoPL[1]->SetTextFont(43); + tInfoPL[1]->SetTextSize(0.04); + tInfoPL[1]->SetTextColor(kRed); + h->GetListOfFunctions()->Add(tInfoPL[1]->Clone()); + } + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextColor(textColor); + tInfo->SetTextSize(0.06); + tInfo->SetTextFont(43); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } +} + +bool ITSFeeCheck::checkReason(Quality checkResult, TString text) +{ + auto flags = checkResult.getFlags(); + for (int i = 0; i < int(flags.size()); i++) { + if (text.Contains(flags[i].second.c_str())) + return true; + } + return false; +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSFeeTask.cxx b/Modules/ITS/src/ITSFeeTask.cxx new file mode 100644 index 0000000000..c94ffdbd94 --- /dev/null +++ b/Modules/ITS/src/ITSFeeTask.cxx @@ -0,0 +1,805 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSFeeTask.cxx +/// \author Jian Liu +/// \author Liang Zhang +/// \author Pietro Fecchio +/// \author Antonio Palasciano +/// + +#include "ITS/ITSFeeTask.h" +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" + +#include +#include + +using namespace o2::framework; +using namespace o2::header; + +namespace o2::quality_control_modules::its +{ + +ITSFeeTask::ITSFeeTask() + : TaskInterface() +{ +} + +ITSFeeTask::~ITSFeeTask() +{ + delete mTFInfo; + delete mTrigger; + delete mTriggerVsFeeId; + delete mTriggerVsFeeId_reset; + delete mFlag1Check; + delete mDecodingCheck; + delete mProcessingTime; + delete mPayloadSize; + delete mLaneStatusSummaryIB; + delete mLaneStatusSummaryML; + delete mLaneStatusSummaryOL; + delete mLaneStatusSummaryGlobal; + delete mRDHSummary; + delete mRDHSummaryCumulative; + delete mTrailerCount; + delete mTrailerCount_reset; + delete mCalibrationWordCount; + delete mCalibStage; + delete mCalibLoop; + delete mActiveLanes; + for (int i = 0; i < NFlags; i++) { + delete mLaneStatus[i]; + delete mLaneStatusCumulative[i]; + } + for (int i = 0; i < 2; i++) { + delete mLaneStatusOverview[i]; + } + for (int i = 0; i < NLayer; i++) { + delete mLaneStatusSummary[i]; + } + + // delete mInfoCanvas; +} + +void ITSFeeTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initializing the ITSFeeTask" << ENDM; + getParameters(); + createFeePlots(); + setPlotsFormat(); +} + +void ITSFeeTask::createFeePlots() +{ + + mTrigger = new TH1I("TriggerFlag", "Trigger vs counts", mTriggerType.size(), 0.5, mTriggerType.size() + 0.5); + getObjectsManager()->startPublishing(mTrigger); // mTrigger + + mTFInfo = new TH1I("STFInfo", "STF vs count", 10000, 0, 10000); + getObjectsManager()->startPublishing(mTFInfo); // mTFInfo + + mProcessingTime = new TH1I("ProcessingTime", "Processing Time", 10000, 0, 10000); // last bin: overflow + getObjectsManager()->startPublishing(mProcessingTime); // mProcessingTime + + mProcessingTime2 = new TH1D("ProcessingTime2", "Processing Time (last bin: overflow)", 30001, 0, 120004); // last bin: overflow + getObjectsManager()->startPublishing(mProcessingTime2); // mProcessingTime + + mTriggerVsFeeId = new TH2I("TriggerVsFeeid", "Trigger count vs Trigger ID and Fee ID", NFees, 0, NFees, mTriggerType.size(), 0.5, mTriggerType.size() + 0.5); + getObjectsManager()->startPublishing(mTriggerVsFeeId); // mTriggervsFeeId + + mTriggerVsFeeId_reset = new TH2I("TriggerVsFeeid_reset", "Trigger count vs Trigger ID and Fee ID", NFees, 0, NFees, mTriggerType.size(), 0.5, mTriggerType.size() + 0.5); + getObjectsManager()->startPublishing(mTriggerVsFeeId_reset); // mTriggervsFeeId + + for (int i = 0; i < NFlags; i++) { + mLaneStatus[i] = new TH2I(Form("LaneStatus/laneStatusFlag%s", mLaneStatusFlag[i].c_str()), Form("Lane Status Flag: %s", mLaneStatusFlag[i].c_str()), NFees, 0, NFees, NLanesMax, 0, NLanesMax); + mLaneStatusCumulative[i] = new TH2I(Form("LaneStatus/laneStatusFlagCumulative%s", mLaneStatusFlag[i].c_str()), Form("Lane Status Flags since SOX: %s", mLaneStatusFlag[i].c_str()), NFees, 0, NFees, NLanesMax, 0, NLanesMax); + getObjectsManager()->startPublishing(mLaneStatus[i]); // mlaneStatus + getObjectsManager()->startPublishing(mLaneStatusCumulative[i]); // mlaneStatus + } + + mLaneStatusOverview[0] = new TH2Poly(); + mLaneStatusOverview[0]->SetName("LaneStatus/laneStatusOverviewFlagWARNING"); + + mLaneStatusOverview[1] = new TH2Poly(); + mLaneStatusOverview[1]->SetName("LaneStatus/laneStatusOverviewFlagERROR"); + + for (int i = 0; i < NLayer; i++) { + mLaneStatusSummary[i] = new TH1I(Form("LaneStatusSummary/LaneStatusSummaryL%i", i), Form("Lane Status Summary L%i", i), 3, 0, 3); + getObjectsManager()->startPublishing(mLaneStatusSummary[i]); // mLaneStatusSummary + } + + mLaneStatusSummaryIB = new TH1D("LaneStatusSummary/LaneStatusSummaryIB", "Lane Status Summary IB", 3, 0, 3); + getObjectsManager()->startPublishing(mLaneStatusSummaryIB); // mLaneStatusSummaryIB + mLaneStatusSummaryML = new TH1D("LaneStatusSummary/LaneStatusSummaryML", "Lane Status Summary ML", 3, 0, 3); + getObjectsManager()->startPublishing(mLaneStatusSummaryML); // mLaneStatusSummaryML + mLaneStatusSummaryOL = new TH1D("LaneStatusSummary/LaneStatusSummaryOL", "Lane Status Summary OL", 3, 0, 3); + getObjectsManager()->startPublishing(mLaneStatusSummaryOL); // mLaneStatusSummaryOL + + mLaneStatusSummaryGlobal = new TH1D("LaneStatusSummary/LaneStatusSummaryGlobal", "Lane Status Summary Global", 4, 0, 4); + mLaneStatusSummaryGlobal->SetMaximum(1); + TLine* mLaneStatusSummaryLine = new TLine(0, 0.1, 4, 0.1); + mLaneStatusSummaryLine->SetLineStyle(9); + mLaneStatusSummaryLine->SetLineColor(kRed); + + TLatex* mLaneStatusSummaryInfo = new TLatex(0.1, 0.11, Form("#bf{%s}", "Threshold value")); + mLaneStatusSummaryInfo->SetTextSize(0.05); + mLaneStatusSummaryInfo->SetTextFont(43); + mLaneStatusSummaryInfo->SetTextColor(kRed); + + mLaneStatusSummaryGlobal->GetListOfFunctions()->Add(mLaneStatusSummaryLine); + mLaneStatusSummaryGlobal->GetListOfFunctions()->Add(mLaneStatusSummaryInfo); + getObjectsManager()->startPublishing(mLaneStatusSummaryGlobal); // mLaneStatusSummaryGlobal + + mFlag1Check = new TH2I("Flag1Check", "Flag 1 Check", NFees, 0, NFees, 3, 0, 3); // Row 1 : transmission_timeout, Row 2 : packet_overflow, Row 3 : lane_starts_violation + getObjectsManager()->startPublishing(mFlag1Check); // mFlag1Check + + mDecodingCheck = new TH2I("DecodingCheck", "Error in parsing data", NFees, 0, NFees, 6, 0, 6); // 0: DataFormat not recognized, 1: DDW index != 0, 2: DDW wrong identifier, 3: IHW wrong identifier, 4: CDW wrong version, 5: Empty Payload -- adapt y range! + getObjectsManager()->startPublishing(mDecodingCheck); + + mPayloadSize = new TH2F("PayloadSize", "Payload Size", NFees, 0, NFees, mNPayloadSizeBins, 0, 4.096e5); + getObjectsManager()->startPublishing(mPayloadSize); // mPayloadSize + + mRDHSummary = new TH2I("RDHSummary", "Detector field in first and last page", NFees, 0, NFees, mRDHDetField.size(), 0, mRDHDetField.size()); + getObjectsManager()->startPublishing(mRDHSummary); + + mRDHSummaryCumulative = new TH2I("RDHSummaryCumulative", "Detector field in first and last page, since SOX", NFees, 0, NFees, mRDHDetField.size(), 0, mRDHDetField.size()); + getObjectsManager()->startPublishing(mRDHSummaryCumulative); + + mTrailerCount = new TH2I("TrailerCount", "Internal triggers per Orbit", NFees, 0, NFees, 21, -1, 20); // negative value if #ROF exceeds 20 + getObjectsManager()->startPublishing(mTrailerCount); + + mTrailerCount_reset = new TH2I("TrailerCount_reset", "Internal triggers per Orbit for last TF", NFees, 0, NFees, 21, -1, 20); // negative value if #ROF exceeds 20 + getObjectsManager()->startPublishing(mTrailerCount_reset); + + mActiveLanes = new TH2I("ActiveLanes", "Number of lanes enabled in IHW", NFees, 0, NFees, NLanesMax, 0, NLanesMax); + getObjectsManager()->startPublishing(mActiveLanes); + + mCalibrationWordCount = new TH1I("CalibrationWordCount", "Calibration Data Word count", NFees, 0, NFees); + + mCalibStage = new TH2I("CalibStage", "Stage in calib scan (chip row number)", NFees, 0, NFees, 512, 0, 512); + + mCalibLoop = new TH2I("CalibLoop", "Calib loop (register value)", NFees, 0, NFees, 256, 0, 256); + + if (mDecodeCDW) { + getObjectsManager()->startPublishing(mCalibrationWordCount); + getObjectsManager()->startPublishing(mCalibLoop); + getObjectsManager()->startPublishing(mCalibStage); + } +} + +void ITSFeeTask::setAxisTitle(TH1* object, const char* xTitle, const char* yTitle) +{ + object->GetXaxis()->SetTitle(xTitle); + object->GetYaxis()->SetTitle(yTitle); +} + +void ITSFeeTask::drawLayerName(TH2* histo2D) +{ + TLatex* t[NLayer]; + double minTextPosX[NLayer] = { 1, 42, 92, 150, 205, 275, 370 }; + for (int ilayer = 0; ilayer < NLayer; ilayer++) { + t[ilayer] = new TLatex(minTextPosX[ilayer], 28.3, Form("Layer %d", ilayer)); + histo2D->GetListOfFunctions()->Add(t[ilayer]); + } + for (const int& lay : LayerBoundaryFEE) { + auto l = new TLine(lay, 0, lay, histo2D->GetNbinsY()); + histo2D->GetListOfFunctions()->Add(l); + } +} + +void ITSFeeTask::setPlotsFormat() +{ + if (mTrigger) { + setAxisTitle(mTrigger, "Trigger ID", "Counts"); + mTrigger->SetMinimum(0); + mTrigger->SetFillColor(kBlue); + for (int i = 0; i < mTriggerType.size(); i++) { + mTrigger->GetXaxis()->SetBinLabel(i + 1, mTriggerType.at(i).second); + } + } + + if (mTFInfo) { + setAxisTitle(mTFInfo, "STF ID", "Counts"); + } + + if (mTriggerVsFeeId) { + setAxisTitle(mTriggerVsFeeId, "FeeID", "Trigger ID"); + mTriggerVsFeeId->SetMinimum(0); + mTriggerVsFeeId->SetStats(0); + for (int i = 0; i < mTriggerType.size(); i++) { + mTriggerVsFeeId->GetYaxis()->SetBinLabel(i + 1, mTriggerType.at(i).second); + } + setAxisTitle(mTriggerVsFeeId_reset, "FeeID", "Trigger ID"); + mTriggerVsFeeId_reset->SetMinimum(0); + mTriggerVsFeeId_reset->SetStats(0); + for (int i = 0; i < mTriggerType.size(); i++) { + mTriggerVsFeeId_reset->GetYaxis()->SetBinLabel(i + 1, mTriggerType.at(i).second); + } + } + + if (mProcessingTime) { + setAxisTitle(mProcessingTime, "STF", "Time (us)"); + } + + if (mProcessingTime2) { + setAxisTitle(mProcessingTime2, "Time (us)", "STF count"); + } + + if (mRDHSummary) { + setAxisTitle(mRDHSummary, "QC FEEId", ""); + mRDHSummary->SetStats(0); + for (int idf = 0; idf < mRDHDetField.size(); idf++) { + mRDHSummary->GetYaxis()->SetBinLabel(idf + 1, mRDHDetField.at(idf).second); + } + drawLayerName(mRDHSummary); + } + + if (mRDHSummaryCumulative) { + setAxisTitle(mRDHSummaryCumulative, "QC FEEId", ""); + mRDHSummaryCumulative->SetStats(0); + for (int idf = 0; idf < mRDHDetField.size(); idf++) { + mRDHSummaryCumulative->GetYaxis()->SetBinLabel(idf + 1, mRDHDetField.at(idf).second); + } + drawLayerName(mRDHSummaryCumulative); + } + + if (mTrailerCount) { + setAxisTitle(mTrailerCount, "QC FEEId", "Estimated ROF frequenccy"); + mTrailerCount->SetStats(0); + mTrailerCount->GetYaxis()->SetBinLabel(3, "11 kHz"); + mTrailerCount->GetYaxis()->SetBinLabel(6, "45 kHz"); + mTrailerCount->GetYaxis()->SetBinLabel(8, "67 kHz"); + mTrailerCount->GetYaxis()->SetBinLabel(11, "101 kHz"); + mTrailerCount->GetYaxis()->SetBinLabel(14, "135 kHz"); + mTrailerCount->GetYaxis()->SetBinLabel(20, "202 kHz"); + + setAxisTitle(mTrailerCount_reset, "QC FEEId", "Estimated ROF frequenccy"); + mTrailerCount_reset->SetStats(0); + mTrailerCount_reset->GetYaxis()->SetBinLabel(3, "11 kHz"); + mTrailerCount_reset->GetYaxis()->SetBinLabel(6, "45 kHz"); + mTrailerCount_reset->GetYaxis()->SetBinLabel(8, "67 kHz"); + mTrailerCount_reset->GetYaxis()->SetBinLabel(11, "101 kHz"); + mTrailerCount_reset->GetYaxis()->SetBinLabel(14, "135 kHz"); + mTrailerCount_reset->GetYaxis()->SetBinLabel(20, "202 kHz"); + } + + if (mCalibrationWordCount) { + setAxisTitle(mCalibrationWordCount, "QC FEEId", "Number of decoded CDWs"); + } + + if (mCalibStage) { + setAxisTitle(mCalibStage, "QC FEEId", "Chip row number"); + } + + if (mCalibLoop) { + setAxisTitle(mCalibLoop, "QC FEEId", "DAC register value"); + } + + if (mActiveLanes) { + setAxisTitle(mActiveLanes, "QC FEEId", "Number of enabled lanes"); + } + + for (int i = 0; i < NFlags; i++) { + if (mLaneStatus[i]) { + setAxisTitle(mLaneStatus[i], "QC FEEId", "Lane"); + mLaneStatus[i]->SetStats(0); + drawLayerName(mLaneStatus[i]); + setAxisTitle(mLaneStatusCumulative[i], "QC FEEId", "Lane"); + mLaneStatusCumulative[i]->SetStats(0); + drawLayerName(mLaneStatusCumulative[i]); + } + } + + for (int i = 0; i < 2; i++) { + TString title = (i == 0) ? "Fraction of lanes into WARNING" : "Fraction of lanes in Not OK status"; + title += ";mm (IB 3x);mm (IB 3x)"; + mLaneStatusOverview[i]->SetTitle(title); + mLaneStatusOverview[i]->SetStats(0); + mLaneStatusOverview[i]->SetOption("lcolz"); + mLaneStatusOverview[i]->SetMinimum(0); + mLaneStatusOverview[i]->SetMaximum(1); + mLaneStatusOverview[i]->SetBit(TH1::kIsAverage); + for (int ilayer = 0; ilayer < 7; ilayer++) { + for (int istave = 0; istave < NStaves[ilayer]; istave++) { + double* px = new double[4]; + double* py = new double[4]; + getStavePoint(ilayer, istave, px, py); + if (ilayer < 3) { + for (int icoo = 0; icoo < 4; icoo++) { + px[icoo] *= 3.; + py[icoo] *= 3.; + } + } + mLaneStatusOverview[i]->AddBin(4, px, py); + } + } + getObjectsManager()->startPublishing(mLaneStatusOverview[i]); // mLaneStatusOverview + } + + for (int i = 0; i < NLayer; i++) { + if (mLaneStatusSummary[i]) { + setAxisTitle(mLaneStatusSummary[i], "", "#Lanes"); + for (int j = 0; j < NFlags; j++) { + mLaneStatusSummary[i]->GetXaxis()->SetBinLabel(j + 1, mLaneStatusFlag[j].c_str()); + } + mLaneStatusSummary[i]->GetXaxis()->CenterLabels(); + mLaneStatusSummary[i]->SetStats(0); + } + } + + if (mLaneStatusSummaryIB) { + setAxisTitle(mLaneStatusSummaryIB, "", "Fraction of Lanes"); + for (int i = 0; i < NFlags; i++) { + mLaneStatusSummaryIB->GetXaxis()->SetBinLabel(i + 1, mLaneStatusFlag[i].c_str()); + } + mLaneStatusSummaryIB->GetXaxis()->CenterLabels(); + mLaneStatusSummaryIB->SetStats(0); + } + + if (mLaneStatusSummaryML) { + setAxisTitle(mLaneStatusSummaryML, "", "Fraction of Lanes"); + for (int i = 0; i < NFlags; i++) { + mLaneStatusSummaryML->GetXaxis()->SetBinLabel(i + 1, mLaneStatusFlag[i].c_str()); + } + mLaneStatusSummaryML->GetXaxis()->CenterLabels(); + mLaneStatusSummaryML->SetStats(0); + } + + if (mLaneStatusSummaryOL) { + setAxisTitle(mLaneStatusSummaryOL, "", "Fraction of Lanes"); + for (int i = 0; i < NFlags; i++) { + mLaneStatusSummaryOL->GetXaxis()->SetBinLabel(i + 1, mLaneStatusFlag[i].c_str()); + } + mLaneStatusSummaryOL->GetXaxis()->CenterLabels(); + mLaneStatusSummaryOL->SetStats(0); + } + + if (mLaneStatusSummaryGlobal) { + setAxisTitle(mLaneStatusSummaryGlobal, "", "Fraction Lanes"); + for (int i = 0; i < NFlags; i++) { + mLaneStatusSummaryGlobal->GetXaxis()->SetBinLabel(i + 1, mLaneStatusFlag[i].c_str()); + } + mLaneStatusSummaryGlobal->GetXaxis()->SetBinLabel(4, "TOTAL"); + mLaneStatusSummaryGlobal->GetXaxis()->CenterLabels(); + mLaneStatusSummaryGlobal->SetStats(0); + } + + if (mFlag1Check) { + setAxisTitle(mFlag1Check, "QC FEEId", "Flag"); + } + + if (mDecodingCheck) { + setAxisTitle(mDecodingCheck, "QC FEEId", "Error ID"); + } + + if (mPayloadSize) { + setAxisTitle(mPayloadSize, "QC FEEId", "Avg. Payload size"); + mPayloadSize->SetStats(0); + drawLayerName(mPayloadSize); + } +} + +void ITSFeeTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity : " << activity.mId << ENDM; +} + +void ITSFeeTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + + if (nCycleID % nResetCycle == 0) { + mTrailerCount_reset->Reset(); + mTriggerVsFeeId_reset->Reset(); + } + nCycleID++; +} + +void ITSFeeTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + // set timer + std::chrono::time_point start; + std::chrono::time_point end; + int difference; + start = std::chrono::high_resolution_clock::now(); + + int nStops[NFees] = {}; + int payloadTot[NFees] = {}; + int TDTcounter[NFees] = {}; + + DPLRawParser parser(ctx.inputs()); + + resetLanePlotsAndCounters(false); // not full reset // action taken depending on mResetLaneStatus and mResetPayload + + // manual call of DPL data iterator to catch exceptoin: + try { + auto it = parser.begin(); + } catch (const std::runtime_error& error) { + ILOG(Error, Support) << "Error during parsing DPL data: " << error.what() << ENDM; + return; + } + + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + + auto rdh = reinterpret_cast(it.raw()); + // Decoding data format (RDHv* --> v6 and v7 have same bits for what is considered here) + auto feeID = o2::raw::RDHUtils::getFEEID(rdh); + int istave = (int)(feeID & 0x00ff); + int ilink = (int)((feeID & 0x0f00) >> 8); + int ilayer = (int)((feeID & 0xf000) >> 12); + int ifee = 3 * StaveBoundary[ilayer] - (StaveBoundary[ilayer] - StaveBoundary[NLayerIB]) * (ilayer >= NLayerIB) + istave * (3 - (ilayer >= NLayerIB)) + ilink; + int memorysize = (int)(o2::raw::RDHUtils::getMemorySize(rdh)); + int headersize = o2::raw::RDHUtils::getHeaderSize(rdh); + payloadTot[ifee] += memorysize - headersize; + bool clockEvt = false; + bool RecoveryOngoing = false; + bool RampOngoing = false; + + // Operations at first or last page of the orbit: + // - detector field decoding + if ((int)(o2::raw::RDHUtils::getStop(rdh)) || (int)(o2::raw::RDHUtils::getPageCounter(rdh)) == 0) { + + uint32_t summaryLaneStatus = o2::raw::RDHUtils::getDetectorField(rdh); + + for (int ibin = 0; ibin < mRDHDetField.size(); ibin++) { + if (summaryLaneStatus & (1 << mRDHDetField.at(ibin).first)) { + mRDHSummary->Fill(ifee, ibin); + mRDHSummaryCumulative->Fill(ifee, ibin); + TString description = mRDHDetField.at(ibin).second; + if (description == "ClockEvt") { + clockEvt = true; + } + if (description == "TriggerRamp") { + RampOngoing = true; + } + if (description == "Recovery") { + RampOngoing = RecoveryOngoing = true; + } + } + } + } + + // Operations at any page inside the enabled orbit: + // - decoding identifier of each payload word and increasing counter of TDTs (if enabled) + // - decoding identifier of each payload word and decode full CDWs (if enabled) + + bool doLookForTDT = (mPayloadParseEvery_n_HBF_per_TF > 0) && (nStops[ifee] % mPayloadParseEvery_n_HBF_per_TF == 0) && (mTimeFrameId % mPayloadParseEvery_n_TF == 0); + + if (doLookForTDT || mDecodeCDW) { + int dataformat = (int)o2::raw::RDHUtils::getDataFormat(rdh); + if (dataformat != 0 && dataformat != 2) { + mDecodingCheck->Fill(ifee, 0); + } + auto const* payload = it.data(); + size_t payloadSize = it.size(); + int PayloadPerGBTW = (dataformat < 2) ? 16 : 10; + int PaddingBytes = PayloadPerGBTW - 10; + const uint16_t* gbtw_bb; // identifier and byte before + + for (int32_t ip = PayloadPerGBTW; ip <= payloadSize; ip += PayloadPerGBTW) { + gbtw_bb = (const uint16_t*)&payload[ip - PaddingBytes - 2]; + if (doLookForTDT && (*gbtw_bb & 0xff01) == 0xf001) { // checking that it is a TDT (0xf0) with packet_done (0x1) + TDTcounter[ifee]++; + } + if (mDecodeCDW && (*gbtw_bb & 0xff00) == 0xf800) { // chedking that it is a CDW (0xf8) + const CalibrationWordUserField* cdw; + try { + cdw = reinterpret_cast(&payload[ip - PayloadPerGBTW]); + } catch (const std::runtime_error& error) { + ILOG(Error, Support) << "Error during reading of calibration data word: " << error.what() << ENDM; + return; + } + + mCalibrationWordCount->Fill(ifee); + if (cdw->userField2.content.cdwver == 1) { + mCalibStage->Fill(ifee, (int)(cdw->userField0.content.rowid)); + mCalibLoop->Fill(ifee, (int)(cdw->userField1.content.loopvalue)); + } else { // TODO: add compatibility to older versions of CDW + mDecodingCheck->Fill(ifee, 4); + } + + } // if mDecodeCDW and it is CDW + + } // loop payload + + } // if doLookForTDT || mDecodeCDW + + // Check on empty payload + if (!it.size()) { + mDecodingCheck->Fill(ifee, 5); + } + + // Operations at the first page of each orbit + // - decoding ITS header work and fill histogram with number of active lanes + if (mEnableIHWReading) { + if ((int)(o2::raw::RDHUtils::getPageCounter(rdh)) == 0) { + const GBTITSHeaderWord* ihw; + try { + ihw = reinterpret_cast(it.data()); + } catch (const std::runtime_error& error) { + ILOG(Error, Support) << "Error during reading of its header data: " << error.what() << ENDM; + return; + } + + uint8_t ihwID = ihw->indexWord.indexBits.id; + + if (ihwID != 0xe0) { + mDecodingCheck->Fill(ifee, 3); + } + + uint32_t activelaneMask = ihw->IHWcontent.laneBits.activeLanes; + + int nactivelanes = 0; + for (int i = 0; i < NLanesMax; i++) { + nactivelanes += ((activelaneMask >> i) & 0x1); + } + + // if (!RecoveryOngoing) use it if we want a cleaner situation for the checker + mActiveLanes->Fill(ifee, nactivelanes); + } + } + + // Operations at last page of each orbit: + + // - decoding Diagnostic Word DDW0 and fill lane status plots and vectors + if ((int)(o2::raw::RDHUtils::getStop(rdh)) && it.size()) { + + // - read triggers in RDH and fill histogram + // - fill histogram with packet_done TDTs counted so far and reset counter + // fill trailer count histo and reset counters + if (doLookForTDT) { + + if (!RampOngoing && !clockEvt) { + mTrailerCount->Fill(ifee, TDTcounter[ifee] < 21 ? TDTcounter[ifee] : -1); + mTrailerCount_reset->Fill(ifee, TDTcounter[ifee] < 21 ? TDTcounter[ifee] : -1); + } + TDTcounter[ifee] = 0; + } + + nStops[ifee]++; + for (int i = 0; i < mTriggerType.size(); i++) { + if (((o2::raw::RDHUtils::getTriggerType(rdh)) >> mTriggerType.at(i).first & 1) == 1) { + mTrigger->Fill(i + 1); + mTriggerVsFeeId->Fill(ifee, i + 1); + mTriggerVsFeeId_reset->Fill(ifee, i + 1); + } + } + + if (precisePayload) { + mPayloadSize->Fill(ifee, payloadTot[ifee]); + payloadTot[ifee] = 0; + } + + const GBTDiagnosticWord* ddw; + try { + ddw = reinterpret_cast(it.data()); + } catch (const std::runtime_error& error) { + ILOG(Error, Support) << "Error during reading late diagnostic data: " << error.what() << ENDM; + return; + } + + uint64_t laneInfo = ddw->laneWord.laneBits.laneStatus; + + uint8_t flag1 = ddw->indexWord.indexBits.flag1; + for (int i = 0; i < 3; i++) { + if (flag1 >> i & 0x1) { + mFlag1Check->Fill(ifee, i); + } + } + + uint8_t index = ddw->indexWord.indexBits.index; + if (index != 0) { + mDecodingCheck->Fill(ifee, 1); + } + + uint8_t id = ddw->indexWord.indexBits.id; + if (id != 0xe4) { + mDecodingCheck->Fill(ifee, 2); + } + + for (int i = 0; i < NLanesMax; i++) { + int laneValue = laneInfo >> (2 * i) & 0x3; + if (laneValue) { + mStatusFlagNumber[ilayer][istave][i][laneValue - 1]++; + mLaneStatus[laneValue - 1]->Fill(ifee, i); + mLaneStatusCumulative[laneValue - 1]->Fill(ifee, i); + } + } + + for (int iflag = 0; iflag < 3; iflag++) { + if (clockEvt) { + int feeInStave = (ifee - feeBoundary[ilayer]) - (feePerStave[ilayer] * (istave)); + int startingLane = (feeInStave - 1) * lanesPerFeeId[ilayer]; + for (int indexLaneFee = indexFeeLow[ilayer]; indexLaneFee < indexFeeUp[ilayer]; indexLaneFee++) { + mLaneStatus[iflag]->Fill(ifee, indexLaneFee); + mLaneStatusCumulative[iflag]->Fill(ifee, indexLaneFee); + } + for (int indexLane = startingLane; indexLane < (startingLane + lanesPerFeeId[ilayer]); indexLane++) { + mStatusFlagNumber[ilayer][istave][indexLane][iflag]++; + } + } + } + } + } + + // Filling histograms: loop over mStatusFlagNumber[ilayer][istave][ilane][iflag] + int counterSummary[4][3] = { { 0 } }; + int layerSummary[7][3] = { { 0 } }; + + int mapLayerToBarrel[7] = { 1, 1, 1, 2, 2, 3, 3 }; + + for (int iflag = 0; iflag < NFlags; iflag++) { + for (int ilayer = 0; ilayer < NLayer; ilayer++) { + for (int istave = 0; istave < NStaves[ilayer]; istave++) { + for (int ilane = 0; ilane < NLanesMax; ilane++) { + if (mStatusFlagNumber[ilayer][istave][ilane][iflag] > 0) { + counterSummary[0][iflag]++; + counterSummary[mapLayerToBarrel[ilayer]][iflag]++; // IB, ML, OL + layerSummary[ilayer][iflag]++; + } + } + } + mLaneStatusSummary[ilayer]->SetBinContent(iflag + 1, layerSummary[ilayer][iflag]); + } + mLaneStatusSummaryGlobal->SetBinContent(iflag + 1, 1. * counterSummary[0][iflag] / NLanesTotal); + mLaneStatusSummaryIB->SetBinContent(iflag + 1, 1. * counterSummary[1][iflag] / NLanesIB); + mLaneStatusSummaryML->SetBinContent(iflag + 1, 1. * counterSummary[2][iflag] / NLanesML); + mLaneStatusSummaryOL->SetBinContent(iflag + 1, 1. * counterSummary[3][iflag] / NLanesOL); + } + mLaneStatusSummaryGlobal->SetBinContent(4, 1. * (counterSummary[0][0] + counterSummary[0][1] + counterSummary[0][2]) / NLanesTotal); + + for (int ilayer = 0; ilayer < NLayer; ilayer++) { + for (int istave = 0; istave < NStaves[ilayer]; istave++) { + int countWarning = 0; + int countNOK = 0; + for (int ilane = 0; ilane < NLanesMax; ilane++) { + if (mStatusFlagNumber[ilayer][istave][ilane][0] > 0) + countWarning++; + if (mStatusFlagNumber[ilayer][istave][ilane][1] > 0 || (mStatusFlagNumber[ilayer][istave][ilane][2] > 0)) + countNOK++; + } + mLaneStatusOverview[0]->SetBinContent(istave + 1 + StaveBoundary[ilayer], (float)(countWarning) / (float)(NLanePerStaveLayer[ilayer])); + mLaneStatusOverview[0]->SetBinError(istave + 1 + StaveBoundary[ilayer], 1e-15); + mLaneStatusOverview[1]->SetBinContent(istave + 1 + StaveBoundary[ilayer], (float)(countNOK) / (float)(NLanePerStaveLayer[ilayer])); + mLaneStatusOverview[1]->SetBinError(istave + 1 + StaveBoundary[ilayer], 1e-15); + } + } + + if (!precisePayload) { + for (int i = 0; i < NFees; i++) { + if (nStops[i]) { + float payloadAvg = (float)payloadTot[i] / nStops[i]; + mPayloadSize->Fill(i, payloadAvg); + } + } + } + + mTimeFrameId++; + mTFInfo->Fill(mTimeFrameId % 10000); + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - start).count(); + mProcessingTime2->Fill(difference < 120000 ? difference : 120001); + if (mTimeFrameId < 10000) { + mProcessingTime->SetBinContent(mTimeFrameId, difference); + } +} + +void ITSFeeTask::getParameters() +{ + mNPayloadSizeBins = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "NPayloadSizeBins", mNPayloadSizeBins); + mResetLaneStatus = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "ResetLaneStatus", mResetLaneStatus); + mResetPayload = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "ResetPayload", mResetPayload); + mPayloadParseEvery_n_HBF_per_TF = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "PayloadParsingEvery_n_HBFperTF", mPayloadParseEvery_n_HBF_per_TF); + mPayloadParseEvery_n_TF = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "PayloadParsingEvery_n_TF", mPayloadParseEvery_n_TF); + mEnableIHWReading = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "EnableIHWReading", mEnableIHWReading); + mDecodeCDW = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "DecodeCDW", mDecodeCDW); + nResetCycle = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nResetCycle", nResetCycle); + precisePayload = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "precisePayload", precisePayload); +} + +void ITSFeeTask::getStavePoint(int layer, int stave, double* px, double* py) +{ + float stepAngle = TMath::Pi() * 2 / NStaves[layer]; // the angle between to stave + float midAngle = StartAngle[layer] + (stave * stepAngle); // mid point angle + float staveRotateAngle = TMath::Pi() / 2 - (stave * stepAngle); // how many angle this stave rotate(compare with first stave) + px[1] = MidPointRad[layer] * TMath::Cos(midAngle); // there are 4 point to decide this TH2Poly bin + // 0:left point in this stave; + // 1:mid point in this stave; + // 2:right point in this stave; + // 3:higher point int this stave; + py[1] = MidPointRad[layer] * TMath::Sin(midAngle); // 4 point calculated accord the blueprint + // roughly calculate + if (layer < NLayerIB) { + px[0] = 7.7 * TMath::Cos(staveRotateAngle) + px[1]; + py[0] = -7.7 * TMath::Sin(staveRotateAngle) + py[1]; + px[2] = -7.7 * TMath::Cos(staveRotateAngle) + px[1]; + py[2] = 7.7 * TMath::Sin(staveRotateAngle) + py[1]; + px[3] = 5.623 * TMath::Sin(staveRotateAngle) + px[1]; + py[3] = 5.623 * TMath::Cos(staveRotateAngle) + py[1]; + } else { + px[0] = 21 * TMath::Cos(staveRotateAngle) + px[1]; + py[0] = -21 * TMath::Sin(staveRotateAngle) + py[1]; + px[2] = -21 * TMath::Cos(staveRotateAngle) + px[1]; + py[2] = 21 * TMath::Sin(staveRotateAngle) + py[1]; + px[3] = 40 * TMath::Sin(staveRotateAngle) + px[1]; + py[3] = 40 * TMath::Cos(staveRotateAngle) + py[1]; + } +} + +void ITSFeeTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ITSFeeTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSFeeTask::resetGeneralPlots() +{ + mTFInfo->Reset(); + mTriggerVsFeeId->Reset(); + mTrigger->Reset(); +} + +void ITSFeeTask::resetLanePlotsAndCounters(bool isFullReset) +{ + if (mResetLaneStatus || isFullReset) { + mRDHSummary->Reset("ICES"); // option ICES is to not remove layer lines and labels + mFlag1Check->Reset(); + mLaneStatusSummaryIB->Reset(); + mLaneStatusSummaryML->Reset(); + mLaneStatusSummaryOL->Reset(); + mLaneStatusSummaryGlobal->Reset("ICES"); + for (int i = 0; i < NFlags; i++) { + mLaneStatus[i]->Reset("ICES"); + } + mLaneStatusOverview[0]->Reset("content"); + mLaneStatusOverview[1]->Reset("content"); + for (int i = 0; i < NLayer; i++) { + mLaneStatusSummary[i]->Reset(); + } + + memset(mStatusFlagNumber, 0, sizeof(mStatusFlagNumber)); // reset counters + } + if (mResetPayload || isFullReset) { + mPayloadSize->Reset("ICES"); + } +} + +void ITSFeeTask::reset() +{ + // it is expected that this reset function will be executed only at the end of run + resetGeneralPlots(); + resetLanePlotsAndCounters(true); // full reset of all plots + mTimeFrameId = 0; + mDecodingCheck->Reset(); + mRDHSummaryCumulative->Reset(); + mTrailerCount->Reset(); + mActiveLanes->Reset(); + for (int i = 0; i < NFlags; i++) { + mLaneStatusCumulative[i]->Reset("ICES"); + } + mProcessingTime->Reset(); + mProcessingTime2->Reset(); + ILOG(Debug, Devel) << "Reset" << ENDM; + + if (mDecodeCDW) { + mCalibrationWordCount->Reset(); + mCalibLoop->Reset(); + mCalibStage->Reset(); + } +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSFhrCheck.cxx b/Modules/ITS/src/ITSFhrCheck.cxx new file mode 100644 index 0000000000..1b8a0e5577 --- /dev/null +++ b/Modules/ITS/src/ITSFhrCheck.cxx @@ -0,0 +1,376 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSFhrCheck.cxx +/// \author Liang Zhang +/// \author Jian Liu +/// + +#include "ITS/ITSFhrCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include + +#include +#include +#include +#include +#include +#include +#include "Common/Utils.h" +#include "TMath.h" + +namespace o2::quality_control_modules::its +{ + +Quality ITSFhrCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "General/ErrorPlots") { + result = Quality::Good; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast ErrorPlots to TH1D*" << ENDM; + continue; + } + if (h->GetMaximum() > 0) { + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "BAD:Decoding error(s) detected;"); + } + } else if (mo->getName() == "General/General_Occupancy") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast General_Occupancy to TH2Polly*" << ENDM; + continue; + } + result.addMetadata("Gen_Occu_IB", "good"); + result.addMetadata("Gen_Occu_OB", "good"); + result.addMetadata("Gen_Occu_empty", "good"); + result.set(Quality::Good); + std::vector skipbins = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "skipbins", "")); + + TIter next(h->GetBins()); + int ibin = 1; + double_t nBadStaves[4]; + TString sErrorReason = ""; + while (TH2PolyBin* Bin = (TH2PolyBin*)next()) { + if (std::find(skipbins.begin(), skipbins.end(), ibin) != skipbins.end()) { + ibin++; + continue; + } + + double X_center = (Bin->GetXMax() + Bin->GetXMin()) / 2; + double Y_center = (Bin->GetYMax() + Bin->GetYMin()) / 2; + double phi = TMath::ATan2(Y_center, X_center); + int sector = 2 * (phi + TMath::Pi()) / TMath::Pi(); + + if (ibin <= 48) { // IB + fhrcutIB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "fhrcutIB", fhrcutIB); + if (h->GetBinContent(ibin) > fhrcutIB) { + result.updateMetadata("Gen_Occu_IB", "bad"); + result.set(Quality::Bad); + TString text = "Bad: IB stave has high FHR;"; + if (!checkReason(result, text)) + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), text.Data()); + } + } else { // OB + fhrcutOB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "fhrcutOB", fhrcutOB); + if (h->GetBinContent(ibin) > fhrcutOB) { + result.updateMetadata("Gen_Occu_OB", "bad"); + result.set(Quality::Bad); + TString text = "Bad: OB stave has high FHR;"; + if (!checkReason(result, text)) + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), text.Data()); + } + } + + if (Bin->GetContent() < 1e-15) { // in case of empty stave + nBadStaves[sector]++; + } + ibin++; + } + + for (int iSector = 0; iSector < 4; iSector++) { + if (nBadStaves[iSector] / 48 > 0.1) { + result.updateMetadata("Gen_Occu_empty", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "Bad: Many empty staves in sector; IGNORE if run has just started OR it's TECH RUN"); + break; + } else if (nBadStaves[iSector] / 48 > 0.05) { + result.updateMetadata("Gen_Occu_empty", "medium"); + result.set(Quality::Medium); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "Medium: Many empty staves in sector; IGNORE if run has just started OR it's TECH RUN"); + } + } + + } else if (mo->getName() == "General/Noisy_Pixel") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast NoisyPixel to TH2Polly*" << ENDM; + continue; + } + result.addMetadata("Noi_Pix", "good"); + for (int ibin = 0; ibin < h->GetNumberOfBins(); ++ibin) { + if (h->GetBinContent(ibin) == 0) { + continue; + } + if (ibin < 48) { + if ((h->GetBinContent(ibin) / (double)mNPixelPerStave[0]) > 0.0001) { + result.updateMetadata("Noi_Pix", "bad"); + } else if ((h->GetBinContent(ibin) / (double)mNPixelPerStave[0]) > 0.00005 && strcmp(result.getMetadata("Noi_Pix").c_str(), "good") == 0) { + result.updateMetadata("Noi_Pix", "medium"); + } + } else if (ibin < 102) { + if ((h->GetBinContent(ibin) / (double)mNPixelPerStave[1]) > 0.0001) { + result.updateMetadata("Noi_Pix", "bad"); + } else if ((h->GetBinContent(ibin) / (double)mNPixelPerStave[1]) > 0.00005 && strcmp(result.getMetadata("Noi_Pix").c_str(), "good") == 0) { + result.updateMetadata("Noi_Pix", "medium"); + } + } else { + if ((h->GetBinContent(ibin) / (double)mNPixelPerStave[2]) > 0.0001) { + result.updateMetadata("Noi_Pix", "bad"); + } else if ((h->GetBinContent(ibin) / (double)mNPixelPerStave[2]) > 0.00005 && strcmp(result.getMetadata("Noi_Pix").c_str(), "good") == 0) { + result.updateMetadata("Noi_Pix", "medium"); + } + } + } + } + TString objectName = mo->getName(); + if (objectName.Contains("ChipStave")) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast ChipStave to TH2D*" << ENDM; + continue; + } + TString layerString = TString(objectName(15, 1).Data()); + int layer = layerString.Atoi(); + result.addMetadata(Form("Layer%d", layer), "good"); + double maxOccupancy = h->GetMaximum(); + if (maxOccupancy > pow(10, -5)) { + result.updateMetadata(Form("Layer%d", layer), "bad"); + } else if (maxOccupancy > pow(10, -6)) { + result.updateMetadata(Form("Layer%d", layer), "medium"); + } + } + } + return result; +} + +void ITSFhrCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + std::vector vPlotWithTextMessage = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "plotWithTextMessage", "")); + std::vector vTextMessage = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "textMessage", "")); + std::map ShifterInfoText; + + if ((int)vTextMessage.size() == (int)vPlotWithTextMessage.size()) { + for (int i = 0; i < (int)vTextMessage.size(); i++) { + ShifterInfoText[vPlotWithTextMessage[i]] = vTextMessage[i]; + } + } else + + ILOG(Warning, Support) << "Bad list of plot with TextMessages for shifter, check .json" << ENDM; + + std::shared_ptr tShifterInfo = std::make_shared(0.005, 0.006, Form("#bf{%s}", TString(ShifterInfoText[mo->getName()]).Data())); + tShifterInfo->SetTextSize(0.04); + tShifterInfo->SetTextFont(43); + tShifterInfo->SetNDC(); + + TLatex* text[5]; + TString status; + int textColor; + if (mo->getName() == "General/ErrorPlots") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast ErrorPlots to TH1D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + text[0] = new TLatex(0.05, 0.95, "#bf{Quality::Good}"); + text[1] = new TLatex(0.2, 0.4, "There is no Error found"); + for (int i = 0; i < 2; ++i) { + text[i]->SetTextFont(43); + text[i]->SetTextSize(0.06); + text[i]->SetTextColor(kGreen); + text[i]->SetNDC(); + h->GetListOfFunctions()->Add(text[i]); + } + } else if (checkResult == Quality::Bad) { + text[0] = new TLatex(0.05, 0.95, "#bf{Quality::Bad}"); + text[1] = new TLatex(0.2, 0.65, "Decoding error(s) detected"); + text[2] = new TLatex(0.2, 0.6, "Do not call, create a log entry"); + for (int i = 0; i < 3; ++i) { + text[i]->SetTextFont(43); + text[i]->SetTextSize(0.06); + text[i]->SetTextColor(kRed); + text[i]->SetNDC(); + h->GetListOfFunctions()->Add(text[i]); + } + } + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + + } else if (mo->getName() == "General/General_Occupancy") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast General_Occupancy to TH2Poly*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality GOOD"; + textColor = kGreen; + } else if (checkResult == Quality::Bad) { + status = "Quality::Bad (call expert)"; + textColor = kRed; + } + + if (strcmp(checkResult.getMetadata("Gen_Occu_IB").c_str(), "bad") == 0) { + fhrcutIB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "fhrcutIB", fhrcutIB); + tInfo[0] = std::make_shared(0.4, 0.7, Form("IB has stave(s) with FHR > %.2e hits/ev/pix", fhrcutIB)); + tInfo[0]->SetTextColor(kRed); + tInfo[0]->SetTextSize(0.03); + tInfo[0]->SetTextFont(43); + tInfo[0]->SetNDC(); + h->GetListOfFunctions()->Add(tInfo[0]->Clone()); + } + if (strcmp(checkResult.getMetadata("Gen_Occu_OB").c_str(), "bad") == 0) { + fhrcutOB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "fhrcutOB", fhrcutOB); + tInfo[1] = std::make_shared(0.4, 0.65, Form("OB has stave(s) with FHR > %.2e hits/ev/pix", fhrcutOB)); + tInfo[1]->SetTextColor(kRed); + tInfo[1]->SetTextSize(0.03); + tInfo[1]->SetTextFont(43); + tInfo[1]->SetNDC(); + h->GetListOfFunctions()->Add(tInfo[1]->Clone()); + } + if (strcmp(checkResult.getMetadata("Gen_Occu_empty").c_str(), "bad") == 0) { + tInfo[2] = std::make_shared(0.4, 0.3, "#splitline{ITS sector has more than 10% of empty staves}{IGNORE them if run has just started OR if it's TECH RUN}"); + tInfo[2]->SetTextColor(kRed); + tInfo[2]->SetTextSize(0.03); + tInfo[2]->SetTextFont(43); + tInfo[2]->SetNDC(); + h->GetListOfFunctions()->Add(tInfo[2]->Clone()); + } else if (strcmp(checkResult.getMetadata("Gen_Occu_empty").c_str(), "medium") == 0) { + tInfo[2] = std::make_shared(0.4, 0.3, "#splitline{ITS sector has more than 5% of empty staves}{IGNORE them if run has just started OR if it's TECH RUN}"); + tInfo[2]->SetTextColor(kOrange); + tInfo[2]->SetTextSize(0.03); + tInfo[2]->SetTextFont(43); + tInfo[2]->SetNDC(); + h->GetListOfFunctions()->Add(tInfo[2]->Clone()); + } + + tInfo[3] = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo[3]->SetTextColor(textColor); + tInfo[3]->SetTextSize(0.06); + tInfo[3]->SetTextFont(43); + tInfo[3]->SetNDC(); + h->GetListOfFunctions()->Add(tInfo[3]->Clone()); + + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + + } else if (mo->getName() == "General/Noisy_Pixel") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast Noisy_Pixel to TH2Poly*" << ENDM; + return; + } + if (strcmp(checkResult.getMetadata("Noi_Pix").c_str(), "good") == 0) { + text[0] = new TLatex(0, 0, "Quality::Good"); + text[0]->SetTextAlign(23); + text[0]->SetTextSize(0.08); + text[0]->SetTextColor(kGreen); + h->GetListOfFunctions()->Add(text[0]); + } else if (strcmp(checkResult.getMetadata("Noi_Pix").c_str(), "bad") == 0) { + text[0] = new TLatex(0, 100, "Quality::Bad"); + text[1] = new TLatex(0, 0, "Noisy Pixel over 0.01%"); + text[2] = new TLatex(0, -100, "Please Call Expert"); + for (int i = 0; i < 3; ++i) { + text[i]->SetTextAlign(23); + text[i]->SetTextSize(0.08); + text[i]->SetTextColor(kRed); + h->GetListOfFunctions()->Add(text[i]); + } + } else if (strcmp(checkResult.getMetadata("Noi_Pix").c_str(), "medium") == 0) { + text[0] = new TLatex(0, 100, "Quality::Medium"); + text[1] = new TLatex(0, 0, "Noisy Pixel over 0.005%"); + text[2] = new TLatex(0, -100, "Please Notify Expert On MM"); + for (int i = 0; i < 3; ++i) { + text[i]->SetTextAlign(23); + text[i]->SetTextSize(0.08); + text[i]->SetTextColor(46); + h->GetListOfFunctions()->Add(text[i]); + } + } + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + TString objectName = mo->getName(); + if (objectName.Contains("ChipStave")) { + TString layerString = TString(objectName(15, 1).Data()); + int layer = layerString.Atoi(); + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast ChipStave to TH2D*" << ENDM; + return; + } + if (strcmp(checkResult.getMetadata(Form("Layer%d", layer)).c_str(), "good") == 0) { + text[0] = new TLatex(0.5, 0.6, "Quality::Good"); + text[0]->SetNDC(); + text[0]->SetTextAlign(23); + text[0]->SetTextSize(0.08); + text[0]->SetTextColor(kGreen); + h->GetListOfFunctions()->Add(text[0]); + } else if (strcmp(checkResult.getMetadata(Form("Layer%d", layer)).c_str(), "medium") == 0) { + text[0] = new TLatex(0.5, 0.73, "Quality::Medium"); + text[0]->SetNDC(); + text[1] = new TLatex(0.5, 0.6, "Max Chip Occupancy Over 10^{-6}"); + text[1]->SetNDC(); + text[2] = new TLatex(0.5, 0.47, "Please Notify Expert On MM"); + text[2]->SetNDC(); + for (int i = 0; i < 3; ++i) { + text[i]->SetTextAlign(23); + text[i]->SetTextSize(0.08); + text[i]->SetTextColor(46); + h->GetListOfFunctions()->Add(text[i]); + } + } else if (strcmp(checkResult.getMetadata(Form("Layer%d", layer)).c_str(), "bad") == 0) { + text[0] = new TLatex(0.5, 0.73, "Quality::Bad"); + text[0]->SetNDC(); + text[1] = new TLatex(0.5, 0.6, "Max Chip Occupancy Over 10^{-5}"); + text[1]->SetNDC(); + text[2] = new TLatex(0.5, 0.47, "Please Call Expert"); + text[2]->SetNDC(); + for (int i = 0; i < 3; ++i) { + text[i]->SetTextAlign(23); + text[i]->SetTextSize(0.08); + text[i]->SetTextColor(kRed); + h->GetListOfFunctions()->Add(text[i]); + } + } + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } +} +bool ITSFhrCheck::checkReason(Quality checkResult, TString text) +{ + auto flags = checkResult.getFlags(); + for (int i = 0; i < int(flags.size()); i++) { + if (text.Contains(flags[i].second.c_str())) + return true; + } + return false; +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSFhrTask.cxx b/Modules/ITS/src/ITSFhrTask.cxx new file mode 100644 index 0000000000..2d29f1873c --- /dev/null +++ b/Modules/ITS/src/ITSFhrTask.cxx @@ -0,0 +1,859 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSFhrTask.cxx +/// \author Liang Zhang +/// \author Jian Liu +/// \author Zhen Zhang +/// + +#include "ITS/ITSFhrTask.h" + +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include "Framework/TimingInfo.h" + +#include "Common/Utils.h" + +#ifdef WITH_OPENMP +#include +#endif + +using namespace o2::framework; +using namespace o2::itsmft; +using namespace o2::header; + +namespace o2::quality_control_modules::its +{ + +ITSFhrTask::ITSFhrTask() + : TaskInterface() +{ +} + +ITSFhrTask::~ITSFhrTask() +{ + delete mGeneralOccupancy; + delete mGeneralNoisyPixel; + delete mDecoder; + delete mChipDataBuffer; + delete mErrorPlots; + delete mErrorVsFeeid; + delete mChipStaveOccupancy; + delete mChipStaveEventHitCheck; + delete mOccupancyPlot; + delete mDeadChipPos; + delete mAliveChipPos; + delete mTotalDeadChipPos; + delete mTotalAliveChipPos; + for (int istave = 0; istave < 48; istave++) { + delete mStaveHitmap[istave]; + } + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + delete[] mHitnumberLane[istave]; + delete[] mOccupancyLane[istave]; + delete[] mChipPhi[istave]; + delete[] mChipZ[istave]; + delete[] mChipStat[istave]; + int maxlink = mLayer < NLayerIB ? 3 : 2; + for (int ilink = 0; ilink < maxlink; ilink++) { + delete[] mErrorCount[istave][ilink]; + } + delete[] mErrorCount[istave]; + for (int ihic = 0; ihic < nHicPerStave[mLayer]; ihic++) { + delete[] mHitPixelID_InStave[istave][ihic]; + } + delete[] mHitPixelID_InStave[istave]; + } + delete[] mHitnumberLane; + delete[] mOccupancyLane; + delete[] mChipPhi; + delete[] mChipZ; + delete[] mChipStat; + delete[] mErrorCount; + delete[] mHitPixelID_InStave; +} + +void ITSFhrTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize ITSFhrTask" << ENDM; + getParameters(); + mGeneralOccupancy = new TH2Poly(); + mGeneralOccupancy->SetTitle("General Occupancy;mm (IB 3x);mm (IB 3x)"); + mGeneralOccupancy->SetName("General/General_Occupancy"); + mGeneralOccupancy->SetStats(0); + mGeneralOccupancy->SetMinimum(pow(10, mMinGeneralAxisRange)); + mGeneralOccupancy->SetMaximum(pow(10, mMaxGeneralAxisRange)); + + mGeneralNoisyPixel = new TH2Poly(); + mGeneralNoisyPixel->SetTitle("Noisy Pixel Number;mm (IB 3x);mm (IB 3x)"); + mGeneralNoisyPixel->SetName("General/Noisy_Pixel"); + mGeneralNoisyPixel->SetStats(0); + mGeneralNoisyPixel->SetMinimum(mMinGeneralNoisyAxisRange); + mGeneralNoisyPixel->SetMaximum(mMaxGeneralNoisyAxisRange); + + createGeneralPlots(); + createOccupancyPlots(); + setPlotsFormat(); + mDecoder = new o2::itsmft::RawPixelDecoder(); + mDecoder->init(); + mDecoder->setSkipRampUpData(mIgnoreRampUpData); + mDecoder->setNThreads(mNThreads); + mDecoder->setUserDataOrigin(header::DataOrigin("DS")); // set user data origin in dpl + mDecoder->setUserDataDescription(header::DataDescription("RAWDATA0")); + mChipsBuffer.resize(24120); + + if (mLayer != -1) { + // define the hitnumber, occupancy, errorcount array + mHitPixelID_InStave = new std::unordered_map**[NStaves[mLayer]]; + mHitnumberLane = new int*[NStaves[mLayer]]; + mOccupancyLane = new double*[NStaves[mLayer]]; + mChipPhi = new double*[NStaves[mLayer]]; + mChipZ = new double*[NStaves[mLayer]]; + + mChipStat = new int*[NStaves[mLayer]]; + mErrorCount = new int**[NStaves[mLayer]]; + + for (int ilayer = 0; ilayer < 7; ilayer++) { + for (int istave = 0; istave < NStaves[ilayer]; istave++) { + double* px = new double[4]; + double* py = new double[4]; + getStavePoint(ilayer, istave, px, py); + if (ilayer < 3) { + for (int icoo = 0; icoo < 4; icoo++) { + px[icoo] *= 3.; + py[icoo] *= 3.; + } + } + mGeneralOccupancy->AddBin(4, px, py); + mGeneralNoisyPixel->AddBin(4, px, py); + } + } + if (mGeneralOccupancy) { + getObjectsManager()->startPublishing(mGeneralOccupancy); + } + if (mGeneralNoisyPixel) { + getObjectsManager()->startPublishing(mGeneralNoisyPixel); + } + // define the errorcount array, there is some reason cause break when I define errorcount and hitnumber, occupancy at same block. + if (mLayer < NLayerIB) { + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + mErrorCount[istave] = new int*[3]; + for (int ilink = 0; ilink < 3; ilink++) { + mErrorCount[istave][ilink] = new int[o2::itsmft::GBTLinkDecodingStat::NErrorsDefined]; + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + mErrorCount[istave][ilink][ierror] = 0; + } + } + } + } else { + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + mErrorCount[istave] = new int*[2]; + for (int ilink = 0; ilink < 2; ilink++) { + mErrorCount[istave][ilink] = new int[o2::itsmft::GBTLinkDecodingStat::NErrorsDefined]; + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + mErrorCount[istave][ilink][ierror] = 0; + } + } + } + } + // define the hitnumber and occupancy array + if (mLayer < NLayerIB) { + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + mHitnumberLane[istave] = new int[nChipsPerHic[mLayer]]; + mOccupancyLane[istave] = new double[nChipsPerHic[mLayer]]; + mChipPhi[istave] = new double[nChipsPerHic[mLayer]]; + mChipZ[istave] = new double[nChipsPerHic[mLayer]]; + + mChipStat[istave] = new int[nChipsPerHic[mLayer]]; + mHitPixelID_InStave[istave] = new std::unordered_map*[nHicPerStave[mLayer]]; + for (int ihic = 0; ihic < nHicPerStave[mLayer]; ihic++) { + mHitPixelID_InStave[istave][ihic] = new std::unordered_map[nChipsPerHic[mLayer]]; + } + for (int ichip = 0; ichip < nChipsPerHic[mLayer]; ichip++) { + mHitnumberLane[istave][ichip] = 0; + mOccupancyLane[istave][ichip] = 0; + mChipPhi[istave][ichip] = 0; + mChipZ[istave][ichip] = 0; + + mChipStat[istave][ichip] = 0; + mChipStaveOccupancy->GetXaxis()->SetBinLabel(ichip + 1, Form("Chip %i", ichip)); + mChipStaveEventHitCheck->GetXaxis()->SetBinLabel(ichip + 1, Form("Chip %i", ichip)); + } + } + } else { + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + mHitnumberLane[istave] = new int[nHicPerStave[mLayer] * 2]; + mOccupancyLane[istave] = new double[nHicPerStave[mLayer] * 2]; + mChipPhi[istave] = new double[nHicPerStave[mLayer] * nChipsPerHic[mLayer]]; + mChipZ[istave] = new double[nHicPerStave[mLayer] * nChipsPerHic[mLayer]]; + + mChipStat[istave] = new int[nHicPerStave[mLayer] * nChipsPerHic[mLayer]]; + mHitPixelID_InStave[istave] = new std::unordered_map*[nHicPerStave[mLayer]]; + for (int ihic = 0; ihic < nHicPerStave[mLayer]; ihic++) { + mHitPixelID_InStave[istave][ihic] = new std::unordered_map[nChipsPerHic[mLayer]]; + } + for (int ichip = 0; ichip < nHicPerStave[mLayer] * nChipsPerHic[mLayer]; ichip++) { + mChipPhi[istave][ichip] = 0; + mChipZ[istave][ichip] = 0; + + mChipStat[istave][ichip] = 0; + } + mChipStaveOccupancy->GetYaxis()->SetBinLabel(istave + 1, Form("Stave %i", istave)); + mChipStaveEventHitCheck->GetYaxis()->SetBinLabel(istave + 1, Form("Stave %i", istave)); + if (mLayer < 5) { + for (int ihic = 0; ihic < nHicPerStave[mLayer]; ihic++) { + mHitnumberLane[istave][2 * ihic] = 0; + mHitnumberLane[istave][2 * ihic + 1] = 0; + mOccupancyLane[istave][2 * ihic] = 0; + mOccupancyLane[istave][2 * ihic + 1] = 0; + mChipStaveOccupancy->GetXaxis()->SetBinLabel(2 * ihic + 1, Form("%s", OBLabel34[2 * ihic])); + mChipStaveOccupancy->GetXaxis()->SetBinLabel(2 * ihic + 2, Form("%s", OBLabel34[2 * ihic + 1])); + mChipStaveOccupancy->GetXaxis()->SetLabelSize(0.02); + mChipStaveEventHitCheck->GetXaxis()->SetBinLabel(2 * ihic + 1, Form("%s", OBLabel34[2 * ihic])); + mChipStaveEventHitCheck->GetXaxis()->SetBinLabel(2 * ihic + 2, Form("%s", OBLabel34[2 * ihic + 1])); + mChipStaveEventHitCheck->GetXaxis()->SetLabelSize(0.02); + } + + } else { + for (int ihic = 0; ihic < nHicPerStave[mLayer]; ihic++) { + mHitnumberLane[istave][2 * ihic] = 0; + mHitnumberLane[istave][2 * ihic + 1] = 0; + mOccupancyLane[istave][2 * ihic] = 0; + mOccupancyLane[istave][2 * ihic + 1] = 0; + mChipStaveOccupancy->GetXaxis()->SetBinLabel(2 * ihic + 1, Form("%s", OBLabel56[2 * ihic])); + mChipStaveOccupancy->GetXaxis()->SetBinLabel(2 * ihic + 2, Form("%s", OBLabel56[2 * ihic + 1])); + mChipStaveEventHitCheck->GetXaxis()->SetBinLabel(2 * ihic + 1, Form("%s", OBLabel56[2 * ihic])); + mChipStaveEventHitCheck->GetXaxis()->SetBinLabel(2 * ihic + 2, Form("%s", OBLabel56[2 * ihic + 1])); + } + } + } + } + } +} + +void ITSFhrTask::createErrorTriggerPlots() +{ + mErrorPlots = new TH1D("General/ErrorPlots", "Decoding Errors", o2::itsmft::GBTLinkDecodingStat::NErrorsDefined, 0.5, (float)o2::itsmft::GBTLinkDecodingStat::NErrorsDefined + 0.5); + mErrorPlots->SetMinimum(0); + mErrorPlots->SetFillColor(kRed); + getObjectsManager()->startPublishing(mErrorPlots); // mErrorPlots +} + +void ITSFhrTask::createGeneralPlots() +{ + + createErrorTriggerPlots(); + + mErrorVsFeeid = new TH2I("General/ErrorVsFeeid", "Error count vs Error id and Fee id", (3 * StaveBoundary[3]) + (2 * (StaveBoundary[7] - StaveBoundary[3])), 0, (3 * StaveBoundary[3]) + (2 * (StaveBoundary[7] - StaveBoundary[3])), o2::itsmft::GBTLinkDecodingStat::NErrorsDefined, 0.5, (float)o2::itsmft::GBTLinkDecodingStat::NErrorsDefined + 0.5); + mErrorVsFeeid->SetMinimum(0); + mErrorVsFeeid->SetStats(0); + getObjectsManager()->startPublishing(mErrorVsFeeid); +} + +void ITSFhrTask::createOccupancyPlots() // create general plots like error, trigger, TF id plots and so on.... + // create occupancy plots like chip stave occupancy, occupancy distribution, hic hit map plots and so on.... +{ + const int nDim(2); + int nBins[nDim] = { 1024, 512 }; + double Min[nDim] = { 0, 0 }; + double Max[nDim] = { 1024, 512 }; + mTotalDeadChipPos = new TH2D(Form("Occupancy/TotalDeadChipPos"), Form("TotalDeadChipPos "), nHicPerStave[6] * 7 * 0.5, -0.5 * mLength[6], 0.5 * mLength[6], NStaves[6] * 4, -180, 180); + mTotalDeadChipPos->SetStats(0); + getObjectsManager()->startPublishing(mTotalDeadChipPos); + + mTotalAliveChipPos = new TH2D(Form("Occupancy/TotalAliveChipPos"), Form("TotalAliveChipPos "), nHicPerStave[6] * 7 * 0.5, -0.5 * mLength[6], 0.5 * mLength[6], NStaves[6] * 4, -180, 180); + mTotalAliveChipPos->SetStats(0); + getObjectsManager()->startPublishing(mTotalAliveChipPos); + // create IB plots + if (mLayer < NLayerIB) { + int nBinstmp[nDim] = { nBins[0] * nChipsPerHic[mLayer] / ReduceFraction, nBins[1] / ReduceFraction }; + double Maxtmp[nDim] = { Max[0] * nChipsPerHic[mLayer], Max[1] }; + for (int istave = 0; istave < 48; istave++) { + mStaveHitmap[istave] = new THnSparseI(Form("Occupancy/Layer%d/Stave%d/Layer%dStave%dHITMAP", mLayer, istave, mLayer, istave), Form("Hits on Layer %d, Stave %d", mLayer, istave), nDim, nBinstmp, Min, Maxtmp); + if (istave < NStaves[mLayer]) { + getObjectsManager()->startPublishing(mStaveHitmap[istave]); + } + } + + mDeadChipPos = new TH2D(Form("Occupancy/Layer%d/Layer%dDeadChipPos", mLayer, mLayer), Form("DeadChipPos on Layer %d", mLayer), nChipsPerHic[mLayer], -0.5 * mLength[mLayer], 0.5 * mLength[mLayer], NStaves[mLayer], -180, 180); + mAliveChipPos = new TH2D(Form("Occupancy/Layer%d/Layer%dAliveChipPos", mLayer, mLayer), Form("AliveChipPos on Layer %d", mLayer), nChipsPerHic[mLayer], -0.5 * mLength[mLayer], 0.5 * mLength[mLayer], NStaves[mLayer], -180, 180); + + mChipStaveOccupancy = new TH2D(Form("Occupancy/Layer%d/Layer%dChipStave", mLayer, mLayer), Form("ITS Layer%d, Occupancy vs Chip and Stave", mLayer), nHicPerStave[mLayer] * nChipsPerHic[mLayer], -0.5, nHicPerStave[mLayer] * nChipsPerHic[mLayer] - 0.5, NStaves[mLayer], -0.5, NStaves[mLayer] - 0.5); + mChipStaveOccupancy->SetStats(0); + mChipStaveOccupancy->GetYaxis()->SetTickLength(0.01); + getObjectsManager()->startPublishing(mDeadChipPos); + getObjectsManager()->startPublishing(mAliveChipPos); + getObjectsManager()->startPublishing(mChipStaveOccupancy); // mChipStaveOccupancy + mChipStaveEventHitCheck = new TH2I(Form("Occupancy/Layer%d/Layer%dChipStaveEventHit", mLayer, mLayer), Form("ITS Layer%d, Event Hit Check vs Chip and Stave", mLayer), nHicPerStave[mLayer] * nChipsPerHic[mLayer], -0.5, nHicPerStave[mLayer] * nChipsPerHic[mLayer] - 0.5, NStaves[mLayer], -0.5, NStaves[mLayer] - 0.5); + mChipStaveEventHitCheck->SetStats(0); + getObjectsManager()->startPublishing(mChipStaveEventHitCheck); + + mOccupancyPlot = new TH1D(Form("Occupancy/Layer%dOccupancy", mLayer), Form("ITS Layer %d Noise pixels occupancy distribution", mLayer), 300, -15, 0); + getObjectsManager()->startPublishing(mOccupancyPlot); // mOccupancyPlot + + } else { + // Create OB plots + int nBinstmp[nDim] = { (nBins[0] * (nChipsPerHic[mLayer] / 2) * (nHicPerStave[mLayer] / 2) / ReduceFraction), (nBins[1] * 2 * NSubStave[mLayer] / ReduceFraction) }; + double Maxtmp[nDim] = { (double)(nBins[0] * (nChipsPerHic[mLayer] / 2) * (nHicPerStave[mLayer] / 2)), (double)(nBins[1] * 2 * NSubStave[mLayer]) }; + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + mStaveHitmap[istave] = new THnSparseI(Form("Occupancy/Layer%d/Stave%d/Layer%dStave%dHITMAP", mLayer, istave, mLayer, istave), Form("Hits on Layer %d, Stave %d", mLayer, istave), nDim, nBinstmp, Min, Maxtmp); + if (mCutTFForSparse > 0) + getObjectsManager()->startPublishing(mStaveHitmap[istave]); + } + mDeadChipPos = new TH2D(Form("Occupancy/Layer%d/Layer%dDeadChipPos", mLayer, mLayer), Form("DeadChipPos on Layer %d", mLayer), nHicPerStave[mLayer] * 7 * 0.5, -0.5 * mLength[mLayer], 0.5 * mLength[mLayer], NStaves[mLayer] * 4, -180, 180); + mAliveChipPos = new TH2D(Form("Occupancy/Layer%d/Layer%dAliveChipPos", mLayer, mLayer), Form("AliveChipPos on Layer %d", mLayer), nHicPerStave[mLayer] * 7 * 0.5, -0.5 * mLength[mLayer], 0.5 * mLength[mLayer], NStaves[mLayer] * 4, -180, 180); + + mChipStaveOccupancy = new TH2D(Form("Occupancy/Layer%d/Layer%dChipStave", mLayer, mLayer), Form("ITS Layer%d, Occupancy vs Chip and Stave", mLayer), nHicPerStave[mLayer] * nLanePerHic[mLayer], -0.5, nHicPerStave[mLayer] * nLanePerHic[mLayer] - 0.5, NStaves[mLayer], -0.5, NStaves[mLayer] - 0.5); + mChipStaveOccupancy->SetStats(0); + getObjectsManager()->startPublishing(mDeadChipPos); + getObjectsManager()->startPublishing(mAliveChipPos); + getObjectsManager()->startPublishing(mChipStaveOccupancy); + + mChipStaveEventHitCheck = new TH2I(Form("Occupancy/Layer%d/Layer%dChipStaveEventHit", mLayer, mLayer), Form("ITS Layer%d, Event Hit Check vs Chip and Stave", mLayer), nHicPerStave[mLayer] * nLanePerHic[mLayer], -0.5, nHicPerStave[mLayer] * nLanePerHic[mLayer] - 0.5, NStaves[mLayer], -0.5, NStaves[mLayer] - 0.5); + mChipStaveEventHitCheck->SetStats(0); + getObjectsManager()->startPublishing(mChipStaveEventHitCheck); + + mOccupancyPlot = new TH1D(Form("Occupancy/Layer%dOccupancy", mLayer), Form("ITS Layer %d Noise pixels occupancy Distribution", mLayer), 300, -15, 0); + getObjectsManager()->startPublishing(mOccupancyPlot); // mOccupancyPlot + } +} + +void ITSFhrTask::setAxisTitle(TH1* object, const char* xTitle, const char* yTitle) +{ + object->GetXaxis()->SetTitle(xTitle); + object->GetYaxis()->SetTitle(yTitle); +} + +void ITSFhrTask::setPlotsFormat() +{ + // set general plots format + if (mErrorPlots) { + setAxisTitle(mErrorPlots, "Error ID", "Counts"); + } + if (mErrorVsFeeid) { + setAxisTitle(mErrorVsFeeid, "FeeID", "Error ID"); + } + if (mTotalDeadChipPos) { + setAxisTitle(mTotalDeadChipPos, "ChipZ", "ChipPhi"); + } + if (mTotalAliveChipPos) { + setAxisTitle(mTotalAliveChipPos, "ChipZ", "ChipPhi"); + } + if (mOccupancyPlot) { + mOccupancyPlot->GetXaxis()->SetTitle("log(Occupancy)"); + } + + if (mDeadChipPos) { + setAxisTitle(mDeadChipPos, "ChipZ", "ChipPhi"); + } + if (mAliveChipPos) { + setAxisTitle(mAliveChipPos, "ChipZ", "ChipPhi"); + } + + if (mLayer < NLayerIB) { + if (mChipStaveOccupancy) { + setAxisTitle(mChipStaveOccupancy, "Chip Number", "Stave Number"); + } + if (mChipStaveEventHitCheck) { + setAxisTitle(mChipStaveEventHitCheck, "Chip Number", "Stave Number"); + } + } else { + if (mChipStaveOccupancy) { + setAxisTitle(mChipStaveOccupancy, "", "Stave Number"); + } + if (mChipStaveEventHitCheck) { + setAxisTitle(mChipStaveEventHitCheck, "", "Stave Number"); + } + } +} + +void ITSFhrTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity : " << activity.mId << ENDM; + reset(); +} + +void ITSFhrTask::startOfCycle() { ILOG(Debug, Devel) << "startOfCycle" << ENDM; } + +void ITSFhrTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + if (mGeom == nullptr) { + o2::its::GeometryTGeo::adopt(TaskInterface::retrieveConditionAny("ITS/Config/Geometry")); + mGeom = o2::its::GeometryTGeo::Instance(); + ILOG(Debug, Devel) << "Loaded new instance of mGeom" << ENDM; + } + // set timer + std::chrono::time_point start; + std::chrono::time_point end; + int difference; + start = std::chrono::high_resolution_clock::now(); + + // set Decoder + mDecoder->startNewTF(ctx.inputs()); + mDecoder->setDecodeNextAuto(true); + + // define digit hit vector + std::vector** digVec = new std::vector*[NStaves[mLayer]]; // IB : digVec[stave][0]; OB : digVec[stave][hic] + const math_utils::Point3D loc(0., 0., 0.); + + if (mLayer < NLayerIB) { + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + digVec[istave] = new std::vector[nHicPerStave[mLayer]]; + } + } else { + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + digVec[istave] = new std::vector[nHicPerStave[mLayer]]; + } + } + + // decode raw data and save digit hit to digit hit vector, and save hitnumber per chip/hic + // get the position of all chips in this layer + for (int ichip = ChipBoundary[mLayer]; ichip < ChipBoundary[mLayer + 1]; ichip++) { + int stave = 0, chip = 0; + auto glo = mGeom->getMatrixL2G(ichip)(loc); + if (mLayer < NLayerIB) { + stave = ichip / 9 - StaveBoundary[mLayer]; + chip = ichip % 9; + mChipPhi[stave][chip] = glo.phi() * 180 / TMath::Pi(); + mChipZ[stave][chip] = glo.Z(); + } else { + stave = (ichip - ChipBoundary[mLayer]) / (14 * nHicPerStave[mLayer]); + chip = (ichip - ChipBoundary[mLayer]) % (14 * nHicPerStave[mLayer]); + mChipPhi[stave][chip] = glo.phi() * 180 / TMath::Pi(); + mChipZ[stave][chip] = glo.Z(); + } + } + + while ((mChipDataBuffer = mDecoder->getNextChipData(mChipsBuffer))) { + if (mChipDataBuffer) { + int stave = 0, chip = 0; + int hic = 0; + int lane = 0; + + const auto& pixels = mChipDataBuffer->getData(); + if (mChipDataBuffer->getChipID() < ChipBoundary[mLayer] || mChipDataBuffer->getChipID() >= ChipBoundary[mLayer + 1]) { // useful for data replay + continue; + } + for (auto& pixel : pixels) { + if (mLayer < NLayerIB) { + stave = mChipDataBuffer->getChipID() / 9 - StaveBoundary[mLayer]; + chip = mChipDataBuffer->getChipID() % 9; + hic = 0; + mHitnumberLane[stave][chip]++; + mChipStat[stave][chip]++; + } else { + stave = (mChipDataBuffer->getChipID() - ChipBoundary[mLayer]) / (14 * nHicPerStave[mLayer]); + int chipIdLocal = (mChipDataBuffer->getChipID() - ChipBoundary[mLayer]) % (14 * nHicPerStave[mLayer]); + chip = chipIdLocal % 14; + hic = (chipIdLocal % (14 * nHicPerStave[mLayer])) / 14; + lane = (chipIdLocal % (14 * nHicPerStave[mLayer])) / (14 / 2); + + mHitnumberLane[stave][lane]++; + mChipStat[stave][chipIdLocal]++; + } + digVec[stave][hic].emplace_back(mChipDataBuffer->getChipID(), pixel.getRow(), pixel.getCol()); + } + if (mLayer < NLayerIB) { + if (pixels.size() > (unsigned int)mHitCutForCheck) { + mChipStaveEventHitCheck->Fill(chip, stave); + } + } else { + if (pixels.size() > (unsigned int)mHitCutForCheck) { + mChipStaveEventHitCheck->Fill(lane, stave); + } + } + } + } + + // calculate active staves according digit hit vector + std::vector activeStaves; + for (int i = 0; i < NStaves[mLayer]; i++) { + for (int j = 0; j < nHicPerStave[mLayer]; j++) { + if (digVec[i][j].size() != 0) { + activeStaves.push_back(i); + break; + } + } + } + +#ifdef WITH_OPENMP + omp_set_num_threads(mNThreads); +#pragma omp parallel for schedule(dynamic) +#endif + // save digit hit vector to unordered_map by openMP multiple threads + // the reason of this step is: it will spend many time If we THnSparse::Fill the THnspase hit by hit. + // So we want save hit information to undordered_map and fill THnSparse by THnSparse::SetBinContent (pixel by pixel) + for (int i = 0; i < (int)activeStaves.size(); i++) { + int istave = activeStaves[i]; + if (mLayer < NLayerIB) { + for (auto& digit : digVec[istave][0]) { + int chip = digit.getChipIndex() % 9; + mHitPixelID_InStave[istave][0][chip][1000 * digit.getColumn() + digit.getRow()]++; + nHitsTotal++; + if (mTFCount <= mCutTFForSparse) { + Double_t pixelPos[2] = { 1. * (digit.getColumn() + (1024 * chip)), 1. * digit.getRow() }; + mStaveHitmap[istave]->Fill(pixelPos); + } + } + } else { + for (int ihic = 0; ihic < nHicPerStave[mLayer]; ihic++) { + for (auto& digit : digVec[istave][ihic]) { + int chip = ((digit.getChipIndex() - ChipBoundary[mLayer]) % (14 * nHicPerStave[mLayer])) % 14; + mHitPixelID_InStave[istave][ihic][chip][1000 * digit.getColumn() + digit.getRow()]++; + nHitsTotal++; + int ilink = ihic / (nHicPerStave[mLayer] / 2); + if (mTFCount <= mCutTFForSparse) { + if (chip < 7) { + Double_t pixelPos[2] = { 1. * ((ihic % (nHicPerStave[mLayer] / NSubStave[mLayer]) * ((nChipsPerHic[mLayer] / 2) * NCols)) + chip * NCols + digit.getColumn()), 1. * (NRows - digit.getRow() - 1 + (1024 * ilink)) }; + mStaveHitmap[istave]->Fill(pixelPos); + } else { + Double_t pixelPos[2] = { 1. * ((ihic % (nHicPerStave[mLayer] / NSubStave[mLayer]) * ((nChipsPerHic[mLayer] / 2) * NCols)) + (nChipsPerHic[mLayer] / 2) * NCols - (chip - 7) * NCols - digit.getColumn() * 1.), 1. * (NRows + digit.getRow() + (1024 * ilink)) }; + mStaveHitmap[istave]->Fill(pixelPos); + } + } + } + } + } + } + + // Reset Error plots + mErrorPlots->Reset(); + mErrorVsFeeid->Reset(); // Error is statistic by decoder so if we didn't reset decoder, then we need reset Error plots, and use TH::SetBinContent function + + // define tmp occupancy plot, which will use for multiple threads + TH1D** occupancyPlotTmp = new TH1D*[(int)activeStaves.size()]; + for (int i = 0; i < (int)activeStaves.size(); i++) { + occupancyPlotTmp[i] = new TH1D("", "", 300, -15, 0); + } + + int totalhit = 0; + +#ifdef WITH_OPENMP + omp_set_num_threads(mNThreads); +#pragma omp parallel for schedule(dynamic) reduction(+ \ + : totalhit) +#endif + // fill Monitor Objects use openMP multiple threads, and calculate the occupancy + for (int i = 0; i < (int)activeStaves.size(); i++) { + int istave = activeStaves[i]; + if (digVec[istave][0].size() < 1 && mLayer < NLayerIB) { + continue; + } + const auto* DecoderTmp = mDecoder; + int RUid = StaveBoundary[mLayer] + istave; + const o2::itsmft::RUDecodeData* RUdecode = DecoderTmp->getRUDecode(RUid); + if (!RUdecode) { + continue; + } + if (mLayer < NLayerIB) { + for (int ilink = 0; ilink < RUDecodeData::MaxLinksPerRU; ilink++) { + const auto* GBTLinkInfo = DecoderTmp->getGBTLink(RUdecode->links[ilink]); + if (!GBTLinkInfo) { + continue; + } + + mNoisyPixelNumber[mLayer][istave] = 0; + for (int ichip = 0 + (ilink * 3); ichip < (ilink * 3) + 3; ichip++) { + std::unordered_map::iterator iter; + + if (mDoHitmapFilter == 1) { + for (auto iter = mHitPixelID_InStave[istave][0][ichip].begin(); iter != mHitPixelID_InStave[istave][0][ichip].end();) { + if ((double)iter->second / 10 < (double)nHitsTotal / ((ChipBoundary[mLayer + 1] - ChipBoundary[mLayer]) * 1024 * 512)) { // noisy if more than 3x of averaged per chip + mHitPixelID_InStave[istave][0][ichip].erase(iter++); + } else { + ++iter; + } + } + } + + for (auto iter = mHitPixelID_InStave[istave][0][ichip].begin(); iter != mHitPixelID_InStave[istave][0][ichip].end(); iter++) { + if ((iter->second > mHitCutForNoisyPixel) && + (iter->second / (double)GBTLinkInfo->statistics.nTriggers) > mOccupancyCutForNoisyPixel) { + mNoisyPixelNumber[mLayer][istave]++; // count only in 10000 events as soon as nTriggers is 1e6 + occupancyPlotTmp[i]->Fill(log10((double)iter->second / GBTLinkInfo->statistics.nTriggers)); + } + + totalhit += (int)iter->second; + } + + mOccupancyLane[istave][ichip] = mHitnumberLane[istave][ichip] / (GBTLinkInfo->statistics.nTriggers * 1024. * 512.); + } + + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + if (GBTLinkInfo->statistics.errorCounts[ierror] <= 0) { + continue; + } + mErrorCount[istave][ilink][ierror] = GBTLinkInfo->statistics.errorCounts[ierror]; + } + } + } else { + for (int ilink = 0; ilink < RUDecodeData::MaxLinksPerRU; ilink++) { + const auto* GBTLinkInfo = DecoderTmp->getGBTLink(RUdecode->links[ilink]); + if (!GBTLinkInfo) { + continue; + } + mNoisyPixelNumber[mLayer][istave] = 0; + + for (int ihic = 0; ihic < ((nHicPerStave[mLayer] / NSubStave[mLayer])); ihic++) { + for (int ichip = 0; ichip < nChipsPerHic[mLayer]; ichip++) { + if (GBTLinkInfo->statistics.nTriggers > 0) { + + if (mDoHitmapFilter == 1) { + for (auto iter = mHitPixelID_InStave[istave][ihic + ilink * ((nHicPerStave[mLayer] / NSubStave[mLayer]))][ichip].begin(); iter != mHitPixelID_InStave[istave][ihic + ilink * ((nHicPerStave[mLayer] / NSubStave[mLayer]))][ichip].end();) { + if ((double)iter->second / 100 < (double)nHitsTotal / ((ChipBoundary[mLayer + 1] - ChipBoundary[mLayer]) * 1024 * 512)) { // noisy if more than 3x of averaged per chip + mHitPixelID_InStave[istave][ihic + ilink * ((nHicPerStave[mLayer] / NSubStave[mLayer]))][ichip].erase(iter++); + + } else { + ++iter; + } + } + } + for (auto iter = mHitPixelID_InStave[istave][ihic + ilink * ((nHicPerStave[mLayer] / NSubStave[mLayer]))][ichip].begin(); iter != mHitPixelID_InStave[istave][ihic + ilink * ((nHicPerStave[mLayer] / NSubStave[mLayer]))][ichip].end(); iter++) { + if ((iter->second > mHitCutForNoisyPixel) && + (iter->second / (double)GBTLinkInfo->statistics.nTriggers) > mOccupancyCutForNoisyPixel) { + mNoisyPixelNumber[mLayer][istave]++; + occupancyPlotTmp[i]->Fill(log10((double)iter->second / GBTLinkInfo->statistics.nTriggers)); + } + } + } + } + + if (mLayer == 3 || mLayer == 4) { + mOccupancyLane[istave][2 * (ihic + (ilink * 4))] = mHitnumberLane[istave][2 * (ihic + (ilink * 4))] / (GBTLinkInfo->statistics.nTriggers * 1024. * 512. * nChipsPerHic[mLayer] / 2); + mOccupancyLane[istave][2 * (ihic + (ilink * 4)) + 1] = mHitnumberLane[istave][2 * (ihic + (ilink * 4)) + 1] / (GBTLinkInfo->statistics.nTriggers * 1024. * 512. * nChipsPerHic[mLayer] / 2); + } else { + mOccupancyLane[istave][2 * (ihic + (ilink * 7))] = mHitnumberLane[istave][2 * (ihic + (ilink * 7))] / (GBTLinkInfo->statistics.nTriggers * 1024. * 512. * nChipsPerHic[mLayer] / 2); + mOccupancyLane[istave][2 * (ihic + (ilink * 7)) + 1] = mHitnumberLane[istave][2 * (ihic + (ilink * 7)) + 1] / (GBTLinkInfo->statistics.nTriggers * 1024. * 512. * nChipsPerHic[mLayer] / 2); + } + } + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + if (GBTLinkInfo->statistics.errorCounts[ierror] <= 0) { + continue; + } + mErrorCount[istave][ilink][ierror] = GBTLinkInfo->statistics.errorCounts[ierror]; + } + } + } + } + // fill Occupancy plots, chip stave occupancy plots and error statistic plots + for (int i = 0; i < (int)activeStaves.size(); i++) { + int istave = activeStaves[i]; + mOccupancyPlot->Add(occupancyPlotTmp[i]); + if (mLayer < NLayerIB) { + for (int ichip = 0; ichip < nChipsPerHic[mLayer]; ichip++) { + mChipStaveOccupancy->SetBinContent(ichip + 1, istave + 1, mOccupancyLane[istave][ichip]); + if (!mChipStat[istave][ichip]) { + mDeadChipPos->SetBinContent(mDeadChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mDeadChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 1); + mTotalDeadChipPos->SetBinContent(mTotalDeadChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mTotalDeadChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 1); + } else { + mAliveChipPos->SetBinContent(mAliveChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mAliveChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 1); + mTotalAliveChipPos->SetBinContent(mTotalAliveChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mTotalAliveChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 1); + mDeadChipPos->SetBinContent(mDeadChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mDeadChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 0); // not dead + mTotalDeadChipPos->SetBinContent(mTotalDeadChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mTotalDeadChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 0); // not dead + } + int ilink = ichip / 3; + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + if (mErrorVsFeeid && (mErrorCount[istave][ilink][ierror] != 0)) { + mErrorVsFeeid->SetBinContent(((istave + StaveBoundary[mLayer]) * 3) + ilink + 1, ierror + 1, mErrorCount[istave][ilink][ierror]); + } + } + } + mGeneralOccupancy->SetBinContent(istave + 1 + StaveBoundary[mLayer], *(std::max_element(mOccupancyLane[istave], mOccupancyLane[istave] + nChipsPerHic[mLayer]))); + mGeneralNoisyPixel->SetBinContent(istave + 1 + StaveBoundary[mLayer], mNoisyPixelNumber[mLayer][istave]); + } else { + for (int ichip = 0; ichip < nHicPerStave[mLayer] * nChipsPerHic[mLayer]; ichip++) { + if (!mChipStat[istave][ichip]) { + mDeadChipPos->SetBinContent(mDeadChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mDeadChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 1); + mTotalDeadChipPos->SetBinContent(mTotalDeadChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mTotalDeadChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 1); + } else { + mAliveChipPos->SetBinContent(mAliveChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mAliveChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 1); + mTotalAliveChipPos->SetBinContent(mTotalAliveChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mTotalAliveChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 1); + mDeadChipPos->SetBinContent(mDeadChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mDeadChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 0); // not dead + mTotalDeadChipPos->SetBinContent(mTotalDeadChipPos->GetXaxis()->FindBin(mChipZ[istave][ichip]), mTotalDeadChipPos->GetYaxis()->FindBin(mChipPhi[istave][ichip]), 0); // not dead + } + } + + for (int ihic = 0; ihic < nHicPerStave[mLayer]; ihic++) { + int ilink = ihic / (nHicPerStave[mLayer] / 2); + mChipStaveOccupancy->SetBinContent(2 * ihic + 1, istave + 1, mOccupancyLane[istave][2 * ihic]); + mChipStaveOccupancy->SetBinContent(2 * ihic + 2, istave + 1, mOccupancyLane[istave][2 * ihic + 1]); + if (ihic == 0 || ihic == 7) { + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + if (mErrorVsFeeid && (mErrorCount[istave][ilink][ierror] != 0)) { + mErrorVsFeeid->SetBinContent((3 * StaveBoundary[3]) + ((StaveBoundary[mLayer] - StaveBoundary[NLayerIB] + istave) * 2) + ilink + 1, ierror + 1, mErrorCount[istave][ilink][ierror]); + } + } + } + } + mGeneralOccupancy->SetBinContent(istave + 1 + StaveBoundary[mLayer], *(std::max_element(mOccupancyLane[istave], mOccupancyLane[istave] + nHicPerStave[mLayer] * 2))); + mGeneralNoisyPixel->SetBinContent(istave + 1 + StaveBoundary[mLayer], mNoisyPixelNumber[mLayer][istave]); + } + } + + mDeadChipPos->ResetStats(); + mAliveChipPos->ResetStats(); + + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + int feeError = mErrorVsFeeid->Integral(1, mErrorVsFeeid->GetXaxis()->GetNbins(), ierror + 1, ierror + 1); + mErrorPlots->SetBinContent(ierror + 1, feeError); + } + + // delete pointor in monitorData() + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + delete[] digVec[istave]; + } + delete[] digVec; + + for (int i = 0; i < (int)activeStaves.size(); i++) { + delete occupancyPlotTmp[i]; + } + delete[] occupancyPlotTmp; + + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - start).count(); + + mTFCount++; +} + +void ITSFhrTask::getParameters() +{ + mIgnoreRampUpData = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "IgnoreRampUpData", mIgnoreRampUpData); + mNThreads = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "decoderThreads", mNThreads); + mLayer = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "Layer", mLayer); + mHitCutForCheck = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "HitNumberCut", mHitCutForCheck); + mHitCutForNoisyPixel = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "HitNumberCutForNoisyPixel", mHitCutForNoisyPixel); + mOccupancyCutForNoisyPixel = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "OccupancyNumberCutForNoisyPixel", mOccupancyCutForNoisyPixel); + mMaxGeneralAxisRange = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "MaxGeneralAxisRange", mMaxGeneralAxisRange); + mMinGeneralAxisRange = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "MinGeneralAxisRange", mMinGeneralAxisRange); + mMaxGeneralNoisyAxisRange = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "MaxGeneralNoisyAxisRange", mMaxGeneralNoisyAxisRange); + mMinGeneralNoisyAxisRange = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "MinGeneralNoisyAxisRange", mMinGeneralNoisyAxisRange); + mPhibins = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "Phibins", mPhibins); + mEtabins = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "Etabins", mEtabins); + mCutTFForSparse = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "CutSparseTF", mCutTFForSparse); + mDoHitmapFilter = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "DoHitmapFilter", mDoHitmapFilter); + mPhysicalOccupancyIB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "PhysicalOccupancyIB", mPhysicalOccupancyIB); + mPhysicalOccupancyOB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "PhysicalOccupancyOB", mPhysicalOccupancyOB); +} + +void ITSFhrTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ITSFhrTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSFhrTask::resetGeneralPlots() +{ + resetObject(mErrorPlots); + resetObject(mErrorVsFeeid); +} + +void ITSFhrTask::resetOccupancyPlots() +{ + mChipStaveOccupancy->Reset(); + mChipStaveEventHitCheck->Reset(); + mOccupancyPlot->Reset(); + mDeadChipPos->Reset(); + mAliveChipPos->Reset(); + mTotalDeadChipPos->Reset(); + mTotalAliveChipPos->Reset(); + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + mStaveHitmap[istave]->Reset(); + } +} + +void ITSFhrTask::resetObject(TH1* obj) +{ + if (obj) { + obj->Reset(); + } +} + +void ITSFhrTask::reset() +{ + resetGeneralPlots(); + resetOccupancyPlots(); + mGeneralOccupancy->Reset("content"); + mGeneralNoisyPixel->Reset("content"); + mDecoder->clearStat(); + + if (mLayer < NLayerIB) { + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + for (int ilink = 0; ilink < 3; ilink++) { + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + mErrorCount[istave][ilink][ierror] = 0; + } + } + for (int ichip = 0; ichip < nChipsPerHic[mLayer]; ichip++) { + mHitnumberLane[istave][ichip] = 0; + mOccupancyLane[istave][ichip] = 0; + mHitPixelID_InStave[istave][0][ichip].clear(); + mChipStat[istave][ichip] = 0; + } + } + } else { + for (int istave = 0; istave < NStaves[mLayer]; istave++) { + for (int ilink = 0; ilink < 2; ilink++) { + for (int ierror = 0; ierror < o2::itsmft::GBTLinkDecodingStat::NErrorsDefined; ierror++) { + mErrorCount[istave][ilink][ierror] = 0; + } + } + for (int ihic = 0; ihic < nHicPerStave[mLayer]; ihic++) { + mHitnumberLane[istave][2 * ihic] = 0; + mHitnumberLane[istave][2 * ihic + 1] = 0; + mOccupancyLane[istave][2 * ihic] = 0; + mOccupancyLane[istave][2 * ihic + 1] = 0; + for (int ichip = 0; ichip < nChipsPerHic[mLayer]; ichip++) { + mHitPixelID_InStave[istave][ihic][ichip].clear(); + mChipStat[istave][ihic * nChipsPerHic[mLayer] + ichip] = 0; + } + } + } + } + std::fill(&mNoisyPixelNumber[0][0], &mNoisyPixelNumber[0][0] + 7 * 48, 0); + ILOG(Debug, Devel) << "Reset" << ENDM; +} + +void ITSFhrTask::getStavePoint(int layer, int stave, double* px, double* py) +{ + float stepAngle = TMath::Pi() * 2 / NStaves[layer]; // the angle between to stave + float midAngle = StartAngle[layer] + (stave * stepAngle); // mid point angle + float staveRotateAngle = TMath::Pi() / 2 - (stave * stepAngle); // how many angle this stave rotate(compare with first stave) + px[1] = MidPointRad[layer] * TMath::Cos(midAngle); // there are 4 point to decide this TH2Poly bin + // 0:left point in this stave; + // 1:mid point in this stave; + // 2:right point in this stave; + // 3:higher point int this stave; + py[1] = MidPointRad[layer] * TMath::Sin(midAngle); // 4 point calculated accord the blueprint + // roughly calculate + if (layer < NLayerIB) { + px[0] = 7.7 * TMath::Cos(staveRotateAngle) + px[1]; + py[0] = -7.7 * TMath::Sin(staveRotateAngle) + py[1]; + px[2] = -7.7 * TMath::Cos(staveRotateAngle) + px[1]; + py[2] = 7.7 * TMath::Sin(staveRotateAngle) + py[1]; + px[3] = 5.623 * TMath::Sin(staveRotateAngle) + px[1]; + py[3] = 5.623 * TMath::Cos(staveRotateAngle) + py[1]; + } else { + px[0] = 21 * TMath::Cos(staveRotateAngle) + px[1]; + py[0] = -21 * TMath::Sin(staveRotateAngle) + py[1]; + px[2] = -21 * TMath::Cos(staveRotateAngle) + px[1]; + py[2] = 21 * TMath::Sin(staveRotateAngle) + py[1]; + px[3] = 40 * TMath::Sin(staveRotateAngle) + px[1]; + py[3] = 40 * TMath::Cos(staveRotateAngle) + py[1]; + } +} +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSHelpers.cxx b/Modules/ITS/src/ITSHelpers.cxx new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/Modules/ITS/src/ITSHelpers.cxx @@ -0,0 +1 @@ + diff --git a/Modules/ITS/src/ITSNoisyPixelTask.cxx b/Modules/ITS/src/ITSNoisyPixelTask.cxx new file mode 100644 index 0000000000..86dbbc9557 --- /dev/null +++ b/Modules/ITS/src/ITSNoisyPixelTask.cxx @@ -0,0 +1,544 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// +/// +/// ITSNoisyPixelTask.cxx +/// +/// + +#include "QualityControl/QcInfoLogger.h" +#include "ITS/ITSNoisyPixelTask.h" + +#include +#include +#include +#include +#include +#include +#include +#include "CCDB/BasicCCDBManager.h" +#include +#include "Common/Utils.h" + +using o2::itsmft::Digit; +using namespace o2::itsmft; +using namespace o2::its; + +namespace o2::quality_control_modules::its +{ + +ITSNoisyPixelTask::ITSNoisyPixelTask() : TaskInterface() {} + +ITSNoisyPixelTask::~ITSNoisyPixelTask() +{ + + if (mEnableOrderedHitsObject) { + delete hOrderedHitsAddressIB; + delete hOrderedHitsAddressML; + delete hOrderedHitsAddressOL; + } + + for (Int_t iLayer = 0; iLayer < NLayer; iLayer++) { + + if (!mEnableLayers[iLayer]) + continue; + + // INNER BARREL + if (iLayer < 3) { + + delete hOccupancyIB[iLayer]; + + for (Int_t iStave = 0; iStave < mNStaves[iLayer]; iStave++) + delete hNoisyPixelMapIB[iLayer][iStave]; + + } + + // OUTER BARREL + else { + + delete hOccupancyOB[iLayer - 3]; + + for (Int_t iStave = 0; iStave < mNStaves[iLayer]; iStave++) + delete hNoisyPixelMapOB[iLayer - 3][iStave]; + } + } +} + +void ITSNoisyPixelTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + + ILOG(Debug, Devel) << "initialize ITSNoisyPixelTask" << ENDM; + + getJsonParameters(); + + if (mLocalGeometryFile == 1) { + ILOG(Debug, Devel) << "Getting geometry from local file" << ENDM; + o2::base::GeometryManager::loadGeometry(mGeomPath.c_str()); + } else { + ILOG(Debug, Devel) << "Getting geometry from ccdb - timestamp: " << std::stol(mGeoTimestamp) << ENDM; + std::map metadata; + TaskInterface::retrieveConditionAny("GLO/Config/GeometryAligned", metadata, std::stol(mGeoTimestamp)); + if (!o2::base::GeometryManager::isGeometryLoaded()) { + ILOG(Fatal, Support) << "Can't retrive geometry from ccdb timestamp: " << std::stol(mGeoTimestamp) << ENDM; + throw std::runtime_error("Can't retrive geometry from ccdb!"); + } + } + mGeom = o2::its::GeometryTGeo::Instance(); + + createAllHistos(); + + publishHistos(); + + // get dict from ccdb + mTimestamp = std::stol(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "dicttimestamp", "0")); + long int ts = mTimestamp ? mTimestamp : o2::ccdb::getCurrentTimestamp(); + ILOG(Debug, Devel) << "Getting dictionary from ccdb - timestamp: " << ts << ENDM; + auto& mgr = o2::ccdb::BasicCCDBManager::instance(); + mgr.setTimestamp(ts); + mDict = mgr.get("ITS/Calib/ClusterDictionary"); + ILOG(Debug, Devel) << "Dictionary size: " << mDict->getSize() << ENDM; +} + +void ITSNoisyPixelTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; +} + +void ITSNoisyPixelTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ITSNoisyPixelTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + std::chrono::time_point start; + std::chrono::time_point end; + int difference; + start = std::chrono::high_resolution_clock::now(); + + ILOG(Debug, Devel) << "START DOING QC General" << ENDM; + + int lay = -1, sta, hsta, mod, chip; + + int col = 0, row = 0, ChipID = 0; + + uint64_t label; + + // CASE1. HITS FROM CLUSTERS + if (mQueryOption == kCluster) { + auto RofArr = ctx.inputs().get>("clustersrof"); + auto clusArr = ctx.inputs().get>("compclus"); + int ROFCycle = RofArr.size(); + NormalizeOccupancyPlots(ROFCycle); + + for (const auto& rof : RofArr) { + + auto clustersInFrame = rof.getROFData(clusArr); + + for (auto& hit : clustersInFrame) { + + ChipID = hit.getSensorID(); + col = hit.getCol(); + row = hit.getRow(); + + if (mEnableOrderedHitsObject) { + + label = 1024 * 512; + label = label * ChipID + 1024 * row + col; + hashtable[label]++; + } + + mGeom->getChipId(ChipID, lay, sta, hsta, mod, chip); + + if (lay < 3) { + + Double_t Addr[3] = { (double)col, (double)row, (double)chip }; + hOccupancyIB[lay]->Fill(chip, sta, 1. / ((mROFcounter + ROFCycle) * 5.24e5)); + hNoisyPixelMapIB[lay][sta]->Fill(Addr); + + } else { + + std::vector XY = MapOverHIC(col, row, chip); + Double_t Addr[3] = { (double)XY[0], (double)XY[1], (double)(mod + hsta * mNHicPerStave[lay] / 2) }; + + hOccupancyOB[lay - 3]->Fill(mod + hsta * mNHicPerStave[lay] / 2, sta, 1. / ((mROFcounter + ROFCycle) * 5.24e5 * mNChipsPerHic[lay])); + hNoisyPixelMapOB[lay - 3][sta]->Fill(Addr); + } + } + } + mROFcounter += ROFCycle; + mROFcycle += ROFCycle; + } + // END OF CASE1 (HITS FROM CLUSTERS) + + // CASE2. HITS FROM DIGITS + if (mQueryOption == kDigit) { + auto RofArr = ctx.inputs().get>("digitsrof"); + auto digits = ctx.inputs().get>("digits"); + int ROFCycle = RofArr.size(); + NormalizeOccupancyPlots(ROFCycle); + + for (auto&& hit : digits) { + + ChipID = hit.getChipIndex(); + col = hit.getColumn(); + row = hit.getRow(); + + if (mEnableOrderedHitsObject) { + + label = 1024 * 512; + label = label * ChipID + 1024 * row + col; + hashtable[label]++; + } + + mGeom->getChipId(ChipID, lay, sta, hsta, mod, chip); + + if (lay < 3) { + + Double_t Addr[3] = { (double)col, (double)row, (double)chip }; + hOccupancyIB[lay]->Fill(chip, sta, 1. / ((mROFcounter + ROFCycle) * 5.24e5)); + hNoisyPixelMapIB[lay][sta]->Fill(Addr); + + } else { + + std::vector XY = MapOverHIC(col, row, chip); + Double_t Addr[3] = { (double)XY[0], (double)XY[1], (double)(mod + hsta * mNHicPerStave[lay] / 2) }; + + hOccupancyOB[lay - 3]->Fill(mod + hsta * mNHicPerStave[lay] / 2, sta, 1. / ((mROFcounter + ROFCycle) * 5.24e5 * mNChipsPerHic[lay])); + hNoisyPixelMapOB[lay - 3][sta]->Fill(Addr); + } + } + + mROFcounter += ROFCycle; + mROFcycle += ROFCycle; + } + // END OF CASE2 (HITS FROM DIGITS) + + // SHOWING NOISY PIXELS IN ORDER OF HITS + // x-axis label will be displayed as L_-[, for OB]-;; + if (mEnableOrderedHitsObject && mROFcycle >= mOccUpdateFrequency) { + + hOrderedHitsAddressIB->Reset(); + hOrderedHitsAddressML->Reset(); + hOrderedHitsAddressOL->Reset(); + + std::multimap> orderedHitsIB; + std::multimap> orderedHitsML; + std::multimap> orderedHitsOL; + + for (auto [key, value] : hashtable) { + + int chipid_ = (int)(key / (1024 * 512)); + + mGeom->getChipId(chipid_, lay, sta, hsta, mod, chip); + + if (!mEnableLayers[lay]) + continue; + + if (lay < 3) + orderedHitsIB.insert({ value, key }); + else if (lay < 5) + orderedHitsML.insert({ value, key }); + else + orderedHitsOL.insert({ value, key }); + } + + int counterbin = 1; + for (auto [kkey, vvalue] : orderedHitsIB) { + + int chipid_ = (int)(vvalue / (1024 * 512)); + mGeom->getChipId(chipid_, lay, sta, hsta, mod, chip); + + int column_ = (int)((vvalue % (1024 * 512)) % 1024); + int row_ = (int)((vvalue % (1024 * 512)) / 1024); + hOrderedHitsAddressIB->GetXaxis()->SetBinLabel(counterbin, Form("L%d_%d-%d;%d;%d", lay, sta, chip, column_, row_)); + + hOrderedHitsAddressIB->SetBinContent(counterbin, 1. * kkey / mROFcycle); + + if (counterbin == nmostnoisy) { + counterbin = 1; + break; + } else + counterbin++; + } + + for (auto [kkey, vvalue] : orderedHitsML) { + + int chipid_ = (int)(vvalue / (1024 * 512)); + mGeom->getChipId(chipid_, lay, sta, hsta, mod, chip); + + int column_ = (int)((vvalue % (1024 * 512)) % 1024); + int row_ = (int)((vvalue % (1024 * 512)) / 1024); + int mod_offset = (int)(hsta * mNHicPerStave[lay] / 2); + hOrderedHitsAddressML->GetXaxis()->SetBinLabel(counterbin, Form("L%d_%d-%d-%d;%d;%d", lay, sta, mod + mod_offset, chip, column_, row_)); + + hOrderedHitsAddressML->SetBinContent(counterbin, 1. * kkey / mROFcycle); + + if (counterbin == nmostnoisy) { + counterbin = 1; + break; + } else + counterbin++; + } + + for (auto [kkey, vvalue] : orderedHitsOL) { + + int chipid_ = (int)(vvalue / (1024 * 512)); + mGeom->getChipId(chipid_, lay, sta, hsta, mod, chip); + + int column_ = (int)((vvalue % (1024 * 512)) % 1024); + int row_ = (int)((vvalue % (1024 * 512)) / 1024); + int mod_offset = (int)(hsta * mNHicPerStave[lay] / 2); + hOrderedHitsAddressOL->GetXaxis()->SetBinLabel(counterbin, Form("L%d_%d-%d-%d;%d;%d", lay, sta, mod + mod_offset, chip, column_, row_)); + + hOrderedHitsAddressOL->SetBinContent(counterbin, 1. * kkey / mROFcycle); + + if (counterbin == nmostnoisy) { + counterbin = 1; + break; + } else + counterbin++; + } + + hashtable.clear(); + mROFcycle = 0; + } + + // setting errors for occupancy plots (for the merger) + for (int ilayer = 0; ilayer < 7; ilayer++) { + if (ilayer < 3) { + for (int ix = 1; ix <= hOccupancyIB[ilayer]->GetNbinsX(); ix++) { + for (int iy = 1; iy <= hOccupancyIB[ilayer]->GetNbinsY(); iy++) { + hOccupancyIB[ilayer]->SetBinError(ix, iy, 1e-15); + } + } + } else { + for (int ix = 1; ix <= hOccupancyOB[ilayer - 3]->GetNbinsX(); ix++) { + for (int iy = 1; iy <= hOccupancyOB[ilayer - 3]->GetNbinsY(); iy++) { + hOccupancyOB[ilayer - 3]->SetBinError(ix, iy, 1e-15); + } + } + } + } + + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - start).count(); + mTotalTimeInQCTask += difference; + ILOG(Debug, Devel) << "Time in QC Noisy Pixel Task: " << difference << ENDM; +} + +void ITSNoisyPixelTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ITSNoisyPixelTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSNoisyPixelTask::reset() +{ + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + + if (mEnableOrderedHitsObject) { + hOrderedHitsAddressIB->Reset(); + hOrderedHitsAddressML->Reset(); + hOrderedHitsAddressOL->Reset(); + } + + for (Int_t iLayer = 0; iLayer < NLayer; iLayer++) { + if (!mEnableLayers[iLayer]) + continue; + + // INNER BARREL + if (iLayer < 3) { + hOccupancyIB[iLayer]->Reset(); + + for (Int_t iStave = 0; iStave < mNStaves[iLayer]; iStave++) + hNoisyPixelMapIB[iLayer][iStave]->Reset(); + + } // END INNER BARREL + // OUTER BARRREL + else { + + hOccupancyOB[iLayer - 3]->Reset(); + + for (Int_t iStave = 0; iStave < mNStaves[iLayer]; iStave++) + hNoisyPixelMapOB[iLayer - 3][iStave]->Reset(); + } + } +} + +void ITSNoisyPixelTask::createAllHistos() +{ + + if (mEnableOrderedHitsObject) { + hOrderedHitsAddressIB = new TH1D("OrderedHitsAddressIB", "OrderedHitsAddresIB", nmostnoisy, 0, nmostnoisy); + hOrderedHitsAddressIB->SetTitle("Noisiest pixels in IB - "); + formatAxes(hOrderedHitsAddressIB, "#", "Hits/ROF"); + addObject(hOrderedHitsAddressIB); + + hOrderedHitsAddressML = new TH1D("OrderedHitsAddressML", "OrderedHitsAddresML", nmostnoisy, 0, nmostnoisy); + hOrderedHitsAddressML->SetTitle("Noisiest pixels in ML - "); + formatAxes(hOrderedHitsAddressML, "#", "Hits/ROF"); + addObject(hOrderedHitsAddressML); + + hOrderedHitsAddressOL = new TH1D("OrderedHitsAddressOL", "OrderedHitsAddresOL", nmostnoisy, 0, nmostnoisy); + hOrderedHitsAddressOL->SetTitle("Noisiest pixels in OL - "); + formatAxes(hOrderedHitsAddressOL, "#", "Hits/ROF"); + addObject(hOrderedHitsAddressOL); + } + + for (Int_t iLayer = 0; iLayer < NLayer; iLayer++) { + + if (!mEnableLayers[iLayer]) + continue; + + // INNER BARREL + if (iLayer < 3) { + + hOccupancyIB[iLayer] = new TH2D(Form("Layer%d/PixelOccupancy", iLayer), Form("Layer%dPixelOccupancy", iLayer), mNChipsPerHic[iLayer], 0, mNChipsPerHic[iLayer], mNStaves[iLayer], 0, mNStaves[iLayer]); + hOccupancyIB[iLayer]->SetTitle(Form("Hits per pixel per ROF, L%d", iLayer)); + hOccupancyIB[iLayer]->SetBit(TH1::kIsAverage); + addObject(hOccupancyIB[iLayer]); + formatAxes(hOccupancyIB[iLayer], "Chip Number", "Stave Number", 1, 1.10); + hOccupancyIB[iLayer]->SetStats(0); + + for (Int_t iStave = 0; iStave < mNStaves[iLayer]; iStave++) { + + Int_t thn_binning[3] = { 1024, 512, mNChipsPerHic[iLayer] }; + Double_t thn_min[3] = { 0, 0, 0 }, thn_max[3] = { 1024, 512, (double)mNChipsPerHic[iLayer] }; + hNoisyPixelMapIB[iLayer][iStave] = new THnSparseD(Form("Layer%d/Stave%d/NoisyPixelMapVsChip", iLayer, iStave), Form("Layer%dStave%dNoisyPixelMapVsChip", iLayer, iStave), 3, thn_binning, thn_min, thn_max); + hNoisyPixelMapIB[iLayer][iStave]->SetTitle(Form("Pixels Hits Map on L%d_%d. x:colum, y:row, z:chip", iLayer, iStave)); + addObject(hNoisyPixelMapIB[iLayer][iStave]); + + } // end of per-stave objects + } // END OF INNER BARREL + // OUTER BARREL + else { + + hOccupancyOB[iLayer - 3] = new TH2D(Form("Layer%d/PixelOccupancy", iLayer), Form("Layer%dPixelOccupancy", iLayer), mNHicPerStave[iLayer], 0, mNHicPerStave[iLayer], mNStaves[iLayer], 0, mNStaves[iLayer]); + hOccupancyOB[iLayer - 3]->SetTitle(Form("Hits per pixel per ROF L%d", iLayer)); + hOccupancyOB[iLayer - 3]->SetBit(TH1::kIsAverage); + addObject(hOccupancyOB[iLayer - 3]); + formatAxes(hOccupancyOB[iLayer - 3], "HIC Number", "Stave Number", 1, 1.10); + hOccupancyOB[iLayer - 3]->SetStats(0); + + for (Int_t iStave = 0; iStave < mNStaves[iLayer]; iStave++) { + + Int_t thn_binning_ob[3] = { 1024 * 7, 512 * 2, mNHicPerStave[iLayer] }; + Double_t thn_min_ob[3] = { 0, 0, 0 }, thn_max_ob[3] = { 1024 * 7, 512 * 2, (double)mNHicPerStave[iLayer] }; + hNoisyPixelMapOB[iLayer - 3][iStave] = new THnSparseD(Form("Layer%d/Stave%d/NoisyPixelMapVsHIC", iLayer, iStave), Form("Layer%dStave%dNoisyPixelMapVsHIC", iLayer, iStave), 3, thn_binning_ob, thn_min_ob, thn_max_ob); + hNoisyPixelMapOB[iLayer - 3][iStave]->SetTitle(Form("Pixels Hits Map on L%d_%d. x:colum along HIC, y:row along HIC, z:HIC", iLayer, iStave)); + addObject(hNoisyPixelMapOB[iLayer - 3][iStave]); + } + } + } +} + +void ITSNoisyPixelTask::getJsonParameters() +{ + mLocalGeometryFile = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "isLocalGeometry", mLocalGeometryFile); + mGeoTimestamp = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "geomstamp", mGeoTimestamp); + mGeomPath = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "geomPath", mGeomPath); + + std::string LayerConfig = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "layer", "0000000"); + + for (int ilayer = 0; ilayer < NLayer; ilayer++) { + if (LayerConfig[ilayer] != '0') { + mEnableLayers[ilayer] = 1; + ILOG(Debug, Devel) << "enable layer : " << ilayer << ENDM; + } else { + mEnableLayers[ilayer] = 0; + } + } + + std::string queryOption = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "queryOption", "from-digit"); + if (queryOption.find("digit") != std::string::npos) + mQueryOption = kDigit; + else if (queryOption.find("cluster") != std::string::npos) + mQueryOption = kCluster; + + mOccUpdateFrequency = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "orderedPlotsUpdateFrequency", mOccUpdateFrequency); + int request_nmostnoisy = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "orderedPlotsBinNumber", 25); + + if (request_nmostnoisy > 0) + nmostnoisy = request_nmostnoisy; + mEnableOrderedHitsObject = (mOccUpdateFrequency >= 0 && request_nmostnoisy > 0); +} + +void ITSNoisyPixelTask::addObject(TObject* aObject) +{ + if (!aObject) { + ILOG(Debug, Devel) << " ERROR: trying to add non-existent object " << ENDM; + return; + } else + mPublishedObjects.push_back(aObject); +} + +void ITSNoisyPixelTask::formatAxes(TH1* h, const char* xTitle, const char* yTitle, float xOffset, float yOffset) +{ + h->GetXaxis()->SetTitle(xTitle); + h->GetYaxis()->SetTitle(yTitle); + h->GetXaxis()->SetTitleOffset(xOffset); + h->GetYaxis()->SetTitleOffset(yOffset); +} + +void ITSNoisyPixelTask::NormalizeOccupancyPlots(int n_cycle) +{ + // re-normalizing occupancy histograms before filling them with new hits + long double norm_factor = (long double)mROFcounter / (long double)(mROFcounter + n_cycle); + + for (Int_t iLayer = 0; iLayer < NLayer; iLayer++) { + if (!mEnableLayers[iLayer]) + continue; + + if (iLayer < 3) + hOccupancyIB[iLayer]->Scale(norm_factor); + + else + + hOccupancyOB[iLayer - 3]->Scale(norm_factor); + } +} + +std::vector ITSNoisyPixelTask::MapOverHIC(int col, int row, int chip) +{ + /* + Assuming the following order: + ----------------------------- + | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + ----------------------------- + | 7 | 8 | 9 |10 |11 |12 |13 | + ----------------------------- + */ + int X, Y; + if (chip < 7) { + X = (6 - chip) * 1024 + col; + Y = 512 + row; + } + if (chip >= 7) { + X = (chip - 7) * 1024 + col; + Y = row; + } + std::vector V{ X, Y }; + return V; +} + +void ITSNoisyPixelTask::publishHistos() +{ + for (unsigned int iObj = 0; iObj < mPublishedObjects.size(); iObj++) { + getObjectsManager()->startPublishing(mPublishedObjects.at(iObj)); + ILOG(Debug, Devel) << " Object will be published: " << mPublishedObjects.at(iObj)->GetName() << ENDM; + } +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSRawTask.cxx b/Modules/ITS/src/ITSRawTask.cxx new file mode 100644 index 0000000000..19281887d0 --- /dev/null +++ b/Modules/ITS/src/ITSRawTask.cxx @@ -0,0 +1,813 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSRawTask.cxx +/// \author Zhaozhong Shi +/// \author Markus Keil +/// \author Mario Sitta +/// \author Jian Liu +/// \author Liang Zhang +/// \brief ITS QC task for raw data analysis +/// + +#include "QualityControl/QcInfoLogger.h" +#include "ITS/ITSRawTask.h" +#include "ITS/ITSTaskVariables.h" +#include + +#include +#include +#include +using o2::itsmft::Digit; + +using namespace std; +using namespace o2::itsmft; +using namespace o2::its; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace its +{ + +ITSRawTask::ITSRawTask() + : TaskInterface() +{ + + o2::base::GeometryManager::loadGeometry(); + gStyle->SetPadRightMargin(0.15); + gStyle->SetPadLeftMargin(0.15); + m_objects.clear(); + m_publishedObjects.clear(); + enableLayers(); + + createHistos(); + + for (int i = 0; i < NError; i++) { + mErrors[i] = 0; + mErrorPre[i] = 0; + mErrorPerFile[i] = 0; + } + + gStyle->SetOptFit(0); + gStyle->SetOptStat(0); +} + +ITSRawTask::~ITSRawTask() +{ + delete mChipData; + delete mReader; + delete mCurr; + delete mPrev; + delete hErrorPlots; + delete hFileNameInfo; + delete hErrorFile; + delete hInfoCanvas; + for (int i = 0; i < NLayer; i++) { + delete hOccupancyPlot[i]; + delete hEtaPhiHitmap[i]; + delete hChipStaveOccupancy[i]; + } + for (int i = 0; i < 7; i++) { + for (int j = 0; j < 48; j++) { + for (int k = 0; k < 14; k++) { + delete hHicHitmap[i][j][k]; + for (int l = 0; l < 14; l++) { + delete hChipHitmap[i][j][k][l]; + } + } + } + } + for (int i = 0; i < 3; i++) { + delete hIBHitmap[i]; + } + delete mDigits; + delete gm; + for (int i = 0; i < NError; i++) { + delete pt[i]; + } + delete ptFileName; + delete ptNFile; + delete ptNEvent; + delete bulbGreen; + delete bulbRed; + delete bulbYellow; + delete bulb; +} + +void ITSRawTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize ITSRawTask" << ENDM; + + o2::its::GeometryTGeo* geom = o2::its::GeometryTGeo::Instance(); + geom->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + int numOfChips = geom->getNumberOfChips(); + ILOG(Debug, Devel) << "numOfChips = " << numOfChips << ENDM; + setNChips(numOfChips); + + for (int i = 0; i < NError; i++) { + pt[i] = new TPaveText(0.20, 0.80 - i * 0.05, 0.85, 0.85 - i * 0.05, "NDC"); + formatPaveText(pt[i], 0.04, gStyle->GetTextColor(), 12, ErrorType[i].Data()); + hErrorPlots->GetListOfFunctions()->Add(pt[i]); + } + + ptFileName = new TPaveText(0.20, 0.40, 0.85, 0.50, "NDC"); + ptFileName->SetName(""); + formatPaveText(ptFileName, 0.04, gStyle->GetTextColor(), 12, "Current File Processing: "); + + ptNFile = new TPaveText(0.20, 0.30, 0.85, 0.40, "NDC"); + ptFileName->SetName("FileName"); + formatPaveText(ptNFile, 0.04, gStyle->GetTextColor(), 12, "File Processed: "); + + ptNEvent = new TPaveText(0.20, 0.20, 0.85, 0.30, "NDC"); + ptNEvent->SetName("NEvent"); + formatPaveText(ptNEvent, 0.04, gStyle->GetTextColor(), 12, "Event Processed: "); + + bulbRed = new TPaveText(0.60, 0.75, 0.90, 0.85, "NDC"); + bulbRed->SetName("BulbRed"); + formatPaveText(bulbRed, 0.04, kRed, 12, "Red = QC Waiting"); + + bulbYellow = new TPaveText(0.60, 0.65, 0.90, 0.75, "NDC"); + bulbYellow->SetName("BulbYellow"); + formatPaveText(bulbYellow, 0.04, kYellow, 12, "Yellow = QC Pausing"); + + bulbGreen = new TPaveText(0.60, 0.55, 0.90, 0.65, "NDC"); + bulbGreen->SetName("BulbGreen"); + formatPaveText(bulbGreen, 0.04, kGreen, 12, "Green= QC Processing"); + + hInfoCanvas->SetTitle("QC Process Information Canvas"); + hInfoCanvas->GetListOfFunctions()->Add(ptFileName); + hInfoCanvas->GetListOfFunctions()->Add(ptNFile); + hInfoCanvas->GetListOfFunctions()->Add(ptNEvent); + hInfoCanvas->GetListOfFunctions()->Add(bulb); + hInfoCanvas->GetListOfFunctions()->Add(bulbRed); + hInfoCanvas->GetListOfFunctions()->Add(bulbYellow); + hInfoCanvas->GetListOfFunctions()->Add(bulbGreen); + + // InfoCanvas->SetStats(false); + + publishHistos(); + + ILOG(Debug, Devel) << "DONE Inititing Publication = " << ENDM; + + bulb->SetFillColor(kRed); + mTotalFileDone = 0; + TotalHisTime = 0; + mCounted = 0; + mYellowed = 0; +} + +void ITSRawTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; +} + +void ITSRawTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ITSRawTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + double eta, phi; + int lay, sta, ssta, mod, chip; + UShort_t col = 0, row = 0, ChipID = 0; + std::chrono::time_point start; + std::chrono::time_point startLoop; + std::chrono::time_point end; + int difference; + + start = std::chrono::high_resolution_clock::now(); + + ofstream timefout("HisTimeGlobal.dat", ios::app); + + ofstream timefout2("HisTimeLoop.dat", ios::app); + + ILOG(Debug, Devel) << "BEEN HERE BRO" << ENDM; + + int FileID = ctx.inputs().get("File"); + int EPID = ctx.inputs().get("EP"); + getProcessStatus(ctx.inputs().get("Finish"), FileFinish); + updateFile(ctx.inputs().get("Run"), ctx.inputs().get("EP"), FileID); + + // Will Fix Later// + + int ResetDecision = ctx.inputs().get("in"); + ILOG(Debug, Devel) << "Reset Histogram Decision = " << ResetDecision + << ENDM; + if (ResetDecision == 1) { + reset(); + } + + auto digits = ctx.inputs().get>("digits"); + auto events = ctx.inputs().get("Events"); + ILOG(Debug, Devel) << "Digit Size Getting For This TimeFrame (Event) = " << digits.size() << ENDM; + + mErrors = ctx.inputs().get>("Error"); + + for (int i = 0; i < NError; i++) { + mErrorPerFile[i] = mErrors[i] - mErrorPre[i]; + } + + for (int i = 0; i < NError; i++) { + ILOG(Debug, Devel) << " i = " << i << " Error = " << mErrors[i] << " ErrorPre = " << mErrorPre[i] + << " ErrorPerFile = " << mErrorPerFile[i] << ENDM; + hErrorPlots->SetBinContent(i + 1, mErrors[i]); + hErrorFile->SetBinContent((FileID + 1 + (EPID - 4) * 12), i + 1, mErrorPerFile[i]); + } + + if (FileFinish == 1) { + for (int i = 0; i < NError; i++) { + mErrorPre[i] = mErrors[i]; + } + } + + difference = std::chrono::duration_cast(end - start).count(); + // ILOG(Debug, Devel) << "Before Loop = " << difference/1000.0 << "s" << ENDM; + timefout << "Before Loop = " << difference / 1000.0 << "s" << std::endl; + int i = 0; + for (auto&& pixeldata : digits) { + startLoop = std::chrono::high_resolution_clock::now(); + + ChipID = pixeldata.getChipIndex(); + col = pixeldata.getColumn(); + row = pixeldata.getRow(); + // mNEvent = pixeldata.getROFrame(); + mNEvent = events.get()[i].NEvent; + i++; + // cout << "Event Compare: " << NEvent << ", " << NEventPre << endl; + + if (mNEvent % occUpdateFrequency == 0 && mNEvent > 0 && mNEvent != mNEventPre) { + updateOccupancyPlots(mNEventPre); + // cout << "Carried out, " << NEventPre << endl; + } + + // cout << "NFrame = " << NEvent << endl; + if (mCounted < mTotalCounted) { + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - startLoop).count(); + // ILOG(Debug, Devel) << "Before Geo = " << difference << "ns" << ENDM; + timefout2 << "Getting Value Time = " << difference << "ns" << std::endl; + } + + if (mNEvent % 1000000 == 0 && mNEvent > 0) { + ILOG(Debug, Devel) << "ChipID = " << ChipID << " col = " << col << " row = " << row << " mNEvent = " << mNEvent << ENDM; + } + // wouldnt this update this update the text for every digit in events 1000, 2000 ... ? + if (mNEvent % 1000 == 0 || mNEventPre != mNEvent) { + ptNEvent->Clear(); + ptNEvent->AddText(Form("Event Being Processed: %d", mNEvent)); + } + + if (mCounted < mTotalCounted) { + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - startLoop).count(); + // ILOG(Debug, Devel) << "Before Geo = " << difference << "ns" << ENDM; + timefout2 << "Before Geo = " << difference << "ns" << std::endl; + } + + gm->getChipId(ChipID, lay, sta, ssta, mod, chip); + // cout << "getID : " << ChipID << ", " << lay << ", " << sta << ", " << ssta << ", " << mod << ", " << chip << endl; + gm->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G)); + const math_utils::Point3D loc(0., 0., 0.); + auto glo = gm->getMatrixL2G(ChipID)(loc); + + if (!mlayerEnable[lay]) { + continue; + } + + if (mCounted < mTotalCounted) { + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - startLoop).count(); + // ILOG(Debug, Devel) << "After Geo = " << difference << "ns" << ENDM; + timefout2 << "After Geo = " << difference << "ns" << std::endl; + } + + int hicCol, hicRow; + // Todo: check if chipID is really chip ID + getHicCoordinates(lay, chip, col, row, hicRow, hicCol); + // if (lay == 0 && sta == 0 && ssta == 0 && mod == 0 && chip == 0) cout << ChipID << ", " << col << ", " << row << endl; + hHicHitmap[lay][sta][mod]->Fill(hicCol, hicRow); + if (lay > NLayerIB && chip > 6) { + // OB HICs: take into account that chip IDs are 0 .. 6, 8 .. 14 + hChipHitmap[lay][sta][mod][chip - 1]->Fill(col, row); + } else { + hChipHitmap[lay][sta][mod][chip]->Fill(col, row); + // hIBHitmap[lay]->Fill(hicCol, row+(sta*NRowHis)); + } + + if (mCounted < mTotalCounted) { + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - startLoop).count(); + // ILOG(Debug, Devel) << "After Geo = " << difference << "ns" << ENDM; + timefout2 << "Fill HitMaps = " << difference << "ns" << std::endl; + } + + if (mCounted < mTotalCounted) { + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - startLoop).count(); + // ILOG(Debug, Devel) << "After Geo = " << difference << "ns" << ENDM; + timefout2 << "Before glo etaphi = " << difference << "ns" << std::endl; + } + + eta = glo.eta(); + phi = glo.phi(); + hEtaPhiHitmap[lay]->Fill(eta, phi); + + if (mCounted < mTotalCounted) { + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - startLoop).count(); + // ILOG(Debug, Devel) << "After Geo = " << difference << "ns" << ENDM; + timefout2 << "After glo etaphi = " << difference << "ns" << std::endl; + mCounted = mCounted + 1; + } + + mNEventPre = mNEvent; + + } // end digits loop + i = 0; + if (mNEventPre > 0) { + updateOccupancyPlots(mNEventPre); + } + // cout << "EndUpdateOcc " << NEventPre <(end - start).count(); + ILOG(Debug, Devel) << "Time After Loop = " << difference / 1000.0 << "s" + << ENDM; + timefout << "Time After Loop = " << difference / 1000.0 << "s" << std::endl; + + ILOG(Debug, Devel) << "NEventDone = " << mNEvent << ENDM; + ILOG(Debug, Devel) << "Test " << ENDM; + + digits.clear(); + + end = std::chrono::high_resolution_clock::now(); + difference = std::chrono::duration_cast(end - start).count(); + TotalHisTime = TotalHisTime + difference; + ILOG(Debug, Devel) << "Time in Histogram = " << difference / 1000.0 << "s" + << ENDM; + timefout << "Time in Histogram = " << difference / 1000.0 << "s" << std::endl; + + if (mNEvent == 0 && ChipID == 0 && row == 0 && col == 0 && mYellowed == 0) { + bulb->SetFillColor(kYellow); + mYellowed = 1; + } +} + +void ITSRawTask::addObject(TObject* aObject, bool published) +{ + if (!aObject) { + ILOG(Debug, Devel) << "ERROR: trying to add non-existent object" << ENDM; + return; + } + m_objects.push_back(aObject); + if (published) { + m_publishedObjects.push_back(aObject); + } +} + +void ITSRawTask::createHistos() +{ + createGlobalHistos(); + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + if (!mlayerEnable[iLayer]) { + continue; + } + createLayerHistos(iLayer); + } +} + +void ITSRawTask::createGlobalHistos() +{ + hErrorPlots = new TH1D("General/ErrorPlots", "Decoding Errors", NError, 0.5, NError + 0.5); + formatAxes(hErrorPlots, "Error ID", "Counts"); + hErrorPlots->SetMinimum(0); + hErrorPlots->SetFillColor(kRed); + + hFileNameInfo = new TH1D("General/FileNameInfo", "FileNameInfo", 5, 0, 1); + formatAxes(hFileNameInfo, "InputFile", "Total Files Processed", 1.1); + + hErrorFile = new TH2D("General/ErrorFile", "Decoding Errors vs File ID", NFiles + 1, -0.5, NFiles + 0.5, NError, 0.5, NError + 0.5); + formatAxes(hErrorFile, "File ID (data-link)", "Error ID"); + formatStatistics(hErrorFile); + // format2DZaxis(hErrorFile); + hErrorFile->GetZaxis()->SetTitle("Counts"); + hErrorFile->SetMinimum(0); + + hInfoCanvas = new TH1D("General/InfoCanvas", "InfoCanvas", 3, -0.5, 2.5); + bulb = new TEllipse(0.2, 0.75, 0.30, 0.20); + + addObject(hErrorPlots); + addObject(hFileNameInfo); + addObject(hErrorFile); + addObject(hInfoCanvas); +} + +void ITSRawTask::createLayerHistos(int aLayer) +{ + createEtaPhiHitmap(aLayer); + createChipStaveOcc(aLayer); + + // 1d- occupancy histogram of the full layer, x-axis units = log (occupancy) + hOccupancyPlot[aLayer] = new TH1D(Form("Occupancy/Layer%dOccupancy", aLayer), + Form("ITS Layer %d Occupancy Distribution", aLayer), 300, -15, 0); + formatAxes(hOccupancyPlot[aLayer], "Occupancy", "Counts", 1., 2.2); + addObject(hOccupancyPlot[aLayer]); + /* +//HITMAPS of the full inner layers + int maxX, maxY, nBinsX, nBinsY; + + + maxX = 9 * NColHis; + maxY = NStaves[aLayer] * NRowHis; + nBinsX = maxX / SizeReduce; + nBinsY = maxY / SizeReduce; + if(aLayer < NLayerIB){ + hIBHitmap[aLayer] = new TH2I(Form("Occupancy/Layer%dHITMAP", aLayer), + Form("Hits on Layer %d", aLayer), nBinsX, 0, maxX, nBinsY, 0, maxY); + addObject(hIBHitmap[aLayer]); + } +*/ + // HITMAPS per HIC, binning in groups of SizeReduce * SizeReduce pixels + // chipHitmap: fine binning, one hitmap per chip, but not to be saved to CCDB (only for determination of noisy pixels) + for (int iStave = 0; iStave < NStaves[aLayer]; iStave++) { + createStaveHistos(aLayer, iStave); + } +} + +// hChipStaveOccupancy: Occupancy histograms for complete layer +// y-axis: number of stave +// x-axis: number of chip (IB) or number of HIC (OB) +void ITSRawTask::createChipStaveOcc(int aLayer) +{ + int nBinsX; + if (aLayer < NLayerIB) { + nBinsX = nChipsPerHic[aLayer]; + hChipStaveOccupancy[aLayer] = new TH2D(Form("Occupancy/Layer%d/Layer%dChipStave", aLayer, aLayer), + Form("ITS Layer%d, Occupancy vs Chip and Stave", aLayer), nBinsX, -.5, nBinsX - .5, NStaves[aLayer], -.5, NStaves[aLayer] - .5); + formatAxes(hChipStaveOccupancy[aLayer], "Chip Number", "Stave Number", 1., 1.1); + formatStatistics(hChipStaveOccupancy[aLayer]); + // format2DZaxis(hChipStaveOccupancy[aLayer]); + } else { + nBinsX = nHicPerStave[aLayer]; + hChipStaveOccupancy[aLayer] = new TH2D(Form("Occupancy/Layer%d/Layer%dHicStave", aLayer, aLayer), + Form("ITS Layer%d, Occupancy vs Hic and Stave", aLayer), nBinsX, -.5, nBinsX - .5, NStaves[aLayer], -.5, NStaves[aLayer] - .5); + formatAxes(hChipStaveOccupancy[aLayer], "Hic Number", "Stave Number", 1., 1.1); + formatStatistics(hChipStaveOccupancy[aLayer]); + // format2DZaxis(hChipStaveOccupancy[aLayer]); + } + + hChipStaveOccupancy[aLayer]->GetZaxis()->SetTitle("Number of Hits"); + hChipStaveOccupancy[aLayer]->GetZaxis()->SetTitleOffset(1.4); + addObject(hChipStaveOccupancy[aLayer]); +} + +// hEtaPhiHitmap: eta-phi hitmaps for complete layers, binning 100 x 100 pixels +// using eta coverage of TDR, assuming phi runs from 0 ... 2*Pi +void ITSRawTask::createEtaPhiHitmap(int aLayer) +{ + int NEta, NPhi; + if (aLayer < NLayerIB) { + NEta = 9 * 10; + NPhi = NStaves[aLayer] * 5; + } else { + NEta = nHicPerStave[aLayer] * 70; + NPhi = NStaves[aLayer] * 10; + } + hEtaPhiHitmap[aLayer] = new TH2I(Form("Occupancy/Layer%d/Layer%dEtaPhi", aLayer, aLayer), + Form("ITS Layer%d, Hits vs Eta and Phi", aLayer), NEta, (-1) * etaCoverage[aLayer], etaCoverage[aLayer], NPhi, PhiMin, PhiMax); + formatAxes(hEtaPhiHitmap[aLayer], "#eta", "#phi", 1., 1.1); + hEtaPhiHitmap[aLayer]->GetZaxis()->SetTitle("Number of Hits"); + hEtaPhiHitmap[aLayer]->GetZaxis()->SetTitleOffset(1.4); + addObject(hEtaPhiHitmap[aLayer]); +} + +void ITSRawTask::createStaveHistos(int aLayer, int aStave) +{ + for (int iHic = 0; iHic < nHicPerStave[aLayer]; iHic++) { + createHicHistos(aLayer, aStave, iHic); + } +} + +void ITSRawTask::createHicHistos(int aLayer, int aStave, int aHic) +{ + TString Name, Title; + int nBinsX, nBinsY, maxX, maxY, nChips; + + if (aLayer < NLayerIB) { + Name = Form("Occupancy/Layer%d/Stave%d/Layer%dStave%dHITMAP", aLayer, aStave, aLayer, aStave); + Title = Form("Hits on Layer %d, Stave %d", aLayer, aStave); + maxX = 9 * NColHis; + maxY = NRowHis; + nChips = 9; + } else { + Name = Form("Occupancy/Layer%d/Stave%d/HIC%d/Layer%dStave%dHIC%dHITMAP", aLayer, aStave, aHic, aLayer, aStave, aHic); + Title = Form("Hits on Layer %d, Stave %d, Hic %d", aLayer, aStave, aHic); + maxX = 7 * NColHis; + maxY = 2 * NRowHis; + nChips = 14; + } + nBinsX = maxX / mSizeReduce; + nBinsY = maxY / mSizeReduce; + hHicHitmap[aLayer][aStave][aHic] = new TH2I(Name, Title, nBinsX, 0, maxX, nBinsY, 0, maxY); + formatAxes(hHicHitmap[aLayer][aStave][aHic], "Column", "Row", 1., 1.1); + // formatting, moved here from initialize + hHicHitmap[aLayer][aStave][aHic]->GetZaxis()->SetTitleOffset(1.50); + hHicHitmap[aLayer][aStave][aHic]->GetZaxis()->SetTitle("Number of Hits"); + hHicHitmap[aLayer][aStave][aHic]->GetXaxis()->SetNdivisions(-32); + hHicHitmap[aLayer][aStave][aHic]->Draw("COLZ"); // should this really be drawn here? + addObject(hHicHitmap[aLayer][aStave][aHic]); + + for (int iChip = 0; iChip < nChips; iChip++) { + hChipHitmap[aLayer][aStave][aHic][iChip] = new TH2I(Form("chipHitmapL%dS%dH%dC%d", aLayer, aStave, aHic, iChip), + Form("chipHitmapL%dS%dH%dC%d", aLayer, aStave, aHic, iChip), 1024, -.5, 1023.5, 512, -.5, 511.5); + addObject(hChipHitmap[aLayer][aStave][aHic][iChip], false); + } +} + +// To be checked: +// - something like this should exist in the official geometry already +// - is aChip really the chipID (i.e. 0..6, 8.. 14 in case of OB HICs)? +void ITSRawTask::getHicCoordinates(int aLayer, int aChip, int aCol, int aRow, int& aHicRow, int& aHicCol) +{ + aChip &= 0xf; + if (aLayer < NLayerIB) { + aHicCol = aChip * NCols + aCol; + aHicRow = aRow; + } else { // OB Hic: chip row 0 at center of HIC + if (aChip < 7) { + aHicCol = aChip * NCols + aCol; + aHicRow = NRows - aRow - 1; + } else { + aHicRow = NRows + aRow; + aHicCol = 7 * NCols - ((aChip - 8) * NCols + aCol); + } + } +} + +void ITSRawTask::formatStatistics(TH2* h) +{ + + h->SetStats(0); +} +/* +void ITSRawTask::format2DZaxis(TH2 *h){ + + TPaletteAxis *palette = (TPaletteAxis*)h->GetListOfFunctions()->FindObject("palette"); + palette->SetLabelSize(0.5); + // TPad *pad = () + +} +*/ +void ITSRawTask::formatAxes(TH1* h, const char* xTitle, const char* yTitle, float xOffset, float yOffset) +{ + h->GetXaxis()->SetTitle(xTitle); + h->GetYaxis()->SetTitle(yTitle); + h->GetXaxis()->SetTitleOffset(xOffset); + h->GetYaxis()->SetTitleOffset(yOffset); +} + +void ITSRawTask::formatPaveText(TPaveText* aPT, float aTextSize, Color_t aTextColor, short aTextAlign, const char* aText) +{ + aPT->SetTextSize(aTextSize); + aPT->SetTextAlign(aTextAlign); + aPT->SetFillColor(0); + aPT->SetTextColor(aTextColor); + aPT->AddText(aText); +} + +void ITSRawTask::ConfirmXAxis(TH1* h) +{ + // Remove the current axis + h->GetXaxis()->SetLabelOffset(999); + h->GetXaxis()->SetTickLength(0); + // Redraw the new axis + gPad->Update(); + int XTicks = (h->GetXaxis()->GetXmax() - h->GetXaxis()->GetXmin()) / mDivisionStep; + + TGaxis* newaxis = new TGaxis(gPad->GetUxmin(), gPad->GetUymin(), gPad->GetUxmax(), gPad->GetUymin(), + h->GetXaxis()->GetXmin(), h->GetXaxis()->GetXmax(), XTicks, "N"); + newaxis->SetLabelOffset(0.0); + newaxis->Draw(); + h->GetListOfFunctions()->Add(newaxis); +} + +void ITSRawTask::ReverseYAxis(TH1* h) +{ + // Remove the current axis + h->GetYaxis()->SetLabelOffset(999); + h->GetYaxis()->SetTickLength(0); + + // Redraw the new axis + gPad->Update(); + + int YTicks = (h->GetYaxis()->GetXmax() - h->GetYaxis()->GetXmin()) / mDivisionStep; + TGaxis* newaxis = new TGaxis(gPad->GetUxmin(), gPad->GetUymax(), gPad->GetUxmin() - 0.001, gPad->GetUymin(), + h->GetYaxis()->GetXmin(), h->GetYaxis()->GetXmax(), YTicks, "N"); + + newaxis->SetLabelOffset(0); + newaxis->Draw(); + h->GetListOfFunctions()->Add(newaxis); +} + +void ITSRawTask::publishHistos() +{ + for (unsigned int iObj = 0; iObj < m_publishedObjects.size(); iObj++) { + getObjectsManager()->startPublishing(m_publishedObjects.at(iObj)); + } +} + +void ITSRawTask::addMetadata(int runID, int EpID, int fileID) +{ + for (unsigned int iObj = 0; iObj < m_publishedObjects.size(); iObj++) { + getObjectsManager()->addMetadata(m_publishedObjects.at(iObj)->GetName(), "Run", Form("%d", runID)); + getObjectsManager()->addMetadata(m_publishedObjects.at(iObj)->GetName(), "EP", Form("%d", EpID)); + getObjectsManager()->addMetadata(m_publishedObjects.at(iObj)->GetName(), "File", Form("%d", fileID)); + } +} + +void ITSRawTask::getProcessStatus(int aInfoFile, int& aFileFinish) +{ + aFileFinish = aInfoFile % 10; + // cout<<"aInfoFile = "<SetBinContent(1,FileID); + // hInfoCanvas->SetBinContent(2,TotalFileDone); + ptFileName->Clear(); + ptNFile->Clear(); + ptFileName->AddText(Form("File Being Proccessed: %s", FileName.Data())); + ptNFile->AddText(Form("File Processed: %d ", mTotalFileDone)); + + addMetadata(aRunID, aEpID, aFileID); + } + RunIDPre = aRunID; + FileIDPre = aFileID; +} + +void ITSRawTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ITSRawTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSRawTask::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + + mTotalFileDone = 0; + ptNFile->Clear(); + ptNFile->AddText(Form("File Processed: %d ", mTotalFileDone)); + mYellowed = 0; + + resetHitmaps(); + resetOccupancyPlots(); + + ILOG(Debug, Devel) << "DONE the histogram Resetting" << ENDM; +} + +// reset method for all plots that are supposed to be reset once +void ITSRawTask::resetHitmaps() +{ + hErrorPlots->Reset(); + hErrorFile->Reset(); + + // for (int iLayer = 0; iLayer < NLayerIB; iLayer ++) { + + // if (!layerEnable[iLayer]) continue; + // hIBHitmap[iLayer]->Reset(); + + //} + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + if (!mlayerEnable[iLayer]) { + continue; + } + hEtaPhiHitmap[iLayer]->Reset(); + // hIBHitmap[iLayer]->Reset(); + for (int iStave = 0; iStave < NStaves[iLayer]; iStave++) { + for (int iHic = 0; iHic < nHicPerStave[iLayer]; iHic++) { + hHicHitmap[iLayer][iStave][iHic]->Reset(); + for (int iChip = 0; iChip < nChipsPerHic[iLayer]; iChip++) { + hChipHitmap[iLayer][iStave][iHic][iChip]->Reset(); + } + } + } + } +} + +// reset method for all histos that are to be reset regularly +// (occupancy plots when recalculating / updating the occupancies) +void ITSRawTask::resetOccupancyPlots() +{ + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + if (!mlayerEnable[iLayer]) { + continue; + } + hOccupancyPlot[iLayer]->Reset(); + hChipStaveOccupancy[iLayer]->Reset(); + } +} + +void ITSRawTask::updateOccupancyPlots(int nEvents) +{ + double pixelOccupancy, chipOccupancy; + + resetOccupancyPlots(); + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + if (!mlayerEnable[iLayer]) { + continue; + } + // hEtaPhiHitmap[iLayer]->Reset(); + for (int iStave = 0; iStave < NStaves[iLayer]; iStave++) { + for (int iHic = 0; iHic < nHicPerStave[iLayer]; iHic++) { + for (int iChip = 0; iChip < nChipsPerHic[iLayer]; iChip++) { + chipOccupancy = hChipHitmap[iLayer][iStave][iHic][iChip]->Integral(); + chipOccupancy = chipOccupancy / ((double)nEvents * (double)NPixels); + // cout << "chipOcc = " << hChipHitmap[iLayer][iStave][iHic][iChip]->Integral() << ", " << iLayer << ", " << iStave << ", " << iHic << ", " << iChip << endl; + if (iLayer < NLayerIB) { + hChipStaveOccupancy[iLayer]->Fill(iChip, iStave, chipOccupancy); + } else { + hChipStaveOccupancy[iLayer]->Fill(iHic, iStave, chipOccupancy / nChipsPerHic[iLayer]); + } + for (int iCol = 0; iCol < NCols; iCol++) { + for (int iRow = 0; iRow < NRows; iRow++) { + pixelOccupancy = hChipHitmap[iLayer][iStave][iHic][iChip]->GetBinContent(iCol + 1, iRow + 1); + if (pixelOccupancy > 0) { + pixelOccupancy /= (double)nEvents; + hOccupancyPlot[iLayer]->Fill(log10(pixelOccupancy)); + } + } + } + } + } + } + } +} + +void ITSRawTask::enableLayers() +{ + + string mRunType, mEventPerPush, mTrackError, mWorkDir, enable[7]; + std::ifstream RunFileType("Config/RunType.dat"); + RunFileType >> mRunType; + + if (mRunType == "FakeHitRate") { + std::ifstream EventPush("Config/ConfigFakeRate.dat"); + EventPush >> mEventPerPush >> mTrackError >> mWorkDir >> enable[0] >> enable[1] >> enable[2] >> enable[3] >> enable[4] >> enable[5] >> enable[6]; + } + if (mRunType == "ThresholdScan") { + std::ifstream EventPush("Config/ConfigThreshold.dat"); + EventPush >> mEventPerPush >> mTrackError >> mWorkDir >> enable[0] >> enable[1] >> enable[2] >> enable[3] >> enable[4] >> enable[5] >> enable[6]; + } + for (int i = 0; i < 7; i++) { + mlayerEnable[i] = stoi(enable[i]); + } + // cout << "LayerEnable = " << " layer0 = " << layerEnable[0] << " layer5 = " << layerEnable[5] << endl; +} + +} // namespace its +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/ITS/src/ITSThresholdCalibrationCheck.cxx b/Modules/ITS/src/ITSThresholdCalibrationCheck.cxx new file mode 100644 index 0000000000..8c03bffa08 --- /dev/null +++ b/Modules/ITS/src/ITSThresholdCalibrationCheck.cxx @@ -0,0 +1,37 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSTrackSimCheck.cxx +/// \author Artem Isakov +/// \author Jian Liu +/// + +#include "ITS/ITSThresholdCalibrationCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" + +#include +#include +#include + +namespace o2::quality_control_modules::its +{ + +Quality ITSThresholdCalibrationCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + return result; +} + +void ITSThresholdCalibrationCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ +} +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSThresholdCalibrationTask.cxx b/Modules/ITS/src/ITSThresholdCalibrationTask.cxx new file mode 100644 index 0000000000..7edef32d30 --- /dev/null +++ b/Modules/ITS/src/ITSThresholdCalibrationTask.cxx @@ -0,0 +1,717 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file ITSThresholdCalibrationTask.cxx +/// \author Artem Isakov +/// + +#include "QualityControl/QcInfoLogger.h" +#include "ITS/ITSThresholdCalibrationTask.h" +#include "TLine.h" +#include "TLatex.h" +#include "Framework/InputRecordWalker.h" +#include "Common/Utils.h" + +using namespace o2::itsmft; +using namespace o2::its; + +namespace o2::quality_control_modules::its +{ + +ITSThresholdCalibrationTask::ITSThresholdCalibrationTask() : TaskInterface() +{ +} + +ITSThresholdCalibrationTask::~ITSThresholdCalibrationTask() +{ + int iScan = CalibType; + if (CalibType <= 3) { + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + delete hCalibrationLayer[iLayer][iScan]; + delete hCalibrationRMSLayer[iLayer][iScan]; + if (iScan == THR) { + delete hCalibrationThrNoiseLayer[iLayer]; + delete hCalibrationThrNoiseRMSLayer[iLayer]; + } + } + for (int iBarrel = 0; iBarrel < 3; iBarrel++) { + delete hCalibrationChipAverage[iScan][iBarrel]; + delete hCalibrationRMSChipAverage[iScan][iBarrel]; + } + } + + for (int iBarrel = 0; iBarrel < 3; iBarrel++) { + if (iScan == THR) { + delete hCalibrationThrNoiseChipAverage[iBarrel]; + delete hCalibrationThrNoiseRMSChipAverage[iBarrel]; + } + delete hCalibrationChipDone[iBarrel]; + delete hUnsuccess[iBarrel]; + if (iScan == TOT) { + delete hTimeOverThreshold[iBarrel]; + delete hTimeOverThresholdRms[iBarrel]; + delete hToA[iBarrel]; + delete hToARms[iBarrel]; + } + if (iScan == pixel) { + delete hCalibrationDColChipAverage[iBarrel]; + delete hCalibrationPixel_noise[iBarrel]; + delete hCalibrationPixel_dead[iBarrel]; + delete hCalibrationPixel_inEff[iBarrel]; + } + } + + if (iScan == TOT) { + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + delete hTimeOverThresholdLayer[iLayer]; + delete hTimeOverThresholdRmsLayer[iLayer]; + delete hToALayer[iLayer]; + delete hToARmsLayer[iLayer]; + } + } +} + +void ITSThresholdCalibrationTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + + ILOG(Debug, Devel) << "initialize ITSThresholdCalibrationTask" << ENDM; + + std::string mCalibrationType = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "CalibrationType", "THR"); // THR, ITHR, VCASN, TOT, pixel_noise, pixel_dead, pixel_ineff + + if (mCalibrationType == "THR") + CalibType = THR; + else if (mCalibrationType == "ITHR") + CalibType = ITHR; + else if (mCalibrationType == "VCASN") + CalibType = VCASN; + else if (mCalibrationType == "TOT") + CalibType = TOT; + else if (mCalibrationType == "Pixel") + CalibType = pixel; + + else + ILOG(Fatal, Support) << "Scan Type from .json " << mCalibrationType << " is Unknown! (should be THR, ITHR, VCASN or Pixel" << ENDM; + + createAllHistos(); + publishHistos(); +} + +void ITSThresholdCalibrationTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; +} + +void ITSThresholdCalibrationTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ITSThresholdCalibrationTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + std::string inStringChipDone, inString, inPixel; + char scanType; + for (auto&& input : o2::framework::InputRecordWalker(ctx.inputs())) { + if (input.header != nullptr && input.payload != nullptr) { + const auto* header = o2::framework::DataRefUtils::getHeader(input); + + if ((strcmp(header->dataOrigin.str, "ITS") == 0) && (strcmp(header->dataDescription.str, "TSTR") == 0)) { + const auto tmpstring = ctx.inputs().get>(input); + std::copy(tmpstring.begin(), tmpstring.end(), std::back_inserter(inString)); + } + if ((strcmp(header->dataOrigin.str, "ITS") == 0) && (strcmp(header->dataDescription.str, "QCSTR") == 0)) { + const auto tmpstring = ctx.inputs().get>(input); + std::copy(tmpstring.begin(), tmpstring.end(), std::back_inserter(inStringChipDone)); + } + if ((strcmp(header->dataOrigin.str, "ITS") == 0) && (strcmp(header->dataDescription.str, "PIXTYP") == 0)) { + const auto tmpstring = ctx.inputs().get>(input); + std::copy(tmpstring.begin(), tmpstring.end(), std::back_inserter(inPixel)); + } + + if ((strcmp(header->dataOrigin.str, "ITS") == 0) && (strcmp(header->dataDescription.str, "SCANT") == 0)) { + scanType = ctx.inputs().get(input); + } + } + } + + Int_t iScan; + if (scanType == 'V') + iScan = 0; + else if (scanType == 'I') + iScan = 1; + else if (scanType == 'T') + iScan = 2; + else if (scanType == 'A' || scanType == 'D') + iScan = 3; + else if (scanType == 'P') { + iScan = 4; + } + + if (iScan != CalibType) + ILOG(Fatal, Support) << "Scan Type from Data: " << scanType << " is different from scan type from .json " << CalibType << ENDM; + + auto splitRes = splitString(inString, "O2"); + + if (scanType == 'A' || scanType == 'D') + doAnalysisPixel(inPixel); + else + doAnalysisTHR(inString, iScan); + + //--------------- Fill chips for which scan is completed + auto splitResChipDone = splitString(inStringChipDone, "O2"); + for (auto StaveStr : splitResChipDone) { + if (StaveStr.size() > 0) { + CalibrationResStructTHR result = CalibrationParserTHR(StaveStr); + + int currentStave = StaveBoundary[result.Layer] + result.Stave + 1; + int iBarrel = getBarrel(result.Layer); + int currentChip = getCurrentChip(iBarrel, result.ChipID, result.HIC, result.Hs); + if (hCalibrationChipDone[iBarrel]->GetBinContent(currentChip, currentStave) > 0) { + continue; // chip may appear >twice here + } + hCalibrationChipDone[iBarrel]->Fill(currentChip - 1, currentStave - 1); + } + } +} + +void ITSThresholdCalibrationTask::doAnalysisTHR(std::string inString, int iScan) +{ + + auto splitRes = splitString(inString, "O2"); + + //-------------- General TH2 plots + for (auto StaveStr : splitRes) { + if (StaveStr.size() > 0) { + CalibrationResStructTHR result = CalibrationParserTHR(StaveStr); + + int currentStave = StaveBoundary[result.Layer] + result.Stave + 1; + int iBarrel = getBarrel(result.Layer); + int currentChip = getCurrentChip(iBarrel, result.ChipID, result.HIC, result.Hs); + + if (iScan <= 3) { + // fill 2D and 1D plots for THR/ITHR/VCASN + hCalibrationChipAverage[iScan][iBarrel]->SetBinContent(currentChip, currentStave, result.MainVal); + hCalibrationRMSChipAverage[iScan][iBarrel]->SetBinContent(currentChip, currentStave, result.RMS); + hCalibrationLayer[result.Layer][iScan]->Fill(result.MainVal); + hCalibrationRMSLayer[result.Layer][iScan]->Fill(result.RMS); + if (iScan == 2) { + hCalibrationThrNoiseChipAverage[iBarrel]->SetBinContent(currentChip, currentStave, result.Noise); + hCalibrationThrNoiseRMSChipAverage[iBarrel]->Fill(currentChip, currentStave, result.NoiseRMS); + hCalibrationThrNoiseLayer[result.Layer]->Fill(result.Noise); + hCalibrationThrNoiseRMSLayer[result.Layer]->Fill(result.NoiseRMS); + } + // Fill percentage of success + hUnsuccess[iBarrel]->SetBinContent(currentChip, currentStave, result.status); + } else if (iScan == 4) { + // fill 2D plots for the pulse length scan + hTimeOverThreshold[iBarrel]->SetBinContent(currentChip, currentStave, result.Tot); + hTimeOverThresholdRms[iBarrel]->SetBinContent(currentChip, currentStave, result.TotRms); + hToA[iBarrel]->SetBinContent(currentChip, currentStave, result.ToA); + hToARms[iBarrel]->SetBinContent(currentChip, currentStave, result.ToARms); + // fill 1D plots for the pulse length scan + hTimeOverThresholdLayer[result.Layer]->Fill(result.Tot); + hTimeOverThresholdRmsLayer[result.Layer]->Fill(result.TotRms); + hToALayer[result.Layer]->Fill(result.ToA); + hToARmsLayer[result.Layer]->Fill(result.ToARms); + } + } + } +} + +void ITSThresholdCalibrationTask::doAnalysisPixel(std::string inString) +{ + + auto splitRes = splitString(inString, "O2"); + + //-------------- General TH2 plots + for (auto StaveStr : splitRes) { + if (StaveStr.size() > 0) { + CalibrationResStructPixel result = CalibrationParserPixel(StaveStr); + + int currentStave = StaveBoundary[result.Layer] + result.Stave + 1; + int iBarrel = getBarrel(result.Layer); + int currentChip = getCurrentChip(iBarrel, result.ChipID, result.HIC, result.Hs); + + if (result.Type == 0) + hCalibrationPixel_noise[iBarrel]->SetBinContent(currentChip, currentStave, result.counts); + else if (result.Type == 1) + hCalibrationPixel_dead[iBarrel]->SetBinContent(currentChip, currentStave, result.counts); + else if (result.Type == 2) + hCalibrationPixel_inEff[iBarrel]->SetBinContent(currentChip, currentStave, result.counts); + else + hCalibrationDColChipAverage[iBarrel]->SetBinContent(currentChip, currentStave, result.Dcols); + } + } +} + +int ITSThresholdCalibrationTask::getCurrentChip(int barrel, int chipid, int hic, int hs) +{ + int currentChip; + switch (barrel) { + case 0: { + currentChip = chipid + 1; + break; + } + case 1: { + currentChip = chipid < 7 ? (chipid + 1) + 14 * (hic - 1) + hs * 56 : chipid + 14 * (hic - 1) + hs * 56; // this is already from 1 to 112 + break; + } + case 2: { + currentChip = chipid < 7 ? (chipid + 1) + 14 * (hic - 1) + hs * 98 : chipid + 14 * (hic - 1) + hs * 98; // this is already from 1 to 196 + break; + } + } + return currentChip; +} + +ITSThresholdCalibrationTask::CalibrationResStructPixel ITSThresholdCalibrationTask::CalibrationParserPixel(std::string input) +{ + CalibrationResStructPixel result; + auto StaveINFO = splitString(input, ","); + + for (std::string info : StaveINFO) { + if (info.size() == 0) + continue; + + std::string name = splitString(info, ":")[0]; + std::string data = splitString(info, ":")[1]; + + if (name == "ChipID") { + int o2chipid = std::stod(data); + int Hs, HIC, ChipID, Layer, Stave; + mp.expandChipInfoHW(o2chipid, Layer, Stave, Hs, HIC, ChipID); + result.Hs = Hs; + result.HIC = HIC; + result.Layer = Layer; + result.Stave = Stave; + result.ChipID = ChipID; + } else if (name == "PixelType") { + if (data == "Noisy") + result.Type = 0; + else if (data == "Dead") + result.Type = 1; + else if (data == "Ineff") + result.Type = 2; + } else if (name == "PixelNos") { + result.counts = std::stoi(data); + } else if (name == "DcolNos") { + result.Dcols = std::stoi(data); + } + } + return result; +} + +ITSThresholdCalibrationTask::CalibrationResStructTHR ITSThresholdCalibrationTask::CalibrationParserTHR(std::string input) +{ + CalibrationResStructTHR result; + auto StaveINFO = splitString(input, ","); + for (std::string info : StaveINFO) { + if (info.size() == 0) + continue; + + if (info.substr(0, 1) == "L") { + result.Layer = std::stod(info.substr(1, 1)); + result.Stave = std::stod(info.substr(3, 2)); + } else { + + std::string name = splitString(info, ":")[0]; + std::string data = splitString(info, ":")[1]; + if (name == "ChipID") { + int o2chipid = std::stod(data); + int Hs, HIC, ChipID, Layer, Stave; + mp.expandChipInfoHW(o2chipid, Layer, Stave, Hs, HIC, ChipID); + result.Hs = Hs; + result.HIC = HIC; + result.Layer = Layer; + result.Stave = Stave; + result.ChipID = ChipID; + } else if (name == "VCASN" || name == "THR" || name == "ITHR") { + result.MainVal = std::stof(data); + } else if (name == "Rms") { + result.RMS = std::stof(data); + } else if (name == "Status") { + result.status = std::stof(data); + } else if (name == "Noise") { + result.Noise = std::stof(data); + } else if (name == "NoiseRms") { + result.NoiseRMS = std::stof(data); + } else if (name == "Tot") { + result.Tot = std::stof(data) / 1000; // to get micro-seconds + } else if (name == "TotRms") { + result.TotRms = std::stof(data) / 1000; // to get micro-seconds + } else if (name == "ToA") { + result.ToA = std::stof(data); // this will be in nano-seconds automatically + } else if (name == "ToARms") { + result.ToARms = std::stof(data); // this will be in nano-seconds automatically + } + } + } + return result; +} + +void ITSThresholdCalibrationTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ITSThresholdCalibrationTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSThresholdCalibrationTask::reset() +{ + ILOG(Debug, Devel) << "Resetting the histogram" << ENDM; + int iScan = CalibType; + if (iScan <= 3) { + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + hCalibrationLayer[iLayer][iScan]->Reset(); + hCalibrationRMSLayer[iLayer][iScan]->Reset(); + + if (iScan == THR) { + hCalibrationThrNoiseLayer[iLayer]->Reset(); + hCalibrationThrNoiseRMSLayer[iLayer]->Reset(); + } + } + for (int iBarrel = 0; iBarrel < 3; iBarrel++) { + hCalibrationChipAverage[iScan][iBarrel]->Reset(); + hCalibrationRMSChipAverage[iScan][iBarrel]->Reset(); + } + } + + for (int iBarrel = 0; iBarrel < 3; iBarrel++) { + + hUnsuccess[iBarrel]->Reset(); + if (iScan == THR) { + hCalibrationThrNoiseChipAverage[iBarrel]->Reset(); + hCalibrationThrNoiseRMSChipAverage[iBarrel]->Reset(); + } + hCalibrationChipDone[iBarrel]->Reset(); + + if (iScan == TOT) { + hTimeOverThreshold[iBarrel]->Reset(); + hTimeOverThresholdRms[iBarrel]->Reset(); + hToA[iBarrel]->Reset(); + hToARms[iBarrel]->Reset(); + } + if (iScan > 3) { + hCalibrationDColChipAverage[iBarrel]->Reset(); + hCalibrationPixel_dead[iBarrel]->Reset(); + hCalibrationPixel_noise[iBarrel]->Reset(); + hCalibrationPixel_inEff[iBarrel]->Reset(); + } + } + if (iScan == TOT) { + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + hTimeOverThresholdLayer[iLayer]->Reset(); + hTimeOverThresholdRmsLayer[iLayer]->Reset(); + hToALayer[iLayer]->Reset(); + hToARmsLayer[iLayer]->Reset(); + } + } +} + +void ITSThresholdCalibrationTask::createAllHistos() +{ + + int iScan = CalibType; + if (iScan <= 3) { + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + hCalibrationLayer[iLayer][iScan] = new TH1F(Form("%sLayer%d", sScanTypes[iScan].Data(), iLayer), Form("%s for Layer%d", sScanTypes[iScan].Data(), iLayer), nXmax[iScan], -0.5, nXmax[iScan] - 0.5); + hCalibrationLayer[iLayer][iScan]->SetStats(0); + formatAxes(hCalibrationLayer[iLayer][iScan], Form("%s (%s) ", sScanTypes[iScan].Data(), sXtitles[iScan].Data()), "Chip counts", 1, 1.10); + addObject(hCalibrationLayer[iLayer][iScan]); + + hCalibrationRMSLayer[iLayer][iScan] = new TH1F(Form("%sRMSLayer%d", sScanTypes[iScan].Data(), iLayer), Form("%s RMS for Layer%d", sScanTypes[iScan].Data(), iLayer), 50, -0.5, 9.5); + hCalibrationRMSLayer[iLayer][iScan]->SetStats(0); + formatAxes(hCalibrationRMSLayer[iLayer][iScan], Form("RMS (%s)", sXtitles[iScan].Data()), "Chip counts", 1, 1.10); + addObject(hCalibrationRMSLayer[iLayer][iScan]); + } + + for (int iBarrel = 0; iBarrel < 3; iBarrel++) { + + hCalibrationChipAverage[iScan][iBarrel] = new TH2F(Form("%sChipAverage%s", sScanTypes[iScan].Data(), sBarrelType[iBarrel].Data()), Form("Average chip %s for %s", sScanTypes[iScan].Data(), sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hCalibrationChipAverage[iScan][iBarrel]->SetMinimum(nZmin[iScan]); + hCalibrationChipAverage[iScan][iBarrel]->SetMaximum(nZmax[iScan]); + hCalibrationChipAverage[iScan][iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hCalibrationChipAverage[iScan][iBarrel], "Chip", "", 1, 1.10); + formatLayers(hCalibrationChipAverage[iScan][iBarrel], iBarrel); + + addObject(hCalibrationChipAverage[iScan][iBarrel]); + + hCalibrationRMSChipAverage[iScan][iBarrel] = new TH2F(Form("%sRMSChipAverage%s", sScanTypes[iScan].Data(), sBarrelType[iBarrel].Data()), Form("Average chip %s RMS for %s", sScanTypes[iScan].Data(), sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hCalibrationRMSChipAverage[iScan][iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hCalibrationRMSChipAverage[iScan][iBarrel], "Chip", "", 1, 1.10); + formatLayers(hCalibrationRMSChipAverage[iScan][iBarrel], iBarrel); + + addObject(hCalibrationRMSChipAverage[iScan][iBarrel]); + } + } + //------------------Noise histograms for THR scan, success rate, and chip completed + // + histograms for pulse length scan: time over threshold, time over threshold rms, rise time, rise time rms for each barrel (IB, ML, OL) + for (int iBarrel = 0; iBarrel < 3; iBarrel++) { // TH2 for THR noise plots + + // Noise 2D plot + if (iScan == THR) { + hCalibrationThrNoiseChipAverage[iBarrel] = new TH2F(Form("ThrNoiseChipAverage%s", sBarrelType[iBarrel].Data()), Form("Average chip threshold noise for %s", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hCalibrationThrNoiseChipAverage[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hCalibrationThrNoiseChipAverage[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hCalibrationThrNoiseChipAverage[iBarrel], iBarrel); + addObject(hCalibrationThrNoiseChipAverage[iBarrel]); + + // Noise RMS 2D plot + hCalibrationThrNoiseRMSChipAverage[iBarrel] = new TH2F(Form("ThrNoiseRMSChipAverage%s", sBarrelType[iBarrel].Data()), Form("Average chip threshold NoiseRMS for %s", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hCalibrationThrNoiseRMSChipAverage[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hCalibrationThrNoiseRMSChipAverage[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hCalibrationThrNoiseRMSChipAverage[iBarrel], iBarrel); + addObject(hCalibrationThrNoiseRMSChipAverage[iBarrel]); + } + // Chip done 2D plot + hCalibrationChipDone[iBarrel] = new TH2F(Form("ChipDone%s", sBarrelType[iBarrel].Data()), Form("Chips Done %s", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hCalibrationChipDone[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hCalibrationChipDone[iBarrel], "Chip", "", 1, 1.10); + + formatLayers(hCalibrationChipDone[iBarrel], iBarrel); + addObject(hCalibrationChipDone[iBarrel]); + + // Unsuccess 2D plot + hUnsuccess[iBarrel] = new TH2F(Form("ChipUnsuccess%s", sBarrelType[iBarrel].Data()), Form("Percentage of success %s", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hUnsuccess[iBarrel]->SetStats(0); + hUnsuccess[iBarrel]->SetMinimum(0); + hUnsuccess[iBarrel]->SetMaximum(100); + if (iBarrel != 0) + formatAxes(hUnsuccess[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hUnsuccess[iBarrel], iBarrel); + addObject(hUnsuccess[iBarrel]); + + // ToT 2D plot + if (iScan == TOT) { + hTimeOverThreshold[iBarrel] = new TH2F(Form("TimeOverThreshold%s", sBarrelType[iBarrel].Data()), Form("Time over threshold for %s (in #mus)", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hTimeOverThreshold[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hTimeOverThreshold[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hTimeOverThreshold[iBarrel], iBarrel); + addObject(hTimeOverThreshold[iBarrel]); + + // ToT RMS 2D plot + hTimeOverThresholdRms[iBarrel] = new TH2F(Form("TimeOverThresholdRms%s", sBarrelType[iBarrel].Data()), Form("Time over threshold RMS for %s (in #mus)", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hTimeOverThresholdRms[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hTimeOverThresholdRms[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hTimeOverThresholdRms[iBarrel], iBarrel); + addObject(hTimeOverThresholdRms[iBarrel]); + + // Rise time 2D plot + hToA[iBarrel] = new TH2F(Form("ToA%s", sBarrelType[iBarrel].Data()), Form("ToA for %s (in ns)", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hToA[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hToA[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hToA[iBarrel], iBarrel); + addObject(hToA[iBarrel]); + + // Rise time RMS 2D plot + hToARms[iBarrel] = new TH2F(Form("ToARms%s", sBarrelType[iBarrel].Data()), Form("ToA RMS for %s (in ns)", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hToARms[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hToARms[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hToARms[iBarrel], iBarrel); + addObject(hToARms[iBarrel]); + } + } + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + // Noise distribution for each layer + + if (iScan == THR) { + hCalibrationThrNoiseLayer[iLayer] = new TH1F(Form("ThrNoiseLayer%d", iLayer), Form("Threshold Noise for Layer%d", iLayer), 10, -0.5, 9.5); + formatAxes(hCalibrationThrNoiseLayer[iLayer], "THR noise (e) ", "Chip counts", 1, 1.10); + addObject(hCalibrationThrNoiseLayer[iLayer]); + + // Noise distribution RMS for each layer + hCalibrationThrNoiseRMSLayer[iLayer] = new TH1F(Form("ThrNoiseRMSLayer%d", iLayer), Form("Threshold Noise RMS for Layer%d", iLayer), 50, -0.5, 9.5); + formatAxes(hCalibrationThrNoiseRMSLayer[iLayer], "THR noise RMS (e)", "Chip counts", 1, 1.10); + addObject(hCalibrationThrNoiseRMSLayer[iLayer]); + } + // Time over threshold for each layer + if (iScan == TOT) { + hTimeOverThresholdLayer[iLayer] = new TH1F(Form("TimeOverThresholdLayer%d", iLayer), Form("Time over threshold distribution for Layer%d", iLayer), 200, 0, 20); + formatAxes(hTimeOverThresholdLayer[iLayer], "ToT (#mus) ", "Counts", 1, 1.10); + addObject(hTimeOverThresholdLayer[iLayer]); + + // Time over threshold RMS for each layer + hTimeOverThresholdRmsLayer[iLayer] = new TH1F(Form("TimeOverThresholdRmsLayer%d", iLayer), Form("Time over threshold Rms distribution for Layer%d", iLayer), 100, 0, 10); + formatAxes(hTimeOverThresholdRmsLayer[iLayer], "ToT Rms (#mus) ", "Counts", 1, 1.10); + addObject(hTimeOverThresholdRmsLayer[iLayer]); + + // Rise time for each layer + hToALayer[iLayer] = new TH1F(Form("ToALayer%d", iLayer), Form("ToA distribution for Layer%d", iLayer), 1600, 0, 800); + formatAxes(hToALayer[iLayer], "ToA (ns) ", "Counts", 1, 1.10); + addObject(hToALayer[iLayer]); + + // Rise time RMS for each layer + hToARmsLayer[iLayer] = new TH1F(Form("ToARmsLayer%d", iLayer), Form("ToA Rms distribution for Layer%d", iLayer), 1000, 0, 500); + formatAxes(hToARmsLayer[iLayer], "ToA Rms (ns) ", "Counts", 1, 1.10); + addObject(hToARmsLayer[iLayer]); + } + } + + if (iScan == pixel) { + + for (int iBarrel = 0; iBarrel < 3; iBarrel++) { + + hCalibrationPixel_dead[iBarrel] = new TH2D(Form("DeadPixels%s", sBarrelType[iBarrel].Data()), Form("Number of Dead pixels per chip for %s", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hCalibrationPixel_dead[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hCalibrationPixel_dead[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hCalibrationPixel_dead[iBarrel], iBarrel); + addObject(hCalibrationPixel_dead[iBarrel]); + + hCalibrationPixel_noise[iBarrel] = new TH2D(Form("NoisePixels%s", sBarrelType[iBarrel].Data()), Form("Number of Noise pixels per chip for %s", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hCalibrationPixel_noise[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hCalibrationPixel_noise[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hCalibrationPixel_noise[iBarrel], iBarrel); + addObject(hCalibrationPixel_noise[iBarrel]); + + hCalibrationPixel_inEff[iBarrel] = new TH2D(Form("InEffPixels%s", sBarrelType[iBarrel].Data()), Form("Number of InEff pixels per chip for %s", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hCalibrationPixel_inEff[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hCalibrationPixel_inEff[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hCalibrationPixel_inEff[iBarrel], iBarrel); + addObject(hCalibrationPixel_inEff[iBarrel]); + + hCalibrationDColChipAverage[iBarrel] = new TH2D(Form("DCols%s", sBarrelType[iBarrel].Data()), Form("Number of DCols per chip for %s", sBarrelType[iBarrel].Data()), nChips[iBarrel], -0.5, nChips[iBarrel] - 0.5, nStaves[iBarrel], -0.5, nStaves[iBarrel] - 0.5); + hCalibrationDColChipAverage[iBarrel]->SetStats(0); + if (iBarrel != 0) + formatAxes(hCalibrationDColChipAverage[iBarrel], "Chip", "", 1, 1.10); + formatLayers(hCalibrationDColChipAverage[iBarrel], iBarrel); + addObject(hCalibrationDColChipAverage[iBarrel]); + } + } +} +void ITSThresholdCalibrationTask::addObject(TObject* aObject) +{ + if (!aObject) { + ILOG(Debug, Devel) << " ERROR: trying to add non-existent object " << ENDM; + return; + } else { + mPublishedObjects.push_back(aObject); + } +} + +void ITSThresholdCalibrationTask::formatLayers(TH2* h, Int_t iBarrel) +{ + Int_t iLayerBegin, iLayerEnd; + + switch (iBarrel) { + case 0: { + iLayerBegin = 1; + iLayerEnd = 3; + break; + } + case 1: { + iLayerBegin = 4; + iLayerEnd = 5; + break; + } + case 2: { + iLayerBegin = 6; + iLayerEnd = 7; + break; + } + }; + + for (Int_t iLayer = iLayerBegin; iLayer <= iLayerEnd; iLayer++) { + + TLatex* msg = new TLatex(0.5 + iBarrel * 4, StaveBoundary[iLayer - 1] + 2 + iBarrel * 2, Form("L%d", iLayer - 1)); + msg->SetTextSize(18); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + + if (iLayer < iLayerEnd) { + auto l = new TLine(-0.5, StaveBoundary[iLayer] - 0.5, nChipsPerStave[iLayer] - 0.5, StaveBoundary[iLayer] - 0.5); + h->GetListOfFunctions()->Add(l); + } + } + + //-----------Setting Labels for axis + if (iBarrel == 0) { + for (int i = 1; i <= h->GetNbinsX(); i++) { + h->GetXaxis()->SetBinLabel(i, Form("Chip %d ", i - 1)); + } + } + + int iLayer = iLayerBegin - 1; + int iStave = 0; + for (int i = 1; i <= h->GetNbinsY(); i++) { + h->GetYaxis()->SetBinLabel(i, Form("L%d_%02d", iLayer, iStave)); + iStave++; + if (iStave >= NStaves[iLayer]) { + iStave = 0; + iLayer++; + } + } +} + +Int_t ITSThresholdCalibrationTask::getBarrel(Int_t iLayer) +{ + + Int_t iBarrel; + if (iLayer < 3) + iBarrel = 0; + else if (iLayer < 5) + iBarrel = 1; + else + iBarrel = 2; + + return iBarrel; +} + +void ITSThresholdCalibrationTask::formatAxes(TH1* h, const char* xTitle, const char* yTitle, float xOffset, float yOffset) +{ + h->GetXaxis()->SetTitle(xTitle); + h->GetYaxis()->SetTitle(yTitle); + h->GetXaxis()->SetTitleOffset(xOffset); + h->GetYaxis()->SetTitleOffset(yOffset); +} + +void ITSThresholdCalibrationTask::publishHistos() +{ + for (unsigned int iObj = 0; iObj < mPublishedObjects.size(); iObj++) { + getObjectsManager()->startPublishing(mPublishedObjects.at(iObj)); + ILOG(Debug, Devel) << " Object will be published: " << mPublishedObjects.at(iObj)->GetName() << ENDM; + } +} + +std::vector ITSThresholdCalibrationTask::splitString(std::string s, std::string delimiter) +{ + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + std::string token; + std::vector res; + + while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { + token = s.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + res.push_back(token); + } + + res.push_back(s.substr(pos_start)); + return res; +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSTrackCheck.cxx b/Modules/ITS/src/ITSTrackCheck.cxx new file mode 100644 index 0000000000..da71918c9c --- /dev/null +++ b/Modules/ITS/src/ITSTrackCheck.cxx @@ -0,0 +1,524 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ITSTrackCheck.cxx +/// \author Artem Isakov +/// \author Jian Liu +/// + +#include "ITS/ITSTrackCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include "TMath.h" +#include "TLatex.h" +#include "Common/Utils.h" + +namespace o2::quality_control_modules::its +{ + +Quality ITSTrackCheck::check(std::map>* moMap) +{ + mEtaRatio = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "EtaRatio", mEtaRatio); + mPhiRatio = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "PhiRatio", mPhiRatio); + + Quality result; + Int_t id = 0; + std::map>::iterator iter; + for (iter = moMap->begin(); iter != moMap->end(); ++iter) { + + if (((std::string)iter->second->getName()).find("NClusters") != std::string::npos) { + auto* h = dynamic_cast(iter->second->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast NClusters to TH1D*" << ENDM; + continue; + } + result.set(Quality::Good); + result.addMetadata("CheckTracks4", "good"); + result.addMetadata("CheckTracks5", "good"); + result.addMetadata("CheckTracks6", "good"); + result.addMetadata("CheckTracks7", "good"); + result.addMetadata("CheckEmpty", "good"); + result.addMetadata("CheckMean", "good"); + if (h->GetMean() < 5.2 || h->GetMean() > 6.2) { + + result.updateMetadata("CheckMean", "medium"); + result.set(Quality::Medium); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), Form("Medium: Mean (%.1f) is outside 5.2-5.9, ignore for COSMICS and TECHNICALS", h->GetMean())); + } + if (h->GetBinContent(h->FindBin(4)) < 1e-15) { + result.updateMetadata("CheckTracks4", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "BAD: no tracks with 4 clusters"); + } + if (h->GetBinContent(h->FindBin(5)) < 1e-15) { + result.updateMetadata("CheckTracks5", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "BAD: no tracks with 5 clusters"); + } + if (h->GetBinContent(h->FindBin(6)) < 1e-15) { + result.updateMetadata("CheckTracks6", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "BAD: no tracks with 6 clusters"); + } + if (h->GetBinContent(h->FindBin(7)) < 1e-15) { + result.updateMetadata("CheckTracks7", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "BAD: no tracks with 7 clusters"); + } + if (h->GetEntries() < 1e-15) { + result.updateMetadata("CheckEmpty", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "BAD: no tracks!"); + } + } + + if (((std::string)iter->second->getName()).find("AngularDistribution") != std::string::npos) { + auto* hAngular = dynamic_cast(iter->second->getObject()); + if (hAngular == nullptr) { + ILOG(Error, Support) << "could not cast AngularDistribution to TH2D*" << ENDM; + continue; + } + result.set(Quality::Good); + result.addMetadata("CheckAngEmpty", "good"); + result.addMetadata("CheckAsymmEta", "good"); + result.addMetadata("CheckAsymmPhi", "good"); + TH1D* projectEta = (TH1D*)hAngular->ProjectionX(); + Double_t ratioEta = abs(1. - (projectEta->Integral(1, projectEta->FindBin(0)) / projectEta->Integral(projectEta->FindBin(0), projectEta->GetNbinsX()))); + TH1D* projectPhi = (TH1D*)hAngular->ProjectionY(); + Double_t ratioPhi = abs(projectPhi->Integral(projectPhi->FindBin(0), projectPhi->FindBin(TMath::Pi())) / projectPhi->Integral(projectPhi->FindBin(TMath::Pi()), projectPhi->FindBin(TMath::TwoPi())) - 1); + if (hAngular->GetEntries() < 1e-15) { + result.updateMetadata("CheckAngEmpty", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "BAD: no tracks!"); + } + if (ratioEta > mEtaRatio) { + result.updateMetadata("CheckAsymmEta", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "BAD: Eta asymmetry"); + } + if (ratioPhi > mPhiRatio) { + result.updateMetadata("CheckAsymmPhi", "bad"); + result.set(Quality::Bad); + result.addFlag(o2::quality_control::FlagTypeFactory::Unknown(), "BAD: Phi asymmetry"); + } + } + + if (iter->second->getName() == "VertexCoordinates") { + auto* h = dynamic_cast(iter->second->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast VertexCoordinates to TH2D*" << ENDM; + continue; + } + result.set(Quality::Good); + result.addMetadata("CheckXYVertexEmpty", "good"); + result.addMetadata("CheckXVertexMean", "good"); + result.addMetadata("CheckYVertexMean", "good"); + result.addMetadata("CheckXVertexRms", "good"); + result.addMetadata("CheckYVertexRms", "good"); + if (std::abs(h->GetMean(1)) > 0.05) { + result.updateMetadata("CheckXVertexMean", "medium"); + result.set(Quality::Medium); + } + if (std::abs(h->GetMean(2)) > 0.05) { + result.updateMetadata("CheckYVertexMean", "medium"); + result.set(Quality::Medium); + } + if (h->GetRMS(1) > 0.1) { + result.updateMetadata("CheckXVertexRms", "medium"); + result.set(Quality::Medium); + } + if (h->GetRMS(2) > 0.1) { + result.updateMetadata("CheckYVertexRms", "medium"); + result.set(Quality::Medium); + } + if (h->GetEntries() < 1e-15 || (h->Integral(h->GetXaxis()->FindBin(-0.2), h->GetXaxis()->FindBin(+0.2), h->GetYaxis()->FindBin(0.05), h->GetYaxis()->FindBin(0.2)) < 1e-15)) { + result.updateMetadata("CheckXYVertexEmpty", "bad"); + result.set(Quality::Bad); + } + } + + if (iter->second->getName() == "VertexRvsZ") { + auto* h = dynamic_cast(iter->second->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast VertexRvsZ to TH2D*" << ENDM; + continue; + } + result.set(Quality::Good); + result.addMetadata("CheckRZVertexZDisplacedBad", "good"); + TH1D* projectZ = (TH1D*)h->ProjectionY(); + if ((projectZ->Integral(1, projectZ->FindBin(-10)) > 0) || (projectZ->Integral(projectZ->FindBin(10), projectZ->GetNbinsX()) > 0)) { + result.updateMetadata("CheckRZVertexZDisplacedBad", "bad"); + result.set(Quality::Bad); + } + } + + if (iter->second->getName() == "VertexZ") { + auto* h = dynamic_cast(iter->second->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast VertexZ to TH1D*" << ENDM; + continue; + } + result.set(Quality::Good); + result.addMetadata("CheckZVertexEmpty", "good"); + result.addMetadata("CheckZVertexMeanMed", "good"); + result.addMetadata("CheckZVertexMeanBad", "good"); + result.addMetadata("CheckZVertexRms", "good"); + if (std::abs(h->GetMean()) > 1. && std::abs(h->GetMean()) < 3.) { + result.updateMetadata("CheckZVertexMeanMed", "medium"); + result.set(Quality::Medium); + } + if (h->GetRMS() >= 7.) { + result.updateMetadata("CheckZVertexRms", "medium"); + result.set(Quality::Medium); + } + if (h->GetEntries() < 1e-15 || h->Integral(h->FindBin(0) + 1, h->FindBin(10)) < 1e-15) { + result.updateMetadata("CheckZVertexEmpty", "bad"); + result.set(Quality::Bad); + } + if (std::abs(h->GetMean()) >= 3.) { + result.updateMetadata("CheckZVertexMeanBad", "bad"); + result.set(Quality::Bad); + } + } + } + return result; +} + +void ITSTrackCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + std::vector vPlotWithTextMessage = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "plotWithTextMessage", "")); + std::vector vTextMessage = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "textMessage", "")); + std::map ShifterInfoText; + + if ((int)vTextMessage.size() == (int)vPlotWithTextMessage.size()) { + for (int i = 0; i < (int)vTextMessage.size(); i++) { + ShifterInfoText[vPlotWithTextMessage[i]] = vTextMessage[i]; + } + } else + ILOG(Warning, Support) << "Bad list of plot with TextMessages for shifter, check .json" << ENDM; + + std::shared_ptr tShifterInfo = std::make_shared(0.005, 0.006, Form("#bf{%s}", TString(ShifterInfoText[mo->getName()]).Data())); + tShifterInfo->SetTextSize(0.04); + tShifterInfo->SetTextFont(43); + tShifterInfo->SetNDC(); + + TString text[2]; + TString status; + int textColor; + + if (((std::string)mo->getName()).find("NClusters") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast NClusters to TH1D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else { + + if (checkResult == Quality::Bad) { + status = "Quality::Bad (call expert)"; + textColor = kRed; + } else { + status = "Quality::Medium"; + textColor = kOrange; + } + + if (strcmp(checkResult.getMetadata("CheckTracks4").c_str(), "bad") == 0) { + tMessage[0] = std::make_shared(0.12, 0.6, "0 tracks with 4 clusters (OK if it's synthetic run)"); + tMessage[0]->SetTextFont(43); + tMessage[0]->SetTextSize(0.04); + tMessage[0]->SetTextColor(kRed); + tMessage[0]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[0]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckTracks5").c_str(), "bad") == 0) { + tMessage[1] = std::make_shared(0.12, 0.55, "0 tracks with 5 clusters (OK if it's synthetic run)"); + tMessage[1]->SetTextFont(43); + tMessage[1]->SetTextSize(0.04); + tMessage[1]->SetTextColor(kRed); + tMessage[1]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[1]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckTracks6").c_str(), "bad") == 0) { + tMessage[2] = std::make_shared(0.12, 0.5, "0 tracks with 6 clusters (OK if it's synthetic run)"); + tMessage[2]->SetTextFont(43); + tMessage[2]->SetTextSize(0.04); + tMessage[2]->SetTextColor(kRed); + tMessage[2]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[2]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckTracks7").c_str(), "bad") == 0) { + tMessage[3] = std::make_shared(0.12, 0.45, "0 tracks with 7 clusters (call!)"); + tMessage[3]->SetTextFont(43); + tMessage[3]->SetTextSize(0.04); + tMessage[3]->SetTextColor(kRed); + tMessage[3]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[3]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckEmpty").c_str(), "bad") == 0) { + tMessage[4] = std::make_shared(0.12, 0.4, "NO ITS TRACKS"); + tMessage[4]->SetTextFont(43); + tMessage[4]->SetTextSize(0.04); + tMessage[4]->SetTextColor(kRed); + tMessage[4]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[4]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckMean").c_str(), "medium") == 0) { + tMessage[5] = std::make_shared(0.12, 0.76, Form("#splitline{Mean (%.1f) is outside 5.2-6.2}{ignore for COSMICS and TECHNICALS}", h->GetMean())); + tMessage[5]->SetTextFont(43); + tMessage[5]->SetTextSize(0.04); + tMessage[5]->SetTextColor(kOrange); + tMessage[5]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[5]->Clone()); + } + } + + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextFont(43); + tInfo->SetTextSize(0.06); + tInfo->SetTextColor(textColor); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + + if (((std::string)mo->getName()).find("AngularDistribution") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast AngularDistribution to TH2D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else { + status = "Quality::BAD (call expert)"; + textColor = kRed; + if (strcmp(checkResult.getMetadata("CheckAngEmpty").c_str(), "bad") == 0) { + tMessage[0] = std::make_shared(0.12, 0.65, "NO ITS TRACKS"); + tMessage[0]->SetTextFont(43); + tMessage[0]->SetTextSize(0.04); + tMessage[0]->SetTextColor(kRed); + tMessage[0]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[0]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckAsymmEta").c_str(), "bad") == 0) { + tMessage[1] = std::make_shared(0.12, 0.6, "Asymmetric Eta distribution (OK if there are disabled ITS sectors)"); + tMessage[1]->SetTextFont(43); + tMessage[1]->SetTextSize(0.04); + tMessage[1]->SetTextColor(kRed); + tMessage[1]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[1]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckAsymmPhi").c_str(), "bad") == 0) { + tMessage[2] = std::make_shared(0.12, 0.55, "Asymmetric Phi distribution (OK if there are disabled ITS sectors)"); + tMessage[2]->SetTextFont(43); + tMessage[2]->SetTextSize(0.04); + tMessage[2]->SetTextColor(kRed); + tMessage[2]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[2]->Clone()); + } + } + + tInfo = std::make_shared(0.05, 0.95, Form("#bf{%s}", status.Data())); + tInfo->SetTextFont(43); + tInfo->SetTextSize(0.06); + tInfo->SetTextColor(textColor); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + + if (mo->getName() == "VertexCoordinates") { + Double_t positionX, positionY; + auto* h = dynamic_cast(mo->getObject()); + + if (h == nullptr) { + ILOG(Error, Support) << "could not cast TVertexCoordinates to TH2D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality::GOOD"; + textColor = kGreen; + } else { + if (strcmp(checkResult.getMetadata("CheckXVertexMean").c_str(), "medium") == 0) { + status = "Quality::Medium (do not call, inform expert on MM if run is ongoing since >5min)"; + textColor = kOrange; + tMessage[0] = std::make_shared(0.12, 0.65, Form("X mean is %.4f", h->GetMean(1))); + tMessage[0]->SetTextFont(43); + tMessage[0]->SetTextSize(0.04); + tMessage[0]->SetTextColor(kOrange); + tMessage[0]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[0]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckYVertexMean").c_str(), "medium") == 0) { + status = "Quality::Medium (do not call, inform expert on MM if run is ongoing since >5min)"; + textColor = kOrange; + tMessage[1] = std::make_shared(0.12, 0.6, Form("Y mean is %.4f", h->GetMean(2))); + tMessage[1]->SetTextFont(43); + tMessage[1]->SetTextSize(0.04); + tMessage[1]->SetTextColor(kOrange); + tMessage[1]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[1]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckXVertexRms").c_str(), "medium") == 0) { + status = "Quality::Medium (do not call, inform expert on MM if run is ongoing since >5min)"; + textColor = kOrange; + tMessage[2] = std::make_shared(0.12, 0.55, Form("X RMS is %.4f", h->GetRMS(1))); + tMessage[2]->SetTextFont(43); + tMessage[2]->SetTextSize(0.04); + tMessage[2]->SetTextColor(kOrange); + tMessage[2]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[2]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckYVertexRms").c_str(), "medium") == 0) { + status = "Quality::Medium (do not call, inform expert on MM if run is ongoing since >5min)"; + textColor = kOrange; + tMessage[3] = std::make_shared(0.12, 0.5, Form("Y RMS is %.4f", h->GetRMS(2))); + tMessage[3]->SetTextFont(43); + tMessage[3]->SetTextSize(0.04); + tMessage[3]->SetTextColor(kOrange); + tMessage[3]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[3]->Clone()); + } + + if (strcmp(checkResult.getMetadata("CheckXYVertexEmpty").c_str(), "bad") == 0) { + status = "Quality::Bad (call expert)"; + textColor = kRed; + tMessage[4] = std::make_shared(0.12, 0.45, "NO/BAD VERTICES (OK if run is COSMICS or if run has JUST(<5min) started)"); + tMessage[4]->SetTextFont(43); + tMessage[4]->SetTextSize(0.04); + tMessage[4]->SetTextColor(kRed); + tMessage[4]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[4]->Clone()); + } + } + + tInfo = std::make_shared(0.12, 0.75, Form("#bf{%s}", status.Data())); + tInfo->SetTextFont(43); + tInfo->SetTextSize(0.04); + tInfo->SetTextColor(textColor); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + + if (mo->getName() == "VertexRvsZ") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast VertexRvsZ to TH2D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality:GOOD"; + textColor = kGreen; + } else { + status = "Quality::Bad (call expert)"; + textColor = kRed; + if (strcmp(checkResult.getMetadata("CheckRZVertexZDisplacedBad").c_str(), "bad") == 0) { + tMessage[0] = std::make_shared(0.12, 0.65, Form("INFO: vertex Z displaced > 10 cm")); + tMessage[0]->SetTextFont(43); + tMessage[0]->SetTextSize(0.04); + tMessage[0]->SetTextColor(kRed); + tMessage[0]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[0]->Clone()); + } + } + + tInfo = std::make_shared(0.12, 0.75, Form("#bf{%s}", status.Data())); + tInfo->SetTextFont(43); + tInfo->SetTextSize(0.04); + tInfo->SetTextColor(textColor); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } + + if (mo->getName() == "VertexZ") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast VertexZ to TH1D*" << ENDM; + return; + } + if (checkResult == Quality::Good) { + status = "Quality:GOOD"; + textColor = kGreen; + } else { + if (strcmp(checkResult.getMetadata("CheckZVertexMeanMed").c_str(), "medium") == 0) { + status = "Quality::Medium (do not call, inform expert on MM if run is ongoing since >5min)"; + textColor = kOrange; + tMessage[0] = std::make_shared(0.12, 0.65, Form("Z mean is %.4f", h->GetMean(1))); + tMessage[0]->SetTextFont(43); + tMessage[0]->SetTextSize(0.04); + tMessage[0]->SetTextColor(kOrange); + tMessage[0]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[0]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckZVertexRms").c_str(), "medium") == 0) { + status = "Quality::Medium (do not call, inform expert on MM if run is ongoing since >5min)"; + textColor = kOrange; + tMessage[1] = std::make_shared(0.12, 0.6, Form("Z RMS is %.4f", h->GetRMS(1))); + tMessage[1]->SetTextFont(43); + tMessage[1]->SetTextSize(0.04); + tMessage[1]->SetTextColor(kOrange); + tMessage[1]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[1]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckZVertexMeanBad").c_str(), "bad") == 0) { + status = "Quality::Bad (call expert)"; + textColor = kRed; + tMessage[2] = std::make_shared(0.12, 0.55, Form("Z Mean is %.4f (OK if run has JUST(<5min) started)", h->GetMean(1))); + tMessage[2]->SetTextFont(43); + tMessage[2]->SetTextSize(0.04); + tMessage[2]->SetTextColor(kRed); + tMessage[2]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[2]->Clone()); + } + if (strcmp(checkResult.getMetadata("CheckZVertexEmpty").c_str(), "bad") == 0) { + status = "Quality::Bad (call expert)"; + textColor = kRed; + tMessage[3] = std::make_shared(0.12, 0.45, "NO/BAD VERTICES (OK if run is COSMICS or if run has JUST(<5min) started)"); + tMessage[3]->SetTextFont(43); + tMessage[3]->SetTextSize(0.04); + tMessage[3]->SetTextColor(kRed); + tMessage[3]->SetNDC(); + h->GetListOfFunctions()->Add(tMessage[3]->Clone()); + } + } + + tInfo = std::make_shared(0.12, 0.75, Form("#bf{%s}", status.Data())); + tInfo->SetTextFont(43); + tInfo->SetTextSize(0.04); + tInfo->SetTextColor(textColor); + tInfo->SetNDC(); + h->GetListOfFunctions()->Add(tInfo->Clone()); + if (ShifterInfoText[mo->getName()] != "") + h->GetListOfFunctions()->Add(tShifterInfo->Clone()); + } +} + +int ITSTrackCheck::getDigit(int number, int digit) +{ + return number % (int)pow(10, digit) / (int)pow(10, digit - 1); +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSTrackSimTask.cxx b/Modules/ITS/src/ITSTrackSimTask.cxx new file mode 100644 index 0000000000..8d55234ddd --- /dev/null +++ b/Modules/ITS/src/ITSTrackSimTask.cxx @@ -0,0 +1,577 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file ITSTrackSimTask.cxx +/// \author Artem Isakov +/// MC simulation tool inspired by Detectors/ITS/macros/CheckTracks +// +/// + +#include "QualityControl/QcInfoLogger.h" +#include "ITS/ITSTrackSimTask.h" +#include "DataFormatsITS/TrackITS.h" +#include +#include + +#include "SimulationDataFormat/MCTrack.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "Field/MagneticField.h" +#include "TGeoGlobalMagField.h" +#include "DetectorsBase/Propagator.h" + +#include "DataFormatsITSMFT/CompCluster.h" +#include "Steer/MCKinematicsReader.h" //ADDED FOR MC FILE READING + +#include + +#include "GPUCommonDef.h" +#include "ReconstructionDataFormats/Track.h" +#include "CommonDataFormat/RangeReference.h" +#include "CommonConstants/MathConstants.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "TFile.h" +#include "TTree.h" +#include +using namespace o2::constants::math; +using namespace o2::itsmft; +using namespace o2::its; + +namespace o2::quality_control_modules::its +{ + +ITSTrackSimTask::ITSTrackSimTask() : TaskInterface() +{ +} + +ITSTrackSimTask::~ITSTrackSimTask() +{ + delete hNumRecoValid_pt; + delete hEfficiency_pt; + + delete hNumRecoValid_eta; + delete hEfficiency_eta; + + delete hNumRecoValid_phi; + delete hEfficiency_phi; + + delete hNumRecoValid_r; + delete hEfficiency_r; + + delete hNumRecoValid_z; + delete hEfficiency_z; + + delete hTrackImpactTransvFake; + delete hTrackImpactTransvValid; + + delete hPrimaryReco_pt; + delete hPrimaryGen_pt; + + delete hAngularDistribution; + + delete hDuplicate_pt; + delete hNumDuplicate_pt; + delete hDuplicate_phi; + delete hNumDuplicate_phi; + delete hDuplicate_eta; + delete hNumDuplicate_eta; + delete hDuplicate_r; + delete hNumDuplicate_r; + delete hDuplicate_z; + delete hNumDuplicate_z; + + for (Int_t icls = 0; icls < 4; icls++) { + delete hNumRecoFake_pt[icls]; + delete hDenTrue_pt[icls]; + delete hFakeTrack_pt[icls]; + delete hNumRecoFake_eta[icls]; + delete hDenTrue_eta[icls]; + delete hFakeTrack_eta[icls]; + delete hNumRecoFake_phi[icls]; + delete hDenTrue_phi[icls]; + delete hFakeTrack_phi[icls]; + delete hNumRecoFake_r[icls]; + delete hDenTrue_r[icls]; + delete hFakeTrack_r[icls]; + delete hNumRecoFake_z[icls]; + delete hDenTrue_z[icls]; + delete hFakeTrack_z[icls]; + delete hNumRecoFake_QoverPt[icls]; + delete hDenTrue_QoverPt[icls]; + delete hFakeTrack_QoverPt[icls]; + } +} + +void ITSTrackSimTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize ITSTrackSimTask" << ENDM; + mO2GrpPath = mCustomParameters["o2GrpPath"]; + mCollisionsContextPath = mCustomParameters["collisionsContextPath"]; + createAllHistos(); + publishHistos(); + o2::base::Propagator::initFieldFromGRP(mO2GrpPath.c_str()); + auto field = static_cast(TGeoGlobalMagField::Instance()->GetField()); + double orig[3] = { 0., 0., 0. }; + bz = field->getBz(orig); + + o2::base::GeometryManager::loadGeometry(); + mGeom = o2::its::GeometryTGeo::Instance(); +} + +void ITSTrackSimTask::startOfActivity(const Activity& activity) +{ + mRunNumber = activity.mId; + ILOG(Debug, Devel) << "startOfActivity" << ENDM; +} + +void ITSTrackSimTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ITSTrackSimTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + ILOG(Debug, Devel) << "START DOING QC General" << ENDM; + o2::steer::MCKinematicsReader reader(mCollisionsContextPath.c_str()); + info.resize(reader.getNEvents(0)); + for (int i = 0; i < reader.getNEvents(0); ++i) { + std::vector const& mcArr = reader.getTracks(i); + info[i].resize(mcArr.size()); + } + + auto clusArr = ctx.inputs().get>("compclus"); // used to get hit information + auto clusLabArr = ctx.inputs().get*>("mcclustruth").release(); + + for (int iCluster = 0; iCluster < clusArr.size(); iCluster++) { + + auto lab = (clusLabArr->getLabels(iCluster))[0]; + if (!lab.isValid() || lab.getSourceID() != 0) + continue; + + int TrackID = lab.getTrackID(); + + if (TrackID < 0) { + continue; + } + + if (!lab.isCorrect()) + continue; + const auto& Cluster = (clusArr)[iCluster]; + unsigned short& ok = info[lab.getEventID()][lab.getTrackID()].clusters; // bitmask with track hits at each layer + auto layer = mGeom->getLayer(Cluster.getSensorID()); + float r = 0.f; + if (layer == 0) + ok |= 0b1; + if (layer == 1) + ok |= 0b10; + if (layer == 2) + ok |= 0b100; + if (layer == 3) + ok |= 0b1000; + if (layer == 4) + ok |= 0b10000; + if (layer == 5) + ok |= 0b100000; + if (layer == 6) + ok |= 0b1000000; + } + + for (int i = 0; i < reader.getNEvents(0); ++i) { + std::vector const& mcArr = reader.getTracks(i); + auto mcHeader = reader.getMCEventHeader(0, i); // SourceID=0 for ITS + + for (int mc = 0; mc < mcArr.size(); mc++) { + const auto& mcTrack = (mcArr)[mc]; + + info[i][mc].isFilled = false; + if (mcTrack.Vx() * mcTrack.Vx() + mcTrack.Vy() * mcTrack.Vy() > 1) + continue; + if (TMath::Abs(mcTrack.GetPdgCode()) != 211) + continue; // Select pions + if (TMath::Abs(mcTrack.GetEta()) > 1.2) + continue; + if (info[i][mc].clusters != 0b1111111) + continue; + Double_t distance = sqrt(pow(mcHeader.GetX() - mcTrack.Vx(), 2) + pow(mcHeader.GetY() - mcTrack.Vy(), 2) + pow(mcHeader.GetZ() - mcTrack.Vz(), 2)); + info[i][mc].isFilled = true; + info[i][mc].r = distance; + info[i][mc].pt = mcTrack.GetPt(); + info[i][mc].eta = mcTrack.GetEta(); + info[i][mc].phi = mcTrack.GetPhi(); + info[i][mc].z = mcTrack.Vz(); + info[i][mc].isPrimary = mcTrack.isPrimary(); + if (mcTrack.isPrimary()) { + hPrimaryGen_pt->Fill(mcTrack.GetPt()); + // True Generated primaries: denominator of the efficiency plots + hDenTrue_r[4]->Fill(distance); + hDenTrue_pt[4]->Fill(mcTrack.GetPt()); + hDenTrue_eta[4]->Fill(mcTrack.GetEta()); + hDenTrue_phi[4]->Fill(mcTrack.GetPhi()); + hDenTrue_z[4]->Fill(mcTrack.Vz()); + } + } + } + + auto trackArr = ctx.inputs().get>("tracks"); // MC Tracks + auto MCTruth = ctx.inputs().get>("mstruth"); // MC track label, contains info about EventID, TrackID, SourceID etc + + for (int itrack = 0; itrack < trackArr.size(); itrack++) { + const auto& track = trackArr[itrack]; + const auto& MCinfo = MCTruth[itrack]; + if (MCinfo.isNoise()) + continue; + Float_t ip[2]{ 0., 0. }; + Float_t vx = 0., vy = 0., vz = 0.; // Assumed primary vertex at 0,0,0 + track.getImpactParams(vx, vy, vz, bz, ip); + Int_t iNClusters = track.getNumberOfClusters(); + + hAngularDistribution->Fill(track.getEta(), track.getPhi()); + + if (info[MCinfo.getEventID()][MCinfo.getTrackID()].isFilled) { + + if (info[MCinfo.getEventID()][MCinfo.getTrackID()].isPrimary) { + // True Generated primaries for QoverPt plot, because MCTrack does not have charge function + hDenTrue_QoverPt[4]->Fill(track.getSign() / info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + if (iNClusters == 4) { + hDenTrue_QoverPt[0]->Fill(track.getSign() / info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hDenTrue_r[0]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + hDenTrue_pt[0]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hDenTrue_eta[0]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hDenTrue_phi[0]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hDenTrue_z[0]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + } else if (iNClusters == 5) { + hDenTrue_QoverPt[1]->Fill(track.getSign() / info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hDenTrue_r[1]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + hDenTrue_pt[1]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hDenTrue_eta[1]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hDenTrue_phi[1]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hDenTrue_z[1]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + } else if (iNClusters == 6) { + hDenTrue_QoverPt[2]->Fill(track.getSign() / info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hDenTrue_r[2]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + hDenTrue_pt[2]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hDenTrue_eta[2]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hDenTrue_phi[2]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hDenTrue_z[2]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + } else if (iNClusters == 7) { + hDenTrue_QoverPt[3]->Fill(track.getSign() / info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hDenTrue_r[3]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + hDenTrue_pt[3]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hDenTrue_eta[3]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hDenTrue_phi[3]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hDenTrue_z[3]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + } + } + + if (info[MCinfo.getEventID()][MCinfo.getTrackID()].isReco != 0) { + hNumDuplicate_pt->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hNumDuplicate_phi->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hNumDuplicate_eta->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hNumDuplicate_z->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + hNumDuplicate_r->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + continue; + } + info[MCinfo.getEventID()][MCinfo.getTrackID()].isReco++; + + if (MCinfo.isFake()) { + hNumRecoFake_pt[4]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hNumRecoFake_phi[4]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hNumRecoFake_eta[4]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hNumRecoFake_z[4]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + hNumRecoFake_r[4]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + hNumRecoFake_QoverPt[4]->Fill(track.getSign() / info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + if (iNClusters == 4) { + hNumRecoFake_pt[0]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hNumRecoFake_phi[0]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hNumRecoFake_eta[0]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hNumRecoFake_z[0]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + hNumRecoFake_r[0]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + hNumRecoFake_QoverPt[0]->Fill(track.getSign() / info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + } else if (iNClusters == 5) { + hNumRecoFake_pt[1]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hNumRecoFake_phi[1]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hNumRecoFake_eta[1]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hNumRecoFake_z[1]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + hNumRecoFake_r[1]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + hNumRecoFake_QoverPt[1]->Fill(track.getSign() / info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + } else if (iNClusters == 6) { + hNumRecoFake_pt[2]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hNumRecoFake_phi[2]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hNumRecoFake_eta[2]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hNumRecoFake_z[2]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + hNumRecoFake_r[2]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + hNumRecoFake_QoverPt[2]->Fill(track.getSign() / info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + } else if (iNClusters == 7) { + hNumRecoFake_pt[3]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hNumRecoFake_phi[3]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hNumRecoFake_eta[3]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hNumRecoFake_z[3]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + hNumRecoFake_r[3]->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + hNumRecoFake_QoverPt[3]->Fill(track.getSign() / info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + } + + if (info[MCinfo.getEventID()][MCinfo.getTrackID()].isPrimary) { + hTrackImpactTransvFake->Fill(ip[0]); + hPrimaryReco_pt->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + } + } else { + if (info[MCinfo.getEventID()][MCinfo.getTrackID()].isPrimary) { + // True primaries reconstructed: numerator of efficiency plots + hNumRecoValid_pt->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hNumRecoValid_phi->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hNumRecoValid_eta->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + hNumRecoValid_z->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].z); + hNumRecoValid_r->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].r); + + hTrackImpactTransvValid->Fill(ip[0]); + hPrimaryReco_pt->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + } + } + } + } + hEfficiency_pt->SetPassedHistogram(*hNumRecoValid_pt, "f"); + hEfficiency_pt->SetTotalHistogram(*hDenTrue_pt[4], "f"); + hDuplicate_pt->SetPassedHistogram(*hNumDuplicate_pt, "f"); + hDuplicate_pt->SetTotalHistogram(*hDenTrue_pt[4], "f"); + + hEfficiency_phi->SetPassedHistogram(*hNumRecoValid_phi, "f"); + hEfficiency_phi->SetTotalHistogram(*hDenTrue_phi[4], "f"); + hDuplicate_phi->SetPassedHistogram(*hNumDuplicate_phi, "f"); + hDuplicate_phi->SetTotalHistogram(*hDenTrue_phi[4], "f"); + + hEfficiency_eta->SetPassedHistogram(*hNumRecoValid_eta, "f"); + hEfficiency_eta->SetTotalHistogram(*hDenTrue_eta[4], "f"); + hDuplicate_eta->SetPassedHistogram(*hNumDuplicate_eta, "f"); + hDuplicate_eta->SetTotalHistogram(*hDenTrue_eta[4], "f"); + + hEfficiency_r->SetPassedHistogram(*hNumRecoValid_r, "f"); + hEfficiency_r->SetTotalHistogram(*hDenTrue_r[4], "f"); + hDuplicate_r->SetPassedHistogram(*hNumDuplicate_r, "f"); + hDuplicate_r->SetTotalHistogram(*hDenTrue_r[4], "f"); + + hEfficiency_z->SetPassedHistogram(*hNumRecoValid_z, "f"); + hEfficiency_z->SetTotalHistogram(*hDenTrue_z[4], "f"); + hDuplicate_z->SetPassedHistogram(*hNumDuplicate_z, "f"); + hDuplicate_z->SetTotalHistogram(*hDenTrue_z[4], "f"); + + for (Int_t icls = 0; icls < 5; icls++) { + hFakeTrack_pt[icls]->SetPassedHistogram(*hNumRecoFake_pt[icls], "f"); + hFakeTrack_pt[icls]->SetTotalHistogram(*hDenTrue_pt[icls], "f"); + hFakeTrack_phi[icls]->SetPassedHistogram(*hNumRecoFake_phi[icls], "f"); + hFakeTrack_phi[icls]->SetTotalHistogram(*hDenTrue_phi[icls], "f"); + hFakeTrack_eta[icls]->SetPassedHistogram(*hNumRecoFake_eta[icls], "f"); + hFakeTrack_eta[icls]->SetTotalHistogram(*hDenTrue_eta[icls], "f"); + hFakeTrack_r[icls]->SetPassedHistogram(*hNumRecoFake_r[icls], "f"); + hFakeTrack_r[icls]->SetTotalHistogram(*hDenTrue_r[icls], "f"); + hFakeTrack_z[icls]->SetPassedHistogram(*hNumRecoFake_z[icls], "f"); + hFakeTrack_z[icls]->SetTotalHistogram(*hDenTrue_z[icls], "f"); + hFakeTrack_QoverPt[icls]->SetPassedHistogram(*hNumRecoFake_QoverPt[icls], "f"); + hFakeTrack_QoverPt[icls]->SetTotalHistogram(*hDenTrue_QoverPt[icls], "f"); + } +} + +void ITSTrackSimTask::endOfCycle() +{ + + for (unsigned int iObj = 0; iObj < mPublishedObjects.size(); iObj++) + getObjectsManager()->addMetadata(mPublishedObjects.at(iObj)->GetName(), "Run", std::to_string(mRunNumber)); + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ITSTrackSimTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSTrackSimTask::reset() +{ + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + hNumRecoValid_pt->Reset(); + hNumRecoValid_phi->Reset(); + hNumRecoValid_eta->Reset(); + hNumRecoValid_r->Reset(); + hNumRecoValid_z->Reset(); + + hTrackImpactTransvValid->Reset(); + hTrackImpactTransvFake->Reset(); + + hPrimaryGen_pt->Reset(); + hPrimaryReco_pt->Reset(); + + hAngularDistribution->Reset(); + hNumDuplicate_pt->Reset(); + + for (Int_t icls = 0; icls < 5; icls++) { + hNumRecoFake_pt[icls]->Reset(); + hDenTrue_pt[icls]->Reset(); + hNumRecoFake_phi[icls]->Reset(); + hDenTrue_phi[icls]->Reset(); + hNumRecoFake_eta[icls]->Reset(); + hDenTrue_eta[icls]->Reset(); + hNumRecoFake_r[icls]->Reset(); + hDenTrue_r[icls]->Reset(); + hNumRecoFake_z[icls]->Reset(); + hDenTrue_z[icls]->Reset(); + hNumRecoFake_QoverPt[icls]->Reset(); + hDenTrue_QoverPt[icls]->Reset(); + } +} + +void ITSTrackSimTask::createAllHistos() +{ + const Int_t nb = 100; + Double_t xbins[nb + 1], ptcutl = 0.01, ptcuth = 10.; + Double_t a = TMath::Log(ptcuth / ptcutl) / nb; + for (Int_t i = 0; i <= nb; i++) + xbins[i] = ptcutl * TMath::Exp(i * a); + + hDuplicate_pt = new TEfficiency("Duplicate_pt", "#it{p}_{T} fraction of mother duplicate track;#it{p}_{T} (GeV/#it{c});Fraction", nb, xbins); + addObject(hDuplicate_pt); + hEfficiency_pt = new TEfficiency("efficiency_pt", "Primary pions with 7cls - tracking efficiency vs #it{p}_{T}; #it{p}_{T} (GeV/#it{c}); {p}_{T}Efficiency", nb, xbins); + addObject(hEfficiency_pt); + hNumDuplicate_pt = new TH1D("NumDuplicate_pt", "", nb, xbins); + hNumRecoValid_pt = new TH1D("NumRecoValid_pt", "", nb, xbins); + hFakeTrack_pt[4] = new TEfficiency("faketrack_pt", "#it{p}_{T} fake-track rate;#it{p}_{T} (GeV/#it{c});Fake-track rate", nb, xbins); + addObject(hFakeTrack_pt[4]); + hNumRecoFake_pt[4] = new TH1D("NumRecoFake_pt", "", nb, xbins); + hDenTrue_pt[4] = new TH1D("DenTrueMC_pt", "", nb, xbins); + + hEfficiency_phi = new TEfficiency("efficiency_phi", "Primary pions with 7cls - tracking efficiency vs #phi;#phi;Efficiency", 60, 0, TMath::TwoPi()); + addObject(hEfficiency_phi); + hDuplicate_phi = new TEfficiency("Duplicate_phi", "#phi fraction of mother duplicate track;#phi;Fraction", 60, 0, TMath::TwoPi()); + addObject(hDuplicate_phi); + hNumDuplicate_phi = new TH1D("NumDuplicate_phi", "", 60, 0, TMath::TwoPi()); + hNumRecoValid_phi = new TH1D("NumRecoValid_phi", "", 60, 0, TMath::TwoPi()); + hFakeTrack_phi[4] = new TEfficiency("faketrack_phi", "#phi fake-track rate;#phi;Fake-track rate", 60, 0, TMath::TwoPi()); + addObject(hFakeTrack_phi[4]); + hNumRecoFake_phi[4] = new TH1D("NumRecoFake_phi", "_4Cluster", 60, 0, TMath::TwoPi()); + hDenTrue_phi[4] = new TH1D("DenTrueMC_phi", "", 60, 0, TMath::TwoPi()); + + hEfficiency_eta = new TEfficiency("efficiency_eta", "Primary pions with 7 cls - tracking efficiency vs #eta;#eta;Efficiency", 30, -1.5, 1.5); + addObject(hEfficiency_eta); + hDuplicate_eta = new TEfficiency("Duplicate_eta", "#eta fraction of mother duplicate track;#eta;Fraction", 30, -1.5, 1.5); + addObject(hDuplicate_eta); + hNumDuplicate_eta = new TH1D("NumDuplicate_eta", "", 30, -1.5, 1.5); + hNumRecoValid_eta = new TH1D("NumRecoValid_eta", "", 30, -1.5, 1.5); + hFakeTrack_eta[4] = new TEfficiency("faketrack_eta", "#eta fake-track rate;#eta;Fake-track rate", 30, -1.5, 1.5); + addObject(hFakeTrack_eta[4]); + hNumRecoFake_eta[4] = new TH1D("NumRecoFake_eta", "", 30, -1.5, 1.5); + hDenTrue_eta[4] = new TH1D("DenTrueMC_eta", "", 30, -1.5, 1.5); + + hEfficiency_r = new TEfficiency("efficiency_r", "Primary pions with 7 cls - tracking efficiency vs r;r (cm);Efficiency", 100, 0, 5); + addObject(hEfficiency_r); + hDuplicate_r = new TEfficiency("Duplicate_r", "r fraction of mother duplicate track;r (cm);Fraction", 100, 0, 5); + addObject(hDuplicate_r); + hNumRecoValid_r = new TH1D("NumRecoValid_r", "", 100, 0, 5); + hNumDuplicate_r = new TH1D("NumDuplicate_r", "", 100, 0, 5); + hFakeTrack_r[4] = new TEfficiency("faketrack_r", "r fake-track rate;r (cm);Fake-track rate", 100, 0, 5); + addObject(hFakeTrack_r[4]); + hNumRecoFake_r[4] = new TH1D("NumRecoFake_r", "", 100, 0, 5); + hDenTrue_r[4] = new TH1D("DenTrueMC_r", "", 100, 0, 5); + + hEfficiency_z = new TEfficiency("efficiency_z", "Primary pions with 7 cls - tracking efficiency vs z;z (cm);Efficiency", 101, -5, 5); + addObject(hEfficiency_z); + hDuplicate_z = new TEfficiency("Duplicate_z", "z fraction of mother duplicate track;z (cm);Fraction", 101, -5, 5); + addObject(hDuplicate_z); + hNumRecoValid_z = new TH1D("NumRecoValid_z", "", 101, -5, 5); + hNumDuplicate_z = new TH1D("NumDuplicate_z", "", 101, -5, 5); + hFakeTrack_z[4] = new TEfficiency("faketrack_z", "z fake-track rate;z (cm);Fake-track rate", 101, -5, 5); + addObject(hFakeTrack_z[4]); + hNumRecoFake_z[4] = new TH1D("NumRecoFake_z", "", 101, -5, 5); + hDenTrue_z[4] = new TH1D("DenTrueMC_z", "", 101, -5, 5); + + hFakeTrack_QoverPt[4] = new TEfficiency("faketrack_QoverPt", "#it{Q/p}_{T} fake-track rate;#it{Q/p}_{T} (GeV/#it{c});Fake-track rate", nb, xbins); + addObject(hFakeTrack_QoverPt[4]); + hNumRecoFake_QoverPt[4] = new TH1D("NumRecoFake_QoverPt", "", nb, xbins); + hDenTrue_QoverPt[4] = new TH1D("DenTrueMC_QoverPt", "", nb, xbins); + + for (Int_t icls = 0; icls < 4; icls++) { + hFakeTrack_pt[icls] = new TEfficiency(Form("faketrack_%dCluster_pt", icls + 4), Form("#it{p}_{T} fake-track rate %d cluster tracks;#it{p}_{T} (GeV/#it{c});Fake-track rate", icls + 4), nb, xbins); + addObject(hFakeTrack_pt[icls]); + hNumRecoFake_pt[icls] = new TH1D(Form("NumRecoFake_%dCluster_pt", icls + 4), "", nb, xbins); + hDenTrue_pt[icls] = new TH1D(Form("DenTrueMC_%dCluster_pt", icls + 4), "", nb, xbins); + hFakeTrack_phi[icls] = new TEfficiency(Form("faketrack_%dCluster_phi", icls + 4), Form("#phi fake-track rate %d cluster tracks;#phi;Fake-track rate", icls + 4), 60, 0, TMath::TwoPi()); + addObject(hFakeTrack_phi[icls]); + hNumRecoFake_phi[icls] = new TH1D(Form("NumRecoFake_%dCluster_phi", icls + 4), "", 60, 0, TMath::TwoPi()); + hDenTrue_phi[icls] = new TH1D(Form("DenTrueMC_%dCluster_phi", icls + 4), "", 60, 0, TMath::TwoPi()); + hFakeTrack_eta[icls] = new TEfficiency(Form("faketrack_%dCluster_eta", icls + 4), Form("#eta fake-track rate %d cluster tracks;#eta;Fake-track rate", icls + 4), 30, -1.5, 1.5); + addObject(hFakeTrack_eta[icls]); + hNumRecoFake_eta[icls] = new TH1D(Form("NumRecoFake_%dCluster_eta", icls + 4), "", 30, -1.5, 1.5); + hDenTrue_eta[icls] = new TH1D(Form("DenTrueMC_%dCluster_eta", icls + 4), "", 30, -1.5, 1.5); + hFakeTrack_r[icls] = new TEfficiency(Form("faketrack_%dCluster_r", icls + 4), Form("r fake-track rate %d cluster tracks;r (cm);Fake-track rate", icls + 4), 100, 0, 5); + addObject(hFakeTrack_r[icls]); + hNumRecoFake_r[icls] = new TH1D(Form("NumRecoFake_%dCluster_r", icls + 4), "", 100, 0, 5); + hDenTrue_r[icls] = new TH1D(Form("DenTrueMC_%dCluster_r", icls + 4), "", 100, 0, 5); + hFakeTrack_z[icls] = new TEfficiency(Form("faketrack_%dCluster_z", icls + 4), Form("z fake-track rate %d cluster tracks;z (cm);Fake-track rate", icls + 4), 101, -5, 5); + addObject(hFakeTrack_z[icls]); + hNumRecoFake_z[icls] = new TH1D(Form("NumRecoFake_%dCluster_z", icls + 4), "", 101, -5, 5); + hDenTrue_z[icls] = new TH1D(Form("DenTrueMC_%dCluster_z", icls + 4), "", 101, -5, 5); + hFakeTrack_QoverPt[icls] = new TEfficiency(Form("faketrack_%dCluster_QoverPt", icls + 4), Form("#it{Q/p}_{T} fake-track rate %d cluster tracks;#it{Q/p}_{T} (GeV/#it{c});Fake-track rate", icls + 4), nb, xbins); + addObject(hFakeTrack_QoverPt[icls]); + hNumRecoFake_QoverPt[icls] = new TH1D(Form("NumRecoFake_%dCluster_QoverPt", icls + 4), "", nb, xbins); + hDenTrue_QoverPt[icls] = new TH1D(Form("DenTrueMC_%dCluster_QoverPt", icls + 4), "", nb, xbins); + } + + hTrackImpactTransvValid = new TH1F("ImpactTransvVaild", "Transverse impact parameter for valid primary tracks; D (cm)", 60, -0.1, 0.1); + hTrackImpactTransvValid->SetTitle("Transverse impact parameter distribution of valid primary tracks"); + addObject(hTrackImpactTransvValid); + formatAxes(hTrackImpactTransvValid, "D (cm)", "counts", 1, 1.10); + + hTrackImpactTransvFake = new TH1F("ImpactTransvFake", "Transverse impact parameter for fake primary tracks; D (cm)", 60, -0.1, 0.1); + hTrackImpactTransvFake->SetTitle("Transverse impact parameter distribution of fake primary tracks"); + addObject(hTrackImpactTransvFake); + formatAxes(hTrackImpactTransvFake, "D (cm)", "counts", 1, 1.10); + + hPrimaryGen_pt = new TH1D("PrimaryGen_pt", ";#it{p}_{T} (GeV/#it{c});counts", nb, xbins); + hPrimaryGen_pt->SetTitle("#it{p}_{T} of primary generated particles"); + addObject(hPrimaryGen_pt); + formatAxes(hPrimaryGen_pt, "#it{p}_{T} (GeV/#it{c})", "counts", 1, 1.10); + + hPrimaryReco_pt = new TH1D("PrimaryReco_pt", ";#it{p}_{T} (GeV/#it{c});counts", nb, xbins); + hPrimaryReco_pt->SetTitle("#it{p}_{T} of primary reconstructed particles"); + addObject(hPrimaryReco_pt); + formatAxes(hPrimaryReco_pt, "#it{p}_{T} (GeV/#it{c})", "counts", 1, 1.10); + + hAngularDistribution = new TH2D("AngularDistribution", "AngularDistribution", 30, -1.5, 1.5, 60, 0, TMath::TwoPi()); + hAngularDistribution->SetTitle("AngularDistribution"); + addObject(hAngularDistribution); + formatAxes(hAngularDistribution, "#eta", "#phi", 1, 1.10); + hAngularDistribution->SetStats(0); +} + +void ITSTrackSimTask::addObject(TObject* aObject) +{ + if (!aObject) { + ILOG(Debug, Devel) << " ERROR: trying to add non-existent object " << ENDM; + return; + } else { + mPublishedObjects.push_back(aObject); + } +} + +void ITSTrackSimTask::formatAxes(TH1* h, const char* xTitle, const char* yTitle, float xOffset, float yOffset) +{ + h->GetXaxis()->SetTitle(xTitle); + h->GetYaxis()->SetTitle(yTitle); + h->GetXaxis()->SetTitleOffset(xOffset); + h->GetYaxis()->SetTitleOffset(yOffset); +} + +void ITSTrackSimTask::publishHistos() +{ + for (unsigned int iObj = 0; iObj < mPublishedObjects.size(); iObj++) { + getObjectsManager()->startPublishing(mPublishedObjects.at(iObj)); + ILOG(Debug, Devel) << " Object will be published: " << mPublishedObjects.at(iObj)->GetName() << ENDM; + } +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ITSTrackTask.cxx b/Modules/ITS/src/ITSTrackTask.cxx new file mode 100644 index 0000000000..296b30e763 --- /dev/null +++ b/Modules/ITS/src/ITSTrackTask.cxx @@ -0,0 +1,1216 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file ITSTrackTask.cxx +/// \author Artem Isakov +/// + +#include "QualityControl/QcInfoLogger.h" +#include "ITS/ITSTrackTask.h" +#include +#include +#include +#include "ReconstructionDataFormats/Vertex.h" +#include "ReconstructionDataFormats/PrimaryVertex.h" +#include "ITStracking/IOUtils.h" +#include +#include "Common/Utils.h" + +#include +#include "ITStracking/Constants.h" + +#include "DCAFitter/DCAFitterN.h" + +using namespace o2::itsmft; +using namespace o2::its; + +namespace o2::quality_control_modules::its +{ + +ITSTrackTask::ITSTrackTask() : TaskInterface() +{ + // createAllHistos(); +} + +ITSTrackTask::~ITSTrackTask() // make_shared objects will be delete automatically +{ + delete hVertexCoordinates; + delete hVertexRvsZ; + delete hVertexZ; + delete hVertexContributors; + delete hVertexContvsZ; + delete hAssociatedClusterFraction; + delete hNtracks; + delete hTrackPtVsEta; + delete hTrackPtVsPhi; +} + +void ITSTrackTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + + ILOG(Debug, Devel) << "initialize ITSTrackTask" << ENDM; + + mVertexXYsize = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "vertexXYsize", mVertexXYsize); + mVertexZsize = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "vertexZsize", mVertexZsize); + mVertexRsize = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "vertexRsize", mVertexRsize); + mNtracksMAX = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "NtracksMAX", mNtracksMAX); + nBCbins = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nBCbins", nBCbins); + mDoNorm = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "doNorm", mDoNorm); + mInvMasses = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "InvMasses", mInvMasses); + mPublishMore = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "publishMore", mPublishMore); + + // analysis for its-only residual + mAlignmentMonitor = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "doAlignmentMonitor", mAlignmentMonitor); + mDefaultMomResPar = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "UseDefaultMomResPar", mDefaultMomResPar); + if (mAlignmentMonitor == 1 && mDefaultMomResPar == 0) { + std::vector vMomResParMEAS1 = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "MomResParMEAS1", "")); + std::vector vMomResParMEAS2 = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "MomResParMEAS2", "")); + std::vector vMomResParMSC1 = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "MomResParMSC1", "")); + std::vector vMomResParMSC2 = convertToArray(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "MomResParMSC2", "")); + + for (int l = 0; l < NLayer; l++) { + mSigmaMeas[0][l] = (double)vMomResParMEAS1[l]; + mSigmaMeas[1][l] = (double)vMomResParMEAS2[l]; + mSigmaMsc[0][l] = (double)vMomResParMSC1[l]; + mSigmaMsc[1][l] = (double)vMomResParMSC2[l]; + } + } + mResMonNclMin = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "ResidualMonitorNclMin", mResMonNclMin); + mResMonTrackMinPt = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "ResidualMonitorTrackMinPt", mResMonTrackMinPt); + + fitfuncXY.loadparameters(mSigmaMeas, mSigmaMsc); + + // pt bins definition: 20 MeV/c width up to 1 GeV/c, 100 MeV/c afterwards + ptBins[0] = 0.; + for (int i = 1; i < 141; i++) { + ptBins[i] = i <= 50 ? ptBins[i - 1] + 0.02 : ptBins[i - 1] + 0.1; + } + + createAllHistos(); + publishHistos(); +} + +void ITSTrackTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; +} + +void ITSTrackTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ITSTrackTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + + ILOG(Debug, Devel) << "START DOING QC General" << ENDM; + + if (mTimestamp == -1) { // get dict from ccdb + mTimestamp = std::stol(o2::quality_control_modules::common::getFromConfig(mCustomParameters, "dicttimestamp", "0")); + long int ts = mTimestamp ? mTimestamp : ctx.services().get().creation; + ILOG(Debug, Devel) << "Getting dictionary from ccdb - timestamp: " << ts << ENDM; + + std::map metadata; + mDict = TaskInterface::retrieveConditionAny("ITS/Calib/ClusterDictionary", metadata, ts); + ILOG(Debug, Devel) << "Dictionary size: " << mDict->getSize() << ENDM; + + if (mAlignmentMonitor == 1) { + o2::its::GeometryTGeo::adopt(TaskInterface::retrieveConditionAny("ITS/Config/Geometry", metadata, ts)); + mGeom = o2::its::GeometryTGeo::Instance(); + if (!mGeom) { + ILOG(Fatal, Support) << "Can't retrive ITS geometry from ccdb - timestamp: " << ts << ENDM; + throw std::runtime_error("Can't retrive ITS geometry from ccdb!"); + } + ILOG(Debug, Devel) << "Loaded new instance of mGeom (for ITS alignment monitoring)" << ENDM; + } + } + + auto trackArr = ctx.inputs().get>("tracks"); + auto trackRofArr = ctx.inputs().get>("rofs"); + auto clusRofArr = ctx.inputs().get>("clustersrof"); + auto clusArr = ctx.inputs().get>("compclus"); + auto vertexArr = ctx.inputs().get>>>("Vertices"); + auto vertexRofArr = ctx.inputs().get>("Verticesrof"); + + auto clusIdx = ctx.inputs().get>("clusteridx"); + auto clusPatternArr = ctx.inputs().get>("patterns"); + auto pattIt = clusPatternArr.begin(); + + // Multiply cos(lambda) plot before refilling + if (mPublishMore) { + for (int ix = 1; ix <= hNClusterVsChipITS->GetNbinsX(); ix++) { + double integral = hNClusterVsChipITS->Integral(ix, ix, 0, -1); + if (integral < 1e-15) { + continue; + } + for (int iy = 1; iy <= hNClusterVsChipITS->GetNbinsY(); iy++) { + double binc = hNClusterVsChipITS->GetBinContent(ix, iy); + if (binc < 1e-15) { + continue; + } + hNClusterVsChipITS->SetBinContent(ix, iy, binc * integral); + hNClusterVsChipITS->SetBinError(ix, iy, std::sqrt(binc * integral)); + } + } + } + + std::vector clSize; + for (const auto& clus : clusArr) { + auto ClusterID = clus.getPatternID(); + if (ClusterID != o2::itsmft::CompCluster::InvalidPatternID && !mDict->isGroup(ClusterID)) { // Normal (frequent) cluster shapes + clSize.push_back(mDict->getNpixels(ClusterID)); + } else { + o2::itsmft::ClusterPattern patt(pattIt); + clSize.push_back(patt.getNPixels()); + } + } + + for (const auto& vertex : vertexArr) { + hVertexCoordinates->Fill(vertex.getX(), vertex.getY()); + hVertexRvsZ->Fill(vertex.getZ(), sqrt(vertex.getX() * vertex.getX() + vertex.getY() * vertex.getY())); + hVertexZ->Fill(vertex.getZ()); + hVertexContvsZ->Fill(vertex.getZ(), vertex.getNContributors()); + hVertexContributors->Fill(vertex.getNContributors()); + } + + // loop on vertices per ROF + for (int iROF = 0; iROF < vertexRofArr.size(); iROF++) { + + int start = vertexRofArr[iROF].getFirstEntry(); + int end = start + vertexRofArr[iROF].getNEntries(); + int nvtxROF = 0; + int nvtxROF_nocut = vertexRofArr[iROF].getNEntries(); + for (int ivtx = start; ivtx < end; ivtx++) { + auto& vertex = vertexArr[ivtx]; + if (vertex.getNContributors() > 0) { // TODO: for now no cut on contributors + nvtxROF++; + } + if (vertex.getNContributors() > 2) { // Apply cut for normalization + nVertices++; + } else if (nvtxROF_nocut == 1 && vertex.getNContributors() == 2) { + nVertices++; + } + } + hVerticesRof->Fill(nvtxROF); + } + + // DCAFitter2 class initialization for the v0 part + using Vec3D = ROOT::Math::SVector; // this is a type of the fitted vertex + o2::vertexing::DCAFitter2 ft; + ft.setBz(5.0); + ft.setPropagateToPCA(true); + ft.setMaxR(30); + ft.setMaxDZIni(0.1); + ft.setMaxDXYIni(0.1); + ft.setMinParamChange(1e-3); + ft.setMinRelChi2Change(0.9); + ft.setMaxChi2(10); + // prepare variables for v0 + float vx = 0, vy = 0, vz = 0; + float dca[2]{ 0., 0. }; + float bz = 5.0; + // loop on tracks per ROF + for (int iROF = 0; iROF < trackRofArr.size(); iROF++) { + + int nClusterCntTrack = 0; + int nTracks = trackRofArr[iROF].getNEntries(); + int start = trackRofArr[iROF].getFirstEntry(); + int end = start + trackRofArr[iROF].getNEntries(); + for (int itrack = start; itrack < end; itrack++) { + + auto& track = trackArr[itrack]; + auto out = track.getParamOut(); + Float_t Eta = -log(tan(out.getTheta() / 2.)); + hTrackEta->getNum()->Fill(Eta); + hTrackPhi->getNum()->Fill(out.getPhi()); + hAngularDistribution->getNum()->Fill(Eta, out.getPhi()); + hNClusters->getNum()->Fill(track.getNumberOfClusters()); + hTrackPtVsEta->Fill(out.getPt(), Eta); + hTrackPtVsPhi->Fill(out.getPt(), out.getPhi()); + + hNClustersPerTrackEta->getNum()->Fill(Eta, track.getNumberOfClusters()); + hNClustersPerTrackPhi->getNum()->Fill(out.getPhi(), track.getNumberOfClusters()); + hNClustersPerTrackPt->getNum()->Fill(out.getPt(), track.getNumberOfClusters()); + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + if (track.getPattern() & (0x1 << iLayer)) { // check first layer (from inside) on which there is a hit + hHitFirstLayerPhiAll->getNum()->Fill(out.getPhi(), iLayer); + if (track.getNumberOfClusters() == 4) { + hHitFirstLayerPhi4cls->getNum()->Fill(out.getPhi(), iLayer); + } else if (track.getNumberOfClusters() == 5) { + hHitFirstLayerPhi5cls->getNum()->Fill(out.getPhi(), iLayer); + } else if (track.getNumberOfClusters() == 6) { + hHitFirstLayerPhi6cls->getNum()->Fill(out.getPhi(), iLayer); + } else if (track.getNumberOfClusters() == 7) { + hHitFirstLayerPhi7cls->getNum()->Fill(out.getPhi(), iLayer); + } + break; + } + } + nClusterCntTrack += track.getNumberOfClusters(); + + std::vector hitUpdate; + int chipIDs[7] = { -1, -1, -1, -1, -1, -1, -1 }; + if (mAlignmentMonitor == 1 && out.getPt() > mResMonTrackMinPt && track.getNumberOfClusters() >= mResMonNclMin) { + for (int iLayer = 0; iLayer < NLayer; iLayer++) + hitUpdate.push_back(false); + } + + for (int icluster = 0; icluster < track.getNumberOfClusters(); icluster++) { + const int index = clusIdx[track.getFirstClusterEntry() + icluster]; + auto& cluster = clusArr[index]; + auto ChipID = cluster.getSensorID(); + int ClusterID = cluster.getPatternID(); // used for normal (frequent) cluster shapes + + int layer = 0; + while (ChipID >= ChipBoundary[layer] && layer <= NLayer) { + layer++; + } + layer--; + + if (mPublishMore) { + double clusterSizeWithCorrection = (double)clSize[index] * (std::cos(std::atan(out.getTgl()))); + hNClusterVsChipITS->Fill(ChipID + 1, clusterSizeWithCorrection); + } + + // Residual Monitoring + if (mAlignmentMonitor == 1 && out.getPt() > mResMonTrackMinPt && track.getNumberOfClusters() >= mResMonNclMin) { + + if (layer < 0 || layer >= NLayer) + continue; + + o2::math_utils::Point3D locC; // local coordinates + if (ClusterID != o2::itsmft::CompCluster::InvalidPatternID) { // Normal (frequent) cluster shapes + if (!mDict->isGroup(ClusterID)) { + locC = mDict->getClusterCoordinates(cluster); + } else { + o2::itsmft::ClusterPattern patt(pattIt); + locC = mDict->getClusterCoordinates(cluster, patt, true); + } + } else { // invalid pattern + continue; + } + + hitUpdate[layer] = true; + auto gloC = mGeom->getMatrixL2G(ChipID) * locC; + inputG_C[3 * layer + 0] = gloC.X(); + inputG_C[3 * layer + 1] = gloC.Y(); + inputG_C[3 * layer + 2] = gloC.Z(); + + chipIDs[layer] = ChipID; + } + } + + // Residual Monitoring + if (mAlignmentMonitor == 1 && out.getPt() > mResMonTrackMinPt && track.getNumberOfClusters() >= mResMonNclMin) { + int NclValid = 0; + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + if (hitUpdate[iLayer]) + NclValid++; + } + if (NclValid < mResMonNclMin) + continue; + + for (int ipar = 0; ipar < 6; ipar++) + FitparXY[ipar] = 0; + + double Cost_Fit = 0; + circleFitXY(inputG_C, FitparXY, Cost_Fit, hitUpdate, 0); + + double RecRadius = std::abs(1 / FitparXY[0]); + double CircleXc = FitparXY[0] > 0 ? RecRadius * std::cos(FitparXY[1] + FitparXY[4] + 0.5 * TMath::Pi()) : RecRadius * std::cos(FitparXY[1] + FitparXY[4] - 0.5 * TMath::Pi()); + double CircleYc = FitparXY[0] > 0 ? RecRadius * std::sin(FitparXY[1] + FitparXY[4] + 0.5 * TMath::Pi()) : RecRadius * std::sin(FitparXY[1] + FitparXY[4] - 0.5 * TMath::Pi()); + + CircleXc += FitparXY[2]; + CircleYc += FitparXY[3]; + + for (int ipar = 0; ipar < 2; ipar++) + FitparDZ[ipar] = 0; + double z_meas[NLayer]; + double beta[NLayer]; + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + z_meas[iLayer] = inputG_C[3 * iLayer + 2]; + beta[iLayer] = std::atan2(inputG_C[3 * iLayer + 1] - CircleYc, inputG_C[3 * iLayer + 0] - CircleXc); + } + + lineFitDZ(z_meas, beta, FitparDZ, RecRadius, false, hitUpdate); + + for (int iLayer = 0; iLayer < NLayer; iLayer++) { + if (chipIDs[iLayer] < 0) + continue; + double meas_GXc = inputG_C[(3 * iLayer) + 0]; // alpha + double meas_GYc = inputG_C[(3 * iLayer) + 1]; // beta + double meas_GZc = inputG_C[(3 * iLayer) + 2]; // gamma + double proj_GXc = RecRadius * std::cos(beta[iLayer]) + CircleXc; + double proj_GYc = RecRadius * std::sin(beta[iLayer]) + CircleYc; + double proj_GZc = (FitparDZ[0]) * (beta[iLayer]) + (FitparDZ[1]); + TVector3 measXY(meas_GXc, meas_GYc, 0); + TVector3 projXY(proj_GXc, proj_GYc, 0); + double sign = +1; + if (measXY.Cross(projXY).Z() > 0) + sign = +1; + else + sign = -1; + double dxy = sign * std::sqrt(std::pow(proj_GXc - meas_GXc, 2) + std::pow(proj_GYc - meas_GYc, 2)); + double dz = proj_GZc - meas_GZc; + + hResidualXY[iLayer]->Fill(dxy, chipIDs[iLayer]); + hResidualZD[iLayer]->Fill(dz, chipIDs[iLayer]); + } + } + + // Find V0s + if (mInvMasses == 1) { + if (track.getSign() < 0) // choose only positive tracks + continue; + track.getImpactParams(vx, vy, vz, bz, dca); + if ((track.getNumberOfClusters() < 6) || (abs(track.getTgl()) > 1.5) || (abs(dca[0]) < 0.06)) // conditions for the track acceptance + continue; + + for (int intrack = start; intrack < end; intrack++) { // goes through the tracks one more time + auto& ntrack = trackArr[intrack]; + if (ntrack.getSign() > 0) // choose only negative tracks + continue; + ntrack.getImpactParams(vx, vy, vz, bz, dca); + if ((ntrack.getNumberOfClusters() < 6) || (abs(ntrack.getTgl()) > 1.5) || (abs(dca[0]) < 0.06)) // conditions for the track acceptance + continue; + + int nc = 0; + try { + nc = ft.process(track, ntrack); + } catch (...) { + continue; + } + if (nc == 0) + continue; + + int ibest = 0; + float bestChi2 = 1e7; + for (int i = 0; i < nc; i++) { + auto chi2 = ft.getChi2AtPCACandidate(i); + if (chi2 > bestChi2) + continue; + bestChi2 = chi2; + ibest = i; + } + // conditions for v0 + auto vtx = ft.getPCACandidate(ibest); + auto x = vtx[0] + 0.02985; + auto y = vtx[1] + 0.01949; + auto r = sqrt(x * x + y * y); + if ((r < 0.5) || (r > 3.5)) + continue; + + const auto& t0 = ft.getTrack(0, ibest); // Positive daughter track + const auto& t1 = ft.getTrack(1, ibest); // Negative daughter track + + auto r0 = t0.getXYZGlo(); + auto r1 = t1.getXYZGlo(); + auto dx = r0.X() - r1.X(); + auto dy = r0.Y() - r1.Y(); + auto dz = r0.Z() - r1.Z(); + auto d = sqrt(dx * dx + dy * dy + dz * dz); + if (d > 0.02) + continue; + std::array p0; // Positive daughter momentum + t0.getPxPyPzGlo(p0); + std::array p1; // Negative daughter momentum + t1.getPxPyPzGlo(p1); + std::array v0p; // V0 particle momentum + v0p = { p0[0] + p1[0], p0[1] + p1[1], p0[2] + p1[2] }; + + // Strangness inv mass calculation + auto pV0 = sqrt(v0p[0] * v0p[0] + v0p[1] * v0p[1] + v0p[2] * v0p[2]); // Particle momentum + auto p2DaughterPos = p0[0] * p0[0] + p0[1] * p0[1] + p0[2] * p0[2]; // Positive daughter momentum + auto p2DaughterNeg = p1[0] * p1[0] + p1[1] * p1[1] + p1[2] * p1[2]; // Negative daughter momentum + // K0s + auto enDaughterPos = sqrt(mPiInvMass * mPiInvMass + p2DaughterPos); // Positive daughter energy + auto enDaughterNeg = sqrt(mPiInvMass * mPiInvMass + p2DaughterNeg); // Negative daughter energy + auto enV0 = enDaughterPos + enDaughterNeg; + auto K0sInvMass = sqrt(enV0 * enV0 - pV0 * pV0); + hInvMassK0s->Fill(K0sInvMass); + // Lambda + enDaughterPos = sqrt(mProtonInvMass * mProtonInvMass + p2DaughterPos); // Positive daughter energy + enDaughterNeg = sqrt(mPiInvMass * mPiInvMass + p2DaughterNeg); // Negative daughter energy + enV0 = enDaughterPos + enDaughterNeg; + auto LambdaInvMass = sqrt(enV0 * enV0 - pV0 * pV0); + hInvMassLambda->Fill(LambdaInvMass); + // LambdaBar + enDaughterPos = sqrt(mPiInvMass * mPiInvMass + p2DaughterPos); // Positive daughter energy + enDaughterNeg = sqrt(mProtonInvMass * mProtonInvMass + p2DaughterNeg); // Negative daughter energy + enV0 = enDaughterPos + enDaughterNeg; + auto LambdaBarInvMass = sqrt(enV0 * enV0 - pV0 * pV0); + hInvMassLambdaBar->Fill(LambdaBarInvMass); + } + } + } + + int nTotCls = clusRofArr[iROF].getNEntries(); + + float clusterRatio = nTotCls > 0 ? (float)nClusterCntTrack / (float)nTotCls : -1; + hAssociatedClusterFraction->Fill(clusterRatio); + hNtracks->Fill(nTracks); + + const auto bcdata = trackRofArr[iROF].getBCData(); + hClusterVsBunchCrossing->Fill(bcdata.bc, clusterRatio); + + } // end loop on ROFs + + mNRofs += trackRofArr.size(); + + // Scale angular distributions by latest number of vertices + Double_t normalization = 1.0; + + if (mDoNorm == 1 && nVertices > 0) + normalization = 1. * nVertices; + else if (mDoNorm == 2) + normalization = 1. * mNRofs; + + hAngularDistribution->getDen()->SetBinContent(1, 1, normalization); + hTrackEta->getDen()->SetBinContent(1, normalization); + hTrackPhi->getDen()->SetBinContent(1, normalization); + hNClusters->getDen()->SetBinContent(1, normalization); + hNClustersPerTrackEta->getDen()->SetBinContent(1, 1, normalization); + hNClustersPerTrackPhi->getDen()->SetBinContent(1, 1, normalization); + hNClustersPerTrackPt->getDen()->SetBinContent(1, 1, normalization); + hHitFirstLayerPhiAll->getDen()->SetBinContent(1, 1, normalization); + hHitFirstLayerPhi4cls->getDen()->SetBinContent(1, 1, normalization); + hHitFirstLayerPhi5cls->getDen()->SetBinContent(1, 1, normalization); + hHitFirstLayerPhi6cls->getDen()->SetBinContent(1, 1, normalization); + hHitFirstLayerPhi7cls->getDen()->SetBinContent(1, 1, normalization); + if (mPublishMore) { + // Normalize hNClusterVsChipITS to the clusters per chip + for (int ix = 1; ix <= hNClusterVsChipITS->GetNbinsX(); ix++) { + double integral = hNClusterVsChipITS->Integral(ix, ix, 0, -1); + if (integral < 1e-15) { + continue; + } + for (int iy = 1; iy <= hNClusterVsChipITS->GetNbinsY(); iy++) { + double binc = hNClusterVsChipITS->GetBinContent(ix, iy); + if (binc < 1e-15) { + continue; + } + double bine = hNClusterVsChipITS->GetBinError(ix, iy); + hNClusterVsChipITS->SetBinContent(ix, iy, binc / integral); + hNClusterVsChipITS->SetBinError(ix, iy, (binc / integral) * std::sqrt((bine / binc) * (bine / binc) + (std::sqrt(integral) / integral) * (std::sqrt(integral) / integral))); + } + } + } +} + +void ITSTrackTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + + hAngularDistribution->update(); + hTrackEta->update(); + hTrackPhi->update(); + hNClusters->update(); + hNClustersPerTrackEta->update(); + hNClustersPerTrackPhi->update(); + hNClustersPerTrackPt->update(); + hHitFirstLayerPhiAll->update(); + hHitFirstLayerPhi4cls->update(); + hHitFirstLayerPhi5cls->update(); + hHitFirstLayerPhi6cls->update(); + hHitFirstLayerPhi7cls->update(); +} + +void ITSTrackTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ITSTrackTask::reset() +{ + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + hAngularDistribution->Reset(); + hNClusters->Reset(); + hTrackPhi->Reset(); + hTrackEta->Reset(); + hVerticesRof->Reset(); + nVertices = 0; + mNRofs = 0; + hVertexCoordinates->Reset(); + hVertexRvsZ->Reset(); + hVertexZ->Reset(); + hVertexContributors->Reset(); + hVertexContvsZ->Reset(); + + hAssociatedClusterFraction->Reset(); + hNtracks->Reset(); + hNClustersPerTrackEta->Reset(); + hNClustersPerTrackPhi->Reset(); + hNClustersPerTrackPt->Reset(); + hHitFirstLayerPhiAll->Reset(); + hHitFirstLayerPhi4cls->Reset(); + hHitFirstLayerPhi5cls->Reset(); + hHitFirstLayerPhi6cls->Reset(); + hHitFirstLayerPhi7cls->Reset(); + hClusterVsBunchCrossing->Reset(); + if (mPublishMore) { + hNClusterVsChipITS->Reset(); + } + + hInvMassK0s->Reset(); + hInvMassLambda->Reset(); + hInvMassLambdaBar->Reset(); + + hTrackPtVsEta->Reset(); + hTrackPtVsPhi->Reset(); + + if (mAlignmentMonitor == 1) { + for (int l = 0; l < NLayer; l++) { + hResidualXY[l]->Reset(); + hResidualZD[l]->Reset(); + } + } +} + +void ITSTrackTask::createAllHistos() +{ + std::string titleNorm = mDoNorm == 1 ? "/ n_vertices" : mDoNorm == 2 ? "/ n_rofs" + : ""; + hAngularDistribution = std::make_unique("AngularDistribution", "AngularDistribution", 40, -2.0, 2.0, 60, 0, TMath::TwoPi(), true); + hAngularDistribution->SetTitle("AngularDistribution"); + addObject(hAngularDistribution.get()); + formatAxes(hAngularDistribution.get(), "#eta", "#phi", 1, 1.10); + hAngularDistribution->SetStats(0); + + hNClusters = std::make_unique("NClusters", "NClusters", 15, -0.5, 14.5, true); + hNClusters->SetTitle("hNClusters"); + addObject(hNClusters.get()); + formatAxes(hNClusters.get(), "Number of clusters per Track", Form("Counts %s", titleNorm.c_str()), 1, 1.10); + hNClusters->SetStats(0); + hNClusters->SetOption("HIST"); + + hTrackEta = std::make_unique("EtaDistribution", "EtaDistribution", 40, -2.0, 2.0, true); + hTrackEta->SetTitle(Form("Eta Distribution of tracks %s ", titleNorm.c_str())); + hTrackEta->SetMinimum(0); + addObject(hTrackEta.get()); + formatAxes(hTrackEta.get(), "#eta", Form("Counts %s", titleNorm.c_str()), 1, 1.10); + hTrackEta->SetStats(0); + + hTrackPhi = std::make_unique("PhiDistribution", "PhiDistribution", 65, -0.1, TMath::TwoPi(), true); + hTrackPhi->SetTitle(Form("Phi Distribution of tracks %s", titleNorm.c_str())); + hTrackPhi->SetMinimum(0); + addObject(hTrackPhi.get()); + formatAxes(hTrackPhi.get(), "#phi", Form("Counts %s", titleNorm.c_str()), 1, 1.10); + hTrackPhi->SetStats(0); + + hVerticesRof = new TH1D("VerticesRof", "VerticesRof", 101, -0.5, 100.5); + hVerticesRof->SetTitle("Distribution n_vertices / ROF"); + addObject(hVerticesRof); + formatAxes(hVerticesRof, "vertices / ROF", "Counts", 1, 1.10); + + hVertexCoordinates = new TH2D("VertexCoordinates", "VertexCoordinates", (int)(mVertexXYsize * 2 / 0.01), -1. * mVertexXYsize, mVertexXYsize, (int)(mVertexXYsize * 2 / 0.01), -1 * mVertexXYsize, mVertexXYsize); + hVertexCoordinates->SetTitle("Coordinates of track vertex"); + addObject(hVertexCoordinates); + formatAxes(hVertexCoordinates, "X coordinate (cm)", "Y coordinate (cm)", 1, 1.10); + hVertexCoordinates->SetStats(0); + + hVertexRvsZ = new TH2D("VertexRvsZ", "VertexRvsZ", (int)(mVertexZsize * 2 / 0.01), -mVertexZsize, mVertexZsize, (int)(mVertexRsize / 0.01), 0, mVertexRsize); + hVertexRvsZ->SetTitle("Distance to primary vertex vs Z"); + addObject(hVertexRvsZ); + formatAxes(hVertexRvsZ, "Z coordinate (cm)", "R (cm)", 1, 1.10); + + hVertexZ = new TH1D("VertexZ", "VertexZ", (int)(mVertexZsize * 2 / 0.01), -mVertexZsize, mVertexZsize); + hVertexZ->SetTitle("Z coordinate of vertex"); + addObject(hVertexZ); + formatAxes(hVertexZ, "Z coordinate (cm)", "counts", 1, 1.10); + hVertexZ->SetStats(0); + + hVertexContributors = new TH1D("NVertexContributors", "NVertexContributors", 500, 0, 500); + hVertexContributors->SetTitle("NVertexContributors"); + addObject(hVertexContributors); + formatAxes(hVertexContributors, "Number of contributors for vertex", "Counts", 1, 1.10); + hVertexContributors->SetStats(0); + + hVertexContvsZ = new TH2D("VertexContvsZ", "Vertex Contributors vs Z", (int)(mVertexZsize * 2 / 0.01), -mVertexZsize, mVertexZsize, 500, 0, 500); + addObject(hVertexContvsZ); + formatAxes(hVertexContvsZ, "Z coordinate (cm)", "N contributors", 1, 1.10); + hVertexContvsZ->SetStats(0); + + hAssociatedClusterFraction = new TH1D("AssociatedClusterFraction", "AssociatedClusterFraction", 100, 0, 1); + hAssociatedClusterFraction->SetTitle("The fraction of clusters into tracks event by event"); + addObject(hAssociatedClusterFraction); + formatAxes(hAssociatedClusterFraction, "Clusters in tracks / All clusters", "Counts", 1, 1.10); + hAssociatedClusterFraction->SetStats(0); + + hNtracks = new TH1D("Ntracks", "Ntracks", (int)mNtracksMAX, 0, mNtracksMAX); + hNtracks->SetTitle("The number of tracks event by event"); + addObject(hNtracks); + formatAxes(hNtracks, "# tracks", "Counts", 1, 1.10); + hNtracks->SetStats(0); + + hNClustersPerTrackEta = std::make_unique("NClustersPerTrackEta", "NClustersPerTrackEta", 400, -2.0, 2.0, 15, -0.5, 14.5, true); + hNClustersPerTrackEta->SetTitle("Eta vs NClusters Per Track"); + addObject(hNClustersPerTrackEta.get()); + formatAxes(hNClustersPerTrackEta.get(), "#eta", "# of Clusters per Track", 1, 1.10); + hNClustersPerTrackEta->SetStats(0); + + hNClustersPerTrackPhi = std::make_unique("NClustersPerTrackPhi", "NClustersPerTrackPhi", 65, 0, TMath::TwoPi(), 15, -0.5, 14.5, true); + hNClustersPerTrackPhi->SetTitle("Phi vs NClusters Per Track"); + addObject(hNClustersPerTrackPhi.get()); + formatAxes(hNClustersPerTrackPhi.get(), "#phi", "# of Clusters per Track", 1, 1.10); + hNClustersPerTrackPhi->SetStats(0); + + hNClustersPerTrackPt = std::make_unique("NClustersPerTrackPt", "NClustersPerTrackPt", 150, 0, 10, 15, -0.5, 14.5, true); + hNClustersPerTrackPt->SetTitle("#it{p_{T}} vs NClusters Per Track"); + addObject(hNClustersPerTrackPt.get()); + formatAxes(hNClustersPerTrackPt.get(), "#it{p_{T}} (GeV/c)", "# of Clusters per Track", 1, 1.10); + hNClustersPerTrackPt->SetStats(0); + + hHitFirstLayerPhiAll = std::make_unique("HitFirstLayerAll", "HitFirstLayerPhiAll", 65, -0.1, TMath::TwoPi(), 4, -0.5, 3.5, true); + hHitFirstLayerPhiAll->SetTitle("Layer with 1st track hit vs Phi - all tracks"); + addObject(hHitFirstLayerPhiAll.get()); + formatAxes(hHitFirstLayerPhiAll.get(), "#phi", "Layer with 1st hit", 1, 1.10); + hHitFirstLayerPhiAll->SetStats(0); + + hHitFirstLayerPhi4cls = std::make_unique("HitFirstLayer4cls", "HitFirstLayerPhi4cls", 65, -0.1, TMath::TwoPi(), 4, -0.5, 3.5, true); + hHitFirstLayerPhi4cls->SetTitle("Layer with 1st track hit vs Phi - 4 cls tracks"); + addObject(hHitFirstLayerPhi4cls.get()); + formatAxes(hHitFirstLayerPhi4cls.get(), "#phi", "Layer with 1st hit", 1, 1.10); + hHitFirstLayerPhi4cls->SetStats(0); + + hHitFirstLayerPhi5cls = std::make_unique("HitFirstLayer5cls", "HitFirstLayerPhi5cls", 65, -0.1, TMath::TwoPi(), 4, -0.5, 3.5, true); + hHitFirstLayerPhi5cls->SetTitle("Layer with 1st track hit vs Phi - 5 cls tracks"); + addObject(hHitFirstLayerPhi5cls.get()); + formatAxes(hHitFirstLayerPhi5cls.get(), "#phi", "Layer with 1st hit", 1, 1.10); + hHitFirstLayerPhi5cls->SetStats(0); + + hHitFirstLayerPhi6cls = std::make_unique("HitFirstLayer6cls", "HitFirstLayerPhi6cls", 65, -0.1, TMath::TwoPi(), 4, -0.5, 3.5, true); + hHitFirstLayerPhi6cls->SetTitle("Layer with 1st track hit vs Phi - 6 cls tracks"); + addObject(hHitFirstLayerPhi6cls.get()); + formatAxes(hHitFirstLayerPhi6cls.get(), "#phi", "Layer with 1st hit", 1, 1.10); + hHitFirstLayerPhi6cls->SetStats(0); + + hHitFirstLayerPhi7cls = std::make_unique("HitFirstLayer7cls", "HitFirstLayerPhi7cls", 65, -0.1, TMath::TwoPi(), 4, -0.5, 3.5, true); + hHitFirstLayerPhi7cls->SetTitle("Layer with 1st track hit vs Phi - 7 cls tracks"); + addObject(hHitFirstLayerPhi7cls.get()); + formatAxes(hHitFirstLayerPhi7cls.get(), "#phi", "Layer with 1st hit", 1, 1.10); + hHitFirstLayerPhi7cls->SetStats(0); + + hClusterVsBunchCrossing = new TH2D("BunchCrossingIDvsClusterRatio", "BunchCrossingIDvsClusterRatio", nBCbins, 0, 4095, 100, 0, 1); + hClusterVsBunchCrossing->SetTitle("Bunch Crossing ID vs Cluster Ratio"); + addObject(hClusterVsBunchCrossing); + formatAxes(hClusterVsBunchCrossing, "Bunch Crossing ID", "Fraction of clusters in tracks", 1, 1.10); + hClusterVsBunchCrossing->SetStats(0); + + for (int i = 0; i < 2125; i++) { + if (i <= 432) { + mChipBins[i] = i + 1; + } else { + mChipBins[i] = i + 1 + 13 * (i - 432); + } + } + mCoslBins[0] = 0; + for (int i = 1; i < 25; i++) { + if (mCoslBins[i - 1] + 0.5 < 6.05) { + mCoslBins[i] = mCoslBins[i - 1] + 0.5; + } else { + mCoslBins[i] = mCoslBins[i - 1] + 0.75; + } + } + + if (mPublishMore) { + hNClusterVsChipITS = new TH2D(Form("NClusterVsChipITS"), Form("NClusterVsChipITS"), 2124, mChipBins, 24, mCoslBins); + hNClusterVsChipITS->SetTitle(Form("Corrected cluster size for track clusters vs Chip Full Detector")); + hNClusterVsChipITS->SetBit(TH1::kIsAverage); + addObject(hNClusterVsChipITS); + formatAxes(hNClusterVsChipITS, "ChipID + 1", "(Cluster size x cos(#lambda)) / n_clusters", 1, 1.10); + hNClusterVsChipITS->SetStats(0); + hNClusterVsChipITS->SetMaximum(1); + + // NClusterVsChip Full Detector distinguishable + for (int l = 0; l < NLayer + 1; l++) { + auto line = new TLine(ChipBoundary[l], 0, ChipBoundary[l], 15); + hNClusterVsChipITS->GetListOfFunctions()->Add(line); + } + } + + // Invariant mass K0s, Lambda, LambdaBar + hInvMassK0s = new TH1D("hInvMassK0s", "K0s invariant mass", 160, 0.0, 1.0); + hInvMassK0s->SetTitle(Form("Invariant mass of K0s")); + addObject(hInvMassK0s); + formatAxes(hInvMassK0s, "m_{inv} (Gev/c)", "Counts", 1, 1.10); + hInvMassK0s->SetStats(0); + + hInvMassLambda = new TH1D("hInvMassLambda", "Lambda invariant mass", 400, 1.0, 2.0); + hInvMassLambda->SetTitle(Form("Invariant mass of Lambda")); + addObject(hInvMassLambda); + formatAxes(hInvMassLambda, "m_{inv} (Gev/c)", "Counts", 1, 1.10); + hInvMassLambda->SetStats(0); + + hInvMassLambdaBar = new TH1D("hInvMassLambdaBar", "LambdaBar invariant mass", 400, 1.0, 2.0); + hInvMassLambdaBar->SetTitle(Form("Invariant mass of LambdaBar")); + addObject(hInvMassLambdaBar); + formatAxes(hInvMassLambdaBar, "m_{inv} (Gev/c)", "Counts", 1, 1.10); + hInvMassLambdaBar->SetStats(0); + + hTrackPtVsEta = new TH2D("hTrackPtVsEta", "Track #it{p}_{T} Vs #eta", 150, 0, 15, 40, -2.0, 2.0); + addObject(hTrackPtVsEta); + formatAxes(hTrackPtVsEta, "#it{p}_{T} (GeV/#it{c})", "#eta", 1, 1.10); + hTrackPtVsEta->SetStats(0); + + hTrackPtVsPhi = new TH2D("hTrackPtVsPhi", "Track #it{p}_{T} Vs #phi", 150, 0, 15, 65, 0, TMath::TwoPi()); + addObject(hTrackPtVsPhi); + formatAxes(hTrackPtVsPhi, "#it{p}_{T} (GeV/#it{c})", "#phi", 1, 1.10); + hTrackPtVsPhi->SetStats(0); + + if (mAlignmentMonitor == 1) { + for (int l = 0; l < NLayer; l++) { + // sensor + int NBinsChipID = (l < NLayerIB) ? (ChipBoundary[l + 1] - ChipBoundary[l]) : (ChipBoundary[l + 1] - ChipBoundary[l]) / 14; + + hResidualXY[l] = std::make_unique(Form("hResidualXY%d", l), Form("ChipID vs dxy, Layer %d", l), + 500, -0.05, 0.05, NBinsChipID, ChipBoundary[l] - 0.5, ChipBoundary[l + 1] - 0.5); + addObject(hResidualXY[l].get()); + formatAxes(hResidualXY[l].get(), "dxy(cm)", (l < NLayerIB) ? "ChipID(Sensor Unit)" : "ChipID(HIC Unit)", 1, 1.10); + hResidualXY[l]->SetStats(0); + + hResidualZD[l] = std::make_unique(Form("hResidualZD%d", l), Form("ChipID vs dz, Layer %d", l), + 500, -0.05, 0.05, NBinsChipID, ChipBoundary[l] - 0.5, ChipBoundary[l + 1] - 0.5); + addObject(hResidualZD[l].get()); + formatAxes(hResidualZD[l].get(), "dz(cm)", (l < NLayerIB) ? "ChipID(Sensor Unit)" : "ChipID(HIC Unit)", 1, 1.10); + hResidualZD[l]->SetStats(0); + } + } +} + +void ITSTrackTask::addObject(TObject* aObject) +{ + if (!aObject) { + ILOG(Debug, Devel) << " ERROR: trying to add non-existent object " << ENDM; + return; + } else { + mPublishedObjects.push_back(aObject); + } +} + +void ITSTrackTask::publishHistos() +{ + for (unsigned int iObj = 0; iObj < mPublishedObjects.size(); iObj++) { + getObjectsManager()->startPublishing(mPublishedObjects.at(iObj)); + ILOG(Debug, Devel) << " Object will be published: " << mPublishedObjects.at(iObj)->GetName() << ENDM; + } +} + +void ITSTrackTask::circleFitXY(double* input, double* par, double& MSEvalue, std::vector hitUpdate, int step) +{ + + int hitentries = 0; + for (int a = 0; a < hitUpdate.size(); a++) { + if (hitUpdate[a] == true) + hitentries++; + } + + std::vector hr; + + double frphiX = 0; + double frphiY = 0; + int nfrdet = 0; + for (int a = 0; a < hitUpdate.size(); a++) { + if (hitUpdate[a] == false) + continue; + if (a != 2) + continue; + + frphiX += input[(3 * a) + 0]; + frphiY += input[(3 * a) + 1]; + nfrdet++; + } + frphiX /= nfrdet; + frphiY /= nfrdet; + + double FitFrame = std::atan2(frphiY, frphiX) - TMath::Pi() / 4.; + TMatrixD RotF(2, 2); + RotF[0] = { TMath::Cos(FitFrame), TMath::Sin(FitFrame) }; + RotF[1] = { -TMath::Sin(FitFrame), TMath::Cos(FitFrame) }; + + TMatrixD RotFInv(2, 2); + RotFInv[0] = { TMath::Cos(FitFrame), -TMath::Sin(FitFrame) }; + RotFInv[1] = { TMath::Sin(FitFrame), TMath::Cos(FitFrame) }; + + std::vector i, j, k; //[hitentries] + std::vector irot, jrot; //[hitentries] + + int fa = 0; + int index[7] = { -1, -1, -1, -1, -1, -1, -1 }; + for (int a = 0; a < hitUpdate.size(); a++) { + if (hitUpdate[a] == false) + continue; + i.push_back(input[(3 * a) + 0]); + j.push_back(input[(3 * a) + 1]); + k.push_back(input[(3 * a) + 2]); + + TMatrixD gloX[2]; + gloX[0].ResizeTo(1, 2); + gloX[0][0] = { i[fa], j[fa] }; + gloX[0].T(); + gloX[1].ResizeTo(2, 1); + gloX[1] = RotF * gloX[0]; + gloX[1].T(); + + irot.push_back(gloX[1][0][0]); + jrot.push_back(gloX[1][0][1]); + hr.push_back(TVector3(irot[fa], jrot[fa], a)); + index[a] = fa; + fa++; + } + + int cntR[] = { 0, 0 }; + std::vector initR; + + // standard seeding + // 012 + (2)34(5) + 56 + int hit1[] = { 0, 1, 2 }; + int hit2[] = { 3, 4, 5, 2 }; + bool hit_mid = false; + int hit3[] = { 6, 5 }; + for (int i1 = 0; i1 < 3; i1++) { + // i1 -> hit1[i1] + for (int i2 = 0; i2 < 4; i2++) { + // i2 -> hit2[i2] + if (hit_mid == true && i2 >= 2) + continue; + for (int i3 = 0; i3 < 2; i3++) { + // i3 -> hit3[i3] + if (hit1[i1] == hit2[i2] || hit2[i2] == hit3[i3]) + continue; + // if(hit_mid==true) continue; + if (hitUpdate[hit1[i1]] == false) + continue; + if (hitUpdate[hit2[i2]] == false) + continue; + if (hitUpdate[hit3[i3]] == false) + continue; + double hitX[3] = { i[index[hit1[i1]]], i[index[hit2[i2]]], i[index[hit3[i3]]] }; + double hitY[3] = { j[index[hit1[i1]]], j[index[hit2[i2]]], j[index[hit3[i3]]] }; + + double d12 = -(hitX[1] - hitX[0]) / (hitY[1] - hitY[0]); + double d23 = -(hitX[2] - hitX[1]) / (hitY[2] - hitY[1]); + + double x12 = 0.5 * (hitX[0] + hitX[1]); + double x23 = 0.5 * (hitX[1] + hitX[2]); + double y12 = 0.5 * (hitY[0] + hitY[1]); + double y23 = 0.5 * (hitY[1] + hitY[2]); + + double CenterX = ((-d23 * x23 + d12 * x12) + (y23 - y12)) / (-d23 + d12); + double CenterY = d12 * (CenterX - x12) + y12; + + double temp_R = std::sqrt(std::pow(CenterX - hitX[0], 2) + std::pow(CenterY - hitY[0], 2)); + initR.push_back(TVector3(CenterX, CenterY, temp_R)); + if (i2 < 2) { + hit_mid = true; // mid hit is successfully used. Do not find inner or outer hits for initial radius searching + } + cntR[0]++; + } + } + } + + if (initR.size() == 0) { + initR.push_back(TVector3(0, 0, 10000)); + cntR[0]++; + } + + double mean_X[] = { 0, 0 }; + double mean_Y[] = { 0, 0 }; + double mean_R[] = { 0, 0 }; + for (int i = 0; i < cntR[0]; i++) { + mean_X[0] += initR[i].X() / (double)cntR[0]; + mean_Y[0] += initR[i].Y() / (double)cntR[0]; + mean_R[0] += initR[i](2) / (double)cntR[0]; + } + + for (int i = 0; i < cntR[0]; i++) { + if (std::abs(mean_R[0] - initR[i](2)) < mean_R[0]) { + mean_X[1] += initR[i].X(); + mean_Y[1] += initR[i].Y(); + mean_R[1] += initR[i](2); + cntR[1]++; + } + } + mean_R[1] /= cntR[1]; + + mean_R[1] *= std::pow(sqrt(10), step); + if (mean_R[1] < 1.0e+1) + mean_R[1] = 1.0e+1; + if (mean_R[1] > 1.0e+6) + mean_R[1] = 1.0e+6; + + double thetaR = std::atan2(jrot[0], irot[0]); + + double temp_parA[4]; + temp_parA[0] = +1 / mean_R[1]; + temp_parA[1] = 0; + + double temp_parB[4]; + temp_parB[0] = -1 / mean_R[1]; + temp_parB[1] = 0; + + // make the functor object + fitfuncXY.init(); + fitfuncXY.set(hr, thetaR, ITS_AbsBz); + ROOT::Math::Functor fcn(fitfuncXY, 4); + + double pStartA[4] = { temp_parA[0], temp_parA[1], 0, 0 }; + fitterA.SetFCN(fcn, pStartA); + fitterA.Config().ParSettings(0).SetStepSize((float)FitStepSize[0]); + fitterA.Config().ParSettings(1).SetStepSize((float)FitStepSize[1]); + fitterA.Config().ParSettings(2).SetStepSize((float)FitStepSize[2]); + fitterA.Config().ParSettings(3).SetStepSize((float)FitStepSize[3]); + fitterA.Config().ParSettings(0).SetLimits(+1.0e-10, +1.0e-1); // + side + + double pStartB[4] = { temp_parB[0], temp_parB[1], 0, 0 }; + fitterB.SetFCN(fcn, pStartB); + fitterB.Config().ParSettings(0).SetStepSize((float)FitStepSize[0]); + fitterB.Config().ParSettings(1).SetStepSize((float)FitStepSize[1]); + fitterB.Config().ParSettings(2).SetStepSize((float)FitStepSize[2]); + fitterB.Config().ParSettings(3).SetStepSize((float)FitStepSize[3]); + fitterB.Config().ParSettings(0).SetLimits(-1.0e-1, -1.0e-10); // - side + + ROOT::Math::MinimizerOptions minOpt; + for (int iTol = 0; iTol < 4; iTol++) { + + minOpt.SetTolerance(std::pow(10, iTol) * FitTolerance); + + fitterA.Config().SetMinimizerOptions(minOpt); + fitterB.Config().SetMinimizerOptions(minOpt); + bool okA = fitterA.FitFCN(); + bool okB = fitterB.FitFCN(); + + if (!okA) { + if (!okB) { + const ROOT::Fit::FitResult& resultA = fitterA.Result(); + const double* parFitA = resultA.GetParams(); + double MSEvalueA = resultA.MinFcnValue(); + const ROOT::Fit::FitResult& resultB = fitterB.Result(); + const double* parFitB = resultB.GetParams(); + double MSEvalueB = resultB.MinFcnValue(); + + TMatrixD vxyA[2]; + vxyA[0].ResizeTo(1, 2); + vxyA[0][0] = { parFitA[2], parFitA[3] }; + vxyA[0].T(); + vxyA[1].ResizeTo(2, 1); + vxyA[1] = RotFInv * vxyA[0]; + vxyA[1].T(); + + TMatrixD vxyB[2]; + vxyB[0].ResizeTo(1, 2); + vxyB[0][0] = { parFitB[2], parFitB[3] }; + vxyB[0].T(); + vxyB[1].ResizeTo(2, 1); + vxyB[1] = RotFInv * vxyB[0]; + vxyB[1].T(); + + if (MSEvalueA < MSEvalueB) { + MSEvalue = MSEvalueA; + par[0] = parFitA[0]; + par[1] = parFitA[1]; + par[2] = vxyA[1][0][0]; + par[3] = vxyA[1][0][1]; + par[4] = thetaR + FitFrame; + par[5] = iTol * 10 + okA; + } else { + MSEvalue = MSEvalueB; + par[0] = parFitB[0]; + par[1] = parFitB[1]; + par[2] = vxyB[1][0][0]; + par[3] = vxyB[1][0][1]; + par[4] = thetaR + FitFrame; + par[5] = iTol * 10 + okB; + } + + } else { + const ROOT::Fit::FitResult& resultB = fitterB.Result(); + const double* parFitB = resultB.GetParams(); + MSEvalue = resultB.MinFcnValue(); + + TMatrixD vxyB[2]; + vxyB[0].ResizeTo(1, 2); + vxyB[0][0] = { parFitB[2], parFitB[3] }; + vxyB[0].T(); + vxyB[1].ResizeTo(2, 1); + vxyB[1] = RotFInv * vxyB[0]; + vxyB[1].T(); + + par[0] = parFitB[0]; + par[1] = parFitB[1]; + par[2] = vxyB[1][0][0]; + par[3] = vxyB[1][0][1]; + par[4] = thetaR + FitFrame; + par[5] = iTol * 10 + okB; + break; + } + + } else { + if (!okB) { + const ROOT::Fit::FitResult& resultA = fitterA.Result(); + const double* parFitA = resultA.GetParams(); + MSEvalue = resultA.MinFcnValue(); + + TMatrixD vxyA[2]; + vxyA[0].ResizeTo(1, 2); + vxyA[0][0] = { parFitA[2], parFitA[3] }; + vxyA[0].T(); + vxyA[1].ResizeTo(2, 1); + vxyA[1] = RotFInv * vxyA[0]; + vxyA[1].T(); + + par[0] = parFitA[0]; + par[1] = parFitA[1]; + par[2] = vxyA[1][0][0]; + par[3] = vxyA[1][0][1]; + par[4] = thetaR + FitFrame; + par[5] = iTol * 10 + okA; + break; + } else { + const ROOT::Fit::FitResult& resultA = fitterA.Result(); + const double* parFitA = resultA.GetParams(); + double MSEvalueA = resultA.MinFcnValue(); + const ROOT::Fit::FitResult& resultB = fitterB.Result(); + const double* parFitB = resultB.GetParams(); + double MSEvalueB = resultB.MinFcnValue(); + + TMatrixD vxyA[2]; + vxyA[0].ResizeTo(1, 2); + vxyA[0][0] = { parFitA[2], parFitA[3] }; + vxyA[0].T(); + vxyA[1].ResizeTo(2, 1); + vxyA[1] = RotFInv * vxyA[0]; + vxyA[1].T(); + + TMatrixD vxyB[2]; + vxyB[0].ResizeTo(1, 2); + vxyB[0][0] = { parFitB[2], parFitB[3] }; + vxyB[0].T(); + vxyB[1].ResizeTo(2, 1); + vxyB[1] = RotFInv * vxyB[0]; + vxyB[1].T(); + + if (MSEvalueA < MSEvalueB) { + MSEvalue = MSEvalueA; + par[0] = parFitA[0]; + par[1] = parFitA[1]; + par[2] = vxyA[1][0][0]; + par[3] = vxyA[1][0][1]; + par[4] = thetaR + FitFrame; + par[5] = iTol * 10 + okA; + } else { + MSEvalue = MSEvalueB; + par[0] = parFitB[0]; + par[1] = parFitB[1]; + par[2] = vxyB[1][0][0]; + par[3] = vxyB[1][0][1]; + par[4] = thetaR + FitFrame; + par[5] = iTol * 10 + okB; + } + break; + } + } + } +} + +void ITSTrackTask::lineFitDZ(double* zIn, double* betaIn, double* parz, double Radius, bool vertex, std::vector hitUpdate) +{ + + int hitentries = 0; + for (int a = 0; a < hitUpdate.size(); a++) { + if (hitUpdate[a] == true) + hitentries++; + } + if (vertex == true) + hitentries++; + + std::vector z, beta; + std::vector index; + + int fa = 0; + for (int a = 0; a < hitUpdate.size(); a++) { + if (hitUpdate[a] == true) { + z.push_back(zIn[a]); + beta.push_back(betaIn[a]); + index.push_back(a); + fa++; + } + } + if (vertex == true) { + z[fa] = zIn[hitUpdate.size()]; + beta[fa] = betaIn[hitUpdate.size()]; + index[fa] = hitUpdate.size(); + fa++; + } + + int tothits = hitentries; + + int last = tothits - 1; + TMatrixD MatA(tothits, 2); + TMatrixD MatAT(2, tothits); + TMatrixD MatZ(tothits, 1); + double beta2sum = 0; + double betasum = 0; + double nhits = 0; + + double Sigma_tot[tothits]; + for (int l = 0; l < tothits; l++) { + Sigma_tot[l] = getsigma(Radius, index[l], ITS_AbsBz, 0); + } + if (vertex == true) { + MatAT[0][0] = beta[last] / std::pow(Sigma_tot[last] * 1e-4 / Radius, 2); + MatAT[1][0] = 1 / std::pow(Sigma_tot[last] * 1e-4 / Radius, 2); + + MatZ[0] = { z[last] }; + + beta2sum += beta[last] * beta[last] / std::pow(Sigma_tot[last] * 1e-4 / Radius, 2); + betasum += beta[last] / std::pow(Sigma_tot[last] * 1e-4 / Radius, 2); + nhits += 1 / std::pow(Sigma_tot[last] * 1e-4 / Radius, 2); + + for (int i = 1; i < tothits; i++) { + + MatAT[0][i] = beta[i - 1] / std::pow(Sigma_tot[i - 1] * 1e-4 / Radius, 2); + MatAT[1][i] = 1 / std::pow(Sigma_tot[i - 1] * 1e-4 / Radius, 2); + + MatZ[i] = { z[i - 1] }; + + beta2sum += beta[i - 1] * beta[i - 1] / std::pow(Sigma_tot[i - 1] * 1e-4 / Radius, 2); + betasum += beta[i - 1] / std::pow(Sigma_tot[i - 1] * 1e-4 / Radius, 2); + nhits += 1 / std::pow(Sigma_tot[i - 1] * 1e-4 / Radius, 2); + } + } else { + for (int i = 0; i < tothits; i++) { + MatAT[0][i] = beta[i] / std::pow(Sigma_tot[i] * 1e-4 / Radius, 2); + MatAT[1][i] = 1 / std::pow(Sigma_tot[i] * 1e-4 / Radius, 2); + + MatZ[i] = { z[i] }; + + beta2sum += beta[i] * beta[i] / std::pow(Sigma_tot[i] * 1e-4 / Radius, 2); + betasum += beta[i] / std::pow(Sigma_tot[i] * 1e-4 / Radius, 2); + nhits += 1 / std::pow(Sigma_tot[i] * 1e-4 / Radius, 2); + } + } + TMatrixD MatATAInv(2, 2); + double DetATA = nhits * beta2sum - betasum * betasum; + MatATAInv[0] = { nhits / DetATA, -betasum / DetATA }; + MatATAInv[1] = { -betasum / DetATA, beta2sum / DetATA }; + + TMatrixD MatP(2, 1); + + MatP = MatATAInv * MatAT * MatZ; + + parz[0] = MatP[0][0]; + parz[1] = MatP[1][0]; +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ReductorBinContent.cxx b/Modules/ITS/src/ReductorBinContent.cxx new file mode 100644 index 0000000000..0466de9041 --- /dev/null +++ b/Modules/ITS/src/ReductorBinContent.cxx @@ -0,0 +1,61 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReductorBinContent.cxx +/// \author Artem Kotliarov +/// + +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include +#include "ITS/ReductorBinContent.h" +#include +#include + +namespace o2::quality_control_modules::its +{ +void* ReductorBinContent::getBranchAddress() +{ + return &mStats; +} + +const char* ReductorBinContent::getBranchLeafList() +{ + return Form("binContent[%i]/D", nBins); +} + +void ReductorBinContent::update(TObject* obj) +{ + + // initialize arrays + for (int j = 0; j < 100; j++) { + mStats.binContent[j] = -1.0; + } + + // Access the histograms embedded in 'obj'. + std::string histoClass = obj->IsA()->GetName(); + + if (histoClass.find("TH1") != std::string::npos) { + TH1* histo = (TH1*)obj->Clone("clone"); + int numberOfBins = histo->GetXaxis()->GetNbins(); + for (int j = 1; j <= numberOfBins; j++) { + mStats.binContent[j - 1] = histo->GetBinContent(j); + } + delete histo; + } else { + ILOG(Error, Support) << "Error: 'histo' not found." << ENDM; + } +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/ReductorIntegralContent.cxx b/Modules/ITS/src/ReductorIntegralContent.cxx new file mode 100644 index 0000000000..0454a541de --- /dev/null +++ b/Modules/ITS/src/ReductorIntegralContent.cxx @@ -0,0 +1,74 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReductorIntegralContent.cxx +/// \author Artem Kotliarov +/// + +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include +#include "ITS/ReductorIntegralContent.h" +#include +#include + +namespace o2::quality_control_modules::its +{ +void* ReductorIntegralContent::getBranchAddress() +{ + return &mStats; +} + +const char* ReductorIntegralContent::getBranchLeafList() +{ + return Form("integral[%i]/D", nBins); +} + +void ReductorIntegralContent::update(TObject* obj) +{ + + // initialize arrays + // + + for (int j = 0; j < 100; j++) { + mStats.integral[j] = 0; + } + + // Access the histograms embedded in 'obj'. + std::string histoClass = obj->IsA()->GetName(); + TH2* histo = (TH2*)obj->Clone("clone"); + int numberOfBinsX = histo->GetXaxis()->GetNbins(); + int numberOfBinsY = histo->GetYaxis()->GetNbins(); + std::string name = obj->GetName(); + if (histoClass.find("TH2") != std::string::npos) { + if (name.find("PayloadSize") != std::string::npos) { + + for (int id = 0; id < 9; id++) { + for (int ix = FeeBoundary[id] + 1; ix <= FeeBoundary[id + 1]; ix++) { + for (int iy = 1; iy <= numberOfBinsY; iy++) { + mStats.integral[id] += histo->GetBinContent(ix, iy) * histo->GetYaxis()->GetBinCenter(iy); + } + } + } + } else if (name.find("TriggerVsFeeid") != std::string::npos) { + for (int j = 1; j <= numberOfBinsY; j++) { + mStats.integral[j - 1] = histo->Integral(1, numberOfBinsX, j, j); // Summation over all Fee IDs for a given trigger + } + } + delete histo; + } else { + ILOG(Error, Support) << "Error: 'histo' not found." << ENDM; + } +} +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/TH2XlineReductor.cxx b/Modules/ITS/src/TH2XlineReductor.cxx new file mode 100755 index 0000000000..a0b74c7954 --- /dev/null +++ b/Modules/ITS/src/TH2XlineReductor.cxx @@ -0,0 +1,77 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2XlineReductor.cxx +/// \author Ivan Ravasenga on the model from Piotr Konopka +/// + +#include +#include +#include "ITS/TH2XlineReductor.h" +#include + +namespace o2::quality_control_modules::its +{ + +void* TH2XlineReductor::getBranchAddress() +{ + return &mStats; +} + +const char* TH2XlineReductor::getBranchLeafList() +{ + return Form("mean[%i]/D:stddev[%i]:entries[%i]:mean_scaled[%i]", NDIM, NDIM, NDIM, NDIM); +} + +void TH2XlineReductor::update(TObject* obj) +{ + auto histo = dynamic_cast(obj); + + // initialize arrays + for (int i = 0; i < NDIM; i++) { + mStats.mean[i] = -1.; + mStats.stddev[i] = -1.; + mStats.entries[i] = -1.; + mStats.mean_scaled[i] = -1.; + } + if (histo) { + Double_t entriesx = 0.; + Double_t sum = 0.; + for (int iy = 1; iy <= histo->GetNbinsY(); iy++) { + for (int ix = 1; ix <= histo->GetNbinsX(); ix++) { + if (histo->GetBinContent(ix, iy) > 0.) { + entriesx++; + } + sum += histo->GetBinContent(ix, iy); + } + Double_t meanx = !entriesx ? 0. : sum / entriesx; + mStats.mean[iy - 1] = meanx; + // printf ("entiresx %f \n", entriesx); + // printf("meanx = %f \n",meanx); + mStats.entries[iy - 1] = entriesx; + mStats.mean_scaled[iy - 1] = meanx * 512. * 1024.; + sum = 0.; + for (int ix = 1; ix <= histo->GetNbinsX(); ix++) { + Double_t binc = histo->GetBinContent(ix, iy); + if (binc > 0.) { + sum += (binc - meanx) * (binc - meanx); + } + } + mStats.stddev[iy - 1] = !entriesx ? 0. : entriesx == 1 ? TMath::Sqrt(sum / (entriesx)) + : TMath::Sqrt(sum / (entriesx - 1)); + // printf("stddev %f \n",mStats.stddev[iy-1]); + entriesx = 0.; + sum = 0.; + } // end loop on y bins + } // end if +} + +} // namespace o2::quality_control_modules::its diff --git a/Modules/ITS/src/TrendingTaskConfigITS.cxx b/Modules/ITS/src/TrendingTaskConfigITS.cxx new file mode 100644 index 0000000000..73579512e4 --- /dev/null +++ b/Modules/ITS/src/TrendingTaskConfigITS.cxx @@ -0,0 +1,92 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskConfig.cxx +/// \author Piotr Konopka +/// + +#include "ITS/TrendingTaskConfigITS.h" +#include + +using boost::property_tree::ptree; + +namespace o2::quality_control::postprocessing +{ + +TrendingTaskConfigITS::TrendingTaskConfigITS( + std::string id, const boost::property_tree::ptree& config) + : PostProcessingConfig(id, config) +{ + for (const auto& plotConfig : + config.get_child("qc.postprocessing." + id + ".plots")) { + if (const auto& sourceNames = plotConfig.second.get_child_optional("names"); + sourceNames.has_value()) { + const auto& sourceVarexps = + plotConfig.second.get_child_optional("varexp"); // take all varexps + const auto& sourceTitles = + plotConfig.second.get_child_optional("title"); // take all titles + ptree::const_iterator itname = sourceNames.value().begin(); + ptree::const_iterator itexp = sourceVarexps.value().begin(); + ptree::const_iterator ittitle = sourceTitles.value().begin(); + + // for (const auto& sourceName : sourceNames.value() && const auto& + // sourceVarexp : sourceVarexps.value() && const auto& sourceTitle : + // sourceTitles.value()) { + while (itname != sourceNames.value().end() || + itexp != sourceVarexps.value().end() || + ittitle != sourceTitles.value().end()) { + plots.push_back({ itname->second.data(), ittitle->second.data(), + itexp->second.data(), + plotConfig.second.get("selection", ""), + plotConfig.second.get("option", "") }); + itname++; + itexp++; + ittitle++; + } + } + } + for (const auto& dataSourceConfig : + config.get_child("qc.postprocessing." + id + ".dataSources")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); + sourceNames.has_value()) { + const auto& sourcePaths = dataSourceConfig.second.get_child_optional("paths"); // take all paths + ptree::const_iterator itname = sourceNames.value().begin(); + ptree::const_iterator itpath = sourcePaths.value().begin(); + while (itname != sourceNames.value().end() || + itpath != sourcePaths.value().end()) { + dataSources.push_back({ dataSourceConfig.second.get("type", ""), + itpath->second.data(), + itname->second.data(), + dataSourceConfig.second.get("reductorName", ""), + dataSourceConfig.second.get("moduleName", "") }); + itpath++; + itname++; + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSources.push_back( + { dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name"), + dataSourceConfig.second.get("reductorName"), + dataSourceConfig.second.get("moduleName") }); + } else { + throw std::runtime_error( + "No 'name' value or a 'names' vector in the " + "path 'qc.postprocessing." + + id + ".dataSources'"); + } + } + maxObjectTimeShiftMs = config.get("qc.postprocessing." + id + ".maxObjectTimeShiftMs", 0); +} + +} // namespace o2::quality_control::postprocessing diff --git a/Modules/ITS/src/TrendingTaskITSCluster.cxx b/Modules/ITS/src/TrendingTaskITSCluster.cxx new file mode 100644 index 0000000000..886f99126c --- /dev/null +++ b/Modules/ITS/src/TrendingTaskITSCluster.cxx @@ -0,0 +1,402 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSCluster.cxx +/// \author Ivan Ravasenga on the structure from Piotr Konopka +/// +#include "ITS/TrendingTaskITSCluster.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "ITS/TH2XlineReductor.h" +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::repository; +using namespace o2::quality_control_modules::its; + +void TrendingTaskITSCluster::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingTaskConfigITS(getID(), config); +} + +void TrendingTaskITSCluster::initialize(Trigger, framework::ServiceRegistryRef) +{ + // Preparing data structure of TTree + mTrend = std::make_unique(); // todo: retrieve last TTree, so we + // continue trending. maybe do it + // optionally? + mTrend->SetName(PostProcessingInterface::getName().c_str()); + // mTrend->Branch("meta", &mMetaData, "runNumber/I"); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("ntreeentries", &ntreeentries); + mTrend->Branch("time", &mTime); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create( + source.moduleName, source.reductorName)); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), + reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TrendingTaskITSCluster::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + trendValues(t, qcdb); + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSCluster::finalize(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSCluster::storeTrend(repository::DatabaseInterface& qcdb) +{ + ILOG(Debug, Devel) << "Storing the trend, entries: " << mTrend->GetEntries() << ENDM; + + auto mo = std::make_shared(mTrend.get(), getName(), "o2::quality_control_modules::its::TrendingTaskITSCluster", + mConfig.detectorName, mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); +} + +void TrendingTaskITSCluster::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + // We use current date and time. This for planned processing (not history). We + // still might need to use the objects + // timestamps in the end, but this would become ambiguous if there is more + // than one data source. + mTime = TDatime().Convert(); + mMetaData.runNumber = t.activity.mId; + int count = 0; + for (auto& dataSource : mConfig.dataSources) { + + // todo: make it agnostic to MOs, QOs or other objects. Let the reductor + // cast to whatever it needs. + if (dataSource.type == "repository") { + // auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name); + auto mo = qcdb.retrieveMO(dataSource.path, "", t.timestamp, t.activity); + if (mo == nullptr) { + continue; + } + if (!count) { + std::map entryMetadata = mo->getMetadataMap(); // full list of metadata as a map + mMetaData.runNumber = std::stoi(entryMetadata[metadata_keys::runNumber]); // get and set run number + ntreeentries = (Int_t)mTrend->GetEntries() + 1; + runlist.push_back(std::to_string(mMetaData.runNumber)); + } + TObject* obj = mo ? mo->getObject() : nullptr; + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (obj && reductor) { + reductor->update(obj); + } + } else if (dataSource.type == "repository-quality") { + auto qo = qcdb.retrieveQO(dataSource.path + "/" + dataSource.name); + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (qo && reductor) { + reductor->update(qo.get()); + } + } else { + ILOGE << "Unknown type of data source '" << dataSource.type << "'."; + } + count++; + } + mTrend->Fill(); +} + +void TrendingTaskITSCluster::storePlots(repository::DatabaseInterface& qcdb) +{ + // + // Create average plots for all layers + // + int ilay = 0; + int colidx = 0; + int mkridx = 0; + // adding canvas for the average plots for the layers + + TString vAverageObjectName[4] = { "avg_grouped_cluster_mean", "avg_grouped_cluster_std", "avg_cluster_size_summary", "avg_cluster_std" }; + TString vAverageObjectTitle[4] = { "Avg grouped topologies size mean", "Avg grouped topologies size rms", "Avg cluster size mean", "Avg cluster size rms" }; + TString vAverageObjectYTitle[4] = { "Avg grouped cluster size (pixel)", "std dev. of grouped cluster size (pixel)", "Avg cluster size (pixel)", "Std dev. of cluster size dist (pixel)" }; + int vAverageCanvasRunType[4] = { -1, -1, -1, -1 }; + TCanvas* c_avg[4]; + TMultiGraph* gTrends_avg[4]; + TLegend* legend_avg[4]; + for (int i = 0; i < 4; i++) { + c_avg[i] = new TCanvas(vAverageObjectName[i]); + gTrends_avg[i] = new TMultiGraph(vAverageObjectName[i].Data(), vAverageObjectName[i].Data()); + legend_avg[i] = new TLegend(0.91, 0.1, 0.98, 0.9); + } + + double min = 0.0; + double max = 20.0; + for (const auto& plot : mConfig.plots) { + // separate loop for average plots, skipped for others + // This does 4 plots, the mean and std for the clusters and grouped topologies each + // all 7 layers are done on the same plot + // afterwards, a separate loop on all the plots is run to create the other plots. In that, these ones are skipped. + if (plot.name.find("avg") != std::string::npos) { + long int n = mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), "goff"); // plot.option.c_str()); + // post processing plot + TGraph* g = new TGraph(n, mTrend->GetV2(), mTrend->GetV1()); + SetGraphStyle(g, col[colidx], mkr[mkridx]); + int index = -1; + if (plot.name.find("avg_grouped_mean") != std::string::npos) + index = 0; + if (plot.name.find("avg_grouped_stddev") != std::string::npos) + index = 1; + if (plot.name.find("avg_cluster_mean") != std::string::npos) + index = 2; + if (plot.name.find("avg_cluster_stddev") != std::string::npos) { + index = 3; + ilay++; + colidx++; + mkridx++; + } + + if (vAverageCanvasRunType[index] == -1) + vAverageCanvasRunType[index] = plot.varexp.find("ntreeentries") != std::string::npos ? 1 : 0; + gTrends_avg[index]->Add((TGraph*)g->Clone()); + legend_avg[index]->AddEntry((TGraph*)g->Clone(), Form("layer %d", ilay)); + delete g; + } + } + + for (int index = 0; index < 4; index++) { + c_avg[index]->cd(); + + int npoints = runlist.size(); + TH1F* hfake = new TH1F("hfake", "hfake", npoints, 0.5, (double)npoints + 0.5); + + SetGraphNameAndAxes(hfake, "hfake", vAverageObjectTitle[index].Data(), vAverageCanvasRunType[index] == 1 ? "run" : "time", vAverageObjectYTitle[index].Data(), gTrends_avg[index]->GetYaxis()->GetXmin(), gTrends_avg[index]->GetYaxis()->GetXmax(), runlist); + hfake->SetStats(kFALSE); + + hfake->Draw(); + gTrends_avg[index]->Draw(); + legend_avg[index]->Draw(); + + auto mo = std::make_shared(c_avg[index], mConfig.taskName, "o2::quality_control_modules::its::TrendingTaskITSCluster", mConfig.detectorName, mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); + + delete hfake; + delete legend_avg[index]; + delete gTrends_avg[index]; + delete c_avg[index]; + } + + // Create and save trends for each stave + // + int countplots = 0; + ilay = 0; + double ymin[NTRENDSCLUSTER] = { 0, 1e-1, -.5, 1e-9 }; + double ymax[NTRENDSCLUSTER] = { 50, 1e-5, 15.5, 1 }; + + // + // Create canvas with multiple trends - average threshold - 1 canvas per layer + // + ilay = 0; + countplots = 0; + TCanvas* c[NLAYERS * NTRENDSCLUSTER]; + TMultiGraph* gTrends_layer[NLAYERS * NTRENDSCLUSTER]; + TLegend* legstaves[NLAYERS]; + for (int idx = 0; idx < NLAYERS * NTRENDSCLUSTER; idx++) { // define canvases + c[idx] = new TCanvas( + Form("cluster_%s_trends_L%d", trendnames[idx % NTRENDSCLUSTER].c_str(), + idx / NTRENDSCLUSTER), + Form("cluster_%s_trends_L%d", trendnames[idx % NTRENDSCLUSTER].c_str(), + idx / NTRENDSCLUSTER)); + gTrends_layer[idx] = new TMultiGraph( + Form("cluster_%s_trends_L%d", trendnames[idx % NTRENDSCLUSTER].c_str(), + idx / NTRENDSCLUSTER), + Form("cluster_%s_trends_L%d", trendnames[idx % NTRENDSCLUSTER].c_str(), + idx / NTRENDSCLUSTER)); + } + + for (int ilay = 0; ilay < NLAYERS; ilay++) { // define legends + legstaves[ilay] = new TLegend(0.91, 0.1, 0.98, 0.9); + if (ilay > 2) { + legstaves[ilay]->SetNColumns(2); + } + legstaves[ilay]->SetName(Form("legstaves_L%d", ilay)); + SetLegendStyle(legstaves[ilay]); + PrepareLegend(legstaves[ilay], ilay); + } + + ilay = 0; + for (const auto& plot : mConfig.plots) { + int colidx = countplots > 41 ? countplots - 42 : countplots > 34 ? countplots - 35 + : countplots > 27 ? countplots - 28 + : countplots > 20 ? countplots - 21 + : countplots > 13 ? countplots - 14 + : countplots > 6 ? countplots - 7 + : countplots; + int mkridx = countplots > 41 ? 6 : countplots > 34 ? 5 + : countplots > 27 ? 4 + : countplots > 20 ? 3 + : countplots > 13 ? 2 + : countplots > 6 ? 1 + : 0; + int index = 0; + if (plot.name.find("occ") != std::string::npos) + index = 3; + else if (plot.name.find("chips") != std::string::npos) + index = 2; + else if (plot.name.find("avg") != std::string::npos) { + continue; + } else if (plot.name.find("stddev") != std::string::npos) + index = 1; + else + index = 0; + + bool isrun = plot.varexp.find("ntreeentries") != std::string::npos ? true : false; // vs run or vs time + long int n = mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), "goff"); + TGraph* g = new TGraph(n, mTrend->GetV2(), mTrend->GetV1()); + SetGraphStyle(g, col[colidx], mkr[mkridx]); + gTrends_layer[ilay * NTRENDSCLUSTER + index]->Add((TGraph*)g->Clone()); + delete g; + + if (plot.name.find("occ") != std::string::npos) + countplots++; + + if (countplots > nStaves[ilay] - 1) { + + for (int id = 0; id < 4; id++) { + c[ilay * NTRENDSCLUSTER + id]->cd(); + c[ilay * NTRENDSCLUSTER + id]->SetTickx(); + c[ilay * NTRENDSCLUSTER + id]->SetTicky(); + + int npoints = (int)runlist.size(); + TH1F* hfake = new TH1F("hfake", "hfake", npoints, 0.5, (double)npoints + 0.5); + max = gTrends_layer[ilay * NTRENDSCLUSTER + id]->GetYaxis()->GetXmax(); + + if (id == 2) + min = -0.5; + else + min = gTrends_layer[ilay * NTRENDSCLUSTER + id]->GetYaxis()->GetXmin(); + float max, min; + max = gTrends_layer[ilay * NTRENDSCLUSTER + id]->GetYaxis()->GetXmax(); + + if (id == 2) + min = -0.5; + else + min = gTrends_layer[ilay * NTRENDSCLUSTER + id]->GetYaxis()->GetXmin(); + SetGraphNameAndAxes(hfake, "hfake", + Form("L%d - %s trends", ilay, trendtitles[id].c_str()), + isrun ? "run" : "time", ytitles[id], min, max, runlist); + + hfake->SetStats(kFALSE); + hfake->Draw(); + + gTrends_layer[ilay * NTRENDSCLUSTER + id]->Draw(); + legstaves[ilay]->Draw("same"); + + ILOG(Debug, Devel) << " Saving canvas for layer " << ilay << " to CCDB " + << ENDM; + auto mo = std::make_shared(c[ilay * NTRENDSCLUSTER + id], mConfig.taskName, "o2::quality_control_modules::its::TrendingTaskITSFhr", + mConfig.detectorName, mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); + + delete c[ilay * NTRENDSCLUSTER + id]; + delete gTrends_layer[ilay * NTRENDSCLUSTER + id]; + delete hfake; + } + countplots = 0; + ilay++; + + } // end loop on plots + } + for (int idx = 0; idx < NLAYERS; idx++) { + delete legstaves[idx]; + } +} + +void TrendingTaskITSCluster::SetLegendStyle(TLegend* leg) +{ + leg->SetTextFont(42); + leg->SetLineColor(kWhite); + leg->SetFillColor(0); +} + +void TrendingTaskITSCluster::SetGraphStyle(TGraph* g, int col, int mkr) +{ + g->SetLineColor(col); + g->SetMarkerStyle(mkr); + g->SetMarkerColor(col); +} + +void TrendingTaskITSCluster::SetGraphNameAndAxes(TH1* g, std::string name, + std::string title, std::string xtitle, + std::string ytitle, double ymin, + double ymax, std::vector runlist) +{ + g->SetTitle(title.c_str()); + g->SetName(name.c_str()); + + g->GetXaxis()->SetTitle(xtitle.c_str()); + g->GetYaxis()->SetTitle(ytitle.c_str()); + g->GetYaxis()->SetRangeUser(ymin, ymax); + + if (xtitle.find("time") != std::string::npos) { + g->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + g->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of + // 2019-12-19. + g->GetXaxis()->SetTimeOffset(0.0); + g->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + if (xtitle.find("run") != std::string::npos) { + g->GetXaxis()->SetNdivisions(505); // It deals with highly congested dates labels + for (int ipoint = 0; ipoint < runlist.size(); ipoint++) { + g->GetXaxis()->SetBinLabel(g->GetXaxis()->FindBin(ipoint + 1.), runlist[ipoint].c_str()); + } + } +} + +void TrendingTaskITSCluster::PrepareLegend(TLegend* leg, int layer) +{ + for (int istv = 0; istv < nStaves[layer]; istv++) { + int colidx = istv > 41 ? istv - 42 : istv > 34 ? istv - 35 + : istv > 27 ? istv - 28 + : istv > 20 ? istv - 21 + : istv > 13 ? istv - 14 + : istv > 6 ? istv - 7 + : istv; + int mkridx = istv > 41 ? 6 : istv > 34 ? 5 + : istv > 27 ? 4 + : istv > 20 ? 3 + : istv > 13 ? 2 + : istv > 6 ? 1 + : 0; + TGraph* gr = new TGraph(); // dummy histo + SetGraphStyle(gr, col[colidx], mkr[mkridx]); + leg->AddEntry(gr, Form("%02d", istv), "pl"); + } +} diff --git a/Modules/ITS/src/TrendingTaskITSError.cxx b/Modules/ITS/src/TrendingTaskITSError.cxx new file mode 100644 index 0000000000..c5e07cef7c --- /dev/null +++ b/Modules/ITS/src/TrendingTaskITSError.cxx @@ -0,0 +1,287 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSError.cxx +/// \author Mariia Selina on the structure from Piotr Konopka +/// + +#include "ITS/TrendingTaskITSError.h" +#include "ITS/ReductorBinContent.h" + +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/ObjectMetadataKeys.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::repository; +using namespace o2::quality_control_modules::its; + +void TrendingTaskITSError::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingTaskConfigITS(getID(), config); +} + +void TrendingTaskITSError::initialize(Trigger, framework::ServiceRegistryRef) +{ + // Preparing data structure of TTree + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("Time", &mTime); + mTrend->Branch("Entries", &nEntries); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create(source.moduleName, source.reductorName)); + if (source.name == "ChipErrorPlots") + reductor->setParams(o2::itsmft::ChipStat::NErrorsDefined); + else + reductor->setParams(o2::itsmft::GBTLinkDecodingStat::NErrorsDefined); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TrendingTaskITSError::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + trendValues(t, qcdb); + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSError::finalize(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSError::storeTrend(repository::DatabaseInterface& qcdb) +{ + auto mo = std::make_shared(mTrend.get(), getName(), "o2::quality_control_modules::its::TrendingTaskITSError", + mConfig.detectorName, mMetaData.runNumber); // IVAN TEST + mo->setIsOwner(false); + qcdb.storeMO(mo); +} + +void TrendingTaskITSError::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + // We use current date and time. This for planned processing (not history). We + // still might need to use the objects + // timestamps in the end, but this would become ambiguous if there is more + // than one data source. + + mTime = TDatime().Convert(); + mMetaData.runNumber = t.activity.mId; + int count = 0; + + for (auto& dataSource : mConfig.dataSources) { + // todo: make it agnostic to MOs, QOs or other objects. Let the reductor + // cast to whatever it needs. + if (dataSource.type == "repository") { + auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name, t.timestamp, t.activity); + if (mo == nullptr) { + continue; + } + if (!count) { + std::map entryMetadata = mo->getMetadataMap(); // full list of metadata as a map + mMetaData.runNumber = std::stoi(entryMetadata[metadata_keys::runNumber]); // get and set run number + runlist.push_back(std::to_string(mMetaData.runNumber)); + nEntries = (Int_t)mTrend->GetEntriesFast() + 1; + } + TObject* obj = mo ? mo->getObject() : nullptr; + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (obj && reductor) { + reductor->update(obj); + } + } else { + ILOGE << "Unknown type of data source '" << dataSource.type << "'."; + } + count++; + } + mTrend->Fill(); +} + +void TrendingTaskITSError::storePlots(repository::DatabaseInterface& qcdb) +{ + // ILOG(Info, Support) << "Generating and storing " << mConfig.plots.size() << " plots." << ENDM; + int countplots = 0; + int countITSpart = 0; + bool isRun = false; + std::string name_Xaxis; + long int numberOfEntries = mTrend->GetEntriesFast(); + // Define output graphs + TString plots[2] = { "LinkErrorPlots", "ChipErrorPlots" }; + for (TString plotName : plots) { + + TMultiGraph* multi_trend; + countplots = 0; + + // Lane status summary plots + for (const auto& plot : mConfig.plots) { + + if (plot.varexp.find(plotName.Data()) == std::string::npos) + continue; + // Initialize MultiGraph and Legend + if (countplots == 0) { + multi_trend = new TMultiGraph(); + SetGraphName(multi_trend, plot.name, Form("Trending plot of %s", plotName.Data())); + + isRun = plot.selection.find("Entries") != std::string::npos ? true : false; + name_Xaxis = plot.selection.c_str(); + } + + // Retrieve data for trend plot + mTrend->Draw(Form("%s:%s", name_Xaxis.c_str(), plot.varexp.c_str()), "", "goff"); + TGraph* trend_plot = new TGraph(numberOfEntries, mTrend->GetV1(), mTrend->GetV2()); + SetGraphStyle(trend_plot, plot.name, Form("ID%d", countplots), colors[countplots], markers[countplots]); + multi_trend->Add((TGraph*)trend_plot->Clone(), plot.option.c_str()); + delete trend_plot; + countplots++; + } + + SetGraphAxes(multi_trend, Form("%s", isRun ? "Run" : "Time"), "Number of errors", !isRun); + + // Canvas settings + std::string name; + if (plotName == "LinkErrorPlots") + name = "GTBLinkDecodingErrorsSummary_Trends"; + else + name = "ChipDecodingErrorsSummary_Trends"; + TCanvas* canvas = new TCanvas(Form("%s", name.c_str()), Form("%s", name.c_str())); + SetCanvasSettings(canvas); + + // Plot as a function of run number requires a dummy TH1 histogram + TH1D* hDummy = nullptr; + if (isRun) { + hDummy = new TH1D("hDummy", Form("%s; %s; %s", multi_trend->GetTitle(), multi_trend->GetXaxis()->GetTitle(), multi_trend->GetYaxis()->GetTitle()), numberOfEntries, 0.5, numberOfEntries + 0.5); + SetHistoAxes(hDummy, runlist, multi_trend->GetYaxis()->GetXmin(), multi_trend->GetYaxis()->GetXmax()); + } + + // Draw plots + if (hDummy) + hDummy->Draw(); + multi_trend->Draw(Form("%s", hDummy ? "" : "a")); + + TLegend* legend = (TLegend*)canvas->BuildLegend(0.77, 0.12, 1, 1); + SetLegendStyle(legend, Form("%s_legend", plotName.Data()), isRun); + legend->Draw("SAME"); + + // Upload plots + auto mo = std::make_shared(canvas, mConfig.taskName, "o2::quality_control_modules::its::TrendingTaskITSError", mConfig.detectorName, mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); + + delete legend; + delete canvas; + delete multi_trend; // All included plots are deleted automatically + delete hDummy; + } +} +void TrendingTaskITSError::SetLegendStyle(TLegend* legend, const std::string& name, bool isRun) +{ + legend->SetTextFont(170); + legend->SetLineColor(kWhite); + legend->SetFillColor(0); + legend->SetName(Form("%s_legend", name.c_str())); + legend->SetFillStyle(0); + legend->SetBorderSize(0); + if (isRun) + legend->GetListOfPrimitives()->Remove((TLegendEntry*)legend->GetListOfPrimitives()->First()); // Delete entry from dummy histogram +} + +void TrendingTaskITSError::SetCanvasSettings(TCanvas* canvas) +{ + canvas->SetTickx(); + canvas->SetTicky(); + canvas->SetBottomMargin(0.18); + canvas->SetRightMargin(0.23); +} + +void TrendingTaskITSError::SetGraphStyle(TGraph* graph, const std::string& name, const std::string& title, int col, int mkr) +{ + graph->SetName(Form("%s", name.c_str())); + graph->SetTitle(Form("%s", title.c_str())); + graph->SetLineColor(col); + graph->SetMarkerStyle(mkr); + graph->SetMarkerColor(col); + graph->SetMarkerSize(1.0); + graph->SetLineWidth(1); +} + +void TrendingTaskITSError::SetGraphName(TMultiGraph* graph, const std::string& name, const std::string& title) +{ + graph->SetTitle(title.c_str()); + graph->SetName(name.c_str()); +} + +void TrendingTaskITSError::SetGraphAxes(TMultiGraph* graph, const std::string& xtitle, + const std::string& ytitle, bool isTime) +{ + graph->GetXaxis()->SetTitle(xtitle.c_str()); + graph->GetXaxis()->SetTitleSize(0.045); + + graph->GetYaxis()->SetTitle(ytitle.c_str()); + graph->GetYaxis()->SetTitleSize(0.045); + + graph->GetXaxis()->SetNdivisions(505); + graph->GetXaxis()->SetTimeOffset(0.0); + graph->GetXaxis()->SetLabelOffset(0.02); + graph->GetXaxis()->SetLabelSize(0.033); + graph->GetXaxis()->SetTitleOffset(2.3); + if (isTime) { + graph->GetXaxis()->SetTimeFormat("#splitline{%d.%m.%y}{%H:%M}"); + graph->GetXaxis()->SetTimeDisplay(1); + } +} + +void TrendingTaskITSError::SetHistoAxes(TH1* hist, const std::vector& runlist, + const double& Ymin, const double& Ymax) +{ + hist->SetStats(0); + hist->GetXaxis()->SetTitleSize(0.045); + hist->GetYaxis()->SetTitleSize(0.045); + hist->GetYaxis()->SetRangeUser(Ymin, Ymax); + + hist->GetXaxis()->SetLabelOffset(0.02); + hist->GetXaxis()->SetLabelSize(0.033); + hist->GetXaxis()->SetTitleOffset(2.3); + hist->GetXaxis()->SetNdivisions(-505); + + for (int iX = 0; iX < runlist.size(); iX++) { + hist->GetXaxis()->SetBinLabel(iX + 1, runlist[iX].c_str()); + } +} diff --git a/Modules/ITS/src/TrendingTaskITSFEE.cxx b/Modules/ITS/src/TrendingTaskITSFEE.cxx new file mode 100644 index 0000000000..a122f61514 --- /dev/null +++ b/Modules/ITS/src/TrendingTaskITSFEE.cxx @@ -0,0 +1,345 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSFEE.cxx +/// \author Artem Kotliarov on the structure from Piotr Konopka +/// + +#include "ITS/TrendingTaskITSFEE.h" +#include "ITS/ReductorBinContent.h" + +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/ObjectMetadataKeys.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::repository; +using namespace o2::quality_control_modules::its; + +void TrendingTaskITSFEE::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingTaskConfigITS(getID(), config); +} + +void TrendingTaskITSFEE::initialize(Trigger, framework::ServiceRegistryRef) +{ + // Preparing data structure of TTree + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("Time", &mTime); + mTrend->Branch("Entries", &nEntries); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create(source.moduleName, source.reductorName)); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TrendingTaskITSFEE::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + trendValues(t, qcdb); + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSFEE::finalize(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSFEE::storeTrend(repository::DatabaseInterface& qcdb) +{ + auto mo = std::make_shared(mTrend.get(), getName(), "o2::quality_control_modules::its::TrendingTaskITSFEE", + mConfig.detectorName, mMetaData.runNumber); // IVAN TEST + mo->setIsOwner(false); + qcdb.storeMO(mo); +} + +void TrendingTaskITSFEE::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + // We use current date and time. This for planned processing (not history). We + // still might need to use the objects + // timestamps in the end, but this would become ambiguous if there is more + // than one data source. + + mTime = TDatime().Convert(); + mMetaData.runNumber = t.activity.mId; + int count = 0; + + for (auto& dataSource : mConfig.dataSources) { + // todo: make it agnostic to MOs, QOs or other objects. Let the reductor + // cast to whatever it needs. + if (dataSource.type == "repository") { + auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name, t.timestamp, t.activity); + if (mo == nullptr) { + continue; + } + if (!count) { + std::map entryMetadata = mo->getMetadataMap(); // full list of metadata as a map + mMetaData.runNumber = std::stoi(entryMetadata[metadata_keys::runNumber]); // get and set run number + runlist.push_back(std::to_string(mMetaData.runNumber)); + nEntries = (Int_t)mTrend->GetEntriesFast() + 1; + } + TObject* obj = mo ? mo->getObject() : nullptr; + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (obj && reductor) { + reductor->update(obj); + } + } else { + ILOG(Debug, Devel) << "Unknown type of data source '" << dataSource.type << "'." << ENDM; + } + count++; + } + mTrend->Fill(); +} + +void TrendingTaskITSFEE::storePlots(repository::DatabaseInterface& qcdb) +{ + int countplots = 0; + int countITSpart = 0; + bool isRun = false; + std::string name_Xaxis; + long int numberOfEntries = mTrend->GetEntriesFast(); + + // Define output graphs + TMultiGraph* multi_trend; + + // Lane status summary plots + for (const auto& plot : mConfig.plots) { + + if (plot.varexp.find("binContent") == std::string::npos) + continue; + + // Checks neeeded to segregate plots from input list + if (countplots > nFlags - 1) { + countplots = 0; + countITSpart++; + } + + // Initialize MultiGraph and Legend + if (countplots == 0) { + multi_trend = new TMultiGraph(); + SetGraphName(multi_trend, plot.name, Form("Lane status summary trend %s", itsParts[countITSpart].c_str())); + + isRun = plot.selection.find("Entries") != std::string::npos ? true : false; + name_Xaxis = plot.selection.c_str(); + } + + // Retrieve data for trend plot + mTrend->Draw(Form("%s:%s", name_Xaxis.c_str(), plot.varexp.c_str()), "", "goff"); + TGraph* trend_plot = new TGraph(numberOfEntries, mTrend->GetV1(), mTrend->GetV2()); + SetGraphStyle(trend_plot, plot.name, trend_titles[countplots], colors[countplots], markers[countplots]); + multi_trend->Add((TGraph*)trend_plot->Clone(), plot.option.c_str()); + delete trend_plot; + + // Create and save plots + if (countplots == nFlags - 1) { + + SetGraphAxes(multi_trend, Form("%s", isRun ? "Run" : "Time"), "Fraction of lanes", !isRun); + + // Canvas settings + std::string name = "LaneStatusSummary_" + itsParts[countITSpart] + "_Trends"; + TCanvas* canvas = new TCanvas(Form("%s", name.c_str()), Form("%s", name.c_str())); + SetCanvasSettings(canvas); + + // Plot as a function of run number requires a dummy TH1 histogram + TH1D* hDummy = nullptr; + if (isRun) { + hDummy = new TH1D("hDummy", Form("%s; %s; %s", multi_trend->GetTitle(), multi_trend->GetXaxis()->GetTitle(), multi_trend->GetYaxis()->GetTitle()), numberOfEntries, 0.5, numberOfEntries + 0.5); + SetHistoAxes(hDummy, runlist, multi_trend->GetYaxis()->GetXmin(), multi_trend->GetYaxis()->GetXmax()); + } + + // Draw plots + if (hDummy) + hDummy->Draw(); + multi_trend->Draw(Form("%s", hDummy ? "" : "a")); + + TLegend* legend = (TLegend*)canvas->BuildLegend(0.93, 0.55, 1.0, 0.9); + SetLegendStyle(legend, Form("LaneStatusSummary_%s_legend", itsParts[countITSpart].c_str()), isRun); + legend->Draw("SAME"); + + // Upload plots + auto mo = std::make_shared(canvas, mConfig.taskName, "o2::quality_control_modules::its::TrendingTaskITSFEE", mConfig.detectorName, mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); + + delete legend; + delete canvas; + delete multi_trend; // All included plots are deleted automatically + delete hDummy; + } + countplots++; + } // end loop on plots + + //___________________________________________________________________________ + // Trigger Count trending plot + countplots = 0; + + for (const auto& plot : mConfig.plots) { + + if (plot.varexp.find("integral") == std::string::npos) + continue; + + // Initialize MultiGraph and Legend + if (countplots == 0) { + multi_trend = new TMultiGraph(); + SetGraphName(multi_trend, plot.name, "Trigger count trend"); + + isRun = plot.selection.find("Entries") != std::string::npos ? true : false; + name_Xaxis = plot.selection.c_str(); + } + + // Retrieve data for trend plot + mTrend->Draw(Form("%s:%s", name_Xaxis.c_str(), plot.varexp.c_str()), "", "goff"); + TGraph* trend_plot = new TGraph(numberOfEntries, mTrend->GetV1(), mTrend->GetV2()); + SetGraphStyle(trend_plot, plot.name, mTriggerType[countplots], colors[countplots], markers[countplots]); + multi_trend->Add((TGraph*)trend_plot->Clone(), plot.option.c_str()); + delete trend_plot; + + if (countplots == nTriggers - 1) { + + SetGraphAxes(multi_trend, Form("%s", isRun ? "Run" : "Time"), "Sum over all FEE", !isRun); + + // Canvas settings + TCanvas* canvas = new TCanvas("Trigger_count_trend", "Trigger_count_trend"); + SetCanvasSettings(canvas); + + // Plot as a function of run number requires a dummy TH1 histogram + TH1I* hDummy = nullptr; + if (isRun) { + hDummy = new TH1I("hDummy", Form("%s; %s; %s", multi_trend->GetTitle(), multi_trend->GetXaxis()->GetTitle(), multi_trend->GetYaxis()->GetTitle()), numberOfEntries, 0.5, numberOfEntries + 0.5); + SetHistoAxes(hDummy, runlist, multi_trend->GetYaxis()->GetXmin(), multi_trend->GetYaxis()->GetXmax()); + } + + // Draw plots + if (hDummy) + hDummy->Draw(); + multi_trend->Draw(Form("%s", hDummy ? "" : "a")); + + TLegend* legend = (TLegend*)canvas->BuildLegend(0.93, 0.15, 1.0, 0.9); + SetLegendStyle(legend, "Trigger_count_trend", isRun); + legend->Draw("SAME"); + + // Upload plots + auto mo = std::make_shared(canvas, mConfig.taskName, "o2::quality_control_modules::its::TrendingTaskITSFEE", mConfig.detectorName, mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); + + delete legend; + delete canvas; + delete multi_trend; // All included plots are deleted automatically + delete hDummy; + } + countplots++; + } // end loop on plots +} + +void TrendingTaskITSFEE::SetLegendStyle(TLegend* legend, const std::string& name, bool isRun) +{ + legend->SetTextFont(42); + legend->SetLineColor(kWhite); + legend->SetFillColor(0); + legend->SetName(Form("%s_legend", name.c_str())); + legend->SetFillStyle(0); + legend->SetBorderSize(0); + if (isRun) + legend->GetListOfPrimitives()->Remove((TLegendEntry*)legend->GetListOfPrimitives()->First()); // Delete entry from dummy histogram +} + +void TrendingTaskITSFEE::SetCanvasSettings(TCanvas* canvas) +{ + canvas->SetTickx(); + canvas->SetTicky(); + canvas->SetBottomMargin(0.18); + canvas->SetRightMargin(0.07); +} + +void TrendingTaskITSFEE::SetGraphStyle(TGraph* graph, const std::string& name, const std::string& title, int col, int mkr) +{ + graph->SetName(Form("%s", name.c_str())); + graph->SetTitle(Form("%s", title.c_str())); + graph->SetLineColor(col); + graph->SetMarkerStyle(mkr); + graph->SetMarkerColor(col); + graph->SetMarkerSize(1.0); + graph->SetLineWidth(1); +} + +void TrendingTaskITSFEE::SetGraphName(TMultiGraph* graph, const std::string& name, const std::string& title) +{ + graph->SetTitle(title.c_str()); + graph->SetName(name.c_str()); +} + +void TrendingTaskITSFEE::SetGraphAxes(TMultiGraph* graph, const std::string& xtitle, + const std::string& ytitle, bool isTime) +{ + graph->GetXaxis()->SetTitle(xtitle.c_str()); + graph->GetXaxis()->SetTitleSize(0.045); + + graph->GetYaxis()->SetTitle(ytitle.c_str()); + graph->GetYaxis()->SetTitleSize(0.045); + + graph->GetXaxis()->SetNdivisions(505); + graph->GetXaxis()->SetTimeOffset(0.0); + graph->GetXaxis()->SetLabelOffset(0.02); + graph->GetXaxis()->SetLabelSize(0.033); + graph->GetXaxis()->SetTitleOffset(2.3); + if (isTime) { + graph->GetXaxis()->SetTimeFormat("#splitline{%d.%m.%y}{%H:%M}"); + graph->GetXaxis()->SetTimeDisplay(1); + } +} + +void TrendingTaskITSFEE::SetHistoAxes(TH1* hist, const std::vector& runlist, + const double& Ymin, const double& Ymax) +{ + hist->SetStats(0); + hist->GetXaxis()->SetTitleSize(0.045); + hist->GetYaxis()->SetTitleSize(0.045); + hist->GetYaxis()->SetRangeUser(Ymin, Ymax); + + hist->GetXaxis()->SetLabelOffset(0.02); + hist->GetXaxis()->SetLabelSize(0.033); + hist->GetXaxis()->SetTitleOffset(2.3); + hist->GetXaxis()->SetNdivisions(-505); + + for (int iX = 0; iX < runlist.size(); iX++) { + hist->GetXaxis()->SetBinLabel(iX + 1, runlist[iX].c_str()); + } +} diff --git a/Modules/ITS/src/TrendingTaskITSFhr.cxx b/Modules/ITS/src/TrendingTaskITSFhr.cxx new file mode 100644 index 0000000000..7d21aea2d1 --- /dev/null +++ b/Modules/ITS/src/TrendingTaskITSFhr.cxx @@ -0,0 +1,329 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSFhr.cxx +/// \author Ivan Ravasenga on the structure from Piotr Konopka +/// + +#include "ITS/TrendingTaskITSFhr.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/ActivityHelpers.h" +#include "ITS/TH2XlineReductor.h" +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::repository; +using namespace o2::quality_control_modules::its; + +void TrendingTaskITSFhr::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingTaskConfigITS(getID(), config); +} + +void TrendingTaskITSFhr::initialize(Trigger, framework::ServiceRegistryRef) +{ + // Preparing data structure of TTree + mTrend = std::make_unique(); // todo: retrieve last TTree, so we + // continue trending. maybe do it + // optionally? + mTrend->SetName(PostProcessingInterface::getName().c_str()); + // mTrend->Branch("meta", &mMetaData, "runNumber/I"); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("ntreeentries", &ntreeentries); + mTrend->Branch("time", &mTime); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create( + source.moduleName, source.reductorName)); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), + reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TrendingTaskITSFhr::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + trendValues(t, qcdb); + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSFhr::finalize(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSFhr::storeTrend(repository::DatabaseInterface& qcdb) +{ + ILOG(Debug, Devel) << "Storing the trend, entries: " << mTrend->GetEntries() << ENDM; + + auto mo = std::make_shared(mTrend.get(), getName(), "o2::quality_control_modules::its::TrendingTaskITSFhr", + mConfig.detectorName, mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); +} + +void TrendingTaskITSFhr::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + // We use current date and time. This for planned processing (not history). We + // still might need to use the objects + // timestamps in the end, but this would become ambiguous if there is more + // than one data source. + mTime = TDatime().Convert(); + mMetaData.runNumber = t.activity.mId; + int count = 0; + for (auto& dataSource : mConfig.dataSources) { + + // The FHR Task generates objects specific to particular detector layers. + // Since detector layers are assigned to different subsets of FLPs, the merged objects + // do not have common validity, as FLPs do not have QC cycles ideally in sync. + // Thus we have look for the objects a bit behind and forward from the trigger timestamp as well. + auto timestamp = t.timestamp; + const auto objFullPath = t.activity.mProvenance + "/" + dataSource.path; + const auto filterMetadata = activity_helpers::asDatabaseMetadata(t.activity, false); + const auto objectValidity = qcdb.getLatestObjectValidity(objFullPath, filterMetadata); + const auto maxShiftMs = mConfig.maxObjectTimeShiftMs; + if (objectValidity.isValid() && t.timestamp >= objectValidity.getMin() - maxShiftMs && t.timestamp <= objectValidity.getMax() + maxShiftMs) { + timestamp = objectValidity.getMax() - 1; + } else { + ILOG(Warning, Devel) << "Could not find an object '" << objFullPath << "' in the proximity of timestamp " + << t.timestamp << " with max shift of " << mConfig.maxObjectTimeShiftMs << "ms" << ENDM; + continue; + } + + if (dataSource.type == "repository") { + + // auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name); + auto mo = qcdb.retrieveMO(dataSource.path, "", timestamp, t.activity); + if (mo == nullptr) { + continue; + } + if (!count) { + std::map entryMetadata = mo->getMetadataMap(); // full list of metadata as a map + mMetaData.runNumber = std::stoi(entryMetadata[metadata_keys::runNumber]); // get and set run number + ntreeentries = (Int_t)mTrend->GetEntries() + 1; + runlist.push_back(std::to_string(mMetaData.runNumber)); + } + TObject* obj = mo ? mo->getObject() : nullptr; + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (obj && reductor) { + reductor->update(obj); + } + } else if (dataSource.type == "repository-quality") { + auto qo = qcdb.retrieveQO(dataSource.path + "/" + dataSource.name, timestamp); + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (qo && reductor) { + reductor->update(qo.get()); + } + } else { + ILOGE << "Unknown type of data source '" << dataSource.type << "'."; + } + count++; + } + mTrend->Fill(); +} + +void TrendingTaskITSFhr::storePlots(repository::DatabaseInterface& qcdb) +{ + // + // Create and save trends for each stave + // + int countplots = 0; + int ilay = 0; + double ymin[NTRENDSFHR] = { 1e-15, 1e-1, -.5, 1e-9 }; + double ymaxIB[NTRENDSFHR] = { 1e-3, 1e-5, 9.5, 1 }; + double ymaxOB[NTRENDSFHR] = { 1e-3, 1e-5, 30.5, 1 }; + + // There was previously loop on plots for each stave + // Removed to reduce number of saved histograms + // + // Create canvas with multiple trends - average threshold - 1 canvas per layer + // + ilay = 0; + countplots = 0; + TCanvas* c[NLAYERS * NTRENDSFHR]; + TMultiGraph* gTrendsAll[NLAYERS * NTRENDSFHR]; + TLegend* legstaves[NLAYERS]; + for (int idx = 0; idx < NLAYERS * NTRENDSFHR; idx++) { // define canvases + if (idx < 13) { + c[idx] = new TCanvas( + Form("fhr_%s_trends_L%d", trendnamesIB[idx % NTRENDSFHR].c_str(), + idx / NTRENDSFHR), + Form("fhr_%s_trends_L%d", trendnamesIB[idx % NTRENDSFHR].c_str(), + idx / NTRENDSFHR)); + + gTrendsAll[idx] = new TMultiGraph( + Form("fhr_%s_trends_L%d_multigraph", trendnamesIB[idx % NTRENDSFHR].c_str(), + idx / NTRENDSFHR), + Form("fhr_%s_trends_L%d_multigraph", trendnamesIB[idx % NTRENDSFHR].c_str(), + idx / NTRENDSFHR)); + } else { + c[idx] = new TCanvas( + Form("fhr_%s_trends_L%d", trendnamesOB[idx % NTRENDSFHR].c_str(), + idx / NTRENDSFHR), + Form("fhr_%s_trends_L%d", trendnamesOB[idx % NTRENDSFHR].c_str(), + idx / NTRENDSFHR)); + + gTrendsAll[idx] = new TMultiGraph( + Form("fhr_%s_trends_L%d_multigraph", trendnamesOB[idx % NTRENDSFHR].c_str(), + idx / NTRENDSFHR), + Form("fhr_%s_trends_L%d_multigraph", trendnamesOB[idx % NTRENDSFHR].c_str(), + idx / NTRENDSFHR)); + } + } + + for (int ilay = 0; ilay < NLAYERS; ilay++) { // define legends + legstaves[ilay] = new TLegend(0.91, 0.1, 0.98, 0.9); + legstaves[ilay]->SetName(Form("legstaves_L%d", ilay)); + SetLegendStyle(legstaves[ilay]); + PrepareLegend(legstaves[ilay], ilay); + } + ilay = 0; + + for (const auto& plot : mConfig.plots) { + + int colidx = countplots % 7; + int mkridx = countplots / 7; + int index = 0; + if (plot.name.find("occ") != std::string::npos) + index = 3; + else if (plot.name.find("chips") != std::string::npos || plot.name.find("lanes") != std::string::npos) + index = 2; + else if (plot.name.find("stddev") != std::string::npos) + index = 1; + else + index = 0; + + bool isrun = plot.varexp.find("ntreeentries") != std::string::npos ? true : false; // vs run or vs time + long int n = mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), "goff"); + // post processing plot + ILOG(Debug, Devel) << " Drawing " << plot.name << ENDM; + TGraph* g = new TGraph(n, mTrend->GetV2(), mTrend->GetV1()); + SetGraphStyle(g, col[colidx], mkr[mkridx]); + gTrendsAll[ilay * NTRENDSFHR + index]->Add((TGraph*)g->Clone()); + delete g; + + if (plot.name.find("occ") != std::string::npos) + countplots++; + + if (countplots > nStaves[ilay] - 1) { + for (int id = 0; id < 4; id++) { + c[ilay * NTRENDSFHR + id]->cd(); + c[ilay * NTRENDSFHR + id]->SetTickx(); + c[ilay * NTRENDSFHR + id]->SetTicky(); + + int npoints = (int)runlist.size(); + TH1F* hfake = new TH1F("hfake", "hfake", npoints, 0.5, (double)npoints + 0.5); + + Double_t max, min; + max = gTrendsAll[ilay * NTRENDSFHR + id]->GetYaxis()->GetXmax(); + if (id == 2) + min = -0.5; + else + min = gTrendsAll[ilay * NTRENDSFHR + id]->GetYaxis()->GetXmin(); + + SetGraphNameAndAxes(hfake, + Form("L%d - %s trends", ilay, trendtitlesOB[id].c_str()), + isrun ? "run" : "time", ytitlesOB[id], min, max, runlist); + + hfake->Draw(); + gTrendsAll[ilay * NTRENDSFHR + id]->Draw(); + legstaves[ilay]->Draw("same"); + + ILOG(Debug, Devel) << " Saving canvas for layer " << ilay << " to CCDB " + << ENDM; + auto mo = std::make_shared(c[ilay * NTRENDSFHR + id], mConfig.taskName, "o2::quality_control_modules::its::TrendingTaskITSFhr", + mConfig.detectorName, mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); + delete c[ilay * NTRENDSFHR + id]; + delete gTrendsAll[ilay * NTRENDSFHR + id]; + delete hfake; + } + countplots = 0; + ilay++; + } + } // end loop on plots + + for (int idx = 0; idx < NLAYERS; idx++) { + delete legstaves[idx]; + } +} + +void TrendingTaskITSFhr::SetLegendStyle(TLegend* leg) +{ + leg->SetTextFont(42); + leg->SetLineColor(kWhite); + leg->SetFillColor(0); +} + +void TrendingTaskITSFhr::SetGraphStyle(TGraph* g, int col, int mkr) +{ + g->SetLineColor(col); + g->SetMarkerStyle(mkr); + g->SetMarkerColor(col); +} + +void TrendingTaskITSFhr::SetGraphNameAndAxes(TH1* g, + std::string title, std::string xtitle, + std::string ytitle, double ymin, + double ymax, std::vector runlist) +{ + g->SetStats(0); + g->SetTitle(title.c_str()); + g->GetXaxis()->SetTitle(xtitle.c_str()); + g->GetYaxis()->SetTitle(ytitle.c_str()); + g->GetYaxis()->SetRangeUser(ymin, ymax); + + if (xtitle.find("time") != std::string::npos) { + g->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + g->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of + // 2019-12-19. + g->GetXaxis()->SetTimeOffset(0.0); + g->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + if (xtitle.find("run") != std::string::npos) { + g->GetXaxis()->SetNdivisions(505); // It deals with highly congested dates labels + for (int ipoint = 0; ipoint < runlist.size(); ipoint++) { + g->GetXaxis()->SetBinLabel(g->GetXaxis()->FindBin(ipoint + 1.), runlist[ipoint].c_str()); + } + } +} + +void TrendingTaskITSFhr::PrepareLegend(TLegend* leg, int layer) +{ + for (int istv = 0; istv < nStaves[layer]; istv++) { + int colidx = istv % 7; + int mkridx = istv / 7; + TGraph* gr = new TGraph(); // dummy histo + SetGraphStyle(gr, col[colidx], mkr[mkridx]); + leg->AddEntry(gr, Form("%02d", istv), "pl"); + } +} diff --git a/Modules/ITS/src/TrendingTaskITSThr.cxx b/Modules/ITS/src/TrendingTaskITSThr.cxx new file mode 100644 index 0000000000..f4132d67f2 --- /dev/null +++ b/Modules/ITS/src/TrendingTaskITSThr.cxx @@ -0,0 +1,296 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSThr.cxx +/// \author Ivan Ravasenga on the structure from Piotr Konopka +/// + +#include "ITS/TrendingTaskITSThr.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::repository; + +void TrendingTaskITSThr::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingTaskConfigITS(getID(), config); +} + +void TrendingTaskITSThr::initialize(Trigger, framework::ServiceRegistryRef) +{ + // Preparing data structure of TTree + mTrend = std::make_unique(); // todo: retrieve last TTree, so we + // continue trending. maybe do it + // optionally? + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("ntreeentries", &ntreeentries); + mTrend->Branch("time", &mTime); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create( + source.moduleName, source.reductorName)); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), + reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TrendingTaskITSThr::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + trendValues(t, qcdb); + + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSThr::finalize(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSThr::storeTrend(repository::DatabaseInterface& qcdb) +{ + ILOG(Debug, Devel) << "Storing the trend, entries: " << mTrend->GetEntries() << ENDM; + + auto mo = std::make_shared(mTrend.get(), getName(), + mConfig.className, + mConfig.detectorName, mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); +} + +void TrendingTaskITSThr::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + // We use current date and time. This for planned processing (not history). We + // still might need to use the objects + // timestamps in the end, but this would become ambiguous if there is more + // than one data source. + mTime = TDatime().Convert(); + mMetaData.runNumber = t.activity.mId; + int count = 0; + + for (auto& dataSource : mConfig.dataSources) { + + // todo: make it agnostic to MOs, QOs or other objects. Let the reductor + // cast to whatever it needs. + if (dataSource.type == "repository") { + // auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name); + auto mo = qcdb.retrieveMO(dataSource.path, "", t.timestamp, t.activity); + if (mo == nullptr) { + continue; + } + if (!count) { + std::map entryMetadata = mo->getMetadataMap(); // full list of metadata as a map + mMetaData.runNumber = std::stoi(entryMetadata[metadata_keys::runNumber]); // get and set run number + ntreeentries = (Int_t)mTrend->GetEntries() + 1; + runlist.push_back(std::to_string(mMetaData.runNumber)); + } + TObject* obj = mo ? mo->getObject() : nullptr; + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (obj && reductor) { + reductor->update(obj); + } + } else if (dataSource.type == "repository-quality") { + auto qo = qcdb.retrieveQO(dataSource.path + "/" + dataSource.name); + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (qo && reductor) { + reductor->update(qo.get()); + } + } else { + ILOGE << "Unknown type of data source '" << dataSource.type << "'."; + } + count++; + } + mTrend->Fill(); +} +void TrendingTaskITSThr::storePlots(repository::DatabaseInterface& qcdb) +{ + // + // Create canvas with multiple trends - average threshold - 1 canvas per layer + // + int ilay = 0; + int countplots = 0; + TCanvas* c[NLAYERS * NTRENDSTHR]; + TMultiGraph* gTrends_all[NLAYERS * NTRENDSTHR]; + + TLegend* legstaves[NLAYERS]; + for (int idx = 0; idx < NLAYERS * NTRENDSTHR; idx++) { // define canvases + c[idx] = new TCanvas( + Form("threshold_%s_trends_L%d", trendnames[idx % NTRENDSTHR].c_str(), + idx / NTRENDSTHR), + Form("threshold_%s_trends_L%d", trendnames[idx % NTRENDSTHR].c_str(), + idx / NTRENDSTHR)); + + gTrends_all[idx] = new TMultiGraph( + Form("threshold_%s_trends_L%d", trendnames[idx % NTRENDSTHR].c_str(), + idx / NTRENDSTHR), + Form("threshold_%s_trends_L%d", trendnames[idx % NTRENDSTHR].c_str(), + idx / NTRENDSTHR)); + } + + for (int ilay = 0; ilay < NLAYERS; ilay++) { // define legends + legstaves[ilay] = new TLegend(0.91, 0.1, 0.98, 0.9); + legstaves[ilay]->SetName(Form("legstaves_L%d", ilay)); + SetLegendStyle(legstaves[ilay]); + PrepareLegend(legstaves[ilay], ilay); + } + ilay = 0; + for (const auto& plot : mConfig.plots) { + int colidx = countplots > 41 ? countplots - 42 : countplots > 34 ? countplots - 35 + : countplots > 27 ? countplots - 28 + : countplots > 20 ? countplots - 21 + : countplots > 13 ? countplots - 14 + : countplots > 6 ? countplots - 7 + : countplots; + int mkridx = countplots > 41 ? 6 : countplots > 34 ? 5 + : countplots > 27 ? 4 + : countplots > 20 ? 3 + : countplots > 13 ? 2 + : countplots > 6 ? 1 + : 0; + int add = (plot.name.find("rms") != std::string::npos) + ? 1 + : plot.name.find("Active") != std::string::npos ? 2 + : 0; + bool isrun = 1; // time no longer needed + long int n = mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), "goff"); + TGraph* g = new TGraph(n, mTrend->GetV2(), mTrend->GetV1()); + SetGraphStyle(g, col[colidx], mkr[mkridx]); + gTrends_all[ilay * NTRENDSTHR + add]->Add((TGraph*)g->Clone()); + delete g; + if (plot.name.find("Active") != std::string::npos) { + countplots++; + } + + if (countplots > nStaves[ilay] - 1) { + for (int id = 0; id < NTRENDSTHR; id++) { + c[ilay * NTRENDSTHR + id]->cd(); + c[ilay * NTRENDSTHR + id]->SetTickx(); + c[ilay * NTRENDSTHR + id]->SetTicky(); + if (id == 2) + c[ilay * NTRENDSTHR + id]->SetLogy(); + double ymin = 0.; + double ymax = id == 1 + ? 20. + : id == 2 ? 100 + : 250.; + int npoints = (int)runlist.size(); + TH1F* hfake = new TH1F("hfake", "hfake", npoints, 0.5, (double)npoints + 0.5); + hfake->SetStats(0); + SetGraphNameAndAxes(hfake, "hfake", Form("L%d - %s trends", ilay, trendtitles[id].c_str()), isrun ? "run" : "time", ytitles[id], ymin, ymax, runlist); + hfake->Draw(); + gTrends_all[ilay * NTRENDSTHR + id]->Draw(); + legstaves[ilay]->Draw(); + ILOG(Debug, Devel) << " Saving canvas for layer " << ilay << " to CCDB " + << ENDM; + auto mo = std::make_shared(c[ilay * NTRENDSTHR + id], mConfig.taskName, "o2::quality_control_modules::its::TrendingTaskITSThr", + mConfig.detectorName); + mo->setIsOwner(false); + qcdb.storeMO(mo); + + delete hfake; + delete gTrends_all[ilay * NTRENDSTHR + id]; + delete c[ilay * NTRENDSTHR + id]; + } + + countplots = 0; + ilay++; + } + } // end loop on plots + + for (int ilayer = 0; ilayer < NLAYERS; ilayer++) { + delete legstaves[ilayer]; + } +} + +void TrendingTaskITSThr::SetLegendStyle(TLegend* leg) +{ + leg->SetTextFont(42); + leg->SetLineColor(kWhite); + leg->SetFillColor(0); +} + +void TrendingTaskITSThr::SetGraphStyle(TGraph* g, int col, int mkr) +{ + g->SetLineColor(col); + g->SetMarkerStyle(mkr); + g->SetMarkerColor(col); +} + +void TrendingTaskITSThr::SetGraphNameAndAxes(TH1* g, std::string name, + std::string title, std::string xtitle, + std::string ytitle, double ymin, + double ymax, std::vector runlist) +{ + g->SetTitle(title.c_str()); + g->SetName(name.c_str()); + + g->GetXaxis()->SetTitle(xtitle.c_str()); + g->GetYaxis()->SetTitle(ytitle.c_str()); + g->GetYaxis()->SetRangeUser(ymin, ymax); + + if (xtitle.find("time") != std::string::npos) { + g->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + g->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of + // 2019-12-19. + g->GetXaxis()->SetTimeOffset(0.0); + g->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + if (xtitle.find("run") != std::string::npos) { + g->GetXaxis()->SetNdivisions(505); // It deals with highly congested dates labels + for (int ipoint = 0; ipoint < (int)runlist.size(); ipoint++) { + g->GetXaxis()->SetBinLabel(g->GetXaxis()->FindBin(ipoint + 1.), runlist[ipoint].c_str()); + } + } +} + +void TrendingTaskITSThr::PrepareLegend(TLegend* leg, int layer) +{ + for (int istv = 0; istv < nStaves[layer]; istv++) { + int colidx = istv > 41 ? istv - 42 : istv > 34 ? istv - 35 + : istv > 27 ? istv - 28 + : istv > 20 ? istv - 21 + : istv > 13 ? istv - 14 + : istv > 6 ? istv - 7 + : istv; + int mkridx = istv > 41 ? 6 : istv > 34 ? 5 + : istv > 27 ? 4 + : istv > 20 ? 3 + : istv > 13 ? 2 + : istv > 6 ? 1 + : 0; + TGraph* gr = new TGraph(); // dummy histo + SetGraphStyle(gr, col[colidx], mkr[mkridx]); + leg->AddEntry(gr, Form("%02d", istv), "pl"); + } +} diff --git a/Modules/ITS/src/TrendingTaskITSTracks.cxx b/Modules/ITS/src/TrendingTaskITSTracks.cxx new file mode 100644 index 0000000000..b7b7775310 --- /dev/null +++ b/Modules/ITS/src/TrendingTaskITSTracks.cxx @@ -0,0 +1,287 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskITSTracks.cxx +/// \author Ivan Ravasenga on the structure from Piotr Konopka +/// + +#include "ITS/TrendingTaskITSTracks.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::repository; + +void TrendingTaskITSTracks::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingTaskConfigITS(getID(), config); +} + +void TrendingTaskITSTracks::initialize(Trigger, framework::ServiceRegistryRef) +{ + // Preparing data structure of TTree + mTrend = std::make_unique(); // todo: retrieve last TTree, so we + // continue trending. maybe do it + // optionally? + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("ntreeentries", &ntreeentries); + mTrend->Branch("time", &mTime); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create( + source.moduleName, source.reductorName)); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), + reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TrendingTaskITSTracks::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + trendValues(t, qcdb); + + storePlots(qcdb); + storeTrend(qcdb); +} + +void TrendingTaskITSTracks::finalize(Trigger t, framework::ServiceRegistryRef services) +{ +} + +void TrendingTaskITSTracks::storeTrend(repository::DatabaseInterface& qcdb) +{ + ILOG(Debug, Devel) << "Storing the trend, entries: " << mTrend->GetEntries() << ENDM; + + auto mo = std::make_shared(mTrend.get(), getName(), + mConfig.className, + mConfig.detectorName, + mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); +} + +void TrendingTaskITSTracks::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + // We use current date and time. This for planned processing (not history). We + // still might need to use the objects + // timestamps in the end, but this would become ambiguous if there is more + // than one data source. + mTime = TDatime().Convert(); + mMetaData.runNumber = t.activity.mId; + int count = 0; + + for (auto& dataSource : mConfig.dataSources) { + // std::cout<<"TrendingTaskITSTracks dataSource type "< entryMetadata = mo->getMetadataMap(); // full list of metadata as a map + mMetaData.runNumber = std::stoi(entryMetadata[metadata_keys::runNumber]); // get and set run number + ntreeentries = (Int_t)mTrend->GetEntries() + 1; + runlist.push_back(std::to_string(mMetaData.runNumber)); + } + TObject* obj = mo ? mo->getObject() : nullptr; + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (obj && reductor) { + reductor->update(obj); + } + } else if (dataSource.type == "repository-quality") { + auto qo = qcdb.retrieveQO(dataSource.path + "/" + dataSource.name); + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (qo && reductor) { + reductor->update(qo.get()); + } + } else { + ILOGE << "Unknown type of data source '" << dataSource.type << "'."; + } + count++; + } + mTrend->Fill(); +} + +void TrendingTaskITSTracks::storePlots(repository::DatabaseInterface& qcdb) +{ + // + // Create and save trends for each stave + // + + if (runlist.size() == 0) { + ILOG(Info, Support) << "There are no plots to store, skipping" << ENDM; + return; + } + + ILOG(Info, Support) << "Generating and storing " << mConfig.plots.size() << " plots." + << ENDM; + + int countplots = 0; + int ilay = 0; + for (const auto& plot : mConfig.plots) { + + int class1 = 0; + double ymin = 0.; + double ymax = 1.; + if (plot.name.find("mean") != std::string::npos) { + if (plot.name.find("NCluster") != std::string::npos) { + class1 = 0; + ymin = 3; + ymax = 8.; + } else if (plot.name.find("EtaDistribution") != std::string::npos) { + class1 = 0; + ymin = -1.5; + ymax = 1.5; + } else if (plot.name.find("PhiDistribution") != std::string::npos) { + class1 = 0; + ymin = 0.; + ymax = TMath::TwoPi(); + } else if (plot.name.find("VertexZ") != std::string::npos) { + class1 = 0; + ymin = -5.; + ymax = 5.; + } else if (plot.name.find("NVertexContributors") != std::string::npos) { + class1 = 0; + ymin = 0.; + ymax = 50.; + } else if (plot.name.find("AssociatedClusterFraction") != std::string::npos) { + class1 = 0; + ymin = 0.; + ymax = 1.; + } else if (plot.name.find("Ntracks") != std::string::npos) { + class1 = 0; + ymin = 0.; + ymax = 20.; + } else if (plot.name.find("VertexX") != std::string::npos) { + class1 = 0; + ymin = -0.1; + ymax = 0.1; + } else if (plot.name.find("VertexY") != std::string::npos) { + class1 = 2; + ymin = -0.1; + ymax = 0.1; + } + + } else if (plot.name.find("stddev") != std::string::npos) { + if (plot.name.find("NCluster") != std::string::npos) { + class1 = 1; + } else if (plot.name.find("EtaDistribution") != std::string::npos) { + class1 = 1; + } else if (plot.name.find("PhiDistribution") != std::string::npos) { + class1 = 1; + } else if (plot.name.find("VertexZ") != std::string::npos) { + class1 = 1; + } else if (plot.name.find("NVertexContributors") != std::string::npos) { + class1 = 1; + } else if (plot.name.find("AssociatedClusterFraction") != std::string::npos) { + class1 = 1; + } else if (plot.name.find("Ntracks") != std::string::npos) { + class1 = 1; + } else if (plot.name.find("VertexX") != std::string::npos) { + class1 = 1; + } else if (plot.name.find("VertexY") != std::string::npos) { + class1 = 3; + } + ymin = 0.; + ymax = 10.; + } + + bool isrun = plot.varexp.find("ntreeentries") != std::string::npos ? true : false; // vs run or vs time + long int n = mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), + "goff"); + + double* x = mTrend->GetV2(); + double* y = mTrend->GetV1(); + + // post processing plot + TGraph* g = new TGraph(n, x, y); + + SetGraphStyle(g, col[(int)class1 % 2], mkr[(int)class1 % 2]); + + SetGraphNameAndAxes(g, plot.name, plot.title, isrun ? "run" : "time", plot.title, ymin, + ymax, runlist); + ILOG(Debug, Support) << " Saving " << plot.name << " to CCDB " << ENDM; + auto mo = std::make_shared(g, mConfig.taskName, "o2::quality_control_modules::its::TrendingTaskITSTracks", + mConfig.detectorName, mMetaData.runNumber); + mo->setIsOwner(false); + qcdb.storeMO(mo); + + // It should delete everything inside. Confirmed by trying to delete histo + // after and getting a segfault. + delete g; + } // end loop on plots +} + +void TrendingTaskITSTracks::SetLegendStyle(TLegend* leg) +{ + leg->SetTextFont(42); + leg->SetLineColor(kWhite); + leg->SetFillColor(0); +} + +void TrendingTaskITSTracks::SetGraphStyle(TGraph* g, int col, int mkr) +{ + g->SetLineColor(col); + g->SetMarkerStyle(mkr); + g->SetMarkerColor(col); +} + +void TrendingTaskITSTracks::SetGraphNameAndAxes(TGraph* g, std::string name, + std::string title, std::string xtitle, + std::string ytitle, double ymin, + double ymax, std::vector runlist) +{ + g->SetTitle(title.c_str()); + g->SetName(name.c_str()); + + g->GetXaxis()->SetTitle(xtitle.c_str()); + g->GetYaxis()->SetTitle(ytitle.c_str()); + g->GetYaxis()->SetRangeUser(ymin, ymax); + + if (xtitle.find("time") != std::string::npos) { + g->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + g->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of + // 2019-12-19. + g->GetXaxis()->SetTimeOffset(0.0); + g->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + if (xtitle.find("run") != std::string::npos) { + g->GetXaxis()->SetNdivisions(505); // It deals with highly congested dates labels + for (int ipoint = 0; ipoint < g->GetN(); ipoint++) { + g->GetXaxis()->SetBinLabel(g->GetXaxis()->FindBin(ipoint + 1.), runlist[ipoint].c_str()); + g->GetXaxis()->LabelsOption("v"); + } + } +} + +void TrendingTaskITSTracks::PrepareLegend(TLegend* leg, int layer) +{ +} diff --git a/Modules/ITS/src/runITS.cxx b/Modules/ITS/src/runITS.cxx new file mode 100644 index 0000000000..77d99eaea0 --- /dev/null +++ b/Modules/ITS/src/runITS.cxx @@ -0,0 +1,109 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runITS.cxx +/// \author Zhaozhong Shi +/// \author Markus Keil +// \author Mario Sitta +// \author Jian Liu +// \author Li'Ang Zhang +/// \brief This is an executable to run the ITS QC Task. +/// + +#include +#include "QualityControl/InfrastructureGenerator.h" + +using namespace o2; +using namespace o2::framework; +using namespace o2::utilities; + +void customize(std::vector& policies) +{ + DataSampling::CustomizeInfrastructure(policies); + quality_control::customizeInfrastructure(policies); +} + +void customize(std::vector& policies) +{ + DataSampling::CustomizeInfrastructure(policies); +} + +void customize(std::vector& workflowOptions) +{ + workflowOptions.push_back( + ConfigParamSpec{ "config-path", VariantType::String, "", { "Path to the config file. Overwrite the default paths. Do not use with no-data-sampling." } }); + workflowOptions.push_back( + ConfigParamSpec{ "no-data-sampling", VariantType::Bool, false, { "Skips data sampling, connects directly the task to the producer." } }); +} + +#include +#include +#include +#include + +#include + +#include +#include +#include "QualityControl/Check.h" +#include "QualityControl/InfrastructureGenerator.h" +#include "QualityControl/runnerUtils.h" +#include "QualityControl/ExamplePrinterSpec.h" +#include "QualityControl/MonitorObject.h" +#include "ITSQCDataReaderWorkflow/TestDataReader.h" +#include "DetectorsBase/GeometryManager.h" + +std::string getConfigPath(const ConfigContext& config); + +using namespace o2::configuration; +using namespace o2::framework; +using namespace o2::quality_control::checker; +using namespace std::chrono; + +WorkflowSpec defineDataProcessing(const ConfigContext& config) +{ + WorkflowSpec specs; + + o2::base::GeometryManager::loadGeometry(); + + ILOG(Info, Support) << "START READER" << ENDM; + + specs.emplace_back(o2::its::getTestDataReaderSpec()); + + // Path to the config file + std::string qcConfigurationSource = getConfigPath(config); + ILOG(Debug, Devel) << "Using config file '" << qcConfigurationSource << "'" << ENDM; + + // Generation of Data Sampling infrastructure + auto configInterface = ConfigurationFactory::getConfiguration(qcConfigurationSource); + auto dataSamplingTree = configInterface->getRecursive("dataSamplingPolicies"); + DataSampling::GenerateInfrastructure(specs, dataSamplingTree); + + // Generation of the QC topology (one task, one checker in this case) + quality_control::generateStandaloneInfrastructure(specs, configInterface->getRecursive()); + + return specs; +} + +// TODO merge this with the one from runReadout.cxx +std::string getConfigPath(const ConfigContext& config) +{ + // Determine the default config file path and name (based on option no-data-sampling and the QC_ROOT path) + bool noDS = config.options().get("no-data-sampling"); + std::string filename = !noDS ? "its.json" : "basic-no-sampling.json"; + std::string defaultConfigPath = getenv("QUALITYCONTROL_ROOT") != nullptr ? std::string(getenv("QUALITYCONTROL_ROOT")) + "/etc/" + filename : "$QUALITYCONTROL_ROOT undefined"; + // The the optional one by the user + auto userConfigPath = config.options().get("config-path"); + // Finally build the config path based on the default or the user-base one + std::string path = std::string("json:/") + (userConfigPath.empty() ? defaultConfigPath : userConfigPath); + return path; +} diff --git a/Modules/ITS/src/runITSClustersRootFileReader.cxx b/Modules/ITS/src/runITSClustersRootFileReader.cxx new file mode 100644 index 0000000000..cacdf97b6e --- /dev/null +++ b/Modules/ITS/src/runITSClustersRootFileReader.cxx @@ -0,0 +1,143 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runITSClustersRootFileReader.cxx +/// \author Kotliarov Artem + +/// This is an executable that reads clusters from a root file from disk and sends the data to QC via the Data Processing Layer. +/// The code is inspired by a similar reader "runMFTClustersRootFileReader.cxx" (authors Guillermo Contreras, Tomas Herman, Katarina Krizkova Gajdosova, Diana Maria Krupova) + +/// Code run: +/// o2-qc-its-clusters-root-file-reader --qc-its-clusters-root-file => File_Clusters.root | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/itsCluster.json + +// C++ +#include +// ROOT +#include +#include +// O2 +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2; +using namespace o2::framework; +using namespace o2::itsmft; + +class ITSClustersRootFileReader : public o2::framework::Task +{ + + public: + void init(framework::InitContext& ic) + { + + // open input file + auto filename = ic.options().get("qc-its-clusters-root-file"); + mFile = std::make_unique(filename.c_str(), "READ"); + if (!mFile->IsOpen()) { + ILOG(Error, Support) << "ITSClustersRootFileReader::init. Cannot open file: " << filename.c_str() << ENDM; + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // load TTree and branches + mTree = (TTree*)mFile->Get("o2sim"); + mTree->SetBranchAddress("ITSClustersROF", &profs); + mTree->SetBranchAddress("ITSClusterComp", &pclusters); + mTree->SetBranchAddress("ITSClusterPatt", &ppatterns); + + // check entries + mNumberOfEntries = mTree->GetEntries(); + if (mNumberOfEntries == 0) { + ILOG(Error, Support) << "ITSClustersRootFileReader::init. No entries." << ENDM; + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + } + + void run(framework::ProcessingContext& pc) + { + + // Check if this is the last Entry + if (mCurrentEntry == mNumberOfEntries) { + ILOG(Info, Support) << " ITSClustersRootFileReader::run. End of file reached." << ENDM; + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // load Entry from TTree + mTree->GetEntry(mCurrentEntry); + + // Prepare ROFs output + std::vector* clusRofArr = new std::vector(); + std::copy(rofs.begin(), rofs.end(), std::back_inserter(*clusRofArr)); + + // Prepare vector with clusters for all ROFs + std::vector* clusArr = new std::vector(); + std::copy(clusters.begin(), clusters.end(), std::back_inserter(*clusArr)); + + // Prepare vector with cluster patterns for all ROFs + std::vector* clusPatternArr = new std::vector(); + std::copy(patterns.begin(), patterns.end(), std::back_inserter(*clusPatternArr)); + + // Output vectors + pc.outputs().snapshot(Output{ "ITS", "CLUSTERSROF", 0 }, *clusRofArr); + pc.outputs().snapshot(Output{ "ITS", "COMPCLUSTERS", 0 }, *clusArr); + pc.outputs().snapshot(Output{ "ITS", "PATTERNS", 0 }, *clusPatternArr); + + // move to a new entry in TTree + ++mCurrentEntry; + } + + private: + std::unique_ptr mFile = nullptr; // root file with Clusters + TTree* mTree = nullptr; // TTree object inside file + std::vector rofs, *profs = &rofs; // pointer to ROF branch + std::vector clusters, *pclusters = &clusters; // pointer to Cluster branch + std::vector patterns, *ppatterns = &patterns; // pointer to Pattern branch + + unsigned long mNumberOfEntries = 0; // number of entries from TTree + unsigned long mCurrentEntry = 0; // index of current entry + +}; // end class definition + +WorkflowSpec defineDataProcessing(const ConfigContext&) +{ + WorkflowSpec specs; // To return the work flow + + // Define the outputs + std::vector outputs; + outputs.emplace_back("ITS", "CLUSTERSROF", 0, Lifetime::Timeframe); + outputs.emplace_back("ITS", "COMPCLUSTERS", 0, Lifetime::Timeframe); + outputs.emplace_back("ITS", "PATTERNS", 0, Lifetime::Timeframe); + + // The producer to generate data in the workflow + DataProcessorSpec producer{ + "QC-ITS-clusters-root-file-reader", + Inputs{}, + outputs, + AlgorithmSpec{ adaptFromTask() }, + Options{ { "qc-its-clusters-root-file", VariantType::String, "o2clus_its.root", { "Name of the input file with clusters" } } } + }; + specs.push_back(producer); + + return specs; +} diff --git a/Modules/ITS/src/runITSTracksRootFileReader.cxx b/Modules/ITS/src/runITSTracksRootFileReader.cxx new file mode 100644 index 0000000000..b067d38686 --- /dev/null +++ b/Modules/ITS/src/runITSTracksRootFileReader.cxx @@ -0,0 +1,213 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runITSTracksRootFileReader.cxx +/// \author Kotliarov Artem + +/// This is an executable that reads clusters from a root file from disk and sends the data to QC via the Data Processing Layer. + +/// Code run: +/// o2-qc-its-tracks-root-file-reader --qc-its-tracks-root-file => File_Tracks.root --qc-its-clusters-root-file => File_Clusters.root | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/itsTrack.json + +// C++ +#include +// ROOT +#include +#include +// O2 +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "ReconstructionDataFormats/Vertex.h" +#include "ReconstructionDataFormats/PrimaryVertex.h" +#include + +using namespace o2; +using namespace o2::framework; +using namespace o2::itsmft; + +class ITSTracksRootFileReader : public o2::framework::Task +{ + public: + void init(framework::InitContext& ic) + { + ILOG(Debug, Devel) << "In ITSTracksRootFileReader::init ... entering " << ENDM; + + // Tracks + auto filenameTracks = ic.options().get("qc-its-tracks-root-file"); + + mFileTracks = std::make_unique(filenameTracks.c_str(), "READ"); + if (!mFileTracks->IsOpen()) { + ILOG(Error, Support) << "ITSTracksRootFileReader::init. Cannot open file: " << filenameTracks.c_str() << ENDM; + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // Tracks: load TTree and branches + mTreeTracks = (TTree*)mFileTracks->Get("o2sim"); + mTreeTracks->SetBranchAddress("ITSTrack", &ptracks); + mTreeTracks->SetBranchAddress("ITSTracksROF", &ptrackRofs); + mTreeTracks->SetBranchAddress("Vertices", &pvertices); + mTreeTracks->SetBranchAddress("VerticesROF", &pverticesRof); + mTreeTracks->SetBranchAddress("ITSTrackClusIdx", &ptrackclsID); + + // Clusters + auto filenameCluster = ic.options().get("qc-its-clusters-root-file"); + + mFileClusters = std::make_unique(filenameCluster.c_str(), "READ"); + if (!mFileClusters->IsOpen()) { + ILOG(Error, Support) << "ITSTracksRootFileReader::init. Cannot open file: " << filenameCluster.c_str() << ENDM; + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // Clusters: load TTree and branches + mTreeClusters = (TTree*)mFileClusters->Get("o2sim"); + mTreeClusters->SetBranchAddress("ITSClusterComp", &pclusters); + mTreeClusters->SetBranchAddress("ITSClustersROF", &pclusterRofs); + mTreeClusters->SetBranchAddress("ITSClusterPatt", &ppatterns); + + // check match of entries in loaded files + unsigned long mNumberOfEntriesTrack = mTreeTracks->GetEntries(); + unsigned long mNumberOfEntriesCluster = mTreeClusters->GetEntries(); + + if (mNumberOfEntriesTrack != mNumberOfEntriesCluster) { + ILOG(Error, Support) << "ITSTracksRootFileReader::init. Mismatch of entries in loaded files." << ENDM; + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // check entries + mNumberOfEntries = mTreeTracks->GetEntries(); + if (mNumberOfEntries == 0) { + ILOG(Error, Support) << "ITSTracksRootFileReader::init. No entries." << ENDM; + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + } + + void run(framework::ProcessingContext& pc) + { + // Check if this is the last Entry + if (mCurrentEntry == mNumberOfEntries) { + LOG(info) << " ITSClustersRootFileReader::run. End of files reached."; + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // load Entry from TTree + mTreeTracks->GetEntry(mCurrentEntry); + mTreeClusters->GetEntry(mCurrentEntry); + + // Tracks + std::vector* trackRofArr = new std::vector(); + std::copy(trackRofs.begin(), trackRofs.end(), std::back_inserter(*trackRofArr)); + + std::vector* trackArr = new std::vector(); + std::copy(tracks.begin(), tracks.end(), std::back_inserter(*trackArr)); + + std::vector>>* vertexArr = new std::vector>>(); + std::copy(vertices.begin(), vertices.end(), std::back_inserter(*vertexArr)); + + std::vector* vertexRofArr = new std::vector(); + std::copy(verticesRof.begin(), verticesRof.end(), std::back_inserter(*vertexRofArr)); + + std::vector* clusIdx = new std::vector(); + std::copy(trackclsID.begin(), trackclsID.end(), std::back_inserter(*clusIdx)); + + // Clusters + std::vector* clusRofArr = new std::vector(); + std::copy(clusterRofs.begin(), clusterRofs.end(), std::back_inserter(*clusRofArr)); + + std::vector* clusArr = new std::vector(); + std::copy(clusters.begin(), clusters.end(), std::back_inserter(*clusArr)); + + std::vector* clusPatternArr = new std::vector(); + std::copy(patterns.begin(), patterns.end(), std::back_inserter(*clusPatternArr)); + + // Output vectors + pc.outputs().snapshot(Output{ "ITS", "ITSTrackROF", 0 }, *trackRofArr); + pc.outputs().snapshot(Output{ "ITS", "TRACKS", 0 }, *trackArr); + pc.outputs().snapshot(Output{ "ITS", "VERTICES", 0 }, *vertexArr); + pc.outputs().snapshot(Output{ "ITS", "VERTICESROF", 0 }, *vertexRofArr); + pc.outputs().snapshot(Output{ "ITS", "TRACKCLSID", 0 }, *clusIdx); + + pc.outputs().snapshot(Output{ "ITS", "CLUSTERSROF", 0 }, *clusRofArr); + pc.outputs().snapshot(Output{ "ITS", "COMPCLUSTERS", 0 }, *clusArr); + pc.outputs().snapshot(Output{ "ITS", "PATTERNS", 0 }, *clusPatternArr); + + // move to a new entry in TTree + ++mCurrentEntry; + } + + private: + std::unique_ptr mFileTracks = nullptr; // root file with Tracks + std::unique_ptr mFileClusters = nullptr; // root file with Clusters + TTree* mTreeTracks = nullptr; // TTree object inside file with Tracks + TTree* mTreeClusters = nullptr; // TTree object inside file with Clusters + + // Tracks + std::vector trackRofs, *ptrackRofs = &trackRofs; // pointer to TracksROF branch + std::vector tracks, *ptracks = &tracks; // pointer to Track branch + std::vector>> vertices, *pvertices = &vertices; // pointer to Vertices branch + std::vector verticesRof, *pverticesRof = &verticesRof; // pointer to VerticesRof branch + std::vector trackclsID, *ptrackclsID = &trackclsID; // pointer to TrackClusIdx branch + + // Clusters + std::vector clusterRofs, *pclusterRofs = &clusterRofs; // pointer to ClustersROF branch + std::vector clusters, *pclusters = &clusters; // pointer to ClusterComp branch + std::vector patterns, *ppatterns = &patterns; // pointer to ClusterPatt branch + + unsigned long mNumberOfEntries = 0; // number of entries from TTree + unsigned long mCurrentEntry = 0; // index of current entry + +}; // end class definition + +WorkflowSpec defineDataProcessing(const ConfigContext&) +{ + WorkflowSpec specs; // To return the work flow + + // Define the outputs + std::vector outputs; + outputs.emplace_back("ITS", "ITSTrackROF", 0, Lifetime::Timeframe); + outputs.emplace_back("ITS", "TRACKS", 0, Lifetime::Timeframe); + outputs.emplace_back("ITS", "VERTICES", 0, Lifetime::Timeframe); + outputs.emplace_back("ITS", "VERTICESROF", 0, Lifetime::Timeframe); + outputs.emplace_back("ITS", "TRACKCLSID", 0, Lifetime::Timeframe); + + outputs.emplace_back("ITS", "CLUSTERSROF", 0, Lifetime::Timeframe); + outputs.emplace_back("ITS", "COMPCLUSTERS", 0, Lifetime::Timeframe); + outputs.emplace_back("ITS", "PATTERNS", 0, Lifetime::Timeframe); + + // The producer to generate data in the workflow + DataProcessorSpec producer{ + "QC-ITS-tracks-root-file-reader", + Inputs{}, + outputs, + AlgorithmSpec{ adaptFromTask() }, + Options{ { "qc-its-tracks-root-file", VariantType::String, "o2trac_its.root", { "Name of the input file with tracks" } }, { "qc-its-clusters-root-file", VariantType::String, "o2clus_its.root", { "Name of the input file with clusters" } } } + }; + specs.push_back(producer); + + return specs; +} diff --git a/Modules/ITS/test/testITS.cxx b/Modules/ITS/test/testITS.cxx new file mode 100644 index 0000000000..7d42a91238 --- /dev/null +++ b/Modules/ITS/test/testITS.cxx @@ -0,0 +1,36 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testITS.cxx +/// \author +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2 +{ +namespace quality_control_modules +{ +namespace itstaskraw +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace itstaskraw +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MFT/CMakeLists.txt b/Modules/MFT/CMakeLists.txt new file mode 100644 index 0000000000..d4034bb07f --- /dev/null +++ b/Modules/MFT/CMakeLists.txt @@ -0,0 +1,107 @@ +# ---- Library ---- + +add_library(O2QcMFT) + +target_sources(O2QcMFT PRIVATE + src/QcMFTDigitCheck.cxx + src/QcMFTDigitTask.cxx + src/QcMFTClusterCheck.cxx + src/QcMFTClusterTask.cxx + src/QcMFTTrackCheck.cxx + src/QcMFTTrackTask.cxx + src/QcMFTTrackMCTask.cxx + src/QcMFTReadoutCheck.cxx + src/QcMFTReadoutTask.cxx + src/QcMFTReadoutTrend.cxx + src/QcMFTOccupancyTrend.cxx) + +target_include_directories( + O2QcMFT + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcMFT PUBLIC O2QualityControl O2::DataFormatsITSMFT O2::ITSMFTReconstruction O2::MFTTracking O2::MFTBase O2::ITSMFTBase O2::DetectorsBase O2QcCommon) + +install(TARGETS O2QcMFT + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcMFT + HEADERS + include/MFT/QcMFTDigitCheck.h + include/MFT/QcMFTDigitTask.h + include/MFT/QcMFTClusterCheck.h + include/MFT/QcMFTClusterTask.h + include/MFT/QcMFTTrackCheck.h + include/MFT/QcMFTTrackTask.h + include/MFT/QcMFTTrackMCTask.h + include/MFT/QcMFTReadoutCheck.h + include/MFT/QcMFTReadoutTask.h + include/MFT/QcMFTReadoutTrend.h + include/MFT/QcMFTUtilTables.h + include/MFT/QcMFTOccupancyTrend.h + LINKDEF include/MFT/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/MFT + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Executables ---- + +set(EXE_SRCS + src/runMFTDigitsRootFileReader.cxx + src/runMFTDigitsHotPixelRootFileReader.cxx + src/runMFTClustersRootFileReader.cxx + src/runMFTTracksRootFileReader.cxx) +set(EXE_NAMES + o2-qc-mft-digits-root-file-reader + o2-qc-mft-digits-hot-pixel-root-file-reader + o2-qc-mft-clusters-root-file-reader + o2-qc-mft-tracks-root-file-reader) + +list(LENGTH EXE_SRCS count) +math(EXPR count "${count}-1") +foreach(i RANGE ${count}) + list(GET EXE_SRCS ${i} src) + list(GET EXE_NAMES ${i} name) + add_executable(${name} ${src}) + target_link_libraries(${name} PRIVATE O2QualityControl O2QcMFT O2::ITSMFTBase) +endforeach() + +install( + TARGETS o2-qc-mft-digits-root-file-reader + o2-qc-mft-digits-hot-pixel-root-file-reader + o2-qc-mft-clusters-root-file-reader + o2-qc-mft-tracks-root-file-reader + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcMFT.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcMFT Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + +# ---- Install config files and scripts ---- + +install(FILES mft-digits.json + mft-clusters.json + mft-tracks.json + mft-tracks-mc.json + mft-readout.json + mft-readout-trend.json + mft-occupancy-trend.json + mft-trend-slices.json + DESTINATION etc) diff --git a/Modules/MFT/include/MFT/LinkDef.h b/Modules/MFT/include/MFT/LinkDef.h new file mode 100644 index 0000000000..b597284d08 --- /dev/null +++ b/Modules/MFT/include/MFT/LinkDef.h @@ -0,0 +1,18 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::mft::QcMFTDigitTask + ; +#pragma link C++ class o2::quality_control_modules::mft::QcMFTDigitCheck + ; +#pragma link C++ class o2::quality_control_modules::mft::QcMFTClusterTask + ; +#pragma link C++ class o2::quality_control_modules::mft::QcMFTClusterCheck + ; +#pragma link C++ class o2::quality_control_modules::mft::QcMFTTrackTask + ; +#pragma link C++ class o2::quality_control_modules::mft::QcMFTTrackCheck + ; +#pragma link C++ class o2::quality_control_modules::mft::QcMFTTrackMCTask + ; +#pragma link C++ class o2::quality_control_modules::mft::QcMFTReadoutCheck + ; +#pragma link C++ class o2::quality_control_modules::mft::QcMFTReadoutTask + ; +#pragma link C++ class o2::quality_control_modules::mft::QcMFTReadoutTrend + ; +#pragma link C++ class o2::quality_control_modules::mft::QcMFTOccupancyTrend + ; + +#endif diff --git a/Modules/MFT/include/MFT/QcMFTClusterCheck.h b/Modules/MFT/include/MFT/QcMFTClusterCheck.h new file mode 100644 index 0000000000..01abc719a6 --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTClusterCheck.h @@ -0,0 +1,78 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTClusterCheck.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// + +#ifndef QC_MFT_CLUSTER_CHECK_H +#define QC_MFT_CLUSTER_CHECK_H + +// Quality Control +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::mft +{ + +/// \brief MFT Cluster Check +/// +class QcMFTClusterCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + QcMFTClusterCheck() = default; + /// Destructor + ~QcMFTClusterCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + int mLadderThresholdMedium; + int mLadderThresholdBad; + + // ladder checker + bool mAdjacentLaddersEmpty; + int mEmptyCount; + + // masked chips part + bool mFirstCall; + std::vector mMaskedChips; + std::vector mChipMapName; + + void readMaskedChips(std::shared_ptr mo); + void createMaskedChipsNames(); + + // to form the name of the masked chips histograms + int mHalf[936] = { 0 }; + int mDisk[936] = { 0 }; + int mFace[936] = { 0 }; + int mZone[936] = { 0 }; + int mSensor[936] = { 0 }; + int mTransID[936] = { 0 }; + int mLayer[936] = { 0 }; + int mLadder[936] = { 0 }; + float mX[936] = { 0 }; + float mY[936] = { 0 }; + void getChipMapData(); + + ClassDefOverride(QcMFTClusterCheck, 2); +}; + +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_CLUSTER_CHECK_H diff --git a/Modules/MFT/include/MFT/QcMFTClusterTask.h b/Modules/MFT/include/MFT/QcMFTClusterTask.h new file mode 100644 index 0000000000..ad8befeb38 --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTClusterTask.h @@ -0,0 +1,129 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTClusterTask.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// \author David Grund +/// \author Jakub Juracka +/// + +#ifndef QC_MFT_CLUSTER_TASK_H +#define QC_MFT_CLUSTER_TASK_H + +#include +#include +#include +#include +#include +#include +#include "ReconstructionDataFormats/BaseCluster.h" +#include "MFTBase/GeometryTGeo.h" +#include + +// Quality Control +#include "QualityControl/TaskInterface.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" + +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::mft +{ + +/// \brief MFT Cluster QC task +/// +class QcMFTClusterTask /*final*/ : public TaskInterface // todo add back the "final" when doxygen is fixed +{ + public: + /// \brief Constructor + QcMFTClusterTask(); + /// Destructor + ~QcMFTClusterTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + double orbitToSeconds(uint32_t orbit, uint32_t refOrbit) + { + return (orbit - refOrbit) * o2::constants::lhc::LHCOrbitNS / 1E9; + } + + private: + std::unique_ptr mClusterLayerIndexH0 = nullptr; + std::unique_ptr mClusterLayerIndexH1 = nullptr; + + std::unique_ptr mClusterOccupancy = nullptr; + std::unique_ptr mClusterPatternIndex = nullptr; + std::unique_ptr mClusterSizeSummary = nullptr; + std::unique_ptr mGroupedClusterSizeSummary = nullptr; + std::unique_ptr mClusterOccupancySummary = nullptr; + + std::unique_ptr mClusterPatternSensorIndices = nullptr; + std::vector> mClusterChipOccupancyMap; + + std::unique_ptr mClusterZ = nullptr; + std::vector> mClusterXYinLayer; + std::vector> mClusterRinLayer; + std::unique_ptr mClusterRinAllLayers = nullptr; + std::unique_ptr mClusterRinAllLayersStack = nullptr; + + std::unique_ptr mClustersROFSize = nullptr; + std::unique_ptr mClustersBC = nullptr; + + std::vector> mClustersGlobal; + + int mOnlineQC; + + const TString mColors[10] = { "#1F77B4", "#FF7F0E", "#2CA02C", "#D62728", "#8C564B", "#E377C2", "#9467BD", "#BCBD22", "#7F7F7F", "#17BECF" }; + + // needed to construct the name and path of some histograms + int mHalf[936] = { 0 }; + int mDisk[936] = { 0 }; + int mFace[936] = { 0 }; + int mZone[936] = { 0 }; + int mSensor[936] = { 0 }; + int mTransID[936] = { 0 }; + int mLadder[936] = { 0 }; + float mX[936] = { 0 }; + float mY[936] = { 0 }; + + // internal functions + void getChipMapData(); + void updateCanvas(); + + // cluster size in pixels + int mClusterSize = { 0 }; + + // dictionary + const o2::itsmft::TopologyDictionary* mDict = nullptr; + + o2::mft::GeometryTGeo* mGeom = nullptr; + // where the geometry file is stored + std::string mGeomPath; + + // reference orbit used in relative time calculation + uint32_t mRefOrbit = -1; +}; + +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_CLUSTER_TASK_H diff --git a/Modules/MFT/include/MFT/QcMFTDigitCheck.h b/Modules/MFT/include/MFT/QcMFTDigitCheck.h new file mode 100644 index 0000000000..e799719816 --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTDigitCheck.h @@ -0,0 +1,104 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTDigitCheck.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova + +#ifndef QC_MFT_DIGIT_CHECK_H +#define QC_MFT_DIGIT_CHECK_H + +// Quality Control +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::mft +{ + +/// \brief MFT Digit Check +/// +class QcMFTDigitCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + QcMFTDigitCheck() = default; + /// Destructor + ~QcMFTDigitCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + int mLadderThresholdMedium; + int mLadderThresholdBad; + + // ladder checker + bool mAdjacentLaddersEmpty; + int mEmptyCount; + + // masked chips part + bool mFirstCall; + std::vector mMaskedChips; + std::vector mChipMapName; + + void readMaskedChips(std::shared_ptr mo); + void createMaskedChipsNames(); + + // noise scan check + + void readNoiseMap(std::shared_ptr mo, long timestamp); + int mNoiseScan; + int mNCycles; + int mNCyclesNoiseMap; + int mDisNoisy; + int mNewNoisy; + int mTotalNoisy; + std::vector mNoisyPix; + std::vector mOldNoisyPix; + std::vector mNewNoisyPix; + + bool mQualityGood; + bool mQualityMedium; + bool mQualityBad; + int mNoiseTotalMediumMin; + int mNoiseTotalMediumMax; + int mNoiseTotalBadMin; + int mNoiseTotalBadMax; + int mNoiseNewMediumMin; + int mNoiseNewMediumMax; + int mNoiseNewBadMax; + int mNoiseDisMediumMin; + int mNoiseDisMediumMax; + int mNoiseDisBadMax; + + // to form the name of the masked chips histograms + int mHalf[936] = { 0 }; + int mDisk[936] = { 0 }; + int mFace[936] = { 0 }; + int mZone[936] = { 0 }; + int mSensor[936] = { 0 }; + int mTransID[936] = { 0 }; + int mLayer[936] = { 0 }; + int mLadder[936] = { 0 }; + float mX[936] = { 0 }; + float mY[936] = { 0 }; + void getChipMapData(); + + ClassDefOverride(QcMFTDigitCheck, 2); +}; + +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_DIGIT_CHECK_H diff --git a/Modules/MFT/include/MFT/QcMFTDigitTask.h b/Modules/MFT/include/MFT/QcMFTDigitTask.h new file mode 100644 index 0000000000..a2b794b44c --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTDigitTask.h @@ -0,0 +1,124 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTDigitTask.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// \author David Grund +/// + +#ifndef QC_MFT_DIGIT_TASK_H +#define QC_MFT_DIGIT_TASK_H + +// ROOT +#include +#include +#include +// O2 +#include +#include +// Quality Control +#include "QualityControl/TaskInterface.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" + +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::mft +{ + +/// \brief MFT Digit QC task +/// +class QcMFTDigitTask final : public TaskInterface +{ + public: + /// \brief Constructor + QcMFTDigitTask() = default; + /// Destructor + ~QcMFTDigitTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + double orbitToSeconds(uint32_t orbit, uint32_t refOrbit) + { + return (orbit - refOrbit) * o2::constants::lhc::LHCOrbitNS / 1E9; + } + + private: + // variables + const double maxBinXPixelOccupancyMap = 1024; + const double maxBinYPixelOccupancyMap = 512; + const double minBinPixelOccupancyMap = 0; + const double shiftPixelOccupancyMap = 0.5; + const int binWidthPixelOccupancyMap = 1; + + const int numberOfOccupancyMaps = 20; + const int numberOfChips = 936; + + int mCurrentFLP; + int mNoiseScan; + int mNumberOfPixelMapsPerFLP[5] = { 66, 66, 82, 118, 136 }; + + int mVectorIndexOfChips[936] = { 0 }; + int mOccupancyMapIndexOfChips[936] = { 0 }; + int mVectorIndexOfOccupancyMaps[20] = { 0 }; + + int mHalf[936] = { 0 }; + int mDisk[936] = { 0 }; + int mFace[936] = { 0 }; + int mZone[936] = { 0 }; + int mSensor[936] = { 0 }; + int mTransID[936] = { 0 }; + int mLayer[936] = { 0 }; + int mLadder[936] = { 0 }; + float mX[936] = { 0 }; + float mY[936] = { 0 }; + + std::unique_ptr mMergerTest = nullptr; + std::unique_ptr mDigitChipOccupancy = nullptr; + std::unique_ptr mDigitChipStdDev = nullptr; + std::unique_ptr mDigitOccupancySummary = nullptr; + std::unique_ptr mDigitDoubleColumnSensorIndices = nullptr; + + std::unique_ptr mDigitsROFSize = nullptr; + std::unique_ptr mDigitsBC = nullptr; + + std::vector> mDigitChipOccupancyMap; + std::vector> mDigitPixelOccupancyMap; + + // reference orbit used in relative time calculation + uint32_t mRefOrbit = -1; + + // functions + int getVectorIndexChipOccupancyMap(int chipIndex); + int getIndexChipOccupancyMap(int vectorChipOccupancyMapIndex); + int getVectorIndexPixelOccupancyMap(int chipIndex); + int getChipIndexPixelOccupancyMap(int vectorIndex); + void getNameOfChipOccupancyMap(TString& folderName, TString& histogramName, int iOccupancyMapIndex); + void getNameOfPixelOccupancyMap(TString& folderName, TString& histogramName, int iChipIndex); + void resetArrays(int* array1, int* array2, int* array3); + void getChipMapData(); +}; + +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_DIGIT_TASK_H diff --git a/Modules/MFT/include/MFT/QcMFTOccupancyTrend.h b/Modules/MFT/include/MFT/QcMFTOccupancyTrend.h new file mode 100644 index 0000000000..6e5ecb6cce --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTOccupancyTrend.h @@ -0,0 +1,51 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTOccupancyTrend.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// +#ifndef MFT_OCCUPANCY_TREND_H +#define MFT_OCCUPANCY_TREND_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::mft +{ + +/// \brief A Reductor which obtains the bin content of a TH1. +/// A Reductor which obtains the bin content of a TH1. + +class QcMFTOccupancyTrend : public quality_control::postprocessing::ReductorTObject +{ + public: + QcMFTOccupancyTrend() = default; + ~QcMFTOccupancyTrend() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + float binContentOverflow; + Double_t mean; + Double_t stddev; + Double_t entries; + } mStats; +}; + +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_OCCUPANCY_TREND_H diff --git a/Modules/MFT/include/MFT/QcMFTReadoutCheck.h b/Modules/MFT/include/MFT/QcMFTReadoutCheck.h new file mode 100644 index 0000000000..42209a056a --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTReadoutCheck.h @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTReadoutCheck.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// + +#ifndef QC_MFT_READOUT_CHECK_H +#define QC_MFT_READOUT_CHECK_H + +// ROOT +#include +// Quality Control +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::mft +{ + +/// \brief MFT Readout Header Check +/// +class QcMFTReadoutCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + QcMFTReadoutCheck() = default; + /// Destructor + ~QcMFTReadoutCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + int mWarningThresholdMedium; + int mWarningThresholdBad; + int mErrorThresholdMedium; + int mErrorThresholdBad; + int mFaultThresholdMedium; + int mFaultThresholdBad; + + std::vector mVectorOfFaultBins; + std::vector mVectorOfErrorBins; + std::vector mVectorOfWarningBins; + + void drawLatex(TH1F* histo, double xmin, double ymin, Color_t color, TString text, float tsize, Font_t tfont); + void resetVector(std::vector& vector); + Quality checkQualityStatus(TH1F* histo, std::vector& vector); + void writeMessages(TH1F* histo, std::vector& vector, Quality checkResult); + + ClassDefOverride(QcMFTReadoutCheck, 2); +}; + +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_READOUT_CHECK_H diff --git a/Modules/MFT/include/MFT/QcMFTReadoutTask.h b/Modules/MFT/include/MFT/QcMFTReadoutTask.h new file mode 100644 index 0000000000..0c4651b277 --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTReadoutTask.h @@ -0,0 +1,109 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTReadoutTask.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// + +#ifndef QC_MFT_READOUT_TASK_H +#define QC_MFT_READOUT_TASK_H + +// O2 +#include + +// Quality Control +#include "QualityControl/TaskInterface.h" +#include "Headers/RAWDataHeader.h" +#include "Headers/RDHAny.h" +#include "DetectorsRaw/RDHUtils.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::mft +{ + +/// \brief MFT Basic Readout Header QC task +/// +class QcMFTReadoutTask /*final*/ : public TaskInterface // todo add back the "final" when doxygen is fixed +{ + // addapted from ITSFeeTask + struct MFTDDW { // GBT diagnostic word + union { + uint64_t word0 = 0x0; + struct { + uint64_t laneStatus : 50; + uint16_t someInfo : 14; + } laneBits; + } laneWord; + union { + uint64_t word1 = 0x0; + struct { + uint16_t someInfo2 : 8; + uint16_t id : 8; + uint64_t padding : 48; + } indexBits; + } indexWord; + }; + + public: + /// \brief Constructor + QcMFTReadoutTask() = default; + /// Destructor + ~QcMFTReadoutTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + const int nLanes = 25; + const int maxRUidx = 104; + std::array mChipIndex; + + int mHalf[936] = { 0 }; + int mDisk[936] = { 0 }; + int mFace[936] = { 0 }; + int mZone[936] = { 0 }; + int mSensor[936] = { 0 }; + int mTransID[936] = { 0 }; + int mLayer[936] = { 0 }; + int mLadder[936] = { 0 }; + float mX[936] = { 0 }; + float mY[936] = { 0 }; + + // histos + std::unique_ptr mSummaryChipOk = nullptr; + std::unique_ptr mSummaryChipWarning = nullptr; + std::unique_ptr mSummaryChipError = nullptr; + std::unique_ptr mSummaryChipFault = nullptr; + std::unique_ptr mZoneSummaryChipWarning = nullptr; + std::unique_ptr mZoneSummaryChipError = nullptr; + std::unique_ptr mZoneSummaryChipFault = nullptr; + + // maps RU+lane to Chip + void generateChipIndex(); + + // chip map data for summary histogram per zone + void getChipMapData(); +}; + +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_READOUT_TASK_H diff --git a/Modules/MFT/include/MFT/QcMFTReadoutTrend.h b/Modules/MFT/include/MFT/QcMFTReadoutTrend.h new file mode 100644 index 0000000000..36f2432afa --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTReadoutTrend.h @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTReadoutTrend.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// +#ifndef MFT_READOUT_TREND_H +#define MFT_READOUT_TREND_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::mft +{ + +/// \brief A Reductor which obtains the bin content of a TH1. +/// A Reductor which obtains the bin content of a TH1. + +class QcMFTReadoutTrend : public quality_control::postprocessing::ReductorTObject +{ + public: + QcMFTReadoutTrend() = default; + ~QcMFTReadoutTrend() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + float binContent[936]; + float binContentOverflow; + Double_t mean; + Double_t stddev; + Double_t entries; + } mStats; +}; + +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_READOUT_TREND_H diff --git a/Modules/MFT/include/MFT/QcMFTTrackCheck.h b/Modules/MFT/include/MFT/QcMFTTrackCheck.h new file mode 100644 index 0000000000..739ea54b72 --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTTrackCheck.h @@ -0,0 +1,56 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTTrackCheck.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Diana Maria Krupova +/// \author Katarina Krizkova Gajdosova +/// + +#ifndef QC_MFT_TRACK_CHECK_H +#define QC_MFT_TRACK_CHECK_H + +// Quality Control +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::mft +{ + +/// \brief MFT Track Check +/// +class QcMFTTrackCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + QcMFTTrackCheck() = default; + /// Destructor + ~QcMFTTrackCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + // ROF check + bool mFirstCall; + int mROF; + int mOnlineQC; + void readAlpideCCDB(std::shared_ptr mo); + + ClassDefOverride(QcMFTTrackCheck, 2); +}; + +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_TRACK_CHECK_H diff --git a/Modules/MFT/include/MFT/QcMFTTrackMCTask.h b/Modules/MFT/include/MFT/QcMFTTrackMCTask.h new file mode 100644 index 0000000000..a58948ecd3 --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTTrackMCTask.h @@ -0,0 +1,98 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file QcMFTTrackMCTask.h +/// \author Diana Krupova +/// Sara Haidlova +/// inspired by ITS sim task + +#ifndef QC_MFT_TRACK_MC_TASK_H +#define QC_MFT_TRACK_MC_TASK_H + +// ROOT +#include +#include +#include +// O2 +#include +#include "SimulationDataFormat/MCTrack.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "DataFormatsMFT/TrackMFT.h" +// QualityControl +#include "QualityControl/TaskInterface.h" + +class TH1D; +class TH2D; + +using namespace o2::quality_control::core; +using namespace std; + +namespace o2::quality_control_modules::mft +{ + +class QcMFTTrackMCTask : public TaskInterface +{ + + public: + QcMFTTrackMCTask() = default; + ~QcMFTTrackMCTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + struct InfoStruct { + bool isFilled = 0; + int isReco = 0; + bool isPrimary = 0; + float pt; + float eta; + float phi; + }; + + private: + std::vector> info; + + std::unique_ptr hRecoValid_pt = nullptr; + std::unique_ptr hRecoFake_pt = nullptr; + std::unique_ptr hTrue_pt = nullptr; + + std::unique_ptr hRecoValid_eta = nullptr; + std::unique_ptr hRecoFake_eta = nullptr; + std::unique_ptr hTrue_eta = nullptr; + + std::unique_ptr hRecoValid_phi = nullptr; + std::unique_ptr hRecoFake_phi = nullptr; + std::unique_ptr hTrue_phi = nullptr; + + std::unique_ptr hEfficiency_pt = nullptr; + std::unique_ptr hEfficiency_phi = nullptr; + std::unique_ptr hEfficiency_eta = nullptr; + std::unique_ptr hFakeTrack_pt = nullptr; + std::unique_ptr hFakeTrack_phi = nullptr; + std::unique_ptr hFakeTrack_eta = nullptr; + + std::unique_ptr hPrimaryReco_pt = nullptr; + std::unique_ptr hPrimaryGen_pt = nullptr; + + std::unique_ptr hResolution_pt = nullptr; + + int mRunNumber = 0; + std::string mCollisionsContextPath; +}; +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_TRACK_MC_TASK_H diff --git a/Modules/MFT/include/MFT/QcMFTTrackTask.h b/Modules/MFT/include/MFT/QcMFTTrackTask.h new file mode 100644 index 0000000000..246d526b5d --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTTrackTask.h @@ -0,0 +1,94 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTTrackTask.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Diana Maria Krupova +/// \author Katarina Krizkova Gajdosova +/// \author David Grund +/// + +#ifndef QC_MFT_TRACK_TASK_H +#define QC_MFT_TRACK_TASK_H + +// ROOT +#include +#include +// Quality Control +#include "QualityControl/TaskInterface.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" +// O2 +#include "CommonConstants/LHCConstants.h" +#include "MFTBase/GeometryTGeo.h" + +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::common; +using namespace std; + +namespace o2::quality_control_modules::mft +{ + +/// \brief MFT Track QC task +/// +class QcMFTTrackTask /*final*/ : public TaskInterface // todo add back the "final" when doxygen is fixed +{ + public: + /// \brief Constructor + QcMFTTrackTask() = default; + /// Destructor + ~QcMFTTrackTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + o2::mft::GeometryTGeo* mGeom = nullptr; + + std::unique_ptr mNumberOfTracksPerTF = nullptr; + std::unique_ptr mTrackNumberOfClusters = nullptr; + std::unique_ptr mCATrackNumberOfClusters = nullptr; + std::unique_ptr mLTFTrackNumberOfClusters = nullptr; + std::unique_ptr mTrackInvQPt = nullptr; + std::unique_ptr mTrackChi2 = nullptr; + std::unique_ptr mTrackCharge = nullptr; + std::unique_ptr mTrackPhi = nullptr; + std::unique_ptr mPositiveTrackPhi = nullptr; + std::unique_ptr mNegativeTrackPhi = nullptr; + std::unique_ptr mTrackEta = nullptr; + std::array, 6> mTrackEtaNCls = { nullptr }; + std::array, 6> mTrackPhiNCls = { nullptr }; + std::array, 6> mTrackXYNCls = { nullptr }; + std::array, 6> mTrackEtaPhiNCls = { nullptr }; + std::unique_ptr mCATrackEta = nullptr; + std::unique_ptr mLTFTrackEta = nullptr; + std::unique_ptr mCATrackPt = nullptr; + std::unique_ptr mLTFTrackPt = nullptr; + std::unique_ptr mTrackTanl = nullptr; + std::unique_ptr mTrackROFNEntries = nullptr; + std::unique_ptr mTracksBC = nullptr; + std::unique_ptr mAssociatedClusterFraction = nullptr; + std::unique_ptr mClusterRatioVsBunchCrossing = nullptr; + + static constexpr array sMinNClustersList = { 5, 6, 7, 8, 9, 10 }; +}; + +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_TRACK_TASK_H diff --git a/Modules/MFT/include/MFT/QcMFTUtilTables.h b/Modules/MFT/include/MFT/QcMFTUtilTables.h new file mode 100644 index 0000000000..88a989773e --- /dev/null +++ b/Modules/MFT/include/MFT/QcMFTUtilTables.h @@ -0,0 +1,493 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file BasicDigitQcTaskConversionTable.h +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// \author Jakub Juracka +/// + +#ifndef QC_MFT_UTIL_TABLES_H +#define QC_MFT_UTIL_TABLES_H + +// header file to be used by QcMFTDigitTask.cxx + +namespace o2::quality_control_modules::mft +{ +class QcMFTUtilTables +{ + public: + int mLadder[936] = { + 0, 0, 10, 10, 11, 11, 12, 12, 13, 13, + 23, 23, 1, 1, 1, 2, 2, 2, 3, 3, + 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, + 7, 7, 7, 8, 8, 8, 9, 9, 9, 14, + 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, + 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, + 21, 21, 21, 22, 22, 22, 0, 0, 10, 10, + 11, 11, 12, 12, 13, 13, 23, 23, 1, 1, + 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, + 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, + 8, 8, 9, 9, 9, 14, 14, 14, 15, 15, + 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, + 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, + 22, 22, 0, 0, 12, 12, 13, 13, 25, 25, + 1, 1, 1, 2, 2, 2, 5, 5, 5, 6, + 6, 6, 7, 7, 7, 10, 10, 10, 11, 11, + 11, 14, 14, 14, 15, 15, 15, 18, 18, 18, + 19, 19, 19, 20, 20, 20, 23, 23, 23, 24, + 24, 24, 3, 3, 3, 3, 4, 4, 4, 4, + 8, 8, 8, 8, 9, 9, 9, 9, 16, 16, + 16, 16, 17, 17, 17, 17, 21, 21, 21, 21, + 22, 22, 22, 22, 0, 0, 0, 1, 1, 1, + 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, + 16, 16, 17, 17, 17, 18, 18, 18, 30, 30, + 30, 31, 31, 31, 2, 2, 2, 2, 3, 3, + 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, + 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, + 11, 11, 11, 11, 12, 12, 12, 12, 19, 19, + 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, + 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, + 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, + 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, + 29, 29, 0, 0, 0, 1, 1, 1, 15, 15, + 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, + 32, 32, 32, 33, 33, 33, 2, 2, 2, 2, + 3, 3, 3, 3, 6, 6, 6, 6, 7, 7, + 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, + 10, 10, 10, 10, 13, 13, 13, 13, 14, 14, + 14, 14, 19, 19, 19, 19, 20, 20, 20, 20, + 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, + 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, + 30, 30, 30, 30, 31, 31, 31, 31, 4, 4, + 4, 4, 4, 5, 5, 5, 5, 5, 11, 11, + 11, 11, 11, 12, 12, 12, 12, 12, 21, 21, + 21, 21, 21, 22, 22, 22, 22, 22, 28, 28, + 28, 28, 28, 29, 29, 29, 29, 29, 0, 0, + 10, 10, 11, 11, 12, 12, 13, 13, 23, 23, + 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, + 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, + 7, 8, 8, 8, 9, 9, 9, 14, 14, 14, + 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, + 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, + 21, 22, 22, 22, 0, 0, 10, 10, 11, 11, + 12, 12, 13, 13, 23, 23, 1, 1, 1, 2, + 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, + 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, + 9, 9, 9, 14, 14, 14, 15, 15, 15, 16, + 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, + 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, + 0, 0, 12, 12, 13, 13, 25, 25, 1, 1, + 1, 2, 2, 2, 5, 5, 5, 6, 6, 6, + 7, 7, 7, 10, 10, 10, 11, 11, 11, 14, + 14, 14, 15, 15, 15, 18, 18, 18, 19, 19, + 19, 20, 20, 20, 23, 23, 23, 24, 24, 24, + 3, 3, 3, 3, 4, 4, 4, 4, 8, 8, + 8, 8, 9, 9, 9, 9, 16, 16, 16, 16, + 17, 17, 17, 17, 21, 21, 21, 21, 22, 22, + 22, 22, 0, 0, 0, 1, 1, 1, 13, 13, + 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, + 17, 17, 17, 18, 18, 18, 30, 30, 30, 31, + 31, 31, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, + 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, + 11, 11, 12, 12, 12, 12, 19, 19, 19, 19, + 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, + 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, + 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, + 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, + 0, 0, 0, 1, 1, 1, 15, 15, 15, 16, + 16, 16, 17, 17, 17, 18, 18, 18, 32, 32, + 32, 33, 33, 33, 2, 2, 2, 2, 3, 3, + 3, 3, 6, 6, 6, 6, 7, 7, 7, 7, + 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, + 10, 10, 13, 13, 13, 13, 14, 14, 14, 14, + 19, 19, 19, 19, 20, 20, 20, 20, 23, 23, + 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, + 26, 26, 26, 26, 27, 27, 27, 27, 30, 30, + 30, 30, 31, 31, 31, 31, 4, 4, 4, 4, + 4, 5, 5, 5, 5, 5, 11, 11, 11, 11, + 11, 12, 12, 12, 12, 12, 21, 21, 21, 21, + 21, 22, 22, 22, 22, 22, 28, 28, 28, 28, + 28, 29, 29, 29, 29, 29 + }; + + float mX[936] = { + -8.8, -8.8, 8.2, 8.2, 9.9, 9.9, -9.9, -9.9, -8.2, -8.2, + 8.8, 8.8, -7.1, -7.1, -7.1, -5.4, -5.4, -5.4, -3.7, -3.7, + -3.7, -2, -2, -2, -0.3, -0.3, -0.3, 1.4, 1.4, 1.4, + 3.1, 3.1, 3.1, 4.8, 4.8, 4.8, 6.5, 6.5, 6.5, -6.5, + -6.5, -6.5, -4.8, -4.8, -4.8, -3.1, -3.1, -3.1, -1.4, -1.4, + -1.4, 0.3, 0.3, 0.3, 2, 2, 2, 3.7, 3.7, 3.7, + 5.4, 5.4, 5.4, 7.1, 7.1, 7.1, -8.8, -8.8, 8.2, 8.2, + 9.9, 9.9, -9.9, -9.9, -8.2, -8.2, 8.8, 8.8, -7.1, -7.1, + -7.1, -5.4, -5.4, -5.4, -3.7, -3.7, -3.7, -2, -2, -2, + -0.3, -0.3, -0.3, 1.4, 1.4, 1.4, 3.1, 3.1, 3.1, 4.8, + 4.8, 4.8, 6.5, 6.5, 6.5, -6.5, -6.5, -6.5, -4.8, -4.8, + -4.8, -3.1, -3.1, -3.1, -1.4, -1.4, -1.4, 0.3, 0.3, 0.3, + 2, 2, 2, 3.7, 3.7, 3.7, 5.4, 5.4, 5.4, 7.1, + 7.1, 7.1, -10.5, -10.5, 9.9, 9.9, -9.9, -9.9, 10.5, 10.5, + -8.8, -8.8, -8.8, -7.1, -7.1, -7.1, -2, -2, -2, -0.3, + -0.3, -0.3, 1.4, 1.4, 1.4, 6.5, 6.5, 6.5, 8.2, 8.2, + 8.2, -8.2, -8.2, -8.2, -6.5, -6.5, -6.5, -1.4, -1.4, -1.4, + 0.3, 0.3, 0.3, 2, 2, 2, 7.1, 7.1, 7.1, 8.8, + 8.8, 8.8, -5.4, -5.4, -5.4, -5.4, -3.7, -3.7, -3.7, -3.7, + 3.1, 3.1, 3.1, 3.1, 4.8, 4.8, 4.8, 4.8, -4.8, -4.8, + -4.8, -4.8, -3.1, -3.1, -3.1, -3.1, 3.7, 3.7, 3.7, 3.7, + 5.4, 5.4, 5.4, 5.4, -12.2, -12.2, -12.2, -10.5, -10.5, -10.5, + 9.9, 9.9, 9.9, 11.6, 11.6, 11.6, 13.3, 13.3, 13.3, -13.3, + -13.3, -13.3, -11.6, -11.6, -11.6, -9.9, -9.9, -9.9, 10.5, 10.5, + 10.5, 12.2, 12.2, 12.2, -8.8, -8.8, -8.8, -8.8, -7.1, -7.1, + -7.1, -7.1, -5.4, -5.4, -5.4, -5.4, -3.7, -3.7, -3.7, -3.7, + -2, -2, -2, -2, -0.3, -0.3, -0.3, -0.3, 1.4, 1.4, + 1.4, 1.4, 3.1, 3.1, 3.1, 3.1, 4.8, 4.8, 4.8, 4.8, + 6.5, 6.5, 6.5, 6.5, 8.2, 8.2, 8.2, 8.2, -8.2, -8.2, + -8.2, -8.2, -6.5, -6.5, -6.5, -6.5, -4.8, -4.8, -4.8, -4.8, + -3.1, -3.1, -3.1, -3.1, -1.4, -1.4, -1.4, -1.4, 0.3, 0.3, + 0.3, 0.3, 2, 2, 2, 2, 3.7, 3.7, 3.7, 3.7, + 5.4, 5.4, 5.4, 5.4, 7.1, 7.1, 7.1, 7.1, 8.8, 8.8, + 8.8, 8.8, -13.9, -13.9, -13.9, -12.2, -12.2, -12.2, 11.6, 11.6, + 11.6, 13.3, 13.3, 13.3, -13.3, -13.3, -13.3, -11.6, -11.6, -11.6, + 12.2, 12.2, 12.2, 13.9, 13.9, 13.9, -10.5, -10.5, -10.5, -10.5, + -8.8, -8.8, -8.8, -8.8, -3.7, -3.7, -3.7, -3.7, -2, -2, + -2, -2, -0.3, -0.3, -0.3, -0.3, 1.4, 1.4, 1.4, 1.4, + 3.1, 3.1, 3.1, 3.1, 8.2, 8.2, 8.2, 8.2, 9.9, 9.9, + 9.9, 9.9, -9.9, -9.9, -9.9, -9.9, -8.2, -8.2, -8.2, -8.2, + -3.1, -3.1, -3.1, -3.1, -1.4, -1.4, -1.4, -1.4, 0.3, 0.3, + 0.3, 0.3, 2, 2, 2, 2, 3.7, 3.7, 3.7, 3.7, + 8.8, 8.8, 8.8, 8.8, 10.5, 10.5, 10.5, 10.5, -7.1, -7.1, + -7.1, -7.1, -7.1, -5.4, -5.4, -5.4, -5.4, -5.4, 4.8, 4.8, + 4.8, 4.8, 4.8, 6.5, 6.5, 6.5, 6.5, 6.5, -6.5, -6.5, + -6.5, -6.5, -6.5, -4.8, -4.8, -4.8, -4.8, -4.8, 5.4, 5.4, + 5.4, 5.4, 5.4, 7.1, 7.1, 7.1, 7.1, 7.1, 8.8, 8.8, + -8.2, -8.2, -9.9, -9.9, 9.9, 9.9, 8.2, 8.2, -8.8, -8.8, + 7.1, 7.1, 7.1, 5.4, 5.4, 5.4, 3.7, 3.7, 3.7, 2, + 2, 2, 0.3, 0.3, 0.3, -1.4, -1.4, -1.4, -3.1, -3.1, + -3.1, -4.8, -4.8, -4.8, -6.5, -6.5, -6.5, 6.5, 6.5, 6.5, + 4.8, 4.8, 4.8, 3.1, 3.1, 3.1, 1.4, 1.4, 1.4, -0.3, + -0.3, -0.3, -2, -2, -2, -3.7, -3.7, -3.7, -5.4, -5.4, + -5.4, -7.1, -7.1, -7.1, 8.8, 8.8, -8.2, -8.2, -9.9, -9.9, + 9.9, 9.9, 8.2, 8.2, -8.8, -8.8, 7.1, 7.1, 7.1, 5.4, + 5.4, 5.4, 3.7, 3.7, 3.7, 2, 2, 2, 0.3, 0.3, + 0.3, -1.4, -1.4, -1.4, -3.1, -3.1, -3.1, -4.8, -4.8, -4.8, + -6.5, -6.5, -6.5, 6.5, 6.5, 6.5, 4.8, 4.8, 4.8, 3.1, + 3.1, 3.1, 1.4, 1.4, 1.4, -0.3, -0.3, -0.3, -2, -2, + -2, -3.7, -3.7, -3.7, -5.4, -5.4, -5.4, -7.1, -7.1, -7.1, + 10.5, 10.5, -9.9, -9.9, 9.9, 9.9, -10.5, -10.5, 8.8, 8.8, + 8.8, 7.1, 7.1, 7.1, 2, 2, 2, 0.3, 0.3, 0.3, + -1.4, -1.4, -1.4, -6.5, -6.5, -6.5, -8.2, -8.2, -8.2, 8.2, + 8.2, 8.2, 6.5, 6.5, 6.5, 1.4, 1.4, 1.4, -0.3, -0.3, + -0.3, -2, -2, -2, -7.1, -7.1, -7.1, -8.8, -8.8, -8.8, + 5.4, 5.4, 5.4, 5.4, 3.7, 3.7, 3.7, 3.7, -3.1, -3.1, + -3.1, -3.1, -4.8, -4.8, -4.8, -4.8, 4.8, 4.8, 4.8, 4.8, + 3.1, 3.1, 3.1, 3.1, -3.7, -3.7, -3.7, -3.7, -5.4, -5.4, + -5.4, -5.4, 12.2, 12.2, 12.2, 10.5, 10.5, 10.5, -9.9, -9.9, + -9.9, -11.6, -11.6, -11.6, -13.3, -13.3, -13.3, 13.3, 13.3, 13.3, + 11.6, 11.6, 11.6, 9.9, 9.9, 9.9, -10.5, -10.5, -10.5, -12.2, + -12.2, -12.2, 8.8, 8.8, 8.8, 8.8, 7.1, 7.1, 7.1, 7.1, + 5.4, 5.4, 5.4, 5.4, 3.7, 3.7, 3.7, 3.7, 2, 2, + 2, 2, 0.3, 0.3, 0.3, 0.3, -1.4, -1.4, -1.4, -1.4, + -3.1, -3.1, -3.1, -3.1, -4.8, -4.8, -4.8, -4.8, -6.5, -6.5, + -6.5, -6.5, -8.2, -8.2, -8.2, -8.2, 8.2, 8.2, 8.2, 8.2, + 6.5, 6.5, 6.5, 6.5, 4.8, 4.8, 4.8, 4.8, 3.1, 3.1, + 3.1, 3.1, 1.4, 1.4, 1.4, 1.4, -0.3, -0.3, -0.3, -0.3, + -2, -2, -2, -2, -3.7, -3.7, -3.7, -3.7, -5.4, -5.4, + -5.4, -5.4, -7.1, -7.1, -7.1, -7.1, -8.8, -8.8, -8.8, -8.8, + 13.9, 13.9, 13.9, 12.2, 12.2, 12.2, -11.6, -11.6, -11.6, -13.3, + -13.3, -13.3, 13.3, 13.3, 13.3, 11.6, 11.6, 11.6, -12.2, -12.2, + -12.2, -13.9, -13.9, -13.9, 10.5, 10.5, 10.5, 10.5, 8.8, 8.8, + 8.8, 8.8, 3.7, 3.7, 3.7, 3.7, 2, 2, 2, 2, + 0.3, 0.3, 0.3, 0.3, -1.4, -1.4, -1.4, -1.4, -3.1, -3.1, + -3.1, -3.1, -8.2, -8.2, -8.2, -8.2, -9.9, -9.9, -9.9, -9.9, + 9.9, 9.9, 9.9, 9.9, 8.2, 8.2, 8.2, 8.2, 3.1, 3.1, + 3.1, 3.1, 1.4, 1.4, 1.4, 1.4, -0.3, -0.3, -0.3, -0.3, + -2, -2, -2, -2, -3.7, -3.7, -3.7, -3.7, -8.8, -8.8, + -8.8, -8.8, -10.5, -10.5, -10.5, -10.5, 7.1, 7.1, 7.1, 7.1, + 7.1, 5.4, 5.4, 5.4, 5.4, 5.4, -4.8, -4.8, -4.8, -4.8, + -4.8, -6.5, -6.5, -6.5, -6.5, -6.5, 6.5, 6.5, 6.5, 6.5, + 6.5, 4.8, 4.8, 4.8, 4.8, 4.8, -5.4, -5.4, -5.4, -5.4, + -5.4, -7.1, -7.1, -7.1, -7.1, -7.1 + }; + + float mY[936] = { + -1.7, -4.715, -1.7, -4.715, -1.7, -4.715, -1.7, -4.715, -1.7, -4.715, + -1.7, -4.715, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, + -7.73, -3.546, -6.561, -9.576, -3.8, -6.815, -9.83, -3.706, -6.721, -9.736, + -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, + -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -3.706, -6.721, + -9.736, -3.8, -6.815, -9.83, -3.546, -6.561, -9.576, -1.7, -4.715, -7.73, + -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -1.7, -4.715, + -1.7, -4.715, -1.7, -4.715, -1.7, -4.715, -1.7, -4.715, -1.7, -4.715, + -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -3.546, -6.561, -9.576, + -3.8, -6.815, -9.83, -3.706, -6.721, -9.736, -1.7, -4.715, -7.73, -1.7, + -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, + -7.73, -1.7, -4.715, -7.73, -3.706, -6.721, -9.736, -3.8, -6.815, -9.83, + -3.546, -6.561, -9.576, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, + -4.715, -7.73, -1.7, -4.715, -1.7, -4.715, -1.7, -4.715, -1.7, -4.715, + -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -3.55, -6.565, -9.58, -3.8, + -6.815, -9.83, -3.71, -6.725, -9.74, -1.7, -4.715, -7.73, -1.7, -4.715, + -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -3.71, -6.725, -9.74, + -3.8, -6.815, -9.83, -3.55, -6.565, -9.58, -1.7, -4.715, -7.73, -1.7, + -4.715, -7.73, -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, + -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, + -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, + -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, + -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, + -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, + -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, + -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, -3.5, -6.515, -9.53, -12.545, + -4.735, -7.75, -10.765, -13.78, -4.9, -7.915, -10.93, -13.945, -4.84, -7.855, + -10.87, -13.885, -3.97, -6.985, -10, -13.015, -1.7, -4.715, -7.73, -10.745, + -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, + -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, + -3.97, -6.985, -10, -13.015, -4.84, -7.855, -10.87, -13.885, -4.9, -7.915, + -10.93, -13.945, -4.735, -7.75, -10.765, -13.78, -3.5, -6.515, -9.53, -12.545, + -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, + -7.73, -10.745, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, + -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, + -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -1.7, -4.715, -7.73, -10.745, + -1.7, -4.715, -7.73, -10.745, -4.125, -7.14, -10.155, -13.17, -5.155, -8.17, + -11.185, -14.2, -5.3, -8.315, -11.33, -14.345, -5.245, -8.26, -11.275, -14.29, + -4.5, -7.515, -10.53, -13.545, -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, + -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, + -4.5, -7.515, -10.53, -13.545, -5.245, -8.26, -11.275, -14.29, -5.3, -8.315, + -11.33, -14.345, -5.155, -8.17, -11.185, -14.2, -4.125, -7.14, -10.155, -13.17, + -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, -7.73, -10.745, -1.7, -4.715, + -7.73, -10.745, -13.76, -1.7, -4.715, -7.73, -10.745, -13.76, -1.7, -4.715, + -7.73, -10.745, -13.76, -1.7, -4.715, -7.73, -10.745, -13.76, -1.7, -4.715, + -7.73, -10.745, -13.76, -1.7, -4.715, -7.73, -10.745, -13.76, -1.7, -4.715, + -7.73, -10.745, -13.76, -1.7, -4.715, -7.73, -10.745, -13.76, 1.7, 4.715, + 1.7, 4.715, 1.7, 4.715, 1.7, 4.715, 1.7, 4.715, 1.7, 4.715, + 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 3.546, + 6.561, 9.576, 3.8, 6.815, 9.83, 3.706, 6.721, 9.736, 1.7, 4.715, + 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, + 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 3.706, 6.721, 9.736, 3.8, + 6.815, 9.83, 3.546, 6.561, 9.576, 1.7, 4.715, 7.73, 1.7, 4.715, + 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 1.7, 4.715, 1.7, 4.715, + 1.7, 4.715, 1.7, 4.715, 1.7, 4.715, 1.7, 4.715, 7.73, 1.7, + 4.715, 7.73, 1.7, 4.715, 7.73, 3.546, 6.561, 9.576, 3.8, 6.815, + 9.83, 3.706, 6.721, 9.736, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, + 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, + 4.715, 7.73, 3.706, 6.721, 9.736, 3.8, 6.815, 9.83, 3.546, 6.561, + 9.576, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, + 1.7, 4.715, 1.7, 4.715, 1.7, 4.715, 1.7, 4.715, 1.7, 4.715, + 7.73, 1.7, 4.715, 7.73, 3.55, 6.565, 9.58, 3.8, 6.815, 9.83, + 3.71, 6.725, 9.74, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, + 4.715, 7.73, 1.7, 4.715, 7.73, 3.71, 6.725, 9.74, 3.8, 6.815, + 9.83, 3.55, 6.565, 9.58, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, + 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, + 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, + 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, + 7.73, 10.745, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, + 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, + 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, + 4.715, 7.73, 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, + 1.7, 4.715, 7.73, 10.745, 3.5, 6.515, 9.53, 12.545, 4.735, 7.75, + 10.765, 13.78, 4.9, 7.915, 10.93, 13.945, 4.84, 7.855, 10.87, 13.885, + 3.97, 6.985, 10, 13.015, 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, + 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, + 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, 3.97, 6.985, + 10, 13.015, 4.84, 7.855, 10.87, 13.885, 4.9, 7.915, 10.93, 13.945, + 4.735, 7.75, 10.765, 13.78, 3.5, 6.515, 9.53, 12.545, 1.7, 4.715, + 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, + 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, + 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, + 7.73, 1.7, 4.715, 7.73, 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, + 7.73, 10.745, 4.125, 7.14, 10.155, 13.17, 5.155, 8.17, 11.185, 14.2, + 5.3, 8.315, 11.33, 14.345, 5.245, 8.26, 11.275, 14.29, 4.5, 7.515, + 10.53, 13.545, 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, + 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, 4.5, 7.515, + 10.53, 13.545, 5.245, 8.26, 11.275, 14.29, 5.3, 8.315, 11.33, 14.345, + 5.155, 8.17, 11.185, 14.2, 4.125, 7.14, 10.155, 13.17, 1.7, 4.715, + 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, 1.7, 4.715, 7.73, 10.745, + 13.76, 1.7, 4.715, 7.73, 10.745, 13.76, 1.7, 4.715, 7.73, 10.745, + 13.76, 1.7, 4.715, 7.73, 10.745, 13.76, 1.7, 4.715, 7.73, 10.745, + 13.76, 1.7, 4.715, 7.73, 10.745, 13.76, 1.7, 4.715, 7.73, 10.745, + 13.76, 1.7, 4.715, 7.73, 10.745, 13.76 + }; + // bin numbers for chip hit maps + double mNumberOfBinsInOccupancyMaps[20][6] = { + // half0 + { 12, -10, 10, 4, -12, 0 }, // disk0, face 0 + { 12, -10, 10, 4, -12, 0 }, // disk0, face 1 + + { 12, -10, 10, 4, -12, 0 }, + { 12, -10, 10, 4, -12, 0 }, + + { 13, -11, 10, 4, -12, 0 }, + { 13, -10, 11, 4, -12, 0 }, + + { 16, -13, 14, 5, -15, 0 }, + { 16, -14, 13, 5, -15, 0 }, + + { 17, -14, 14, 5, -15, 0 }, + { 17, -14, 14, 5, -15, 0 }, + + // half1 + { 12, -10, 10, 4, 0, 12 }, + { 12, -10, 10, 4, 0, 12 }, + + { 12, -10, 10, 4, 0, 12 }, + { 12, -10, 10, 4, 0, 12 }, + + { 13, -10, 11, 4, 0, 12 }, + { 13, -11, 10, 4, 0, 12 }, + + { 16, -14, 13, 5, 0, 15 }, + { 16, -13, 14, 5, 0, 15 }, + + { 17, -14, 14, 5, 0, 15 }, + { 17, -14, 14, 5, 0, 15 }, + }; + + // names for occupancy maps (for outside acceptance and ladder checker) + std::string mDigitChipMapNames[20] = { + "ChipOccupancyMaps/Half_0/Disk_0/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_0/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_1/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_1/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_2/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_2/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_3/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_3/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_4/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_4/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_0/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_0/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_1/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_1/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_2/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_2/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_3/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_3/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_4/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_4/Face_1/mDigitChipOccupancyMap" + }; + + std::string mClusterChipMapNames[20] = { + "ChipOccupancyMaps/Half_0/Disk_0/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_0/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_1/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_1/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_2/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_2/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_3/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_3/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_4/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_4/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_0/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_0/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_1/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_1/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_2/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_2/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_3/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_3/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_4/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_4/Face_1/mClusterChipOccupancyMap" + }; + // binX for outside acceptance (-1 due to uneven number of empty bins different parts of the detector) + int mBinX[20][21] = { + // half0 + { 1, 2, 3, 4, 8, 9, 10, 11, 12, 1, 11, 12, 5, 6, 7, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 3, 4, 5, 9, 10, 11, 12, 1, 2, 12, 6, 7, 8, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 3, 4, 8, 9, 10, 11, 12, 1, 11, 12, 5, 6, 7, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 3, 4, 5, 9, 10, 11, 12, 1, 2, 12, 6, 7, 8, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 3, 11, 12, 13, 1, 13, 6, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 3, 11, 12, 13, 1, 13, 6, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 16, 1, 2, 14, 15, 16, 6, 7, 8, 9, 10 }, + { 1, 2, 3, 4, 5, 6, 12, 13, 14, 15, 16, 1, 2, 3, 15, 16, 7, 8, 9, 10, 11 }, + { 1, 2, 3, 4, 14, 15, 16, 17, 1, 2, 16, 17, 7, 8, 9, 10, 11, -1, -1, -1, -1 }, + { 1, 2, 3, 4, 14, 15, 16, 17, 1, 2, 16, 17, 7, 8, 9, 10, 11, -1, -1, -1, -1 }, + + // half1 + { 6, 7, 8, 1, 2, 12, 1, 2, 3, 4, 5, 9, 10, 11, 12, -1, -1, -1, -1, -1, -1 }, + { 5, 6, 7, 1, 11, 12, 1, 2, 3, 4, 8, 9, 10, 11, 12, -1, -1, -1, -1, -1, -1 }, + { 6, 7, 8, 1, 2, 12, 1, 2, 3, 4, 5, 9, 10, 11, 12, -1, -1, -1, -1, -1, -1 }, + { 5, 6, 7, 1, 11, 12, 1, 2, 3, 4, 8, 9, 10, 11, 12, -1, -1, -1, -1, -1, -1 }, + { 6, 7, 8, 1, 13, 1, 2, 3, 11, 12, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 6, 7, 8, 1, 13, 1, 2, 3, 11, 12, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 7, 8, 9, 10, 11, 1, 2, 3, 15, 16, 1, 2, 3, 4, 5, 6, 12, 13, 14, 15, 16 }, + { 6, 7, 8, 9, 10, 1, 2, 14, 15, 16, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15, 16 }, + { 7, 8, 9, 10, 11, 1, 2, 16, 17, 1, 2, 3, 4, 14, 15, 16, 17, -1, -1, -1, -1 }, + { 7, 8, 9, 10, 11, 1, 2, 16, 17, 1, 2, 3, 4, 14, 15, 16, 17, -1, -1, -1, -1 }, + }; + + // binY for outside acceptance (-1 due to uneven number of empty bins different parts of the detector) + int mBinY[20][21] = { + // half0 + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 4, 4, 4, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 4, 4, 4, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 4, 4, 4, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 4, 4, 4, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 1, 1, 1, 2, 2, 4, 4, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 1, 1, 1, 2, 2, 4, 4, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 5, 5, 5, 5, 5 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 5, 5, 5, 5, 5 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, 5, -1, -1, -1, -1 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, 5, -1, -1, -1, -1 }, + + // half1 + { 1, 1, 1, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 3, 3, 4, 4, 4, 4, 4, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 3, 3, 4, 4, 4, 4, 4, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + { 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }, + { 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 }, + { 1, 1, 1, 1, 1, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, -1, -1, -1, -1 }, + { 1, 1, 1, 1, 1, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, -1, -1, -1, -1 }, + }; + + // bin limits for digits per ROF axis + static constexpr float mROFBins[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, + 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, + 210, 220, 230, 240, 250, 260, 270, 280, 290, 300, + 310, 320, 330, 340, 350, 360, 370, 380, 390, 400, + 410, 420, 430, 440, 450, 460, 470, 480, 490, 500, + 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, + 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, + 710, 720, 730, 740, 750, 760, 770, 780, 790, 800, + 810, 820, 830, 840, 850, 860, 870, 880, 890, 900, + 910, 920, 930, 940, 950, 960, 970, 980, 990, 1000, + 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, + 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, + 3100, 3200, 3300, 3400, 3500, 3600, 3700, 3800, 3900, 4000, + 4100, 4200, 4300, 4400, 4500, 4600, 4700, 4800, 4900, 5000, + 5100, 5200, 5300, 5400, 5500, 5600, 5700, 5800, 5900, 6000, + 6100, 6200, 6300, 6400, 6500, 6600, 6700, 6800, 6900, 7000, + 7100, 7200, 7300, 7400, 7500, 7600, 7700, 7800, 7900, 8000, + 8100, 8200, 8300, 8400, 8500, 8600, 8700, 8800, 8900, 9000, + 9100, 9200, 9300, 9400, 9500, 9600, 9700, 9800, 9900, 10000, + 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, + 21000, 22000, 23000, 24000, 25000, 26000, 27000, 28000, 29000, 30000, + 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000, + 41000, 42000, 43000, 44000, 45000, 46000, 47000, 48000, 49000, 50000 }; + static constexpr int nROFBins = sizeof(mROFBins) / sizeof(mROFBins[0]) - 1; +}; +} // namespace o2::quality_control_modules::mft + +#endif // QC_MFT_UTIL_TABLES_H diff --git a/Modules/MFT/mft-clusters.json b/Modules/MFT/mft-clusters.json new file mode 100644 index 0000000000..ee6828bc9d --- /dev/null +++ b/Modules/MFT/mft-clusters.json @@ -0,0 +1,115 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "0" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "http://alice-ccdb.cern.ch" + } + }, + "tasks" : { + "Clusters" : { + "active" : "true", + "className" : "o2::quality_control_modules::mft::QcMFTClusterTask", + "moduleName" : "QcMFT", + "detectorName" : "MFT", + "cycleDurationSeconds" : "60", + "dataSource_comment" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "mft-clusters" + }, + "taskParameters" : { + "myOwnKey" : "myOwnValue", + "onlineQC" : "1", + "maxClusterROFSize" : "5000", + "maxDuration" : "60000", + "timeBinSize" : "0.1", + "ROFLengthInBC" : "198" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "true", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "false", + "needPropagatorD": "false" + }, + "location" : "remote" + } + }, + "checks" : { + "Clusters" : { + "active" : "true", + "dataSource" : [ { + "type" : "Task", + "name" : "Clusters", + "MOs" : ["mClusterOccupancy","mClusterPatternIndex","mClusterSizeSummary", "mGroupedClusterSizeSummary", + "ChipOccupancyMaps/Half_0/Disk_0/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_0/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_1/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_1/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_2/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_2/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_3/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_3/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_4/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_4/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_0/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_0/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_1/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_1/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_2/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_2/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_3/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_3/Face_0/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_4/Face_1/mClusterChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_4/Face_0/mClusterChipOccupancyMap", + "mClusterOccupancySummary" + ] + } ], + "checkParameters" : { + "LadderThresholdMedium" : "1", + "LadderThresholdBad" : "2" + }, + "className" : "o2::quality_control_modules::mft::QcMFTClusterCheck", + "moduleName" : "QcMFT", + "detectorName" : "MFT", + "policy" : "OnEachSeparately" + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "mft-clusters", + "active" : "true", + "machines" : [], + "query" : "randomcluster:MFT/COMPCLUSTERS/0;clustersrof:MFT/CLUSTERSROF/0;patterns:MFT/PATTERNS/0;cldict:MFT/CLUSDICT/0?lifetime=condition&ccdb-path=MFT/Calib/ClusterDictionary", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.01", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/MFT/mft-digits.json b/Modules/MFT/mft-digits.json new file mode 100644 index 0000000000..41cc158118 --- /dev/null +++ b/Modules/MFT/mft-digits.json @@ -0,0 +1,128 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "0" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "http://alice-ccdb.cern.ch" + } + }, + "tasks" : { + "Digits" : { + "active" : "true", + "className" : "o2::quality_control_modules::mft::QcMFTDigitTask", + "moduleName" : "QcMFT", + "detectorName" : "MFT", + "cycleDurationSeconds" : "60", + "dataSource_comment" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "mft-digits" + }, + "taskParameters" : { + "FLP" : "0", + "NoiseScan" : "1", + "maxDigitROFSize" : "5000", + "maxDuration" : "60000", + "timeBinSize" : "0.1", + "ROFLengthInBC" : "198" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "true", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "false", + "needPropagatorD": "false" + }, + "location" : "remote" + } + }, + "checks" : { + "Digits" : { + "active" : "true", + "className" : "o2::quality_control_modules::mft::QcMFTDigitCheck", + "moduleName" : "QcMFT", + "detectorName" : "MFT", + "policy" : "OnEachSeparately", + "checkParameters" : { + "LadderThresholdMedium" : "1", + "LadderThresholdBad" : "2", + "NoiseScan" : "1", + "NCyclesNoiseMap" : "3", + "NoiseTotalMediumMin" : "7500", + "NoiseTotalMediumMax" : "8500", + "NoiseTotalBadMin" : "7000", + "NoiseTotalBadMax" : "9000", + "NoiseNewMediumMin" : "100", + "NoiseNewMediumMax" : "500", + "NoiseNewBadMax" : "1000", + "NoiseDisMediumMin" : "100", + "NoiseDisMediumMax" : "500", + "NoiseDisBadMax" : "1000" + + }, + "dataSource" : [ { + "type" : "Task", + "name" : "Digits", + "MOs" : ["mDigitChipOccupancy", + "ChipOccupancyMaps/Half_0/Disk_0/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_0/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_1/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_1/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_2/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_2/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_3/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_3/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_4/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_0/Disk_4/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_0/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_0/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_1/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_1/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_2/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_2/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_3/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_3/Face_0/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_4/Face_1/mDigitChipOccupancyMap", + "ChipOccupancyMaps/Half_1/Disk_4/Face_0/mDigitChipOccupancyMap", + "mDigitOccupancySummary" + ] + } ] + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "mft-digits", + "active" : "true", + "machines" : [], + "query" : "randomdigit:MFT/DIGITS/0;digitsrof:MFT/DIGITSROF/0", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.01", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/MFT/mft-occupancy-trend.json b/Modules/MFT/mft-occupancy-trend.json new file mode 100644 index 0000000000..0b38dcc1c2 --- /dev/null +++ b/Modules/MFT/mft-occupancy-trend.json @@ -0,0 +1,77 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "0" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "OccupancyTrend": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcMFT", + "detectorName": "MFT", + "dataSources": [ + { + "type": "repository", + "path": "MFT/MO/Digits", + "names": [ "mDigitChipOccupancy" ], + "reductorName": "o2::quality_control_modules::mft::QcMFTOccupancyTrend", + "moduleName": "QcMFT" + }, + { + "type": "repository", + "path": "MFT/MO/Clusters", + "names": [ "mClusterOccupancy" ], + "reductorName": "o2::quality_control_modules::mft::QcMFTOccupancyTrend", + "moduleName": "QcMFT" + } + ], + "plots": [ + { + "name": "mDigitEmptyChipsTrend", + "title": "Trend of total number of chips with no digits", + "varexp": "mDigitChipOccupancy.binContentOverflow:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Chips with no digits:time" + }, + { + "name": "mClusterEmptyChipsTrend", + "title": "Trend of total number of chips with no clusters", + "varexp": "mClusterOccupancy.binContentOverflow:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "Chips with no clusters:time" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/MFT/mft-readout-trend.json b/Modules/MFT/mft-readout-trend.json new file mode 100644 index 0000000000..be785fc796 --- /dev/null +++ b/Modules/MFT/mft-readout-trend.json @@ -0,0 +1,75 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "0" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "ReadoutTrend": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcMFT", + "detectorName": "MFT", + "dataSources": [ + { + "type": "repository", + "path": "MFT/MO/Readout", + "names": [ "mSummaryChipError", "mSummaryChipWarning", "mSummaryChipFault" ], + "reductorName": "o2::quality_control_modules::mft::QcMFTReadoutTrend", + "moduleName": "QcMFT" + } + ], + "plots": [ + { + "name": "mChipErrorTrend", + "title": "Trend of total number of chips in error", + "varexp": "mSummaryChipError.binContentOverflow:time", + "selection": "", + "option": "*L" + }, + { + "name": "mChipWarningTrend", + "title": "Trend of total number of chips in warning", + "varexp": "mSummaryChipWarning.binContentOverflow:time", + "selection": "", + "option": "*L" + }, + { + "name": "mChipFaultTrend", + "title": "Trend of total number of chips in fault", + "varexp": "mSummaryChipFault.binContentOverflow:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/MFT/mft-readout.json b/Modules/MFT/mft-readout.json new file mode 100644 index 0000000000..cb0e47303b --- /dev/null +++ b/Modules/MFT/mft-readout.json @@ -0,0 +1,115 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "0" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + } + }, + "tasks" : { + "Readout" : { + "active" : "true", + "className" : "o2::quality_control_modules::mft::QcMFTReadoutTask", + "moduleName" : "QcMFT", + "detectorName" : "MFT", + "cycleDurationSeconds" : "60", + "dataSource_comment" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "direct", + "query": "filter:MFT/RAWDATA" + }, + "taskParameters" : { + "myOwnKey" : "myOwnValue" + }, + "location" : "remote" + } + }, + "checks" : { + "Readout" : { + "active" : "true", + "className" : "o2::quality_control_modules::mft::QcMFTReadoutCheck", + "moduleName" : "QcMFT", + "detectorName" : "MFT", + "policy" : "OnEachSeparately", + "checkParameters" : { + "WarningThresholdMedium" : "0", + "WarningThresholdBad" : "0", + "ErrorThresholdMedium" : "20", + "ErrorThresholdBad" : "150", + "FaultThresholdMedium" : "10", + "FaultThresholdBad" : "20" + }, + "dataSource" : [ { + "type" : "Task", + "name" : "Readout", + "MOs" : ["mSummaryChipOk","mSummaryChipFault", "mSummaryChipError", "mSummaryChipWarning", "mZoneSummaryChipWarning", "mZoneSummaryChipError", "mZoneSummaryChipFault"] + } ] + } + }, + "postprocessing": { + "ReadoutTrend": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcMFT", + "detectorName": "MFT", + "dataSources": [ + { + "type": "repository", + "path": "MFT/MO/Readout", + "names": [ "mSummaryChipError", "mSummaryChipWarning", "mSummaryChipFault" ], + "reductorName": "o2::quality_control_modules::mft::QcMFTReadoutTrend", + "moduleName": "QcMFT" + } + ], + "plots": [ + { + "name": "mChipErrorTrend", + "title": "Trend of total number of chips in error", + "varexp": "mSummaryChipError.binContentOverflow:time", + "selection": "", + "option": "*L" + }, + { + "name": "mChipWarningTrend", + "title": "Trend of total number of chips in warning", + "varexp": "mSummaryChipWarning.binContentOverflow:time", + "selection": "", + "option": "*L" + }, + { + "name": "mChipFaultTrend", + "title": "Trend of total number of chips in fault", + "varexp": "mSummaryChipFault.binContentOverflow:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:MFT/MO/Readout/mSummaryChipWarning" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/MFT/mft-tracks-mc.json b/Modules/MFT/mft-tracks-mc.json new file mode 100644 index 0000000000..1a95ca3195 --- /dev/null +++ b/Modules/MFT/mft-tracks-mc.json @@ -0,0 +1,63 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + } + }, + "tasks" : { + "TracksMC" : { + "active" : "true", + "className" : "o2::quality_control_modules::mft::QcMFTTrackMCTask", + "moduleName" : "QcMFT", + "detectorName" : "MFT", + "cycleDurationSeconds" : "30", + "dataSource_comment" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "tracks-mc" + }, + "location" : "remote", + "taskParameters" : { + "collisionsContextPath": "./collisioncontext.root" + } + } + } + }, + + "dataSamplingPolicies" : [ + { + "id" : "tracks-mc", + "active" : "true", + "machines" : [], + "query" : "tracks:MFT/TRACKS/0;mctruth:MFT/TRACKSMCTR/0", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "1", + "seed" : "1234" + } + ], + + "blocking" : "false" + } + ] + } + diff --git a/Modules/MFT/mft-tracks.json b/Modules/MFT/mft-tracks.json new file mode 100644 index 0000000000..286ff137ec --- /dev/null +++ b/Modules/MFT/mft-tracks.json @@ -0,0 +1,91 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "http://alice-ccdb.cern.ch" + } + }, + "tasks" : { + "Tracks" : { + "active" : "true", + "className" : "o2::quality_control_modules::mft::QcMFTTrackTask", + "moduleName" : "QcMFT", + "detectorName" : "MFT", + "cycleDurationSeconds" : "60", + "dataSource_comment" : "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "mft-track" + }, + "taskParameters" : { + "ROFLengthInBC" : "198", + "MaxTrackROFSize" : "1000", + "MaxDuration" : "60000", + "TimeBinSize" : "0.1" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "true", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "false", + "needPropagatorD": "false" + }, + "location" : "remote" + } + }, + "checks" : { + "Tracks" : { + "active" : "true", + "className" : "o2::quality_control_modules::mft::QcMFTTrackCheck", + "moduleName" : "QcMFT", + "detectorName" : "MFT", + "policy" : "OnEachSeparately", + "checkParameters" : { + "onlineQC" : "1" + }, + "dataSource" : [ { + "type" : "Task", + "name" : "Tracks", + "MOs" : ["mClusterRatioVsBunchCrossing" + ] + } ] + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "mft-track", + "active" : "true", + "machines" : [], + "query" : "tracks:MFT/TRACKS/0;tracksrofs:MFT/MFTTrackROF/0;clustersrofs:MFT/CLUSTERSROF/0", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.05", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/MFT/mft-trend-slices.json b/Modules/MFT/mft-trend-slices.json new file mode 100644 index 0000000000..3a7167abe2 --- /dev/null +++ b/Modules/MFT/mft-trend-slices.json @@ -0,0 +1,96 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "0" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "TrendSlices": { + "active": "true", + "className": "o2::quality_control::postprocessing::SliceTrendingTask", + "moduleName": "QcMFT", + "detectorName": "MFT", + "dataSources": [ + { + "type": "repository", + "path": "MFT/MO/Digits", + "names": [ "mDigitChipOccupancy" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ [ "1", "468", "936" ] ], + "moduleName": "QcMFT" + }, + { + "type": "repository", + "path": "MFT/MO/Clusters", + "names": [ "mClusterPatternIndex" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ [ "0", "4", "11", "300" ] ], + "moduleName": "QcMFT" + } + ], + "plots": [ + { + "name": "mMeanDigitOccupancyPerHalfMFTTrend", + "title": "Mean digit occupancy trend for each MFT half", + "varexp": "mDigitChipOccupancy.meanY:multigraphtime", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:0.5", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "Mean digit occupancy for H0 (blue) and H1 (yellow):time" + }, + { + "name": "mClusterPatternIDMeanTrend", + "title": "Mean cluster pattern ID trend for different regions", + "varexp": "mClusterPatternIndex.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.5", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "Mean cluster pattern ID:time" + }, + { + "name": "mClusterPatternIDStdDevTrend", + "title": "StdDev of cluster pattern ID trend for different regions", + "varexp": "mClusterPatternIndex.stddevX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.5", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "StdDev of cluster pattern ID:time" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/MFT/src/QcMFTClusterCheck.cxx b/Modules/MFT/src/QcMFTClusterCheck.cxx new file mode 100644 index 0000000000..f342907a62 --- /dev/null +++ b/Modules/MFT/src/QcMFTClusterCheck.cxx @@ -0,0 +1,343 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTClusterCheck.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// \author David Grund +/// \author Sara Haidlova +/// \author Jakub Juracka +/// + +// C++ +#include +// Fair +#include +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +// O2 +#include +#include + +// Quality Control +#include "MFT/QcMFTClusterCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "MFT/QcMFTUtilTables.h" +#include "QualityControl/UserCodeInterface.h" +#include "QualityControl/CustomParameters.h" + +using namespace std; + +namespace o2::quality_control_modules::mft +{ + +void QcMFTClusterCheck::configure() +{ + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + if (auto param = mCustomParameters.find("LadderThresholdMedium"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - LadderThresholdMedium: " << param->second << ENDM; + mLadderThresholdMedium = stoi(param->second); + } + if (auto param = mCustomParameters.find("LadderThresholdBad"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - LadderThresholdBad: " << param->second << ENDM; + mLadderThresholdBad = stoi(param->second); + } + + // no call to beautifier yet + mFirstCall = true; + mEmptyCount = 0; + mAdjacentLaddersEmpty = false; +} + +Quality QcMFTClusterCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + bool isEmpty = true; + int adjacentCount = 0; + + for (auto& [moName, mo] : *moMap) { + + (void)moName; + + if (mFirstCall) { + mFirstCall = false; + readMaskedChips(mo); + getChipMapData(); + createMaskedChipsNames(); + } + + if (mo->getName() == "mClusterOccupancy") { + auto* hClusterOccupancy = dynamic_cast(mo->getObject()); + if (hClusterOccupancy == nullptr) { + ILOG(Error, Support) << "Could not cast mClusterOccupancy to TH1F." << ENDM; + return Quality::Null; + } + + for (int iBin = 0; iBin < hClusterOccupancy->GetNbinsX(); iBin++) { + if (hClusterOccupancy->GetBinContent(iBin + 1) == 0) { + hClusterOccupancy->Fill(937); // number of chips with zero clusters stored in the overflow bin + } + } + } + + if (mo->getName() == "mClusterPatternIndex") { + auto* hClusterPatternIndex = dynamic_cast(mo->getObject()); + if (hClusterPatternIndex == nullptr) { + ILOG(Error, Support) << "Could not cast mClusterPatternIndex to TH1F." << ENDM; + return Quality::Null; + } + } + + if (mo->getName() == "mClusterSizeSummary") { + auto* hClusterSizeSummary = dynamic_cast(mo->getObject()); + if (hClusterSizeSummary == nullptr) { + ILOG(Error, Support) << "Could not cast hClusterSizeSummary to TH1F." << ENDM; + return Quality::Null; + } + } + + if (mo->getName() == "mGroupedClusterSizeSummary") { + auto* hGroupedClusterSizeSummary = dynamic_cast(mo->getObject()); + if (hGroupedClusterSizeSummary == nullptr) { + ILOG(Error, Support) << "Could not cast hGroupedClusterSizeSummary to TH1F." << ENDM; + return Quality::Null; + } + } + // checker for empty ladders + QcMFTUtilTables MFTTable; + for (int i = 0; i < 20; i++) { + if (mo->getName() == MFTTable.mClusterChipMapNames[i]) { + adjacentCount = 0; + auto* hClusterChipOccupancyMap = dynamic_cast(mo->getObject()); + if (hClusterChipOccupancyMap == nullptr) { + return Quality::Null; + } + // loop over bins in occupancy maps + for (int iBinX = 0; iBinX < hClusterChipOccupancyMap->GetNbinsX(); iBinX++) { + int emptyValidChips = 0; + bool hasNonEmptyChip = false; + + for (int iBinY = 0; iBinY < hClusterChipOccupancyMap->GetNbinsY(); iBinY++) { + // Check if the bin contains data before further checks + if (hClusterChipOccupancyMap->GetBinContent(iBinX + 1, iBinY + 1) != 0) { + hasNonEmptyChip = true; + break; // Exit early if a non-empty chip is found (most of them should be non-empty) + } + + bool isMasked = false; + bool isOutsideAcc = false; + + // Check if chip is outside acceptance + for (int k = 0; k < 21; k++) { + if (mo->getName().find(MFTTable.mClusterChipMapNames[i]) != std::string::npos) { + if (iBinX + 1 == MFTTable.mBinX[i][k] && iBinY + 1 == MFTTable.mBinY[i][k]) { + isOutsideAcc = true; + break; + } + } + } + + // Check if chip is masked if it is in detector acceptance + if (!isOutsideAcc) { + for (int j = 0; j < mMaskedChips.size(); j++) { + if (mo->getName().find(mChipMapName[j]) != std::string::npos) { + int maskedX = hClusterChipOccupancyMap->GetXaxis()->FindBin(mX[mMaskedChips[j]]); + int maskedY = hClusterChipOccupancyMap->GetYaxis()->FindBin(mY[mMaskedChips[j]]); + if (iBinX + 1 == maskedX && iBinY + 1 == maskedY) { + isMasked = true; + break; // break the loop if you find the bin in the masked list + } + } + } + } + + // If chip is not masked and not outside acceptance, count it + if (!isMasked && !isOutsideAcc) { + emptyValidChips++; + } + } + + // Determine if column is empty + isEmpty = (emptyValidChips > 0 && !hasNonEmptyChip); + + if (isEmpty) { + mEmptyCount++; + adjacentCount++; + } else { + adjacentCount = 0; + } + + if (adjacentCount >= mLadderThresholdBad) { + mAdjacentLaddersEmpty = true; + } + } + } + } + + if (mo->getName() == "mClusterOccupancySummary") { + auto* hClusterOccupancySummary = dynamic_cast(mo->getObject()); + if (hClusterOccupancySummary == nullptr) { + ILOG(Error, Support) << "Could not cast hClusterOccupancySummary to TH2F." << ENDM; + return Quality::Null; + } + + if (mAdjacentLaddersEmpty) { + result = Quality::Bad; + } else if (mEmptyCount >= mLadderThresholdMedium) { + result = Quality::Medium; + } else { + result = Quality::Good; + } + // We rely on 'mClusterOccupancySummary' being run after chip maps in the list of MOs in the config file + mEmptyCount = 0; + mAdjacentLaddersEmpty = false; + } + } + return result; +} + +void QcMFTClusterCheck::readMaskedChips(std::shared_ptr mo) +{ + long timestamp = mo->getValidity().getMin(); + map headers; + map filter; + auto calib = UserCodeInterface::retrieveConditionAny("MFT/Calib/DeadMap/", filter, timestamp); + if (calib == nullptr) { + ILOG(Error, Support) << "Could not retrieve deadmap from CCDB." << ENDM; + return; + } + for (int i = 0; i < calib->size(); i++) { + if (calib->isFullChipMasked(i)) { + mMaskedChips.push_back(i); + } + } +} + +void QcMFTClusterCheck::getChipMapData() +{ + const o2::itsmft::ChipMappingMFT mapMFT; + auto chipMapData = mapMFT.getChipMappingData(); + QcMFTUtilTables MFTTable; + + for (int i = 0; i < 936; i++) { + mHalf[i] = chipMapData[i].half; + mDisk[i] = chipMapData[i].disk; + mLayer[i] = chipMapData[i].layer; + mFace[i] = mLayer[i] % 2; + mZone[i] = chipMapData[i].zone; + mSensor[i] = chipMapData[i].localChipSWID; + mTransID[i] = chipMapData[i].cable; + mLadder[i] = MFTTable.mLadder[i]; + mX[i] = MFTTable.mX[i]; + mY[i] = MFTTable.mY[i]; + } +} + +void QcMFTClusterCheck::createMaskedChipsNames() +{ + for (int i = 0; i < mMaskedChips.size(); i++) { + mChipMapName.push_back(Form("ChipOccupancyMaps/Half_%d/Disk_%d/Face_%d/mClusterChipOccupancyMap", + mHalf[mMaskedChips[i]], mDisk[mMaskedChips[i]], mFace[mMaskedChips[i]])); + } +} + +void QcMFTClusterCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + + // print skull in maps to display dead chips + int nMaskedChips = mMaskedChips.size(); + for (int i = 0; i < nMaskedChips; i++) { + if (mo->getName().find(mChipMapName[i]) != std::string::npos) { + auto* hMap = dynamic_cast(mo->getObject()); + int binCx = hMap->GetXaxis()->FindBin(mX[mMaskedChips[i]]); + int binCy = hMap->GetYaxis()->FindBin(mY[mMaskedChips[i]]); + TLatex* tl = new TLatex(hMap->GetXaxis()->GetBinCenter(binCx), hMap->GetYaxis()->GetBinCenter(binCy), "N"); + tl->SetTextAlign(22); + tl->SetTextFont(142); + tl->SetTextSize(0.08); + hMap->GetListOfFunctions()->Add(tl); + } + } + + QcMFTUtilTables MFTTable; + for (int iHalf = 0; iHalf < 2; iHalf++) { + for (int iDisk = 0; iDisk < 5; iDisk++) { + for (int iFace = 0; iFace < 2; iFace++) { + int idx = (iDisk * 2 + iFace) + (10 * iHalf); + if (mo->getName().find(MFTTable.mClusterChipMapNames[idx]) != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + for (int i = 0; i < 21; i++) { + int binX = MFTTable.mBinX[idx][i]; + int binY = MFTTable.mBinY[idx][i]; + if (binX == -1 || binY == -1) { + continue; + } + TBox* b = new TBox(h->GetXaxis()->GetBinLowEdge(binX), h->GetYaxis()->GetBinLowEdge(binY), h->GetXaxis()->GetBinWidth(binX) + h->GetXaxis()->GetBinLowEdge(binX), h->GetYaxis()->GetBinWidth(binY) + h->GetYaxis()->GetBinLowEdge(binY)); + b->SetFillStyle(4055); + b->SetFillColor(15); + h->GetListOfFunctions()->Add(b); + } + } + } + } + } + + if (mo->getName().find("mClusterOccupancySummary") != std::string::npos) { + auto* hOccupancySummary = dynamic_cast(mo->getObject()); + TPaveText* msg1 = new TPaveText(0.10, 0.9, 0.35, 1.0, "NDC NB"); + TPaveText* msg2 = new TPaveText(0.65, 0.9, 0.90, 1.0, "NDC NB"); + hOccupancySummary->GetListOfFunctions()->Add(msg1); + hOccupancySummary->GetListOfFunctions()->Add(msg2); + msg1->SetName(Form("%s_msg1", mo->GetName())); + msg2->SetName(Form("%s_msg2", mo->GetName())); + if (checkResult == Quality::Good) { + LOG(info) << "Quality::Good"; + msg1->Clear(); + msg1->AddText("Quality GOOD"); + msg1->SetFillColor(kGreen); + msg2->Clear(); + msg2->AddText("No action needed"); + msg2->SetFillColor(kGreen); + } else if (checkResult == Quality::Medium) { + LOG(info) << "Quality::Medium"; + msg1->Clear(); + msg1->AddText("Quality MEDIUM"); + msg1->SetFillColor(kOrange); + msg2->Clear(); + msg2->AddText("Refer to QC instructions"); + msg2->SetFillColor(kOrange); + } else if (checkResult == Quality::Bad) { + LOG(info) << "Quality::Bad"; + msg1->Clear(); + msg1->AddText("Quality BAD"); + msg1->SetFillColor(kRed); + msg2->Clear(); + msg2->AddText("Call the on-call!"); + msg2->SetFillColor(kRed); + } + } +} + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/QcMFTClusterTask.cxx b/Modules/MFT/src/QcMFTClusterTask.cxx new file mode 100644 index 0000000000..a743d0b2cc --- /dev/null +++ b/Modules/MFT/src/QcMFTClusterTask.cxx @@ -0,0 +1,483 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTClusterTask.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// \author David Grund +/// \author Jakub Juracka +/// + +// C++ +#include +#include +#include +// ROOT +#include +#include +#include +#include +#include +#include +// O2 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Quality Control +#include "QualityControl/QcInfoLogger.h" +#include "MFT/QcMFTClusterTask.h" +#include "MFT/QcMFTUtilTables.h" +#include "QualityControl/ObjectsManager.h" +#include "QualityControl/TaskInterface.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" +#include "DetectorsBase/GRPGeomHelper.h" + +using namespace o2::mft; +o2::itsmft::ChipMappingMFT mMFTMapper; + +namespace o2::quality_control_modules::mft +{ + +QcMFTClusterTask::QcMFTClusterTask() + : TaskInterface() +{ +} + +QcMFTClusterTask::~QcMFTClusterTask() +{ + /* + not needed for unique pointers + */ +} + +void QcMFTClusterTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize QcMFTClusterTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // loading custom parameters + mOnlineQC = 1; + if (auto param = mCustomParameters.find("onlineQC"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - onlineQC: " << param->second << ENDM; + mOnlineQC = stoi(param->second); + } + + auto maxClusterROFSize = 5000; + if (auto param = mCustomParameters.find("maxClusterROFSize"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - maxClusterROFSize: " << param->second << ENDM; + maxClusterROFSize = stoi(param->second); + } + + auto maxDuration = 60.f; + if (auto param = mCustomParameters.find("maxDuration"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - maxDuration: " << param->second << ENDM; + maxDuration = stof(param->second); + } + + auto ROFLengthInBC = 198; + if (auto param = mCustomParameters.find("ROFLengthInBC"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ROFLengthInBC: " << param->second << ENDM; + ROFLengthInBC = stoi(param->second); + } + auto ROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / ROFLengthInBC; + + if (auto param = mCustomParameters.find("geomFileName"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - geometry filename: " << param->second << ENDM; + mGeomPath = param->second; + } + + getChipMapData(); + + // define histograms + mClusterLayerIndexH0 = std::make_unique( + "mClusterLayerIndexH0", "Clusters per layer in H0;Layer;# entries per orbit", 10, -0.5, 9.5, true); + mClusterLayerIndexH0->GetXaxis()->SetBinLabel(1, "d0-f0"); + mClusterLayerIndexH0->GetXaxis()->SetBinLabel(2, "d0-f1"); + mClusterLayerIndexH0->GetXaxis()->SetBinLabel(3, "d1-f0"); + mClusterLayerIndexH0->GetXaxis()->SetBinLabel(4, "d1-f1"); + mClusterLayerIndexH0->GetXaxis()->SetBinLabel(5, "d2-f0"); + mClusterLayerIndexH0->GetXaxis()->SetBinLabel(6, "d2-f1"); + mClusterLayerIndexH0->GetXaxis()->SetBinLabel(7, "d3-f0"); + mClusterLayerIndexH0->GetXaxis()->SetBinLabel(8, "d3-f1"); + mClusterLayerIndexH0->GetXaxis()->SetBinLabel(9, "d4-f0"); + mClusterLayerIndexH0->GetXaxis()->SetBinLabel(10, "d4-f1"); + mClusterLayerIndexH0->SetStats(0); + getObjectsManager()->startPublishing(mClusterLayerIndexH0.get()); + getObjectsManager()->setDisplayHint(mClusterLayerIndexH0.get(), "hist"); + + mClusterLayerIndexH1 = std::make_unique( + "mClusterLayerIndexH1", "Clusters per layer in H1;Layer;# entries per orbit", 10, -0.5, 9.5, true); + mClusterLayerIndexH1->GetXaxis()->SetBinLabel(1, "d0-f0"); + mClusterLayerIndexH1->GetXaxis()->SetBinLabel(2, "d0-f1"); + mClusterLayerIndexH1->GetXaxis()->SetBinLabel(3, "d1-f0"); + mClusterLayerIndexH1->GetXaxis()->SetBinLabel(4, "d1-f1"); + mClusterLayerIndexH1->GetXaxis()->SetBinLabel(5, "d2-f0"); + mClusterLayerIndexH1->GetXaxis()->SetBinLabel(6, "d2-f1"); + mClusterLayerIndexH1->GetXaxis()->SetBinLabel(7, "d3-f0"); + mClusterLayerIndexH1->GetXaxis()->SetBinLabel(8, "d3-f1"); + mClusterLayerIndexH1->GetXaxis()->SetBinLabel(9, "d4-f0"); + mClusterLayerIndexH1->GetXaxis()->SetBinLabel(10, "d4-f1"); + mClusterLayerIndexH1->SetStats(0); + getObjectsManager()->startPublishing(mClusterLayerIndexH1.get()); + getObjectsManager()->setDisplayHint(mClusterLayerIndexH1.get(), "hist"); + + mClusterOccupancy = std::make_unique( + "mClusterOccupancy", "Chip Cluster Occupancy;Chip ID;# entries per orbit", 936, -0.5, 935.5, true); + mClusterOccupancy->SetStats(0); + getObjectsManager()->startPublishing(mClusterOccupancy.get()); + getObjectsManager()->setDisplayHint(mClusterOccupancy.get(), "hist"); + + mClusterPatternIndex = std::make_unique( + "mClusterPatternIndex", "Cluster Pattern ID;Pattern ID;# entries per orbit", 500, -0.5, 499.5, true); + mClusterPatternIndex->SetStats(0); + getObjectsManager()->startPublishing(mClusterPatternIndex.get()); + getObjectsManager()->setDisplayHint(mClusterPatternIndex.get(), "hist logy"); + + mClusterSizeSummary = std::make_unique( + "mClusterSizeSummary", "Cluster Size Summary; Cluster Size (pixels);# entries per orbit", 100, 0.5, 100.5, true); + mClusterSizeSummary->SetStats(0); + getObjectsManager()->startPublishing(mClusterSizeSummary.get()); + getObjectsManager()->setDisplayHint(mClusterSizeSummary.get(), "hist logy"); + + mGroupedClusterSizeSummary = std::make_unique( + "mGroupedClusterSizeSummary", "Grouped Cluster Size Summary; Grouped Cluster Size (pixels);# entries per orbit", 100, 0.5, 100.5, true); + mGroupedClusterSizeSummary->SetStats(0); + getObjectsManager()->startPublishing(mGroupedClusterSizeSummary.get()); + getObjectsManager()->setDisplayHint(mGroupedClusterSizeSummary.get(), "hist logy"); + + mClusterOccupancySummary = std::make_unique("mClusterOccupancySummary", + "Cluster Occupancy Summary;;", 10, -0.5, 9.5, 8, -0.5, 7.5, true); + mClusterOccupancySummary->GetXaxis()->SetBinLabel(1, "d0-f0"); + mClusterOccupancySummary->GetXaxis()->SetBinLabel(2, "d0-f1"); + mClusterOccupancySummary->GetXaxis()->SetBinLabel(3, "d1-f0"); + mClusterOccupancySummary->GetXaxis()->SetBinLabel(4, "d1-f1"); + mClusterOccupancySummary->GetXaxis()->SetBinLabel(5, "d2-f0"); + mClusterOccupancySummary->GetXaxis()->SetBinLabel(6, "d2-f1"); + mClusterOccupancySummary->GetXaxis()->SetBinLabel(7, "d3-f0"); + mClusterOccupancySummary->GetXaxis()->SetBinLabel(8, "d3-f1"); + mClusterOccupancySummary->GetXaxis()->SetBinLabel(9, "d4-f0"); + mClusterOccupancySummary->GetXaxis()->SetBinLabel(10, "d4-f1"); + mClusterOccupancySummary->GetYaxis()->SetBinLabel(1, "h0-z0"); + mClusterOccupancySummary->GetYaxis()->SetBinLabel(2, "h0-z1"); + mClusterOccupancySummary->GetYaxis()->SetBinLabel(3, "h0-z2"); + mClusterOccupancySummary->GetYaxis()->SetBinLabel(4, "h0-z3"); + mClusterOccupancySummary->GetYaxis()->SetBinLabel(5, "h1-z0"); + mClusterOccupancySummary->GetYaxis()->SetBinLabel(6, "h1-z1"); + mClusterOccupancySummary->GetYaxis()->SetBinLabel(7, "h1-z2"); + mClusterOccupancySummary->GetYaxis()->SetBinLabel(8, "h1-z3"); + mClusterOccupancySummary->SetStats(0); + getObjectsManager()->startPublishing(mClusterOccupancySummary.get()); + getObjectsManager()->setDefaultDrawOptions(mClusterOccupancySummary.get(), "colz"); + + mClusterZ = std::make_unique( + "mClusterZ", "Z position of clusters; Z (cm); # entries per orbit", 400, -80, -40, true); + mClusterZ->SetStats(0); + getObjectsManager()->startPublishing(mClusterZ.get()); + getObjectsManager()->setDisplayHint(mClusterZ.get(), "hist"); + + mClustersROFSize = std::make_unique( + "mClustersROFSize", "Distribution of the #clusters per ROF; # clusters per ROF; # entries", QcMFTUtilTables::nROFBins, const_cast(QcMFTUtilTables::mROFBins), false); + mClustersROFSize->SetStats(0); + getObjectsManager()->startPublishing(mClustersROFSize.get()); + getObjectsManager()->setDisplayHint(mClustersROFSize.get(), "hist logx logy"); + + mClustersBC = std::make_unique( + "mClustersBC", "Clusters per BC; BCid; # entries per orbit", o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches, true); + mClustersBC->SetMinimum(0.1); + getObjectsManager()->startPublishing(mClustersBC.get()); + getObjectsManager()->setDisplayHint(mClustersBC.get(), "hist"); + + // only for online QC (disabled in A-QC) + if (mOnlineQC == 1) { + mClusterPatternSensorIndices = std::make_unique( + "mClusterPatternSensorIndices", "Cluster Pattern ID vs Chip ID;Chip ID;Pattern ID", 936, -0.5, 935.5, 500, -0.5, 499.5, true); + mClusterPatternSensorIndices->SetStats(0); + getObjectsManager()->startPublishing(mClusterPatternSensorIndices.get()); + getObjectsManager()->setDefaultDrawOptions(mClusterPatternSensorIndices.get(), "colz"); + + // define chip occupancy maps + QcMFTUtilTables MFTTable; + for (int iHalf = 0; iHalf < 2; iHalf++) { + for (int iDisk = 0; iDisk < 5; iDisk++) { + for (int iFace = 0; iFace < 2; iFace++) { + int idx = (iDisk * 2 + iFace) + (10 * iHalf); + auto chipmap = std::make_unique( + Form("ChipOccupancyMaps/Half_%d/Disk_%d/Face_%d/mClusterChipOccupancyMap", iHalf, iDisk, iFace), + Form("Cluster Chip Map h%d-d%d-f%d;x (cm);y (cm)", iHalf, iDisk, iFace), + MFTTable.mNumberOfBinsInOccupancyMaps[idx][0], + MFTTable.mNumberOfBinsInOccupancyMaps[idx][1], + MFTTable.mNumberOfBinsInOccupancyMaps[idx][2], + MFTTable.mNumberOfBinsInOccupancyMaps[idx][3], + MFTTable.mNumberOfBinsInOccupancyMaps[idx][4], + MFTTable.mNumberOfBinsInOccupancyMaps[idx][5], true); + chipmap->SetStats(0); + mClusterChipOccupancyMap.push_back(std::move(chipmap)); + getObjectsManager()->startPublishing(mClusterChipOccupancyMap[idx].get()); + getObjectsManager()->setDefaultDrawOptions(mClusterChipOccupancyMap[idx].get(), "colz"); + } // loop over faces + } // loop over disks + } // loop over halfs + + // layer histograms + for (auto nMFTLayer = 0; nMFTLayer < 10; nMFTLayer++) { + auto clusterXY = std::make_unique( + Form("ClusterXYinLayer/mClusterXYinLayer%d", nMFTLayer), + Form("Cluster Position in Layer %d; x (cm); y (cm)", nMFTLayer), 400, -20, 20, 400, -20, 20, true); + clusterXY->SetStats(0); + mClusterXYinLayer.push_back(std::move(clusterXY)); + getObjectsManager()->startPublishing(mClusterXYinLayer[nMFTLayer].get()); + getObjectsManager()->setDefaultDrawOptions(mClusterXYinLayer[nMFTLayer].get(), "colz"); + + auto clusterR = std::make_unique( + Form("ClusterRinLayer/mClusterRinLayer%d", nMFTLayer), + Form("Cluster Radial Position in Layer %d; r (cm); # entries per orbit", nMFTLayer), 400, 0, 20, true); + mClusterRinLayer.push_back(std::move(clusterR)); + getObjectsManager()->startPublishing(mClusterRinLayer[nMFTLayer].get()); + getObjectsManager()->setDisplayHint(mClusterRinLayer[nMFTLayer].get(), "hist"); + } + // canvas for for cluster R in all layers + mClusterRinAllLayers = std::make_unique("mClusterRinAllLayers", "Cluster Radial Position in All MFT Layers"); + getObjectsManager()->startPublishing(mClusterRinAllLayers.get()); + mClusterRinAllLayersStack = std::make_unique("mClusterRinAllLayersStack", "Cluster Radial Position in All MFT Layers; r (cm); # entries"); + for (auto nMFTLayer = 0; nMFTLayer < 10; nMFTLayer++) { + mClusterRinLayer[nMFTLayer]->getNum()->SetLineColor(TColor::GetColor(mColors[nMFTLayer])); + mClusterRinLayer[nMFTLayer]->getNum()->SetTitle(Form("D%dF%d", static_cast(std::floor(nMFTLayer / 2.)), nMFTLayer % 2 == 0 ? 0 : 1)); + mClusterRinAllLayersStack->Add(mClusterRinLayer[nMFTLayer]->getNum()); + } + } +} + +void QcMFTClusterTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + + // reset histograms + reset(); +} + +void QcMFTClusterTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void QcMFTClusterTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto mNOrbitsPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + + if (mDict == nullptr) { + ILOG(Info, Support) << "Getting dictionary from ccdb" << ENDM; + auto mDictPtr = ctx.inputs().get("cldict"); + mDict = mDictPtr.get(); + ILOG(Info, Support) << "Dictionary loaded with size: " << mDict->getSize() << ENDM; + } + if (!mGeom) { + o2::mft::GeometryTGeo::adopt(TaskInterface::retrieveConditionAny("MFT/Config/Geometry")); + mGeom = o2::mft::GeometryTGeo::Instance(); + ILOG(Info, Support) << "GeometryTGeo loaded" << ENDM; + } + + // get the clusters + const auto clusters = ctx.inputs().get>("randomcluster"); + const auto clustersROFs = ctx.inputs().get>("clustersrof"); + + if (clusters.empty()) { + return; + } + + // get cluster patterns and iterator + auto clustersPattern = ctx.inputs().get>("patterns"); + auto patternIt = clustersPattern.begin(); + + // get clusters with global xy position + mClustersGlobal.clear(); + mClustersGlobal.reserve(clusters.size()); + o2::mft::ioutils::convertCompactClusters(clusters, patternIt, mClustersGlobal, mDict); + + // get correct timing info of the first TF orbit + if (mRefOrbit == -1) { + mRefOrbit = ctx.services().get().firstTForbit; + } + + // reset the cluster pattern iterator which will be used later + patternIt = clustersPattern.begin(); + + // fill the clusters time histograms + for (const auto& rof : clustersROFs) { + mClustersROFSize->getNum()->Fill(rof.getNEntries()); + mClustersBC->getNum()->Fill(rof.getBCData().bc, rof.getNEntries()); + } + + // fill all other histograms + for (auto& oneCluster : clusters) { + int sensorID = oneCluster.getSensorID(); + int layerID = mMFTMapper.chip2Layer(sensorID); // used instead of previous layerID = mDisk[sensorID] * 2 + mFace[sensorID] + (mHalf[sensorID] == 0) ? mClusterLayerIndexH0->getNum()->Fill(layerID) + : mClusterLayerIndexH1->getNum()->Fill(layerID); + mClusterOccupancy->getNum()->Fill(sensorID); + mClusterPatternIndex->getNum()->Fill(oneCluster.getPatternID()); + if (mOnlineQC == 1) + mClusterPatternSensorIndices->getNum()->Fill(sensorID, oneCluster.getPatternID()); + + if (oneCluster.getPatternID() != o2::itsmft::CompCluster::InvalidPatternID && !mDict->isGroup(oneCluster.getPatternID())) { + mClusterSizeSummary->getNum()->Fill(mDict->getNpixels(oneCluster.getPatternID())); + } else { + o2::itsmft::ClusterPattern patt(patternIt); + mGroupedClusterSizeSummary->getNum()->Fill(patt.getNPixels()); + } + + // fill occupancy maps + int idx = layerID + (10 * mHalf[sensorID]); + if (mOnlineQC == 1) + mClusterChipOccupancyMap[idx]->getNum()->Fill(mX[sensorID], mY[sensorID]); + + // fill info into the summary histo + int xBin = mDisk[sensorID] * 2 + mFace[sensorID]; + int yBin = mZone[sensorID] + mHalf[sensorID] * 4; + mClusterOccupancySummary->getNum()->Fill(xBin, yBin); + } + + // fill the histograms that use global position of cluster + for (auto& oneGlobalCluster : mClustersGlobal) { + mClusterZ->getNum()->Fill(oneGlobalCluster.getZ()); + int layerID = mMFTMapper.chip2Layer(oneGlobalCluster.getSensorID()); + if (mOnlineQC == 1) { + mClusterXYinLayer[layerID]->getNum()->Fill(oneGlobalCluster.getX(), oneGlobalCluster.getY()); + mClusterRinLayer[layerID]->getNum()->Fill(std::sqrt(std::pow(oneGlobalCluster.getX(), 2) + std::pow(oneGlobalCluster.getY(), 2))); + } + } + + // fill the denominators + mClusterLayerIndexH0->getDen()->SetBinContent(1, mClusterLayerIndexH0->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mClusterLayerIndexH1->getDen()->SetBinContent(1, mClusterLayerIndexH1->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mClusterOccupancy->getDen()->SetBinContent(1, mClusterOccupancy->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mClusterPatternIndex->getDen()->SetBinContent(1, mClusterPatternIndex->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mClusterSizeSummary->getDen()->SetBinContent(1, mClusterSizeSummary->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mGroupedClusterSizeSummary->getDen()->SetBinContent(1, mGroupedClusterSizeSummary->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mClusterOccupancySummary->getDen()->SetBinContent(1, 1, mClusterOccupancySummary->getDen()->GetBinContent(1, 1) + mNOrbitsPerTF); + mClusterZ->getDen()->SetBinContent(1, mClusterZ->getDen()->GetBinContent(1) + mNOrbitsPerTF); + for (int i = 0; i < QcMFTUtilTables::nROFBins; i++) + mClustersROFSize->getDen()->SetBinContent(i + 1, QcMFTUtilTables::mROFBins[i + 1] - QcMFTUtilTables::mROFBins[i]); + mClustersBC->getDen()->SetBinContent(1, mClustersBC->getDen()->GetBinContent(1) + mNOrbitsPerTF); + if (mOnlineQC == 1) { + mClusterPatternSensorIndices->getDen()->SetBinContent(1, 1, mClusterPatternSensorIndices->getDen()->GetBinContent(1, 1) + mNOrbitsPerTF); + for (int i = 0; i < 20; i++) + mClusterChipOccupancyMap[i]->getDen()->SetBinContent(1, 1, mClusterChipOccupancyMap[i]->getDen()->GetBinContent(1, 1) + mNOrbitsPerTF); + // layer histograms + for (auto nMFTLayer = 0; nMFTLayer < 10; nMFTLayer++) { // there are 10 layers + mClusterXYinLayer[nMFTLayer]->getDen()->SetBinContent(1, 1, mClusterXYinLayer[nMFTLayer]->getDen()->GetBinContent(1, 1) + mNOrbitsPerTF); + mClusterRinLayer[nMFTLayer]->getDen()->SetBinContent(1, mClusterRinLayer[nMFTLayer]->getDen()->GetBinContent(1) + mNOrbitsPerTF); + } + } +} + +void QcMFTClusterTask::endOfCycle() +{ + // update all THRatios + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + + mClusterLayerIndexH0->update(); + mClusterLayerIndexH1->update(); + mClusterOccupancy->update(); + mClusterPatternIndex->update(); + mClusterSizeSummary->update(); + mGroupedClusterSizeSummary->update(); + mClusterOccupancySummary->update(); + mClusterZ->update(); + mClustersROFSize->update(); + mClustersBC->update(); + if (mOnlineQC == 1) { + mClusterPatternSensorIndices->update(); + for (int i = 0; i < 20; i++) + mClusterChipOccupancyMap[i]->update(); + // layer histograms + for (auto nMFTLayer = 0; nMFTLayer < 10; nMFTLayer++) { // there are 10 layers + mClusterXYinLayer[nMFTLayer]->update(); + mClusterRinLayer[nMFTLayer]->update(); + } + updateCanvas(); + } +} + +void QcMFTClusterTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void QcMFTClusterTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mClusterLayerIndexH0->Reset(); + mClusterLayerIndexH1->Reset(); + mClusterOccupancy->Reset(); + mClusterPatternIndex->Reset(); + mClusterSizeSummary->Reset(); + mGroupedClusterSizeSummary->Reset(); + mClusterOccupancySummary->Reset(); + mClusterZ->Reset(); + mClustersROFSize->Reset(); + mClustersBC->Reset(); + if (mOnlineQC == 1) { + mClusterPatternSensorIndices->Reset(); + for (int i = 0; i < 20; i++) + mClusterChipOccupancyMap[i]->Reset(); + // layer histograms + for (auto nMFTLayer = 0; nMFTLayer < 10; nMFTLayer++) { // there are 10 layers + mClusterXYinLayer[nMFTLayer]->Reset(); + mClusterRinLayer[nMFTLayer]->Reset(); + } + mClusterRinAllLayers->Clear(); + } +} + +void QcMFTClusterTask::getChipMapData() +{ + const o2::itsmft::ChipMappingMFT mapMFT; + auto chipMapData = mapMFT.getChipMappingData(); + QcMFTUtilTables MFTTable; + + for (int i = 0; i < 936; i++) { + mHalf[i] = chipMapData[i].half; + mDisk[i] = chipMapData[i].disk; + mFace[i] = (chipMapData[i].layer) % 2; + mZone[i] = chipMapData[i].zone; + mSensor[i] = chipMapData[i].localChipSWID; + mTransID[i] = chipMapData[i].cable; + mLadder[i] = MFTTable.mLadder[i]; + mX[i] = MFTTable.mX[i]; + mY[i] = MFTTable.mY[i]; + } +} + +void QcMFTClusterTask::updateCanvas() +{ + mClusterRinAllLayers->cd(); + mClusterRinAllLayers->Clear(); + mClusterRinAllLayersStack->Draw("nostack hist"); + mClusterRinAllLayers->Update(); + gPad->BuildLegend(0.83, 0.50, 0.90, 0.90, "", "l"); +} + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/QcMFTDigitCheck.cxx b/Modules/MFT/src/QcMFTDigitCheck.cxx new file mode 100644 index 0000000000..1464679781 --- /dev/null +++ b/Modules/MFT/src/QcMFTDigitCheck.cxx @@ -0,0 +1,521 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTDigitCheck.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// \author David Grund +/// \author Sara Haidlova +/// \author Jakub Juracka +/// + +// C++ +#include +// Fair +#include +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +// O2 +#include +#include + +// Quality Control +#include "MFT/QcMFTDigitCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "MFT/QcMFTUtilTables.h" +#include "QualityControl/UserCodeInterface.h" +#include "QualityControl/CustomParameters.h" + +using namespace std; + +namespace o2::quality_control_modules::mft +{ + +void QcMFTDigitCheck::configure() +{ + if (auto param = mCustomParameters.find("LadderThresholdMedium"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - LadderThresholdMedium: " << param->second << ENDM; + mLadderThresholdMedium = stoi(param->second); + } + if (auto param = mCustomParameters.find("LadderThresholdBad"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - LadderThresholdBad: " << param->second << ENDM; + mLadderThresholdBad = stoi(param->second); + } + mNoiseScan = 0; + if (auto param = mCustomParameters.find("NoiseScan"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseScan: " << param->second << ENDM; + mNoiseScan = stoi(param->second); + } + mNCyclesNoiseMap = 3; + if (auto param = mCustomParameters.find("NCyclesNoiseMap"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NCyclesNoiseMap: " << param->second << ENDM; + mNCyclesNoiseMap = stoi(param->second); + } + mNoiseTotalMediumMin = 7500; + if (auto param = mCustomParameters.find("NoiseTotalMediumMin"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseTotalMediumMin: " << param->second << ENDM; + mNoiseTotalMediumMin = stoi(param->second); + } + mNoiseTotalMediumMax = 8500; + if (auto param = mCustomParameters.find("NoiseTotalMediumMax"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseTotalMediumMax: " << param->second << ENDM; + mNoiseTotalMediumMax = stoi(param->second); + } + mNoiseTotalBadMin = 7000; + if (auto param = mCustomParameters.find("NoiseTotalBadMin"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseTotalBadMin: " << param->second << ENDM; + mNoiseTotalBadMin = stoi(param->second); + } + mNoiseTotalBadMax = 9000; + if (auto param = mCustomParameters.find("NoiseTotalBadMax"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseTotalBadMax: " << param->second << ENDM; + mNoiseTotalBadMax = stoi(param->second); + } + mNoiseNewMediumMin = 100; + if (auto param = mCustomParameters.find("NoiseNewMediumMin"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseNewMediumMin: " << param->second << ENDM; + mNoiseNewMediumMin = stoi(param->second); + } + mNoiseNewMediumMax = 500; + if (auto param = mCustomParameters.find("NoiseNewMediumMax"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseNewMediumMax: " << param->second << ENDM; + mNoiseNewMediumMax = stoi(param->second); + } + mNoiseNewBadMax = 1000; + if (auto param = mCustomParameters.find("NoiseNewBadMax"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseNewBadMax: " << param->second << ENDM; + mNoiseNewBadMax = stoi(param->second); + } + mNoiseDisMediumMin = 100; + if (auto param = mCustomParameters.find("NoiseDisMediumMin"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseDisMediumMin: " << param->second << ENDM; + mNoiseDisMediumMin = stoi(param->second); + } + mNoiseDisMediumMax = 500; + if (auto param = mCustomParameters.find("NoiseDisMediumMax"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseDisMediumMax: " << param->second << ENDM; + mNoiseDisMediumMax = stoi(param->second); + } + mNoiseDisBadMax = 1000; + if (auto param = mCustomParameters.find("NoiseDisBadMax"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseDisBadMax: " << param->second << ENDM; + mNoiseDisBadMax = stoi(param->second); + } + + // no call to beautifier yet + mFirstCall = true; + + mNCycles = 0; + mNewNoisy = 0; + mDisNoisy = 0; + mTotalNoisy = 0; + + mEmptyCount = 0; + mAdjacentLaddersEmpty = false; + mQualityGood = false; + mQualityMedium = false; + mQualityBad = false; +} + +Quality QcMFTDigitCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + bool isEmpty = true; + int adjacentCount = 0; + + for (auto& [moName, mo] : *moMap) { + + (void)moName; + + if (mFirstCall) { + mFirstCall = false; + readMaskedChips(mo); + getChipMapData(); + createMaskedChipsNames(); + } + + if (mo->getName() == "mDigitChipOccupancy") { + auto* hDigitChipOccupancy = dynamic_cast(mo->getObject()); + if (hDigitChipOccupancy == nullptr) { + ILOG(Error, Support) << "Could not cast mDigitChipOccupancy to TH1F." << ENDM; + return Quality::Null; + } + + for (int iBin = 0; iBin < hDigitChipOccupancy->GetNbinsX(); iBin++) { + if (hDigitChipOccupancy->GetBinContent(iBin + 1) == 0) { + hDigitChipOccupancy->Fill(937); // number of chips with zero digits stored in the overflow bin + } + } + } + + // checker for empty ladders + QcMFTUtilTables MFTTable; + for (int i = 0; i < 20; i++) { + if (mo->getName() == MFTTable.mDigitChipMapNames[i]) { + adjacentCount = 0; + auto* hDigitChipOccupancyMap = dynamic_cast(mo->getObject()); + if (hDigitChipOccupancyMap == nullptr) { + ILOG(Error, Support) << "Could not cast mDigitChipMap to TH2F." << ENDM; + return Quality::Null; + } + // loop over bins in occupancy maps + for (int iBinX = 0; iBinX < hDigitChipOccupancyMap->GetNbinsX(); iBinX++) { + int emptyValidChips = 0; + bool hasNonEmptyChip = false; + + for (int iBinY = 0; iBinY < hDigitChipOccupancyMap->GetNbinsY(); iBinY++) { + // Check if the bin contains data before further checks + if (hDigitChipOccupancyMap->GetBinContent(iBinX + 1, iBinY + 1) != 0) { + hasNonEmptyChip = true; + break; // Exit early if a non-empty chip is found (most of them should be non-empty) + } + + bool isMasked = false; + bool isOutsideAcc = false; + + // Check if chip is outside acceptance + for (int k = 0; k < 21; k++) { + if (mo->getName().find(MFTTable.mDigitChipMapNames[i]) != std::string::npos) { + if (iBinX + 1 == MFTTable.mBinX[i][k] && iBinY + 1 == MFTTable.mBinY[i][k]) { + isOutsideAcc = true; + break; + } + } + } + + // Check if chip is masked if it is in detector acceptance + if (!isOutsideAcc) { + for (int j = 0; j < mMaskedChips.size(); j++) { + if (mo->getName().find(mChipMapName[j]) != std::string::npos) { + int maskedX = hDigitChipOccupancyMap->GetXaxis()->FindBin(mX[mMaskedChips[j]]); + int maskedY = hDigitChipOccupancyMap->GetYaxis()->FindBin(mY[mMaskedChips[j]]); + if (iBinX + 1 == maskedX && iBinY + 1 == maskedY) { + isMasked = true; + break; // break the loop if you find the bin in the masked list + } + } + } + } + + // If chip is not masked and not outside acceptance, count it + if (!isMasked && !isOutsideAcc) { + emptyValidChips++; + } + } + + // Determine if column is empty + isEmpty = (emptyValidChips > 0 && !hasNonEmptyChip); + + if (isEmpty) { + mEmptyCount++; + adjacentCount++; + } else { + adjacentCount = 0; + } + + if (adjacentCount >= mLadderThresholdBad) { + mAdjacentLaddersEmpty = true; + } + } + } + } + if (mo->getName() == "mDigitOccupancySummary") { + auto* hDigitOccupancySummary = dynamic_cast(mo->getObject()); + if (hDigitOccupancySummary == nullptr) { + ILOG(Error, Support) << "Could not cast mDigitOccupancySummary to TH2F." << ENDM; + return Quality::Null; + } + // check if all FLPs are sending data + // with a missing FLP, the corresponding zones remain empty in the summary histogram + bool mEmptyZone = false; + for (int iBinX = 0; iBinX < hDigitOccupancySummary->GetNbinsX(); iBinX++) { + for (int iBinY = 0; iBinY < hDigitOccupancySummary->GetNbinsY(); iBinY++) { + if ((hDigitOccupancySummary->GetBinContent(iBinX + 1, iBinY + 1)) == 0) { + mEmptyZone = true; + } + } + } + + if (mAdjacentLaddersEmpty || mEmptyZone) { + result = Quality::Bad; + } else if (mEmptyCount >= mLadderThresholdMedium) { + result = Quality::Medium; + } else { + result = Quality::Good; + } + // We rely on 'mDigitOccupancySummary' being run after chip maps in the list of MOs in the config file + mEmptyCount = 0; + mAdjacentLaddersEmpty = false; + } + } + return result; +} + +void QcMFTDigitCheck::readMaskedChips(std::shared_ptr mo) +{ + long timestamp = mo->getValidity().getMin(); + map headers; + map filter; + auto calib = UserCodeInterface::retrieveConditionAny("MFT/Calib/DeadMap/", filter, timestamp); + if (calib == nullptr) { + ILOG(Error, Support) << "Could not retrieve deadmap from CCDB." << ENDM; + return; + } + for (int i = 0; i < calib->size(); i++) { + if (calib->isFullChipMasked(i)) { + mMaskedChips.push_back(i); + } + } +} + +void QcMFTDigitCheck::readNoiseMap(std::shared_ptr mo, long timestamp) +{ + map headers; + map filter; + auto calib = UserCodeInterface::retrieveConditionAny("MFT/Calib/NoiseMap/", filter, timestamp); + if (calib == nullptr) { + ILOG(Error, Support) << "Could not retrieve noisemap from CCDB." << ENDM; + return; + } + mNoisyPix.clear(); + for (int chipID = 0; chipID < 936; chipID++) { + for (int row = 0; row < 512; row++) { + for (int col = 0; col < 1024; col++) { + int noise = calib->getNoiseLevel(chipID, row, col); + if (!noise) { + noise = -1; + } + mNoisyPix.push_back(noise); + } + } + } +} + +void QcMFTDigitCheck::getChipMapData() +{ + const o2::itsmft::ChipMappingMFT mapMFT; + auto chipMapData = mapMFT.getChipMappingData(); + QcMFTUtilTables MFTTable; + + for (int i = 0; i < 936; i++) { + mHalf[i] = chipMapData[i].half; + mDisk[i] = chipMapData[i].disk; + mLayer[i] = chipMapData[i].layer; + mFace[i] = mLayer[i] % 2; + mZone[i] = chipMapData[i].zone; + mSensor[i] = chipMapData[i].localChipSWID; + mTransID[i] = chipMapData[i].cable; + mLadder[i] = MFTTable.mLadder[i]; + mX[i] = MFTTable.mX[i]; + mY[i] = MFTTable.mY[i]; + } +} + +void QcMFTDigitCheck::createMaskedChipsNames() +{ + for (int i = 0; i < mMaskedChips.size(); i++) { + mChipMapName.push_back(Form("ChipOccupancyMaps/Half_%d/Disk_%d/Face_%d/mDigitChipOccupancyMap", + mHalf[mMaskedChips[i]], mDisk[mMaskedChips[i]], mFace[mMaskedChips[i]])); + } +} + +void QcMFTDigitCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + mNCycles++; + + // print skull in maps to display dead chips + int nMaskedChips = mMaskedChips.size(); + for (int i = 0; i < nMaskedChips; i++) { + if (mo->getName().find(mChipMapName[i]) != std::string::npos) { + auto* hMap = dynamic_cast(mo->getObject()); + int binCx = hMap->GetXaxis()->FindBin(mX[mMaskedChips[i]]); + int binCy = hMap->GetYaxis()->FindBin(mY[mMaskedChips[i]]); + TLatex* tl = new TLatex(hMap->GetXaxis()->GetBinCenter(binCx), hMap->GetYaxis()->GetBinCenter(binCy), "N"); + tl->SetTextAlign(22); + tl->SetTextFont(142); + tl->SetTextSize(0.08); + hMap->GetListOfFunctions()->Add(tl); + tl->Draw(); + } + } + + QcMFTUtilTables MFTTable; + for (int iHalf = 0; iHalf < 2; iHalf++) { + for (int iDisk = 0; iDisk < 5; iDisk++) { + for (int iFace = 0; iFace < 2; iFace++) { + int idx = (iDisk * 2 + iFace) + (10 * iHalf); + if (mo->getName().find(MFTTable.mDigitChipMapNames[idx]) != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + for (int i = 0; i < 21; i++) { + int binX = MFTTable.mBinX[idx][i]; + int binY = MFTTable.mBinY[idx][i]; + if (binX == -1 || binY == -1) { + continue; + } + TBox* b = new TBox(h->GetXaxis()->GetBinLowEdge(binX), h->GetYaxis()->GetBinLowEdge(binY), h->GetXaxis()->GetBinWidth(binX) + h->GetXaxis()->GetBinLowEdge(binX), h->GetYaxis()->GetBinWidth(binY) + h->GetYaxis()->GetBinLowEdge(binY)); + b->SetFillStyle(4055); + b->SetFillColor(15); + h->GetListOfFunctions()->Add(b); + b->Draw(); + } + } + } + } + } + + if (mNoiseScan == 1) { + if (mNCycles == 1) { + long timestamp = mo->getValidity().getMin(); + readNoiseMap(mo, timestamp); + mOldNoisyPix = mNoisyPix; + } + + if (mNCycles == mNCyclesNoiseMap) { + long timestamp = o2::ccdb::getCurrentTimestamp(); + readNoiseMap(mo, timestamp); + mNewNoisyPix = mNoisyPix; + + for (int i = 0; i < mNewNoisyPix.size(); i++) { + if (mNewNoisyPix[i] == -1 && mOldNoisyPix[i] != -1) { + mDisNoisy++; + } + if (mNewNoisyPix[i] != -1 && mOldNoisyPix[i] == -1) { + mNewNoisy++; + } + if (mNewNoisyPix[i] != -1) { + mTotalNoisy++; + } + } + // quality of a noise scan + bool isTotalNoiseGood = (mNoiseTotalMediumMin <= mTotalNoisy) && (mTotalNoisy <= mNoiseTotalMediumMax); + bool isNewNoiseGood = (mNoiseNewMediumMin <= mNewNoisy) && (mNewNoisy <= mNoiseNewMediumMax); + bool isDisNoiseGood = (mNoiseDisMediumMin <= mDisNoisy) && (mDisNoisy <= mNoiseDisMediumMax); + bool isTotalNoiseMedium = (mNoiseTotalBadMin <= mTotalNoisy && mTotalNoisy < mNoiseTotalMediumMin) || (mNoiseTotalMediumMax < mTotalNoisy && mTotalNoisy <= mNoiseTotalBadMax); + bool isNewNoiseMedium = (mNewNoisy < mNoiseNewMediumMin) || (mNoiseNewMediumMax < mNewNoisy && mNewNoisy <= mNoiseNewBadMax); + bool isDisNoiseMedium = (mDisNoisy < mNoiseDisMediumMin) || (mNoiseDisMediumMax < mDisNoisy && mDisNoisy <= mNoiseDisBadMax); + bool isTotalNoiseBad = (mTotalNoisy < mNoiseTotalBadMin) || (mNoiseTotalBadMax < mTotalNoisy); + bool isNewNoiseBad = mNoiseNewBadMax < mNewNoisy; + bool isDisNoiseBad = mNoiseDisBadMax < mDisNoisy; + + if (isTotalNoiseGood && isNewNoiseGood && isDisNoiseGood) { + mQualityGood = true; + } + + if (isTotalNoiseMedium || isNewNoiseMedium || isDisNoiseMedium) { + mQualityMedium = true; + } + if (isTotalNoiseBad || isNewNoiseBad || isDisNoiseBad) { + mQualityBad = true; + mQualityMedium = false; + } + } + + if (mo->getName().find("mDigitChipOccupancy") != std::string::npos) { + auto* DigitOccupancy = dynamic_cast(mo->getObject()); + if (DigitOccupancy != nullptr) { + TLatex* tl_total = new TLatex(0.14, 0.87, Form("Total noisy pixels: %i", mTotalNoisy)); + TLatex* tl_new = new TLatex(0.14, 0.83, Form("New noisy pixels: %i", mNewNoisy)); + TLatex* tl_dis = new TLatex(0.14, 0.79, Form("Disappeared noisy pixels: %i", mDisNoisy)); + TPaveText* msg = new TPaveText(0.65, 0.9, 0.95, 1.0, "NDC NB"); + Color_t QualityColor; + if (mQualityGood) { + QualityColor = kGreen; + msg->AddText("No action needed"); + } else if (mQualityMedium) { + QualityColor = kOrange; + msg->AddText("Refer to QC instructions"); + } else if (mQualityBad) { + QualityColor = kRed; + msg->AddText("Call the on-call!"); + } else { + QualityColor = kBlue; + } + tl_total->SetNDC(); + tl_total->SetTextFont(42); + tl_total->SetTextSize(0.03); + tl_total->SetTextColor(QualityColor); + tl_new->SetNDC(); + tl_new->SetTextFont(42); + tl_new->SetTextSize(0.03); + tl_new->SetTextColor(QualityColor); + tl_dis->SetNDC(); + tl_dis->SetTextFont(42); + tl_dis->SetTextSize(0.03); + tl_dis->SetTextColor(QualityColor); + msg->SetFillColor(QualityColor); + // add it to the histo + DigitOccupancy->GetListOfFunctions()->Add(tl_total); + DigitOccupancy->GetListOfFunctions()->Add(tl_new); + DigitOccupancy->GetListOfFunctions()->Add(tl_dis); + DigitOccupancy->GetListOfFunctions()->Add(msg); + tl_total->Draw(); + tl_new->Draw(); + tl_dis->Draw(); + msg->Draw(); + } + } + } + + if (mo->getName().find("mDigitOccupancySummary") != std::string::npos) { + auto* hDigitOccupancySummary = dynamic_cast(mo->getObject()); + TPaveText* msg1 = new TPaveText(0.10, 0.9, 0.35, 1.0, "NDC NB"); + TPaveText* msg2 = new TPaveText(0.65, 0.9, 0.90, 1.0, "NDC NB"); + hDigitOccupancySummary->GetListOfFunctions()->Add(msg1); + hDigitOccupancySummary->GetListOfFunctions()->Add(msg2); + msg1->SetName(Form("%s_msg", mo->GetName())); + msg2->SetName(Form("%s_msg2", mo->GetName())); + if (checkResult == Quality::Good) { + LOG(info) << "Quality::Good"; + msg1->Clear(); + msg1->AddText("Quality GOOD"); + msg1->SetFillColor(kGreen); + msg1->Draw(); + msg2->Clear(); + msg2->AddText("No action needed"); + msg2->SetFillColor(kGreen); + msg2->Draw(); + } else if (checkResult == Quality::Medium) { + LOG(info) << "Quality::Medium"; + msg1->Clear(); + msg1->AddText("Quality MEDIUM"); + msg1->SetFillColor(kOrange); + msg1->Draw(); + msg2->Clear(); + msg2->AddText("Refer to QC instructions"); + msg2->SetFillColor(kOrange); + msg2->Draw(); + } else if (checkResult == Quality::Bad) { + LOG(info) << "Quality::Bad"; + msg1->Clear(); + msg1->AddText("Quality BAD"); + msg1->SetFillColor(kRed); + msg1->Draw(); + msg2->Clear(); + msg2->AddText("Call the on-call!"); + msg2->SetFillColor(kRed); + msg2->Draw(); + } + } +} +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/QcMFTDigitTask.cxx b/Modules/MFT/src/QcMFTDigitTask.cxx new file mode 100644 index 0000000000..e9c5b5902b --- /dev/null +++ b/Modules/MFT/src/QcMFTDigitTask.cxx @@ -0,0 +1,478 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTDigitTask.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// \author David Grund +/// + +// C++ +#include +#include +#include +// ROOT +#include +#include +#include +#include +// O2 +#include +#include +#include +#include +#include +#include +#include +#include +#include +// Quality Control +#include "QualityControl/QcInfoLogger.h" +#include "MFT/QcMFTDigitTask.h" +#include "MFT/QcMFTUtilTables.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" +#include "DetectorsBase/GRPGeomHelper.h" + +namespace o2::quality_control_modules::mft +{ + +QcMFTDigitTask::~QcMFTDigitTask() +{ + /* + not needed for unique pointers + */ +} + +void QcMFTDigitTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize QcMFTDigitTask" << ENDM; + + // loading custom parameters + if (auto param = mCustomParameters.find("FLP"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - FLP: " << param->second << ENDM; + mCurrentFLP = stoi(param->second); + } + + if (auto param = mCustomParameters.find("NoiseScan"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - NoiseScan: " << param->second << ENDM; + mNoiseScan = stoi(param->second); + } + + auto maxDigitROFSize = 5000; + if (auto param = mCustomParameters.find("maxDigitROFSize"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - maxDigitROFSize: " << param->second << ENDM; + maxDigitROFSize = stoi(param->second); + } + + auto maxDuration = 60.f; + if (auto param = mCustomParameters.find("maxDuration"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - maxDuration: " << param->second << ENDM; + maxDuration = stof(param->second); + } + + auto timeBinSize = 0.01f; + if (auto param = mCustomParameters.find("timeBinSize"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - timeBinSize: " << param->second << ENDM; + timeBinSize = stof(param->second); + } + + auto NofTimeBins = static_cast(maxDuration / timeBinSize); + + auto ROFLengthInBC = 198; + if (auto param = mCustomParameters.find("ROFLengthInBC"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ROFLengthInBC: " << param->second << ENDM; + ROFLengthInBC = stoi(param->second); + } + auto ROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / ROFLengthInBC; + + getChipMapData(); + + // reset arrays of vector and chip IDs + resetArrays(mVectorIndexOfChips, mOccupancyMapIndexOfChips, mVectorIndexOfOccupancyMaps); + + // Defining histograms + + mMergerTest = std::make_unique( + "mMergerTest", "Merger testing from different FLPs;FLP ID;# entries", 5, -0.5, 4.5); + mMergerTest->SetStats(0); + mMergerTest->GetXaxis()->SetBinLabel(1, "FLP 182"); + mMergerTest->GetXaxis()->SetBinLabel(2, "FLP 183"); + mMergerTest->GetXaxis()->SetBinLabel(3, "FLP 184"); + mMergerTest->GetXaxis()->SetBinLabel(4, "FLP 185"); + mMergerTest->GetXaxis()->SetBinLabel(5, "FLP 186"); + getObjectsManager()->startPublishing(mMergerTest.get()); + + mDigitChipOccupancy = std::make_unique( + "mDigitChipOccupancy", "Digit Chip Occupancy;Chip ID;# entries per orbit", 936, -0.5, 935.5, true); + mDigitChipOccupancy->SetStats(0); + getObjectsManager()->startPublishing(mDigitChipOccupancy.get()); + getObjectsManager()->setDisplayHint(mDigitChipOccupancy.get(), "hist"); + + mDigitDoubleColumnSensorIndices = std::make_unique( + "mDigitDoubleColumnSensorIndices", "Double Column vs Chip ID;Double Column;Chip ID", + 512, -0.5, 511.5, 936, -0.5, 935.5, true); + mDigitDoubleColumnSensorIndices->SetStats(0); + getObjectsManager()->startPublishing(mDigitDoubleColumnSensorIndices.get()); + getObjectsManager()->setDisplayHint(mDigitDoubleColumnSensorIndices.get(), "colz"); + + if (mNoiseScan == 1) { // to be executed only for special runs + mDigitChipStdDev = std::make_unique( + "mDigitChipStdDev", "Digit Chip Std Dev;Chip ID;Chip std dev", 936, -0.5, 935.5); + mDigitChipStdDev->SetStats(0); + getObjectsManager()->startPublishing(mDigitChipStdDev.get()); + } + + mDigitOccupancySummary = std::make_unique( + "mDigitOccupancySummary", "Digit Occupancy Summary;;", 10, -0.5, 9.5, 8, -0.5, 7.5, true); + mDigitOccupancySummary->GetXaxis()->SetBinLabel(1, "d0-f0"); + mDigitOccupancySummary->GetXaxis()->SetBinLabel(2, "d0-f1"); + mDigitOccupancySummary->GetXaxis()->SetBinLabel(3, "d1-f0"); + mDigitOccupancySummary->GetXaxis()->SetBinLabel(4, "d1-f1"); + mDigitOccupancySummary->GetXaxis()->SetBinLabel(5, "d2-f0"); + mDigitOccupancySummary->GetXaxis()->SetBinLabel(6, "d2-f1"); + mDigitOccupancySummary->GetXaxis()->SetBinLabel(7, "d3-f0"); + mDigitOccupancySummary->GetXaxis()->SetBinLabel(8, "d3-f1"); + mDigitOccupancySummary->GetXaxis()->SetBinLabel(9, "d4-f0"); + mDigitOccupancySummary->GetXaxis()->SetBinLabel(10, "d4-f1"); + mDigitOccupancySummary->GetYaxis()->SetBinLabel(1, "h0-z0"); + mDigitOccupancySummary->GetYaxis()->SetBinLabel(2, "h0-z1"); + mDigitOccupancySummary->GetYaxis()->SetBinLabel(3, "h0-z2"); + mDigitOccupancySummary->GetYaxis()->SetBinLabel(4, "h0-z3"); + mDigitOccupancySummary->GetYaxis()->SetBinLabel(5, "h1-z0"); + mDigitOccupancySummary->GetYaxis()->SetBinLabel(6, "h1-z1"); + mDigitOccupancySummary->GetYaxis()->SetBinLabel(7, "h1-z2"); + mDigitOccupancySummary->GetYaxis()->SetBinLabel(8, "h1-z3"); + mDigitOccupancySummary->SetStats(0); + getObjectsManager()->startPublishing(mDigitOccupancySummary.get()); + getObjectsManager()->setDisplayHint(mDigitOccupancySummary.get(), "colz"); + + mDigitsROFSize = std::make_unique("mDigitsROFSize", + "Distribution of the #digits per ROF; # digits per ROF; # entries", + QcMFTUtilTables::nROFBins, const_cast(QcMFTUtilTables::mROFBins), false); + mDigitsROFSize->SetStats(0); + getObjectsManager()->startPublishing(mDigitsROFSize.get()); + getObjectsManager()->setDisplayHint(mDigitsROFSize.get(), "hist logx logy"); + + mDigitsBC = std::make_unique("mDigitsBC", + "Digits per BC; BCid; # entries per orbit", + o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches, true); + mDigitsBC->SetMinimum(0.1); + getObjectsManager()->startPublishing(mDigitsBC.get()); + getObjectsManager()->setDisplayHint(mDigitsBC.get(), "hist"); + + // Chip hit maps + + QcMFTUtilTables MFTTable; + for (int iVectorOccupancyMapIndex = 0; iVectorOccupancyMapIndex < 4; iVectorOccupancyMapIndex++) { + // create only hit maps corresponding to the FLP + int iOccupancyMapIndex = getIndexChipOccupancyMap(iVectorOccupancyMapIndex); + + // generate folder and histogram name using the mapping table + TString folderName = ""; + TString histogramName = ""; + getNameOfChipOccupancyMap(folderName, histogramName, iOccupancyMapIndex); + + auto chiphitmap = std::make_unique( + folderName, histogramName, + MFTTable.mNumberOfBinsInOccupancyMaps[iOccupancyMapIndex][0], + MFTTable.mNumberOfBinsInOccupancyMaps[iOccupancyMapIndex][1], + MFTTable.mNumberOfBinsInOccupancyMaps[iOccupancyMapIndex][2], + MFTTable.mNumberOfBinsInOccupancyMaps[iOccupancyMapIndex][3], + MFTTable.mNumberOfBinsInOccupancyMaps[iOccupancyMapIndex][4], + MFTTable.mNumberOfBinsInOccupancyMaps[iOccupancyMapIndex][5], true); + chiphitmap->SetStats(0); + mDigitChipOccupancyMap.push_back(std::move(chiphitmap)); + getObjectsManager()->startPublishing(mDigitChipOccupancyMap[iVectorOccupancyMapIndex].get()); + getObjectsManager()->setDefaultDrawOptions(mDigitChipOccupancyMap[iVectorOccupancyMapIndex].get(), "colz"); + } + + // Pixel hit maps + int maxVectorIndex = mNumberOfPixelMapsPerFLP[mCurrentFLP] + mNumberOfPixelMapsPerFLP[4 - mCurrentFLP]; + for (int iVectorIndex = 0; iVectorIndex < maxVectorIndex; iVectorIndex++) { + // create only hit maps corresponding to the FLP + int iChipIndex = getChipIndexPixelOccupancyMap(iVectorIndex); + } + + if (mNoiseScan == 1) { // to be executed only for special runs + for (int iVectorIndex = 0; iVectorIndex < maxVectorIndex; iVectorIndex++) { + // create only hit maps corresponding to the FLP + int iChipIndex = getChipIndexPixelOccupancyMap(iVectorIndex); + // generate folder and histogram name using the mapping table + TString folderName = ""; + TString histogramName = ""; + getNameOfPixelOccupancyMap(folderName, histogramName, iChipIndex); + + auto pixelhitmap = std::make_unique( + folderName, histogramName, + maxBinXPixelOccupancyMap / binWidthPixelOccupancyMap, + minBinPixelOccupancyMap - shiftPixelOccupancyMap, + maxBinXPixelOccupancyMap - shiftPixelOccupancyMap, + maxBinYPixelOccupancyMap / binWidthPixelOccupancyMap, + minBinPixelOccupancyMap - shiftPixelOccupancyMap, + maxBinYPixelOccupancyMap - shiftPixelOccupancyMap); + pixelhitmap->SetStats(0); + mDigitPixelOccupancyMap.push_back(std::move(pixelhitmap)); + getObjectsManager()->startPublishing(mDigitPixelOccupancyMap[iVectorIndex].get()); + getObjectsManager()->setDefaultDrawOptions(mDigitPixelOccupancyMap[iVectorIndex].get(), "colz"); + } + } +} + +void QcMFTDigitTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + + // reset histograms + reset(); +} + +void QcMFTDigitTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void QcMFTDigitTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto mNOrbitsPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + + mMergerTest->Fill(mCurrentFLP); + mMergerTest->Fill(-1); // To test what happenes with the normalisation when merged. + // get the digits + const auto digits = ctx.inputs().get>("randomdigit"); + if (digits.empty()) { + return; + } + + // get the rofs + const auto rofs = ctx.inputs().get>("digitsrof"); + if (rofs.empty()) { + return; + } + + // get correct timing info of the first TF orbit + if (mRefOrbit == -1) { + mRefOrbit = ctx.services().get().firstTForbit; + } + + // fill the digits time histograms + for (const auto& rof : rofs) { + mDigitsROFSize->getNum()->Fill(rof.getNEntries()); + float seconds = orbitToSeconds(rof.getBCData().orbit, mRefOrbit) + rof.getBCData().bc * o2::constants::lhc::LHCBunchSpacingNS * 1e-9; + mDigitsBC->getNum()->Fill(rof.getBCData().bc, rof.getNEntries()); + } + + // fill the pixel hit maps and overview histograms + for (auto& oneDigit : digits) { + + int chipIndex = oneDigit.getChipIndex(); + + int vectorIndex = getVectorIndexPixelOccupancyMap(chipIndex); + if (vectorIndex < 0) // if the chip is not from wanted FLP, the array will give -1 + continue; + + // fill double column histogram + mDigitDoubleColumnSensorIndices->getNum()->Fill(oneDigit.getColumn() >> 1, oneDigit.getChipIndex()); + + // fill info into the summary histo + int xBin = mDisk[chipIndex] * 2 + mFace[chipIndex]; + int yBin = mZone[chipIndex] + mHalf[chipIndex] * 4; + mDigitOccupancySummary->getNum()->Fill(xBin, yBin); + + // fill pixel hit maps + if (mNoiseScan == 1) + mDigitPixelOccupancyMap[vectorIndex]->Fill(oneDigit.getColumn(), oneDigit.getRow()); + + // fill overview histograms + mDigitChipOccupancy->getNum()->Fill(chipIndex); + if (mNoiseScan == 1) + mDigitChipStdDev->SetBinContent(chipIndex + 1, mDigitPixelOccupancyMap[vectorIndex]->GetStdDev(1)); + + // fill integrated chip hit maps + int vectorOccupancyMapIndex = getVectorIndexChipOccupancyMap(chipIndex); + if (vectorOccupancyMapIndex < 0) + continue; + mDigitChipOccupancyMap[vectorOccupancyMapIndex]->getNum()->Fill(mX[chipIndex], mY[chipIndex]); + } + + // fill the denominators + mDigitChipOccupancy->getDen()->SetBinContent(1, mDigitChipOccupancy->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mDigitOccupancySummary->getDen()->SetBinContent(1, 1, mDigitOccupancySummary->getDen()->GetBinContent(1, 1) + mNOrbitsPerTF); + mDigitDoubleColumnSensorIndices->getDen()->SetBinContent(1, 1, mDigitDoubleColumnSensorIndices->getDen()->GetBinContent(1, 1) + mNOrbitsPerTF); + for (int i = 0; i < QcMFTUtilTables::nROFBins; i++) + mDigitsROFSize->getDen()->SetBinContent(i + 1, QcMFTUtilTables::mROFBins[i + 1] - QcMFTUtilTables::mROFBins[i]); + mDigitsBC->getDen()->SetBinContent(1, mDigitsBC->getDen()->GetBinContent(1) + mNOrbitsPerTF); + for (int i = 0; i < 4; i++) + mDigitChipOccupancyMap[i]->getDen()->SetBinContent(1, 1, mDigitChipOccupancyMap[i]->getDen()->GetBinContent(1, 1) + mNOrbitsPerTF); +} + +void QcMFTDigitTask::endOfCycle() +{ + // update all THRatios + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + + mDigitChipOccupancy->update(); + mDigitOccupancySummary->update(); + mDigitDoubleColumnSensorIndices->update(); + mDigitsROFSize->update(); + mDigitsBC->update(); + for (int i = 0; i < 4; i++) + mDigitChipOccupancyMap[i]->update(); +} + +void QcMFTDigitTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void QcMFTDigitTask::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + + mMergerTest->Reset(); + mDigitChipOccupancy->Reset(); + mDigitDoubleColumnSensorIndices->Reset(); + if (mNoiseScan == 1) + mDigitChipStdDev->Reset(); + mDigitOccupancySummary->Reset(); + mDigitsROFSize->Reset(); + mDigitsBC->Reset(); + for (int i = 0; i < 4; i++) + mDigitChipOccupancyMap[i]->Reset(); + if (mNoiseScan == 1) { + int maxVectorIndex = mNumberOfPixelMapsPerFLP[mCurrentFLP] + mNumberOfPixelMapsPerFLP[4 - mCurrentFLP]; + for (int j = 0; j < maxVectorIndex; j++) + mDigitPixelOccupancyMap[j]->Reset(); + } +} + +void QcMFTDigitTask::getNameOfChipOccupancyMap(TString& folderName, TString& histogramName, int iOccupancyMapIndex) +{ + folderName = Form("ChipOccupancyMaps/Half_%d/Disk_%d/Face_%d/mDigitChipOccupancyMap", + int(iOccupancyMapIndex / 10), int((iOccupancyMapIndex % 10) / 2), (iOccupancyMapIndex % 10) % 2); + + histogramName = Form("Digit Chip Map h%d-d%d-f%d;x (cm);y (cm)", + int(iOccupancyMapIndex / 10), int((iOccupancyMapIndex % 10) / 2), (iOccupancyMapIndex % 10) % 2); +} + +void QcMFTDigitTask::getNameOfPixelOccupancyMap(TString& folderName, TString& histogramName, int iChipIndex) +{ + folderName = Form("PixelOccupancyMaps/Half_%d/Disk_%d/Face_%d/mDigitPixelOccupancyMap-z%d-l%d-s%d-tr%d", + mHalf[iChipIndex], mDisk[iChipIndex], mFace[iChipIndex], mZone[iChipIndex], + mLadder[iChipIndex], mSensor[iChipIndex], mTransID[iChipIndex]); + + histogramName = Form("Pixel Map h%d-d%d-f%d-z%d-l%d-s%d-tr%d", + mHalf[iChipIndex], mDisk[iChipIndex], mFace[iChipIndex], mZone[iChipIndex], + mLadder[iChipIndex], mSensor[iChipIndex], mTransID[iChipIndex]); +} + +void QcMFTDigitTask::getChipMapData() +{ + const o2::itsmft::ChipMappingMFT mapMFT; + auto chipMapData = mapMFT.getChipMappingData(); + QcMFTUtilTables MFTTable; + + for (int i = 0; i < 936; i++) { + mHalf[i] = chipMapData[i].half; + mDisk[i] = chipMapData[i].disk; + mLayer[i] = chipMapData[i].layer; + mFace[i] = mLayer[i] % 2; + mZone[i] = chipMapData[i].zone; + mSensor[i] = chipMapData[i].localChipSWID; + mTransID[i] = chipMapData[i].cable; + mLadder[i] = MFTTable.mLadder[i]; + mX[i] = MFTTable.mX[i]; + mY[i] = MFTTable.mY[i]; + } +} + +int QcMFTDigitTask::getVectorIndexChipOccupancyMap(int chipIndex) +{ + + int occupancyMapIndex = mOccupancyMapIndexOfChips[chipIndex]; + + int vectorOccupancyMapIndex = mVectorIndexOfOccupancyMaps[occupancyMapIndex]; + + return vectorOccupancyMapIndex; +} + +int QcMFTDigitTask::getIndexChipOccupancyMap(int vectorChipOccupancyMapIndex) +{ + int vectorOccupancyMapHalf = int(vectorChipOccupancyMapIndex / 2); + + int occupancyMapIndex; + if (vectorOccupancyMapHalf == 0) { + occupancyMapIndex = vectorChipOccupancyMapIndex + mCurrentFLP * 2; + } else { + occupancyMapIndex = (vectorChipOccupancyMapIndex % 2) + (4 - mCurrentFLP) * 2 + numberOfOccupancyMaps / 2; + } + // fill the array of vector ID for corresponding hit map + // (opposite matching) + mVectorIndexOfOccupancyMaps[occupancyMapIndex] = vectorChipOccupancyMapIndex; + + return occupancyMapIndex; +} + +int QcMFTDigitTask::getVectorIndexPixelOccupancyMap(int chipIndex) +{ + + int vectorIndex = mVectorIndexOfChips[chipIndex]; + + return vectorIndex; +} + +int QcMFTDigitTask::getChipIndexPixelOccupancyMap(int vectorIndex) +{ + int vectorHalf = 0; + if (int(vectorIndex / mNumberOfPixelMapsPerFLP[mCurrentFLP]) < 1) { + vectorHalf = 0; + } else { + vectorHalf = 1; + } + + int chipIndex = vectorIndex + vectorHalf * (-mNumberOfPixelMapsPerFLP[mCurrentFLP] + numberOfChips / 2); + + int maxDisk = 0; + if (vectorHalf == 0) { + maxDisk = mCurrentFLP; + } else { + maxDisk = 4 - mCurrentFLP; + } + + for (int idisk = 0; idisk < maxDisk; idisk++) + chipIndex = chipIndex + mNumberOfPixelMapsPerFLP[idisk]; + + // fill the array of vector index for corresponding chipIndex + // (opposite matching) + mVectorIndexOfChips[chipIndex] = vectorIndex; + // fill the array of hit map ID for corresponding chipIndex + mOccupancyMapIndexOfChips[chipIndex] = mLayer[chipIndex] + mHalf[chipIndex] * numberOfOccupancyMaps / 2; + + return chipIndex; +} + +void QcMFTDigitTask::resetArrays(int* array1, int* array2, int* array3) +{ + + for (int iChip = 0; iChip < numberOfChips; iChip++) { + array1[iChip] = -1; + array2[iChip] = -1; + } + + for (int iMap = 0; iMap < numberOfOccupancyMaps; iMap++) + array3[iMap] = -1; +} + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/QcMFTOccupancyTrend.cxx b/Modules/MFT/src/QcMFTOccupancyTrend.cxx new file mode 100644 index 0000000000..dabdf10e6e --- /dev/null +++ b/Modules/MFT/src/QcMFTOccupancyTrend.cxx @@ -0,0 +1,47 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTOccupancyTrend.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// + +#include +#include "MFT/QcMFTOccupancyTrend.h" + +namespace o2::quality_control_modules::mft +{ + +void* QcMFTOccupancyTrend::getBranchAddress() +{ + return &mStats; +} + +const char* QcMFTOccupancyTrend::getBranchLeafList() +{ + return "binContentOverflow:mean/D:stddev:entries"; +} + +void QcMFTOccupancyTrend::update(TObject* obj) +{ + auto histo = dynamic_cast(obj); + if (histo) { + mStats.binContentOverflow = histo->GetBinContent(937); + mStats.entries = histo->GetEntries(); + mStats.stddev = histo->GetStdDev(); + mStats.mean = histo->GetMean(); + } +} + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/QcMFTReadoutCheck.cxx b/Modules/MFT/src/QcMFTReadoutCheck.cxx new file mode 100644 index 0000000000..c0e0cf474d --- /dev/null +++ b/Modules/MFT/src/QcMFTReadoutCheck.cxx @@ -0,0 +1,348 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTReadoutCheck.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova + +// Fair +#include +// ROOT +#include +#include +#include +#include + +// Quality Control +#include "MFT/QcMFTReadoutCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace std; + +namespace o2::quality_control_modules::mft +{ + +void QcMFTReadoutCheck::configure() +{ + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + if (auto param = mCustomParameters.find("FaultThresholdMedium"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - FaultThresholdMedium: " << param->second << ENDM; + mFaultThresholdMedium = stoi(param->second); + } + if (auto param = mCustomParameters.find("FaultThresholdBad"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - FaultThresholdBad: " << param->second << ENDM; + mFaultThresholdBad = stoi(param->second); + } + if (auto param = mCustomParameters.find("ErrorThresholdMedium"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - ErrorThresholdMedium: " << param->second << ENDM; + mErrorThresholdMedium = stoi(param->second); + } + if (auto param = mCustomParameters.find("ErrorThresholdBad"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - ErrorThresholdBad: " << param->second << ENDM; + mErrorThresholdBad = stoi(param->second); + } + if (auto param = mCustomParameters.find("WarningThresholdMedium"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - WarningThresholdMedium: " << param->second << ENDM; + mWarningThresholdMedium = stoi(param->second); + } + if (auto param = mCustomParameters.find("WarningThresholdBad"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - WarningThresholdBad: " << param->second << ENDM; + mWarningThresholdBad = stoi(param->second); + } +} + +Quality QcMFTReadoutCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + + (void)moName; + + if (mo->getName() == "mSummaryChipOk") { + auto* hOK = dynamic_cast(mo->getObject()); + if (hOK == nullptr) { + ILOG(Error, Support) << "Could not cast mSummaryChipOK to TH1F." << ENDM; + return Quality::Null; + } + float den = hOK->GetBinContent(0); // normalisation stored in the uderflow bin + + for (int iBin = 0; iBin < hOK->GetNbinsX(); iBin++) { + float num = hOK->GetBinContent(iBin + 1); + float ratio = (den > 0) ? (num / den) : 0.0; + hOK->SetBinContent(iBin + 1, ratio); + } + } + + if (mo->getName() == "mSummaryChipFault") { + resetVector(mVectorOfFaultBins); + auto* hFault = dynamic_cast(mo->getObject()); + if (hFault == nullptr) { + ILOG(Error, Support) << "Could not cast mSummaryChipFault to TH1F." << ENDM; + return Quality::Null; + } + float den = hFault->GetBinContent(0); // normalisation stored in the uderflow bin + + for (int iBin = 0; iBin < hFault->GetNbinsX(); iBin++) { + if (hFault->GetBinContent(iBin + 1) != 0) { + mVectorOfFaultBins.push_back(iBin + 1); + hFault->Fill(937); // number of chips with problem stored in the overflow bin + } + float num = hFault->GetBinContent(iBin + 1); + float ratio = (den > 0) ? (num / den) : 0.0; + hFault->SetBinContent(iBin + 1, ratio); + } + result = checkQualityStatus(hFault, mVectorOfFaultBins); + } + + if (mo->getName() == "mSummaryChipError") { + resetVector(mVectorOfErrorBins); + auto* hError = dynamic_cast(mo->getObject()); + if (hError == nullptr) { + ILOG(Error, Support) << "Could not cast mSummaryChipError to TH1F." << ENDM; + return Quality::Null; + } + float den = hError->GetBinContent(0); // normalisation stored in the uderflow bin + + for (int iBin = 0; iBin < hError->GetNbinsX(); iBin++) { + if (hError->GetBinContent(iBin + 1) != 0) { + mVectorOfErrorBins.push_back(iBin + 1); + hError->Fill(937); // number of chips with problem stored in the overflow bin + } + float num = hError->GetBinContent(iBin + 1); + float ratio = (den > 0) ? (num / den) : 0.0; + hError->SetBinContent(iBin + 1, ratio); + } + result = checkQualityStatus(hError, mVectorOfErrorBins); + } + + if (mo->getName() == "mSummaryChipWarning") { + resetVector(mVectorOfWarningBins); + auto* hWarning = dynamic_cast(mo->getObject()); + if (hWarning == nullptr) { + ILOG(Error, Support) << "Could not cast mSummaryChipWarning to TH1F." << ENDM; + return Quality::Null; + } + float den = hWarning->GetBinContent(0); // normalisation stored in the uderflow bin + + for (int iBin = 0; iBin < hWarning->GetNbinsX(); iBin++) { + if (hWarning->GetBinContent(iBin + 1) != 0) { + mVectorOfWarningBins.push_back(iBin + 1); + hWarning->Fill(937); // number of chips with problem stored in the overflow bin + } + float num = hWarning->GetBinContent(iBin + 1); + float ratio = (den > 0) ? (num / den) : 0.0; + hWarning->SetBinContent(iBin + 1, ratio); + } + result = checkQualityStatus(hWarning, mVectorOfWarningBins); + } + + if (mo->getName() == "mZoneSummaryChipWarning") { + auto* hWarningSummary = dynamic_cast(mo->getObject()); + if (hWarningSummary == nullptr) { + ILOG(Error, Support) << "Could not cast mZoneSummaryChipWarning to TH2F." << ENDM; + return Quality::Null; + } + + float den = hWarningSummary->GetBinContent(0, 0); // normalisation stored in the underflow bin + + for (int iBinX = 0; iBinX < hWarningSummary->GetNbinsX(); iBinX++) { + for (int iBinY = 0; iBinY < hWarningSummary->GetNbinsY(); iBinY++) { + float num = hWarningSummary->GetBinContent(iBinX + 1, iBinY + 1); + float ratio = (den > 0) ? (num / den) : 0.0; + hWarningSummary->SetBinContent(iBinX + 1, iBinY + 1, ratio); + } + } + } + + if (mo->getName() == "mZoneSummaryChipError") { + auto* hErrorSummary = dynamic_cast(mo->getObject()); + if (hErrorSummary == nullptr) { + ILOG(Error, Support) << "Could not cast mZoneSummaryChipError to TH2F." << ENDM; + return Quality::Null; + } + + float den = hErrorSummary->GetBinContent(0, 0); // normalisation stored in the underflow bin + + for (int iBinX = 0; iBinX < hErrorSummary->GetNbinsX(); iBinX++) { + for (int iBinY = 0; iBinY < hErrorSummary->GetNbinsY(); iBinY++) { + float num = hErrorSummary->GetBinContent(iBinX + 1, iBinY + 1); + float ratio = (den > 0) ? (num / den) : 0.0; + hErrorSummary->SetBinContent(iBinX + 1, iBinY + 1, ratio); + } + } + } + + if (mo->getName() == "mZoneSummaryChipFault") { + auto* hFaultSummary = dynamic_cast(mo->getObject()); + if (hFaultSummary == nullptr) { + ILOG(Error, Support) << "Could not cast mZoneSummaryChipFault to TH2F." << ENDM; + return Quality::Null; + } + + float den = hFaultSummary->GetBinContent(0, 0); // normalisation stored in the underflow bin + + for (int iBinX = 0; iBinX < hFaultSummary->GetNbinsX(); iBinX++) { + for (int iBinY = 0; iBinY < hFaultSummary->GetNbinsY(); iBinY++) { + float num = hFaultSummary->GetBinContent(iBinX + 1, iBinY + 1); + float ratio = (den > 0) ? (num / den) : 0.0; + hFaultSummary->SetBinContent(iBinX + 1, iBinY + 1, ratio); + } + } + } + + } // end of loop over MO + + return result; +} + +void QcMFTReadoutCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "mSummaryChipFault") { + auto* hFault = dynamic_cast(mo->getObject()); + writeMessages(hFault, mVectorOfFaultBins, checkResult); + } + + if (mo->getName() == "mSummaryChipError") { + auto* hError = dynamic_cast(mo->getObject()); + writeMessages(hError, mVectorOfErrorBins, checkResult); + } + + if (mo->getName() == "mSummaryChipWarning") { + auto* hWarning = dynamic_cast(mo->getObject()); + writeMessages(hWarning, mVectorOfWarningBins, checkResult); + } +} + +void QcMFTReadoutCheck::drawLatex(TH1F* histo, double xmin, double ymin, Color_t color, TString text, + float tsize = 0.025, Font_t tfont = 42) +{ + + TLatex* tl = new TLatex(xmin, ymin, Form("%s", text.Data())); + tl->SetNDC(); + tl->SetTextFont(tfont); + tl->SetTextSize(tsize); + tl->SetTextColor(color); + + // add it to the histo + histo->GetListOfFunctions()->Add(tl); + tl->Draw(); +} + +void QcMFTReadoutCheck::resetVector(std::vector& vector) +{ + vector.clear(); +} + +Quality QcMFTReadoutCheck::checkQualityStatus(TH1F* histo, std::vector& vector) +{ + Quality result = Quality::Good; + + if (strcmp(histo->GetName(), "mSummaryChipFault") == 0) { + if (vector.size() > mFaultThresholdBad) + result = Quality::Bad; + if (vector.size() > mFaultThresholdMedium && vector.size() <= mFaultThresholdBad) + result = Quality::Medium; + if (vector.size() <= mFaultThresholdMedium) + result = Quality::Good; + } + if (strcmp(histo->GetName(), "mSummaryChipError") == 0) { + if (vector.size() > mErrorThresholdBad) { + result = Quality::Bad; + } + if (vector.size() > mErrorThresholdMedium && vector.size() <= mErrorThresholdBad) { + result = Quality::Medium; + } + if (vector.size() <= mErrorThresholdMedium) { + result = Quality::Good; + } + } + if (strcmp(histo->GetName(), "mSummaryChipWarning") == 0) { + if (vector.size() > mWarningThresholdBad) { + result = Quality::Bad; + } + if (vector.size() > mWarningThresholdMedium && vector.size() <= mWarningThresholdBad) { + result = Quality::Medium; + } + if (vector.size() <= mWarningThresholdMedium) { + result = Quality::Good; + } + } + + return result; +} + +void QcMFTReadoutCheck::writeMessages(TH1F* histo, std::vector& vector, Quality checkResult) +{ + if (checkResult == Quality::Good) { + histo->SetFillColor(kGreen + 2); + histo->SetLineColor(kGreen + 2); + if (strcmp(histo->GetName(), "mSummaryChipFault") == 0) { + drawLatex(histo, 0.14, 0.87, kGreen + 2, "Quality is good.", 0.04); + drawLatex(histo, 0.14, 0.87 - 0.025, kGreen + 2, Form("%lu chips in Fault as expected.", vector.size())); + } + if (strcmp(histo->GetName(), "mSummaryChipError") == 0) { + drawLatex(histo, 0.14, 0.87, kGreen + 2, "Quality is good.", 0.04); + drawLatex(histo, 0.14, 0.87 - 0.025, kGreen + 2, Form("%lu chips in Error as expected.", vector.size())); + }; + if (strcmp(histo->GetName(), "mSummaryChipWarning") == 0) { + drawLatex(histo, 0.14, 0.87, kGreen + 2, "Quality is good.", 0.04); + drawLatex(histo, 0.14, 0.87 - 0.025, kGreen + 2, Form("%lu chips in Warning as expected.", vector.size())); + } + } else if (checkResult == Quality::Medium) { + histo->SetFillColor(kOrange + 7); + histo->SetLineColor(kOrange + 7); + if (strcmp(histo->GetName(), "mSummaryChipFault") == 0) { + drawLatex(histo, 0.14, 0.87, kOrange + 7, "Quality is Medium.", 0.04); + drawLatex(histo, 0.14, 0.87 - 0.025, kOrange + 7, Form("%lu chips in Fault. Write a logbook entry tagging MFT.", vector.size())); + } + if (strcmp(histo->GetName(), "mSummaryChipError") == 0) { + drawLatex(histo, 0.14, 0.87, kOrange + 7, "Quality is Medium.", 0.04); + drawLatex(histo, 0.14, 0.87 - 0.025, kOrange + 7, Form("%lu chips in Error. Write a logbook entry tagging MFT.", vector.size())); + } + if (strcmp(histo->GetName(), "mSummaryChipWarning") == 0) { + drawLatex(histo, 0.14, 0.87, kOrange + 7, "Quality is Medium.", 0.04); + drawLatex(histo, 0.14, 0.87 - 0.025, kOrange + 7, Form("%lu chips in Warning. Write a logbook entry tagging MFT.", vector.size())); + } + } else if (checkResult == Quality::Bad) { + histo->SetFillColor(kRed + 1); + histo->SetLineColor(kRed + 1); + if (strcmp(histo->GetName(), "mSummaryChipFault") == 0) { + drawLatex(histo, 0.14, 0.87, kRed + 1, "Quality is Bad.", 0.04); + drawLatex(histo, 0.14, 0.87 - 0.025, kRed + 1, Form("%lu chips in Fault. Inform the MFT oncall immediately!.", vector.size())); + } + if (strcmp(histo->GetName(), "mSummaryChipError") == 0) { + drawLatex(histo, 0.14, 0.87, kRed + 1, "Quality is Bad.", 0.04); + drawLatex(histo, 0.14, 0.87 - 0.025, kRed + 1, Form("%lu chips in Error. Inform the MFT oncall immediately!.", vector.size())); + } + if (strcmp(histo->GetName(), "mSummaryChipWarning") == 0) { + drawLatex(histo, 0.14, 0.87, kRed + 1, "Quality is Bad.", 0.04); + drawLatex(histo, 0.14, 0.87 - 0.025, kRed + 1, Form("%lu chips in Warning. Inform the MFT oncall immediately!.", vector.size())); + } + } + histo->SetMaximum(histo->GetMaximum() * 1.7); + int midBinIteration = (vector.size() < 10) ? vector.size() : 10; + for (int iBin = 0; iBin < midBinIteration; iBin++) { + drawLatex(histo, 0.15, 0.85 - 0.025 - iBin * 0.025, kBlack, Form("%s", histo->GetXaxis()->GetBinLabel(vector[iBin]))); + } + int maxBinIteration = (vector.size() < 20) ? vector.size() : 20; + for (int iBin = midBinIteration; iBin < maxBinIteration; iBin++) { + drawLatex(histo, 0.55, 0.85 - 0.025 - (iBin - midBinIteration) * 0.025, kBlack, Form("%s", histo->GetXaxis()->GetBinLabel(vector[iBin]))); + } +} + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/QcMFTReadoutTask.cxx b/Modules/MFT/src/QcMFTReadoutTask.cxx new file mode 100644 index 0000000000..ae004d3a5f --- /dev/null +++ b/Modules/MFT/src/QcMFTReadoutTask.cxx @@ -0,0 +1,320 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTReadoutTask.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// + +// ROOT +#include +#include +// O2 +#include +#include +#include + +// Quality Control +#include "QualityControl/QcInfoLogger.h" +#include "MFT/QcMFTReadoutTask.h" +#include "MFT/QcMFTUtilTables.h" + +using namespace o2::framework; +using namespace o2::header; + +namespace o2::quality_control_modules::mft +{ + +QcMFTReadoutTask::~QcMFTReadoutTask() +{ + /* + not needed for unique pointers + */ +} + +void QcMFTReadoutTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize QcMFTReadoutTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // create the index to link a RU+lane to a chip + //============================================== + generateChipIndex(); + const o2::itsmft::ChipMappingMFT mapMFT; // MFT maps + auto chipMapData = mapMFT.getChipMappingData(); + + // Defining chip summary histograms + //============================================== + int nChips = 936; + // --> error + mSummaryChipError = std::make_unique("mSummaryChipError", "Summary of chips in Error", nChips, -0.5, nChips - 0.5); + getObjectsManager()->startPublishing(mSummaryChipError.get()); + mSummaryChipError->GetYaxis()->SetTitle("#Entries per DDW"); + mSummaryChipError->SetStats(0); + // --> fault + mSummaryChipFault = std::make_unique("mSummaryChipFault", "Summary of chips in Fault", nChips, -0.5, nChips - 0.5); + getObjectsManager()->startPublishing(mSummaryChipFault.get()); + mSummaryChipFault->GetYaxis()->SetTitle("#Entries per DDW"); + mSummaryChipFault->SetStats(0); + // --> warning + mSummaryChipWarning = std::make_unique("mSummaryChipWarning", "Summary of chips in Warning", nChips, -0.5, nChips - 0.5); + getObjectsManager()->startPublishing(mSummaryChipWarning.get()); + mSummaryChipWarning->GetYaxis()->SetTitle("#Entries per DDW"); + mSummaryChipWarning->SetStats(0); + // --> ok + mSummaryChipOk = std::make_unique("mSummaryChipOk", "Summary of chips in OK", nChips, -0.5, nChips - 0.5); + getObjectsManager()->startPublishing(mSummaryChipOk.get()); + mSummaryChipOk->GetYaxis()->SetTitle("#Entries per DDW"); + mSummaryChipOk->SetStats(0); + + for (int i = 0; i < nChips; i++) { + int face = (chipMapData[i].layer) % 2; + mSummaryChipError->GetXaxis() + ->SetBinLabel(i + 1, Form("Chip %i:h%d-d%d-f%d-z%d-tr%d", i, chipMapData[i].half, + chipMapData[i].disk, face, chipMapData[i].zone, chipMapData[i].cable)); + mSummaryChipFault->GetXaxis() + ->SetBinLabel(i + 1, Form("Chip %i:h%d-d%d-f%d-z%d-tr%d", i, chipMapData[i].half, + chipMapData[i].disk, face, chipMapData[i].zone, chipMapData[i].cable)); + mSummaryChipWarning->GetXaxis() + ->SetBinLabel(i + 1, Form("Chip %i:h%d-d%d-f%d-z%d-tr%d", i, chipMapData[i].half, + chipMapData[i].disk, face, chipMapData[i].zone, chipMapData[i].cable)); + mSummaryChipOk->GetXaxis() + ->SetBinLabel(i + 1, Form("Chip %i:h%d-d%d-f%d-z%d-tr%d", i, chipMapData[i].half, + chipMapData[i].disk, face, chipMapData[i].zone, chipMapData[i].cable)); + } + + // Defining summary histograms per zone + mZoneSummaryChipWarning = std::make_unique( + "mZoneSummaryChipWarning", + "Summary of chips in warning per zone;;", + 10, -0.5, 9.5, 8, -0.5, 7.5); + mZoneSummaryChipWarning->GetXaxis()->SetBinLabel(1, "d0-f0"); + mZoneSummaryChipWarning->GetXaxis()->SetBinLabel(2, "d0-f1"); + mZoneSummaryChipWarning->GetXaxis()->SetBinLabel(3, "d1-f0"); + mZoneSummaryChipWarning->GetXaxis()->SetBinLabel(4, "d1-f1"); + mZoneSummaryChipWarning->GetXaxis()->SetBinLabel(5, "d2-f0"); + mZoneSummaryChipWarning->GetXaxis()->SetBinLabel(6, "d2-f1"); + mZoneSummaryChipWarning->GetXaxis()->SetBinLabel(7, "d3-f0"); + mZoneSummaryChipWarning->GetXaxis()->SetBinLabel(8, "d3-f1"); + mZoneSummaryChipWarning->GetXaxis()->SetBinLabel(9, "d4-f0"); + mZoneSummaryChipWarning->GetXaxis()->SetBinLabel(10, "d4-f1"); + mZoneSummaryChipWarning->GetYaxis()->SetBinLabel(1, "h0-z0"); + mZoneSummaryChipWarning->GetYaxis()->SetBinLabel(2, "h0-z1"); + mZoneSummaryChipWarning->GetYaxis()->SetBinLabel(3, "h0-z2"); + mZoneSummaryChipWarning->GetYaxis()->SetBinLabel(4, "h0-z3"); + mZoneSummaryChipWarning->GetYaxis()->SetBinLabel(5, "h1-z0"); + mZoneSummaryChipWarning->GetYaxis()->SetBinLabel(6, "h1-z1"); + mZoneSummaryChipWarning->GetYaxis()->SetBinLabel(7, "h1-z2"); + mZoneSummaryChipWarning->GetYaxis()->SetBinLabel(8, "h1-z3"); + mZoneSummaryChipWarning->SetStats(0); + getObjectsManager()->startPublishing(mZoneSummaryChipWarning.get()); + getObjectsManager()->setDefaultDrawOptions(mZoneSummaryChipWarning.get(), "colz"); + + mZoneSummaryChipError = std::make_unique( + "mZoneSummaryChipError", + "Summary of chips in error per zone;;", + 10, -0.5, 9.5, 8, -0.5, 7.5); + mZoneSummaryChipError->GetXaxis()->SetBinLabel(1, "d0-f0"); + mZoneSummaryChipError->GetXaxis()->SetBinLabel(2, "d0-f1"); + mZoneSummaryChipError->GetXaxis()->SetBinLabel(3, "d1-f0"); + mZoneSummaryChipError->GetXaxis()->SetBinLabel(4, "d1-f1"); + mZoneSummaryChipError->GetXaxis()->SetBinLabel(5, "d2-f0"); + mZoneSummaryChipError->GetXaxis()->SetBinLabel(6, "d2-f1"); + mZoneSummaryChipError->GetXaxis()->SetBinLabel(7, "d3-f0"); + mZoneSummaryChipError->GetXaxis()->SetBinLabel(8, "d3-f1"); + mZoneSummaryChipError->GetXaxis()->SetBinLabel(9, "d4-f0"); + mZoneSummaryChipError->GetXaxis()->SetBinLabel(10, "d4-f1"); + mZoneSummaryChipError->GetYaxis()->SetBinLabel(1, "h0-z0"); + mZoneSummaryChipError->GetYaxis()->SetBinLabel(2, "h0-z1"); + mZoneSummaryChipError->GetYaxis()->SetBinLabel(3, "h0-z2"); + mZoneSummaryChipError->GetYaxis()->SetBinLabel(4, "h0-z3"); + mZoneSummaryChipError->GetYaxis()->SetBinLabel(5, "h1-z0"); + mZoneSummaryChipError->GetYaxis()->SetBinLabel(6, "h1-z1"); + mZoneSummaryChipError->GetYaxis()->SetBinLabel(7, "h1-z2"); + mZoneSummaryChipError->GetYaxis()->SetBinLabel(8, "h1-z3"); + mZoneSummaryChipError->SetStats(0); + getObjectsManager()->startPublishing(mZoneSummaryChipError.get()); + getObjectsManager()->setDefaultDrawOptions(mZoneSummaryChipError.get(), "colz"); + + mZoneSummaryChipFault = std::make_unique( + "mZoneSummaryChipFault", + "Summary of chips in fault per zone;;", + 10, -0.5, 9.5, 8, -0.5, 7.5); + mZoneSummaryChipFault->GetXaxis()->SetBinLabel(1, "d0-f0"); + mZoneSummaryChipFault->GetXaxis()->SetBinLabel(2, "d0-f1"); + mZoneSummaryChipFault->GetXaxis()->SetBinLabel(3, "d1-f0"); + mZoneSummaryChipFault->GetXaxis()->SetBinLabel(4, "d1-f1"); + mZoneSummaryChipFault->GetXaxis()->SetBinLabel(5, "d2-f0"); + mZoneSummaryChipFault->GetXaxis()->SetBinLabel(6, "d2-f1"); + mZoneSummaryChipFault->GetXaxis()->SetBinLabel(7, "d3-f0"); + mZoneSummaryChipFault->GetXaxis()->SetBinLabel(8, "d3-f1"); + mZoneSummaryChipFault->GetXaxis()->SetBinLabel(9, "d4-f0"); + mZoneSummaryChipFault->GetXaxis()->SetBinLabel(10, "d4-f1"); + mZoneSummaryChipFault->GetYaxis()->SetBinLabel(1, "h0-z0"); + mZoneSummaryChipFault->GetYaxis()->SetBinLabel(2, "h0-z1"); + mZoneSummaryChipFault->GetYaxis()->SetBinLabel(3, "h0-z2"); + mZoneSummaryChipFault->GetYaxis()->SetBinLabel(4, "h0-z3"); + mZoneSummaryChipFault->GetYaxis()->SetBinLabel(5, "h1-z0"); + mZoneSummaryChipFault->GetYaxis()->SetBinLabel(6, "h1-z1"); + mZoneSummaryChipFault->GetYaxis()->SetBinLabel(7, "h1-z2"); + mZoneSummaryChipFault->GetYaxis()->SetBinLabel(8, "h1-z3"); + mZoneSummaryChipFault->SetStats(0); + getObjectsManager()->startPublishing(mZoneSummaryChipFault.get()); + getObjectsManager()->setDefaultDrawOptions(mZoneSummaryChipFault.get(), "colz"); + + // get map data for summary histograms per zone + getChipMapData(); +} + +void QcMFTReadoutTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + + // reset histograms + reset(); +} + +void QcMFTReadoutTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void QcMFTReadoutTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + // get the input + DPLRawParser parser(ctx.inputs()); + + // loop over input + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + // get the header + auto rdh = reinterpret_cast(it.raw()); + auto feeID = o2::raw::RDHUtils::getFEEID(rdh); + // get detector field + uint32_t summaryLaneStatus = o2::raw::RDHUtils::getDetectorField(rdh); + // check if last rdh in HBF and get the DDW word + if ((int)(o2::raw::RDHUtils::getStop(rdh)) && it.size()) { + auto const* ddw = reinterpret_cast(it.data()); + uint16_t ddwIndex = ddw->indexWord.indexBits.id; + if (ddwIndex == 0xE4) { // it is a diagnostic data word + // fill histogram bin with #DDW + mSummaryChipOk->Fill(-1); // counter stored in the underflow bin! + mSummaryChipWarning->Fill(-1); // counter stored in the underflow bin! + mSummaryChipError->Fill(-1); // counter stored in the underflow bin! + mSummaryChipFault->Fill(-1); // counter stored in the underflow bin! + mZoneSummaryChipWarning->Fill(-1, -1); // counter stored in the underflow bin! + mZoneSummaryChipError->Fill(-1, -1); // counter stored in the underflow bin! + mZoneSummaryChipFault->Fill(-1, -1); // counter stored in the underflow bin! + uint64_t ddwLaneStatus = ddw->laneWord.laneBits.laneStatus; + uint16_t rdhFeeIndex = feeID; + int RUindex = (rdhFeeIndex & 127); // look only at the rightmost 7 bits + // check the status of each lane + for (int i = 0; i < nLanes; i++) { + int idx = RUindex * nLanes + i; + // check if it is a valide lane + if (mChipIndex[idx] == -1) { + continue; + } + // get the two bits corresponding to the lane i + int MFTlaneStatus = ((ddwLaneStatus >> (i * 2)) & (3)); + // get zone for summary histos + int xBin = mDisk[mChipIndex[idx]] * 2 + mFace[mChipIndex[idx]]; + int yBin = mZone[mChipIndex[idx]] + mHalf[mChipIndex[idx]] * 4; + // fill the info + if (MFTlaneStatus == 0) { + mSummaryChipOk->Fill(mChipIndex[idx]); + } + if (MFTlaneStatus == 1) { + mSummaryChipWarning->Fill(mChipIndex[idx]); + mZoneSummaryChipWarning->Fill(xBin, yBin); + } + if (MFTlaneStatus == 2) { + mSummaryChipError->Fill(mChipIndex[idx]); + mZoneSummaryChipError->Fill(xBin, yBin); + } + if (MFTlaneStatus == 3) { + mSummaryChipFault->Fill(mChipIndex[idx]); + mZoneSummaryChipFault->Fill(xBin, yBin); + } + } // end loop over lanes + } // end if is a DDW + } // end if rdh->stop + } // end loop over input +} + +void QcMFTReadoutTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void QcMFTReadoutTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void QcMFTReadoutTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mSummaryChipError->Reset(); + mSummaryChipFault->Reset(); + mSummaryChipWarning->Reset(); + mSummaryChipOk->Reset(); + mZoneSummaryChipWarning->Reset(); + mZoneSummaryChipError->Reset(); + mZoneSummaryChipFault->Reset(); +} + +void QcMFTReadoutTask::generateChipIndex() +// generate index to relate a RU+lane to a chip +{ + // initialise + for (int i = 0; i < maxRUidx; i++) { + for (int j = 0; j < nLanes; j++) { + int idx = i * nLanes + j; + mChipIndex[idx] = -1; + } + } + + // fill + const o2::itsmft::ChipMappingMFT mapMFT; // MFT maps + auto chipMapData = mapMFT.getChipMappingData(); + for (int i = 0; i < 936; i++) { + int j = chipMapData[i].ruHWID; + int k = chipMapData[i].cable; + int idx = j * nLanes + k; + mChipIndex[idx] = chipMapData[i].globalChipSWID; + } +} + +void QcMFTReadoutTask::getChipMapData() +{ + const o2::itsmft::ChipMappingMFT mapMFT; + auto chipMapData = mapMFT.getChipMappingData(); + QcMFTUtilTables MFTTable; + + for (int i = 0; i < 936; i++) { + mHalf[i] = chipMapData[i].half; + mDisk[i] = chipMapData[i].disk; + mLayer[i] = chipMapData[i].layer; + mFace[i] = mLayer[i] % 2; + mZone[i] = chipMapData[i].zone; + mSensor[i] = chipMapData[i].localChipSWID; + mTransID[i] = chipMapData[i].cable; + mLadder[i] = MFTTable.mLadder[i]; + mX[i] = MFTTable.mX[i]; + mY[i] = MFTTable.mY[i]; + } +} + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/QcMFTReadoutTrend.cxx b/Modules/MFT/src/QcMFTReadoutTrend.cxx new file mode 100644 index 0000000000..af86b6d9d5 --- /dev/null +++ b/Modules/MFT/src/QcMFTReadoutTrend.cxx @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTReadoutTrend.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// + +#include +#include "MFT/QcMFTReadoutTrend.h" + +namespace o2::quality_control_modules::mft +{ + +void* QcMFTReadoutTrend::getBranchAddress() +{ + return &mStats; +} + +const char* QcMFTReadoutTrend::getBranchLeafList() +{ + return "binContent[936]:binContentOverflow:mean/D:stddev:entries"; +} + +void QcMFTReadoutTrend::update(TObject* obj) +{ + auto histo = dynamic_cast(obj); + if (histo) { + for (int i = 0; i < 936; i++) { + mStats.binContent[i] = histo->GetBinContent(i + 1); + } + mStats.binContentOverflow = histo->GetBinContent(937); + mStats.entries = histo->GetEntries(); + mStats.stddev = histo->GetStdDev(); + mStats.mean = histo->GetMean(); + } +} + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/QcMFTTrackCheck.cxx b/Modules/MFT/src/QcMFTTrackCheck.cxx new file mode 100644 index 0000000000..fb065e51a1 --- /dev/null +++ b/Modules/MFT/src/QcMFTTrackCheck.cxx @@ -0,0 +1,124 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTTrackCheck.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Diana Maria Krupova +/// \author Katarina Krizkova Gajdosova +// C++ +#include +// Fair +#include +// ROOT +#include +#include +// O2 +#include "ITSMFTBase/DPLAlpideParam.h" +// Quality Control +#include "MFT/QcMFTTrackCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/UserCodeInterface.h" +#include "QualityControl/CustomParameters.h" + +using namespace std; + +namespace o2::quality_control_modules::mft +{ + +void QcMFTTrackCheck::configure() +{ + // loading custom parameters + mOnlineQC = 1; + if (auto param = mCustomParameters.find("onlineQC"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Custom parameter - onlineQC: " << param->second << ENDM; + mOnlineQC = stoi(param->second); + } + // no call to beautifier yet + mFirstCall = true; +} + +Quality QcMFTTrackCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + + (void)moName; + + if (mo->getName() == "mClusterRatioVsBunchCrossing") { + auto* hGroupedClusterSizeSummary = dynamic_cast(mo->getObject()); + if (hGroupedClusterSizeSummary == nullptr) { + ILOG(Error, Support) << "Could not cast mClusterRatioVsBunchCrossing to TH2F." << ENDM; + return Quality::Null; + } + } + } + return result; +} + +void QcMFTTrackCheck::readAlpideCCDB(std::shared_ptr mo) +{ + mROF = 0; + if (mOnlineQC == 1) { + long timestamp = o2::ccdb::getCurrentTimestamp(); + map headers; + map filter; + auto alpideParam = UserCodeInterface::retrieveConditionAny>("MFT/Config/AlpideParam/", filter, timestamp); + if (alpideParam == nullptr) { + ILOG(Error, Support) << "Could not retrieve Alpide strobe length from CCDB." << ENDM; + return; + } + mROF = alpideParam->roFrameLengthInBC; + } else { // load the CCDB object in async processing + o2::ccdb::CcdbApi api_ccdb; + api_ccdb.init("alice-ccdb.cern.ch"); + int runNo; + long timestamp = -1; + + map hdRCT = api_ccdb.retrieveHeaders("RCT/Info/RunInformation", map(), runNo); + const auto startRCT = hdRCT.find("STF"); + if (startRCT != hdRCT.end()) { + timestamp = stol(startRCT->second); + } else { + ILOG(Error, Support) << "STF not found in headers!" << ENDM; + } + map headers; + map filter; + auto alpideParam = UserCodeInterface::retrieveConditionAny>("MFT/Config/AlpideParam/", filter, timestamp); + if (alpideParam == nullptr) { + ILOG(Error, Support) << "Could not retrieve Alpide strobe length from CCDB." << ENDM; + return; + } + mROF = alpideParam->roFrameLengthInBC; + } +} + +void QcMFTTrackCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mFirstCall) { + mFirstCall = false; + readAlpideCCDB(mo); + } + if (mo->getName().find("mClusterRatioVsBunchCrossing") != std::string::npos) { + auto* hClusterRatioVsBunchCrossing = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.73, 0.9, 0.95, 1.0, "NDC NB"); + hClusterRatioVsBunchCrossing->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->AddText(Form("ROF length from CCDB = %i", mROF)); + msg->Draw(); + } +} + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/QcMFTTrackMCTask.cxx b/Modules/MFT/src/QcMFTTrackMCTask.cxx new file mode 100644 index 0000000000..7f062ce81b --- /dev/null +++ b/Modules/MFT/src/QcMFTTrackMCTask.cxx @@ -0,0 +1,239 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// // distributed under the terms of the GNU General Public License v3 (GPL +// // Version 3), copied verbatim in the file "COPYING". +// // +// // See http://alice-o2.web.cern.ch/license for full licensing information. +// // +// // In applying this license CERN does not waive the privileges and immunities +// // granted to it by virtue of its status as an Intergovernmental Organization +// // or submit itself to any jurisdiction. +// + +/// +/// \file QcMFTTrackMCTask.cxx +/// \author Diana Krupova +/// Sara Haidlova +/// MC simulation tool inspired by ITS +/// +/// + +// O2 +#include +#include +#include +#include +#include +#include +#include +#include +#include //ADDED FOR MC FILE READING +#include +#include +#include +#include +// QualityControl +#include "QualityControl/QcInfoLogger.h" +#include "MFT/QcMFTTrackMCTask.h" + +using namespace o2::constants::math; +using namespace o2::itsmft; + +namespace o2::quality_control_modules::mft +{ + +QcMFTTrackMCTask::~QcMFTTrackMCTask() +{ + /* + not needed for unique pointers + */ +} + +void QcMFTTrackMCTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize QcMFTTrackMCTask" << ENDM; + + mCollisionsContextPath = mCustomParameters["collisionsContextPath"]; + + ILOG(Debug, Devel) << "Creating all the histograms!" << ENDM; + const Int_t nb = 100; + Double_t xbins[nb + 1], ptcutl = 0.01, ptcuth = 10.; + Double_t a = TMath::Log(ptcuth / ptcutl) / nb; + for (Int_t i = 0; i <= nb; i++) + xbins[i] = ptcutl * TMath::Exp(i * a); + + hEfficiency_pt = std::make_unique("efficiency_pt", "#it{p}_{T} efficiency of tracking; #it{p}_{T} (GeV/#it{c}); {p}_{T}Efficiency", nb, xbins); + getObjectsManager()->startPublishing(hEfficiency_pt.get()); + hFakeTrack_pt = std::make_unique("faketrack_pt", "#it{p}_{T} fake-track rate;#it{p}_{T} (GeV/#it{c});Fake-track rate", nb, xbins); // Fake-track rate", nb, xbins) + getObjectsManager()->startPublishing(hFakeTrack_pt.get()); + hRecoValid_pt = std::make_unique("RecoValid_pt", "", nb, xbins); + hRecoFake_pt = std::make_unique("RecoFake_pt", "", nb, xbins); + hTrue_pt = std::make_unique("TrueMC_pt", "", nb, xbins); + + hEfficiency_phi = std::make_unique("efficiency_phi", "#phi efficiency of tracking;#phi;Efficiency", 180, -TwoPI, TwoPI); + getObjectsManager()->startPublishing(hEfficiency_phi.get()); + hFakeTrack_phi = std::make_unique("faketrack_phi", "#phi fake-track rate;#phi;Fake-track rate", 180, -TwoPI, TwoPI); + getObjectsManager()->startPublishing(hFakeTrack_phi.get()); + + hRecoValid_phi = std::make_unique("RecoValid_phi", "", 180, -TwoPI, TwoPI); + hRecoFake_phi = std::make_unique("RecoFake_phi", "", 180, -TwoPI, TwoPI); + hTrue_phi = std::make_unique("TrueMC_phi", "", 180, -TwoPI, TwoPI); + + hEfficiency_eta = std::make_unique("efficiency_eta", "#eta efficiency of tracking;#eta;Efficiency", 50, -4, -2); + getObjectsManager()->startPublishing(hEfficiency_eta.get()); + hFakeTrack_eta = std::make_unique("faketrack_eta", "#eta fake-track rate;#eta;Fake-track rate", 50, -4, -2); + getObjectsManager()->startPublishing(hFakeTrack_eta.get()); + hRecoValid_eta = std::make_unique("RecoValid_eta", "", 50, -4, -2); + hRecoFake_eta = std::make_unique("RecoFake_eta", "", 50, -4, -2); + hTrue_eta = std::make_unique("TrueMC_eta", "", 50, -4, -2); + + hPrimaryGen_pt = std::make_unique("PrimaryGen_pt", ";#it{p}_{T} (GeV/#it{c});counts", 100, 0, 10); + hPrimaryGen_pt->SetTitle("#it{p}_{T} of primary generated particles"); + getObjectsManager()->startPublishing(hPrimaryGen_pt.get()); + hPrimaryGen_pt->GetXaxis()->SetTitleOffset(1); + hPrimaryGen_pt->GetYaxis()->SetTitleOffset(1.10); + + hPrimaryReco_pt = std::make_unique("PrimaryReco_pt", ";#it{p}_{T} (GeV/#it{c});counts", 100, 0, 10); + hPrimaryReco_pt->SetTitle("#it{p}_{T} of primary reconstructed particles"); + getObjectsManager()->startPublishing(hPrimaryReco_pt.get()); + hPrimaryReco_pt->GetXaxis()->SetTitleOffset(1); + hPrimaryReco_pt->GetYaxis()->SetTitleOffset(1.10); + + hResolution_pt = std::make_unique("Resolution_pt", ";1/(#it{p}_{T}^{reco})- 1/(#it{p}_{T}^{gen});counts", 150, -10, 10); + hResolution_pt->SetTitle("Resolution of 1/#it{p}_{T} of primary particles"); + getObjectsManager()->startPublishing(hResolution_pt.get()); + hResolution_pt->GetXaxis()->SetTitleOffset(1); + hResolution_pt->GetYaxis()->SetTitleOffset(1.10); +} + +void QcMFTTrackMCTask::startOfActivity(const Activity& activity) +{ + mRunNumber = activity.mId; + ILOG(Debug, Devel) << "startOfActivity" << activity.mId << ENDM; + // reset histograms + reset(); +} + +void QcMFTTrackMCTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void QcMFTTrackMCTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + ILOG(Debug, Devel) << "START DOING QC General" << ENDM; + o2::steer::MCKinematicsReader reader(mCollisionsContextPath.c_str()); + info.resize(reader.getNEvents(0)); + for (int i = 0; i < reader.getNEvents(0); ++i) { + std::vector const& mcArr = reader.getTracks(i); + info[i].resize(mcArr.size()); + } + + for (int i = 0; i < reader.getNEvents(0); ++i) { + std::vector const& mcArr = reader.getTracks(i); + auto mcHeader = reader.getMCEventHeader(0, i); + for (int mc = 0; mc < mcArr.size(); mc++) { + const auto& mcTrack = (mcArr)[mc]; + info[i][mc].isFilled = false; + info[i][mc].isFilled = true; + info[i][mc].pt = mcTrack.GetPt(); + info[i][mc].eta = mcTrack.GetEta(); + info[i][mc].phi = TMath::ATan2(mcTrack.Py(), mcTrack.Px()); + info[i][mc].isPrimary = mcTrack.isPrimary(); + if (mcTrack.isPrimary()) { + hPrimaryGen_pt->Fill(mcTrack.GetPt()); + } + hTrue_pt->Fill(mcTrack.GetPt()); + hTrue_eta->Fill(mcTrack.GetEta()); + hTrue_phi->Fill(TMath::ATan2(mcTrack.Py(), mcTrack.Px())); + } + } + + auto trackArr = ctx.inputs().get>("tracks"); // MFT Tracks + auto MCTruth = ctx.inputs().get>("mctruth"); // MC track label, contains info about EventID, TrackID, SourceID etc + + for (int itrack = 0; itrack < trackArr.size(); itrack++) { + const auto& track = trackArr[itrack]; + const auto& MCinfo = MCTruth[itrack]; + if (MCinfo.isNoise()) + continue; + + if (info[MCinfo.getEventID()][MCinfo.getTrackID()].isFilled) { + info[MCinfo.getEventID()][MCinfo.getTrackID()].isReco++; + if (MCinfo.isFake()) { + hRecoFake_pt->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hRecoFake_phi->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hRecoFake_eta->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + if (info[MCinfo.getEventID()][MCinfo.getTrackID()].isPrimary) { + hPrimaryReco_pt->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + } + } else { + hRecoValid_pt->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hRecoValid_phi->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].phi); + hRecoValid_eta->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].eta); + if (info[MCinfo.getEventID()][MCinfo.getTrackID()].isPrimary) { + hPrimaryReco_pt->Fill(info[MCinfo.getEventID()][MCinfo.getTrackID()].pt); + hResolution_pt->Fill(1 / (track.getPt()) - 1 / (info[MCinfo.getEventID()][MCinfo.getTrackID()].pt)); + } + } + } + } + + hEfficiency_pt->SetPassedHistogram(*hRecoValid_pt, "f"); + hEfficiency_pt->SetTotalHistogram(*hTrue_pt, "f"); + hFakeTrack_pt->SetPassedHistogram(*hRecoFake_pt, "f"); + hFakeTrack_pt->SetTotalHistogram(*hTrue_pt, "f"); + + hFakeTrack_phi->SetPassedHistogram(*hRecoFake_phi, "f"); + hFakeTrack_phi->SetTotalHistogram(*hTrue_phi, "f"); + hEfficiency_phi->SetPassedHistogram(*hRecoValid_phi, "f"); + hEfficiency_phi->SetTotalHistogram(*hTrue_phi, "f"); + + hFakeTrack_eta->SetPassedHistogram(*hRecoFake_eta, "f"); + hFakeTrack_eta->SetTotalHistogram(*hTrue_eta, "f"); + hEfficiency_eta->SetPassedHistogram(*hRecoValid_eta, "f"); + hEfficiency_eta->SetTotalHistogram(*hTrue_eta, "f"); + + ILOG(Debug, Devel) << "END DOING QC General" << ENDM; +} + +void QcMFTTrackMCTask::endOfCycle() +{ + getObjectsManager()->addMetadata(hEfficiency_pt->GetName(), "Run", std::to_string(mRunNumber)); + getObjectsManager()->addMetadata(hFakeTrack_pt->GetName(), "Run", std::to_string(mRunNumber)); + getObjectsManager()->addMetadata(hEfficiency_phi->GetName(), "Run", std::to_string(mRunNumber)); + getObjectsManager()->addMetadata(hFakeTrack_phi->GetName(), "Run", std::to_string(mRunNumber)); + getObjectsManager()->addMetadata(hEfficiency_eta->GetName(), "Run", std::to_string(mRunNumber)); + getObjectsManager()->addMetadata(hFakeTrack_eta->GetName(), "Run", std::to_string(mRunNumber)); + getObjectsManager()->addMetadata(hPrimaryGen_pt->GetName(), "Run", std::to_string(mRunNumber)); + getObjectsManager()->addMetadata(hPrimaryReco_pt->GetName(), "Run", std::to_string(mRunNumber)); + getObjectsManager()->addMetadata(hResolution_pt->GetName(), "Run", std::to_string(mRunNumber)); + ILOG(Debug, Devel) << "All objects were updated" << ENDM; +} + +void QcMFTTrackMCTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void QcMFTTrackMCTask::reset() +{ + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + hRecoValid_pt->Reset(); + hRecoFake_pt->Reset(); + hTrue_pt->Reset(); + + hRecoValid_phi->Reset(); + hRecoFake_phi->Reset(); + hTrue_phi->Reset(); + + hRecoValid_eta->Reset(); + hRecoFake_eta->Reset(); + hTrue_eta->Reset(); + + hPrimaryGen_pt->Reset(); + hPrimaryReco_pt->Reset(); + + hResolution_pt->Reset(); +} + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/QcMFTTrackTask.cxx b/Modules/MFT/src/QcMFTTrackTask.cxx new file mode 100644 index 0000000000..0a61a86616 --- /dev/null +++ b/Modules/MFT/src/QcMFTTrackTask.cxx @@ -0,0 +1,407 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QcMFTTrackTask.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras +/// \author Diana Maria Krupova +/// \author Katarina Krizkova Gajdosova +/// \author David Grund +/// \author Jakub Juracka +/// + +// C++ +#include +#include +// ROOT +#include +#include +#include +// O2 +#include +#include +#include +#include +#include +#include +#include +#include +// Quality Control +#include "QualityControl/QcInfoLogger.h" +#include "MFT/QcMFTTrackTask.h" +#include "MFT/QcMFTUtilTables.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include "QualityControl/CustomParameters.h" +#include "QualityControl/ObjectsManager.h" + +namespace o2::quality_control_modules::mft +{ + +QcMFTTrackTask::~QcMFTTrackTask() +{ + /* + not needed for unique pointers + */ +} + +void QcMFTTrackTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize QcMFTTrackTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // Loading custom parameters + auto MaxTrackROFSize = 1000; + if (auto param = mCustomParameters.find("MaxTrackROFSize"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - MaxTrackROFSize: " << param->second << ENDM; + MaxTrackROFSize = stoi(param->second); + } + + auto ROFLengthInBC = 198; + if (auto param = mCustomParameters.find("ROFLengthInBC"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ROFLengthInBC: " << param->second << ENDM; + ROFLengthInBC = stoi(param->second); + } + auto ROFsPerOrbit = o2::constants::lhc::LHCMaxBunches / ROFLengthInBC; + + auto MaxDuration = 60.f; + if (auto param = mCustomParameters.find("MaxDuration"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - MaxDuration: " << param->second << ENDM; + MaxDuration = stof(param->second); + } + + auto TimeBinSize = 0.01f; + if (auto param = mCustomParameters.find("TimeBinSize"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TimeBinSize: " << param->second << ENDM; + TimeBinSize = stof(param->second); + } + + auto NofTimeBins = static_cast(MaxDuration / TimeBinSize); + + // Creating histos + + double maxTracksPerTF = 5000; + + mNumberOfTracksPerTF = std::make_unique("mMFTTracksPerTF", + "Number of tracks per TimeFrame; Number of tracks per TF; # entries per orbit", maxTracksPerTF, -0.5, maxTracksPerTF - 0.5, true); + getObjectsManager()->startPublishing(mNumberOfTracksPerTF.get()); + getObjectsManager()->setDisplayHint(mNumberOfTracksPerTF.get(), "hist"); + + mTrackNumberOfClusters = std::make_unique("mMFTTrackNumberOfClusters", + "Number Of Clusters Per Track; # clusters; # entries per orbit", 10, 0.5, 10.5, true); + getObjectsManager()->startPublishing(mTrackNumberOfClusters.get()); + getObjectsManager()->setDisplayHint(mTrackNumberOfClusters.get(), "hist"); + + mCATrackNumberOfClusters = std::make_unique("CA/mMFTCATrackNumberOfClusters", + "Number Of Clusters Per CA Track; # clusters; # entries per orbit", 10, 0.5, 10.5, true); + getObjectsManager()->startPublishing(mCATrackNumberOfClusters.get()); + getObjectsManager()->setDisplayHint(mCATrackNumberOfClusters.get(), "hist"); + + mLTFTrackNumberOfClusters = std::make_unique("LTF/mMFTLTFTrackNumberOfClusters", + "Number Of Clusters Per LTF Track; # clusters; # entries per orbit", 10, 0.5, 10.5, true); + getObjectsManager()->startPublishing(mLTFTrackNumberOfClusters.get()); + getObjectsManager()->setDisplayHint(mLTFTrackNumberOfClusters.get(), "hist"); + + mTrackInvQPt = std::make_unique("mMFTTrackInvQPt", "Track q/p_{T}; q/p_{T} [1/GeV]; # entries per orbit", 250, -10, 10, true); + getObjectsManager()->startPublishing(mTrackInvQPt.get()); + getObjectsManager()->setDisplayHint(mTrackInvQPt.get(), "hist"); + + mTrackChi2 = std::make_unique("mMFTTrackChi2", "Track #chi^{2}/NDF; #chi^{2}/NDF; # entries per orbit", 210, -0.5, 20.5, true); + getObjectsManager()->startPublishing(mTrackChi2.get()); + getObjectsManager()->setDisplayHint(mTrackChi2.get(), "hist"); + + mTrackCharge = std::make_unique("mMFTTrackCharge", "Track Charge; q; # entries per orbit", 3, -1.5, 1.5, true); + getObjectsManager()->startPublishing(mTrackCharge.get()); + getObjectsManager()->setDisplayHint(mTrackCharge.get(), "hist"); + + mTrackPhi = std::make_unique("mMFTTrackPhi", "Track #phi; #phi; # entries per orbit", 100, -3.2, 3.2, true); + getObjectsManager()->startPublishing(mTrackPhi.get()); + getObjectsManager()->setDisplayHint(mTrackPhi.get(), "hist"); + + mPositiveTrackPhi = std::make_unique("mMFTPositiveTrackPhi", "Positive Track #phi; #phi; # entries per orbit", 100, -3.2, 3.2, true); + getObjectsManager()->startPublishing(mPositiveTrackPhi.get()); + getObjectsManager()->setDisplayHint(mPositiveTrackPhi.get(), "hist"); + + mNegativeTrackPhi = std::make_unique("mMFTNegativeTrackPhi", "Negative Track #phi; #phi; # entries per orbit", 100, -3.2, 3.2, true); + getObjectsManager()->startPublishing(mNegativeTrackPhi.get()); + getObjectsManager()->setDisplayHint(mNegativeTrackPhi.get(), "hist"); + + mTrackEta = std::make_unique("mMFTTrackEta", "Track #eta; #eta; # entries per orbit", 50, -4, -2, true); + getObjectsManager()->startPublishing(mTrackEta.get()); + getObjectsManager()->setDisplayHint(mTrackEta.get(), "hist"); + + for (auto minNClusters : sMinNClustersList) { + auto nHisto = minNClusters - sMinNClustersList[0]; + mTrackEtaNCls[nHisto] = std::make_unique(Form("mMFTTrackEta_%d_MinClusters", minNClusters), Form("Track #eta (NCls >= %d); #eta; # entries per orbit", minNClusters), 50, -4, -2, true); + getObjectsManager()->startPublishing(mTrackEtaNCls[nHisto].get()); + getObjectsManager()->setDisplayHint(mTrackEtaNCls[nHisto].get(), "hist"); + + mTrackPhiNCls[nHisto] = std::make_unique(Form("mMFTTrackPhi_%d_MinClusters", minNClusters), Form("Track #phi (NCls >= %d); #phi; # entries per orbit", minNClusters), 100, -3.2, 3.2, true); + getObjectsManager()->startPublishing(mTrackPhiNCls[nHisto].get()); + getObjectsManager()->setDisplayHint(mTrackPhiNCls[nHisto].get(), "hist"); + + mTrackXYNCls[nHisto] = std::make_unique(Form("mMFTTrackXY_%d_MinClusters", minNClusters), Form("Track Position (NCls >= %d); x; y", minNClusters), 320, -16, 16, 320, -16, 16, true); + getObjectsManager()->startPublishing(mTrackXYNCls[nHisto].get()); + getObjectsManager()->setDisplayHint(mTrackXYNCls[nHisto].get(), "logz colz"); + + mTrackEtaPhiNCls[nHisto] = std::make_unique(Form("mMFTTrackEtaPhi_%d_MinClusters", minNClusters), Form("Track #eta , #phi (NCls >= %d); #eta; #phi", minNClusters), 50, -4, -2, 100, -3.2, 3.2, true); + getObjectsManager()->startPublishing(mTrackEtaPhiNCls[nHisto].get()); + getObjectsManager()->setDisplayHint(mTrackEtaPhiNCls[nHisto].get(), "colz"); + } + + mCATrackEta = std::make_unique("CA/mMFTCATrackEta", "CA Track #eta; #eta; # entries per orbit", 50, -4, -2, true); + getObjectsManager()->startPublishing(mCATrackEta.get()); + getObjectsManager()->setDisplayHint(mCATrackEta.get(), "hist"); + + mLTFTrackEta = std::make_unique("LTF/mMFTLTFTrackEta", "LTF Track #eta; #eta; # entries per orbit", 50, -4, -2, true); + getObjectsManager()->startPublishing(mLTFTrackEta.get()); + getObjectsManager()->setDisplayHint(mLTFTrackEta.get(), "hist"); + + mCATrackPt = std::make_unique("CA/mMFTCATrackPt", "CA Track p_{T}; p_{T} (GeV/c); # entries per orbit", 300, 0, 30, true); + getObjectsManager()->startPublishing(mCATrackPt.get()); + getObjectsManager()->setDisplayHint(mCATrackPt.get(), "hist logy"); + + mLTFTrackPt = std::make_unique("LTF/mMFTLTFTrackPt", "LTF Track p_{T}; p_{T} (GeV/c); # entries per orbit", 300, 0, 30, true); + getObjectsManager()->startPublishing(mLTFTrackPt.get()); + getObjectsManager()->setDisplayHint(mLTFTrackPt.get(), "hist logy"); + + mTrackTanl = std::make_unique("mMFTTrackTanl", "Track tan #lambda; tan #lambda; # entries per orbit", 100, -25, 0, true); + getObjectsManager()->startPublishing(mTrackTanl.get()); + getObjectsManager()->setDisplayHint(mTrackTanl.get(), "hist"); + + mTrackROFNEntries = std::make_unique("mMFTTrackROFSize", "Distribution of the #tracks per ROF; # tracks per ROF; # entries", QcMFTUtilTables::nROFBins, const_cast(QcMFTUtilTables::mROFBins), false); + getObjectsManager()->startPublishing(mTrackROFNEntries.get()); + getObjectsManager()->setDisplayHint(mTrackROFNEntries.get(), "hist logx logy"); + + mTracksBC = std::make_unique("mMFTTracksBC", "Tracks per BC; BCid; # entries per orbit", o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches, true); + mTracksBC->SetMinimum(0.1); + getObjectsManager()->startPublishing(mTracksBC.get()); + getObjectsManager()->setDisplayHint(mTracksBC.get(), "hist"); + + mAssociatedClusterFraction = std::make_unique("mAssociatedClusterFraction", "Fraction of clusters in tracks; Clusters in tracks / All clusters; # entries per orbit", 100, 0, 1, true); + getObjectsManager()->startPublishing(mAssociatedClusterFraction.get()); + getObjectsManager()->setDisplayHint(mAssociatedClusterFraction.get(), "hist logy"); + + mClusterRatioVsBunchCrossing = std::make_unique("mClusterRatioVsBunchCrossing", "Bunch Crossing ID vs Cluster Ratio;BC ID;Fraction of clusters in tracks", + o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches, 100, 0, 1, true); + getObjectsManager()->startPublishing(mClusterRatioVsBunchCrossing.get()); + getObjectsManager()->setDisplayHint(mClusterRatioVsBunchCrossing.get(), "colz logz"); +} + +void QcMFTTrackTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + + // reset histograms + reset(); +} + +void QcMFTTrackTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void QcMFTTrackTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + if (!mGeom) { + o2::mft::GeometryTGeo::adopt(TaskInterface::retrieveConditionAny("MFT/Config/Geometry")); + mGeom = o2::mft::GeometryTGeo::Instance(); + ILOG(Info, Support) << "GeometryTGeo loaded" << ENDM; + } + auto mNOrbitsPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + + // get the tracks + const auto tracks = ctx.inputs().get>("tracks"); + if (tracks.empty()) { + return; + } + const auto tracksrofs = ctx.inputs().get>("tracksrofs"); + if (tracksrofs.empty()) { + return; + } + const auto clustersrofs = ctx.inputs().get>("clustersrofs"); + if (clustersrofs.empty()) { + return; + } + + // fill the tracks histogram + + mNumberOfTracksPerTF->getNum()->Fill(tracks.size()); + + for (int iROF = 0; iROF < tracksrofs.size(); iROF++) { + int nClusterCountTrack = 0; + int nTracks = tracksrofs[iROF].getNEntries(); + + mTrackROFNEntries->getNum()->Fill(nTracks); + + int start = tracksrofs[iROF].getFirstEntry(); + int end = start + tracksrofs[iROF].getNEntries(); + + for (int itrack = start; itrack < end; itrack++) { + auto& track = tracks[itrack]; + nClusterCountTrack += track.getNumberOfPoints(); + } + + int nTotalClusters = clustersrofs[iROF].getNEntries(); + + float clusterRatio = nTotalClusters > 0 ? (float)nClusterCountTrack / (float)nTotalClusters : -1; + + const auto bcData = tracksrofs[iROF].getBCData(); + mTracksBC->getNum()->Fill(bcData.bc, nTracks); + mAssociatedClusterFraction->getNum()->Fill(clusterRatio); + mClusterRatioVsBunchCrossing->getNum()->Fill(bcData.bc, clusterRatio); + } + + for (auto& oneTrack : tracks) { + mTrackNumberOfClusters->getNum()->Fill(oneTrack.getNumberOfPoints()); + mTrackChi2->getNum()->Fill(oneTrack.getChi2OverNDF()); + mTrackCharge->getNum()->Fill(oneTrack.getCharge()); + mTrackPhi->getNum()->Fill(oneTrack.getPhi()); + mTrackEta->getNum()->Fill(oneTrack.getEta()); + mTrackTanl->getNum()->Fill(oneTrack.getTanl()); + + for (auto minNClusters : sMinNClustersList) { + if (oneTrack.getNumberOfPoints() >= minNClusters) { + mTrackEtaNCls[minNClusters - sMinNClustersList[0]]->getNum()->Fill(oneTrack.getEta()); + mTrackPhiNCls[minNClusters - sMinNClustersList[0]]->getNum()->Fill(oneTrack.getPhi()); + mTrackXYNCls[minNClusters - sMinNClustersList[0]]->getNum()->Fill(oneTrack.getX(), oneTrack.getY()); + mTrackEtaPhiNCls[minNClusters - sMinNClustersList[0]]->getNum()->Fill(oneTrack.getEta(), oneTrack.getPhi()); + } + } + + if (oneTrack.getCharge() == +1) { + mPositiveTrackPhi->getNum()->Fill(oneTrack.getPhi()); + mTrackInvQPt->getNum()->Fill(1 / oneTrack.getPt()); + } + + if (oneTrack.getCharge() == -1) { + mNegativeTrackPhi->getNum()->Fill(oneTrack.getPhi()); + mTrackInvQPt->getNum()->Fill(-1 / oneTrack.getPt()); + } + + if (oneTrack.isCA()) { + mCATrackNumberOfClusters->getNum()->Fill(oneTrack.getNumberOfPoints()); + mCATrackEta->getNum()->Fill(oneTrack.getEta()); + mCATrackPt->getNum()->Fill(oneTrack.getPt()); + } + if (oneTrack.isLTF()) { + mLTFTrackNumberOfClusters->getNum()->Fill(oneTrack.getNumberOfPoints()); + mLTFTrackEta->getNum()->Fill(oneTrack.getEta()); + mLTFTrackPt->getNum()->Fill(oneTrack.getPt()); + } + } + + // fill the denominators + mNumberOfTracksPerTF->getDen()->SetBinContent(1, mNumberOfTracksPerTF->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mTrackNumberOfClusters->getDen()->SetBinContent(1, mTrackNumberOfClusters->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mCATrackNumberOfClusters->getDen()->SetBinContent(1, mCATrackNumberOfClusters->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mLTFTrackNumberOfClusters->getDen()->SetBinContent(1, mLTFTrackNumberOfClusters->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mTrackInvQPt->getDen()->SetBinContent(1, mTrackInvQPt->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mTrackChi2->getDen()->SetBinContent(1, mTrackChi2->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mTrackCharge->getDen()->SetBinContent(1, mTrackCharge->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mTrackPhi->getDen()->SetBinContent(1, mTrackPhi->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mPositiveTrackPhi->getDen()->SetBinContent(1, mPositiveTrackPhi->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mNegativeTrackPhi->getDen()->SetBinContent(1, mNegativeTrackPhi->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mTrackEta->getDen()->SetBinContent(1, mTrackEta->getDen()->GetBinContent(1) + mNOrbitsPerTF); + for (auto minNClusters : sMinNClustersList) { + auto nHisto = minNClusters - sMinNClustersList[0]; + mTrackEtaNCls[nHisto]->getDen()->SetBinContent(1, mTrackEtaNCls[nHisto]->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mTrackPhiNCls[nHisto]->getDen()->SetBinContent(1, mTrackPhiNCls[nHisto]->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mTrackXYNCls[nHisto]->getDen()->SetBinContent(1, 1, mTrackXYNCls[nHisto]->getDen()->GetBinContent(1, 1) + mNOrbitsPerTF); + mTrackEtaPhiNCls[nHisto]->getDen()->SetBinContent(1, 1, mTrackEtaPhiNCls[nHisto]->getDen()->GetBinContent(1, 1) + mNOrbitsPerTF); + } + mCATrackEta->getDen()->SetBinContent(1, mCATrackEta->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mLTFTrackEta->getDen()->SetBinContent(1, mLTFTrackEta->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mCATrackPt->getDen()->SetBinContent(1, mCATrackPt->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mLTFTrackPt->getDen()->SetBinContent(1, mLTFTrackPt->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mTrackTanl->getDen()->SetBinContent(1, mTrackTanl->getDen()->GetBinContent(1) + mNOrbitsPerTF); + for (int i = 0; i < QcMFTUtilTables::nROFBins; i++) + mTrackROFNEntries->getDen()->SetBinContent(i + 1, QcMFTUtilTables::mROFBins[i + 1] - QcMFTUtilTables::mROFBins[i]); + mTracksBC->getDen()->SetBinContent(1, mTracksBC->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mAssociatedClusterFraction->getDen()->SetBinContent(1, mAssociatedClusterFraction->getDen()->GetBinContent(1) + mNOrbitsPerTF); + mClusterRatioVsBunchCrossing->getDen()->SetBinContent(1, 1, mClusterRatioVsBunchCrossing->getDen()->GetBinContent(1, 1) + mNOrbitsPerTF); +} + +void QcMFTTrackTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + + mNumberOfTracksPerTF->update(); + mTrackNumberOfClusters->update(); + mCATrackNumberOfClusters->update(); + mLTFTrackNumberOfClusters->update(); + mTrackInvQPt->update(); + mTrackChi2->update(); + mTrackCharge->update(); + mTrackPhi->update(); + mPositiveTrackPhi->update(); + mNegativeTrackPhi->update(); + mTrackEta->update(); + for (auto minNClusters : sMinNClustersList) { + auto nHisto = minNClusters - sMinNClustersList[0]; + mTrackEtaNCls[nHisto]->update(); + mTrackPhiNCls[nHisto]->update(); + mTrackXYNCls[nHisto]->update(); + mTrackEtaPhiNCls[nHisto]->update(); + } + mCATrackEta->update(); + mLTFTrackEta->update(); + mCATrackPt->update(); + mLTFTrackPt->update(); + mTrackTanl->update(); + mTrackROFNEntries->update(); + mTracksBC->update(); + mAssociatedClusterFraction->update(); + mClusterRatioVsBunchCrossing->update(); +} + +void QcMFTTrackTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void QcMFTTrackTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + + mNumberOfTracksPerTF->Reset(); + mTrackNumberOfClusters->Reset(); + mCATrackNumberOfClusters->Reset(); + mLTFTrackNumberOfClusters->Reset(); + mTrackInvQPt->Reset(); + mTrackChi2->Reset(); + mTrackCharge->Reset(); + mTrackPhi->Reset(); + mPositiveTrackPhi->Reset(); + mNegativeTrackPhi->Reset(); + mTrackEta->Reset(); + for (auto minNClusters : sMinNClustersList) { + auto nHisto = minNClusters - sMinNClustersList[0]; + mTrackEtaNCls[nHisto]->Reset(); + mTrackPhiNCls[nHisto]->Reset(); + mTrackXYNCls[nHisto]->Reset(); + mTrackEtaPhiNCls[nHisto]->Reset(); + } + mCATrackEta->Reset(); + mLTFTrackEta->Reset(); + mCATrackPt->Reset(); + mLTFTrackPt->Reset(); + mTrackTanl->Reset(); + mTrackROFNEntries->Reset(); + mTracksBC->Reset(); + mAssociatedClusterFraction->Reset(); + mClusterRatioVsBunchCrossing->Reset(); +} + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MFT/src/runMFTClustersRootFileReader.cxx b/Modules/MFT/src/runMFTClustersRootFileReader.cxx new file mode 100644 index 0000000000..776199333e --- /dev/null +++ b/Modules/MFT/src/runMFTClustersRootFileReader.cxx @@ -0,0 +1,167 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runMFTClustersRootFileReader.cxx +/// \author Guillermo Contreras +/// \author Tomas Herman +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// +/// \brief This is an executable that reads clusters from a root file from disk and sends the data to QC via DPL. +/// +/// This is an executable that reads clusters from a root file from disk and sends the data to QC via the Data Processing Layer. +/// The idea is based in a similar reader by Andrea Ferrero and the DigitReaderSpec definition in MFT +// It can be used as a data source for QC development. For example, one can do: +/// \code{.sh} +/// o2-qc-mft-clusters-root-file-reader --mft-cluster-infile=some_data_file | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/your_config.json +/// \endcode +/// + +// C++ +#include +// ROOT +#include +#include +// O2 +#include +#include +#include +#include +#include +#include +#include + +using namespace o2; +using namespace o2::framework; +using namespace o2::itsmft; + +class MFTClustersRootFileReader : public o2::framework::Task +{ + public: + //_________________________________________________________________________________________________ + void init(framework::InitContext& ic) + { + LOG(info) << " In MFTClustersRootFileReader::init ... entering "; + + // open the input file + auto filename = ic.options().get("mft-cluster-infile"); + mFile = std::make_unique(filename.c_str(), "OLD"); + if (!mFile->IsOpen()) { + LOG(error) << "MFTClustersRootFileReader::init. Cannot open the file: " << filename.c_str(); + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // get the tree + mTree = (TTree*)mFile->Get("o2sim"); + mTree->SetBranchAddress("MFTClusterComp", &pclusters); + mTree->SetBranchAddress("MFTClustersROF", &profs); + mTree->SetBranchAddress("MFTClusterPatt", &ppatterns); + + // check that it has entries + mNumberOfTF = mTree->GetEntries(); + if (mNumberOfTF == 0) { + LOG(error) << "MFTClustersRootFileReader::init. No TFs "; + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + } + + //_________________________________________________________________________________________________ + + void run(framework::ProcessingContext& pc) + { + + // Check if this is the last TF + if (mCurrentTF == mNumberOfTF) { + LOG(info) << " MFTClustersRootFileReader::run. End of file reached"; + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } + + mTree->GetEntry(mCurrentTF); // get TF + mNumberOfROF = rofs.size(); // get number of ROFs in this TF + + // prepare the rof output + std::vector* oneROFvec = new std::vector(); + std::copy(rofs.begin() + mCurrentROF, rofs.begin() + mCurrentROF + 1, std::back_inserter(*oneROFvec)); + + // get the clusters in current ROF + // --> get the current ROF + auto& rof = rofs[mCurrentROF]; + // --> find the ranges + int index = rof.getFirstEntry(); // first cluster position + int numberOfClustersInROF = rof.getNEntries(); + int lastIndex = index + numberOfClustersInROF; + + // --> fill in the corresponding clusters + std::vector* clustersInROF = new std::vector(); + std::copy(clusters.begin() + index, clusters.begin() + lastIndex, std::back_inserter(*clustersInROF)); + + // prepare and fill vector with cluster patterns for all ROFs + std::vector* clusPatternArr = new std::vector(); + std::copy(patterns.begin(), patterns.end(), std::back_inserter(*clusPatternArr)); + + // fill in the message + pc.outputs().snapshot(Output{ "MFT", "COMPCLUSTERS", 0 }, *clustersInROF); + pc.outputs().snapshot(Output{ "MFT", "CLUSTERSROF", 0 }, *oneROFvec); + pc.outputs().snapshot(Output{ "MFT", "PATTERNS", 0 }, *clusPatternArr); + + // update the ROF counter + mCurrentROF++; + + // check if we need to read a new TF + if (mCurrentROF == mNumberOfROF) { + mCurrentTF++; + mCurrentROF = 0; + } + } + + private: + std::unique_ptr mFile = nullptr; // file to be read + TTree* mTree = nullptr; // tree inside the file + std::vector rofs, *profs = &rofs; // pointer to ROF branch + std::vector clusters, *pclusters = &clusters; // pointer to cluster branch + std::vector patterns, *ppatterns = &patterns; // pointer to pattern branch + + unsigned long mNumberOfTF = 0; // number of TF + unsigned long mNumberOfROF = 0; // number of ROFs in current TF + unsigned long mCurrentROF = 0; // idx of current ROF + unsigned long mCurrentTF = 0; // idx of current TF + +}; // end class definition + +WorkflowSpec defineDataProcessing(const ConfigContext&) +{ + WorkflowSpec specs; // to return the work flow + + // define the outputs + std::vector outputs; + outputs.emplace_back("MFT", "COMPCLUSTERS", 0, Lifetime::Timeframe); + outputs.emplace_back("MFT", "CLUSTERSROF", 0, Lifetime::Timeframe); + outputs.emplace_back("MFT", "PATTERNS", 0, Lifetime::Timeframe); + + // The producer to generate some data in the workflow + DataProcessorSpec producer{ + "clusters-root-file-reader-mft", + Inputs{}, + outputs, + AlgorithmSpec{ adaptFromTask() }, + Options{ { "mft-cluster-infile", VariantType::String, "mftclusters.root", { "Name of the input file" } } } + }; + specs.push_back(producer); + + return specs; +} diff --git a/Modules/MFT/src/runMFTDigitsHotPixelRootFileReader.cxx b/Modules/MFT/src/runMFTDigitsHotPixelRootFileReader.cxx new file mode 100644 index 0000000000..76ddca4c08 --- /dev/null +++ b/Modules/MFT/src/runMFTDigitsHotPixelRootFileReader.cxx @@ -0,0 +1,179 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runMFTDigitsHotPixelRootFileReader.cxx +/// \author Guillermo Contreras +/// \author Tomas Herman +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// +/// \brief This is an executable that reads digits from a root file from disk adds hot pixels and sends the data to QC via DPL. +/// +/// This is an executable that reads digits from a root file from disk and sends the data to QC via the Data Processing Layer. +/// The idea is based in a similar reader by Andrea Ferrero and the DigitReaderSpec definition in MFT +// It can be used as a data source for QC development with hot pixels. For example, one can do: +/// \code{.sh} +/// o2-qc-mft-digits-hot-pixel-root-file-reader --mft-digit-infile=some_data_file | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/your_config.json +/// \endcode +/// +// C++ +#include +// ROOT +#include +#include +// O2 +#include +#include +#include +#include +#include +#include + +using namespace o2; +using namespace o2::framework; +using namespace o2::itsmft; + +class MFTDigitsHotPixelRootFileReader : public o2::framework::Task +{ + public: + //_________________________________________________________________________________________________ + void init(framework::InitContext& ic) + { + LOG(info) << " In MFTDigitsHotPixelRootFileReader::init ... entering "; + + // open the input file + auto filename = ic.options().get("mft-digit-infile"); + mFile = std::make_unique(filename.c_str(), "OLD"); + if (!mFile->IsOpen()) { + LOG(error) << "MFTDigitsHotPixelRootFileReader::init. Cannot open the file: " << filename.c_str(); + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // get the tree + mTree = (TTree*)mFile->Get("o2sim"); + mTree->SetBranchAddress("MFTDigit", &pdigits); + mTree->SetBranchAddress("MFTDigitROF", &profs); + + // check that it has entries + mNumberOfTF = mTree->GetEntries(); + if (mNumberOfTF == 0) { + LOG(error) << "MFTDigitsHotPixelRootFileReader::init. No TFs "; + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + } + + //_________________________________________________________________________________________________ + + void run(framework::ProcessingContext& pc) + { + + // Check if this is the last TF + if (mCurrentTF == mNumberOfTF) { + LOG(info) << " MFTDigitsHotPixelRootFileReader::run. End of file reached"; + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } + + mTree->GetEntry(mCurrentTF); // get TF + mNumberOfROF = rofs.size(); // get number of ROFs in this TF + + // prepare the rof output + std::vector* oneROFvec = new std::vector(); + std::copy(rofs.begin() + mCurrentROF, rofs.begin() + mCurrentROF + 1, std::back_inserter(*oneROFvec)); + + // get the digits in current ROF + // --> get the current ROF + auto& rof = rofs[mCurrentROF]; + // --> find the ranges + int index = rof.getFirstEntry(); // first digit position + int numberOfDigitsInROF = rof.getNEntries(); + int lastIndex = index + numberOfDigitsInROF; + + // --> fill in the corresponding digits + std::vector* digitsInROF = new std::vector(); + std::copy(digits.begin() + index, digits.begin() + lastIndex, std::back_inserter(*digitsInROF)); + + // --> create a noisy digit + AddHotPixel(digitsInROF, 2, 200, 200, 165); + AddHotPixel(digitsInROF, 10, 200, 200, 165); + AddHotPixel(digitsInROF, 35, 200, 200, 165); + + // fill in the message + pc.outputs().snapshot(Output{ "MFT", "DIGITS", 0 }, *digitsInROF); + pc.outputs().snapshot(Output{ "MFT", "DIGITSROF", 0 }, *oneROFvec); + + // update the ROF counter + mCurrentROF++; + + // check if we need to read a new TF + if (mCurrentROF == mNumberOfROF) { + mCurrentTF++; + mCurrentROF = 0; + } + } + + private: + std::unique_ptr mFile = nullptr; // file to be read + TTree* mTree = nullptr; // tree inside the file + std::vector rofs, *profs = &rofs; // pointer to ROF branch + std::vector digits, *pdigits = &digits; // pointer to digit branch + + unsigned long mNumberOfTF = 0; // number of TF + unsigned long mNumberOfROF = 0; // number of ROFs in current TF + unsigned long mCurrentROF = 0; // idx of current ROF + unsigned long mCurrentTF = 0; // idx of current TF + + void AddHotPixel(std::vector* digitsInROF, int chipID, int col, int row, int charge) + { + o2::itsmft::Digit NoisyPixel; + NoisyPixel.setChipIndex(chipID); + NoisyPixel.setPixelIndex(row, col); + NoisyPixel.setCharge(charge); + + auto check = 1; + for (auto& one_digit : *digitsInROF) { + if ((one_digit.getChipIndex() == chipID) && (one_digit.getColumn() == col) && (one_digit.getRow() == row)) + check = 0; + } + + if (check) + digitsInROF->push_back(NoisyPixel); + } + +}; // end class definition + +WorkflowSpec defineDataProcessing(const ConfigContext&) +{ + WorkflowSpec specs; // to return the work flow + + // define the outputs + std::vector outputs; + outputs.emplace_back("MFT", "DIGITS", 0, Lifetime::Timeframe); + outputs.emplace_back("MFT", "DIGITSROF", 0, Lifetime::Timeframe); + + // The producer to generate some data in the workflow + DataProcessorSpec producer{ + "digits-root-file-reader-mft", + Inputs{}, + outputs, + AlgorithmSpec{ adaptFromTask() }, + Options{ { "mft-digit-infile", VariantType::String, "mftdigits.root", { "Name of the input file" } } } + }; + specs.push_back(producer); + + return specs; +} diff --git a/Modules/MFT/src/runMFTDigitsRootFileReader.cxx b/Modules/MFT/src/runMFTDigitsRootFileReader.cxx new file mode 100644 index 0000000000..7c18ea4e71 --- /dev/null +++ b/Modules/MFT/src/runMFTDigitsRootFileReader.cxx @@ -0,0 +1,158 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runMFTDigitsRootFileReader.cxx +/// \author Guillermo Contreras +/// \author Tomas Herman +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// +/// \brief This is an executable that reads digits from a root file from disk and sends the data to QC via DPL. +/// +/// This is an executable that reads digits from a root file from disk and sends the data to QC via the Data Processing Layer. +/// The idea is based in a similar reader by Andrea Ferrero and the DigitReaderSpec definition in MFT +// It can be used as a data source for QC development. For example, one can do: +/// \code{.sh} +/// o2-qc-mft-digits-root-file-reader --mft-digit-infile=some_data_file | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/your_config.json +/// \endcode +/// + +// C++ +#include +// ROOT +#include +#include +// O2 +#include +#include +#include +#include +#include +#include + +using namespace o2; +using namespace o2::framework; +using namespace o2::itsmft; + +class MFTDigitsRootFileReader : public o2::framework::Task +{ + public: + //_________________________________________________________________________________________________ + void init(framework::InitContext& ic) + { + LOG(info) << " In MFTDigitsRootFileReader::init ... entering "; + + // open the input file + auto filename = ic.options().get("mft-digit-infile"); + mFile = std::make_unique(filename.c_str(), "OLD"); + if (!mFile->IsOpen()) { + LOG(error) << "MFTDigitsRootFileReader::init. Cannot open the file: " << filename.c_str(); + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // get the tree + mTree = (TTree*)mFile->Get("o2sim"); + mTree->SetBranchAddress("MFTDigit", &pdigits); + mTree->SetBranchAddress("MFTDigitROF", &profs); + + // check that it has entries + mNumberOfTF = mTree->GetEntries(); + if (mNumberOfTF == 0) { + LOG(error) << "MFTDigitsRootFileReader::init. No TFs "; + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + } + + //_________________________________________________________________________________________________ + + void run(framework::ProcessingContext& pc) + { + + // Check if this is the last TF + if (mCurrentTF == mNumberOfTF) { + LOG(info) << " MFTDigitsRootFileReader::run. End of file reached"; + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } + + mTree->GetEntry(mCurrentTF); // get TF + mNumberOfROF = rofs.size(); // get number of ROFs in this TF + + // prepare the rof output + std::vector* oneROFvec = new std::vector(); + std::copy(rofs.begin() + mCurrentROF, rofs.begin() + mCurrentROF + 1, std::back_inserter(*oneROFvec)); + + // get the digits in current ROF + // --> get the current ROF + auto& rof = rofs[mCurrentROF]; + // --> find the ranges + int index = rof.getFirstEntry(); // first digit position + int numberOfDigitsInROF = rof.getNEntries(); + int lastIndex = index + numberOfDigitsInROF; + + // --> fill in the corresponding digits + std::vector* digitsInROF = new std::vector(); + std::copy(digits.begin() + index, digits.begin() + lastIndex, std::back_inserter(*digitsInROF)); + + // fill in the message + pc.outputs().snapshot(Output{ "MFT", "DIGITS", 0 }, *digitsInROF); + pc.outputs().snapshot(Output{ "MFT", "DIGITSROF", 0 }, *oneROFvec); + + // update the ROF counter + mCurrentROF++; + + // check if we need to read a new TF + if (mCurrentROF == mNumberOfROF) { + mCurrentTF++; + mCurrentROF = 0; + } + } + + private: + std::unique_ptr mFile = nullptr; // file to be read + TTree* mTree = nullptr; // tree inside the file + std::vector rofs, *profs = &rofs; // pointer to ROF branch + std::vector digits, *pdigits = &digits; // pointer to digit branch + + unsigned long mNumberOfTF = 0; // number of TF + unsigned long mNumberOfROF = 0; // number of ROFs in current TF + unsigned long mCurrentROF = 0; // idx of current ROF + unsigned long mCurrentTF = 0; // idx of current TF + +}; // end class definition + +WorkflowSpec defineDataProcessing(const ConfigContext&) +{ + WorkflowSpec specs; // to return the work flow + + // define the outputs + std::vector outputs; + outputs.emplace_back("MFT", "DIGITS", 0, Lifetime::Timeframe); + outputs.emplace_back("MFT", "DIGITSROF", 0, Lifetime::Timeframe); + + // The producer to generate some data in the workflow + DataProcessorSpec producer{ + "digits-root-file-reader-mft", + Inputs{}, + outputs, + AlgorithmSpec{ adaptFromTask() }, + Options{ { "mft-digit-infile", VariantType::String, "mftdigits.root", { "Name of the input file" } } } + }; + specs.push_back(producer); + + return specs; +} diff --git a/Modules/MFT/src/runMFTTracksRootFileReader.cxx b/Modules/MFT/src/runMFTTracksRootFileReader.cxx new file mode 100644 index 0000000000..7a9b2395fb --- /dev/null +++ b/Modules/MFT/src/runMFTTracksRootFileReader.cxx @@ -0,0 +1,161 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runMFTTracksRootFileReader.cxx +/// \author Guillermo Contreras +/// \author Tomas Herman +/// \author Katarina Krizkova Gajdosova +/// \author Diana Maria Krupova +/// +/// \brief This is an executable that reads tracks from a root file from disk and sends the data to QC via DPL. +/// +/// This is an executable that reads tracks from a root file from disk and sends the data to QC via the Data Processing Layer. +/// The idea is based in a similar reader by Andrea Ferrero and the DigitReaderSpec definition in MFT +// It can be used as a data source for QC development. For example, one can do: +/// \code{.sh} +/// o2-qc-mft-tracks-root-file-reader --mft-track-infile=some_data_file | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/your_config.json +/// \endcode +/// + +// C++ +#include +// ROOT +#include +#include +// O2 +#include +#include +#include +#include +#include +#include +#include + +using namespace o2; +using namespace o2::framework; +using namespace o2::itsmft; +using namespace o2::mft; + +class MFTTracksRootFileReader : public o2::framework::Task +{ + public: + //_________________________________________________________________________________________________ + void init(framework::InitContext& ic) + { + LOG(info) << " In MFTTracksRootFileReader::init ... entering "; + + // open the input file + auto filename = ic.options().get("mft-track-infile"); + mFile = std::make_unique(filename.c_str(), "OLD"); + if (!mFile->IsOpen()) { + LOG(error) << "MFTTracksRootFileReader::init. Cannot open the file: " << filename.c_str(); + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + + // get the tree + mTree = (TTree*)mFile->Get("o2sim"); + mTree->SetBranchAddress("MFTTrack", &ptracks); + mTree->SetBranchAddress("MFTTracksROF", &profs); + + // check that it has entries + mNumberOfTF = mTree->GetEntries(); + if (mNumberOfTF == 0) { + LOG(error) << "MFTTracksRootFileReader::init. No TFs "; + ic.services().get().endOfStream(); + ic.services().get().readyToQuit(QuitRequest::Me); + return; + } + } + + //_________________________________________________________________________________________________ + + void run(framework::ProcessingContext& pc) + { + + // Check if this is the last TF + if (mCurrentTF == mNumberOfTF) { + LOG(info) << " MFTDTracksRootFileReader::run. End of file reached"; + pc.services().get().endOfStream(); + pc.services().get().readyToQuit(QuitRequest::Me); + return; + } + + mTree->GetEntry(mCurrentTF); // get TF + mNumberOfROF = rofs.size(); // get number of ROFs in this TF + + // prepare the rof output + std::vector* oneROFvec = new std::vector(); + std::copy(rofs.begin() + mCurrentROF, rofs.begin() + mCurrentROF + 1, std::back_inserter(*oneROFvec)); + + // get the tracks in current ROF + // --> get the current ROF + auto& rof = rofs[mCurrentROF]; + // --> find the ranges + int index = rof.getFirstEntry(); // first track position + int numberOfTracksInROF = rof.getNEntries(); + int lastIndex = index + numberOfTracksInROF; + + // --> fill in the corresponding tracks + std::vector* tracksInROF = new std::vector(); + std::copy(tracks.begin() + index, tracks.begin() + lastIndex, std::back_inserter(*tracksInROF)); + + // fill in the message + pc.outputs().snapshot(Output{ "MFT", "TRACKS", 0 }, *tracksInROF); + pc.outputs().snapshot(Output{ "MFT", "MFTTrackROF", 0 }, *oneROFvec); + + // update the ROF counter + mCurrentROF++; + + // check if we need to read a new TF + if (mCurrentROF == mNumberOfROF) { + mCurrentTF++; + mCurrentROF = 0; + } + } + + private: + std::unique_ptr mFile = nullptr; // file to be read + TTree* mTree = nullptr; // tree inside the file + std::vector rofs, *profs = &rofs; // pointer to ROF branch + std::vector tracks, *ptracks = &tracks; // pointer to digit branch + + unsigned long mNumberOfTF = 0; // number of TF + unsigned long mNumberOfROF = 0; // number of ROFs in current TF + unsigned long mCurrentROF = 2; // idx of current ROF + unsigned long mCurrentTF = 0; // idx of current TF + +}; // end class definition + +WorkflowSpec defineDataProcessing(const ConfigContext&) +{ + WorkflowSpec specs; // to return the work flow + + // define the outputs + std::vector outputs; + + outputs.emplace_back("MFT", "TRACKS", 0, Lifetime::Timeframe); + outputs.emplace_back("MFT", "MFTTrackROF", 0, Lifetime::Timeframe); + + // The producer to generate some data in the workflow + DataProcessorSpec producer{ + "tracks-root-file-reader-mft", + Inputs{}, + outputs, + AlgorithmSpec{ adaptFromTask() }, + Options{ { "mft-track-infile", VariantType::String, "mfttracks.root", { "Name of the input file" } } } + }; + specs.push_back(producer); + + return specs; +} diff --git a/Modules/MFT/test/testQcMFT.cxx b/Modules/MFT/test/testQcMFT.cxx new file mode 100644 index 0000000000..3e62b98c2e --- /dev/null +++ b/Modules/MFT/test/testQcMFT.cxx @@ -0,0 +1,31 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testMFT.cxx +/// \author Tomas Herman +/// \author Guillermo Contreras + +// Quality Control +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::mft +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::mft diff --git a/Modules/MUON/CMakeLists.txt b/Modules/MUON/CMakeLists.txt new file mode 100644 index 0000000000..a40647cd16 --- /dev/null +++ b/Modules/MUON/CMakeLists.txt @@ -0,0 +1,5 @@ +# ---- Muon directories ---- + +add_subdirectory(Common) +add_subdirectory(MCH) +add_subdirectory(MID) diff --git a/Modules/MUON/Common/CMakeLists.txt b/Modules/MUON/Common/CMakeLists.txt new file mode 100644 index 0000000000..d9f46030da --- /dev/null +++ b/Modules/MUON/Common/CMakeLists.txt @@ -0,0 +1,80 @@ +set(MODULE_NAME "O2QcMUONCommon") + +# ---- Files ---- + +set(SRCS + src/Helpers.cxx + src/HistPlotter.cxx + src/MuonTrack.cxx + src/TrackPlotter.cxx + src/TracksCheck.cxx + src/TracksTask.cxx + src/TracksPostProcessing.cxx + src/TracksPostProcessingConfig.cxx + src/MatchingEfficiencyCheck.cxx +) + +set(HEADERS + include/MUONCommon/HistPlotter.h + include/MUONCommon/MuonTrack.h + include/MUONCommon/TrackCheck.h + include/MUONCommon/TrackPlotter.h + include/MUONCommon/TracksTask.h + include/MUONCommon/TracksCheck.h + include/MUONCommon/TracksPostProcessing.h + include/MUONCommon/TracksPostProcessingConfig.h + include/MUONCommon/MatchingEfficiencyCheck.h +) + +# ---- Library ---- + +add_library(${MODULE_NAME} SHARED ${SRCS}) + +target_include_directories( + ${MODULE_NAME} + PUBLIC $ $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +target_link_libraries(${MODULE_NAME} PUBLIC O2QualityControl O2QcCommon O2::MCHMappingImpl4 O2::DataFormatsMCH O2::MCHTracking O2::MCHRawElecMap O2::ReconstructionDataFormats O2::MCHGeometryTransformer O2::DataFormatsGlobalTracking) + +# Digit.h is moving from MCHBase to DataFormatsMCH : let's handle both +# gracefully for the moment... +get_target_property(O2_INCLUDE_DIRS O2::CommonDataFormat INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(ROOT_INCLUDE_DIRS ROOT::Core INTERFACE_INCLUDE_DIRECTORIES) + +set(CMAKE_REQUIRED_INCLUDES ${O2_INCLUDE_DIRS} ${ROOT_INCLUDE_DIRS}) + +install( + TARGETS ${MODULE_NAME} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +# ---- ROOT dictionary ---- + +add_root_dictionary(${MODULE_NAME} + HEADERS include/MUONCommon/TrackPlotter.h + include/MUONCommon/TracksCheck.h + include/MUONCommon/TracksTask.h + include/MUONCommon/TracksCheck.h + include/MUONCommon/TracksPostProcessing.h + include/MUONCommon/MatchingEfficiencyCheck.h + LINKDEF include/MUONCommon/LinkDef.h) + +# ---- Tests ---- + +set( + TEST_SRCS +) + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} PRIVATE ${MODULE_NAME} Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 60) +endforeach() diff --git a/Modules/MUON/Common/Readme.md b/Modules/MUON/Common/Readme.md new file mode 100644 index 0000000000..a6a2167607 --- /dev/null +++ b/Modules/MUON/Common/Readme.md @@ -0,0 +1,262 @@ +# Muon Tracks QC + +## TracksTask + +The tracks task processes the forward muon tracks and produces a number of plots showing the kinematics and time distributions of the tracks. +The type of tracks that are processed is controlled via the `GID` list in the task configuration. The following options can be selected: +* `MCH`: all standalone MCH tracks are considered, independently of their matching with MFT and MID +* `MFT-MCH`: tracks matched from MFT to MCH, independently of their matching with MID +* `MCH-MID`: tracks matched from MCH to MID, independently of their matching with MFT +* `MFT-MCH-MID`: global forward tracks matched to all three detectors + +For each selection listed in the `GID` parameter, two sets of similar plots are produced, one for all the tracks, the second only considering the tracks that pass some standard cuts, namely: +* `cutRAbsMin < RAbs < cutRAbsMax` +* `cutEtaMin < eta < cutEtaMax` +* `pT > cutPtMin` +* `sigmaPDCA < nSigmaPDCA` +* `chi2 < cutChi2Max` + +All the cuts are computed using the standalone MCH tracks parameters, regardless of the matching, in order to have a consistent selection for all track types. + +In addition to the single-track cuts listed above, a cut (`diMuonTimeCut`) on the time difference (in nanoseconds) between track pairs is applied when filling the di-muon invariant mass plot. + +For each selection, the following histograms are filled: +* track kinematic parameters (pT, eta, phi) and their correlations +* distribution of the BC ids associated to the tracks +* tracks multiplicity per TF +* track quality parameters (chi2, RAbs, P*DCA, matching chi2 and probablity) +* 2-D distributions of the MCH track impact points at the MFT exit and MID entrance planes + +The plots are saved in sub-folders inside `GLO/MUONTracks`, one folder for each `GID`. An additional sub-folder is aslo created for the plots after selection cuts. +In the example below, the folder structure would look like this: + +``` +GLO/MUONTracks/MFT-MCH/ +GLO/MUONTracks/MFT-MCH/WithCuts/ +GLO/MUONTracks/MCH-MID/ +GLO/MUONTracks/MCH-MID/WithCuts/ +GLO/MUONTracks/MFT-MCH-MID/ +GLO/MUONTracks/MFT-MCH-MID/WithCuts/ +``` + +#### Configuration +```json +{ + "qc": { + "tasks": { + "TaskMUONTracks": { + "active": "true", + "className": "o2::quality_control_modules::muon::TracksTask", + "moduleName": "QcMUONCommon", + "detectorName": "GLO", + "taskName": "MUONTracks", + "cycleDurationSeconds": "300", + "maxNumberCycles": "-1", + "dataSource": { + "type": "direct", + "query": "trackMCH:MCH/TRACKS;trackMCHROF:MCH/TRACKROFS;trackMCHTRACKCLUSTERS:MCH/TRACKCLUSTERS;trackMFT:MFT/TRACKS;trackMFTROF:MFT/MFTTrackROF;trackMFTClIdx:MFT/TRACKCLSID;alpparMFT:MFT/ALPIDEPARAM;trackMID:MID/TRACKS;trackMIDROF:MID/TRACKROFS;trackMIDTRACKCLUSTERS:MID/TRACKCLUSTERS;trackClMIDROF:MID/TRCLUSROFS;matchMCHMID:GLO/MTC_MCHMID;fwdtracks:GLO/GLFWD" + }, + "taskParameters": { + "maxTracksPerTF": "600", + "cutRAbsMin": "17.6", + "cutRAbsMax": "89.5", + "cutEtaMin": "-4.0", + "cutEtaMax": "-2.5", + "cutPtMin": "0.5", + "cutPtMin": "0.5", + "nSigmaPDCA": "6", + "cutChi2Max": "1000", + "diMuonTimeCut": "100", + "fullHistos": "0", + "GID" : "MFT-MCH,MCH-MID,MFT-MCH-MID" + }, + "grpGeomRequest": { + "geomRequest": "Aligned", + "askGRPECS": "true", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "false", + "needPropagatorD": "false" + }, + "location": "remote" + } + } + } +} +``` + +## Tracks Post-processing + +The tracks post-processing task takes the plots produced by the tracks task and computes the matching efficiency from their ratios. +Each data source in the task configuration specifies the path of the plots from matched tracks(numerator), the path of the reference plots from un-matched tracks (denominator), and the path where to store the matching efficiency plots (ratios). The data source also contains a `names` parameter with the list of plots to be compared. Each plot name can be followed by an optional number, separated by a semi-colon, that indicates the rebinning factor to be applied to the plots before computing the ratio. + +#### Configuration + +```json +{ + "qc": { + "postprocessing": { + "MUONTracks": { + "active": "true", + "className": "o2::quality_control_modules::muon::TracksPostProcessing", + "moduleName": "QcMUONCommon", + "detectorName": "GLO", + "dataSources": [ + { + "plotsPath": "GLO/MO/MUONTracks/MCH-MID/WithCuts", + "refsPath": "MCH/MO/Tracks/WithCuts", + "outputPath": "MCH-MID/WithCuts/MatchEff", + "names": [ "TrackPt:5", "TrackEta:5", "TrackPhi:10", "TrackEtaPt", "TrackPhiPt", "TrackPosAtMID" ] + }, + { + "plotsPath": "GLO/MO/MUONTracks/MCH-MID/WithCuts", + "refsPath": "MCH/MO/Tracks/WithCuts", + "outputPath": "MCH-MID/WithCuts/MatchEff", + "names": [ "TrackPt:5", "TrackEta:5", "TrackPhi:10", "TrackEtaPt", "TrackPhiPt", "TrackPosAtMID" ] + }, + { + "plotsPath": "GLO/MO/MUONTracks/MFT-MCH-MID", + "refsPath": "MCH/MO/Tracks", + "outputPath": "MFT-MCH-MID/MatchEff-MCH", + "names": [ "TrackPt:5", "TrackEta:5", "TrackPhi:10", "TrackEtaPt", "TrackPhiPt", "TrackPosAtMID", "TrackPosAtMFT" ] + }, + { + "plotsPath": "GLO/MO/MUONTracks/MFT-MCH-MID", + "refsPath": "GLO/MO/MUONTracks/MCH-MID", + "outputPath": "MFT-MCH-MID/MatchEff-MCH-MID", + "names": [ "TrackPt:5", "TrackEta:5", "TrackPhi:10", "TrackEtaPt", "TrackPhiPt", "TrackPosAtMID", "TrackPosAtMFT" ] + }, + { + "plotsPath": "GLO/MO/MUONTracks/MFT-MCH-MID/WithCuts", + "refsPath": "MCH/MO/Tracks/WithCuts", + "outputPath": "MFT-MCH-MID/WithCuts/MatchEff-MCH", + "names": [ "TrackPt:5", "TrackEta:5", "TrackPhi:10", "TrackEtaPt", "TrackPhiPt", "TrackPosAtMID", "TrackPosAtMFT" ] + }, + { + "plotsPath": "GLO/MO/MUONTracks/MFT-MCH-MID/WithCuts", + "refsPath": "GLO/MO/MUONTracks/MCH-MID/WithCuts", + "outputPath": "MFT-MCH-MID/WithCuts/MatchEff-MCH-MID", + "names": [ "TrackPt:5", "TrackEta:5", "TrackPhi:10", "TrackEtaPt", "TrackPhiPt", "TrackPosAtMID", "TrackPosAtMFT" ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:GLO/MO/MUONTracks/MCH-MID/TrackPt" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} +``` + +## Tracks Checker + +The tracks checker verifies the following distributions: +* `TracksPerTF`: it checks that the average is within configurable limits +* `TrackPhi`: it verifies that the mean number of tracks in each quadrant of the spectrometer does not differ by more than a configurable threshold +* `TrackQOverPt`: it verifies that the relative difference between the total number of positive and negative tracks (charge asymmetry) is below a configurable threshold + +#### Confguration + +```json +{ + "qc": { + "checks": { + "TracksCheck": { + "active": "true", + "className": "o2::quality_control_modules::muon::TracksCheck", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "policy": "OnAll", + "extendedCheckParameters": { + "default": { + "default": { + "minTracksPerTF": "100", + "maxTracksPerTF": "500", + "maxDeltaPhi": "0.2", + "maxChargeAsymmetry": "0.2", + "markerSize": "0.8" + } + } + }, + "dataSource": [ + { + "type": "Task", + "name": "MCHTracks", + "MOs" : [ + "WithCuts/TracksPerTF", + "WithCuts/TrackPhi", + "WithCuts/TrackQOverPt" + ] + } + ] + } + } + } +} +``` + +## Matching Efficiency Checker + +The matching efficiency checker takes the output of the tracks post-processing and verifies that the efficiency values in each plot are within configurable limits. The limits are defined separately for each histogram, or group of histograms, whose name matches a given string. +For 1-D plots, the check can be additionally restricetd to one or more ranges in the x variable, in case some kinematical regions need to be excluded. + +For example, the following line corresponds to an acceptable range, for the plots named `TrackEta`, of `[0.3,1.0]` and only checked within the eta ranges `[-4.0,-3.5]` and `[-3.0,-2.5]`: + +``` +"range:TrackEta": "0.3,1.0:-4.0,-3.5:-3.0,-2.5" +``` + +If no limit is specified for a given plot, the corresponding values are not checked and the plot is only beautified. + +For 2-D plots, the beautification only consists in fixing the z-axis range to `[0.0,1.2]`. +For 1-D plots, the acceptable range is also indicated with horizontal lines, and the plot is painted in red if the quality is judged to be bad. + +#### Configuration + +```json +{ + "qc": { + "checks": { + "MUONMatchCheck": { + "active": "true", + "className": "o2::quality_control_modules::muon::MatchingEfficiencyCheck", + "moduleName": "QcMUONCommon", + "detectorName": "GLO", + "policy": "OnAll", + "extendedCheckParameters": { + "default": { + "default": { + "range:TrackEta": "0.3,1.0:-4.0,-3.5:-3.0,-2.5", + "range:TrackPt": "0.4,1.0:0.5,2.0", + "range:WithCuts": "0.4,1.0" + } + } + }, + "dataSource": [ + { + "type": "PostProcessing", + "name": "MUONTracks", + "MOs" : [ + "MCH-MID/MatchEff/TrackPhi", + "MCH-MID/MatchEff/TrackPt", + "MCH-MID/MatchEff/TrackEta", + "MCH-MID/WithCuts/MatchEff/TrackPhi", + "MCH-MID/WithCuts/MatchEff/TrackPt", + "MCH-MID/WithCuts/MatchEff/TrackEta", + "MCH-MID/WithCuts/MatchEff/TrackPosAtMID" + ] + } + ] + } + } + } +} +``` diff --git a/Modules/MUON/Common/etc/qc-tracks-mch.json b/Modules/MUON/Common/etc/qc-tracks-mch.json new file mode 100644 index 0000000000..b5e7c39ff3 --- /dev/null +++ b/Modules/MUON/Common/etc/qc-tracks-mch.json @@ -0,0 +1,88 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "localhost:6464", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "MCHTracks": { + "active": "true", + "className": "o2::quality_control_modules::muon::TracksTask", + "moduleName": "QcMUONCommon", + "detectorName": "MCH", + "taskName": "Tracks", + "cycleDurationSeconds": "600", + "dataSource": { + "type": "direct", + "query": "trackMCH:MCH/TRACKS;trackMCHROF:MCH/TRACKROFS;trackMCHTRACKCLUSTERS:MCH/TRACKCLUSTERS" + }, + "taskParameters": { + "cutRAbsMin": "17.6", + "cutRAbsMax": "89.5", + "cutEtaMin": "-4.0", + "cutEtaMax": "-2.5", + "cutPtMin": "0.5", + "nSigmaPDCA": "6", + "cutChi2Max": "1000", + "diMuonTimeCut": "100", "": "in nanoseconds", + "etaBins": "100", + "phiBins": "100", + "ptBins": "100", + "maxTracksPerTF": "1000", + "GID" : "MCH" + }, + "grpGeomRequest" : { + "geomRequest": "Aligned", + "askGRPECS": "true", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location": "remote" + } + }, + "checks": { + "TracksCheck": { + "active": "true", + "className": "o2::quality_control_modules::muon::TracksCheck", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "policy": "OnAll", + "extendedCheckParameters": { + "default": { + "default": { + "minTracksPerTF": "100", + "maxTracksPerTF": "500", + "maxDeltaPhi": "0.2", + "maxChargeAsymmetry": "0.2", + "markerSize": "0.8" + } + } + }, + "dataSource": [ + { + "type": "Task", + "name": "MCHTracks", + "MOs" : [ + "WithCuts/TracksPerTF", + "WithCuts/TrackPhi", + "WithCuts/TrackQOverPt" + ] + } + ] + } + } + } +} diff --git a/Modules/MUON/Common/etc/qc-tracks-mchmid.json b/Modules/MUON/Common/etc/qc-tracks-mchmid.json new file mode 100644 index 0000000000..b9a6d7a826 --- /dev/null +++ b/Modules/MUON/Common/etc/qc-tracks-mchmid.json @@ -0,0 +1,35 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "MUONTracks": { + "active": "true", + "className": "o2::quality_control_modules::muon::TracksTask", + "moduleName": "QcMUONCommon", + "detectorName": "GLO", + "cycleDurationSeconds": "600", + "dataSource": { + "type": "direct", + "query": "trackMCH:MCH/TRACKS;trackMCHROF:MCH/TRACKROFS;trackMCHTRACKCLUSTERS:MCH/TRACKCLUSTERS;trackMID:MID/TRACKS;trackMIDROF:MID/TRACKROFS;trackMIDTRACKCLUSTERS:MID/TRACKCLUSTERS;trackClMIDROF:MID/TRCLUSROFS;matchMCHMID:GLO/MTC_MCHMID" + }, + "taskParameters": { + "maxTracksPerTF": "600", + "GID" : "MCH,MID,MCH-MID" + }, + "location": "remote" + } + } + } +} diff --git a/Modules/MUON/Common/etc/qc-tracks-mftmch.json b/Modules/MUON/Common/etc/qc-tracks-mftmch.json new file mode 100644 index 0000000000..0807a8a8a8 --- /dev/null +++ b/Modules/MUON/Common/etc/qc-tracks-mftmch.json @@ -0,0 +1,35 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "MUONTracks": { + "active": "true", + "className": "o2::quality_control_modules::muon::TracksTask", + "moduleName": "QcMUONCommon", + "detectorName": "GLO", + "cycleDurationSeconds": "600", + "dataSource": { + "type": "direct", + "query": "trackMCH:MCH/TRACKS;trackMCHROF:MCH/TRACKROFS;trackMCHTRACKCLUSTERS:MCH/TRACKCLUSTERS;trackMFT:MFT/TRACKS;trackMFTROF:MFT/MFTTrackROF;trackMFTClIdx:MFT/TRACKCLSID;alpparMFT:MFT/ALPIDEPARAM;fwdtracks:GLO/GLFWD" + }, + "taskParameters": { + "maxTracksPerTF": "600", + "GID" : "MCH,MFT,MFT-MCH" + }, + "location": "remote" + } + } + } +} diff --git a/Modules/MUON/Common/etc/qc-tracks-mftmchmid.json b/Modules/MUON/Common/etc/qc-tracks-mftmchmid.json new file mode 100644 index 0000000000..903429d23c --- /dev/null +++ b/Modules/MUON/Common/etc/qc-tracks-mftmchmid.json @@ -0,0 +1,35 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "MUONTracks": { + "active": "true", + "className": "o2::quality_control_modules::muon::TracksTask", + "moduleName": "QcMUONCommon", + "detectorName": "GLO", + "cycleDurationSeconds": "600", + "dataSource": { + "type": "direct", + "query": "trackMCH:MCH/TRACKS;trackMCHROF:MCH/TRACKROFS;trackMCHTRACKCLUSTERS:MCH/TRACKCLUSTERS;trackMFT:MFT/TRACKS;trackMFTROF:MFT/MFTTrackROF;trackMFTClIdx:MFT/TRACKCLSID;alpparMFT:MFT/ALPIDEPARAM;trackMID:MID/TRACKS;trackMIDROF:MID/TRACKROFS;trackMIDTRACKCLUSTERS:MID/TRACKCLUSTERS;trackClMIDROF:MID/TRCLUSROFS;matchMCHMID:GLO/MTC_MCHMID;fwdtracks:GLO/GLFWD" + }, + "taskParameters": { + "maxTracksPerTF": "600", + "GID" : "MCH,MFT,MID,MFT-MCH,MCH-MID,MFT-MCH-MID" + }, + "location": "remote" + } + } + } +} diff --git a/Modules/MUON/Common/include/MUONCommon/Helpers.h b/Modules/MUON/Common/include/MUONCommon/Helpers.h new file mode 100644 index 0000000000..09b69e1c2d --- /dev/null +++ b/Modules/MUON/Common/include/MUONCommon/Helpers.h @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_MUON_COMMON_HELPERS_H +#define QC_MODULE_MUON_COMMON_HELPERS_H + +#include "QualityControl/CustomParameters.h" +#include + +class TH1; +class TLine; + +namespace o2::quality_control_modules::muon +{ +template +T getConfigurationParameter(o2::quality_control::core::CustomParameters customParameters, std::string parName, const T defaultValue) +{ + T result = defaultValue; + auto parOpt = customParameters.atOptional(parName); + if (parOpt.has_value()) { + std::stringstream ss(parOpt.value()); + ss >> result; + } + return result; +} + +template +T getConfigurationParameter(o2::quality_control::core::CustomParameters customParameters, std::string parName, const T defaultValue, const o2::quality_control::core::Activity& activity) +{ + auto parOpt = customParameters.atOptional(parName, activity); + if (parOpt.has_value()) { + T result; + std::stringstream ss(parOpt.value()); + ss >> result; + return result; + } + return getConfigurationParameter(customParameters, parName, defaultValue); +} + +// create an array of bins in a given range with log10 spacing +std::vector makeLogBinning(double xmin, double xmax, int nbins); + +// add lines to an histogram with specified color, style and width, using ROOT's TAttLine conventions +TLine* addHorizontalLine(TH1& histo, double y, + int lineColor = 1, int lineStyle = 10, + int lineWidth = 1); +TLine* addVerticalLine(TH1& histo, double x, + int lineColor = 1, int lineStyle = 10, + int lineWidth = 1); +void cleanup(TH1& histo, const char* classname); +void markBunchCrossing(TH1& histo, + gsl::span bunchCrossings); + +} // namespace o2::quality_control_modules::muon + +#endif diff --git a/Modules/MUON/Common/include/MUONCommon/HistPlotter.h b/Modules/MUON/Common/include/MUONCommon/HistPlotter.h new file mode 100644 index 0000000000..4600401897 --- /dev/null +++ b/Modules/MUON/Common/include/MUONCommon/HistPlotter.h @@ -0,0 +1,57 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_MUON_COMMON_HIST_PLOTTER_H +#define QC_MODULE_MUON_COMMON_HIST_PLOTTER_H + +#include "QualityControl/ObjectsManager.h" +#include +#include +#include + +namespace o2::quality_control_modules::muon +{ + +class HistPlotter +{ + public: + HistPlotter() = default; + virtual ~HistPlotter() = default; + + public: + struct HistInfo { + TObject* object; + std::string drawOptions; + std::string displayHints; + }; + + /** reset all histograms */ + void reset(); + + virtual void endOfCycle() {} + + std::vector& histograms() { return mHistograms; } + const std::vector& histograms() const { return mHistograms; } + + virtual void publish(std::shared_ptr objectsManager, + o2::quality_control::core::PublicationPolicy policy = o2::quality_control::core::PublicationPolicy::Forever); + virtual void publish(std::shared_ptr objectsManager, HistInfo& hinfo, + o2::quality_control::core::PublicationPolicy policy = o2::quality_control::core::PublicationPolicy::Forever); + virtual void unpublish(std::shared_ptr objectsManager); + + private: + std::vector mHistograms; + std::vector mPublishedHistograms; +}; + +} // namespace o2::quality_control_modules::muon + +#endif diff --git a/Modules/MUON/Common/include/MUONCommon/LinkDef.h b/Modules/MUON/Common/include/MUONCommon/LinkDef.h new file mode 100644 index 0000000000..3148492dc2 --- /dev/null +++ b/Modules/MUON/Common/include/MUONCommon/LinkDef.h @@ -0,0 +1,12 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::muon::TrackPlotter + ; +#pragma link C++ class o2::quality_control_modules::muon::TracksTask + ; +#pragma link C++ class o2::quality_control_modules::muon::TracksCheck + ; +#pragma link C++ class o2::quality_control_modules::muon::TracksPostProcessing + ; +#pragma link C++ class o2::quality_control_modules::muon::MatchingEfficiencyCheck + ; + +#endif diff --git a/Modules/MUON/Common/include/MUONCommon/MatchingEfficiencyCheck.h b/Modules/MUON/Common/include/MUONCommon/MatchingEfficiencyCheck.h new file mode 100644 index 0000000000..ecdf355d9b --- /dev/null +++ b/Modules/MUON/Common/include/MUONCommon/MatchingEfficiencyCheck.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MatchingEfficiencyCheck.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_MUON_MATCHINGEFFICIENCYCHECK_H +#define QC_MODULE_MUON_MATCHINGEFFICIENCYCHECK_H + +#include "QualityControl/CheckInterface.h" + +#include + +namespace o2::quality_control_modules::muon +{ + +/// \brief Check whether the matching efficiency is within some configurable limits +/// +/// \author Andrea Ferrero +class MatchingEfficiencyCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + MatchingEfficiencyCheck() = default; + /// Destructor + ~MatchingEfficiencyCheck() override = default; + + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + ClassDefOverride(MatchingEfficiencyCheck, 1); + + private: + void initRange(std::string key); + std::optional> getRange(std::string key); + + Activity mActivity; + std::map> mRanges; + std::map>> mIntervals; + std::map mQualities; +}; + +} // namespace o2::quality_control_modules::muon + +#endif // QC_MODULE_MUON_MATCHINGEFFICIENCYCHECK_H diff --git a/Modules/MUON/Common/include/MUONCommon/MuonTrack.h b/Modules/MUON/Common/include/MUONCommon/MuonTrack.h new file mode 100644 index 0000000000..8453de3243 --- /dev/null +++ b/Modules/MUON/Common/include/MUONCommon/MuonTrack.h @@ -0,0 +1,176 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_MUON_COMMON_TRACK_H +#define QC_MODULE_MUON_COMMON_TRACK_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::muon +{ + +class MuonTrack +{ + public: + using Time = o2::dataformats::TimeStampWithError; + + public: + MuonTrack() = default; + MuonTrack(const o2::mch::TrackMCH* track, int trackID, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit); + MuonTrack(const o2::dataformats::TrackMCHMID* track, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit); + MuonTrack(const o2::dataformats::GlobalFwdTrack* track, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit); + + void init(); + + ROOT::Math::PxPyPzMVector getMuonMomentum() const { return mMuonMomentum; } + ROOT::Math::PxPyPzMVector getMuonMomentumAtVertex() const { return mMuonMomentumAtVertex; } + ROOT::Math::PxPyPzMVector getMuonMomentumMCH() const { return mMuonMomentumMCH; } + ROOT::Math::PxPyPzMVector getMuonMomentumAtVertexMCH() const { return mMuonMomentumAtVertexMCH; } + double getP() const; + double getPt() const; + double getEta() const; + double getPhi() const; + double getDCA() const { return mDCA; } + double getPDCA() const { return getMuonMomentum().P() * mDCA; } + double getPMCH() const; + double getPtMCH() const; + double getEtaMCH() const; + double getPhiMCH() const; + double getDCAMCH() const { return mDCAMCH; } + double getPDCAMCH() const { return getMuonMomentumMCH().P() * mDCAMCH; } + double getRAbs() const { return mRAbs; } + double getChi2OverNDF() const { return mChi2OverNDF; } + double getChi2OverNDFMFT() const { return mChi2OverNDFMFT; } + double getChi2OverNDFMCH() const { return mChi2OverNDFMCH; } + double getChi2OverNDFMID() const { return mChi2OverNDFMID; } + + /// get the track x position + double getXMid() const { return mTrackParametersAtMID.getNonBendingCoor(); } + /// get the track y position + double getYMid() const { return mTrackParametersAtMID.getBendingCoor(); } + /// get the track z position where the parameters are evaluated + double getZMid() const { return mTrackParametersAtMID.getZ(); } + + const o2::dataformats::MatchInfoFwd& getMatchInfoFwd() const { return mMatchInfoFwd; } + + /// get the interaction record associated to this track + InteractionRecord getIR() const { return mIR; } + /// get the interaction record associated to the MFT track + InteractionRecord getIRMFT() const { return mIRMFT; } + /// get the interaction record associated to the MCH track + InteractionRecord getIRMCH() const { return mIRMCH; } + /// get the interaction record associated to the MID track + InteractionRecord getIRMID() const { return mIRMID; } + + Time getTime() const { return mTime; } + Time getTimeMFT() const { return mTimeMFT; } + Time getTimeMCH() const { return mTimeMCH; } + Time getTimeMID() const { return mTimeMID; } + + /// get the ROF associated to the MCH track + o2::mch::ROFRecord getRofMCH() const { return mRofMCH; } + + Time getRofTimeMCH() const { return mRofTimeMCH; } + + /// get the interaction record associated to the MFT track + int getTrackIdMFT() const { return mTrackIdMFT; } + /// get the interaction record associated to the MCH track + int getTrackIdMCH() const { return mTrackIdMCH; } + /// get the interaction record associated to the MID track + int getTrackIdMID() const { return mTrackIdMID; } + + /// get the interaction record associated to the MCH track + const o2::mft::TrackMFT* getTrackMFT() const { return mTrackMFT; } + /// get the interaction record associated to the MCH track + const o2::mch::TrackMCH* getTrackMCH() const { return mTrackMCH; } + /// get the interaction record associated to the MID track + const o2::mid::Track* getTrackMID() const { return mTrackMID; } + + const o2::mch::TrackParam& getTrackParamMFT() const { return mTrackParametersMFT; } + const o2::mch::TrackParam& getTrackParamMCH() const { return mTrackParametersMCH; } + const o2::mch::TrackParam& getTrackParamMID() const { return mTrackParametersMID; } + + bool extrapToZMFT(o2::mch::TrackParam& trackParam, float z) const; + bool extrapToZMCH(o2::mch::TrackParam& trackParam, float z) const; + bool extrapToZMID(o2::mch::TrackParam& trackParam, float z) const; + + bool hasMFT() const { return (mTrackIdMFT >= 0); } + bool hasMCH() const { return (mTrackIdMCH >= 0); } + bool hasMID() const { return (mTrackIdMID >= 0); } + + /// get the muon sign + short getSign() const { return mSign; } + + bool canBeMuon() const; + + static constexpr double sAbsZBeg = -90.; ///< Position of the begining of the absorber (cm) + static constexpr double sAbsZEnd = -505.; ///< Position of the end of the absorber (cm) + + private: + o2::dataformats::MatchInfoFwd mMatchInfoFwd; + + o2::mch::TrackParam mTrackParameters; + o2::mch::TrackParam mTrackParametersMFT; + o2::mch::TrackParam mTrackParametersMCH; + o2::mch::TrackParam mTrackParametersMID; + o2::mch::TrackParam mTrackParametersAtMID; + + ROOT::Math::PxPyPzMVector mMuonMomentum; + ROOT::Math::PxPyPzMVector mMuonMomentumAtVertex; + ROOT::Math::PxPyPzMVector mMuonMomentumMCH; + ROOT::Math::PxPyPzMVector mMuonMomentumAtVertexMCH; + + float mDCA{ 0 }; + float mDCAMCH{ 0 }; + float mRAbs{ 0 }; + float mChi2OverNDF{ 0 }; + float mChi2OverNDFMFT{ 0 }; + float mChi2OverNDFMCH{ 0 }; + float mChi2OverNDFMID{ 0 }; + + InteractionRecord mIR{}; ///< associated interaction record + InteractionRecord mIRMFT{}; ///< MFT interaction record + InteractionRecord mIRMCH{}; ///< MCH interaction record + InteractionRecord mIRMID{}; ///< MID interaction record + + Time mTime; + Time mTimeMFT; + Time mTimeMCH; + Time mTimeMID; + + o2::mch::ROFRecord mRofMCH{}; + + Time mRofTimeMCH; + + int mTrackIdMFT{ -1 }; + int mTrackIdMCH{ -1 }; + int mTrackIdMID{ -1 }; + + const o2::mft::TrackMFT* mTrackMFT{ nullptr }; + const o2::mch::TrackMCH* mTrackMCH{ nullptr }; + const o2::mid::Track* mTrackMID{ nullptr }; + + short mSign; +}; + +} // namespace o2::quality_control_modules::muon + +#endif diff --git a/Modules/MUON/Common/include/MUONCommon/TrackPlotter.h b/Modules/MUON/Common/include/MUONCommon/TrackPlotter.h new file mode 100644 index 0000000000..8cd34bce4a --- /dev/null +++ b/Modules/MUON/Common/include/MUONCommon/TrackPlotter.h @@ -0,0 +1,227 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_MUON_COMMON_TRACK_PLOTTER_H +#define QC_MODULE_MUON_COMMON_TRACK_PLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MUONCommon/MuonTrack.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" + +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include +#include "MFTTracking/Constants.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using GID = o2::dataformats::GlobalTrackID; + +using namespace o2::quality_control_modules::common; + +using MuonCutFunc = std::function; +using DiMuonCutFunc = std::function; + +namespace o2::quality_control_modules::muon +{ + +class TrackPlotter : public HistPlotter +{ + public: + static constexpr Double_t sLastMFTPlaneZ = o2::mft::constants::mft::LayerZCoordinate()[9]; + + TrackPlotter(int maxTracksPerTF, int etaBins, int phiBins, int ptBins, GID::Source source, std::string path, bool fullHistos = false); + ~TrackPlotter() = default; + + public: + void setMuonCuts(const std::vector& cuts) { mMuonCuts = cuts; } + void setDiMuonCuts(const std::vector& cuts) { mDiMuonCuts = cuts; } + + void addMuonCut(const MuonCutFunc f) { mMuonCuts.push_back(f); } + void addDiMuonCut(const DiMuonCutFunc f) { mDiMuonCuts.push_back(f); } + + void fillHistograms(const o2::globaltracking::RecoContainer& recoCont); + void setFirstTForbit(int orbit) { mFirstTForbit = orbit; } + + void endOfCycle(); + + const std::vector>& getMuonTracks() const { return mMuonTracks; } + + private: + /** create histograms related to tracks */ + void createTrackHistos(int maxTracksPerTF, int etaBins, int phiBins, int ptBins); + /** create histograms related to track pairs */ + void createTrackPairHistos(); + + /** create one histogram with relevant drawing options / stat box status.*/ + template + std::unique_ptr createHisto(const char* name, const char* title, + int nbins, double xmin, double xmax, + bool optional, + bool statBox = false, + const char* drawOptions = "", + const char* displayHints = ""); + template + std::unique_ptr createHisto(const char* name, const char* title, + int nbins, double* xbins, + bool optional, + bool statBox = false, + const char* drawOptions = "", + const char* displayHints = ""); + template + std::unique_ptr createHisto(const char* name, const char* title, + int nbins, double xmin, double xmax, + int nbinsy, double ymin, double ymax, + bool optional, + bool statBox = false, + const char* drawOptions = "", + const char* displayHints = ""); + + /** fill histograms related to a single track */ + void fillTrackHistos(const MuonTrack& track); + + /** fill histograms for track pairs */ + void fillTrackPairHistos(gsl::span> tracks); + + void normalizePlot(TH1* h); + + GID::Source mSrc; + std::string mPath; + + bool mFullHistos; + + uint32_t mFirstTForbit{ 0 }; + int mNOrbitsPerTF{ -1 }; + + std::vector> mMuonTracks; + + std::vector mMuonCuts; + std::vector mDiMuonCuts; + + std::unique_ptr mTrackBC; ///< BC associated to the track + std::unique_ptr mTrackDT; ///< time difference between MCH/MID tracks segments + std::array, 3> mNofTracksPerTF; ///< number of tracks per TF + std::array, 3> mTrackChi2OverNDF; ///< chi2/ndf for the track + std::array, 3> mTrackDCA; ///< DCA (cm) of the track + std::array, 3> mTrackPDCA; ///< p (GeV/c) x DCA (cm) of the track + std::array, 3> mTrackRAbs; ///< R at absorber end of the track + // kinematic variables, using MCH tracks parameters + std::array, 3> mTrackEta; ///< eta of the track + std::array, 3> mTrackPhi; ///< phi (in degrees) of the track + std::array, 3> mTrackPt; ///< Pt (Gev/c) of the track + std::unique_ptr mTrackQOverPt; ///< Q / Pt of the track + std::array, 3> mTrackEtaPhi; ///< phi (in degrees) vs. eta of the track + std::array, 3> mTrackEtaPt; ///< Pt (Gev/c) vs. eta of the track + std::array, 3> mTrackPhiPt; ///< Pt (Gev/c) vs. phi (in degrees) of the track + // kinematic variables, using global tracks parameters (only instantiated when MFT is included) + std::array, 3> mTrackEtaGlobal; ///< eta of the track + std::array, 3> mTrackPhiGlobal; ///< phi (in degrees) of the track + std::array, 3> mTrackPtGlobal; ///< Pt (Gev/c) of the track + std::unique_ptr mTrackQOverPtGlobal; ///< Q / Pt of the track + std::array, 3> mTrackEtaPhiGlobal; ///< phi (in degrees) vs. eta of the track + std::array, 3> mTrackEtaPtGlobal; ///< Pt (Gev/c) vs. eta of the track + std::array, 3> mTrackPhiPtGlobal; ///< Pt (Gev/c) vs. phi (in degrees) of the track + + std::unique_ptr mTrackPosAtMFT; ///< MCH track poisiton at MFT exit + std::unique_ptr mTrackPosAtMID; ///< MCH track poisiton at MID entrance + + std::unique_ptr mMatchChi2MCHMID; + + // plots specific to MFT-MCH(-MID) matched tracks + std::unique_ptr mMatchNMFTCandidates; + std::unique_ptr mMatchScoreMFTMCH; + std::unique_ptr mMatchChi2MFTMCH; + std::array, 3> mTrackEtaCorr; ///< correlation between MCH and global track parameters - eta + std::array, 3> mTrackDEtaVsEta; ///< deviation between MCH and global track parameters - eta + std::array, 3> mTrackPhiCorr; ///< correlation between MCH and global track parameters - phi + std::array, 3> mTrackDPhiVsPhi; ///< deviation between MCH and global track parameters - phi + std::array, 3> mTrackPtCorr; ///< correlation between MCH and global track parameters - pT + std::array, 3> mTrackDPtVsPt; ///< deviation between MCH and global track parameters - pT + + std::unique_ptr mMinvFull; ///< invariant mass of unlike-sign track pairs + std::unique_ptr mMinv; ///< invariant mass background of unlike-sign track pairs + std::unique_ptr mMinvBgd; ///< invariant mass background of unlike-sign, out-of-time track pairs + std::unique_ptr mDimuonDT; ///< time difference between di-muon tracks +}; + +template +std::unique_ptr TrackPlotter::createHisto(const char* name, const char* title, + int nbins, double xmin, double xmax, + bool optional, + bool statBox, + const char* drawOptions, + const char* displayHints) +{ + if (optional && !mFullHistos) { + return nullptr; + } + std::string fullTitle = std::string("[") + GID::getSourceName(mSrc) + "] " + title; + auto h = std::make_unique(name, fullTitle.c_str(), nbins, xmin, xmax); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h.get(), drawOptions, displayHints }); + return h; +} + +template +std::unique_ptr TrackPlotter::createHisto(const char* name, const char* title, + int nbins, double* xbins, + bool optional, + bool statBox, + const char* drawOptions, + const char* displayHints) +{ + if (optional && !mFullHistos) { + return nullptr; + } + std::string fullTitle = std::string("[") + GID::getSourceName(mSrc) + "] " + title; + auto h = std::make_unique(name, fullTitle.c_str(), nbins, xbins); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h.get(), drawOptions, displayHints }); + return h; +} + +template +std::unique_ptr TrackPlotter::createHisto(const char* name, const char* title, + int nbins, double xmin, double xmax, + int nbinsy, double ymin, double ymax, + bool optional, + bool statBox, + const char* drawOptions, + const char* displayHints) +{ + if (optional && !mFullHistos) { + return nullptr; + } + std::string fullTitle = std::string("[") + GID::getSourceName(mSrc) + "] " + title; + auto h = std::make_unique(name, fullTitle.c_str(), nbins, xmin, xmax, nbinsy, ymin, ymax); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h.get(), drawOptions, displayHints }); + return h; +} + +} // namespace o2::quality_control_modules::muon + +#endif diff --git a/Modules/MUON/Common/include/MUONCommon/TracksCheck.h b/Modules/MUON/Common/include/MUONCommon/TracksCheck.h new file mode 100644 index 0000000000..49d9553588 --- /dev/null +++ b/Modules/MUON/Common/include/MUONCommon/TracksCheck.h @@ -0,0 +1,55 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TracksCheck.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_MUON_COMMON_TRACKS_CHECK_H +#define QC_MODULE_MUON_COMMON_TRACKS_CHECK_H + +#include "QualityControl/CheckInterface.h" +#include + +namespace o2::quality_control_modules::muon +{ + +/// \brief Check the number and kinematical distribution of tracks +/// +/// \author Andrea Ferrero +class TracksCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + TracksCheck() = default; + /// Destructor + ~TracksCheck() override = default; + + void configure() override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + std::unordered_map mQualities; + int mMinTracksPerTF{ 50 }; + int mMaxTracksPerTF{ 500 }; + float mMaxDeltaPhi{ 0.1 }; + float mMaxChargeAsymmetry{ 0.1 }; + float mMarkerSize{ 1.5 }; + + ClassDefOverride(TracksCheck, 2); +}; +} // namespace o2::quality_control_modules::muon + +#endif diff --git a/Modules/MUON/Common/include/MUONCommon/TracksPostProcessing.h b/Modules/MUON/Common/include/MUONCommon/TracksPostProcessing.h new file mode 100644 index 0000000000..15cd36c0f5 --- /dev/null +++ b/Modules/MUON/Common/include/MUONCommon/TracksPostProcessing.h @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TracksPostProcessing.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Post-processing of the MUON tracks +/// + +#ifndef QC_MODULE_MUON_COMMON_PP_TRACKS_H +#define QC_MODULE_MUON_COMMON_PP_TRACKS_H + +#include "MUONCommon/HistPlotter.h" +#include "MUONCommon/TracksPostProcessingConfig.h" +#include "QualityControl/PostProcessingInterface.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include +#include +#include + +namespace o2::quality_control::core +{ +class Activity; +} + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::muon +{ + +using namespace o2::quality_control::core; +using GID = o2::dataformats::GlobalTrackID; +class MatchingEfficiencyPlotterInterface; + +/// \brief A post-processing task which processes and trends MCH digits and produces plots. +class TracksPostProcessing : public PostProcessingInterface +{ + public: + TracksPostProcessing() = default; + ~TracksPostProcessing() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + void createTrackHistos(); + void removeTrackHistos(); + void updateTrackHistos(Trigger t, repository::DatabaseInterface* qcdb); + + std::unique_ptr mConfig; + + std::vector> mMatchingEfficiencyPlotters; +}; + +} // namespace o2::quality_control_modules::muon + +#endif // QC_MODULE_MUON_COMMON_PP_TRACKS_H diff --git a/Modules/MUON/Common/include/MUONCommon/TracksPostProcessingConfig.h b/Modules/MUON/Common/include/MUONCommon/TracksPostProcessingConfig.h new file mode 100644 index 0000000000..1961b2b731 --- /dev/null +++ b/Modules/MUON/Common/include/MUONCommon/TracksPostProcessingConfig.h @@ -0,0 +1,90 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TracksPostProcessingConfig.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Header file for the configuration of MCH post-processing tasks +/// \since 16/06/2022 +/// + +#ifndef QC_MODULE_MUON_TRACKS_PP_CONF_H +#define QC_MODULE_MUON_TRACKS_PP_CONF_H + +#include "QualityControl/PostProcessingConfig.h" +#include +#include +#include +#include + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::muon +{ + +/// \brief MCH trending configuration structure +struct TracksPostProcessingConfig : PostProcessingConfig { + TracksPostProcessingConfig() = default; + TracksPostProcessingConfig(std::string name, const boost::property_tree::ptree& config); + ~TracksPostProcessingConfig() = default; + + const bool hasParameter(std::string name) const + { + auto entry = parameters.find(name); + return (entry != parameters.end()); + } + + template + const T getParameter(std::string name) const; + + template + const T getParameter(std::string name, T defaultValue) const; + + struct DataSource { + std::string plotsPath; + std::string refsPath; + std::string outputPath; + std::string name; + int rebin{ 1 }; + }; + + std::map parameters; + std::vector dataSources; +}; + +template +const T TracksPostProcessingConfig::getParameter(std::string name) const +{ + T result{}; + auto entry = parameters.find(name); + if (entry != parameters.end()) { + std::istringstream istr(entry->second); + istr >> result; + } + + return result; +} + +template +const T TracksPostProcessingConfig::getParameter(std::string name, T defaultValue) const +{ + T result = defaultValue; + auto entry = parameters.find(name); + if (entry != parameters.end()) { + std::istringstream istr(entry->second); + istr >> result; + } + + return result; +} + +} // namespace o2::quality_control_modules::muon + +#endif // QC_MODULE_MUON_TRACKS_PP_CONF_H diff --git a/Modules/MUON/Common/include/MUONCommon/TracksTask.h b/Modules/MUON/Common/include/MUONCommon/TracksTask.h new file mode 100644 index 0000000000..97091dd41a --- /dev/null +++ b/Modules/MUON/Common/include/MUONCommon/TracksTask.h @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_MUON_COMMON_TRACKS_TASK_H +#define QC_MODULE_MUON_COMMON_TRACKS_TASK_H + +#include "QualityControl/TaskInterface.h" +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include +#include "MUONCommon/TrackPlotter.h" +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::muon +{ + +using namespace o2::quality_control::core; +using GID = o2::dataformats::GlobalTrackID; + +class TracksTask /*final*/ : public TaskInterface +{ + public: + TracksTask(); + ~TracksTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + /** check whether all the expected inputs are present.*/ + bool assertInputs(o2::framework::ProcessingContext& ctx); + void createTrackHistos(const o2::quality_control::core::Activity& activity); + void removeTrackHistos(); + + private: + std::map> mTrackPlotters; + std::map> mTrackPlottersWithCuts; + std::shared_ptr mDataRequest; + o2::globaltracking::RecoContainer mRecoCont; + GID::mask_t mSrc = GID::getSourcesMask("MCH-MID"); + GID::mask_t mAllowedSources = GID::getSourcesMask("MFT,MCH,MID,MCH-MID,MFT-MCH,MFT-MCH-MID"); + + // MCH-MID + gsl::span mMCHMIDTracks; + // MFT-MCH + gsl::span mMFTMCHTracks; + // MFT-MCH-MID + gsl::span mMFTMCHMIDTracks; +}; + +} // namespace o2::quality_control_modules::muon + +#endif diff --git a/Modules/MUON/Common/src/Helpers.cxx b/Modules/MUON/Common/src/Helpers.cxx new file mode 100644 index 0000000000..416e1d0b00 --- /dev/null +++ b/Modules/MUON/Common/src/Helpers.cxx @@ -0,0 +1,152 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "MUONCommon/Helpers.h" +#include +#include +#include +#include +#include +#include "QualityControl/ObjectsManager.h" + +#include + +namespace o2::quality_control_modules::muon +{ +template <> +std::string getConfigurationParameter(o2::quality_control::core::CustomParameters customParameters, std::string parName, const std::string defaultValue) +{ + std::string result = defaultValue; + auto parOpt = customParameters.atOptional(parName); + if (parOpt.has_value()) { + result = parOpt.value(); + } + return result; +} + +template <> +std::string getConfigurationParameter(o2::quality_control::core::CustomParameters customParameters, std::string parName, const std::string defaultValue, const o2::quality_control::core::Activity& activity) +{ + auto parOpt = customParameters.atOptional(parName, activity); + if (parOpt.has_value()) { + std::string result = parOpt.value(); + return result; + } + return getConfigurationParameter(customParameters, parName, defaultValue); +} + +template <> +bool getConfigurationParameter(o2::quality_control::core::CustomParameters customParameters, std::string parName, const bool defaultValue) +{ + bool result = defaultValue; + auto parOpt = customParameters.atOptional(parName); + if (parOpt.has_value()) { + std::string value = parOpt.value(); + std::transform(value.begin(), value.end(), value.begin(), ::toupper); + if (value == "TRUE" || value == "YES" || value == "1") { + return true; + } + if (value == "FALSE" || value == "NO" || value == "0") { + return false; + } + throw std::invalid_argument(std::string("error parsing boolean configurable parameter: key=") + parName + " value=" + value); + } + return result; +} + +template <> +bool getConfigurationParameter(o2::quality_control::core::CustomParameters customParameters, std::string parName, const bool defaultValue, const o2::quality_control::core::Activity& activity) +{ + auto parOpt = customParameters.atOptional(parName, activity); + if (parOpt.has_value()) { + std::string value = parOpt.value(); + std::transform(value.begin(), value.end(), value.begin(), ::toupper); + if (value == "TRUE" || value == "YES" || value == "1") { + return true; + } + if (value == "FALSE" || value == "NO" || value == "0") { + return false; + } + throw std::invalid_argument(std::string("error parsing boolean configurable parameter: key=") + parName + " value=" + value); + } + return getConfigurationParameter(customParameters, parName, defaultValue); +} + +//_________________________________________________________________________________________ + +std::vector makeLogBinning(double min, double max, int nbins) +{ + auto logMin = std::log10(min); + auto logMax = std::log10(max); + auto binWidth = (logMax - logMin) / nbins; + std::vector bins(nbins + 1); + for (int i = 0; i <= nbins; i++) { + bins[i] = std::pow(10, logMin + i * binWidth); + } + return bins; +} + +//_________________________________________________________________________________________ + +TLine* addHorizontalLine(TH1& histo, double y, + int lineColor, int lineStyle, + int lineWidth) +{ + auto nbins = histo.GetXaxis()->GetNbins(); + + TLine* line = new TLine(histo.GetBinLowEdge(1), y, histo.GetBinLowEdge(nbins) + histo.GetBinWidth(nbins), y); + line->SetLineColor(lineColor); + line->SetLineStyle(lineStyle); + line->SetLineWidth(lineWidth); + histo.GetListOfFunctions()->Add(line); + return line; +} + +void markBunchCrossing(TH1& histo, + gsl::span bunchCrossings) +{ + for (auto b : bunchCrossings) { + addVerticalLine(histo, b * 1.0, 1, 10, 1); + } +} + +TLine* addVerticalLine(TH1& histo, double x, + int lineColor, int lineStyle, + int lineWidth) +{ + double max = histo.GetBinContent(histo.GetMaximumBin()); + TLine* line = new TLine(x, histo.GetMinimum(), + x, max * 1.05); + line->SetLineColor(lineColor); + line->SetLineStyle(lineStyle); + line->SetLineWidth(lineWidth); + histo.GetListOfFunctions()->Add(line); + return line; +} + +// remove all elements of class c +// from histo->GetListOfFunctions() +void cleanup(TH1& histo, const char* classname) +{ + TList* elements = histo.GetListOfFunctions(); + TIter next(elements); + TObject* obj; + std::vector toBeRemoved; + while ((obj = next())) { + if (strcmp(obj->ClassName(), classname) == 0) { + toBeRemoved.push_back(obj); + } + } + for (auto o : toBeRemoved) { + elements->Remove(o); + } +} +} // namespace o2::quality_control_modules::muon diff --git a/Modules/MUON/Common/src/HistPlotter.cxx b/Modules/MUON/Common/src/HistPlotter.cxx new file mode 100644 index 0000000000..9f77418fdc --- /dev/null +++ b/Modules/MUON/Common/src/HistPlotter.cxx @@ -0,0 +1,67 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "MUONCommon/HistPlotter.h" + +#include +#include + +namespace o2::quality_control_modules::muon +{ + +void HistPlotter::publish(std::shared_ptr objectsManager, HistInfo& hinfo, o2::quality_control::core::PublicationPolicy policy) +{ + objectsManager->startPublishing(hinfo.object, policy); + objectsManager->setDefaultDrawOptions(hinfo.object, hinfo.drawOptions); + objectsManager->setDisplayHint(hinfo.object, hinfo.displayHints); + mPublishedHistograms.push_back(hinfo); +} + +void HistPlotter::publish(std::shared_ptr objectsManager, o2::quality_control::core::PublicationPolicy policy) +{ + for (auto hinfo : mHistograms) { + publish(objectsManager, hinfo, policy); + } +} + +void HistPlotter::unpublish(std::shared_ptr objectsManager) +{ + for (auto hinfo : mPublishedHistograms) { + objectsManager->stopPublishing(hinfo.object); + } +} + +void HistPlotter::reset() +{ + for (auto hinfo : mHistograms) { + TH1* histo = dynamic_cast(hinfo.object); + if (histo) { + histo->Reset(); + } else { + TCanvas* c = dynamic_cast(hinfo.object); + if (c) { + TObject* obj; + TIter next(c->GetListOfPrimitives()); + while ((obj = next())) { + if (!obj->InheritsFrom("TH1")) { + continue; + } + histo = dynamic_cast(obj); + if (histo) { + histo->Reset(); + } + } + } + } + } +} + +} // namespace o2::quality_control_modules::muon diff --git a/Modules/MUON/Common/src/MatchingEfficiencyCheck.cxx b/Modules/MUON/Common/src/MatchingEfficiencyCheck.cxx new file mode 100644 index 0000000000..810a414db8 --- /dev/null +++ b/Modules/MUON/Common/src/MatchingEfficiencyCheck.cxx @@ -0,0 +1,219 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MatchingEfficiencyCheck.cxx +/// \author Andrea Ferrero +/// + +#include "MUONCommon/MatchingEfficiencyCheck.h" +#include "MUONCommon/Helpers.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::muon +{ +void MatchingEfficiencyCheck::configure() {} + +void MatchingEfficiencyCheck::startOfActivity(const Activity& activity) +{ + mActivity = activity; +} + +void MatchingEfficiencyCheck::endOfActivity(const Activity& activity) +{ + mActivity = Activity{}; + mRanges.clear(); + mIntervals.clear(); + mQualities.clear(); +} + +static std::string getBaseName(std::string name) +{ + auto pos = name.rfind("/"); + return ((pos < std::string::npos) ? name.substr(pos + 1) : name); +} + +void MatchingEfficiencyCheck::initRange(std::string key) +{ + // Get acceptable range for this histogram + auto iter = mRanges.find(key); + if (iter != mRanges.end()) { + return; + } + + std::string parKey = std::string("range:") + key; + std::string parValue = getConfigurationParameter(mCustomParameters, parKey, "", mActivity); + + auto tokens = o2::utils::Str::tokenize(parValue, ':', false, true); + if (tokens.empty()) { + return; + } + + auto range = o2::utils::Str::tokenize(tokens[0], ',', false, true); + if (range.size() == 2) { + double min = std::stod(range[0]); + double max = std::stod(range[1]); + iter = mRanges.insert(std::pair{ key, std::make_pair(min, max) }).first; + } + + if (tokens.size() > 1) { + std::vector> intervals; + for (size_t ti = 1; ti < tokens.size(); ti++) { + auto interval = o2::utils::Str::tokenize(tokens[ti], ',', false, true); + if (interval.size() == 2) { + double xmin = std::stod(interval[0]); + double xmax = std::stod(interval[1]); + intervals.push_back(std::make_pair(xmin, xmax)); + } + } + mIntervals[key] = intervals; + } +} + +std::optional> MatchingEfficiencyCheck::getRange(std::string key) +{ + std::optional> result; + + // Get acceptable range for this histogram + auto iter = mRanges.find(key); + if (iter != mRanges.end()) { + result = iter->second; + } + + return result; +} + +Quality MatchingEfficiencyCheck::check(std::map>* moMap) +{ + for (auto& [moKey, mo] : *moMap) { + + auto moName = mo->getName(); + + TH1* hist = dynamic_cast(mo->getObject()); + if (!hist) { + continue; + } + + auto key = getBaseName(moName); + + initRange(key); + auto range = getRange(key); + if (!range) { + continue; + } + + // Get list of bin intervals, if any + std::vector> binIntervals; + auto iter = mIntervals.find(key); + if (iter == mIntervals.end()) { + // No range specified for this histogram, all bins are considered + binIntervals.emplace_back(std::make_pair(1, hist->GetNbinsX())); + } else { + // Collect all bin ranges that have to be checked for this histogram + double epsilon = 0.001 * hist->GetXaxis()->GetBinWidth(1); + for (auto i : iter->second) { + int binMin = hist->GetXaxis()->FindBin(i.first + epsilon); + int binMax = hist->GetXaxis()->FindBin(i.second - epsilon); + binIntervals.push_back(std::make_pair(binMin, binMax)); + } + } + + // Quality is good by default, unless the average is outside the acceptable range + // in at least one of the bin intervals + mQualities[moName] = Quality::Good; + + // Loop over bin intervals, compute the average bin value in each interval, + // and compare it to the acceptable range + // If outside, set the quality to Bad + for (auto i : binIntervals) { + // Set the quality to Null if the interval is invalid + if (i.second < i.first) { + mQualities[moName] = Quality::Null; + break; + } + + for (int bin = i.first; bin <= i.second; bin++) { + if (hist->GetBinContent(bin) < range->first || hist->GetBinContent(bin) > range->second) { + mQualities[moName] = Quality::Bad; + mQualities[moName].addFlag(o2::quality_control::FlagTypeFactory::BadTracking(), "Matching efficiency not in the expected range"); + break; + } + } + } + } + + Quality result = mQualities.empty() ? Quality::Null : Quality::Good; + for (auto& [key, q] : mQualities) { + if (q.isWorseThan(result)) { + result = q; + } + } + + return result; +} + +void MatchingEfficiencyCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + TH1* hist = dynamic_cast(mo->getObject()); + if (!hist) { + return; + } + + hist->SetMinimum(0); + hist->SetMaximum(1.2); + + auto moName = mo->getName(); + auto quality = mQualities[moName]; + if (quality == Quality::Bad) { + hist->SetLineColor(kRed); + hist->SetMarkerColor(kRed); + } + + auto key = getBaseName(moName); + auto range = getRange(key); + if (!range) { + return; + } + + // Get list of bin intervals, if any + std::vector> intervals; + auto iter = mIntervals.find(key); + if (iter == mIntervals.end()) { + // No range specified for this histogram, full X range considered + intervals.emplace_back(std::make_pair(hist->GetXaxis()->GetXmin(), hist->GetXaxis()->GetXmax())); + } else { + // Collect bin ranges that have to be checked for this histogram + intervals = iter->second; + } + + for (auto i : intervals) { + // draw horizontal limits + double xmin = i.first; + double xmax = i.second; + TLine* l1 = new TLine(xmin, range->first, xmax, range->first); + l1->SetLineColor(kBlue); + l1->SetLineStyle(kDotted); + hist->GetListOfFunctions()->Add(l1); + + TLine* l2 = new TLine(xmin, range->second, xmax, range->second); + l2->SetLineColor(kBlue); + l2->SetLineStyle(kDotted); + hist->GetListOfFunctions()->Add(l2); + } +} + +} // namespace o2::quality_control_modules::muon diff --git a/Modules/MUON/Common/src/MuonTrack.cxx b/Modules/MUON/Common/src/MuonTrack.cxx new file mode 100644 index 0000000000..831536c9b9 --- /dev/null +++ b/Modules/MUON/Common/src/MuonTrack.cxx @@ -0,0 +1,587 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "MUONCommon/MuonTrack.h" +#include "QualityControl/QcInfoLogger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::dataformats; +using TrackMFT = o2::mft::TrackMFT; +using TrackMCH = o2::mch::TrackMCH; +using TrackMID = o2::mid::Track; +using InteractionRecord = o2::InteractionRecord; + +namespace +{ + +using namespace o2::quality_control_modules::muon; + +constexpr double muonMass = 0.1056584; +constexpr double muonMass2 = muonMass * muonMass; + +static InteractionRecord getMFTTrackIR(int iTrack, const o2::globaltracking::RecoContainer& recoCont) +{ + // if the MID track is present, use the time from MID + auto rofs = recoCont.getMFTTracksROFRecords(); + for (const auto& rof : rofs) { + if (iTrack < rof.getFirstEntry() || iTrack >= (rof.getFirstEntry() + rof.getNEntries())) { + continue; + } + return rof.getBCData(); + } + + return InteractionRecord{}; +} + +static MuonTrack::Time getMFTTrackTime(int iTrack, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) +{ + // if the MID track is present, use the time from MID + auto rofs = recoCont.getMFTTracksROFRecords(); + for (const auto& rof : rofs) { + if (iTrack < rof.getFirstEntry() || iTrack >= (rof.getFirstEntry() + rof.getNEntries())) { + continue; + } + auto bcDiff = rof.getBCData().differenceInBC(InteractionRecord{ 0, firstTForbit }); + float tMean = (bcDiff + 0.5) * o2::constants::lhc::LHCBunchSpacingMUS; + float tErr = 1.0 * o2::constants::lhc::LHCBunchSpacingMUS; + return { tMean, tErr }; + } + + return MuonTrack::Time{}; +} + +static InteractionRecord getMIDTrackIR(int iTrack, const o2::globaltracking::RecoContainer& recoCont) +{ + // if the MID track is present, use the time from MID + auto rofs = recoCont.getMIDTracksROFRecords(); + for (const auto& rof : rofs) { + if (iTrack < rof.firstEntry || iTrack >= (rof.firstEntry + rof.nEntries)) { + continue; + } + return rof.interactionRecord; + } + + return InteractionRecord{}; +} + +static MuonTrack::Time getMIDTrackTime(int iTrack, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) +{ + // if the MID track is present, use the time from MID + auto rofs = recoCont.getMIDTracksROFRecords(); + for (const auto& rof : rofs) { + if (iTrack < rof.firstEntry || iTrack >= (rof.firstEntry + rof.nEntries)) { + continue; + } + auto time = rof.getTimeMUS(InteractionRecord{ 0, firstTForbit }); + if (time.second) { + return time.first; + } else { + return MuonTrack::Time{}; + } + } + + return MuonTrack::Time{}; +} + +static o2::mch::ROFRecord getMCHTrackROF(int iTrack, const o2::globaltracking::RecoContainer& recoCont) +{ + // if the MID track is present, use the time from MID + auto rofs = recoCont.getMCHTracksROFRecords(); + for (const auto& rof : rofs) { + if (iTrack < rof.getFirstIdx() || iTrack > rof.getLastIdx()) { + continue; + } + return rof; + } + + return o2::mch::ROFRecord{}; +} + +static InteractionRecord getMCHTrackIR(int iTrack, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) +{ + auto tracksMCH = recoCont.getMCHTracks(); + if (iTrack < 0 || iTrack >= tracksMCH.size()) { + return InteractionRecord{}; + } + return tracksMCH[iTrack].getMeanIR(firstTForbit); + + // if the MID track is present, use the time from MID + auto rofs = recoCont.getMCHTracksROFRecords(); + for (const auto& rof : rofs) { + if (iTrack < rof.getFirstIdx() || iTrack > rof.getLastIdx()) { + continue; + } + return rof.getBCData(); + } + + return InteractionRecord{}; +} + +static InteractionRecord getMCHTrackIR(const o2::mch::TrackMCH* track, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) +{ + return track->getMeanIR(firstTForbit); + + // if the MID track is present, use the time from MID + auto tracksMCH = recoCont.getMCHTracks(); + int iMCH{ -1 }; + for (int i = 0; i < tracksMCH.size(); i++) { + if (track == (&(tracksMCH[i]))) { + iMCH = i; + break; + } + } + if (iMCH >= 0) { + return getMCHTrackIR(iMCH, recoCont, firstTForbit); + } + + return InteractionRecord{}; +} + +static MuonTrack::Time getMCHTrackTime(int iTrack, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) +{ + auto tracksMCH = recoCont.getMCHTracks(); + if (iTrack < 0 || iTrack >= tracksMCH.size()) { + return MuonTrack::Time{}; + } + return tracksMCH[iTrack].getTimeMUS(); +} + +static MuonTrack::Time getMCHTrackTime(const o2::mch::TrackMCH* track, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) +{ + return track->getTimeMUS(); +} + +static InteractionRecord getGlobalFwdTrackIR(const GlobalFwdTrack* track, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) +{ + auto iMID = track->getMIDTrackID(); + auto tracksMID = recoCont.getMIDTracks(); + if (iMID >= 0 && iMID < tracksMID.size()) { + // if the MID track is present, use the time from MID + return getMIDTrackIR(iMID, recoCont); + } + + auto iMCH = track->getMCHTrackID(); + auto tracksMCH = recoCont.getMCHTracks(); + if (iMCH >= 0 && iMCH < tracksMCH.size()) { + // if the MID track is present, use the time from MID + return getMCHTrackIR(iMCH, recoCont, firstTForbit); + } + + return InteractionRecord{}; +} + +static MuonTrack::Time getGlobalFwdTrackTime(const GlobalFwdTrack* track, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) +{ + auto iMID = track->getMIDTrackID(); + auto tracksMID = recoCont.getMIDTracks(); + if (iMID >= 0 && iMID < tracksMID.size()) { + // if the MID track is present, use the time from MID + return getMIDTrackTime(iMID, recoCont, firstTForbit); + } + + auto iMCH = track->getMCHTrackID(); + auto tracksMCH = recoCont.getMCHTracks(); + if (iMCH >= 0 && iMCH < tracksMCH.size()) { + // if the MID track is present, use the time from MID + return getMCHTrackTime(iMCH, recoCont, firstTForbit); + } + + return MuonTrack::Time{}; +} + +static bool getParametersAtVertex(o2::mch::TrackParam& trackParamAtVertex, bool correctForMCS = true) +{ + bool result = false; + if (trackParamAtVertex.getZ() > o2::quality_control_modules::muon::MuonTrack::sAbsZBeg) { + result = o2::mch::TrackExtrap::extrapToZ(trackParamAtVertex, 0); + } else { + if (correctForMCS) { + result = o2::mch::TrackExtrap::extrapToVertex(trackParamAtVertex, 0.0, 0.0, 0.0, 0.0, 0.0); + } else { + result = o2::mch::TrackExtrap::extrapToVertexWithoutBranson(trackParamAtVertex, 0.0); + } + } + return result; +} + +static ROOT::Math::PxPyPzMVector getMuonMomentum(const o2::mch::TrackParam& par) +{ + return { par.px(), par.py(), par.pz(), muonMass }; +} + +static ROOT::Math::PxPyPzMVector getMuonMomentumAtVertex(const o2::mch::TrackParam& par) +{ + o2::mch::TrackParam trackParamAtVertex = par; + bool result = getParametersAtVertex(trackParamAtVertex); + if (!result) { + ILOG(Error, Devel) << "Track extrap failed" << ENDM; + return ROOT::Math::PxPyPzMVector(); + } + + double px = trackParamAtVertex.px(); + double py = trackParamAtVertex.py(); + double pz = trackParamAtVertex.pz(); + return { px, py, pz, muonMass }; +} + +static float getDCA(const o2::mch::TrackParam& par) +{ + o2::mch::TrackParam trackParamAtDCA = par; + bool result = getParametersAtVertex(trackParamAtDCA, false); + if (!result) { + ILOG(Error, Devel) << "Track extrap failed" << ENDM; + return 0; + } + + double dcaX = trackParamAtDCA.getBendingCoor(); + double dcaY = trackParamAtDCA.getNonBendingCoor(); + return std::sqrt(dcaX * dcaX + dcaY * dcaY); +} + +static float getPDCA(const o2::mch::TrackParam& par) +{ + auto p = par.p(); + auto dca = getDCA(par); + return (p * dca); +} + +static float getRAbsMCH(const o2::mch::TrackParam& par) +{ + o2::mch::TrackParam trackParamAtAbs = par; + if (!o2::mch::TrackExtrap::extrapToZ(trackParamAtAbs, o2::quality_control_modules::muon::MuonTrack::sAbsZEnd)) { + return 0; + } + double xAbs = trackParamAtAbs.getNonBendingCoor(); + double yAbs = trackParamAtAbs.getBendingCoor(); + return std::sqrt(xAbs * xAbs + yAbs * yAbs); +} + +static o2::mch::TrackParam forwardTrackToMCHTrack(const o2::track::TrackParFwd& track) +{ + const auto phi = track.getPhi(); + const auto sinPhi = std::sin(phi); + const auto tgL = track.getTgl(); + + const auto SlopeX = std::cos(phi) / tgL; + const auto SlopeY = sinPhi / tgL; + const auto InvP_yz = track.getInvQPt() / std::sqrt(sinPhi * sinPhi + tgL * tgL); + + const std::array params{ track.getX(), SlopeX, track.getY(), SlopeY, InvP_yz }; + const std::array cov{ + 1, + 0, 1, + 0, 0, 1, + 0, 0, 0, 1, + 0, 0, 0, 0, 1 + }; + + return { track.getZ(), params.data(), cov.data() }; +} + +static o2::mch::TrackParam forwardTrackToMCHTrack(const o2::track::TrackParCovFwd& track) +{ + const auto phi = track.getPhi(); + const auto sinPhi = std::sin(phi); + const auto tgL = track.getTgl(); + + const auto SlopeX = std::cos(phi) / tgL; + const auto SlopeY = sinPhi / tgL; + const auto InvP_yz = track.getInvQPt() / std::sqrt(sinPhi * sinPhi + tgL * tgL); + + const std::array params{ track.getX(), SlopeX, track.getY(), SlopeY, InvP_yz }; + const std::array cov{ + 1, + 0, 1, + 0, 0, 1, + 0, 0, 0, 1, + 0, 0, 0, 0, 1 + }; + + return { track.getZ(), params.data(), cov.data() }; +} + +} // namespace + +//_________________________________________________________________________________________________________ + +namespace o2::quality_control_modules::muon +{ + +MuonTrack::MuonTrack(const o2::mch::TrackMCH* track, int trackID, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) +{ + mTrackMCH = track; + + mTrackIdMCH = trackID; + + mChi2OverNDF = track->getChi2OverNDF(); + + mIR = getMCHTrackIR(track, recoCont, firstTForbit); + mIRMCH = mIR; + + mTime = track->getTimeMUS(); + mTimeMCH = mTime; + + o2::InteractionRecord startIR{ 0, firstTForbit }; + mRofMCH = getMCHTrackROF(mTrackIdMCH, recoCont); + mRofTimeMCH = mRofMCH.getTimeMUS(startIR, 32).first; + + mTrackParameters.setZ(track->getZ()); + mTrackParameters.setParameters(track->getParameters()); + + mTrackParametersMCH.setZ(track->getZ()); + mTrackParametersMCH.setParameters(track->getParameters()); + + mTrackParametersAtMID.setZ(track->getZAtMID()); + mTrackParametersAtMID.setParameters(track->getParametersAtMID()); + + mChi2OverNDFMCH = mTrackMCH->getChi2OverNDF(); + + init(); +} + +MuonTrack::MuonTrack(const TrackMCHMID* track, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) +{ + auto tracksMCH = recoCont.getMCHTracks(); + auto tracksMID = recoCont.getMIDTracks(); + + auto iMCH = track->getMCHRef().getIndex(); + auto iMID = track->getMIDRef().getIndex(); + + if (iMCH >= 0 && iMCH >= tracksMCH.size()) { + throw std::length_error(fmt::format("[MuonTrackImpl] bad MCH track index: iMCH={} tracksMCH.size()={}", iMCH, tracksMCH.size())); + } + if (iMID >= 0 && iMID >= tracksMID.size()) { + throw std::length_error(fmt::format("[MuonTrackImpl] bad MID track index: iMID={} tracksMID.size()={}", iMID, tracksMID.size())); + } + + mTrackIdMCH = iMCH; + mTrackIdMID = iMID; + + mTrackMCH = &(tracksMCH[iMCH]); + mTrackMID = &(tracksMID[iMID]); + + mIR = track->getIR(); + auto trackTimeMUS = track->getTimeMUS(InteractionRecord{ 0, firstTForbit }, 128, true); + if (trackTimeMUS.second) { + mTime = trackTimeMUS.first; + } + + auto& trackMCH = tracksMCH[iMCH]; + + if (iMCH >= 0) { + // mTrackMCH = &(tracksMCH[iMCH]); + mIRMCH = getMCHTrackIR(iMCH, recoCont, firstTForbit); + mTimeMCH = trackMCH.getTimeMUS(); + + o2::InteractionRecord startIR{ 0, firstTForbit }; + mRofMCH = getMCHTrackROF(mTrackIdMCH, recoCont); + mRofTimeMCH = mRofMCH.getTimeMUS(startIR, 32).first; + + mChi2OverNDFMCH = mTrackMCH->getChi2OverNDF(); + } + if (iMID >= 0) { + // mTrackMID = &(tracksMID[iMID]); + mIRMID = getMIDTrackIR(iMID, recoCont); + if (trackTimeMUS.second) { + mTimeMID = trackTimeMUS.first; + } + + mChi2OverNDFMID = mTrackMID->getChi2OverNDF(); + } + + mChi2OverNDF = trackMCH.getChi2OverNDF(); + + mTrackParameters.setZ(trackMCH.getZ()); + mTrackParameters.setParameters(trackMCH.getParameters()); + + mTrackParametersMCH.setZ(trackMCH.getZ()); + mTrackParametersMCH.setParameters(trackMCH.getParameters()); + + mTrackParametersAtMID.setZ(trackMCH.getZAtMID()); + mTrackParametersAtMID.setParameters(trackMCH.getParametersAtMID()); + + init(); +} + +MuonTrack::MuonTrack(const GlobalFwdTrack* track, const o2::globaltracking::RecoContainer& recoCont, uint32_t firstTForbit) : mTrackParameters(forwardTrackToMCHTrack(*track)) +{ + auto tracksMFT = recoCont.getMFTTracks(); + auto tracksMCH = recoCont.getMCHTracks(); + auto tracksMID = recoCont.getMIDTracks(); + + auto iMFT = track->getMFTTrackID(); + auto iMCH = track->getMCHTrackID(); + auto iMID = track->getMIDTrackID(); + + if (iMFT >= 0 && iMFT >= tracksMFT.size()) { + throw std::length_error(fmt::format("[MuonTrack(GlobalFwdTrack)] bad MFT track index: iMFT={} tracksMFT.size()={}", iMFT, tracksMFT.size())); + } + if (iMCH >= 0 && iMCH >= tracksMCH.size()) { + throw std::length_error(fmt::format("[MuonTrack(GlobalFwdTrack)] bad MCH track index: iMCH={} tracksMCH.size()={}", iMCH, tracksMCH.size())); + } + if (iMID >= 0 && iMID >= tracksMID.size()) { + throw std::length_error(fmt::format("[MuonTrack(GlobalFwdTrack)] bad MID track index: iMID={} tracksMID.size()={}", iMID, tracksMID.size())); + } + + mMatchInfoFwd = *track; + + mTrackIdMFT = iMFT; + mTrackIdMCH = iMCH; + mTrackIdMID = iMID; + + if (iMFT >= 0) { + mTrackMFT = &(tracksMFT[iMFT]); + mIRMFT = getMFTTrackIR(iMFT, recoCont); + mTimeMFT = getMFTTrackTime(iMFT, recoCont, firstTForbit); + mTrackParametersMFT.setZ(tracksMFT[iMFT].getOutParam().getZ()); + mTrackParametersMFT.setParameters(forwardTrackToMCHTrack(tracksMFT[iMFT].getOutParam()).getParameters()); + } + if (iMCH >= 0) { + mTrackMCH = &(tracksMCH[iMCH]); + auto& trackMCH = tracksMCH[iMCH]; + mTrackParametersMCH.setZ(trackMCH.getZ()); + mTrackParametersMCH.setParameters(trackMCH.getParameters()); + + mTrackParametersAtMID.setZ(trackMCH.getZAtMID()); + mTrackParametersAtMID.setParameters(trackMCH.getParametersAtMID()); + + mIRMCH = getMCHTrackIR(iMCH, recoCont, firstTForbit); + mTimeMCH = getMCHTrackTime(iMCH, recoCont, firstTForbit); + + o2::InteractionRecord startIR{ 0, firstTForbit }; + mRofMCH = getMCHTrackROF(mTrackIdMCH, recoCont); + mRofTimeMCH = mRofMCH.getTimeMUS(startIR, 32).first; + + mChi2OverNDFMCH = mTrackMCH->getChi2OverNDF(); + } + if (iMID >= 0) { + mTrackMID = &(tracksMID[iMID]); + mIRMID = getMIDTrackIR(iMID, recoCont); + mTimeMID = getMIDTrackTime(iMID, recoCont, firstTForbit); + + mChi2OverNDFMID = mTrackMID->getChi2OverNDF(); + } + + mIR = ::getGlobalFwdTrackIR(track, recoCont, firstTForbit); + mTime = ::getGlobalFwdTrackTime(track, recoCont, firstTForbit); + + // mChi2OverNDF = track->getTrackChi2(); + mChi2OverNDF = mTrackMCH->getChi2OverNDF(); + + init(); +} + +void MuonTrack::init() +{ + mSign = mTrackParameters.getCharge(); + + mMuonMomentum = ::getMuonMomentum(mTrackParameters); + mMuonMomentumAtVertex = ::getMuonMomentumAtVertex(mTrackParameters); + + mMuonMomentumMCH = ::getMuonMomentum(mTrackParametersMCH); + mMuonMomentumAtVertexMCH = ::getMuonMomentumAtVertex(mTrackParametersMCH); + + mDCA = ::getDCA(mTrackParameters); + mDCAMCH = ::getDCA(mTrackParametersMCH); + mRAbs = getRAbsMCH(mTrackParametersMCH); +} + +bool MuonTrack::extrapToZMFT(o2::mch::TrackParam& trackParam, float z) const +{ + trackParam = mTrackParametersMFT; + return o2::mch::TrackExtrap::extrapToZ(trackParam, z); +} + +bool MuonTrack::extrapToZMCH(o2::mch::TrackParam& trackParam, float z) const +{ + trackParam = mTrackParametersMCH; + return o2::mch::TrackExtrap::extrapToZ(trackParam, z); +} + +bool MuonTrack::extrapToZMID(o2::mch::TrackParam& trackParam, float z) const +{ + trackParam = mTrackParametersMID; + return o2::mch::TrackExtrap::extrapToZ(trackParam, z); +} + +double MuonTrack::getP() const +{ + return mMuonMomentumAtVertex.P(); +} + +double MuonTrack::getPt() const +{ + return mMuonMomentumAtVertex.Pt(); +} + +double MuonTrack::getEta() const +{ + return mMuonMomentumAtVertex.eta(); +} + +double MuonTrack::getPhi() const +{ + return mMuonMomentumAtVertex.phi() * TMath::RadToDeg(); +} + +double MuonTrack::getPMCH() const +{ + return mMuonMomentumAtVertexMCH.P(); +} + +double MuonTrack::getPtMCH() const +{ + return mMuonMomentumAtVertexMCH.Pt(); +} + +double MuonTrack::getEtaMCH() const +{ + return mMuonMomentumAtVertexMCH.eta(); +} + +double MuonTrack::getPhiMCH() const +{ + return mMuonMomentumAtVertexMCH.phi() * TMath::RadToDeg(); +} + +bool MuonTrack::canBeMuon() const +{ + bool inABS = (mRAbs > 17.6) && (mRAbs < 89.5); + bool inHoleMID = (std::abs(getXMid()) < 50) && (std::abs(getYMid()) < 50); + bool outOfMID = (std::abs(getXMid()) > 250) || (std::abs(getYMid()) > 300); + bool inMID = (!inHoleMID) && (!outOfMID); + // return (inABS); + return (inABS && inMID); + // return true; + // return (mRAbs > 17.6 && mRAbs < 89.5 && std::abs(getXMid()) > 50 && std::abs(getXMid()) < 250 && std::abs(getYMid()) > 50 && std::abs(getYMid()) < 300); +} + +} // namespace o2::quality_control_modules::muon diff --git a/Modules/MUON/Common/src/TrackPlotter.cxx b/Modules/MUON/Common/src/TrackPlotter.cxx new file mode 100644 index 0000000000..aaca0e135d --- /dev/null +++ b/Modules/MUON/Common/src/TrackPlotter.cxx @@ -0,0 +1,574 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "MUONCommon/TrackPlotter.h" +#include "MUONCommon/Helpers.h" + +#include "DetectorsBase/GRPGeomHelper.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +static void setXAxisLabels(TProfile* h) +{ + TAxis* axis = h->GetXaxis(); + for (int i = 1; i <= 10; i++) { + auto label = fmt::format("CH{}", i); + axis->SetBinLabel(i, label.c_str()); + } +} + +template +static void Fill(std::unique_ptr& hist, double x) +{ + if (!hist) { + return; + } + hist->Fill(x); +} + +template +static void Fill(std::unique_ptr& hist, double x, double y) +{ + if (!hist) { + return; + } + hist->Fill(x, y); +} + +template <> +void Fill(std::unique_ptr& hist, double x) +{ + if (!hist) { + return; + } + hist->getNum()->Fill(x); +} + +template <> +void Fill(std::unique_ptr& hist, double x, double y) +{ + if (!hist) { + return; + } + hist->getNum()->Fill(x, y); +} + +} // namespace + +using namespace o2::dataformats; + +namespace o2::quality_control_modules::muon +{ + +TrackPlotter::TrackPlotter(int maxTracksPerTF, + int etaBins, int phiBins, int ptBins, + GID::Source source, + std::string path, + bool fullHistos) + : mSrc(source), + mPath(path), + mFullHistos(fullHistos) +{ + createTrackHistos(maxTracksPerTF, etaBins, phiBins, ptBins); + createTrackPairHistos(); +} + +template <> +std::unique_ptr TrackPlotter::createHisto(const char* name, const char* title, + int nbins, double xmin, double xmax, + bool optional, + bool statBox, + const char* drawOptions, + const char* displayHints) +{ + if (optional && !mFullHistos) { + return nullptr; + } + std::string fullTitle = GID::getSourceName(mSrc) + " " + title; + auto h = std::make_unique(name, fullTitle.c_str(), nbins, xmin, xmax, true); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h.get(), drawOptions, displayHints }); + return h; +} + +template <> +std::unique_ptr TrackPlotter::createHisto(const char* name, const char* title, + int nbins, double xmin, double xmax, + int nbinsy, double ymin, double ymax, + bool optional, + bool statBox, + const char* drawOptions, + const char* displayHints) +{ + if (optional && !mFullHistos) { + return nullptr; + } + std::string fullTitle = GID::getSourceName(mSrc) + " " + title; + auto h = std::make_unique(name, fullTitle.c_str(), nbins, xmin, xmax, nbinsy, ymin, ymax, true); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h.get(), drawOptions, displayHints }); + return h; +} + +void TrackPlotter::createTrackHistos(int maxTracksPerTF, int etaBins, int phiBins, int ptBins) +{ + int nbins = 100; + auto xLogBins = makeLogBinning(1, maxTracksPerTF, nbins); + mNofTracksPerTF[0] = createHisto(TString::Format("%sPositive/TracksPerTF", mPath.c_str()), "Number of tracks per TimeFrame (+);Number of tracks per TF", nbins, xLogBins.data(), true, false, "logx"); + mNofTracksPerTF[1] = createHisto(TString::Format("%sNegative/TracksPerTF", mPath.c_str()), "Number of tracks per TimeFrame (-);Number of tracks per TF", nbins, xLogBins.data(), true, false, "logx"); + mNofTracksPerTF[2] = createHisto(TString::Format("%sTracksPerTF", mPath.c_str()), "Number of tracks per TimeFrame;Number of tracks per TF", nbins, xLogBins.data(), false, false, "logx"); + + mTrackChi2OverNDF[0] = createHisto(TString::Format("%sPositive/TrackMCHChi2OverNDF", mPath.c_str()), "Track #chi^{2}/ndf (MCH +);#chi^{2}/ndf;entries/s", 500, 0, 50, true, false, "hist"); + mTrackChi2OverNDF[1] = createHisto(TString::Format("%sNegative/TrackMCHChi2OverNDF", mPath.c_str()), "Track #chi^{2}/ndf (MCH -);#chi^{2}/ndf;entries/s", 500, 0, 50, true, false, "hist"); + mTrackChi2OverNDF[2] = createHisto(TString::Format("%sTrackMCHChi2OverNDF", mPath.c_str()), "Track #chi^{2}/ndf (MCH);#chi^{2}/ndf;entries/s", 500, 0, 50, false, false, "hist"); + + mTrackDCA[0] = createHisto(TString::Format("%sPositive/TrackDCA", mPath.c_str()), "Track DCA (+);DCA (cm);entries/s", 500, 0, 500, true, false, "hist"); + mTrackDCA[1] = createHisto(TString::Format("%sNegative/TrackDCA", mPath.c_str()), "Track DCA (-);DCA (cm);entries/s", 500, 0, 500, true, false, "hist"); + mTrackDCA[2] = createHisto(TString::Format("%sTrackDCA", mPath.c_str()), "Track DCA;DCA (cm);entries/s", 500, 0, 500, false, false, "hist"); + + mTrackPDCA[0] = createHisto(TString::Format("%sPositive/TrackPDCA", mPath.c_str()), "Track p#timesDCA (+);p#timesDCA (GeVcm/c);entries/s", 5000, 0, 5000, true, false, "hist"); + mTrackPDCA[1] = createHisto(TString::Format("%sNegative/TrackPDCA", mPath.c_str()), "Track p#timesDCA (-);p#timesDCA (GeVcm/c);entries/s", 5000, 0, 5000, true, false, "hist"); + mTrackPDCA[2] = createHisto(TString::Format("%sTrackPDCA", mPath.c_str()), "Track p#timesDCA;p#timesDCA (GeVcm/c);entries/s", 5000, 0, 5000, false, false, "hist"); + + mTrackRAbs[0] = createHisto(TString::Format("%sPositive/TrackRAbs", mPath.c_str()), "Track R_{abs} (+);R_{abs} (cm);entries/s", 1000, 0, 100, true, false, "hist"); + mTrackRAbs[1] = createHisto(TString::Format("%sNegative/TrackRAbs", mPath.c_str()), "Track R_{abs} (-);R_{abs} (cm);entries/s", 1000, 0, 100, true, false, "hist"); + mTrackRAbs[2] = createHisto(TString::Format("%sTrackRAbs", mPath.c_str()), "Track R_{abs};R_{abs} (cm);entries/s", 1000, 0, 100, false, false, "hist"); + + mTrackEta[0] = createHisto(TString::Format("%sPositive/TrackEta", mPath.c_str()), "Track #eta (+);#eta;entries/s", etaBins, -4.5, -2, true, false, "hist"); + mTrackEta[1] = createHisto(TString::Format("%sNegative/TrackEta", mPath.c_str()), "Track #eta (-);#eta;entries/s", etaBins, -4.5, -2, true, false, "hist"); + mTrackEta[2] = createHisto(TString::Format("%sTrackEta", mPath.c_str()), "Track #eta;#eta;entries/s", etaBins, -4.5, -2, false, false, "hist"); + + mTrackPhi[0] = createHisto(TString::Format("%sPositive/TrackPhi", mPath.c_str()), "Track #phi (+);#phi (deg);entries/s", phiBins, -180, 180, true, false, "hist"); + mTrackPhi[1] = createHisto(TString::Format("%sNegative/TrackPhi", mPath.c_str()), "Track #phi (-);#phi (deg);entries/s", phiBins, -180, 180, true, false, "hist"); + mTrackPhi[2] = createHisto(TString::Format("%sTrackPhi", mPath.c_str()), "Track #phi;#phi (deg);entries/s", phiBins, -180, 180, false, false, "hist"); + + mTrackEtaPhi[0] = createHisto(TString::Format("%sPositive/TrackEtaPhi", mPath.c_str()), "Track #phi vs #eta (+);#eta;#phi", etaBins / 5, -4.5, -2, phiBins / 5, -180, 180, true, false, "colz"); + mTrackEtaPhi[1] = createHisto(TString::Format("%sNegative/TrackEtaPhi", mPath.c_str()), "Track #phi vs #eta (-);#eta;#phi", etaBins / 5, -4.5, -2, phiBins / 5, -180, 180, true, false, "colz"); + mTrackEtaPhi[2] = createHisto(TString::Format("%sTrackEtaPhi", mPath.c_str()), "Track #phi vs #eta;#eta;#phi", etaBins / 5, -4.5, -2, phiBins / 5, -180, 180, false, false, "colz"); + + mTrackPt[0] = createHisto(TString::Format("%sPositive/TrackPt", mPath.c_str()), "Track p_{T} (+);p_{T} (GeV/c);entries/s", ptBins, 0, 30, true, false, "hist logy"); + mTrackPt[1] = createHisto(TString::Format("%sNegative/TrackPt", mPath.c_str()), "Track p_{T} (-);p_{T} (GeV/c);entries/s", ptBins, 0, 30, true, false, "hist logy"); + mTrackPt[2] = createHisto(TString::Format("%sTrackPt", mPath.c_str()), "Track p_{T};p_{T} (GeV/c);entries/s", ptBins, 0, 30, false, false, "hist logy"); + + mTrackQOverPt = createHisto(TString::Format("%sTrackQOverPt", mPath.c_str()), "Track q/p_{T};q/p_{T} (GeV/c)^{-1};entries/s", 200, -10, 10, false, false, "hist logy"); + + mTrackEtaPt[0] = createHisto(TString::Format("%sPositive/TrackEtaPt", mPath.c_str()), "Track p_{T} vs #eta (+);#eta;p_{T} (GeV/c)", etaBins / 5, -4.5, -2, ptBins / 5, 0, 30, true, false, "colz"); + mTrackEtaPt[1] = createHisto(TString::Format("%sNegative/TrackEtaPt", mPath.c_str()), "Track p_{T} vs #eta (-);#eta;p_{T} (GeV/c)", etaBins / 5, -4.5, -2, ptBins / 5, 0, 30, true, false, "colz"); + mTrackEtaPt[2] = createHisto(TString::Format("%sTrackEtaPt", mPath.c_str()), "Track p_{T} vs #eta;#eta;p_{T} (GeV/c)", etaBins / 5, -4.5, -2, ptBins / 5, 0, 30, true, false, "colz"); + + mTrackPhiPt[0] = createHisto(TString::Format("%sPositive/TrackPhiPt", mPath.c_str()), "Track p_{T} vs #phi (+);#phi;p_{T} (GeV/c)", phiBins / 5, -180, 180, ptBins / 5, 0, 30, true, false, "colz"); + mTrackPhiPt[1] = createHisto(TString::Format("%sNegative/TrackPhiPt", mPath.c_str()), "Track p_{T} vs #phi (-);#phi;p_{T} (GeV/c)", phiBins / 5, -180, 180, ptBins / 5, 0, 30, true, false, "colz"); + mTrackPhiPt[2] = createHisto(TString::Format("%sTrackPhiPt", mPath.c_str()), "Track p_{T} vs #phi;#phi;p_{T} (GeV/c)", phiBins / 5, -180, 180, ptBins / 5, 0, 30, true, false, "colz"); + + mTrackBC = createHisto(TString::Format("%sTrackBC", mPath.c_str()), "Track BC;BC;entries/s", o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches, false, false, "hist"); + + if (mSrc == GID::MFTMCH || mSrc == GID::MFTMCHMID) { + mMatchScoreMFTMCH = createHisto(TString::Format("%sMatchScoreMFTMCH", mPath.c_str()), "Match Score MFT-MCH;score", 1000, 0, 100, false, false, "hist"); + mMatchChi2MFTMCH = createHisto(TString::Format("%sMatchChi2MFTMCH", mPath.c_str()), "Match #chi^{2} MFT-MCH;#chi^{2}", 1000, 0, 100, false, false, "hist"); + mMatchNMFTCandidates = createHisto(TString::Format("%sMatchNMFTCandidates", mPath.c_str()), "MFT Candidates;candidates", 1000, 0, 1000, false, false, "hist"); + + mTrackEtaGlobal[0] = createHisto(TString::Format("%sPositive/TrackEtaGlobal", mPath.c_str()), "Global track #eta (+);#eta;entries/s", etaBins, -4.5, -2, true, false, "hist"); + mTrackEtaGlobal[1] = createHisto(TString::Format("%sNegative/TrackEtaGlobal", mPath.c_str()), "Global track #eta (-);#eta;entries/s", etaBins, -4.5, -2, true, false, "hist"); + mTrackEtaGlobal[2] = createHisto(TString::Format("%sTrackEtaGlobal", mPath.c_str()), "Global track #eta;#eta;entries/s", etaBins, -4.5, -2, false, false, "hist"); + + mTrackPhiGlobal[0] = createHisto(TString::Format("%sPositive/TrackPhiGlobal", mPath.c_str()), "Global track #phi (+);#phi (deg);entries/s", phiBins, -180, 180, true, false, "hist"); + mTrackPhiGlobal[1] = createHisto(TString::Format("%sNegative/TrackPhiGlobal", mPath.c_str()), "Global track #phi (-);#phi (deg);entries/s", phiBins, -180, 180, true, false, "hist"); + mTrackPhiGlobal[2] = createHisto(TString::Format("%sTrackPhiGlobal", mPath.c_str()), "Global track #phi;#phi (deg);entries/s", phiBins, -180, 180, false, false, "hist"); + + mTrackPtGlobal[0] = createHisto(TString::Format("%sPositive/TrackPtGlobal", mPath.c_str()), "Global track p_{T} (+);p_{T} (GeV/c);entries/s", ptBins, 0, 30, true, false, "hist logy"); + mTrackPtGlobal[1] = createHisto(TString::Format("%sNegative/TrackPtGlobal", mPath.c_str()), "Global track p_{T} (-);p_{T} (GeV/c);entries/s", ptBins, 0, 30, true, false, "hist logy"); + mTrackPtGlobal[2] = createHisto(TString::Format("%sTrackPtGlobal", mPath.c_str()), "Global track p_{T};p_{T} (GeV/c);entries/s", ptBins, 0, 30, false, false, "hist logy"); + + mTrackQOverPtGlobal = createHisto(TString::Format("%sTrackQOverPtGlobal", mPath.c_str()), "Global track q/p_{T};q/p_{T} (GeV/c)^{-1};entries/s", 200, -10, 10, false, false, "hist logy"); + + mTrackEtaPhiGlobal[0] = createHisto(TString::Format("%sPositive/TrackEtaPhiGlobal", mPath.c_str()), "Global track #phi vs #eta (+);#eta;#phi", etaBins / 5, -4.5, -2, phiBins / 5, -180, 180, true, false, "colz"); + mTrackEtaPhiGlobal[1] = createHisto(TString::Format("%sNegative/TrackEtaPhiGlobal", mPath.c_str()), "Global track #phi vs #eta (-);#eta;#phi", etaBins / 5, -4.5, -2, phiBins / 5, -180, 180, true, false, "colz"); + mTrackEtaPhiGlobal[2] = createHisto(TString::Format("%sTrackEtaPhiGlobal", mPath.c_str()), "Global track #phi vs #eta;#eta;#phi", etaBins / 5, -4.5, -2, phiBins / 5, -180, 180, false, false, "colz"); + + mTrackEtaPtGlobal[0] = createHisto(TString::Format("%sPositive/TrackEtaPtGlobal", mPath.c_str()), "Global track p_{T} vs #eta (+);#eta;p_{T} (GeV/c)", etaBins / 5, -4.5, -2, ptBins / 5, 0, 30, true, false, "colz"); + mTrackEtaPtGlobal[1] = createHisto(TString::Format("%sNegative/TrackEtaPtGlobal", mPath.c_str()), "Global track p_{T} vs #eta (-);#eta;p_{T} (GeV/c)", etaBins / 5, -4.5, -2, ptBins / 5, 0, 30, true, false, "colz"); + mTrackEtaPtGlobal[2] = createHisto(TString::Format("%sTrackEtaPtGlobal", mPath.c_str()), "Global track p_{T} vs #eta;#eta;p_{T} (GeV/c)", etaBins / 5, -4.5, -2, ptBins / 5, 0, 30, true, false, "colz"); + + mTrackPhiPtGlobal[0] = createHisto(TString::Format("%sPositive/TrackPhiPtGlobal", mPath.c_str()), "Global track p_{T} vs #phi (+);#phi;p_{T} (GeV/c)", phiBins / 5, -180, 180, ptBins / 5, 0, 30, true, false, "colz"); + mTrackPhiPtGlobal[1] = createHisto(TString::Format("%sNegative/TrackPhiPtGlobal", mPath.c_str()), "Global track p_{T} vs #phi (-);#phi;p_{T} (GeV/c)", phiBins / 5, -180, 180, ptBins / 5, 0, 30, true, false, "colz"); + mTrackPhiPtGlobal[2] = createHisto(TString::Format("%sTrackPhiPtGlobal", mPath.c_str()), "Global track p_{T} vs #phi;#phi;p_{T} (GeV/c)", phiBins / 5, -180, 180, ptBins / 5, 0, 30, true, false, "colz"); + + mTrackEtaCorr[0] = createHisto(TString::Format("%sPositive/TrackEtaCorr", mPath.c_str()), "Track #eta - GLO vs MCH (+);#eta^{MCH};#eta^{GLO}", etaBins / 5, -4.5, -2, etaBins / 5, -4.5, -2, true, false, "colz"); + mTrackEtaCorr[1] = createHisto(TString::Format("%sNegative/TrackEtaCorr", mPath.c_str()), "Track #eta - GLO vs MCH (-);#eta^{MCH};#eta^{GLO}", etaBins / 5, -4.5, -2, etaBins / 5, -4.5, -2, true, false, "colz"); + mTrackEtaCorr[2] = createHisto(TString::Format("%sTrackEtaCorr", mPath.c_str()), "Track #eta - GLO vs MCH;#eta^{MCH};#eta^{GLO}", etaBins / 5, -4.5, -2, etaBins / 5, -4.5, -2, true, false, "colz"); + + mTrackDEtaVsEta[0] = createHisto(TString::Format("%sPositive/TrackDEtaVsEta", mPath.c_str()), "Track #eta^{GLO}-#eta^{MCH} vs #eta^{MCH} (+);#eta^{MCH};#eta^{GLO}-#eta^{MCH}", etaBins / 5, -4.5, -2, 200, -1, 1, true, false, "colz"); + mTrackDEtaVsEta[1] = createHisto(TString::Format("%sNegative/TrackDEtaVsEta", mPath.c_str()), "Track #eta^{GLO}-#eta^{MCH} vs #eta^{MCH} (-);#eta^{MCH};#eta^{GLO}-#eta^{MCH}", etaBins / 5, -4.5, -2, 200, -1, 1, true, false, "colz"); + mTrackDEtaVsEta[2] = createHisto(TString::Format("%sTrackDEtaVsEta", mPath.c_str()), "Track #eta^{GLO}-#eta^{MCH} vs #eta^{MCH};#eta^{MCH};#eta^{GLO}-#eta^{MCH}", etaBins / 5, -4.5, -2, 200, -1, 1, false, false, "colz"); + + mTrackPhiCorr[0] = createHisto(TString::Format("%sPositive/TrackPhiCorr", mPath.c_str()), "Track #phi - GLO vs MCH (+);#phi^{MCH};#phi^{GLO}", phiBins / 5, -180, 180, phiBins / 5, -180, 180, true, false, "colz"); + mTrackPhiCorr[1] = createHisto(TString::Format("%sNegative/TrackPhiCorr", mPath.c_str()), "Track #phi - GLO vs MCH (-);#phi^{MCH};#phi^{GLO}", phiBins / 5, -180, 180, phiBins / 5, -180, 180, true, false, "colz"); + mTrackPhiCorr[2] = createHisto(TString::Format("%sTrackPhiCorr", mPath.c_str()), "Track #phi - GLO vs MCH;#phi^{MCH};#phi^{GLO}", phiBins / 5, -180, 180, phiBins / 5, -180, 180, true, false, "colz"); + + mTrackDPhiVsPhi[0] = createHisto(TString::Format("%sPositive/TrackDPhiVsPhi", mPath.c_str()), "Track #phi^{GLO}-#phi^{MCH} vs #phi^{MCH} (+);#phi^{MCH};#phi^{GLO}-#phi^{MCH}", phiBins / 5, -180, 180, 200, -100, 100, true, false, "colz"); + mTrackDPhiVsPhi[1] = createHisto(TString::Format("%sNegative/TrackDPhiVsPhi", mPath.c_str()), "Track #phi^{GLO}-#phi^{MCH} vs #phi^{MCH} (-);#phi^{MCH};#phi^{GLO}-#phi^{MCH}", phiBins / 5, -180, 180, 200, -100, 100, true, false, "colz"); + mTrackDPhiVsPhi[2] = createHisto(TString::Format("%sTrackDPhiVsPhi", mPath.c_str()), "Track #phi^{GLO}-#phi^{MCH} vs #phi^{MCH};#phi^{MCH};#phi^{GLO}-#phi^{MCH}", phiBins / 5, -180, 180, 200, -100, 100, false, false, "colz"); + + mTrackPtCorr[0] = createHisto(TString::Format("%sPositive/TrackPtCorr", mPath.c_str()), "Track p_{T} - GLO vs MCH (+);p_{T}^{MCH};p_{T}^{GLO}", ptBins / 5, 0, 30, ptBins / 5, 0, 30, true, false, "colz"); + mTrackPtCorr[1] = createHisto(TString::Format("%sNegative/TrackPtCorr", mPath.c_str()), "Track p_{T} - GLO vs MCH (-);p_{T}^{MCH};p_{T}^{GLO}", ptBins / 5, 0, 30, ptBins / 5, 0, 30, true, false, "colz"); + mTrackPtCorr[2] = createHisto(TString::Format("%sTrackPtCorr", mPath.c_str()), "Track p_{T} - GLO vs MCH;p_{T}^{MCH};p_{T}^{GLO}", ptBins / 5, 0, 30, ptBins / 5, 0, 30, true, false, "colz"); + + mTrackDPtVsPt[0] = createHisto(TString::Format("%sPositive/TrackDPtVsPt", mPath.c_str()), "Track p_{T}^{GLO}-p_{T}^{MCH} vs p_{T}^{MCH} (+);p_{T}^{MCH};p_{T}^{GLO}-p_{T}^{MCH}", ptBins / 5, 0, 30, 200, -10, 10, true, false, "colz"); + mTrackDPtVsPt[1] = createHisto(TString::Format("%sNegative/TrackDPtVsPt", mPath.c_str()), "Track p_{T}^{GLO}-p_{T}^{MCH} vs p_{T}^{MCH} (-);p_{T}^{MCH};p_{T}^{GLO}-p_{T}^{MCH}", ptBins / 5, 0, 30, 200, -10, 10, true, false, "colz"); + mTrackDPtVsPt[2] = createHisto(TString::Format("%sTrackDPtVsPt", mPath.c_str()), "Track p_{T}^{GLO}-p_{T}^{MCH} vs p_{T}^{MCH};p_{T}^{MCH};p_{T}^{GLO}-p_{T}^{MCH}", ptBins / 5, 0, 30, 200, -10, 10, false, false, "colz"); + } + + if (mSrc == GID::MCHMID || mSrc == GID::MFTMCHMID) { + mMatchChi2MCHMID = createHisto(TString::Format("%sMatchChi2MCHMID", mPath.c_str()), "Match #chi^{2} MCH-MID;#chi^{2}", 1000, 0, 100, false, true, "hist"); + mTrackDT = createHisto(TString::Format("%sTrackDT", mPath.c_str()), "MCH-MID time correlation;ns", 4000, -500, 500, false, true, "hist"); + } + + mTrackPosAtMFT = createHisto(TString::Format("%sTrackPosAtMFT", mPath.c_str()), "MCH Track position at MFT exit;X (cm);Y (cm)", 100, -50, 50, 100, -50, 50, false, false, "colz"); + mTrackPosAtMID = createHisto(TString::Format("%sTrackPosAtMID", mPath.c_str()), "MCH Track position at MID entrance;X (cm);Y (cm)", 80, -400, 400, 80, -400, 400, false, false, "colz"); +} + +void TrackPlotter::createTrackPairHistos() +{ + mMinvFull = createHisto(TString::Format("%sMinvFull", mPath.c_str()), "#mu^{+}#mu^{-} invariant mass;M_{#mu^{+}#mu^{-}} (GeV/c^{2})", 5000, 0, 100, false, true, "hist"); + mMinv = createHisto(TString::Format("%sMinv", mPath.c_str()), "#mu^{+}#mu^{-} invariant mass;M_{#mu^{+}#mu^{-}} (GeV/c^{2})", 200, 1, 5, false, true, "hist"); + mMinvBgd = createHisto(TString::Format("%sMinvBgd", mPath.c_str()), "#mu^{+}#mu^{-} inv. mass background;M_{#mu^{+}#mu^{-}} (GeV/c^{2})", 200, 1, 5, true, true, "hist"); + mDimuonDT = createHisto(TString::Format("%sDimuonTimeDiff", mPath.c_str()), "#mu^{+}#mu^{-} time difference;ns", 4000, -2000, 2000, false, true, "hist"); +} + +void TrackPlotter::normalizePlot(TH1* hist) +{ + static constexpr double sOrbitLengthInSeconds = o2::constants::lhc::LHCOrbitMUS / 1000000; + + TH1DRatio* h1 = dynamic_cast(hist); + if (h1) { + h1->getDen()->Fill((Double_t)0, sOrbitLengthInSeconds * mNOrbitsPerTF); + } else { + TH2DRatio* h2 = dynamic_cast(hist); + if (h2) { + h2->getDen()->Fill((Double_t)0, (Double_t)0, sOrbitLengthInSeconds * mNOrbitsPerTF); + } + } +} + +void TrackPlotter::fillTrackPairHistos(gsl::span> tracks) +{ + if (tracks.size() > 1) { + for (auto i = 0; i < tracks.size(); i++) { + if (!(tracks[i].second)) { + continue; + } + const MuonTrack& ti = tracks[i].first; + auto pi = ti.getMuonMomentumAtVertex(); + + for (auto j = i + 1; j < tracks.size(); j++) { + if (!(tracks[j].second)) { + continue; + } + const MuonTrack& tj = tracks[j].first; + + if (ti.getSign() == tj.getSign()) { + continue; + } + + auto dt = (ti.getIR() - tj.getIR()).bc2ns(); + auto dtMUS = ti.getTime().getTimeStamp() - tj.getTime().getTimeStamp(); + Fill(mDimuonDT, dtMUS * 1000); + + bool diMuonOK = true; + for (auto& cut : mDiMuonCuts) { + if (!cut(ti, tj)) { + diMuonOK = false; + break; + } + } + // tracks are considered to be correlated if they are closer than 1 us in time + auto pj = tj.getMuonMomentumAtVertex(); + auto p = pi + pj; + if (diMuonOK) { + Fill(mMinv, p.M()); + Fill(mMinvFull, p.M()); + } + // the shape of the combinatorial background is derived by combining tracks + // than belong to different orbits (more than 90 us apart) + if (std::abs(dtMUS) > 1) { + Fill(mMinvBgd, p.M()); + } + } + } + } +} + +void TrackPlotter::fillTrackHistos(const MuonTrack& track) +{ + int q = (track.getSign() < 0) ? 1 : 0; + + Fill(mTrackBC, track.getIR().bc); + + auto dtMUS = track.getTimeMID().getTimeStamp() - track.getTimeMCH().getTimeStamp(); + Fill(mTrackDT, dtMUS * 1000); + + switch (mSrc) { + case GID::MCHMID: { + Fill(mMatchChi2MCHMID, track.getMatchInfoFwd().getMIDMatchingChi2()); + break; + } + case GID::MFTMCH: { + Fill(mMatchScoreMFTMCH, track.getMatchInfoFwd().getMFTMCHMatchingScore()); + Fill(mMatchChi2MFTMCH, track.getMatchInfoFwd().getMFTMCHMatchingChi2()); + Fill(mMatchNMFTCandidates, track.getMatchInfoFwd().getNMFTCandidates()); + break; + } + case GID::MFTMCHMID: { + Fill(mMatchScoreMFTMCH, track.getMatchInfoFwd().getMFTMCHMatchingScore()); + Fill(mMatchChi2MFTMCH, track.getMatchInfoFwd().getMFTMCHMatchingChi2()); + Fill(mMatchChi2MCHMID, track.getMatchInfoFwd().getMIDMatchingChi2()); + Fill(mMatchNMFTCandidates, track.getMatchInfoFwd().getNMFTCandidates()); + break; + } + default: + break; + } + + double chi2 = track.getChi2OverNDF(); + Fill(mTrackChi2OverNDF[q], chi2); + Fill(mTrackChi2OverNDF[2], chi2); + + double dca = track.getDCA(); + Fill(mTrackDCA[q], dca); + Fill(mTrackDCA[2], dca); + + double pdca = track.getPDCAMCH(); + Fill(mTrackPDCA[q], pdca); + Fill(mTrackPDCA[2], pdca); + + auto rAbs = track.getRAbs(); + Fill(mTrackRAbs[q], rAbs); + Fill(mTrackRAbs[2], rAbs); + + // Kinematic distributions, both from MCH tracks parameters and from global tracks parameters if MFT is included + double eta = track.getEta(); + double etaMCH = track.hasMCH() ? track.getEtaMCH() : 0; + Fill(mTrackEta[q], etaMCH); + Fill(mTrackEta[2], etaMCH); + Fill(mTrackEtaGlobal[q], eta); + Fill(mTrackEtaGlobal[2], eta); + Fill(mTrackEtaCorr[q], etaMCH, eta); + Fill(mTrackEtaCorr[2], etaMCH, eta); + Fill(mTrackDEtaVsEta[q], etaMCH, eta - etaMCH); + Fill(mTrackDEtaVsEta[2], etaMCH, eta - etaMCH); + + double phi = track.getPhi(); + double phiMCH = track.hasMCH() ? track.getPhiMCH() : 0; + Fill(mTrackPhi[q], phiMCH); + Fill(mTrackPhi[2], phiMCH); + Fill(mTrackPhiGlobal[q], phi); + Fill(mTrackPhiGlobal[2], phi); + Fill(mTrackPhiCorr[q], phiMCH, phi); + Fill(mTrackPhiCorr[2], phiMCH, phi); + Fill(mTrackDPhiVsPhi[q], phiMCH, phi - phiMCH); + Fill(mTrackDPhiVsPhi[2], phiMCH, phi - phiMCH); + + Fill(mTrackEtaPhi[q], etaMCH, phiMCH); + Fill(mTrackEtaPhi[2], etaMCH, phiMCH); + Fill(mTrackEtaPhiGlobal[q], eta, phi); + Fill(mTrackEtaPhiGlobal[2], eta, phi); + + double pt = track.getPt(); + double ptMCH = track.hasMCH() ? track.getPtMCH() : 0; + Fill(mTrackPt[q], ptMCH); + Fill(mTrackPt[2], ptMCH); + Fill(mTrackPtGlobal[q], pt); + Fill(mTrackPtGlobal[2], pt); + Fill(mTrackPtCorr[q], ptMCH, pt); + Fill(mTrackPtCorr[2], ptMCH, pt); + Fill(mTrackDPtVsPt[q], ptMCH, pt - ptMCH); + Fill(mTrackDPtVsPt[2], ptMCH, pt - ptMCH); + + Fill(mTrackEtaPt[q], etaMCH, ptMCH); + Fill(mTrackEtaPt[2], etaMCH, ptMCH); + Fill(mTrackEtaPtGlobal[q], eta, pt); + Fill(mTrackEtaPtGlobal[2], eta, pt); + + Fill(mTrackPhiPt[q], phiMCH, ptMCH); + Fill(mTrackPhiPt[2], phiMCH, ptMCH); + Fill(mTrackPhiPtGlobal[q], phi, pt); + Fill(mTrackPhiPtGlobal[2], phi, pt); + + if (track.getSign() != 0 && ptMCH != 0) { + double qOverPtMCH = track.getSign() / ptMCH; + Fill(mTrackQOverPt, qOverPtMCH); + } + + if (track.getSign() != 0 && pt != 0) { + double qOverPt = track.getSign() / pt; + Fill(mTrackQOverPtGlobal, qOverPt); + } + + o2::mch::TrackParam trackParamAtMFT; + float zMFT = sLastMFTPlaneZ; + track.extrapToZMCH(trackParamAtMFT, zMFT); + double xMCH = trackParamAtMFT.getNonBendingCoor(); + double yMCH = trackParamAtMFT.getBendingCoor(); + Fill(mTrackPosAtMFT, trackParamAtMFT.getNonBendingCoor(), trackParamAtMFT.getBendingCoor()); + + Fill(mTrackPosAtMID, track.getXMid(), track.getYMid()); +} + +void TrackPlotter::fillHistograms(const o2::globaltracking::RecoContainer& recoCont) +{ + static bool sFirst = true; + + if (sFirst) { + o2::mch::TrackExtrap::setField(); + sFirst = false; + } + + if (mNOrbitsPerTF < 0) { + mNOrbitsPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + } + + mMuonTracks.clear(); + + if (mSrc == GID::MCH) { + auto tracksMCH = recoCont.getMCHTracks(); + int trackID = 0; + for (auto& t : tracksMCH) { + mMuonTracks.emplace_back(std::make_pair({ &t, trackID, recoCont, mFirstTForbit }, true)); + trackID += 1; + } + } + if (mSrc == GID::MFTMCH || mSrc == GID::MFTMCHMID) { + auto tracksFwd = recoCont.getGlobalFwdTracks(); + std::map>> matchingCandidates; + // loop over the global forward tracks and collect the matching candidates + for (size_t ti = 0; ti < tracksFwd.size(); ti++) { + auto& t = tracksFwd[ti]; + // skip tracks without MID if full matching is requested + if (mSrc == GID::MFTMCHMID && t.getMIDTrackID() < 0) { + continue; + } + + // associate the matching candidate to the corresponding MCH standalone track, and store the matching chi2 + int64_t mchTrackIndex = t.getMCHTrackID(); + double matchingChi2 = t.getMFTMCHMatchingChi2(); + auto matchingCandidateIterator = matchingCandidates.find(mchTrackIndex); + if (matchingCandidateIterator != matchingCandidates.end()) { + matchingCandidateIterator->second.push_back(std::make_pair(ti, matchingChi2)); + } else { + matchingCandidates[mchTrackIndex].push_back(std::make_pair(ti, matchingChi2)); + } + } + + // loop over the matching candidate for each standalone MCH track, sort the candidates according to the matching chi2, + // and for each MCH track pick the leading candidate for further processing + for (auto& [mchTrackIndex, matchingCandidatesVector] : matchingCandidates) { + if (matchingCandidates.empty()) { + continue; + } + + // sort the vector of matching candidates in ascending order of the matching chi2 + std::sort(matchingCandidatesVector.begin(), matchingCandidatesVector.end(), + [](const std::pair& t1, const std::pair& t2) -> bool { + return (t1.second < t2.second); + }); + + // store the leading candidate + auto& t = tracksFwd[matchingCandidatesVector[0].first]; + mMuonTracks.emplace_back(std::make_pair({ &t, recoCont, mFirstTForbit }, true)); + } + } + if (mSrc == GID::MCHMID) { + auto tracksMCHMID = recoCont.getMCHMIDMatches(); + for (auto& t : tracksMCHMID) { + mMuonTracks.emplace_back(std::make_pair({ &t, recoCont, mFirstTForbit }, true)); + } + } + + int nPos{ 0 }; + int nNeg{ 0 }; + int nTot{ 0 }; + + for (auto& t : mMuonTracks) { + bool ok = true; + for (auto& cut : mMuonCuts) { + if (!cut(t.first)) { + ok = false; + break; + } + } + t.second = ok; + if (!t.second) { + continue; + } + + nTot += 1; + if (t.first.getSign() < 0) { + nNeg += 1; + } else { + nPos += 1; + } + } + + Fill(mNofTracksPerTF[0], nPos); + Fill(mNofTracksPerTF[1], nNeg); + Fill(mNofTracksPerTF[2], nTot); + + for (const auto& mt : mMuonTracks) { + if (!mt.second) { + continue; + } + fillTrackHistos(mt.first); + } + + fillTrackPairHistos(mMuonTracks); + + for (auto hinfo : histograms()) { + TH1* h1 = dynamic_cast(hinfo.object); + if (h1) { + normalizePlot(h1); + } + } +} + +void TrackPlotter::endOfCycle() +{ + for (auto hinfo : histograms()) { + TH1DRatio* h1 = dynamic_cast(hinfo.object); + if (h1) { + h1->update(); + } else { + TH2DRatio* h2 = dynamic_cast(hinfo.object); + if (h2) { + h2->update(); + } + } + } +} + +} // namespace o2::quality_control_modules::muon diff --git a/Modules/MUON/Common/src/TracksCheck.cxx b/Modules/MUON/Common/src/TracksCheck.cxx new file mode 100644 index 0000000000..64dab85719 --- /dev/null +++ b/Modules/MUON/Common/src/TracksCheck.cxx @@ -0,0 +1,315 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "MUONCommon/TracksCheck.h" +#include "MUONCommon/Helpers.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" +#include +#include + +// ROOT +#include +#include +#include +#include +#include + +using namespace o2::quality_control; + +namespace o2::quality_control_modules::muon +{ + +void TracksCheck::configure() +{ +} + +void TracksCheck::startOfActivity(const Activity& activity) +{ + mMinTracksPerTF = getConfigurationParameter(mCustomParameters, "minTracksPerTF", mMinTracksPerTF, activity); + mMaxTracksPerTF = getConfigurationParameter(mCustomParameters, "maxTracksPerTF", mMaxTracksPerTF, activity); + mMaxDeltaPhi = getConfigurationParameter(mCustomParameters, "maxDeltaPhi", mMaxDeltaPhi, activity); + mMaxChargeAsymmetry = getConfigurationParameter(mCustomParameters, "maxChargeAsymmetry", mMaxChargeAsymmetry, activity); + mMarkerSize = getConfigurationParameter(mCustomParameters, "markerSize", mMarkerSize, activity); + + mQualities.clear(); +} + +void TracksCheck::endOfActivity(const Activity& activity) +{ +} + +Quality TracksCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + + // number of tracks per TF + if (mo->getName().find("TracksPerTF") != std::string::npos) { + TH1* h = dynamic_cast(mo->getObject()); + if (!h) { + continue; + } + + // quality flags initialization + mQualities[mo->getName()] = Quality::Good; + if (result == Quality::Null) { + result.set(Quality::Good); + } + + // check that the average number of tracks per TF is within given limits + if (h->GetMean() < mMinTracksPerTF || h->GetMean() > mMaxTracksPerTF) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::BadTracking(), "Number of tracks per TF not in the expected range"); + mQualities[mo->getName()] = Quality::Bad; + } + } + + // azimuthal distribution of tracks + if (mo->getName().find("TrackPhi") != std::string::npos) { + TH1* h = dynamic_cast(mo->getObject()); + if (!h) { + continue; + } + + // quality flags initialization + mQualities[mo->getName()] = Quality::Good; + if (result == Quality::Null) { + result.set(Quality::Good); + } + + // compute the average number of entries in each quadrant, as well as the global average + float quadrantNBins = (float)(h->GetXaxis()->GetNbins()) / 4; + int quadrantFirstBin[4]{ + (int)(quadrantNBins * 2), // Q0 + (int)(quadrantNBins * 3), // Q1 + 0, // Q2 + (int)(quadrantNBins) // Q3 + }; + int quadrantLastBin[4]{ + (int)(quadrantNBins * 3) - 1, // Q0 + h->GetXaxis()->GetNbins(), // Q1 + (int)(quadrantNBins)-1, // Q2 + (int)(quadrantNBins * 2) - 1 // Q3 + }; + + std::array quadrantAverage; + double globalAverage = 0; + for (int q = 0; q < 4; q++) { + quadrantAverage[q] = 0; + for (int i = quadrantFirstBin[q]; i <= quadrantLastBin[q]; i++) { + quadrantAverage[q] += h->GetBinContent(i); + globalAverage += h->GetBinContent(i); + } + quadrantAverage[q] /= quadrantLastBin[q] - quadrantFirstBin[q] + 1; + } + globalAverage /= h->GetXaxis()->GetNbins(); + + // Check that the relative deviation between each quadrant is below a given threshold + // Set the quality to Bad if any pair of quadrants deviates more than the threshold + for (int qi = 0; qi < 4; qi++) { + for (int qj = qi + 1; qj < 4; qj++) { + auto delta = std::fabs(quadrantAverage[qi] - quadrantAverage[qj]) / globalAverage; + if (delta > mMaxDeltaPhi) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::BadTracking(), fmt::format("large deviation in phi distribution {:.2f} > {:.2f}", delta, mMaxDeltaPhi)); + mQualities[mo->getName()] = Quality::Bad; + } + } + } + } + + // distribution of charge-over-pT + if (mo->getName().find("TrackQOverPt") != std::string::npos) { + TH1* h = dynamic_cast(mo->getObject()); + if (!h) { + continue; + } + + // quality flags initialization + mQualities[mo->getName()] = Quality::Good; + if (result == Quality::Null) { + result.set(Quality::Good); + } + + // compute the charge asymmetry as the relative difference + // between the total number of positive and negative tracks + int zeroBin = h->GetXaxis()->FindBin((double)(0)); + double negIntegral = h->Integral(1, zeroBin - 1); + double posIntegral = h->Integral(zeroBin, h->GetXaxis()->GetNbins()); + double chargeRatio = (posIntegral > 0) ? negIntegral / posIntegral : 0; + double chargeAsymmetry = std::fabs(chargeRatio - 1); + + // check that the charge asymmetry is below a given threshold + if (chargeAsymmetry > mMaxChargeAsymmetry) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::BadTracking(), fmt::format("large charge asymmetry {:.2f} > {:.2f}", chargeAsymmetry, mMaxChargeAsymmetry)); + mQualities[mo->getName()] = Quality::Bad; + } + } + } + + return result; +} + +static void updateTitle(TH1* hist, std::string suffix) +{ + if (!hist) { + return; + } + TString title = hist->GetTitle(); + title.Append(" "); + title.Append(suffix.c_str()); + hist->SetTitle(title); +} + +static std::string getCurrentTime() +{ + time_t t; + time(&t); + + struct tm* tmp; + tmp = localtime(&t); + + char timestr[500]; + strftime(timestr, sizeof(timestr), "(%d/%m/%Y - %R)", tmp); + + std::string result = timestr; + return result; +} + +static int getQualityColor(Quality q) +{ + if (q == Quality::Null) + return kViolet - 6; + if (q == Quality::Bad) + return kRed; + if (q == Quality::Medium) + return kOrange - 3; + if (q == Quality::Good) + return kGreen + 2; + + return 0; +} + +// add a text label to an histogram, using a TPaveText to properly scale the text with the canvas size +// The x1,y1,x2,y2 parameters specify the corners of the pave +// The text is drawn with the ROOT color index specified in the color parameter +static void drawQualityLabel(TH1* h, const std::string& label, int color, float x1, float y1, float x2, float y2, int align) +{ + if (!h) { + return; + } + + auto* t = new TPaveText(x1, y1, x2, y2, "brNDC"); + t->AddText(label.c_str()); + t->SetBorderSize(0); + t->SetFillStyle(0); + t->SetTextAlign(align); + t->SetTextColor(color); + t->SetTextFont(42); + h->GetListOfFunctions()->Add(t); +} + +/// Add a marker to an histogram at a given position +/// The marker is draw with a TPolyLine such that it scales nicely with the size of the pad +/// The default dimensions of the marker are +/// * horizontal: 1/20 of the X-axis range +/// * vertical: 1/10 of the histogram values range +/// Parameters: +/// * histo: the histogram to which the marker is associated +/// * x0, y0: coordinates of the tip of the marker +/// * color: ROOT index of the marker fill color +/// * markerSize: overall scaling factor for the marker dimensions +/// * logx, logy: wether the X or Y axis are in logarithmic scale +static TPolyLine* addMarker(TH1& histo, double x0, double y0, int color, float markerSize, bool logx, bool logy) +{ + double xmin = logx ? std::log10(histo.GetXaxis()->GetXmin()) : histo.GetXaxis()->GetXmin(); + double xmax = logx ? std::log10(histo.GetXaxis()->GetXmax()) : histo.GetXaxis()->GetXmax(); + double xSize = (xmax - xmin) / 20; + double ySize = (histo.GetMaximum() - histo.GetMinimum()) / 10; + double x1 = logx ? std::pow(10, std::log10(x0) - xSize * markerSize / 2) : x0 - xSize * markerSize / 2; + double x2 = logx ? std::pow(10, std::log10(x0) + xSize * markerSize / 2) : x0 + xSize * markerSize / 2; + double xMarker[4] = { x0, x1, x2, x0 }; + double yMarker[4] = { y0, y0 + ySize * markerSize, y0 + ySize * markerSize, y0 }; + auto* m = new TPolyLine(4, xMarker, yMarker); + m->SetNDC(kFALSE); + m->SetFillColor(color); + m->SetOption("f"); + m->SetLineWidth(0); + histo.GetListOfFunctions()->Add(m); + return m; +} + +void TracksCheck::beautify(std::shared_ptr mo, Quality /*checkResult*/) +{ + auto currentTime = getCurrentTime(); + updateTitle(dynamic_cast(mo->getObject()), currentTime); + + Quality moQuality = Quality::Null; + if (mQualities.count(mo->getName()) > 0) { + moQuality = mQualities[mo->getName()]; + } + + int color = getQualityColor(moQuality); + + if (mo->getName().find("TracksPerTF") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + return; + } + + // get the maximum of the histogram + double max = h->GetBinContent(h->GetMaximumBin()); + + // adjust the vertical scale to provide some space on the top + h->SetMinimum(0); + h->SetMaximum(1.2 * max); + + // add vertical lines to display the acceptable range + addVerticalLine(*h, mMinTracksPerTF, color, kDashed, 1); + addVerticalLine(*h, mMaxTracksPerTF, color, kDashed, 1); + + // add a marker at the position of the histogram mean + addMarker(*h, h->GetMean(), max * 1.05, color, mMarkerSize, true, false); + + // draw the quality flag associated to the plot + drawQualityLabel(dynamic_cast(mo->getObject()), moQuality.getName(), color, 0.5, 0.75, 0.88, 0.88, 33); + } + + if (mo->getName().find("TrackPhi") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + return; + } + + // adjust the vertical scale to provide some space on the top + h->SetMinimum(0); + h->SetMaximum(1.2 * h->GetMaximum()); + + // draw the quality flag associated to the plot + drawQualityLabel(dynamic_cast(mo->getObject()), moQuality.getName(), color, 0.5, 0.75, 0.88, 0.88, 33); + + } else if (mo->getName().find("TrackQOverPt") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + return; + } + + h->SetMinimum(0); + + // draw the quality flag associated to the plot + drawQualityLabel(h, moQuality.getName(), color, 0.5, 0.75, 0.88, 0.88, 33); + } +} + +} // namespace o2::quality_control_modules::muon diff --git a/Modules/MUON/Common/src/TracksPostProcessing.cxx b/Modules/MUON/Common/src/TracksPostProcessing.cxx new file mode 100644 index 0000000000..d1b18e6cd6 --- /dev/null +++ b/Modules/MUON/Common/src/TracksPostProcessing.cxx @@ -0,0 +1,258 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TracksPostProcessing.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Post-processing of the MUON tracks +/// + +#include "MUONCommon/TracksPostProcessing.h" + +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/CcdbDatabase.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/ActivityHelpers.h" + +#include +#include +#include +#include + +using namespace o2::quality_control_modules::muon; +using namespace o2::quality_control::repository; + +namespace o2::quality_control_modules::muon +{ + +//_________________________________________________________________________________________ +// Helper function for retrieving a MonitorObject from the QCDB, in the form of a std::pair, bool> +// A non-null MO is returned in the first element of the pair if the MO is found in the QCDB +// The second element of the pair is set to true if the MO has a time stamp more recent than the last retrieved one + +static std::pair, bool> getMO(repository::DatabaseInterface& qcdb, std::string path, std::string name, Trigger t, uint64_t& timeStamp, int maxShiftMs = 1000 * 600) +{ + auto ts = t.timestamp; + const auto objFullPath = t.activity.mProvenance + "/" + path + "/" + name; + const auto filterMetadata = activity_helpers::asDatabaseMetadata(t.activity, false); + const auto objectValidity = qcdb.getLatestObjectValidity(objFullPath, filterMetadata); + if (objectValidity.isValid() && t.timestamp >= objectValidity.getMin() - maxShiftMs && t.timestamp <= objectValidity.getMax() + maxShiftMs) { + ts = objectValidity.getMax() - 1; + } else { + ILOG(Warning, Devel) << "Could not find an object '" << objFullPath << "' in the proximity of timestamp " + << t.timestamp << " with max shift of " << maxShiftMs << "ms" << ENDM; + return { nullptr, false }; + } + + // retrieve MO from CCDB + auto mo = qcdb.retrieveMO(path, name, ts, t.activity); + if (!mo) { + return { nullptr, false }; + } + // get the MO creation time stamp + long newTimeStamp{ 0 }; + auto iter = mo->getMetadataMap().find(metadata_keys::created); + if (iter != mo->getMetadataMap().end()) { + newTimeStamp = std::stol(iter->second); + } + // check if the object is newer than the last visited one + if (newTimeStamp <= timeStamp) { + return { mo, false }; + } + + // update the time stamp of the last visited object + timeStamp = newTimeStamp; + + // check if the object is not older than a given number of milliseconds + long elapsed = static_cast(t.timestamp) - ts; + if (elapsed > maxShiftMs) { + return { mo, false }; + } + + return { mo, true }; +} + +template +T* getPlotFromMO(std::shared_ptr mo) +{ + // Get ROOT object + TObject* obj = mo ? mo->getObject() : nullptr; + return dynamic_cast(obj); +} + +class MatchingEfficiencyPlotterInterface : public HistPlotter +{ + public: + MatchingEfficiencyPlotterInterface() = default; + ~MatchingEfficiencyPlotterInterface() = default; + virtual void update(repository::DatabaseInterface& qcdb, Trigger t, std::shared_ptr objectsManager) = 0; +}; + +template +class MatchingEfficiencyPlotter : public MatchingEfficiencyPlotterInterface +{ + public: + MatchingEfficiencyPlotter(std::string pathMatched, std::string pathMCH, std::string path, std::string plotName, int rebin); + ~MatchingEfficiencyPlotter() = default; + + void update(repository::DatabaseInterface& qcdb, Trigger t, std::shared_ptr objectsManager) override; + + private: + std::string mPlotPath[2]; + std::string mPlotName[2]; + uint64_t mTimestamp[2]; + std::string mName; + std::shared_ptr mHistMatchingEff; + int mRebin; +}; + +template +MatchingEfficiencyPlotter::MatchingEfficiencyPlotter(std::string pathMatched, std::string pathMCH, std::string path, std::string plotName, int rebin) + : MatchingEfficiencyPlotterInterface(), mRebin(rebin) +{ + mPlotPath[0] = pathMCH; + mPlotName[0] = plotName; + mPlotPath[1] = pathMatched; + mPlotName[1] = plotName; + + mTimestamp[0] = 0; + mTimestamp[1] = 0; + + mName = path + "/" + mPlotName[1]; +} + +template +void MatchingEfficiencyPlotter::update(repository::DatabaseInterface& qcdb, Trigger t, std::shared_ptr objectsManager) +{ + std::string pathMCH = mPlotPath[0]; + auto moMCH = getMO(qcdb, pathMCH, mPlotName[0], t, mTimestamp[0]); + std::string pathMatched = mPlotPath[1]; + auto moMatched = getMO(qcdb, pathMatched, mPlotName[1], t, mTimestamp[1]); + // check if both MOs are existing and updated + if (!moMCH.second || !moMatched.second) { + return; + } + + auto hMCH = getPlotFromMO(moMCH.first); + auto hMatched = getPlotFromMO(moMatched.first); + // check if both plots are valid + if (!hMCH || !hMatched) { + return; + } + + TString name = mName.c_str(); + auto title = TString::Format("%s - matching eff.", hMatched->GetTitle()); + + if (!mHistMatchingEff) { + HIST* h = dynamic_cast(hMatched->Clone()); + if (!h) { + return; + } + + if (mRebin != 1) { + h->Rebin(mRebin); + } + mHistMatchingEff.reset(h); + mHistMatchingEff->SetStats(0); + mHistMatchingEff->SetNameTitle(name, title); + mHistMatchingEff->SetMarkerSize(0.25); + mHistMatchingEff->SetLineColor(kBlack); + std::string option; + if (mHistMatchingEff->InheritsFrom(TH2::Class())) { + option = "colz"; + } else { + option = "PE"; + } + histograms().emplace_back(HistInfo{ mHistMatchingEff.get(), option, "" }); + publish(objectsManager, histograms().back()); + } + + if (mRebin != 1) { + HIST* h1 = dynamic_cast(hMatched->Clone()); + HIST* h2 = dynamic_cast(hMCH->Clone()); + h1->Rebin(mRebin); + h2->Rebin(mRebin); + mHistMatchingEff->Divide(h1, h2); + delete h1; + delete h2; + } else { + mHistMatchingEff->Divide(hMatched, hMCH); + } + mHistMatchingEff->SetNameTitle(name, title); +} + +//_________________________________________________________________________________________ + +void TracksPostProcessing::createTrackHistos() +{ + for (auto& dataSource : mConfig->dataSources) { + auto pathMatched = dataSource.plotsPath; + auto pathRef = dataSource.refsPath; + auto path = dataSource.outputPath; + mMatchingEfficiencyPlotters.emplace_back(std::make_shared>(dataSource.plotsPath, dataSource.refsPath, dataSource.outputPath, dataSource.name, dataSource.rebin)); + } + + for (auto& plot : mMatchingEfficiencyPlotters) { + plot->publish(getObjectsManager()); + } +} + +//_________________________________________________________________________________________ + +void TracksPostProcessing::removeTrackHistos() +{ + for (auto& plot : mMatchingEfficiencyPlotters) { + plot->unpublish(getObjectsManager()); + } + mMatchingEfficiencyPlotters.clear(); +} + +//_________________________________________________________________________________________ + +void TracksPostProcessing::configure(const boost::property_tree::ptree& config) +{ + mConfig = std::make_unique(getID(), config); +} + +//_________________________________________________________________________________________ + +void TracksPostProcessing::initialize(Trigger, framework::ServiceRegistryRef) +{ + removeTrackHistos(); + createTrackHistos(); +} + +//_________________________________________________________________________________________ + +void TracksPostProcessing::updateTrackHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + for (auto& plot : mMatchingEfficiencyPlotters) { + plot->update(*qcdb, t, getObjectsManager()); + } +} + +//_________________________________________________________________________________________ + +void TracksPostProcessing::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + updateTrackHistos(t, &qcdb); +} + +//_________________________________________________________________________________________ + +void TracksPostProcessing::finalize(Trigger t, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::muon diff --git a/Modules/MUON/Common/src/TracksPostProcessingConfig.cxx b/Modules/MUON/Common/src/TracksPostProcessingConfig.cxx new file mode 100644 index 0000000000..995592a341 --- /dev/null +++ b/Modules/MUON/Common/src/TracksPostProcessingConfig.cxx @@ -0,0 +1,87 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TracksPostProcessingConfig.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief File for the configuration of MCH post-processing tasks +/// \since 05/10/2021 +/// + +#include "MUONCommon/TracksPostProcessingConfig.h" +#include +#include + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::muon +{ + +TracksPostProcessingConfig::TracksPostProcessingConfig(std::string name, const boost::property_tree::ptree& config) + : PostProcessingConfig(name, config) +{ + // parameters + if (const auto& customConfigs = config.get_child_optional("qc.postprocessing." + name + ".customization"); customConfigs.has_value()) { + for (const auto& customConfig : customConfigs.value()) { + if (const auto& customNames = customConfig.second.get_child_optional("name"); customNames.has_value()) { + parameters.insert(std::make_pair(customConfig.second.get("name"), customConfig.second.get("value"))); + } + } + } + + // Data source configuration + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".dataSources")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + auto plotsPath = dataSourceConfig.second.get("plotsPath"); + auto refsPath = dataSourceConfig.second.get("refsPath"); + auto outputPath = dataSourceConfig.second.get("outputPath"); + for (const auto& sourceName : sourceNames.value()) { + int rebin = 1; + std::string nameFull = sourceName.second.data(); + std::string name = nameFull; + auto pos = name.find(":"); + if (pos < std::string::npos) { + name = nameFull.substr(0, pos); + auto rebinStr = nameFull.substr(pos + 1); + rebin = std::stoi(rebinStr); + } + dataSources.push_back({ plotsPath, refsPath, outputPath, name, rebin }); + } + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + name + ".dataSources'"); + } + } +} + +template <> +const std::string TracksPostProcessingConfig::getParameter(std::string name) const +{ + std::string result; + auto entry = parameters.find(name); + if (entry != parameters.end()) { + result = entry->second; + } + + return result; +} + +template <> +const std::string TracksPostProcessingConfig::getParameter(std::string name, std::string defaultValue) const +{ + std::string result = defaultValue; + auto entry = parameters.find(name); + if (entry != parameters.end()) { + result = entry->second; + } + + return result; +} + +} // namespace o2::quality_control_modules::muon diff --git a/Modules/MUON/Common/src/TracksTask.cxx b/Modules/MUON/Common/src/TracksTask.cxx new file mode 100644 index 0000000000..d4788c3d84 --- /dev/null +++ b/Modules/MUON/Common/src/TracksTask.cxx @@ -0,0 +1,353 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "MUONCommon/TracksTask.h" + +#include "MUONCommon/Helpers.h" +#include "QualityControl/ObjectsManager.h" +#include +#include +#include +#include +#include +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace o2::quality_control_modules::muon +{ + +TracksTask::TracksTask() +{ +} + +TracksTask::~TracksTask() = default; + +GID::mask_t adaptSource(GID::mask_t src) +{ + if (src[GID::Source::MFTMCHMID] == 1) { + src.reset(GID::Source::MFTMCHMID); // does not exist + src.set(GID::Source::MFTMCH); + // ensure we request the individual tracks as we use their information in the plotter + src.set(GID::Source::MFT); + src.set(GID::Source::MCH); + src.set(GID::Source::MID); + } + if (src[GID::Source::MFTMCH] == 1) { + // ensure we request the individual tracks as we use their information in the plotter + src.set(GID::Source::MFT); + src.set(GID::Source::MCH); + } + if (src[GID::Source::MCHMID] == 1) { + // ensure we request the individual tracks as we use their information in the plotter + src.set(GID::Source::MCH); + src.set(GID::Source::MID); + } + return src; +} + +void TracksTask::initialize(o2::framework::InitContext& /*ic*/) +{ + ILOG(Debug, Devel) << "initialize TracksTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + ILOG(Info, Support) << "loading sources" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + auto srcFixed = mSrc; + // For track type selection + if (auto param = mCustomParameters.find("GID"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - GID (= sources by user): " << param->second << ENDM; + ILOG(Info, Devel) << "Allowed sources = " << mAllowedSources << " " << GID::getSourcesNames(mAllowedSources) << ENDM; + auto requested = GID::getSourcesMask(param->second); + ILOG(Info, Devel) << "Requested Sources = " << requested << " " << GID::getSourcesNames(requested) << ENDM; + mSrc = mAllowedSources & requested; + srcFixed = adaptSource(mSrc); + ILOG(Info, Devel) << "Allowed requested sources = " << mSrc << " " << GID::getSourcesNames(mSrc) << ENDM; + ILOG(Info, Devel) << "Sources for data request = " << srcFixed << " " << GID::getSourcesNames(srcFixed) << ENDM; + } + + ILOG(Info, Support) << "Will do DataRequest for " << GID::getSourcesNames(srcFixed) << ENDM; + if (srcFixed[GID::Source::MFTMCHMID] == 1) { + srcFixed.reset(GID::Source::MFTMCHMID); + srcFixed.set(GID::Source::MFTMCH); + } + mDataRequest = std::make_shared(); + mDataRequest->requestTracks(srcFixed, false); +} + +void TracksTask::createTrackHistos(const Activity& activity) +{ + bool fullHistos = getConfigurationParameter(mCustomParameters, "fullHistos", false, activity); + + double maxTracksPerTF = getConfigurationParameter(mCustomParameters, "maxTracksPerTF", 400, activity); + double cutRAbsMin = getConfigurationParameter(mCustomParameters, "cutRAbsMin", 17.6, activity); + double cutRAbsMax = getConfigurationParameter(mCustomParameters, "cutRAbsMax", 89.5, activity); + double cutEtaMin = getConfigurationParameter(mCustomParameters, "cutEtaMin", -4.0, activity); + double cutEtaMax = getConfigurationParameter(mCustomParameters, "cutEtaMax", -2.5, activity); + double cutPtMin = getConfigurationParameter(mCustomParameters, "cutPtMin", 0.5, activity); + double cutChi2Min = getConfigurationParameter(mCustomParameters, "cutChi2Min", 0, activity); + double cutChi2Max = getConfigurationParameter(mCustomParameters, "cutChi2Max", 1000, activity); + double nSigmaPDCA = getConfigurationParameter(mCustomParameters, "nSigmaPDCA", 6, activity); + double matchScoreMaxMFT = getConfigurationParameter(mCustomParameters, "matchScoreMaxMFT", 1000, activity); + double matchChi2MaxMFT = getConfigurationParameter(mCustomParameters, "matchChi2MaxMFT", 1000, activity); + double diMuonTimeCut = getConfigurationParameter(mCustomParameters, "diMuonTimeCut", 100, activity) / 1000; + + int etaBins = getConfigurationParameter(mCustomParameters, "etaBins", 200, activity); + int phiBins = getConfigurationParameter(mCustomParameters, "phiBins", 180, activity); + int ptBins = getConfigurationParameter(mCustomParameters, "ptBins", 300, activity); + + //====================================== + // Track plotters without cuts + + auto createPlotter = [&](GID::Source source, std::string path) { + if (mSrc[source] == 1) { + ILOG(Info, Devel) << "Creating plotter for path " << path << ENDM; + mTrackPlotters[source] = std::make_unique(maxTracksPerTF, etaBins, phiBins, ptBins, source, path, fullHistos); + mTrackPlotters[source]->publish(getObjectsManager()); + } + }; + + createPlotter(GID::Source::MCH, ""); + createPlotter(GID::Source::MCHMID, "MCH-MID/"); + createPlotter(GID::Source::MFTMCH, "MFT-MCH/"); + createPlotter(GID::Source::MFTMCHMID, "MFT-MCH-MID/"); + + //====================================== + // Track plotters with cuts + + std::vector muonCuts{ + // Rabs cut + [cutRAbsMin, cutRAbsMax](const MuonTrack& t) { return ((t.getRAbs() >= cutRAbsMin) && (t.getRAbs() <= cutRAbsMax)); }, + // Eta cut + [cutEtaMin, cutEtaMax](const MuonTrack& t) { return ((t.getMuonMomentumAtVertexMCH().eta() >= cutEtaMin) && (t.getMuonMomentumAtVertexMCH().eta() <= cutEtaMax)); }, + // Pt cut + [cutPtMin](const MuonTrack& t) { return ((t.getMuonMomentumAtVertexMCH().Pt() >= cutPtMin)); }, + // pDCA cut + [nSigmaPDCA](const MuonTrack& t) { + static const double sigmaPDCA23 = 80.; + static const double sigmaPDCA310 = 54.; + static const double relPRes = 0.0004; + static const double slopeRes = 0.0005; + + double thetaAbs = TMath::ATan(t.getRAbs() / 505.) * TMath::RadToDeg(); + + double pUncorr = t.getTrackParamMCH().p(); + double p = t.getMuonMomentumAtVertexMCH().P(); + + double pDCA = pUncorr * t.getDCAMCH(); + double sigmaPDCA = (thetaAbs < 3) ? sigmaPDCA23 : sigmaPDCA310; + double nrp = nSigmaPDCA * relPRes * p; + double pResEffect = sigmaPDCA / (1. - nrp / (1. + nrp)); + double slopeResEffect = 535. * slopeRes * p; + double sigmaPDCAWithRes = TMath::Sqrt(pResEffect * pResEffect + slopeResEffect * slopeResEffect); + if (pDCA > nSigmaPDCA * sigmaPDCAWithRes) { + return false; + } + + return true; + }, + // MFT-MCH match score + [matchScoreMaxMFT](const MuonTrack& t) { + if (t.hasMFT() && t.hasMCH() && t.getMatchInfoFwd().getMFTMCHMatchingScore() > matchScoreMaxMFT) + return false; + return true; + }, + // MFT-MCH chi2 score + [matchChi2MaxMFT](const MuonTrack& t) { + if (t.hasMFT() && t.hasMCH() && t.getMatchInfoFwd().getMFTMCHMatchingChi2() > matchChi2MaxMFT) + return false; + return true; + }, + // MCH chi2 cut + [cutChi2Min, cutChi2Max](const MuonTrack& t) { return ((t.getChi2OverNDFMCH() >= cutChi2Min) && (t.getChi2OverNDFMCH() <= cutChi2Max)); } + }; + + std::vector diMuonCuts{ + // cut on time difference between the two muon tracks + [diMuonTimeCut](const MuonTrack& t1, const MuonTrack& t2) { return (std::abs(t1.getTime().getTimeStamp() - t2.getTime().getTimeStamp()) < diMuonTimeCut); } + }; + + auto createPlotterWithCuts = [&](GID::Source source, std::string path) { + if (mSrc[source] == 1) { + ILOG(Info, Devel) << "Creating plotter for path " << path << ENDM; + mTrackPlottersWithCuts[source] = std::make_unique(maxTracksPerTF, etaBins, phiBins, ptBins, source, path, fullHistos); + mTrackPlottersWithCuts[source]->setMuonCuts(muonCuts); + mTrackPlottersWithCuts[source]->setDiMuonCuts(diMuonCuts); + mTrackPlottersWithCuts[source]->publish(getObjectsManager()); + } + }; + + createPlotterWithCuts(GID::Source::MCH, "WithCuts/"); + createPlotterWithCuts(GID::Source::MCHMID, "MCH-MID/WithCuts/"); + createPlotterWithCuts(GID::Source::MFTMCH, "MFT-MCH/WithCuts/"); + createPlotterWithCuts(GID::Source::MFTMCHMID, "MFT-MCH-MID/WithCuts/"); +} + +void TracksTask::removeTrackHistos() +{ + ILOG(Debug, Devel) << "Un-publishing objects" << ENDM; + for (auto& p : mTrackPlotters) { + p.second->unpublish(getObjectsManager()); + } + for (auto& p : mTrackPlottersWithCuts) { + p.second->unpublish(getObjectsManager()); + } + + ILOG(Debug, Devel) << "Destroying objects" << ENDM; + mTrackPlotters.clear(); + mTrackPlottersWithCuts.clear(); +} + +void TracksTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity : " << activity << ENDM; + createTrackHistos(activity); +} + +void TracksTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +bool TracksTask::assertInputs(o2::framework::ProcessingContext& ctx) +{ + if (!ctx.inputs().isValid("trackMCH")) { + ILOG(Info, Support) << "no mch tracks available on input" << ENDM; + return false; + } + if (!ctx.inputs().isValid("trackMCHROF")) { + ILOG(Info, Support) << "no mch track rofs available on input" << ENDM; + return false; + } + if (!ctx.inputs().isValid("trackMCHTRACKCLUSTERS")) { + ILOG(Info, Support) << "no mch track clusters available on input" << ENDM; + return false; + } + if (mSrc[GID::Source::MCHMID] == 1) { + if (!ctx.inputs().isValid("matchMCHMID")) { + ILOG(Info, Support) << "no muon (mch+mid) track available on input" << ENDM; + return false; + } + if (!ctx.inputs().isValid("trackMID")) { + ILOG(Info, Support) << "no mid track available on input" << ENDM; + return false; + } + } + if (mSrc[GID::Source::MFTMCH] == 1) { + if (!ctx.inputs().isValid("fwdtracks")) { + ILOG(Info, Support) << "no muon (mch+mft) track available on input" << ENDM; + return false; + } + } + if (mSrc[GID::Source::MFTMCHMID] == 1) { + if (!ctx.inputs().isValid("fwdtracks")) { + ILOG(Info, Support) << "no muon (mch+mft+mid) track available on input" << ENDM; + return false; + } + } + return true; +} + +void TracksTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + ILOG(Debug, Devel) << "Debug: MonitorData" << ENDM; + + int firstTForbit = ctx.services().get().firstTForbit; + ILOG(Debug, Devel) << "Debug: firstTForbit=" << firstTForbit << ENDM; + + if (!assertInputs(ctx)) { + return; + } + + ILOG(Debug, Devel) << "Debug: Asserted inputs" << ENDM; + + mRecoCont.collectData(ctx, *mDataRequest.get()); + + ILOG(Debug, Devel) << "Debug: Collected data" << ENDM; + + for (auto& p : mTrackPlotters) { + if (p.second) { + p.second->setFirstTForbit(firstTForbit); + } + } + for (auto& p : mTrackPlottersWithCuts) { + if (p.second) { + p.second->setFirstTForbit(firstTForbit); + } + } + + if (mSrc[GID::MCH] == 1) { + ILOG(Debug, Devel) << "Debug: MCH requested" << ENDM; + if (mRecoCont.isTrackSourceLoaded(GID::MCH)) { + ILOG(Debug, Devel) << "Debug: MCH source loaded" << ENDM; + mTrackPlotters[GID::MCH]->fillHistograms(mRecoCont); + mTrackPlottersWithCuts[GID::MCH]->fillHistograms(mRecoCont); + } + } + if (mSrc[GID::MCHMID] == 1) { + ILOG(Debug, Devel) << "Debug: MCHMID requested" << ENDM; + if (mRecoCont.isMatchSourceLoaded(GID::MCHMID)) { + ILOG(Debug, Devel) << "Debug: MCHMID source loaded" << ENDM; + mTrackPlotters[GID::MCHMID]->fillHistograms(mRecoCont); + mTrackPlottersWithCuts[GID::MCHMID]->fillHistograms(mRecoCont); + } + } + if (mSrc[GID::MFTMCH] == 1) { + ILOG(Debug, Devel) << "Debug: MFTMCH requested" << ENDM; + if (mRecoCont.isTrackSourceLoaded(GID::MFTMCH)) { + mTrackPlotters[GID::MFTMCH]->fillHistograms(mRecoCont); + mTrackPlottersWithCuts[GID::MFTMCH]->fillHistograms(mRecoCont); + } + } + if (mSrc[GID::MFTMCHMID] == 1) { + ILOG(Debug, Devel) << "Debug: MFTMCHMID requested" << ENDM; + if (mRecoCont.isTrackSourceLoaded(GID::MFTMCH)) { + mTrackPlotters[GID::MFTMCHMID]->fillHistograms(mRecoCont); + mTrackPlottersWithCuts[GID::MFTMCHMID]->fillHistograms(mRecoCont); + } + } +} + +void TracksTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + for (auto& p : mTrackPlotters) { + p.second->endOfCycle(); + } + for (auto& p : mTrackPlottersWithCuts) { + p.second->endOfCycle(); + } +} + +void TracksTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; + removeTrackHistos(); +} + +void TracksTask::reset() +{ + ILOG(Debug, Devel) << "reset" << ENDM; + for (auto& p : mTrackPlotters) { + p.second->reset(); + } + for (auto& p : mTrackPlottersWithCuts) { + p.second->reset(); + } +} + +} // namespace o2::quality_control_modules::muon diff --git a/Modules/MUON/MCH/CMakeLists.txt b/Modules/MUON/MCH/CMakeLists.txt new file mode 100644 index 0000000000..39f1785ab8 --- /dev/null +++ b/Modules/MUON/MCH/CMakeLists.txt @@ -0,0 +1,196 @@ +set(MODULE_NAME "O2QcMuonChambers") + +# ---- Files ---- + +set(SRCS + src/Helpers.cxx + src/TH2ElecMapReductor.cxx + src/ClusterChargeReductor.cxx + src/ClusterSizeReductor.cxx + src/GlobalHistogram.cxx + src/DecodingErrorsPlotter.cxx + src/HeartBeatPacketsPlotter.cxx + src/FECSyncStatusPlotter.cxx + src/RatesPlotter.cxx + src/RatesTrendsPlotter.cxx + src/OrbitsPlotter.cxx + src/EfficiencyPlotter.cxx + src/EfficiencyTrendsPlotter.cxx + src/ClusterChargePlotter.cxx + src/ClusterChargeTrendsPlotter.cxx + src/ClusterSizePlotter.cxx + src/ClusterSizeTrendsPlotter.cxx + src/PedestalsTask.cxx + src/DigitsTask.cxx + src/RofsTask.cxx + src/PreclustersTask.cxx + src/DecodingTask.cxx + src/ErrorTask.cxx + src/PedestalsCheck.cxx + src/DecodingCheck.cxx + src/DigitsCheck.cxx + src/PreclustersCheck.cxx + src/ClustersCheck.cxx + src/TracksTask.cxx + src/DecodingPostProcessing.cxx + src/DigitsPostProcessing.cxx + src/PreclustersPostProcessing.cxx + src/QualityAggregatorTask.cxx + src/PostProcessingConfigMCH.cxx + src/TrendingTracks.cxx + src/ClustersTask.cxx +) + +set(HEADERS + include/MCH/Helpers.h + include/MCH/HistoOnCycle.h + include/MCH/TH2ElecMapReductor.h + include/MCH/ClusterChargeReductor.h + include/MCH/ClusterSizeReductor.h + include/MCH/GlobalHistogram.h + include/MCH/DecodingErrorsPlotter.h + include/MCH/HeartBeatPacketsPlotter.h + include/MCH/FECSyncStatusPlotter.h + include/MCH/RatesPlotter.h + include/MCH/RatesTrendsPlotter.h + include/MCH/OrbitsPlotter.h + include/MCH/EfficiencyPlotter.h + include/MCH/EfficiencyTrendsPlotter.h + include/MCH/ClusterChargePlotter.h + include/MCH/ClusterChargeTrendsPlotter.h + include/MCH/ClusterSizePlotter.h + include/MCH/ClusterSizeTrendsPlotter.h + include/MCH/PedestalsTask.h + include/MCH/DigitsTask.h + include/MCH/PhysicsTaskDigits.h + include/MCH/RofsTask.h + include/MCH/PreclustersTask.h + include/MCH/PhysicsTaskPreclusters.h + include/MCH/DecodingTask.h + include/MCH/ErrorTask.h + include/MCH/PedestalsCheck.h + include/MCH/DecodingCheck.h + include/MCH/DigitsCheck.h + include/MCH/PreclustersCheck.h + include/MCH/ClustersCheck.h + include/MCH/TracksTask.h + include/MCH/DedodingPostProcessing.h + include/MCH/DigitsPostProcessing.h + include/MCH/PreclustersPostProcessing.h + include/MCH/QualityAggregatorTask.h + include/MCH/PostProcessingConfigMCH.h + include/MCH/TrendingTracks.h + include/MCH/ClustersTask.h + + # legacy tasks + include/MCH/PhysicsTaskDigits.h + include/MCH/PhysicsTaskRofs.h + include/MCH/PhysicsTaskPreclusters.h +) + +# ---- Library ---- + +add_library(${MODULE_NAME} SHARED ${SRCS}) + +target_include_directories( + ${MODULE_NAME} + PUBLIC $ $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +target_link_libraries(${MODULE_NAME} PUBLIC + $ + O2::CommonDataFormat + O2::DataFormatsMCH + O2::GPUCommon + O2::MCHBase + O2::MCHCalibration + O2::MCHConstants + O2::MCHDigitFiltering + O2::MCHGlobalMapping + O2::MCHMappingImpl4 + O2::MCHMappingSegContour + O2::MCHPreClustering + O2::MCHRawDecoder + O2::MCHTracking + O2QcMUONCommon + O2QualityControl +) + +target_compile_definitions(${MODULE_NAME} PRIVATE $<$:MCH_HAS_MAPPING_FACTORY>) + +# Digit.h is moving from MCHBase to DataFormatsMCH : let's handle both +# gracefully for the moment... +get_target_property(O2_INCLUDE_DIRS O2::CommonDataFormat INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(ROOT_INCLUDE_DIRS ROOT::Core INTERFACE_INCLUDE_DIRECTORIES) + +set(CMAKE_REQUIRED_INCLUDES ${O2_INCLUDE_DIRS} ${ROOT_INCLUDE_DIRS}) +check_include_file_cxx("DataFormatsMCH/Digit.h" HAVE_DIGIT_IN_DATAFORMATS) + +if(HAVE_DIGIT_IN_DATAFORMATS) + target_compile_definitions(${MODULE_NAME} PRIVATE HAVE_DIGIT_IN_DATAFORMATS) +endif() + +install( + TARGETS ${MODULE_NAME} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/MCH + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- ROOT dictionary ---- + +add_root_dictionary(${MODULE_NAME} + HEADERS + include/MCH/TH2ElecMapReductor.h + include/MCH/ClusterChargeReductor.h + include/MCH/GlobalHistogram.h + include/MCH/PedestalsTask.h + include/MCH/DigitsTask.h + include/MCH/RofsTask.h + include/MCH/PreclustersTask.h + include/MCH/DigitsCheck.h + include/MCH/PreclustersCheck.h + include/MCH/ClustersCheck.h + include/MCH/DecodingTask.h + include/MCH/DecodingCheck.h + include/MCH/ErrorTask.h + include/MCH/PedestalsCheck.h + include/MCH/TracksTask.h + include/MCH/DecodingPostProcessing.h + include/MCH/DigitsPostProcessing.h + include/MCH/PreclustersPostProcessing.h + include/MCH/QualityAggregatorTask.h + include/MCH/PostProcessingConfigMCH.h + include/MCH/TrendingTracks.h + include/MCH/ClustersTask.h + + # legacy tasks + include/MCH/PhysicsTaskDigits.h + include/MCH/PhysicsTaskRofs.h + include/MCH/PhysicsTaskPreclusters.h + LINKDEF include/MCH/LinkDef.h) + +# ---- Tests ---- + +set( + TEST_SRCS +) + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} PRIVATE ${MODULE_NAME} Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 60) +endforeach() + + +add_executable(o2-qc-mch-clustermap-display src/Clustermap-Display.cxx) +target_link_libraries(o2-qc-mch-clustermap-display PRIVATE O2QualityControl O2::MCHMappingSegContour O2::MCHMappingImpl4 O2::MCHMappingInterface O2::MCHContour O2::MCHGeometryCreator O2::MCHGeometryTransformer O2::MCHConstants O2::MCHGlobalMapping) +install(TARGETS o2-qc-mch-clustermap-display RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/Modules/MUON/MCH/etc/qc-errors.json b/Modules/MUON/MCH/etc/qc-errors.json new file mode 100644 index 0000000000..adbd563e81 --- /dev/null +++ b/Modules/MUON/MCH/etc/qc-errors.json @@ -0,0 +1,31 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "http://ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "MCHErrors": { + "active": "true", + "taskName": "Errors", + "className": "o2::quality_control_modules::muonchambers::ErrorTask", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "cycleDurationSeconds": "600", + "dataSource": { + "type": "direct", + "query": "errors:MCH/PROCERRORS/0" + } + } + } + } +} \ No newline at end of file diff --git a/Modules/MUON/MCH/etc/qc-mch-clusters.json b/Modules/MUON/MCH/etc/qc-mch-clusters.json new file mode 100644 index 0000000000..dc21f6e032 --- /dev/null +++ b/Modules/MUON/MCH/etc/qc-mch-clusters.json @@ -0,0 +1,81 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "http://ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "Clusters": { + "active": "true", + "className": "o2::quality_control_modules::muonchambers::ClustersTask", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "cycleDurationSeconds": "300", + "dataSource": { + "type": "direct", + "query": "tracks:MCH/TRACKS;trackrofs:MCH/TRACKROFS;trackclusters:MCH/TRACKCLUSTERS" + }, + "taskParameters": { + }, + "grpGeomRequest": { + "geomRequest": "Aligned", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location": "remote" + } + }, + "checks": { + "ClustersCheck": { + "active": "true", + "className": "o2::quality_control_modules::muonchambers::ClustersCheck", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "policy": "OnAll", + "extendedCheckParameters": { + "default": { + "default": { + "minClustersPerTrack": "9.5", + "maxClustersPerTrack": "11.5", + "minClustersPerChamber": "0.8", + "clustersPerChamberRangeMin": "0.6", + "clustersPerChamberRangeMax": "1.2", + "minClusterSize": "6", + "clusterSizeRangeMin": "0", + "clusterSizeRangeMax": "10", + "markerSize": "0.8" + } + } + }, + "dataSource": [ + { + "type": "Task", + "name": "MCHClusters", + "MOs" : "all" + "MOs" : [ + "ClustersPerTrack", + "ClustersPerChamber", + "ClusterSizePerChamber" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [] +} + diff --git a/Modules/MUON/MCH/etc/qc-mch-decoding.json b/Modules/MUON/MCH/etc/qc-mch-decoding.json new file mode 100644 index 0000000000..b4a8b7254a --- /dev/null +++ b/Modules/MUON/MCH/etc/qc-mch-decoding.json @@ -0,0 +1,98 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "http://ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable", + "maxObjectSize": "12582912" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "MCHDecoding": { + "active": "true", + "taskName": "Decoding", + "className": "o2::quality_control_modules::muonchambers::DecodingTask", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "cycleDurationSeconds": "300", + "dataSource": { + "type": "direct", + "query": "rawerrors:MCH/ERRORS;hbpackets:MCH/HBPACKETS" + }, + "taskParameters" : { + "HBExpectedBc" : "456190" + }, + "location": "remote" + } + }, + "postprocessing": { + "Decoding": { + "active": "true", + "className": "o2::quality_control_modules::muonchambers::DecodingPostProcessing", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "customization": [ + { + "name": "FullHistos", + "value": "0" + }, + { + "name": "ReferenceTimeStamp", + "value": "1669672094149" + } + ], + "dataSources": [ + { + "type": "repository", + "path": "MCH/MO/Decoding", + "names": [ + "errors:DecodingErrorsFEC", "hbpackets:HBCoarseTimeFEC", "syncstatus:SyncStatusFEC" + ], + "reductorName": "o2::quality_control_modules::muonchambers::TH2ElecMapReductor", + "moduleName": "QcMuonChambers" + } + ], + "plots": [ + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "300sec" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks": { + "DecodingCheck": { + "active": "true", + "className": "o2::quality_control_modules::muonchambers::DecodingCheck", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "policy": "OnAny", + "checkParameters": { + "GoodFracHistName": "DecodingErrors/LastCycle/GoodBoardsFractionPerDE", + "SyncFracHistName": "SyncErrors/LastCycle/SyncedBoardsFractionPerDE" + "MinGoodErrorFrac": "0.9", + "MinGoodSyncFrac": "0.9", + "MaxBadDE_ST12": "2", + "MaxBadDE_ST345": "5" + }, + "dataSource": [{ + "type": "PostProcessing", + "name": "Decoding", + "MOs" : "all" + }] + } + } + } +} diff --git a/Modules/MUON/MCH/etc/qc-mch-digits.json b/Modules/MUON/MCH/etc/qc-mch-digits.json new file mode 100644 index 0000000000..e660008c97 --- /dev/null +++ b/Modules/MUON/MCH/etc/qc-mch-digits.json @@ -0,0 +1,115 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "http://ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable", + "maxObjectSize": "12582912" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "MCHDigits": { + "active": "true", + "taskName": "Digits", + "className": "o2::quality_control_modules::muonchambers::DigitsTask", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "cycleDurationSeconds": "300", + "dataSource": { + "type": "direct", + "query": "digits:MCH/DIGITS" + }, + "taskParameters" : { + "Diagnostic" : "false" + }, + "location": "remote" + } + }, + "postprocessing": { + "Digits": { + "active": "true", + "className": "o2::quality_control_modules::muonchambers::DigitsPostProcessing", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "customization": [ + { + "name": "FullHistos", + "value": "0" + }, + { + "name": "ChannelRateMin", + "value": "0.00001" + }, + { + "name": "ChannelRateMax", + "value": "10" + }, + { + "name": "ReferenceTimeStamp", + "value": "1669672094149" + } + ], + "dataSources": [ + { + "type": "repository", + "path": "MCH/MO/Digits", + "names": [ + "rate:Occupancy_Elec", "rate_signal:OccupancySignal_Elec", "orbits:DigitOrbitInTF", "orbits_signal:DigitSignalOrbitInTF" + ], + "reductorName": "o2::quality_control_modules::muonchambers::TH2ElecMapReductor", + "moduleName": "QcMuonChambers" + } + ], + "plots": [ + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "300sec" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks": { + "DigitsCheck": { + "active": "true", + "className": "o2::quality_control_modules::muonchambers::DigitsCheck", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "policy": "OnAll", + "checkParameters": { + "MeanRateHistName": "RatesSignal/LastCycle/MeanRate", + "MeanRateRatioHistName": "RatesSignal/LastCycle/MeanRateRefRatio", + "GoodChanFracHistName": "RatesSignal/LastCycle/GoodChannelsFraction", + "GoodChanFracRatioHistName": "RatesSignal/LastCycle/GoodChannelsFractionRefRatio", + "MinRate": "0.01", + "MaxRate": "1", + "MaxRateDelta": "0.1", + "MinGoodFraction": "0.9", + "MaxGoodFractionDelta": "0.15", + "RatePlotScaleMin": "0.0001", + "RatePlotScaleMax": "10", + "MaxBadDE_ST12": "2", + "MaxBadDE_ST345": "5" + }, + "dataSource": [ + { + "type": "PostProcessing", + "name": "Digits", + "MOs" : "all" + } + ] + } + } + } +} diff --git a/Modules/MUON/MCH/etc/qc-mch-preclusters.json b/Modules/MUON/MCH/etc/qc-mch-preclusters.json new file mode 100644 index 0000000000..fe5cd03008 --- /dev/null +++ b/Modules/MUON/MCH/etc/qc-mch-preclusters.json @@ -0,0 +1,98 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "http://ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "MCHPreclusters": { + "active": "true", + "taskName": "Preclusters", + "className": "o2::quality_control_modules::muonchambers::PreclustersTask", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "cycleDurationSeconds": "300", + "dataSource": { + "type": "direct", + "query": "preclusters:MCH/PRECLUSTERS/0;preclusterdigits:MCH/PRECLUSTERDIGITS/0" + }, + "location": "remote" + } + }, + "postprocessing": { + "Preclusters": { + "active": "true", + "className": "o2::quality_control_modules::muonchambers::PreclustersPostProcessing", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "customization": [ + { + "name": "FullHistos", + "value": "0" + }, + { + "name": "ReferenceTimeStamp", + "value": "1669672094149" + } + ], + "dataSources": [ + { + "type": "repository", + "path": "MCH/MO/Preclusters", + "names": [ + "eff:Pseudoeff_Elec", "clcharge:ClusterChargeHist", "clsize:ClusterSizeHist" + ], + "reductorName": "o2::quality_control_modules::muonchambers::TH2ElecMapReductor", + "moduleName": "QcMuonChambers" + } + ], + "plots": [ + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "300sec" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks": { + "PreclustersCheck": { + "active": "true", + "className": "o2::quality_control_modules::muonchambers::PreclustersCheck", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "policy": "OnAny", + "checkParameters": { + "MeanEffHistNameB": "Efficiency/LastCycle/MeanEfficiencyB", + "MeanEffHistNameNB": "Efficiency/LastCycle/MeanEfficiencyNB", + "MeanEffRatioHistNameB": "Efficiency/LastCycle/MeanEfficiencyRefRatioB", + "MeanEffRatioHistNameNB": "Efficiency/LastCycle/MeanEfficiencyRefRatioNB", + "MinEfficiency": "0.8", + "MaxEfficiencyDelta": "0.1", + "PseudoeffPlotScaleMin": "0.0", + "PseudoeffPlotScaleMax": "1.2", + "MaxBadDE_ST12": "2", + "MaxBadDE_ST345": "5" + }, + "dataSource": [{ + "type": "PostProcessing", + "name": "Preclusters", + "MOs" : "all" + }] + } + } + } +} diff --git a/Modules/MUON/MCH/etc/qc-tracks.json b/Modules/MUON/MCH/etc/qc-tracks.json new file mode 100644 index 0000000000..ecf8cf8467 --- /dev/null +++ b/Modules/MUON/MCH/etc/qc-tracks.json @@ -0,0 +1,47 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "http://ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "Tracks": { + "active": "true", + "className": "o2::quality_control_modules::muonchambers::TracksTask", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "cycleDurationSeconds": "300", + "dataSource": { + "type": "direct", + "query": "tracks:MCH/TRACKS;trackrofs:MCH/TRACKROFS;trackclusters:MCH/TRACKCLUSTERS" + }, + "taskParameters": { + "maxTracksPerTF": "10" + }, + "grpGeomRequest": { + "geomRequest": "Aligned", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location": "remote" + } + }, + "checks": {} + }, + "dataSamplingPolicies": [] +} + diff --git a/Modules/MUON/MCH/etc/qc-trending-tracks.json b/Modules/MUON/MCH/etc/qc-trending-tracks.json new file mode 100644 index 0000000000..b68835cb79 --- /dev/null +++ b/Modules/MUON/MCH/etc/qc-trending-tracks.json @@ -0,0 +1,61 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", "": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "postprocessing": { + "MCHTrendingTracks": { + "active": "true", + "className": "o2::quality_control_modules::muonchambers::TrendingTracks", + "moduleName": "QcMuonChambers", + "detectorName": "MCH", + "dataSources": [ + { + "type": "repository", + "path": "MCH/MO/MCHTracks", + "names": [ + "TracksPerTF", "ClustersPerTrack" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "TracksNum", + "title": "Number of tracks per TF", + "varexp": "TracksPerTF.mean:time", + "selection": "", + "option": "*L" + }, + { + "name": "ClustersPerTrack", + "title": "Number of clusters per track", + "varexp": "ClustersPerTrack.mean:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "2min" + ], + "stopTrigger": [ + "endofrun" + ] + } + } + } +} diff --git a/Modules/MUON/MCH/include/MCH/ClusterChargePlotter.h b/Modules/MUON/MCH/include/MCH/ClusterChargePlotter.h new file mode 100644 index 0000000000..7085197aef --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/ClusterChargePlotter.h @@ -0,0 +1,72 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterChargePlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_CHARGEPLOTTER_H +#define QC_MODULE_CHARGEPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCH/ClusterChargeReductor.h" +#include "MCH/Helpers.h" +#include +#include +#include + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class ClusterChargePlotter : public HistPlotter +{ + public: + ClusterChargePlotter(std::string path, bool fullPlots = false); + + void update(TH2F* hCharge); + + private: + void addHisto(TH1* h, bool statBox, const char* drawOptions, const char* displayHints) + { + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h, drawOptions, displayHints }); + } + + void addCanvas(TCanvas* c, TH1* h, bool statBox, const char* drawOptions, const char* displayHints) + { + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ c, "", displayHints }); + } + + std::unique_ptr mChargeReductor; + + std::unique_ptr mHistogramChargePerDE; + std::array, getNumDE()> mHistogramCharge; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_CHARGEPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/ClusterChargeReductor.h b/Modules/MUON/MCH/include/MCH/ClusterChargeReductor.h new file mode 100644 index 0000000000..387e3758b3 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/ClusterChargeReductor.h @@ -0,0 +1,221 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterChargeReductor.h +/// \author Andrea Ferrero +/// +#ifndef QUALITYCONTROL_CLUSTERCHARGEREDUCTOR_H +#define QUALITYCONTROL_CLUSTERCHARGEREDUCTOR_H + +#include "MCH/Helpers.h" +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief A Reductor which obtains the most popular characteristics of TH1. +/// +/// A Reductor which obtains the most popular characteristics of TH1. + +class ClusterChargeReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + ClusterChargeReductor(); + ~ClusterChargeReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + Double_t getDeValue(int deid); + + private: + static constexpr int sDeNum{ getNumDE() }; + + struct { + union { + struct { + // CH 01 + Double_t val100; + Double_t val101; + Double_t val102; + Double_t val103; + // CH 02 + Double_t val200; + Double_t val201; + Double_t val202; + Double_t val203; + // CH 03 + Double_t val300; + Double_t val301; + Double_t val302; + Double_t val303; + // CH 04 + Double_t val400; + Double_t val401; + Double_t val402; + Double_t val403; + // CH 05 + Double_t val501; + Double_t val502; + Double_t val503; + Double_t val504; + Double_t val505; + Double_t val506; + Double_t val507; + Double_t val508; + Double_t val509; + Double_t val510; + Double_t val511; + Double_t val512; + Double_t val513; + Double_t val514; + Double_t val515; + Double_t val516; + Double_t val517; + // CH 05 + Double_t val600; + Double_t val601; + Double_t val602; + Double_t val603; + Double_t val604; + Double_t val605; + Double_t val606; + Double_t val607; + Double_t val608; + Double_t val609; + Double_t val610; + Double_t val611; + Double_t val612; + Double_t val613; + Double_t val614; + Double_t val615; + Double_t val616; + Double_t val617; + // CH 05 + Double_t val700; + Double_t val701; + Double_t val702; + Double_t val703; + Double_t val704; + Double_t val705; + Double_t val706; + Double_t val707; + Double_t val708; + Double_t val709; + Double_t val710; + Double_t val711; + Double_t val712; + Double_t val713; + Double_t val714; + Double_t val715; + Double_t val716; + Double_t val717; + Double_t val718; + Double_t val719; + Double_t val720; + Double_t val721; + Double_t val722; + Double_t val723; + Double_t val724; + Double_t val725; + // CH 05 + Double_t val800; + Double_t val801; + Double_t val802; + Double_t val803; + Double_t val804; + Double_t val805; + Double_t val806; + Double_t val807; + Double_t val808; + Double_t val809; + Double_t val810; + Double_t val811; + Double_t val812; + Double_t val813; + Double_t val814; + Double_t val815; + Double_t val816; + Double_t val817; + Double_t val818; + Double_t val819; + Double_t val820; + Double_t val821; + Double_t val822; + Double_t val823; + Double_t val824; + Double_t val825; + // CH 05 + Double_t val900; + Double_t val901; + Double_t val902; + Double_t val903; + Double_t val904; + Double_t val905; + Double_t val906; + Double_t val907; + Double_t val908; + Double_t val909; + Double_t val910; + Double_t val911; + Double_t val912; + Double_t val913; + Double_t val914; + Double_t val915; + Double_t val916; + Double_t val917; + Double_t val918; + Double_t val919; + Double_t val920; + Double_t val921; + Double_t val922; + Double_t val923; + Double_t val924; + Double_t val925; + // CH 05 + Double_t val1000; + Double_t val1001; + Double_t val1002; + Double_t val1003; + Double_t val1004; + Double_t val1005; + Double_t val1006; + Double_t val1007; + Double_t val1008; + Double_t val1009; + Double_t val1010; + Double_t val1011; + Double_t val1012; + Double_t val1013; + Double_t val1014; + Double_t val1015; + Double_t val1016; + Double_t val1017; + Double_t val1018; + Double_t val1019; + Double_t val1020; + Double_t val1021; + Double_t val1022; + Double_t val1023; + Double_t val1024; + Double_t val1025; + } named; + Double_t values[sDeNum]; + } deValues; + Double_t entries; + } mStats; +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QUALITYCONTROL_CLUSTERCHARGEREDUCTOR_H diff --git a/Modules/MUON/MCH/include/MCH/ClusterChargeTrendsPlotter.h b/Modules/MUON/MCH/include/MCH/ClusterChargeTrendsPlotter.h new file mode 100644 index 0000000000..b9b5f96be3 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/ClusterChargeTrendsPlotter.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterChargeTrendsPlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_CLUSTERCHARGETRENDSPLOTTER_H +#define QC_MODULE_CLUSTERCHARGETRENDSPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCH/Helpers.h" +#include "MCH/ClusterChargeReductor.h" + +class TH2F; + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class ClusterChargeTrendsPlotter : public HistPlotter +{ + public: + ClusterChargeTrendsPlotter(std::string path, bool fullPlots = false); + + void update(long time, TH2F* hEfficiency); + + private: + void addCanvas(TCanvas* c, const char* displayHints) + { + histograms().emplace_back(HistInfo{ c, "", displayHints }); + } + + // Data reductor + std::unique_ptr mReductor; + // Trend plots + std::array, getNumDE()> mTrends; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_CLUSTERCHARGETRENDSPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/ClusterSizePlotter.h b/Modules/MUON/MCH/include/MCH/ClusterSizePlotter.h new file mode 100644 index 0000000000..e39e761aeb --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/ClusterSizePlotter.h @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterSizePlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_CLUSTERSIZEPLOTTER_H +#define QC_MODULE_CLUSTERSIZEPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCH/ClusterSizeReductor.h" +#include "MCH/Helpers.h" +#include +#include +#include +#include + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class ClusterSizePlotter : public HistPlotter +{ + public: + ClusterSizePlotter(std::string path, bool fullPlots = false); + + void update(TH2F* hCharge); + + private: + void addHisto(TH1* h, bool statBox, const char* drawOptions, const char* displayHints); + void addCanvas(TCanvas* c, TH1* h, bool statBox, const char* drawOptions, const char* displayHints); + + void fillHistograms(TH2F* h); + + std::string mPath; + + std::unique_ptr mClusterSizeReductor; + + std::array, 3> mHistogramClusterSizePerDE; + std::array, getNumDE() * 3> mHistogramClusterSize; + std::array, getNumDE()> mLegendClusterSize; + std::array, getNumDE()> mCanvasClusterSize; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_CLUSTERSIZEPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/ClusterSizeReductor.h b/Modules/MUON/MCH/include/MCH/ClusterSizeReductor.h new file mode 100644 index 0000000000..07fbfa0336 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/ClusterSizeReductor.h @@ -0,0 +1,224 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterSizeReductor.h +/// \author Andrea Ferrero +/// +#ifndef QUALITYCONTROL_CLUSTERSIZEREDUCTOR_H +#define QUALITYCONTROL_CLUSTERSIZEREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" +#include "MCH/Helpers.h" +#include "MCHRawCommon/DataFormats.h" +#include "MCHRawElecMap/Mapper.h" +#include + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief A Reductor which obtains the most popular characteristics of TH1. +/// +/// A Reductor which obtains the most popular characteristics of TH1. + +class ClusterSizeReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + ClusterSizeReductor(); + ~ClusterSizeReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + Double_t getDeValue(int deid, int cathode = 2); + + private: + static constexpr int sDeNum{ getNumDE() }; + + struct { + union { + struct { + // CH 01 + Double_t val100; + Double_t val101; + Double_t val102; + Double_t val103; + // CH 02 + Double_t val200; + Double_t val201; + Double_t val202; + Double_t val203; + // CH 03 + Double_t val300; + Double_t val301; + Double_t val302; + Double_t val303; + // CH 04 + Double_t val400; + Double_t val401; + Double_t val402; + Double_t val403; + // CH 05 + Double_t val501; + Double_t val502; + Double_t val503; + Double_t val504; + Double_t val505; + Double_t val506; + Double_t val507; + Double_t val508; + Double_t val509; + Double_t val510; + Double_t val511; + Double_t val512; + Double_t val513; + Double_t val514; + Double_t val515; + Double_t val516; + Double_t val517; + // CH 05 + Double_t val600; + Double_t val601; + Double_t val602; + Double_t val603; + Double_t val604; + Double_t val605; + Double_t val606; + Double_t val607; + Double_t val608; + Double_t val609; + Double_t val610; + Double_t val611; + Double_t val612; + Double_t val613; + Double_t val614; + Double_t val615; + Double_t val616; + Double_t val617; + // CH 05 + Double_t val700; + Double_t val701; + Double_t val702; + Double_t val703; + Double_t val704; + Double_t val705; + Double_t val706; + Double_t val707; + Double_t val708; + Double_t val709; + Double_t val710; + Double_t val711; + Double_t val712; + Double_t val713; + Double_t val714; + Double_t val715; + Double_t val716; + Double_t val717; + Double_t val718; + Double_t val719; + Double_t val720; + Double_t val721; + Double_t val722; + Double_t val723; + Double_t val724; + Double_t val725; + // CH 05 + Double_t val800; + Double_t val801; + Double_t val802; + Double_t val803; + Double_t val804; + Double_t val805; + Double_t val806; + Double_t val807; + Double_t val808; + Double_t val809; + Double_t val810; + Double_t val811; + Double_t val812; + Double_t val813; + Double_t val814; + Double_t val815; + Double_t val816; + Double_t val817; + Double_t val818; + Double_t val819; + Double_t val820; + Double_t val821; + Double_t val822; + Double_t val823; + Double_t val824; + Double_t val825; + // CH 05 + Double_t val900; + Double_t val901; + Double_t val902; + Double_t val903; + Double_t val904; + Double_t val905; + Double_t val906; + Double_t val907; + Double_t val908; + Double_t val909; + Double_t val910; + Double_t val911; + Double_t val912; + Double_t val913; + Double_t val914; + Double_t val915; + Double_t val916; + Double_t val917; + Double_t val918; + Double_t val919; + Double_t val920; + Double_t val921; + Double_t val922; + Double_t val923; + Double_t val924; + Double_t val925; + // CH 05 + Double_t val1000; + Double_t val1001; + Double_t val1002; + Double_t val1003; + Double_t val1004; + Double_t val1005; + Double_t val1006; + Double_t val1007; + Double_t val1008; + Double_t val1009; + Double_t val1010; + Double_t val1011; + Double_t val1012; + Double_t val1013; + Double_t val1014; + Double_t val1015; + Double_t val1016; + Double_t val1017; + Double_t val1018; + Double_t val1019; + Double_t val1020; + Double_t val1021; + Double_t val1022; + Double_t val1023; + Double_t val1024; + Double_t val1025; + } named; + Double_t values[sDeNum]; + } deValues[3]; + Double_t entries; + } mStats; +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QUALITYCONTROL_CLUSTERSIZEREDUCTOR_H diff --git a/Modules/MUON/MCH/include/MCH/ClusterSizeTrendsPlotter.h b/Modules/MUON/MCH/include/MCH/ClusterSizeTrendsPlotter.h new file mode 100644 index 0000000000..a12591afc7 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/ClusterSizeTrendsPlotter.h @@ -0,0 +1,59 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterSizeTrendsPlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_CLUSTERSIZETRENDSPLOTTER_H +#define QC_MODULE_CLUSTERSIZETRENDSPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCH/Helpers.h" +#include "MCH/ClusterSizeReductor.h" +#include + +class TH2F; + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class ClusterSizeTrendsPlotter : public HistPlotter +{ + public: + ClusterSizeTrendsPlotter(std::string path, bool fullPlots = false); + + void update(long time, TH2F* hEfficiency); + + private: + void addCanvas(TCanvas* c, const char* displayHints) + { + histograms().emplace_back(HistInfo{ c, "", displayHints }); + } + + // Data reductor + std::unique_ptr mReductor; + // Trend plots + std::array, getNumDE()> mTrends; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_CLUSTERSIZETRENDSPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/ClustersCheck.h b/Modules/MUON/MCH/include/MCH/ClustersCheck.h new file mode 100644 index 0000000000..045a7764c2 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/ClustersCheck.h @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClustersCheck.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_MCH_CLUSTERSCHECK_H +#define QC_MODULE_MCH_CLUSTERSCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "QualityControl/Quality.h" +#include + +namespace o2::quality_control::core +{ +class MonitorObject; +} + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief Check if the clusters sizes and tracks-clusters association is within expected limits +/// +/// \author Andrea Ferrero +class ClustersCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + ClustersCheck() = default; + /// Destructor + ~ClustersCheck() override = default; + + // Override interface + void configure() override; + void startOfActivity(const Activity& activity) override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + /// quality flag associated to each input plot + std::unordered_map mQualities; + /// acceptable limits for the average number of clusters associated tracks + double mMinClustersPerTrack{ 9 }; + double mMaxClustersPerTrack{ 11 }; + /// minimum acceptale number of clusters per track in each chamber + double mMinClustersPerChamber{ 0.8 }; + /// vertical range of the cluster per chamber plot + double mClustersPerChamberRangeMin{ 0.6 }; + double mClustersPerChamberRangeMax{ 1.2 }; + /// minimum acceptable value for the average cluster size per chamber + double mMinClusterSize{ 4 }; + /// vertical range of the cluster size per chamber plot + double mClusterSizeRangeMin{ 0 }; + double mClusterSizeRangeMax{ 10 }; + /// size of the marker showing the average number of clusters + float mMarkerSize{ 1.2 }; + + ClassDefOverride(ClustersCheck, 1); +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_CLUSTERSCHECK_H diff --git a/Modules/MUON/MCH/include/MCH/ClustersTask.h b/Modules/MUON/MCH/include/MCH/ClustersTask.h new file mode 100644 index 0000000000..93d28f8c98 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/ClustersTask.h @@ -0,0 +1,77 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_MCH_CLUSTERS_TASK_H +#define QC_MODULE_MCH_CLUSTERS_TASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include "MCHGeometryTransformer/Transformations.h" +#include "MUONCommon/HistPlotter.h" +#include +#include + +namespace o2::mch +{ +class Cluster; +} // namespace o2::mch + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::muonchambers +{ + +class ClustersTask /*final*/ : public TaskInterface +{ + public: + ClustersTask(); + ~ClustersTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + /** check whether all the expected inputs are present.*/ + bool assertInputs(o2::framework::ProcessingContext& ctx); + + /** create histograms related to clusters (those attached to tracks) */ + void createClusterHistos(); + + /** fill histogram related to each cluster */ + void fillClusterHistos(gsl::span clusters); + + private: + int dsbinx(int deid, int dsid) const; + + std::unique_ptr mNofClustersPerDualSampa; //< aka cluster map + std::unique_ptr mNofClustersPerTrack; ///< number of clusters per track + std::unique_ptr mClusterSizePerChamber; ///< mean cluster size per chamber + std::unique_ptr mNofClustersPerChamber; ///< mean number of clusters per chamber + ///< distribution of the cluster size in each station separately + std::array, 5> mClusterSizeDistributionPerStation; + + o2::mch::raw::Det2ElecMapper mDet2ElecMapper; + o2::mch::raw::Solar2FeeLinkMapper mSolar2FeeLinkMapper; + std::unique_ptr mTransformation; + + muon::HistPlotter mHistPlotter; +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif diff --git a/Modules/MUON/MCH/include/MCH/DecodingCheck.h b/Modules/MUON/MCH/include/MCH/DecodingCheck.h new file mode 100644 index 0000000000..9a08348295 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/DecodingCheck.h @@ -0,0 +1,87 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DecodingCheck.h +/// \author Andrea Ferrero, Sebastien Perrin +/// + +#ifndef QC_MODULE_MCH_DECODINGCHECK_H +#define QC_MODULE_MCH_DECODINGCHECK_H + +#include "MCH/Helpers.h" +#include "QualityControl/CheckInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "MCHRawCommon/DataFormats.h" +#include "MCHRawElecMap/Mapper.h" +#include + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief Check if the occupancy on each pad is between the two specified values +/// +/// \author Andrea Ferrero, Sebastien Perrin +class DecodingCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + DecodingCheck() = default; + /// Destructor + ~DecodingCheck() override = default; + + // Override interface + void configure() override; + void startOfActivity(const Activity& activity) override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + std::string mGoodFracHistName{ "DecodingErrors/GoodBoardsFractionPerDE" }; + std::string mGoodFracPerSolarHistName{ "DecodingErrors/GoodBoardsFractionPerSolar" }; + std::string mSyncFracHistName{ "SyncErrors/SyncedBoardsFractionPerDE" }; + std::string mSyncFracPerSolarHistName{ "SyncErrors/SyncedBoardsFractionPerSolar" }; + std::string mGoodFracRefCompHistName{ "DecodingErrors/RefComp/GoodBoardsFractionPerDE" }; + std::string mSyncFracRefCompHistName{ "SyncErrors/RefComp/SyncedBoardsFractionPerDE" }; + std::string mGoodFracPerSolarRefCompHistName{ "DecodingErrors/RefComp/GoodBoardsFractionPerSolar" }; + std::string mSyncFracPerSolarRefCompHistName{ "SyncErrors/RefComp/SyncedBoardsFractionPerSolar" }; + int mMaxBadST12{ 2 }; + int mMaxBadST345{ 3 }; + double mMinGoodErrorFrac{ 0.9 }; + std::array, 5> mMinGoodErrorFracPerStation; + double mMinGoodErrorFracRatio{ 0.9 }; + double mMinGoodErrorFracPerSolar{ 0.5 }; + double mMinGoodErrorFracRatioPerSolar{ 0.9 }; + + double mMinGoodSyncFrac{ 0.9 }; + std::array, 5> mMinGoodSyncFracPerStation; + double mMinGoodSyncFracRatio{ 0.9 }; + double mMinGoodSyncFracPerSolar{ 0.5 }; + double mMinGoodSyncFracRatioPerSolar{ 0.9 }; + + double mMinHeartBeatRate{ 0 }; + double mMaxHeartBeatRate{ 2 }; + + double mGoodFracRatioPlotRange{ 0.2 }; + double mGoodFracRatioPerSolarPlotRange{ 0.2 }; + double mSyncFracRatioPlotRange{ 0.2 }; + double mSyncFracRatioPerSolarPlotRange{ 0.2 }; + + QualityChecker mQualityChecker; + std::array mSolarQuality; + + ClassDefOverride(DecodingCheck, 2); +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_DECODINGCHECK_H diff --git a/Modules/MUON/MCH/include/MCH/DecodingErrorsPlotter.h b/Modules/MUON/MCH/include/MCH/DecodingErrorsPlotter.h new file mode 100644 index 0000000000..e1cc0d1f09 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/DecodingErrorsPlotter.h @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DecodingErrorsPlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_DECODINGERRORSPLOTTER_H +#define QC_MODULE_DECODINGERRORSPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCHRawElecMap/Mapper.h" +#include +#include + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class DecodingErrorsPlotter : public HistPlotter +{ + public: + DecodingErrorsPlotter(std::string path); + + void update(TH2F* h2); + + private: + void addHisto(TH1* h, bool statBox, const char* drawOptions, const char* displayHints) + { + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h, drawOptions, displayHints }); + } + + o2::mch::raw::Det2ElecMapper mDet2ElecMapper; + o2::mch::raw::Solar2FeeLinkMapper mSolar2FeeLinkMapper; + + std::string mPath; + + std::unique_ptr mHistogramErrorsPerDE; ///< error codes per DE + std::unique_ptr mHistogramErrorsPerChamber; ///< error codes per chamber + std::unique_ptr mHistogramErrorsPerFeeId; ///< error codes per FEE ID + + std::unique_ptr mHistogramGoodBoardsPerDE; ///< fraction of boards without errors per DE + std::unique_ptr mHistogramGoodBoardsPerSolar; ///< fraction of boards with errors per DE +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_DECODINGERRORSPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/DecodingPostProcessing.h b/Modules/MUON/MCH/include/MCH/DecodingPostProcessing.h new file mode 100644 index 0000000000..b9df5538e0 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/DecodingPostProcessing.h @@ -0,0 +1,126 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DecodingPostProcessing.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Trending of the MCH tracking +/// \since 21/06/2022 +/// + +#ifndef QC_MODULE_MCH_PP_DIAGNOSTICS_H +#define QC_MODULE_MCH_PP_DIAGNOSTICS_H + +#include "MCH/PostProcessingConfigMCH.h" +#include "MCH/Helpers.h" +#include "MCH/HistoOnCycle.h" +#include "MCH/DecodingErrorsPlotter.h" +#include "MCH/HeartBeatPacketsPlotter.h" +#include "MCH/FECSyncStatusPlotter.h" +#include "Common/ReferenceComparatorTask.h" +#include "Common/TH2Ratio.h" + +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief A post-processing task which trends MCH hits and stores them in a TTree and produces plots. +class DecodingPostProcessing : public ReferenceComparatorTask +{ + public: + DecodingPostProcessing() = default; + ~DecodingPostProcessing() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + /** create one histogram with relevant drawing options / stat box status.*/ + template + void publishHisto(T* h, bool statBox = false, + const char* drawOptions = "", + const char* displayHints = ""); + + void createDecodingErrorsHistos(Trigger t, repository::DatabaseInterface* qcdb); + void createHeartBeatPacketsHistos(Trigger t, repository::DatabaseInterface* qcdb); + void createSyncStatusHistos(Trigger t, repository::DatabaseInterface* qcdb); + + void updateDecodingErrorsHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateHeartBeatPacketsHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateSyncStatusHistos(Trigger t, repository::DatabaseInterface* qcdb); + + static std::string errorsSourceName() { return "errors"; } + static std::string hbPacketsSourceName() { return "hbpackets"; } + static std::string syncStatusSourceName() { return "syncstatus"; } + + TH1* getHistogram(std::string_view plotName); + + bool mFullHistos{ false }; + bool mEnableLastCycleHistos{ false }; + bool mEnableTrending{ false }; + + PostProcessingConfigMCH mConfig; + + // CCDB object accessors + std::map mCcdbObjects; + + // Decoding errors histograms =============================================== + + // On-cycle plots generators + std::unique_ptr> mErrorsOnCycle; + std::unique_ptr> mHBPacketsOnCycle; + std::unique_ptr> mSyncStatusOnCycle; + // Plotters and histograms + std::unique_ptr mErrorsPlotter; + std::unique_ptr mErrorsPlotterOnCycle; + std::unique_ptr mHBPacketsPlotter; + std::unique_ptr mHBPacketsPlotterOnCycle; + std::unique_ptr mSyncStatusPlotter; + std::unique_ptr mSyncStatusPlotterOnCycle; + + std::unique_ptr mHistogramQualityPerDE; ///< quality flags for each DE, to be filled by checker task + std::unique_ptr mHistogramQualityPerSolar; ///< quality flags for each SOLAR, to be filled by checker task + + std::vector mHistogramsAll; +}; + +template +void DecodingPostProcessing::publishHisto(T* h, bool statBox, + const char* drawOptions, + const char* displayHints) +{ + if (!statBox) { + h->SetStats(0); + } + getObjectsManager()->startPublishing(h); + if (drawOptions) { + getObjectsManager()->setDefaultDrawOptions(h, drawOptions); + } + if (displayHints) { + getObjectsManager()->setDisplayHint(h, displayHints); + } +} + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_PP_DIGITS_H diff --git a/Modules/MUON/MCH/include/MCH/DecodingTask.h b/Modules/MUON/MCH/include/MCH/DecodingTask.h new file mode 100644 index 0000000000..33ead304a6 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/DecodingTask.h @@ -0,0 +1,116 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DecodingTask.h +/// \author Andrea Ferrero +/// \author Sebastien Perrin +/// + +#ifndef QC_MODULE_MCH_DECODINGTASK_H +#define QC_MODULE_MCH_DECODINGTASK_H + +#include "Common/TH2Ratio.h" +#include "MCH/Helpers.h" +#include +#include +#include "MCHGlobalMapping/DsIndex.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/TaskInterface.h" +#include "QualityControl/CheckInterface.h" +#include "QualityControl/MonitorObject.h" + +using namespace o2::quality_control_modules::common; + +class TH1I; + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief Quality Control Task for the analysis of raw data decoding errors +/// \author Andrea Ferrero +/// \author Sebastien Perrin +class DecodingTask /*final*/ : public TaskInterface +{ + public: + /// \brief Constructor + DecodingTask() = default; + /// Destructor + ~DecodingTask() override = default; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + /// \brief functions that instatiate and publish the plots + void createErrorHistos(); + void createHeartBeatHistos(); + /// \brief decoding of the TimeFrame data sent from the (sub)TimeFrame builder + void decodeTF(framework::ProcessingContext& pc); + /// \brief decoding of the TimeFrame data sent directly from readout + void decodeReadout(const o2::framework::DataRef& input); + /// \brief helper function to process the TimeFrame data + void decodeBuffer(gsl::span buf); + /// \brief helper function to process one CRU page + void decodePage(gsl::span page); + /// \brief helper function to process the array of errors sent via DPL + void processErrors(framework::ProcessingContext& pc); + /// \brief helper function for updating the error histograms + void plotError(uint16_t solarId, int dsAddr, int chip, uint32_t error); + /// \brief helper function to process the heart-beat packets sent via DPL + void processHBPackets(framework::ProcessingContext& pc); + /// \brief helper function for updating the heart-beat packets histograms + void plotHBPacket(uint16_t solarId, int dsAddr, int chip, uint32_t bc); + /// \brief checker function for the heart-beat packets histograms + void updateSyncErrors(); + + /// \brief helper function for pubishing and formatting a given plot + template + void publishObject(T* histo, std::string drawOption, std::string displayHints, bool statBox, bool isExpert); + + /// \brief electronics mapping functions + o2::mch::raw::Elec2DetMapper mElec2DetMapper{ nullptr }; + + /// \brief raw data decoder + o2::mch::raw::PageDecoder mDecoder; + + /// \brief expected bunch-crossing value in heart-beat packets + int mHBExpectedBc{ 456190 }; + + struct HBCount { + HBCount() = default; + uint16_t nSync{ 0 }; + uint16_t nOutOfSync{ 0 }; + }; + std::array mHBcount; + + /// \brief number of processed time-frames + std::unique_ptr mHistogramTimeFramesCount; + + /// \brief decoding error plots + std::unique_ptr mHistogramErrorsFEC; ///< error codes per FEC + + /// \brief time synchronization diagnostics plots + std::unique_ptr mHistogramHBTimeFEC; ///< bunch-crossing from HB packets vs. FEC id + std::unique_ptr mHistogramHBCoarseTimeFEC; ///< bunch-crossing from HB packets vs. FEC id, coarse scale + std::unique_ptr mSyncStatusFEC; ///< time synchronization status of each DS board (OK, out-of-synch, missing good HB) + + std::vector mAllHistograms; +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_DECODINGTASK_H diff --git a/Modules/MUON/MCH/include/MCH/DigitsCheck.h b/Modules/MUON/MCH/include/MCH/DigitsCheck.h new file mode 100644 index 0000000000..fe0a48b296 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/DigitsCheck.h @@ -0,0 +1,108 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsCheck.h +/// \author Andrea Ferrero, Sebastien Perrin +/// + +#ifndef QC_MODULE_MCH_DIGITSCHECK_H +#define QC_MODULE_MCH_DIGITSCHECK_H + +#include "MCH/Helpers.h" +#include "QualityControl/CheckInterface.h" +#include "QualityControl/Quality.h" +#include +#include + +namespace o2::quality_control::core +{ +class MonitorObject; +} + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief Check if the occupancy on each pad is between the two specified values +/// +/// \author Andrea Ferrero, Sebastien Perrin +class DigitsCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + DigitsCheck() = default; + /// Destructor + ~DigitsCheck() override = default; + + // Override interface + void configure() override; + void startOfActivity(const Activity& activity) override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + std::array checkMeanRates(TH1* h); + std::array checkBadChannels(TH1* h); + std::array checkMeanRateRatios(TH1* h); + std::array checkBadChannelRatios(TH1* h); + void checkSolarMeanRates(TH1* h); + void checkSolarBadChannels(TH1* h); + void checkSolarMeanRateRatios(TH1* h); + void checkSolarBadChannelRatios(TH1* h); + + std::string mMeanRateHistName{ "RatesSignal/MeanRate" }; + std::string mGoodChanFracHistName{ "RatesSignal/GoodChannelsFraction" }; + std::string mMeanRatePerSolarHistName{ "RatesSignal/MeanRatePerSolar" }; + std::string mGoodChanFracPerSolarHistName{ "RatesSignal/GoodChannelsFractionPerSolar" }; + std::string mMeanRateRefCompHistName{ "RatesSignal/RefComp/MeanRate" }; + std::string mGoodChanFracRefCompHistName{ "RatesSignal/RefComp/GoodChannelsFraction" }; + std::string mMeanRatePerSolarRefCompHistName{ "RatesSignal/RefComp/MeanRatePerSolar" }; + std::string mGoodChanFracPerSolarRefCompHistName{ "RatesSignal/RefComp/GoodChannelsFractionPerSolar" }; + int mMaxBadST12{ 2 }; + int mMaxBadST345{ 3 }; + + // Rate lower threshold + double mMinRate{ 0.001 }; + std::array, 5> mMinRatePerStation; + double mMinRatePerSolar{ 0.001 }; + // Rate upper threshold + double mMaxRate{ 10 }; + std::array, 5> mMaxRatePerStation; + double mMaxRatePerSolar{ 10 }; + // Rate ratio threshold + double mMinRateRatio{ 0.9 }; + double mMinRateRatioPerSolar{ 0.9 }; + + // Good channels fraction threshold + double mMinGoodFraction{ 0.9 }; + std::array, 5> mMinGoodFractionPerStation; + double mMinGoodFractionPerSolar{ 0.5 }; + // Good channels ratio threshold + double mMinGoodFractionRatio{ 0.9 }; + double mMinGoodFractionRatioPerSolar{ 0.9 }; + + // Vertical plot ranges + double mRatePlotScaleMin{ 0 }; + double mRatePlotScaleMax{ 10 }; + double mRateRatioPlotScaleRange{ 0.2 }; + double mRateRatioPerSolarPlotScaleRange{ 0.2 }; + double mGoodFractionRatioPlotScaleRange{ 0.2 }; + double mGoodFractionRatioPerSolarPlotScaleRange{ 0.2 }; + + QualityChecker mQualityChecker; + std::array mSolarQuality; + + ClassDefOverride(DigitsCheck, 3); +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_DIGITSCHECK_H diff --git a/Modules/MUON/MCH/include/MCH/DigitsPostProcessing.h b/Modules/MUON/MCH/include/MCH/DigitsPostProcessing.h new file mode 100644 index 0000000000..d53a7ff1ab --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/DigitsPostProcessing.h @@ -0,0 +1,113 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsPostProcessing.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Post-processing of the MCH digits +/// \since 21/06/2022 +/// + +#ifndef QC_MODULE_MCH_PP_DIGITS_H +#define QC_MODULE_MCH_PP_DIGITS_H + +#include "MCH/PostProcessingConfigMCH.h" +#include "MCH/HistoOnCycle.h" +#include "MCH/RatesPlotter.h" +#include "MCH/RatesTrendsPlotter.h" +#include "MCH/OrbitsPlotter.h" +#include "Common/ReferenceComparatorTask.h" +#include "Common/TH2Ratio.h" +#include "QualityControl/PostProcessingInterface.h" + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief A post-processing task which processes and trends MCH digits and produces plots. +class DigitsPostProcessing : public ReferenceComparatorTask +{ + public: + DigitsPostProcessing() = default; + ~DigitsPostProcessing() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + void createRatesHistos(Trigger t, repository::DatabaseInterface* qcdb); + void createOrbitHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateRateHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateOrbitHistos(Trigger t, repository::DatabaseInterface* qcdb); + + static std::string rateSourceName() { return "rate"; } + static std::string rateSignalSourceName() { return "rate_signal"; } + static std::string orbitsSourceName() { return "orbits"; } + static std::string orbitsSignalSourceName() { return "orbits_signal"; } + + TH1* getHistogram(std::string_view plotName); + + bool mFullHistos{ false }; + bool mEnableLastCycleHistos{ false }; + bool mEnableTrending{ false }; + + float mChannelRateMin{ 0 }; + float mChannelRateMax{ 100 }; + + PostProcessingConfigMCH mConfig; + + // CCDB object accessors + std::map mCcdbObjects; + + // Hit rate histograms =============================================== + + // On-cycle plots generators + std::unique_ptr> mElecMapOnCycle; + std::unique_ptr> mElecMapSignalOnCycle; + // Plotters + std::unique_ptr mRatesPlotter; + std::unique_ptr mRatesPlotterOnCycle; + std::unique_ptr mRatesPlotterSignal; + std::unique_ptr mRatesPlotterSignalOnCycle; + // Trends + std::unique_ptr mRatesTrendsPlotter; + std::unique_ptr mRatesTrendsPlotterSignal; + + // Time histograms ============================================== + + // On-cycle plots generators + std::unique_ptr> mDigitsOrbitsOnCycle; + std::unique_ptr> mDigitsSignalOrbitsOnCycle; + // Plotters + std::unique_ptr mOrbitsPlotter; + std::unique_ptr mOrbitsPlotterOnCycle; + std::unique_ptr mOrbitsPlotterSignal; + std::unique_ptr mOrbitsPlotterSignalOnCycle; + + std::unique_ptr mHistogramQualityPerDE; ///< quality flags for each DE, to be filled by checker task + std::unique_ptr mHistogramQualityPerSolar; ///< quality flags for each SOLAR, to be filled by checker task + + std::vector mHistogramsAll; +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_PP_DIGITS_H diff --git a/Modules/MUON/MCH/include/MCH/DigitsTask.h b/Modules/MUON/MCH/include/MCH/DigitsTask.h new file mode 100644 index 0000000000..61deb84c6e --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/DigitsTask.h @@ -0,0 +1,103 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsTask.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_MUONCHAMBERS_DIGITSTASK_H +#define QC_MODULE_MUONCHAMBERS_DIGITSTASK_H + +#include "QualityControl/TaskInterface.h" +#ifdef HAVE_DIGIT_IN_DATAFORMATS +#include "DataFormatsMCH/Digit.h" +#else +#include "MCHBase/Digit.h" +#endif +#include "MCHDigitFiltering/DigitFilter.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" + +class TH1F; +class TH2F; + +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::common; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +/// \brief Quality Control Task for the analysis of MCH physics data +/// \author Andrea Ferrero +/// \author Sebastien Perrin +class DigitsTask /*final*/ : public TaskInterface // todo add back the "final" when doxygen is fixed +{ + public: + /// \brief Constructor + DigitsTask() = default; + /// Destructor + ~DigitsTask() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void plotDigit(const o2::mch::Digit& digit); + void updateOrbits(); + void resetOrbits(); + + template + void publishObject(T* histo, std::string drawOption, bool statBox, bool isExpert); + + bool mEnable1DRateMaps{ true }; // whether to publish 1D maps of channel rates + bool mEnable2DRateMaps{ false }; // whether to publish 2D maps of channel rates + + bool mFullHistos{ false }; // publish extra diagnostics plots + + o2::mch::DigitFilter mIsSignalDigit; + + uint32_t mNOrbits{ 0 }; + + // 2D Histograms, using Elec view (where x and y uniquely identify each pad based on its Elec info (fee, link, de) + std::unique_ptr mHistogramOccupancyElec; // Occupancy histogram (Elec view) + std::unique_ptr mHistogramSignalOccupancyElec; // Occupancy histogram (Elec view) for signal-like digits + + // 1D rate histograms using Elec view, where each x bin corresponds to the unique ID of a DualSAMPA board + std::unique_ptr mHistogramRatePerDualSampa; + std::unique_ptr mHistogramRateSignalPerDualSampa; + + std::unique_ptr mHistogramDigitsOrbitElec; + std::unique_ptr mHistogramDigitsSignalOrbitElec; + + std::unique_ptr mHistogramDigitsBcInOrbit; + std::unique_ptr mHistogramAmplitudeVsSamples; + + std::map> mHistogramADCamplitudeDE; // Histogram of ADC distribution per DE + + std::vector mAllHistograms; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_MUONCHAMBERS_DIGITSTASK_H diff --git a/Modules/MUON/MCH/include/MCH/EfficiencyPlotter.h b/Modules/MUON/MCH/include/MCH/EfficiencyPlotter.h new file mode 100644 index 0000000000..1e72c8f3b6 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/EfficiencyPlotter.h @@ -0,0 +1,82 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file EfficiencyPlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_EFFICIENCYPLOTTER_H +#define QC_MODULE_EFFICIENCYPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCH/GlobalHistogram.h" +#include "MCH/TH2ElecMapReductor.h" +#include "MCHRawElecMap/Mapper.h" +#include + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class EfficiencyPlotter : public HistPlotter +{ + public: + EfficiencyPlotter(std::string path, bool fullPlots = false); + + void update(TH2F* hEfficiency); + + private: + void addHisto(TH1* h, bool statBox, const char* drawOptions, const char* displayHints) + { + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h, drawOptions, displayHints }); + } + + void addCanvas(TCanvas* c, TH1* h, bool statBox, const char* drawOptions, const char* displayHints) + { + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ c, "", displayHints }); + } + + void fillAverageHistograms(); + void fillGlobalHistograms(TH2F* h); + + o2::mch::raw::Elec2DetMapper mElec2DetMapper; + o2::mch::raw::Det2ElecMapper mDet2ElecMapper; + o2::mch::raw::FeeLink2SolarMapper mFeeLink2SolarMapper; + o2::mch::raw::Solar2FeeLinkMapper mSolar2FeeLinkMapper; + + std::unique_ptr mElecMapReductor; + + std::array, 2> mHistogramMeanEfficiencyPerDE; + std::unique_ptr mHistogramMeanEfficiencyPerSolar; + + std::array>, 2> mHistogramEfficiencyDE; // 2D hit rate map for each DE + std::array, 2> mHistogramEfficiencyGlobal; // Efficiency histogram (global XY view) +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_EFFICIENCYPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/EfficiencyTrendsPlotter.h b/Modules/MUON/MCH/include/MCH/EfficiencyTrendsPlotter.h new file mode 100644 index 0000000000..997021aeff --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/EfficiencyTrendsPlotter.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Helpers.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_EFFICIENCYTRENDSPLOTTER_H +#define QC_MODULE_EFFICIENCYTRENDSPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCH/Helpers.h" +#include "MCH/TH2ElecMapReductor.h" + +class TH2F; + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class EfficiencyTrendsPlotter : public HistPlotter +{ + public: + EfficiencyTrendsPlotter(std::string path, bool fullPlots = false); + + void update(long time, TH2F* hEfficiency); + + private: + void addCanvas(TCanvas* c, const char* displayHints) + { + histograms().emplace_back(HistInfo{ c, "", displayHints }); + } + + // Data reductor + std::unique_ptr mElecMapReductor; + // Trend plots + std::array, getNumDE()> mTrendsEfficiency; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_EFFICIENCYTRENDSPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/ErrorTask.h b/Modules/MUON/MCH/include/MCH/ErrorTask.h new file mode 100644 index 0000000000..c26c43ae49 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/ErrorTask.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ErrorTask.h +/// \author Philippe Pillot +/// + +#ifndef QC_MODULE_MUONCHAMBERS_ERRORTASK_H +#define QC_MODULE_MUONCHAMBERS_ERRORTASK_H + +#include +#include + +#include "QualityControl/TaskInterface.h" + +class TProfile; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief Quality Control Task for the analysis of MCH processing errors +/// \author Philippe Pillot +class ErrorTask final : public TaskInterface +{ + public: + /// \brief Constructor + ErrorTask() = default; + /// Destructor + ~ErrorTask() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + auto createProfile(const char* name, const char* title, int nbins, double xmin, double xmax); + + std::unique_ptr mSummary{}; + std::unique_ptr mMultipleDigitsInSamePad{}; + std::unique_ptr mTooManyLocalMaxima{}; +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MUONCHAMBERS_ERRORTASK_H diff --git a/Modules/MUON/MCH/include/MCH/FECSyncStatusPlotter.h b/Modules/MUON/MCH/include/MCH/FECSyncStatusPlotter.h new file mode 100644 index 0000000000..39afd9e7ae --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/FECSyncStatusPlotter.h @@ -0,0 +1,63 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file FECSyncStatusPlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_SYNCSTATUSPLOTTER_H +#define QC_MODULE_SYNCSTATUSPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCH/GlobalHistogram.h" +#include "MCHRawElecMap/Mapper.h" +#include +#include + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class FECSyncStatusPlotter : public HistPlotter +{ + public: + FECSyncStatusPlotter(std::string path); + + void update(TH2F* h2); + + private: + void addHisto(TH1* h, bool statBox, const char* drawOptions, const char* displayHints) + { + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h, drawOptions, displayHints }); + } + + o2::mch::raw::Det2ElecMapper mDet2ElecMapper; + + std::unique_ptr mGoodBoardsFractionPerDE; ///< fraction of out-of-sync DS boards per detection element + std::unique_ptr mGoodTFFractionPerDE; ///< fraction of in-sync TFs per DS boards, averaged on detection elements + std::unique_ptr mGoodTFFractionPerSolar; ///< fraction of in-sync TFs per DS boards, averaged on SOLAR boards +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_SYNCSTATUSPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/GlobalHistogram.h b/Modules/MUON/MCH/include/MCH/GlobalHistogram.h new file mode 100644 index 0000000000..2835e70b95 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/GlobalHistogram.h @@ -0,0 +1,109 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GlobalHistogram.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_MUONCHAMBERS_GLOBALHISTOGRAM_H +#define QC_MODULE_MUONCHAMBERS_GLOBALHISTOGRAM_H + +#include +#include + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class DetectorHistogram +{ + public: + DetectorHistogram(TString name, TString title, int deId, int cathode); + DetectorHistogram(TString name, TString title, int deId, int cathode, TH2F* hist); + ~DetectorHistogram(); + + void Fill(double padX, double padY, double padSizeX, double padSizeY, double val = 1); + void Set(double padX, double padY, double padSizeX, double padSizeY, double val); + + int getNbinsX(); + int getNbinsY(); + float getXmin(); + float getXmax(); + float getYmin(); + float getYmax(); + + TH2F* getHist() { return mHist.first; } + + private: + void init(); + void addContour(); + + int mDeId{ 0 }; + int mCathode{ 0 }; + + TString mName; + TString mTitle; + std::pair mHist; + + bool mFlipX{ false }; + bool mFlipY{ false }; + float mShiftX{ 0 }; + float mShiftY{ 0 }; + + float mHistWidth{ 0 }; + float mHistHeight{ 0 }; +}; + +class GlobalHistogram +{ + public: + GlobalHistogram(std::string name, std::string title, int id, float rescale); + GlobalHistogram(std::string name, std::string title, int id, float rescale, TH2F* hist); + ~GlobalHistogram(); + + void init(); + + // add the histograms of the individual detection elements + void add(std::map>& histB, std::map>& histNB); + + // replace the contents with the histograms of the individual detection elements, including null bins + void set_includeNull(std::map>& histB, std::map>& histNB); + + // replace the contents with the histograms of the individual detection elements + void set(std::map>& histB, std::map>& histNB, bool doAverage = true, bool includeNullBins = false); + + TH2F* getHist() { return mHist.first; } + + private: + void initST345(); + void initST12(); + void getDeCenter(int de, float& xB0, float& yB0, float& xNB0, float& yNB0); + void getDeCenterST12(int de, float& xB0, float& yB0, float& xNB0, float& yNB0); + void getDeCenterST3(int de, float& xB0, float& yB0, float& xNB0, float& yNB0); + void getDeCenterST4(int de, float& xB0, float& yB0, float& xNB0, float& yNB0); + void getDeCenterST5(int de, float& xB0, float& yB0, float& xNB0, float& yNB0); + + TString mName; + TString mTitle; + int mId; + float mScaleFactor; + std::pair mHist; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_MUONCHAMBERS_GLOBALHISTOGRAM_H diff --git a/Modules/MUON/MCH/include/MCH/HeartBeatPacketsPlotter.h b/Modules/MUON/MCH/include/MCH/HeartBeatPacketsPlotter.h new file mode 100644 index 0000000000..325feecede --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/HeartBeatPacketsPlotter.h @@ -0,0 +1,63 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HeartBeatPacketsPlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_HBPACKETSPLOTTER_H +#define QC_MODULE_HBPACKETSPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCH/GlobalHistogram.h" +#include "MCHRawElecMap/Mapper.h" +#include +#include + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class HeartBeatPacketsPlotter : public HistPlotter +{ + public: + HeartBeatPacketsPlotter(std::string path, bool fullPlots = false); + + void update(TH2F* h2); + + private: + void addHisto(TH1* h, bool statBox, const char* drawOptions, const char* displayHints) + { + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h, drawOptions, displayHints }); + } + + o2::mch::raw::Elec2DetMapper mElec2DetMapper; + o2::mch::raw::FeeLink2SolarMapper mFeeLink2SolarMapper; + + std::map> mHistogramHBRateDE[2]; // 2D hit rate map for each DE + std::shared_ptr mHistogramHBRateGlobal[2]; // Rate histogram (global XY view) +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_HBPACKETSPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/Helpers.h b/Modules/MUON/MCH/include/MCH/Helpers.h new file mode 100644 index 0000000000..95cfb26802 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/Helpers.h @@ -0,0 +1,228 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Helpers.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_MUONCHAMBERS_HELPERS_H +#define QC_MODULE_MUONCHAMBERS_HELPERS_H + +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/Quality.h" +#include "QualityControl/CustomParameters.h" +#include "MCHConstants/DetectionElements.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace o2::quality_control::core +{ +class Activity; +} + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +int getDEindex(int de); +constexpr int getNumDE() { return (4 * 4 + 18 * 2 + 26 * 4); } +int getNumDEinChamber(int chIndex); +std::pair getDEindexInChamber(int deId); +int getDEFromIndex(int index); + +int getSolarIndex(int solarId); +int getSolarIdFromIndex(int index); +constexpr int getNumSolar() { return 624; } +int getNumSolarPerChamber(int chamberId); + +void getThresholdsPerStation(o2::quality_control::core::CustomParameters customParameters, + const o2::quality_control::core::Activity& activity, + std::string parKey, + std::array, 5>& thresholds, + double& defaultThreshold); + +o2::quality_control::core::Quality checkDetectorQuality(gsl::span& deQuality); + +void addChamberLabelsForDE(TH1* h); +void addChamberDelimiters(TH1* h, float xmin = 0, float xmax = 0); +void addChamberDelimiters(TH2* h); +void addChamberLabelsForSolar(TH1* h); +void addChamberDelimitersToSolarHistogram(TH1* h, float xmin = 0, float xmax = 0); +void addChamberDelimitersToSolarHistogram(TH2* h); +void drawThreshold(TH1* histogram, double threshold); +void drawThresholdsPerStation(TH1* histogram, const std::array, 5>& thresholdsPerStation, double defaultThreshold); +void addDEBinLabels(TH1* histogram); +void addSolarBinLabels(TH1* histogram); + +std::string getHistoPath(int deId); +bool matchHistName(std::string hist, std::string name); + +void splitDataSourceName(std::string s, std::string& type, std::string& name); + +// check the overall spectrometer quality based on the individual detectors +struct QualityChecker { + QualityChecker(); + + void reset(); + void addCheckResult(gsl::span quality); + o2::quality_control::core::Quality checkST12(int i); + o2::quality_control::core::Quality checkST345(int i); + o2::quality_control::core::Quality getQuality(); + + int mMaxBadST12{ 2 }; + int mMaxBadST345{ 5 }; + + std::array mChamberMap; + std::array, getNumDE()> mDeMap; + std::array mQuality; +}; + +// build a unique FEC ID from a FEE/LINK/DSADDR triple +struct FecId { + FecId(int feeId, int linkId, int dsAddr) + { + mFecId = feeId * sLinkNum * sDsNum + linkId * sDsNum + dsAddr; + } + + FecId(int fecId) : mFecId(fecId) {} + + int getFeeId() { return (mFecId / (sLinkNum * sDsNum)); } + int getLinkId() { return ((mFecId % (sLinkNum * sDsNum)) / sDsNum); } + int getDsAddr() { return (mFecId % sDsNum); } + + static constexpr int max() { return ((sFeeNum * sLinkNum * sDsNum) - 1); } + + static constexpr int sFeeNum = 64; + static constexpr int sLinkNum = 12; + static constexpr int sDsNum = 40; + + int mFecId{ 0 }; +}; + +//_________________________________________________________________________________________ + +struct CcdbObjectHelper { + CcdbObjectHelper(); + CcdbObjectHelper(std::string p, std::string n); + + bool update(o2::quality_control::repository::DatabaseInterface* qcdb, + long timeStamp = -1, + const o2::quality_control::core::Activity& activity = {}); + void setStartIme(); + long getTimeStamp() { return mTimeStamp; } + template + T* get() + { + if (!mObject) { + return nullptr; + } + if (!mObject->getObject()) { + return nullptr; + } + + // Get histogram object + T* h = dynamic_cast(mObject->getObject()); + return h; + } + + std::shared_ptr mObject; + std::string mPath; + std::string mName; + uint64_t mTimeStart{ 0 }; + uint64_t mTimeStamp{ 0 }; +}; + +//_________________________________________________________________________________________ + +class TrendGraph : public TCanvas +{ + public: + TrendGraph(std::string name, std::string title, std::string label, std::optional refValue = {}); + + void update(uint64_t time, float val); + + private: + std::optional mRefValue; + std::string mAxisLabel; + std::unique_ptr mGraph; + std::unique_ptr mGraphRef; + std::unique_ptr mGraphHist; + std::array, 2> mLegends; +}; + +//_________________________________________________________________________________________ + +class QualityTrendGraph : public TCanvas +{ + public: + QualityTrendGraph(std::string name, std::string title); + + void update(uint64_t time, o2::quality_control::core::Quality q); + + private: + std::unique_ptr mGraph; + std::unique_ptr mGraphHist; + std::array, 4> mLabels; +}; + +//_________________________________________________________________________________________ + +class TrendMultiGraph : public TCanvas +{ + public: + TrendMultiGraph(std::string name, std::string title, std::string label); + + void addGraph(std::string name, std::string title, std::optional refValue = {}); + void addLegends(); + void update(long time, gsl::span values); + + void setRange(float min, float max) + { + mYmin = min; + mYmax = max; + } + + private: + std::string mAxisLabel; + + float mYmin{ 0 }; + float mYmax{ 0 }; + + int mNGraphs{ 0 }; + std::unique_ptr mGraphHist; + std::array, 10> mRefValues; + std::array, 10> mGraphs; + std::array, 10> mGraphsRef; + std::array, 5> mLegends; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_MUONCHAMBERS_HELPERS_H diff --git a/Modules/MUON/MCH/include/MCH/HistoOnCycle.h b/Modules/MUON/MCH/include/MCH/HistoOnCycle.h new file mode 100644 index 0000000000..4281f97763 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/HistoOnCycle.h @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HistoOnCycle.h +/// \author Andrea Ferrero +/// +#ifndef QUALITYCONTROL_MCH_HISTOONCYCLE_H +#define QUALITYCONTROL_MCH_HISTOONCYCLE_H + +#include +#include + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief An utility class that generates histograms with data from the last processing cycle +/// +/// An utility class that generates histograms with data from the last processing cycle + +template +class HistoOnCycle : public T +{ + public: + HistoOnCycle() = default; + ~HistoOnCycle() = default; + + void update(TObject* obj); + + private: + std::unique_ptr mHistPrevCycle; +}; + +template +void HistoOnCycle::update(TObject* obj) +{ + auto histo = dynamic_cast(obj); + if (!histo) { + return; + } + + // if not already done, initialize the internal plot that stores the data from the previous cycle + if (!mHistPrevCycle) { + std::string name = std::string(T::GetName()) + "PrevCycle"; + std::string title = std::string(T::GetTitle()) + " - PrevCycle"; + Bool_t bStatus = TH1::AddDirectoryStatus(); + TH1::AddDirectory(kFALSE); + mHistPrevCycle = std::make_unique(); + histo->Copy(*mHistPrevCycle); + mHistPrevCycle->SetNameTitle(name.c_str(), title.c_str()); + mHistPrevCycle->Reset("ICES"); + TH1::AddDirectory(bStatus); + } + + histo->Copy(*this); + T::SetNameTitle(TString::Format("%sOnCycle", histo->GetName()), TString::Format("%s - OnCycle", histo->GetTitle())); + T::Add(mHistPrevCycle.get(), -1); + + mHistPrevCycle->Reset("ICES"); + mHistPrevCycle->Add(histo); +} + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QUALITYCONTROL_MCH_HISTOONCYCLE_H diff --git a/Modules/MUON/MCH/include/MCH/LinkDef.h b/Modules/MUON/MCH/include/MCH/LinkDef.h new file mode 100644 index 0000000000..d23c1a2b2d --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/LinkDef.h @@ -0,0 +1,35 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::muonchambers::TH2ElecMapReductor + ; +// tasks +#pragma link C++ class o2::quality_control_modules::muonchambers::PedestalsTask + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::DigitsTask + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::PreclustersTask + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::RofsTask + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::DecodingTask + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::TracksTask + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::ErrorTask + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::ClustersTask + ; +// Post-processing +#pragma link C++ class o2::quality_control_modules::muonchambers::DecodingPostProcessing + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::DigitsPostProcessing + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::PreclustersPostProcessing + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::QualityAggregatorTask + ; +// Trending +#pragma link C++ class o2::quality_control_modules::muonchambers::TrendingTracks + ; +// Checks +#pragma link C++ class o2::quality_control_modules::muonchambers::PedestalsCheck + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::DecodingCheck + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::DigitsCheck + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::PreclustersCheck + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::ClustersCheck + ; +// Aggregators +// legacy tasks +#pragma link C++ class o2::quality_control_modules::muonchambers::PhysicsTaskDigits + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::PhysicsTaskRofs + ; +#pragma link C++ class o2::quality_control_modules::muonchambers::PhysicsTaskPreclusters + ; + +#endif diff --git a/Modules/MUON/MCH/include/MCH/OrbitsPlotter.h b/Modules/MUON/MCH/include/MCH/OrbitsPlotter.h new file mode 100644 index 0000000000..0d561cc46c --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/OrbitsPlotter.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file OrbitsPlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_ORBITSPLOTTER_H +#define QC_MODULE_ORBITSPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCHRawElecMap/Mapper.h" +#include + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class OrbitsPlotter : public HistPlotter +{ + public: + OrbitsPlotter(std::string path); + + void update(TH2F* h); + + private: + void addHisto(TH1* h, bool statBox, const char* drawOptions, const char* displayHints) + { + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h, drawOptions, displayHints }); + } + + o2::mch::raw::Elec2DetMapper mElec2DetMapper; + o2::mch::raw::FeeLink2SolarMapper mFeeLink2SolarMapper; + + std::unique_ptr mHistogramOrbits; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_ORBITSPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/PedestalsCheck.h b/Modules/MUON/MCH/include/MCH/PedestalsCheck.h new file mode 100644 index 0000000000..5a7ea8fc70 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/PedestalsCheck.h @@ -0,0 +1,71 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalsCheck.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_MCH_PEDESTALSCHECK_H +#define QC_MODULE_MCH_PEDESTALSCHECK_H + +#include "MCH/Helpers.h" +#include "QualityControl/CheckInterface.h" +#include "QualityControl/Quality.h" + +namespace o2::quality_control_modules::muonchambers +{ + +class PedestalsCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + PedestalsCheck() = default; + /// Destructor + ~PedestalsCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + /// Maximum number of bad detection elements for "good" quality status + int mMaxBadST12{ 1 }; + int mMaxBadST345{ 3 }; + /// Maximum fraction of bad channels in one DE for "good" quality status + float mMaxBadFractionPerDE{ 0.1 }; + /// Maximum fraction of empty channels in one DE for "good" quality status + float mMaxEmptyFractionPerDE{ 0.1 }; + /// Minimum statistics per DE for "good" quality status + float mMinStatisticsPerDE{ 1000 }; + /// Minimum value of the z-axis range for the pedestals plots + double mPedestalsPlotScaleMin{ 40 }; + /// Maximum value of the z-axis range for the pedestals plots + double mPedestalsPlotScaleMax{ 250 }; + /// Minimum value of the z-axis range for the noise plots + double mNoisePlotScaleMin{ 0 }; + /// Maximum value of the z-axis range for the noise plots + double mNoisePlotScaleMax{ 1.5 }; + + Quality mQualityBadChannels; + Quality mQualityEmptyChannels; + Quality mQualityStatistics; + std::vector mErrorMessages; + + QualityChecker mQualityChecker; + + ClassDefOverride(PedestalsCheck, 4); +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_TOF_TOFCHECKRAWSTIME_H diff --git a/Modules/MUON/MCH/include/MCH/PedestalsTask.h b/Modules/MUON/MCH/include/MCH/PedestalsTask.h new file mode 100644 index 0000000000..be964f9d80 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/PedestalsTask.h @@ -0,0 +1,129 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalsTask.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_MUONCHAMBERS_PEDESTALSTASK_H +#define QC_MODULE_MUONCHAMBERS_PEDESTALSTASK_H + +#include "QualityControl/TaskInterface.h" +#include "MCHRawElecMap/Mapper.h" +#include "MCH/GlobalHistogram.h" +#include "Framework/DataRef.h" +#include "MCHCalibration/PedestalData.h" +#include "MCHRawCommon/DataFormats.h" +#include +#include +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief Quality Control Task for the analysis of MCH pedestal data +/// \author Andrea Ferrero +/// \author Sebastien Perrin +class PedestalsTask final : public TaskInterface +{ + public: + /// \brief Constructor + PedestalsTask(); + /// Destructor + ~PedestalsTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + template + void publishObject(T* histo, std::string drawOption, bool statBox, bool doPublish = true) + { + histo->SetOption(drawOption.c_str()); + if (!statBox) { + histo->SetStats(0); + } + mAllHistograms.push_back(histo); + if (doPublish) { + getObjectsManager()->startPublishing(histo); + getObjectsManager()->setDefaultDrawOptions(histo, drawOption); + } + } + + /// check if a given electronics channel is associated with a detector pad + bool checkPadMapping(uint16_t feeId, uint8_t linkId, uint8_t eLinkId, o2::mch::raw::DualSampaChannelId channel, int& deId, int& padId); + + void monitorDataDigits(o2::framework::ProcessingContext& ctx); + void monitorDataPedestals(o2::framework::ProcessingContext& ctx); + void monitorDataBadChannels(o2::framework::ProcessingContext& ctx); + + void PlotPedestal(uint16_t solarID, uint8_t dsID, uint8_t channel, double stat, double mean, double rms); + void PlotPedestalDE(uint16_t solarID, uint8_t dsID, uint8_t channel, double stat, double mean, double rms); + void PlotBadChannel(uint16_t solarID, uint8_t dsID, uint8_t channel); + void PlotBadChannelDE(uint16_t solarID, uint8_t dsID, uint8_t channel); + void processElecMaps(); + + static constexpr int sMaxFeeId = 64; + static constexpr int sMaxLinkId = 12; + static constexpr int sMaxDsId = 40; + + o2::mch::raw::Elec2DetMapper mElec2DetMapper; + o2::mch::raw::FeeLink2SolarMapper mFeeLink2SolarMapper; + o2::mch::raw::Solar2FeeLinkMapper mSolar2FeeLinkMapper; + + bool mFullHistos{ false }; + + /// helper class that performs the actual computation of the pedestals from the input digits + o2::mch::calibration::PedestalData mPedestalData; + + std::unique_ptr mHistogramStat; + std::unique_ptr mHistogramPedestals; + std::unique_ptr mHistogramNoise; + std::unique_ptr mHistogramBadChannels; + + std::unique_ptr mHistogramStatDE; + std::unique_ptr mHistogramPedestalsDE; + std::unique_ptr mHistogramNoiseDE; + std::unique_ptr mHistogramEmptyChannelsDE; + std::unique_ptr mHistogramBadChannelsDE; + + std::array>, 2> mHistogramStatXY; + std::array>, 2> mHistogramPedestalsXY; + std::array>, 2> mHistogramNoiseXY; + std::array>, 2> mHistogramBadChannelsXY; + + std::map> mHistogramNoiseDistributionDE[5][2]; + std::array, 5> mHistogramNoiseDistribution; + + std::array, 2> mHistogramStatMCH; + std::array, 2> mHistogramPedestalsMCH; + std::array, 2> mHistogramNoiseMCH; + std::array, 2> mHistogramBadChannelsMCH; + + std::unique_ptr mCanvasCheckerMessages; + + int mPrintLevel; + + std::vector mAllHistograms; +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MUONCHAMBERS_PEDESTALSTASK_H diff --git a/Modules/MUON/MCH/include/MCH/PhysicsTaskDigits.h b/Modules/MUON/MCH/include/MCH/PhysicsTaskDigits.h new file mode 100644 index 0000000000..27c95680bc --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/PhysicsTaskDigits.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PhysicsTaskDigits.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_MUONCHAMBERS_PHYSICSTASKDIGITS_H +#define QC_MODULE_MUONCHAMBERS_PHYSICSTASKDIGITS_H + +#include "QualityControl/TaskInterface.h" + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +/// \brief Dummy task for the compatibility with the current full system test +/// \author Andrea Ferrero +class PhysicsTaskDigits /*final*/ : public TaskInterface // todo add back the "final" when doxygen is fixed +{ + public: + /// \brief Constructor + PhysicsTaskDigits() = default; + /// Destructor + ~PhysicsTaskDigits() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override {} + void startOfActivity(const Activity& activity) override {} + void startOfCycle() override {} + void monitorData(o2::framework::ProcessingContext& ctx) override {} + void endOfCycle() override {} + void endOfActivity(const Activity& activity) override {} + void reset() override {} +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_MUONCHAMBERS_PHYSICSTASKDIGITS_H diff --git a/Modules/MUON/MCH/include/MCH/PhysicsTaskPreclusters.h b/Modules/MUON/MCH/include/MCH/PhysicsTaskPreclusters.h new file mode 100644 index 0000000000..6e0cfe1469 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/PhysicsTaskPreclusters.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PhysicsTaskPreclusters.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_MUONCHAMBERS_PHYSICSTASKPRECLUSTERS_H +#define QC_MODULE_MUONCHAMBERS_PHYSICSTASKPRECLUSTERS_H + +#include "QualityControl/TaskInterface.h" + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +/// \brief Dummy task for the compatibility with the current full system test +/// \author Andrea Ferrero +class PhysicsTaskPreclusters /*final*/ : public o2::quality_control::core::TaskInterface // todo add back the "final" when doxygen is fixed +{ + public: + /// \brief Constructor + PhysicsTaskPreclusters() = default; + /// Destructor + ~PhysicsTaskPreclusters() = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override {} + void startOfActivity(const o2::quality_control::core::Activity& activity) override {} + void startOfCycle() override {} + void monitorData(o2::framework::ProcessingContext& ctx) override {} + void endOfCycle() override {} + void endOfActivity(const o2::quality_control::core::Activity& activity) override {} + void reset() override {} +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_MUONCHAMBERS_PHYSICSTASKPRECLUSTERS_H diff --git a/Modules/MUON/MCH/include/MCH/PhysicsTaskRofs.h b/Modules/MUON/MCH/include/MCH/PhysicsTaskRofs.h new file mode 100644 index 0000000000..58bc9efd12 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/PhysicsTaskRofs.h @@ -0,0 +1,51 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PhysicsTaskRofs.h +/// \author Andrea Ferrero +/// \author Sebastien Perrin +/// + +#ifndef QC_MODULE_MCH_PHYSICSTASKROFS_H +#define QC_MODULE_MCH_PHYSICSTASKROFS_H + +#include "QualityControl/TaskInterface.h" + +using namespace o2::quality_control_modules::muon; + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief Quality Control Task for the analysis of raw data decoding errors +/// \author Andrea Ferrero +/// \author Sebastien Perrin +class PhysicsTaskRofs /*final*/ : public TaskInterface +{ + public: + /// \brief Constructor + PhysicsTaskRofs() = default; + /// Destructor + ~PhysicsTaskRofs() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override {} + void startOfActivity(const Activity& activity) override {} + void startOfCycle() override {} + void monitorData(o2::framework::ProcessingContext& ctx) override {} + void endOfCycle() override {} + void endOfActivity(const Activity& activity) override {} + void reset() override {} +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_DECODINGERRORSTASK_H diff --git a/Modules/MUON/MCH/include/MCH/PostProcessingConfigMCH.h b/Modules/MUON/MCH/include/MCH/PostProcessingConfigMCH.h new file mode 100644 index 0000000000..4c9d52efbb --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/PostProcessingConfigMCH.h @@ -0,0 +1,100 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingConfigMCH.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Header file for the configuration of MCH post-processing tasks +/// \since 16/06/2022 +/// + +#ifndef QC_MODULE_MCH_TRENDING_CONF_H +#define QC_MODULE_MCH_TRENDING_CONF_H + +#include "QualityControl/PostProcessingConfig.h" +#include +#include +#include +#include + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief MCH trending configuration structure +struct PostProcessingConfigMCH : PostProcessingConfig { + PostProcessingConfigMCH() = default; + PostProcessingConfigMCH(std::string name, const boost::property_tree::ptree& config); + ~PostProcessingConfigMCH() = default; + + const bool hasParameter(std::string name) const + { + auto entry = parameters.find(name); + return (entry != parameters.end()); + } + + template + const T getParameter(std::string name) const; + + template + const T getParameter(std::string name, T defaultValue) const; + + struct Plot { + std::string name; + std::string title; + std::string varexp; + std::string selection; + std::string option; + std::string graphErrors; + }; + + struct DataSource { + std::string type; + std::string path; + std::string name; + std::string reductorName; + std::string moduleName; + }; + + std::map parameters; + std::vector plots; + std::vector dataSources; +}; + +template +const T PostProcessingConfigMCH::getParameter(std::string name) const +{ + T result{}; + auto entry = parameters.find(name); + if (entry != parameters.end()) { + std::istringstream istr(entry->second); + istr >> result; + } + + return result; +} + +template +const T PostProcessingConfigMCH::getParameter(std::string name, T defaultValue) const +{ + T result = defaultValue; + auto entry = parameters.find(name); + if (entry != parameters.end()) { + std::istringstream istr(entry->second); + istr >> result; + } + + return result; +} + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_TRENDING_CONF_H diff --git a/Modules/MUON/MCH/include/MCH/PreclustersCheck.h b/Modules/MUON/MCH/include/MCH/PreclustersCheck.h new file mode 100644 index 0000000000..fb3085bc27 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/PreclustersCheck.h @@ -0,0 +1,81 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PreclustersCheck.h +/// \author Andrea Ferrero, Sebastien Perrin +/// + +#ifndef QC_MODULE_MCH_PHYSICSPRECLUSTERSCHECK_H +#define QC_MODULE_MCH_PHYSICSPRECLUSTERSCHECK_H + +#include "MCH/Helpers.h" +#include "QualityControl/CheckInterface.h" +#include "QualityControl/Quality.h" +#include + +namespace o2::quality_control::core +{ +class MonitorObject; +} + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief Checker for the pre-clusters plots +/// +/// \author Andrea Ferrero, Sebastien Perrin +class PreclustersCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + PreclustersCheck() = default; + /// Destructor + ~PreclustersCheck() override = default; + + // Override interface + void configure() override; + void startOfActivity(const Activity& activity) override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + std::array checkMeanEfficiencies(TH1* h); + std::array checkMeanEfficiencyRatios(TH1* h); + void checkSolarMeanEfficiencies(TH1* h); + void checkSolarMeanEfficiencyRatios(TH1* h); + + std::string mMeanEffHistNameB{ "Efficiency/MeanEfficiencyB" }; + std::string mMeanEffHistNameNB{ "Efficiency/MeanEfficiencyNB" }; + std::string mMeanEffPerSolarHistName{ "Efficiency/MeanEfficiencyPerSolar" }; + std::string mMeanEffRefCompHistNameB{ "Efficiency/RefComp/MeanEfficiencyB" }; + std::string mMeanEffRefCompHistNameNB{ "Efficiency/RefComp/MeanEfficiencyNB" }; + std::string mMeanEffPerSolarRefCompHistName{ "Efficiency/RefComp/MeanEfficiencyPerSolar" }; + int mMaxBadST12{ 2 }; + int mMaxBadST345{ 3 }; + double mMinEfficiency{ 0.8 }; + std::array, 5> mMinEfficiencyPerStation; + double mMinEfficiencyPerSolar{ 0.5 }; + double mMinEfficiencyRatio{ 0.9 }; + double mMinEfficiencyRatioPerSolar{ 0.9 }; + double mPseudoeffPlotScaleMin{ 0.0 }; + double mPseudoeffPlotScaleMax{ 1.05 }; + double mEfficiencyRatioScaleRange{ 0.2 }; + double mEfficiencyRatioPerSolarScaleRange{ 0.2 }; + + QualityChecker mQualityChecker; + std::array mSolarQuality; + + ClassDefOverride(PreclustersCheck, 3); +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_TOF_TOFCHECKRAWSTIME_H diff --git a/Modules/MUON/MCH/include/MCH/PreclustersPostProcessing.h b/Modules/MUON/MCH/include/MCH/PreclustersPostProcessing.h new file mode 100644 index 0000000000..351e4ca3d4 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/PreclustersPostProcessing.h @@ -0,0 +1,131 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PreclustersPostProcessing.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Post-processing of the MCH pre-clusters +/// \since 21/06/2022 +/// + +#ifndef QC_MODULE_MCH_PP_PRECLUSTERS_H +#define QC_MODULE_MCH_PP_PRECLUSTERS_H + +#include "MCH/PostProcessingConfigMCH.h" +#include "MCH/Helpers.h" +#include "MCH/HistoOnCycle.h" +#include "MCH/EfficiencyPlotter.h" +#include "MCH/EfficiencyTrendsPlotter.h" +#include "MCH/ClusterSizePlotter.h" +#include "MCH/ClusterSizeTrendsPlotter.h" +#include "MCH/ClusterChargePlotter.h" +#include "MCH/ClusterChargeTrendsPlotter.h" +#include "Common/ReferenceComparatorTask.h" +#include "Common/TH2Ratio.h" + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief A post-processing task which processes and trends MCH pre-clusters and produces plots. +class PreclustersPostProcessing : public ReferenceComparatorTask +{ + public: + PreclustersPostProcessing() = default; + ~PreclustersPostProcessing() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + /** create one histogram with relevant drawing options / stat box status.*/ + template + void publishHisto(T* h, bool statBox = false, + const char* drawOptions = "", + const char* displayHints = ""); + void createEfficiencyHistos(Trigger t, repository::DatabaseInterface* qcdb); + void createClusterChargeHistos(Trigger t, repository::DatabaseInterface* qcdb); + void createClusterSizeHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateEfficiencyHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateClusterChargeHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateClusterSizeHistos(Trigger t, repository::DatabaseInterface* qcdb); + + static std::string effSourceName() { return "eff"; } + static std::string clusterChargeSourceName() { return "clcharge"; } + static std::string clusterSizeSourceName() { return "clsize"; } + + TH1* getHistogram(std::string_view plotName); + + bool mFullHistos{ false }; + bool mEnableLastCycleHistos{ false }; + bool mEnableTrending{ false }; + + PostProcessingConfigMCH mConfig; + + // CCDB object accessors + std::map mCcdbObjects; + + // Hit rate histograms =============================================== + + // On-cycle plots generators + std::unique_ptr> mElecMapOnCycle; + std::unique_ptr> mClusterChargeOnCycle; + std::unique_ptr> mClusterSizeOnCycle; + // Plotters + std::unique_ptr mEfficiencyPlotter; + std::unique_ptr mEfficiencyPlotterOnCycle; + + std::unique_ptr mClusterChargePlotter; + std::unique_ptr mClusterChargePlotterOnCycle; + + std::unique_ptr mClusterSizePlotter; + std::unique_ptr mClusterSizePlotterOnCycle; + + std::unique_ptr mEfficiencyTrendsPlotter; + std::unique_ptr mClusterChargeTrendsPlotter; + std::unique_ptr mClusterSizeTrendsPlotter; + + std::unique_ptr mHistogramQualityPerDE; ///< quality flags for each DE, to be filled by checker task + std::unique_ptr mHistogramQualityPerSolar; ///< quality flags for each SOLAR, to be filled by checker task + + std::vector mHistogramsAll; +}; + +template +void PreclustersPostProcessing::publishHisto(T* h, bool statBox, + const char* drawOptions, + const char* displayHints) +{ + if (!statBox) { + h->SetStats(0); + } + getObjectsManager()->startPublishing(h); + if (drawOptions) { + getObjectsManager()->setDefaultDrawOptions(h, drawOptions); + } + if (displayHints) { + getObjectsManager()->setDisplayHint(h, displayHints); + } +} + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_PP_PRECLUSTERS_H diff --git a/Modules/MUON/MCH/include/MCH/PreclustersTask.h b/Modules/MUON/MCH/include/MCH/PreclustersTask.h new file mode 100644 index 0000000000..e5015b2b7e --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/PreclustersTask.h @@ -0,0 +1,94 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PreclustersTask.h +/// \author Andrea Ferrero +/// \author Sebastien Perrin +/// + +#ifndef QC_MODULE_MUONCHAMBERS_PRECLUSTERSTASK_H +#define QC_MODULE_MUONCHAMBERS_PRECLUSTERSTASK_H + +#include "QualityControl/TaskInterface.h" +#include "Common/TH1Ratio.h" +#include "Common/TH2Ratio.h" +#ifdef HAVE_DIGIT_IN_DATAFORMATS +#include "DataFormatsMCH/Digit.h" +#else +#include "MCHBase/Digit.h" +#endif +#include "MCHDigitFiltering/DigitFilter.h" +#include "MCHBase/PreCluster.h" + +using namespace o2::quality_control_modules::common; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +/// \brief Quality Control Task for the analysis of MCH physics data +/// \author Andrea Ferrero +/// \author Sebastien Perrin +class PreclustersTask /*final*/ : public o2::quality_control::core::TaskInterface // todo add back the "final" when doxygen is fixed +{ + public: + /// \brief Constructor + PreclustersTask() = default; + /// Destructor + ~PreclustersTask() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const o2::quality_control::core::Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const o2::quality_control::core::Activity& activity) override; + void reset() override; + + private: + template + void publishObject(T* histo, std::string drawOption, bool statBox); + + void plotPrecluster(const o2::mch::PreCluster& preCluster, gsl::span digits); + + bool mEnable1DPseudoeffMaps{ true }; // whether to publish 1D maps of channel pseudo-efficiencies + bool mEnable2DPseudoeffMaps{ false }; // whether to publish 2D maps of channel pseudo-efficiencies + + o2::mch::DigitFilter mIsSignalDigit; + + std::unique_ptr mHistogramPseudoeffElec; // Mergeable object, Occupancy histogram (Elec view) + + // 1D pseudo-efficiency histogram using Elec view, where each x bin corresponds to the unique ID of a DualSAMPA board + std::unique_ptr mHistogramPseudoeffPerDualSampa; + + std::unique_ptr mHistogramPreclustersPerDE; // number of pre-clusters per DE and per TF + std::unique_ptr mHistogramPreclustersSignalPerDE; // number of pre-clusters with signal per DE and per TF + + ///< distribution of the cluster charge and size in each station, total and separately for each cathode + std::array, 3> mHistogramClusterChargePerStation; + std::array, 3> mHistogramClusterSizePerStation; + + std::unique_ptr mHistogramClusterCharge; + std::unique_ptr mHistogramClusterSize; + + std::vector mAllHistograms; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_MUONCHAMBERS_PRECLUSTERSTASK_H diff --git a/Modules/MUON/MCH/include/MCH/QualityAggregatorTask.h b/Modules/MUON/MCH/include/MCH/QualityAggregatorTask.h new file mode 100644 index 0000000000..679ff623d8 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/QualityAggregatorTask.h @@ -0,0 +1,70 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityAggregatorTask.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Post-processing of the MCH summary qualities +/// \since 21/06/2022 +/// + +#ifndef QC_MODULE_MCH_PP_QUALITY_H +#define QC_MODULE_MCH_PP_QUALITY_H + +#include "QualityControl/PostProcessingInterface.h" +#include "CCDB/CcdbApi.h" + +#include + +using namespace o2::framework; + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; + +class TH2F; + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief A post-processing task which processes and trends MCH pre-clusters and produces plots. +class QualityAggregatorTask : public PostProcessingInterface +{ + public: + using CcdbApi = o2::ccdb::CcdbApi; + + QualityAggregatorTask() = default; + ~QualityAggregatorTask() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + CcdbApi mAPI; + std::string mCCDBpath{ "http://ccdb-test.cern.ch:8080" }; // CCDB path + + std::string mObjectPathBadDE{ "MCH/Calib/BadDE" }; + std::string mObjectPathBadSOLAR{ "MCH/Calib/BadSOLAR" }; + + std::vector mDEPlotPaths; + std::optional> mPreviousBadDEs; + + std::vector mSOLARPlotPaths; + std::optional> mPreviousBadSolarBoards; + + std::unique_ptr mHistogramQualityPerDE; ///< quality flags for each DE, to be filled by checker task + std::unique_ptr mHistogramQualityPerSolar; ///< quality flags for each SOLAR, to be filled by checker task +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_PP_QUALITY_H diff --git a/Modules/MUON/MCH/include/MCH/RatesPlotter.h b/Modules/MUON/MCH/include/MCH/RatesPlotter.h new file mode 100644 index 0000000000..c3068c67cb --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/RatesPlotter.h @@ -0,0 +1,85 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RatesPlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_RATESPLOTTER_H +#define QC_MODULE_RATESPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCH/GlobalHistogram.h" +#include "MCH/TH2ElecMapReductor.h" +#include "MCHRawElecMap/Mapper.h" +#include + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class RatesPlotter : public HistPlotter +{ + public: + RatesPlotter(std::string path, float rateMin, float rateMax, bool perStationPlots = false, bool fullPlots = false); + + void update(TH2F* hRates); + + private: + void addHisto(TH1* h, bool statBox, const char* drawOptions, const char* displayHints) + { + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h, drawOptions, displayHints }); + } + + void addCanvas(TCanvas* c, TH1* h, bool statBox, const char* drawOptions, const char* displayHints) + { + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ c, "", displayHints }); + } + + void fillAverageHistos(TH2F* h); + void fillGlobalHistos(TH2F* h); + + o2::mch::raw::Elec2DetMapper mElec2DetMapper; + o2::mch::raw::FeeLink2SolarMapper mFeeLink2SolarMapper; + + std::unique_ptr mElecMapReductor; + + std::unique_ptr mHistogramRatePerStation; + + std::unique_ptr mHistogramMeanRatePerDE; + std::unique_ptr mHistogramMeanRatePerSolar; + + std::unique_ptr mHistogramGoodChannelsFractionPerDE; + std::unique_ptr mHistogramGoodChannelsFractionPerSolar; + + std::map> mHistogramRateDE[2]; // 2D hit rate map for each DE + std::shared_ptr mHistogramRateGlobal[2]; // Rate histogram (global XY view) +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_RATESPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/RatesTrendsPlotter.h b/Modules/MUON/MCH/include/MCH/RatesTrendsPlotter.h new file mode 100644 index 0000000000..47395e0d5e --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/RatesTrendsPlotter.h @@ -0,0 +1,65 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RatesTrendsPlotter.h +/// \author Andrea Ferrero +/// + +#ifndef QC_MODULE_RATESTRENDSPLOTTER_H +#define QC_MODULE_RATESTRENDSPLOTTER_H + +#include "MUONCommon/HistPlotter.h" +#include "MCH/Helpers.h" +#include "MCH/TH2ElecMapReductor.h" +#include + +class TH2F; + +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +class RatesTrendsPlotter : public HistPlotter +{ + public: + RatesTrendsPlotter(std::string path, bool fullPlots = false); + + void update(long time, TH2F* hEfficiency); + + private: + void addCanvas(TCanvas* c, const char* displayHints) + { + histograms().emplace_back(HistInfo{ c, "", displayHints }); + } + + long mTime{ 0 }; + std::string mPath; + + // Data reductor + std::unique_ptr mReductor; + // Trend plots + std::unique_ptr mOrbits; + std::array, getNumDE()> mTrendsDE; + std::array, 10> mTrendsChamber; + std::unique_ptr mTrends; +}; + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 + +#endif // QC_MODULE_RATESTRENDSPLOTTER_H diff --git a/Modules/MUON/MCH/include/MCH/RofsTask.h b/Modules/MUON/MCH/include/MCH/RofsTask.h new file mode 100644 index 0000000000..f5337b155b --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/RofsTask.h @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RofsTask.h +/// \author Andrea Ferrero +/// \author Sebastien Perrin +/// + +#ifndef QC_MODULE_MCH_ROFSTASK_H +#define QC_MODULE_MCH_ROFSTASK_H + +#include +#include + +#include "DataFormatsMCH/Digit.h" +#include "DataFormatsMCH/ROFRecord.h" +#include "MCHDigitFiltering/DigitFilter.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/TaskInterface.h" + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief Quality Control Task for the analysis of raw data decoding errors +/// \author Andrea Ferrero +/// \author Sebastien Perrin +class RofsTask /*final*/ : public o2::quality_control::core::TaskInterface +{ + public: + /// \brief Constructor + RofsTask() = default; + /// Destructor + ~RofsTask() override = default; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const o2::quality_control::core::Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const o2::quality_control::core::Activity& activity) override; + void reset() override; + + private: + template + void publishObject(std::shared_ptr histo, std::string drawOption, bool statBox); + + void plotROF(const o2::mch::ROFRecord& rof, gsl::span digits); + + o2::mch::DigitFilter mIsSignalDigit; ///< functor to select signal-like digits + + std::shared_ptr mHistRofSize; ///< number of digits per ROF + std::shared_ptr mHistRofSize_Signal; ///< number of signal-like digits per ROF + std::shared_ptr mHistRofNStations; ///< number of stations per ROF + std::shared_ptr mHistRofNStations_Signal; ///< number of stations per ROF from signal-like digits + std::shared_ptr mHistRofTime; ///< average ROF time in orbit + std::shared_ptr mHistRofTime_Signal; ///< average ROF time in orbit from signal-like digits + std::shared_ptr mHistRofWidth; ///< ROF width in BC + + std::vector mAllHistograms; +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_ROFSTASK_H diff --git a/Modules/MUON/MCH/include/MCH/TH2ElecMapReductor.h b/Modules/MUON/MCH/include/MCH/TH2ElecMapReductor.h new file mode 100644 index 0000000000..9fb185ce10 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/TH2ElecMapReductor.h @@ -0,0 +1,102 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2ElecMapReductor.h +/// \author Andrea Ferrero +/// +#ifndef QUALITYCONTROL_TH2ELECMAPREDUCTOR_H +#define QUALITYCONTROL_TH2ELECMAPREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" +#include "MCHRawCommon/DataFormats.h" +#include "MCHRawElecMap/Mapper.h" +#include + +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief A Reductor which extracts the average features from a 2D map in electronics coordinates +/// +/// A Reductor which extracts the average features from a 2D map in electronics coordinates + +class TH2ElecMapReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + TH2ElecMapReductor(float min = std::numeric_limits::min(), float max = std::numeric_limits::max()); + ~TH2ElecMapReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + float getChamberValue(int chid); + float getDeValue(int deid, int cathode = 2); + float getOrbits() { return meanOrbits; } + + // SOLAR related accessors + float getSolarValue(int solarId); + int getSolarNumPads(int solarId); + int getSolarNumPadsBad(int solarId); + int getSolarNumPadsNoStat(int solarId); + + // DE related accessors + int getNumPads(int deid, int cathode); + int getNumPads(int deid) + { + return getNumPads(deid, 0) + getNumPads(deid, 1); + } + int getNumPadsBad(int deid, int cathode); + int getNumPadsBad(int deid) + { + return getNumPadsBad(deid, 0) + getNumPadsBad(deid, 1); + } + int getNumPadsNoStat(int deid, int cathode); + int getNumPadsNoStat(int deid) + { + return getNumPadsNoStat(deid, 0) + getNumPadsNoStat(deid, 1); + } + + private: + static constexpr int sDeNum{ 156 }; + static constexpr uint32_t sSolarIndexMax{ 32 * 24 }; + + int checkPadMapping(uint16_t feeId, uint8_t linkId, uint8_t eLinkId, o2::mch::raw::DualSampaChannelId channel, int& cid); + + o2::mch::raw::Elec2DetMapper mElec2DetMapper; + o2::mch::raw::Det2ElecMapper mDet2ElecMapper; + o2::mch::raw::FeeLink2SolarMapper mFeeLink2SolarMapper; + o2::mch::raw::Solar2FeeLinkMapper mSolar2FeeLinkMapper; + + float mMin; + float mMax; + + // SOLAR related values + int solarNumPads[sSolarIndexMax]; + int solarNumPadsBad[sSolarIndexMax]; + int solarNumPadsNoStat[sSolarIndexMax]; + float solarValues[sSolarIndexMax]; + + // DE related values + int deNumPads[2][sDeNum]; + int deNumPadsBad[2][sDeNum]; + int deNumPadsNoStat[2][sDeNum]; + float deValues[3][sDeNum]; + + // chamber related values + float chValues[10]; + float meanOrbits; + float entries; +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QUALITYCONTROL_TH2ELECMAPREDUCTOR_H diff --git a/Modules/MUON/MCH/include/MCH/TracksTask.h b/Modules/MUON/MCH/include/MCH/TracksTask.h new file mode 100644 index 0000000000..20bb5602a9 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/TracksTask.h @@ -0,0 +1,126 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#ifndef QC_MODULE_MCH_TRACKS_TASK_H +#define QC_MODULE_MCH_TRACKS_TASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include "MCHGeometryTransformer/Transformations.h" + +namespace o2::mch +{ +class Cluster; +class TrackMCH; +} // namespace o2::mch + +using namespace o2::quality_control::core; + +class TH1F; +class TProfile; + +namespace o2::quality_control_modules::muonchambers +{ + +class TracksTask /*final*/ : public TaskInterface +{ + public: + TracksTask(); + ~TracksTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + /** check whether all the expected inputs are present.*/ + bool assertInputs(o2::framework::ProcessingContext& ctx); + + /** create one histogram with relevant drawing options / stat box status.*/ + template + std::unique_ptr createHisto(const char* name, const char* title, + int nbins, double xmin, double xmax, + bool statBox = false, + const char* drawOptions = "", + const char* displayHints = ""); + + /** create histograms related to clusters (those attached to tracks) */ + void createClusterHistos(); + /** create histograms related to tracks */ + void createTrackHistos(); + /** create histograms related to track pairs */ + void createTrackPairHistos(); + + /** fill histogram related to each cluster */ + void fillClusterHistos(gsl::span clusters); + + /** fill histograms related to a single track */ + bool fillTrackHistos(const o2::mch::TrackMCH& track, + gsl::span clusters); + + /** fill histograms for track pairs */ + void fillTrackPairHistos(gsl::span tracks); + + private: + int dsbinx(int deid, int dsid) const; + + std::unique_ptr mNofTracksPerTF; ///< number of tracks per TF + std::unique_ptr mTrackBC; ///< BC associated to the track + std::unique_ptr mTrackChi2OverNDF; ///< chi2/ndf for the track + std::unique_ptr mTrackDCA; ///< DCA (cm) of the track + std::unique_ptr mTrackEta; ///< eta of the track + std::unique_ptr mTrackPDCA; ///< p (GeV/c) x DCA (cm) of the track + std::unique_ptr mTrackPhi; ///< phi (in degrees) of the track + std::unique_ptr mTrackPt; ///< Pt (Gev/c^2) of the track + std::unique_ptr mTrackRAbs; ///< R at absorber end of the track + + std::unique_ptr mNofClustersPerDualSampa; //< aka cluster map + std::unique_ptr mNofClustersPerTrack; ///< number of clusters per track + std::unique_ptr mClusterSizePerChamber; ///< mean cluster size per chamber + std::unique_ptr mNofClustersPerChamber; ///< mean number of clusters per chamber + + std::unique_ptr mMinv; ///< invariant mass of unlike-sign track pairs + + o2::mch::raw::Det2ElecMapper mDet2ElecMapper; + o2::mch::raw::Solar2FeeLinkMapper mSolar2FeeLinkMapper; + std::unique_ptr mTransformation; +}; + +template +std::unique_ptr TracksTask::createHisto(const char* name, const char* title, + int nbins, double xmin, double xmax, + bool statBox, + const char* drawOptions, + const char* displayHints) +{ + auto h = std::make_unique(name, title, nbins, xmin, xmax); + if (!statBox) { + h->SetStats(0); + } + getObjectsManager()->startPublishing(h.get()); + if (drawOptions) { + getObjectsManager()->setDefaultDrawOptions(h.get(), drawOptions); + } + if (displayHints) { + getObjectsManager()->setDisplayHint(h.get(), displayHints); + } + return h; +} + +} // namespace o2::quality_control_modules::muonchambers + +#endif diff --git a/Modules/MUON/MCH/include/MCH/TrendingTracks.h b/Modules/MUON/MCH/include/MCH/TrendingTracks.h new file mode 100644 index 0000000000..d7df1c55c7 --- /dev/null +++ b/Modules/MUON/MCH/include/MCH/TrendingTracks.h @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTracks.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Trending of the MCH tracking +/// \since 21/06/2022 +/// + +#ifndef QC_MODULE_MCH_TRENDING_TRACKS_H +#define QC_MODULE_MCH_TRENDING_TRACKS_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" + +#include "MCH/PostProcessingConfigMCH.h" + +#include +#include +#include + +class TProfile; + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::muonchambers +{ + +/// \brief A post-processing task which trends MCH hits and stores them in a TTree and produces plots. +class TrendingTracks : public PostProcessingInterface +{ + public: + TrendingTracks() = default; + ~TrendingTracks() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + std::string nameTracksNum = "TracksPerTF"; + std::string nameClusPerTrack = "ClustersPerTrack"; + std::string nameClusPerChamber = "ClustersPerChamber"; + + private: + struct MetaData { + Int_t runNumber = 0; + }; + + void computeClustersPerChamber(TProfile* p); + void trendValues(const Trigger& t, repository::DatabaseInterface&); + void generatePlots(); + + PostProcessingConfigMCH mConfig; + MetaData mMetaData; + UInt_t mTime; + float mClusCH[10]; /// average number of clusters in each chamber + + std::unique_ptr mTrend; + std::map mPlots; + std::unordered_map> mReductors; + + TProfile* mHistClusPerChamberPrev = nullptr; + + // These are initialized from the PostProcessingConfigMCH.h +}; + +} // namespace o2::quality_control_modules::muonchambers + +#endif // QC_MODULE_MCH_TRENDING_TRACKS_H diff --git a/Modules/MUON/MCH/src/ClusterChargePlotter.cxx b/Modules/MUON/MCH/src/ClusterChargePlotter.cxx new file mode 100644 index 0000000000..3c88aecadb --- /dev/null +++ b/Modules/MUON/MCH/src/ClusterChargePlotter.cxx @@ -0,0 +1,77 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterChargePlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/ClusterChargePlotter.h" + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +ClusterChargePlotter::ClusterChargePlotter(std::string path, bool fullPlots) +{ + mChargeReductor = std::make_unique(); + + //---------------------------------- + // Charge MPV histograms + //---------------------------------- + + mHistogramChargePerDE = std::make_unique(TString::Format("%sClusterChargeMPVHist", path.c_str()), + TString::Format("Charge vs DE"), getNumDE(), 0, getNumDE()); + addHisto(mHistogramChargePerDE.get(), false, "histo", ""); + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int deID = getDEindex(de); + if (deID < 0) { + continue; + } + mHistogramCharge[deID] = std::make_unique(TString::Format("%s%sClusterCharge_%03d", path.c_str(), getHistoPath(de).c_str(), de), + TString::Format("Cluster Charge (DE%03d)", de), 256, 0, 256 * 50); + if (fullPlots) { + addHisto(mHistogramCharge[deID].get(), false, "hist", ""); + } + } +} + +//_________________________________________________________________________________________ + +void ClusterChargePlotter::update(TH2F* hCharge) +{ + // extract the MPVs of the cluster charge distributions + mChargeReductor->update(hCharge); + + for (size_t de = 0; de < mHistogramChargePerDE->GetXaxis()->GetNbins(); de++) { + mHistogramChargePerDE->SetBinContent(de + 1, mChargeReductor->getDeValue(de)); + mHistogramChargePerDE->SetBinError(de + 1, 0.1); + } + + for (int xbin = 1; xbin <= hCharge->GetXaxis()->GetNbins(); xbin++) { + if (xbin > getNumDE()) { + break; + } + TH1F* proj = (TH1F*)hCharge->ProjectionY("_proj", xbin, xbin); + int de = xbin - 1; + mHistogramCharge[de]->Reset(); + mHistogramCharge[de]->Add(proj); + delete proj; + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/ClusterChargeReductor.cxx b/Modules/MUON/MCH/src/ClusterChargeReductor.cxx new file mode 100644 index 0000000000..e13792be25 --- /dev/null +++ b/Modules/MUON/MCH/src/ClusterChargeReductor.cxx @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterChargeReductor.cxx +/// \author Andrea Ferrero +/// \author Sebastien Perrin +/// + +#include "MCH/ClusterChargeReductor.h" +#include +#include + +namespace o2::quality_control_modules::muonchambers +{ + +ClusterChargeReductor::ClusterChargeReductor() : quality_control::postprocessing::ReductorTObject() +{ +} + +void* ClusterChargeReductor::getBranchAddress() +{ + return &mStats; +} + +const char* ClusterChargeReductor::getBranchLeafList() +{ + return "DE100/D:DE101:DE102:DE103:DE200:DE201:DE202:DE203:DE300:DE301:DE302:DE303:DE400:DE401:DE402:DE403:DE500:DE501:DE502:DE503:DE504:DE505:DE506:DE507:DE508:DE509:DE510:DE511:DE512:DE513:DE514:DE515:DE516:DE517:DE600:DE601:DE602:DE603:DE604:DE605:DE606:DE607:DE608:DE609:DE610:DE611:DE612:DE613:DE614:DE615:DE616:DE617:DE700:DE701:DE702:DE703:DE704:DE705:DE706:DE707:DE708:DE709:DE710:DE711:DE712:DE713:DE714:DE715:DE716:DE717:DE718:DE719:DE720:DE721:DE722:DE723:DE724:DE725:DE800:DE801:DE802:DE803:DE804:DE805:DE806:DE807:DE808:DE809:DE810:DE811:DE812:DE813:DE814:DE815:DE816:DE817:DE818:DE819:DE820:DE821:DE822:DE823:DE824:DE825:DE900:DE901:DE902:DE903:DE904:DE905:DE906:DE907:DE908:DE909:DE910:DE911:DE912:DE913:DE914:DE915:DE916:DE917:DE918:DE919:DE920:DE921:DE922:DE923:DE924:DE925:DE1000:DE1001:DE1002:DE1003:DE1004:DE1005:DE1006:DE1007:DE1008:DE1009:DE1010:DE1011:DE1012:DE1013:DE1014:DE1015:DE1016:DE1017:DE1018:DE1019:DE1020:DE1021:DE1022:DE1023:DE1024:DE1025:entries"; +} + +Double_t ClusterChargeReductor::getDeValue(int deid) +{ + if (deid < 0 || deid >= sDeNum) { + return 0; + } + return mStats.deValues.values[deid]; +} + +void ClusterChargeReductor::update(TObject* obj) +{ + auto h = dynamic_cast(obj); + if (!h) { + return; + } + + for (int xbin = 1; xbin <= h->GetXaxis()->GetNbins(); xbin++) { + if (xbin > sDeNum) { + break; + } + int de = xbin - 1; + + TH1F* proj = (TH1F*)h->ProjectionY("_proj", xbin, xbin); + int binmax = proj->GetMaximumBin(); + double xmax = proj->GetXaxis()->GetBinCenter(binmax); + mStats.deValues.values[de] = xmax; + delete proj; + } +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/ClusterChargeTrendsPlotter.cxx b/Modules/MUON/MCH/src/ClusterChargeTrendsPlotter.cxx new file mode 100644 index 0000000000..14a3d57a19 --- /dev/null +++ b/Modules/MUON/MCH/src/ClusterChargeTrendsPlotter.cxx @@ -0,0 +1,65 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterChargeTrendsPlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/ClusterChargeTrendsPlotter.h" +#include +#include + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +ClusterChargeTrendsPlotter::ClusterChargeTrendsPlotter(std::string path, bool fullPlots) +{ + mReductor = std::make_unique(); + + //-------------------------------------------------- + // Cluster charge trends + //-------------------------------------------------- + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int deID = getDEindex(de); + if (deID < 0) { + continue; + } + + mTrends[deID] = std::make_unique(fmt::format("{}{}/ClusterCharge_DE{}", path, getHistoPath(de), de), + fmt::format("DE{} Cluster Charge MPV", de), "charge (ADC)"); + // mTrends[deID]->setRange(0, 1.2); + if (fullPlots) { + addCanvas(mTrends[deID].get(), ""); + } + } +} + +//_________________________________________________________________________________________ + +void ClusterChargeTrendsPlotter::update(long time, TH2F* h) +{ + // extract the integrated average occupancies + mReductor->update(h); + + for (size_t de = 0; de < getNumDE(); de++) { + mTrends[de]->update(time, mReductor->getDeValue(de)); + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/ClusterSizePlotter.cxx b/Modules/MUON/MCH/src/ClusterSizePlotter.cxx new file mode 100644 index 0000000000..54b3247410 --- /dev/null +++ b/Modules/MUON/MCH/src/ClusterSizePlotter.cxx @@ -0,0 +1,155 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterSizePlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/ClusterSizePlotter.h" +#include "MCH/Helpers.h" +#include "MCHMappingInterface/Segmentation.h" +#include +#include + +using namespace o2::mch::raw; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +ClusterSizePlotter::ClusterSizePlotter(std::string path, bool fullPlots) : mPath(path) +{ + mClusterSizeReductor = std::make_unique(); + + std::string sc[3] = { "B", "NB", "" }; + std::string sc2[3] = { " (B)", " (NB)", "" }; + int histColors[3] = { kRed, kBlue, kBlack }; + for (int ci = 0; ci < 3; ci++) { + + //---------------------------------- + // Mean cluster size histograms + //---------------------------------- + + mHistogramClusterSizePerDE[ci] = std::make_unique(TString::Format("%sMeanClusterSize%s", mPath.c_str(), sc[ci].c_str()), + TString::Format("Cluster Size vs DE%s", sc2[ci].c_str()), getNumDE(), 0, getNumDE()); + addHisto(mHistogramClusterSizePerDE[ci].get(), false, "histo", ""); + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int deID = getDEindex(de); + if (deID < 0) { + continue; + } + int histId = deID * 3 + ci; + mHistogramClusterSize[histId] = std::make_unique(TString::Format("%s%sClusterSize_%03d%sHist", mPath.c_str(), getHistoPath(de).c_str(), de, sc[ci].c_str()), + TString::Format("Cluster Size (DE%03d%s)", de, sc[ci].c_str()), 100, 0, 100); + mHistogramClusterSize[histId]->SetLineColor(histColors[ci]); + } + } + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int deID = getDEindex(de); + if (deID < 0) { + continue; + } + int histId = deID * 3; + mCanvasClusterSize[deID] = std::make_unique(TString::Format("%s%sClusterSize_%03d", mPath.c_str(), getHistoPath(de).c_str(), de), + TString::Format("Cluster Size (DE%03d)", de), 800, 600); + if (fullPlots) { + addCanvas(mCanvasClusterSize[deID].get(), mHistogramClusterSize[histId + 2].get(), false, "", ""); + } + + mLegendClusterSize[deID] = std::make_unique(0.7, 0.6, 0.88, 0.88); + mLegendClusterSize[deID]->SetBorderSize(0); + mLegendClusterSize[deID]->AddEntry(mHistogramClusterSize[histId].get(), "bending", "l"); + mLegendClusterSize[deID]->AddEntry(mHistogramClusterSize[histId + 1].get(), "non-bending", "l"); + mLegendClusterSize[deID]->AddEntry(mHistogramClusterSize[histId + 2].get(), "full", "l"); + } +} + +//_________________________________________________________________________________________ + +void ClusterSizePlotter::addHisto(TH1* h, bool statBox, const char* drawOptions, const char* displayHints) +{ + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ h, drawOptions, displayHints }); +} + +//_________________________________________________________________________________________ + +void ClusterSizePlotter::addCanvas(TCanvas* c, TH1* h, bool statBox, const char* drawOptions, const char* displayHints) +{ + h->SetOption(drawOptions); + if (!statBox) { + h->SetStats(0); + } + histograms().emplace_back(HistInfo{ c, "", displayHints }); +} + +//_________________________________________________________________________________________ + +void ClusterSizePlotter::fillHistograms(TH2F* hSize) +{ + // extract the MPVs of the cluster charge distributions + mClusterSizeReductor->update(hSize); + + for (int ci = 0; ci < 3; ci++) { + for (size_t de = 0; de < mHistogramClusterSizePerDE[ci]->GetXaxis()->GetNbins(); de++) { + mHistogramClusterSizePerDE[ci]->SetBinContent(de + 1, mClusterSizeReductor->getDeValue(de, ci)); + mHistogramClusterSizePerDE[ci]->SetBinError(de + 1, 0.1); + } + } + + for (int xbin = 1; xbin <= hSize->GetXaxis()->GetNbins(); xbin++) { + if (xbin > getNumDE() * 3) { + break; + } + TH1F* proj = (TH1F*)hSize->ProjectionY("_proj", xbin, xbin); + int deId = (xbin - 1) / 3; + int cId = (xbin - 1) % 3; + int histId = deId * 3 + cId; + mHistogramClusterSize[histId]->Reset(); + mHistogramClusterSize[histId]->Add(proj); + delete proj; + } + + for (int i = 0; i < mCanvasClusterSize.size(); i++) { + int histId = i * 3; + double max1 = mHistogramClusterSize[histId]->GetMaximum(); + double max2 = mHistogramClusterSize[histId + 1]->GetMaximum(); + double max3 = mHistogramClusterSize[histId + 2]->GetMaximum(); + double max = std::max(max1, std::max(max2, max3)); + mCanvasClusterSize[i]->Clear(); + mCanvasClusterSize[i]->cd(); + mHistogramClusterSize[histId + 2]->Draw(); + mHistogramClusterSize[histId + 2]->SetMaximum(max * 1.05); + mHistogramClusterSize[histId]->Draw("histsame"); + mHistogramClusterSize[histId + 1]->Draw("histsame"); + mLegendClusterSize[i]->Draw(); + } +} + +//_________________________________________________________________________________________ + +void ClusterSizePlotter::update(TH2F* hSize) +{ + fillHistograms(hSize); +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/ClusterSizeReductor.cxx b/Modules/MUON/MCH/src/ClusterSizeReductor.cxx new file mode 100644 index 0000000000..26df0066af --- /dev/null +++ b/Modules/MUON/MCH/src/ClusterSizeReductor.cxx @@ -0,0 +1,71 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterSizeReductor.cxx +/// \author Andrea Ferrero +/// \author Sebastien Perrin +/// + +#include "MCH/ClusterSizeReductor.h" +#include +#include + +namespace o2::quality_control_modules::muonchambers +{ + +ClusterSizeReductor::ClusterSizeReductor() : quality_control::postprocessing::ReductorTObject() +{ +} + +void* ClusterSizeReductor::getBranchAddress() +{ + return &mStats; +} + +const char* ClusterSizeReductor::getBranchLeafList() +{ + return "DE100/D:DE101:DE102:DE103:DE200:DE201:DE202:DE203:DE300:DE301:DE302:DE303:DE400:DE401:DE402:DE403:DE500:DE501:DE502:DE503:DE504:DE505:DE506:DE507:DE508:DE509:DE510:DE511:DE512:DE513:DE514:DE515:DE516:DE517:DE600:DE601:DE602:DE603:DE604:DE605:DE606:DE607:DE608:DE609:DE610:DE611:DE612:DE613:DE614:DE615:DE616:DE617:DE700:DE701:DE702:DE703:DE704:DE705:DE706:DE707:DE708:DE709:DE710:DE711:DE712:DE713:DE714:DE715:DE716:DE717:DE718:DE719:DE720:DE721:DE722:DE723:DE724:DE725:DE800:DE801:DE802:DE803:DE804:DE805:DE806:DE807:DE808:DE809:DE810:DE811:DE812:DE813:DE814:DE815:DE816:DE817:DE818:DE819:DE820:DE821:DE822:DE823:DE824:DE825:DE900:DE901:DE902:DE903:DE904:DE905:DE906:DE907:DE908:DE909:DE910:DE911:DE912:DE913:DE914:DE915:DE916:DE917:DE918:DE919:DE920:DE921:DE922:DE923:DE924:DE925:DE1000:DE1001:DE1002:DE1003:DE1004:DE1005:DE1006:DE1007:DE1008:DE1009:DE1010:DE1011:DE1012:DE1013:DE1014:DE1015:DE1016:DE1017:DE1018:DE1019:DE1020:DE1021:DE1022:DE1023:DE1024:DE1025:entries"; +} + +Double_t ClusterSizeReductor::getDeValue(int deid, int cathode) +{ + if (deid < 0 || deid >= sDeNum) { + return 0; + } + if (cathode < 0 || cathode > 2) { + return 0; + } + return mStats.deValues[cathode].values[deid]; +} + +void ClusterSizeReductor::update(TObject* obj) +{ + auto h = dynamic_cast(obj); + if (!h) { + return; + } + + for (int xbin = 1; xbin <= h->GetXaxis()->GetNbins(); xbin++) { + if (xbin > (sDeNum * 3)) { + break; + } + int deid = (xbin - 1) / 3; + int cathode = (xbin - 1) % 3; + + TH1F* proj = (TH1F*)h->ProjectionY("_proj", xbin, xbin); + auto clsize = proj->GetMean(); + mStats.deValues[cathode].values[deid] = clsize; + delete proj; + } +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/ClusterSizeTrendsPlotter.cxx b/Modules/MUON/MCH/src/ClusterSizeTrendsPlotter.cxx new file mode 100644 index 0000000000..9eee2e171f --- /dev/null +++ b/Modules/MUON/MCH/src/ClusterSizeTrendsPlotter.cxx @@ -0,0 +1,75 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterSizeTrendsPlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/ClusterSizeTrendsPlotter.h" +#include +#include + +using namespace o2::mch::raw; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +ClusterSizeTrendsPlotter::ClusterSizeTrendsPlotter(std::string path, bool fullPlots) +{ + mReductor = std::make_unique(); + + //-------------------------------------------------- + // Efficiency trends + //-------------------------------------------------- + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int deID = getDEindex(de); + if (deID < 0) { + continue; + } + + mTrends[deID] = std::make_unique(fmt::format("{}{}/ClusterSize_DE{}", path, getHistoPath(de), de), + fmt::format("DE{} Cluster Size", de), "cluster size"); + mTrends[deID]->addGraph("B", "bending "); + mTrends[deID]->addGraph("NB", "non-bending"); + mTrends[deID]->addGraph("BNB", "full "); + mTrends[deID]->addLegends(); + + if (fullPlots) { + addCanvas(mTrends[deID].get(), ""); + } + } +} + +//_________________________________________________________________________________________ + +void ClusterSizeTrendsPlotter::update(long time, TH2F* h) +{ + // extract the integrated average occupancies + mReductor->update(h); + + for (size_t de = 0; de < getNumDE(); de++) { + std::array values; + for (int ci = 0; ci < 3; ci++) { + values[ci] = mReductor->getDeValue(de, ci); + } + mTrends[de]->update(time, values); + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/Clustermap-Display.cxx b/Modules/MUON/MCH/src/Clustermap-Display.cxx new file mode 100644 index 0000000000..cba2758d5b --- /dev/null +++ b/Modules/MUON/MCH/src/Clustermap-Display.cxx @@ -0,0 +1,803 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// @author Victor Valencia + +#include "boost/program_options.hpp" +#include "MCHMappingInterface/CathodeSegmentation.h" +#include "MCHMappingInterface/Segmentation.h" +#include "MCHMappingSegContour/CathodeSegmentationContours.h" +#include "MCHMappingSegContour/CathodeSegmentationSVGWriter.h" +#include "MCHGeometryTransformer/Transformations.h" +#include "TGeoManager.h" +#include "MCHContour/SVGWriter.h" +#include "MCHConstants/DetectionElements.h" +#include "MCHGlobalMapping/ChannelCode.h" +#include "MCHGlobalMapping/DsIndex.h" +#include +#include +#include "TFile.h" +#include "TDirectory.h" +#include "TList.h" +#include "TH1F.h" +#include "TColor.h" +#include "TROOT.h" +#include "TStyle.h" +#include +#include "DetectorsBase/GeometryManager.h" +#include "MCHContour/Polygon.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/MonitorObjectCollection.h" + +using namespace o2::mch::mapping; + +namespace po = boost::program_options; + +// Function with 156 translation offsets to not overlap the deIds of each chamber +std::pair getTranslationOffset(int deId) +{ + + std::map> translationOffsets = { + + // 1st type of Chamber Shape (chambers 1,2,3,4) + { 100, { 5.0, 0.0 } }, + { 101, { -5.0, 0.0 } }, + { 102, { -5.0, 10.0 } }, + { 103, { 5.0, 10.0 } }, + { 200, { 5.0, 0.0 } }, + { 201, { -5.0, 0.0 } }, + { 202, { -5.0, 10.0 } }, + { 203, { 5.0, 10.0 } }, + { 300, { 5.0, 0.0 } }, + { 301, { -5.0, 0.0 } }, + { 302, { -5.0, 10.0 } }, + { 303, { 5.0, 10.0 } }, + { 400, { 5.0, 0.0 } }, + { 401, { -5.0, 0.0 } }, + { 402, { -5.0, 10.0 } }, + { 403, { 5.0, 10.0 } }, + + // 2nd type of Chamber Shape (chambers 5,6) + { 500, { 5.0, 5.0 } }, + { 501, { 5.0, -10.0 } }, + { 502, { 5.0, -20.0 } }, + { 503, { 5.0, -30.0 } }, + { 504, { 5.0, -40.0 } }, + { 505, { 0, -40.0 } }, + { 506, { 0, -30.0 } }, + { 507, { 0, -20.0 } }, + { 508, { 0, -10.0 } }, + { 509, { 0.0, 5.0 } }, + { 510, { 0.0, 20.0 } }, + { 511, { 0.0, 30.0 } }, + { 512, { 0.0, 40.0 } }, + { 513, { 0.0, 50.0 } }, + { 514, { 5.0, 50.0 } }, + { 515, { 5.0, 40.0 } }, + { 516, { 5.0, 30.0 } }, + { 517, { 5.0, 20.0 } }, + { 600, { 5.0, 5.0 } }, + { 601, { 5.0, -10.0 } }, + { 602, { 5.0, -20.0 } }, + { 603, { 5.0, -30.0 } }, + { 604, { 5.0, -40.0 } }, + { 605, { 0, -40.0 } }, + { 606, { 0, -30.0 } }, + { 607, { 0, -20.0 } }, + { 608, { 0, -10.0 } }, + { 609, { 0.0, 5.0 } }, + { 610, { 0.0, 20.0 } }, + { 611, { 0.0, 30.0 } }, + { 612, { 0.0, 40.0 } }, + { 613, { 0.0, 50.0 } }, + { 614, { 5.0, 50.0 } }, + { 615, { 5.0, 40.0 } }, + { 616, { 5.0, 30.0 } }, + { 617, { 5.0, 20.0 } }, + + // 3rd type of Chamber Shape (chambers 7,8,9,10) + { 700, { 3.0, 5.0 } }, + { 701, { 3.0, -10.0 } }, + { 702, { 3.0, -20.0 } }, + { 703, { 3.0, -30.0 } }, + { 704, { 3.0, -50.0 } }, + { 705, { 3, -60.0 } }, + { 706, { 3, -80.0 } }, + { 707, { 0, -80.0 } }, + { 708, { 0, -60.0 } }, + { 709, { 0.0, -50 } }, + { 710, { 0.0, -30.0 } }, + { 711, { 0.0, -20.0 } }, + { 712, { 0.0, -10.0 } }, + { 713, { 0.0, 5.0 } }, + { 714, { 0.0, 20.0 } }, + { 715, { 0.0, 30.0 } }, + { 716, { 0.0, 42.0 } }, + { 717, { 0.0, 64.0 } }, + { 718, { 0.0, 79.0 } }, + { 719, { 0.0, 101.0 } }, + { 720, { 3.0, 101.0 } }, + { 721, { 3.0, 79 } }, + { 722, { 3.0, 64.0 } }, + { 723, { 3.0, 42.0 } }, + { 724, { 3.0, 30 } }, + { 725, { 3.0, 20 } }, + { 800, { 3.0, 5.0 } }, + { 801, { 3.0, -10.0 } }, + { 802, { 3.0, -20.0 } }, + { 803, { 3.0, -30.0 } }, + { 804, { 3.0, -50.0 } }, + { 805, { 3, -60.0 } }, + { 806, { 3, -80.0 } }, + { 807, { 0, -80.0 } }, + { 808, { 0, -60.0 } }, + { 809, { 0.0, -50 } }, + { 810, { 0.0, -30.0 } }, + { 811, { 0.0, -20.0 } }, + { 812, { 0.0, -10.0 } }, + { 813, { 0.0, 5.0 } }, + { 814, { 0.0, 20.0 } }, + { 815, { 0.0, 30.0 } }, + { 816, { 0.0, 42.0 } }, + { 817, { 0.0, 64.0 } }, + { 818, { 0.0, 79.0 } }, + { 819, { 0.0, 101.0 } }, + { 820, { 3.0, 101.0 } }, + { 821, { 3.0, 79 } }, + { 822, { 3.0, 64.0 } }, + { 823, { 3.0, 42.0 } }, + { 824, { 3.0, 30 } }, + { 825, { 3.0, 20 } }, + { 900, { 0.0, 5.0 } }, + { 901, { 0.0, -10.0 } }, + { 902, { 0.0, -20.0 } }, + { 903, { 0.0, -30.0 } }, + { 904, { 0.0, -50.0 } }, + { 905, { 0, -60.0 } }, + { 906, { 0, -80.0 } }, + { 907, { 0, -80.0 } }, + { 908, { 0, -60.0 } }, + { 909, { 0.0, -50 } }, + { 910, { 0.0, -30.0 } }, + { 911, { 0.0, -20.0 } }, + { 912, { 0.0, -10.0 } }, + { 913, { 0.0, 5.0 } }, + { 914, { 0.0, 20.0 } }, + { 915, { 0.0, 30.0 } }, + { 916, { 0.0, 50.0 } }, + { 917, { 0.0, 70.0 } }, + { 918, { 0.0, 90.0 } }, + { 919, { 0.0, 110.0 } }, + { 920, { 0.0, 110.0 } }, + { 921, { 0.0, 90 } }, + { 922, { 0.0, 70 } }, + { 923, { 0.0, 50 } }, + { 924, { 0.0, 30 } }, + { 925, { 0.0, 20 } }, + { 1000, { 0.0, 5.0 } }, + { 1001, { 0.0, -10.0 } }, + { 1002, { 0.0, -20.0 } }, + { 1003, { 0.0, -30.0 } }, + { 1004, { 0.0, -50.0 } }, + { 1005, { 0, -60.0 } }, + { 1006, { 0, -80.0 } }, + { 1007, { 0, -80.0 } }, + { 1008, { 0, -60.0 } }, + { 1009, { 0.0, -50 } }, + { 1010, { 0.0, -30.0 } }, + { 1011, { 0.0, -20.0 } }, + { 1012, { 0.0, -10.0 } }, + { 1013, { 0.0, 5.0 } }, + { 1014, { 0.0, 20.0 } }, + { 1015, { 0.0, 30.0 } }, + { 1016, { 0.0, 50.0 } }, + { 1017, { 0.0, 70.0 } }, + { 1018, { 0.0, 90.0 } }, + { 1019, { 0.0, 110.0 } }, + { 1020, { 0.0, 110.0 } }, + { 1021, { 0.0, 90 } }, + { 1022, { 0.0, 70 } }, + { 1023, { 0.0, 50 } }, + { 1024, { 0.0, 30 } }, + { 1025, { 0.0, 20 } } + + }; + + return translationOffsets[deId]; +} + +// function to transform local contour coordinates to global contour coordinates +std::vector> transformLocalToGlobal(int deId, bool bending, const o2::mch::geo::TransformationCreator& transformation) +{ + + CathodeSegmentation cSeg{ deId, bending }; + + std::vector> dualSampaContoursIn = getDualSampaContours(cSeg); + std::vector> dualSampaContoursOut; + + auto transformDeId = transformation(deId); + + for (int i = 0; i < dualSampaContoursIn.size(); i++) { + + auto dsContourIn = dualSampaContoursIn[i]; + o2::mch::contour::Contour dsContourOut; + + for (auto p = 0; p < dsContourIn.size(); p++) { + + auto polygIn = dsContourIn[p]; // One contour may contains several polygons + std::vector> verticesOut; + + for (auto v = 0; v < polygIn.size(); v++) { + + auto vertexIn = polygIn[v]; // Each polygon contains several vertex + o2::math_utils::Point3D lpos(vertexIn.x, vertexIn.y, 0.0); // local + o2::math_utils::Point3D gpos; // global + + transformDeId.LocalToMaster(lpos, gpos); // Transformations: Rotations + Translations (local --> global) with LocalToMaster + + o2::mch::contour::Vertex vertexOut; + + auto offset = getTranslationOffset(deId); + + vertexOut.y = -gpos.Y(); // Extra reflection to count the DeIds anticlockwise + vertexOut.y += offset.second; + vertexOut.x = gpos.X() + offset.first; + + verticesOut.push_back(vertexOut); + } + + o2::mch::contour::Polygon polygOut(verticesOut.begin(), verticesOut.end()); + dsContourOut.addPolygon(polygOut); + } + + dualSampaContoursOut.push_back(dsContourOut); + } + + return dualSampaContoursOut; +} + +// Getting all deID of a given Chamber +std::vector getAllDeIds(int nChamber) +{ + + std::vector deIds; + int i_1 = *std::move(o2::mch::constants::deId2DeIndex(nChamber * 100)); + int i_2 = *std::move(o2::mch::constants::deId2DeIndex((nChamber + 1) * 100)); + int Chamberlength = i_2 - i_1; + + if (nChamber < 10) { + + // Obtaining DeIds for chambers from 1 to 9 + for (auto i = 0; i < Chamberlength; i++) { + deIds.push_back(o2::mch::constants::deIdsForAllMCH[i + i_1]); + } + } else { + + // Obtaining DeIds for chamber 10 + for (int i = 0; i < 26; i++) { + + deIds.push_back(o2::mch::constants::deIdsForAllMCH[i + i_1]); + } + } + return deIds; +} + +// Getting DualSampas of a given deId +std::vector getDualSampas(int deId) +{ + + std::vector dualSampas; + const o2::mch::mapping::Segmentation& seg = o2::mch::mapping::segmentation(deId); + seg.forEachDualSampa([&dualSampas](int ds) { dualSampas.push_back(ds); }); + return dualSampas; +} + +// Retrieve bending or non-bending DualSampas associated with a given DeId +std::vector getDualSampasBorNB(int deId, bool isBending) +{ + + std::vector dualSampas; + const o2::mch::mapping::Segmentation& seg = o2::mch::mapping::segmentation(deId); + + seg.forEachDualSampa([&dualSampas, isBending, &seg](int dualSampaId) { + bool foundDualSampa = false; + + seg.forEachPad([&foundDualSampa, isBending, dualSampaId, &seg](int dePadIndex) { + if (seg.isBendingPad(dePadIndex) == isBending && seg.padDualSampaId(dePadIndex) == dualSampaId) { + foundDualSampa = true; + return; + } }); + + if (foundDualSampa) { + dualSampas.push_back(dualSampaId); + } + }); + + return dualSampas; +} + +// Convert DsIndex (Global) to DsId (Local) +uint16_t convertDsIndextoDsId(o2::mch::DsIndex dsIndex) +{ + o2::mch::raw::DsDetId dsDetId = o2::mch::getDsDetId(dsIndex); + uint16_t dsId = dsDetId.dsId(); + return dsId; +} + +// Convert from DsId & deId (Local) to DsIndex (Global) +uint16_t getDsIndexFromDsIdAndDeId(uint16_t dsId, uint16_t deId) +{ + o2::mch::raw::DsDetId dsDetId(deId, dsId); // Create a DsDetId object with the given deId and dsId + return o2::mch::getDsIndex(dsDetId); // Get the corresponding dsIndex from the getDsIndex function +} + +// Getting ClustersPerDualSampa (TH1F Histogram stored in root file) +TH1F* getrootHistogramTH1F(const std::string& rootfile) +{ + TFile* file = TFile::Open(rootfile.c_str()); + TH1F* ClustersperDualSampa = (TH1F*)file->Get("ClustersPerDualSampa"); + return ClustersperDualSampa; +} + +TH1F* getrootHistogram(const std::string& rootfile) +{ + TFile* file = TFile::Open(rootfile.c_str()); + TDirectoryFile* dir = dynamic_cast(file->GetDirectory("int")); + TDirectoryFile* mhcDir = dynamic_cast(dir->GetDirectory("MCH")); + + o2::quality_control::core::MonitorObjectCollection* coll = dynamic_cast(mhcDir->FindObjectAny("Clusters")); + o2::quality_control::core::MonitorObject* obj = dynamic_cast(coll->FindObject("ClustersPerDualSampa")); + return dynamic_cast(obj->getObject()); +} + +// Storing clusters and dsindex from TH1F histogram +std::pair, std::vector> processClustersperDualSampa(const TH1F* ClustersperDualSampa) +{ + + std::vector nClusters; + std::vector dsindex; + std::vector dsId; + + for (int i = 1; i <= ClustersperDualSampa->GetNbinsX(); i++) { + int clusters = ClustersperDualSampa->GetBinContent(i); + nClusters.push_back(clusters); + dsindex.push_back(i - 1); + dsId.push_back(convertDsIndextoDsId(i - 1)); + o2::mch::raw::DsDetId dsDetId = o2::mch::getDsDetId(i - 1); + uint16_t currentDsId = dsDetId.dsId(); + uint16_t deId = dsDetId.deId(); + } + + return std::make_pair(nClusters, dsindex); +} + +// Getting transformations from the aligned geometry +o2::mch::geo::TransformationCreator loadGeometry(const std::string& name) +{ + + o2::base::GeometryManager::loadGeometry(name.c_str()); + + auto transformation = o2::mch::geo::transformationFromTGeoManager(*gGeoManager); + + return transformation; +} + +// Creation of Color Gradiant Palette +std::vector colorGradiant() +{ + + std::vector hexcolors; + gStyle->SetPalette(kSunset); + TColor::InvertPalette(); + TArrayI colors = TColor::GetPalette(); + + for (int i = 0; i < colors.GetSize(); i++) { + + Int_t col = colors.At(i); + TColor* tcol = gROOT->GetColor(col); + hexcolors.push_back(tcol->AsHexString()); + } + + TColor::InvertPalette(); + + return hexcolors; +} + +// Calculation of Nmax for two types of normalisation (maximum of clusters per chamber) <--> (maximum ratio of Clusters/DSArea per chamber) +double calculateNmax(int nChamber, bool bending, const TH1F* ClustersperDualSampa, bool IsNormalizedPerDSArea) +{ + + // Load clusters from TH1F Histogram + auto nClusters_dsindex = processClustersperDualSampa(ClustersperDualSampa); + std::vector nClusters = nClusters_dsindex.first; + + // Getting all DeIds for all Chambers + auto deIds = getAllDeIds(nChamber); + + int dsIndex; + double Nmax = 0.0; + std::vector> areas; // Vector with Areas of DS Contours paired with dsIndex + + if (IsNormalizedPerDSArea == false) { + + // Calculate maximum number of clusters + for (auto deId : deIds) { + // Get the map index to dsId + auto dsIds = getDualSampasBorNB(deId, bending); + for (auto dsId : dsIds) { + // Convert local dsId to global dsIndex (for a given deId) + dsIndex = getDsIndexFromDsIdAndDeId(dsId, deId); + // Get maximum number of clusters + Nmax = std::max(Nmax, static_cast(nClusters[dsIndex])); + } + } + } else { + + // Calculate maximum ratio of clusters/DSArea + double maxRatio = 0.0; + double epsilon = 0.1; + + for (auto deId : deIds) { + + CathodeSegmentation cSeg{ deId, bending }; + + // auto dualSampaContoursOut = transformLocalToGlobal(deId, bending, transformation); + // Get the map index to dsId + auto dsIds = getDualSampasBorNB(deId, bending); + for (auto dsId : dsIds) { + o2::mch::contour::Contour dualSampaContour = getDualSampaContour(cSeg, dsId); + double area = 0.0; // Area of a DS Contour + for (const auto& poly : dualSampaContour.getPolygons()) { + area += poly.signedArea(); + } + // Convert local dsId to global dsIndex (for a given deId) + dsIndex = getDsIndexFromDsIdAndDeId(dsId, deId); + areas.push_back(std::make_pair(area, dsIndex)); + // Calculate the ratio of clusters to DS contour area + double ratio = (nClusters[dsIndex] / std::abs(areas.back().first) + epsilon); + maxRatio = std::max(maxRatio, ratio); + } + } + Nmax = maxRatio; + } + + return Nmax; +} + +// Save dsIndex with 0 clusters in a txt.file (useful for the muon reject list) +void saveDsIndexfor0clusters(int nChamber, bool bending, const TH1F* ClustersperDualSampa, std::ofstream& outFile) +{ + // Load clusters from TH1F Histogram + auto nClusters_dsindex = processClustersperDualSampa(ClustersperDualSampa); + std::vector nClusters = nClusters_dsindex.first; + + // Getting all DeIds for all Chambers + auto deIds = getAllDeIds(nChamber); + + int dsIndex; + // Iterate through deIds and dsIds + for (auto deId : deIds) { + auto dsIds = getDualSampasBorNB(deId, bending); + for (auto dsId : dsIds) { + // Convert local dsId to global dsIndex (for a given deId) + dsIndex = getDsIndexFromDsIdAndDeId(dsId, deId); + + // Check if clusters are zero and save dsIndex to the text file + if (nClusters[dsIndex] == 0) { + outFile << "-d " << dsIndex << " "; + } + } + } +} + +// Gradient of Number for the color scale +std::vector numberGradient(double n, int m) +{ + std::vector grad; + double step = n / (m - 1); + for (int i = 0; i < m; ++i) { + grad.push_back(i * step); + } + return grad; +} + +// Add Rentangle with 255 colors + 11 Numbers (from 0 to Nmax) +void addRectangleContour(int nChamber, o2::mch::contour::Contour& contour, o2::mch::contour::SVGWriter& w, double Nmax, bool IsNormalizedPerDSArea) +{ + + double rectWidth; + double rectHeight; + double rectX; + double rectY; + + double step = 1.0 / 10; + double textX; + double textY; + double startY; + double spacing; + + std::string title; + + if (IsNormalizedPerDSArea == false) { + + title = "Nclusters"; + } else { + + title = "#/cm^2"; + }; + + // size of rectangle depending on chamber + if (nChamber == 1 || nChamber == 2) { + rectWidth = 10; + rectHeight = 200; + rectX = 110; + rectY = -96; + + textX = rectX + rectWidth + 5; + startY = 82 + rectY + rectHeight / 2; + spacing = 1 + rectHeight / 11; + w.text(title, textX - 19, startY - 190); + } else if (nChamber == 3 || nChamber == 4) { + rectWidth = 20; + rectHeight = 200; + rectX = 130; + rectY = -94; + + textX = rectX + rectWidth + 5; + startY = 82 + rectY + rectHeight / 2; + spacing = 1 + rectHeight / 11; + w.text(title, textX - 25, startY - 190); + } else if (nChamber == 5 || nChamber == 6) { + rectWidth = 20; + rectHeight = 225; + rectX = 175; + rectY = -20; + + textX = rectX + rectWidth + 5; + startY = 90 + rectY + rectHeight / 2; + spacing = 0.7 + rectHeight / 11; + w.text(title, textX - 25, startY - 210); + } else if (nChamber == 7 || nChamber == 8) { + rectWidth = 30; + rectHeight = 325; + rectX = 270; + rectY = -20; + + textX = rectX + rectWidth + 5; + startY = 130 + rectY + rectHeight / 2; + spacing = 1.5 + rectHeight / 11; + w.text(title, textX - 30, startY - 300); + } else if (nChamber == 9 || nChamber == 10) { + rectWidth = 35; + rectHeight = 325; + rectX = 270; + rectY = -20; + + textX = rectX + rectWidth + 5; + startY = 129 + rectY + rectHeight / 2; + spacing = 1.5 + rectHeight / 11; + w.text(title, textX - 30, startY - 300); + } + + double stepX = rectWidth / 255.0; + double stepY = rectHeight / 255.0; + + for (int i = 0; i < 255; i++) { + + double startY = rectY + i * stepY; + double endY = rectY + (i + 1) * stepY; + + double startX = rectX; + double endX = rectX + rectWidth; + + contour.addPolygon({ { startX, startY }, + { endX, startY }, + { endX, endY }, + { startX, endY } }); + } + + std::vector numbers = numberGradient(Nmax, 11); + double n; + + // Produce numbers for the color scale + for (int i = 10; i >= 0; i--) { + + std::stringstream ss; + n = i * step; + double textY = startY + (1 - i) * spacing; + ss << std::fixed << std::setprecision(1) << numbers[i]; + std::string numberString = ss.str(); + w.text(numberString, textX, textY); + } +} + +// Creating Chambers in SVG format +void svgChamber(o2::mch::contour::SVGWriter& w, int nChamber, bool bending, const TH1F* ClustersperDualSampa, o2::mch::geo::TransformationCreator transformation, bool IsNormalizedPerDSArea, bool WhiteOrGreen) +{ + + // Load clusters from TH1F Histogram + auto nClusters_dsindex = processClustersperDualSampa(ClustersperDualSampa); + std::vector nClusters = nClusters_dsindex.first; + std::vector dsindex = nClusters_dsindex.second; + + // Colors Vector in HEX RBG format + std::vector colors = colorGradiant(); + + // Color for 0 clusters + std::string ColorFor0clusters; + if (WhiteOrGreen) { + ColorFor0clusters = "#00FF00"; // Green + } else { + ColorFor0clusters = "#FFFFFF"; // White + } + + // Getting all deIds for all Chambers + auto deIds = getAllDeIds(nChamber); + + double epsilon = 0.01; + + int colorId; + + std::vector> areas; // Vector with Areas of DS Contours paired with dsIndex + + double Nmax = calculateNmax(nChamber, bending, ClustersperDualSampa, IsNormalizedPerDSArea); // Maximun Ratio + + // All DeId transformated + SVGWRITER of all chambers + for (auto deId : deIds) { + auto dualSampaContoursOut = transformLocalToGlobal(deId, bending, transformation); + w.svgGroupStart("dualsampas"); + std::string str = ".dualsampas { fill:"; + str += colors[0]; // Assign the first color in the vector + str += "; stroke-width: 0.25px; stroke: #333333;}"; + w.addStyle(str); + + auto dsIds = getDualSampasBorNB(deId, bending); // Corresponding all DSIndex to DsId + + for (auto i = 0; i < dualSampaContoursOut.size(); i++) { + + auto& contour = dualSampaContoursOut[i]; + double area = 0.0; + + for (const auto& poly : contour.getPolygons()) { + area += poly.signedArea(); + } + + auto dsId = dsIds[i]; + int dsIndex = getDsIndexFromDsIdAndDeId(dsId, deId); // Corresponding DsId and DeId to DSIndex + areas.push_back(std::make_pair(area, dsIndex)); + + // Select the type of Normalisation (Clusters/NClustersMax <--> Clusters/DSArea) + if (IsNormalizedPerDSArea == true) { + colorId = int((nClusters[dsIndex] / std::abs(areas.back().first)) / (Nmax + epsilon) * colors.size()); + } else { + colorId = int(nClusters[dsIndex] / (Nmax + epsilon) * colors.size()); + } + + if (nClusters[dsIndex] == 0) { + w.contour(dualSampaContoursOut[i], ColorFor0clusters); + } else { + w.contour(dualSampaContoursOut[i], colors[colorId]); + } + } + + w.svgGroupEnd(); + } + + // Add rectangle to display the color scale with numbers (0 to Nmax) + o2::mch::contour::Contour rectangleContour; + addRectangleContour(nChamber, rectangleContour, w, Nmax, IsNormalizedPerDSArea); + + for (auto i = 0; i < rectangleContour.size(); i++) { + + w.polygon(rectangleContour[i], colors[rectangleContour.size() - 1 - i]); + } +} + +int main(int argc, char* argv[]) +{ + + // Generating various command arguments + bool norm = false; + bool green = false; + bool dsindexof0clusters = false; + std::string rootfileleft = "DATA_QC.root"; + std::string rootfileright = "MC_QC.root"; + + po::variables_map vm; + po::options_description generic("Generic options"); + + generic.add_options()("help", "produce help message")("normperarea", "normalize per unit area")("dslist", "save all dsIndex with 0clusters in a txt file")("green", "green color for 0 clusters")("rootfileleft", po::value(&rootfileleft), "select root file for the left chamber")("rootfileright", po::value(&rootfileright), "select root file for the right chamber"); + + po::options_description cmdline; + cmdline.add(generic); + + po::store(po::command_line_parser(argc, argv).options(cmdline).run(), vm); + po::notify(vm); + + if (vm.count("help")) { + std::cout << generic << "\n"; + return 2; + } + + if (vm.count("normperarea")) { + norm = true; + } + + if (vm.count("green")) { + green = true; + } + + if (vm.count("dslist")) { + dsindexof0clusters = true; + } + + // Define the bounding boxes for the 10 images: + std::vector> bboxes = { + { -175, -175, 175, 175 }, + { -175, -175, 175, 175 }, + { -200, -200, 200, 200 }, + { -200, -200, 200, 200 }, + { -260, -260, 260, 260 }, + { -260, -260, 260, 260 }, + { -360, -360, 360, 360 }, + { -450, -450, 450, 450 }, + { -450, -450, 450, 450 }, + { -450, -450, 450, 450 } + }; + + // Load the aligned geometry from root file + std::string alignedgeom = "o2sim_geometry-aligned.root"; + + auto loadGeom = loadGeometry(alignedgeom); + + // Creating directory to save all output files + std::string directory = "output/"; + mkdir(directory.c_str(), 0777); // 0777 sets permissions; + + std::ofstream outFile; + + if (dsindexof0clusters) { + // Open a text file to save dsIndex with 0 clusters + outFile.open(directory + "dsIndex_with_0_clusters.txt"); + } + + // Generate two sets (left and right) of 10 bending and non-bending chambers using SVGWriter + for (auto isBendingPlane : { true, false }) { + + for (int i = 0; i < 10; i++) { + + // output directory with files + std::string filename = "CHAMBERS-" + std::to_string(i + 1) + "-" + (isBendingPlane ? "B" : "NB") + ".html"; + std::ofstream outv(directory + filename); + + // Creating bboxes + o2::mch::contour::SVGWriter wSegLeft(bboxes[i]); + o2::mch::contour::SVGWriter wSegRight(bboxes[i]); + + if (dsindexof0clusters) { + + saveDsIndexfor0clusters(i + 1, isBendingPlane, getrootHistogram(rootfileleft), outFile); + } + + svgChamber(wSegLeft, i + 1, isBendingPlane, getrootHistogram(rootfileleft), loadGeom, norm, green); + svgChamber(wSegRight, i + 1, isBendingPlane, getrootHistogram(rootfileright), loadGeom, norm, green); + + // Write in HTML left and right chambers (using
tag) + outv << "
" << std::endl; + wSegLeft.writeHTML(outv); + outv << "
" << std::endl; + wSegRight.writeHTML(outv); + outv << "
" << std::endl; + } + } + return 0; +} \ No newline at end of file diff --git a/Modules/MUON/MCH/src/ClustersCheck.cxx b/Modules/MUON/MCH/src/ClustersCheck.cxx new file mode 100644 index 0000000000..851df5102d --- /dev/null +++ b/Modules/MUON/MCH/src/ClustersCheck.cxx @@ -0,0 +1,271 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClustersCheck.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/ClustersCheck.h" +#include "MUONCommon/Helpers.h" +#include "QualityControl/MonitorObject.h" + +#include +#include + +// ROOT +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control; +using namespace o2::quality_control_modules::muon; + +namespace o2::quality_control_modules::muonchambers +{ + +void ClustersCheck::configure() +{ +} + +void ClustersCheck::startOfActivity(const Activity& activity) +{ + mMinClustersPerTrack = getConfigurationParameter(mCustomParameters, "minClustersPerTrack", mMinClustersPerTrack, activity); + mMaxClustersPerTrack = getConfigurationParameter(mCustomParameters, "maxClustersPerTrack", mMaxClustersPerTrack, activity); + mMinClustersPerChamber = getConfigurationParameter(mCustomParameters, "minClustersPerChamber", mMinClustersPerChamber, activity); + mClustersPerChamberRangeMin = getConfigurationParameter(mCustomParameters, "clustersPerChamberRangeMin", mClustersPerChamberRangeMin, activity); + mClustersPerChamberRangeMax = getConfigurationParameter(mCustomParameters, "clustersPerChamberRangeMax", mClustersPerChamberRangeMax, activity); + + mMinClusterSize = getConfigurationParameter(mCustomParameters, "minClusterSize", mMinClusterSize, activity); + mClusterSizeRangeMin = getConfigurationParameter(mCustomParameters, "clusterSizeRangeMin", mClusterSizeRangeMin, activity); + mClusterSizeRangeMax = getConfigurationParameter(mCustomParameters, "clusterSizeRangeMax", mClusterSizeRangeMax, activity); + + mMarkerSize = getConfigurationParameter(mCustomParameters, "markerSize", mMarkerSize, activity); + + mQualities.clear(); +} + +Quality ClustersCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + + // number of clusters per track + if (mo->getName().find("ClustersPerTrack") != std::string::npos) { + TH1F* h = dynamic_cast(mo->getObject()); + if (!h) { + continue; + } + + // quality flags initialization + mQualities[mo->getName()] = Quality::Good; + if (result == Quality::Null) { + result.set(Quality::Good); + } + + // check that the average number of clusters per tracks is within given limits + if (h->GetMean() < mMinClustersPerTrack || h->GetMean() > mMaxClustersPerTrack) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::BadTracking(), "Number of clusters per track not in the expected range"); + mQualities[mo->getName()] = Quality::Bad; + } + } + + // average number of clusters per chamber + if (mo->getName().find("ClustersPerChamber") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + continue; + } + + mQualities[mo->getName()] = Quality::Good; + if (result == Quality::Null) { + result.set(Quality::Good); + } + + for (int bin = 1; bin <= h->GetXaxis()->GetNbins(); bin++) { + double nclus = h->GetBinContent(bin); + if (nclus < mMinClustersPerChamber) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::BadTracking(), fmt::format("Too few clusters in CH{}", bin)); + mQualities[mo->getName()] = Quality::Bad; + } + } + } + + // average cluster size per chamber + if (mo->getName().find("ClusterSizePerChamber") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + continue; + } + + mQualities[mo->getName()] = Quality::Good; + if (result == Quality::Null) { + result.set(Quality::Good); + } + + for (int bin = 1; bin <= h->GetXaxis()->GetNbins(); bin++) { + double nclus = h->GetBinContent(bin); + if (nclus < mMinClusterSize) { + result.set(Quality::Bad); + result.addFlag(FlagTypeFactory::BadTracking(), fmt::format("Too small cluster size in CH{}", bin)); + mQualities[mo->getName()] = Quality::Bad; + } + } + } + } + + return result; +} + +// get the ROOT color code associated to a given quality level +static int getQualityColor(Quality q) +{ + int result = 0; + if (q == Quality::Null) { + result = kViolet - 6; + } + if (q == Quality::Bad) { + result = kRed; + } + if (q == Quality::Medium) { + result = kOrange - 3; + } + if (q == Quality::Good) { + result = kGreen + 2; + } + + return result; +} + +// add a text label to display the quality associated with a plot +static void drawQualityLabel(TH1* h, const std::string& label, int color, float x1, float y1, float x2, float y2, int align) +{ + if (!h) { + return; + } + + auto* t = new TPaveText(x1, y1, x2, y2, "brNDC"); + t->AddText(label.c_str()); + t->SetBorderSize(0); + t->SetFillStyle(0); + t->SetTextAlign(align); + t->SetTextColor(color); + t->SetTextFont(42); + h->GetListOfFunctions()->Add(t); +} + +/// Add a marker to an histogram at a given position +/// The marker is draw with a TPolyLine such that it scales nicely with the size of the pad +/// The default dimensions of the marker are +/// * horizontal: 1/20 of the X-axis range +/// * vertical: 1/10 of the histogram values range +/// Parameters: +/// * histo: the histogram to which the marker is associated +/// * x0, y0: coordinates of the tip of the marker +/// * color: ROOT index of the marker fill color +/// * markerSize: overall scaling factor for the marker dimensions +static TPolyLine* addMarker(TH1& histo, double x0, double y0, int color, float markerSize) +{ + double xSize = (histo.GetXaxis()->GetXmax() - histo.GetXaxis()->GetXmin()) / 20; + double ySize = (histo.GetMaximum() - histo.GetMinimum()) / 10; + double xMarker[4] = { x0, x0 - xSize * markerSize / 2, x0 + xSize * markerSize / 2, x0 }; + double yMarker[4] = { y0, y0 + ySize * markerSize, y0 + ySize * markerSize, y0 }; + auto* m = new TPolyLine(4, xMarker, yMarker); + m->SetNDC(kFALSE); + m->SetFillColor(color); + m->SetOption("f"); + m->SetLineWidth(0); + histo.GetListOfFunctions()->Add(m); + return m; +} + +void ClustersCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + Quality moQuality = Quality::Null; + if (mQualities.count(mo->getName()) > 0) { + moQuality = mQualities[mo->getName()]; + } + + int color = getQualityColor(moQuality); + + if (mo->getName().find("ClustersPerTrack") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + + // get the maximum of the histogram + double max = h->GetBinContent(h->GetMaximumBin()); + + // adjust the vertical scale to provide some space on the top + h->SetMinimum(0); + h->SetMaximum(1.2 * max); + + // add vertical lines to display the acceptable range + addVerticalLine(*h, mMinClustersPerTrack, color, kDashed, 1); + addVerticalLine(*h, mMaxClustersPerTrack, color, kDashed, 1); + + // add a marker at the position of the histogram mean + addMarker(*h, h->GetMean(), max * 1.05, color, mMarkerSize); + + // draw the quality flag associated to the plot + drawQualityLabel(dynamic_cast(mo->getObject()), moQuality.getName(), color, 0.5, 0.75, 0.88, 0.88, 33); + } + + if (mo->getName().find("ClustersPerChamber") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + + // adjust the vertical scale to provide some space on the top + h->SetMinimum(std::min(h->GetBinContent(h->GetMinimumBin()), + mClustersPerChamberRangeMin)); + h->SetMaximum(std::max(h->GetBinContent(h->GetMaximumBin()), + mClustersPerChamberRangeMax)); + h->SetMarkerStyle(kCircle); + h->SetDrawOption("EP"); + + // draw horizontal threshold + addHorizontalLine(*h, mMinClustersPerChamber, color, kDashed, 1); + + // draw the quality flag associated to the plot + drawQualityLabel(dynamic_cast(mo->getObject()), moQuality.getName(), color, 0.5, 0.75, 0.88, 0.88, 33); + } + + if (mo->getName().find("ClusterSizePerChamber") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + + // adjust the vertical scale to provide some space on the top + h->SetMinimum(std::min(h->GetBinContent(h->GetMinimumBin()), + mClusterSizeRangeMin)); + h->SetMaximum(std::max(h->GetBinContent(h->GetMaximumBin()), + mClusterSizeRangeMax)); + h->SetMarkerStyle(kCircle); + h->SetDrawOption("EP"); + + // draw horizontal threshold + addHorizontalLine(*h, mMinClusterSize, color, kDashed, 1); + + // draw the quality flag associated to the plot + drawQualityLabel(dynamic_cast(mo->getObject()), moQuality.getName(), color, 0.5, 0.75, 0.88, 0.88, 33); + } +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/ClustersTask.cxx b/Modules/MUON/MCH/src/ClustersTask.cxx new file mode 100644 index 0000000000..c2b271477f --- /dev/null +++ b/Modules/MUON/MCH/src/ClustersTask.cxx @@ -0,0 +1,216 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "MCH/ClustersTask.h" + +#include "MCHGlobalMapping/DsIndex.h" +#include "MUONCommon/HistPlotter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "QualityControl/QcInfoLogger.h" + +namespace +{ + +static void setXAxisLabels(TProfile* h) +{ + TAxis* axis = h->GetXaxis(); + for (int i = 1; i <= 10; i++) { + auto label = fmt::format("CH{}", i); + axis->SetBinLabel(i, label.c_str()); + } +} + +std::array getClustersPerChamber(gsl::span clusters) +{ + std::array clustersPerChamber; + clustersPerChamber.fill(0); + for (const auto& cluster : clusters) { + int chamberId = cluster.getChamberId(); + clustersPerChamber[chamberId]++; + } + return clustersPerChamber; +} + +} // namespace + +namespace o2::quality_control_modules::muonchambers +{ + +ClustersTask::ClustersTask() +{ +} + +ClustersTask::~ClustersTask() = default; + +void ClustersTask::createClusterHistos() +{ + std::string drawOptions; + std::string displayHints; + + using o2::quality_control_modules::muon::HistPlotter; + auto& histograms = mHistPlotter.histograms(); + + mNofClustersPerTrack = std::make_unique("ClustersPerTrack", "Number of clusters per track;Mean number of clusters per track", 20, 0, 20); + mNofClustersPerTrack->SetStats(0); + mNofClustersPerDualSampa = std::make_unique("ClustersPerDualSampa", "Number of clusters per dual sampa;Number of clusters per DS", o2::mch::NumberOfDualSampas, 0, o2::mch::NumberOfDualSampas - 1); + + histograms.emplace_back(HistPlotter::HistInfo{ mNofClustersPerTrack.get(), drawOptions, displayHints }); + histograms.emplace_back(HistPlotter::HistInfo{ mNofClustersPerDualSampa.get(), drawOptions, displayHints }); + + mNofClustersPerChamber = std::make_unique("ClustersPerChamber", "Clusters per chamber;;Number of clusters", 10, 1, 11); + mNofClustersPerChamber->SetStats(0); + setXAxisLabels(mNofClustersPerChamber.get()); + mClusterSizePerChamber = std::make_unique("ClusterSizePerChamber", "Cluster size per chamber;;Mean number of pads per cluster", 10, 1, 11); + mClusterSizePerChamber->SetStats(0); + setXAxisLabels(mClusterSizePerChamber.get()); + + histograms.emplace_back(HistPlotter::HistInfo{ mNofClustersPerChamber.get(), drawOptions, displayHints }); + histograms.emplace_back(HistPlotter::HistInfo{ mClusterSizePerChamber.get(), drawOptions, displayHints }); + + for (int s = 0; s < 5; s++) { + mClusterSizeDistributionPerStation[s] = std::make_unique(TString::Format("ClusterSizeDistribution_ST%d", (s + 1)), + TString::Format("Cluster size distribution - ST%d", (s + 1)), + 100, 0, 100); + histograms.emplace_back(HistPlotter::HistInfo{ mClusterSizeDistributionPerStation[s].get(), drawOptions, displayHints }); + } + + mHistPlotter.publish(getObjectsManager()); +} + +void ClustersTask::initialize(o2::framework::InitContext& /*ic*/) +{ + ILOG(Debug, Devel) << "initialize ClustersTask" << ENDM; + + createClusterHistos(); + + mDet2ElecMapper = o2::mch::raw::createDet2ElecMapper(); + mSolar2FeeLinkMapper = o2::mch::raw::createSolar2FeeLinkMapper(); +} + +void ClustersTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity : " << activity.mId << ENDM; +} + +void ClustersTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ClustersTask::fillClusterHistos(gsl::span clusters) +{ + if (mTransformation.get() == nullptr) { + // should not happen, but better be safe than sorry + return; + } + + auto clustersPerChamber = getClustersPerChamber(clusters); + + for (auto i = 0; i < clustersPerChamber.size(); i++) { + mNofClustersPerChamber->Fill(i + 1, clustersPerChamber[i]); + } + + for (const auto& cluster : clusters) { + int deId = cluster.getDEId(); + const o2::mch::mapping::Segmentation& seg = o2::mch::mapping::segmentation(deId); + int b, nb; + + o2::math_utils::Point3D global{ cluster.getX(), + cluster.getY(), cluster.getZ() }; + auto t = (*mTransformation)(deId).Inverse(); + auto local = t(global); + + seg.findPadPairByPosition(local.X(), local.Y(), b, nb); + + if (b >= 0) { + int dsId = seg.padDualSampaId(b); + mNofClustersPerDualSampa->Fill(o2::mch::getDsIndex(o2::mch::raw::DsDetId(deId, dsId))); + } + if (nb >= 0) { + int dsId = seg.padDualSampaId(nb); + mNofClustersPerDualSampa->Fill(o2::mch::getDsIndex(o2::mch::raw::DsDetId(deId, dsId))); + } + int chamberId = cluster.getChamberId(); + mClusterSizePerChamber->Fill(chamberId + 1, cluster.nDigits); + mNofClustersPerChamber->Fill(chamberId + 1, 1.0); + int stationId = chamberId / 2; + if (stationId >= 0 && stationId < 5) { + mClusterSizeDistributionPerStation[stationId]->Fill(cluster.nDigits); + } + } +} + +bool ClustersTask::assertInputs(o2::framework::ProcessingContext& ctx) +{ + if (!ctx.inputs().isValid("tracks")) { + ILOG(Info, Support) << "no mch tracks available on input" << ENDM; + return false; + } + if (!ctx.inputs().isValid("trackrofs")) { + ILOG(Info, Support) << "no mch track rofs available on input" << ENDM; + return false; + } + if (!ctx.inputs().isValid("trackclusters")) { + ILOG(Info, Support) << "no mch track clusters available on input" << ENDM; + return false; + } + return true; +} + +void ClustersTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + if (!assertInputs(ctx)) { + return; + } + + if (mTransformation.get() == nullptr && gGeoManager != nullptr) { + ILOG(Info, Support) << "get transformation from gGeoManager" << ENDM; + mTransformation = std::make_unique(o2::mch::geo::transformationFromTGeoManager(*gGeoManager)); + } + + auto tracks = ctx.inputs().get>("tracks"); + auto rofs = ctx.inputs().get>("trackrofs"); + auto clusters = ctx.inputs().get>("trackclusters"); + + decltype(tracks.size()) nok{ 0 }; + + for (const auto& track : tracks) { + const auto trackClusters = clusters.subspan(track.getFirstClusterIdx(), track.getNClusters()); + mNofClustersPerTrack->Fill(trackClusters.size()); + fillClusterHistos(trackClusters); + } +} + +void ClustersTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ClustersTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ClustersTask::reset() +{ + ILOG(Warning, Support) << "reset" << ENDM; + mHistPlotter.reset(); +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/DecodingCheck.cxx b/Modules/MUON/MCH/src/DecodingCheck.cxx new file mode 100644 index 0000000000..bcd915a4fc --- /dev/null +++ b/Modules/MUON/MCH/src/DecodingCheck.cxx @@ -0,0 +1,542 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DecodingCheck.cxx +/// \author Andrea Ferrero, Sebastien Perrin +/// + +#include "MCHMappingInterface/Segmentation.h" +#include "MCH/DecodingCheck.h" +#include "MCH/Helpers.h" +#include "MUONCommon/Helpers.h" +#include "QualityControl/ReferenceUtils.h" +#include "QualityControl/QcInfoLogger.h" + +// ROOT +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control_modules::muon; + +namespace o2::quality_control_modules::muonchambers +{ + +void DecodingCheck::configure() +{ +} + +void DecodingCheck::startOfActivity(const Activity& activity) +{ + mGoodFracHistName = getConfigurationParameter(mCustomParameters, "GoodFracHistName", mGoodFracHistName, activity); + mSyncFracHistName = getConfigurationParameter(mCustomParameters, "SyncFracHistName", mSyncFracHistName, activity); + + mGoodFracPerSolarHistName = getConfigurationParameter(mCustomParameters, "GoodFracPerSolarHistName", mGoodFracPerSolarHistName, activity); + mSyncFracPerSolarHistName = getConfigurationParameter(mCustomParameters, "SyncFracPerSolarHistName", mSyncFracPerSolarHistName, activity); + + mGoodFracRefCompHistName = getConfigurationParameter(mCustomParameters, "GoodFracRefCompHistName", mGoodFracRefCompHistName, activity); + mSyncFracRefCompHistName = getConfigurationParameter(mCustomParameters, "SyncFracRefCompHistName", mSyncFracRefCompHistName, activity); + + mGoodFracPerSolarRefCompHistName = getConfigurationParameter(mCustomParameters, "GoodFracPerSolarRefCompHistName", mGoodFracPerSolarRefCompHistName, activity); + mSyncFracPerSolarRefCompHistName = getConfigurationParameter(mCustomParameters, "SyncFracPerSolarRefCompHistName", mSyncFracPerSolarRefCompHistName, activity); + + getThresholdsPerStation(mCustomParameters, activity, "MinGoodErrorFrac", mMinGoodErrorFracPerStation, mMinGoodErrorFrac); + mMinGoodErrorFracRatio = getConfigurationParameter(mCustomParameters, "MinGoodErrorFracRatio", mMinGoodErrorFracRatio, activity); + + mMinGoodErrorFracPerSolar = getConfigurationParameter(mCustomParameters, "MinGoodErrorFracPerSolar", mMinGoodErrorFracPerSolar, activity); + mMinGoodErrorFracRatioPerSolar = getConfigurationParameter(mCustomParameters, "MinGoodErrorFracRatioPerSolar", mMinGoodErrorFracRatioPerSolar, activity); + + getThresholdsPerStation(mCustomParameters, activity, "MinGoodSyncFrac", mMinGoodSyncFracPerStation, mMinGoodSyncFrac); + mMinGoodSyncFracRatio = getConfigurationParameter(mCustomParameters, "MinGoodSyncFracRatio", mMinGoodSyncFracRatio, activity); + + mMinGoodSyncFracPerSolar = getConfigurationParameter(mCustomParameters, "MinGoodSyncFracPerSolar", mMinGoodSyncFracPerSolar, activity); + mMinGoodSyncFracRatioPerSolar = getConfigurationParameter(mCustomParameters, "MinGoodSyncFracRatioPerSolar", mMinGoodSyncFracRatioPerSolar, activity); + + mMinHeartBeatRate = getConfigurationParameter(mCustomParameters, "MinHeartBeatRate", mMinHeartBeatRate, activity); + mMaxHeartBeatRate = getConfigurationParameter(mCustomParameters, "MaxHeartBeatRate", mMaxHeartBeatRate, activity); + + mGoodFracRatioPlotRange = getConfigurationParameter(mCustomParameters, "GoodFracRatioPlotRange", mGoodFracRatioPlotRange, activity); + mGoodFracRatioPerSolarPlotRange = getConfigurationParameter(mCustomParameters, "GoodFracRatioPerSolarPlotRange", mGoodFracRatioPerSolarPlotRange, activity); + + mSyncFracRatioPlotRange = getConfigurationParameter(mCustomParameters, "SyncFracRatioPlotRange", mSyncFracRatioPlotRange, activity); + mSyncFracRatioPerSolarPlotRange = getConfigurationParameter(mCustomParameters, "SyncFracRatioPerSolarPlotRange", mSyncFracRatioPerSolarPlotRange, activity); + + mMaxBadST12 = getConfigurationParameter(mCustomParameters, "MaxBadDE_ST12", mMaxBadST12, activity); + mMaxBadST345 = getConfigurationParameter(mCustomParameters, "MaxBadDE_ST345", mMaxBadST345, activity); + mQualityChecker.mMaxBadST12 = mMaxBadST12; + mQualityChecker.mMaxBadST345 = mMaxBadST345; +} + +Quality DecodingCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + std::array errorsQuality; + std::fill(errorsQuality.begin(), errorsQuality.end(), Quality::Null); + std::array syncQuality; + std::fill(syncQuality.begin(), syncQuality.end(), Quality::Null); + + mQualityChecker.reset(); + std::fill(mSolarQuality.begin(), mSolarQuality.end(), Quality::Good); + + for (auto& [moName, mo] : *moMap) { + + if (matchHistName(mo->getName(), mGoodFracHistName)) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return result; + } + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int chamberId = (de - 100) / 100; + int stationId = chamberId / 2; + + int deId = getDEindex(de); + if (deId < 0) { + continue; + } + + auto minGoodErrorFrac = mMinGoodErrorFrac; + if (stationId >= 0 && stationId < 5) { + if (mMinGoodErrorFracPerStation[stationId]) { + minGoodErrorFrac = mMinGoodErrorFracPerStation[stationId].value(); + } + } + + double val = h->GetBinContent(deId + 1); + if (val < minGoodErrorFrac) { + errorsQuality[deId] = Quality::Bad; + } else { + errorsQuality[deId] = Quality::Good; + } + } + mQualityChecker.addCheckResult(errorsQuality); + } + + if (matchHistName(mo->getName(), mGoodFracPerSolarHistName)) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return result; + } + + for (int solar = 0; solar < h->GetXaxis()->GetNbins(); solar++) { + int bin = solar + 1; + double value = h->GetBinContent(bin); + if (value >= mMinGoodErrorFracPerSolar) { + continue; + } + mSolarQuality[solar] = Quality::Bad; + } + } + + if (matchHistName(mo->getName(), mSyncFracHistName)) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return result; + } + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int chamberId = (de - 100) / 100; + int stationId = chamberId / 2; + + int deId = getDEindex(de); + if (deId < 0) { + continue; + } + + auto minGoodSyncFrac = mMinGoodSyncFrac; + if (stationId >= 0 && stationId < 5) { + if (mMinGoodSyncFracPerStation[stationId]) { + minGoodSyncFrac = mMinGoodSyncFracPerStation[stationId].value(); + } + } + + double val = h->GetBinContent(deId + 1); + if (val < minGoodSyncFrac) { + syncQuality[deId] = Quality::Bad; + } else { + syncQuality[deId] = Quality::Good; + } + } + mQualityChecker.addCheckResult(syncQuality); + } + + if (matchHistName(mo->getName(), mSyncFracPerSolarHistName)) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return result; + } + + for (int solar = 0; solar < h->GetXaxis()->GetNbins(); solar++) { + int bin = solar + 1; + double value = h->GetBinContent(bin); + if (value >= mMinGoodSyncFracPerSolar) { + continue; + } + mSolarQuality[solar] = Quality::Bad; + } + } + } + return mQualityChecker.getQuality(); +} + +void DecodingCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if ((mo->getName().find("RefComp/") != std::string::npos)) { + TCanvas* canvas = dynamic_cast(mo->getObject()); + if (!canvas) { + return; + } + + auto ratioPlot = o2::quality_control::checker::getRatioPlotFromCanvas(canvas); + if (!ratioPlot) { + return; + } + + std::string messages; + auto refCompPlots = o2::quality_control::checker::getPlotsFromCanvas(canvas, messages); + + double ratioPlotRange{ -1 }; + double ratioThreshold{ -1 }; + bool isSolar{ false }; + + if (matchHistName(mo->getName(), mGoodFracRefCompHistName)) { + ratioPlotRange = mGoodFracRatioPlotRange; + ratioThreshold = mMinGoodErrorFracRatio; + isSolar = false; + } else if (matchHistName(mo->getName(), mGoodFracPerSolarRefCompHistName)) { + ratioPlotRange = mGoodFracRatioPerSolarPlotRange; + ratioThreshold = mMinGoodErrorFracRatioPerSolar; + isSolar = true; + } else if (matchHistName(mo->getName(), mSyncFracRefCompHistName)) { + ratioPlotRange = mSyncFracRatioPlotRange; + ratioThreshold = mMinGoodSyncFracRatio; + isSolar = false; + } else if (matchHistName(mo->getName(), mSyncFracPerSolarRefCompHistName)) { + ratioPlotRange = mSyncFracRatioPerSolarPlotRange; + ratioThreshold = mMinGoodSyncFracRatioPerSolar; + isSolar = true; + } + + if (ratioPlotRange < 0) { + return; + } + + ratioPlot->SetMinimum(1.0 - ratioPlotRange); + ratioPlot->SetMaximum(1.0 + ratioPlotRange); + ratioPlot->GetXaxis()->SetTickLength(0); + + if (isSolar) { + addChamberDelimitersToSolarHistogram(ratioPlot); + addSolarBinLabels(ratioPlot); + } else { + addChamberDelimiters(ratioPlot); + addDEBinLabels(ratioPlot); + } + drawThreshold(ratioPlot, ratioThreshold); + + if (refCompPlots.first) { + refCompPlots.first->SetMinimum(0); + refCompPlots.first->SetMaximum(1.05); + if (isSolar) { + addChamberDelimitersToSolarHistogram(refCompPlots.first); + addChamberLabelsForSolar(refCompPlots.first); + addSolarBinLabels(refCompPlots.first); + if (refCompPlots.second) { + addSolarBinLabels(refCompPlots.second); + } + } else { + addChamberDelimiters(refCompPlots.first); + addChamberLabelsForDE(refCompPlots.first); + addDEBinLabels(refCompPlots.first); + if (refCompPlots.second) { + addDEBinLabels(refCompPlots.second); + } + } + } + + if (refCompPlots.second) { + if (checkResult == Quality::Good) { + refCompPlots.second->SetLineColor(kGreen + 2); + } else if (checkResult == Quality::Bad) { + refCompPlots.second->SetLineColor(kRed); + } else if (checkResult == Quality::Medium) { + refCompPlots.second->SetLineColor(kOrange - 3); + } else if (checkResult == Quality::Null) { + refCompPlots.second->SetLineColor(kViolet - 6); + } + } + return; + } + + if (mo->getName().find("DecodingErrorsPerDE") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + + // disable ticks on horizontal axis + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + + TText* xtitle = new TText(); + xtitle->SetNDC(); + xtitle->SetText(0.87, 0.05, "chamber #"); + xtitle->SetTextSize(15); + h->GetListOfFunctions()->Add(xtitle); + + float scaleMin = h->GetYaxis()->GetXmin(); + float scaleMax = h->GetYaxis()->GetXmax(); + + // draw chamber delimiters + float xpos = 0; + for (int ch = 1; ch <= 9; ch++) { + xpos += getNumDEinChamber(ch - 1); + TLine* delimiter = new TLine(xpos, scaleMin, xpos, scaleMax); + delimiter->SetLineColor(kBlack); + delimiter->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(delimiter); + } + + // draw x-axis labels + xpos = 0; + for (int ch = 0; ch <= 9; ch += 1) { + float x1 = xpos; + xpos += getNumDEinChamber(ch); + float x2 = (ch < 9) ? xpos : h->GetXaxis()->GetXmax(); + float x0 = 0.8 * (x1 + x2) / (2 * h->GetXaxis()->GetXmax()) + 0.1; + float y0 = 0.08; + TText* label = new TText(); + label->SetNDC(); + label->SetText(x0, y0, TString::Format("%d", ch + 1)); + label->SetTextSize(15); + label->SetTextAlign(22); + h->GetListOfFunctions()->Add(label); + } + } + + if (mo->getName().find("GoodBoardsFractionPerDE") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + + float scaleMin{ 0 }; + float scaleMax{ 1.05 }; + h->SetMinimum(scaleMin); + h->SetMaximum(scaleMax); + h->GetYaxis()->SetTitle("good boards fraction"); + + addChamberDelimiters(h, scaleMin, scaleMax); + addChamberLabelsForDE(h); + + // only the plot used for the check is beautified by changing the color + // and adding the horizontal lines corresponding to the thresholds + if (matchHistName(mo->getName(), mGoodFracHistName)) { + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + + drawThresholdsPerStation(h, mMinGoodErrorFracPerStation, mMinGoodErrorFrac); + } + } + + if (mo->getName().find("GoodBoardsFractionPerSolar") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + + float scaleMin{ 0 }; + float scaleMax{ 1.05 }; + h->SetMinimum(scaleMin); + h->SetMaximum(scaleMax); + h->GetYaxis()->SetTitle("good boards fraction"); + + addChamberDelimitersToSolarHistogram(h, scaleMin, scaleMax); + addChamberLabelsForSolar(h); + + // only the plot used for the check is beautified by changing the color + // and adding the horizontal lines corresponding to the thresholds + if (matchHistName(mo->getName(), mGoodFracPerSolarHistName)) { + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + + drawThreshold(h, mMinGoodErrorFracPerSolar); + } + } + + if (mo->getName().find("SyncedBoardsFractionPerDE") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + + float scaleMin{ 0 }; + float scaleMax{ 1.05 }; + h->SetMinimum(scaleMin); + h->SetMaximum(scaleMax); + + addChamberDelimiters(h, scaleMin, scaleMax); + addChamberLabelsForDE(h); + + // only the plot used for the check is beautified by changing the color + // and adding the horizontal lines corresponding to the thresholds + if (matchHistName(mo->getName(), mSyncFracHistName)) { + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + + drawThresholdsPerStation(h, mMinGoodSyncFracPerStation, mMinGoodSyncFrac); + } + } + + if (mo->getName().find("SyncedBoardsFractionPerSolar") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + + float scaleMin{ 0 }; + float scaleMax{ 1.05 }; + h->SetMinimum(scaleMin); + h->SetMaximum(scaleMax); + + addChamberDelimitersToSolarHistogram(h, scaleMin, scaleMax); + addChamberLabelsForSolar(h); + + // only the plot used for the check is beautified by changing the color + // and adding the horizontal lines corresponding to the thresholds + if (matchHistName(mo->getName(), mSyncFracPerSolarHistName)) { + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + + drawThreshold(h, mMinGoodSyncFracPerSolar); + } + } + + // Normalize the heartBeat rate plots + if (mo->getName().find("HBRate_ST") != std::string::npos) { + TH2F* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + h->SetMinimum(mMinHeartBeatRate); + h->SetMaximum(mMaxHeartBeatRate); + } + + // update quality flags for each DE + if (mo->getName().find("QualityFlagPerDE") != std::string::npos) { + TH2F* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + + std::string badDEs; + for (int deIndex = 0; deIndex < mQualityChecker.mQuality.size(); deIndex++) { + float ybin = 0; + if (mQualityChecker.mQuality[deIndex] == Quality::Good) { + ybin = 3; + } + if (mQualityChecker.mQuality[deIndex] == Quality::Medium) { + ybin = 2; + } + if (mQualityChecker.mQuality[deIndex] == Quality::Bad) { + ybin = 1; + std::string deIdStr = std::to_string(getDEFromIndex(deIndex)); + if (badDEs.empty()) { + badDEs = deIdStr; + } else { + badDEs += std::string(" ") + deIdStr; + } + } + + h->SetBinContent(deIndex + 1, ybin, 1); + + if (!badDEs.empty()) { + std::string text = std::string("Bad DEs: ") + badDEs; + TPaveLabel* label = new TPaveLabel(0.2, 0.8, 0.8, 0.88, text.c_str(), "blNDC"); + label->SetBorderSize(1); + h->GetListOfFunctions()->Add(label); + + ILOG(Warning, Support) << "[DecodingCheck] " << text << ENDM; + } + } + } + + // update quality flags for each SOLAR + if (mo->getName().find("QualityFlagPerSolar") != std::string::npos) { + TH2F* h = dynamic_cast(mo->getObject()); + if (!h) { + return; + } + + std::string badSolarBoards; + for (int solar = 0; solar < mSolarQuality.size(); solar++) { + float ybin = 0; + if (mSolarQuality[solar] == Quality::Good) { + ybin = 3; + } + if (mSolarQuality[solar] == Quality::Medium) { + ybin = 2; + } + if (mSolarQuality[solar] == Quality::Bad) { + ybin = 1; + std::string solarId = std::to_string(getSolarIdFromIndex(solar)); + if (badSolarBoards.empty()) { + badSolarBoards = solarId; + } else { + badSolarBoards += std::string(" ") + solarId; + } + } + + h->SetBinContent(solar + 1, ybin, 1); + } + + if (!badSolarBoards.empty()) { + std::string badSolarList = std::string("Bad SOLAR boards: ") + badSolarBoards; + TPaveLabel* label = new TPaveLabel(0.2, 0.8, 0.8, 0.88, badSolarList.c_str(), "blNDC"); + label->SetBorderSize(1); + h->GetListOfFunctions()->Add(label); + + ILOG(Warning, Support) << "[DecodingCheck] " << badSolarList << ENDM; + } + } +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/DecodingErrorsPlotter.cxx b/Modules/MUON/MCH/src/DecodingErrorsPlotter.cxx new file mode 100644 index 0000000000..03080083b1 --- /dev/null +++ b/Modules/MUON/MCH/src/DecodingErrorsPlotter.cxx @@ -0,0 +1,217 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DecodingErrorsPlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/DecodingErrorsPlotter.h" +#include "MCH/Helpers.h" +#include "MCHRawDecoder/ErrorCodes.h" +#include "MCHGlobalMapping/DsIndex.h" +#include + +using namespace o2::mch; +using namespace o2::mch::raw; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +static void setXAxisLabels(TH2F* hErrors) +{ + TAxis* ay = hErrors->GetXaxis(); + for (int i = 1; i <= 10; i++) { + auto label = fmt::format("CH{}", i); + ay->SetBinLabel(i, label.c_str()); + } +} + +static void setYAxisLabels(TH2F* hErrors) +{ + TAxis* ax = hErrors->GetYaxis(); + for (int i = 0; i < getErrorCodesSize(); i++) { + ax->SetBinLabel(i + 1, errorCodeAsString(1 << i).c_str()); + ax->ChangeLabel(i + 1, 45); + } +} + +DecodingErrorsPlotter::DecodingErrorsPlotter(std::string path) : mPath(path) +{ + mDet2ElecMapper = createDet2ElecMapper(); + mSolar2FeeLinkMapper = createSolar2FeeLinkMapper(); + + //-------------------------------------------- + // Fraction of DS boards in error per DE + //-------------------------------------------- + + // Number of decoding errors, grouped by FEE ID and normalized to the number of processed TF + mHistogramGoodBoardsPerDE = std::make_unique(TString::Format("%sGoodBoardsFractionPerDE", path.c_str()), + "Fraction of boards not in error", getNumDE(), 0, getNumDE()); + addDEBinLabels(mHistogramGoodBoardsPerDE.get()); + addHisto(mHistogramGoodBoardsPerDE.get(), false, "", ""); + + mHistogramGoodBoardsPerSolar = std::make_unique(TString::Format("%sGoodBoardsFractionPerSolar", path.c_str()), + "Fraction of boards not in error per SOLAR board", getNumSolar(), 0, getNumSolar()); + addSolarBinLabels(mHistogramGoodBoardsPerSolar.get()); + addHisto(mHistogramGoodBoardsPerSolar.get(), false, "", ""); + + //-------------------------------------------- + // Decoding errors per chamber, DE and FEEID + //-------------------------------------------- + + // Number of decoding errors, grouped by FEE ID and normalized to the number of processed TF + mHistogramErrorsPerFeeId = std::make_unique(TString::Format("%sDecodingErrorsPerFeeId", path.c_str()), + "FEE ID vs. Error Type", 64, 0, 64, + getErrorCodesSize(), 0, getErrorCodesSize()); + setYAxisLabels(mHistogramErrorsPerFeeId.get()); + addHisto(mHistogramErrorsPerFeeId.get(), false, "colz", "gridy logz"); + + // Number of decoding errors, grouped by DE ID and normalized to the number of processed TF + mHistogramErrorsPerDE = std::make_unique(TString::Format("%sDecodingErrorsPerDE", path.c_str()), + "Error Type vs. DE ID", + getNumDE(), 0, getNumDE(), + getErrorCodesSize(), 0, getErrorCodesSize()); + setYAxisLabels(mHistogramErrorsPerDE.get()); + addHisto(mHistogramErrorsPerDE.get(), false, "colz", "gridy logz"); + + // Number of decoding errors, grouped by chamber ID and normalized to the number of processed TF + mHistogramErrorsPerChamber = std::make_unique(TString::Format("%sDecodingErrorsPerChamber", path.c_str()), + "Chamber Number vs. Error Type", 10, 1, 11, + getErrorCodesSize(), 0, getErrorCodesSize()); + setXAxisLabels(mHistogramErrorsPerChamber.get()); + setYAxisLabels(mHistogramErrorsPerChamber.get()); + addHisto(mHistogramErrorsPerChamber.get(), false, "colz", "gridx gridy logz"); +} + +//_________________________________________________________________________________________ + +static bool isWarning(int errorID) +{ + // Temporary solution, until the error code severity is implemented directly in O2 + uint32_t error = ((uint32_t)1) << errorID; + if (error == ErrorCodes::ErrorTruncatedData) { + return true; + } + if (error == ErrorCodes::ErrorTruncatedDataUL) { + return true; + } + return false; +} + +//_________________________________________________________________________________________ + +void DecodingErrorsPlotter::update(TH2F* h) +{ + if (!h) { + return; + } + + auto incrementBin = [&](TH2F* h, int bx, int by, float val) { + auto entries = h->GetBinContent(bx, by); + h->SetBinContent(bx, by, entries + val); + }; + + mHistogramErrorsPerFeeId->Reset("ICES"); + mHistogramErrorsPerDE->Reset("ICES"); + mHistogramErrorsPerChamber->Reset("ICES"); + + std::array, getNumDE()> goodBoardsFractionPerDE; + std::array, getNumSolar()> goodBoardsFractionPerSolar; + + // loop over bins in electronics coordinates, and map the channels to the corresponding cathode pads + int nbinsx = h->GetXaxis()->GetNbins(); + int nbinsy = h->GetYaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + // address of the DS board in detector representation + auto dsDetId = getDsDetId(i - 1); + auto deId = dsDetId.deId(); + auto dsId = dsDetId.dsId(); + + int chamber = deId / 100; + + int deIndex = getDEindex(deId); + if (deIndex < 0) { + continue; + } + + // Using the mapping to go from (deId, dsId) to Elec info (fee, link) and fill Elec Histogram, + // where one bin is one physical pad + // get the unique solar ID and the DS address associated to this digit + int feeId = -1; + int solarId = -1; + std::optional dsElecId = mDet2ElecMapper(dsDetId); + if (dsElecId) { + solarId = dsElecId->solarId(); + uint32_t dsAddr = dsElecId->elinkId(); + + std::optional feeLinkId = mSolar2FeeLinkMapper(solarId); + if (feeLinkId) { + feeId = feeLinkId->feeId(); + } + } + int solarIndex = (solarId >= 0) ? getSolarIndex(solarId) : -1; + if (solarIndex < 0) { + continue; + } + + bool hasError = false; + + for (int j = 1; j <= nbinsy; j++) { + auto count = h->GetBinContent(i, j); + if (feeId >= 0) { + incrementBin(mHistogramErrorsPerFeeId.get(), feeId + 1, j, count); + // do not include warnings when counting the boards in error + if (!isWarning(j - 1) && count > 0) { + hasError = true; + } + } + + incrementBin(mHistogramErrorsPerDE.get(), deIndex + 1, j, count); + incrementBin(mHistogramErrorsPerChamber.get(), chamber, j, count); + } + + goodBoardsFractionPerDE[deIndex].first += 1; + goodBoardsFractionPerSolar[solarIndex].first += 1; + if (!hasError) { + goodBoardsFractionPerDE[deIndex].second += 1; + goodBoardsFractionPerSolar[solarIndex].second += 1; + } + } + + // update fraction of good boards per DE + for (int i = 0; i < getNumDE(); i++) { + if (goodBoardsFractionPerDE[i].first > 0) { + float frac = goodBoardsFractionPerDE[i].second / goodBoardsFractionPerDE[i].first; + mHistogramGoodBoardsPerDE->SetBinContent(i + 1, frac); + } else { + mHistogramGoodBoardsPerDE->SetBinContent(i + 1, 0); + } + } + + // update fraction of good boards per Solar + for (int i = 0; i < getNumSolar(); i++) { + if (goodBoardsFractionPerSolar[i].first > 0) { + float frac = goodBoardsFractionPerSolar[i].second / goodBoardsFractionPerSolar[i].first; + mHistogramGoodBoardsPerSolar->SetBinContent(i + 1, frac); + } else { + mHistogramGoodBoardsPerSolar->SetBinContent(i + 1, 0); + } + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/DecodingPostProcessing.cxx b/Modules/MUON/MCH/src/DecodingPostProcessing.cxx new file mode 100644 index 0000000000..4c0f2f3d37 --- /dev/null +++ b/Modules/MUON/MCH/src/DecodingPostProcessing.cxx @@ -0,0 +1,324 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DecodingPostProcessing.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Trending of the MCH tracking +/// \since 21/06/2022 +/// + +#include "MCH/DecodingPostProcessing.h" +#include "MCH/PostProcessingConfigMCH.h" +#include "MUONCommon/Helpers.h" +#include "Common/ReferenceComparatorPlot.h" +#include +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::muon; +using namespace o2::quality_control_modules::muonchambers; +using namespace o2::mch::raw; + +void DecodingPostProcessing::configure(const boost::property_tree::ptree& config) +{ + ReferenceComparatorTask::configure(config); + + mConfig = PostProcessingConfigMCH(getID(), config); +} + +//_________________________________________________________________________________________ + +void DecodingPostProcessing::createDecodingErrorsHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + //---------------------------------- + // Decoding errors plotters + //---------------------------------- + + mErrorsPlotter.reset(); + mErrorsPlotter = std::make_unique("DecodingErrors/"); + mErrorsPlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mErrorsPlotter->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + + if (mEnableLastCycleHistos) { + // Helpers to extract plots from last cycle + auto obj = mCcdbObjects.find(errorsSourceName()); + if (obj != mCcdbObjects.end()) { + mErrorsOnCycle.reset(); + mErrorsOnCycle = std::make_unique>(); + } + + mErrorsPlotterOnCycle.reset(); + mErrorsPlotterOnCycle = std::make_unique("DecodingErrors/LastCycle/"); + mErrorsPlotterOnCycle->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mErrorsPlotterOnCycle->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + } +} + +//_________________________________________________________________________________________ + +void DecodingPostProcessing::createHeartBeatPacketsHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + //---------------------------------- + // HeartBeat packets plotters + //---------------------------------- + + mHBPacketsPlotter.reset(); + mHBPacketsPlotter = std::make_unique("HeartBeatPackets/", mFullHistos); + mHBPacketsPlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mHBPacketsPlotter->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + + if (mEnableLastCycleHistos) { + // Helpers to extract plots from last cycle + auto obj = mCcdbObjects.find(hbPacketsSourceName()); + if (obj != mCcdbObjects.end()) { + mHBPacketsOnCycle.reset(); + mHBPacketsOnCycle = std::make_unique>(); + } + + mHBPacketsPlotterOnCycle.reset(); + mHBPacketsPlotterOnCycle = std::make_unique("HeartBeatPackets/LastCycle/", mFullHistos); + mHBPacketsPlotterOnCycle->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mHBPacketsPlotterOnCycle->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + } +} + +//_________________________________________________________________________________________ + +void DecodingPostProcessing::createSyncStatusHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + //---------------------------------- + // Sync status plotters + //---------------------------------- + + mSyncStatusPlotter.reset(); + mSyncStatusPlotter = std::make_unique("SyncErrors/"); + mSyncStatusPlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mSyncStatusPlotter->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + + if (mEnableLastCycleHistos) { + // Helpers to extract plots from last cycle + auto obj = mCcdbObjects.find(syncStatusSourceName()); + if (obj != mCcdbObjects.end()) { + mSyncStatusOnCycle.reset(); + mSyncStatusOnCycle = std::make_unique>(); + } + + mSyncStatusPlotterOnCycle.reset(); + mSyncStatusPlotterOnCycle = std::make_unique("SyncErrors/LastCycle/"); + mSyncStatusPlotterOnCycle->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mSyncStatusPlotterOnCycle->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + } +} + +//_________________________________________________________________________________________ + +void DecodingPostProcessing::initialize(Trigger t, framework::ServiceRegistryRef services) +{ + ReferenceComparatorTask::initialize(t, services); + + auto& qcdb = services.get(); + const auto& activity = t.activity; + + mFullHistos = getConfigurationParameter(mCustomParameters, "FullHistos", mFullHistos, activity); + mEnableLastCycleHistos = getConfigurationParameter(mCustomParameters, "EnableLastCycleHistos", mEnableLastCycleHistos, activity); + mEnableTrending = getConfigurationParameter(mCustomParameters, "EnableTrending", mEnableTrending, activity); + + mCcdbObjects.clear(); + mCcdbObjects.emplace(errorsSourceName(), CcdbObjectHelper()); + mCcdbObjects.emplace(hbPacketsSourceName(), CcdbObjectHelper()); + mCcdbObjects.emplace(syncStatusSourceName(), CcdbObjectHelper()); + + // set objects path from configuration + for (auto source : mConfig.dataSources) { + std::string sourceType, sourceName; + splitDataSourceName(source.name, sourceType, sourceName); + if (sourceType.empty()) { + continue; + } + + auto obj = mCcdbObjects.find(sourceType); + if (obj != mCcdbObjects.end()) { + obj->second.mPath = source.path; + obj->second.mName = sourceName; + } + } + + // instantiate and publish the histograms + createDecodingErrorsHistos(t, &qcdb); + createHeartBeatPacketsHistos(t, &qcdb); + createSyncStatusHistos(t, &qcdb); + + //-------------------------------------------------- + // Detector quality histogram + //-------------------------------------------------- + + mHistogramQualityPerDE.reset(); + mHistogramQualityPerDE = std::make_unique("QualityFlagPerDE", "Quality Flag vs DE", getNumDE(), 0, getNumDE(), 3, 0, 3); + addDEBinLabels(mHistogramQualityPerDE.get()); + addChamberDelimiters(mHistogramQualityPerDE.get()); + addChamberLabelsForDE(mHistogramQualityPerDE.get()); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(1, "Bad"); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(2, "Medium"); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(3, "Good"); + mHistogramQualityPerDE->SetOption("colz"); + mHistogramQualityPerDE->SetStats(0); + getObjectsManager()->startPublishing(mHistogramQualityPerDE.get(), core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistogramQualityPerDE.get(), "colz"); + getObjectsManager()->setDisplayHint(mHistogramQualityPerDE.get(), "gridy"); + + mHistogramQualityPerSolar.reset(); + mHistogramQualityPerSolar = std::make_unique("QualityFlagPerSolar", "Quality Flag vs Solar", getNumSolar(), 0, getNumSolar(), 3, 0, 3); + addSolarBinLabels(mHistogramQualityPerSolar.get()); + addChamberDelimitersToSolarHistogram(mHistogramQualityPerSolar.get()); + addChamberLabelsForSolar(mHistogramQualityPerSolar.get()); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(1, "Bad"); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(2, "Medium"); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(3, "Good"); + mHistogramQualityPerSolar->SetOption("col"); + mHistogramQualityPerSolar->SetStats(0); + getObjectsManager()->startPublishing(mHistogramQualityPerSolar.get(), core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistogramQualityPerSolar.get(), "col"); + getObjectsManager()->setDisplayHint(mHistogramQualityPerSolar.get(), "gridy"); +} + +//_________________________________________________________________________________________ + +void DecodingPostProcessing::updateDecodingErrorsHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + auto obj = mCcdbObjects.find(errorsSourceName()); + if (obj != mCcdbObjects.end() && obj->second.update(qcdb, t.timestamp, t.activity)) { + TH2FRatio* hr = obj->second.get(); + if (hr) { + mErrorsPlotter->update(hr); + if (mEnableLastCycleHistos) { + // extract the average occupancies on the last cycle + mErrorsOnCycle->update(hr); + mErrorsPlotterOnCycle->update(mErrorsOnCycle.get()); + } + } + } +} + +//_________________________________________________________________________________________ + +void DecodingPostProcessing::updateHeartBeatPacketsHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + auto obj = mCcdbObjects.find(hbPacketsSourceName()); + if (obj != mCcdbObjects.end() && obj->second.update(qcdb, t.timestamp, t.activity)) { + TH2FRatio* hr = obj->second.get(); + if (hr) { + mHBPacketsPlotter->update(hr); + if (mEnableLastCycleHistos) { + // extract the average occupancies on the last cycle + mHBPacketsOnCycle->update(hr); + mHBPacketsPlotterOnCycle->update(mHBPacketsOnCycle.get()); + } + } + } +} + +//_________________________________________________________________________________________ + +void DecodingPostProcessing::updateSyncStatusHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + auto obj = mCcdbObjects.find(syncStatusSourceName()); + if (obj != mCcdbObjects.end() && obj->second.update(qcdb, t.timestamp, t.activity)) { + TH2F* hr = obj->second.get(); + if (hr) { + mSyncStatusPlotter->update(hr); + if (mEnableLastCycleHistos) { + // extract the average occupancies on the last cycle + mSyncStatusOnCycle->update(hr); + mSyncStatusPlotterOnCycle->update(mSyncStatusOnCycle.get()); + } + } + } +} + +//_________________________________________________________________________________________ + +TH1* DecodingPostProcessing::getHistogram(std::string_view plotName) +{ + TH1* result{ nullptr }; + for (auto hist : mHistogramsAll) { + if (plotName == hist->GetName()) { + result = hist; + break; + } + } + return result; +} + +void DecodingPostProcessing::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + updateDecodingErrorsHistos(t, &qcdb); + updateHeartBeatPacketsHistos(t, &qcdb); + updateSyncStatusHistos(t, &qcdb); + + auto& comparatorPlots = getComparatorPlots(); + for (auto& [plotName, plot] : comparatorPlots) { + TH1* hist = getHistogram(plotName); + if (!hist) { + continue; + } + + plot->update(hist); + } +} + +//_________________________________________________________________________________________ + +void DecodingPostProcessing::finalize(Trigger t, framework::ServiceRegistryRef) +{ +} diff --git a/Modules/MUON/MCH/src/DecodingTask.cxx b/Modules/MUON/MCH/src/DecodingTask.cxx new file mode 100644 index 0000000000..18077503d6 --- /dev/null +++ b/Modules/MUON/MCH/src/DecodingTask.cxx @@ -0,0 +1,389 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DecodingTask.cxx +/// \author Sebastien Perrin +/// + +#include "MCH/DecodingTask.h" +#include "MUONCommon/Helpers.h" +#include "DetectorsRaw/RDHUtils.h" +#include "QualityControl/QcInfoLogger.h" +#include "Framework/WorkflowSpec.h" +#include "Framework/DataRefUtils.h" + +#include "MCHRawElecMap/Mapper.h" +#include "MCHRawDecoder/PageDecoder.h" +#include "MCHRawDecoder/ErrorCodes.h" +#include "MCHBase/DecoderError.h" +#include "MCHBase/HeartBeatPacket.h" + +#include + +using namespace o2; +using namespace o2::framework; +using namespace o2::mch; +using namespace o2::mch::raw; +using RDH = o2::header::RDHAny; +using namespace o2::quality_control_modules::muon; + +namespace o2::quality_control_modules::muonchambers +{ + +template +void DecodingTask::publishObject(T* histo, std::string drawOption, std::string displayHints, bool statBox, bool isExpert) +{ + histo->SetOption(drawOption.c_str()); + if (!statBox) { + histo->SetStats(0); + } + mAllHistograms.push_back(histo); + getObjectsManager()->startPublishing(histo); + getObjectsManager()->setDefaultDrawOptions(histo, drawOption); + getObjectsManager()->setDisplayHint(histo, displayHints); +} + +//_____________________________________________________________________________ + +void DecodingTask::createErrorHistos() +{ + const uint32_t nElecXbins = NumberOfDualSampas; + + // Number of decoding errors, grouped by chamber ID and normalized to the number of processed TF + mHistogramErrorsFEC = std::make_unique("DecodingErrors_Elec", "Error Code vs. FEC ID", nElecXbins, 0, nElecXbins, getErrorCodesSize(), 0, getErrorCodesSize(), true); + { + TAxis* ax = mHistogramErrorsFEC->GetYaxis(); + for (int i = 0; i < getErrorCodesSize(); i++) { + ax->SetBinLabel(i + 1, errorCodeAsString(1 << i).c_str()); + ax->ChangeLabel(i + 1, 45); + } + } + publishObject(mHistogramErrorsFEC.get(), "colz", "gridy logz", false, false); +} + +//_____________________________________________________________________________ + +void DecodingTask::createHeartBeatHistos() +{ + const uint32_t nElecXbins = NumberOfDualSampas; + + // Heart-beat packets time distribution and synchronization errors + mHistogramHBTimeFEC = std::make_unique("HBTime_Elec", "HB time vs. FEC ID", nElecXbins, 0, nElecXbins, 40, mHBExpectedBc - 20, mHBExpectedBc + 20, true); + mHistogramHBTimeFEC->Sumw2(kFALSE); + publishObject(mHistogramHBTimeFEC.get(), "colz", "logz", false, false); + + uint64_t max = ((static_cast(0x100000) / 100) + 1) * 100; + mHistogramHBCoarseTimeFEC = std::make_unique("HBCoarseTime_Elec", "HB time vs. FEC ID (coarse)", nElecXbins, 0, nElecXbins, 100, 0, max, true); + mHistogramHBCoarseTimeFEC->Sumw2(kFALSE); + publishObject(mHistogramHBCoarseTimeFEC.get(), "colz", "", false, false); + + mSyncStatusFEC = std::make_unique("SyncStatus_Elec", "Heart-beat status vs. FEC ID", nElecXbins, 0, nElecXbins, 3, 0, 3, true); + mSyncStatusFEC->Sumw2(kFALSE); + mSyncStatusFEC->GetYaxis()->SetBinLabel(1, "OK"); + mSyncStatusFEC->GetYaxis()->SetBinLabel(2, "Out-of-sync"); + mSyncStatusFEC->GetYaxis()->SetBinLabel(3, "Missing"); + publishObject(mSyncStatusFEC.get(), "colz", "gridy", false, false); +} + +//_____________________________________________________________________________ + +void DecodingTask::initialize(o2::framework::InitContext& /*ic*/) +{ + ILOG(Debug, Devel) << "initialize DecodingErrorsTask" << ENDM; + + // expected bunch-crossing value in heart-beat packets + mHBExpectedBc = getConfigurationParameter(mCustomParameters, "HBExpectedBc", mHBExpectedBc); + + mElec2DetMapper = createElec2DetMapper(); + + mHistogramTimeFramesCount = std::make_unique("TimeFramesCount", "Number of Time Frames", 1, 0, 1); + publishObject(mHistogramTimeFramesCount.get(), "hist", "", true, false); + + createErrorHistos(); + createHeartBeatHistos(); +} + +//_____________________________________________________________________________ + +void DecodingTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity : " << activity.mId << ENDM; +} + +//_____________________________________________________________________________ + +void DecodingTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +//_____________________________________________________________________________ + +void DecodingTask::decodeTF(framework::ProcessingContext& pc) +{ + // get the input buffer + auto& inputs = pc.inputs(); + DPLRawParser parser(inputs, o2::framework::select("")); + + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + auto const* raw = it.raw(); + if (!raw) { + continue; + } + size_t payloadSize = it.size(); + + gsl::span buffer(reinterpret_cast(raw), sizeof(RDH) + payloadSize); + decodeBuffer(buffer); + } +} + +//_____________________________________________________________________________ + +void DecodingTask::decodeReadout(const o2::framework::DataRef& input) +{ + // get the input buffer + if (input.spec->binding != "readout") { + return; + } + + const auto* header = o2::framework::DataRefUtils::getHeader(input); + if (!header) { + return; + } + + size_t payloadSize = o2::framework::DataRefUtils::getPayloadSize(input); + if (payloadSize == 0) { + return; + } + + auto const* raw = input.payload; + + gsl::span buffer(reinterpret_cast(raw), payloadSize); + decodeBuffer(buffer); +} + +//_____________________________________________________________________________ + +void DecodingTask::decodeBuffer(gsl::span buf) +{ + // RDH source ID for the MCH system + static constexpr int sMCHSourceId = 10; + + size_t bufSize = buf.size(); + size_t pageStart = 0; + while (bufSize > pageStart) { + const RDH* rdh = reinterpret_cast(&(buf[pageStart])); + auto rdhHeaderSize = o2::raw::RDHUtils::getHeaderSize(rdh); + if (rdhHeaderSize != 64) { + break; + } + auto pageSize = o2::raw::RDHUtils::getOffsetToNext(rdh); + + // skip all buffers that do not belong to MCH + auto sourceId = o2::raw::RDHUtils::getSourceID(rdh); + if (sourceId != sMCHSourceId) { + continue; + } + + gsl::span page(reinterpret_cast(rdh), pageSize); + decodePage(page); + + pageStart += pageSize; + } +} + +//_____________________________________________________________________________ + +void DecodingTask::decodePage(gsl::span page) +{ + auto errorHandler = [&](DsElecId dsElecId, int8_t chip, uint32_t error) { + int feeId{ -1 }; + uint32_t solarId = dsElecId.solarId(); + uint32_t dsAddr = dsElecId.elinkId(); + + plotError(solarId, dsAddr, chip, error); + }; + + if (!mDecoder) { + o2::mch::raw::DecodedDataHandlers handlers; + handlers.sampaErrorHandler = errorHandler; + mDecoder = o2::mch::raw::createPageDecoder(page, handlers); + } + mDecoder(page); +} + +//_____________________________________________________________________________ + +void DecodingTask::processErrors(framework::ProcessingContext& pc) +{ + auto rawerrors = pc.inputs().get>("rawerrors"); + for (auto& error : rawerrors) { + plotError(error.getSolarID(), error.getDsID(), error.getChip(), error.getError()); + } +} + +//_____________________________________________________________________________ + +void DecodingTask::plotError(uint16_t solarId, int dsAddr, int chip, uint32_t error) +{ + int fecId = -1; + o2::mch::raw::DsElecId dsElecId{ solarId, static_cast(dsAddr / 5), static_cast(dsAddr % 5) }; + if (auto opt = mElec2DetMapper(dsElecId); opt.has_value()) { + o2::mch::raw::DsDetId dsDetId = opt.value(); + fecId = getDsIndex(dsDetId); + } + if (fecId < 0) { + return; + } + + uint32_t errMask = 1; + for (int i = 0; i < getErrorCodesSize(); i++) { + // Fill the error histogram if the i-th bin is set in the error word + if ((error & errMask) != 0) { + mHistogramErrorsFEC->getNum()->Fill(fecId, 0.5 + i); + } + errMask <<= 1; + } +} + +//_____________________________________________________________________________ + +void DecodingTask::processHBPackets(framework::ProcessingContext& pc) +{ + std::fill(mHBcount.begin(), mHBcount.end(), HBCount()); + auto hbpackets = pc.inputs().get>("hbpackets"); + for (auto& hbp : hbpackets) { + plotHBPacket(hbp.getSolarID(), hbp.getDsID(), hbp.getChip(), hbp.getBunchCrossing()); + } + updateSyncErrors(); +} + +//_____________________________________________________________________________ + +void DecodingTask::plotHBPacket(uint16_t solarId, int dsAddr, int chip, uint32_t bc) +{ + int mBcMin{ mHBExpectedBc - 2 }; + int mBcMax{ mHBExpectedBc + 2 }; + + int fecId = -1; + o2::mch::raw::DsElecId dsElecId{ solarId, static_cast(dsAddr / 5), static_cast(dsAddr % 5) }; + if (auto opt = mElec2DetMapper(dsElecId); opt.has_value()) { + o2::mch::raw::DsDetId dsDetId = opt.value(); + fecId = getDsIndex(dsDetId); + } + if (fecId < 0) { + return; + } + + mHistogramHBCoarseTimeFEC->getNum()->Fill(fecId, bc); + if (bc < mHistogramHBTimeFEC->getNum()->GetYaxis()->GetXmin()) { + bc = mHistogramHBTimeFEC->getNum()->GetYaxis()->GetXmin(); + } + if (bc > mHistogramHBTimeFEC->getNum()->GetYaxis()->GetXmax()) { + bc = mHistogramHBTimeFEC->getNum()->GetYaxis()->GetXmax(); + } + mHistogramHBTimeFEC->getNum()->Fill(fecId, bc); + + if (bc >= mBcMin && bc <= mBcMax) { + mHBcount[fecId].nSync += 1; + } else { + mHBcount[fecId].nOutOfSync += 1; + } +} + +//_____________________________________________________________________________ + +void DecodingTask::updateSyncErrors() +{ + for (size_t fecId = 0; fecId < mHBcount.size(); fecId++) { + bool isOutOfSync = false; + if (mHBcount[fecId].nOutOfSync > 0) { + isOutOfSync = true; + } + + bool isMissing = false; + if (mHBcount[fecId].nSync < 1.5) { + isMissing = true; + } + + if (!isOutOfSync && !isMissing) { + mSyncStatusFEC->getNum()->Fill(0.5 + fecId, 0); + } + if (isOutOfSync) { + mSyncStatusFEC->getNum()->Fill(0.5 + fecId, 1); + } + if (isMissing) { + mSyncStatusFEC->getNum()->Fill(0.5 + fecId, 2); + } + } +} + +//_____________________________________________________________________________ + +void DecodingTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + for (auto&& input : ctx.inputs()) { + if (input.spec->binding == "readout") { + decodeReadout(input); + } + if (input.spec->binding == "TF") { + decodeTF(ctx); + } + if (input.spec->binding == "rawerrors") { + processErrors(ctx); + } + if (input.spec->binding == "hbpackets") { + processHBPackets(ctx); + } + } + + mHistogramTimeFramesCount->Fill(0.5); +} + +//_____________________________________________________________________________ + +void DecodingTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + + int nTF = static_cast(mHistogramTimeFramesCount->GetBinContent(1)); + mHistogramErrorsFEC->getDen()->SetBinContent(1, 1, nTF); + mHistogramHBTimeFEC->getDen()->SetBinContent(1, 1, nTF); + mHistogramHBCoarseTimeFEC->getDen()->SetBinContent(1, 1, nTF); + mSyncStatusFEC->getDen()->SetBinContent(1, 1, nTF); + + mHistogramErrorsFEC->update(); + mHistogramHBCoarseTimeFEC->update(); + mHistogramHBTimeFEC->update(); + mSyncStatusFEC->update(); +} + +//_____________________________________________________________________________ + +void DecodingTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +//_____________________________________________________________________________ + +void DecodingTask::reset() +{ + // clean all the monitor objects here + ILOG(Info, Devel) << "Resetting the histograms" << ENDM; + + for (auto h : mAllHistograms) { + h->Reset("ICES"); + } +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/DigitsCheck.cxx b/Modules/MUON/MCH/src/DigitsCheck.cxx new file mode 100644 index 0000000000..2c9322ed9b --- /dev/null +++ b/Modules/MUON/MCH/src/DigitsCheck.cxx @@ -0,0 +1,647 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsCheck.cxx +/// \author Andrea Ferrero, Sebastien Perrin +/// + +#include "MCH/DigitsCheck.h" +#include "MUONCommon/Helpers.h" +#include "QualityControl/ReferenceUtils.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" + +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control_modules::muon; + +namespace o2::quality_control_modules::muonchambers +{ + +void DigitsCheck::configure() +{ +} + +void DigitsCheck::startOfActivity(const Activity& activity) +{ + // input histogram names customization + mMeanRateHistName = getConfigurationParameter(mCustomParameters, "MeanRateHistName", mMeanRateHistName, activity); + mGoodChanFracHistName = getConfigurationParameter(mCustomParameters, "GoodChanFracHistName", mGoodChanFracHistName, activity); + + mMeanRatePerSolarHistName = getConfigurationParameter(mCustomParameters, "MeanRatePerSolarHistName", mMeanRatePerSolarHistName, activity); + mGoodChanFracPerSolarHistName = getConfigurationParameter(mCustomParameters, "GoodChanFracPerSolarHistName", mGoodChanFracPerSolarHistName, activity); + + mMeanRateRefCompHistName = getConfigurationParameter(mCustomParameters, "MeanRateRefCompHistName", mMeanRateRefCompHistName, activity); + mGoodChanFracRefCompHistName = getConfigurationParameter(mCustomParameters, "GoodChanFracRefCompHistName", mGoodChanFracRefCompHistName, activity); + + mMeanRatePerSolarRefCompHistName = getConfigurationParameter(mCustomParameters, "MeanRatePerSolarRefCompHistName", mMeanRatePerSolarRefCompHistName, activity); + mGoodChanFracPerSolarRefCompHistName = getConfigurationParameter(mCustomParameters, "GoodChanFracPerSolarRefCompHistName", mGoodChanFracPerSolarRefCompHistName, activity); + + // threshold customization + getThresholdsPerStation(mCustomParameters, activity, "MinRate", mMinRatePerStation, mMinRate); + getThresholdsPerStation(mCustomParameters, activity, "MaxRate", mMaxRatePerStation, mMaxRate); + mMinRateRatio = getConfigurationParameter(mCustomParameters, "MinRateRatio", mMinRateRatio, activity); + + mMinRatePerSolar = getConfigurationParameter(mCustomParameters, "MinRatePerSolar", mMinRatePerSolar, activity); + mMaxRatePerSolar = getConfigurationParameter(mCustomParameters, "MaxRatePerSolar", mMaxRatePerSolar, activity); + mMinRateRatioPerSolar = getConfigurationParameter(mCustomParameters, "MinRateRatioPerSolar", mMinRateRatioPerSolar, activity); + + getThresholdsPerStation(mCustomParameters, activity, "MinGoodFraction", mMinGoodFractionPerStation, mMinGoodFraction); + mMinGoodFractionRatio = getConfigurationParameter(mCustomParameters, "MinGoodFractionRatio", mMinGoodFractionRatio, activity); + + mMinGoodFractionPerSolar = getConfigurationParameter(mCustomParameters, "MinGoodFractionPerSolar", mMinGoodFractionPerSolar, activity); + mMinGoodFractionRatioPerSolar = getConfigurationParameter(mCustomParameters, "MinGoodFractionRatioPerSolar", mMinGoodFractionRatioPerSolar, activity); + + mMaxBadST12 = getConfigurationParameter(mCustomParameters, "MaxBadDE_ST12", mMaxBadST12, activity); + mMaxBadST345 = getConfigurationParameter(mCustomParameters, "MaxBadDE_ST345", mMaxBadST345, activity); + + mRatePlotScaleMin = getConfigurationParameter(mCustomParameters, "RatePlotScaleMin", mRatePlotScaleMin, activity); + mRatePlotScaleMax = getConfigurationParameter(mCustomParameters, "RatePlotScaleMax", mRatePlotScaleMax, activity); + mRateRatioPlotScaleRange = getConfigurationParameter(mCustomParameters, "RateRatioPlotScaleRange", mRateRatioPlotScaleRange, activity); + mRateRatioPerSolarPlotScaleRange = getConfigurationParameter(mCustomParameters, "RateRatioPerSolarPlotScaleRange", mRateRatioPerSolarPlotScaleRange, activity); + mGoodFractionRatioPlotScaleRange = getConfigurationParameter(mCustomParameters, "GoodFractionRatioPlotScaleRange", mGoodFractionRatioPlotScaleRange, activity); + mGoodFractionRatioPerSolarPlotScaleRange = getConfigurationParameter(mCustomParameters, "GoodFractionRatioPerSolarPlotScaleRange", mGoodFractionRatioPerSolarPlotScaleRange, activity); + + mQualityChecker.mMaxBadST12 = mMaxBadST12; + mQualityChecker.mMaxBadST345 = mMaxBadST345; +} + +template +std::array checkPlot(TH1* h, Lambda check) +{ + std::array result; + std::fill(result.begin(), result.end(), Quality::Null); + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int chamberId = (de - 100) / 100; + int stationId = chamberId / 2; + + int deId = getDEindex(de); + if (deId < 0) { + continue; + } + int bin = deId + 1; + + double val = h->GetBinContent(bin); + if (check(val, stationId)) { + result[deId] = Quality::Good; + } else { + result[deId] = Quality::Bad; + } + } + + return result; +} + +std::array DigitsCheck::checkMeanRates(TH1* h) +{ + auto checkFunction = [&](double val, int station) -> bool { + auto minRate = mMinRate; + auto maxRate = mMaxRate; + if (station >= 0 && station < 5) { + if (mMinRatePerStation[station]) { + minRate = mMinRatePerStation[station].value(); + } + if (mMaxRatePerStation[station]) { + maxRate = mMaxRatePerStation[station].value(); + } + } + return (val >= minRate && val <= maxRate); + }; + return checkPlot(h, checkFunction); +} + +std::array DigitsCheck::checkMeanRateRatios(TH1* h) +{ + auto checkFunction = [&](double val, int /*station*/) -> bool { + auto minRateRatio = mMinRateRatio; + return (val >= minRateRatio); + }; + return checkPlot(h, checkFunction); +} + +std::array DigitsCheck::checkBadChannels(TH1* h) +{ + auto checkFunction = [&](double val, int station) -> bool { + auto minGoodFraction = mMinGoodFraction; + if (station >= 0 && station < 5) { + if (mMinGoodFractionPerStation[station]) { + minGoodFraction = mMinGoodFractionPerStation[station].value(); + } + } + return (val >= minGoodFraction); + }; + return checkPlot(h, checkFunction); +} + +std::array DigitsCheck::checkBadChannelRatios(TH1* h) +{ + auto checkFunction = [&](double val, int /*station*/) -> bool { + auto minGoodFractionRatio = mMinGoodFractionRatio; + return (val >= minGoodFractionRatio); + }; + return checkPlot(h, checkFunction); +} + +void DigitsCheck::checkSolarMeanRates(TH1* h) +{ + for (int solar = 0; solar < h->GetXaxis()->GetNbins(); solar++) { + int bin = solar + 1; + double value = h->GetBinContent(bin); + if (value >= mMinRatePerSolar && value <= mMaxRatePerSolar) { + continue; + } + mSolarQuality[solar] = Quality::Bad; + } +} + +void DigitsCheck::checkSolarMeanRateRatios(TH1* h) +{ + for (int solar = 0; solar < h->GetXaxis()->GetNbins(); solar++) { + int bin = solar + 1; + double value = h->GetBinContent(bin); + if (value >= mMinRateRatioPerSolar) { + continue; + } + mSolarQuality[solar] = Quality::Bad; + } +} + +void DigitsCheck::checkSolarBadChannels(TH1* h) +{ + for (int solar = 0; solar < h->GetXaxis()->GetNbins(); solar++) { + int bin = solar + 1; + double value = h->GetBinContent(bin); + if (value >= mMinGoodFractionPerSolar) { + continue; + } + mSolarQuality[solar] = Quality::Bad; + } +} + +void DigitsCheck::checkSolarBadChannelRatios(TH1* h) +{ + for (int solar = 0; solar < h->GetXaxis()->GetNbins(); solar++) { + int bin = solar + 1; + double value = h->GetBinContent(bin); + if (value >= mMinGoodFractionRatioPerSolar) { + continue; + } + mSolarQuality[solar] = Quality::Bad; + } +} + +template +static T* getHisto(TCanvas* c, std::string hname) +{ + if (!c) { + return nullptr; + } + + T* h = dynamic_cast(c->GetPrimitive(hname.c_str())); + return h; +} + +template +static T* getHisto(std::shared_ptr mo) +{ + TObject* obj = dynamic_cast(mo->getObject()); + if (!obj) { + return nullptr; + } + + T* h{ nullptr }; + if (obj->InheritsFrom("TH1")) { + h = dynamic_cast(obj); + } + + if (obj->InheritsFrom("TCanvas")) { + TCanvas* c = dynamic_cast(obj); + h = getHisto(c, mo->getName() + "Hist"); + } + + return h; +} + +Quality DigitsCheck::check(std::map>* moMap) +{ + mQualityChecker.reset(); + std::fill(mSolarQuality.begin(), mSolarQuality.end(), Quality::Good); + + for (auto& [moName, mo] : *moMap) { + + // DE histograms + if (matchHistName(mo->getName(), mMeanRateHistName)) { + TH1F* h = getHisto(mo); + if (h) { + auto q = checkMeanRates(h); + mQualityChecker.addCheckResult(q); + } + } + if (matchHistName(mo->getName(), mGoodChanFracHistName)) { + TH1F* h = getHisto(mo); + if (h) { + auto q = checkBadChannels(h); + mQualityChecker.addCheckResult(q); + } + } + // comparisons with reference + if (matchHistName(mo->getName(), mMeanRateRefCompHistName)) { + TCanvas* canvas = dynamic_cast(mo->getObject()); + if (canvas) { + auto ratioPlot = o2::quality_control::checker::getRatioPlotFromCanvas(canvas); + if (ratioPlot) { + auto q = checkMeanRateRatios(ratioPlot); + mQualityChecker.addCheckResult(q); + } + } + } + if (matchHistName(mo->getName(), mGoodChanFracRefCompHistName)) { + TCanvas* canvas = dynamic_cast(mo->getObject()); + if (canvas) { + auto ratioPlot = o2::quality_control::checker::getRatioPlotFromCanvas(canvas); + if (ratioPlot) { + auto q = checkBadChannelRatios(ratioPlot); + mQualityChecker.addCheckResult(q); + } + } + } + + // SOLAR histograms + if (matchHistName(mo->getName(), mMeanRatePerSolarHistName)) { + TH1F* h = getHisto(mo); + if (h) { + checkSolarMeanRates(h); + } + } + if (matchHistName(mo->getName(), mGoodChanFracPerSolarHistName)) { + TH1F* h = getHisto(mo); + if (h) { + checkSolarBadChannels(h); + } + } + // comparisons with reference + if (matchHistName(mo->getName(), mMeanRatePerSolarRefCompHistName)) { + TCanvas* canvas = dynamic_cast(mo->getObject()); + if (canvas) { + auto ratioPlot = o2::quality_control::checker::getRatioPlotFromCanvas(canvas); + if (ratioPlot) { + checkSolarMeanRateRatios(ratioPlot); + } + } + } + if (matchHistName(mo->getName(), mGoodChanFracPerSolarRefCompHistName)) { + TCanvas* canvas = dynamic_cast(mo->getObject()); + if (canvas) { + auto ratioPlot = o2::quality_control::checker::getRatioPlotFromCanvas(canvas); + if (ratioPlot) { + checkSolarBadChannelRatios(ratioPlot); + } + } + } + } + + return mQualityChecker.getQuality(); +} + +static void updateTitle(TH1* hist, std::string suffix) +{ + if (!hist) { + return; + } + TString title = hist->GetTitle(); + title.Append(" "); + title.Append(suffix.c_str()); + hist->SetTitle(title); +} + +void DigitsCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if ((mo->getName().find("RefComp/") != std::string::npos)) { + TCanvas* canvas = dynamic_cast(mo->getObject()); + if (!canvas) { + return; + } + + auto ratioPlot = o2::quality_control::checker::getRatioPlotFromCanvas(canvas); + if (!ratioPlot) { + return; + } + + std::string messages; + auto refCompPlots = o2::quality_control::checker::getPlotsFromCanvas(canvas, messages); + + double ratioPlotRange{ -1 }; + double ratioThreshold{ -1 }; + double plotScaleMin{ -1 }; + double plotScaleMax{ -1 }; + bool isSolar{ false }; + + if (matchHistName(mo->getName(), mMeanRateRefCompHistName)) { + ratioPlotRange = mRateRatioPlotScaleRange; + ratioThreshold = mMinRateRatio; + plotScaleMin = mRatePlotScaleMin; + plotScaleMax = mRatePlotScaleMax; + isSolar = false; + } else if (matchHistName(mo->getName(), mMeanRatePerSolarRefCompHistName)) { + ratioPlotRange = mRateRatioPerSolarPlotScaleRange; + ratioThreshold = mMinRateRatioPerSolar; + plotScaleMin = mRatePlotScaleMin; + plotScaleMax = mRatePlotScaleMax; + isSolar = true; + } else if (matchHistName(mo->getName(), mGoodChanFracRefCompHistName)) { + ratioPlotRange = mGoodFractionRatioPlotScaleRange; + ratioThreshold = mMinGoodFractionRatio; + plotScaleMin = 0; + plotScaleMax = 1.05; + isSolar = false; + } else if (matchHistName(mo->getName(), mGoodChanFracPerSolarRefCompHistName)) { + ratioPlotRange = mGoodFractionRatioPerSolarPlotScaleRange; + ratioThreshold = mMinGoodFractionRatioPerSolar; + plotScaleMin = 0; + plotScaleMax = 1.05; + isSolar = true; + } + + if (ratioPlotRange < 0) { + return; + } + + ratioPlot->SetMinimum(1.0 - ratioPlotRange); + ratioPlot->SetMaximum(1.0 + ratioPlotRange); + ratioPlot->GetXaxis()->SetTickLength(0); + + if (isSolar) { + addChamberDelimitersToSolarHistogram(ratioPlot); + addSolarBinLabels(ratioPlot); + } else { + addChamberDelimiters(ratioPlot); + addDEBinLabels(ratioPlot); + } + drawThreshold(ratioPlot, ratioThreshold); + + if (refCompPlots.first) { + refCompPlots.first->SetMinimum(plotScaleMin); + refCompPlots.first->SetMaximum(plotScaleMax); + if (isSolar) { + addChamberDelimitersToSolarHistogram(refCompPlots.first); + addChamberLabelsForSolar(refCompPlots.first); + addSolarBinLabels(refCompPlots.first); + if (refCompPlots.second) { + addSolarBinLabels(refCompPlots.second); + } + } else { + addChamberDelimiters(refCompPlots.first); + addChamberLabelsForDE(refCompPlots.first); + addDEBinLabels(refCompPlots.first); + if (refCompPlots.second) { + addDEBinLabels(refCompPlots.second); + } + } + } + + if (refCompPlots.second) { + if (checkResult == Quality::Good) { + refCompPlots.second->SetLineColor(kGreen + 2); + } else if (checkResult == Quality::Bad) { + refCompPlots.second->SetLineColor(kRed); + } else if (checkResult == Quality::Medium) { + refCompPlots.second->SetLineColor(kOrange - 3); + } else if (checkResult == Quality::Null) { + refCompPlots.second->SetLineColor(kViolet - 6); + } + } + return; + } + + if (mo->getName().find("Occupancy_Elec") != std::string::npos || + mo->getName().find("OccupancySignal_Elec") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + h->SetDrawOption("colz"); + h->SetMinimum(mRatePlotScaleMin); + h->SetMaximum(mRatePlotScaleMax); + + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } + + if ((mo->getName().find("Rate_ST12") != std::string::npos) || + (mo->getName().find("Rate_ST345") != std::string::npos) || + (mo->getName().find("Rate_XY") != std::string::npos)) { + auto* h = dynamic_cast(mo->getObject()); + + h->SetMinimum(mRatePlotScaleMin); + h->SetMaximum(mRatePlotScaleMax); + + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0.0); + h->GetYaxis()->SetLabelSize(0.0); + } + + if (mo->getName().find("MeanRate") != std::string::npos) { + TH1F* h = getHisto(mo); + if (!h) { + return; + } + + double scaleMin{ mRatePlotScaleMin }; + double scaleMax{ mRatePlotScaleMax }; + h->SetMinimum(scaleMin); + h->SetMaximum(scaleMax); + h->GetYaxis()->SetTitle("rate (kHz)"); + + if (mo->getName().find("MeanRatePerSolar") != std::string::npos) { + addChamberDelimitersToSolarHistogram(h, scaleMin, scaleMax); + addChamberLabelsForSolar(h); + } else { + addChamberDelimiters(h, scaleMin, scaleMax); + addChamberLabelsForDE(h); + } + + // only the plot used for the check is beautified by changing the color + // and adding the horizontal lines corresponding to the thresholds + if (matchHistName(mo->getName(), mMeanRateHistName)) { + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + + drawThresholdsPerStation(h, mMinRatePerStation, mMinRate); + drawThresholdsPerStation(h, mMaxRatePerStation, mMaxRate); + } + + // also beautify the SOLAR plot + if (matchHistName(mo->getName(), mMeanRatePerSolarHistName)) { + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + + drawThreshold(h, mMinRatePerSolar); + drawThreshold(h, mMaxRatePerSolar); + } + } + + if (mo->getName().find("GoodChannelsFraction") != std::string::npos) { + TH1F* h = getHisto(mo); + if (!h) { + return; + } + + float scaleMin{ 0 }; + float scaleMax{ 1.05 }; + h->SetMinimum(scaleMin); + h->SetMaximum(scaleMax); + h->GetYaxis()->SetTitle("fraction"); + + if (mo->getName().find("GoodChannelsFractionPerSolar") != std::string::npos) { + addChamberDelimitersToSolarHistogram(h, scaleMin, scaleMax); + addChamberLabelsForSolar(h); + } else { + addChamberDelimiters(h, scaleMin, scaleMax); + addChamberLabelsForDE(h); + } + + // only the plot used for the check is beautified by changing the color + // and adding the horizontal lines corresponding to the thresholds + if (matchHistName(mo->getName(), mGoodChanFracHistName)) { + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + + drawThresholdsPerStation(h, mMinGoodFractionPerStation, mMinGoodFraction); + } + + // also beautify the SOLAR plot + if (matchHistName(mo->getName(), mGoodChanFracPerSolarHistName)) { + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + + drawThreshold(h, mMinGoodFractionPerSolar); + } + } + + // update quality flags for each DE + if (mo->getName().find("QualityFlagPerDE") != std::string::npos) { + TH2F* h = getHisto(mo); + if (!h) { + return; + } + + std::string badDEs; + for (int deIndex = 0; deIndex < mQualityChecker.mQuality.size(); deIndex++) { + float ybin = 0; + if (mQualityChecker.mQuality[deIndex] == Quality::Good) { + ybin = 3; + } + if (mQualityChecker.mQuality[deIndex] == Quality::Medium) { + ybin = 2; + } + if (mQualityChecker.mQuality[deIndex] == Quality::Bad) { + ybin = 1; + std::string deIdStr = std::to_string(getDEFromIndex(deIndex)); + if (badDEs.empty()) { + badDEs = deIdStr; + } else { + badDEs += std::string(" ") + deIdStr; + } + } + + h->SetBinContent(deIndex + 1, ybin, 1); + } + + if (!badDEs.empty()) { + std::string text = std::string("Bad DEs: ") + badDEs; + TPaveLabel* label = new TPaveLabel(0.2, 0.8, 0.8, 0.88, text.c_str(), "blNDC"); + label->SetBorderSize(1); + h->GetListOfFunctions()->Add(label); + + ILOG(Warning, Support) << "[DecodingCheck] " << text << ENDM; + } + } + + // update quality flags for each SOLAR + if (mo->getName().find("QualityFlagPerSolar") != std::string::npos) { + TH2F* h = getHisto(mo); + if (!h) { + return; + } + + std::string badSolarBoards; + for (int solar = 0; solar < mSolarQuality.size(); solar++) { + float ybin = 0; + if (mSolarQuality[solar] == Quality::Good) { + ybin = 3; + } + if (mSolarQuality[solar] == Quality::Medium) { + ybin = 2; + } + if (mSolarQuality[solar] == Quality::Bad) { + ybin = 1; + std::string solarId = std::to_string(getSolarIdFromIndex(solar)); + if (badSolarBoards.empty()) { + badSolarBoards = solarId; + } else { + badSolarBoards += std::string(" ") + solarId; + } + } + + h->SetBinContent(solar + 1, ybin, 1); + } + + if (!badSolarBoards.empty()) { + std::string badSolarList = std::string("Bad SOLAR boards: ") + badSolarBoards; + TPaveLabel* label = new TPaveLabel(0.2, 0.8, 0.8, 0.88, badSolarList.c_str(), "blNDC"); + label->SetBorderSize(1); + h->GetListOfFunctions()->Add(label); + + ILOG(Warning, Support) << "[DigitsCheck] " << badSolarList << ENDM; + } + } +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/DigitsPostProcessing.cxx b/Modules/MUON/MCH/src/DigitsPostProcessing.cxx new file mode 100644 index 0000000000..3f3b6341fe --- /dev/null +++ b/Modules/MUON/MCH/src/DigitsPostProcessing.cxx @@ -0,0 +1,385 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsPostProcessing.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Post-processing of the MCH digits +/// \since 21/06/2022 +/// + +#include "MCH/DigitsPostProcessing.h" +#include "MUONCommon/Helpers.h" +#include "Common/ReferenceComparatorPlot.h" +#include "QualityControl/ReferenceUtils.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include + +using namespace o2::quality_control_modules::muonchambers; +using namespace o2::quality_control_modules::muon; + +void DigitsPostProcessing::configure(const boost::property_tree::ptree& config) +{ + ReferenceComparatorTask::configure(config); + + mConfig = PostProcessingConfigMCH(getID(), config); +} + +//_________________________________________________________________________________________ + +void DigitsPostProcessing::createRatesHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + //---------------------------------- + // Rate plotters + //---------------------------------- + + mRatesPlotter.reset(); + mRatesPlotter = std::make_unique("Rates/", mChannelRateMin, mChannelRateMax, true, mFullHistos); + mRatesPlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mRatesPlotter->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + + mRatesPlotterSignal.reset(); + mRatesPlotterSignal = std::make_unique("RatesSignal/", mChannelRateMin, mChannelRateMax, true, mFullHistos); + mRatesPlotterSignal->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mRatesPlotterSignal->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + + //---------------------------------- + // Rate plotters for last cycle + //---------------------------------- + + if (mEnableLastCycleHistos) { + // Helpers to extract plots from last cycle + auto obj = mCcdbObjects.find(rateSourceName()); + if (obj != mCcdbObjects.end()) { + mElecMapOnCycle.reset(); + mElecMapOnCycle = std::make_unique>(); + } + + obj = mCcdbObjects.find(rateSignalSourceName()); + if (obj != mCcdbObjects.end()) { + mElecMapSignalOnCycle.reset(); + mElecMapSignalOnCycle = std::make_unique>(); + } + + mRatesPlotterOnCycle.reset(); + mRatesPlotterOnCycle = std::make_unique("Rates/LastCycle/", mChannelRateMin, mChannelRateMax, false, mFullHistos); + mRatesPlotterOnCycle->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mRatesPlotterOnCycle->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + + mRatesPlotterSignalOnCycle.reset(); + mRatesPlotterSignalOnCycle = std::make_unique("RatesSignal/LastCycle/", mChannelRateMin, mChannelRateMax, false, mFullHistos); + mRatesPlotterSignalOnCycle->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mRatesPlotterSignalOnCycle->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + } + + //---------------------------------- + // Rate trends + //---------------------------------- + + if (mEnableTrending) { + mRatesTrendsPlotter.reset(); + mRatesTrendsPlotter = std::make_unique("Trends/Rates/", mFullHistos); + mRatesTrendsPlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + mRatesTrendsPlotterSignal.reset(); + mRatesTrendsPlotterSignal = std::make_unique("Trends/RatesSignal/", mFullHistos); + mRatesTrendsPlotterSignal->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + } +} + +//_________________________________________________________________________________________ + +void DigitsPostProcessing::createOrbitHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + //---------------------------------- + // Orbit plotters + //---------------------------------- + + mOrbitsPlotter.reset(); + mOrbitsPlotter = std::make_unique("Orbits/"); + mOrbitsPlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mOrbitsPlotter->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + + mOrbitsPlotterSignal.reset(); + mOrbitsPlotterSignal = std::make_unique("OrbitsSignal/"); + mOrbitsPlotterSignal->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mOrbitsPlotterSignal->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + + //---------------------------------- + // Orbit plotters for last cycle + //---------------------------------- + + if (mEnableLastCycleHistos) { + // Helpers to extract plots from last cycle + auto obj = mCcdbObjects.find(orbitsSourceName()); + if (obj != mCcdbObjects.end()) { + mDigitsOrbitsOnCycle.reset(); + mDigitsOrbitsOnCycle = std::make_unique>(); + } + + obj = mCcdbObjects.find(orbitsSignalSourceName()); + if (obj != mCcdbObjects.end()) { + mDigitsSignalOrbitsOnCycle.reset(); + mDigitsSignalOrbitsOnCycle = std::make_unique>(); + } + + mOrbitsPlotterOnCycle.reset(); + mOrbitsPlotterOnCycle = std::make_unique("Orbits/LastCycle/"); + mOrbitsPlotterOnCycle->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mOrbitsPlotterOnCycle->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + + mOrbitsPlotterSignalOnCycle.reset(); + mOrbitsPlotterSignalOnCycle = std::make_unique("OrbitsSignal/LastCycle/"); + mOrbitsPlotterSignalOnCycle->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mOrbitsPlotterSignalOnCycle->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + } +} + +//_________________________________________________________________________________________ + +void DigitsPostProcessing::initialize(Trigger t, framework::ServiceRegistryRef services) +{ + ReferenceComparatorTask::initialize(t, services); + + auto& qcdb = services.get(); + const auto& activity = t.activity; + + mFullHistos = getConfigurationParameter(mCustomParameters, "FullHistos", mFullHistos, activity); + mEnableLastCycleHistos = getConfigurationParameter(mCustomParameters, "EnableLastCycleHistos", mEnableLastCycleHistos, activity); + mEnableTrending = getConfigurationParameter(mCustomParameters, "EnableTrending", mEnableTrending, activity); + + mChannelRateMin = getConfigurationParameter(mCustomParameters, "ChannelRateMin", mChannelRateMin, activity); + mChannelRateMax = getConfigurationParameter(mCustomParameters, "ChannelRateMax", mChannelRateMax, activity); + + mCcdbObjects.clear(); + mCcdbObjects.emplace(rateSourceName(), CcdbObjectHelper()); + mCcdbObjects.emplace(rateSignalSourceName(), CcdbObjectHelper()); + mCcdbObjects.emplace(orbitsSourceName(), CcdbObjectHelper()); + mCcdbObjects.emplace(orbitsSignalSourceName(), CcdbObjectHelper()); + + // set objects path from configuration + for (auto source : mConfig.dataSources) { + std::string sourceType, sourceName; + splitDataSourceName(source.name, sourceType, sourceName); + if (sourceType.empty()) { + continue; + } + + auto obj = mCcdbObjects.find(sourceType); + if (obj != mCcdbObjects.end()) { + obj->second.mPath = source.path; + obj->second.mName = sourceName; + } + } + + // instantiate and publish the histograms + createRatesHistos(t, &qcdb); + createOrbitHistos(t, &qcdb); + + //-------------------------------------------------- + // Detector quality histogram + //-------------------------------------------------- + + mHistogramQualityPerDE.reset(); + mHistogramQualityPerDE = std::make_unique("QualityFlagPerDE", "Quality Flag vs DE", getNumDE(), 0, getNumDE(), 3, 0, 3); + addDEBinLabels(mHistogramQualityPerDE.get()); + addChamberDelimiters(mHistogramQualityPerDE.get()); + addChamberLabelsForDE(mHistogramQualityPerDE.get()); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(1, "Bad"); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(2, "Medium"); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(3, "Good"); + mHistogramQualityPerDE->SetOption("col"); + mHistogramQualityPerDE->SetStats(0); + getObjectsManager()->startPublishing(mHistogramQualityPerDE.get(), core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistogramQualityPerDE.get(), "col"); + getObjectsManager()->setDisplayHint(mHistogramQualityPerDE.get(), "gridy"); + + mHistogramQualityPerSolar.reset(); + mHistogramQualityPerSolar = std::make_unique("QualityFlagPerSolar", "Quality Flag vs Solar", getNumSolar(), 0, getNumSolar(), 3, 0, 3); + addSolarBinLabels(mHistogramQualityPerSolar.get()); + addChamberDelimitersToSolarHistogram(mHistogramQualityPerSolar.get()); + addChamberLabelsForSolar(mHistogramQualityPerSolar.get()); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(1, "Bad"); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(2, "Medium"); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(3, "Good"); + mHistogramQualityPerSolar->SetOption("col"); + mHistogramQualityPerSolar->SetStats(0); + getObjectsManager()->startPublishing(mHistogramQualityPerSolar.get(), core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistogramQualityPerSolar.get(), "col"); + getObjectsManager()->setDisplayHint(mHistogramQualityPerSolar.get(), "gridy"); +} + +//_________________________________________________________________________________________ + +void DigitsPostProcessing::updateRateHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + auto obj = mCcdbObjects.find(rateSourceName()); + if (obj != mCcdbObjects.end() && obj->second.update(qcdb, t.timestamp, t.activity)) { + TH2FRatio* hr = obj->second.get(); + if (hr) { + mRatesPlotter->update(hr); + if (mEnableLastCycleHistos) { + // extract the average occupancies on the last cycle + mElecMapOnCycle->update(hr); + mRatesPlotterOnCycle->update(mElecMapOnCycle.get()); + } + + if (mEnableTrending) { + auto time = obj->second.getTimeStamp() / 1000; // ROOT expects seconds since epoch + if (mEnableLastCycleHistos) { + mRatesTrendsPlotter->update(time, mElecMapOnCycle.get()); + } else { + mRatesTrendsPlotter->update(time, hr); + } + } + } + } + + obj = mCcdbObjects.find(rateSignalSourceName()); + if (obj != mCcdbObjects.end() && obj->second.update(qcdb, t.timestamp, t.activity)) { + TH2FRatio* hr = obj->second.get(); + if (hr) { + mRatesPlotterSignal->update(hr); + if (mEnableLastCycleHistos) { + // extract the average occupancies on the last cycle + mElecMapSignalOnCycle->update(hr); + mRatesPlotterSignalOnCycle->update(mElecMapSignalOnCycle.get()); + } + + if (mEnableTrending) { + auto time = obj->second.getTimeStamp() / 1000; // ROOT expects seconds since epoch + if (mEnableLastCycleHistos) { + mRatesTrendsPlotterSignal->update(time, mElecMapSignalOnCycle.get()); + } else { + mRatesTrendsPlotterSignal->update(time, hr); + } + } + } + } +} + +//_________________________________________________________________________________________ + +void DigitsPostProcessing::updateOrbitHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + auto obj = mCcdbObjects.find(orbitsSourceName()); + if (obj != mCcdbObjects.end() && obj->second.update(qcdb, t.timestamp, t.activity)) { + TH2F* hr = obj->second.get(); + if (hr) { + mOrbitsPlotter->update(hr); + if (mEnableLastCycleHistos) { + // extract the average occupancies on the last cycle + mDigitsOrbitsOnCycle->update(hr); + mOrbitsPlotterOnCycle->update(mDigitsOrbitsOnCycle.get()); + } + } + } + + obj = mCcdbObjects.find(orbitsSignalSourceName()); + if (obj != mCcdbObjects.end() && obj->second.update(qcdb, t.timestamp, t.activity)) { + TH2F* hr = obj->second.get(); + if (hr) { + mOrbitsPlotterSignal->update(hr); + if (mEnableLastCycleHistos) { + // extract the average occupancies on the last cycle + mDigitsSignalOrbitsOnCycle->update(hr); + mOrbitsPlotterSignalOnCycle->update(mDigitsSignalOrbitsOnCycle.get()); + } + } + } +} + +//_________________________________________________________________________________________ + +TH1* DigitsPostProcessing::getHistogram(std::string_view plotName) +{ + TH1* result{ nullptr }; + for (auto hist : mHistogramsAll) { + if (plotName == hist->GetName()) { + result = hist; + break; + } + } + return result; +} + +void DigitsPostProcessing::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + updateRateHistos(t, &qcdb); + updateOrbitHistos(t, &qcdb); + + auto& comparatorPlots = getComparatorPlots(); + for (auto& [plotName, plot] : comparatorPlots) { + TH1* hist = getHistogram(plotName); + if (!hist) { + continue; + } + + plot->update(hist); + } +} + +//_________________________________________________________________________________________ + +void DigitsPostProcessing::finalize(Trigger t, framework::ServiceRegistryRef) +{ +} diff --git a/Modules/MUON/MCH/src/DigitsTask.cxx b/Modules/MUON/MCH/src/DigitsTask.cxx new file mode 100644 index 0000000000..7fe968e291 --- /dev/null +++ b/Modules/MUON/MCH/src/DigitsTask.cxx @@ -0,0 +1,282 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsTask.cxx +/// \author Andrea Ferrero +/// \author Sebastien Perrin +/// + +#include "MCH/DigitsTask.h" +#include "MCH/Helpers.h" +#include "MUONCommon/Helpers.h" +#include "MCHMappingInterface/Segmentation.h" +#include "MCHRawDecoder/DataDecoder.h" +#include "QualityControl/QcInfoLogger.h" +#include "DetectorsBase/GRPGeomHelper.h" +#include +#include +#include +#include "MCHGlobalMapping/DsIndex.h" + +#include +#include + +using namespace std; +using namespace o2::mch; +using namespace o2::mch::raw; +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +template +void DigitsTask::publishObject(T* histo, std::string drawOption, bool statBox, bool isExpert) +{ + histo->SetOption(drawOption.c_str()); + if (!statBox) { + histo->SetStats(0); + } + mAllHistograms.push_back(histo); + if (mFullHistos || (isExpert == false)) { + getObjectsManager()->startPublishing(histo); + getObjectsManager()->setDefaultDrawOptions(histo, drawOption); + } +} + +void DigitsTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize DigitsTask" << AliceO2::InfoLogger::InfoLogger::endm; + + mIsSignalDigit = o2::mch::createDigitFilter(20, true, true); + + // flags to enable the publication of either 1D and 2D maps of channel rates + mEnable1DRateMaps = getConfigurationParameter(mCustomParameters, "Enable1DRateMaps", mEnable1DRateMaps); + mEnable2DRateMaps = getConfigurationParameter(mCustomParameters, "Enable2DRateMaps", mEnable2DRateMaps); + + // flag to enable extra disagnostics plots; it also enables on-cycle plots + mFullHistos = getConfigurationParameter(mCustomParameters, "FullHistos", mFullHistos); + + resetOrbits(); + + const uint32_t nElecXbins = NumberOfDualSampas; + + // Histograms in electronics coordinates + if (mEnable1DRateMaps) { + mHistogramRatePerDualSampa = std::make_unique("RatePerDualSampa", "Average rate per dual sampa;DS index;rate (kHz)", o2::mch::NumberOfDualSampas, 0, o2::mch::NumberOfDualSampas, false); + mHistogramRatePerDualSampa->Sumw2(kFALSE); + publishObject(mHistogramRatePerDualSampa.get(), "hist", false, false); + + mHistogramRateSignalPerDualSampa = std::make_unique("RateSignalPerDualSampa", "Average rate per dual sampa (signal);DS index;rate (kHz)", o2::mch::NumberOfDualSampas, 0, o2::mch::NumberOfDualSampas, false); + mHistogramRateSignalPerDualSampa->Sumw2(kFALSE); + publishObject(mHistogramRateSignalPerDualSampa.get(), "hist", false, false); + } + + if (mEnable2DRateMaps) { + mHistogramOccupancyElec = std::make_unique("Occupancy_Elec", "Occupancy", nElecXbins, 0, nElecXbins, 64, 0, 64, true); + mHistogramOccupancyElec->Sumw2(kFALSE); + publishObject(mHistogramOccupancyElec.get(), "colz", false, false); + + mHistogramSignalOccupancyElec = std::make_unique("OccupancySignal_Elec", "Occupancy (signal)", nElecXbins, 0, nElecXbins, 64, 0, 64, true); + mHistogramSignalOccupancyElec->Sumw2(kFALSE); + publishObject(mHistogramSignalOccupancyElec.get(), "colz", false, false); + } + + mHistogramDigitsOrbitElec = std::make_unique("DigitOrbit_Elec", "Digit orbits vs DS Id", nElecXbins, 0, nElecXbins, 130, -1, 129); + publishObject(mHistogramDigitsOrbitElec.get(), "colz", true, false); + + mHistogramDigitsSignalOrbitElec = std::make_unique("DigitSignalOrbit_Elec", "Digit orbits vs DS Id (signal)", nElecXbins, 0, nElecXbins, 130, -1, 129); + publishObject(mHistogramDigitsSignalOrbitElec.get(), "colz", true, false); + + if (mFullHistos) { + mHistogramDigitsBcInOrbit = std::make_unique("Expert/DigitsBcInOrbit_Elec", "Digit BC vs DS Id", nElecXbins, 0, nElecXbins, 3600, 0, 3600); + publishObject(mHistogramDigitsBcInOrbit.get(), "colz", false, true); + + mHistogramAmplitudeVsSamples = std::make_unique("Expert/AmplitudeVsSamples", "Digit amplitude vs nsamples", 1000, 0, 1000, 1000, 0, 10000); + publishObject(mHistogramAmplitudeVsSamples.get(), "colz", false, true); + + // Histograms in detector coordinates + for (auto de : o2::mch::constants::deIdsForAllMCH) { + auto h = std::make_unique(TString::Format("Expert/%sADCamplitude_DE%03d", getHistoPath(de).c_str(), de), + TString::Format("ADC amplitude (DE%03d)", de), 5000, 0, 5000); + publishObject(h.get(), "hist", false, true); + mHistogramADCamplitudeDE.emplace(de, std::move(h)); + } + } +} + +void DigitsTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; +} + +void DigitsTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; +} + +static bool checkInput(o2::framework::ProcessingContext& ctx, std::string binding) +{ + bool result = false; + for (auto&& input : ctx.inputs()) { + if (input.spec->binding == binding) { + result = true; + break; + } + } + return result; +} + +void DigitsTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + static auto nOrbitsPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + mNOrbits += nOrbitsPerTF; + + auto digits = ctx.inputs().get>("digits"); + for (auto& d : digits) { + plotDigit(d); + } +} + +void DigitsTask::plotDigit(const o2::mch::Digit& digit) +{ + int ADC = digit.getADC(); + int deId = digit.getDetID(); + int padId = digit.getPadID(); + + if (ADC < 0 || deId <= 0 || padId < 0) { + return; + } + + // Fill NHits Elec Histogram and ADC distribution + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + + int dsId = segment.padDualSampaId(padId); + int channel = segment.padDualSampaChannel(padId); + + bool isSignal = mIsSignalDigit(digit); + + //-------------------------------------------------------------------------- + // Occupancy plots + //-------------------------------------------------------------------------- + + // fecId and channel uniquely identify each physical pad + int fecId = getDsIndex(DsDetId{ deId, dsId }); + + if (mEnable1DRateMaps) { + mHistogramRatePerDualSampa->getNum()->Fill(fecId); + if (isSignal) { + mHistogramRateSignalPerDualSampa->getNum()->Fill(fecId); + } + } + if (mEnable2DRateMaps) { + mHistogramOccupancyElec->getNum()->Fill(fecId, channel); + if (isSignal) { + mHistogramSignalOccupancyElec->getNum()->Fill(fecId, channel); + } + } + + //-------------------------------------------------------------------------- + // Time plots + //-------------------------------------------------------------------------- + + auto tfTime = digit.getTime(); + int orbit = -256; + int bc = 3559; + if (tfTime != o2::mch::raw::DataDecoder::tfTimeInvalid) { + orbit = digit.getTime() / static_cast(o2::constants::lhc::LHCMaxBunches); + bc = digit.getTime() % static_cast(o2::constants::lhc::LHCMaxBunches); + } + mHistogramDigitsOrbitElec->Fill(fecId, orbit); + if (isSignal) { + mHistogramDigitsSignalOrbitElec->Fill(fecId, orbit); + } + + if (mFullHistos) { + mHistogramDigitsBcInOrbit->Fill(fecId, bc); + + //-------------------------------------------------------------------------- + // ADC amplitude plots + //-------------------------------------------------------------------------- + + auto h = mHistogramADCamplitudeDE.find(deId); + if ((h != mHistogramADCamplitudeDE.end()) && (h->second != NULL)) { + h->second->Fill(ADC); + } + mHistogramAmplitudeVsSamples->Fill(digit.getNofSamples(), ADC); + } +} + +void DigitsTask::updateOrbits() +{ + static constexpr double sOrbitLengthInNanoseconds = 3564 * 25; + static constexpr double sOrbitLengthInMicroseconds = sOrbitLengthInNanoseconds / 1000; + static constexpr double sOrbitLengthInMilliseconds = sOrbitLengthInMicroseconds / 1000; + + if (mEnable1DRateMaps) { + for (int dsIndex = 0; dsIndex <= NumberOfDualSampas; dsIndex++) { + mHistogramRatePerDualSampa->getDen()->SetBinContent(dsIndex + 1, mNOrbits * sOrbitLengthInMilliseconds * numberOfDualSampaChannels(dsIndex)); + mHistogramRateSignalPerDualSampa->getDen()->SetBinContent(dsIndex + 1, mNOrbits * sOrbitLengthInMilliseconds * numberOfDualSampaChannels(dsIndex)); + } + } + + if (mEnable2DRateMaps) { + mHistogramOccupancyElec->getDen()->SetBinContent(1, 1, mNOrbits * sOrbitLengthInMilliseconds); + mHistogramSignalOccupancyElec->getDen()->SetBinContent(1, 1, mNOrbits * sOrbitLengthInMilliseconds); + } +} + +void DigitsTask::resetOrbits() +{ + mNOrbits = 0; +} + +void DigitsTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; + + updateOrbits(); + + // update mergeable ratios + if (mEnable1DRateMaps) { + mHistogramRatePerDualSampa->update(); + mHistogramRateSignalPerDualSampa->update(); + } + if (mEnable2DRateMaps) { + mHistogramOccupancyElec->update(); + mHistogramSignalOccupancyElec->update(); + } +} + +void DigitsTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; +} + +void DigitsTask::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histograms" << AliceO2::InfoLogger::InfoLogger::endm; + + resetOrbits(); + + for (auto h : mAllHistograms) { + h->Reset(); + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/EfficiencyPlotter.cxx b/Modules/MUON/MCH/src/EfficiencyPlotter.cxx new file mode 100644 index 0000000000..fc3b64610e --- /dev/null +++ b/Modules/MUON/MCH/src/EfficiencyPlotter.cxx @@ -0,0 +1,170 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file EfficiencyPlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/EfficiencyPlotter.h" +#include "MCH/Helpers.h" +#include "MCHMappingInterface/Segmentation.h" +#include "MCHGlobalMapping/DsIndex.h" + +using namespace o2::mch; +using namespace o2::mch::raw; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +EfficiencyPlotter::EfficiencyPlotter(std::string path, bool fullPlots) +{ + mElec2DetMapper = createElec2DetMapper(); + mDet2ElecMapper = createDet2ElecMapper(); + mFeeLink2SolarMapper = createFeeLink2SolarMapper(); + mSolar2FeeLinkMapper = createSolar2FeeLinkMapper(); + + mElecMapReductor = std::make_unique(); + + //---------------------------------- + // Mean efficiency histograms + //---------------------------------- + + std::string sc[2] = { "B", "NB" }; + for (int ci = 0; ci < 2; ci++) { + mHistogramMeanEfficiencyPerDE[ci] = std::make_unique(TString::Format("%sMeanEfficiency%s", path.c_str(), sc[ci].c_str()), + TString::Format("Mean Efficiency vs DE (%s)", sc[ci].c_str()), + getNumDE(), 0, getNumDE()); + addDEBinLabels(mHistogramMeanEfficiencyPerDE[ci].get()); + addHisto(mHistogramMeanEfficiencyPerDE[ci].get(), false, "histo", "histo"); + } + + mHistogramMeanEfficiencyPerSolar = std::make_unique(TString::Format("%sMeanEfficiencyPerSolar", path.c_str()), "Mean Efficiency per SOLAR board", + getNumSolar(), 0, getNumSolar()); + addSolarBinLabels(mHistogramMeanEfficiencyPerSolar.get()); + addHisto(mHistogramMeanEfficiencyPerSolar.get(), false, "histo", "histo"); + + //-------------------------------------------------- + // Efficiency histograms in global detector coordinates + //-------------------------------------------------- + + mHistogramEfficiencyGlobal[0] = std::make_unique(fmt::format("{}Efficiency_ST12", path), "ST12 Efficiency", 0, 5); + mHistogramEfficiencyGlobal[0]->init(); + addHisto(mHistogramEfficiencyGlobal[0]->getHist(), false, "colz", ""); + + mHistogramEfficiencyGlobal[1] = std::make_unique(fmt::format("{}Efficiency_ST345", path), "ST345 Efficiency", 1, 10); + mHistogramEfficiencyGlobal[1]->init(); + addHisto(mHistogramEfficiencyGlobal[1]->getHist(), false, "colz", ""); + + //-------------------------------------------------- + // Efficiency histograms in detector coordinates + //-------------------------------------------------- + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + auto h = std::make_shared(TString::Format("%s%sEfficiency_XY_B_%03d", path.c_str(), getHistoPath(de).c_str(), de), + TString::Format("Hit Efficiency (DE%03d B)", de), de, int(0)); + mHistogramEfficiencyDE[0].insert(make_pair(de, h)); + if (fullPlots) { + addHisto(h->getHist(), false, "colz", ""); + } + + h = std::make_shared(TString::Format("%s%sEfficiency_XY_NB_%03d", path.c_str(), getHistoPath(de).c_str(), de), + TString::Format("Hit Efficiency (DE%03d NB)", de), de, int(1)); + mHistogramEfficiencyDE[1].insert(make_pair(de, h)); + if (fullPlots) { + addHisto(h->getHist(), false, "colz", ""); + } + } +} + +//_________________________________________________________________________________________ + +void EfficiencyPlotter::fillAverageHistograms() +{ + for (int ci = 0; ci < 2; ci++) { + for (size_t de = 0; de < mHistogramMeanEfficiencyPerDE[ci]->GetXaxis()->GetNbins(); de++) { + mHistogramMeanEfficiencyPerDE[ci]->SetBinContent(de + 1, mElecMapReductor->getDeValue(de, ci)); + mHistogramMeanEfficiencyPerDE[ci]->SetBinError(de + 1, 0.1); + } + } + + for (size_t solar = 0; solar < mHistogramMeanEfficiencyPerSolar->GetXaxis()->GetNbins(); solar++) { + mHistogramMeanEfficiencyPerSolar->SetBinContent(solar + 1, mElecMapReductor->getSolarValue(solar)); + mHistogramMeanEfficiencyPerSolar->SetBinError(solar + 1, 0.1); + } +} + +//_________________________________________________________________________________________ + +void EfficiencyPlotter::fillGlobalHistograms(TH2F* h) +{ + if (!h) { + return; + } + + // loop over bins in electronics coordinates, and map the channels to the corresponding cathode pads + int nbinsx = h->GetXaxis()->GetNbins(); + int nbinsy = h->GetYaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + // address of the DS board in detector representation + auto dsDetId = getDsDetId(i - 1); + auto deId = dsDetId.deId(); + auto dsId = dsDetId.dsId(); + + for (int j = 1; j <= nbinsy; j++) { + int channel = j - 1; + int padId = -1; + + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + padId = segment.findPadByFEE(dsId, int(channel)); + + if (padId < 0) { + continue; + } + + double eff = h->GetBinContent(i, j); + + double padX = segment.padPositionX(padId); + double padY = segment.padPositionY(padId); + float padSizeX = segment.padSizeX(padId); + float padSizeY = segment.padSizeY(padId); + int cathode = segment.isBendingPad(padId) ? 0 : 1; + + // Fill 2D rate histograms + auto hEfficiency = mHistogramEfficiencyDE[cathode].find(deId); + if ((hEfficiency != mHistogramEfficiencyDE[cathode].end()) && (hEfficiency->second != NULL)) { + hEfficiency->second->Set(padX, padY, padSizeX, padSizeY, eff); + } + } + } + + mHistogramEfficiencyGlobal[0]->set(mHistogramEfficiencyDE[0], mHistogramEfficiencyDE[1]); + mHistogramEfficiencyGlobal[1]->set(mHistogramEfficiencyDE[0], mHistogramEfficiencyDE[1]); +} + +//_________________________________________________________________________________________ + +void EfficiencyPlotter::update(TH2F* hEfficiency) +{ + // extract the integrated average occupancies + mElecMapReductor->update(hEfficiency); + + fillAverageHistograms(); + fillGlobalHistograms(hEfficiency); +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/EfficiencyTrendsPlotter.cxx b/Modules/MUON/MCH/src/EfficiencyTrendsPlotter.cxx new file mode 100644 index 0000000000..b2b2b7133e --- /dev/null +++ b/Modules/MUON/MCH/src/EfficiencyTrendsPlotter.cxx @@ -0,0 +1,77 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file EfficiencyTrendsPlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/EfficiencyTrendsPlotter.h" +#include "MCH/Helpers.h" +#include "MCHMappingInterface/Segmentation.h" +#include +#include + +using namespace o2::mch::raw; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +EfficiencyTrendsPlotter::EfficiencyTrendsPlotter(std::string path, bool fullPlots) +{ + mElecMapReductor = std::make_unique(); + + //-------------------------------------------------- + // Efficiency trends + //-------------------------------------------------- + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int deID = getDEindex(de); + if (deID < 0) { + continue; + } + + mTrendsEfficiency[deID] = std::make_unique(fmt::format("{}{}/Efficiency_DE{}", path, getHistoPath(de), de), + fmt::format("DE{} Efficiency", de), "efficiency"); + mTrendsEfficiency[deID]->addGraph("B", "bending "); + mTrendsEfficiency[deID]->addGraph("NB", "non-bending"); + mTrendsEfficiency[deID]->addLegends(); + // mTrendsEfficiency[deID]->setRange(0, 1.2); + + if (fullPlots) { + addCanvas(mTrendsEfficiency[deID].get(), ""); + } + } +} + +//_________________________________________________________________________________________ + +void EfficiencyTrendsPlotter::update(long time, TH2F* h) +{ + // extract the integrated average occupancies + mElecMapReductor->update(h); + + for (size_t de = 0; de < getNumDE(); de++) { + std::array values; + for (int ci = 0; ci < 2; ci++) { + values[ci] = mElecMapReductor->getDeValue(de, ci); + } + mTrendsEfficiency[de]->update(time, values); + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/ErrorTask.cxx b/Modules/MUON/MCH/src/ErrorTask.cxx new file mode 100644 index 0000000000..4779b24c7b --- /dev/null +++ b/Modules/MUON/MCH/src/ErrorTask.cxx @@ -0,0 +1,161 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ErrorTask.cxx +/// \author Philippe Pillot +/// + +#include + +#include + +#include "QualityControl/QcInfoLogger.h" +#include "MCH/ErrorTask.h" +#include "MCHBase/Error.h" +#include "MCHBase/ErrorMap.h" +#include +#include +#include + +namespace o2::quality_control_modules::muonchambers +{ + +using namespace o2::mch; + +constexpr uint32_t getNDE() +{ + return 156; +} + +constexpr uint32_t getDEIdxOffset(uint32_t chamber) +{ + constexpr int offset[10] = { 0, 4, 8, 12, 16, 34, 52, 78, 104, 130 }; + return offset[chamber]; +} + +constexpr uint32_t getDEIdx(uint32_t de) +{ + if (de < 100 || de > 1025) { + return getNDE(); + } + return getDEIdxOffset(de / 100 - 1) + de % 100; +} + +constexpr uint32_t getDE(uint32_t idx) +{ + uint32_t chamber = 9; + while (idx < getDEIdxOffset(chamber)) { + --chamber; + } + return (chamber + 1) * 100 + idx - getDEIdxOffset(chamber); +} + +auto ErrorTask::createProfile(const char* name, const char* title, int nbins, double xmin, double xmax) +{ + // set the bin error option to "i" to make sure bins filled with zeros also get an error, + // otherwise the merging of TProfile with labels set on the x-axis ignores these bins, + // which biases the counting of the number of entries in these bins and thus the + // calculation of the average if their content is not always zero. + auto h = std::make_unique(name, title, nbins, xmin, xmax, "i"); + h->SetStats(0); + getObjectsManager()->startPublishing(h.get()); + return h; +} + +void ErrorTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize ErrorTask" << ENDM; + + mSummary = createProfile("Summary", "summary of all processing errors;;# per TF", + Error::typeNames.size(), 0., Error::typeNames.size()); + int i = 0; + for (const auto& typeName : Error::typeNames) { + mSummary->GetXaxis()->SetBinLabel(++i, typeName.second.c_str()); + } + + auto type = ErrorType::PreClustering_MultipleDigitsInSamePad; + mMultipleDigitsInSamePad = createProfile(Error::typeNames.at(type).c_str(), + (Error::typeDescriptions.at(type) + ";DE;# per TF").c_str(), + getNDE(), 0., getNDE()); + + type = ErrorType::Clustering_TooManyLocalMaxima; + mTooManyLocalMaxima = createProfile(Error::typeNames.at(type).c_str(), + (Error::typeDescriptions.at(type) + ";DE;# per TF").c_str(), + getNDE(), 0., getNDE()); + + for (auto i = 0; i < getNDE(); ++i) { + mMultipleDigitsInSamePad->GetXaxis()->SetBinLabel(i + 1, std::to_string(getDE(i)).c_str()); + mTooManyLocalMaxima->GetXaxis()->SetBinLabel(i + 1, std::to_string(getDE(i)).c_str()); + } +} + +void ErrorTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + reset(); +} + +void ErrorTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ErrorTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto errors = ctx.inputs().get>("errors"); + + ErrorMap errorMap{}; + errorMap.add(errors); + + // summary of all processing errors + for (const auto& [type, name] : Error::typeNames) { + mSummary->Fill(name.c_str(), errorMap.getNumberOfErrors(type)); + } + + // preclustering error "multiple digits on the same pad" per DE + std::array errorsPerDE{}; + errorMap.forEach(ErrorType::PreClustering_MultipleDigitsInSamePad, [&errorsPerDE](Error error) { + errorsPerDE[getDEIdx(error.id0)] += error.count; + }); + for (int i = 0; i <= getNDE(); ++i) { + mMultipleDigitsInSamePad->Fill(i, errorsPerDE[i]); + } + + // clustering error "too many local maxima" per DE + errorsPerDE.fill(0); + errorMap.forEach(ErrorType::Clustering_TooManyLocalMaxima, [&errorsPerDE](Error error) { + errorsPerDE[getDEIdx(error.id0)] += error.count; + }); + for (int i = 0; i <= getNDE(); ++i) { + mTooManyLocalMaxima->Fill(i, errorsPerDE[i]); + } +} + +void ErrorTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ErrorTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ErrorTask::reset() +{ + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mSummary->Reset(); + mMultipleDigitsInSamePad->Reset(); + mTooManyLocalMaxima->Reset(); +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/FECSyncStatusPlotter.cxx b/Modules/MUON/MCH/src/FECSyncStatusPlotter.cxx new file mode 100644 index 0000000000..a153e015b8 --- /dev/null +++ b/Modules/MUON/MCH/src/FECSyncStatusPlotter.cxx @@ -0,0 +1,145 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file FECSyncStatusPlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/FECSyncStatusPlotter.h" +#include "MCH/Helpers.h" +#include "MCHMappingInterface/Segmentation.h" +#include "MCHGlobalMapping/DsIndex.h" +#include +#include + +using namespace o2::mch; +using namespace o2::mch::raw; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +static void setYAxisLabels(TH2F* hErrors) +{ + TAxis* ay = hErrors->GetYaxis(); + for (int i = 1; i <= 10; i++) { + auto label = fmt::format("CH{}", i); + ay->SetBinLabel(i, label.c_str()); + } +} + +FECSyncStatusPlotter::FECSyncStatusPlotter(std::string path) +{ + mDet2ElecMapper = o2::mch::raw::createDet2ElecMapper(); + + //-------------------------------------------- + // Fraction of synchronized boards per DE + //-------------------------------------------- + + mGoodTFFractionPerDE = std::make_unique(TString::Format("%sSyncedBoardsFractionPerDE", path.c_str()), "Synchronized boards fraction per DE", getNumDE(), 0, getNumDE()); + addDEBinLabels(mGoodTFFractionPerDE.get()); + addHisto(mGoodTFFractionPerDE.get(), false, "hist", ""); + + mGoodTFFractionPerSolar = std::make_unique(TString::Format("%sSyncedBoardsFractionPerSolar", path.c_str()), "Synchronized boards fraction per SOLAR", getNumSolar(), 0, getNumSolar()); + addSolarBinLabels(mGoodTFFractionPerSolar.get()); + addHisto(mGoodTFFractionPerSolar.get(), false, "hist", ""); + + mGoodBoardsFractionPerDE = std::make_unique(TString::Format("%sSyncedBoardsFractionPerDEalt", path.c_str()), "Synchronized boards fraction (v.2)", getNumDE(), 0, getNumDE()); + addHisto(mGoodBoardsFractionPerDE.get(), false, "hist", ""); +} + +//_________________________________________________________________________________________ + +void FECSyncStatusPlotter::update(TH2F* h) +{ + if (!h) { + return; + } + + std::vector deNumBoards(static_cast(getNumDE())); + std::vector deGoodBoards(static_cast(getNumDE())); + std::vector deGoodFrac(static_cast(getNumDE())); + + std::fill(deNumBoards.begin(), deNumBoards.end(), 0); + std::fill(deGoodBoards.begin(), deGoodBoards.end(), 0); + std::fill(deGoodFrac.begin(), deGoodFrac.end(), 0); + + std::vector solarNumBoards(static_cast(getNumSolar())); + std::vector solarGoodFrac(static_cast(getNumSolar())); + + std::fill(solarNumBoards.begin(), solarNumBoards.end(), 0); + std::fill(solarGoodFrac.begin(), solarGoodFrac.end(), 0); + + int nbinsx = h->GetXaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + // address of the DS board in detector representation + auto dsDetId = getDsDetId(i - 1); + auto deId = dsDetId.deId(); + + int deIndex = getDEindex(deId); + if (deIndex < 0) { + continue; + } + + auto dsElecId = mDet2ElecMapper(dsDetId); + if (!dsElecId) { + continue; + } + auto solarId = dsElecId->solarId(); + + int solarIndex = getSolarIndex(solarId); + if (solarIndex < 0) { + continue; + } + + auto nGood = h->GetBinContent(i, 1); + auto nBad = h->GetBinContent(i, 2); + auto nMissing = h->GetBinContent(i, 3); + auto nTF = nGood + nBad + nMissing; + auto goodFrac = nGood / nTF; + + deNumBoards[deIndex] += 1; + if (nGood == nTF) { + deGoodBoards[deIndex] += 1; + } + deGoodFrac[deIndex] += goodFrac; + + solarNumBoards[solarIndex] += 1; + solarGoodFrac[solarIndex] += goodFrac; + } + + // update the average number of out-of-sync boards + for (size_t i = 0; i < deNumBoards.size(); i++) { + if (deNumBoards[i] > 0) { + mGoodBoardsFractionPerDE->SetBinContent(i + 1, deGoodBoards[i] / deNumBoards[i]); + mGoodTFFractionPerDE->SetBinContent(i + 1, deGoodFrac[i] / deNumBoards[i]); + } else { + mGoodBoardsFractionPerDE->SetBinContent(i + 1, 0); + mGoodTFFractionPerDE->SetBinContent(i + 1, 0); + } + } + + for (size_t i = 0; i < solarNumBoards.size(); i++) { + if (solarNumBoards[i] > 0) { + mGoodTFFractionPerSolar->SetBinContent(i + 1, solarGoodFrac[i] / solarNumBoards[i]); + } else { + mGoodTFFractionPerSolar->SetBinContent(i + 1, 0); + } + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/GlobalHistogram.cxx b/Modules/MUON/MCH/src/GlobalHistogram.cxx new file mode 100644 index 0000000000..084a4a8a60 --- /dev/null +++ b/Modules/MUON/MCH/src/GlobalHistogram.cxx @@ -0,0 +1,1034 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GlobalHistogram.cxx +/// \author Andrea Ferrero +/// + +#include +#include +#include + +#include + +#include "MCHMappingInterface/Segmentation.h" +#include "MCHMappingInterface/CathodeSegmentation.h" +#ifdef MCH_HAS_MAPPING_FACTORY +#include "MCHMappingFactory/CreateSegmentation.h" +#endif +#include "MCHMappingSegContour/CathodeSegmentationContours.h" + +#include "MCH/GlobalHistogram.h" + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +static int getLR(int de) +{ + int lr = -1; + int deId = de % 100; + if ((de >= 100) && (de < 500)) { + if ((deId >= 1) && (deId <= 2)) { + // ST12 left + lr = 0; + } else { + lr = 1; + } + } + if ((de >= 500) && (de < 700)) { + if (((deId >= 0) && (deId <= 4)) || ((deId >= 14) && (deId <= 17))) { + // ST3 right + lr = 1; + } + if (((deId >= 5) && (deId <= 13))) { + // ST3 left + lr = 0; + } + } + if ((de >= 700) && (de < 1100)) { + if (((deId >= 0) && (deId <= 6)) || ((deId >= 20) && (deId <= 25))) { + // ST4/5 right + lr = 1; + } + if (((deId >= 7) && (deId <= 19))) { + // ST4/5 left + lr = 0; + } + } + return lr; +} + +static int getBT(int de) +{ + int bt = -1; + int deId = de % 100; + if ((de >= 100) && (de < 500)) { + if ((deId == 0) || (deId == 1)) { + // ST12 top + bt = 1; + } else { + bt = 0; + } + } + return bt; +} + +static int getDetectorHistWidth(int deId) +{ + if (deId >= 500) { + return (40 * 6 + 20); + } else if (deId >= 300) { + return 130; + } else { + return 100; + } + return 10; +} + +static int getDetectorHistXbins(int deId) +{ + return (getDetectorHistWidth(deId) * 2); +} + +static int getDetectorHistHeight(int deId) +{ + if (deId >= 500) { + return (50); + } else if (deId >= 300) { + return 130; + } else { + return 100; + } + return 10; +} + +static int getDetectorHistYbins(int deId) +{ + return (getDetectorHistHeight(deId) * 2); +} + +static bool getDetectorFlipX(int deId) +{ + int lr = getLR(deId); + bool flip = (lr == 1); + + if (deId >= 700) { + int mod = (deId % 100); + if (mod == 0 || mod == 2 || mod == 3 || mod == 4 || mod == 9 || mod == 10 || mod == 11 || mod == 13 || mod == 15 || mod == 16 || mod == 17 || mod == 21 || mod == 22 || mod == 23 || mod == 24) { + flip = !flip; + } + } else if (deId >= 500) { + int mod = (deId % 100); + if (mod == 2 || mod == 7 || mod == 11 || mod == 16) { + flip = !flip; + } + } else { + flip = (lr == 0); + } + + return flip; +} + +static bool getDetectorFlipY(int deId) +{ + if (deId >= 100 && deId < 500) { + int bt = getBT(deId); + if (bt == 0) { + return true; + } + } + if (deId == 510 || deId == 517 || deId == 610 || deId == 617) { + return true; + } + if (deId >= 700) { + if ((deId % 100) == 14) { + return true; + } + if ((deId % 100) == 25) { + return true; + } + } + return false; +} + +static float getDetectorShiftX(int deId) +{ + if (deId < 500) { + return 5; + } + + return 0; +} + +static float getDetectorShiftY(int deId) +{ + if (deId < 500) { + return 5; + } + + return 0; +} + +DetectorHistogram::DetectorHistogram(TString name, TString title, int deId, int cathode) + : mName(name), mTitle(title), mDeId(deId), mCathode(cathode), mFlipX(getDetectorFlipX(deId)), mFlipY(getDetectorFlipY(deId)), mShiftX(getDetectorShiftX(deId)), mShiftY(getDetectorShiftY(deId)) +{ + mHist = std::make_pair(new TH2F(name, title, getNbinsX(), getXmin(), getXmax(), getNbinsY(), getYmin(), getYmax()), true); + addContour(); + mHist.first->SetOption("colz"); +} + +DetectorHistogram::DetectorHistogram(TString name, TString title, int deId, int cathode, TH2F* hist) + : mName(name), mTitle(title), mDeId(deId), mCathode(cathode), mFlipX(getDetectorFlipX(deId)), mFlipY(getDetectorFlipY(deId)), mShiftX(getDetectorShiftX(deId)), mShiftY(getDetectorShiftY(deId)) +{ + mHist = std::make_pair(hist, false); + init(); + addContour(); + mHist.first->SetOption("colz"); +} + +DetectorHistogram::~DetectorHistogram() +{ + if (mHist.first && mHist.second) { + delete mHist.first; + } +} + +int DetectorHistogram::getNbinsX() +{ + return getDetectorHistXbins(mDeId); +} + +int DetectorHistogram::getNbinsY() +{ + return getDetectorHistYbins(mDeId); +} + +float DetectorHistogram::getXmin() +{ + if (mDeId < 500) { + if (getDetectorFlipX(mDeId)) { + return -1.0 * getDetectorHistWidth(mDeId); + } else { + return 0; + } + } + + return -1.0 * getDetectorHistWidth(mDeId) / 2; +} + +float DetectorHistogram::getXmax() +{ + if (mDeId < 500) { + if (getDetectorFlipX(mDeId)) { + return 0; + } else { + return getDetectorHistWidth(mDeId); + } + } + + return getDetectorHistWidth(mDeId) / 2; +} + +float DetectorHistogram::getYmin() +{ + if (mDeId < 500) { + if (getDetectorFlipY(mDeId)) { + return -1.0 * getDetectorHistHeight(mDeId); + } else { + return 0; + } + } + + return -1.0 * getDetectorHistHeight(mDeId) / 2; +} + +float DetectorHistogram::getYmax() +{ + if (mDeId < 500) { + if (getDetectorFlipY(mDeId)) { + return 0; + } else { + return getDetectorHistHeight(mDeId); + } + } + + return getDetectorHistHeight(mDeId) / 2; +} + +void DetectorHistogram::init() +{ + mHist.first->Reset(); + + mHist.first->SetNameTitle(mName, mTitle); + + mHist.first->GetXaxis()->Set(getNbinsX(), getXmin(), getXmax()); + mHist.first->GetYaxis()->Set(getNbinsY(), getYmin(), getYmax()); + mHist.first->SetBinsLength(); +} + +void DetectorHistogram::addContour() +{ + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(mDeId); + const o2::mch::mapping::CathodeSegmentation& csegment = (mCathode == 0) ? segment.bending() : segment.nonBending(); + o2::mch::contour::Contour envelop = o2::mch::mapping::getEnvelop(csegment); + std::vector> vertices = envelop.getVertices(); + + TLine* line{ nullptr }; + + for (unsigned int vi = 0; vi < vertices.size(); vi++) { + o2::mch::contour::Vertex v1 = vertices[vi]; + o2::mch::contour::Vertex v2 = (vi < (vertices.size() - 1)) ? vertices[vi + 1] : vertices[0]; + + v1.x += mShiftX; + v2.x += mShiftX; + v1.y += mShiftY; + v2.y += mShiftY; + + if (mFlipX) { + v1.x *= -1; + v2.x *= -1; + } + if (mFlipY) { + v1.y *= -1; + v2.y *= -1; + } + + line = new TLine(v1.x, v1.y, v2.x, v2.y); + mHist.first->GetListOfFunctions()->Add(line); + line = new TLine(v1.x, v1.y, v2.x, v2.y); + mHist.first->GetListOfFunctions()->Add(line); + } +} + +void DetectorHistogram::Fill(double padX, double padY, double padSizeX, double padSizeY, double val) +{ + if (!mHist.first) { + return; + } + + padX += mShiftX; + padY += mShiftY; + + if (mFlipX) { + padX *= -1.0; + } + if (mFlipY) { + padY *= -1.0; + } + + int binx_min = mHist.first->GetXaxis()->FindBin(padX - padSizeX / 2 + 0.1); + int binx_max = mHist.first->GetXaxis()->FindBin(padX + padSizeX / 2 - 0.1); + int biny_min = mHist.first->GetYaxis()->FindBin(padY - padSizeY / 2 + 0.1); + int biny_max = mHist.first->GetYaxis()->FindBin(padY + padSizeY / 2 - 0.1); + for (int by = biny_min; by <= biny_max; by++) { + float y = mHist.first->GetYaxis()->GetBinCenter(by); + for (int bx = binx_min; bx <= binx_max; bx++) { + float x = mHist.first->GetXaxis()->GetBinCenter(bx); + mHist.first->Fill(x, y, val); + } + } +} + +void DetectorHistogram::Set(double padX, double padY, double padSizeX, double padSizeY, double val) +{ + if (!mHist.first) { + return; + } + + padX += mShiftX; + padY += mShiftY; + + if (mFlipX) { + padX *= -1.0; + } + if (mFlipY) { + padY *= -1.0; + } + + int binx_min = mHist.first->GetXaxis()->FindBin(padX - padSizeX / 2 + 0.1); + int binx_max = mHist.first->GetXaxis()->FindBin(padX + padSizeX / 2 - 0.1); + int biny_min = mHist.first->GetYaxis()->FindBin(padY - padSizeY / 2 + 0.1); + int biny_max = mHist.first->GetYaxis()->FindBin(padY + padSizeY / 2 - 0.1); + for (int by = biny_min; by <= biny_max; by++) { + for (int bx = binx_min; bx <= binx_max; bx++) { + mHist.first->SetBinContent(bx, by, val); + } + } +} + +static float getGlobalHistDeWidth(int id) +{ + float result{ 0 }; + + switch (id) { + case 0: + result = 130; + break; + case 1: + result = 250; + break; + } + + return result; +} + +static float getGlobalHistDeHeight(int id) +{ + float result{ 0 }; + + switch (id) { + case 0: + result = 130; + break; + case 1: + result = 60; + break; + } + + return result; +} + +static int getNHistPerChamberX(int /* id */) +{ + return 2; +} + +static int getNHistPerChamberY(int id) +{ + return (id == 0) ? 2 : 13; +} + +static int getNStations(int id) +{ + int nStations = (id == 0) ? 2 : 3; + return nStations; +} + +static float getGlobalHistWidth(int id) +{ + constexpr int chambersPerStation = 2; + + return (getGlobalHistDeWidth(id) * getNHistPerChamberX(id) * chambersPerStation * getNStations(id)); +} + +static float getGlobalHistHeight(int id) +{ + return (getGlobalHistDeHeight(id) * getNHistPerChamberY(id) * 2); +} + +GlobalHistogram::GlobalHistogram(std::string name, std::string title, int id, float rescale) : mName(name), mTitle(title), mId(id), mScaleFactor(rescale) +{ + auto hist = new TH2F(name.c_str(), title.c_str(), getGlobalHistWidth(id) / rescale, 0, getGlobalHistWidth(id), + getGlobalHistHeight(id) / rescale, 0, getGlobalHistHeight(id)); + mHist = std::make_pair(hist, true); + mHist.first->SetOption("colz"); +} + +GlobalHistogram::GlobalHistogram(std::string name, std::string title, int id, float rescale, TH2F* hist) : mName(name), mTitle(title), mId(id), mScaleFactor(rescale) +{ + mHist = std::make_pair(hist, false); + mHist.first->SetOption("colz"); +} + +GlobalHistogram::~GlobalHistogram() +{ + if (mHist.first && mHist.second) { + delete mHist.first; + } +} + +void GlobalHistogram::init() +{ + mHist.first->Reset(); + + mHist.first->SetNameTitle(mName, mTitle); + + mHist.first->GetXaxis()->Set(getGlobalHistWidth(mId) / mScaleFactor, 0, getGlobalHistWidth(mId)); + mHist.first->GetYaxis()->Set(getGlobalHistHeight(mId) / mScaleFactor, 0, getGlobalHistHeight(mId)); + mHist.first->SetBinsLength(); + + switch (mId) { + case 0: + initST12(); + break; + case 1: + initST345(); + break; + } +} + +void GlobalHistogram::initST12() +{ + std::vector allDE; + + auto addDE = [&allDE](int detElemId) { + allDE.push_back(detElemId); + }; + o2::mch::mapping::forEachDetectionElement(addDE); + + TLine* line; + + float histWidth = getHist()->GetXaxis()->GetXmax(); + float histHeight = getHist()->GetYaxis()->GetXmax(); + float stationWidth = histWidth / 2; + + line = new TLine(0, histHeight / 2, histWidth, histHeight / 2); + getHist()->GetListOfFunctions()->Add(line); + + line = new TLine(stationWidth, 0, stationWidth, histHeight); + getHist()->GetListOfFunctions()->Add(line); + + for (auto& de : allDE) { + if (de >= 500) { + continue; + } + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(de); + + std::vector> vertices[2]; + const o2::mch::mapping::CathodeSegmentation& csegmentB = segment.bending(); + o2::mch::contour::Contour envelopB = o2::mch::mapping::getEnvelop(csegmentB); + vertices[0] = envelopB.getVertices(); + + const o2::mch::mapping::CathodeSegmentation& csegmentNB = segment.nonBending(); + o2::mch::contour::Contour envelopNB = o2::mch::mapping::getEnvelop(csegmentNB); + vertices[1] = envelopNB.getVertices(); + + float x0[2], y0[2], xNB0, yNB0; + getDeCenter(de, x0[0], y0[0], x0[1], y0[1]); + bool flipX = getDetectorFlipX(de); + bool flipY = getDetectorFlipY(de); + + float shiftX = getDetectorShiftX(de); + float shiftY = getDetectorShiftY(de); + + for (unsigned int ci = 0; ci < 2; ci++) { + for (unsigned int vi = 0; vi < vertices[ci].size(); vi++) { + o2::mch::contour::Vertex v1 = vertices[ci][vi]; + o2::mch::contour::Vertex v2 = (vi < (vertices[ci].size() - 1)) ? vertices[ci][vi + 1] : vertices[ci][0]; + + v1.x += shiftX; + v1.y += shiftY; + v2.x += shiftX; + v2.y += shiftY; + + if (flipX) { + v1.x *= -1; + v2.x *= -1; + } + + if (flipY) { + v1.y *= -1; + v2.y *= -1; + } + + line = new TLine(v1.x + x0[ci], v1.y + y0[ci], v2.x + x0[ci], v2.y + y0[ci]); + getHist()->GetListOfFunctions()->Add(line); + } + } + } +} + +void GlobalHistogram::initST345() +{ + std::vector allDE; + + auto addDE = [&allDE](int detElemId) { + allDE.push_back(detElemId); + }; + o2::mch::mapping::forEachDetectionElement(addDE); + + TLine* line; + + float histWidth = getHist()->GetXaxis()->GetXmax(); + float histHeight = getHist()->GetYaxis()->GetXmax(); + float stationWidth = histWidth / 3; + + line = new TLine(0, histHeight / 2, histWidth, histHeight / 2); + getHist()->GetListOfFunctions()->Add(line); + + line = new TLine(stationWidth, 0, stationWidth, histHeight); + getHist()->GetListOfFunctions()->Add(line); + + line = new TLine(stationWidth * 2, 0, stationWidth * 2, histHeight); + getHist()->GetListOfFunctions()->Add(line); + + for (auto& de : allDE) { + if (de < 500) { + continue; + } + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(de); + const o2::mch::mapping::CathodeSegmentation& csegment = segment.bending(); + o2::mch::contour::Contour envelop = o2::mch::mapping::getEnvelop(csegment); + std::vector> vertices = envelop.getVertices(); + + float xB0, yB0, xNB0, yNB0; + getDeCenter(de, xB0, yB0, xNB0, yNB0); + bool isR = getLR(de) == 1; + bool flipY = getDetectorFlipY(de); + + for (unsigned int vi = 0; vi < vertices.size(); vi++) { + o2::mch::contour::Vertex v1 = vertices[vi]; + o2::mch::contour::Vertex v2 = (vi < (vertices.size() - 1)) ? vertices[vi + 1] : vertices[0]; + + if (flipY) { + v1.y *= -1; + v2.y *= -1; + } + + if (isR) { + line = new TLine(-v1.x + xB0, v1.y + yB0, -v2.x + xB0, v2.y + yB0); + getHist()->GetListOfFunctions()->Add(line); + line = new TLine(-v1.x + xNB0, v1.y + yNB0, -v2.x + xNB0, v2.y + yNB0); + getHist()->GetListOfFunctions()->Add(line); + } else { + line = new TLine(v1.x + xB0, v1.y + yB0, v2.x + xB0, v2.y + yB0); + getHist()->GetListOfFunctions()->Add(line); + line = new TLine(v1.x + xNB0, v1.y + yNB0, v2.x + xNB0, v2.y + yNB0); + getHist()->GetListOfFunctions()->Add(line); + } + } + } +} + +void GlobalHistogram::getDeCenter(int de, float& xB0, float& yB0, float& xNB0, float& yNB0) +{ + xB0 = 0; + yB0 = 0; + xNB0 = 0; + yNB0 = 0; + + if ((de >= 100) && (de < 500)) { + getDeCenterST12(de, xB0, yB0, xNB0, yNB0); + } + + if ((de >= 500) && (de < 700)) { + getDeCenterST3(de, xB0, yB0, xNB0, yNB0); + } + + if ((de >= 700) && (de < 900)) { + getDeCenterST4(de, xB0, yB0, xNB0, yNB0); + } + + if ((de >= 900) && (de < 1100)) { + getDeCenterST5(de, xB0, yB0, xNB0, yNB0); + } +} + +void GlobalHistogram::getDeCenterST12(int de, float& xB0, float& yB0, float& xNB0, float& yNB0) +{ + float deWidth = getGlobalHistDeWidth(mId); + float deHeight = getGlobalHistDeHeight(mId); + + int yOffset = 0; + // DE index within the chamber + int deId = de % 100; + + int xId = getLR(de), yId = -1; + + switch (deId) { + case 0: + case 1: + yId = 1; + break; + case 2: + case 3: + yId = 0; + break; + } + + yId += yOffset; + + xB0 = xNB0 = deWidth; + + yB0 = yNB0 = deHeight; + yB0 += getNHistPerChamberY(mId) * deHeight; + + int chamber = de / 100; + if ((chamber % 2) == 0) { + xB0 += getNHistPerChamberX(mId) * deWidth; + xNB0 += getNHistPerChamberX(mId) * deWidth; + } + + int station = (de - 100) / 200 + 1; + if (de >= 300) { + xB0 += getNHistPerChamberX(mId) * deWidth * 2; + xNB0 += getNHistPerChamberX(mId) * deWidth * 2; + } + + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(de); + const o2::mch::mapping::CathodeSegmentation& csegment = segment.bending(); + o2::mch::contour::Contour envelop = o2::mch::mapping::getEnvelop(csegment); + std::vector> vertices = envelop.getVertices(); + o2::mch::contour::BBox bbox = o2::mch::mapping::getBBox(csegment); +} + +void GlobalHistogram::getDeCenterST3(int de, float& xB0, float& yB0, float& xNB0, float& yNB0) +{ + float deWidth = getGlobalHistDeWidth(mId); + float deHeight = getGlobalHistDeHeight(mId); + + int yOffset = 2; + // DE index within the chamber + int deId = de % 100; + + int xId = getLR(de), yId = -1; + + switch (deId) { + case 13: + case 14: + yId = 0; + break; + case 12: + case 15: + yId = 1; + break; + case 11: + case 16: + yId = 2; + break; + case 10: + case 17: + yId = 3; + break; + case 9: + case 0: + yId = 4; + break; + case 8: + case 1: + yId = 5; + break; + case 7: + case 2: + yId = 6; + break; + case 6: + case 3: + yId = 7; + break; + case 5: + case 4: + yId = 8; + break; + } + + yId += yOffset; + + xB0 = xNB0 = deWidth * xId + deWidth / 2; + + yB0 = yNB0 = deHeight * yId + deHeight / 2; + yB0 += getNHistPerChamberY(mId) * deHeight; + + int chamber = de / 100; + if ((chamber % 2) == 0) { + xB0 += getNHistPerChamberX(mId) * deWidth; + xNB0 += getNHistPerChamberX(mId) * deWidth; + } + + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(de); + const o2::mch::mapping::CathodeSegmentation& csegment = segment.bending(); + o2::mch::contour::Contour envelop = o2::mch::mapping::getEnvelop(csegment); + std::vector> vertices = envelop.getVertices(); + o2::mch::contour::BBox bbox = o2::mch::mapping::getBBox(csegment); + + double xmax = bbox.xmax(); + if (xId == 0) { + // ST3 left, shift all detectors to the right + xB0 += 120 - xmax; + xNB0 += 120 - xmax; + if (deId == 9) { + xB0 -= 22.5; + xNB0 -= 22.5; + } + } + if (xId == 1) { + // ST3 right, shift all detectors to the left + xB0 -= 120 - xmax; + xNB0 -= 120 - xmax; + if (deId == 0) { + xB0 += 22.5; + xNB0 += 22.5; + } + } +} + +void GlobalHistogram::getDeCenterST4(int de, float& xB0, float& yB0, float& xNB0, float& yNB0) +{ + float deWidth = getGlobalHistDeWidth(mId); + float deHeight = getGlobalHistDeHeight(mId); + + int yOffset = 0; + // DE index within the chamber + int deId = de % 100; + + int xId = getLR(de), yId = -1; + + switch (deId) { + case 19: + case 20: + yId = 0; + break; + case 18: + case 21: + yId = 1; + break; + case 17: + case 22: + yId = 2; + break; + case 16: + case 23: + yId = 3; + break; + case 15: + case 24: + yId = 4; + break; + case 14: + case 25: + yId = 5; + break; + case 13: + case 0: + yId = 6; + break; + case 12: + case 1: + yId = 7; + break; + case 11: + case 2: + yId = 8; + break; + case 10: + case 3: + yId = 9; + break; + case 9: + case 4: + yId = 10; + break; + case 8: + case 5: + yId = 11; + break; + case 7: + case 6: + yId = 12; + break; + } + + yId += yOffset; + + xB0 = xNB0 = deWidth * xId + deWidth / 2 + getNHistPerChamberX(mId) * 2 * deWidth; + + yB0 = yNB0 = deHeight * yId + deHeight / 2; + yB0 += getNHistPerChamberY(mId) * deHeight; + + int chamber = de / 100; + if ((chamber % 2) == 0) { + xB0 += getNHistPerChamberX(mId) * deWidth; + xNB0 += getNHistPerChamberX(mId) * deWidth; + } + + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(de); + const o2::mch::mapping::CathodeSegmentation& csegment = segment.bending(); + o2::mch::contour::Contour envelop = o2::mch::mapping::getEnvelop(csegment); + std::vector> vertices = envelop.getVertices(); + o2::mch::contour::BBox bbox = o2::mch::mapping::getBBox(csegment); + + double xmax = bbox.xmax(); + if (xId == 0) { + // ST3 left, shift all detectors to the right + xB0 += 120 - xmax; + xNB0 += 120 - xmax; + if (deId == 13) { + xB0 -= 40; + xNB0 -= 40; + } + } + if (xId == 1) { + // ST3 right, shift all detectors to the left + xB0 -= 120 - xmax; + xNB0 -= 120 - xmax; + if (deId == 0) { + xB0 += 40; + xNB0 += 40; + } + } +} + +void GlobalHistogram::getDeCenterST5(int de, float& xB0, float& yB0, float& xNB0, float& yNB0) +{ + float deWidth = getGlobalHistDeWidth(mId); + + getDeCenterST4(de, xB0, yB0, xNB0, yNB0); + xB0 += getNHistPerChamberX(mId) * 2 * deWidth; + xNB0 += getNHistPerChamberX(mId) * 2 * deWidth; +} + +void GlobalHistogram::add(std::map>& histB, std::map>& histNB) +{ + set(histB, histNB, false); +} + +void GlobalHistogram::set_includeNull(std::map>& histB, std::map>& histNB) +{ + set(histB, histNB, true, true); +} + +void GlobalHistogram::set(std::map>& histB, std::map>& histNB, bool doAverage, bool includeNullBins) +{ + int deMin = (mId == 0) ? 100 : 500; + int deMax = (mId == 0) ? 403 : 1100; + + for (auto& ih : histB) { + int de = ih.first; + if (de < deMin || de > deMax) { + continue; + } + + auto hB = ih.second.get(); + if (!hB) { + continue; + } + if (!hB->getHist()) { + continue; + } + + DetectorHistogram* hNB = nullptr; + auto jh = histNB.find(de); + if (jh != histNB.end()) { + hNB = jh->second.get(); + } + if (!hNB) { + continue; + } + if (!hNB->getHist()) { + continue; + } + + TH2F* hist[2] = { hB->getHist(), hNB->getHist() }; + + float xB0, yB0, xNB0, yNB0; + getDeCenter(de, xB0, yB0, xNB0, yNB0); + + float x0[2] = { xB0, xNB0 }; + float y0[2] = { yB0, yNB0 }; + + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(de); + const o2::mch::mapping::CathodeSegmentation& csegmentB = segment.bending(); + o2::mch::contour::BBox bboxB = o2::mch::mapping::getBBox(csegmentB); + o2::mch::contour::Contour envelopB = o2::mch::mapping::getEnvelop(csegmentB); + + const o2::mch::mapping::CathodeSegmentation& csegmentNB = segment.nonBending(); + o2::mch::contour::BBox bboxNB = o2::mch::mapping::getBBox(csegmentNB); + o2::mch::contour::Contour envelopNB = o2::mch::mapping::getEnvelop(csegmentNB); + + float xMin[2] = { static_cast(xB0 - bboxB.width() / 2), static_cast(xNB0 - bboxNB.width() / 2) }; + float xMax[2] = { static_cast(xB0 + bboxB.width() / 2), static_cast(xNB0 + bboxNB.width() / 2) }; + float yMin[2] = { static_cast(yB0 + bboxB.ymin()), static_cast(yNB0 + bboxNB.ymin()) }; + float yMax[2] = { static_cast(yB0 + bboxB.ymax()), static_cast(yNB0 + bboxNB.ymax()) }; + + if (mId == 0) { + bool flipX = getDetectorFlipX(de); + bool flipY = getDetectorFlipY(de); + float shiftX = getDetectorShiftX(de); + float shiftY = getDetectorShiftY(de); + xMin[0] = flipX ? xB0 - 1.0 * (bboxB.width() + shiftX) : xB0 + shiftX; + xMin[1] = flipX ? xNB0 - 1.0 * (bboxNB.width() + shiftX) : xNB0 + shiftX; + xMax[0] = flipX ? xB0 - shiftX : (xB0 + bboxB.width() + shiftX); + xMax[1] = flipX ? xNB0 - shiftX : (xNB0 + bboxNB.width() + shiftX); + + yMin[0] = flipY ? yB0 - 1.0 * (bboxB.height() + shiftY) : yB0 + shiftY; + yMin[1] = flipY ? yNB0 - 1.0 * (bboxNB.height() + shiftY) : yNB0 + shiftY; + yMax[0] = flipY ? yB0 - shiftY : (yB0 + bboxB.height() + shiftY); + yMax[1] = flipY ? yNB0 - shiftY : (yNB0 + bboxNB.height() + shiftY); + } + + float binWidthX = getHist()->GetXaxis()->GetBinWidth(1); + float binWidthY = getHist()->GetYaxis()->GetBinWidth(1); + + // loop on bending and non-bending planes + for (int i = 0; i < 2; i++) { + + if (hist[i] == nullptr) { + continue; + } + + // loop on destination bins + int binXmin = getHist()->GetXaxis()->FindBin(xMin[i] + binWidthX / 2); + int binXmax = getHist()->GetXaxis()->FindBin(xMax[i] - binWidthX / 2); + int binYmin = getHist()->GetYaxis()->FindBin(yMin[i] + binWidthY / 2); + int binYmax = getHist()->GetYaxis()->FindBin(yMax[i] - binWidthY / 2); + + for (int by = binYmin; by <= binYmax; by++) { + // vertical boundaries of current bin, in DE coordinates + float minY = getHist()->GetYaxis()->GetBinLowEdge(by) - y0[i]; + float maxY = getHist()->GetYaxis()->GetBinUpEdge(by) - y0[i]; + + // find Y bin range in source histogram + int srcBinYmin = hist[i]->GetYaxis()->FindBin(minY); + if (hist[i]->GetYaxis()->GetBinCenter(srcBinYmin) < minY) { + srcBinYmin += 1; + } + int srcBinYmax = hist[i]->GetYaxis()->FindBin(maxY); + if (hist[i]->GetYaxis()->GetBinCenter(srcBinYmax) > maxY) { + srcBinYmax -= 1; + } + + for (int bx = binXmin; bx <= binXmax; bx++) { + // horizontal boundaries of current bin, in DE coordinates + float minX = getHist()->GetXaxis()->GetBinLowEdge(bx) - x0[i]; + float maxX = getHist()->GetXaxis()->GetBinUpEdge(bx) - x0[i]; + + // find X bin range in source histogram + int srcBinXmin = hist[i]->GetXaxis()->FindBin(minX); + if (hist[i]->GetXaxis()->GetBinCenter(srcBinXmin) < minX) { + srcBinXmin += 1; + } + int srcBinXmax = hist[i]->GetXaxis()->FindBin(maxX); + if (hist[i]->GetXaxis()->GetBinCenter(srcBinXmax) > maxX) { + srcBinXmax -= 1; + } + + // loop on source bins, and compute the sum or average + int nBins = 0; + float tot = 0; + for (int sby = srcBinYmin; sby <= srcBinYmax; sby++) { + for (int sbx = srcBinXmin; sbx <= srcBinXmax; sbx++) { + float val = hist[i]->GetBinContent(sbx, sby); + if (val == 0 && !includeNullBins) { + continue; + } + nBins += 1; + tot += val; + } + } + + if (doAverage && (nBins > 0)) { + tot /= nBins; + } + getHist()->SetBinContent(bx, by, tot); + } + } + } + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/HeartBeatPacketsPlotter.cxx b/Modules/MUON/MCH/src/HeartBeatPacketsPlotter.cxx new file mode 100644 index 0000000000..6df2d60700 --- /dev/null +++ b/Modules/MUON/MCH/src/HeartBeatPacketsPlotter.cxx @@ -0,0 +1,121 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file HeartBeatPacketsPlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/HeartBeatPacketsPlotter.h" +#include "MCH/Helpers.h" +#include "MCHMappingInterface/Segmentation.h" +#include "MCHGlobalMapping/DsIndex.h" +#include +#include + +using namespace o2::mch; +using namespace o2::mch::raw; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +HeartBeatPacketsPlotter::HeartBeatPacketsPlotter(std::string path, bool fullPlots) +{ + mElec2DetMapper = createElec2DetMapper(); + mFeeLink2SolarMapper = createFeeLink2SolarMapper(); + + //-------------------------------------------------- + // Rates histograms in global detector coordinates + //-------------------------------------------------- + + mHistogramHBRateGlobal[0] = std::make_shared(fmt::format("{}HBRate_ST12", path), "ST12 HeartBeat Rate", 0, 5); + mHistogramHBRateGlobal[0]->init(); + addHisto(mHistogramHBRateGlobal[0]->getHist(), false, "colz", "colz"); + + mHistogramHBRateGlobal[1] = std::make_shared(fmt::format("{}HBRate_ST345", path), "ST345 HeartBeat Rate", 1, 10); + mHistogramHBRateGlobal[1]->init(); + addHisto(mHistogramHBRateGlobal[1]->getHist(), false, "colz", "colz"); + + //-------------------------------------------------- + // Rates histograms in detector coordinates + //-------------------------------------------------- + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + auto h = std::make_shared(TString::Format("%s%sHBRate_XY_B_%03d", path.c_str(), getHistoPath(de).c_str(), de), + TString::Format("HeartBeat Rate (DE%03d B)", de), de, int(0)); + mHistogramHBRateDE[0].insert(make_pair(de, h)); + if (fullPlots) { + addHisto(h->getHist(), false, "colz", "colz"); + } + + h = std::make_shared(TString::Format("%s%sHBRate_XY_NB_%03d", path.c_str(), getHistoPath(de).c_str(), de), + TString::Format("HeartBeat Rate (DE%03d NB)", de), de, int(1)); + mHistogramHBRateDE[1].insert(make_pair(de, h)); + if (fullPlots) { + addHisto(h->getHist(), false, "colz", "colz"); + } + } +} + +//_________________________________________________________________________________________ + +void HeartBeatPacketsPlotter::update(TH2F* h) +{ + if (!h) { + return; + } + + int nbinsx = h->GetXaxis()->GetNbins(); + int nbinsy = h->GetYaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + // address of the DS board in detector representation + auto dsDetId = getDsDetId(i - 1); + auto deId = dsDetId.deId(); + auto dsId = dsDetId.dsId(); + + // total number of HB packets received + auto nHB = h->Integral(i, i, 1, nbinsy); + + for (int channel = 0; channel < 64; channel++) { + int padId = -1; + + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + padId = segment.findPadByFEE(dsId, int(channel)); + + if (padId < 0) { + continue; + } + + double padX = segment.padPositionX(padId); + double padY = segment.padPositionY(padId); + float padSizeX = segment.padSizeX(padId); + float padSizeY = segment.padSizeY(padId); + int cathode = segment.isBendingPad(padId) ? 0 : 1; + + // Fill 2D rate histograms + auto hRate = mHistogramHBRateDE[cathode].find(deId); + if ((hRate != mHistogramHBRateDE[cathode].end()) && (hRate->second != NULL)) { + hRate->second->Set(padX, padY, padSizeX, padSizeY, nHB); + } + } + } + + mHistogramHBRateGlobal[0]->set(mHistogramHBRateDE[0], mHistogramHBRateDE[1]); + mHistogramHBRateGlobal[1]->set(mHistogramHBRateDE[0], mHistogramHBRateDE[1]); +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/Helpers.cxx b/Modules/MUON/MCH/src/Helpers.cxx new file mode 100644 index 0000000000..5d58ae3bd2 --- /dev/null +++ b/Modules/MUON/MCH/src/Helpers.cxx @@ -0,0 +1,1129 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Helpers.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/Helpers.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QualityObject.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" +#include "MCHRawElecMap/Mapper.h" +#include "MCHMappingInterface/CathodeSegmentation.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +std::string getHistoPath(int deId) +{ + return fmt::format("ST{}/DE{}/", (deId - 100) / 200 + 1, deId); +} + +//_________________________________________________________________________________________ + +bool matchHistName(std::string hist, std::string name) +{ + if (name.empty()) { + return false; + } + + int64_t pos = hist.find(name); + int64_t histSize = hist.size(); + int64_t nameSize = name.size(); + int64_t diff = histSize - nameSize; + return ((pos >= 0) && (pos == diff)); +} + +//_________________________________________________________________________________________ + +int getChamberIndex(int deId) +{ + return (deId / 100) - 1; +} + +int getNumDEinChamber(int chIndex) +{ + int nDE = 0; + switch (chIndex) { + case 0: + case 1: + case 2: + case 3: + nDE = 4; + break; + case 4: + case 5: + nDE = 18; + break; + case 6: + case 7: + case 8: + case 9: + nDE = 26; + break; + default: + break; + } + return nDE; +} + +std::pair getDEindexInChamber(int deId) +{ + std::pair result = std::make_pair(int(-1), int(-1)); + int nDE = getNumDEinChamber(getChamberIndex(deId)); + if (nDE == 0) { + return result; + } + // number of detectors in one half chamber + int nDEhc = nDE / 2; + + // detector index within the chamber + int idx = (deId - 100) % 100; + // minimum and maximum detector indexes for the L side + int lMin = (nDEhc + 1) / 2; + int lMax = lMin + nDEhc - 1; + if (idx >= lMin && idx <= lMax) { + // detector on the L side, simply sibtract lMin + result.second = 0; + result.first = idx - lMin; + } else { + // detector on the R side, compute index separately above and below middle horizontal axis + result.second = 1; + if (idx > lMax) { + idx -= nDE; + } + result.first = lMin - idx - 1; + } + return result; +} + +int getChamberOffset(int chIndex) +{ + int offset = 0; + for (int c = 0; c < chIndex; c++) { + offset += getNumDEinChamber(c); + } + return offset; +} + +int getDEindex(int deId) +{ + auto idx = getDEindexInChamber(deId); + if (idx.first < 0 || idx.second < 0) { + return -1; + } + int offset = getChamberOffset(getChamberIndex(deId)); + + // number of detectors in one half chamber + int nDE = getNumDEinChamber(getChamberIndex(deId)); + if (idx.second > 0) { + idx.first += nDE / 2; + } + + return idx.first + offset; +} + +int getDEFromIndex(int index) +{ + int deId = 0; + for (int chamber = 9; chamber >= 0; chamber--) { + int offset = getChamberOffset(chamber); + if (offset > index) { + continue; + } + + int indexInChamber = index - offset; + + int nDE = getNumDEinChamber(chamber); + if (nDE == 0) { + return 0; + } + // number of detectors in one half chamber + int nDEhc = nDE / 2; + + // minimum and maximum detector indexes for the L side + int lMin = (nDEhc + 1) / 2; + int lMax = lMin + nDEhc - 1; + if (indexInChamber < nDEhc) { + // detector on the L side, simply add lMin + // std::cout << "DE on L side, indexInChamber=" << indexInChamber << " nDEhc=" << nDEhc << std::endl; + deId = lMin + indexInChamber; + } else { + // number of detectors in one quarter of chamber + // std::cout << "DE on R side, indexInChamber=" << indexInChamber << " nDEhc=" << nDEhc << std::endl; + // detector on the R side, compute deId separately above and below middle horizontal axis + int indexInHalfChamber = indexInChamber - nDEhc; + deId = lMin - indexInHalfChamber - 1; + if (deId < 0) { + deId = lMax + (nDEhc - indexInHalfChamber); + } + } + + deId += (chamber + 1) * 100; + break; + } + + return deId; +} + +//_________________________________________________________________________________________ + +std::map buildSolarIdToSolarIndexMap() +{ + static std::map m; + if (m.empty()) { + uint32_t solarIndex{ 0 }; + // fill a sorted list of DetectionElement IDs + std::set deIds; + o2::mch::mapping::forEachDetectionElement([&](int deId) { + deIds.insert(deId); + }); + // loop over DE IDs, and add all the corresponding SOLAR IDs to the output vector + for (auto deId : deIds) { + auto solarIds = o2::mch::raw::getSolarUIDs(deId); + for (uint32_t solarId : solarIds) { + m.emplace(std::make_pair(solarId, solarIndex)); + solarIndex++; + } + } + } + return m; +} + +std::vector buildSolarIndexToSolarIdMap() +{ + static std::vector v; + if (v.empty()) { + auto m = buildSolarIdToSolarIndexMap(); + v.resize(m.size()); + for (auto [solarId, solarIndex] : m) { + v[solarIndex] = solarId; + } + } + return v; +} + +int getSolarIndex(int solarId) +{ + static std::map m = buildSolarIdToSolarIndexMap(); + try { + return m.at(solarId); + } catch (const std::exception&) { + ILOG(Error, Support) << "Invalid Solar Id: " << solarId << ENDM; + } + return -1; +} + +int getSolarIdFromIndex(int index) +{ + static std::vector v = buildSolarIndexToSolarIdMap(); + try { + return v.at(index); + } catch (const std::exception&) { + ILOG(Error, Support) << "Invalid Solar Index: " << index << ENDM; + } + return -1; +} + +int getNumSolarPerChamber(int chamberId) +{ + static std::array v{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + if (v[0] == 0) { + o2::mch::mapping::forEachDetectionElement([&](int deId) { + int chId = deId / 100; + if (chId > 0 && chId <= 10) { + auto solarIds = o2::mch::raw::getSolarUIDs(deId); + v[chId - 1] += solarIds.size(); + } + }); + } + if (chamberId > 0 && chamberId <= 10) { + return v[chamberId - 1]; + } + return -1; +} + +//_________________________________________________________________________________________ + +void getThresholdsPerStation(o2::quality_control::core::CustomParameters customParameters, + const o2::quality_control::core::Activity& activity, + std::string parKey, + std::array, 5>& thresholds, + double& defaultThreshold) +{ + defaultThreshold = common::getFromExtendedConfig(activity, customParameters, parKey, defaultThreshold); + + for (int stationIndex = 0; stationIndex < 5; stationIndex++) { + auto parKeyForStation = parKey + ":ST" + std::to_string(stationIndex + 1); + auto parValue = common::getFromExtendedConfig(activity, customParameters, parKeyForStation, ""); + if (!parValue.empty()) { + try { + thresholds[stationIndex] = std::stod(parValue); + } catch (std::exception& exception) { + ILOG(Error, Support) << "Cannot convert value for key \"" << parKey << "\" to double, string is " << parValue << ENDM; + } + } + } +} + +//_________________________________________________________________________________________ + +QualityChecker::QualityChecker() +{ + for (auto de : o2::mch::constants::deIdsForAllMCH) { + auto idx = getDEindex(de); + if (idx >= 0) { + mChamberMap[idx] = getChamberIndex(de); + mDeMap[idx] = getDEindexInChamber(de); + } + } + + reset(); +} + +void QualityChecker::reset() +{ + std::fill(mQuality.begin(), mQuality.end(), Quality::Null); +} + +void QualityChecker::addCheckResult(gsl::span result) +{ + if (mQuality.size() > result.size()) { + return; + } + + for (int i = 0; i < mQuality.size(); i++) { + if ((mQuality[i] == Quality::Null) || (result[i] == Quality::Bad)) { + mQuality[i] = result[i]; + } + } +} + +Quality QualityChecker::getQuality() +{ + Quality result = Quality::Null; + for (int i = 0; i < mQuality.size(); i++) { + if (mQuality[i] != Quality::Null) { + result = Quality::Good; + } + } + + // number of bad detection elements for ST12 and ST345 and separately for L/R sides + int nBadDE[2][2] = { { 0, 0 }, { 0, 0 } }; + + for (int i = 0; i < mQuality.size(); i++) { + // if detector is OK, skip it + if (mQuality[i] != Quality::Bad) { + continue; + } + auto chamber1 = mChamberMap[i]; + int station1 = chamber1 / 2; + auto deidx1 = mDeMap[i]; + + int si = (station1 < 2) ? 0 : 1; + int lr = deidx1.second; + nBadDE[si][lr] += 1; + + for (int j = i + 1; j < mQuality.size(); j++) { + // if detector is OK, skip it + if (mQuality[j] != Quality::Bad) { + continue; + } + + // if we reach here, it means that both detectors are bad + // now we check if they are also facing each other in the same station, + // and therefore could lead to acceptance holes + auto chamber2 = mChamberMap[j]; + int station2 = chamber2 / 2; + auto deidx2 = mDeMap[j]; + + // we only check detector pairs that: + // a. belong to the same station(s) + // b. are not in the same chamber + // c. are on the same side of the spectrometer + // In the quadrants case, we combine together both stations since + // the tracking requires 3 chambers out of 4, as opposed to SLATs + // where one point per station is required + + // skip detection elements in the same chamber + if (chamber2 == chamber1) { + continue; + } + // skip detection elements on opposite sides + if (deidx1.second != deidx2.second) { + continue; + } + + if (station1 < 2) { + // Quadrants: check that both DE are in ST12 + if (station2 >= 2) { + continue; + } + } else { + // SLATs: check that both DE belong to the same station + if (station2 != station1) { + continue; + } + } + + // if two detectors directly facing each other are both bad, than + // we flag the overall quality as bad + if (deidx2.first == deidx1.first) { + result = Quality::Bad; + break; + } + if (station1 >= 2) { + // for SLATs, we also combine detector I in one chamber + // with detectors I-1 and I+1 in the other chamber + if (deidx2.first == (deidx1.first - 1)) { + result = Quality::Bad; + break; + } + if (deidx2.first == (deidx1.first + 1)) { + result = Quality::Bad; + break; + } + } + } + } + + if (result == Quality::Good) { + // If no bad combination was found, check the total number of bad DEs, + // separately for ST12 and ST345. + // If the number is above threshold, set the quality to Medium + if ((nBadDE[0][0] + nBadDE[0][1]) > mMaxBadST12) { + result = Quality::Medium; + } + if ((nBadDE[1][0] + nBadDE[1][1]) > mMaxBadST345) { + result = Quality::Medium; + } + } + + return result; +} + +//_________________________________________________________________________________________ + +void addChamberLabelsForDE(TH1* h) +{ + if (!h) { + return; + } + // disable ticks on horizontal axis + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0); + + TText* xtitle = new TText(); + xtitle->SetNDC(); + xtitle->SetText(0.87, 0.03, "chamber"); + xtitle->SetTextSize(10); + xtitle->SetTextFont(42); + h->GetListOfFunctions()->Add(xtitle); + + // draw x-axis labels + for (int ch = 0; ch <= 9; ch += 1) { + float x1 = static_cast(getChamberOffset(ch)); + float x2 = (ch < 9) ? static_cast(getChamberOffset(ch + 1)) : h->GetXaxis()->GetXmax(); + float x0 = 0.8 * (x1 + x2) / (2 * h->GetXaxis()->GetXmax()) + 0.1; + float y0 = 0.05; + TText* label = new TText(); + label->SetNDC(); + label->SetText(x0, y0, TString::Format("%d", ch + 1)); + label->SetTextSize(10); + label->SetTextFont(42); + label->SetTextAlign(22); + h->GetListOfFunctions()->Add(label); + } +} + +//_________________________________________________________________________________________ + +void addChamberDelimiters(TH1* h, float xmin, float xmax) +{ + if (!h) { + return; + } + + float scaleMin = xmin; + float scaleMax = xmax; + if (xmin == xmax) { + scaleMin = h->GetMinimum(); + scaleMax = h->GetMaximum(); + } + + // draw chamber delimiters + for (int ch = 1; ch <= 9; ch++) { + float xpos = static_cast(getChamberOffset(ch)); + TLine* delimiter = new TLine(xpos, scaleMin, xpos, scaleMax); + delimiter->SetLineColor(kBlack); + delimiter->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(delimiter); + } +} + +//_________________________________________________________________________________________ + +void addChamberLabelsForSolar(TH1* h) +{ + if (!h) { + return; + } + // disable ticks on horizontal axis + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0); + + TText* xtitle = new TText(); + xtitle->SetNDC(); + xtitle->SetText(0.87, 0.03, "chamber"); + xtitle->SetTextSize(10); + xtitle->SetTextFont(42); + h->GetListOfFunctions()->Add(xtitle); + + // draw x-axis labels + float xMin{ 0 }; + float xMax{ 0 }; + for (int ch = 1; ch <= 10; ch += 1) { + xMin = xMax; + xMax += static_cast(getNumSolarPerChamber(ch)); + float x0 = 0.8 * (xMax + xMin) / (2 * h->GetXaxis()->GetXmax()) + 0.1; + float y0 = 0.05; + TText* label = new TText(); + label->SetNDC(); + label->SetText(x0, y0, TString::Format("%d", ch)); + label->SetTextSize(10); + label->SetTextFont(42); + label->SetTextAlign(22); + h->GetListOfFunctions()->Add(label); + } +} + +//_________________________________________________________________________________________ + +void addChamberDelimitersToSolarHistogram(TH1* h, float xmin, float xmax) +{ + if (!h) { + return; + } + + float scaleMin = xmin; + float scaleMax = xmax; + if (xmin == xmax) { + scaleMin = h->GetMinimum(); + scaleMax = h->GetMaximum(); + } + + // draw chamber delimiters + float xpos{ 0 }; + for (int ch = 1; ch <= 9; ch++) { + xpos += static_cast(getNumSolarPerChamber(ch)); + TLine* delimiter = new TLine(xpos, scaleMin, xpos, scaleMax); + delimiter->SetLineColor(kBlack); + delimiter->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(delimiter); + } + + // draw x-axis labels + /*float xMin{ 0 }; + float xMax{ 0 }; + for (int ch = 1; ch <= 10; ch += 1) { + xMin = xMax; + xMax += static_cast(getNumSolarPerChamber(ch)); + float x0 = 0.8 * (xMax + xMin) / (2 * h->GetXaxis()->GetXmax()) + 0.1; + float y0 = 0.05; + TText* label = new TText(); + label->SetNDC(); + label->SetText(x0, y0, TString::Format("%d", ch)); + label->SetTextSize(10); + label->SetTextFont(42); + label->SetTextAlign(22); + h->GetListOfFunctions()->Add(label); + }*/ +} + +//_________________________________________________________________________________________ + +void addChamberDelimiters(TH2* h) +{ + if (!h) { + return; + } + // disable ticks on horizontal axis + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0); + + TText* xtitle = new TText(); + xtitle->SetNDC(); + xtitle->SetText(0.87, 0.03, "chamber"); + xtitle->SetTextSize(10); + xtitle->SetTextFont(42); + h->GetListOfFunctions()->Add(xtitle); + + float scaleMin = h->GetYaxis()->GetXmin(); + float scaleMax = h->GetYaxis()->GetXmax(); + + // draw chamber delimiters + for (int ch = 1; ch <= 9; ch++) { + float xpos = static_cast(getChamberOffset(ch)); + TLine* delimiter = new TLine(xpos, scaleMin, xpos, scaleMax); + delimiter->SetLineColor(kBlack); + delimiter->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(delimiter); + } + + // draw x-axis labels + for (int ch = 0; ch <= 9; ch += 1) { + float x1 = static_cast(getChamberOffset(ch)); + float x2 = (ch < 9) ? static_cast(getChamberOffset(ch + 1)) : h->GetXaxis()->GetXmax(); + float x0 = 0.8 * (x1 + x2) / (2 * h->GetXaxis()->GetXmax()) + 0.1; + float y0 = 0.05; + TText* label = new TText(); + label->SetNDC(); + label->SetText(x0, y0, TString::Format("%d", ch + 1)); + label->SetTextSize(10); + label->SetTextFont(42); + label->SetTextAlign(22); + h->GetListOfFunctions()->Add(label); + } +} + +//_________________________________________________________________________________________ + +void addChamberDelimitersToSolarHistogram(TH2* h) +{ + if (!h) { + return; + } + // disable ticks on horizontal axis + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0); + + TText* xtitle = new TText(); + xtitle->SetNDC(); + xtitle->SetText(0.87, 0.03, "chamber"); + xtitle->SetTextSize(10); + xtitle->SetTextFont(42); + h->GetListOfFunctions()->Add(xtitle); + + float scaleMin = h->GetYaxis()->GetXmin(); + float scaleMax = h->GetYaxis()->GetXmax(); + + // draw chamber delimiters + float xpos{ 0 }; + for (int ch = 1; ch <= 9; ch++) { + xpos += static_cast(getNumSolarPerChamber(ch)); + TLine* delimiter = new TLine(xpos, scaleMin, xpos, scaleMax); + delimiter->SetLineColor(kBlack); + delimiter->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(delimiter); + } + + // draw x-axis labels + float xMin{ 0 }; + float xMax{ 0 }; + for (int ch = 1; ch <= 10; ch += 1) { + xMin = xMax; + xMax += static_cast(getNumSolarPerChamber(ch)); + float x0 = 0.8 * (xMax + xMin) / (2 * h->GetXaxis()->GetXmax()) + 0.1; + float y0 = 0.05; + TText* label = new TText(); + label->SetNDC(); + label->SetText(x0, y0, TString::Format("%d", ch)); + label->SetTextSize(10); + label->SetTextFont(42); + label->SetTextAlign(22); + h->GetListOfFunctions()->Add(label); + } +} + +//_________________________________________________________________________________________ + +void drawThreshold(TH1* histogram, double threshold) +{ + TLine* l = new TLine(histogram->GetXaxis()->GetXmin(), threshold, histogram->GetXaxis()->GetXmax(), threshold); + l->SetLineColor(kBlue); + l->SetLineStyle(kDashed); + histogram->GetListOfFunctions()->Add(l); +} + +//_________________________________________________________________________________________ + +void drawThresholdsPerStation(TH1* histogram, const std::array, 5>& thresholdsPerStation, double defaultThreshold) +{ + double xmin = 0; + double xmax = 0; + for (int stationIndex = 0; stationIndex < 5; stationIndex++) { + // draw threshold line for this station + double threshold = defaultThreshold; + if (thresholdsPerStation[stationIndex]) { + threshold = thresholdsPerStation[stationIndex].value(); + } + xmax = static_cast(getChamberOffset((stationIndex + 1) * 2)); + + TLine* l = new TLine(xmin, threshold, xmax, threshold); + l->SetLineColor(kBlue); + l->SetLineStyle(kDashed); + histogram->GetListOfFunctions()->Add(l); + + xmin = xmax; + } +} + +//_________________________________________________________________________________________ + +void addDEBinLabels(TH1* histogram) +{ + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int bin = getDEindex(de) + 1; + auto binLabel = std::string("DE") + std::to_string(de); + histogram->GetXaxis()->SetBinLabel(bin, binLabel.c_str()); + } +} + +//_________________________________________________________________________________________ + +void addSolarBinLabels(TH1* histogram) +{ + auto solarIds = o2::mch::raw::getSolarUIDs(); + for (auto solar : solarIds) { + int bin = getSolarIndex(solar) + 1; + auto binLabel = std::string("S") + std::to_string(solar); + histogram->GetXaxis()->SetBinLabel(bin, binLabel.c_str()); + } +} + +//_________________________________________________________________________________________ + +void splitDataSourceName(std::string s, std::string& type, std::string& name) +{ + std::string delimiter = ":"; + size_t pos = s.find(delimiter); + if (pos != std::string::npos) { + type = s.substr(0, pos); + s.erase(0, pos + delimiter.length()); + } + name = s; +} + +//_________________________________________________________________________________________ + +static long getMoTimeStamp(std::shared_ptr mo) +{ + long timeStamp{ 0 }; + + auto iter = mo->getMetadataMap().find(repository::metadata_keys::created); + if (iter != mo->getMetadataMap().end()) { + timeStamp = std::stol(iter->second); + } + + return timeStamp; +} + +//_________________________________________________________________________________________ + +static bool checkMoTimeStamp(std::shared_ptr mo, uint64_t& timeStampOld) +{ + auto timeStamp = getMoTimeStamp(mo); + bool result = (timeStamp != timeStampOld) ? true : false; + timeStampOld = timeStamp; + return result; +} + +//_________________________________________________________________________________________ + +template +T* getPlotFromMO(std::shared_ptr mo) +{ + // Get ROOT object + TObject* obj = mo ? mo->getObject() : nullptr; + if (!obj) { + return nullptr; + } + + // Get histogram object + T* h = dynamic_cast(obj); + return h; +} + +//_________________________________________________________________________________________ + +CcdbObjectHelper::CcdbObjectHelper() +{ + setStartIme(); +} + +//_________________________________________________________________________________________ + +CcdbObjectHelper::CcdbObjectHelper(std::string p, std::string n) : mPath(p), mName(n) +{ + setStartIme(); +} + +//_________________________________________________________________________________________ + +void CcdbObjectHelper::setStartIme() +{ + mTimeStart = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +//_________________________________________________________________________________________ + +bool CcdbObjectHelper::update(repository::DatabaseInterface* qcdb, long timeStamp, const core::Activity& activity) +{ + mObject = qcdb->retrieveMO(mPath, mName, timeStamp, activity); + if (!mObject) { + return false; + } + + if (timeStamp > mTimeStart) { + // we are requesting a new object, so check that the retrieved one + // was created after the start of the processing + if (getMoTimeStamp(mObject) < mTimeStart) { + return false; + } + } + + if (!checkMoTimeStamp(mObject, mTimeStamp)) { + return false; + } + + return true; +} + +//_________________________________________________________________________________________ + +TrendGraph::TrendGraph(std::string name, std::string title, std::string label, std::optional refValue) + : TCanvas(name.c_str(), title.c_str()), + mRefValue(refValue), + mAxisLabel(label) +{ + mGraphHist = std::make_unique(0); + + mGraph = std::make_unique(0); + mGraph->SetMarkerStyle(kCircle); + mGraph->SetTitle(fmt::format("{};time;{}", title, label).c_str()); + + if (refValue) { + mGraphRef = std::make_unique(0); + mGraphRef->SetLineColor(kRed); + mGraphRef->SetLineStyle(kDashed); + + float delta = (0.85 - 0.15) / 2; + for (int i = 0; i < 2; i++) { + mLegends[i] = std::make_unique((delta * i) + 0.15, 0.79, (delta * (i + 1)) + 0.15, 0.89); + mLegends[i]->SetBorderSize(0); + } + mLegends[0]->AddEntry(mGraph.get(), "data ", "lp"); + mLegends[1]->AddEntry(mGraphRef.get(), "reference", "l"); + } +} + +//_________________________________________________________________________________________ + +void TrendGraph::update(uint64_t time, float val) +{ + mGraph->AddPoint(time, val); + mGraphHist->AddPoint(time, 0); + if (mRefValue && mGraphRef) { + mGraphRef->AddPoint(time, mRefValue.value()); + } + + Clear(); + cd(); + + mGraphHist->Draw("A"); + + auto hAxis = mGraphHist->GetHistogram(); + hAxis->SetTitle(GetTitle()); + hAxis->GetYaxis()->SetTitle(mAxisLabel.c_str()); + hAxis->GetXaxis()->SetTimeDisplay(1); + hAxis->GetXaxis()->SetNdivisions(505); + hAxis->GetXaxis()->SetTimeOffset(0.0); + hAxis->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + + Double_t min = mGraphRef ? mRefValue.value() : std::numeric_limits::max(); + Double_t max = mGraphRef ? mRefValue.value() : std::numeric_limits::min(); + min = TMath::Min(min, TMath::MinElement(mGraph->GetN(), mGraph->GetY())); + max = TMath::Max(max, TMath::MaxElement(mGraph->GetN(), mGraph->GetY())); + auto delta = max - min; + min -= 0.1 * delta; + if (mGraphRef) { + // leave some space at the top for the legend + max += 0.3 * delta; + } else { + max += 0.1 * delta; + } + + hAxis->SetMinimum(min); + hAxis->SetMaximum(max); + hAxis->Draw("AXIS"); + + mGraph->Draw("PL,SAME"); + if (mGraphRef) { + mGraphRef->Draw("L,SAME"); + } + + for (auto& l : mLegends) { + if (l) { + l->Draw(); + } + } +} + +//_________________________________________________________________________________________ + +QualityTrendGraph::QualityTrendGraph(std::string name, std::string title) : TCanvas(name.c_str(), title.c_str()) +{ + SetGridy(1); + mGraphHist = std::make_unique(0); + + mGraph = std::make_unique(0); + mGraph->SetMarkerStyle(kCircle); + mGraph->SetTitle(fmt::format("{};time;quality", title).c_str()); + + mLabels[0] = std::make_unique(0.09, 0.1 + 0.8 / 8.0, "Null"); + mLabels[1] = std::make_unique(0.09, 0.1 + 3.0 * 0.8 / 8.0, "Bad"); + mLabels[2] = std::make_unique(0.09, 0.1 + 5.0 * 0.8 / 8.0, "Medium"); + mLabels[3] = std::make_unique(0.09, 0.1 + 7.0 * 0.8 / 8.0, "Good"); + for (auto& l : mLabels) { + l->SetNDC(); + l->SetTextAlign(32); + l->SetTextSize(0.08); + l->SetTextFont(42); + } +} + +//_________________________________________________________________________________________ + +void QualityTrendGraph::update(uint64_t time, Quality q) +{ + float val = 0.5; + if (q == Quality::Bad) { + val = 1.5; + } + if (q == Quality::Medium) { + val = 2.5; + } + if (q == Quality::Good) { + val = 3.5; + } + mGraph->AddPoint(time, val); + mGraphHist->AddPoint(time, 0); + + Clear(); + cd(); + + mGraphHist->Draw("A"); + + auto hAxis = mGraphHist->GetHistogram(); + hAxis->SetTitle(GetTitle()); + hAxis->GetXaxis()->SetTimeDisplay(1); + hAxis->GetXaxis()->SetNdivisions(505); + hAxis->GetXaxis()->SetTimeOffset(0.0); + hAxis->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + hAxis->GetYaxis()->SetTitle(""); + hAxis->GetYaxis()->SetTickLength(0.0); + hAxis->GetYaxis()->SetLabelSize(0.0); + hAxis->GetYaxis()->SetNdivisions(3, kFALSE); + hAxis->SetMinimum(0); + hAxis->SetMaximum(4); + hAxis->Draw("AXIS"); + + mGraph->Draw("PL,SAME"); + + for (auto& l : mLabels) { + l->Draw(); + } +} + +//_________________________________________________________________________________________ + +TrendMultiGraph::TrendMultiGraph(std::string name, std::string title, std::string label) + : TCanvas(name.c_str(), title.c_str()), + mAxisLabel(label) +{ + mGraphHist = std::make_unique(0); +} + +//_________________________________________________________________________________________ + +void TrendMultiGraph::addGraph(std::string name, std::string title, std::optional refValue) +{ + if (mNGraphs >= 10) { + return; + } + + std::array markerStyles{ kPlus, kStar, kCircle, kMultiply, kFullTriangleUp, + kFullTriangleDown, kOpenCircle, kOpenSquare, kOpenTriangleUp, kOpenDiamond }; + std::array markerColors{ 2, 4, 1, 3, kOrange, 6, 7, 8, 9, 11 }; + + mGraphs[mNGraphs] = std::make_unique(0); + mGraphs[mNGraphs]->SetNameTitle(name.c_str(), title.c_str()); + mGraphs[mNGraphs]->SetMarkerStyle(markerStyles[mNGraphs]); + mGraphs[mNGraphs]->SetMarkerColor(markerColors[mNGraphs]); + mGraphs[mNGraphs]->SetLineColor(markerColors[mNGraphs]); + + if (refValue) { + mRefValues[mNGraphs] = refValue; + mGraphsRef[mNGraphs] = std::make_unique(0); + mGraphsRef[mNGraphs]->SetNameTitle((name + "Ref").c_str(), title.c_str()); + mGraphsRef[mNGraphs]->SetMarkerStyle(markerStyles[mNGraphs]); + mGraphsRef[mNGraphs]->SetMarkerColor(markerColors[mNGraphs]); + mGraphsRef[mNGraphs]->SetLineColor(markerColors[mNGraphs]); + mGraphsRef[mNGraphs]->SetLineStyle(kDashed); + } + + mNGraphs += 1; +} + +//_________________________________________________________________________________________ + +void TrendMultiGraph::addLegends() +{ + int nrows = 0; + int ncols = 0; + switch (mNGraphs) { + case 1: + case 2: + case 3: + case 4: + case 5: + nrows = 1; + ncols = mNGraphs; + break; + case 6: + case 7: + case 8: + case 9: + case 10: + nrows = 2; + ncols = mNGraphs / 2; + break; + default: + break; + } + + if (nrows == 0 || ncols == 0) { + return; + } + + float delta = (0.85 - 0.15) / ncols; + for (int i = 0; i < ncols; i++) { + mLegends[i] = std::make_unique((delta * i) + 0.15, 0.79, (delta * (i + 1)) + 0.15, 0.89); + mLegends[i]->SetBorderSize(0); + } + + for (int i = 0; i < mNGraphs; i++) { + int lid = i % ncols; + mLegends[lid]->AddEntry(mGraphs[i].get(), mGraphs[i]->GetTitle(), "lp"); + if (mGraphsRef[i]) { + mLegends[lid]->AddEntry(mGraphsRef[i].get(), (std::string("ref. ") + mGraphs[i]->GetTitle()).c_str(), "l"); + } + } +} + +//_________________________________________________________________________________________ + +void TrendMultiGraph::update(long time, gsl::span values) +{ + if (values.size() > mNGraphs) { + return; + } + + mGraphHist->AddPoint(time, 0); + for (int i = 0; i < values.size(); i++) { + auto& gr = mGraphs[i]; + gr->AddPoint(time, values[i]); + if (mRefValues[i] && mGraphsRef[i]) { + auto& grref = mGraphsRef[i]; + grref->AddPoint(time, mRefValues[i].value()); + } + } + + // compute optimal vertical range if not set by user + Double_t min; + Double_t max; + if (mYmin != mYmax) { + min = mYmin; + max = mYmax; + } else { + min = std::numeric_limits::max(); + max = std::numeric_limits::min(); + for (int i = 0; i < values.size(); i++) { + auto& gr = mGraphs[i]; + min = TMath::Min(min, TMath::MinElement(gr->GetN(), gr->GetY())); + max = TMath::Max(max, TMath::MaxElement(gr->GetN(), gr->GetY())); + } + auto delta = max - min; + min -= 0.1 * delta; + max += 0.3 * delta; + } + + Clear(); + cd(); + + mGraphHist->Draw("A"); + auto hAxis = mGraphHist->GetHistogram(); + hAxis->SetTitle(GetTitle()); + // hAxis->GetYaxis()->SetTitleOffset(1.4); + hAxis->GetYaxis()->SetTitle(mAxisLabel.c_str()); + hAxis->GetXaxis()->SetTimeDisplay(1); + hAxis->GetXaxis()->SetNdivisions(505); + hAxis->GetXaxis()->SetTimeOffset(0.0); + hAxis->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + hAxis->SetMinimum(min); + hAxis->SetMaximum(max); + hAxis->Draw("AXIS"); + + for (auto& gr : mGraphs) { + if (gr) { + gr->Draw("PL,SAME"); + } + } + + for (auto& gr : mGraphsRef) { + if (gr) { + gr->Draw("L,SAME"); + } + } + + for (auto& l : mLegends) { + if (l) { + l->Draw(); + } + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/OrbitsPlotter.cxx b/Modules/MUON/MCH/src/OrbitsPlotter.cxx new file mode 100644 index 0000000000..fbec23eb79 --- /dev/null +++ b/Modules/MUON/MCH/src/OrbitsPlotter.cxx @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file OrbitsPlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/OrbitsPlotter.h" +#include "MCH/Helpers.h" +#include "MCHGlobalMapping/DsIndex.h" +#include + +using namespace o2::mch; +using namespace o2::mch::raw; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +OrbitsPlotter::OrbitsPlotter(std::string path) +{ + mElec2DetMapper = createElec2DetMapper(); + mFeeLink2SolarMapper = createFeeLink2SolarMapper(); + + //---------------------------------- + // Orbits histogram + //---------------------------------- + mHistogramOrbits = std::make_unique(TString::Format("%sDigitOrbitInTFDE", path.c_str()), "Digit orbits vs DE", getNumDE(), 0, getNumDE(), 768, -384, 384); + addHisto(mHistogramOrbits.get(), false, "colz", "colz"); +} + +//_________________________________________________________________________________________ + +void OrbitsPlotter::update(TH2F* h) +{ + if (!h) { + return; + } + + auto incrementBin = [&](TH2F* h, float x, float y, float val) { + int bx = h->GetXaxis()->FindBin(x); + int by = h->GetYaxis()->FindBin(y); + auto entries = h->GetBinContent(bx, by); + h->SetBinContent(bx, by, entries + val); + }; + + mHistogramOrbits->Reset(); + + // loop over bins in electronics coordinates, and map the channels to the corresponding cathode pads + int nbinsx = h->GetXaxis()->GetNbins(); + int nbinsy = h->GetYaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + // address of the DS board in detector representation + auto dsDetId = getDsDetId(i - 1); + auto deId = dsDetId.deId(); + + int deIndex = getDEindex(deId); + if (deIndex < 0) { + continue; + } + + for (int j = 1; j <= nbinsy; j++) { + float entries = h->GetBinContent(i, j); + int orbit = h->GetYaxis()->GetBinCenter(j); + incrementBin(mHistogramOrbits.get(), deIndex, orbit, entries); + } + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/PedestalsCheck.cxx b/Modules/MUON/MCH/src/PedestalsCheck.cxx new file mode 100644 index 0000000000..c6e69d5f2f --- /dev/null +++ b/Modules/MUON/MCH/src/PedestalsCheck.cxx @@ -0,0 +1,464 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalsCheck.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/PedestalsCheck.h" +#include "MUONCommon/Helpers.h" + +#include +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control_modules::muon; + +namespace o2::quality_control_modules::muonchambers +{ + +void PedestalsCheck::configure() +{ + mMaxBadFractionPerDE = getConfigurationParameter(mCustomParameters, "MaxBadFractionPerDE", mMaxBadFractionPerDE); + mMaxEmptyFractionPerDE = getConfigurationParameter(mCustomParameters, "MaxEmptyFractionPerDE", mMaxEmptyFractionPerDE); + mMinStatisticsPerDE = getConfigurationParameter(mCustomParameters, "MinStatisticsPerDE", mMinStatisticsPerDE); + mPedestalsPlotScaleMin = getConfigurationParameter(mCustomParameters, "PedestalsPlotScaleMin", mPedestalsPlotScaleMin); + mPedestalsPlotScaleMax = getConfigurationParameter(mCustomParameters, "PedestalsPlotScaleMax", mPedestalsPlotScaleMax); + mNoisePlotScaleMin = getConfigurationParameter(mCustomParameters, "NoisePlotScaleMin", mNoisePlotScaleMin); + mNoisePlotScaleMax = getConfigurationParameter(mCustomParameters, "NoisePlotScaleMax", mNoisePlotScaleMax); + + mMaxBadST12 = getConfigurationParameter(mCustomParameters, "MaxBadDE_ST12", mMaxBadST12); + mMaxBadST345 = getConfigurationParameter(mCustomParameters, "MaxBadDE_ST345", mMaxBadST345); + + mQualityChecker.mMaxBadST12 = mMaxBadST12; + mQualityChecker.mMaxBadST345 = mMaxBadST345; +} + +Quality PedestalsCheck::check(std::map>* moMap) +{ + Quality resultTable = Quality::Null; + Quality result = Quality::Null; + + mQualityBadChannels = Quality::Null; + std::array qualityBadChannelsDE; + QualityChecker qualityCheckerBadChannels; + qualityCheckerBadChannels.mMaxBadST12 = mMaxBadST12; + qualityCheckerBadChannels.mMaxBadST345 = mMaxBadST345; + + mQualityEmptyChannels = Quality::Null; + std::array qualityEmptyChannelsDE; + QualityChecker qualityCheckerEmptyChannels; + qualityCheckerEmptyChannels.mMaxBadST12 = mMaxBadST12; + qualityCheckerEmptyChannels.mMaxBadST345 = mMaxBadST345; + + mQualityStatistics = Quality::Null; + std::array qualityStatisticsDE; + QualityChecker qualityCheckerStatistics; + qualityCheckerStatistics.mMaxBadST12 = mMaxBadST12; + qualityCheckerStatistics.mMaxBadST345 = mMaxBadST345; + + mQualityChecker.reset(); + + for (auto& [moName, mo] : *moMap) { + // check if the bad channels table was received from the calibrator + if (mo->getName().find("BadChannels_Elec") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return result; + } + + if (h->GetEntries() == 0) { + resultTable = Quality::Bad; + } else { + resultTable = Quality::Good; + } + } + + // check fraction of bad channels per detection element + if (mo->getName().find("BadChannelsPerDE") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return result; + } + + // initialize the DE qualities to Good + std::fill(qualityBadChannelsDE.begin(), qualityBadChannelsDE.end(), Quality::Good); + if (h->GetEntries() == 0) { + // plot is empty, set quality to Null + std::fill(qualityBadChannelsDE.begin(), qualityBadChannelsDE.end(), Quality::Null); + } else { + int nbinsx = h->GetXaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + // set the DE quality to Bad if the fraction of bad channels is above the threshold + if (h->GetBinContent(i) > mMaxBadFractionPerDE) { + qualityBadChannelsDE[i - 1] = Quality::Bad; + } + } + + // update the quality checkers + qualityCheckerBadChannels.addCheckResult(qualityBadChannelsDE); + mQualityChecker.addCheckResult(qualityBadChannelsDE); + } + } + + // check fraction of empty channels per detection element + if (mo->getName().find("EmptyChannelsPerDE") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return result; + } + + // initialize the DE qualities to Good + std::fill(qualityEmptyChannelsDE.begin(), qualityEmptyChannelsDE.end(), Quality::Good); + if (h->GetEntries() == 0) { + // plot is empty, set quality to Null + std::fill(qualityEmptyChannelsDE.begin(), qualityEmptyChannelsDE.end(), Quality::Medium); + } else { + int nbinsx = h->GetXaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + // set the DE quality to Bad if the fraction of empty channels is above the threshold + if (h->GetBinContent(i) > mMaxEmptyFractionPerDE) { + qualityEmptyChannelsDE[i - 1] = Quality::Bad; + } + } + } + + // update the quality checkers + qualityCheckerEmptyChannels.addCheckResult(qualityEmptyChannelsDE); + mQualityChecker.addCheckResult(qualityEmptyChannelsDE); + } + + // check fraction of empty channels per detection element + if (mo->getName().find("StatisticsPerDE") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + return result; + } + + // initialize the DE qualities to Good + std::fill(qualityStatisticsDE.begin(), qualityStatisticsDE.end(), Quality::Good); + if (h->GetEntries() == 0) { + // plot is empty, set quality to Null + std::fill(qualityStatisticsDE.begin(), qualityStatisticsDE.end(), Quality::Medium); + } else { + int nbinsx = h->GetXaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + // set the DE quality to Bad if the fraction of empty channels is above the threshold + if (h->GetBinContent(i) < mMinStatisticsPerDE) { + qualityStatisticsDE[i - 1] = Quality::Bad; + } + } + } + + // update the quality checkers + qualityCheckerStatistics.addCheckResult(qualityStatisticsDE); + mQualityChecker.addCheckResult(qualityStatisticsDE); + } + } + + // compute the aggregated quality + mQualityEmptyChannels = qualityCheckerEmptyChannels.getQuality(); + mQualityBadChannels = qualityCheckerBadChannels.getQuality(); + mQualityStatistics = qualityCheckerStatistics.getQuality(); + result = mQualityChecker.getQuality(); + if (resultTable == Quality::Bad) { + result = Quality::Bad; + } + + // update the error messages + mErrorMessages.clear(); + if (result == Quality::Good) { + mErrorMessages.emplace_back("Quality: GOOD"); + } else if (result == Quality::Medium) { + mErrorMessages.emplace_back("Quality: MEDIUM"); + mErrorMessages.emplace_back("Add Logbook entry"); + } else if (result == Quality::Bad) { + mErrorMessages.emplace_back("Quality: BAD"); + mErrorMessages.emplace_back("Contact MCH on-call"); + } else if (result == Quality::Null) { + mErrorMessages.emplace_back("Quality: NULL\n"); + } + mErrorMessages.emplace_back(""); + + if (resultTable == Quality::Bad) { + mErrorMessages.emplace_back("Missing Bad Channels Table"); + } + + if (mQualityEmptyChannels == Quality::Null) { + mErrorMessages.emplace_back("Empty channels plot not filled"); + } else if (mQualityEmptyChannels == Quality::Bad) { + mErrorMessages.emplace_back("Too many empty channels"); + } else if (mQualityEmptyChannels == Quality::Medium) { + mErrorMessages.emplace_back("Some detectors have empty channels"); + } + + if (mQualityBadChannels == Quality::Null) { + mErrorMessages.emplace_back("Bad channels plot not filled"); + } else if (mQualityBadChannels == Quality::Bad) { + mErrorMessages.emplace_back("Too many bad channels"); + } else if (mQualityBadChannels == Quality::Medium) { + mErrorMessages.emplace_back("Some detectors have bad channels"); + } + + if (mQualityStatistics == Quality::Null) { + mErrorMessages.emplace_back("Statistics plot not filled"); + } else if (mQualityStatistics == Quality::Bad) { + mErrorMessages.emplace_back("Statistics too small"); + } else if (mQualityStatistics == Quality::Medium) { + mErrorMessages.emplace_back("Some detectors have small statistics"); + } + + return result; +} + +static void updateTitle(TH1* hist, std::string suffix) +{ + if (!hist) { + return; + } + TString title = hist->GetTitle(); + title.Append(" "); + title.Append(suffix.c_str()); + hist->SetTitle(title); +} + +static std::string getCurrentTime() +{ + time_t t; + time(&t); + + struct tm* tmp; + tmp = localtime(&t); + + char timestr[500]; + strftime(timestr, sizeof(timestr), "(%x - %X)", tmp); + + std::string result = timestr; + return result; +} + +void PedestalsCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + auto currentTime = getCurrentTime(); + updateTitle(dynamic_cast(mo->getObject()), currentTime); + + if (mo->getName().find("CheckerMessages") != std::string::npos) { + auto* canvas = dynamic_cast(mo->getObject()); + if (!canvas) { + return; + } + canvas->cd(); + + TPaveText* msg = new TPaveText(0.1, 0.1, 0.9, 0.9, "NDC"); + for (auto s : mErrorMessages) { + msg->AddText(s.c_str()); + } + if (checkResult == Quality::Good) { + msg->SetTextColor(kGreen + 2); + } + if (checkResult == Quality::Medium) { + msg->SetTextColor(kOrange); + } + if (checkResult == Quality::Bad) { + msg->SetTextColor(kRed); + } + if (checkResult == Quality::Null) { + msg->SetTextColor(kBlack); + } + msg->SetBorderSize(0); + msg->SetFillColor(kWhite); + msg->Draw(); + } + + if (mo->getName().find("EmptyChannelsPerDE") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + + h->SetMinimum(0); + h->SetMaximum(1.1); + + addChamberDelimiters(h, 0, 1.1); + addChamberLabelsForDE(h); + + TLine* delimiter = new TLine(h->GetXaxis()->GetXmin(), mMaxEmptyFractionPerDE, h->GetXaxis()->GetXmax(), mMaxEmptyFractionPerDE); + delimiter->SetLineColor(kBlack); + delimiter->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(delimiter); + + if (mQualityEmptyChannels == Quality::Good) { + h->SetFillColor(kGreen); + } else if (mQualityEmptyChannels == Quality::Bad) { + h->SetFillColor(kRed); + } else if (mQualityEmptyChannels == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } + + if (mo->getName().find("BadChannelsPerDE") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + + h->SetMinimum(0); + h->SetMaximum(1.1); + + addChamberDelimiters(h, 0, 1.1); + addChamberLabelsForDE(h); + + TLine* delimiter = new TLine(h->GetXaxis()->GetXmin(), mMaxBadFractionPerDE, h->GetXaxis()->GetXmax(), mMaxBadFractionPerDE); + delimiter->SetLineColor(kBlack); + delimiter->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(delimiter); + + if (mQualityBadChannels == Quality::Good) { + h->SetFillColor(kGreen); + } else if (mQualityBadChannels == Quality::Bad) { + h->SetFillColor(kRed); + } else if (mQualityBadChannels == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } + + if (mo->getName().find("StatisticsPerDE") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + + auto min = h->GetMinimum(); + auto max = 1.05 * h->GetMaximum(); + + if (max < mMinStatisticsPerDE) { + max = 1.05 * mMinStatisticsPerDE; + } + h->SetMinimum(min); + h->SetMaximum(max); + + addChamberDelimiters(h, min, max); + addChamberLabelsForDE(h); + + TLine* delimiter = new TLine(h->GetXaxis()->GetXmin(), mMinStatisticsPerDE, h->GetXaxis()->GetXmax(), mMinStatisticsPerDE); + delimiter->SetLineColor(kBlack); + delimiter->SetLineStyle(kDashed); + h->GetListOfFunctions()->Add(delimiter); + + if (mQualityStatistics == Quality::Good) { + h->SetFillColor(kGreen); + } else if (mQualityStatistics == Quality::Bad) { + h->SetFillColor(kRed); + } else if (mQualityStatistics == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } + + if (mo->getName().find("PedestalsPerDE") != std::string::npos || + mo->getName().find("NoisePerDE") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + + auto min = 0; + auto max = 1.05 * h->GetMaximum(); + h->SetMinimum(min); + h->SetMaximum(max); + + addChamberDelimiters(h, min, max); + addChamberLabelsForDE(h); + } + + if (mo->getName().find("Pedestals_Elec") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + + h->SetMinimum(mPedestalsPlotScaleMin); + h->SetMaximum(mPedestalsPlotScaleMax); + } + + if (mo->getName().find("Noise_Elec") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + + h->SetMinimum(mNoisePlotScaleMin); + h->SetMaximum(mNoisePlotScaleMax); + } + + if ((mo->getName().find("Pedestals_ST12") != std::string::npos) || + (mo->getName().find("Pedestals_ST345") != std::string::npos)) { + auto* h = dynamic_cast(mo->getObject()); + h->SetMinimum(mPedestalsPlotScaleMin); + h->SetMaximum(mPedestalsPlotScaleMax); + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0.0); + h->GetYaxis()->SetLabelSize(0.0); + } + + if ((mo->getName().find("Noise_ST12") != std::string::npos) || + (mo->getName().find("Noise_ST345") != std::string::npos)) { + auto* h = dynamic_cast(mo->getObject()); + h->SetMinimum(mNoisePlotScaleMin); + h->SetMaximum(mNoisePlotScaleMax); + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0.0); + h->GetYaxis()->SetLabelSize(0.0); + } + + if ((mo->getName().find("BadChannels_ST12") != std::string::npos) || + (mo->getName().find("BadChannels_ST345") != std::string::npos)) { + auto* h = dynamic_cast(mo->getObject()); + double min = 1; + int nbinsx = h->GetXaxis()->GetNbins(); + int nbinsy = h->GetYaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + for (int j = 1; j <= nbinsy; j++) { + auto value = h->GetBinContent(i, j); + if (value > 0 && value < min) { + min = value; + } + } + } + h->SetMinimum(0.99 * min); + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0.0); + h->GetYaxis()->SetLabelSize(0.0); + } + + if (mo->getName().find("Pedestals_XY") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + h->SetMinimum(mPedestalsPlotScaleMin); + h->SetMaximum(mPedestalsPlotScaleMax); + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0.0); + h->GetYaxis()->SetLabelSize(0.0); + } + + if (mo->getName().find("Noise_XY") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + h->SetMinimum(mNoisePlotScaleMin); + h->SetMaximum(mNoisePlotScaleMax); + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0.0); + h->GetYaxis()->SetLabelSize(0.0); + } + + if (mo->getName().find("BadChannels_XY") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + h->SetMinimum(0); + h->SetMaximum(1); + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0.0); + h->GetYaxis()->SetLabelSize(0.0); + } +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/PedestalsTask.cxx b/Modules/MUON/MCH/src/PedestalsTask.cxx new file mode 100644 index 0000000000..221501a574 --- /dev/null +++ b/Modules/MUON/MCH/src/PedestalsTask.cxx @@ -0,0 +1,616 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PedestalsTask.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/PedestalsTask.h" +#include "MCH/Helpers.h" +#include "MUONCommon/Helpers.h" +#include "Headers/RAWDataHeader.h" +#include "DPLUtils/DPLRawParser.h" +#include "QualityControl/QcInfoLogger.h" +#include "MCHMappingInterface/Segmentation.h" +#include "MCHCalibration/PedestalChannel.h" +#ifdef MCH_HAS_MAPPING_FACTORY +#include "MCHMappingFactory/CreateSegmentation.h" +#endif +#include "MCHConstants/DetectionElements.h" + +using namespace std; +using namespace o2::framework; +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ +PedestalsTask::PedestalsTask() : TaskInterface() +{ +} + +PedestalsTask::~PedestalsTask() +{ +} + +void PedestalsTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize PedestalsTask" << ENDM; + // flag to enable extra disagnostics plots + mFullHistos = getConfigurationParameter(mCustomParameters, "FullHistos", mFullHistos); + + mElec2DetMapper = o2::mch::raw::createElec2DetMapper(); + mFeeLink2SolarMapper = o2::mch::raw::createFeeLink2SolarMapper(); + mSolar2FeeLinkMapper = o2::mch::raw::createSolar2FeeLinkMapper(); + + const uint32_t nElecXbins = PedestalsTask::sMaxFeeId * PedestalsTask::sMaxLinkId * PedestalsTask::sMaxDsId; + + mHistogramStat = std::make_unique("Statistics_Elec", "Statistics", nElecXbins, 0, nElecXbins, 64, 0, 64); + publishObject(mHistogramStat.get(), "colz", false); + + mHistogramPedestals = std::make_unique("Pedestals_Elec", "Pedestals", nElecXbins, 0, nElecXbins, 64, 0, 64); + publishObject(mHistogramPedestals.get(), "colz", false); + + mHistogramNoise = std::make_unique("Noise_Elec", "Noise", nElecXbins, 0, nElecXbins, 64, 0, 64); + publishObject(mHistogramNoise.get(), "colz", false); + + mHistogramBadChannels = std::make_unique("BadChannels_Elec", "Bad Channels", nElecXbins, 0, nElecXbins, 64, 0, 64); + publishObject(mHistogramBadChannels.get(), "colz", false); + + // values averaged over detection elements + mHistogramStatDE = std::make_unique("StatisticsPerDE", "Statistics per DE", getNumDE(), 0, getNumDE()); + publishObject(mHistogramStatDE.get(), "", false); + + mHistogramEmptyChannelsDE = std::make_unique("EmptyChannelsPerDE", "Empty Channels per DE", getNumDE(), 0, getNumDE()); + publishObject(mHistogramEmptyChannelsDE.get(), "", false); + + mHistogramPedestalsDE = std::make_unique("PedestalsPerDE", "Pedestals per DE", getNumDE(), 0, getNumDE()); + publishObject(mHistogramPedestalsDE.get(), "", false); + + mHistogramNoiseDE = std::make_unique("NoisePerDE", "Noise per DE", getNumDE(), 0, getNumDE()); + publishObject(mHistogramNoiseDE.get(), "", false); + + mHistogramBadChannelsDE = std::make_unique("BadChannelsPerDE", "Bad Channels per DE", getNumDE(), 0, getNumDE()); + publishObject(mHistogramBadChannelsDE.get(), "", false); + + std::string stname[2]{ "ST12", "ST345" }; + float scaleFactors[2]{ 5, 10 }; + for (int i = 0; i < 2; i++) { + mHistogramStatMCH[i] = std::make_unique(fmt::format("Statistics_{}", stname[i]), "Statistics", i, scaleFactors[i]); + mHistogramStatMCH[i]->init(); + publishObject(mHistogramStatMCH[i]->getHist(), "colz", false); + + mHistogramPedestalsMCH[i] = std::make_unique(fmt::format("Pedestals_{}", stname[i]), "Pedestals", i, scaleFactors[i]); + mHistogramPedestalsMCH[i]->init(); + publishObject(mHistogramPedestalsMCH[i]->getHist(), "colz", false); + + mHistogramNoiseMCH[i] = std::make_unique(fmt::format("Noise_{}", stname[i]), "Noise", i, scaleFactors[i]); + mHistogramNoiseMCH[i]->init(); + publishObject(mHistogramNoiseMCH[i]->getHist(), "colz", false); + + mHistogramBadChannelsMCH[i] = std::make_unique(fmt::format("BadChannels_{}", stname[i]), "Bad Channels", i, scaleFactors[i]); + mHistogramBadChannelsMCH[i]->init(); + publishObject(mHistogramBadChannelsMCH[i]->getHist(), "colz", false); + } + + for (int si = 0; si < 5; si++) { + mHistogramNoiseDistribution[si] = std::make_unique(TString::Format("ST%d/Noise_Distr_ST%d", si + 1, si + 1), + TString::Format("Noise distribution (ST%d)", si + 1), 1000, 0, 10); + publishObject(mHistogramNoiseDistribution[si].get(), "hist", false); + } + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + for (int pi = 0; pi < 5; pi++) { + auto hNoiseDE = std::make_shared(TString::Format("%sNoise_Distr_DE%03d_b_%d", getHistoPath(de).c_str(), de, pi), + TString::Format("Noise distribution (DE%03d B, %d)", de, pi), 1000, 0, 10); + mHistogramNoiseDistributionDE[pi][0].insert(make_pair(de, hNoiseDE)); + publishObject(hNoiseDE.get(), "hist", false, mFullHistos); + + hNoiseDE = std::make_shared(TString::Format("%sNoise_Distr_DE%03d_nb_%d", getHistoPath(de).c_str(), de, pi), + TString::Format("Noise distribution (DE%03d NB, %d)", de, pi), 1000, 0, 10); + mHistogramNoiseDistributionDE[pi][1].insert(make_pair(de, hNoiseDE)); + publishObject(hNoiseDE.get(), "hist", false, mFullHistos); + } + { + auto hStatXY = std::make_shared(TString::Format("%sStatistics_XY_B_%03d", getHistoPath(de).c_str(), de), + TString::Format("Statistics (DE%03d B)", de), de, 0); + mHistogramStatXY[0].insert(make_pair(de, hStatXY)); + publishObject(hStatXY->getHist(), "colz", false, mFullHistos); + + auto hPedXY = std::make_shared(TString::Format("%sPedestals_XY_B_%03d", getHistoPath(de).c_str(), de), + TString::Format("Pedestals (DE%03d B)", de), de, 0); + mHistogramPedestalsXY[0].insert(make_pair(de, hPedXY)); + publishObject(hPedXY->getHist(), "colz", false, mFullHistos); + + auto hNoiseXY = std::make_shared(TString::Format("%sNoise_XY_B_%03d", getHistoPath(de).c_str(), de), + TString::Format("Noise (DE%03d B)", de), de, 0); + mHistogramNoiseXY[0].insert(make_pair(de, hNoiseXY)); + publishObject(hNoiseXY->getHist(), "colz", false, mFullHistos); + + auto hBadChannelsXY = std::make_shared(TString::Format("%sBadChannels_XY_B_%03d", getHistoPath(de).c_str(), de), + TString::Format("Bad Channels (DE%03d B)", de), de, 0); + mHistogramBadChannelsXY[0].insert(make_pair(de, hBadChannelsXY)); + publishObject(hBadChannelsXY->getHist(), "colz", false, mFullHistos); + } + { + auto hStatXY = std::make_shared(TString::Format("%sStatistics_XY_NB_%03d", getHistoPath(de).c_str(), de), + TString::Format("Statistics (DE%03d NB)", de), de, 1); + mHistogramStatXY[1].insert(make_pair(de, hStatXY)); + publishObject(hStatXY->getHist(), "colz", false, mFullHistos); + + auto hPedXY = std::make_shared(TString::Format("%sPedestals_XY_NB_%03d", getHistoPath(de).c_str(), de), + TString::Format("Pedestals (DE%03d NB)", de), de, 1); + mHistogramPedestalsXY[1].insert(make_pair(de, hPedXY)); + publishObject(hPedXY->getHist(), "colz", false, mFullHistos); + + auto hNoiseXY = std::make_shared(TString::Format("%sNoise_XY_NB_%03d", getHistoPath(de).c_str(), de), + TString::Format("Noise (DE%03d NB)", de), de, 1); + mHistogramNoiseXY[1].insert(make_pair(de, hNoiseXY)); + publishObject(hNoiseXY->getHist(), "colz", false, mFullHistos); + + auto hBadChannelsXY = std::make_shared(TString::Format("%sBadChannels_XY_NB_%03d", getHistoPath(de).c_str(), de), + TString::Format("Bad Channels (DE%03d NB)", de), de, 1); + mHistogramBadChannelsXY[1].insert(make_pair(de, hBadChannelsXY)); + publishObject(hBadChannelsXY->getHist(), "colz", false, mFullHistos); + } + } + + mCanvasCheckerMessages = std::make_unique("CheckerMessages", "Checker Messages", 800, 600); + getObjectsManager()->startPublishing(mCanvasCheckerMessages.get()); + + mPrintLevel = 0; +} + +void PedestalsTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; +} + +void PedestalsTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +bool PedestalsTask::checkPadMapping(uint16_t feeId, uint8_t linkId, uint8_t eLinkId, o2::mch::raw::DualSampaChannelId channel, int& deId, int& padId) +{ + uint16_t solarId = -1; + int dsIddet = -1; + padId = -1; + + o2::mch::raw::FeeLinkId feeLinkId{ feeId, linkId }; + + if (auto opt = mFeeLink2SolarMapper(feeLinkId); opt.has_value()) { + solarId = opt.value(); + } + if (solarId < 0 || solarId > 1023) { + return false; + } + + o2::mch::raw::DsElecId dsElecId{ solarId, static_cast(eLinkId / 5), static_cast(eLinkId % 5) }; + + if (auto opt = mElec2DetMapper(dsElecId); opt.has_value()) { + o2::mch::raw::DsDetId dsDetId = opt.value(); + dsIddet = dsDetId.dsId(); + deId = dsDetId.deId(); + } + + if (deId < 0 || dsIddet < 0) { + return false; + } + + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + padId = segment.findPadByFEE(dsIddet, int(channel)); + + if (padId < 0) { + return false; + } + return true; +} + +void PedestalsTask::PlotPedestal(uint16_t solarID, uint8_t dsID, uint8_t channel, double stat, double mean, double rms) +{ + std::optional feeLinkID = mSolar2FeeLinkMapper(solarID); + if (!feeLinkID) { + return; + } + + auto feeId = feeLinkID->feeId(); + auto linkId = feeLinkID->linkId(); + + int xbin = feeId * 12 * 40 + (linkId % 12) * 40 + dsID + 1; + int ybin = channel + 1; + + mHistogramStat->SetBinContent(xbin, ybin, stat); + mHistogramPedestals->SetBinContent(xbin, ybin, mean); + mHistogramNoise->SetBinContent(xbin, ybin, rms); + + // PlotPedestalDE(solarID, dsID, channel, stat, mean, rms); +} + +void PedestalsTask::PlotPedestalDE(uint16_t solarID, uint8_t dsID, uint8_t channel, double stat, double mean, double rms) +{ + o2::mch::raw::DsElecId dsElecId(solarID, dsID / 5, dsID % 5); + std::optional dsDetId = mElec2DetMapper(dsElecId); + if (!dsDetId) { + return; + } + + auto deId = dsDetId->deId(); + auto dsIddet = dsDetId->dsId(); + + int padId = -1; + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + padId = segment.findPadByFEE(dsIddet, int(channel)); + if (padId < 0) { + return; + } + + double padX = segment.padPositionX(padId); + double padY = segment.padPositionY(padId); + double padSizeX = segment.padSizeX(padId); + double padSizeY = segment.padSizeY(padId); + int cathode = segment.isBendingPad(padId) ? 0 : 1; + + // Fill the histograms for each detection element + auto hStatXY = mHistogramStatXY[cathode].find(deId); + if ((hStatXY != mHistogramStatXY[cathode].end()) && (hStatXY->second != NULL)) { + hStatXY->second->Set(padX, padY, padSizeX, padSizeY, stat); + } + auto hPedXY = mHistogramPedestalsXY[cathode].find(deId); + if ((hPedXY != mHistogramPedestalsXY[cathode].end()) && (hPedXY->second != NULL)) { + hPedXY->second->Set(padX, padY, padSizeX, padSizeY, mean); + } + auto hNoiseXY = mHistogramNoiseXY[cathode].find(deId); + if ((hNoiseXY != mHistogramNoiseXY[cathode].end()) && (hNoiseXY->second != NULL)) { + hNoiseXY->second->Set(padX, padY, padSizeX, padSizeY, rms); + } +} + +void PedestalsTask::PlotBadChannel(uint16_t solarID, uint8_t dsID, uint8_t channel) +{ + static constexpr double sBinValMin = 0.000001; + std::optional feeLinkID = mSolar2FeeLinkMapper(solarID); + if (!feeLinkID) { + return; + } + + auto feeId = feeLinkID->feeId(); + auto linkId = feeLinkID->linkId(); + + int xbin = feeId * 12 * 40 + (linkId % 12) * 40 + dsID + 1; + int ybin = channel + 1; + + mHistogramBadChannels->SetBinContent(xbin, ybin, sBinValMin); + + // PlotBadChannelDE(solarID, dsID, channel); +} + +void PedestalsTask::PlotBadChannelDE(uint16_t solarID, uint8_t dsID, uint8_t channel) +{ + o2::mch::raw::DsElecId dsElecId(solarID, dsID / 5, dsID % 5); + std::optional dsDetId = mElec2DetMapper(dsElecId); + if (!dsDetId) { + return; + } + + auto deId = dsDetId->deId(); + auto dsIddet = dsDetId->dsId(); + + int padId = -1; + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + padId = segment.findPadByFEE(dsIddet, int(channel)); + if (padId < 0) { + return; + } + + double padX = segment.padPositionX(padId); + double padY = segment.padPositionY(padId); + double padSizeX = segment.padSizeX(padId); + double padSizeY = segment.padSizeY(padId); + int cathode = segment.isBendingPad(padId) ? 0 : 1; + + // Fill the histograms for each detection element + auto hXY = mHistogramBadChannelsXY[cathode].find(deId); + if ((hXY != mHistogramBadChannelsXY[cathode].end()) && (hXY->second != NULL)) { + hXY->second->Set(padX, padY, padSizeX, padSizeY, 0.000001); + } +} + +void PedestalsTask::processElecMaps() +{ + auto updateMean = [](double val, double mean, int N) { return (mean + (val - mean) / N); }; + + for (int si = 0; si < 5; si++) { + mHistogramNoiseDistribution[si]->Reset(); + } + + for (int pi = 0; pi < 5; pi++) { + for (int i = 0; i < 2; i++) { + auto ih = mHistogramNoiseDistributionDE[pi][i].begin(); + while (ih != mHistogramNoiseDistributionDE[pi][i].end()) { + ih->second->Reset(); + ih++; + } + } + } + + std::vector nPadsPerDE(getNumDE()); + std::fill(nPadsPerDE.begin(), nPadsPerDE.end(), 0); + + std::vector nBadPerDE(getNumDE()); + std::fill(nBadPerDE.begin(), nBadPerDE.end(), 0); + + std::vector nEmptyPerDE(getNumDE()); + std::fill(nEmptyPerDE.begin(), nEmptyPerDE.end(), 0); + + std::vector nEntriesPerDE(getNumDE()); + std::fill(nEntriesPerDE.begin(), nEntriesPerDE.end(), 0); + + std::vector meanPedPerDE(getNumDE()); + std::fill(meanPedPerDE.begin(), meanPedPerDE.end(), 0); + + std::vector meanNoisePerDE(getNumDE()); + std::fill(meanNoisePerDE.begin(), meanNoisePerDE.end(), 0); + + std::vector meanStatPerDE(getNumDE()); + std::fill(meanStatPerDE.begin(), meanStatPerDE.end(), 0); + + //.............. + int nbinsx = mHistogramStat->GetXaxis()->GetNbins(); + int nbinsy = mHistogramStat->GetYaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + int index = i - 1; + int ds_addr = (index % 40); + int link_id = (index / 40) % 12; + int fee_id = index / (12 * 40); + + for (int j = 1; j <= nbinsy; j++) { + int chan_addr = j - 1; + int de = -1; + int padId = -1; + + if (!checkPadMapping(fee_id, link_id, ds_addr, chan_addr, de, padId)) { + continue; + } + if (de < 0) { + continue; + } + + int deId = getDEindex(de); + if (deId < 0) { + continue; + } + + nPadsPerDE[deId] += 1; + + double stat = mHistogramStat->GetBinContent(i, j); + float ped = mHistogramPedestals->GetBinContent(i, j); + float noise = mHistogramNoise->GetBinContent(i, j); + auto bad = mHistogramBadChannels->GetBinContent(i, j); + + // update mean values per detection element + if (stat > 0) { + if (bad > 0 && bad < 1) { + nBadPerDE[deId] += 1; + } + + nEntriesPerDE[deId] += 1; + int N = nEntriesPerDE[deId]; + + double ped = mHistogramPedestals->GetBinContent(i, j); + double noise = mHistogramNoise->GetBinContent(i, j); + + meanStatPerDE[deId] = updateMean(stat, meanStatPerDE[deId], N); + meanPedPerDE[deId] = updateMean(ped, meanPedPerDE[deId], N); + meanNoisePerDE[deId] = updateMean(noise, meanNoisePerDE[deId], N); + } else { + nEmptyPerDE[deId] += 1; + } + + // update 2D maps in detector coordinates + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(de); + float padX = segment.padPositionX(padId); + float padY = segment.padPositionY(padId); + float padSizeX = segment.padSizeX(padId); + float padSizeY = segment.padSizeY(padId); + int cathode = segment.isBendingPad(padId) ? 0 : 1; + + // Fill the histograms for each detection element + auto hXY = mHistogramBadChannelsXY[cathode].find(de); + if ((hXY != mHistogramBadChannelsXY[cathode].end()) && (hXY->second != NULL)) { + hXY->second->Set(padX, padY, padSizeX, padSizeY, bad); + } + + hXY = mHistogramStatXY[cathode].find(de); + if ((hXY != mHistogramStatXY[cathode].end()) && (hXY->second != NULL)) { + hXY->second->Set(padX, padY, padSizeX, padSizeY, stat); + } + + hXY = mHistogramPedestalsXY[cathode].find(de); + if ((hXY != mHistogramPedestalsXY[cathode].end()) && (hXY->second != NULL)) { + hXY->second->Set(padX, padY, padSizeX, padSizeY, ped); + } + + hXY = mHistogramNoiseXY[cathode].find(de); + if ((hXY != mHistogramNoiseXY[cathode].end()) && (hXY->second != NULL)) { + hXY->second->Set(padX, padY, padSizeX, padSizeY, noise); + } + + // fill 1D noise distributions + if (noise >= 0.001) { + float szmax = padSizeX; + if (szmax < padSizeY) { + szmax = padSizeY; + } + + int szid = 0; + if (fabs(szmax - 2.5) < 0.001) { + szid = 1; + } else if (fabs(szmax - 5.0) < 0.001) { + szid = 2; + } else if (fabs(szmax - 10.0) < 0.001) { + szid = 3; + } + + auto hNoiseDE = mHistogramNoiseDistributionDE[szid][cathode].find(de); + if ((hNoiseDE != mHistogramNoiseDistributionDE[szid][cathode].end()) && (hNoiseDE->second != NULL)) { + hNoiseDE->second->Fill(noise); + } + + int si = (de - 100) / 200; + if (si >= 0 && si < 5) { + mHistogramNoiseDistribution[si]->Fill(noise); + } + } + } + } + + // update global detector plots + for (int i = 0; i < 2; i++) { + mHistogramBadChannelsMCH[i]->set(mHistogramBadChannelsXY[0], mHistogramBadChannelsXY[1], true); + mHistogramStatMCH[i]->set(mHistogramStatXY[0], mHistogramStatXY[1], true); + mHistogramPedestalsMCH[i]->set(mHistogramPedestalsXY[0], mHistogramPedestalsXY[1], true); + mHistogramNoiseMCH[i]->set(mHistogramNoiseXY[0], mHistogramNoiseXY[1], true); + } + + // update 1-D plots with quantities averaged over detection elements + for (int i = 0; i < getNumDE(); i++) { + mHistogramStatDE->SetBinContent(i + 1, meanStatPerDE[i]); + mHistogramPedestalsDE->SetBinContent(i + 1, meanPedPerDE[i]); + mHistogramNoiseDE->SetBinContent(i + 1, meanNoisePerDE[i]); + + if (nPadsPerDE[i] > 0) { + mHistogramEmptyChannelsDE->SetBinContent(i + 1, nEmptyPerDE[i] / nPadsPerDE[i]); + mHistogramBadChannelsDE->SetBinContent(i + 1, nBadPerDE[i] / nPadsPerDE[i]); + } + } +} + +void PedestalsTask::monitorDataPedestals(o2::framework::ProcessingContext& ctx) +{ + auto pedestals = ctx.inputs().get>("pedestals"); + for (auto& p : pedestals) { + if (p.mEntries == 0) { + continue; + } + + PlotPedestal(p.dsChannelId.getSolarId(), + p.dsChannelId.getElinkId(), + p.dsChannelId.getChannel(), + p.mEntries, + p.mPedestal, + p.getRms()); + } +} + +void PedestalsTask::monitorDataDigits(o2::framework::ProcessingContext& ctx) +{ + auto digits = ctx.inputs().get>("digits"); + mPedestalData.fill(digits); + + for (auto& p : mPedestalData) { + if (p.mEntries == 0) { + continue; + } + + PlotPedestal(p.dsChannelId.getSolarId(), + p.dsChannelId.getElinkId(), + p.dsChannelId.getChannel(), + p.mEntries, + p.mPedestal, + p.getRms()); + } +} + +void PedestalsTask::monitorDataBadChannels(o2::framework::ProcessingContext& ctx) +{ + static constexpr double sBinValMin = 0.000001; + auto badChannels = ctx.inputs().get>("badchan"); + + // Initialize the bad channels map by setting to 1 all the bins that have non-zero statistics + int nbinsx = mHistogramStat->GetXaxis()->GetNbins(); + int nbinsy = mHistogramStat->GetYaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + for (int j = 1; j <= nbinsy; j++) { + auto stat = mHistogramStat->GetBinContent(i, j); + if (stat == 0) { + continue; + } + mHistogramBadChannels->SetBinContent(i, j, 1); + } + } + + // update the bad channels maps with the table received from the calibrator + for (auto& bc : badChannels) { + PlotBadChannel(bc.getSolarId(), bc.getElinkId(), bc.getChannel()); + } +} + +void PedestalsTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + if (ctx.inputs().isValid("pedestals")) { + monitorDataPedestals(ctx); + } else if (ctx.inputs().isValid("digits")) { + monitorDataDigits(ctx); + } + + if (ctx.inputs().isValid("badchan")) { + auto input = ctx.inputs().getDataRefByString("badchan"); + auto subspecification = framework::DataRefUtils::getHeader(input)->subSpecification; + if (subspecification != 0xDEADBEEF) { + monitorDataBadChannels(ctx); + } + } +} + +static void fillGlobalHistograms(std::array>, 2>& hDE, + std::array, 2>& hGlobal, bool doAverage) +{ + hGlobal[0]->set(hDE[0], hDE[1], doAverage); + hGlobal[1]->set(hDE[0], hDE[1], doAverage); +} + +void PedestalsTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + + processElecMaps(); + + fillGlobalHistograms(mHistogramBadChannelsXY, mHistogramBadChannelsMCH, true); + fillGlobalHistograms(mHistogramStatXY, mHistogramStatMCH, true); + fillGlobalHistograms(mHistogramPedestalsXY, mHistogramPedestalsMCH, true); + fillGlobalHistograms(mHistogramNoiseXY, mHistogramNoiseMCH, true); +} + +void PedestalsTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void PedestalsTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Reseting the histogram" << ENDM; + mPedestalData.reset(); + + for (auto h : mAllHistograms) { + h->Reset("ICES"); + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/PostProcessingConfigMCH.cxx b/Modules/MUON/MCH/src/PostProcessingConfigMCH.cxx new file mode 100644 index 0000000000..1c3727712c --- /dev/null +++ b/Modules/MUON/MCH/src/PostProcessingConfigMCH.cxx @@ -0,0 +1,100 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingConfigMCH.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief File for the configuration of MCH post-processing tasks +/// \since 05/10/2021 +/// + +#include "MCH/PostProcessingConfigMCH.h" +#include + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::muonchambers +{ + +PostProcessingConfigMCH::PostProcessingConfigMCH(std::string name, const boost::property_tree::ptree& config) + : PostProcessingConfig(name, config) +{ + // parameters + if (const auto& customConfigs = config.get_child_optional("qc.postprocessing." + name + ".customization"); customConfigs.has_value()) { + for (const auto& customConfig : customConfigs.value()) { + if (const auto& customNames = customConfig.second.get_child_optional("name"); customNames.has_value()) { + parameters.insert(std::make_pair(customConfig.second.get("name"), customConfig.second.get("value"))); + } + } + } + + // Plot configuration + auto plotConfigs = config.get_child_optional("qc.postprocessing." + name + ".plots"); + if (plotConfigs.has_value()) { + for (const auto& plotConfig : plotConfigs.value()) { + plots.push_back({ plotConfig.second.get("name"), + plotConfig.second.get("title", ""), + plotConfig.second.get("varexp"), + plotConfig.second.get("selection", ""), + plotConfig.second.get("option", ""), + plotConfig.second.get("graphErrors", "") }); + } + } + + // Data source configuration + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".dataSources")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + auto reductorName = dataSourceConfig.second.get_optional("reductorName"); + auto moduleName = dataSourceConfig.second.get_optional("moduleName"); + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + sourceName.second.data(), + (reductorName.has_value() ? reductorName.value() : ""), + (moduleName.has_value() ? moduleName.value() : "") }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name"), + dataSourceConfig.second.get("reductorName"), + dataSourceConfig.second.get("moduleName") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + name + ".dataSources'"); + } + } +} + +template <> +const std::string PostProcessingConfigMCH::getParameter(std::string name) const +{ + std::string result; + auto entry = parameters.find(name); + if (entry != parameters.end()) { + result = entry->second; + } + + return result; +} + +template <> +const std::string PostProcessingConfigMCH::getParameter(std::string name, std::string defaultValue) const +{ + std::string result = defaultValue; + auto entry = parameters.find(name); + if (entry != parameters.end()) { + result = entry->second; + } + + return result; +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/PreclustersCheck.cxx b/Modules/MUON/MCH/src/PreclustersCheck.cxx new file mode 100644 index 0000000000..89f04318d9 --- /dev/null +++ b/Modules/MUON/MCH/src/PreclustersCheck.cxx @@ -0,0 +1,488 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PreclustersCheck.cxx +/// \author Andrea Ferrero, Sebastien Perrin +/// + +#include "MCH/PreclustersCheck.h" +#include "MCH/Helpers.h" +#include "MUONCommon/Helpers.h" +#include "QualityControl/ReferenceUtils.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" + +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control_modules::muon; + +namespace o2::quality_control_modules::muonchambers +{ + +void PreclustersCheck::configure() +{ +} + +void PreclustersCheck::startOfActivity(const Activity& activity) +{ + mMeanEffHistNameB = getConfigurationParameter(mCustomParameters, "MeanEffHistNameB", mMeanEffHistNameB, activity); + mMeanEffHistNameNB = getConfigurationParameter(mCustomParameters, "MeanEffHistNameNB", mMeanEffHistNameNB, activity); + mMeanEffPerSolarHistName = getConfigurationParameter(mCustomParameters, "MeanEffPerSolarHistName", mMeanEffPerSolarHistName, activity); + + mMeanEffRefCompHistNameB = getConfigurationParameter(mCustomParameters, "MeanEffRefCompHistNameB", mMeanEffRefCompHistNameB, activity); + mMeanEffRefCompHistNameNB = getConfigurationParameter(mCustomParameters, "MeanEffRefCompHistNameNB", mMeanEffRefCompHistNameNB, activity); + mMeanEffPerSolarRefCompHistName = getConfigurationParameter(mCustomParameters, "MeanEffPerSolarRefCompHistName", mMeanEffPerSolarRefCompHistName, activity); + + getThresholdsPerStation(mCustomParameters, activity, "MinEfficiency", mMinEfficiencyPerStation, mMinEfficiency); + mMinEfficiencyPerSolar = getConfigurationParameter(mCustomParameters, "MinEfficiencyPerSolar", mMinEfficiencyPerSolar, activity); + + mMinEfficiencyRatio = getConfigurationParameter(mCustomParameters, "MinEfficiencyRatio", mMinEfficiencyRatio, activity); + mMinEfficiencyRatioPerSolar = getConfigurationParameter(mCustomParameters, "MinEfficiencyRatioPerSolar", mMinEfficiencyRatioPerSolar, activity); + + mPseudoeffPlotScaleMin = getConfigurationParameter(mCustomParameters, "PseudoeffPlotScaleMin", mPseudoeffPlotScaleMin, activity); + mPseudoeffPlotScaleMax = getConfigurationParameter(mCustomParameters, "PseudoeffPlotScaleMax", mPseudoeffPlotScaleMax, activity); + + mEfficiencyRatioScaleRange = getConfigurationParameter(mCustomParameters, "EfficiencyRatioScaleRange", mEfficiencyRatioScaleRange, activity); + mEfficiencyRatioPerSolarScaleRange = getConfigurationParameter(mCustomParameters, "EfficiencyRatioPerSolarScaleRange", mEfficiencyRatioPerSolarScaleRange, activity); + + mMaxBadST12 = getConfigurationParameter(mCustomParameters, "MaxBadDE_ST12", mMaxBadST12, activity); + mMaxBadST345 = getConfigurationParameter(mCustomParameters, "MaxBadDE_ST345", mMaxBadST345, activity); + + mQualityChecker.mMaxBadST12 = mMaxBadST12; + mQualityChecker.mMaxBadST345 = mMaxBadST345; +} + +template +static T* getHisto(TCanvas* c, std::string hname) +{ + if (!c) { + return nullptr; + } + + T* h = dynamic_cast(c->GetPrimitive(hname.c_str())); + return h; +} + +template +static T* getHisto(std::shared_ptr mo) +{ + TObject* obj = dynamic_cast(mo->getObject()); + if (!obj) { + return nullptr; + } + + T* h{ nullptr }; + if (obj->InheritsFrom("TH1")) { + h = dynamic_cast(obj); + } + + if (obj->InheritsFrom("TCanvas")) { + TCanvas* c = dynamic_cast(obj); + h = getHisto(c, mo->getName() + "Hist"); + } + + return h; +} + +template +std::array checkPlot(TH1* h, Lambda check) +{ + std::array result; + std::fill(result.begin(), result.end(), Quality::Null); + + if (h->GetEntries() == 0) { + return result; + } + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int chamberId = (de - 100) / 100; + int stationId = chamberId / 2; + + int deId = getDEindex(de); + if (deId < 0) { + continue; + } + int bin = deId + 1; + + double val = h->GetBinContent(bin); + if (check(val, stationId)) { + result[deId] = Quality::Good; + } else { + result[deId] = Quality::Bad; + } + } + + return result; +} + +std::array PreclustersCheck::checkMeanEfficiencies(TH1* h) +{ + auto checkFunction = [&](double val, int station) -> bool { + auto minEfficiency = mMinEfficiency; + if (station >= 0 && station < 5) { + if (mMinEfficiencyPerStation[station]) { + minEfficiency = mMinEfficiencyPerStation[station].value(); + } + } + return (val >= minEfficiency); + }; + return checkPlot(h, checkFunction); +} + +std::array PreclustersCheck::checkMeanEfficiencyRatios(TH1* h) +{ + auto checkFunction = [&](double val, int /*station*/) -> bool { + auto minEfficiency = mMinEfficiencyRatio; + return (val >= minEfficiency); + }; + return checkPlot(h, checkFunction); +} + +void PreclustersCheck::checkSolarMeanEfficiencies(TH1* h) +{ + for (int solar = 0; solar < h->GetXaxis()->GetNbins(); solar++) { + int bin = solar + 1; + double value = h->GetBinContent(bin); + if (value >= mMinEfficiencyPerSolar) { + continue; + } + mSolarQuality[solar] = Quality::Bad; + } +} + +void PreclustersCheck::checkSolarMeanEfficiencyRatios(TH1* h) +{ + for (int solar = 0; solar < h->GetXaxis()->GetNbins(); solar++) { + int bin = solar + 1; + double value = h->GetBinContent(bin); + if (value >= mMinEfficiencyRatio) { + continue; + } + mSolarQuality[solar] = Quality::Bad; + } +} + +Quality PreclustersCheck::check(std::map>* moMap) +{ + ILOG(Debug, Devel) << "Entered PreclustersCheck::check" << ENDM; + ILOG(Debug, Devel) << " received a list of size : " << moMap->size() << ENDM; + for (auto& [moName, mo] : *moMap) { + ILOG(Debug, Devel) << "Object: " << moName << " | " << mo->getName() << ENDM; + } + + mQualityChecker.reset(); + std::fill(mSolarQuality.begin(), mSolarQuality.end(), Quality::Good); + + for (auto& [moName, mo] : *moMap) { + + if (matchHistName(moName, mMeanEffHistNameB) || matchHistName(moName, mMeanEffHistNameNB)) { + TH1F* h = getHisto(mo); + if (h) { + auto q = checkMeanEfficiencies(h); + mQualityChecker.addCheckResult(q); + } + } + + if (matchHistName(moName, mMeanEffPerSolarHistName)) { + TH1F* h = getHisto(mo); + if (h) { + checkSolarMeanEfficiencies(h); + } + } + + // Ratios with reference + if (matchHistName(moName, mMeanEffRefCompHistNameB) || matchHistName(moName, mMeanEffRefCompHistNameNB)) { + TCanvas* canvas = dynamic_cast(mo->getObject()); + if (canvas) { + auto ratioPlot = o2::quality_control::checker::getRatioPlotFromCanvas(canvas); + if (ratioPlot) { + auto q = checkMeanEfficiencyRatios(ratioPlot); + mQualityChecker.addCheckResult(q); + } + } + } + + if (matchHistName(moName, mMeanEffPerSolarRefCompHistName)) { + TCanvas* canvas = dynamic_cast(mo->getObject()); + if (canvas) { + auto ratioPlot = o2::quality_control::checker::getRatioPlotFromCanvas(canvas); + if (ratioPlot) { + ILOG(Debug, Devel) << "Checking eff ratio for SOLAR:" << ENDM; + checkSolarMeanEfficiencyRatios(ratioPlot); + } + } + } + } + + return mQualityChecker.getQuality(); +} + +void PreclustersCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if ((mo->getName().find("RefComp/") != std::string::npos)) { + TCanvas* canvas = dynamic_cast(mo->getObject()); + if (!canvas) { + return; + } + + auto ratioPlot = o2::quality_control::checker::getRatioPlotFromCanvas(canvas); + if (!ratioPlot) { + return; + } + + std::string messages; + auto refCompPlots = o2::quality_control::checker::getPlotsFromCanvas(canvas, messages); + + double ratioPlotRange{ -1 }; + double ratioThreshold{ -1 }; + double plotScaleMin{ -1 }; + double plotScaleMax{ -1 }; + bool isSolar{ false }; + + if (matchHistName(mo->getName(), mMeanEffRefCompHistNameB) || + matchHistName(mo->getName(), mMeanEffRefCompHistNameNB)) { + ratioPlotRange = mEfficiencyRatioScaleRange; + ratioThreshold = mMinEfficiencyRatio; + plotScaleMin = mPseudoeffPlotScaleMin; + plotScaleMax = mPseudoeffPlotScaleMax; + isSolar = false; + } else if (matchHistName(mo->getName(), mMeanEffPerSolarRefCompHistName)) { + ratioPlotRange = mEfficiencyRatioPerSolarScaleRange; + ratioThreshold = mMinEfficiencyRatioPerSolar; + plotScaleMin = mPseudoeffPlotScaleMin; + plotScaleMax = mPseudoeffPlotScaleMax; + isSolar = true; + } + + if (ratioPlotRange < 0) { + return; + } + + ratioPlot->SetMinimum(1.0 - ratioPlotRange); + ratioPlot->SetMaximum(1.0 + ratioPlotRange); + ratioPlot->GetXaxis()->SetTickLength(0); + + if (isSolar) { + addChamberDelimitersToSolarHistogram(ratioPlot); + addSolarBinLabels(ratioPlot); + } else { + addChamberDelimiters(ratioPlot); + addDEBinLabels(ratioPlot); + } + drawThreshold(ratioPlot, ratioThreshold); + + if (refCompPlots.first) { + refCompPlots.first->SetMinimum(plotScaleMin); + refCompPlots.first->SetMaximum(plotScaleMax); + if (isSolar) { + addChamberDelimitersToSolarHistogram(refCompPlots.first); + addChamberLabelsForSolar(refCompPlots.first); + addSolarBinLabels(refCompPlots.first); + if (refCompPlots.second) { + addSolarBinLabels(refCompPlots.second); + } + } else { + addChamberDelimiters(refCompPlots.first); + addChamberLabelsForDE(refCompPlots.first); + addDEBinLabels(refCompPlots.first); + if (refCompPlots.second) { + addDEBinLabels(refCompPlots.second); + } + } + } + + if (refCompPlots.second) { + if (checkResult == Quality::Good) { + refCompPlots.second->SetLineColor(kGreen + 2); + } else if (checkResult == Quality::Bad) { + refCompPlots.second->SetLineColor(kRed); + } else if (checkResult == Quality::Medium) { + refCompPlots.second->SetLineColor(kOrange - 3); + } else if (checkResult == Quality::Null) { + refCompPlots.second->SetLineColor(kViolet - 6); + } + } + return; + } + + if ((mo->getName().find("ChargeMPV") != std::string::npos)) { + TH1F* h = getHisto(mo); + if (!h) { + return; + } + + h->SetMinimum(0); + h->SetMaximum(2000); + addChamberDelimiters(h, h->GetMinimum(), h->GetMaximum()); + addDEBinLabels(h); + } + + if ((mo->getName().find("MeanClusterSize") != std::string::npos)) { + TH1F* h = getHisto(mo); + if (!h) { + return; + } + + h->SetMinimum(0); + h->SetMaximum(20); + addChamberDelimiters(h, h->GetMinimum(), h->GetMaximum()); + addDEBinLabels(h); + } + + if ((mo->getName().find("MeanEfficiency") != std::string::npos) || + (mo->getName().find("PreclustersPerDE") != std::string::npos) || + (mo->getName().find("PreclustersSignalPerDE") != std::string::npos)) { + TH1F* h = getHisto(mo); + if (!h) { + return; + } + + if ((mo->getName().find("MeanEfficiencyB") != std::string::npos) || + (mo->getName().find("MeanEfficiencyNB") != std::string::npos)) { + h->SetMinimum(mPseudoeffPlotScaleMin); + h->SetMaximum(1.2); + } else { + h->SetMinimum(0); + h->SetMaximum(1.05 * h->GetMaximum()); + } + + if (mo->getName().find("MeanEfficiencyPerSolar") != std::string::npos) { + addChamberDelimitersToSolarHistogram(h, h->GetMinimum(), h->GetMaximum()); + addChamberLabelsForSolar(h); + } else { + addChamberDelimiters(h, h->GetMinimum(), h->GetMaximum()); + addChamberLabelsForDE(h); + } + + // only the plot used for the check is beautified by changing the color + // and adding the horizontal lines corresponding to the thresholds + if (matchHistName(mo->getName(), mMeanEffHistNameB) || + matchHistName(mo->getName(), mMeanEffHistNameNB) || + matchHistName(mo->getName(), mMeanEffPerSolarHistName)) { + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + + if (matchHistName(mo->getName(), mMeanEffPerSolarHistName)) { + drawThreshold(h, mMinEfficiencyPerSolar); + } else { + drawThresholdsPerStation(h, mMinEfficiencyPerStation, mMinEfficiency); + } + } + } + + if ((mo->getName().find("Pseudoeff_ST12") != std::string::npos) || + (mo->getName().find("Pseudoeff_ST345") != std::string::npos) || + (mo->getName().find("Pseudoeff_B_XY") != std::string::npos) || + (mo->getName().find("Pseudoeff_NB_XY") != std::string::npos)) { + auto* h = dynamic_cast(mo->getObject()); + h->SetMinimum(mPseudoeffPlotScaleMin); + h->SetMaximum(1); + h->GetXaxis()->SetTickLength(0.0); + h->GetXaxis()->SetLabelSize(0.0); + h->GetYaxis()->SetTickLength(0.0); + h->GetYaxis()->SetLabelSize(0.0); + } + + // update quality flags for each DE + if (mo->getName().find("QualityFlagPerDE") != std::string::npos) { + TH2F* h = getHisto(mo); + if (!h) { + return; + } + + std::string badDEs; + for (int deIndex = 0; deIndex < mQualityChecker.mQuality.size(); deIndex++) { + float ybin = 0; + if (mQualityChecker.mQuality[deIndex] == Quality::Good) { + ybin = 3; + } + if (mQualityChecker.mQuality[deIndex] == Quality::Medium) { + ybin = 2; + } + if (mQualityChecker.mQuality[deIndex] == Quality::Bad) { + ybin = 1; + std::string deIdStr = std::to_string(getDEFromIndex(deIndex)); + if (badDEs.empty()) { + badDEs = deIdStr; + } else { + badDEs += std::string(" ") + deIdStr; + } + } + + h->SetBinContent(deIndex + 1, ybin, 1); + } + + if (!badDEs.empty()) { + std::string text = std::string("Bad DEs: ") + badDEs; + TPaveLabel* label = new TPaveLabel(0.2, 0.85, 0.8, 0.92, text.c_str(), "blNDC"); + label->SetBorderSize(1); + h->GetListOfFunctions()->Add(label); + + ILOG(Warning, Support) << "[PreclustersCheck] " << text << ENDM; + } + } + + // update quality flags for each SOLAR + if (mo->getName().find("QualityFlagPerSolar") != std::string::npos) { + TH2F* h = getHisto(mo); + if (!h) { + return; + } + + std::string badSolarBoards; + for (int solar = 0; solar < mSolarQuality.size(); solar++) { + float ybin = 0; + if (mSolarQuality[solar] == Quality::Good) { + ybin = 3; + } + if (mSolarQuality[solar] == Quality::Medium) { + ybin = 2; + } + if (mSolarQuality[solar] == Quality::Bad) { + ybin = 1; + std::string solarId = std::to_string(getSolarIdFromIndex(solar)); + if (badSolarBoards.empty()) { + badSolarBoards = solarId; + } else { + badSolarBoards += std::string(" ") + solarId; + } + } + + h->SetBinContent(solar + 1, ybin, 1); + } + + if (!badSolarBoards.empty()) { + std::string badSolarList = std::string("Bad SOLAR boards: ") + badSolarBoards; + TPaveLabel* label = new TPaveLabel(0.2, 0.85, 0.8, 0.92, badSolarList.c_str(), "blNDC"); + label->SetBorderSize(1); + h->GetListOfFunctions()->Add(label); + + ILOG(Warning, Support) << "[PreclustersCheck] " << badSolarList << ENDM; + } + } +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/PreclustersPostProcessing.cxx b/Modules/MUON/MCH/src/PreclustersPostProcessing.cxx new file mode 100644 index 0000000000..ab10511a7e --- /dev/null +++ b/Modules/MUON/MCH/src/PreclustersPostProcessing.cxx @@ -0,0 +1,346 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PreclustersPostProcessing.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Post-processing of the MCH pre-clusters +/// \since 21/06/2022 +/// + +#include "MCH/PreclustersPostProcessing.h" +#include "MUONCommon/Helpers.h" +#include "Common/ReferenceComparatorPlot.h" +#include "QualityControl/ReferenceUtils.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace o2::quality_control_modules::muonchambers; +using namespace o2::quality_control_modules::muon; + +void PreclustersPostProcessing::configure(const boost::property_tree::ptree& config) +{ + ReferenceComparatorTask::configure(config); + + mConfig = PostProcessingConfigMCH(getID(), config); +} + +//_________________________________________________________________________________________ + +void PreclustersPostProcessing::createEfficiencyHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + //---------------------------------- + // Efficiency plotters + //---------------------------------- + + mEfficiencyPlotter.reset(); + mEfficiencyPlotter = std::make_unique("Efficiency/", mFullHistos); + mEfficiencyPlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mEfficiencyPlotter->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + + if (mEnableLastCycleHistos) { + // Helpers to extract plots from last cycle + auto obj = mCcdbObjects.find(effSourceName()); + if (obj != mCcdbObjects.end()) { + mElecMapOnCycle = std::make_unique>(); + } + + mEfficiencyPlotterOnCycle.reset(); + mEfficiencyPlotterOnCycle = std::make_unique("Efficiency/LastCycle/", mFullHistos); + mEfficiencyPlotterOnCycle->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + for (auto& hinfo : mEfficiencyPlotterOnCycle->histograms()) { + TH1* hist = dynamic_cast(hinfo.object); + if (hist) { + mHistogramsAll.push_back(hist); + } + } + } + + //---------------------------------- + // Efficiency trends + //---------------------------------- + + if (mEnableTrending) { + mEfficiencyTrendsPlotter.reset(); + mEfficiencyTrendsPlotter = std::make_unique("Trends/", mFullHistos); + mEfficiencyTrendsPlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + } +} + +//_________________________________________________________________________________________ + +void PreclustersPostProcessing::createClusterChargeHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + //---------------------------------- + // Cluster charge plotters + //---------------------------------- + + mClusterChargePlotter.reset(); + mClusterChargePlotter = std::make_unique("ClusterCharge/", mFullHistos); + mClusterChargePlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + if (mEnableLastCycleHistos) { + // Helpers to extract plots from last cycle + auto obj = mCcdbObjects.find(clusterChargeSourceName()); + if (obj != mCcdbObjects.end()) { + mClusterChargeOnCycle.reset(); + mClusterChargeOnCycle = std::make_unique>(); + } + + mClusterChargePlotterOnCycle.reset(); + mClusterChargePlotterOnCycle = std::make_unique("ClusterCharge/LastCycle/", mFullHistos); + mClusterChargePlotterOnCycle->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + } + + //---------------------------------- + // Cluster charge trends + //---------------------------------- + + if (mEnableTrending) { + mClusterChargeTrendsPlotter.reset(); + mClusterChargeTrendsPlotter = std::make_unique("Trends/", mFullHistos); + mClusterChargeTrendsPlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + } +} + +//_________________________________________________________________________________________ + +void PreclustersPostProcessing::createClusterSizeHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + //---------------------------------- + // Cluster size plotters + //---------------------------------- + + mClusterSizePlotter.reset(); + mClusterSizePlotter = std::make_unique("ClusterSize/", mFullHistos); + mClusterSizePlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + + if (mEnableLastCycleHistos) { + // Helpers to extract plots from last cycle + auto obj = mCcdbObjects.find(clusterSizeSourceName()); + if (obj != mCcdbObjects.end()) { + mClusterSizeOnCycle.reset(); + mClusterSizeOnCycle = std::make_unique>(); + } + + mClusterSizePlotterOnCycle.reset(); + mClusterSizePlotterOnCycle = std::make_unique("ClusterSize/LastCycle/", mFullHistos); + mClusterSizePlotterOnCycle->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + } + + //---------------------------------- + // Cluster size trends + //---------------------------------- + + if (mEnableTrending) { + mClusterSizeTrendsPlotter.reset(); + mClusterSizeTrendsPlotter = std::make_unique("Trends/", mFullHistos); + mClusterSizeTrendsPlotter->publish(getObjectsManager(), core::PublicationPolicy::ThroughStop); + } +} + +//_________________________________________________________________________________________ + +void PreclustersPostProcessing::initialize(Trigger t, framework::ServiceRegistryRef services) +{ + ReferenceComparatorTask::initialize(t, services); + + auto& qcdb = services.get(); + const auto& activity = t.activity; + + mFullHistos = getConfigurationParameter(mCustomParameters, "FullHistos", mFullHistos, activity); + mEnableLastCycleHistos = getConfigurationParameter(mCustomParameters, "EnableLastCycleHistos", mEnableLastCycleHistos, activity); + mEnableTrending = getConfigurationParameter(mCustomParameters, "EnableTrending", mEnableTrending, activity); + + mCcdbObjects.clear(); + mCcdbObjects.emplace(effSourceName(), CcdbObjectHelper()); + mCcdbObjects.emplace(clusterChargeSourceName(), CcdbObjectHelper()); + mCcdbObjects.emplace(clusterSizeSourceName(), CcdbObjectHelper()); + + // set objects path from configuration + for (const auto& source : mConfig.dataSources) { + std::string sourceType, sourceName; + splitDataSourceName(source.name, sourceType, sourceName); + if (sourceType.empty()) { + continue; + } + + auto obj = mCcdbObjects.find(sourceType); + if (obj != mCcdbObjects.end()) { + obj->second.mPath = source.path; + obj->second.mName = sourceName; + } + } + + // instantiate and publish the histograms + createEfficiencyHistos(t, &qcdb); + createClusterChargeHistos(t, &qcdb); + createClusterSizeHistos(t, &qcdb); + + //-------------------------------------------------- + // Quality histogram + //-------------------------------------------------- + + mHistogramQualityPerDE.reset(); + mHistogramQualityPerDE = std::make_unique("QualityFlagPerDE", "Quality Flag vs DE", getNumDE(), 0, getNumDE(), 3, 0, 3); + addDEBinLabels(mHistogramQualityPerDE.get()); + addChamberDelimiters(mHistogramQualityPerDE.get()); + addChamberLabelsForDE(mHistogramQualityPerDE.get()); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(1, "Bad"); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(2, "Medium"); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(3, "Good"); + mHistogramQualityPerDE->SetOption("colz"); + mHistogramQualityPerDE->SetStats(0); + getObjectsManager()->startPublishing(mHistogramQualityPerDE.get(), core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistogramQualityPerDE.get(), "colz"); + getObjectsManager()->setDisplayHint(mHistogramQualityPerDE.get(), "gridy"); + + mHistogramQualityPerSolar.reset(); + mHistogramQualityPerSolar = std::make_unique("QualityFlagPerSolar", "Quality Flag vs Solar", getNumSolar(), 0, getNumSolar(), 3, 0, 3); + addSolarBinLabels(mHistogramQualityPerSolar.get()); + addChamberDelimitersToSolarHistogram(mHistogramQualityPerSolar.get()); + addChamberLabelsForSolar(mHistogramQualityPerSolar.get()); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(1, "Bad"); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(2, "Medium"); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(3, "Good"); + mHistogramQualityPerSolar->SetOption("col"); + mHistogramQualityPerSolar->SetStats(0); + getObjectsManager()->startPublishing(mHistogramQualityPerSolar.get(), core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistogramQualityPerSolar.get(), "col"); + getObjectsManager()->setDisplayHint(mHistogramQualityPerSolar.get(), "gridy"); +} + +//_________________________________________________________________________________________ + +void PreclustersPostProcessing::updateEfficiencyHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + auto obj = mCcdbObjects.find(effSourceName()); + if (obj != mCcdbObjects.end() && obj->second.update(qcdb, t.timestamp, t.activity)) { + TH2FRatio* hr = obj->second.get(); + if (hr) { + mEfficiencyPlotter->update(hr); + if (mEnableLastCycleHistos) { + // extract the average occupancies on the last cycle + mElecMapOnCycle->update(hr); + mEfficiencyPlotterOnCycle->update(mElecMapOnCycle.get()); + } + + if (mEnableTrending) { + auto time = obj->second.getTimeStamp() / 1000; // ROOT expects seconds since epoch + if (mEnableLastCycleHistos) { + mEfficiencyTrendsPlotter->update(time, mElecMapOnCycle.get()); + } else { + mEfficiencyTrendsPlotter->update(time, hr); + } + } + } + } +} + +//_________________________________________________________________________________________ + +void PreclustersPostProcessing::updateClusterChargeHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + auto obj = mCcdbObjects.find(clusterChargeSourceName()); + if (obj != mCcdbObjects.end() && obj->second.update(qcdb, t.timestamp, t.activity)) { + TH2F* h = obj->second.get(); + if (h) { + mClusterChargePlotter->update(h); + if (mEnableLastCycleHistos) { + // extract the average occupancies on the last cycle + mClusterChargeOnCycle->update(h); + mClusterChargePlotterOnCycle->update(mClusterChargeOnCycle.get()); + } + + if (mEnableTrending) { + auto time = obj->second.getTimeStamp() / 1000; // ROOT expects seconds since epoch + if (mEnableLastCycleHistos) { + mClusterChargeTrendsPlotter->update(time, mClusterChargeOnCycle.get()); + } else { + mClusterChargeTrendsPlotter->update(time, h); + } + } + } + } +} + +//_________________________________________________________________________________________ + +void PreclustersPostProcessing::updateClusterSizeHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + auto obj = mCcdbObjects.find(clusterSizeSourceName()); + if (obj != mCcdbObjects.end() && obj->second.update(qcdb, t.timestamp, t.activity)) { + TH2F* h = obj->second.get(); + if (h) { + mClusterSizePlotter->update(h); + if (mEnableLastCycleHistos) { + // extract the average occupancies on the last cycle + mClusterSizeOnCycle->update(h); + mClusterSizePlotterOnCycle->update(mClusterSizeOnCycle.get()); + } + + if (mEnableTrending) { + auto time = obj->second.getTimeStamp() / 1000; // ROOT expects seconds since epoch + if (mEnableLastCycleHistos) { + mClusterSizeTrendsPlotter->update(time, mClusterSizeOnCycle.get()); + } else { + mClusterSizeTrendsPlotter->update(time, h); + } + } + } + } +} + +//_________________________________________________________________________________________ + +TH1* PreclustersPostProcessing::getHistogram(std::string_view plotName) +{ + TH1* result{ nullptr }; + for (auto hist : mHistogramsAll) { + if (plotName == hist->GetName()) { + result = hist; + break; + } + } + return result; +} + +void PreclustersPostProcessing::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + updateEfficiencyHistos(t, &qcdb); + updateClusterChargeHistos(t, &qcdb); + updateClusterSizeHistos(t, &qcdb); + + auto& comparatorPlots = getComparatorPlots(); + for (auto& [plotName, plot] : comparatorPlots) { + TH1* hist = getHistogram(plotName); + if (!hist) { + continue; + } + + plot->update(hist); + } +} + +//_________________________________________________________________________________________ + +void PreclustersPostProcessing::finalize(Trigger t, framework::ServiceRegistryRef services) +{ + ReferenceComparatorTask::finalize(t, services); +} diff --git a/Modules/MUON/MCH/src/PreclustersTask.cxx b/Modules/MUON/MCH/src/PreclustersTask.cxx new file mode 100644 index 0000000000..181374144e --- /dev/null +++ b/Modules/MUON/MCH/src/PreclustersTask.cxx @@ -0,0 +1,471 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PreclustersTask.cxx +/// \author Andrea Ferrero +/// \author Sebastien Perrin +/// + +#include "MCH/PreclustersTask.h" +#include "MUONCommon/Helpers.h" +#include "MCH/Helpers.h" +#ifdef MCH_HAS_MAPPING_FACTORY +#include "MCHMappingFactory/CreateSegmentation.h" +#endif +#include "MCHGlobalMapping/DsIndex.h" +#include "MCHMappingInterface/Segmentation.h" +#include "MCHMappingInterface/CathodeSegmentation.h" +#include +#include "QualityControl/QcInfoLogger.h" + +using namespace std; +using namespace o2::mch; +using namespace o2::mch::raw; +using namespace o2::quality_control::core; +using namespace o2::quality_control_modules::muon; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +template +void PreclustersTask::publishObject(T* histo, std::string drawOption, bool statBox) +{ + histo->SetOption(drawOption.c_str()); + if (!statBox) { + histo->SetStats(0); + } + mAllHistograms.push_back(histo); + getObjectsManager()->startPublishing(histo); + getObjectsManager()->setDefaultDrawOptions(histo, drawOption); +} + +void PreclustersTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Info, Devel) << "initialize PreclustersTask" << AliceO2::InfoLogger::InfoLogger::endm; + + // flags to enable the publication of either 1D and 2D maps of channel pseudo-efficiencies + mEnable1DPseudoeffMaps = getConfigurationParameter(mCustomParameters, "Enable1DPseudoeffMaps", mEnable1DPseudoeffMaps); + mEnable2DPseudoeffMaps = getConfigurationParameter(mCustomParameters, "Enable2DPseudoeffMaps", mEnable2DPseudoeffMaps); + + mIsSignalDigit = o2::mch::createDigitFilter(20, true, true); + + mHistogramPreclustersPerDE = std::make_unique("PreclustersPerDE", "Number of pre-clusters for each DE", getNumDE(), 0, getNumDE()); + publishObject(mHistogramPreclustersPerDE.get(), "hist", false); + mHistogramPreclustersSignalPerDE = std::make_unique("PreclustersSignalPerDE", "Number of pre-clusters (with signal) for each DE", getNumDE(), 0, getNumDE()); + publishObject(mHistogramPreclustersSignalPerDE.get(), "hist", false); + + const uint32_t nElecXbins = NumberOfDualSampas; + + // Histograms in electronics coordinates + if (mEnable1DPseudoeffMaps) { + mHistogramPseudoeffPerDualSampa = std::make_unique("PseudoeffPerDualSampa", "Average pseudo-efficiency per dual sampa;DS index;efficiency", o2::mch::NumberOfDualSampas, 0, o2::mch::NumberOfDualSampas, false); + mHistogramPseudoeffPerDualSampa->Sumw2(kFALSE); + publishObject(mHistogramPseudoeffPerDualSampa.get(), "hist", false); + } + + if (mEnable2DPseudoeffMaps) { + mHistogramPseudoeffElec = std::make_unique("Pseudoeff_Elec", "Pseudoeff", nElecXbins, 0, nElecXbins, 64, 0, 64); + mHistogramPseudoeffElec->Sumw2(kFALSE); + publishObject(mHistogramPseudoeffElec.get(), "colz", false); + } + + //---------------------------------- + // Charge distribution histograms + //---------------------------------- + + // 256 bins, 50 ADC / bin + mHistogramClusterCharge = std::make_unique("ClusterChargeHist", "Cluster Charge", getNumDE(), 0, getNumDE(), 256, 0, 256 * 50); + publishObject(mHistogramClusterCharge.get(), "colz", false); + + mHistogramClusterChargePerStation[0] = std::make_unique("ClusterCharge/ClusterChargeDistributionB", + "Pre-cluster charge distribution (B)", + 256, 0, 256 * 50, 5, 1, 6); + publishObject(mHistogramClusterChargePerStation[0].get(), "colz", false); + + mHistogramClusterChargePerStation[1] = std::make_unique("ClusterCharge/ClusterChargeDistributionNB", + "Pre-cluster charge distribution (NB)", + 256, 0, 256 * 50, 5, 1, 6); + publishObject(mHistogramClusterChargePerStation[1].get(), "colz", false); + + mHistogramClusterChargePerStation[2] = std::make_unique("ClusterCharge/ClusterChargeDistribution", + "Pre-cluster charge distribution", + 256, 0, 256 * 50, 5, 1, 6); + publishObject(mHistogramClusterChargePerStation[2].get(), "colz", false); + + for (int c = 0; c < 3; c++) { + for (int i = 1; i <= mHistogramClusterChargePerStation[c]->GetYaxis()->GetNbins(); i++) { + mHistogramClusterChargePerStation[c]->GetYaxis()->SetBinLabel(i, TString::Format("ST%d", i)); + } + } + + mHistogramClusterSize = std::make_unique("ClusterSizeHist", "Cluster Size", getNumDE() * 3, 0, getNumDE() * 3, 100, 0, 100); + publishObject(mHistogramClusterSize.get(), "colz", false); + + mHistogramClusterSizePerStation[0] = std::make_unique("ClusterSize/ClusterSizeDistributionB", + "Pre-cluster size distribution (B)", + 50, 0, 50, 5, 1, 6); + publishObject(mHistogramClusterSizePerStation[0].get(), "colz", false); + + mHistogramClusterSizePerStation[1] = std::make_unique("ClusterSize/ClusterSizeDistributionNB", + "Pre-cluster size distribution (NB)", + 50, 0, 50, 5, 1, 6); + publishObject(mHistogramClusterSizePerStation[1].get(), "colz", false); + + mHistogramClusterSizePerStation[2] = std::make_unique("ClusterSize/ClusterSizeDistribution", + "Pre-cluster size distribution", + 50, 0, 50, 5, 1, 6); + publishObject(mHistogramClusterSizePerStation[2].get(), "colz", false); + + for (int c = 0; c < 3; c++) { + for (int i = 1; i <= mHistogramClusterSizePerStation[c]->GetYaxis()->GetNbins(); i++) { + mHistogramClusterSizePerStation[c]->GetYaxis()->SetBinLabel(i, TString::Format("ST%d", i)); + } + } +} + +//_________________________________________________________________________________________________ + +void PreclustersTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Info, Devel) << "startOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; +} + +//_________________________________________________________________________________________________ + +void PreclustersTask::startOfCycle() +{ + ILOG(Info, Devel) << "startOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; +} + +//_________________________________________________________________________________________________ + +static void updateTFcount(TH1D* hDen) +{ + for (int bin = 1; bin <= hDen->GetXaxis()->GetNbins(); bin++) { + auto x = hDen->GetXaxis()->GetBinCenter(bin); + hDen->Fill(x); + } +} + +//_________________________________________________________________________________________________ + +void PreclustersTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + // get the input preclusters and associated digits + auto preClusters = ctx.inputs().get>("preclusters"); + auto digits = ctx.inputs().get>("preclusterdigits"); + + ILOG(Info, Devel) << fmt::format("Received {} pre-clusters and {} digits", preClusters.size(), digits.size()) << AliceO2::InfoLogger::InfoLogger::endm; + + updateTFcount(mHistogramPreclustersPerDE->getDen()); + updateTFcount(mHistogramPreclustersSignalPerDE->getDen()); + + for (auto& p : preClusters) { + plotPrecluster(p, digits); + } +} + +//_________________________________________________________________________________________________ + +// compute the center-of-gravity of a given pre-cluster +static void CoG(gsl::span precluster, double& Xcog, double& Ycog, bool isWide[2]) +{ + + double xmin = 1E9; + double ymin = 1E9; + double xmax = -1E9; + double ymax = -1E9; + double charge[] = { 0.0, 0.0 }; + int multiplicity[] = { 0, 0 }; + isWide[0] = isWide[1] = false; + // isWide tells if a given precluster is extended enough on a given cathode. On the bending side for exemple, a wide precluster would have at least 2 pads fired in the x direction (so when clustering it, we obtain a meaningful value for x). If a precluster is not wide and on a single cathode, when clustering, one of the coordinates will not be computed properly and set to the center of the pad by default. + + double x[] = { 0.0, 0.0 }; + double y[] = { 0.0, 0.0 }; + + double xsize[] = { 0.0, 0.0 }; + double ysize[] = { 0.0, 0.0 }; + + std::set padPos[2]; + + int deId = precluster[0].getDetID(); + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + + for (ssize_t i = 0; (unsigned)i < precluster.size(); ++i) { + const o2::mch::Digit& digit = precluster[i]; + int padid = digit.getPadID(); + + // position and size of current pad + double padPosition[2] = { segment.padPositionX(padid), segment.padPositionY(padid) }; + double padSize[2] = { segment.padSizeX(padid), segment.padSizeY(padid) }; + + // update of xmin/max et ymin/max + xmin = std::min(padPosition[0] - 0.5 * padSize[0], xmin); + xmax = std::max(padPosition[0] + 0.5 * padSize[0], xmax); + ymin = std::min(padPosition[1] - 0.5 * padSize[1], ymin); + ymax = std::max(padPosition[1] + 0.5 * padSize[1], ymax); + + // cathode index + int cathode = segment.isBendingPad(padid) ? 0 : 1; + + // update of the cluster position, size, charge and multiplicity + x[cathode] += padPosition[0] * digit.getADC(); + y[cathode] += padPosition[1] * digit.getADC(); + xsize[cathode] += padSize[0]; + ysize[cathode] += padSize[1]; + charge[cathode] += digit.getADC(); + + if (cathode == 0) { + padPos[0].insert(padPosition[1]); + } else if (cathode == 1) { + padPos[1].insert(padPosition[0]); + } + + multiplicity[cathode] += 1; + } + + // Computation of the CoG coordinates for the two cathodes + for (int cathode = 0; cathode < 2; ++cathode) { + if (charge[cathode] != 0) { + x[cathode] /= charge[cathode]; + y[cathode] /= charge[cathode]; + } + if (multiplicity[cathode] != 0) { + double sqrtCharge = sqrt(charge[cathode]); + xsize[cathode] /= (multiplicity[cathode] * sqrtCharge); + ysize[cathode] /= (multiplicity[cathode] * sqrtCharge); + } else { + xsize[cathode] = 1E9; + ysize[cathode] = 1E9; + } + } + + isWide[0] = (padPos[0].size() > 1); + isWide[1] = (padPos[1].size() > 1); + + // each CoG coordinate is taken from the cathode with the best precision + Xcog = (xsize[0] < xsize[1]) ? x[0] : x[1]; + Ycog = (ysize[0] < ysize[1]) ? y[0] : y[1]; +} + +//_________________________________________________________________________________________________ + +static void getFecChannel(int deId, int padId, int& fecId, int& channel) +{ + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + int dsId = segment.padDualSampaId(padId); + fecId = getDsIndex(DsDetId{ deId, dsId }); + channel = segment.padDualSampaChannel(padId); +} + +//_________________________________________________________________________________________________ + +void PreclustersTask::plotPrecluster(const o2::mch::PreCluster& preCluster, gsl::span digits) +{ + // filter out single-pad clusters + if (preCluster.nDigits < 2) { + return; + } + + // get the digits of this precluster + auto preClusterDigits = digits.subspan(preCluster.firstDigit, preCluster.nDigits); + + // whether a cathode has digits or not + bool cathode[2] = { false, false }; + // total charge on each cathode + float chargeSum[2] = { 0, 0 }; + // largest signal in each cathode + float chargeMax[2] = { 0, 0 }; + // whether the pre-cluster contains at least one signal-like digit in each cathode + float hasSignal[2] = { false, false }; + // number of digits in each cathode + int multiplicity[2] = { 0, 0 }; + + int deId = preClusterDigits[0].getDetID(); + auto chamberId = deId / 100; + auto stationId = ((chamberId - 1) / 2) + 1; + + if (stationId < 1 || stationId > 5) { + ILOG(Info, Devel) << "[PreclustersTask::plotPrecluster()] wrong station ID for DE" << deId << AliceO2::InfoLogger::InfoLogger::endm; + return; + } + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + + // loop over digits and collect information on charge and multiplicity + for (const o2::mch::Digit& digit : preClusterDigits) { + int padid = digit.getPadID(); + + // cathode index + int cid = segment.isBendingPad(padid) ? 0 : 1; + cathode[cid] = true; + chargeSum[cid] += digit.getADC(); + multiplicity[cid] += 1; + + if (digit.getADC() > chargeMax[cid]) { + chargeMax[cid] = digit.getADC(); + } + + if (mIsSignalDigit(digit)) { + hasSignal[cid] = true; + } + } + + mHistogramPreclustersPerDE->getNum()->Fill(getDEindex(deId)); + if (hasSignal[0] || hasSignal[1]) { + mHistogramPreclustersSignalPerDE->getNum()->Fill(getDEindex(deId)); + } + + // compute center-of-gravity of the charge distribution + double Xcog, Ycog; + bool isWide[2]; + CoG(preClusterDigits, Xcog, Ycog, isWide); + + int padIdB = -1; + int padIdNB = -1; + int fecIdB = -1; + int channelB = -1; + int fecIdNB = -1; + int channelNB = -1; + if (segment.findPadPairByPosition(Xcog, Ycog, padIdB, padIdNB)) { + getFecChannel(deId, padIdB, fecIdB, channelB); + getFecChannel(deId, padIdNB, fecIdNB, channelNB); + } else { + ILOG(Info, Devel) << "[PreclustersTask::plotPrecluster()] could not find pad in DE" << deId + << " for pre-cluster at (" << Xcog << "," << Ycog << ")" << AliceO2::InfoLogger::InfoLogger::endm; + return; + } + + // criteria to define a "good" charge cluster in one cathode: + bool isGoodDen[2] = { hasSignal[1] && isWide[1], hasSignal[0] && isWide[0] }; + bool isGoodNum[2] = { cathode[0] && isWide[0], cathode[1] }; + + // Filling histograms to be used for Pseudo-efficiency computation + if (isGoodDen[0]) { + // good cluster on non-bending side, check if there is data from the bending side as well + if (fecIdB >= 0 && channelB >= 0) { + if (mEnable1DPseudoeffMaps) { + mHistogramPseudoeffPerDualSampa->getDen()->Fill(fecIdB); + } + if (mEnable2DPseudoeffMaps) { + mHistogramPseudoeffElec->getDen()->Fill(fecIdB, channelB); + } + } + if (isGoodNum[0]) { // Check if associated to something on Bending + if (fecIdB >= 0 && channelB >= 0) { + if (mEnable1DPseudoeffMaps) { + mHistogramPseudoeffPerDualSampa->getNum()->Fill(fecIdB); + } + if (mEnable2DPseudoeffMaps) { + mHistogramPseudoeffElec->getNum()->Fill(fecIdB, channelB); + } + } + } + } + + if (isGoodDen[1]) { + // good cluster on bending side, check if there is data from the non-bending side as well + if (fecIdNB >= 0 && channelNB >= 0) { + if (mEnable1DPseudoeffMaps) { + mHistogramPseudoeffPerDualSampa->getDen()->Fill(fecIdNB); + } + if (mEnable2DPseudoeffMaps) { + mHistogramPseudoeffElec->getDen()->Fill(fecIdNB, channelNB); + } + } + if (isGoodNum[1]) { // Check if associated to something on Non-Bending + if (fecIdNB >= 0 && channelNB >= 0) { + if (mEnable1DPseudoeffMaps) { + mHistogramPseudoeffPerDualSampa->getNum()->Fill(fecIdNB); + } + if (mEnable2DPseudoeffMaps) { + mHistogramPseudoeffElec->getNum()->Fill(fecIdNB, channelNB); + } + } + } + } + + // This code is slow. Will be re-introduced after some optimizations + // const o2::mch::mapping::CathodeSegmentation& csegment = segment.bending(); + // o2::mch::contour::Contour envelop = o2::mch::mapping::getEnvelop(csegment); + // std::vector> vertices = envelop.getVertices(); + // o2::mch::contour::BBox bbox = o2::mch::mapping::getBBox(csegment); + + // skip a fiducial border around the active area, so that only fully-contained clusters are considered + // if(Xcog < (bbox.xmin() + 5)) return true; + // if(Xcog > (bbox.xmax() - 5)) return true; + // if(Ycog < (bbox.ymin() + 5)) return true; + // if(Ycog > (bbox.ymax() - 5)) return true; + + if (hasSignal[0] || hasSignal[1]) { + int deIndex = getDEindex(deId); + // cluster size, separately on each cathode and combined + mHistogramClusterSize->Fill(deIndex * 3, multiplicity[0]); + mHistogramClusterSize->Fill(deIndex * 3 + 1, multiplicity[1]); + mHistogramClusterSize->Fill(deIndex * 3 + 2, multiplicity[0] + multiplicity[1]); + + mHistogramClusterSizePerStation[0]->Fill(multiplicity[0], stationId); + mHistogramClusterSizePerStation[1]->Fill(multiplicity[1], stationId); + mHistogramClusterSizePerStation[2]->Fill(multiplicity[0] + multiplicity[1], stationId); + + // total cluster charge + float chargeTot = chargeSum[0] + chargeSum[1]; + mHistogramClusterCharge->Fill(deIndex, chargeTot); + + mHistogramClusterChargePerStation[0]->Fill(chargeSum[0], stationId); + mHistogramClusterChargePerStation[1]->Fill(chargeSum[1], stationId); + mHistogramClusterChargePerStation[2]->Fill(chargeTot, stationId); + } +} + +//_________________________________________________________________________________________________ + +void PreclustersTask::endOfCycle() +{ + ILOG(Info, Devel) << "endOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; + + // update mergeable ratios + if (mEnable1DPseudoeffMaps) { + mHistogramPseudoeffPerDualSampa->update(); + } + if (mEnable2DPseudoeffMaps) { + mHistogramPseudoeffElec->update(); + } + mHistogramPreclustersPerDE->update(); + mHistogramPreclustersSignalPerDE->update(); +} + +//_________________________________________________________________________________________________ + +void PreclustersTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Info, Devel) << "endOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; +} + +//_________________________________________________________________________________________________ + +void PreclustersTask::reset() +{ + // clean all the monitor objects here + ILOG(Info, Devel) << "Resetting the histograms" << AliceO2::InfoLogger::InfoLogger::endm; + + for (auto h : mAllHistograms) { + h->Reset("ICES"); + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/QualityAggregatorTask.cxx b/Modules/MUON/MCH/src/QualityAggregatorTask.cxx new file mode 100644 index 0000000000..7dff36bc9f --- /dev/null +++ b/Modules/MUON/MCH/src/QualityAggregatorTask.cxx @@ -0,0 +1,395 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityAggregatorTask.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Post-processing of the MCH summary qualities +/// \since 21/06/2022 +/// + +#include "MCH/QualityAggregatorTask.h" +#include "MCH/Helpers.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/ActivityHelpers.h" +#include "QualityControl/ReferenceUtils.h" +#include "Common/Utils.h" +#include "CCDB/CcdbObjectInfo.h" +#include "CommonUtils/MemFileHelper.h" +#include + +#include + +#include +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; +using namespace o2::quality_control_modules::common; +using namespace o2::quality_control_modules::muonchambers; + +template +o2::ccdb::CcdbObjectInfo createCcdbInfo(const T& object, uint64_t timeStamp, std::string_view reason) +{ + auto clName = o2::utils::MemFileHelper::getClassName(object); + auto flName = o2::ccdb::CcdbApi::generateFileName(clName); + std::map md; + md["upload-reason"] = reason; + constexpr auto fiveDays = 5 * o2::ccdb::CcdbObjectInfo::DAY; + return o2::ccdb::CcdbObjectInfo("MCH/Calib/BadDE", clName, flName, md, timeStamp, timeStamp + fiveDays); +} + +/* +std::string toCSV(const std::set& deIds) +{ + std::stringstream csv; + csv << fmt::format("deid\n"); + + for (auto deId : deIds) { + csv << fmt::format("{}\n", deId); + } + + return csv.str(); +} +*/ +//_________________________________________________________________________________________ +// Helper function for retrieving a MonitorObject from the QCDB, in the form of a std::pair, bool> +// A non-null MO is returned in the first element of the pair if the MO is found in the QCDB +// The second element of the pair is set to true if the MO has a time stamp more recent than a user-supplied threshold + +static std::pair, bool> getMO(DatabaseInterface& qcdb, const std::string& fullPath, Trigger trigger, long notOlderThan) +{ + // find the time-stamp of the most recent object matching the current activity + // if ignoreActivity is true the activity matching criteria are not applied + auto objectTimestamp = trigger.timestamp; + const auto filterMetadata = activity_helpers::asDatabaseMetadata(trigger.activity, false); + const auto objectValidity = qcdb.getLatestObjectValidity(trigger.activity.mProvenance + "/" + fullPath, filterMetadata); + if (objectValidity.isValid()) { + objectTimestamp = objectValidity.getMax() - 1; + } else { + ILOG(Warning, Devel) << "Could not find the object '" << fullPath << "' for activity " << trigger.activity << ENDM; + return { nullptr, false }; + } + + auto [success, path, name] = o2::quality_control::core::RepoPathUtils::splitObjectPath(fullPath); + if (!success) { + return { nullptr, false }; + } + // retrieve QO from CCDB + auto qo = qcdb.retrieveMO(path, name, objectTimestamp, trigger.activity); + if (!qo) { + return { nullptr, false }; + } + + long elapsed = static_cast(trigger.timestamp) - objectTimestamp; + // check if the object is not older than a given number of milliseconds + if (elapsed > notOlderThan) { + ILOG(Warning, Devel) << "Object '" << fullPath << "' for activity " << trigger.activity << " is too old: " << elapsed << " > " << notOlderThan << ENDM; + return { qo, false }; + } + + return { qo, true }; +} + +void QualityAggregatorTask::configure(const boost::property_tree::ptree& config) +{ + // input plots + if (const auto& inputs = config.get_child_optional("qc.postprocessing." + getID() + ".inputsDE"); inputs.has_value()) { + for (const auto& input : inputs.value()) { + mDEPlotPaths.push_back(input.second.get_value()); + } + } + if (const auto& inputs = config.get_child_optional("qc.postprocessing." + getID() + ".inputsSOLAR"); inputs.has_value()) { + for (const auto& input : inputs.value()) { + mSOLARPlotPaths.push_back(input.second.get_value()); + } + } +} + +//_________________________________________________________________________________________ + +void QualityAggregatorTask::initialize(Trigger t, framework::ServiceRegistryRef services) +{ + mCCDBpath = getFromExtendedConfig(t.activity, mCustomParameters, "CCDBpath", mCCDBpath); + mAPI.init(mCCDBpath); + + mObjectPathBadDE = getFromExtendedConfig(t.activity, mCustomParameters, "objectPathBadDE", mObjectPathBadDE); + mObjectPathBadSOLAR = getFromExtendedConfig(t.activity, mCustomParameters, "objectPathBadSOLAR", mObjectPathBadSOLAR); + + //-------------------------------------------------- + // Quality histogram + //-------------------------------------------------- + + mHistogramQualityPerDE.reset(); + mHistogramQualityPerDE = std::make_unique("QualityFlagPerDE", "Quality Flag vs DE", getNumDE(), 0, getNumDE(), 3, 0, 3); + addDEBinLabels(mHistogramQualityPerDE.get()); + addChamberDelimiters(mHistogramQualityPerDE.get()); + addChamberLabelsForDE(mHistogramQualityPerDE.get()); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(1, "Bad"); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(2, "Medium"); + mHistogramQualityPerDE->GetYaxis()->SetBinLabel(3, "Good"); + mHistogramQualityPerDE->SetOption("colz"); + mHistogramQualityPerDE->SetStats(0); + getObjectsManager()->startPublishing(mHistogramQualityPerDE.get(), core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistogramQualityPerDE.get(), "colz"); + getObjectsManager()->setDisplayHint(mHistogramQualityPerDE.get(), "gridy"); + + mHistogramQualityPerSolar.reset(); + mHistogramQualityPerSolar = std::make_unique("QualityFlagPerSolar", "Quality Flag vs Solar", getNumSolar(), 0, getNumSolar(), 3, 0, 3); + addSolarBinLabels(mHistogramQualityPerSolar.get()); + addChamberDelimitersToSolarHistogram(mHistogramQualityPerSolar.get()); + addChamberLabelsForSolar(mHistogramQualityPerSolar.get()); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(1, "Bad"); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(2, "Medium"); + mHistogramQualityPerSolar->GetYaxis()->SetBinLabel(3, "Good"); + mHistogramQualityPerSolar->SetOption("col"); + mHistogramQualityPerSolar->SetStats(0); + getObjectsManager()->startPublishing(mHistogramQualityPerSolar.get(), core::PublicationPolicy::ThroughStop); + getObjectsManager()->setDefaultDrawOptions(mHistogramQualityPerSolar.get(), "col"); + getObjectsManager()->setDisplayHint(mHistogramQualityPerSolar.get(), "gridy"); +} + +//_________________________________________________________________________________________ + +void QualityAggregatorTask::update(Trigger trigger, framework::ServiceRegistryRef services) +{ + ILOG(Info, Devel) << "QualityAggregatorTask::update() called" << ENDM; + auto& qcdb = services.get(); + + // =================== + // Detection elements + // =================== + + std::set badDEs; + std::array deQuality; + std::fill(deQuality.begin(), deQuality.end(), 3); + // fill vector of bad DEs + for (auto plotPath : mDEPlotPaths) { + auto [mo, success] = getMO(qcdb, plotPath, trigger, 600000); + if (!success || !mo) { + ILOG(Warning, Devel) << "Could not retrieve object " << plotPath << ENDM; + continue; + } + + TH2F* hist = dynamic_cast(mo->getObject()); + if (!hist) { + ILOG(Warning, Devel) << "Could not cast the object '" << plotPath << "' to TH2F" << ENDM; + continue; + } + + if (hist->GetXaxis()->GetNbins() != getNumDE()) { + ILOG(Warning, Devel) << "Wrong number of bins for object '" << plotPath << "': " + << hist->GetXaxis()->GetNbins() << " while " << getNumDE() << " were expected" << ENDM; + continue; + } + + for (int index = 0; index < hist->GetXaxis()->GetNbins(); index++) { + int q = 0; + for (int qindex = 1; qindex <= hist->GetYaxis()->GetNbins(); qindex++) { + if (hist->GetBinContent(index + 1, qindex) != 0) { + q = qindex; + break; + } + } + if (q < deQuality[index]) { + deQuality[index] = q; + } + + bool isBad = (q == 1); + if (!isBad) { + continue; + } + + int deId = getDEFromIndex(index); + badDEs.insert(deId); + + ILOG(Debug, Devel) << "Bad detection element DE" << deId << " found in \'" << plotPath << "\'" << ENDM; + } + } + mHistogramQualityPerDE->Reset(); + for (int index = 0; index < mHistogramQualityPerDE->GetXaxis()->GetNbins(); index++) { + mHistogramQualityPerDE->SetBinContent(index + 1, deQuality[index], 1); + } + + std::string badDEsStr; + for (auto deId : badDEs) { + if (badDEsStr.empty()) { + badDEsStr = std::to_string(deId); + } else { + badDEsStr += std::string(" ") + std::to_string(deId); + } + } + if (!badDEsStr.empty()) { + std::string badDEList = std::string("Bad DEs: ") + badDEsStr; + TPaveLabel* label = new TPaveLabel(0.2, 0.8, 0.8, 0.88, badDEList.c_str(), "blNDC"); + label->SetBorderSize(1); + mHistogramQualityPerDE->GetListOfFunctions()->Add(label); + + ILOG(Warning, Support) << "[QualityAggregator] " << badDEList << ENDM; + } + + // check if the list of bad DEs has changed + bool changedDEs = (!mPreviousBadDEs.has_value() || mPreviousBadDEs.value() != badDEs); + + if (changedDEs) { + std::string sBadDEs = "deid\n"; + for (auto deId : badDEs) { + sBadDEs += fmt::format("{}\n", deId); + } + TObjString badDEsObject(sBadDEs.c_str()); + + // time stamp + auto uploadTS = o2::ccdb::getCurrentTimestamp(); + + // CCDB object info + auto clName = o2::utils::MemFileHelper::getClassName(badDEsObject); + auto flName = o2::ccdb::CcdbApi::generateFileName(clName); + constexpr auto fiveDays = 5 * o2::ccdb::CcdbObjectInfo::DAY; + auto info = o2::ccdb::CcdbObjectInfo(mObjectPathBadDE, clName, flName, std::map(), uploadTS, uploadTS + fiveDays); + + // CCDB object image + auto image = o2::ccdb::CcdbApi::createObjectImage(&badDEsObject, &info); + + ILOG(Info, Devel) << "Storing object " << info.getPath() + << " of type " << info.getObjectType() + << " / " << info.getFileName() + << " and size " << image->size() + << " bytes, valid for " << info.getStartValidityTimestamp() + << " : " << info.getEndValidityTimestamp() << ENDM; + + int res = mAPI.storeAsBinaryFile(image->data(), image->size(), info.getFileName(), info.getObjectType(), info.getPath(), info.getMetaData(), info.getStartValidityTimestamp(), info.getEndValidityTimestamp()); + if (res) { + ILOG(Error, Ops) << fmt::format("uploading to {} / {} failed for [{}:{}]", mAPI.getURL(), info.getPath(), info.getStartValidityTimestamp(), info.getEndValidityTimestamp()) << ENDM; + } else { + ILOG(Info, Devel) << fmt::format("uploading to {} / {} succeeded for [{}:{}]", mAPI.getURL(), info.getPath(), info.getStartValidityTimestamp(), info.getEndValidityTimestamp()) << ENDM; + mPreviousBadDEs = badDEs; + } + } + + // ============= + // SOLAR boards + // ============= + + std::set badSolarBoards; + std::array solarQuality; + std::fill(solarQuality.begin(), solarQuality.end(), 3); + // fill vector of bad Solars + for (auto plotPath : mSOLARPlotPaths) { + auto [mo, success] = getMO(qcdb, plotPath, trigger, 600000); + if (!success || !mo) { + ILOG(Warning, Devel) << "Could not retrieve object " << plotPath << ENDM; + continue; + } + + TH2F* hist = dynamic_cast(mo->getObject()); + if (!hist) { + ILOG(Warning, Devel) << "Could not cast the object '" << plotPath << "' to TH2F" << ENDM; + continue; + } + + if (hist->GetXaxis()->GetNbins() != getNumSolar()) { + ILOG(Warning, Devel) << "Wrong number of bins for object '" << plotPath << "': " + << hist->GetXaxis()->GetNbins() << " while " << getNumSolar() << " were expected" << ENDM; + continue; + } + + for (int index = 0; index < hist->GetXaxis()->GetNbins(); index++) { + int q = 0; + for (int qindex = 1; qindex <= hist->GetYaxis()->GetNbins(); qindex++) { + if (hist->GetBinContent(index + 1, qindex) != 0) { + q = qindex; + break; + } + } + if (q < solarQuality[index]) { + solarQuality[index] = q; + } + + bool isBad = (q == 1); + if (!isBad) { + continue; + } + + int solarId = getSolarIdFromIndex(index); + badSolarBoards.insert(solarId); + + ILOG(Debug, Devel) << "Bad SOLAR " << solarId << " found in \'" << plotPath << "\'" << ENDM; + } + } + + mHistogramQualityPerSolar->Reset(); + for (int index = 0; index < mHistogramQualityPerSolar->GetXaxis()->GetNbins(); index++) { + mHistogramQualityPerSolar->SetBinContent(index + 1, solarQuality[index], 1); + } + + std::string badSolarBoardsStr; + for (auto solarId : badSolarBoards) { + if (badSolarBoardsStr.empty()) { + badSolarBoardsStr = std::to_string(solarId); + } else { + badSolarBoardsStr += std::string(" ") + std::to_string(solarId); + } + } + if (!badSolarBoardsStr.empty()) { + std::string badSolarList = std::string("Bad SOLAR boards: ") + badSolarBoardsStr; + TPaveLabel* label = new TPaveLabel(0.2, 0.8, 0.8, 0.88, badSolarList.c_str(), "blNDC"); + label->SetBorderSize(1); + mHistogramQualityPerSolar->GetListOfFunctions()->Add(label); + + ILOG(Warning, Support) << "[QualityAggregator] " << badSolarList << ENDM; + } + + // check if the list of bad Solars has changed + bool changedSolarBoards = (!mPreviousBadSolarBoards.has_value() || mPreviousBadSolarBoards.value() != badSolarBoards); + + if (changedSolarBoards) { + std::string sBadSolars = "solarid\n"; + for (auto solarId : badSolarBoards) { + sBadSolars += fmt::format("{}\n", solarId); + } + TObjString badSolarBoardsObject(sBadSolars.c_str()); + + // time stamp + auto uploadTS = o2::ccdb::getCurrentTimestamp(); + + // CCDB object info + auto clName = o2::utils::MemFileHelper::getClassName(badSolarBoardsObject); + auto flName = o2::ccdb::CcdbApi::generateFileName(clName); + constexpr auto fiveDays = 5 * o2::ccdb::CcdbObjectInfo::DAY; + auto info = o2::ccdb::CcdbObjectInfo(mObjectPathBadSOLAR, clName, flName, std::map(), uploadTS, uploadTS + fiveDays); + + // CCDB object image + auto image = o2::ccdb::CcdbApi::createObjectImage(&badSolarBoardsObject, &info); + + ILOG(Info, Devel) << "Storing object " << info.getPath() + << " of type " << info.getObjectType() + << " / " << info.getFileName() + << " and size " << image->size() + << " bytes, valid for " << info.getStartValidityTimestamp() + << " : " << info.getEndValidityTimestamp() << ENDM; + ILOG(Info, Devel) << badSolarBoardsObject.String().Data() << ENDM; + + int res = mAPI.storeAsBinaryFile(image->data(), image->size(), info.getFileName(), info.getObjectType(), info.getPath(), info.getMetaData(), info.getStartValidityTimestamp(), info.getEndValidityTimestamp()); + if (res) { + ILOG(Error, Ops) << fmt::format("uploading to {} / {} failed for [{}:{}]", mAPI.getURL(), info.getPath(), info.getStartValidityTimestamp(), info.getEndValidityTimestamp()) << ENDM; + } else { + ILOG(Info, Devel) << fmt::format("uploading to {} / {} succeeded for [{}:{}]", mAPI.getURL(), info.getPath(), info.getStartValidityTimestamp(), info.getEndValidityTimestamp()) << ENDM; + mPreviousBadSolarBoards = badSolarBoards; + } + } +} + +//_________________________________________________________________________________________ + +void QualityAggregatorTask::finalize(Trigger t, framework::ServiceRegistryRef) +{ +} diff --git a/Modules/MUON/MCH/src/RatesPlotter.cxx b/Modules/MUON/MCH/src/RatesPlotter.cxx new file mode 100644 index 0000000000..22e88a6229 --- /dev/null +++ b/Modules/MUON/MCH/src/RatesPlotter.cxx @@ -0,0 +1,245 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RatesPlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/RatesPlotter.h" +#include "MUONCommon/Helpers.h" +#include "MCH/Helpers.h" +#include "MCHMappingInterface/Segmentation.h" +#include "MCHGlobalMapping/DsIndex.h" +#include + +using namespace o2::mch; +using namespace o2::mch::raw; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +RatesPlotter::RatesPlotter(std::string path, float rateMin, float rateMax, bool perStationPlots, bool fullPlots) +{ + // mappers used for filling the histograms in detector coordinates + mElec2DetMapper = createElec2DetMapper(); + mFeeLink2SolarMapper = createFeeLink2SolarMapper(); + + // reductor for the rates plot in electronics coordinates + mElecMapReductor = std::make_unique(rateMin, rateMax); + + //---------------------------------- + // Rate distribution histograms + //---------------------------------- + + int nbins = 100; + double xMax = rateMax * 10; + double xMin = (rateMin > 0) ? rateMin / 10 : xMax / 1000000; + auto xbins = makeLogBinning(xMin, xMax, nbins); + std::vector ybins{ 1, 2, 3, 4, 5, 6 }; + mHistogramRatePerStation = std::make_unique(TString::Format("%sRatesDistribution", path.c_str()), + "Rates distribution", + nbins, xbins.data(), + 5, ybins.data()); + for (int i = 1; i <= mHistogramRatePerStation->GetYaxis()->GetNbins(); i++) { + mHistogramRatePerStation->GetYaxis()->SetBinLabel(i, TString::Format("ST%d", i)); + } + + if (perStationPlots || fullPlots) { + addHisto(mHistogramRatePerStation.get(), false, "colz", "logx"); + } + + //---------------------------------- + // Mean rates histograms + //---------------------------------- + + mHistogramMeanRatePerDE = std::make_unique(TString::Format("%sMeanRate", path.c_str()), "Mean Rate", + getNumDE(), 0, getNumDE()); + addDEBinLabels(mHistogramMeanRatePerDE.get()); + addHisto(mHistogramMeanRatePerDE.get(), false, "histo", "logy"); + + mHistogramMeanRatePerSolar = std::make_unique(TString::Format("%sMeanRatePerSolar", path.c_str()), "Mean Rate per SOLAR board", + getNumSolar(), 0, getNumSolar()); + addSolarBinLabels(mHistogramMeanRatePerSolar.get()); + addHisto(mHistogramMeanRatePerSolar.get(), false, "histo", "logy"); + + //---------------------------------- + // "Good" channels fraction histograms + //---------------------------------- + + mHistogramGoodChannelsFractionPerDE = std::make_unique(TString::Format("%sGoodChannelsFraction", path.c_str()), + "Good channels fraction", getNumDE(), 0, getNumDE()); + addDEBinLabels(mHistogramGoodChannelsFractionPerDE.get()); + addHisto(mHistogramGoodChannelsFractionPerDE.get(), false, "histo", ""); + + mHistogramGoodChannelsFractionPerSolar = std::make_unique(TString::Format("%sGoodChannelsFractionPerSolar", path.c_str()), + "Good channels fraction per SOLAR board", getNumSolar(), 0, getNumSolar()); + addSolarBinLabels(mHistogramGoodChannelsFractionPerSolar.get()); + addHisto(mHistogramGoodChannelsFractionPerSolar.get(), false, "histo", ""); + + //-------------------------------------------------- + // Rates histograms in global detector coordinates + //-------------------------------------------------- + + mHistogramRateGlobal[0] = std::make_shared(fmt::format("{}Rate_ST12", path), "ST12 Rate", 0, 5); + mHistogramRateGlobal[0]->init(); + addHisto(mHistogramRateGlobal[0]->getHist(), false, "colz", "colz"); + + mHistogramRateGlobal[1] = std::make_shared(fmt::format("{}Rate_ST345", path), "ST345 Rate", 1, 10); + mHistogramRateGlobal[1]->init(); + addHisto(mHistogramRateGlobal[1]->getHist(), false, "colz", "colz"); + + //-------------------------------------------------- + // Rates histograms in detector coordinates + //-------------------------------------------------- + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + auto h = std::make_shared(TString::Format("%s%sRate_XY_B_%03d", path.c_str(), getHistoPath(de).c_str(), de), + TString::Format("Hit Rate (DE%03d B)", de), de, int(0)); + mHistogramRateDE[0].insert(make_pair(de, h)); + if (fullPlots) { + addHisto(h->getHist(), false, "colz", "colz"); + } + + h = std::make_shared(TString::Format("%s%sRate_XY_NB_%03d", path.c_str(), getHistoPath(de).c_str(), de), + TString::Format("Hit Rate (DE%03d NB)", de), de, int(1)); + mHistogramRateDE[1].insert(make_pair(de, h)); + if (fullPlots) { + addHisto(h->getHist(), false, "colz", "colz"); + } + } +} + +//_________________________________________________________________________________________ + +void RatesPlotter::fillAverageHistos(TH2F* hRates) +{ + // extract the integrated average occupancies + mElecMapReductor->update(hRates); + for (size_t de = 0; de < mHistogramMeanRatePerDE->GetXaxis()->GetNbins(); de++) { + mHistogramMeanRatePerDE->SetBinContent(de + 1, mElecMapReductor->getDeValue(de)); + mHistogramMeanRatePerDE->SetBinError(de + 1, 0.1); + } + + for (size_t de = 0; de < mHistogramGoodChannelsFractionPerDE->GetXaxis()->GetNbins(); de++) { + float nPads = mElecMapReductor->getNumPads(de); + float nPadsBad = mElecMapReductor->getNumPadsBad(de) + mElecMapReductor->getNumPadsNoStat(de); + float nPadsGood = nPads - nPadsBad; + if (nPads > 0) { + mHistogramGoodChannelsFractionPerDE->SetBinContent(de + 1, nPadsGood / nPads); + mHistogramGoodChannelsFractionPerDE->SetBinError(de + 1, 0.1); + } else { + mHistogramGoodChannelsFractionPerDE->SetBinContent(de + 1, 0); + mHistogramGoodChannelsFractionPerDE->SetBinError(de + 1, 1); + } + } + + for (size_t solar = 0; solar < mHistogramMeanRatePerSolar->GetXaxis()->GetNbins(); solar++) { + mHistogramMeanRatePerSolar->SetBinContent(solar + 1, mElecMapReductor->getSolarValue(solar)); + mHistogramMeanRatePerSolar->SetBinError(solar + 1, 0.1); + } + + for (size_t solar = 0; solar < mHistogramGoodChannelsFractionPerSolar->GetXaxis()->GetNbins(); solar++) { + float nPads = mElecMapReductor->getSolarNumPads(solar); + float nPadsBad = mElecMapReductor->getSolarNumPadsBad(solar) + mElecMapReductor->getSolarNumPadsNoStat(solar); + float nPadsGood = nPads - nPadsBad; + if (nPads > 0) { + mHistogramGoodChannelsFractionPerSolar->SetBinContent(solar + 1, nPadsGood / nPads); + mHistogramGoodChannelsFractionPerSolar->SetBinError(solar + 1, 0.1); + } else { + mHistogramGoodChannelsFractionPerSolar->SetBinContent(solar + 1, 0); + mHistogramGoodChannelsFractionPerSolar->SetBinError(solar + 1, 1); + } + } +} + +//_________________________________________________________________________________________ + +void RatesPlotter::fillGlobalHistos(TH2F* h) +{ + if (!h) { + return; + } + + // the rates distribution is not a cumulative plot, + // clear it before updating it with the new values + mHistogramRatePerStation->Reset(); + + // loop over bins in electronics coordinates, and map the channels to the corresponding cathode pads + int nbinsx = h->GetXaxis()->GetNbins(); + int nbinsy = h->GetYaxis()->GetNbins(); + for (int i = 1; i <= nbinsx; i++) { + // address of the DS board in detector representation + auto dsDetId = getDsDetId(i - 1); + auto deId = dsDetId.deId(); + auto dsId = dsDetId.dsId(); + auto chamberId = deId / 100; + auto stationId = ((chamberId - 1) / 2) + 1; + + if (stationId < 1 || stationId > 5) { + continue; + } + + for (int j = 1; j <= nbinsy; j++) { + int channel = j - 1; + int padId = -1; + + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + padId = segment.findPadByFEE(dsId, int(channel)); + + if (padId < 0) { + continue; + } + + double rate = h->GetBinContent(i, j); + + if (rate <= mHistogramRatePerStation->GetXaxis()->GetXmin()) { + rate = mHistogramRatePerStation->GetXaxis()->GetBinCenter(1); + } + if (rate >= mHistogramRatePerStation->GetXaxis()->GetXmax()) { + auto nbins = mHistogramRatePerStation->GetXaxis()->GetNbins(); + rate = mHistogramRatePerStation->GetXaxis()->GetBinCenter(nbins); + } + mHistogramRatePerStation->Fill(rate, stationId); + + double padX = segment.padPositionX(padId); + double padY = segment.padPositionY(padId); + float padSizeX = segment.padSizeX(padId); + float padSizeY = segment.padSizeY(padId); + int cathode = segment.isBendingPad(padId) ? 0 : 1; + + // Fill 2D rate histograms + auto hRate = mHistogramRateDE[cathode].find(deId); + if ((hRate != mHistogramRateDE[cathode].end()) && (hRate->second != NULL)) { + hRate->second->Set(padX, padY, padSizeX, padSizeY, rate); + } + } + } + + mHistogramRateGlobal[0]->set(mHistogramRateDE[0], mHistogramRateDE[1]); + mHistogramRateGlobal[1]->set(mHistogramRateDE[0], mHistogramRateDE[1]); +} + +//_________________________________________________________________________________________ + +void RatesPlotter::update(TH2F* hRates) +{ + fillAverageHistos(hRates); + fillGlobalHistos(hRates); +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/RatesTrendsPlotter.cxx b/Modules/MUON/MCH/src/RatesTrendsPlotter.cxx new file mode 100644 index 0000000000..72ea5b5a6f --- /dev/null +++ b/Modules/MUON/MCH/src/RatesTrendsPlotter.cxx @@ -0,0 +1,92 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RatesTrendsPlotter.cxx +/// \author Andrea Ferrero +/// + +#include "MCH/RatesTrendsPlotter.h" +#include +#include + +using namespace o2::mch::raw; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +RatesTrendsPlotter::RatesTrendsPlotter(std::string path, bool fullPlots) : mPath(path) +{ + mReductor = std::make_unique(); + + mOrbits = std::make_unique(fmt::format("{}Orbits", path), "Orbits", "orbits"); + addCanvas(mOrbits.get(), ""); + + //-------------------------------------------------- + // Rates trends + //-------------------------------------------------- + + for (auto de : o2::mch::constants::deIdsForAllMCH) { + int deID = getDEindex(de); + if (deID < 0) { + continue; + } + mTrendsDE[deID] = std::make_unique(fmt::format("{}{}/Rates_DE{}", path, getHistoPath(de), de), + fmt::format("DE{} Rates", de), "rate (kHz)"); + if (fullPlots) { + addCanvas(mTrendsDE[deID].get(), ""); + } + } + + mTrends = std::make_unique(fmt::format("{}ChamberRates", path), "Chamber Rates", "rate (kHz)"); + for (int i = 0; i < mTrendsChamber.size(); i++) { + int st = (i / 2) + 1; + int ch = i + 1; + mTrendsChamber[i] = std::make_unique(fmt::format("{}ST{}/Rates_CH{}", path, st, ch), + fmt::format("CH{} Rates", ch), "rate (kHz)"); + addCanvas(mTrendsChamber[i].get(), ""); + + mTrends->addGraph(fmt::format("CH{}", ch), fmt::format("CH{} Rates", ch)); + } + mTrends->addLegends(); + // mTrends->setRange(0, 1.2); + addCanvas(mTrends.get(), ""); +} + +//_________________________________________________________________________________________ + +void RatesTrendsPlotter::update(long time, TH2F* h) +{ + // extract the integrated average occupancies + mReductor->update(h); + + mOrbits->update(time, mReductor->getOrbits()); + + for (size_t de = 0; de < getNumDE(); de++) { + mTrendsDE[de]->update(time, mReductor->getDeValue(de)); + } + + std::array values; + for (size_t ch = 0; ch < 10; ch++) { + auto value = mReductor->getChamberValue(ch); + values[ch] = value; + mTrendsChamber[ch]->update(time, value); + } + mTrends->update(time, values); +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/RofsTask.cxx b/Modules/MUON/MCH/src/RofsTask.cxx new file mode 100644 index 0000000000..30ad646c34 --- /dev/null +++ b/Modules/MUON/MCH/src/RofsTask.cxx @@ -0,0 +1,210 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RofsTask.cxx +/// \author Sebastien Perrin +/// + +#include "MCH/RofsTask.h" + +#include +#include + +#include "DetectorsRaw/RDHUtils.h" +#include "QualityControl/QcInfoLogger.h" +#include "Framework/WorkflowSpec.h" +#include "Framework/DataRefUtils.h" +#include "CommonConstants/LHCConstants.h" + +#include "MCHRawElecMap/Mapper.h" +#include "MCHMappingInterface/Segmentation.h" +#include "MCHRawDecoder/PageDecoder.h" +#include "MCHRawDecoder/ErrorCodes.h" +#include "MCHBase/DecoderError.h" + +using namespace o2; +using namespace o2::framework; +using namespace o2::mch::mapping; +using namespace o2::mch::raw; +using RDH = o2::header::RDHAny; +using namespace o2::quality_control::core; + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +template +void RofsTask::publishObject(std::shared_ptr histo, std::string drawOption, bool statBox) +{ + histo->SetOption(drawOption.c_str()); + if (!statBox) { + histo->SetStats(0); + } + mAllHistograms.push_back(histo.get()); + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), drawOption); +} + +void RofsTask::initialize(o2::framework::InitContext& /*ic*/) +{ + ILOG(Debug, Devel) << "initialize RofsTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + static constexpr int nLogBins = 100; + Float_t xbins[nLogBins + 1]; + Float_t logMin = 0; + Float_t logMax = 6; + Float_t dLog = (logMax - logMin) / nLogBins; + for (int b = 0; b <= nLogBins; b++) { + xbins[b] = TMath::Power(10, dLog * b); + } + + mIsSignalDigit = o2::mch::createDigitFilter(20, true, true); + + // ROF size distributions + mHistRofSize = std::make_shared("RofSize", "ROF size", nLogBins, xbins); + publishObject(mHistRofSize, "hist", true); + + // ROF size distributions (signal-like digits only) + mHistRofSize_Signal = std::make_shared("RofSize_Signal", "ROF size (signal-like digits)", nLogBins, xbins); + publishObject(mHistRofSize_Signal, "hist", true); + + // ROF width distributions + mHistRofWidth = std::make_shared("RofWidth", "ROF width", 2000 / 25, 0, 2000 / 25); + publishObject(mHistRofWidth, "hist", true); + + // Number of stations per ROF + mHistRofNStations = std::make_shared("RofNStations", "Number of stations per ROF", 6, 0, 6); + publishObject(mHistRofNStations, "hist", true); + + // Number of stations per ROF (signal-like digits) + mHistRofNStations_Signal = std::make_shared("RofNStations_Signal", "Number of stations per ROF (signal-like digits)", 6, 0, 6); + publishObject(mHistRofNStations_Signal, "hist", true); + + auto bcInOrbit = o2::constants::lhc::LHCMaxBunches; + // ROF time distribution in orbit + mHistRofTime = std::make_shared("RofTime", "ROF time in orbit", bcInOrbit, 0, bcInOrbit); + publishObject(mHistRofTime, "hist", false); + + // ROF time distribution in orbit (signal-like digits) + mHistRofTime_Signal = std::make_shared("RofTime_Signal", "ROF time in orbit (signal-like digits)", bcInOrbit, 0, bcInOrbit); + publishObject(mHistRofTime_Signal, "hist", false); +} + +void RofsTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity : " << activity.mId << ENDM; +} + +void RofsTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void RofsTask::plotROF(const o2::mch::ROFRecord& rof, gsl::span digits) +{ + const auto bcInOrbit = o2::constants::lhc::LHCMaxBunches; + + if (rof.getNEntries() < 1) { + ILOG(Warning, Support) << "Current ROF is empty" << ENDM; + return; + } + + if ((rof.getFirstIdx() + rof.getNEntries()) > digits.size()) { + ILOG(Warning, Support) << "Current ROF has invalid digits range: (rof.getFirstIdx() + rof.getNEntries())=" << (rof.getFirstIdx() + rof.getNEntries()) << " > digits.size()=" << digits.size() << ENDM; + return; + } + + auto rofDigits = digits.subspan(rof.getFirstIdx(), rof.getNEntries()); + if (rofDigits.empty()) { + ILOG(Warning, Support) << "Current ROF has no associated digits" << ENDM; + return; + } + + auto start = rofDigits.front().getTime(); + auto end = rofDigits.back().getTime(); + mHistRofWidth->Fill(end - start + 1); + + // number of signal-like digits in ROF + int nSignal = 0; + + // average ROF time + double rofTime = 0; + double rofTimeSignal = 0; + + // fired stations + std::array stations{ false }; + std::array stationsSignal{ false }; + + for (auto& digit : rofDigits) { + int station = (digit.getDetID() - 100) / 200; + if (station < 0 || station >= 5) { + continue; + } + stations[station] = true; + + rofTime += digit.getTime() % bcInOrbit; + + if (mIsSignalDigit(digit)) { + nSignal += 1; + rofTimeSignal += digit.getTime() % bcInOrbit; + stationsSignal[station] = true; + } + } + mHistRofSize->Fill(rof.getNEntries()); + mHistRofSize_Signal->Fill(nSignal); + + mHistRofTime->Fill(rofTime / rofDigits.size()); + if (nSignal > 0) { + mHistRofTime_Signal->Fill(rofTimeSignal / nSignal); + } + + mHistRofNStations->Fill(std::count(stations.cbegin(), stations.cend(), true)); + mHistRofNStations_Signal->Fill(std::count(stationsSignal.cbegin(), stationsSignal.cend(), true)); +} + +void RofsTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto digits = ctx.inputs().get>("digits"); + auto rofs = ctx.inputs().get>("rofs"); + + for (auto& rof : rofs) { + plotROF(rof, digits); + } +} + +void RofsTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void RofsTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void RofsTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histogramss" << ENDM; + + for (auto h : mAllHistograms) { + h->Reset("ICES"); + } +} + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MCH/src/TH2ElecMapReductor.cxx b/Modules/MUON/MCH/src/TH2ElecMapReductor.cxx new file mode 100644 index 0000000000..1ea30a33fb --- /dev/null +++ b/Modules/MUON/MCH/src/TH2ElecMapReductor.cxx @@ -0,0 +1,335 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2ElecMapReductor.cxx +/// \author Andrea Ferrero +/// \author Sebastien Perrin +/// + +#include +#include "MCH/TH2ElecMapReductor.h" +#include "MCH/Helpers.h" +#include "Common/TH2Ratio.h" +#include "QualityControl/QcInfoLogger.h" +#include "MCHMappingInterface/Segmentation.h" +#include "MCHGlobalMapping/DsIndex.h" +#include +#include +#include +#include +#include + +using namespace o2::mch; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::muonchambers +{ + +TH2ElecMapReductor::TH2ElecMapReductor(float min, float max) + : quality_control::postprocessing::ReductorTObject(), + mMin(min), + mMax(max) +{ + mElec2DetMapper = o2::mch::raw::createElec2DetMapper(); + mDet2ElecMapper = o2::mch::raw::createDet2ElecMapper(); + mFeeLink2SolarMapper = o2::mch::raw::createFeeLink2SolarMapper(); + mSolar2FeeLinkMapper = o2::mch::raw::createSolar2FeeLinkMapper(); +} + +void* TH2ElecMapReductor::getBranchAddress() +{ + return nullptr; +} + +const char* TH2ElecMapReductor::getBranchLeafList() +{ + return ""; +} + +float TH2ElecMapReductor::getDeValue(int deid, int cathode) +{ + if (deid < 0 || deid >= sDeNum) { + return 0; + } + if (cathode < 0 || cathode > 2) { + return 0; + } + return deValues[cathode][deid]; +} + +float TH2ElecMapReductor::getChamberValue(int chid) +{ + if (chid < 0 || chid >= 10) { + return 0; + } + return chValues[chid]; +} + +int TH2ElecMapReductor::getNumPads(int deid, int cathode) +{ + if (deid < 0 || deid >= sDeNum) { + return 0; + } + if (cathode < 0 || cathode > 1) { + return 0; + } + return deNumPads[cathode][deid]; +} + +int TH2ElecMapReductor::getNumPadsBad(int deid, int cathode) +{ + if (deid < 0 || deid >= sDeNum) { + return 0; + } + if (cathode < 0 || cathode > 1) { + return 0; + } + return deNumPadsBad[cathode][deid]; +} + +int TH2ElecMapReductor::getNumPadsNoStat(int deid, int cathode) +{ + if (deid < 0 || deid >= sDeNum) { + return 0; + } + if (cathode < 0 || cathode > 1) { + return 0; + } + return deNumPadsNoStat[cathode][deid]; +} + +float TH2ElecMapReductor::getSolarValue(int solarId) +{ + if (solarId < 0 || solarId >= getNumSolar()) { + return 0; + } + return solarValues[solarId]; +} + +int TH2ElecMapReductor::getSolarNumPads(int solarId) +{ + if (solarId < 0 || solarId >= getNumSolar()) { + return 0; + } + return solarNumPads[solarId]; +} + +int TH2ElecMapReductor::getSolarNumPadsBad(int solarId) +{ + if (solarId < 0 || solarId >= getNumSolar()) { + return 0; + } + return solarNumPadsBad[solarId]; +} + +int TH2ElecMapReductor::getSolarNumPadsNoStat(int solarId) +{ + if (solarId < 0 || solarId >= getNumSolar()) { + return 0; + } + return solarNumPadsNoStat[solarId]; +} + +void TH2ElecMapReductor::update(TObject* obj) +{ + if (sDeNum != getNumDE()) { + ILOG(Warning) << "wrong sDeNum" << ENDM; + return; + } + + auto h = dynamic_cast(obj); + if (!h) { + ILOG(Warning) << "cannot cast to TH2F" << ENDM; + return; + } + + auto* hr = dynamic_cast(obj); + if (!hr) { + ILOG(Warning) << "cannot cast to TH2FRatio" << ENDM; + return; + } + + // cumulative numerators and denominators for the computation of + // the average value over SOLAR boards + std::vector solarValueNum(getNumSolar()); + std::vector solarValueDen(getNumSolar()); + std::fill(solarValueNum.begin(), solarValueNum.end(), 0); + std::fill(solarValueDen.begin(), solarValueDen.end(), 0); + + for (int solar = 0; solar < sSolarIndexMax; solar++) { + solarNumPads[solar] = 0; + solarNumPadsBad[solar] = 0; + solarNumPadsNoStat[solar] = 0; + } + + // cumulative numerators and denominators for the computation of + // the average value over detection elements and chambers + std::vector deValueNum(getNumDE()); + std::vector deValueDen(getNumDE()); + std::fill(deValueNum.begin(), deValueNum.end(), 0); + std::fill(deValueDen.begin(), deValueDen.end(), 0); + + std::vector deValueNumB(getNumDE()); + std::vector deValueDenB(getNumDE()); + std::fill(deValueNumB.begin(), deValueNumB.end(), 0); + std::fill(deValueDenB.begin(), deValueDenB.end(), 0); + + std::vector deValueNumNB(getNumDE()); + std::vector deValueDenNB(getNumDE()); + std::fill(deValueNumNB.begin(), deValueNumNB.end(), 0); + std::fill(deValueDenNB.begin(), deValueDenNB.end(), 0); + + std::vector chValueNum(10); + std::vector chValueDen(10); + std::fill(chValueNum.begin(), chValueNum.end(), 0); + std::fill(chValueDen.begin(), chValueDen.end(), 0); + + for (int i = 0; i < 2; i++) { + for (int de = 0; de < sDeNum; de++) { + deNumPads[i][de] = 0; + deNumPadsBad[i][de] = 0; + deNumPadsNoStat[i][de] = 0; + } + } + + double nOrbits = 0; + + entries = h->GetEntries(); + + int nbinsx = h->GetXaxis()->GetNbins(); + int nbinsy = h->GetYaxis()->GetNbins(); + int ngood = 0; + int nPads = 0; + for (int i = 1; i <= nbinsx; i++) { + auto dsDetId = getDsDetId(i - 1); + auto deId = dsDetId.deId(); + auto dsId = dsDetId.dsId(); + + int deIndex = getDEindex(deId); + if (deIndex < 0) { + continue; + } + + auto dsElecId = mDet2ElecMapper(dsDetId); + if (!dsElecId) { + continue; + } + auto solarId = dsElecId->solarId(); + + int solarIndex = getSolarIndex(solarId); + if (solarIndex < 0) { + continue; + } + + for (int j = 1; j <= nbinsy; j++) { + int channel = j - 1; + + const o2::mch::mapping::Segmentation& segment = o2::mch::mapping::segmentation(deId); + int padId = segment.findPadByFEE(dsId, int(channel)); + + if (padId < 0) { + continue; + } + int cathode = segment.isBendingPad(padId) ? 0 : 1; + + solarNumPads[solarIndex] += 1; + deNumPads[cathode][deIndex] += 1; + + // here we assume that if the number of bins differs between numerator and denominator, + // the denominator contains a single common scaling factor in the first bin (uniform scaling) + Float_t stat = (hr->getNum()->GetNcells() == hr->getDen()->GetNcells()) ? hr->getDen()->GetBinContent(i, j) : hr->getDen()->GetBinContent(1, 1); + if (stat == 0) { + solarNumPadsNoStat[solarIndex] += 1; + deNumPadsNoStat[cathode][deIndex] += 1; + continue; + } + + Float_t value = h->GetBinContent(i, j); + if (value <= mMin || value >= mMax) { + solarNumPadsBad[solarIndex] += 1; + deNumPadsBad[cathode][deIndex] += 1; + } + + nOrbits += stat; + nPads += 1; + + solarValueNum[solarIndex] += value; + solarValueDen[solarIndex] += 1; + + deValueNum[deIndex] += value; + deValueDen[deIndex] += 1; + if (cathode == 0) { + deValueNumB[deIndex] += value; + deValueDenB[deIndex] += 1; + } else { + deValueNumNB[deIndex] += value; + deValueDenNB[deIndex] += 1; + } + + int chamber = deId / 100 - 1; + if (chamber >= 0 && chamber < 10) { + chValueNum[chamber] += value; + chValueDen[chamber] += 1; + } + } + } + + // update the average values + for (size_t solar = 0; solar < solarValueDen.size(); solar++) { + // integrated values + if (solarValueDen[solar] > 0) { + solarValues[solar] = solarValueNum[solar] / solarValueDen[solar]; + } else { + solarValues[solar] = 0; + } + } + for (size_t de = 0; de < deValueDenB.size(); de++) { + // integrated values + if (deValueDenB[de] > 0) { + deValues[0][de] = deValueNumB[de] / deValueDenB[de]; + } else { + deValues[0][de] = 0; + } + } + for (size_t de = 0; de < deValueDenNB.size(); de++) { + // integrated values + if (deValueDenNB[de] > 0) { + deValues[1][de] = deValueNumNB[de] / deValueDenNB[de]; + } else { + deValues[1][de] = 0; + } + } + for (size_t de = 0; de < deValueDen.size(); de++) { + // integrated values + if (deValueDen[de] > 0) { + deValues[2][de] = deValueNum[de] / deValueDen[de]; + } else { + deValues[2][de] = 0; + } + } + for (size_t ch = 0; ch < chValueDen.size(); ch++) { + // integrated values + if (chValueDen[ch] > 0) { + chValues[ch] = chValueNum[ch] / chValueDen[ch]; + } else { + chValues[ch] = 0; + } + } + + if (nPads > 0) { + meanOrbits = nOrbits / nPads; + } else { + meanOrbits = 0; + } +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/TracksTask.cxx b/Modules/MUON/MCH/src/TracksTask.cxx new file mode 100644 index 0000000000..06754b46e3 --- /dev/null +++ b/Modules/MUON/MCH/src/TracksTask.cxx @@ -0,0 +1,348 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include "MCH/TracksTask.h" + +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +constexpr double muonMass = 0.1056584; +constexpr double muonMass2 = muonMass * muonMass; + +static void setXAxisLabels(TProfile* h) +{ + TAxis* axis = h->GetXaxis(); + for (int i = 1; i <= 10; i++) { + auto label = fmt::format("CH{}", i); + axis->SetBinLabel(i, label.c_str()); + } +} + +uint16_t computeDsBinX(int feeId, int linkId, int elinkId) +{ + constexpr uint64_t maxLinkId = 12; + constexpr uint64_t maxElinkId = 40; + + int v = feeId * maxLinkId * maxElinkId + + (linkId % maxLinkId) * maxElinkId + elinkId + 1; + return static_cast(v & 0xFFFF); +} + +std::array getClustersPerChamber(gsl::span clusters) +{ + std::array clustersPerChamber; + clustersPerChamber.fill(0); + for (const auto& cluster : clusters) { + int chamberId = cluster.getChamberId(); + clustersPerChamber[chamberId]++; + } + return clustersPerChamber; +} + +} // namespace + +namespace o2::quality_control_modules::muonchambers +{ + +TracksTask::TracksTask() +{ +} + +TracksTask::~TracksTask() = default; + +void TracksTask::createClusterHistos() +{ + mNofClustersPerTrack = createHisto("ClustersPerTrack", "Number of clusters per track;Mean number of clusters per track", 30, 0, 30); + mNofClustersPerDualSampa = createHisto("ClustersPerDualSampa", "Number of clusters per dual sampa;Number of clusters per DS", 30720, 0, 30719); + + mNofClustersPerChamber = createHisto("ClustersPerChamber", "Clusters per chamber;;Number of clusters", 10, 1, 11); + setXAxisLabels(mNofClustersPerChamber.get()); + mClusterSizePerChamber = createHisto("ClusterSizePerChamber", "Cluster size per chamber;;Mean number of pads per cluster", 10, 1, 11); + setXAxisLabels(mClusterSizePerChamber.get()); +} + +void TracksTask::createTrackHistos() +{ + double maxTracksPerTF = 400; + + if (auto param = mCustomParameters.find("maxTracksPerTF"); param != mCustomParameters.end()) { + maxTracksPerTF = std::stof(param->second); + } + + mNofTracksPerTF = createHisto("TracksPerTF", "Number of tracks per TimeFrame;Number of tracks per TF", maxTracksPerTF, 0, maxTracksPerTF, true, "logy"); + + mTrackDCA = createHisto("TrackDCA", "Track DCA;DCA (cm)", 500, 0, 500); + mTrackPDCA = createHisto("TrackPDCA", "Track p#timesDCA;p#timesDCA (GeVcm/c)", 5000, 0, 5000); + mTrackPt = createHisto("TrackPt", "Track p_{T};p_{T} (GeV/c)", 300, 0, 30, false, "logy"); + mTrackEta = createHisto("TrackEta", "Track #eta;#eta", 200, -4.5, -2); + mTrackPhi = createHisto("TrackPhi", "Track #phi;#phi (deg)", 360, 0, 360); + mTrackRAbs = createHisto("TrackRAbs", "Track R_{abs};R_{abs} (cm)", 1000, 0, 100); + mTrackBC = createHisto("TrackBC", "Track BC;BC", o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches - 1); + mTrackChi2OverNDF = createHisto("TrackChi2OverNDF", "Track #chi^{2}/ndf;#chi^{2}/ndf", 500, 0, 50); +} + +void TracksTask::createTrackPairHistos() +{ + mMinv = createHisto("Minv", "#mu^{+}#mu^{-} invariant mass;M_{#mu^{+}#mu^{-}} (GeV/c^{2})", 300, 0, 6); +} + +void TracksTask::initialize(o2::framework::InitContext& /*ic*/) +{ + ILOG(Debug, Devel) << "initialize TracksTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + createTrackHistos(); + createClusterHistos(); + createTrackPairHistos(); + + mDet2ElecMapper = o2::mch::raw::createDet2ElecMapper(); + mSolar2FeeLinkMapper = o2::mch::raw::createSolar2FeeLinkMapper(); +} + +int TracksTask::dsbinx(int deid, int dsid) const +{ + o2::mch::raw::DsDetId det{ deid, dsid }; + auto elec = mDet2ElecMapper(det); + if (!elec.has_value()) { + ILOGE << "mapping is wrong somewhere..."; + return -1; + } + auto eLinkId = elec->elinkId(); + auto solarId = elec->solarId(); + auto s2f = mSolar2FeeLinkMapper(solarId); + if (!s2f.has_value()) { + ILOGE << "mapping is wrong somewhere..."; + return -1; + } + auto feeId = s2f->feeId(); + auto linkId = s2f->linkId(); + + return computeDsBinX(feeId, linkId, eLinkId); +} + +void TracksTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity : " << activity.mId << ENDM; +} + +void TracksTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +ROOT::Math::PxPyPzMVector getMomentum4D(const o2::mch::TrackParam& trackParam) +{ + double px = trackParam.px(); + double py = trackParam.py(); + double pz = trackParam.pz(); + return { px, py, pz, muonMass }; +} + +ROOT::Math::PxPyPzMVector getMomentum4D(const o2::mch::TrackMCH& track) +{ + o2::mch::TrackParam trackParamAtVertex(track.getZ(), track.getParameters()); + double vz = 0.0; + if (!o2::mch::TrackExtrap::extrapToVertex(trackParamAtVertex, 0.0, 0.0, 0.0, 0.0, 0.0)) { + ILOG(Error, Support) << "Track extrap failed" << ENDM; + return { 0, 0, 0, 0 }; + } + return getMomentum4D(trackParamAtVertex); +} + +void TracksTask::fillClusterHistos(gsl::span clusters) +{ + if (mTransformation.get() == nullptr) { + // should not happen, but better be safe than sorry + return; + } + for (const auto& cluster : clusters) { + int deId = cluster.getDEId(); + const o2::mch::mapping::Segmentation& seg = o2::mch::mapping::segmentation(deId); + int b, nb; + + o2::math_utils::Point3D global{ cluster.getX(), + cluster.getY(), cluster.getZ() }; + auto t = (*mTransformation)(deId).Inverse(); + auto local = t(global); + + seg.findPadPairByPosition(local.X(), local.Y(), b, nb); + + if (b >= 0) { + int dsId = seg.padDualSampaId(b); + mNofClustersPerDualSampa->Fill(dsbinx(deId, dsId)); + } + if (nb >= 0) { + int dsId = seg.padDualSampaId(nb); + mNofClustersPerDualSampa->Fill(dsbinx(deId, dsId)); + } + int chamberId = cluster.getChamberId(); + mClusterSizePerChamber->Fill(chamberId + 1, cluster.nDigits); + } +} + +bool TracksTask::assertInputs(o2::framework::ProcessingContext& ctx) +{ + if (!ctx.inputs().isValid("tracks")) { + ILOG(Info, Support) << "no mch tracks available on input" << ENDM; + return false; + } + if (!ctx.inputs().isValid("trackrofs")) { + ILOG(Info, Support) << "no mch track rofs available on input" << ENDM; + return false; + } + if (!ctx.inputs().isValid("trackclusters")) { + ILOG(Info, Support) << "no mch track clusters available on input" << ENDM; + return false; + } + return true; +} + +void TracksTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + if (!assertInputs(ctx)) { + return; + } + + if (mTransformation.get() == nullptr && gGeoManager != nullptr) { + ILOG(Info, Support) << "get transformation from gGeoManager" << ENDM; + mTransformation = std::make_unique(o2::mch::geo::transformationFromTGeoManager(*gGeoManager)); + } + + auto tracks = ctx.inputs().get>("tracks"); + auto rofs = ctx.inputs().get>("trackrofs"); + auto clusters = ctx.inputs().get>("trackclusters"); + + mNofTracksPerTF->Fill(tracks.size()); + + for (const auto& rof : rofs) { + if (rof.getNEntries() > 0) { + mTrackBC->Fill(rof.getBCData().bc); + } + } + + decltype(tracks.size()) nok{ 0 }; + + for (const auto& track : tracks) { + bool ok = fillTrackHistos(track, clusters); + if (ok) { + ++nok; + } + } + + if (nok != tracks.size()) { + ILOG(Warning, Support) << "Could only extrapolate " << nok << " tracks over " << tracks.size() << ENDM; + } + + for (const auto& rof : rofs) { + if (rof.getNEntries() > 0) { + auto rtracks = tracks.subspan(rof.getFirstIdx(), rof.getNEntries()); + fillTrackPairHistos(rtracks); + } + } +} + +void TracksTask::fillTrackPairHistos(gsl::span tracks) +{ + if (tracks.size() > 1) { + for (auto i = 0; i < tracks.size(); i++) { + auto ti = getMomentum4D(tracks[i]); + for (auto j = i + 1; j < tracks.size(); j++) { + if (tracks[i].getSign() == tracks[j].getSign()) { + continue; + } + auto tj = getMomentum4D(tracks[j]); + auto p = ti + tj; + mMinv->Fill(p.M()); + } + } + } +} + +bool TracksTask::fillTrackHistos(const o2::mch::TrackMCH& track, + gsl::span clusters) +{ + + mNofClustersPerTrack->Fill(track.getNClusters()); + mTrackChi2OverNDF->Fill(track.getChi2OverNDF()); + + const auto trackClusters = clusters.subspan(track.getFirstClusterIdx(), track.getNClusters()); + fillClusterHistos(trackClusters); + + auto clustersPerChamber = getClustersPerChamber(trackClusters); + + for (auto i = 0; i < clustersPerChamber.size(); i++) { + mNofClustersPerChamber->Fill(i + 1, clustersPerChamber[i]); + } + + auto p = track.getP(); // uncorrected p + + o2::mch::TrackParam trackParamAtDCA(track.getZ(), track.getParameters()); + if (!o2::mch::TrackExtrap::extrapToVertexWithoutBranson(trackParamAtDCA, 0.0)) { + return false; + } + + o2::mch::TrackParam trackParamAtOrigin(track.getZ(), track.getParameters()); + if (!o2::mch::TrackExtrap::extrapToVertex(trackParamAtOrigin, 0.0, 0.0, 0.0, 0.0, 0.0)) { + return false; + } + double dcaX = trackParamAtDCA.getBendingCoor(); + double dcaY = trackParamAtDCA.getNonBendingCoor(); + double dca = std::sqrt(dcaX * dcaX + dcaY * dcaY); + mTrackDCA->Fill(dca); + mTrackPDCA->Fill(p * dca); + + auto muon = getMomentum4D(trackParamAtOrigin); + + mTrackEta->Fill(muon.eta()); + mTrackPhi->Fill(muon.phi() * TMath::RadToDeg() + 180); + mTrackPt->Fill(muon.pt()); + + o2::mch::TrackParam trackParamAtAbsEnd(track.getZ(), track.getParameters()); + o2::mch::TrackExtrap::extrapToZ(trackParamAtAbsEnd, -505.); + double xAbs = trackParamAtAbsEnd.getNonBendingCoor(); + double yAbs = trackParamAtAbsEnd.getBendingCoor(); + auto rAbs = std::sqrt(xAbs * xAbs + yAbs * yAbs); + mTrackRAbs->Fill(rAbs); + + return true; +} + +void TracksTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void TracksTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TracksTask::reset() +{ + ILOG(Warning, Support) << "resetting the histograms is not implemented" << ENDM; +} + +} // namespace o2::quality_control_modules::muonchambers diff --git a/Modules/MUON/MCH/src/TrendingTracks.cxx b/Modules/MUON/MCH/src/TrendingTracks.cxx new file mode 100644 index 0000000000..fb2e00bea4 --- /dev/null +++ b/Modules/MUON/MCH/src/TrendingTracks.cxx @@ -0,0 +1,235 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTracks.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Trending of the MCH tracking +/// \since 21/06/2022 +/// + +#include "MCH/TrendingTracks.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/ActivityHelpers.h" +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::muonchambers; + +void TrendingTracks::configure(const boost::property_tree::ptree& config) +{ + mConfig = PostProcessingConfigMCH(getID(), config); + for (int chamber = 0; chamber < 10; chamber++) { + mConfig.plots.push_back({ fmt::format("Clusters_CH{}", chamber + 1), + fmt::format("Clusters CH{}", chamber + 1), + fmt::format("clusCH{}:time", chamber + 1), + "", "*L", "" }); + } +} + +void TrendingTracks::initialize(Trigger, framework::ServiceRegistryRef) +{ + // Setting parameters + + // Preparing data structure of TTree + mTrend.reset(); + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("time", &mTime); + + for (int chamber = 0; chamber < 10; chamber++) { + mTrend->Branch(fmt::format("clusCH{}", chamber + 1).c_str(), &mClusCH[chamber]); + } + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create(source.moduleName, source.reductorName)); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } + getObjectsManager()->startPublishing(mTrend.get(), PublicationPolicy::ThroughStop); +} + +void TrendingTracks::computeClustersPerChamber(TProfile* p) +{ + if (!p) { + return; + } + if (p->GetXaxis()->GetNbins() != 10) { + ILOG(Error, Support) << "Wrong number of bins in moClusPerChamber, can't compute number of clusters per chamber"; + return; + } + + if (!mHistClusPerChamberPrev) { + mHistClusPerChamberPrev = new TProfile(*p); + mHistClusPerChamberPrev->SetName("histClusPerChamberPrev"); + mHistClusPerChamberPrev->Reset(); + } + + TProfile hDiff(*p); + hDiff.SetName("histClusPerChamberDiff"); + hDiff.Add(mHistClusPerChamberPrev, -1); + + for (int ch = 0; ch < 10; ch++) { + mClusCH[ch] = hDiff.GetBinContent(ch + 1); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TrendingTracks::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + trendValues(t, qcdb); + generatePlots(); +} + +void TrendingTracks::finalize(Trigger t, framework::ServiceRegistryRef) +{ + generatePlots(); +} + +void TrendingTracks::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + mTime = activity_helpers::isLegacyValidity(t.activity.mValidity) + ? t.timestamp / 1000 + : t.activity.mValidity.getMax() / 1000; // ROOT expects seconds since epoch. mMetaData.runNumber = t.activity.mId; + + std::shared_ptr moClusPerChamber = nullptr; + + for (auto& dataSource : mConfig.dataSources) { + auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name, t.timestamp, t.activity); + if (!mo) { + continue; + } + ILOG(Debug, Support) << "Got MO " << mo << ENDM; + if (mo->getName().find(nameClusPerChamber) != std::string::npos) { + moClusPerChamber = mo; + } else { + TObject* obj = mo ? mo->getObject() : nullptr; + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (obj && reductor) { + reductor->update(obj); + } + } + } + + if (moClusPerChamber) { + TProfile* p = dynamic_cast(moClusPerChamber->getObject()); + if (!p) { + ILOG(Error, Support) << "Cannot cast moClusPerChamber, can't compute number of clusters per chamber"; + return; + } + computeClustersPerChamber(p); + } + + mTrend->Fill(); +} + +void TrendingTracks::generatePlots() +{ + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, won't generate any plots." << ENDM; + return; + } + + ILOG(Info, Support) << "Generating " << mConfig.plots.size() << " plots." << ENDM; + + for (const auto& plot : mConfig.plots) { + + // Before we generate any new plots, we have to delete existing under the same names. + // It seems that ROOT cannot handle an existence of two canvases with a common name in the same process. + if (mPlots.count(plot.name)) { + delete mPlots[plot.name]; + } + + // we determine the order of the plot, i.e. if it is a histogram (1), graph (2), or any higher dimension. + const size_t plotOrder = std::count(plot.varexp.begin(), plot.varexp.end(), ':') + 1; + // we have to delete the graph errors after the plot is saved, unfortunately the canvas does not take its ownership + TGraphErrors* graphErrors = nullptr; + + TCanvas* c = new TCanvas(); + + mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), plot.option.c_str()); + + c->SetName(plot.name.c_str()); + c->SetTitle(plot.title.c_str()); + + // For graphs we allow to draw errors if they are specified. + if (!plot.graphErrors.empty()) { + if (plotOrder != 2) { + ILOG(Error, Support) << "Non empty graphErrors seen for the plot '" << plot.name << "', which is not a graph, ignoring." << ENDM; + } else { + // We generate some 4-D points, where 2 dimensions represent graph points and 2 others are the error bars + std::string varexpWithErrors(plot.varexp + ":" + plot.graphErrors); + mTrend->Draw(varexpWithErrors.c_str(), plot.selection.c_str(), "goff"); + graphErrors = new TGraphErrors(mTrend->GetSelectedRows(), mTrend->GetVal(1), mTrend->GetVal(0), mTrend->GetVal(2), mTrend->GetVal(3)); + // We draw on the same plot as the main graph, but only error bars + graphErrors->Draw("SAME E"); + // We try to convince ROOT to delete graphErrors together with the rest of the canvas. + if (auto* pad = c->GetPad(0)) { + if (auto* primitives = pad->GetListOfPrimitives()) { + primitives->Add(graphErrors); + } + } + } + } + + // Postprocessing the plot - adding specified titles, configuring time-based plots, flushing buffers. + // Notice that axes and title are drawn using a histogram, even in the case of graphs. + if (auto histo = dynamic_cast(c->GetPrimitive("htemp"))) { + // The title of histogram is printed, not the title of canvas => we set it as well. + histo->SetTitle(plot.title.c_str()); + // We have to update the canvas to make the title appear. + c->Update(); + + // After the update, the title has a different size and it is not in the center anymore. We have to fix that. + if (auto title = dynamic_cast(c->GetPrimitive("title"))) { + title->SetBBoxCenterX(c->GetBBoxCenter().fX); + // It will have an effect only after invoking Draw again. + title->Draw(); + } else { + ILOG(Error, Devel) << "Could not get the title TPaveText of the plot '" << plot.name << "'." << ENDM; + } + + // We have to explicitly configure showing time on x axis. + // I hope that looking for ":time" is enough here and someone doesn't come with an exotic use-case. + if (plot.varexp.find(":time") != std::string::npos) { + histo->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + histo->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + histo->GetXaxis()->SetTimeOffset(0.0); + histo->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + // QCG doesn't empty the buffers before visualizing the plot, nor does ROOT when saving the file, + // so we have to do it here. + histo->BufferEmpty(); + } else { + ILOG(Error, Devel) << "Could not get the htemp histogram of the plot '" << plot.name << "'." << ENDM; + } + + mPlots[plot.name] = c; + getObjectsManager()->startPublishing(c, PublicationPolicy::Once); + } +} diff --git a/Modules/MUON/MCH/src/clustermap-display.md b/Modules/MUON/MCH/src/clustermap-display.md new file mode 100644 index 0000000000..0c4792b31b --- /dev/null +++ b/Modules/MUON/MCH/src/clustermap-display.md @@ -0,0 +1,119 @@ +# MCH Cluster Maps + + - Goal : The general purpose is to track "unexpected" detector issues not well reproduced with MC simulations. These problems generate non-negligible bias in Acc*Eff corrections resulting in large tracking systematic uncertainties. During the data reconstruction, the status of the detector is calculated with the CCDB which is used to discard most of the detector issues. This status map is built with information based on pedestals, occupancy etc. (high and low voltage will be included soon in the statusmap.) Nevertheless, some detector issues (e.g. a cable swapping) are not well detected online and consequently not properly reproduced by the CCBD. The main objective of this code is to spot these issues not included in the status map. + + - SRC FILE: + `Clustermap-Display.cxx` + +- INPUT FILES: +`DATA_QC.root` +`MC_QC.root` +`o2sim_geometry-aligned.root` + + - HELP MESSAGE TO KNOW WHICH OPTIONS ARE AVAILABLE: +```shell + +o2-qc-mch-clustermap-display --help + +``` + + - EXECUTION COMMAND: + +```shell + +o2-qc-mch-clustermap-display --green --normperarea --rootfileleft DATA_QC.root --rootfileright MC_QC.root + +``` + + - OUTPUT FILES: + + Non-bending(NB): + + ```shell + +CHAMBERS-1-NB.html CHAMBERS-2-NB.html CHAMBERS-3-NB.html CHAMBERS-4-NB.html CHAMBERS-5-NB.html CHAMBERS-6-NB.html CHAMBERS-7-NB.html CHAMBERS-8-NB.html CHAMBERS-9-NB.html CHAMBERS-10-NB.html + +``` +Bending(B): + ```shell + +CHAMBERS-1-B.html CHAMBERS-2-B.html CHAMBERS-3-B.html CHAMBERS-4-B.html CHAMBERS-5-B.html CHAMBERS-6-B.html CHAMBERS-7-B.html CHAMBERS-8-B.html CHAMBERS-9-B.html CHAMBERS-10-B.html + +``` + + +# INPUT FILES FOR MCH CLUSTER MAPS + INPUT FILES: + `o2sim_geometry-aligned.root` + `MC_QC.root` +`DATA_QC.root` + + + +## Aligned Geometry File + +- Simulation to obtain the alignement of the muon spectrometer: + +```shell + +o2-sim-serial --timestamp 1663632000000 -n 10 -g fwmugen -m HALL MAG DIPO COMP PIPE ABSO SHIL MCH MID + +``` +- The output file produced: `o2sim_geometry-aligned.root` + + +## MC file +SIMULATIONS: +- Go to lxplus : `ssh -X login@lxplus.cern.ch` +- Source the environment : `source /cvmfs/alice-nightlies.cern.ch/bin/alienv enter VO_ALICE@O2sim::v20230413-1` +- Create a new repository for your simulation +- Check for the generator file : `O2DPG_ROOT/MC/config/PWGDQ/external/generator/GeneratorParamPromptJpsiToMuonEvtGen_pp13TeV.C` **choose the right one** + +- Run the command inside the simulation repository : +```shell + + o2-sim --timestamp 1669594219618 -j 4 -n 5000 -g external -m HALL MAG DIPO COMP PIPE ABSO SHIL MCH MID -o sgn --configKeyValues "GeneratorExternal.fileName=$O2DPG_ROOT/MC/config/PWGDQ/external/generator/GeneratorParamPromptJpsiToMuonEvtGen_pp13TeV.C;GeneratorExternal.funcName=GeneratorParamPromptJpsiToMuonEvtGen_pp13TeV()" + +``` + +DIGITS (this can be done locally with O2 Enviroment): + +```shell + + o2-sim-digitizer-workflow -b --sims=sgn + +``` + + +MC RECONSTRUCTION (this can be done locally with QC Environment): + +```shell + +o2-mch-reco-workflow -b | o2-qc --config json://./qc-mch-clusters.json --local-batch=MC_QC.root | o2-dpl-run -b + +``` + + +- The output file produced: `MC_QC.root` + + +## Data File + +DATA RECONSTRUCTION (this can be done locally with QC Environment): + +```shell + +o2-ctf-reader-workflow --ctf-input /Volumes/LaData/alice/data/2022/LHC22t/529691/compact -max-tf 10000 --onlyDet MCH \ | o2-mch-reco-workflow --disable-mc --disable-root-input \ | o2-qc --config json://./mch-clustermap.json --local batch="DATA_QC.root" + + +``` + +- The output file produced: `DATA_QC.root` + + +**NOTICE THAT THE OUTPUT IS A QUALITY CONTROL OBJECT (NOT A TH1F) -- FILE CONVERTION STEP STILL NEEDED** + +**For the moment, we can save the output file with the browser as a TH1F** + + + diff --git a/Modules/MUON/MCH/test/testMuonChambers.cxx b/Modules/MUON/MCH/test/testMuonChambers.cxx new file mode 100644 index 0000000000..ba7a15e38f --- /dev/null +++ b/Modules/MUON/MCH/test/testMuonChambers.cxx @@ -0,0 +1,25 @@ +/// +/// \file testMuonChambers.cxx +/// \author +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2 +{ +namespace quality_control_modules +{ +namespace muonchambers +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace muonchambers +} // namespace quality_control_modules +} // namespace o2 diff --git a/Modules/MUON/MID/CMakeLists.txt b/Modules/MUON/MID/CMakeLists.txt new file mode 100644 index 0000000000..80ca590ad7 --- /dev/null +++ b/Modules/MUON/MID/CMakeLists.txt @@ -0,0 +1,78 @@ +# ---- Library ---- + +add_library(O2QcMID) + +target_sources( + O2QcMID + PRIVATE src/RawQcCheck.cxx + src/RawQcTask.cxx + src/DigitsQcCheck.cxx + src/DigitsQcTask.cxx + src/ClustQcCheck.cxx + src/ClustQcTask.cxx + src/TracksQcCheck.cxx + src/TracksQcTask.cxx + src/CalibQcCheck.cxx + src/CalibQcTask.cxx + src/CalibMQcCheck.cxx + src/CalibMQcTask.cxx + src/DigitsHelper.cxx + src/HistoHelper.cxx + src/MIDTrending.cxx + src/TrendingTaskConfigMID.cxx + src/MIDAggregator.cxx + ) + +target_include_directories( + O2QcMID + PUBLIC $ $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcMID PUBLIC O2QualityControl O2QcMUONCommon O2::MIDRaw O2::MIDQC O2::MIDWorkflow O2::MIDGlobalMapping) + +install( + TARGETS O2QcMID + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary( + O2QcMID + HEADERS include/MID/RawQcCheck.h + include/MID/RawQcTask.h + include/MID/DigitsQcCheck.h + include/MID/DigitsQcTask.h + include/MID/ClustQcCheck.h + include/MID/ClustQcTask.h + include/MID/TracksQcCheck.h + include/MID/TracksQcTask.h + include/MID/CalibQcTask.h + include/MID/CalibQcCheck.h + include/MID/CalibMQcTask.h + include/MID/CalibMQcCheck.h + include/MID/DigitsHelper.h + include/MID/HistoHelper.h + include/MID/MIDTrending.h + include/MID/TrendingTaskConfigMID.h + include/MID/MIDAggregator.h + LINKDEF include/MID/LinkDef.h) + + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/MID DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Test(s) ---- + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} PRIVATE O2QcMID Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + +# ---- Executables ---- + +install(FILES mid-raw.json mid-digits.json DESTINATION etc) diff --git a/Modules/MUON/MID/include/MID/CalibMQcCheck.h b/Modules/MUON/MID/include/MID/CalibMQcCheck.h new file mode 100644 index 0000000000..e41fcd262f --- /dev/null +++ b/Modules/MUON/MID/include/MID/CalibMQcCheck.h @@ -0,0 +1,46 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibMQcCheck.h +/// \author Valerie Ramillien +/// + +#ifndef QC_MODULE_MID_MIDCALIBMQCCHECK_H +#define QC_MODULE_MID_MIDCALIBMQCCHECK_H + +#include "QualityControl/CheckInterface.h" + +#include "MID/HistoHelper.h" + +namespace o2::quality_control_modules::mid +{ + +class CalibMQcCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CalibMQcCheck() = default; + /// Destructor + ~CalibMQcCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + HistoHelper mHistoHelper; ///! Histogram helper + ClassDefOverride(CalibMQcCheck, 3); +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDCALIBMQCCHECK_H diff --git a/Modules/MUON/MID/include/MID/CalibMQcTask.h b/Modules/MUON/MID/include/MID/CalibMQcTask.h new file mode 100644 index 0000000000..bfc0bfdded --- /dev/null +++ b/Modules/MUON/MID/include/MID/CalibMQcTask.h @@ -0,0 +1,75 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibMQcTask.h +/// \author Valerie Ramillien + +#ifndef QC_MODULE_MID_MIDCALIBMQCTASK_H +#define QC_MODULE_MID_MIDCALIBMQCTASK_H + +#include "QualityControl/TaskInterface.h" + +#include +#include +#include "MID/DigitsHelper.h" + +class TH1F; +class TH2F; +class TH1F; +class TH2F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::mid +{ + +/// \brief Count number of digits per detector elements + +class CalibMQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + CalibMQcTask() = default; + /// Destructor + ~CalibMQcTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void resetDisplayHistos(); + DigitsHelper mDigitsHelper; ///! Digits helper + + std::unique_ptr mNbBadChannelTF{ nullptr }; + + std::unique_ptr mNoise{ nullptr }; + std::array, 4> mBendNoiseMap{}; + std::array, 4> mNBendNoiseMap{}; + + std::unique_ptr mDead{ nullptr }; + std::array, 4> mBendDeadMap{}; + std::array, 4> mNBendDeadMap{}; + + std::unique_ptr mBad{ nullptr }; + std::array, 4> mBendBadMap{}; + std::array, 4> mNBendBadMap{}; +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDCALIBMQCTASK_H diff --git a/Modules/MUON/MID/include/MID/CalibQcCheck.h b/Modules/MUON/MID/include/MID/CalibQcCheck.h new file mode 100644 index 0000000000..a31ba7de86 --- /dev/null +++ b/Modules/MUON/MID/include/MID/CalibQcCheck.h @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibQcCheck.h +/// \author Valerie Ramillien +/// + +#ifndef QC_MODULE_MID_MIDCALIBQCCHECK_H +#define QC_MODULE_MID_MIDCALIBQCCHECK_H + +#include "QualityControl/CheckInterface.h" + +#include "MID/HistoHelper.h" + +namespace o2::quality_control_modules::mid +{ + +class CalibQcCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CalibQcCheck() = default; + /// Destructor + ~CalibQcCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + float mDeadRof = 0; + HistoHelper mHistoHelper; ///! Histogram helper + + ClassDefOverride(CalibQcCheck, 3); +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDCALIBQCCHECK_H diff --git a/Modules/MUON/MID/include/MID/CalibQcTask.h b/Modules/MUON/MID/include/MID/CalibQcTask.h new file mode 100644 index 0000000000..c3506bfaf4 --- /dev/null +++ b/Modules/MUON/MID/include/MID/CalibQcTask.h @@ -0,0 +1,80 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibQcTask.h +/// \author Valerie Ramillien + +#ifndef QC_MODULE_MID_MIDCALIBQCTASK_H +#define QC_MODULE_MID_MIDCALIBQCTASK_H + +#include "QualityControl/TaskInterface.h" + +#include +#include +#include "MID/DigitsHelper.h" + +class TH1F; +class TH2F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::mid +{ + +/// \brief Count number of digits per detector elements + +class CalibQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + CalibQcTask() = default; + /// Destructor + ~CalibQcTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void resetDisplayHistos(); + + DigitsHelper mDigitsHelper; ///! Digits helper + + std::unique_ptr mNbTimeFrame{ nullptr }; + std::unique_ptr mNbNoiseROF{ nullptr }; + std::unique_ptr mNbDeadROF{ nullptr }; + + std::array, 4> mMultNoiseB{}; + std::array, 4> mMultNoiseNB{}; + + std::unique_ptr mNoise{ nullptr }; + + std::array, 4> mBendNoiseMap{}; + std::array, 4> mNBendNoiseMap{}; + + std::array, 4> mMultDeadB{}; + std::array, 4> mMultDeadNB{}; + + std::unique_ptr mDead{ nullptr }; + + std::array, 4> mBendDeadMap{}; + std::array, 4> mNBendDeadMap{}; +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDCALIBQCTASK_H diff --git a/Modules/MUON/MID/include/MID/ClustQcCheck.h b/Modules/MUON/MID/include/MID/ClustQcCheck.h new file mode 100644 index 0000000000..172a905e19 --- /dev/null +++ b/Modules/MUON/MID/include/MID/ClustQcCheck.h @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClustQcCheck.h +/// \author Valerie Ramillien +/// + +#ifndef QC_MODULE_MID_MIDCLUSTQCCHECK_H +#define QC_MODULE_MID_MIDCLUSTQCCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::mid +{ + +class ClustQcCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + ClustQcCheck() = default; + /// Destructor + ~ClustQcCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + float mClusterTF = 0; + int mOrbTF = 32; + // float scaleTime = 0.0114048; // 128 orb/TF * 3564 BC/orb * 25ns + float scaleTime = 0.0000891; // 3564 BC/orb * 25ns + float mClusterScale = 100; + + ClassDefOverride(ClustQcCheck, 3); +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDCLUSTQCCHECK_H diff --git a/Modules/MUON/MID/include/MID/ClustQcTask.h b/Modules/MUON/MID/include/MID/ClustQcTask.h new file mode 100644 index 0000000000..54740fbf1e --- /dev/null +++ b/Modules/MUON/MID/include/MID/ClustQcTask.h @@ -0,0 +1,78 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClustQcTask.h +/// \author Valerie Ramillien + +#ifndef QC_MODULE_MID_MIDCLUSTQCTASK_H +#define QC_MODULE_MID_MIDCLUSTQCTASK_H + +#include "QualityControl/TaskInterface.h" +#include "QualityControl/QcInfoLogger.h" +#include "MIDBase/Mapping.h" + +class TH1F; +class TH2F; +class TProfile; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::mid +{ + +/// \brief Count number of digits per detector elements + +class ClustQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + ClustQcTask() = default; + /// Destructor + ~ClustQcTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + std::shared_ptr mNbClusterTF{ nullptr }; + + std::shared_ptr mClusterMap11{ nullptr }; + std::shared_ptr mClusterMap12{ nullptr }; + std::shared_ptr mClusterMap21{ nullptr }; + std::shared_ptr mClusterMap22{ nullptr }; + + std::shared_ptr mMultClust11{ nullptr }; + std::shared_ptr mMultClust12{ nullptr }; + std::shared_ptr mMultClust21{ nullptr }; + std::shared_ptr mMultClust22{ nullptr }; + + std::shared_ptr mClustBCCounts{ nullptr }; + + std::shared_ptr mClustResX{ nullptr }; + std::shared_ptr mClustResY{ nullptr }; + std::shared_ptr mClustResXDetId{ nullptr }; + std::shared_ptr mClustResYDetId{ nullptr }; + + /////////////////////////// + int mROF = 0; + o2::mid::Mapping mMapping; ///< Mapping +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDCLUSTQCTASK_H diff --git a/Modules/MUON/MID/include/MID/DigitsHelper.h b/Modules/MUON/MID/include/MID/DigitsHelper.h new file mode 100644 index 0000000000..07c115d030 --- /dev/null +++ b/Modules/MUON/MID/include/MID/DigitsHelper.h @@ -0,0 +1,137 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file DigitsHelper.h +/// \author Diego Stocco + +#ifndef QC_MODULE_MID_DIGITSHELPER_H +#define QC_MODULE_MID_DIGITSHELPER_H + +#include +#include +#include +#include +#include +#include "DataFormatsMID/ColumnData.h" + +class TH1F; +class TH2F; +class TH1; +class TH2; + +namespace o2::quality_control_modules::mid +{ +class DigitsHelper +{ + public: + /// Default ctr + DigitsHelper(); + + /// Default destructor + ~DigitsHelper() = default; + + /// @brief Make the 1D histogram with the number of time a strip was fired + /// @param name Histogram name + /// @param title Histogram title + /// @return Histogram 1D + TH1F makeStripHisto(const std::string name, const std::string title) const; + + /// @brief Make the histogram with the 2D representation of the fired strips + /// @param name Histogram name + /// @param title Histogram title + /// @param cathode Bending (0) or Non-bending (1) plane + /// @return Histogram 2D + TH2F makeStripMapHisto(std::string name, std::string title, int cathode) const; + + /// @brief Make 4 histograms with the 2D representation of the fired strips per chamber + /// @param name Base histogram name + /// @param title Base histogram title + /// @param cathode Bending (0) or Non-bending (1) plane + /// @return Array of unique pointer to histograms + std::array, 4> makeStripMapHistos(std::string name, std::string title, int cathode) const; + + /// @brief Make the histogram with the 2D representation of the fired boards + /// @param name Histogram name + /// @param title Histogram title + /// @return Histogram 2D + TH2F makeBoardMapHisto(std::string name, std::string title) const; + + /// @brief Make 4 histograms with the 2D representation of the fired boards per chamber + /// @param name Base histogram name + /// @param title Base histogram title + /// @param cathode Bending (0) or Non-bending (1) plane + /// @return Array of unique pointer to histograms + std::array, 4> makeBoardMapHistos(std::string name, std::string title) const; + + /// @brief Count the number of fired strips + /// @param col Column Data + /// @param cathode Bending (0) or Non-bending (1) plane + /// @return Number of fired strips + unsigned long countDigits(const o2::mid::ColumnData& col, int cathode = -1) const; + + /// @brief Fill the 1D histogram with the number of time a strip was fired + /// @param col Column Data + /// @param histo Pointer to the histogram + void fillStripHisto(const o2::mid::ColumnData& col, TH1* histo) const; + + /// @brief Fill the 2D representation of the fired boards from the 1D strip histogram + /// @param histo Input 1D histogram with fired strips + /// @param histosB Array with the 2D representation of the fired boards per chamber. Last histogram is the sum of the previous four + void fillBoardMapHistosFromStrips(const TH1* histo, std::array, 4>& histosB, std::array, 4>& histosNB) const; + + /// @brief Fill the 2D representation of the fired strips from the 1D histogram + /// @param histo Input 1D histogram with fired strips + /// @param histosB Array with the 2D representation of the fired strips in the bending plane per chamber + /// @param histosNB Array with the 2D representation of the fired strips in the non-bending plane per chamber + void fillStripMapHistos(const TH1* histo, std::array, 4>& stripHistosB, std::array, 4>& stripHistosNB) const; + + struct MapInfo { + int cathode = 0; ///! Cathode + int chamber = 0; ///! Chamber + std::vector bins{}; ///! Bins in the 2D map histograms + }; + + struct ColumnInfo { + int firstLine; ///! First line in column + int lastLine; ///! Last line in column + int nStripsNB; ///! Number of strips in the NB plane + }; + + private: + std::unordered_map mStripsMap{}; ///! Strip id to strip idx + + std::vector mStripIdxToStripMap{}; ///! Strip index to strip map bins + std::vector mStripIdxToBoardMap{}; ///! Strip index to board map bins + + std::array mColumnInfo; ///! Column info + + /// @brief Initializes inner maps + void initMaps(); + + /// @brief Fills one bin in a quick way + /// @param ibin Bin to fill + /// @param wgt Weight + /// @param histo Histogram to fill + void FillBin(TH1* histo, int ibin, double wgt = 1.) const; + + /// @brief Fill the 2D map histogram from the 1D histogram + /// @param histo 1D histogram + /// @param histoMapB 2D map histogram for the bending plane + /// @param histoMapNB 2D map histogram for the non-bending plane + /// @param infoMap Correspondence between histogram bins + void fillMapHistos(const TH1* histo, std::array, 4>& histoMapB, std::array, 4>& histoMapNB, const std::vector& infoMap) const; + + inline int getColumnIdx(int columnId, int deId) const { return 7 * deId + columnId; } +}; + +} // namespace o2::quality_control_modules::mid + +#endif \ No newline at end of file diff --git a/Modules/MUON/MID/include/MID/DigitsQcCheck.h b/Modules/MUON/MID/include/MID/DigitsQcCheck.h new file mode 100644 index 0000000000..d448c10514 --- /dev/null +++ b/Modules/MUON/MID/include/MID/DigitsQcCheck.h @@ -0,0 +1,67 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsQcCheck.h +/// \author Bogdan Vulpescu +/// \author Valerie Ramillien + +#ifndef QC_MODULE_MID_MIDDIGITSQCCHECK_H +#define QC_MODULE_MID_MIDDIGITSQCCHECK_H + +#include "QualityControl/CheckInterface.h" + +#include +#include "QualityControl/Quality.h" +#include "MID/HistoHelper.h" + +namespace o2::quality_control_modules::mid +{ + +/// \brief Count number of digits per detector elements + +class DigitsQcCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + DigitsQcCheck() = default; + /// Destructor + ~DigitsQcCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + double mMeanMultThreshold = 10.; ///! Upper threshold on mean multiplicity + double mMinMultThreshold = 0.001; ///! Lower threshold on mean multiplicity + double mLocalBoardScale = 100.; ///! Local board scale in kHz + int mNbEmptyLocalBoard = 117; ///! Maximum number of allowed empty boards + double mLocalBoardThreshold = 400; ///! Threshold on board multiplicity (kHz) + int mNbBadLocalBoard = 10; ///! Maximum number of local boards above threshold + + int nEmptyLB = 0; + int nBadLB = 0; + double maxVal = 0; + double minVal = 1000; + int LineResp[36] = { 0 }; // NN + + std::unordered_map mQualityMap; ///! Quality map + + HistoHelper mHistoHelper; ///! Histogram helper + + ClassDefOverride(DigitsQcCheck, 3); +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDDIGITSQCCHECK_H diff --git a/Modules/MUON/MID/include/MID/DigitsQcTask.h b/Modules/MUON/MID/include/MID/DigitsQcTask.h new file mode 100644 index 0000000000..408cd2b47b --- /dev/null +++ b/Modules/MUON/MID/include/MID/DigitsQcTask.h @@ -0,0 +1,97 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsQcTask.h +/// \author Bogdan Vulpescu +/// \author Xavier Lopez +/// \author Guillaume Taillepied +/// \author Valerie Ramillien + +#ifndef QC_MODULE_MID_MIDDIGITSQCTASK_H +#define QC_MODULE_MID_MIDDIGITSQCTASK_H + +#include +#include + +#include +#include + +#include "QualityControl/TaskInterface.h" +#include "MIDBase/Mapping.h" +#include "MID/DigitsHelper.h" + +class TH1F; +class TH2F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::mid +{ + +/// \brief Count number of digits per detector elements + +class DigitsQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + DigitsQcTask() = default; + /// Destructor + ~DigitsQcTask() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void resetDisplayHistos(); + + DigitsHelper mDigitsHelper; ///! Digits helper + + std::unique_ptr mROFTimeDiff{ nullptr }; + + std::unique_ptr mNbDigitTF{ nullptr }; + + std::unique_ptr mNbLBEmpty{ nullptr }; + std::unique_ptr mNbLBHighRate{ nullptr }; + std::unique_ptr mLBHighRate{ nullptr }; + + std::unique_ptr mGBTRate{ nullptr }; + std::unique_ptr mCRURate{ nullptr }; + std::unique_ptr mEPRate{ nullptr }; + + std::array, 5> mMultHitB{}; + std::array, 5> mMultHitNB{}; + std::unique_ptr mMeanMultiHits; + + std::array, 4> mLocalBoardsMap{}; + std::unique_ptr mLocalBoardsMapTot; + + std::unique_ptr mHits; + + std::array, 4> mBendHitsMap{}; + std::array, 4> mNBendHitsMap{}; + + std::array, 36> mLineLocalResp{}; + + std::unique_ptr mDigitBCCounts{ nullptr }; + + o2::mid::Mapping mMapping; +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDDIGITSQCTASK_H diff --git a/Modules/MUON/MID/include/MID/HistoHelper.h b/Modules/MUON/MID/include/MID/HistoHelper.h new file mode 100644 index 0000000000..a63622e3cf --- /dev/null +++ b/Modules/MUON/MID/include/MID/HistoHelper.h @@ -0,0 +1,101 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file HistoHelper.h +/// \author Diego Stocco + +#ifndef QC_MODULE_MID_HISTOHELPER_H +#define QC_MODULE_MID_HISTOHELPER_H + +#include +#include + +#include "QualityControl/Quality.h" + +class TH1; +class TLatex; + +namespace o2::quality_control_modules::mid +{ +class HistoHelper +{ + + public: + /// @brief Default ctr + HistoHelper(); + + /// Default destructor + ~HistoHelper() = default; + + /// @brief Sets the number of analyzed TFs + /// @param nTFs Number of analyzed TFs + void setNTFs(unsigned long int nTFs) { mNTFs = nTFs; } + + /// @brief Gets number of analyzed TFs + /// @return Number of analyzed TFs + unsigned long int getNTFs() const { return mNTFs; } + + /// @brief Sets the number of orbits per TF + /// @param nOrbitsPerTF number of orbits per TF + void setNOrbitsPerTF(unsigned long int nOrbitsPerTF) { mNOrbitsPerTF = nOrbitsPerTF; } + + /// @brief Convert the number of analyzed TFs in seconds + /// @return Duration in seconds of the analyzed TFs + double getNTFsAsSeconds() const { return mNTFs * mNOrbitsPerTF * 0.0000891; } + + /// @brief Scale the histogram to the inverse of the analyzed TF duration (result in Hz) + /// @param histo Histogram to normalize + void normalizeHistoToHz(TH1* histo) const; + + /// @brief Scale the histogram to the inverse of the analyzed TF duration (result in kHz) + /// @param histo Histogram to normalize + void normalizeHistoTokHz(TH1* histo) const; + + /// @brief Updates the histogram title + /// @param histo Histogram to update + /// @param append Text to append + void updateTitle(TH1* histo, std::string suffix) const; + + /// @brief Returns the current time + /// @return Current time + std::string getCurrentTime() const; + + /// @brief Adds a TLatex to the histogram list of functions + /// @param histo Histogram to modify + /// @param xmin Bottom-left x position + /// @param ymin Bottom-left y position + /// @param color Text color + /// @param text Text + void addLatex(TH1* histo, double xmin, double ymin, int color, std::string text) const; + + /// @brief Gets the color for this quality + /// @param quality QC Quality + /// @return Color as integer + int getColor(const o2::quality_control::core::Quality& quality) const; + + /// @brief Add number of TFs in the title + /// @param histo Histogram to update + void updateTitleWithNTF(TH1* histo) const; + + private: + /// @brief Normalizes the histogram to the inverse of the analyzed TF duration + /// @param histo Histogram to normalize + /// @param scale Additional scaling factor + bool normalizeHisto(TH1* histo, double scale) const; + + unsigned long int mNTFs = 0; ///! Number of TFs + unsigned long int mNOrbitsPerTF = 32; ///! Number of orbits per TF + std::unordered_map mColors; +}; + +} // namespace o2::quality_control_modules::mid + +#endif \ No newline at end of file diff --git a/Modules/MUON/MID/include/MID/LinkDef.h b/Modules/MUON/MID/include/MID/LinkDef.h new file mode 100644 index 0000000000..1b5af63a71 --- /dev/null +++ b/Modules/MUON/MID/include/MID/LinkDef.h @@ -0,0 +1,24 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::mid::RawQcTask + ; +#pragma link C++ class o2::quality_control_modules::mid::RawQcCheck + ; +#pragma link C++ class o2::quality_control_modules::mid::DigitsQcTask + ; +#pragma link C++ class o2::quality_control_modules::mid::DigitsQcCheck + ; +#pragma link C++ class o2::quality_control_modules::mid::ClustQcTask + ; +#pragma link C++ class o2::quality_control_modules::mid::ClustQcCheck + ; +#pragma link C++ class o2::quality_control_modules::mid::TracksQcTask + ; +#pragma link C++ class o2::quality_control_modules::mid::TracksQcCheck + ; +#pragma link C++ class o2::quality_control_modules::mid::CalibQcTask + ; +#pragma link C++ class o2::quality_control_modules::mid::CalibQcCheck + ; +#pragma link C++ class o2::quality_control_modules::mid::CalibMQcTask + ; +#pragma link C++ class o2::quality_control_modules::mid::CalibMQcCheck + ; +#pragma link C++ class o2::quality_control_modules::mid::DigitsHelper + ; +#pragma link C++ class o2::quality_control_modules::mid::HistoHelper + ; +#pragma link C++ class o2::quality_control::postprocessing::TrendingTaskConfigMID + ; +#pragma link C++ class o2::quality_control::postprocessing::MIDTrending + ; +#pragma link C++ class o2::quality_control_modules::mid::MIDAggregator + ; + +#endif diff --git a/Modules/MUON/MID/include/MID/MIDAggregator.h b/Modules/MUON/MID/include/MID/MIDAggregator.h new file mode 100644 index 0000000000..ba9b2eda43 --- /dev/null +++ b/Modules/MUON/MID/include/MID/MIDAggregator.h @@ -0,0 +1,42 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MIDAggregator.h +/// \author V.Ramillien +/// + +#ifndef QUALITYCONTROL_MIDAGGREGATOR_H +#define QUALITYCONTROL_MIDAGGREGATOR_H + +// ROOT +#include +// QC +#include "QualityControl/AggregatorInterface.h" + +namespace o2::quality_control_modules::mid +{ + +/// \brief Example QC quality aggregator +/// \author My Name +class MIDAggregator : public o2::quality_control::checker::AggregatorInterface +{ + public: + // Override interface + void configure() override; + std::map aggregate(o2::quality_control::core::QualityObjectsMapType& qoMap) override; + + ClassDefOverride(MIDAggregator, 1); +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QUALITYCONTROL_MIDAGGREGATOR_H diff --git a/Modules/MUON/MID/include/MID/MIDTrending.h b/Modules/MUON/MID/include/MID/MIDTrending.h new file mode 100644 index 0000000000..9e0cbced5d --- /dev/null +++ b/Modules/MUON/MID/include/MID/MIDTrending.h @@ -0,0 +1,67 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MIDTrending.h +/// \author based on Piotr Konopka work +/// + +#ifndef QUALITYCONTROL_MIDTRENDING_H +#define QUALITYCONTROL_MIDTRENDING_H + +#include "MID/TrendingTaskConfigMID.h" +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" + +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +class MIDTrending : public PostProcessingInterface +{ + public: + MIDTrending() = default; + ~MIDTrending() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + struct { + Long64_t runNumber = 0; + + } mMetaData; + + void trendValues(const Trigger& t, repository::DatabaseInterface& qcdb); + void generatePlots(repository::DatabaseInterface& qcdb); + + TrendingTaskConfigMID mConfig; + Int_t ntreeentries = 0; + UInt_t mTime; + std::vector mRunList; + std::unique_ptr mTrend; + std::map mPlots; + std::unordered_map> mReductors; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_MIDTRENDING_H diff --git a/Modules/MUON/MID/include/MID/RawQcCheck.h b/Modules/MUON/MID/include/MID/RawQcCheck.h new file mode 100644 index 0000000000..8203c50d59 --- /dev/null +++ b/Modules/MUON/MID/include/MID/RawQcCheck.h @@ -0,0 +1,43 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawQcCheck.h +/// \author Bogdan Vulpescu / Xavier Lopez +/// \author Valerie Ramillien + +#ifndef QC_MODULE_MID_MIDRAWQCCHECK_H +#define QC_MODULE_MID_MIDRAWQCCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::mid +{ + +/// \brief Count number of digits per detector elements + +class RawQcCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + RawQcCheck() = default; + /// Destructor + ~RawQcCheck() override = default; + + // Override interface + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(RawQcCheck, 2); +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDRAWQCCHECK_H diff --git a/Modules/MUON/MID/include/MID/RawQcTask.h b/Modules/MUON/MID/include/MID/RawQcTask.h new file mode 100644 index 0000000000..b273f28fcc --- /dev/null +++ b/Modules/MUON/MID/include/MID/RawQcTask.h @@ -0,0 +1,97 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawQcTask.h +/// \author Bogdan Vulpescu +/// \author Xavier Lopez +/// \author Guillaume Taillepied +/// \author Valerie Ramillien + +#ifndef QC_MODULE_MID_MIDRAWQCTASK_H +#define QC_MODULE_MID_MIDRAWQCTASK_H + +#include "QualityControl/TaskInterface.h" +#include "MIDQC/RawDataChecker.h" +#include "MIDRaw/CrateMasks.h" +#include "MIDRaw/Decoder.h" +#include "MIDRaw/ElectronicsDelay.h" +#include "MIDRaw/FEEIdConfig.h" + +class TH1F; +class TH2F; +class TProfile; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::mid +{ + +/// \brief Count number of digits per detector elements + +class RawQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + RawQcTask() = default; + /// Destructor + ~RawQcTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + std::unique_ptr mDecoder{ nullptr }; + o2::mid::RawDataChecker mChecker{}; + o2::mid::FEEIdConfig mFeeIdConfig{}; + o2::mid::ElectronicsDelay mElectronicsDelay{}; + o2::mid::CrateMasks mCrateMasks{}; + + void InitMultiplicity() + { + for (int i = 0; i < 4; i++) + multHitB[i] = multHitNB[i] = 0; + }; + static int Pattern(uint16_t pattern); + // static int BPPattern(const o2::mid::ROBoard& board, uint8_t station); + // static int NBPPattern(const o2::mid::ROBoard& board, uint8_t station); + // void PatternMultiplicity(const o2::mid::ROBoard& board); + void FillMultiplicity(int multB[], int multNB[]); + + /////////////////////////// + int nEvt = 0; + int iBC = 0; + int iOrbit = 0; + int nMonitor = 0; + int nROF = 0; + int nEntriesROF = 0; + int nBoard = 0; + int multHitB[4] = { 0 }; + int multHitNB[4] = { 0 }; + + TH1F* mRawDataChecker{ nullptr }; + + TH1F* mRawBCCounts{ nullptr }; + // TProfile* mRawBCCounts{ nullptr }; + + TH2F* mRawLocalBoardsMap{ nullptr }; + TH2F* mBusyRawLocalBoards{ nullptr }; +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDRAWQCTASK_H diff --git a/Modules/MUON/MID/include/MID/TracksQcCheck.h b/Modules/MUON/MID/include/MID/TracksQcCheck.h new file mode 100644 index 0000000000..a7b435775e --- /dev/null +++ b/Modules/MUON/MID/include/MID/TracksQcCheck.h @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TracksQcCheck.h +/// \author Valerie Ramillien +/// +/// \file TracksQcCheck.h +/// \author Valerie Ramillien + +#ifndef QC_MODULE_MID_MIDTRACKSQCCHECK_H +#define QC_MODULE_MID_MIDTRACKSQCCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::mid +{ + +class TracksQcCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + TracksQcCheck() = default; + /// Destructor + ~TracksQcCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + double mRatio44Threshold; + float mTracksTF = 0; + int mOrbTF = 32; + float scaleTime = 0.0000891; // 3564 BC/orb * 25ns + int mTracksScale = 100; + + ClassDefOverride(TracksQcCheck, 2); +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDTRACKSQCCHECK_H diff --git a/Modules/MUON/MID/include/MID/TracksQcTask.h b/Modules/MUON/MID/include/MID/TracksQcTask.h new file mode 100644 index 0000000000..6c98b60877 --- /dev/null +++ b/Modules/MUON/MID/include/MID/TracksQcTask.h @@ -0,0 +1,108 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TracksQcTask.h +/// \author Valerie Ramillien +/// + +#ifndef QC_MODULE_MID_MIDTRACKSQCTASK_H +#define QC_MODULE_MID_MIDTRACKSQCTASK_H + +#include "QualityControl/TaskInterface.h" +#include "MIDBase/Mapping.h" + +class TH1F; +class TH2F; +class TProfile; +class TProfile2D; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::mid +{ + +class TracksQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + TracksQcTask() = default; + /// Destructor + ~TracksQcTask() override; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + std::shared_ptr mNbTracksTF{ nullptr }; + std::shared_ptr mMultTracks{ nullptr }; + std::shared_ptr mTrackMapXY{ nullptr }; + std::shared_ptr mTrackDevX{ nullptr }; + std::shared_ptr mTrackDevY{ nullptr }; + std::shared_ptr mTrackDevXY{ nullptr }; + std::shared_ptr mTrackTheta{ nullptr }; + std::shared_ptr mTrackEta{ nullptr }; + std::shared_ptr mTrackThetaI{ nullptr }; + std::shared_ptr mTrackEtaI{ nullptr }; + std::shared_ptr mTrackAlpha{ nullptr }; + std::shared_ptr mTrackPhi{ nullptr }; + std::shared_ptr mTrackThetaD{ nullptr }; + std::shared_ptr mTrackPT{ nullptr }; + + std::shared_ptr mTrackRatio44{ nullptr }; + std::shared_ptr mGTrackRatio44{ nullptr }; + std::shared_ptr mTrackBDetRatio44{ nullptr }; + std::shared_ptr mTrackNBDetRatio44{ nullptr }; + std::shared_ptr mTrackLocRatio44{ nullptr }; + std::shared_ptr mTrackBLocRatio44{ nullptr }; + std::shared_ptr mTrackNBLocRatio44{ nullptr }; + + std::shared_ptr mTrackBCCounts{ nullptr }; + + std::shared_ptr mTrackDetRatio44Map11{ nullptr }; + std::shared_ptr mTrackDetRatio44Map12{ nullptr }; + std::shared_ptr mTrackDetRatio44Map21{ nullptr }; + std::shared_ptr mTrackDetRatio44Map22{ nullptr }; + + std::shared_ptr mTrackLocalBoardsRatio44Map{ nullptr }; + std::shared_ptr mTrackLocalBoardsBRatio44Map{ nullptr }; + std::shared_ptr mTrackLocalBoardsNBRatio44Map{ nullptr }; + + o2::mid::Mapping mMapping; ///< Mapping + int mROF = 0; + int multTracksTot = 0; + int multTracks44Tot = 0; + int multTracksBend44 = 0; + int multTracksNBend44 = 0; + int multTraksB34MT11 = 0; + int multTraksNB34MT11 = 0; + int multTraksB34MT12 = 0; + int multTraksNB34MT12 = 0; + int multTraksB34MT21 = 0; + int multTraksNB34MT21 = 0; + int multTraksB34MT22 = 0; + int multTraksNB34MT22 = 0; + float mGlobEff = 0; + float mGlobBendEff = 0; + float mGlobNBendEff = 0; + std::unordered_map> mLocTracks; + std::unordered_map> mDetTracks; + std::unordered_map mLocColMap; +}; + +} // namespace o2::quality_control_modules::mid + +#endif // QC_MODULE_MID_MIDTRACKSQCTASK_H diff --git a/Modules/MUON/MID/include/MID/TrendingTaskConfigMID.h b/Modules/MUON/MID/include/MID/TrendingTaskConfigMID.h new file mode 100644 index 0000000000..99c7417625 --- /dev/null +++ b/Modules/MUON/MID/include/MID/TrendingTaskConfigMID.h @@ -0,0 +1,57 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskConfigMID.h +/// \author based on Piotr Konopkan work +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKCONFIGMID_H +#define QUALITYCONTROL_TRENDINGTASKCONFIGMID_H + +#include +#include +#include "QualityControl/PostProcessingConfig.h" + +namespace o2::quality_control::postprocessing +{ + +struct TrendingTaskConfigMID : PostProcessingConfig { + TrendingTaskConfigMID() = default; + TrendingTaskConfigMID(std::string name, const boost::property_tree::ptree& config); + ~TrendingTaskConfigMID() = default; + + struct Plot { + std::string name; + std::string title; + std::string varexp; + std::string selection; + std::string option; + std::string graphErrors; + }; + + struct DataSource { + std::string type; + std::string path; + std::string name; + std::string reductorName; + std::string moduleName; + }; + + bool producePlotsOnUpdate{}; + bool resumeTrend{}; + std::vector plots; + std::vector dataSources; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRENDINGTASKCONFIGMID_H diff --git a/Modules/MUON/MID/mid-digits.json b/Modules/MUON/MID/mid-digits.json new file mode 100644 index 0000000000..c64dca523e --- /dev/null +++ b/Modules/MUON/MID/mid-digits.json @@ -0,0 +1,47 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + } + }, + "tasks": { + "QcTaskMIDDigits": { + "active": "true", + "className": "o2::quality_control_modules::mid::DigitsQcTask", + "moduleName": "QcMID", + "detectorName": "MID", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "digits" + }, + "location": "remote" + } + } + }, + "dataSamplingPolicies": [ + { + "id": "digits", + "active": "true", + "machines": [], + "query": "digits:MID/DATA/0;digits_rof:MID/DATAROF/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1", + "seed": "1441" + } + ], + "blocking": "false" + } + ] +} \ No newline at end of file diff --git a/Modules/MUON/MID/mid-raw.json b/Modules/MUON/MID/mid-raw.json new file mode 100755 index 0000000000..3baa943f52 --- /dev/null +++ b/Modules/MUON/MID/mid-raw.json @@ -0,0 +1,79 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "QcTaskMIDRaw": { + "active": "true", + "className": "o2::quality_control_modules::mid::RawQcTask", + "moduleName": "QcMID", + "detectorName": "MID", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "rawdata" + }, + "taskParameters": { + "feeId-config-file": "", + "crate-masks-file": "", + "electronics-delays-file": "" + } + } + }, + "checks": { + "QcCheckMIDRaw": { + "active": "true", + "className": "o2::quality_control_modules::mid::RawQcCheck", + "moduleName": "QcMID", + "detectorName": "MID", + "policy": "OnAny", + "dataSource": [ + { + "type": "Task", + "name": "QcTaskMIDRaw", + "MOs": [ + "" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "rawdata", + "active": "true", + "machines": [], + "query": "rawdata:MID/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/MUON/MID/src/CalibMQcCheck.cxx b/Modules/MUON/MID/src/CalibMQcCheck.cxx new file mode 100644 index 0000000000..f0530133fa --- /dev/null +++ b/Modules/MUON/MID/src/CalibMQcCheck.cxx @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibMQcCheck.cxx +/// \author Valerie Ramillien +/// + +#include "MID/CalibMQcCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +// ROOT +#include +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::mid +{ + +Quality CalibMQcCheck::check(std::map>* moMap) +{ + // printf("\n*********** CalibMQcCheck ****** check \n"); + Quality result = Quality::Null; + for (auto& item : *moMap) { + if (item.second->getName() == "NbBadChannelTF") { + auto nbTFs = dynamic_cast(item.second->getObject())->GetBinContent(1); + if (nbTFs) { + mHistoHelper.setNTFs(nbTFs); + result = (nbTFs == 0) ? Quality::Bad : Quality::Good; + } + return result; + } + } + return result; +} + +void CalibMQcCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + + auto currentTime = mHistoHelper.getCurrentTime(); + mHistoHelper.updateTitle(dynamic_cast(mo->getObject()), mHistoHelper.getCurrentTime()); + if (mHistoHelper.getNTFs() == 0) { + checkResult = Quality::Bad; + } + auto color = mHistoHelper.getColor(checkResult); + if (mo->getName().find("BendBadMap") != std::string::npos) { + auto* h2 = static_cast(mo->getObject()); + if (h2) { + if (checkResult == Quality::Bad) { + mHistoHelper.addLatex(h2, 0.2, 0.8, color, "Calib objects not produced!"); + } + mHistoHelper.addLatex(h2, 0.15, 0.5, color, fmt::format("Quality::{}", checkResult.getName())); + } + } +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/CalibMQcTask.cxx b/Modules/MUON/MID/src/CalibMQcTask.cxx new file mode 100644 index 0000000000..b9231c8e22 --- /dev/null +++ b/Modules/MUON/MID/src/CalibMQcTask.cxx @@ -0,0 +1,166 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \author Valerie Ramillien + +#include "MID/CalibMQcTask.h" + +#include "fmt/format.h" +#include "TH1.h" +#include "TH2.h" +#include "TStyle.h" + +#include "QualityControl/QcInfoLogger.h" + +#include "Framework/InputRecord.h" +#include "DataFormatsMID/ColumnData.h" +#include "MIDWorkflow/ColumnDataSpecsUtils.h" + +namespace o2::quality_control_modules::mid +{ + +CalibMQcTask::~CalibMQcTask() +{ +} + +void CalibMQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + gStyle->SetPalette(kRainBow); + + std::array chId{ "11", "12", "21", "22" }; + + mNbBadChannelTF = std::make_unique("NbBadChannelTF", "Anaylyzed timeframes", 1, 0, 1.); + getObjectsManager()->startPublishing(mNbBadChannelTF.get()); + + mNoise = std::make_unique(mDigitsHelper.makeStripHisto("MNoiseStrips", "Noise strips")); + getObjectsManager()->startPublishing(mNoise.get()); + + mBendNoiseMap = mDigitsHelper.makeStripMapHistos("MBendNoiseMap", "Bending Noise Map", 0); + for (auto& histo : mBendNoiseMap) { + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), "COLZ"); + } + + mNBendNoiseMap = mDigitsHelper.makeStripMapHistos("MNBendNoiseMap", "Non-Bending Noise Map", 1); + for (auto& histo : mNBendNoiseMap) { + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), "COLZ"); + } + + mDead = std::make_unique(mDigitsHelper.makeStripHisto("MDeadStrips", "Dead strips")); + getObjectsManager()->startPublishing(mDead.get()); + + mBendDeadMap = mDigitsHelper.makeStripMapHistos("MBendDeadMap", "Bending Dead Map", 0); + for (auto& histo : mBendDeadMap) { + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), "COLZ"); + } + + mNBendDeadMap = mDigitsHelper.makeStripMapHistos("MNBendDeadMap", "Non-Bending Dead Map", 1); + for (auto& histo : mNBendDeadMap) { + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), "COLZ"); + } + + mBad = std::make_unique(mDigitsHelper.makeStripHisto("MBadStrips", "Bad strips")); + getObjectsManager()->startPublishing(mBad.get()); + + mBendBadMap = mDigitsHelper.makeStripMapHistos("MBendBadMap", "Bending Bad Map", 0); + for (auto& histo : mBendBadMap) { + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), "COLZ"); + } + + mNBendBadMap = mDigitsHelper.makeStripMapHistos("MNBendBadMap", "Non-Bending Bad Map", 1); + for (auto& histo : mNBendBadMap) { + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), "COLZ"); + } +} + +void CalibMQcTask::startOfActivity(const Activity& /*activity*/) +{ + reset(); +} + +void CalibMQcTask::startOfCycle() +{ +} + +void CalibMQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + mNbBadChannelTF->Fill(0.5); + + auto noises = ctx.inputs().get>("noisych"); + auto deads = ctx.inputs().get>("deadch"); + auto bads = ctx.inputs().get>("badch"); + + for (auto& col : noises) { + mDigitsHelper.fillStripHisto(col, mNoise.get()); + } + for (auto& col : deads) { + mDigitsHelper.fillStripHisto(col, mDead.get()); + } + for (auto& col : bads) { + mDigitsHelper.fillStripHisto(col, mBad.get()); + } +} + +void CalibMQcTask::endOfCycle() +{ + // Fill here the 2D representation of the fired strips + // First reset the old histograms + resetDisplayHistos(); + + // Then fill from the strip histogram + mDigitsHelper.fillStripMapHistos(mNoise.get(), mBendNoiseMap, mNBendNoiseMap); + mDigitsHelper.fillStripMapHistos(mDead.get(), mBendDeadMap, mNBendDeadMap); + mDigitsHelper.fillStripMapHistos(mBad.get(), mBendBadMap, mNBendBadMap); +} + +void CalibMQcTask::endOfActivity(const Activity& /*activity*/) +{ + reset(); +} + +void CalibMQcTask::resetDisplayHistos() +{ + for (auto& histo : mBendNoiseMap) { + histo->Reset(); + } + for (auto& histo : mNBendNoiseMap) { + histo->Reset(); + } + for (auto& histo : mBendDeadMap) { + histo->Reset(); + } + for (auto& histo : mNBendDeadMap) { + histo->Reset(); + } + for (auto& histo : mBendBadMap) { + histo->Reset(); + } + for (auto& histo : mNBendBadMap) { + histo->Reset(); + } +} + +void CalibMQcTask::reset() +{ + mNbBadChannelTF->Reset(); + mNoise->Reset(); + mDead->Reset(); + mBad->Reset(); + resetDisplayHistos(); +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/CalibQcCheck.cxx b/Modules/MUON/MID/src/CalibQcCheck.cxx new file mode 100644 index 0000000000..69f9db1207 --- /dev/null +++ b/Modules/MUON/MID/src/CalibQcCheck.cxx @@ -0,0 +1,87 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibQcCheck.cxx +/// \author Valerie Ramillien +/// + +#include "MID/CalibQcCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::mid +{ +void CalibQcCheck::configure() +{ + ILOG(Info, Devel) << "configure CalibQcCheck" << ENDM; + + if (auto param = mCustomParameters.find("NbOrbitPerTF"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - NbOrbitPerTF: " << param->second << ENDM; + mHistoHelper.setNOrbitsPerTF(std::stol(param->second)); + } +} +Quality CalibQcCheck::check(std::map>* moMap) +{ + // printf("\n*********** CalibQcCheck ****** check \n"); + Quality result = Quality::Null; + + for (auto& item : *moMap) { + if (item.second->getName() == "NbTimeFrame") { + mHistoHelper.setNTFs(dynamic_cast(item.second->getObject())->GetBinContent(1)); + result = mHistoHelper.getNTFs() == 0 ? Quality::Bad : Quality::Good; + } else if (item.second->getName() == "NbDeadROF") { + mDeadRof = dynamic_cast(item.second->getObject())->GetBinContent(1); + } + } + return result; +} + +void CalibQcCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + auto currentTime = mHistoHelper.getCurrentTime(); + mHistoHelper.updateTitle(dynamic_cast(mo->getObject()), currentTime); + + if (mo->getName().find("Map") != std::string::npos || mo->getName().find("Strips") != std::string::npos) { + // Normalize Map and Strips objects + if (mo->getName().find("Noise") != std::string::npos) { + // Scale histograms with noise info + auto histo = dynamic_cast(mo->getObject()); + if (histo) { + mHistoHelper.normalizeHistoTokHz(histo); + if (mo->getName().find("Map") != std::string::npos) { + histo->SetMaximum(10.); + } + } + } else if (mo->getName().find("Dead") != std::string::npos) { + if (mDeadRof > 0.) { + // Scale histograms with dead channels info + auto histo = dynamic_cast(mo->getObject()); + if (histo) { + histo->Scale(100. / mDeadRof); + mHistoHelper.updateTitle(histo, " (%)"); + } + } + } + } +} +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/CalibQcTask.cxx b/Modules/MUON/MID/src/CalibQcTask.cxx new file mode 100644 index 0000000000..c9e7bf3b6e --- /dev/null +++ b/Modules/MUON/MID/src/CalibQcTask.cxx @@ -0,0 +1,205 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \author Valerie Ramillien + +#include "MID/CalibQcTask.h" + +#include "fmt/format.h" +#include "TH1.h" +#include "TH2.h" + +#include "QualityControl/QcInfoLogger.h" + +#include "Framework/InputRecord.h" +#include "DataFormatsMID/ColumnData.h" +#include "DataFormatsMID/ROFRecord.h" +#include "MIDBase/DetectorParameters.h" +#include "MIDWorkflow/ColumnDataSpecsUtils.h" + +namespace o2::quality_control_modules::mid +{ + +CalibQcTask::~CalibQcTask() +{ +} + +void CalibQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Info, Devel) << "initialize CalibQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + mNbTimeFrame = std::make_unique("NbTimeFrame", "NbTimeFrame", 1, 0, 1.); + getObjectsManager()->startPublishing(mNbTimeFrame.get()); + + mNbNoiseROF = std::make_unique("NbNoiseROF", "NbNoiseROF", 1, 0, 1.); + getObjectsManager()->startPublishing(mNbNoiseROF.get()); + + mNbDeadROF = std::make_unique("NbDeadROF", "NbDeadROF", 1, 0, 1.); + getObjectsManager()->startPublishing(mNbDeadROF.get()); + + std::array chId{ "11", "12", "21", "22" }; + + // Noise strips Histograms :: + for (int ich = 0; ich < 4; ++ich) { + mMultNoiseB[ich] = std::make_unique(fmt::format("MultNoiseMT{}B", chId[ich]).c_str(), fmt::format("Multiplicity Noise strips - MT{} bending plane", chId[ich]).c_str(), 300, 0, 300); + getObjectsManager()->startPublishing(mMultNoiseB[ich].get()); + mMultNoiseNB[ich] = std::make_unique(fmt::format("MultNoiseMT{}NB", chId[ich]).c_str(), fmt::format("Multiplicity Noise strips - MT{} non-bending plane", chId[ich]).c_str(), 300, 0, 300); + getObjectsManager()->startPublishing(mMultNoiseNB[ich].get()); + } + + mNoise = std::make_unique(mDigitsHelper.makeStripHisto("NoiseStrips", "Noise strips")); + getObjectsManager()->startPublishing(mNoise.get()); + + mBendNoiseMap = mDigitsHelper.makeStripMapHistos("BendNoiseMap", "Bending Noise Map", 0); + for (auto& histo : mBendNoiseMap) { + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), "COLZ"); + } + + mNBendNoiseMap = mDigitsHelper.makeStripMapHistos("NBendNoiseMap", "Non-Bending Noise Map", 1); + for (auto& histo : mNBendNoiseMap) { + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), "COLZ"); + } + + for (int ich = 0; ich < 4; ++ich) { + mMultDeadB[ich] = std::make_unique(fmt::format("MultDeadMT{}B", chId[ich]).c_str(), fmt::format("Multiplicity Dead strips - MT{} bending plane", chId[ich]).c_str(), 300, 0, 300); + getObjectsManager()->startPublishing(mMultDeadB[ich].get()); + mMultDeadNB[ich] = std::make_unique(fmt::format("MultDeadMT{}NB", chId[ich]).c_str(), fmt::format("Multiplicity Dead strips - MT{} non-bending plane", chId[ich]).c_str(), 300, 0, 300); + getObjectsManager()->startPublishing(mMultDeadNB[ich].get()); + } + + mDead = std::make_unique(mDigitsHelper.makeStripHisto("DeadStrips", "Dead strips")); + getObjectsManager()->startPublishing(mDead.get()); + + mBendDeadMap = mDigitsHelper.makeStripMapHistos("BendDeadMap", "Bending Dead Map", 0); + for (auto& histo : mBendDeadMap) { + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), "COLZ"); + } + + mNBendDeadMap = mDigitsHelper.makeStripMapHistos("NBendDeadMap", "Non-Bending Dead Map", 1); + for (auto& histo : mNBendDeadMap) { + getObjectsManager()->startPublishing(histo.get()); + getObjectsManager()->setDefaultDrawOptions(histo.get(), "COLZ"); + } +} + +void CalibQcTask::startOfActivity(const Activity& /*activity*/) +{ + reset(); +} + +void CalibQcTask::startOfCycle() +{ +} + +void CalibQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + mNbTimeFrame->Fill(0.5, 1.); + + auto noises = ctx.inputs().get>("noise"); + auto noiserofs = ctx.inputs().get>("noiserofs"); + + auto deads = ctx.inputs().get>("dead"); + auto deadrofs = ctx.inputs().get>("deadrofs"); + + // Noise + std::array evtSizeB{}; + std::array evtSizeNB{}; + + for (const auto& rof : noiserofs) { + evtSizeB.fill(0); + evtSizeNB.fill(0); + mNbNoiseROF->Fill(0.5, 1.); + for (auto& col : noises.subspan(rof.firstEntry, rof.nEntries)) { + auto ich = o2::mid::detparams::getChamber(col.deId); + evtSizeB[ich] = mDigitsHelper.countDigits(col, 0); + evtSizeNB[ich] = mDigitsHelper.countDigits(col, 1); + mDigitsHelper.fillStripHisto(col, mNoise.get()); + } + for (int ich = 0; ich < 4; ++ich) { + mMultNoiseB[ich]->Fill(evtSizeB[ich]); + mMultNoiseNB[ich]->Fill(evtSizeNB[ich]); + } + } + + for (const auto& rof : deadrofs) { + mNbDeadROF->Fill(0.5, 1.); + evtSizeB.fill(0); + evtSizeNB.fill(0); + for (auto& col : deads.subspan(rof.firstEntry, rof.nEntries)) { + auto ich = o2::mid::detparams::getChamber(col.deId); + evtSizeB[ich] = mDigitsHelper.countDigits(col, 0); + evtSizeNB[ich] = mDigitsHelper.countDigits(col, 1); + mDigitsHelper.fillStripHisto(col, mDead.get()); + } + for (int ich = 0; ich < 4; ++ich) { + mMultDeadB[ich]->Fill(evtSizeB[ich]); + mMultDeadNB[ich]->Fill(evtSizeNB[ich]); + } + } +} + +void CalibQcTask::endOfCycle() +{ + // Fill here the 2D representation of the fired strips + // First reset the old histograms + resetDisplayHistos(); + + // Then fill from the strip histogram + mDigitsHelper.fillStripMapHistos(mNoise.get(), mBendNoiseMap, mNBendNoiseMap); + mDigitsHelper.fillStripMapHistos(mDead.get(), mBendDeadMap, mNBendDeadMap); +} + +void CalibQcTask::endOfActivity(const Activity& /*activity*/) +{ + reset(); +} + +void CalibQcTask::resetDisplayHistos() +{ + for (auto& histo : mBendNoiseMap) { + histo->Reset(); + } + for (auto& histo : mNBendNoiseMap) { + histo->Reset(); + } + for (auto& histo : mBendDeadMap) { + histo->Reset(); + } + for (auto& histo : mNBendDeadMap) { + histo->Reset(); + } +} + +void CalibQcTask::reset() +{ + // clean all the monitor objects here + + ILOG(Info, Devel) << "Resetting the histogram" << ENDM; + + mNbTimeFrame->Reset(); + mNbNoiseROF->Reset(); + mNbDeadROF->Reset(); + + for (auto& histo : mMultNoiseB) { + histo->Reset(); + } + for (auto& histo : mMultNoiseNB) { + histo->Reset(); + } + mNoise->Reset(); + mDead->Reset(); + resetDisplayHistos(); +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/ClustQcCheck.cxx b/Modules/MUON/MID/src/ClustQcCheck.cxx new file mode 100644 index 0000000000..9978086583 --- /dev/null +++ b/Modules/MUON/MID/src/ClustQcCheck.cxx @@ -0,0 +1,158 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClustQcCheck.cxx +/// \author Valerie Ramillien +/// + +#include "MID/ClustQcCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::mid +{ +void ClustQcCheck::configure() +{ // default params :: + if (auto param = mCustomParameters.find("NbOrbitPerTF"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - :NbOrbitPerTF " << param->second << ENDM; + mOrbTF = stof(param->second); + } + if (auto param = mCustomParameters.find("ClusterScale"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - :ClusterScale " << param->second << ENDM; + mClusterScale = stof(param->second); + } +} +Quality ClustQcCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + + (void)moName; + if (mo->getName() == "NbClusterTF") { + auto* h = dynamic_cast(mo->getObject()); + mClusterTF = h->GetBinContent(1); + } + + float scale = 1 / (mClusterTF * scaleTime * mOrbTF); // (Hz) + if (mo->getName() == "ClusterMap11") { + auto* h2 = dynamic_cast(mo->getObject()); + h2->Scale(scale); + result = Quality::Good; + } + if (mo->getName() == "ClusterMap12") { + auto* h2 = dynamic_cast(mo->getObject()); + h2->Scale(scale); + result = Quality::Good; + } + if (mo->getName() == "ClusterMap21") { + auto* h2 = dynamic_cast(mo->getObject()); + h2->Scale(scale); + result = Quality::Good; + } + if (mo->getName() == "ClusterMap22") { + auto* h2 = dynamic_cast(mo->getObject()); + h2->Scale(scale); + result = Quality::Good; + } + } + return result; +} + +static void updateTitle(TH1* hist, std::string suffix) +{ + if (!hist) { + return; + } + TString title = hist->GetTitle(); + title.Append(" "); + title.Append(suffix.c_str()); + hist->SetTitle(title); +} +static std::string getCurrentTime() +{ + time_t t; + time(&t); + + struct tm* tmp; + tmp = localtime(&t); + + char timestr[500]; + strftime(timestr, sizeof(timestr), "(%x - %X)", tmp); + + std::string result = timestr; + return result; +} +static TLatex* drawLatex(double xmin, double ymin, Color_t color, TString text) +{ + + TLatex* tl = new TLatex(xmin, ymin, Form("%s", text.Data())); + tl->SetNDC(); + tl->SetTextFont(22); // Normal 42 + tl->SetTextSize(0.08); + tl->SetTextColor(color); + + return tl; +} +void ClustQcCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + + if (mo->getName() == "ClusterMap11") { + auto* h2 = dynamic_cast(mo->getObject()); + if (h2) { + updateTitle(h2, "(Hz)"); + updateTitle(h2, Form("- TF=%3.0f -", mClusterTF)); + updateTitle(h2, getCurrentTime()); + h2->SetMaximum(mClusterScale); + } + } + if (mo->getName() == "ClusterMap12") { + auto* h2 = dynamic_cast(mo->getObject()); + if (h2) { + updateTitle(h2, "(Hz)"); + updateTitle(h2, Form("- TF=%3.0f -", mClusterTF)); + updateTitle(h2, getCurrentTime()); + h2->SetMaximum(mClusterScale); + } + } + if (mo->getName() == "ClusterMap21") { + auto* h2 = dynamic_cast(mo->getObject()); + if (h2) { + updateTitle(h2, "(Hz)"); + updateTitle(h2, Form("- TF=%3.0f -", mClusterTF)); + updateTitle(h2, getCurrentTime()); + h2->SetMaximum(mClusterScale); + } + } + if (mo->getName() == "ClusterMap22") { + auto* h2 = dynamic_cast(mo->getObject()); + if (h2) { + updateTitle(h2, "(Hz)"); + updateTitle(h2, Form("- TF=%3.0f -", mClusterTF)); + updateTitle(h2, getCurrentTime()); + h2->SetMaximum(mClusterScale); + } + } +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/ClustQcTask.cxx b/Modules/MUON/MID/src/ClustQcTask.cxx new file mode 100644 index 0000000000..eff08841fc --- /dev/null +++ b/Modules/MUON/MID/src/ClustQcTask.cxx @@ -0,0 +1,219 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClustQcTask.cxx +/// \author Valerie Ramillien + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "MID/ClustQcTask.h" +#include +#include "Framework/DataRefUtils.h" +#include "DataFormatsMID/Cluster.h" +#include "DataFormatsMID/ROFRecord.h" +#include "MIDBase/DetectorParameters.h" +#include "MIDBase/GeometryParameters.h" +#include "MIDWorkflow/ColumnDataSpecsUtils.h" + +#define MID_NDE 72 +#define DZpos 10 + +namespace o2::quality_control_modules::mid +{ + +ClustQcTask::~ClustQcTask() +{ +} + +void ClustQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + // ILOG(Info, Devel) << "initialize ClusterQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + mNbClusterTF = std::make_shared("NbClusterTF", "NbTimeFrame", 1, 0, 1.); + getObjectsManager()->startPublishing(mNbClusterTF.get()); + + mMultClust11 = std::make_shared("MultClust11", "Multiplicity Clusters - MT11 ", 100, 0, 100); + getObjectsManager()->startPublishing(mMultClust11.get()); + mMultClust12 = std::make_shared("MultClust12", "Multiplicity Clusters - MT12 ", 100, 0, 100); + getObjectsManager()->startPublishing(mMultClust12.get()); + mMultClust21 = std::make_shared("MultClust21", "Multiplicity Clusters - MT21 ", 100, 0, 100); + getObjectsManager()->startPublishing(mMultClust21.get()); + mMultClust22 = std::make_shared("MultClust22", "Multiplicity Clusters - MT22 ", 100, 0, 100); + getObjectsManager()->startPublishing(mMultClust22.get()); + + mClusterMap11 = std::make_shared("ClusterMap11", "Cluster Map MT11", 300, -300., 300., 300, -300., 300.); + getObjectsManager()->startPublishing(mClusterMap11.get()); + mClusterMap11->GetXaxis()->SetTitle("X Position (cm)"); + mClusterMap11->GetYaxis()->SetTitle("Y Position (cm)"); + mClusterMap11->SetOption("colz"); + mClusterMap11->SetStats(0); + + mClusterMap12 = std::make_shared("ClusterMap12", "Cluster Map MT12", 300, -300., 300., 300, -300., 300.); + getObjectsManager()->startPublishing(mClusterMap12.get()); + mClusterMap12->GetXaxis()->SetTitle("X Position (cm)"); + mClusterMap12->GetYaxis()->SetTitle("Y Position (cm)"); + mClusterMap12->SetOption("colz"); + mClusterMap12->SetStats(0); + + mClusterMap21 = std::make_shared("ClusterMap21", "Cluster Map MT21", 300, -300., 300., 300, -300., 300.); + getObjectsManager()->startPublishing(mClusterMap21.get()); + mClusterMap21->GetXaxis()->SetTitle("X Position (cm)"); + mClusterMap21->GetYaxis()->SetTitle("Y Position (cm)"); + mClusterMap21->SetOption("colz"); + mClusterMap21->SetStats(0); + + mClusterMap22 = std::make_shared("ClusterMap22", "Cluster Map MT22", 300, -300., 300., 300, -300., 300.); + getObjectsManager()->startPublishing(mClusterMap22.get()); + mClusterMap22->GetXaxis()->SetTitle("X Position (cm)"); + mClusterMap22->GetYaxis()->SetTitle("Y Position (cm)"); + mClusterMap22->SetOption("colz"); + mClusterMap22->SetStats(0); + + // mClustBCCounts = std::make_shared("ClustBCCounts", "Cluster Bunch Crossing Counts", o2::constants::lhc::LHCMaxBunches, 0., o2::constants::lhc::LHCMaxBunches); + mClustBCCounts = std::make_shared("ClustBCCounts", "Mean Clusters in Bunch Crossing", o2::constants::lhc::LHCMaxBunches, 0., o2::constants::lhc::LHCMaxBunches); + getObjectsManager()->startPublishing(mClustBCCounts.get()); + mClustBCCounts->GetXaxis()->SetTitle("BC"); + mClustBCCounts->GetYaxis()->SetTitle("Mean Clusters nb"); + + mClustResX = std::make_shared("ClustResX", "Cluster X Resolution ", 300, 0, 30); + getObjectsManager()->startPublishing(mClustResX.get()); + mClustResX->GetXaxis()->SetTitle("X Resolution (cm)"); + mClustResX->GetYaxis()->SetTitle("Entry"); + + mClustResY = std::make_shared("ClustResY", "Cluster Y Resolution ", 300, 0, 30); + getObjectsManager()->startPublishing(mClustResY.get()); + mClustResY->GetXaxis()->SetTitle("Y Resolution (cm)"); + mClustResY->GetYaxis()->SetTitle("Entry"); + + mClustResXDetId = std::make_shared("ClustResXDetId", "Cluster X Resolution vs DetID", MID_NDE, 0, MID_NDE, 300, 0, 30); + getObjectsManager()->startPublishing(mClustResXDetId.get()); + mClustResXDetId->GetXaxis()->SetTitle("DetID"); + mClustResXDetId->GetYaxis()->SetTitle("X Resolution (cm)"); + mClustResXDetId->SetOption("colz"); + mClustResXDetId->SetStats(0); + + mClustResYDetId = std::make_shared("ClustResYDetId", "Cluster Y Resolution vs DetID", MID_NDE, 0, MID_NDE, 300, 0, 30); + getObjectsManager()->startPublishing(mClustResYDetId.get()); + mClustResYDetId->GetXaxis()->SetTitle("DetID"); + mClustResYDetId->GetYaxis()->SetTitle("Y Resolution (cm)"); + mClustResYDetId->SetOption("colz"); + mClustResYDetId->SetStats(0); +} + +void ClustQcTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Info, Devel) << "startOfActivity" << ENDM; + reset(); +} + +void ClustQcTask::startOfCycle() +{ +} + +void ClustQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + + mNbClusterTF->Fill(0.5, 1.); + + auto clusters = ctx.inputs().get>("clusters"); + auto rofs = ctx.inputs().get>("clusterrofs"); + + // auto clusters = o2::mid::specs::getData(ctx, "clusters", o2::mid::EventType::Standard); + // auto rofs = o2::mid::specs::getRofs(ctx, "clusters", o2::mid::EventType::Standard); + + int multClusterMT11 = 0; + int multClusterMT12 = 0; + int multClusterMT21 = 0; + int multClusterMT22 = 0; + + std::pair prevSize; + o2::InteractionRecord prevIr; + + for (const auto& rofRecord : rofs) { // loop ROFRecords == Events // + // printf("========================================================== \n"); + // printf("Clusters :: %05d ROF with first entry %05zu and nentries %02zu , BC %05d, ORB %05d , EventType %02d\n", nROF, rofRecord.firstEntry, rofRecord.nEntries, rofRecord.interactionRecord.bc, rofRecord.interactionRecord.orbit,rofRecord.eventType); + // eventType:: Standard = 0, Calib = 1, FET = 2 + multClusterMT11 = 0; + multClusterMT12 = 0; + multClusterMT21 = 0; + multClusterMT22 = 0; + mROF++; + + for (auto& cluster : clusters.subspan(rofRecord.firstEntry, rofRecord.nEntries)) { // loop Cluster in ROF// + mClustBCCounts->Fill(rofRecord.interactionRecord.bc, rofRecord.nEntries); + // std::cout << " =>> " << cluster << std::endl; + mClustResX->Fill(cluster.getEX()); + mClustResXDetId->Fill(cluster.deId, cluster.getEX()); + mClustResY->Fill(cluster.getEY()); + mClustResYDetId->Fill(cluster.deId, cluster.getEY()); + + for (int i = 0; i < 4; i++) { + if ((cluster.getZ() >= o2::mid::geoparams::DefaultChamberZ[i] - DZpos) && (cluster.getZ() <= o2::mid::geoparams::DefaultChamberZ[i] + DZpos)) { + if (i == 0) { + multClusterMT11 += 1; + mClusterMap11->Fill(cluster.getX(), cluster.getY(), 1); + } else if (i == 1) { + multClusterMT12 += 1; + mClusterMap12->Fill(cluster.getX(), cluster.getY(), 1); + } else if (i == 2) { + multClusterMT21 += 1; + mClusterMap21->Fill(cluster.getX(), cluster.getY(), 1); + } else if (i == 3) { + multClusterMT22 += 1; + mClusterMap22->Fill(cluster.getX(), cluster.getY(), 1); + } + } + } // loop on MT // + } // cluster in ROF // + mMultClust11->Fill(multClusterMT11); + mMultClust12->Fill(multClusterMT12); + mMultClust21->Fill(multClusterMT21); + mMultClust22->Fill(multClusterMT22); + } // ROFRecords // +} + +void ClustQcTask::endOfCycle() +{ +} + +void ClustQcTask::endOfActivity(const Activity& /*activity*/) +{ + reset(); +} + +void ClustQcTask::reset() +{ + mNbClusterTF->Reset(); + + mClusterMap11->Reset(); + mClusterMap12->Reset(); + mClusterMap21->Reset(); + mClusterMap22->Reset(); + mClustBCCounts->Reset(); + mMultClust11->Reset(); + mMultClust12->Reset(); + mMultClust21->Reset(); + mMultClust22->Reset(); +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/DigitsHelper.cxx b/Modules/MUON/MID/src/DigitsHelper.cxx new file mode 100644 index 0000000000..5c663afe53 --- /dev/null +++ b/Modules/MUON/MID/src/DigitsHelper.cxx @@ -0,0 +1,244 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file DigitsHelper.h +/// \author Diego Stocco + +#include "MID/DigitsHelper.h" + +#include +#include "TH1.h" +#include "TH2.h" +#include "MIDRaw/CrateMapper.h" +#include "MIDRaw/CrateParameters.h" +#include "MIDBase/DetectorParameters.h" +#include "MIDBase/GeometryParameters.h" +#include "MIDGlobalMapping/GlobalMapper.h" + +namespace o2::quality_control_modules::mid +{ +DigitsHelper::DigitsHelper() +{ + initMaps(); +} + +void DigitsHelper::initMaps() +{ + o2::mid::GlobalMapper gm; + auto infos = gm.buildStripsInfo(); + o2::mid::CrateMapper cm; + + auto stripHistoB = makeStripMapHisto("templateStripB", "templateStripB", 0); + auto stripHistoNB = makeStripMapHisto("templateStripNB", "templateStripNB", 1); + auto boardHisto = makeBoardMapHisto("templateBoard", "templateBoard"); + + for (auto it = infos.begin(), end = infos.end(); it != end; ++it) { + auto stripIdx = std::distance(infos.begin(), it); + + mStripsMap[it->id] = stripIdx; + + int irpc = o2::mid::detparams::getRPCLine(it->deId); + bool isRightSide = o2::mid::detparams::isRightSide(it->deId); + + int firstLine = gm.getMapping().getFirstBoardBP(it->columnId, it->deId); + int lastLine = gm.getMapping().getLastBoardBP(it->columnId, it->deId); + + int xbinOffsetBoard = 7 - it->columnId; + if (isRightSide) { + xbinOffsetBoard = 15 - xbinOffsetBoard; + } + int ybinOffsetBoard = 4 * irpc + 1; + + std::vector stripMapBins; + std::vector boardMapBins; + + if (it->cathode == 0) { + // BP + int ybinOffsetStrip = 64 * irpc + 1; + int pitchB = it->ywidth; + int iline = it->lineId; + int istrip = it->stripId; + for (int ibin = 0; ibin < pitchB; ++ibin) { + stripMapBins.emplace_back(stripHistoB.GetBin(xbinOffsetBoard, ybinOffsetStrip + pitchB * (16 * iline + istrip) + ibin)); + } + for (int ibin = 0; ibin < pitchB; ++ibin) { + boardMapBins.emplace_back(boardHisto.GetBin(xbinOffsetBoard, ybinOffsetBoard + pitchB * iline + ibin)); + } + } else { + // NBP + int xbinOffsetStrip = 16 * (7 + it->columnId) + 1; + int pitchNB = std::abs(it->xwidth); + if (it->columnId == 6) { + pitchNB = 2; + } + if (o2::mid::geoparams::isShortRPC(it->deId) && it->columnId == 1) { + xbinOffsetStrip += 8; + } + if (lastLine != 2) { + lastLine = 3; + } + int binWidth = pitchNB / 2; + int istrip = it->stripId; + for (int iline = firstLine; iline <= lastLine; ++iline) { + for (int ibin = 0; ibin < binWidth; ++ibin) { + int xbinStrip = xbinOffsetStrip + binWidth * istrip + ibin; + if (!isRightSide) { + xbinStrip = stripHistoNB.GetNbinsX() - xbinStrip + 1; + } + stripMapBins.emplace_back(stripHistoNB.GetBin(xbinStrip, ybinOffsetBoard + iline)); + } + boardMapBins.emplace_back(boardHisto.GetBin(xbinOffsetBoard, ybinOffsetBoard + iline)); + } + } + MapInfo info; + info.cathode = it->cathode; + info.chamber = o2::mid::detparams::getChamber(it->deId); + info.bins = stripMapBins; + mStripIdxToStripMap.emplace_back(info); + info.bins = boardMapBins; + mStripIdxToBoardMap.emplace_back(info); + } + + for (int ide = 0; ide < o2::mid::detparams::NDetectionElements; ++ide) { + for (int icol = gm.getMapping().getFirstColumn(ide); icol < 7; ++icol) { + int idx = getColumnIdx(icol, ide); + mColumnInfo[idx].firstLine = gm.getMapping().getFirstBoardBP(icol, ide); + mColumnInfo[idx].lastLine = gm.getMapping().getLastBoardBP(icol, ide); + mColumnInfo[idx].nStripsNB = gm.getMapping().getNStripsNBP(icol, ide); + } + } +} + +TH1F DigitsHelper::makeStripHisto(const std::string name, const std::string title) const +{ + TH1F histo(name.c_str(), title.c_str(), mStripsMap.size(), 0, mStripsMap.size()); + histo.SetXTitle("Strip index"); + return histo; +} + +std::array, 4> DigitsHelper::makeStripMapHistos(std::string name, std::string title, int cathode) const +{ + std::array, 4> out; + std::array chId{ "11", "12", "21", "22" }; + for (int ich = 0; ich < 4; ++ich) { + out[ich] = std::make_unique(makeStripMapHisto(fmt::format("{}{}", name, chId[ich]), fmt::format("{} MT{}", title, chId[ich]), cathode)); + } + return out; +} + +std::array, 4> DigitsHelper::makeBoardMapHistos(std::string name, std::string title) const +{ + std::array, 4> out; + std::array chId{ "11", "12", "21", "22" }; + for (int ich = 0; ich < 4; ++ich) { + out[ich] = std::make_unique(makeBoardMapHisto(fmt::format("{}{}", name, chId[ich]), fmt::format("{} MT{}", title, chId[ich]))); + } + return out; +} + +unsigned long DigitsHelper::countDigits(const o2::mid::ColumnData& col, int cathode) const +{ + unsigned long counts = 0; + int firstCathode = 0; + int lastCathode = 1; + if (cathode == 0 || cathode == 1) { + firstCathode = lastCathode = cathode; + } + int lastLine = (cathode == 1) ? 0 : 3; + for (int icath = firstCathode; icath <= lastCathode; ++icath) { + for (int iline = 0; iline <= lastLine; ++iline) { + for (int istrip = 0; istrip < 16; ++istrip) { + if (col.isStripFired(istrip, icath, iline)) { + ++counts; + } + } + } + } + return counts; +} + +TH2F DigitsHelper::makeStripMapHisto(std::string name, std::string title, int cathode) const +{ + int nBinsX = (cathode == 0) ? 14 : 224; + int nBinsY = (cathode == 0) ? 64 * o2::mid::detparams::NRPCLines : 4 * o2::mid::detparams::NRPCLines; + TH2F histo(name.c_str(), title.c_str(), nBinsX, -7, 7, nBinsY, 0., 9.); + histo.SetXTitle("Column"); + histo.SetYTitle("Line"); + histo.SetOption("COLZ"); + histo.SetStats(0); + return histo; +} + +TH2F DigitsHelper::makeBoardMapHisto(std::string name, std::string title) const +{ + TH2F histo(name.c_str(), title.c_str(), 14, -7., 7., 4 * o2::mid::detparams::NRPCLines, 0., 9.); + histo.SetXTitle("Column"); + histo.SetYTitle("Line"); + histo.SetOption("COLZ"); + histo.SetStats(0); + return histo; +} + +void DigitsHelper::fillMapHistos(const TH1* histo, std::array, 4>& histoMapB, std::array, 4>& histoMapNB, const std::vector& infoMap) const +{ + for (int ibin = 1, nBins = histo->GetNbinsX(); ibin <= nBins; ++ibin) { + auto idx = ibin - 1; + auto histoMap = infoMap[idx].cathode == 0 ? histoMapB[infoMap[idx].chamber].get() : histoMapNB[infoMap[idx].chamber].get(); + auto wgt = histo->GetBinContent(ibin); + for (auto& ib : infoMap[idx].bins) { + FillBin(histoMap, ib, wgt); + } + } +} + +void DigitsHelper::fillStripMapHistos(const TH1* histo, std::array, 4>& histosB, std::array, 4>& histosNB) const +{ + fillMapHistos(histo, histosB, histosNB, mStripIdxToStripMap); +} + +void DigitsHelper::fillBoardMapHistosFromStrips(const TH1* histo, std::array, 4>& histosB, std::array, 4>& histosNB) const +{ + fillMapHistos(histo, histosB, histosNB, mStripIdxToBoardMap); +} + +void DigitsHelper::FillBin(TH1* histo, int ibin, double wgt) const +{ + histo->AddBinContent(ibin, wgt); + histo->SetEntries(histo->GetEntries() + wgt); +} + +void DigitsHelper::fillStripHisto(const o2::mid::ColumnData& col, TH1* histo) const +{ + int idx = getColumnIdx(col.columnId, col.deId); + int firstLine = mColumnInfo[idx].firstLine; + int lastLine = mColumnInfo[idx].lastLine; + for (int iline = firstLine; iline <= lastLine; ++iline) { + for (int istrip = 0; istrip < 16; ++istrip) { + if (col.isBPStripFired(istrip, iline)) { + auto found = mStripsMap.find(o2::mid::getStripId(col.deId, col.columnId, iline, istrip, 0)); + // The histogram bin corresponds to the strip index + 1 + // Since we know the bin, we can use AddBinContent instead of Fill to save time + FillBin(histo, found->second + 1); + } + } + } + int nStrips = mColumnInfo[idx].nStripsNB; + for (int istrip = 0; istrip < nStrips; ++istrip) { + if (col.isNBPStripFired(istrip)) { + auto found = mStripsMap.find(o2::mid::getStripId(col.deId, col.columnId, firstLine, istrip, 1)); + // The histogram bin corresponds to the strip index + 1 + // Since we know the bin, we can use AddBinContent instead of Fill to save time + FillBin(histo, found->second + 1); + } + } +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/DigitsQcCheck.cxx b/Modules/MUON/MID/src/DigitsQcCheck.cxx new file mode 100644 index 0000000000..e652765b4c --- /dev/null +++ b/Modules/MUON/MID/src/DigitsQcCheck.cxx @@ -0,0 +1,388 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsQcCheck.cxx +/// \author Valerie Ramillien +/// + +#include "MID/DigitsQcCheck.h" +#include +#include +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include + +namespace o2::quality_control_modules::mid +{ + +void DigitsQcCheck::configure() +{ + + ILOG(Info, Devel) << "configure DigitsQcCheck" << ENDM; + if (auto param = mCustomParameters.find("MeanMultThreshold"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - MeanMultThreshold: " << param->second << ENDM; + mMeanMultThreshold = std::stof(param->second); + } + if (auto param = mCustomParameters.find("MinMultThreshold"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - MinMultThreshold: " << param->second << ENDM; + mMinMultThreshold = std::stof(param->second); + } + if (auto param = mCustomParameters.find("NbOrbitPerTF"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - NbOrbitPerTF: " << param->second << ENDM; + mHistoHelper.setNOrbitsPerTF(std::stol(param->second)); + } + if (auto param = mCustomParameters.find("LocalBoardScale"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - LocalBoardScale: " << param->second << ENDM; + mLocalBoardScale = std::stof(param->second); + } + if (auto param = mCustomParameters.find("LocalBoardThreshold"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - LocalBoardThreshold: " << param->second << ENDM; + mLocalBoardThreshold = std::stof(param->second); + } + if (auto param = mCustomParameters.find("NbBadLocalBoard"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - NbBadLocalBoard: " << param->second << ENDM; + mNbBadLocalBoard = std::stoi(param->second); + } + if (auto param = mCustomParameters.find("NbEmptyLocalBoard"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - NbEmptyLocalBoard: " << param->second << ENDM; + mNbEmptyLocalBoard = std::stoi(param->second); + } +} + +Quality DigitsQcCheck::check(std::map>* moMap) +{ + + Quality result = Quality::Good; + // This info must be available from the beginning + TH1* meanMultiHits = nullptr; + for (auto& item : *moMap) { + if (item.second->getName() == "NbDigitTF") { + mHistoHelper.setNTFs(dynamic_cast(item.second->getObject())->GetBinContent(1)); + } else if (item.second->getName() == "MeanMultiHits") { + meanMultiHits = dynamic_cast(item.second->getObject()); + } + } + + // Fill the summary multiplicity histogram + if (meanMultiHits) { + meanMultiHits->Reset(); + std::unordered_map ref; + for (int ibin = 1; ibin <= meanMultiHits->GetNbinsX(); ++ibin) { + std::string hName = "MultHit"; + hName += meanMultiHits->GetXaxis()->GetBinLabel(ibin); + ref[hName] = ibin; + } + + int nGood = 0, nNull = 0, nBad = 0, nMedium = 0; + auto globalQual = Quality::Null; + for (auto& item : *moMap) { + if (item.second->getName().find("MultHitMT") != std::string::npos) { + std::string hName = item.second->getName(); + auto mean = dynamic_cast(item.second->getObject())->GetMean(); + meanMultiHits->SetBinContent(ref[hName], mean); + auto qual = Quality::Good; + if (mean == 0.) { + ++nNull; + qual = Quality::Null; + } else if (mean > mMeanMultThreshold || mean < mMinMultThreshold) { + qual = Quality::Bad; + ++nBad; + } else if (mean > mMeanMultThreshold / 2.) { + qual = Quality::Medium; + ++nMedium; + } else { + ++nGood; + } + mQualityMap[hName] = qual; + } + } + if (nBad > 0) { + globalQual = Quality::Bad; + } else if (nMedium > 0) { + globalQual = Quality::Medium; + } else if (nGood == 8) { + globalQual = Quality::Good; + } + mQualityMap[meanMultiHits->GetName()] = globalQual; + } + + for (auto& item : *moMap) { + if (item.second->getName() == "LocalBoardsMap") { + if (mHistoHelper.getNTFs() > 0) { + nEmptyLB = 0; + nBadLB = 0; + maxVal = 0; + minVal = 1000; + int Resp = 0; + auto histo = dynamic_cast(item.second->getObject()); + if (histo) { + mHistoHelper.normalizeHistoTokHz(histo); + int by0 = 0; // NN + for (int by = 1; by < 37; by++) + LineResp[by - 1] = 0; + for (int bx = 1; bx < 15; bx++) { + for (int by = 1; by < 37; by++) { + Resp = 0; // NN + if (bx > 1) + LineResp[by - 1] = LineResp[by - 1] << 2; + + if (!((bx > 6) && (bx < 9) && (by > 15) && (by < 22))) { + Resp = 1; + double val = histo->GetBinContent(bx, by); + if (val > maxVal) { + maxVal = val; + } + if (val < minVal) { + minVal = val; + } + if (val == 0) { + nEmptyLB++; + Resp = 0; // NN + } else if (val > mLocalBoardThreshold) { + nBadLB++; + Resp = 3; // NN + } + LineResp[by - 1] = LineResp[by - 1] + Resp; + by0 = by; + if ((bx == 1) || (bx == 14) || (by == 1) || (by == 33)) { + if ((bx == 14) && (by > 1) && (by < 33)) { + LineResp[by + 1] = LineResp[by + 1] << 2; + if ((by > 12) && (by < 25)) { + LineResp[by] = LineResp[by] << 2; + LineResp[by + 2] = LineResp[by + 2] << 2; + } + } + by += 3; + } else if (!((bx > 4) && (bx < 11) && (by > 12) && (by < 25))) { + + if ((bx > 10) && (by > 12) && (by < 25)) { + LineResp[by] = LineResp[by] << 2; + } + by += 1; + } + } + } + } + } + + auto qualBadLB = Quality::Good; + auto qualEmptyLB = Quality::Good; + if (nBadLB > 0) { + qualBadLB = Quality::Medium; + if (nBadLB > mNbBadLocalBoard) { + qualBadLB = Quality::Bad; + } + auto flag = o2::quality_control::FlagType(); + qualBadLB.addFlag(flag, fmt::format("{} boards > {} kHz", nBadLB, mLocalBoardThreshold)); + } + if (nEmptyLB > 0) { + qualEmptyLB = Quality::Medium; + if (nEmptyLB > mNbEmptyLocalBoard) { + qualEmptyLB = Quality::Bad; + } + auto flag = o2::quality_control::FlagType(); + qualEmptyLB.addFlag(flag, fmt::format("{} boards empty", nEmptyLB)); + } + + auto qual = Quality::Good; + if (qualBadLB.isWorseThan(qual)) { + qual.set(qualBadLB); + } + if (qualEmptyLB.isWorseThan(qual)) { + qual.set(qualEmptyLB); + } + // copy flags to aggregated quality + for (const auto& flag : qualBadLB.getFlags()) { + qual.addFlag(flag.first, flag.second); + } + for (const auto& flag : qualEmptyLB.getFlags()) { + qual.addFlag(flag.first, flag.second); + } + // copy metadata to aggregated quality + qual.addMetadata(qualBadLB.getMetadataMap()); + qual.addMetadata(qualEmptyLB.getMetadataMap()); + + mQualityMap[item.second->getName()] = qual; + result = qual; + + } // if mNTFInSeconds > 0. + } + + /////NN + for (int by = 1; by < 37; by++) { + std::string nline = std::to_string(by); + std::string HistoName = "RespBoardLine" + nline; + if (item.second->getName() == HistoName) { + auto histo = dynamic_cast(item.second->getObject()); + if (histo) { + histo->SetBins(1, LineResp[by - 1] - 0.5, LineResp[by - 1] + 0.5); + histo->Fill(LineResp[by - 1]); + } + } + } + /////NN + + if (item.second->getName() == "NbLBEmpty") { + auto histo = dynamic_cast(item.second->getObject()); + if (histo) { + histo->SetBins(1, nEmptyLB - 0.5, nEmptyLB + 0.5); + histo->Fill(nEmptyLB); + } + } + if (item.second->getName() == "NbLBHighRate") { + auto histo = dynamic_cast(item.second->getObject()); + if (histo) { + histo->SetBins(1, nBadLB - 0.5, nBadLB + 0.5); + histo->Fill(nBadLB); + } + } + if (item.second->getName() == "LBHighRate") { + auto histo = dynamic_cast(item.second->getObject()); + if (histo) { + histo->SetBins(1, maxVal - 0.5, maxVal + 0.5); + histo->Fill(maxVal); + } + } + } + + return result; +} + +void DigitsQcCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + gStyle->SetPalette(kRainBow); + + auto found = mQualityMap.find(mo->getName()); + if (found != mQualityMap.end()) { + checkResult = found->second; + } + + auto color = mHistoHelper.getColor(checkResult); + + // Multiplicity Histograms + if (mo->getName().find("MultHitMT") != std::string::npos) { + // This matches "MultHitMT*" + if (mHistoHelper.getNTFs() > 0) { + auto histo = dynamic_cast(mo->getObject()); + if (histo) { + histo->SetFillColor(color); + mHistoHelper.addLatex(histo, 0.15, 0.82, color, Form("Limit : [%g;%g]", mMinMultThreshold, mMeanMultThreshold)); + mHistoHelper.addLatex(histo, 0.3, 0.62, color, Form("Mean=%g ", histo->GetMean())); + mHistoHelper.addLatex(histo, 0.3, 0.52, color, fmt::format("Quality::{}", checkResult.getName())); + histo->SetTitleSize(0.04); + histo->SetLineColor(kBlack); + } + } + } else if (mo->getName() == "MeanMultiHits") { + auto histo = dynamic_cast(mo->getObject()); + if (histo) { + mHistoHelper.addLatex(histo, 0.3, 0.52, color, fmt::format("Quality::{}", checkResult.getName())); + mHistoHelper.updateTitleWithNTF(histo); + histo->SetStats(0); + } + } else { + // Change palette contours so that visibility of low-multiplicity strips or boards is improved + std::vector zcontoursLoc{ 0, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10, 25, 50, 75, 100 }; + std::vector zcontoursLoc4, zcontoursStrip; + for (auto& con : zcontoursLoc) { + con *= mLocalBoardScale / zcontoursLoc.back(); + zcontoursLoc4.emplace_back(con * 4); + zcontoursStrip.emplace_back(con / 10.); + } + + // Local Boards Display + std::string lbHistoName = "LocalBoardsMap"; + if (mo->getName().find(lbHistoName) != std::string::npos) { + // This matches "LocalBoardsMap*" + auto histo = dynamic_cast(mo->getObject()); + if (histo) { + if (mo->getName() == lbHistoName) { + // This is LocalBoardsMap and it was already scaled in the checker + float xFlagText = 0.12; + float yFlagText = 0.72; + for (const auto& flag : checkResult.getFlags()) { + mHistoHelper.addLatex(histo, xFlagText, yFlagText, color, flag.second.c_str()); + yFlagText -= 0.1; + } + mHistoHelper.addLatex(histo, 0.3, 0.32, color, fmt::format("Quality::{}", checkResult.getName())); + histo->SetMaximum(zcontoursLoc4.back()); + histo->SetContour(zcontoursLoc4.size(), zcontoursLoc4.data()); + } else { + mHistoHelper.normalizeHistoTokHz(histo); + histo->SetMaximum(zcontoursLoc.back()); + histo->SetContour(zcontoursLoc.size(), zcontoursLoc.data()); + } + mHistoHelper.updateTitleWithNTF(histo); + histo->SetStats(0); + } + } else if (mo->getName().find("BendHitsMap") != std::string::npos) { // Strips Display + // This matches both [N]BendHitsMap* + int maxStrip = 20; // 20kHz Max Display + auto histo = dynamic_cast(mo->getObject()); + if (histo) { + mHistoHelper.normalizeHistoTokHz(histo); + histo->SetMaximum(zcontoursStrip.back()); + histo->SetContour(zcontoursStrip.size(), zcontoursStrip.data()); + histo->SetStats(0); + } + } else if (mo->getName() == "Hits") { + auto histo = dynamic_cast(mo->getObject()); + if (histo) { + mHistoHelper.normalizeHistoTokHz(histo); + histo->SetStats(0); + } + } else if (mo->getName() == "GBTRate") { + auto histo = dynamic_cast(mo->getObject()); + if (histo) { + // if (mHistoHelper.getNTFs() > 0) + mHistoHelper.normalizeHistoTokHz(histo); + histo->SetMinimum(0.); + TString XLabel[32] = { "5R0", "5R1", "4R0", "4R1", "1R0", "1R1", "0R0", "0R1", + "2R0", "2R1", "3R0", "3R1", "7R0", "7R1", "6R0", "6R1", + "5L0", "5L1", "4L0", "4L1", "1L0", "1L1", "0L0", "0L1", + "2L0", "2L1", "3L0", "3L1", "7L0", "7L1", "6L0", "6L1" }; + for (Int_t i = 0; i < 32; ++i) + histo->GetXaxis()->SetBinLabel(i + 1, XLabel[i]); + histo->GetXaxis()->SetLabelSize(0.07); + histo->GetXaxis()->SetLabelColor(4); + } + } else if (mo->getName() == "EPRate") { + auto histo = dynamic_cast(mo->getObject()); + if (histo) { + mHistoHelper.normalizeHistoTokHz(histo); + histo->SetMinimum(0.); + histo->GetXaxis()->SetBinLabel(1, "CRU0(990)-EP0"); + histo->GetXaxis()->SetBinLabel(2, "CRU0(990)-EP1"); + histo->GetXaxis()->SetBinLabel(3, "CRU1(974)-EP0"); + histo->GetXaxis()->SetBinLabel(4, "CRU1(974)-EP1"); + histo->GetXaxis()->SetLabelSize(0.07); + histo->GetXaxis()->SetLabelColor(4); + } + } else if (mo->getName() == "CRURate") { + auto histo = dynamic_cast(mo->getObject()); + if (histo) { + mHistoHelper.normalizeHistoTokHz(histo); + histo->SetMinimum(0.); + histo->GetXaxis()->SetBinLabel(1, "CRU0 (990)"); + histo->GetXaxis()->SetBinLabel(2, "CRU1 (974)"); + histo->GetXaxis()->SetLabelSize(0.07); + histo->GetXaxis()->SetLabelColor(4); + } + } + } + mHistoHelper.updateTitle(dynamic_cast(mo->getObject()), mHistoHelper.getCurrentTime()); +} +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/DigitsQcTask.cxx b/Modules/MUON/MID/src/DigitsQcTask.cxx new file mode 100644 index 0000000000..19b616e608 --- /dev/null +++ b/Modules/MUON/MID/src/DigitsQcTask.cxx @@ -0,0 +1,288 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsQcTask.cxx +/// \author Diego Stocco +/// \author Andrea Ferrero +/// \author Valerie Ramillien + +#include "MID/DigitsQcTask.h" + +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include +#include "DataFormatsMID/ColumnData.h" +#include "DataFormatsMID/ROFRecord.h" +#include "MIDBase/Mapping.h" +#include "MIDBase/DetectorParameters.h" +#include "MIDBase/GeometryParameters.h" +#include "MIDWorkflow/ColumnDataSpecsUtils.h" +#include "MIDGlobalMapping/GlobalMapper.h" +#include "MIDRaw/CrateMapper.h" +#include "MIDRaw/CrateParameters.h" +#include "MIDRaw/Decoder.h" +#include "MID/DigitsHelper.h" + +namespace o2::quality_control_modules::mid +{ + +void DigitsQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Info, Devel) << "initialize DigitsQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will + + mNbDigitTF = std::make_unique("NbDigitTF", "NbTimeFrame", 1, 0, 1.); + getObjectsManager()->startPublishing(mNbDigitTF.get()); + + mROFTimeDiff = std::make_unique("ROFTimeDiff", "ROF time difference vs. min. ROF size", 100, 0, 100, 100, 0, 100); + mROFTimeDiff->SetOption("colz"); + getObjectsManager()->startPublishing(mROFTimeDiff.get()); + + mNbLBEmpty = std::make_unique("NbLBEmpty", "NbLocalBoardEmpty", 1, 0, 1.); + getObjectsManager()->startPublishing(mNbLBEmpty.get()); + + mNbLBHighRate = std::make_unique("NbLBHighRate", "NbLocalBoardHighRate", 1, 0, 1.); + getObjectsManager()->startPublishing(mNbLBHighRate.get()); + + mLBHighRate = std::make_unique("LBHighRate", "LocalBoardHigherRate", 1, 0, 1.); + getObjectsManager()->startPublishing(mLBHighRate.get()); + + mGBTRate = std::make_unique("GBTRate", "GBTRate", 32, 0, 32.); + getObjectsManager()->startPublishing(mGBTRate.get()); + + mCRURate = std::make_unique("CRURate", "CRURate", 2, 0, 2.); + getObjectsManager()->startPublishing(mCRURate.get()); + + mEPRate = std::make_unique("EPRate", "EPRate", 4, 0, 4.); + getObjectsManager()->startPublishing(mEPRate.get()); + + std::array chId{ "11", "12", "21", "22" }; + + for (size_t i = 0; i < 36; ++i) { + std::string nline = ""; + nline = std::to_string(i + 1); + mLineLocalResp[i] = std::make_unique(fmt::format("RespBoardLine{}", nline).c_str(), fmt::format("RespBoardLine{}", nline).c_str(), 1, 0, 1); + getObjectsManager()->startPublishing(mLineLocalResp[i].get()); + } + + for (size_t ich = 0; ich < 5; ++ich) { + std::string chName = ""; + if (ich < 4) { + chName = "MT" + chId[ich]; + } + mMultHitB[ich] = std::make_unique(fmt::format("MultHit{}B", chName).c_str(), fmt::format("Multiplicity Hits - {} bending plane", chName).c_str(), 300, 0, 300); + getObjectsManager()->startPublishing(mMultHitB[ich].get()); + mMultHitNB[ich] = std::make_unique(fmt::format("MultHit{}NB", chName).c_str(), fmt::format("Multiplicity Hits - {} non-bending plane", chName).c_str(), 300, 0, 300); + getObjectsManager()->startPublishing(mMultHitNB[ich].get()); + } + + mMeanMultiHits = std::make_unique("MeanMultiHits", "Min Hits Multiplicity", 8, 0, 8.); + for (int icath = 0; icath < 2; ++icath) { + std::string cathName = (icath == 0) ? "B" : "NB"; + for (int ich = 0; ich < 4; ++ich) { + int ibin = 1 + 4 * icath + ich; + mMeanMultiHits->GetXaxis()->SetBinLabel(ibin, fmt::format("MT{}{}", chId[ich], cathName).c_str()); + } + } + getObjectsManager()->startPublishing(mMeanMultiHits.get()); + + mLocalBoardsMap = mDigitsHelper.makeBoardMapHistos("LocalBoardsMap", "Local boards Occupancy Map"); + + for (int ich = 0; ich < 4; ++ich) { + getObjectsManager()->startPublishing(mLocalBoardsMap[ich].get()); + getObjectsManager()->setDefaultDrawOptions(mLocalBoardsMap[ich].get(), "COLZ"); + } + + mLocalBoardsMapTot = std::make_unique(mDigitsHelper.makeBoardMapHisto("LocalBoardsMap", "Local boards Occupancy Map")); + getObjectsManager()->startPublishing(mLocalBoardsMapTot.get()); + getObjectsManager()->setDefaultDrawOptions(mLocalBoardsMapTot.get(), "COLZ"); + + mHits = std::make_unique(mDigitsHelper.makeStripHisto("Hits", "Fired strips")); + getObjectsManager()->startPublishing(mHits.get()); + + mBendHitsMap = mDigitsHelper.makeStripMapHistos("BendHitsMap", "Bending Hits Map", 0); + for (int ich = 0; ich < 4; ++ich) { + getObjectsManager()->startPublishing(mBendHitsMap[ich].get()); + getObjectsManager()->setDefaultDrawOptions(mBendHitsMap[ich].get(), "COLZ"); + } + + mNBendHitsMap = mDigitsHelper.makeStripMapHistos("NBendHitsMap", "Non-Bending Hits Map", 1); + for (int ich = 0; ich < 4; ++ich) { + getObjectsManager()->startPublishing(mNBendHitsMap[ich].get()); + getObjectsManager()->setDefaultDrawOptions(mNBendHitsMap[ich].get(), "COLZ"); + } + + mDigitBCCounts = std::make_unique("DigitBCCounts", "Digits Bunch Crossing Counts", o2::constants::lhc::LHCMaxBunches, 0., o2::constants::lhc::LHCMaxBunches); + getObjectsManager()->startPublishing(mDigitBCCounts.get()); + mDigitBCCounts->GetXaxis()->SetTitle("BC"); + mDigitBCCounts->GetYaxis()->SetTitle("Number of digits"); +} + +void DigitsQcTask::startOfActivity(const Activity& /*activity*/) +{ + reset(); +} + +void DigitsQcTask::startOfCycle() +{ +} + +void DigitsQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + mNbDigitTF->Fill(0.5, 1.); + + auto digits = ctx.inputs().get>("digits"); + auto rofs = ctx.inputs().get>("digits_rof"); + + // auto digits = o2::mid::specs::getData(ctx, "digits", o2::mid::EventType::Standard); + // auto rofs = o2::mid::specs::getRofs(ctx, "digits", o2::mid::EventType::Standard); + + std::array evtSizeB{}; + std::array evtSizeNB{}; + + unsigned long int prevSize = 0; + o2::InteractionRecord prevIr; + bool isFirst = true; + + o2::mid::CrateMapper cm; + + for (auto& rof : rofs) { + auto eventDigits = digits.subspan(rof.firstEntry, rof.nEntries); + evtSizeB.fill(0); + evtSizeNB.fill(0); + + for (auto& col : eventDigits) { + + auto ich = o2::mid::detparams::getChamber(col.deId); + evtSizeB[ich] += mDigitsHelper.countDigits(col, 0); + evtSizeNB[ich] += mDigitsHelper.countDigits(col, 1); + mDigitsHelper.fillStripHisto(col, mHits.get()); + + for (int lineId = 0; lineId < 4; lineId++) { + if (col.getBendPattern(lineId)) { + auto locId = cm.deLocalBoardToRO(col.deId, col.columnId, lineId); + + int crateId = o2::mid::raw::getCrateId(locId); + int cruId = o2::mid::crateparams::isRightSide(crateId) ? 0 : 1; + int epId = 0; + switch (crateId % 8) { + case 0: + case 1: + case 4: + case 5: + epId = 0; + break; + case 2: + case 3: + case 6: + case 7: + epId = 1; + break; + } + auto locIdInCrate = o2::mid::raw::getLocId(locId); + auto gbtId = o2::mid::crateparams::getGBTIdFromBoardInCrate(locIdInCrate); + auto gbtUniqueId = o2::mid::crateparams::makeGBTUniqueId(crateId, gbtId); + mGBTRate->Fill(gbtUniqueId); + mCRURate->Fill(cruId); + int epIndex = 2 * cruId + epId; + mEPRate->Fill(epIndex); + } + } + } + + unsigned long int sizeTot = 0; + for (int ich = 0; ich < 4; ++ich) { + sizeTot += evtSizeB[ich] + evtSizeNB[ich]; + mMultHitB[ich]->Fill(evtSizeB[ich]); + mMultHitB[4]->Fill(evtSizeB[ich]); + mMultHitNB[ich]->Fill(evtSizeNB[ich]); + mMultHitNB[4]->Fill(evtSizeNB[ich]); + } + + if (!isFirst) { + unsigned long int sizeMin = (sizeTot < prevSize) ? sizeTot : prevSize; + auto timeDiff = rof.interactionRecord.differenceInBC(prevIr); + mROFTimeDiff->Fill(timeDiff, sizeMin); + } + + isFirst = false; + prevSize = sizeTot; + prevIr = rof.interactionRecord; + + mDigitBCCounts->Fill(rof.interactionRecord.bc, sizeTot); + } +} + +void DigitsQcTask::endOfCycle() +{ + // Fill here the 2D representation of the fired strips/boards + // First reset the old histograms + resetDisplayHistos(); + + // Then fill from the strip histogram + mDigitsHelper.fillStripMapHistos(mHits.get(), mBendHitsMap, mNBendHitsMap); + mDigitsHelper.fillBoardMapHistosFromStrips(mHits.get(), mLocalBoardsMap, mLocalBoardsMap); + + for (auto& histo : mLocalBoardsMap) { + mLocalBoardsMapTot->Add(histo.get()); + } +} + +void DigitsQcTask::endOfActivity(const Activity& /*activity*/) +{ + reset(); +} + +void DigitsQcTask::resetDisplayHistos() +{ + for (auto& histo : mBendHitsMap) { + histo->Reset(); + } + for (auto& histo : mNBendHitsMap) { + histo->Reset(); + } + for (auto& histo : mLocalBoardsMap) { + histo->Reset(); + } + mLocalBoardsMapTot->Reset(); +} + +void DigitsQcTask::reset() +{ + // clean all the monitor objects here + + mNbDigitTF->Reset(); + mROFTimeDiff->Reset(); + mNbLBEmpty->Reset(); + mNbLBHighRate->Reset(); + mLBHighRate->Reset(); + + for (auto& histo : mMultHitB) { + histo->Reset(); + } + for (auto& histo : mMultHitNB) { + histo->Reset(); + } + mMeanMultiHits->Reset(); + + mHits->Reset(); + resetDisplayHistos(); + + mDigitBCCounts->Reset(); + + mGBTRate->Reset(); + mCRURate->Reset(); + mEPRate->Reset(); +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/HistoHelper.cxx b/Modules/MUON/MID/src/HistoHelper.cxx new file mode 100644 index 0000000000..374a7813d3 --- /dev/null +++ b/Modules/MUON/MID/src/HistoHelper.cxx @@ -0,0 +1,108 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \file HistoHelper.cxx +/// \author Diego Stocco + +#include "MID/HistoHelper.h" + +#include "TH1.h" +#include "TH2.h" +#include "TString.h" +#include "TLatex.h" +#include "TList.h" + +namespace o2::quality_control_modules::mid +{ + +HistoHelper::HistoHelper() +{ + mColors[o2::quality_control::core::Quality::Null.getLevel()] = kWhite; + mColors[o2::quality_control::core::Quality::Good.getLevel()] = kGreen; + mColors[o2::quality_control::core::Quality::Medium.getLevel()] = kOrange; + mColors[o2::quality_control::core::Quality::Bad.getLevel()] = kRed; +} + +bool HistoHelper::normalizeHisto(TH1* histo, double scale) const +{ + if (mNTFs > 0) { + histo->Scale(1. / (scale * getNTFsAsSeconds())); + return true; + } + return false; +} + +void HistoHelper::normalizeHistoToHz(TH1* histo) const +{ + if (normalizeHisto(histo, 1.)) { + updateTitle(histo, "(Hz)"); + } +} + +void HistoHelper::normalizeHistoTokHz(TH1* histo) const +{ + if (normalizeHisto(histo, 1000.)) { + updateTitle(histo, "(kHz)"); + } +} + +void HistoHelper::updateTitle(TH1* histo, std::string suffix) const +{ + if (!histo) { + return; + } + TString title = histo->GetTitle(); + title.Append(" "); + title.Append(suffix.c_str()); + histo->SetTitle(title); +} + +std::string HistoHelper::getCurrentTime() const +{ + time_t t; + time(&t); + + struct tm* tmp; + tmp = localtime(&t); + + char timestr[500]; + strftime(timestr, sizeof(timestr), "(%x - %X)", tmp); + + std::string result = timestr; + return result; +} + +void HistoHelper::addLatex(TH1* histo, double xmin, double ymin, int color, std::string text) const +{ + + TLatex* tl = new TLatex(xmin, ymin, Form("%s", text.c_str())); + tl->SetNDC(); + tl->SetTextFont(22); // Normal 42 + tl->SetTextSize(0.06); + tl->SetTextColor(color); + histo->GetListOfFunctions()->Add(tl); +} + +int HistoHelper::getColor(const o2::quality_control::core::Quality& quality) const +{ + auto found = mColors.find(quality.getLevel()); + if (found != mColors.end()) { + return found->second; + } + return 1; +} + +void HistoHelper::updateTitleWithNTF(TH1* histo) const +{ + updateTitle(histo, Form("TF=%lu", mNTFs)); +} + +} // namespace o2::quality_control_modules::mid \ No newline at end of file diff --git a/Modules/MUON/MID/src/MIDAggregator.cxx b/Modules/MUON/MID/src/MIDAggregator.cxx new file mode 100644 index 0000000000..ef7f9abed2 --- /dev/null +++ b/Modules/MUON/MID/src/MIDAggregator.cxx @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MIDAggregator.cxx +/// \author V.Ramillien +/// + +#include "MID/MIDAggregator.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace std; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::mid +{ + +void MIDAggregator::configure() {} + +std::map MIDAggregator::aggregate(QualityObjectsMapType& qoMap) +{ + std::map result; + + ILOG(Info, Devel) << "Entered Aggregator::aggregate" << ENDM; + ILOG(Info, Devel) << " received a list of size : " << qoMap.size() << ENDM; + for (const auto& item : qoMap) { + ILOG(Info, Devel) << "Object: " << (*item.second) << ENDM; + } + + if (qoMap.empty()) { + Quality null = Quality::Null; + std::string NullReason = "QO map given to the aggregator '" + mName + "' is empty."; + null.addMetadata(Quality::Null.getName(), NullReason); + return { { mName, null } }; + } + + // we return the worse quality of all the objects we receive + Quality current = Quality::Good; + for (auto qo : qoMap) { + if (qo.second->getQuality().isWorseThan(current)) { + current = qo.second->getQuality(); + } + } + + ILOG(Info, Devel) << " result: " << current << ENDM; + result["newQuality"] = current; + + // add one more + // Quality plus = Quality::Medium; + // result["another"] = plus; + + result["another"] = current; + + return result; +} +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/MIDTrending.cxx b/Modules/MUON/MID/src/MIDTrending.cxx new file mode 100644 index 0000000000..fdb1b7e810 --- /dev/null +++ b/Modules/MUON/MID/src/MIDTrending.cxx @@ -0,0 +1,178 @@ + +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MIDTrending.cxx +/// \author Valerie Ramillien based on Piotr Konopka +/// + +#include "MID/MIDTrending.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/ActivityHelpers.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; +using namespace o2::quality_control::postprocessing; + +void MIDTrending::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingTaskConfigMID(getID(), config); +} + +void MIDTrending::initialize(Trigger, framework::ServiceRegistryRef services) +{ + // Preparing data structure of TTree + mTrend.reset(); + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + + mTrend->Branch("ntreeentries", &ntreeentries); + mTrend->Branch("time", &mTime); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create(source.moduleName, source.reductorName)); + + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void MIDTrending::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + trendValues(t, qcdb); + generatePlots(qcdb); +} + +void MIDTrending::finalize(Trigger, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + auto mo = std::make_shared(mTrend.get(), getName(), "o2::quality_control_modules::mid::MIDTrending", mConfig.detectorName); + mo->setIsOwner(false); + qcdb.storeMO(mo); + generatePlots(qcdb); +} + +void MIDTrending::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + mTime = activity_helpers::isLegacyValidity(t.activity.mValidity) + ? t.timestamp / 1000 + : t.activity.mValidity.getMax() / 1000; // ROOT expects seconds since epoch. + mMetaData.runNumber = t.activity.mId; + int count = 0; + + for (auto& dataSource : mConfig.dataSources) { + + // todo: make it agnostic to MOs, QOs or other objects. Let the reductor cast to whatever it needs. + if (dataSource.type == "repository") { + auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name, t.timestamp); + if (mo == nullptr) { + ILOG(Warning, Devel) << "Could not retrieve MO '" << dataSource.name << "' from QCDB, skipping this data source" << ENDM; + continue; + } + + if (!count) { + std::map entryMetadata = mo->getMetadataMap(); // full list of metadata as a map + mMetaData.runNumber = std::stoi(entryMetadata[metadata_keys::runNumber]); // get and set run number + ntreeentries = (Int_t)mTrend->GetEntries() + 1; + mRunList.push_back(std::to_string(mMetaData.runNumber)); + } + TObject* obj = mo ? mo->getObject() : nullptr; + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (obj && reductor) { + reductor->update(obj); + } + } else { + ILOG(Error, Support) << "Unknown type of data source '" << dataSource.type << "'." << ENDM; + } + count++; + } + + mTrend->Fill(); +} + +void MIDTrending::generatePlots(repository::DatabaseInterface& qcdb) +{ + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, won't generate any plots." << ENDM; + return; + } + + ILOG(Info, Support) << "Generating " << mConfig.plots.size() << " plots." << ENDM; + + std::vector> mCanvasMID; + int mPlot = 0; + + for (const auto& plot : mConfig.plots) { + + mCanvasMID.push_back(std::make_unique(plot.name.c_str(), plot.title.c_str())); + mCanvasMID[mPlot].get()->cd(); + + mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), plot.option.c_str()); + + if (auto histo = dynamic_cast(mCanvasMID[mPlot].get()->GetPrimitive("htemp"))) { + + histo->SetTitle(plot.title.c_str()); + mCanvasMID[mPlot]->Update(); + if (plot.varexp.find(":time") != std::string::npos) { + histo->GetXaxis()->SetTimeDisplay(1); + + // It deals with highly congested dates labels + histo->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + histo->GetXaxis()->SetTimeOffset(0.0); + histo->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + + } + + else if (plot.varexp.find(":runNumber") != std::string::npos) { + histo->GetXaxis()->SetNdivisions(505); + + for (int ir = 0; ir < (int)mRunList.size(); ir++) + histo->GetXaxis()->SetBinLabel(ir + 1, mRunList[ir].c_str()); + } + + histo->BufferEmpty(); + + } else { + ILOG(Error, Devel) << "Could not get the processing histogram of the plot '" << plot.name << "'." << ENDM; + } + + mPlots[plot.name] = mCanvasMID[mPlot].get(); + + auto mo_mid = std::make_shared(mCanvasMID[mPlot].get(), mConfig.taskName, "o2::quality_control_modules::mid::MIDTrending", mConfig.detectorName); + + mo_mid->setIsOwner(false); + qcdb.storeMO(mo_mid); + + ++mPlot; + } +} diff --git a/Modules/MUON/MID/src/RawQcCheck.cxx b/Modules/MUON/MID/src/RawQcCheck.cxx new file mode 100644 index 0000000000..9b715d25fd --- /dev/null +++ b/Modules/MUON/MID/src/RawQcCheck.cxx @@ -0,0 +1,93 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawQcCheck.cxx +/// \author Bogdan Vulpescu / Xavier Lopez +/// + +#include "MID/RawQcCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include + +using namespace std; + +namespace o2::quality_control_modules::mid +{ + +Quality RawQcCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + int nDigMT11, nDigMT12, nDigMT21, nDigMT22; + + for (auto& [moName, mo] : *moMap) { + + (void)moName; + if (mo->getName() == "mDetElemID") { + auto* h = dynamic_cast(mo->getObject()); + + result = Quality::Good; + + nDigMT11 = nDigMT12 = nDigMT21 = nDigMT22 = 0; + // count digits in stations + for (int i = 1; i <= h->GetNbinsX(); i++) { + if (i >= 1 && i <= 18) { + nDigMT11 += h->GetBinContent(i); + } else if (i >= 19 && i <= 36) { + nDigMT12 += h->GetBinContent(i); + } else if (i >= 37 && i <= 54) { + nDigMT21 += h->GetBinContent(i); + } + if (i >= 55 && i <= 72) { + nDigMT22 += h->GetBinContent(i); + } + } + // set check quality + if (nDigMT11 == 0) { + result = Quality::Medium; + if (nDigMT12 == 0) { + result = Quality::Bad; + } + } + if (nDigMT22 == 0) { + result = Quality::Medium; + if (nDigMT21 == 0) { + result = Quality::Bad; + } + } + + } // end check mDetElemID + } + return result; +} + +void RawQcCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "mDetElemID") { + auto* h = dynamic_cast(mo->getObject()); + + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Info) << "Quality::Bad, setting to red"; + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Info) << "Quality::medium, setting to orange"; + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/RawQcTask.cxx b/Modules/MUON/MID/src/RawQcTask.cxx new file mode 100644 index 0000000000..87286d9e49 --- /dev/null +++ b/Modules/MUON/MID/src/RawQcTask.cxx @@ -0,0 +1,284 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawQcTask.cxx +/// \author Bogdan Vulpescu +/// \author Xavier Lopez +/// \author Diego Stocco +/// \author Guillaume Taillepied +/// \author Valerie Ramillien + +#include +#include +#include +#include + +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "MID/RawQcTask.h" +#include + +#include "Framework/DataRefUtils.h" +#include "DataFormatsMID/ColumnData.h" +#include "DataFormatsMID/ROBoard.h" +#include "DataFormatsMID/ROFRecord.h" +#include "DPLUtils/DPLRawParser.h" +#include "MIDQC/RawDataChecker.h" +#include "MIDRaw/CrateMasks.h" +#include "MIDRaw/Decoder.h" +#include "MIDRaw/ElectronicsDelay.h" +#include "MIDRaw/FEEIdConfig.h" +#include "MIDRaw/CrateParameters.h" +#include "Headers/RAWDataHeader.h" + +namespace o2::quality_control_modules::mid +{ + +RawQcTask::~RawQcTask() +{ + delete mRawDataChecker; +} + +void RawQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Info, Devel) << "initialize RawQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + // printf(" =================== > test initialise RAW \n"); + + // Retrieve task parameters from the config file + if (auto param = mCustomParameters.find("feeId-config-file"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - FEE Id config file: " << param->second << ENDM; + if (!param->second.empty()) { + mFeeIdConfig = o2::mid::FEEIdConfig(param->second.c_str()); + } + } + + if (auto param = mCustomParameters.find("crate-masks-file"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - Crate masks file: " << param->second << ENDM; + if (!param->second.empty()) { + mCrateMasks = o2::mid::CrateMasks(param->second.c_str()); + } + } + + if (auto param = mCustomParameters.find("electronics-delays-file"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - Electronics delays file: " << param->second << ENDM; + if (!param->second.empty()) { + mElectronicsDelay = o2::mid::readElectronicsDelay(param->second.c_str()); + } + } + + mChecker.init(mCrateMasks); + + // Histograms to be published + mRawDataChecker = new TH1F("mRawDataChecker", "Raw Data Checker", 2, 0, 2); + mRawDataChecker->GetXaxis()->SetBinLabel(1, "Processed"); + mRawDataChecker->GetXaxis()->SetBinLabel(2, "Faulty"); + getObjectsManager()->startPublishing(mRawDataChecker); + + mRawLocalBoardsMap = new TH2F("RawLocalBoardsMap", "Raw Local boards Occupancy Map", 16, 0, 16, 16, 0, 16); + getObjectsManager()->startPublishing(mRawLocalBoardsMap); + mRawLocalBoardsMap->GetXaxis()->SetTitle("CrateID"); + mRawLocalBoardsMap->GetYaxis()->SetTitle("LocID in Crate"); + mRawLocalBoardsMap->SetOption("colz"); + mRawLocalBoardsMap->SetStats(0); + + mBusyRawLocalBoards = new TH2F("BusyRawLocalBoards", "Busy Raw Local boards", 16, 0, 16, 16, 0, 16); // crateId X: locId Y + getObjectsManager()->startPublishing(mBusyRawLocalBoards); + mBusyRawLocalBoards->GetXaxis()->SetTitle("CrateID"); + mBusyRawLocalBoards->GetYaxis()->SetTitle("LocID in Crate"); + mBusyRawLocalBoards->SetOption("colz"); + mBusyRawLocalBoards->SetStats(0); + + mRawBCCounts = new TH1F("RawBCCounts", "Raw Bunch Crossing Counts", o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches); + // mRawBCCounts = new TProfile("RawBCCounts", "Mean Raw Bunch Crossing Counts", o2::constants::lhc::LHCMaxBunches, 0, o2::constants::lhc::LHCMaxBunches); + getObjectsManager()->startPublishing(mRawBCCounts); + mRawBCCounts->GetXaxis()->SetTitle("BC"); + mRawBCCounts->GetYaxis()->SetTitle("Entry (Ko)"); +} + +static int Pattern(uint16_t pattern) +{ + int nHits = 0; + int mask = 32768; // 1000000000000000 + for (int j = 0; j < 16; j++) { + if ((pattern & mask) != 0) { + nHits += 1; + } + mask >>= 1; + } + return nHits; +} + +static int BPPattern(const o2::mid::ROBoard& board, uint8_t station) +{ + if (board.patternsBP[station] != 0) { + return Pattern(board.patternsBP[station]); + } else + return 0; +} + +static int NBPPattern(const o2::mid::ROBoard& board, uint8_t station) +{ + if (board.patternsNBP[station] != 0) { + return Pattern(board.patternsNBP[station]); + } else + return 0; +} + +void PatternMultiplicity(const o2::mid::ROBoard& board, int multB[], int multNB[]) +{ + for (int i = 0; i < 4; i++) { + multB[i] += BPPattern(board, i); + multNB[i] += NBPPattern(board, i); + } +} + +bool isBoardEmpty(const o2::mid::ROBoard& board) +{ + bool rep = 0; + for (int i = 0; i < 4; i++) { + if ((board.patternsBP[i] != 0) || (board.patternsNBP[i] != 0)) + rep = 0; + else + rep = 1; + } + return rep; +} + +void RawQcTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Info, Devel) << "startOfActivity" << ENDM; + // printf(" =================== > test startOfActivity RAW \n"); + reset(); +} + +void RawQcTask::startOfCycle() +{ + // ILOG(Info, Devel) << "startOfCycle" << ENDM; + // printf(" =================== > test startOfCycle RAW \n"); +} + +void RawQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + + // ILOG(Info, Devel) << "startOfDataMonitoring" << ENDM; + // printf(" =================== > test monitorData RAW \n"); + + o2::framework::DPLRawParser parser(ctx.inputs()); + o2::InteractionRecord IntRecord; + + std::vector dummy; + + if (!mDecoder) { + auto const* rdhPtr = reinterpret_cast(parser.begin().raw()); + mDecoder = createDecoder(*rdhPtr, true, mElectronicsDelay, mCrateMasks, mFeeIdConfig); + ILOG(Info, Devel) << "Created decoder" << ENDM; + } + + mDecoder->clear(); + InitMultiplicity(); + + int count = 0; + int plcount = 0; + int itparsercount = 0; + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + auto const* rdhPtr = reinterpret_cast(it.raw()); + itparsercount++; + gsl::span payload(it.data(), it.size()); + if (!payload.empty()) { + mDecoder->process(payload, *rdhPtr); + ++count; + plcount = 0; + for (auto pl = payload.begin(), end = payload.end(); pl != end; ++pl) { + plcount++; + } + } + } + + nROF = 0; + int nBoardTot = 0; + + for (auto& rof : mDecoder->getROFRecords()) { // boucle sur les ROFRecords // + // printf("========================================================== \n"); + // printf("Raw :: %05d ROF with first entry %05zu and nentries %02zu , BC %05d, ORB %05d , EventType %02d\n", nROF, rof.firstEntry, rof.nEntries, rof.interactionRecord.bc, rof.interactionRecord.orbit,rof.eventType); + // eventType:: Standard = 0, Calib = 1, FET = 2 + + nROF++; + nBoard = 0; + nEntriesROF = 0; + mRawBCCounts->Fill(rof.interactionRecord.bc, float(rof.nEntries) * 20. / 1000.); // board = 20 octets + + for (auto board = mDecoder->getData().begin() + rof.firstEntry; board != mDecoder->getData().begin() + rof.firstEntry + rof.nEntries; ++board) { + + nBoard++; + nBoardTot++; + int boardId = (*board).boardId; + int crateId = o2::mid::raw::getCrateId(boardId); + int locId = o2::mid::raw::getLocId(boardId); + int linkId = o2::mid::crateparams::getGBTIdFromBoardInCrate(locId); + int feeId = o2::mid::crateparams::makeGBTUniqueId(crateId, linkId); + int statusWord = (*board).statusWord; + int triggerWord = (*board).triggerWord; + int isLoc = (statusWord >> 6) & 1; + int busyLoc = (statusWord >> 5) & 1; + int decisionLoc = (statusWord >> 4) & 1; + int isPhys = (triggerWord >> 2) & 1; + int isCalib = (triggerWord >> 3) & 1; + int sOrb = triggerWord & 1; + + if (isLoc && busyLoc) + mBusyRawLocalBoards->Fill(crateId, locId, 1); + + PatternMultiplicity(*board, multHitB, multHitNB); + } + } + + // mChecker.clear(); + // if (!mChecker.process(mDecoder->getData(), mDecoder->getROFRecords(), dummy)) { + // // ILOG(Info, Devel) << mChecker.getDebugMessage() << ENDM; + // mRawDataChecker->Fill("Faulty", mChecker.getNEventsFaulty()); + //} + + // ILOG(Info, Devel) << "Number of busy raised: " << mChecker.getNBusyRaised() << ENDM; + // ILOG(Info, Devel) << "Fraction of faulty events: " << mChecker.getNEventsFaulty() << " / " << mChecker.getNEventsProcessed() << ENDM; + // ILOG(Info, Devel) << "Counts: " << count << ENDM; + + // mRawDataChecker->Fill("Processed", mChecker.getNEventsProcessed()); +} + +void RawQcTask::endOfCycle() +{ + // ILOG(Info, Devel) << "endOfCycle" << ENDM; + // printf(" =================== > test endOfCycle RAW \n"); +} + +void RawQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Info, Devel) << "endOfActivity" << ENDM; + // printf(" =================== > test endOfActivity RAW \n"); + reset(); +} + +void RawQcTask::reset() +{ + // clean all the monitor objects here + + ILOG(Info, Devel) << "Resetting the histogram" << ENDM; + // printf(" =================== > test reset RAW \n"); + mRawDataChecker->Reset(); + mRawBCCounts->Reset(); + mRawLocalBoardsMap->Reset(); + mBusyRawLocalBoards->Reset(); +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/TracksQcCheck.cxx b/Modules/MUON/MID/src/TracksQcCheck.cxx new file mode 100644 index 0000000000..1a004aea50 --- /dev/null +++ b/Modules/MUON/MID/src/TracksQcCheck.cxx @@ -0,0 +1,202 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TracksQcCheck.cxx +/// \author Valerie Ramillien +/// + +#include "MID/TracksQcCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::mid +{ + +void TracksQcCheck::configure() +{ + ILOG(Info, Devel) << "configure TraksQcCheck" << ENDM; + if (auto param = mCustomParameters.find("Ratio44Threshold"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - Ratio44Threshold: " << param->second << ENDM; + mRatio44Threshold = stof(param->second); + } + if (auto param = mCustomParameters.find("NbOrbitPerTF"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - :NbOrbitPerTF " << param->second << ENDM; + mOrbTF = stof(param->second); + } + if (auto param = mCustomParameters.find("TracksScale"); param != mCustomParameters.end()) { + ILOG(Info, Devel) << "Custom parameter - :TracksScale " << param->second << ENDM; + mTracksScale = stof(param->second); + } +} + +Quality TracksQcCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "NbTracksTF") { + auto* h = dynamic_cast(mo->getObject()); + mTracksTF = h->GetBinContent(1); + // std::cout << " mTracksF = "<< mTracksTF << std::endl ; + } + float scale = 1 / (mTracksTF * scaleTime * mOrbTF); // (Hz) + + if (mo->getName() == "TrackMapXY") { + auto* h2 = dynamic_cast(mo->getObject()); + h2->Scale(scale); + } + + (void)moName; + if (mo->getName() == "TrackRatio44") { + auto* h = dynamic_cast(mo->getObject()); + TLine* lineThreshold = new TLine(0., mRatio44Threshold, h->GetXaxis()->GetXmax(), mRatio44Threshold); + lineThreshold->SetLineColor(kGreen); + // lineThreshold->SetLineStyle(kDashed); + lineThreshold->SetLineStyle(9); + lineThreshold->SetLineWidth(3); + + result = Quality::Good; + if (mTracksTF > 0) { + for (int i = 1; i < h->GetNbinsX(); i++) { + if ((i == 1) && (h->GetBinContent(i) < mRatio44Threshold)) { + result = Quality::Bad; + + result.addFlag(FlagTypeFactory::Unknown(), + "Global Ratio44 too low : bin " + std::to_string(i)); + lineThreshold->SetLineColor(kRed); + break; + } else if ((i > 1) && (i < 10) && (h->GetBinContent(i) < mRatio44Threshold)) { + result = Quality::Medium; + result.addFlag(FlagTypeFactory::Unknown(), + "Ratio44 too low in bin " + std::to_string(i)); + lineThreshold->SetLineColor(kOrange); + break; + } + } + h->GetListOfFunctions()->Add(lineThreshold); + } + } // TrackRatio44 + } + return result; +} + +static void updateTitle(TH1* hist, std::string suffix) +{ + if (!hist) { + return; + } + TString title = hist->GetTitle(); + title.Append(" "); + title.Append(suffix.c_str()); + hist->SetTitle(title); +} + +static std::string getCurrentTime() +{ + time_t t; + time(&t); + + struct tm* tmp; + tmp = localtime(&t); + + char timestr[500]; + strftime(timestr, sizeof(timestr), "(%x - %X)", tmp); + + std::string result = timestr; + return result; +} + +static TLatex* drawLatex(double xmin, double ymin, Color_t color, TString text) +{ + + TLatex* tl = new TLatex(xmin, ymin, Form("%s", text.Data())); + tl->SetNDC(); + tl->SetTextFont(62); // Normal 42 + tl->SetTextSize(0.07); + tl->SetTextColor(color); + + return tl; +} + +void TracksQcCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + // std::cout << "beautify ::: " << checkResult << std::endl; + auto currentTime = getCurrentTime(); + // updateTitle(dynamic_cast(mo->getObject()), currentTime); + TLatex* msg; + + if (mo->getName() == "TrackMapXY") { + auto* h2 = dynamic_cast(mo->getObject()); + if (h2) { + updateTitle(h2, "(Hz)"); + updateTitle(h2, Form("- TF=%3.0f -", mTracksTF)); + updateTitle(h2, currentTime); + h2->SetMaximum(mTracksScale); + } + } + if (mTracksTF > 0) { + if (mo->getName() == "TrackRatio44") { + auto* h = dynamic_cast(mo->getObject()); + if (h) { + updateTitle(h, Form("- TF=%3.0f -", mTracksTF)); + updateTitle(h, currentTime); + h->SetMinimum(0.); + h->SetMaximum(1.2); + + if (checkResult == Quality::Good) { + msg = drawLatex(.2, 0.82, kGreen, "All ratio within limits: OK!"); + msg->Draw(); + h->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msg); + // std::cout << "beautify GOOD::: " << std::endl; + + } else if (checkResult == Quality::Bad) { + ILOG(Info, Devel) << "Quality::Bad, setting to red" << ENDM; + // msg = drawLatex(.2, 0.82, kRed, ""Global Ratio too low, call MID on-call"); + msg = drawLatex(.2, 0.82, kRed, Form("Global Ratio44/all < %4.2f too low !! ", mRatio44Threshold)); + msg->Draw(); + h->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msg); + // std::cout << "beautify BAD::: " << std::endl; + + } else if (checkResult == Quality::Medium) { + ILOG(Info, Devel) << "Quality::medium, setting to orange" << ENDM; + msg = drawLatex(.2, 0.82, kOrange, Form("Ratio44/all < %4.2f too low !! ", mRatio44Threshold)); + msg->Draw(); + h->SetFillColor(kOrange); + h->GetListOfFunctions()->Add(msg); + // std::cout << "beautify MEDIUM::: " << std::endl; + } + h->SetTitleSize(0.04); + h->SetLineColor(kBlack); + } + } + } +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/TracksQcTask.cxx b/Modules/MUON/MID/src/TracksQcTask.cxx new file mode 100644 index 0000000000..ec808ff28d --- /dev/null +++ b/Modules/MUON/MID/src/TracksQcTask.cxx @@ -0,0 +1,539 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TracksQcTask.cxx +/// \author Valerie Ramillien +/// + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "MID/TracksQcTask.h" +#include +#include +#include +#include "DataFormatsMID/Track.h" +#include "DataFormatsMID/ROFRecord.h" +#include "MIDBase/DetectorParameters.h" +#include "MIDBase/GeometryParameters.h" +#include "MIDWorkflow/ColumnDataSpecsUtils.h" + +#define MID_NDE 72 +#define MID_NLOC 234 +#define DZpos 10 + +namespace o2::quality_control_modules::mid +{ + +TracksQcTask::~TracksQcTask() +{ +} + +void TracksQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + // ILOG(Info, Devel) << "initialize TracksQcTask" << ENDM; + // printf(" =================== > test initialize Tracks \n"); + + multTracksTot = 0; + multTracks44Tot = 0; + multTracksBend44 = 0; + multTracksNBend44 = 0; + multTraksB34MT11 = 0; + multTraksNB34MT11 = 0; + multTraksB34MT12 = 0; + multTraksNB34MT12 = 0; + multTraksB34MT21 = 0; + multTraksNB34MT21 = 0; + multTraksB34MT22 = 0; + multTraksNB34MT22 = 0; + + mNbTracksTF = std::make_shared("NbTracksTF", "NbTimeFrame", 1, 0, 1.); + getObjectsManager()->startPublishing(mNbTracksTF.get()); + + mTrackBCCounts = std::make_shared("TrackBCCounts", "Mean Tracks in Bunch Crossing ; BC ; Mean Tracks nb", o2::constants::lhc::LHCMaxBunches, 0., o2::constants::lhc::LHCMaxBunches); + getObjectsManager()->startPublishing(mTrackBCCounts.get()); + // mTrackBCCounts->GetXaxis()->SetTitle("BC"); + // mTrackBCCounts->GetYaxis()->SetTitle("Mean Tracks nb"); + + mMultTracks = std::make_shared("MultTracks", "Multiplicity Tracks ", 100, 0, 100); + getObjectsManager()->startPublishing(mMultTracks.get()); + + mTrackMapXY = std::make_shared("TrackMapXY", "Track Map X-Y ; X Position (cm) ; Y Position (cm) ", 300, -300., 300., 300, -300., 300.); + getObjectsManager()->startPublishing(mTrackMapXY.get()); + // mTrackMapXY->GetXaxis()->SetTitle("X Position (cm)"); + // mTrackMapXY->GetYaxis()->SetTitle("Y Position (cm)"); + mTrackMapXY->SetOption("colz"); + mTrackMapXY->SetStats(0); + + mTrackDevXY = std::make_shared("TrackDevXY", "Track Deviation X-Y", 100, -50., 50., 100, -50., 50.); + getObjectsManager()->startPublishing(mTrackDevXY.get()); + mTrackDevXY->GetXaxis()->SetTitle("X Dev (cm)"); + mTrackDevXY->GetYaxis()->SetTitle("Y Dev (cm)"); + mTrackDevXY->SetOption("colz"); + mTrackDevXY->SetStats(0); + + mTrackDevX = std::make_shared("TrackDevX", "Track Deviation X", 100, -50., 50.); + getObjectsManager()->startPublishing(mTrackDevX.get()); + mTrackDevX->GetXaxis()->SetTitle("Track Dev X (cm)"); + mTrackDevX->GetYaxis()->SetTitle("Entry"); + + mTrackDevY = std::make_shared("TrackDevY", "Track Deviation Y", 100, -50., 50.); + getObjectsManager()->startPublishing(mTrackDevY.get()); + mTrackDevY->GetXaxis()->SetTitle("Track Dev Y (cm)"); + mTrackDevY->GetYaxis()->SetTitle("Entry"); + + mTrackTheta = std::make_shared("TrackTheta", "Track Theta", 120, 0., 12.); + getObjectsManager()->startPublishing(mTrackTheta.get()); + mTrackTheta->GetXaxis()->SetTitle("Track Theta"); + mTrackTheta->GetYaxis()->SetTitle("Entry"); + + mTrackEta = std::make_shared("TrackEta", "Track Eta", 250, 2., 4.5); + getObjectsManager()->startPublishing(mTrackEta.get()); + mTrackEta->GetXaxis()->SetTitle("Track Eta"); + mTrackEta->GetYaxis()->SetTitle("Entry"); + + mTrackThetaI = std::make_shared("TrackThetaI", "Track ThetaI", 120, 0., 12.); + getObjectsManager()->startPublishing(mTrackThetaI.get()); + mTrackThetaI->GetXaxis()->SetTitle("Track ThetaI"); + mTrackThetaI->GetYaxis()->SetTitle("Entry"); + + mTrackEtaI = std::make_shared("TrackEtaI", "Track EtaI", 250, 2., 4.5); + getObjectsManager()->startPublishing(mTrackEtaI.get()); + mTrackEtaI->GetXaxis()->SetTitle("Track EtaI"); + mTrackEtaI->GetYaxis()->SetTitle("Entry"); + + mTrackAlpha = std::make_shared("TrackAlpha", "Track Alpha", 240, -12., 12.); + getObjectsManager()->startPublishing(mTrackAlpha.get()); + mTrackAlpha->GetXaxis()->SetTitle("Track Alpha"); + mTrackAlpha->GetYaxis()->SetTitle("Entry"); + + mTrackPhi = std::make_shared("TrackPhi", "Track Phi", 360, -180., 180.); + getObjectsManager()->startPublishing(mTrackPhi.get()); + mTrackPhi->GetXaxis()->SetTitle("Track Phi"); + mTrackPhi->GetYaxis()->SetTitle("Entry"); + + mTrackThetaD = std::make_shared("TrackThetaD", "Track Theta Deviation", 200, -50., 50.); + getObjectsManager()->startPublishing(mTrackThetaD.get()); + mTrackThetaD->GetXaxis()->SetTitle("Track Theta Dev"); + mTrackThetaD->GetYaxis()->SetTitle("Entry"); + + mTrackPT = std::make_shared("TrackPT", "Track pT", 200, 0., 200.); + getObjectsManager()->startPublishing(mTrackPT.get()); + mTrackPT->GetXaxis()->SetTitle("Track pT"); + mTrackPT->GetYaxis()->SetTitle("Entry"); + + mGTrackRatio44 = std::make_shared("GTrackRatio44", "Track 44/all ", 2, -0.5, 1.5); + getObjectsManager()->startPublishing(mGTrackRatio44.get()); + + mTrackRatio44 = std::make_shared("TrackRatio44", "Track 44/all", 9, 0., 9.); + getObjectsManager()->startPublishing(mTrackRatio44.get()); + mTrackRatio44->GetXaxis()->SetBinLabel(1, "Global"); + mTrackRatio44->GetXaxis()->SetBinLabel(2, "MT11 Bend"); + mTrackRatio44->GetXaxis()->SetBinLabel(3, "MT12 Bend"); + mTrackRatio44->GetXaxis()->SetBinLabel(4, "MT21 Bend"); + mTrackRatio44->GetXaxis()->SetBinLabel(5, "MT22 Bend"); + mTrackRatio44->GetXaxis()->SetBinLabel(6, "MT11 NBend"); + mTrackRatio44->GetXaxis()->SetBinLabel(7, "MT12 NBend"); + mTrackRatio44->GetXaxis()->SetBinLabel(8, "MT21 NBend"); + mTrackRatio44->GetXaxis()->SetBinLabel(9, "MT22 NBend"); + mTrackRatio44->GetYaxis()->SetTitle("Track 44/all"); + mTrackRatio44->SetMinimum(0.); + mTrackRatio44->SetMaximum(1.1); + mTrackRatio44->SetStats(0); + + mTrackBDetRatio44 = std::make_shared("TrackBDetRatio44", "Bend Track 44/all vs DetId", MID_NDE, 0., MID_NDE); + mTrackBDetRatio44->GetXaxis()->SetTitle("DetId"); + mTrackBDetRatio44->GetYaxis()->SetTitle("Track 44/all"); + mTrackBDetRatio44->SetMinimum(0.); + mTrackBDetRatio44->SetMaximum(1.1); + getObjectsManager()->startPublishing(mTrackBDetRatio44.get()); + mTrackBDetRatio44->SetStats(0); + + mTrackNBDetRatio44 = std::make_shared("TrackNBDetRatio44", "Non-Bend Track 44/all vs DetId", MID_NDE, 0., MID_NDE); + mTrackNBDetRatio44->GetXaxis()->SetTitle("DetId"); + mTrackNBDetRatio44->GetYaxis()->SetTitle("Track 44/all"); + mTrackNBDetRatio44->SetMinimum(0.); + mTrackNBDetRatio44->SetMaximum(1.1); + getObjectsManager()->startPublishing(mTrackNBDetRatio44.get()); + mTrackNBDetRatio44->SetStats(0); + + Double_t xEdges[4] = { -7., -0.2, 0.2, 7. }; + Double_t yEdges[10] = { 0., 1., 2., 3., 4., 5., 6., 7., 8., 9. }; + + mTrackDetRatio44Map11 = std::make_shared("TrackDetRatio44Map11", "MT11 Detector Track 44/all Map", 3, xEdges, 9, yEdges); + getObjectsManager()->startPublishing(mTrackDetRatio44Map11.get()); + mTrackDetRatio44Map11->GetXaxis()->SetTitle("Column"); + mTrackDetRatio44Map11->GetYaxis()->SetTitle("Line"); + mTrackDetRatio44Map11->SetMinimum(0.); + mTrackDetRatio44Map11->SetMaximum(1.1); + mTrackDetRatio44Map11->SetOption("colz"); + mTrackDetRatio44Map11->SetStats(0); + + mTrackDetRatio44Map12 = std::make_shared("TrackDetRatio44Map12", "MT12 Detector Track 44/all Map", 3, xEdges, 9, yEdges); + getObjectsManager()->startPublishing(mTrackDetRatio44Map12.get()); + mTrackDetRatio44Map12->GetXaxis()->SetTitle("Column"); + mTrackDetRatio44Map12->GetYaxis()->SetTitle("Line"); + mTrackDetRatio44Map12->SetMinimum(0.); + mTrackDetRatio44Map12->SetMaximum(1.1); + mTrackDetRatio44Map12->SetOption("colz"); + mTrackDetRatio44Map12->SetStats(0); + + mTrackDetRatio44Map21 = std::make_shared("TrackDetRatio44Map21", "MT21 Detector Track 44/all Map", 3, xEdges, 9, yEdges); + getObjectsManager()->startPublishing(mTrackDetRatio44Map21.get()); + mTrackDetRatio44Map21->GetXaxis()->SetTitle("Column"); + mTrackDetRatio44Map21->GetYaxis()->SetTitle("Line"); + mTrackDetRatio44Map21->SetMinimum(0.); + mTrackDetRatio44Map21->SetMaximum(1.1); + mTrackDetRatio44Map21->SetOption("colz"); + mTrackDetRatio44Map21->SetStats(0); + + mTrackDetRatio44Map22 = std::make_shared("TrackDetRatio44Map22", "MT22 Detector Track 44/all Map", 3, xEdges, 9, yEdges); + getObjectsManager()->startPublishing(mTrackDetRatio44Map22.get()); + mTrackDetRatio44Map22->GetXaxis()->SetTitle("Column"); + mTrackDetRatio44Map22->GetYaxis()->SetTitle("Line"); + mTrackDetRatio44Map22->SetMinimum(0.); + mTrackDetRatio44Map22->SetMaximum(1.1); + mTrackDetRatio44Map22->SetOption("colz"); + mTrackDetRatio44Map22->SetStats(0); + + mTrackLocRatio44 = std::make_shared("TrackLocRatio44", " Track 44/all vs LocId", MID_NLOC, 0., MID_NLOC); + mTrackLocRatio44->GetXaxis()->SetTitle("LocId"); + mTrackLocRatio44->GetYaxis()->SetTitle("Track 44/all"); + mTrackLocRatio44->SetMinimum(0.); + mTrackLocRatio44->SetMaximum(1.1); + getObjectsManager()->startPublishing(mTrackLocRatio44.get()); + mTrackLocRatio44->SetStats(0); + + mTrackBLocRatio44 = std::make_shared("TrackBLocRatio44", "Bend Track 44/all vs LocId", MID_NLOC, 0., MID_NLOC); + mTrackBLocRatio44->GetXaxis()->SetTitle("LocId"); + mTrackBLocRatio44->GetYaxis()->SetTitle("Track 44/all"); + mTrackBLocRatio44->SetMinimum(0.); + mTrackBLocRatio44->SetMaximum(1.1); + getObjectsManager()->startPublishing(mTrackBLocRatio44.get()); + mTrackBLocRatio44->SetStats(0); + + mTrackNBLocRatio44 = std::make_shared("TrackNBLocRatio44", "Non-Bend Track 44/all vs LocId", MID_NLOC, 0., MID_NLOC); + mTrackNBLocRatio44->GetXaxis()->SetTitle("LocId"); + mTrackNBLocRatio44->GetYaxis()->SetTitle("Track 44/all"); + mTrackNBLocRatio44->SetMinimum(0.); + mTrackNBLocRatio44->SetMaximum(1.1); + getObjectsManager()->startPublishing(mTrackNBLocRatio44.get()); + mTrackNBLocRatio44->SetStats(0); + + mTrackLocalBoardsRatio44Map = std::make_shared("TrackLocalBoardsRatio44Map", "Local boards Track 44/all Map", 14, -7, 7, 36, 0, 9); + getObjectsManager()->startPublishing(mTrackLocalBoardsRatio44Map.get()); + mTrackLocalBoardsRatio44Map->GetXaxis()->SetTitle("Column"); + mTrackLocalBoardsRatio44Map->GetYaxis()->SetTitle("Line"); + mTrackLocalBoardsRatio44Map->SetMinimum(0.); + mTrackLocalBoardsRatio44Map->SetMaximum(1.1); + mTrackLocalBoardsRatio44Map->SetOption("colz"); + mTrackLocalBoardsRatio44Map->SetStats(0); + + mTrackLocalBoardsBRatio44Map = std::make_shared("TrackLocalBoardsBRatio44Map", "Local boards Bend Track 44/all Map", 14, -7, 7, 36, 0, 9); + getObjectsManager()->startPublishing(mTrackLocalBoardsBRatio44Map.get()); + mTrackLocalBoardsBRatio44Map->GetXaxis()->SetTitle("Column"); + mTrackLocalBoardsBRatio44Map->GetYaxis()->SetTitle("Line"); + mTrackLocalBoardsBRatio44Map->SetMinimum(0.); + mTrackLocalBoardsBRatio44Map->SetMaximum(1.1); + mTrackLocalBoardsBRatio44Map->SetOption("colz"); + mTrackLocalBoardsBRatio44Map->SetStats(0); + + mTrackLocalBoardsNBRatio44Map = std::make_shared("TrackLocalBoardsNBRatio44Map", "Local boards Non-Bend Track 44/all Map", 14, -7, 7, 36, 0, 9); + getObjectsManager()->startPublishing(mTrackLocalBoardsNBRatio44Map.get()); + mTrackLocalBoardsNBRatio44Map->GetXaxis()->SetTitle("Column"); + mTrackLocalBoardsNBRatio44Map->GetYaxis()->SetTitle("Line"); + mTrackLocalBoardsNBRatio44Map->SetMinimum(0.); + mTrackLocalBoardsNBRatio44Map->SetMaximum(1.1); + mTrackLocalBoardsNBRatio44Map->SetOption("colz"); + mTrackLocalBoardsNBRatio44Map->SetStats(0); +} +void TracksQcTask::startOfActivity(const Activity& activity) +{ + ILOG(Info, Devel) << "startOfActivity " << activity.mId << ENDM; + reset(); +} + +void TracksQcTask::startOfCycle() +{ +} + +void TracksQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto tracks = ctx.inputs().get>("tracks"); + auto rofs = ctx.inputs().get>("trackrofs"); + + mNbTracksTF->Fill(0.5, 1.); + // auto tracks = o2::mid::specs::getData(ctx, "tracks", o2::mid::EventType::Standard); + // auto rofs = o2::mid::specs::getRofs(ctx, "tracks", o2::mid::EventType::Standard); + + int multTracks; + float DeltaZ = o2::mid::geoparams::DefaultChamberZ[0] - o2::mid::geoparams::DefaultChamberZ[3]; + float Zf = -975.; // Zf= position mid-dipole + float p0 = 3.; // T.m for charge = 1 + float theta, eta; + float thetaI, etaI; + float alpha, phi; + float thetaD, pT; + + for (const auto& rofRecord : rofs) { // loop ROFRecords == Events // + // printf("========================================================== \n"); + // printf("Tracks :: %05d ROF with first entry %05zu and nentries %02zu , BC %05d, ORB %05d , EventType %02d\n", nROF, rofRecord.firstEntry, rofRecord.nEntries, rofRecord.interactionRecord.bc, rofRecord.interactionRecord.orbit,rofRecord.eventType); + mROF++; + multTracks = 0; + mTrackBCCounts->Fill(rofRecord.interactionRecord.bc, rofRecord.nEntries); + + // if(rofRecord.nEntries!=1)continue;/// TEST ONE TACKS ONLY !!! + + for (auto& track : tracks.subspan(rofRecord.firstEntry, rofRecord.nEntries)) { // loop Tracks in ROF// + multTracks += 1; + auto isRightSide = o2::mid::detparams::isRightSide(track.getFiredDEId()); + int deIndex = track.getFiredDEId(); + int rpcLine = o2::mid::detparams::getRPCLine(track.getFiredDEId()); + int colId = track.getFiredColumnId(); + int lineId = track.getFiredLineId(); + + mTrackMapXY->Fill(track.getPositionX(), track.getPositionY(), 1); + mTrackDevX->Fill(track.getDirectionX() * DeltaZ); + mTrackDevY->Fill(track.getDirectionY() * DeltaZ); + mTrackDevXY->Fill(track.getDirectionX() * DeltaZ, track.getDirectionY() * DeltaZ, 1); + theta = TMath::ATan(TMath::Sqrt(track.getPositionX() * track.getPositionX() + track.getPositionY() * track.getPositionY()) / TMath::Abs(track.getPositionZ())); + eta = -log(TMath::Tan(theta / 2)); + mTrackEta->Fill(eta); + theta = theta * TMath::RadToDeg(); + mTrackTheta->Fill(theta); + alpha = TMath::ATan(track.getPositionY() / TMath::Abs(track.getPositionZ())) * TMath::RadToDeg(); + mTrackAlpha->Fill(alpha); + phi = TMath::ATan2(track.getPositionY(), track.getPositionX()) * TMath::RadToDeg(); + mTrackPhi->Fill(phi); + + // Propagate the track position parameters to MT11, MT21 and Zf /// VERIFIER !!!! + float dZ1 = o2::mid::geoparams::DefaultChamberZ[0] - track.getPositionZ(); + float X1 = track.getPositionX() + track.getDirectionX() * dZ1; + float Y1 = track.getPositionY() + track.getDirectionY() * dZ1; + + float dZ2 = o2::mid::geoparams::DefaultChamberZ[2] - track.getPositionZ(); + float X2 = track.getPositionX() + track.getDirectionX() * dZ2; + float Y2 = track.getPositionY() + track.getDirectionY() * dZ2; + + float dZf = Zf - track.getPositionZ(); + float Xf = X1 * Zf / o2::mid::geoparams::DefaultChamberZ[0]; + float Yf = Y2 - (Y2 - Y1) * (o2::mid::geoparams::DefaultChamberZ[2] - Zf) / (o2::mid::geoparams::DefaultChamberZ[2] - o2::mid::geoparams::DefaultChamberZ[0]); + // printf("\n X =%02f ; Y =%02f ; Z =%02f ; dX =%02f ; dY =%02f ; \n",track.getPositionX(),track.getPositionY(),track.getPositionZ(),track.getDirectionX(),track.getDirectionY()); + // printf(" X1 =%02f ; Y1 =%02f ; Z1 =%02f ; \n",X1,Y1, o2::mid::geoparams::DefaultChamberZ[0]); + // printf(" X2 =%02f ; Y2 =%02f ; Z2 =%02f ; \n",X2,Y2, o2::mid::geoparams::DefaultChamberZ[2]); + // printf(" Xf =%02f ; Yf =%02f ; Zf =%02f ; \n",Xf,Yf,Zf); + + thetaI = TMath::ATan(TMath::Sqrt(Xf * Xf + Yf * Yf) / TMath::Abs(Zf)); + etaI = -log(TMath::Tan(thetaI / 2)); + mTrackEtaI->Fill(etaI); + mTrackThetaI->Fill(thetaI * TMath::RadToDeg()); + + thetaD = (o2::mid::geoparams::DefaultChamberZ[0] * Y2 - o2::mid::geoparams::DefaultChamberZ[2] * Y1) / ((o2::mid::geoparams::DefaultChamberZ[2] - o2::mid::geoparams::DefaultChamberZ[0]) * Zf); + mTrackThetaD->Fill(thetaD * TMath::RadToDeg()); + pT = TMath::Abs(p0 / thetaD) * (TMath::Sqrt(Xf * Xf + Yf * Yf) / TMath::Abs(Zf)); + // printf("==========================> thetaD = %05f ; pT = %05f \n",thetaD,pT); + mTrackPT->Fill(pT); + + /// Efficiency part + uint32_t EffWord = track.getEfficiencyWord(); + int EffFlag = track.getEfficiencyFlag(); + uint8_t HitMap = track.getHitMap(); + float BDetEff = 0.; + float NBDetEff = 0.; + float DetEff = 0.; + if (EffFlag > 0) { + multTracksTot++; + if (HitMap == 0xFF) { + mTrackRatio44->Fill(0.5, 1.); // multTracks44Tot++; + mGTrackRatio44->Fill(1.); + } else { + mTrackRatio44->Fill(0.5, 0.); + mGTrackRatio44->Fill(0.); + } + + // track.isFiredChamber(i,j) :: i=0->3 (MT11->MT22) ; j=0->1 (BP->NBP) + uint8_t HitMapB = HitMap & 0xF; + if (track.isFiredChamber(0, 0)) + mTrackRatio44->Fill(1.5, 1.); + else + mTrackRatio44->Fill(1.5, 0.); + if (track.isFiredChamber(1, 0)) + mTrackRatio44->Fill(2.5, 1.); + else + mTrackRatio44->Fill(2.5, 0.); + if (track.isFiredChamber(2, 0)) + mTrackRatio44->Fill(3.5, 1.); + else + mTrackRatio44->Fill(3.5, 0.); + if (track.isFiredChamber(3, 0)) + mTrackRatio44->Fill(4.5, 1.); + else + mTrackRatio44->Fill(4.5, 0.); + + uint8_t HitMapNB = (HitMap >> 4) & 0xF; + if (track.isFiredChamber(0, 1)) + mTrackRatio44->Fill(5.5, 1.); + else + mTrackRatio44->Fill(5.5, 0.); + if (track.isFiredChamber(1, 1)) + mTrackRatio44->Fill(6.5, 1.); + else + mTrackRatio44->Fill(6.5, 0.); + if (track.isFiredChamber(2, 1)) + mTrackRatio44->Fill(7.5, 1.); + else + mTrackRatio44->Fill(7.5, 0.); + if (track.isFiredChamber(3, 1)) + mTrackRatio44->Fill(8.5, 1.); + else + mTrackRatio44->Fill(8.5, 0.); + + if (EffFlag > 1) { // RPCeff + int DetId0 = track.getFiredDEId(); + int chamb = o2::mid::detparams::getChamber(track.getFiredDEId()); + + if (chamb == 1) + DetId0 = DetId0 - 9; // if MT11 not fired + + // if (DetId0==0) printf("************ DetId0 = %i, chamb = %i , HitMap = %X \n",DetId0,chamb,HitMap); + for (int i = 0; i < 4; i++) { + + if (track.isFiredChamber(i, 0)) + mTrackBDetRatio44->Fill(DetId0 + 9 * i, 1.); // fired + else + mTrackBDetRatio44->Fill(DetId0 + 9 * i, 0.); + + if (track.isFiredChamber(i, 1)) + mTrackNBDetRatio44->SetBinContent(DetId0 + 9 * i, 1.); // fired + else + mTrackNBDetRatio44->Fill(DetId0 + 9 * i, 0.); + + std::array, 4> maps = { + mTrackDetRatio44Map11, mTrackDetRatio44Map12, mTrackDetRatio44Map21, mTrackDetRatio44Map22 + }; + auto sideBin = isRightSide == 0 ? -1 : 1; + maps[i]->Fill(sideBin, rpcLine, track.isFiredChamber(i, 0) ? 1.0 : 0.0); + maps[i]->Fill(sideBin, rpcLine, track.isFiredChamber(i, 1) ? 1.0 : 0.0); + } + if (EffFlag > 2) { // LocBoardeff + auto localBoard = mMapping.getBoardId(lineId, colId, deIndex); + + int nZoneHistoX = 1; + if (mMapping.getLastBoardBP(colId, deIndex) == 1) + nZoneHistoX = 2; + else if (mMapping.getLastBoardBP(colId, deIndex) == 0) + nZoneHistoX = 4; + + for (int board = mMapping.getFirstBoardBP(colId, deIndex), lastBoard = mMapping.getLastBoardBP(colId, deIndex); board <= lastBoard; board++) { + // These are the existing bend boards for this column Id (board = n°board in the column) + 1 (for non-bend) + int Fired = 0; + int BFired = 0; + int NBFired = 0; + if (board == lineId) { + // if (localBoard < 10)printf(" Loc %i ====> Fired ; col %i, rpcLine %i nZoneX %i\n", mMapping.getBoardId(board, colId, deIndex), colId, rpcLine, nZoneHistoX); + if (HitMap == 0xFF) + Fired = 1; + if (HitMapB == 0xF) + BFired = 1; + if (HitMapNB == 0xF) + NBFired = 1; + } + + double linePos0 = rpcLine; + if (localBoard == mMapping.getBoardId(board, colId, deIndex)) { // only fire board in the line + mTrackLocRatio44->Fill(localBoard - 1, Fired); + mTrackBLocRatio44->Fill(localBoard - 1, BFired); + mTrackNBLocRatio44->Fill(localBoard - 1, NBFired); + linePos0 = rpcLine + 0.25 * board; + if ((nZoneHistoX == 2) && (board == 1)) + linePos0 += 0.25; + for (int ib = 0; ib < nZoneHistoX; ib++) { + double linePos = linePos0 + (0.25 * ib); + if (isRightSide == 1) { + mTrackLocalBoardsRatio44Map->Fill(colId + 0.5, linePos, Fired); + mTrackLocalBoardsBRatio44Map->Fill(colId + 0.5, linePos, BFired); + mTrackLocalBoardsNBRatio44Map->Fill(colId + 0.5, linePos, NBFired); + } else { + mTrackLocalBoardsRatio44Map->Fill(-colId - 0.5, linePos, Fired); + mTrackLocalBoardsBRatio44Map->Fill(-colId - 0.5, linePos, BFired); + mTrackLocalBoardsNBRatio44Map->Fill(-colId - 0.5, linePos, NBFired); + } + } // board in line loop + } // board loop + } // only fire board in the line + } //(EffFlag>2) + } //(EffFlag>1) + } // Efficiency part (EffFlag>0) + } // tracks in ROF + mMultTracks->Fill(multTracks); + + } // ROFRecords // +} + +void TracksQcTask::endOfCycle() +{ +} + +void TracksQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Info, Devel) << "endOfActivity" << ENDM; + reset(); +} + +void TracksQcTask::reset() +{ + + ILOG(Info, Devel) << "Resetting the histogram" << ENDM; + + mNbTracksTF->Reset(); + mTrackMapXY->Reset(); + mTrackDevX->Reset(); + mTrackDevY->Reset(); + mTrackDevXY->Reset(); + mTrackTheta->Reset(); + mTrackEta->Reset(); + mTrackThetaI->Reset(); + mTrackEtaI->Reset(); + mTrackAlpha->Reset(); + mTrackPhi->Reset(); + mTrackThetaD->Reset(); + mTrackPT->Reset(); + mTrackRatio44->Reset(); + mGTrackRatio44->Reset(); + mTrackBDetRatio44->Reset(); + mTrackNBDetRatio44->Reset(); + mTrackLocRatio44->Reset(); + mTrackBLocRatio44->Reset(); + mTrackNBLocRatio44->Reset(); + mTrackLocalBoardsRatio44Map->Reset(); + mTrackLocalBoardsBRatio44Map->Reset(); + mTrackLocalBoardsNBRatio44Map->Reset(); + + mTrackDetRatio44Map11->Reset(); + mTrackDetRatio44Map12->Reset(); + mTrackDetRatio44Map21->Reset(); + mTrackDetRatio44Map22->Reset(); + + mTrackBCCounts->Reset(); +} + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/MUON/MID/src/TrendingTaskConfigMID.cxx b/Modules/MUON/MID/src/TrendingTaskConfigMID.cxx new file mode 100644 index 0000000000..7bd48567bd --- /dev/null +++ b/Modules/MUON/MID/src/TrendingTaskConfigMID.cxx @@ -0,0 +1,79 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskConfigMID.cxx +/// \author Valerie Ramillien based on Piotr Konopka work +/// + +#include "MID/TrendingTaskConfigMID.h" +#include +using boost::property_tree::ptree; +namespace o2::quality_control::postprocessing +{ + +TrendingTaskConfigMID::TrendingTaskConfigMID(std::string id, const boost::property_tree::ptree& config) + : PostProcessingConfig(id, config) +{ + + for (const auto& plotConfig : config.get_child("qc.postprocessing." + id + ".plots")) { + + if (const auto& sourceNames = plotConfig.second.get_child_optional("names"); + sourceNames.has_value()) { + const auto& sourceVarexps = + plotConfig.second.get_child_optional("varexp"); // take all varexps + const auto& sourceTitles = + plotConfig.second.get_child_optional("title"); // take all titles + + ptree::const_iterator itname = sourceNames.value().begin(); + ptree::const_iterator itexp = sourceVarexps.value().begin(); + ptree::const_iterator ittitle = sourceTitles.value().begin(); + + while (itname != sourceNames.value().end() || + itexp != sourceVarexps.value().end() || + ittitle != sourceTitles.value().end()) { + plots.push_back({ itname->second.data(), ittitle->second.data(), + itexp->second.data(), + plotConfig.second.get("selection", ""), + plotConfig.second.get("option", ""), + plotConfig.second.get("graphErrors", "") }); + + itname++; + itexp++; + ittitle++; + } + } + } + + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + id + ".dataSources")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + + for (const auto& sourceName : sourceNames.value()) { + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + sourceName.second.data(), + dataSourceConfig.second.get("reductorName"), + dataSourceConfig.second.get("moduleName") }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name"), + dataSourceConfig.second.get("reductorName"), + dataSourceConfig.second.get("moduleName") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + id + ".dataSources'"); + } + } +} + +} // namespace o2::quality_control::postprocessing diff --git a/Modules/MUON/MID/test/testQcMID.cxx b/Modules/MUON/MID/test/testQcMID.cxx new file mode 100644 index 0000000000..17d9a74e0a --- /dev/null +++ b/Modules/MUON/MID/test/testQcMID.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testMID.cxx +/// \author Xavier Lopez +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::mid +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::mid diff --git a/Modules/PHOS/CMakeLists.txt b/Modules/PHOS/CMakeLists.txt new file mode 100644 index 0000000000..c31503d2fc --- /dev/null +++ b/Modules/PHOS/CMakeLists.txt @@ -0,0 +1,75 @@ +# ---- Library ---- + +add_library(O2QcPHOS) + +target_sources(O2QcPHOS PRIVATE src/TH1Fraction.cxx + src/TH2Fraction.cxx + src/TH2FMean.cxx + src/TH2SBitmask.cxx + src/RawQcTask.cxx + src/RawCheck.cxx + src/ClusterQcTask.cxx + src/ClusterCheck.cxx + src/CalibQcTask.cxx) + +target_include_directories( + O2QcPHOS + PUBLIC $ $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +target_link_libraries(O2QcPHOS PUBLIC O2QualityControl O2::DataFormatsQualityControl O2::PHOSBase O2::PHOSReconstruction ROOT::Spectrum) + +add_root_dictionary(O2QcPHOS + HEADERS include/PHOS/TH1Fraction.h + include/PHOS/TH2Fraction.h + include/PHOS/TH2FMean.h + include/PHOS/TH2SBitmask.h + include/PHOS/ClusterQcTask.h + include/PHOS/ClusterCheck.h + include/PHOS/RawQcTask.h + include/PHOS/RawCheck.h + include/PHOS/CalibQcTask.h + LINKDEF include/PHOS/LinkDef.h) + +install(TARGETS O2QcPHOS + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install ( + DIRECTORY etc DESTINATION Modules/PHOS +) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/PHOS + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Executables ---- + +# ---- Tests ---- +# +#set( +# TEST_SRCS +#) +# +#foreach(test ${TEST_SRCS}) +# get_filename_component(test_name ${test} NAME) +# string(REGEX REPLACE ".cxx" "" test_name ${test_name}) +# +# add_executable(${test_name} ${test}) +# target_link_libraries(${test_name} PRIVATE ${MODULE_NAME} Boost::unit_test_framework) +# add_test(NAME ${test_name} COMMAND ${test_name}) +# set_property(TARGET ${test_name} +# PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) +# set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +#endforeach() + +# ---- Install config files ---- + +install(FILES etc/phosCalibLED.json + etc/phosCalibPed.json + etc/phosClusters.json + etc/phosPedestal.json + etc/phosRawClu.json + etc/phosRaw.json + DESTINATION etc) diff --git a/Modules/PHOS/README.md b/Modules/PHOS/README.md new file mode 100644 index 0000000000..4723ef2356 --- /dev/null +++ b/Modules/PHOS/README.md @@ -0,0 +1,80 @@ +# This is the documentation of the PHOS Quality Control + +## PHOS QC contains 3 modules: + - RawQcTask analyzes raw data converted to cells and HW errors + + - ClusterQCTask analyzes clusters + + - CalibQcTask analyzes modifications introduced by calibration + +## Classification of HW decoding errors + HW errors summary is shown in histo **ErrorTypePerDDL**. + - X axis shows FEE card number (0...13) of first branch, bin 15 means general SRU error or unknown FEC number, bin 16 - error n TRU. Bins 17..32 the same for second branch of same DDL. + - Y axis represents DDL 0...13 + - Bin content shows summary or errors: as there might be several kinds of errors in same FEC errors are paked as bits of the bin content and new error adds bit if it was not set before. The number of errors are shown in histogram NumberOfErrors with same X and Y axis. + Bit meanings: + - bit 0: not used + - bit 1: not used + - bit 2: not used + - bit 3: incorrect hw address: wrong chip number + - bit 4: error in mapping + - bit 5: channel header error (header mark not found) + - bit 6: channel payload error, incorrect payload size + - bit 7: incorrect hw address + + - bins 15 or 30 (general SRU errors) + - bit 2: wrong FEC number + - bins 16 or 32 (general SRU errors) + bit 1: wrong TRU header + + - non-existing DDL 14 corresponds to error in header + - FEC = 0: Page was not found (page index outside range) + - FEC = 1: Header cannot be decoded (format incorrect) + - FEC = 2: Payload cannot be decoded (format incorrect) + - FEC = 3: Header in memory not belonging to requested superpage + - FEC = 4: Payload in memory not belonging to requested superpage + - FEC = 16: wrong DDL number + +## Avaiable configurations +```bash +# monitor pedestal run: HG/LG pedestals (mean and RMS) and HW decoding errors +02-raw-file-reader-workflow --input-conf PHSraw.cfg | \ +o2-phos-reco-workflow --input-type raw --output-type cells --disable-root-output --pedestal on --keepHGLG on | \ +o2-qc --config json://${QUALITYCONTROL_ROOT}/phosPedestal.json + +# monitor raw data: cells and HW decoding errors +o2-raw-file-reader-workflow --input-conf PHSraw.cfg | \ +o2-phos-reco-workflow --input-type raw --output-type cells --disable-root-output | \ +o2-qc --config json://${QUALITYCONTROL_ROOT}/phosRaw.json + +# monitor raw data: cells and HW decoding errors and clusters +o2-raw-file-reader-workflow --input-conf PHSraw.cfg | \ +o2-phos-reco-workflow --input-type raw --output-type cells --disable-root-output | \ +o2-phos-reco-workflow --input-type cells --output-type clusters --disable-root-input --disable-root-output --disable-mc | \ +o2-qc --config json://${QUALITYCONTROL_ROOT}/phosRawClu.json + +# monitor clusters only +o2-phos-reco-workflow --input-type digits --output-type clusters --disable-mc --disable-root-output | \ +o2-qc --config json://${QUALITYCONTROL_ROOT}/phosClusters.json +``` + +## Calibration QC + quality of calibration is checked by comparing existing CCDB entry and new one. Module **CalibQcTask** shows difference between new and old calibrations. + Available configurations +```bash +# Pedestal calibration +o2-raw-file-reader-workflow --input-conf PHSraw.cfg | \ +o2-phos-reco-workflow --input-type raw --output-type cells --disable-root-output --pedestal on --keepHGLG on | \ +o2-phos-calib-workflow --pedestals --forceupdate | \ +o2-calibration-ccdb-populator-workflow | \ +o2-qc --config json://${QUALITYCONTROL_ROOT}/phosCalibPed.json + +# HG/LG ratio with LED runs +o2-raw-file-reader-workflow --input-conf PHSraw.cfg | \ +o2-phos-reco-workflow --input-type raw --output-type cells --disable-root-output --keepHGLG on | \ +o2-phos-calib-workflow --hglgratio |\ +o2-calibration-ccdb-populator-workflow | \ +o2-qc --config json://${QUALITYCONTROL_ROOT}/phosCalibLED.json +``` + + diff --git a/Modules/PHOS/etc/phos-raw-clusters-epn-staging.json b/Modules/PHOS/etc/phos-raw-clusters-epn-staging.json new file mode 100644 index 0000000000..5c8394dc6f --- /dev/null +++ b/Modules/PHOS/etc/phos-raw-clusters-epn-staging.json @@ -0,0 +1,383 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "alio2-cr1-hv-mvs00:8083", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "influxdb-unix:///tmp/telegraf.sock" + }, + "consul": { + "url": "http://alio2-cr1-hv-mvs00:8500" + }, + "conditionDB": { + "url": "o2-ccdb.internal" + }, + "bookkeeping": { + "url": "" + } + }, + "tasks": { + "RawTask": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "rawerr:PHS/RAWHWERRORS;cells:PHS/CELLS/0;cellstr:PHS/CELLTRIGREC/0" + }, + "taskParameters": { + "physics": "on" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "alio2-cr1-qc04.cern.ch", + "remotePort": "47757", + "mergingMode": "delta", + "localControl": "odc" + }, + "ClusterTask": { + "active": "true", + "className": "o2::quality_control_modules::phos::ClusterQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "clusters:PHS/CLUSTERS;clustertr:PHS/CLUSTERTRIGREC" + }, + "taskParameters": { + "": "" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "alio2-cr1-qc04.cern.ch", + "remotePort": "47758", + "mergingMode": "delta", + "localControl": "odc" + } + }, + "checks": { + "CellsIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "RawTask", + "MOs": [ + "CellHGOccupancyM1", + "CellHGOccupancyM2", + "CellHGOccupancyM3", + "CellHGOccupancyM4" + ] + } + ] + }, + "ClustersIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "ClusterTask", + "MOs": [ + "SpectrumM1", + "SpectrumM2", + "SpectrumM3", + "SpectrumM4" + ] + } + ] + }, + "ErrorsCheck": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawCheck", + "moduleName": "QcPHOS", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mErrorOccuranceThreshold0": "0.1", + "mErrorOccuranceThreshold1": "0.1", + "mErrorOccuranceThreshold2": "0.1", + "mErrorOccuranceThreshold3": "0.1", + "mErrorOccuranceThreshold4": "0.1" + }, + "dataSource": [ + { + "type": "Task", + "name": "RawTask", + "MOs": [ + "ErrorTypeOccurance" + ] + } + ] + }, + "CellsCheck": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawCheck", + "moduleName": "QcPHOS", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mToleratedBadChannelsM1": "1", + "mToleratedBadChannelsM2": "1", + "mToleratedBadChannelsM3": "1", + "mToleratedBadChannelsM4": "1" + }, + "dataSource": [ + { + "type": "Task", + "name": "RawTask", + "MOs": [ + "CellHGOccupancyM1", + "CellHGOccupancyM2", + "CellHGOccupancyM3", + "CellHGOccupancyM4" + ] + } + ] + }, + "ClustersCheck": { + "active": "true", + "className": "o2::quality_control_modules::phos::ClusterCheck", + "moduleName": "QcPHOS", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mMaxCluEnergyMean2": "1.0", + "mMinCluEnergyMean1": "9.0" + }, + "dataSource": [ + { + "type": "Task", + "name": "ClusterTask", + "MOs": [ + "SpectrumM1", + "SpectrumM2", + "SpectrumM3", + "SpectrumM4" + ] + } + ] + } + }, + "postprocessing": { + "PhysicsTrending": { + "active": "true", + "className": "o2::quality_control::postprocessing::SliceTrendingTask", + "moduleName": "QualityControl", + "detectorName": "PHS", + "resumeTrend": "false", + "producePlotsOnUpdate": "true", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:PHS/MO/ClusterTask/SpectrumM1" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "PHS/MO/ClusterTask", + "names": [ + "SpectrumM1", + "SpectrumM2", + "SpectrumM3", + "SpectrumM4" + ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ + "1.", + "10." + ] + ], + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_cluEnergyM1", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM1.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluEnergyM2", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM2.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluEnergyM3", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM3.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluEnergyM4", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM4.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + } + ] + }, + "QualityTask": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QcCommon", + "detectorName": "PHS", + "qualityGroups": [ + { + "name": "global", + "title": "GLOBAL PHS QUALITY", + "path": "PHS/QO/GlobalQuality", + "ignoreQualitiesDetails": [ + "Null", + "Good", + "Medium", + "Bad" + ], + "inputObjects": [ + { + "name": "GlobalQuality", + "title": "Global PHS Quality", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform PHS on-call", + "messageGood": "All checks are OK", + "messageNull": "Not enough stat" + } + ] + }, + { + "name": "details", + "title": "PHS DETAILS", + "path": "PHS/QO", + "ignoreQualitiesDetails": [], + "inputObjects": [ + { + "name": "CellsIncrease", + "title": "Number of cells increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "ClustersIncrease", + "title": "Number of clusters increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "CellsCheck", + "title": "Cells check", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform PHS on-call", + "messageGood": "", + "messageNull": "" + }, + { + "name": "ClustersCheck", + "title": "Clusters check", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform PHS on-call", + "messageGood": "", + "messageNull": "Not enough stat" + }, + { + "name": "ErrorsCheck", + "title": "Errors check", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform PHS on-call", + "messageGood": "", + "messageNull": "" + } + ] + } + ], + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:PHS/QO/GlobalQuality/GlobalQuality" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "aggregators": { + "GlobalQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "PHS", + "dataSource": [ + { + "type": "Check", + "name": "CellsIncrease" + }, + { + "type": "Check", + "name": "ClustersIncrease" + }, + { + "type": "Check", + "name": "CellsCheck" + }, + { + "type": "Check", + "name": "ClustersCheck" + }, + { + "type": "Check", + "name": "ErrorsCheck" + } + ] + } + } + }, + "dataSamplingPolicies": [] +} \ No newline at end of file diff --git a/Modules/PHOS/etc/phos-raw-clusters-epn.json b/Modules/PHOS/etc/phos-raw-clusters-epn.json new file mode 100644 index 0000000000..c3b96b8b2d --- /dev/null +++ b/Modules/PHOS/etc/phos-raw-clusters-epn.json @@ -0,0 +1,407 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ali-qcdb.cern.ch:8083", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "monitoring": { + "url": "influxdb-unix:///tmp/telegraf.sock" + }, + "consul": { + "url": "http://localhost:8500" + }, + "conditionDB": { + "url": "o2-ccdb.internal" + }, + "bookkeeping": { + "url": "ali-bookkeeping:4001" + } + }, + "tasks": { + "RawTask": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "rawerr:PHS/RAWHWERRORS;cells:PHS/CELLS/0;cellstr:PHS/CELLTRIGREC/0" + }, + "taskParameters": { + "physics": "on" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "alio2-cr1-qme06.cern.ch", + "remotePort": "47757", + "mergingMode": "delta", + "localControl": "odc" + }, + "ClusterTask": { + "active": "true", + "className": "o2::quality_control_modules::phos::ClusterQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "clusters:PHS/CLUSTERS;clustertr:PHS/CLUSTERTRIGREC" + }, + "taskParameters": { + "": "" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "alio2-cr1-qme06.cern.ch", + "remotePort": "47758", + "mergingMode": "delta", + "localControl": "odc" + } + }, + "checks": { + "CellsIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "RawTask", + "MOs": [ + "CellHGOccupancyM1", + "CellHGOccupancyM2", + "CellHGOccupancyM3", + "CellHGOccupancyM4" + ] + } + ] + }, + "ClustersIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "ClusterTask", + "MOs": [ + "SpectrumM1", + "SpectrumM2", + "SpectrumM3", + "SpectrumM4" + ] + } + ] + }, + "ErrorsCheck": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawCheck", + "moduleName": "QcPHOS", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mErrorOccuranceThreshold0": "0.1", + "mErrorOccuranceThreshold1": "0.1", + "mErrorOccuranceThreshold2": "0.1", + "mErrorOccuranceThreshold3": "0.1", + "mErrorOccuranceThreshold4": "0.1" + }, + "dataSource": [ + { + "type": "Task", + "name": "RawTask", + "MOs": [ + "ErrorTypeOccurance" + ] + } + ] + }, + "CellsCheck": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawCheck", + "moduleName": "QcPHOS", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mToleratedBadChannelsM1": "1", + "mToleratedBadChannelsM2": "1", + "mToleratedBadChannelsM3": "1", + "mToleratedBadChannelsM4": "1", + "mToleratedBadChannels are used for": "BadMapSummary check", + "which is not currently on the MOs list": "", + "mToleratedDeviatedBranchesM1": "0", + "mToleratedDeviatedBranchesM2": "0", + "mToleratedDeviatedBranchesM3": "0", + "mToleratedDeviatedBranchesM4": "0", + "mBranchOccupancyDeviationAllowedM1": "1.5", + "mBranchOccupancyDeviationAllowedM2": "1.5", + "mBranchOccupancyDeviationAllowedM3": "1.5", + "mBranchOccupancyDeviationAllowedM4": "1.5" + }, + "dataSource": [ + { + "type": "Task", + "name": "RawTask", + "MOs": [ + "CellHGOccupancyM1", + "CellHGOccupancyM2", + "CellHGOccupancyM3", + "CellHGOccupancyM4" + ] + } + ] + }, + "ClustersCheck": { + "active": "true", + "className": "o2::quality_control_modules::phos::ClusterCheck", + "moduleName": "QcPHOS", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mMinCluEnergyMean1": "1.5", + "mMinCluEnergyMean2": "1.5", + "mMinCluEnergyMean3": "1.5", + "mMinCluEnergyMean4": "1.5", + "mMaxCluEnergyMean1": "2.0", + "mMaxCluEnergyMean2": "2.0", + "mMaxCluEnergyMean3": "2.0", + "mMaxCluEnergyMean4": "2.0", + "mCluEnergyRangeL1": "1.", + "mCluEnergyRangeL2": "1.", + "mCluEnergyRangeL3": "1.", + "mCluEnergyRangeL4": "1.", + "mCluEnergyRangeR1": "10.", + "mCluEnergyRangeR2": "10.", + "mCluEnergyRangeR3": "10.", + "mCluEnergyRangeR4": "10." + }, + "dataSource": [ + { + "type": "Task", + "name": "ClusterTask", + "MOs": [ + "SpectrumM1", + "SpectrumM2", + "SpectrumM3", + "SpectrumM4" + ] + } + ] + } + }, + "postprocessing": { + "PhysicsTrending": { + "active": "true", + "className": "o2::quality_control::postprocessing::SliceTrendingTask", + "moduleName": "QualityControl", + "detectorName": "PHS", + "resumeTrend": "false", + "producePlotsOnUpdate": "true", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:PHS/MO/ClusterTask/SpectrumM1" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "PHS/MO/ClusterTask", + "names": [ + "SpectrumM1", + "SpectrumM2", + "SpectrumM3", + "SpectrumM4" + ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ + "1.", + "10." + ] + ], + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_cluEnergyM1", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM1.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluEnergyM2", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM2.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluEnergyM3", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM3.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluEnergyM4", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM4.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + } + ] + }, + "QualityTask": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QcCommon", + "detectorName": "PHS", + "qualityGroups": [ + { + "name": "global", + "title": "GLOBAL PHS QUALITY", + "path": "PHS/QO/GlobalQuality", + "ignoreQualitiesDetails": [ + "Null", + "Good", + "Medium", + "Bad" + ], + "inputObjects": [ + { + "name": "GlobalQuality", + "title": "Global PHS Quality", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform PHS on-call", + "messageGood": "All checks are OK", + "messageNull": "There are empty histograms!!!" + } + ] + }, + { + "name": "details", + "title": "PHS DETAILS", + "path": "PHS/QO", + "ignoreQualitiesDetails": [], + "inputObjects": [ + { + "name": "CellsIncrease", + "title": "Number of cells increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "ClustersIncrease", + "title": "Number of clusters increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "CellsCheck", + "title": "Cells check", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform PHS on-call", + "messageGood": "", + "messageNull": "" + }, + { + "name": "ClustersCheck", + "title": "Clusters check", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform PHS on-call", + "messageGood": "", + "messageNull": "Cluster spectrum histograms are empty! Inform the on-call" + }, + { + "name": "ErrorsCheck", + "title": "Errors check", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform PHS on-call", + "messageGood": "", + "messageNull": "" + } + ] + } + ], + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:PHS/QO/GlobalQuality/GlobalQuality" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "aggregators": { + "GlobalQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "PHS", + "dataSource": [ + { + "type": "Check", + "name": "CellsIncrease" + }, + { + "type": "Check", + "name": "ClustersIncrease" + }, + { + "type": "Check", + "name": "CellsCheck" + }, + { + "type": "Check", + "name": "ClustersCheck" + }, + { + "type": "Check", + "name": "ErrorsCheck" + } + ] + } + } + }, + "dataSamplingPolicies": [] +} \ No newline at end of file diff --git a/Modules/PHOS/etc/phosCalib.json b/Modules/PHOS/etc/phosCalib.json new file mode 100644 index 0000000000..375fe19427 --- /dev/null +++ b/Modules/PHOS/etc/phosCalib.json @@ -0,0 +1,43 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "PHOSCalib": { + "active": "true", + "className": "o2::quality_control_modules::phos::CalibQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query" :"calibdiff:PHS/CALIBDIFF" + }, + "taskParameters": { + "BadMap": "on" + }, + "location": "remote" + } + } + } +} diff --git a/Modules/PHOS/etc/phosCalibLED.json b/Modules/PHOS/etc/phosCalibLED.json new file mode 100644 index 0000000000..4f56cd8b42 --- /dev/null +++ b/Modules/PHOS/etc/phosCalibLED.json @@ -0,0 +1,43 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "PHOSCalibLED": { + "active": "true", + "className": "o2::quality_control_modules::phos::CalibQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query" :"calibdiff:PHS/CALIBDIFF/0" + }, + "taskParameters": { + "LED": "on" + }, + "location": "remote" + } + } + } +} diff --git a/Modules/PHOS/etc/phosCalibPed.json b/Modules/PHOS/etc/phosCalibPed.json new file mode 100644 index 0000000000..a8adc2ca5d --- /dev/null +++ b/Modules/PHOS/etc/phosCalibPed.json @@ -0,0 +1,43 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "PHOSCalibPed": { + "active": "true", + "className": "o2::quality_control_modules::phos::CalibQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query" :"calibdiff:PHS/CALIBDIFF/0" + }, + "taskParameters": { + "pedestal": "on" + }, + "location": "remote" + } + } + } +} diff --git a/Modules/PHOS/etc/phosClusters.json b/Modules/PHOS/etc/phosClusters.json new file mode 100644 index 0000000000..6c055a8001 --- /dev/null +++ b/Modules/PHOS/etc/phosClusters.json @@ -0,0 +1,61 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "PHOSClusters": { + "active": "true", + "className": "o2::quality_control_modules::phos::ClusterQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "100", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "phos-clu" + }, + "taskParameters": { + "": "" + }, + "location": "remote" + } + } + }, + "dataSamplingPolicies": [ + { + "id": "phos-clu", + "active": "true", + "machines": [], + "query_comment" : "query is in the format of binding1:origin1/description1/subSpec1[;binding2:...]", + "query": "clusters:PHS/CLUSTERS/0;clustertr:PHS/CLUSTERTRIGREC/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1.", + "seed": "1234" + } + ], + "blocking": "false" + } + + ] +} diff --git a/Modules/PHOS/etc/phosPedestal.json b/Modules/PHOS/etc/phosPedestal.json new file mode 100644 index 0000000000..d290fec561 --- /dev/null +++ b/Modules/PHOS/etc/phosPedestal.json @@ -0,0 +1,43 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "PHOSPedestal": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query" :"rawerr:PHS/RAWHWERRORS/0;cells:PHS/CELLS/0;cellstr:PHS/CELLTRIGREC/0" + }, + "taskParameters": { + "pedestal": "on" + }, + "location": "remote" + } + } + } +} diff --git a/Modules/PHOS/etc/phosRaw.json b/Modules/PHOS/etc/phosRaw.json new file mode 100644 index 0000000000..763fb2b98f --- /dev/null +++ b/Modules/PHOS/etc/phosRaw.json @@ -0,0 +1,46 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "QcTask": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query": "rawerr:PHS/RAWHWERRORS;fitquality:PHS/CELLFITQA;cells:PHS/CELLS/0;cellstr:PHS/CELLTRIGREC/0" + }, + "taskParameters": { + "physics": "on", + "chi2": "on" + }, + "location": "remote" + } + }, + "dataSamplingPolicies": [ + ] + } +} diff --git a/Modules/PHOS/etc/phosRawClu.json b/Modules/PHOS/etc/phosRawClu.json new file mode 100644 index 0000000000..dcdab985db --- /dev/null +++ b/Modules/PHOS/etc/phosRawClu.json @@ -0,0 +1,375 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "#conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + } + }, + "tasks": { + "RawTask": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "rawerr:PHS/RAWHWERRORS;cells:PHS/CELLS/0;cellstr:PHS/CELLTRIGREC/0", + "#query": "rawerr:PHS/RAWHWERRORS;fitquality:PHS/CELLFITQA;cells:PHS/CELLS/0;cellstr:PHS/CELLTRIGREC/0" + }, + "taskParameters": { + "physics": "on" + }, + "location": "remote" + }, + "ClusterTask": { + "active": "true", + "className": "o2::quality_control_modules::phos::ClusterQcTask", + "moduleName": "QcPHOS", + "detectorName": "PHS", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query": "clusters:PHS/CLUSTERS;clustertr:PHS/CLUSTERTRIGREC" + }, + "taskParameters": { + "": "" + }, + "location": "remote" + } + }, + "checks": { + "CellsIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "RawTask", + "MOs": [ + "CellHGOccupancyM1", + "CellHGOccupancyM2", + "CellHGOccupancyM3", + "CellHGOccupancyM4" + ] + } + ] + }, + "ClustersIncrease": { + "active": "true", + "className": "o2::quality_control_modules::common::IncreasingEntries", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mustIncrease": "true" + }, + "dataSource": [ + { + "type": "Task", + "name": "ClusterTask", + "MOs": [ + "SpectrumM1", + "SpectrumM2", + "SpectrumM3", + "SpectrumM4" + ] + } + ] + }, + "ErrorsCheck": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawCheck", + "moduleName": "QcPHOS", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mErrorOccuranceThreshold0": "0.1", + "mErrorOccuranceThreshold1": "0.1", + "mErrorOccuranceThreshold2": "0.1", + "mErrorOccuranceThreshold3": "0.1", + "mErrorOccuranceThreshold4": "0.1" + }, + "dataSource": [ + { + "type": "Task", + "name": "RawTask", + "MOs": [ + "ErrorTypeOccurance" + ] + } + ] + }, + "CellsCheck": { + "active": "true", + "className": "o2::quality_control_modules::phos::RawCheck", + "moduleName": "QcPHOS", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mToleratedBadChannelsM1": "1", + "mToleratedBadChannelsM2": "1", + "mToleratedBadChannelsM3": "1", + "mToleratedBadChannelsM4": "1" + }, + "dataSource": [ + { + "type": "Task", + "name": "RawTask", + "MOs": [ + "BadMapSummary", + "CellHGOccupancyM1", + "CellHGOccupancyM2", + "CellHGOccupancyM3", + "CellHGOccupancyM4" + ] + } + ] + }, + "ClustersCheck": { + "active": "true", + "className": "o2::quality_control_modules::phos::ClusterCheck", + "moduleName": "QcPHOS", + "policy": "OnAny", + "detectorName": "PHS", + "checkParameters": { + "mMaxCluEnergyMean2": "1.0", + "mMinCluEnergyMean1": "9.0" + }, + "dataSource": [ + { + "type": "Task", + "name": "ClusterTask", + "MOs": [ + "SpectrumM1", + "SpectrumM2", + "SpectrumM3", + "SpectrumM4" + ] + } + ] + } + }, + "postprocessing": { + "PhysicsTrending": { + "active": "true", + "className": "o2::quality_control::postprocessing::SliceTrendingTask", + "moduleName": "QualityControl", + "detectorName": "PHS", + "resumeTrend": "false", + "producePlotsOnUpdate": "true", + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:qcdb:PHS/MO/ClusterTask/SpectrumM1" + ], + "stopTrigger": [ + "usercontrol" + ], + "dataSources": [ + { + "type": "repository", + "path": "PHS/MO/ClusterTask", + "names": [ + "SpectrumM1", + "SpectrumM2", + "SpectrumM3", + "SpectrumM4" + ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ + [ + "1.", + "10." + ] + ], + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_cluEnergyM1", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM1.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluEnergyM2", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM2.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluEnergyM3", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM3.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + }, + { + "name": "mean_of_cluEnergyM4", + "title": "Trend of mean energy of >1GeV clusters", + "varexp": "SpectrumM4.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphAxisLabel": "Mean energy:time", + "graphYRange": "0:10" + } + ] + }, + "QualityTask": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QcCommon", + "detectorName": "PHS", + "qualityGroups": [ + { + "name": "global", + "title": "GLOBAL PHS QUALITY", + "path": "PHS/QO/GlobalQuality", + "ignoreQualitiesDetails": [ + "Null", + "Good", + "Medium", + "Bad" + ], + "inputObjects": [ + { + "name": "GlobalQuality", + "title": "Global PHS Quality", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform the PHS on-call", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty!!!" + } + ] + }, + { + "name": "details", + "title": "PHS DETAILS", + "path": "PHS/QO", + "ignoreQualitiesDetails": [], + "inputObjects": [ + { + "name": "CellsIncrease", + "title": "Number of cells increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "ClustersIncrease", + "title": "Number of clusters increases", + "messageBad": "Entries are not increasing in last cycle", + "messageNull": "" + }, + { + "name": "CellsCheck", + "title": "Cells check", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform the PHS on-call", + "messageGood": "", + "messageNull": "" + }, + { + "name": "ClustersCheck", + "title": "Clusters check", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform the PHS on-call", + "messageGood": "", + "messageNull": "Not enough stat" + }, + { + "name": "ErrorsCheck", + "title": "Errors check", + "messageBad": "Inform PHS on-call", + "messageMedium": "Inform the PHS on-call", + "messageGood": "", + "messageNull": "" + } + ] + } + ], + "initTrigger": [ + "newobject:qcdb:PHS/QO/GlobalQuality/GlobalQuality" + ], + "updateTrigger": [ + "newobject:qcdb:PHS/QO/GlobalQuality/GlobalQuality" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "aggregators": { + "GlobalQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QcCommon", + "policy": "OnAll", + "detectorName": "PHS", + "dataSource": [ + { + "type": "Check", + "name": "CellsIncrease" + }, + { + "type": "Check", + "name": "ClustersIncrease" + }, + { + "type": "Check", + "name": "CellsCheck" + }, + { + "type": "Check", + "name": "ClustersCheck" + }, + { + "type": "Check", + "name": "ErrorsCheck" + } + ] + } + } + }, + "dataSamplingPolicies": [] +} \ No newline at end of file diff --git a/Modules/PHOS/include/PHOS/CalibQcTask.h b/Modules/PHOS/include/PHOS/CalibQcTask.h new file mode 100644 index 0000000000..f9d1e75c30 --- /dev/null +++ b/Modules/PHOS/include/PHOS/CalibQcTask.h @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibQcTask.h +/// \author Dmitri Peresunko +/// \author Dmitry Blau +/// + +#ifndef QC_MODULE_PHOS_QCCALIBTASK_H +#define QC_MODULE_PHOS_QCCALIBTASK_H + +#include "QualityControl/TaskInterface.h" +#include + +class TH2F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::phos +{ + +/// \brief PHOS Quality Control DPL Task for calibration monitoring +class CalibQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + CalibQcTask() = default; + /// Destructor + ~CalibQcTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + protected: + private: + int mMode = 0; ///< Mode of operation: 0: badMap, 1: Pedestals, 2: LED + static constexpr short NHIST2D = 25; + enum histos2D { kChangeHGM1, + kChangeHGM2, + kChangeHGM3, + kChangeHGM4, + kChangeLGM1, + kChangeLGM2, + kChangeLGM3, + kChangeLGM4, + kValueHGM1, + kValueHGM2, + kValueHGM3, + kValueHGM4, + kValueLGM1, + kValueLGM2, + kValueLGM3, + kValueLGM4, + kRMSHGM1, + kRMSHGM2, + kRMSHGM3, + kRMSHGM4, + kRMSLGM1, + kRMSLGM2, + kRMSLGM3, + kRMSLGM4, + kL1phase + }; + std::array mHist2D = { nullptr }; ///< Array of 2D histograms +}; + +} // namespace o2::quality_control_modules::phos + +#endif // QC_MODULE_PHOS_QCCALIBTASK_H diff --git a/Modules/PHOS/include/PHOS/ClusterCheck.h b/Modules/PHOS/include/PHOS/ClusterCheck.h new file mode 100644 index 0000000000..81a23749a4 --- /dev/null +++ b/Modules/PHOS/include/PHOS/ClusterCheck.h @@ -0,0 +1,59 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterCheck.h +/// \author Dmitri Peresunko +/// + +#ifndef QC_MODULE_PHOS_PHOSCLUSTERCHECK_H +#define QC_MODULE_PHOS_PHOSCLUSTERCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "DataFormatsPHOS/Cluster.h" +#include "DataFormatsPHOS/BadChannelsMap.h" +#include "PHOSBase/Geometry.h" + +namespace o2::quality_control_modules::phos +{ + +/// \brief Check mos: appearence of dead regions in occupancy plots, mean and RMS etc. +/// +/// \author Dmitri Peresunko +class ClusterCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + ClusterCheck() = default; + /// Destructor + ~ClusterCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + protected: + int mDeadThreshold[5] = { 10, 10, 10, 10, 10 }; /// Number of new dead channels per module to decalre bad + int mNoisyThreshold[5] = { 2, 2, 2, 2, 2 }; /// Number of new noisy channels per module to send warning + int mMaxOccupancyCut[5] = { 10, 10, 10, 10, 10 }; /// occupancy in noisy channel wrt mean over module + float mCluEnergyRangeL[5] = { 1., 1., 1., 1., 1. }; + float mCluEnergyRangeR[5] = { 10., 10., 10., 10., 10. }; + float mMinCluEnergyMean[5] = { 2., 2., 2., 2., 2 }; + float mMaxCluEnergyMean[5] = { 4., 4., 4., 4., 4 }; + + const o2::phos::BadChannelsMap* mBadMap; /// bad map + ClassDefOverride(ClusterCheck, 2); +}; + +} // namespace o2::quality_control_modules::phos + +#endif // QC_MODULE_PHOS_PHOSCLUSTERCHECK_H diff --git a/Modules/PHOS/include/PHOS/ClusterQcTask.h b/Modules/PHOS/include/PHOS/ClusterQcTask.h new file mode 100644 index 0000000000..3e4379b92d --- /dev/null +++ b/Modules/PHOS/include/PHOS/ClusterQcTask.h @@ -0,0 +1,94 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterQcTask.h +/// \author Dmitri Peresunko +/// \author Dmitry Blau +/// + +#ifndef QC_MODULE_PHOS_QCCLUSTERTASK_H +#define QC_MODULE_PHOS_QCCLUSTERTASK_H + +#include "QualityControl/TaskInterface.h" +#include "DataFormatsPHOS/Cluster.h" +#include "DataFormatsPHOS/BadChannelsMap.h" +#include "PHOSBase/Geometry.h" +#include +#include +#include + +class TH1F; +class TH2F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::phos +{ + +/// \brief PHOS Quality Control DPL Task +class ClusterQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + ClusterQcTask() = default; + /// Destructor + ~ClusterQcTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + protected: + bool checkCluster(const o2::phos::Cluster& c); + + private: + static constexpr short kNhist1D = 9; + enum histos1D { kBCs, + kSpectrumM1, + kSpectrumM2, + kSpectrumM3, + kSpectrumM4, + kPi0M1, + kPi0M2, + kPi0M3, + kPi0M4 + + }; + static constexpr short kNhist2D = 8; + enum histos2D { kOccupancyM1, + kOccupancyM2, + kOccupancyM3, + kOccupancyM4, + kTimeEM1, + kTimeEM2, + kTimeEM3, + kTimeEM4 + }; + float mPtMin = 1.5; /// minimum pi0 pt to fill inv mass histo + float mOccCut = 0.1; /// minimum energy to fill occupancy histo + float mEnergyMinForInvMass = 0.3; /// minimum energy to make 2gamma spectrum + int mMultiplicityMinForInvMass = 2; /// minimum multiplicity to make 2gamma spectrum + std::array mHist1D = { nullptr }; ///< Array of 1D histograms + std::array mHist2D = { nullptr }; ///< Array of 2D histograms + std::vector mBuffer[4]; /// Keep photons per event per module + o2::phos::Geometry* mGeom; /// Pointer to PHOS singleton geometry + std::unique_ptr mBadMap; /// bad map +}; + +} // namespace o2::quality_control_modules::phos + +#endif // QC_MODULE_PHOS_QCCLUSTERTASK_H diff --git a/Modules/PHOS/include/PHOS/LinkDef.h b/Modules/PHOS/include/PHOS/LinkDef.h new file mode 100644 index 0000000000..724cc36bc2 --- /dev/null +++ b/Modules/PHOS/include/PHOS/LinkDef.h @@ -0,0 +1,24 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::phos::TH1Fraction + ; + +#pragma link C++ class o2::quality_control_modules::phos::TH2Fraction + ; + +#pragma link C++ class o2::quality_control_modules::phos::TH2SBitmask + ; + +#pragma link C++ class o2::quality_control_modules::phos::TH2FMean + ; + +#pragma link C++ class o2::quality_control_modules::phos::ClusterQcTask + ; + +#pragma link C++ class o2::quality_control_modules::phos::ClusterCheck + ; + +#pragma link C++ class o2::quality_control_modules::phos::RawQcTask + ; + +#pragma link C++ class o2::quality_control_modules::phos::RawCheck + ; + +#pragma link C++ class o2::quality_control_modules::phos::CalibQcTask + ; + +#endif diff --git a/Modules/PHOS/include/PHOS/RawCheck.h b/Modules/PHOS/include/PHOS/RawCheck.h new file mode 100644 index 0000000000..4f8fb19780 --- /dev/null +++ b/Modules/PHOS/include/PHOS/RawCheck.h @@ -0,0 +1,74 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawCheck.h +/// \author Dmitri Peresunko +/// \author Dmitry Blau +/// + +#ifndef QC_MODULE_PHOS_PHOSRAWCHECK_H +#define QC_MODULE_PHOS_PHOSRAWCHECK_H + +#include "QualityControl/CheckInterface.h" + +class TH1F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::phos +{ + +/// \brief PHOS raw data check +/// It is final because there is no reason to derive from it. Just remove it if needed. +/// \author Cristina Terrevoli +class RawCheck final : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + RawCheck() = default; + /// Destructor + ~RawCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override {} + + protected: + bool checkErrHistograms(MonitorObject* mo); + bool checkPhysicsHistograms(MonitorObject* mo); + bool checkPedestalHistograms(MonitorObject* mo); + + private: + int mMinHGPedestalValue = 18; + int mMaxHGPedestalValue = 60; + int mMinLGPedestalValue = 18; + int mMaxLGPedestalValue = 60; + float mMinHGPedestalRMS = 0.2; + float mMaxHGPedestalRMS = 2.5; + float mMinLGPedestalRMS = 0.2; + float mMaxLGPedestalRMS = 2.5; + + int mToleratedBadChannelsM[5] = { 0, 10, 20, 20, 20 }; // Nuber of channels beyond bad map + int mBadMap[5] = { 0 }; + float mErrorOccuranceThreshold[5] = { 0., 0., 0., 0., 0. }; + const char* mErrorLabel[5] = { "wrong ALTRO", "mapping", "ch. header", + "ch. payload", "wrond hw addr" }; + float mBranchOccupancyDeviationAllowed[5] = { 1.5, 1.5, 1.5, 1.5, 1.5 }; + int mToleratedDeviatedBranches[5] = { 0, 0, 0, 0, 0 }; + Quality mCheckResult = Quality::Null; + ClassDefOverride(RawCheck, 2); +}; + +} // namespace o2::quality_control_modules::phos + +#endif // QC_MODULE_PHOS_PHOSRAWCHECK_H diff --git a/Modules/PHOS/include/PHOS/RawQcTask.h b/Modules/PHOS/include/PHOS/RawQcTask.h new file mode 100644 index 0000000000..3a08ba0b88 --- /dev/null +++ b/Modules/PHOS/include/PHOS/RawQcTask.h @@ -0,0 +1,205 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawQcTask.h +/// \author Dmitri Peresunko +/// \author Dmitry Blau +/// + +#ifndef QC_MODULE_PHOS_PHOSQCRAWTASK_H +#define QC_MODULE_PHOS_PHOSQCRAWTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include "DataFormatsPHOS/Cell.h" +#include "DataFormatsPHOS/TriggerRecord.h" +#include + +#include +class TH2F; +#include "DataFormatsPHOS/BadChannelsMap.h" +#include +#include "PHOS/TH2FMean.h" +#include "PHOS/TH2SBitmask.h" +#include "PHOS/TH1Fraction.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::phos +{ + +/// \brief PHOS Quality Control DPL Task +class RawQcTask final : public TaskInterface +{ + public: + /// \brief Constructor + RawQcTask() = default; + /// Destructor + ~RawQcTask() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + protected: + static constexpr short kNhist1D = 28; + enum histos1D { kBCs, + kTotalDataVolume, + kMessageCounter, + kBadMapSummary, + kHGmeanSummaryM1, + kHGmeanSummaryM2, + kHGmeanSummaryM3, + kHGmeanSummaryM4, + kLGmeanSummaryM1, + kLGmeanSummaryM2, + kLGmeanSummaryM3, + kLGmeanSummaryM4, + kHGrmsSummaryM1, + kHGrmsSummaryM2, + kHGrmsSummaryM3, + kHGrmsSummaryM4, + kLGrmsSummaryM1, + kLGrmsSummaryM2, + kLGrmsSummaryM3, + kLGrmsSummaryM4, + kCellHGSpM1, + kCellHGSpM2, + kCellHGSpM3, + kCellHGSpM4, + kCellLGSpM1, + kCellLGSpM2, + kCellLGSpM3, + kCellLGSpM4 + }; + + static constexpr short kNhist2D = 42; + enum histos2D { kErrorNumber, + kPayloadSizePerDDL, + kChi2M1, + kChi2M2, + kChi2M3, + kChi2M4, + kChi2NormM1, + kChi2NormM2, + kChi2NormM3, + kChi2NormM4, + kHGoccupM1, + kHGoccupM2, + kHGoccupM3, + kHGoccupM4, + kLGoccupM1, + kLGoccupM2, + kLGoccupM3, + kLGoccupM4, + kTimeEM1, + kTimeEM2, + kTimeEM3, + kTimeEM4, + kTRUSTOccupM1, + kTRUSTOccupM2, + kTRUSTOccupM3, + kTRUSTOccupM4, + kTRUDGOccupM1, + kTRUDGOccupM2, + kTRUDGOccupM3, + kTRUDGOccupM4, + kTRUSTMatchM1, + kTRUSTMatchM2, + kTRUSTMatchM3, + kTRUSTMatchM4, + kTRUSTFakeM1, + kTRUSTFakeM2, + kTRUSTFakeM3, + kTRUSTFakeM4, + kTRUDGFakeM1, + kTRUDGFakeM2, + kTRUDGFakeM3, + kTRUDGFakeM4 }; + + static constexpr short kNhist2DMean = 24; + enum histos2DMean { kHGmeanM1, + kHGmeanM2, + kHGmeanM3, + kHGmeanM4, + kLGmeanM1, + kLGmeanM2, + kLGmeanM3, + kLGmeanM4, + kHGrmsM1, + kHGrmsM2, + kHGrmsM3, + kHGrmsM4, + kLGrmsM1, + kLGrmsM2, + kLGrmsM3, + kLGrmsM4, + kCellEM1, + kCellEM2, + kCellEM3, + kCellEM4, + kLEDNpeaksM1, + kLEDNpeaksM2, + kLEDNpeaksM3, + kLEDNpeaksM4 + }; + + static constexpr short kNhist2DBitmask = 1; + enum histos2DBitmask { kErrorType }; + + static constexpr short kNratio1D = 1; + enum ratios1D { kErrorTypeOccurance }; + + void InitHistograms(); + + void CreatePhysicsHistograms(); + void FillPhysicsHistograms(const gsl::span& cells, const gsl::span& tr); + void CreatePedestalHistograms(); + void FillPedestalHistograms(const gsl::span& cells, const gsl::span& tr); + void CreateLEDHistograms(); + void FillLEDHistograms(const gsl::span& cells, const gsl::span& tr); + + void CreateTRUHistograms(); + void FillTRUHistograms(const gsl::span& cells, const gsl::span& tr); + + private: + static constexpr short kNmod = 6; + static constexpr short kMaxErr = 5; + static constexpr short kOcccupancyTh = 10; + + int mMode = 0; ///< Possible modes: 0(def): Physics, 1: Pedestals, 2: LED + bool mFinalized = false; ///< if final histograms calculated + bool mCheckChi2 = false; ///< scan Chi2 distributions + bool mTrNoise = false; ///< check mathing of trigger summary tables and tr.digits + unsigned long long eventCounter = 0; + + std::array mHist1D = { nullptr }; ///< Array of 1D histograms + std::array mHist2D = { nullptr }; ///< Array of 2D histograms + std::array mHist2DMean = { nullptr }; ///< Array of 2D mean histograms + std::array mHist2DBitmask = { nullptr }; ///< Array of 2D mean histograms + std::array mFractions1D = { nullptr }; ///< Array of mergeable 1D fractions + + bool mInitBadMap = true; //! BadMap had to be initialized + const o2::phos::BadChannelsMap* mBadMap = nullptr; //! Bad map for comparison + std::unique_ptr mSpSearcher; + std::vector mSpectra; +}; + +} // namespace o2::quality_control_modules::phos + +#endif // QC_MODULE_PHOS_PHOSRAWQCTASK_H diff --git a/Modules/PHOS/include/PHOS/TH1Fraction.h b/Modules/PHOS/include/PHOS/TH1Fraction.h new file mode 100644 index 0000000000..f5a7a24f70 --- /dev/null +++ b/Modules/PHOS/include/PHOS/TH1Fraction.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1Fraction.h +/// \author Sergey Evdokimov +/// + +#ifndef QC_MODULE_PHOS_TH1FRACTION_H +#define QC_MODULE_PHOS_TH1FRACTION_H + +#include "QualityControl/TaskInterface.h" +#include +#include "Mergers/MergeInterface.h" + +namespace o2::quality_control_modules::phos +{ + +/// \brief Custom TH2F class with special merger + +class TH1Fraction : public TH1D, public o2::mergers::MergeInterface +{ + public: + /// \brief Constructor. + TH1Fraction() = default; + TH1Fraction(const char* name, const char* title, Int_t nbinsx, Double_t xlow, Double_t xup); + TH1Fraction(TH1Fraction const& copymerge); + /// \brief Destructor + virtual ~TH1Fraction() + { + delete mUnderlyingCounts; + } + + void increaseEventCounter(int increment) { mEventCounter += increment; } + void fillUnderlying(double x) { mUnderlyingCounts->Fill(x); } + void update(); + TH1D* getUnderlyingCounts() const { return mUnderlyingCounts; } + unsigned long long getEventCounter() const { return mEventCounter; } + void Reset(Option_t* option = "") override; + + void merge(MergeInterface* const other) override; + + private: + std::string mTreatMeAs = "TH1F"; // the name of the class this object should be considered as when drawing in QCG. + unsigned long long mEventCounter = 0; // event counter + TH1D* mUnderlyingCounts{ nullptr }; // underlying histogram with counts + + ClassDefOverride(TH1Fraction, 1); +}; + +} // namespace o2::quality_control_modules::phos + +#endif // QC_MODULE_PHOS_TH1FRACTION_H diff --git a/Modules/PHOS/include/PHOS/TH2FMean.h b/Modules/PHOS/include/PHOS/TH2FMean.h new file mode 100644 index 0000000000..2e5fea8bcf --- /dev/null +++ b/Modules/PHOS/include/PHOS/TH2FMean.h @@ -0,0 +1,48 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2FMean.h +/// \author Dmitri Peresunko +/// + +#ifndef QC_MODULE_PHOS_TH2FMEAN_H +#define QC_MODULE_PHOS_TH2FMEAN_H + +#include "QualityControl/TaskInterface.h" +#include +#include "Mergers/MergeInterface.h" + +namespace o2::quality_control_modules::phos +{ + +/// \brief Custom TH2F class with special merger + +class TH2FMean : public TH2F, public o2::mergers::MergeInterface +{ + public: + /// \brief Constructor. + TH2FMean() = default; + TH2FMean(const char* name, const char* title, Int_t nbinsx, Double_t xlow, Double_t xup, Int_t nbinsy, Double_t ylow, Double_t yup) : TH2F(name, title, nbinsx, xlow, xup, nbinsy, ylow, yup) {} + /// \brief Default destructor + virtual ~TH2FMean() = default; + + void merge(MergeInterface* const other) override; + + private: + std::string mTreatMeAs = "TH2F"; // the name of the class this object should be considered as when drawing in QCG. + + ClassDefOverride(TH2FMean, 1); +}; + +} // namespace o2::quality_control_modules::phos + +#endif // QC_MODULE_PHOS_TH2FMEAN_H diff --git a/Modules/PHOS/include/PHOS/TH2Fraction.h b/Modules/PHOS/include/PHOS/TH2Fraction.h new file mode 100644 index 0000000000..991833888f --- /dev/null +++ b/Modules/PHOS/include/PHOS/TH2Fraction.h @@ -0,0 +1,61 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2Fraction.h +/// \author Sergey Evdokimov +/// + +#ifndef QC_MODULE_PHOS_TH2FRACTION_H +#define QC_MODULE_PHOS_TH2FRACTION_H + +#include "QualityControl/TaskInterface.h" +#include +#include "Mergers/MergeInterface.h" + +namespace o2::quality_control_modules::phos +{ + +/// \brief Custom TH2F class with special merger + +class TH2Fraction : public TH2D, public o2::mergers::MergeInterface +{ + public: + /// \brief Constructor. + TH2Fraction() = default; + TH2Fraction(const char* name, const char* title, Int_t nbinsx, Double_t xlow, Double_t xup, Int_t nbinsy, Double_t ylow, Double_t yup); + TH2Fraction(TH2Fraction const& copymerge); + /// \brief Destructor + virtual ~TH2Fraction() + { + delete mUnderlyingCounts; + } + + void increaseEventCounter(int increment) { mEventCounter += increment; } + void fillUnderlying(double x, double y) { mUnderlyingCounts->Fill(x, y); } + void update(); + TH2D* getUnderlyingCounts() const { return mUnderlyingCounts; } + unsigned long long getEventCounter() const { return mEventCounter; } + void Reset(Option_t* option = "") override; + + void merge(MergeInterface* const other) override; + + private: + std::string mTreatMeAs = "TH2F"; // the name of the class this object should be considered as when drawing in QCG. + unsigned long long mEventCounter = 0; // event counter + TH2D* mUnderlyingCounts{ nullptr }; // underlying histogram with counts + + ClassDefOverride(TH2Fraction, 1); +}; + +} // namespace o2::quality_control_modules::phos + +#endif // QC_MODULE_PHOS_TH2FRACTION_H diff --git a/Modules/PHOS/include/PHOS/TH2SBitmask.h b/Modules/PHOS/include/PHOS/TH2SBitmask.h new file mode 100644 index 0000000000..6c0ac4725b --- /dev/null +++ b/Modules/PHOS/include/PHOS/TH2SBitmask.h @@ -0,0 +1,48 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2SBitmask.h +/// \author Dmitri Peresunko +/// + +#ifndef QC_MODULE_PHOS_TH2FBITMASK_H +#define QC_MODULE_PHOS_TH2FBITMASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include "Mergers/MergeInterface.h" + +namespace o2::quality_control_modules::phos +{ + +/// \brief Custom TH2S class with special merger combining bit masks + +class TH2SBitmask : public TH2S, public o2::mergers::MergeInterface +{ + public: + /// \brief Constructor. + TH2SBitmask() = default; + TH2SBitmask(const char* name, const char* title, Int_t nbinsx, Double_t xlow, Double_t xup, Int_t nbinsy, Double_t ylow, Double_t yup) : TH2S(name, title, nbinsx, xlow, xup, nbinsy, ylow, yup) {} + /// \brief Default destructor + ~TH2SBitmask() = default; + + void merge(MergeInterface* const other) override; + + private: + std::string mTreatMeAs = "TH2S"; // the name of the class this object should be considered as when drawing in QCG. + + ClassDefOverride(TH2SBitmask, 1); +}; + +} // namespace o2::quality_control_modules::phos + +#endif // QC_MODULE_PHOS_TH2FBITMASK_H diff --git a/Modules/PHOS/src/CalibQcTask.cxx b/Modules/PHOS/src/CalibQcTask.cxx new file mode 100644 index 0000000000..9227d92ed4 --- /dev/null +++ b/Modules/PHOS/src/CalibQcTask.cxx @@ -0,0 +1,342 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibQcTask.cxx +/// \author Dmitri Peresunko +/// \author Dmitry Blau +/// + +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "PHOS/CalibQcTask.h" +#include "Framework/InputRecord.h" +#include "PHOSBase/Geometry.h" +#include "PHOSBase/Mapping.h" +#include +#include +#include +#include +#include +#include + +// using namespace o2::phos; + +namespace o2::quality_control_modules::phos +{ + +CalibQcTask::~CalibQcTask() +{ + for (int i = NHIST2D; i--;) { + if (mHist2D[i]) { + mHist2D[i]->Delete(); + mHist2D[i] = nullptr; + } + } +} +void CalibQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Info, Support) << "==============initialize CalibQcTask==============" << ENDM; + ILOG(Debug, Devel) << "initialize CalibQcTask" << ENDM; + using infoCONTEXT = AliceO2::InfoLogger::InfoLoggerContext; + infoCONTEXT context; + context.setField(infoCONTEXT::FieldName::Facility, "QC"); + context.setField(infoCONTEXT::FieldName::System, "QC"); + context.setField(infoCONTEXT::FieldName::Detector, "PHS"); + QcInfoLogger::GetInfoLogger().setContext(context); + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + if (auto param = mCustomParameters.find("pedestal"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Working in pedestal mode " << ENDM; + if (param->second.find("on") != std::string::npos) { + mMode = 1; + } + } + if (auto param = mCustomParameters.find("LED"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Working in LED mode " << ENDM; + if (param->second.find("on") != std::string::npos) { + mMode = 2; + } + } + if (auto param = mCustomParameters.find("BadMap"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Working in BadMap mode " << ENDM; + if (param->second.find("on") != std::string::npos) { + mMode = 0; + } + } + + if (auto param = mCustomParameters.find("L1phase"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Working in L1phase mode" << ENDM; + if (param->second.find("on") != std::string::npos) { + mMode = 3; + } + } + + ILOG(Info, Support) << "==============Prepare Histos===============" << ENDM; + // Prepare histograms + if (mMode == 1) { // Pedestals + for (Int_t mod = 0; mod < 4; mod++) { + if (!mHist2D[kChangeHGM1 + mod]) { + // pedestal values + mHist2D[kValueHGM1 + mod] = new TH2F(Form("HGPedestalValue%d", mod + 1), Form("Values of HG pedestals in mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kValueHGM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kValueHGM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kValueHGM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kValueHGM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kValueHGM1 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[kValueHGM1 + mod]); + // pedestal RMS + mHist2D[kRMSHGM1 + mod] = new TH2F(Form("HGPedestalRMS%d", mod + 1), Form("RMSs of HG pedestals in mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kRMSHGM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kRMSHGM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kRMSHGM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kRMSHGM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kRMSHGM1 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[kRMSHGM1 + mod]); + + // pedestal change + mHist2D[kChangeHGM1 + mod] = new TH2F(Form("HGPedestalChange%d", mod + 1), Form("Change of HG pedestals in mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kChangeHGM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kChangeHGM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kChangeHGM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kChangeHGM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kChangeHGM1 + mod]->SetStats(0); + mHist2D[kChangeHGM1 + mod]->SetMinimum(-5); + mHist2D[kChangeHGM1 + mod]->SetMaximum(5); + getObjectsManager()->startPublishing(mHist2D[kChangeHGM1 + mod]); + + } else { + mHist2D[kValueHGM1 + mod]->Reset(); + mHist2D[kRMSHGM1 + mod]->Reset(); + mHist2D[kChangeHGM1 + mod]->Reset(); + } + if (!mHist2D[kChangeLGM1 + mod]) { + // pedestal values + mHist2D[kValueLGM1 + mod] = new TH2F(Form("LGPedestalValue%d", mod + 1), Form("Values of LG pedestals in mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kValueLGM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kValueLGM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kValueLGM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kValueLGM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kValueLGM1 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[kValueLGM1 + mod]); + // pedestal RMS + mHist2D[kRMSLGM1 + mod] = new TH2F(Form("LGPedestalRMS%d", mod + 1), Form("RMSs of LG pedestals in mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kRMSLGM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kRMSLGM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kRMSLGM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kRMSLGM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kRMSLGM1 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[kRMSLGM1 + mod]); + + // pedestal change + mHist2D[kChangeLGM1 + mod] = new TH2F(Form("LGPedestalChange%d", mod + 1), Form("Change of LG pedestals in mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kChangeLGM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kChangeLGM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kChangeLGM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kChangeLGM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kChangeLGM1 + mod]->SetStats(0); + mHist2D[kChangeLGM1 + mod]->SetMinimum(-5); + mHist2D[kChangeLGM1 + mod]->SetMaximum(5); + getObjectsManager()->startPublishing(mHist2D[kChangeLGM1 + mod]); + + } else { + mHist2D[kValueLGM1 + mod]->Reset(); + mHist2D[kRMSLGM1 + mod]->Reset(); + mHist2D[kChangeLGM1 + mod]->Reset(); + } + } + } // Pedestals + if (mMode == 2) { // LED + for (Int_t mod = 0; mod < 4; mod++) { + if (!mHist2D[kChangeHGM1 + mod]) { + // HGLG ratio value + mHist2D[kValueHGM1 + mod] = new TH2F(Form("HGLGRatioValue%d", mod + 1), Form("Value of HG/LG ratio in mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kValueHGM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kValueHGM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kValueHGM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kValueHGM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kValueHGM1 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[kValueHGM1 + mod]); + // HGLG ratio change + mHist2D[kChangeHGM1 + mod] = new TH2F(Form("HGLGRatioChange%d", mod + 1), Form("Change of HG/LG ratio in mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kChangeHGM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kChangeHGM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kChangeHGM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kChangeHGM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kChangeHGM1 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[kChangeHGM1 + mod]); + + } else { + mHist2D[kValueHGM1 + mod]->Reset(); + mHist2D[kChangeHGM1 + mod]->Reset(); + } + } + } // LED + if (mMode == 0) { // BadMap + for (Int_t mod = 0; mod < 4; mod++) { + if (!mHist2D[kChangeHGM1 + mod]) { + mHist2D[kChangeHGM1 + mod] = new TH2F(Form("BadMapChange%d", mod + 1), Form("Change of bad map in mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kChangeHGM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kChangeHGM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kChangeHGM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kChangeHGM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kChangeHGM1 + mod]->SetStats(0); + mHist2D[kChangeHGM1 + mod]->SetMinimum(-2); + mHist2D[kChangeHGM1 + mod]->SetMaximum(2); + getObjectsManager()->startPublishing(mHist2D[kChangeHGM1 + mod]); + } else { + mHist2D[kChangeHGM1 + mod]->Reset(); + } + } + } // BadMap + if (mMode == 3) { // L1phase + if (!mHist2D[kL1phase]) { + mHist2D[kL1phase] = new TH2F("L1phase", "Time vs DDL", 14, 0., 14., 100, -200.e-9, 200.e-9); + mHist2D[kL1phase]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kL1phase]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kL1phase]->GetXaxis()->SetTitle("DDL"); + mHist2D[kL1phase]->GetYaxis()->SetTitle("t (s)"); + mHist2D[kL1phase]->SetDrawOption("colz"); + mHist2D[kL1phase]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[kL1phase]); + } else { + mHist2D[kL1phase]->Reset(); + } + } // BadMap + ILOG(Info, Support) << " CalibQcTask histos ready " << ENDM; +} + +void CalibQcTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + reset(); +} + +void CalibQcTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void CalibQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + bool hasCalibDiff = false, hasCalibPars = false, hasPedestals = false, hasL1phase = false; + for (auto&& input : o2::framework::InputRecordWalker(ctx.inputs())) { + // get message header + if (input.header != nullptr && input.payload != nullptr) { + auto payloadSize = o2::framework::DataRefUtils::getPayloadSize(input); + // get payload of a specific input, which is a char array. + // const char* payload = input.payload; + const auto* header = o2::framework::DataRefUtils::getHeader(input); + if (payloadSize) { + if ((strcmp(header->dataOrigin.str, "PHS") == 0) && (strcmp(header->dataDescription.str, "CALIBDIFF") == 0)) { + // LOG(info) << "monitorData() : I found calibdiff in inputs"; + hasCalibDiff = true; + } + if ((strcmp(header->dataOrigin.str, "CLP") == 0) && (strcmp(header->dataDescription.str, "PHOS_HGLGratio") == 0)) { + // LOG(info) << "monitorData() : I found PHOS_HGLGratio in inputs"; + hasCalibPars = true; + } + if ((strcmp(header->dataOrigin.str, "CLP") == 0) && (strcmp(header->dataDescription.str, "PHOS_Pedestal") == 0)) { + // LOG(info) << "monitorData() : I found PHOS_Pedestal in inputs"; + hasPedestals = true; + } + if ((strcmp(header->dataOrigin.str, "CLP") == 0) && (strcmp(header->dataDescription.str, "PHOS_L1phase") == 0)) { + // LOG(info) << "monitorData() : I found PHOS_L1phase in inputs"; + hasL1phase = true; + } + } + } + } + + // std::array + if (mMode == 0) { // Bad map + if (hasCalibDiff) { + auto diff = ctx.inputs().get>("calibdiff"); + char relid[3]; + for (short absId = o2::phos::Mapping::NCHANNELS; absId > 1792; absId--) { + o2::phos::Geometry::absToRelNumbering(absId, relid); + mHist2D[kChangeHGM1 + relid[0] - 1]->SetBinContent(relid[1], relid[2], diff[absId]); + } + } + } else if (mMode == 1) { // Pedestal + if (hasCalibDiff) { + auto diff = ctx.inputs().get>("calibdiff"); + char relid[3]; + for (short absId = o2::phos::Mapping::NCHANNELS; absId > 1792; absId--) { + o2::phos::Geometry::absToRelNumbering(absId, relid); + mHist2D[kChangeHGM1 + relid[0] - 1]->SetBinContent(relid[1], relid[2], diff[absId]); + mHist2D[kChangeLGM1 + relid[0] - 1]->SetBinContent(relid[1], relid[2], diff[absId + o2::phos::Mapping::NCHANNELS]); + } + } + if (hasPedestals) { + auto peds = o2::framework::DataRefUtils::as>(ctx.inputs().get("peds")); + char relid[3]; + for (short absId = o2::phos::Mapping::NCHANNELS; absId > 1792; absId--) { + o2::phos::Geometry::absToRelNumbering(absId, relid); + mHist2D[kValueHGM1 + relid[0] - 1]->SetBinContent(relid[1], relid[2], peds->getHGPedestal(absId)); + mHist2D[kValueLGM1 + relid[0] - 1]->SetBinContent(relid[1], relid[2], peds->getLGPedestal(absId)); + mHist2D[kRMSHGM1 + relid[0] - 1]->SetBinContent(relid[1], relid[2], peds->getHGRMS(absId)); + mHist2D[kRMSLGM1 + relid[0] - 1]->SetBinContent(relid[1], relid[2], peds->getLGRMS(absId)); + } + } + } else if (mMode == 2) { // LED + if (hasCalibDiff) { + auto diff = ctx.inputs().get>("calibdiff"); + char relid[3]; + for (short absId = o2::phos::Mapping::NCHANNELS; absId > 1792; absId--) { + o2::phos::Geometry::absToRelNumbering(absId, relid); + mHist2D[kChangeHGM1 + relid[0] - 1]->SetBinContent(relid[1], relid[2], diff[absId]); + } + } + if (hasCalibPars) { + auto calibParams = o2::framework::DataRefUtils::as>(ctx.inputs().get("calibpars")); + char relid[3]; + for (short absId = o2::phos::Mapping::NCHANNELS; absId > 1792; absId--) { + o2::phos::Geometry::absToRelNumbering(absId, relid); + mHist2D[kValueHGM1 + relid[0] - 1]->SetBinContent(relid[1], relid[2], calibParams->getHGLGRatio(absId)); + } + } + + } else if (mMode == 3) { // L1phase + if (hasL1phase) { + auto vec = ctx.inputs().get>("l1phase"); + for (short it = 0; it < vec.size(); it++) { + mHist2D[kL1phase]->SetBinContent(it / 100 + 1, it % 100 + 1, float(vec[it])); + } + } + } +} + +void CalibQcTask::endOfCycle() +{ + ILOG(Info, Support) << "endOfCycle" << ENDM; +} + +void CalibQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Info, Support) << "endOfActivity" << ENDM; +} + +void CalibQcTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + for (int i = NHIST2D; i--;) { + if (mHist2D[i]) { + mHist2D[i]->Reset(); + } + } +} +} // namespace o2::quality_control_modules::phos diff --git a/Modules/PHOS/src/ClusterCheck.cxx b/Modules/PHOS/src/ClusterCheck.cxx new file mode 100644 index 0000000000..55b9f8f592 --- /dev/null +++ b/Modules/PHOS/src/ClusterCheck.cxx @@ -0,0 +1,291 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterCheck.cxx +/// \author Dmitri Peressounko +/// + +#include "PHOS/ClusterCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +// ROOT +#include +#include +#include +#include +#include +#include +// O2 +#include +#include + +using namespace std; +using namespace o2::quality_control::core; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::phos +{ + +void ClusterCheck::configure() +{ + for (int mod = 1; mod < 5; mod++) { + // mDeadThreshold + if (auto param = mCustomParameters.find(Form("mDeadThreshold%d", mod)); + param != mCustomParameters.end()) { + mDeadThreshold[mod] = stoi(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter " + << Form("mDeadThreshold%d", mod) + << mDeadThreshold[mod] << ENDM; + } + // mNoisyThreshold + if (auto param = mCustomParameters.find(Form("mNoisyThreshold%d", mod)); + param != mCustomParameters.end()) { + mNoisyThreshold[mod] = stoi(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter " + << Form("mNoisyThreshold%d", mod) + << mNoisyThreshold[mod] << ENDM; + } + // mMaxOccupancyCut + if (auto param = mCustomParameters.find(Form("mMaxOccupancyCut%d", mod)); + param != mCustomParameters.end()) { + mMaxOccupancyCut[mod] = stoi(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter " + << Form("mMaxOccupancyCut%d", mod) + << mMaxOccupancyCut[mod] << ENDM; + } + + // mCluEnergyRangeL + if (auto param = mCustomParameters.find(Form("mCluEnergyRangeL%d", mod)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mCluEnergyRangeL%d = ", mod) + << param->second << ENDM; + mCluEnergyRangeL[mod] = stof(param->second); + } + // mCluEnergyRangeR + if (auto param = mCustomParameters.find(Form("mCluEnergyRangeR%d", mod)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mCluEnergyRangeR%d = ", mod) + << param->second << ENDM; + mCluEnergyRangeR[mod] = stof(param->second); + } + // mMinCluEnergyMean + if (auto param = mCustomParameters.find(Form("mMinCluEnergyMean%d", mod)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMinCluEnergyMean%d = ", mod) + << param->second << ENDM; + mMinCluEnergyMean[mod] = stof(param->second); + } + // mMaxCluEnergyMean + if (auto param = mCustomParameters.find(Form("mMaxCluEnergyMean%d", mod)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mMaxCluEnergyMean%d = ", mod) + << param->second << ENDM; + mMaxCluEnergyMean[mod] = stof(param->second); + } + } +} + +Quality ClusterCheck::check(std::map>* moMap) +{ + Quality result = Quality::Good; + for (auto& [moName, mo] : *moMap) { + //check occupancy + if (mo->getName().find("ClusterOccupancyM") != std::string::npos) { + // the problem here is that CcdbUrl must be setted via setCcdbUrl() method + // otherwise it tries to open empty url + // but how to access global qc config here? + // for the timebeing don't include "ClusterOccupancyM" in list of objects to check + if (!mBadMap) { + mBadMap = UserCodeInterface::retrieveConditionAny("PHS/Calib/BadMap"); + } + + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + continue; + } + //get module number: + int m = mo->getName()[mo->getName().find_first_of("1234")] - '0'; + //Compare with current bad map: check for new bad regions + float mean = 0; + int entries = 0, nDead = 0, nNoisy = 0; + for (int ix = 1; ix <= 64; ix++) { + for (int iz = 1; iz <= 56; iz++) { + float cont = h->GetBinContent(ix, iz); + char relid[3] = { char(m), char(ix), char(iz) }; + short absId; + o2::phos::Geometry::relToAbsNumbering(relid, absId); + if (cont > 0) { + mean += cont; + entries++; + } else { + if (mBadMap->isChannelGood(absId)) { //new bad channel + nDead++; + } + } + } + } + if (entries) { + mean /= entries; + } + //scan noisy channels + for (int ix = 1; ix <= 64; ix++) { + for (int iz = 1; iz <= 56; iz++) { + float cont = h->GetBinContent(ix, iz); + if (cont > mMaxOccupancyCut[m] * mean) { + nNoisy++; + } + } + } + // check quality + // dead channels + if (nDead > mDeadThreshold[m]) { + if (result.isBetterThan(Quality::Bad)) { + result.set(Quality::Bad); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too many dead channels M%d", m)); + TLatex* msg = new TLatex(0.2, 0.2, Form("#color[2]{Too many new dead channels: %d}", nDead)); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + TPaveText* msgNotOk = new TPaveText(0.0, 0.0, 0.1, 0.1, "NDC"); + msgNotOk->SetName(Form("%s_msg", mo->GetName())); + msgNotOk->Clear(); + msgNotOk->AddText("Not OK"); + msgNotOk->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msgNotOk); + } else if (nNoisy > mNoisyThreshold[m]) { // noisy channels + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too many noisy channels M%d", m)); + TLatex* msg = new TLatex(0.2, 0.2, Form("#color[2]{Too many noisy channels: %d}", nNoisy)); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + TPaveText* msgNotOk = new TPaveText(0.0, 0.0, 0.1, 0.1, "NDC"); + msgNotOk->SetName(Form("%s_msg", mo->GetName())); + msgNotOk->Clear(); + msgNotOk->AddText("Not OK"); + msgNotOk->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msgNotOk); + } else { + TPaveText* msgOk = new TPaveText(0.0, 0.0, 0.1, 0.1, "NDC"); + msgOk->SetName(Form("%s_msg", mo->GetName())); + msgOk->Clear(); + msgOk->AddText("OK"); + msgOk->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msgOk); + h->SetFillColor(kGreen); + } + } + // check energy spectrum + if (mo->getName().find("SpectrumM") != std::string::npos) { + LOG(info) << "I checking " << mo->getName(); + + //get module number: + int iMod = mo->getName()[mo->getName().find_first_of("1234")] - '0'; + bool isGoodMO = true; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1F*, skipping" << ENDM; + continue; + } + TPaveText* msg = new TPaveText(0.6, 0.5, 1.0, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + + auto nEvents = h->GetEntries(); + if (nEvents == 0) { + if (result.isBetterThan(Quality::Null)) { + result.set(Quality::Null); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("not enough statistics M%d", iMod)); + msg->AddText("Not enough data to check"); + msg->SetFillColor(kOrange); + isGoodMO = false; + } else { + h->GetXaxis()->SetRangeUser(mCluEnergyRangeL[iMod], mCluEnergyRangeR[iMod]); + auto mean = h->GetMean(); + if (mean < mMinCluEnergyMean[iMod]) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too small mean energy M%d", iMod)); + msg->AddText(Form("Mean is too small: %f", mean)); + msg->AddText(Form("Min allowed mean: %f", mMinCluEnergyMean[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + isGoodMO = false; + } else if (mean > mMaxCluEnergyMean[iMod]) { + if (result.isBetterThan(Quality::Medium)) { + result.set(Quality::Medium); + } + result.addFlag(FlagTypeFactory::Unknown(), Form("too big mean energy M%d", iMod)); + msg->AddText(Form("Mean is too big: %f", mean)); + msg->AddText(Form("Max allowed mean: %f", mMaxCluEnergyMean[iMod])); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + isGoodMO = false; + } + } + if (isGoodMO) { + msg->AddText("OK"); + msg->SetFillColor(kGreen); + } + } + } + return result; +} // namespace o2::quality_control_modules::phos + +void ClusterCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName().find("ClusterOccupancyM") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + + if (checkResult == Quality::Good) { + // + msg->Clear(); + msg->AddText("Occupancy OK!!!"); + msg->SetFillColor(kGreen); + // + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red"; + msg->Clear(); + msg->AddText("Too many dead channels"); + msg->AddText("If NOT a technical run,"); + msg->AddText("call PHOS on-call."); + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Info, Devel) << "Quality::medium, setting to orange"; + msg->Clear(); + msg->AddText("Too many noisy channels"); + h->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } +} +} // namespace o2::quality_control_modules::phos diff --git a/Modules/PHOS/src/ClusterQcTask.cxx b/Modules/PHOS/src/ClusterQcTask.cxx new file mode 100644 index 0000000000..2a29ae249d --- /dev/null +++ b/Modules/PHOS/src/ClusterQcTask.cxx @@ -0,0 +1,238 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterQcTask.cxx +/// \author Dmitri Peresunko +/// \author Dmitry Blau +/// + +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "PHOS/ClusterQcTask.h" +#include "DataFormatsPHOS/TriggerRecord.h" +#include "Framework/InputRecord.h" + +// using namespace o2::phos; + +namespace o2::quality_control_modules::phos +{ + +ClusterQcTask::~ClusterQcTask() +{ + for (int i = kNhist1D; i--;) { + if (mHist1D[i]) { + mHist1D[i]->Delete(); + mHist1D[i] = nullptr; + } + } + for (int i = kNhist2D; i--;) { + if (mHist2D[i]) { + mHist2D[i]->Delete(); + mHist2D[i] = nullptr; + } + } +} +void ClusterQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + using infoCONTEXT = AliceO2::InfoLogger::InfoLoggerContext; + infoCONTEXT context; + context.setField(infoCONTEXT::FieldName::Facility, "QC"); + context.setField(infoCONTEXT::FieldName::System, "QC"); + context.setField(infoCONTEXT::FieldName::Detector, "PHS"); + QcInfoLogger::GetInfoLogger().setContext(context); + ILOG(Debug, Devel) << "initialize ClusterQcTask" << ENDM; + + if (auto param = mCustomParameters.find("mPtMin"); param != mCustomParameters.end()) { + mPtMin = std::stof(param->second); + } + if (auto param = mCustomParameters.find("mOccCut"); param != mCustomParameters.end()) { + mOccCut = std::stof(param->second); + } + if (auto param = mCustomParameters.find("mEnergyMinForInvMass"); param != mCustomParameters.end()) { + mEnergyMinForInvMass = std::stof(param->second); + } + if (auto param = mCustomParameters.find("mMultiplicityMinForInvMass"); param != mCustomParameters.end()) { + mMultiplicityMinForInvMass = std::stoi(param->second); + } + + // read alignment to calculate cluster global coordinates + mGeom = o2::phos::Geometry::GetInstance("Run3"); + + // TODO: configure reading bad map from CCDB + mBadMap.reset(new o2::phos::BadChannelsMap()); + + // Prepare histograms + // BC histogram + mHist1D[kBCs] = new TH1F("BCsFromClusters", "BCs of cluster trigger records; BC; event count", 4000, 0, 4000); + getObjectsManager()->startPublishing(mHist1D[kBCs]); + + for (Int_t mod = 0; mod < 4; mod++) { + if (!mHist2D[kOccupancyM1 + mod]) { + mHist2D[kOccupancyM1 + mod] = new TH2F(Form("ClusterOccupancyM%d", mod + 1), Form("Cluster occupancy, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kOccupancyM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kOccupancyM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kOccupancyM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kOccupancyM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kOccupancyM1 + mod]->SetStats(0); + mHist2D[kOccupancyM1 + mod]->SetMinimum(0); + // mHist2D[kOccupancyM1+mod]->SetMaximum(100) ; + getObjectsManager()->startPublishing(mHist2D[kOccupancyM1 + mod]); + } else { + mHist2D[kOccupancyM1 + mod]->Reset(); + } + + if (!mHist2D[kTimeEM1 + mod]) { + mHist2D[kTimeEM1 + mod] = new TH2F(Form("TimevsE%d", mod + 1), Form("Cluster time vs energy, mod %d", mod + 1), 50, 0., 10., 50, -5.e-7, 5.e-7); + mHist2D[kTimeEM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kTimeEM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kTimeEM1 + mod]->GetXaxis()->SetTitle("E, GeV"); + mHist2D[kTimeEM1 + mod]->GetYaxis()->SetTitle("t-t_{0}, ns"); + mHist2D[kTimeEM1 + mod]->SetStats(0); + mHist2D[kTimeEM1 + mod]->SetMinimum(0); + // mHist2D[kTimeEM1 + mod]->SetMaximum(100); + getObjectsManager()->startPublishing(mHist2D[kTimeEM1 + mod]); + } else { + mHist2D[kTimeEM1 + mod]->Reset(); + } + + if (!mHist1D[kSpectrumM1 + mod]) { + mHist1D[kSpectrumM1 + mod] = new TH1F(Form("SpectrumM%d", mod + 1), Form("Cluster spectrum in mod %d", mod + 1), 100, 0., 10.); + mHist1D[kSpectrumM1 + mod]->GetXaxis()->SetTitle("GeV"); + mHist1D[kSpectrumM1 + mod]->SetStats(0); + mHist1D[kSpectrumM1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist1D[kSpectrumM1 + mod]); + } else { + mHist1D[kSpectrumM1 + mod]->Reset(); + } + if (!mHist1D[kPi0M1 + mod]) { + mHist1D[kPi0M1 + mod] = new TH1F(Form("InvMassM%d", mod + 1), Form("inv mass %d", mod + 1), 100, 0., 0.5); + mHist1D[kPi0M1 + mod]->GetXaxis()->SetTitle("m_{#gamma#gamma} (GeV/c^{2})"); + mHist1D[kPi0M1 + mod]->SetStats(0); + mHist1D[kPi0M1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist1D[kPi0M1 + mod]); + } else { + mHist1D[kPi0M1 + mod]->Reset(); + } + } +} + +void ClusterQcTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + reset(); +} + +void ClusterQcTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ClusterQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + + auto clusters = ctx.inputs().get>("clusters"); + auto cluTR = ctx.inputs().get>("clustertr"); + + for (auto& tr : cluTR) { + + mHist1D[kBCs]->Fill(tr.getBCData().bc); + int firstCluInEvent = tr.getFirstEntry(); + int lastCluInEvent = firstCluInEvent + tr.getNumberOfObjects(); + + for (int m = 4; m--;) { + mBuffer[m].clear(); + } + for (int i = firstCluInEvent; i < lastCluInEvent; i++) { + const o2::phos::Cluster& clu = clusters[i]; + float e = clu.getEnergy(); + if (e < 1.e-4) { + continue; + } + + int mod = clu.module(); + // Fill occupancy and time-E histos + float posX, posZ; + clu.getLocalPosition(posX, posZ); + char relid[3]; + mGeom->relPosToRelId(mod, posX, posZ, relid); + + if (e > mOccCut) { + mHist2D[kOccupancyM1 + mod - 1]->Fill(relid[1] - 0.5, relid[2] - 0.5); + } + mHist2D[kTimeEM1 + mod - 1]->Fill(e, clu.getTime()); + mHist1D[kSpectrumM1 + mod - 1]->Fill(e); + + if (!checkCluster(clu)) { + continue; + } + // prepare TLorentsVector + TVector3 vec3; + mGeom->local2Global(mod, posX, posZ, vec3); + double norm = vec3.Mag(); + TLorentzVector v(vec3.X() * e / norm, vec3.Y() * e / norm, vec3.Z() * e / norm, e); + for (const TLorentzVector& v2 : mBuffer[mod - 1]) { + TLorentzVector sum = v + v2; + if (sum.Pt() > mPtMin) { + mHist1D[kPi0M1 + mod - 1]->Fill(sum.M()); + } + } + mBuffer[mod - 1].emplace_back(v); + } + } + +} // function monitor data + +void ClusterQcTask::endOfCycle() +{ + ILOG(Info, Support) << "endOfCycle" << ENDM; +} + +void ClusterQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Info, Support) << "endOfActivity" << ENDM; +} + +void ClusterQcTask::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + for (int i = kNhist1D; i--;) { + if (mHist1D[i]) { + mHist1D[i]->Reset(); + } + } + for (int i = kNhist2D; i--;) { + if (mHist2D[i]) { + mHist2D[i]->Reset(); + } + } +} +bool ClusterQcTask::checkCluster(const o2::phos::Cluster& clu) +{ + // First check BadMap + float posX, posZ; + clu.getLocalPosition(posX, posZ); + short absId; + o2::phos::Geometry::relPosToAbsId(clu.module(), posX, posZ, absId); + if (!mBadMap->isChannelGood(absId)) { + return false; + } + + return (clu.getEnergy() > mEnergyMinForInvMass && clu.getMultiplicity() >= mMultiplicityMinForInvMass); +} + +} // namespace o2::quality_control_modules::phos diff --git a/Modules/PHOS/src/RawCheck.cxx b/Modules/PHOS/src/RawCheck.cxx new file mode 100644 index 0000000000..b15eeeb556 --- /dev/null +++ b/Modules/PHOS/src/RawCheck.cxx @@ -0,0 +1,395 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawCheck.cxx +/// \author Dmitri Peresunko +/// + +#include +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include +#include "PHOS/RawCheck.h" +#include "PHOS/TH1Fraction.h" + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::phos +{ + +void RawCheck::configure() +{ + auto param = mCustomParameters.find("mMinHGPedestalValue"); + if (param != mCustomParameters.end()) { + mMinHGPedestalValue = stoi(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter mMinHGPedestalValue " + << mMinHGPedestalValue << ENDM; + } + param = mCustomParameters.find("mMaxHGPedestalValue"); + if (param != mCustomParameters.end()) { + mMaxHGPedestalValue = stoi(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter mMaxHGPedestalValue " + << mMaxHGPedestalValue << ENDM; + } + param = mCustomParameters.find("mMinLGPedestalValue"); + if (param != mCustomParameters.end()) { + mMinLGPedestalValue = stoi(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter mMinLGPedestalValue " + << mMinLGPedestalValue << ENDM; + } + param = mCustomParameters.find("mMaxLGPedestalValue"); + if (param != mCustomParameters.end()) { + mMaxLGPedestalValue = stoi(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter mMaxLGPedestalValue " + << mMaxLGPedestalValue << ENDM; + } + // RMS + param = mCustomParameters.find("mMinHGPedestalRMS"); + if (param != mCustomParameters.end()) { + mMinHGPedestalRMS = stof(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter mMinHGPedestalRMS " + << mMinHGPedestalRMS << ENDM; + } + param = mCustomParameters.find("mMaxHGPedestalRMS"); + if (param != mCustomParameters.end()) { + mMaxHGPedestalRMS = stof(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter mMaxHGPedestalRMS " + << mMaxHGPedestalRMS << ENDM; + } + param = mCustomParameters.find("mMinLGPedestalRMS"); + if (param != mCustomParameters.end()) { + mMinLGPedestalRMS = stof(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter mMinLGPedestalRMS " + << mMinLGPedestalRMS << ENDM; + } + param = mCustomParameters.find("mMaxLGPedestalRMS"); + if (param != mCustomParameters.end()) { + mMaxLGPedestalRMS = stof(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter mMaxLGPedestalRMS " + << mMaxLGPedestalRMS << ENDM; + } + + for (int mod = 1; mod < 5; mod++) { + // mToleratedBadPedestalValueChannelsM + param = mCustomParameters.find(Form("mToleratedBadChannelsM%d", mod)); + if (param != mCustomParameters.end()) { + mToleratedBadChannelsM[mod] = stoi(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter " + << Form("mToleratedBadChannelsM%d", mod) + << mToleratedBadChannelsM[mod] << ENDM; + } + // mToleratedDeviatedBranches + param = mCustomParameters.find(Form("mToleratedDeviatedBranchesM%d", mod)); + if (param != mCustomParameters.end()) { + mToleratedDeviatedBranches[mod] = stoi(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter " + << Form("mToleratedDeviatedBranchesM%d", mod) + << mToleratedDeviatedBranches[mod] << ENDM; + } + // mBranchOccupancyDeviationAllowed + param = mCustomParameters.find(Form("mBranchOccupancyDeviationAllowedM%d", mod)); + if (param != mCustomParameters.end()) { + mBranchOccupancyDeviationAllowed[mod] = stof(param->second); + ILOG(Debug, Support) << "configure() : Custom parameter " + << Form("mBranchOccupancyDeviationAllowedM%d", mod) + << mBranchOccupancyDeviationAllowed[mod] << ENDM; + } + } + // error occurance + for (int i = 0; i < 5; i++) { + if (auto param = mCustomParameters.find(Form("mErrorOccuranceThreshold%d", i)); + param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "configure() : Custom parameter " + << Form("mErrorOccuranceThreshold%d = ", i) + << param->second << "for the " << mErrorLabel[i] << ENDM; + mErrorOccuranceThreshold[i] = stof(param->second); + } + } + mBadMap[0] = 0; +} + +Quality RawCheck::check(std::map>* moMap) +{ + mCheckResult = Quality::Good; + for (auto& [moName, mo] : *moMap) { + (void)moName; // trick the compiler about not used variable + if (checkErrHistograms(mo.get())) { + continue; + } + if (checkPedestalHistograms(mo.get())) { + continue; + } + if (checkPhysicsHistograms(mo.get())) { + continue; + } + } + return mCheckResult; +} + +bool RawCheck::checkErrHistograms(MonitorObject* mo) +{ + // Return true if mo found and handled + // else return false to search further + + if (mo->getName().find("ErrorTypePerDDL") != std::string::npos) { // HG mean summary 100, 0., 100. + TH2S* h = dynamic_cast(mo->getObject()); + if (h->Integral() == 0) { + TPaveText* msg = new TPaveText(0., 0.8, 0.2, 0.9, "NDC"); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + msg->AddText("OK"); + msg->SetFillColor(kGreen); + mCheckResult = Quality::Good; + h->GetListOfFunctions()->Add(msg); + } else { + TPaveText* msg = new TPaveText(0.7, 0.6, 0.9, 0.9, "NDC"); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + mCheckResult = Quality::Bad; + msg->AddText(Form("HW decoding errors")); + msg->AddText(Form("Notify PHOS oncall")); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msg); + } + return true; + } + if (mo->getName().find("ErrorTypeOccurance") != std::string::npos) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Warning, Devel) << "Could not cast " << mo->getName() << " to TH1Fraction*, skipping" << ENDM; + } else { + h->SetOption("hist"); + h->SetDrawOption("hist"); + bool isGoodMO = true; + for (int i = 1; i <= h->GetXaxis()->GetNbins(); i++) { + if (h->GetBinContent(i) > mErrorOccuranceThreshold[i - 1]) { + isGoodMO = false; + if (mCheckResult.isBetterThan(Quality::Medium)) { + mCheckResult.set(Quality::Medium); + } + mCheckResult.addFlag(FlagTypeFactory::Unknown(), Form("too many %s errors", mErrorLabel[i - 1])); + TLatex* msg = new TLatex(0.2, 0.2 + i * 0.125, Form("#color[2]{Too many %s errors}", mErrorLabel[i - 1])); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + TPaveText* msgNotOk = new TPaveText(0.0, 0.0, 0.1, 0.1, "NDC"); + msgNotOk->SetName(Form("%s_msg", mo->GetName())); + msgNotOk->Clear(); + msgNotOk->AddText("Not OK"); + msgNotOk->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msgNotOk); + h->SetMarkerStyle(21); + h->SetFillColor(kOrange); + } + } + if (isGoodMO) { + TPaveText* msgOk = new TPaveText(0.0, 0.0, 0.1, 0.1, "NDC"); + msgOk->SetName(Form("%s_msg", mo->GetName())); + msgOk->Clear(); + msgOk->AddText("OK"); + msgOk->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msgOk); + h->SetFillColor(kGreen); + } + } + } + return false; +} +bool RawCheck::checkPhysicsHistograms(MonitorObject* mo) +{ + // get number of bad channels in each module. do it only once + if (mo->getName().find("BadMapSummary") != std::string::npos) { + if (mBadMap[0]) { // do it only once + return true; + } + TH1F* h = dynamic_cast(mo->getObject()); + for (int mod = 1; mod < 5; mod++) { + mBadMap[mod] = h->GetBinContent(mod); + } + mBadMap[0] = 1; + return true; + } + // Check occupancy histograms: if statistics is sufficient? + // if yes - check number of dead channels wrt bad map + if (mo->getName().find("CellHGOccupancyM") != std::string::npos) { // HG mean summary 100, 0., 100. + int mod = atoi(&mo->getName()[mo->getName().find("CellHGOccupancyM") + 16]); + TH2F* h = dynamic_cast(mo->getObject()); + float nCounts = h->Integral(); + if ((mod == 1 && nCounts < 17920.) || (mod > 1 && nCounts < 35840.)) { + TPaveText* msg = new TPaveText(0.0, 0.0, 0.3, 0.1, "NDC"); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + msg->AddText("Not enough stat"); + msg->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msg); + return true; + } + // calculate dead channels + int nDead = 0; + for (int ix = 1; ix <= 64; ix++) { + for (int iz = 1; iz <= 56; iz++) { + if (h->GetBinContent(ix, iz) == 0) { + nDead++; + } + } + } + if (mod == 1) { + nDead -= 1792; + } + // calculate deviated branches + TH2F* hBranches = (TH2F*)h->Clone("hBranches"); + hBranches->Rebin2D(16, 28); + double mean = 0; + int nDeviatedBranches = 0; + for (int ix = 1; ix <= 4; ix++) { + mean += hBranches->GetBinContent(ix, 1); + mean += hBranches->GetBinContent(ix, 2); + } + if (mod == 1) { + mean /= 4.; + } else { + mean /= 8.; + } + for (int ix = (mod == 1) ? 3 : 1; ix <= 4; ix++) { + for (int iz = 1; iz <= 2; iz++) { + if (hBranches->GetBinContent(ix, iz) > mean * mBranchOccupancyDeviationAllowed[mod] || + hBranches->GetBinContent(ix, iz) < mean / mBranchOccupancyDeviationAllowed[mod]) + nDeviatedBranches++; + } + } + // define quality + if (mBadMap[0] && (nDead > mBadMap[mod] + mToleratedBadChannelsM[mod])) { + if (mCheckResult.isBetterThan(Quality::Bad)) { + mCheckResult.set(Quality::Bad); + } + mCheckResult.addFlag(FlagTypeFactory::Unknown(), Form("too many dead channels M%d", mod)); + TLatex* msg = new TLatex(0.2, 0.2, Form("#color[2]{Too many dead channels: %d; but bad map has %d channels}", nDead, mToleratedBadChannelsM[mod])); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + TPaveText* msgNotOk = new TPaveText(0.0, 0.0, 0.1, 0.1, "NDC"); + msgNotOk->SetName(Form("%s_msg", mo->GetName())); + msgNotOk->Clear(); + msgNotOk->AddText("Not OK"); + msgNotOk->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msgNotOk); + } else if (nDeviatedBranches > mToleratedDeviatedBranches[mod]) { + if (mCheckResult.isBetterThan(Quality::Medium)) { + mCheckResult.set(Quality::Medium); + } + mCheckResult.addFlag(FlagTypeFactory::Unknown(), Form("Branch oocupancy deviated M%d", mod)); + TLatex* msg = new TLatex(0.2, 0.4, Form("#color[2]{%d deviated branches; but %d allowed}", nDeviatedBranches, mToleratedDeviatedBranches[mod])); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + msg->Draw(); + TPaveText* msgNotOk = new TPaveText(0.0, 0.0, 0.1, 0.1, "NDC"); + msgNotOk->SetName(Form("%s_msg", mo->GetName())); + msgNotOk->Clear(); + msgNotOk->AddText("Not OK"); + msgNotOk->SetFillColor(kRed); + h->GetListOfFunctions()->Add(msgNotOk); + + } else { // OK + TPaveText* msg = new TPaveText(0.0, 0.0, 0.1, 0.1, "NDC"); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + msg->AddText("OK"); + msg->SetFillColor(kGreen); + h->GetListOfFunctions()->Add(msg); + } + return true; + } + return false; +} + +bool RawCheck::checkPedestalHistograms(MonitorObject* mo) +{ + // check pedestal historams + // return true if object found/analyzed + // Only 1D summary plots will be checked + bool toCheck = false; + float lowLimit = 0., upLimit = 0.; + int mod = 0; + if (!toCheck && mo->getName().find("PedHGMeanSum") != std::string::npos) { // HG mean summary 100, 0., 100. + mod = atoi(&mo->getName()[mo->getName().find("PedHGMeanSum") + 12]); + toCheck = true; + lowLimit = mMinHGPedestalValue; + upLimit = mMaxHGPedestalValue; + } + if (!toCheck && mo->getName().find("PedHGRMSSum") != std::string::npos) { // HG RMS summary 100, 0., 10. + mod = atoi(&mo->getName()[mo->getName().find("PedHGRMSSum") + 11]); + toCheck = true; + lowLimit = mMinHGPedestalRMS; + upLimit = mMaxHGPedestalRMS; + } + if (!toCheck && mo->getName().find("PedLGMeanSum") != std::string::npos) { // LG mean summary 100, 0., 100. + mod = atoi(&mo->getName()[mo->getName().find("PedLGMeanSum") + 12]); + toCheck = true; + lowLimit = mMinLGPedestalValue; + upLimit = mMaxLGPedestalValue; + } + if (!toCheck && mo->getName().find("PedLGRMSSum") != std::string::npos) { // HG mean summary 100, 0., 10. + mod = atoi(&mo->getName()[mo->getName().find("PedLGRMSSum") + 11]); + toCheck = true; + lowLimit = mMinLGPedestalRMS; + upLimit = mMaxLGPedestalRMS; + } + if (!toCheck) { + return false; + } + int nTotal = (mod == 1) ? 1792 : 3584; + TH1F* h = dynamic_cast(mo->getObject()); + int startBin = h->GetXaxis()->FindBin(lowLimit); + int lastBin = h->GetXaxis()->FindBin(upLimit); + int nGood = h->Integral(startBin, lastBin); + + if (nGood > nTotal - mBadMap[mod] + mToleratedBadChannelsM[mod]) { + TPaveText* msg = new TPaveText(0.7, 0.8, 0.9, 0.9, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + msg->AddText("OK"); + msg->SetFillColor(kGreen); + mCheckResult = Quality::Good; + } else { + TPaveText* msg = new TPaveText(0.6, 0.6, 0.9, 0.75, "NDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + mCheckResult = Quality::Bad; + msg->AddText(Form("Too many bad channels")); + msg->AddText(Form(" %d ", nTotal - nGood)); + msg->SetFillColor(kRed); + h->SetFillColor(kRed); + } + return true; +} + +} // namespace o2::quality_control_modules::phos diff --git a/Modules/PHOS/src/RawQcTask.cxx b/Modules/PHOS/src/RawQcTask.cxx new file mode 100644 index 0000000000..26e679b782 --- /dev/null +++ b/Modules/PHOS/src/RawQcTask.cxx @@ -0,0 +1,887 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawQcTask.cxx +/// \author Dmitri Peresunko +/// \author Dmitry Blau +/// + +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "PHOS/RawQcTask.h" +#include "Headers/RAWDataHeader.h" +#include "PHOSReconstruction/RawReaderError.h" +#include "PHOSBase/Geometry.h" +#include "PHOSBase/Mapping.h" +#include "Framework/InputRecord.h" +#include "CommonUtils/NameConf.h" + +namespace o2::quality_control_modules::phos +{ + +RawQcTask::~RawQcTask() +{ + for (int i = kNhist1D; i--;) { + if (mHist1D[i]) { + mHist1D[i]->Delete(); + mHist1D[i] = nullptr; + } + } + for (int i = kNhist2D; i--;) { + if (mHist2D[i]) { + mHist2D[i]->Delete(); + mHist2D[i] = nullptr; + } + } + for (int i = kNhist2DMean; i--;) { + if (mHist2DMean[i]) { + mHist2DMean[i]->Delete(); + mHist2DMean[i] = nullptr; + } + } + for (int i = kNhist2DBitmask; i--;) { + if (mHist2DBitmask[i]) { + mHist2DBitmask[i]->Delete(); + mHist2DBitmask[i] = nullptr; + } + } + for (int i = kNratio1D; i--;) { + if (mFractions1D[i]) { + delete mFractions1D[i]; + mFractions1D[i] = nullptr; + } + } +} +void RawQcTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + using infoCONTEXT = AliceO2::InfoLogger::InfoLoggerContext; + infoCONTEXT context; + context.setField(infoCONTEXT::FieldName::Facility, "QC"); + context.setField(infoCONTEXT::FieldName::System, "QC"); + context.setField(infoCONTEXT::FieldName::Detector, "PHS"); + QcInfoLogger::GetInfoLogger().setContext(context); + ILOG(Debug, Devel) << "initialize RawQcTask" << ENDM; + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + if (auto param = mCustomParameters.find("pedestal"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Working in pedestal mode " << ENDM; + if (param->second.find("on") != std::string::npos) { + mMode = 1; + } + } + if (auto param = mCustomParameters.find("LED"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Working in LED mode " << ENDM; + if (param->second.find("on") != std::string::npos) { + mMode = 2; + } + } + if (auto param = mCustomParameters.find("physics"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Working in physics mode " << ENDM; + if (param->second.find("on") != std::string::npos) { + mMode = 0; + } + } + if (auto param = mCustomParameters.find("chi2"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Scan chi2 distributions " << ENDM; + if (param->second.find("on") != std::string::npos) { + mCheckChi2 = true; + } + } + if (auto param = mCustomParameters.find("trignoise"); param != mCustomParameters.end()) { + ILOG(Info, Support) << "Scan trigger ST and Dig matching " << ENDM; + if (param->second.find("on") != std::string::npos) { + mTrNoise = true; + } + } + + InitHistograms(); +} + +void RawQcTask::InitHistograms() +{ + + // First init general histograms for any mode + // BC histogram + mHist1D[kBCs] = new TH1F("BCsFromCells", "BCs of cell trigger records; BC; event count", 4000, 0, 4000); + getObjectsManager()->startPublishing(mHist1D[kBCs]); + + // Statistics histograms + mHist2D[kErrorNumber] = new TH2F("NumberOfErrors", "Number of hardware errors", 32, 0, 32, 15, 0, 15.); // xaxis: FEE card number + 2 for TRU and global errors + mHist2D[kErrorNumber]->GetXaxis()->SetTitle("FEE card"); + mHist2D[kErrorNumber]->GetYaxis()->SetTitle("DDL"); + mHist2D[kErrorNumber]->SetDrawOption("colz"); + mHist2D[kErrorNumber]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[kErrorNumber]); + + mHist2DBitmask[kErrorType] = new TH2SBitmask("ErrorTypePerDDL", "ErrorTypePerDDL", 32, 0, 32, 15, 0, 15.); + mHist2DBitmask[kErrorType]->GetXaxis()->SetTitle("FEE card"); + mHist2DBitmask[kErrorType]->GetYaxis()->SetTitle("DDL"); + mHist2DBitmask[kErrorType]->SetDrawOption("colz"); + mHist2DBitmask[kErrorType]->SetStats(0); + getObjectsManager()->startPublishing(mHist2DBitmask[kErrorType]); + + mFractions1D[kErrorTypeOccurance] = new TH1Fraction("ErrorTypeOccurance", "Errors of differen types per event", 5, 0, 5); + mFractions1D[kErrorTypeOccurance]->GetXaxis()->SetTitle("Error Type"); + mFractions1D[kErrorTypeOccurance]->SetStats(0); + mFractions1D[kErrorTypeOccurance]->GetYaxis()->SetTitle("Occurance (event^{-1})"); + const char* errorLabel[] = { "wrong ALTRO", "mapping", "ch. header", + "ch. payload", "wrond hw addr" }; + for (int i = 1; i <= 5; i++) { + mFractions1D[kErrorTypeOccurance]->GetXaxis()->SetBinLabel(i, errorLabel[i - 1]); + } + getObjectsManager()->startPublishing(mFractions1D[kErrorTypeOccurance]); + + mHist1D[kBadMapSummary] = new TH1F("BadMapSummary", "Number of bad channels", 4, 1., 5.); // xaxis: FEE card number + 2 for TRU and global errors + mHist1D[kBadMapSummary]->GetXaxis()->SetTitle("module"); + mHist1D[kBadMapSummary]->GetYaxis()->SetTitle("N bad channels"); + mHist1D[kBadMapSummary]->SetDrawOption("h"); + mHist1D[kBadMapSummary]->SetStats(0); + getObjectsManager()->startPublishing(mHist1D[kBadMapSummary]); + + if (mCheckChi2) { + for (Int_t mod = 0; mod < 4; mod++) { + if (!mHist2D[kChi2M1 + mod]) { + mHist2D[kChi2M1 + mod] = new TH2F(Form("Chi2M%d", mod + 1), Form("sample fit #chi2/NDF, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kChi2M1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kChi2M1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kChi2M1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kChi2M1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kChi2M1 + mod]->SetStats(0); + mHist2D[kChi2M1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist2D[kChi2M1 + mod]); + + mHist2D[kChi2NormM1 + mod] = new TH2F(Form("Chi2NormM%d", mod + 1), Form("sample fit #chi2/NDF normalization, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kChi2NormM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kChi2NormM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kChi2NormM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kChi2NormM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kChi2NormM1 + mod]->SetStats(0); + mHist2D[kChi2NormM1 + mod]->SetMinimum(0); + // getObjectsManager()->startPublishing(mHist2D[kChi2NormM1 + mod]); + } else { + mHist2D[kChi2M1 + mod]->Reset(); + mHist2D[kChi2NormM1 + mod]->Reset(); + } + } + } + + if (mMode == 0) { // Physics + CreatePhysicsHistograms(); + // CreateTRUHistograms(); + } + if (mMode == 1) { // Pedestals + CreatePedestalHistograms(); + } + if (mMode == 2) { // LED + CreatePhysicsHistograms(); + CreateLEDHistograms(); + // CreateTRUHistograms(); + } +} + +void RawQcTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + reset(); +} + +void RawQcTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + if (mCheckChi2) { + if (mFinalized) { // mean already calculated + for (Int_t mod = 0; mod < 4; mod++) { + if (mHist2D[kChi2M1 + mod]) { + mHist2D[kChi2M1 + mod]->Multiply(mHist2D[kChi2NormM1 + mod]); + } + } + if (mMode != 1) { // for mode 1 another check below + mFinalized = false; + } + } + } + + if (mMode == 1) { // Pedestals + if (mFinalized) { // means were already calculated + for (Int_t mod = 0; mod < 4; mod++) { + if (mHist2DMean[kHGmeanM1 + mod]) { + mHist2DMean[kHGmeanM1 + mod]->Multiply(mHist2D[kHGoccupM1 + mod]); + mHist2DMean[kHGrmsM1 + mod]->Multiply(mHist2D[kHGoccupM1 + mod]); + } + if (mHist2DMean[kLGmeanM1 + mod]) { + mHist2DMean[kLGmeanM1 + mod]->Multiply(mHist2D[kLGoccupM1 + mod]); + mHist2DMean[kLGrmsM1 + mod]->Multiply(mHist2D[kLGoccupM1 + mod]); + } + } + mFinalized = false; + } + } +} + +void RawQcTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + if (ctx.inputs().getPos("rawerr") >= 0) { // to be able to scan e.g. CTF data + auto hwerrors = ctx.inputs().get>("rawerr"); + for (auto e : hwerrors) { + int ibin = mHist2D[kErrorNumber]->Fill(float(e.getFEC()), float(e.getDDL())); + int cont = mHist2DBitmask[kErrorType]->GetBinContent(ibin); + cont |= (1 << e.getError()); + mHist2DBitmask[kErrorType]->SetBinContent(ibin, cont); + mFractions1D[kErrorTypeOccurance]->fillUnderlying(e.getError() - 3); + // LOG(info) << "Error of type " << (int)e.getError(); + } + } + // Bad Map + // Read current bad map if not read yet + if (mInitBadMap) { + mInitBadMap = false; + ILOG(Info, Support) << "Getting bad map" << ENDM; + mBadMap = retrieveConditionAny("PHS/Calib/BadMap"); + if (!mBadMap) { + ILOG(Error, Support) << "Can not get bad map" << ENDM; + mHist1D[kBadMapSummary]->Reset(); + } else { + unsigned short nbm[4] = { 0 }; + for (short absId = 1973; absId <= o2::phos::Mapping::NCHANNELS; absId++) { + if (!mBadMap->isChannelGood(absId)) { + nbm[(absId - 1) / 3584]++; + } + } + for (int mod = 0; mod < 4; mod++) { + mHist1D[kBadMapSummary]->SetBinContent(mod + 1, nbm[mod]); + } + ILOG(Info, Support) << "Bad channels:[" << nbm[0] << "," << nbm[1] << "," << nbm[2] << "," << nbm[3] << "]" << ENDM; + } + } + + // Chi2: not hardware errors but unusual/correpted sample + if (mCheckChi2) { + // vector contains subsequent pairs (address,chi2) + auto chi2list = ctx.inputs().get>("fitquality"); + auto it = chi2list.begin(); + while (it != chi2list.end()) { + short address = *it; + it++; + bool caloFlag = address & 1 << 14; + address &= ~(1 << 14); // remove HG/LG bit 14 + float chi = 0.2 * (*it); + it++; + char relid[3]; + o2::phos::Geometry::absToRelNumbering(address, relid); + mHist2D[kChi2M1 + relid[0] - 1]->Fill(relid[1] - 0.5, relid[2] - 0.5, chi); + mHist2D[kChi2NormM1 + relid[0] - 1]->Fill(relid[1] - 0.5, relid[2] - 0.5); + } + } + + // Cells + auto cells = ctx.inputs().get>("cells"); + auto cellsTR = ctx.inputs().get>("cellstr"); + mFractions1D[kErrorTypeOccurance]->increaseEventCounter(cellsTR.size()); + for (const auto& trigRecord : cellsTR) { + mHist1D[kBCs]->Fill(trigRecord.getBCData().bc); + } + + if (mMode == 0) { // Physics + FillPhysicsHistograms(cells, cellsTR); + // FillTRUHistograms(cells, cellsTR); + } + if (mMode == 1) { // Pedestals + FillPedestalHistograms(cells, cellsTR); + } + if (mMode == 2) { // LED + FillPhysicsHistograms(cells, cellsTR); + FillLEDHistograms(cells, cellsTR); + // FillTRUHistograms(cells, cellsTR); + } +} // function monitor data + +void RawQcTask::endOfCycle() +{ + mFractions1D[kErrorTypeOccurance]->update(); + if (mCheckChi2) { + if (!mFinalized) { // not already calculated + for (Int_t mod = 0; mod < 4; mod++) { + if (mHist2D[kChi2M1 + mod]) { + mHist2D[kChi2M1 + mod]->Divide(mHist2D[kChi2NormM1 + mod]); + } + } + } + } + + if (mMode == 1) { // Pedestals + if (mFinalized) { // means were already calculated + return; + } + for (Int_t mod = 0; mod < 4; mod++) { + if (mHist2DMean[kHGmeanM1 + mod]) { + mHist2DMean[kHGmeanM1 + mod]->Divide(mHist2D[kHGoccupM1 + mod]); + mHist2DMean[kHGrmsM1 + mod]->Divide(mHist2D[kHGoccupM1 + mod]); + mHist1D[kHGmeanSummaryM1 + mod]->Reset(); + mHist1D[kHGrmsSummaryM1 + mod]->Reset(); + double occMin = 1.e+9; + double occMax = 0.; + for (int iz = 1; iz <= 64; iz++) { + for (int ix = 1; ix <= 56; ix++) { + float a = mHist2DMean[kHGmeanM1 + mod]->GetBinContent(iz, ix); + if (a > 0) { + mHist1D[kHGmeanSummaryM1 + mod]->Fill(a); + } + a = mHist2DMean[kHGrmsM1 + mod]->GetBinContent(iz, ix); + if (a > 0) { + mHist1D[kHGrmsSummaryM1 + mod]->Fill(a); + } + a = mHist2D[kHGoccupM1 + mod]->GetBinContent(iz, ix); + if (a > 0) { + if (a < occMin) + occMin = a; + if (a > occMax) + occMax = a; + } + } + } + mHist2D[kHGoccupM1 + mod]->SetMinimum(occMin); + mHist2D[kHGoccupM1 + mod]->SetMaximum(occMax); + } + if (mHist2DMean[kLGmeanM1 + mod]) { + mHist2DMean[kLGmeanM1 + mod]->Divide(mHist2D[kLGoccupM1 + mod]); + mHist2DMean[kLGrmsM1 + mod]->Divide(mHist2D[kLGoccupM1 + mod]); + mHist1D[kLGmeanSummaryM1 + mod]->Reset(); + mHist1D[kLGrmsSummaryM1 + mod]->Reset(); + double occMin = 1.e+9; + double occMax = 0.; + for (int iz = 1; iz <= 64; iz++) { + for (int ix = 1; ix <= 56; ix++) { + float a = mHist2DMean[kLGmeanM1 + mod]->GetBinContent(iz, ix); + if (a > 0) { + mHist1D[kLGmeanSummaryM1 + mod]->Fill(a); + } + a = mHist2DMean[kLGrmsM1 + mod]->GetBinContent(iz, ix); + if (a > 0) { + mHist1D[kLGrmsSummaryM1 + mod]->Fill(a); + } + a = mHist2D[kLGoccupM1 + mod]->GetBinContent(iz, ix); + if (a > 0) { + if (a < occMin) + occMin = a; + if (a > occMax) + occMax = a; + } + } + } + mHist2D[kLGoccupM1 + mod]->SetMinimum(occMin); + mHist2D[kLGoccupM1 + mod]->SetMaximum(occMax); + } + } + mFinalized = true; + } + //==========LED=========== + if (mMode == 2) { // LED + ILOG(Info, Support) << " Caclulating number of peaks" << ENDM; + for (unsigned int absId = 1793; absId <= o2::phos::Mapping::NCHANNELS; absId++) { + int npeaks = mSpSearcher->Search(&(mSpectra[absId - 1793]), 2, "goff", 0.1); + char relid[3]; + o2::phos::Geometry::absToRelNumbering(absId, relid); + short mod = relid[0] - 1; + int ibin = mHist2DMean[kLEDNpeaksM1 + mod]->FindBin(relid[1] - 0.5, relid[2] - 0.5); + mHist2DMean[kLEDNpeaksM1 + mod]->SetBinContent(ibin, npeaks); + } + ILOG(Info, Support) << " Caclulating number of peaks done" << ENDM; + } +} + +void RawQcTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Info, Support) << "endOfActivity" << ENDM; + endOfCycle(); +} + +void RawQcTask::reset() +{ + eventCounter = 0; + // clean all the monitor objects here + mFinalized = false; + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + for (int i = kNhist1D; i--;) { + if (mHist1D[i]) { + mHist1D[i]->Reset(); + } + } + for (int i = kNhist2D; i--;) { + if (mHist2D[i]) { + mHist2D[i]->Reset(); + } + } +} +void RawQcTask::FillLEDHistograms(const gsl::span& cells, const gsl::span& cellsTR) +{ + + // Fill intermediate histograms + for (const auto& tr : cellsTR) { + int firstCellInEvent = tr.getFirstEntry(); + int lastCellInEvent = firstCellInEvent + tr.getNumberOfObjects(); + for (int i = firstCellInEvent; i < lastCellInEvent; i++) { + const o2::phos::Cell c = cells[i]; + if (!c.getTRU() && c.getHighGain()) { + mSpectra[c.getAbsId() - 1793].Fill(c.getEnergy()); + } + } + } +} +void RawQcTask::FillTRUHistograms(const gsl::span& cells, const gsl::span& cellsTR) +{ + std::vector triggerSTTiles; + std::vector triggerDGTiles; + char relId[3] = { 0 }; + for (const auto tr : cellsTR) { + triggerSTTiles.clear(); + triggerDGTiles.clear(); + int firstCellInEvent = tr.getFirstEntry(); + int lastCellInEvent = firstCellInEvent + tr.getNumberOfObjects(); + for (int i = firstCellInEvent; i < lastCellInEvent; i++) { + const o2::phos::Cell& c = cells[i]; + if (c.getTRU()) { + if (c.getType() == o2::phos::TRU4x4) { + o2::phos::Geometry::truAbsToRelNumbering(c.getTRUId(), 1, relId); + // ILOG(Info, Support) << "TRU4x4 [" <<(int)relId[0]<< ","<<(int)relId[1]<< ","<< (int)relId[2] <<"]" << ENDM; + mHist2D[kTRUSTOccupM1 + relId[0] - 1]->Fill(relId[1] - 0.5, relId[2] - 0.5); + triggerSTTiles.push_back(relId[0] + (relId[1] << 3) + (relId[2] << 10)); + } else { // 2x2 + o2::phos::Geometry::truAbsToRelNumbering(c.getTRUId(), 0, relId); + // ILOG(Info, Support) << "TRU2x2 [" <<(int)relId[0]<< ","<<(int)relId[1]<< ","<< (int)relId[2] <<"]" << ENDM; + mHist2D[kTRUDGOccupM1 + relId[0] - 1]->Fill(relId[1] - 0.5, relId[2] - 0.5); + triggerDGTiles.push_back(relId[0] + (relId[1] << 3) + (relId[2] << 10)); + } + } + } + + // ILOG(Info, Support) << " triggerSTTiles=" <> 3) & 0x7F) - ((aST >> 3) & 0x7F); + int dz = ((bDG >> 10) & 0x7F) - ((aST >> 10) & 0x7F); + // ILOG(Info, Support) << " dx=" <>3)&0x7F)<< ","<< ((aST>>10)&0x7F) <<"]" << ENDM; + mHist2D[kTRUSTMatchM1 + (aST & 0x7) - 1]->Fill(((aST >> 3) & 0x7F) - 0.5, ((aST >> 10) & 0x7F) - 0.5); + matched = true; + break; + } + } + } + if (!matched) { + // ILOG(Info, Support) << " Not matched [" <<(aST&0x7) << ","<<((aST>>3)&0x7F)<< ","<< ((aST>>10)&0x7F) <<"]" << ENDM; + mHist2D[kTRUSTFakeM1 + (aST & 0x7) - 1]->Fill(((aST >> 3) & 0x7F) - 0.5, ((aST >> 10) & 0x7F) - 0.5); + // ILOG(Info, Support) << "Filled" << ENDM; + } + } + // ILOG(Info, Support) << "Filled done" << ENDM; + // now vise versa + for (int bDG : triggerDGTiles) { + bool matched = false; + for (int aST : triggerSTTiles) { + if ((aST & 0x7) == (bDG & 0x7)) { // same module + int dx = ((bDG >> 3) & 0x7F) - ((aST >> 3) & 0x7F); + int dz = ((bDG >> 10) & 0x7F) - ((aST >> 10) & 0x7F); + if (dx >= 0 && dx <= 2 && dz >= 0 && dz <= 2) { + matched = true; + break; + } + } + } + if (!matched) { + // ILOG(Info, Support) << "Dig Not matched [" <<(bDG&0x7)<< ","<<((bDG>>3)&0x7F)<< ","<< ((bDG>>10)&0x7F) <<"]" << ENDM; + mHist2D[kTRUDGFakeM1 + (bDG & 0x7) - 1]->Fill(((bDG >> 3) & 0x7F) - 0.5, ((bDG >> 10) & 0x7F) - 0.5); + } + } + // ILOG(Info, Support) << "Filled2 done" << ENDM; + } +} +void RawQcTask::FillPhysicsHistograms(const gsl::span& cells, const gsl::span& cellsTR) +{ + for (const auto& tr : cellsTR) { + int firstCellInEvent = tr.getFirstEntry(); + int lastCellInEvent = firstCellInEvent + tr.getNumberOfObjects(); + for (int i = firstCellInEvent; i < lastCellInEvent; i++) { + const o2::phos::Cell c = cells[i]; + if (c.getTRU()) { + continue; + } + // short cell, float amplitude, float time, int label + short address = c.getAbsId(); + float e = c.getEnergy(); + if (e > kOcccupancyTh) { + // Converts the absolute numbering into the following array + // relid[0] = PHOS Module number 1,...4:module + // relid[1] = Row number inside a PHOS module (Phi coordinate) + // relid[2] = Column number inside a PHOS module (Z coordinate) + char relid[3]; + o2::phos::Geometry::absToRelNumbering(address, relid); + short mod = relid[0] - 1; + int ibin = 0; + float emean = e; + if (c.getHighGain()) { + ibin = mHist2D[kHGoccupM1 + mod]->Fill(relid[1] - 0.5, relid[2] - 0.5); + float n = mHist2D[kHGoccupM1 + mod]->GetBinContent(ibin) - 1; + if (n > 0) { + emean = (e + mHist2DMean[kCellEM1 + mod]->GetBinContent(ibin) * n) / (n + 1); + } + mHist2DMean[kCellEM1 + mod]->SetBinContent(ibin, emean); + mHist1D[kCellHGSpM1 + mod]->Fill(e); + mHist2D[kTimeEM1 + mod]->Fill(e, c.getTime()); + } else { + mHist2D[kLGoccupM1 + mod]->Fill(relid[1] - 0.5, relid[2] - 0.5); + mHist1D[kCellLGSpM1 + mod]->Fill(e); + } + } + } + } +} + +void RawQcTask::FillPedestalHistograms(const gsl::span& cells, const gsl::span& cellsTR) +{ + if (mFinalized) { + for (Int_t mod = 0; mod < 4; mod++) { + mHist2DMean[kHGmeanM1 + mod]->Multiply(mHist2D[kHGoccupM1 + mod]); + mHist2DMean[kHGrmsM1 + mod]->Multiply(mHist2D[kHGoccupM1 + mod]); + mHist2DMean[kLGmeanM1 + mod]->Multiply(mHist2D[kLGoccupM1 + mod]); + mHist2DMean[kLGrmsM1 + mod]->Multiply(mHist2D[kLGoccupM1 + mod]); + } + mFinalized = false; + } + + for (const auto& tr : cellsTR) { + int firstCellInEvent = tr.getFirstEntry(); + int lastCellInEvent = firstCellInEvent + tr.getNumberOfObjects(); + for (int i = firstCellInEvent; i < lastCellInEvent; i++) { + const o2::phos::Cell c = cells[i]; + short address = c.getAbsId(); + char relid[3]; + o2::phos::Geometry::absToRelNumbering(address, relid); + short mod = relid[0] - 1; + if (c.getHighGain()) { + mHist2DMean[kHGmeanM1 + mod]->Fill(relid[1] - 0.5, relid[2] - 0.5, c.getEnergy()); + mHist2DMean[kHGrmsM1 + mod]->Fill(relid[1] - 0.5, relid[2] - 0.5, 1.e+7 * c.getTime()); // to store in Cells format + mHist2D[kHGoccupM1 + mod]->Fill(relid[1] - 0.5, relid[2] - 0.5); + } else { + mHist2DMean[kLGmeanM1 + mod]->Fill(relid[1] - 0.5, relid[2] - 0.5, c.getEnergy()); + mHist2DMean[kLGrmsM1 + mod]->Fill(relid[1] - 0.5, relid[2] - 0.5, 1.e+7 * c.getTime()); + mHist2D[kLGoccupM1 + mod]->Fill(relid[1] - 0.5, relid[2] - 0.5); + } + } + } +} + +void RawQcTask::CreatePedestalHistograms() +{ + // Prepare historams for pedestal run QA + + for (Int_t mod = 0; mod < 4; mod++) { + if (!mHist2DMean[kHGmeanM1 + mod]) { + mHist2DMean[kHGmeanM1 + mod] = new TH2FMean(Form("PedHGmean%d", mod + 1), Form("Pedestal mean High Gain, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2DMean[kHGmeanM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2DMean[kHGmeanM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2DMean[kHGmeanM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2DMean[kHGmeanM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2DMean[kHGmeanM1 + mod]->SetStats(0); + mHist2DMean[kHGmeanM1 + mod]->SetMinimum(0); + mHist2DMean[kHGmeanM1 + mod]->SetMaximum(100); + getObjectsManager()->startPublishing(mHist2DMean[kHGmeanM1 + mod]); + } else { + mHist2DMean[kHGmeanM1 + mod]->Reset(); + } + if (!mHist2DMean[kHGrmsM1 + mod]) { + mHist2DMean[kHGrmsM1 + mod] = new TH2FMean(Form("PedHGrms%d", mod + 1), Form("Pedestal RMS High Gain, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2DMean[kHGrmsM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2DMean[kHGrmsM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2DMean[kHGrmsM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2DMean[kHGrmsM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2DMean[kHGrmsM1 + mod]->SetStats(0); + mHist2DMean[kHGrmsM1 + mod]->SetMinimum(0); + mHist2DMean[kHGrmsM1 + mod]->SetMaximum(2.); + getObjectsManager()->startPublishing(mHist2DMean[kHGrmsM1 + mod]); + } else { + mHist2DMean[kHGrmsM1 + mod]->Reset(); + } + if (!mHist2D[kHGoccupM1 + mod]) { + mHist2D[kHGoccupM1 + mod] = new TH2F(Form("HGOccupancyM%d", mod + 1), Form("High Gain occupancy, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kHGoccupM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kHGoccupM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kHGoccupM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kHGoccupM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kHGoccupM1 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[kHGoccupM1 + mod]); + } else { + mHist2D[kHGoccupM1 + mod]->Reset(); + } + if (!mHist2DMean[kLGmeanM1 + mod]) { + mHist2DMean[kLGmeanM1 + mod] = new TH2FMean(Form("PedLGmean%d", mod + 1), Form("Pedestal mean Low Gain, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2DMean[kLGmeanM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2DMean[kLGmeanM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2DMean[kLGmeanM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2DMean[kLGmeanM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2DMean[kLGmeanM1 + mod]->SetStats(0); + mHist2DMean[kLGmeanM1 + mod]->SetMinimum(0); + mHist2DMean[kLGmeanM1 + mod]->SetMaximum(100); + getObjectsManager()->startPublishing(mHist2DMean[kLGmeanM1 + mod]); + } else { + mHist2DMean[kLGmeanM1 + mod]->Reset(); + } + if (!mHist2DMean[kLGrmsM1 + mod]) { + mHist2DMean[kLGrmsM1 + mod] = new TH2FMean(Form("PedLGrms%d", mod + 1), Form("Pedestal RMS Low Gain, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2DMean[kLGrmsM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2DMean[kLGrmsM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2DMean[kLGrmsM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2DMean[kLGrmsM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2DMean[kLGrmsM1 + mod]->SetStats(0); + mHist2DMean[kLGrmsM1 + mod]->SetMinimum(0); + mHist2DMean[kLGrmsM1 + mod]->SetMaximum(2.); + getObjectsManager()->startPublishing(mHist2DMean[kLGrmsM1 + mod]); + } else { + mHist2DMean[kLGrmsM1 + mod]->Reset(); + } + if (!mHist2D[kLGoccupM1 + mod]) { + mHist2D[kLGoccupM1 + mod] = new TH2F(Form("LGOccupancyM%d", mod + 1), Form("Low Gain occupancy, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kLGoccupM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kLGoccupM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kLGoccupM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kLGoccupM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kLGoccupM1 + mod]->SetStats(0); + getObjectsManager()->startPublishing(mHist2D[kLGoccupM1 + mod]); + } else { + mHist2D[kLGoccupM1 + mod]->Reset(); + } + if (!mHist1D[kHGmeanSummaryM1 + mod]) { + mHist1D[kHGmeanSummaryM1 + mod] = new TH1F(Form("PedHGMeanSum%d", mod + 1), Form("Pedestal HG mean summary, mod %d", mod + 1), 100, 0., 100.); + mHist1D[kHGmeanSummaryM1 + mod]->GetXaxis()->SetTitle("ADC channels"); + // mHist1D[kHGmeanSummaryM1 + mod]->SetStats(0); + mHist1D[kHGmeanSummaryM1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist1D[kHGmeanSummaryM1 + mod]); + } else { + mHist1D[kHGmeanSummaryM1 + mod]->Reset(); + } + if (!mHist1D[kHGrmsSummaryM1 + mod]) { + mHist1D[kHGrmsSummaryM1 + mod] = new TH1F(Form("PedHGRMSSum%d", mod + 1), Form("Pedestal HG RMS summary, mod %d", mod + 1), 100, 0., 10.); + mHist1D[kHGrmsSummaryM1 + mod]->GetXaxis()->SetTitle("ADC channels"); + // mHist1D[kHGrmsSummaryM1 + mod]->SetStats(0); + mHist1D[kHGrmsSummaryM1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist1D[kHGrmsSummaryM1 + mod]); + } else { + mHist1D[kHGrmsSummaryM1 + mod]->Reset(); + } + if (!mHist1D[kLGmeanSummaryM1 + mod]) { + mHist1D[kLGmeanSummaryM1 + mod] = new TH1F(Form("PedLGMeanSum%d", mod + 1), Form("Pedestal LG mean summary, mod %d", mod + 1), 100, 0., 100.); + mHist1D[kLGmeanSummaryM1 + mod]->GetXaxis()->SetTitle("ADC channels"); + // mHist1D[kLGmeanSummaryM1 + mod]->SetStats(0); + mHist1D[kLGmeanSummaryM1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist1D[kLGmeanSummaryM1 + mod]); + } else { + mHist1D[kLGmeanSummaryM1 + mod]->Reset(); + } + if (!mHist1D[kLGrmsSummaryM1 + mod]) { + mHist1D[kLGrmsSummaryM1 + mod] = new TH1F(Form("PedLGRMSSum%d", mod + 1), Form("Pedestal LG RMS summary, mod %d", mod + 1), 100, 0., 10.); + mHist1D[kLGrmsSummaryM1 + mod]->GetXaxis()->SetTitle("ADC channels"); + // mHist1D[kLGrmsSummaryM1 + mod]->SetStats(0); + mHist1D[kLGrmsSummaryM1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist1D[kLGrmsSummaryM1 + mod]); + } else { + mHist1D[kLGrmsSummaryM1 + mod]->Reset(); + } + } +} +void RawQcTask::CreatePhysicsHistograms() +{ + // Prepare historams for pedestal run QA + + for (Int_t mod = 0; mod < 4; mod++) { + if (!mHist2D[kHGoccupM1 + mod]) { + mHist2D[kHGoccupM1 + mod] = new TH2F(Form("CellHGOccupancyM%d", mod + 1), Form("Cell HG occupancy, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kHGoccupM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kHGoccupM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kHGoccupM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kHGoccupM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kHGoccupM1 + mod]->SetStats(0); + mHist2D[kHGoccupM1 + mod]->SetMinimum(0); + // mHist2D[kHGoccupM1+mod]->SetMaximum(100) ; + getObjectsManager()->startPublishing(mHist2D[kHGoccupM1 + mod]); + } else { + mHist2D[kHGoccupM1 + mod]->Reset(); + } + + if (!mHist2D[kLGoccupM1 + mod]) { + mHist2D[kLGoccupM1 + mod] = new TH2F(Form("CellLGOccupancyM%d", mod + 1), Form("Cell LG occupancy, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2D[kLGoccupM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kLGoccupM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kLGoccupM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kLGoccupM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kLGoccupM1 + mod]->SetStats(0); + mHist2D[kLGoccupM1 + mod]->SetMinimum(0); + // mHist2D[kLGoccupM1+mod]->SetMaximum(100) ; + getObjectsManager()->startPublishing(mHist2D[kLGoccupM1 + mod]); + } else { + mHist2D[kLGoccupM1 + mod]->Reset(); + } + + if (!mHist2DMean[kCellEM1 + mod]) { + mHist2DMean[kCellEM1 + mod] = new TH2FMean(Form("CellEmean%d", mod + 1), Form("Cell mean energy, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2DMean[kCellEM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2DMean[kCellEM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2DMean[kCellEM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2DMean[kCellEM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2DMean[kCellEM1 + mod]->SetStats(0); + mHist2DMean[kCellEM1 + mod]->SetMinimum(0); + // mHist2DMean[kCellEM1+mod]->SetMaximum(1.) ; + getObjectsManager()->startPublishing(mHist2DMean[kCellEM1 + mod]); + } else { + mHist2DMean[kCellEM1 + mod]->Reset(); + } + + if (!mHist2D[kTimeEM1 + mod]) { + mHist2D[kTimeEM1 + mod] = new TH2F(Form("TimevsE%d", mod + 1), Form("Cell time vs energy, mod %d", mod + 1), 50, 0., 1000., 50, -5.e-7, 5.e-7); + mHist2D[kTimeEM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kTimeEM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kTimeEM1 + mod]->GetXaxis()->SetTitle("Amp"); + mHist2D[kTimeEM1 + mod]->GetYaxis()->SetTitle("Time (ns)"); + mHist2D[kTimeEM1 + mod]->SetStats(0); + mHist2D[kTimeEM1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist2D[kTimeEM1 + mod]); + } else { + mHist2D[kTimeEM1 + mod]->Reset(); + } + + if (!mHist1D[kCellHGSpM1 + mod]) { + mHist1D[kCellHGSpM1 + mod] = new TH1F(Form("CellHGSpectrumM%d", mod + 1), Form("Cell HG spectrum in mod %d", mod + 1), 100, 0., 5000.); + mHist1D[kCellHGSpM1 + mod]->GetXaxis()->SetTitle("ADC channels"); + mHist1D[kCellHGSpM1 + mod]->SetStats(0); + mHist1D[kCellHGSpM1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist1D[kCellHGSpM1 + mod]); + } else { + mHist1D[kCellHGSpM1 + mod]->Reset(); + } + if (!mHist1D[kCellLGSpM1 + mod]) { + mHist1D[kCellLGSpM1 + mod] = new TH1F(Form("CellLGSpectrumM%d", mod + 1), Form("Cell LG spectrum in mod %d", mod + 1), 100, 0., 5000.); + mHist1D[kCellLGSpM1 + mod]->GetXaxis()->SetTitle("ADC channels"); + mHist1D[kCellLGSpM1 + mod]->SetStats(0); + mHist1D[kCellLGSpM1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist1D[kCellLGSpM1 + mod]); + } else { + mHist1D[kCellLGSpM1 + mod]->Reset(); + } + } +} +void RawQcTask::CreateLEDHistograms() +{ + // Occupancy+mean+spectra + for (Int_t mod = 0; mod < 4; mod++) { + if (!mHist2DMean[kLEDNpeaksM1 + mod]) { + mHist2DMean[kLEDNpeaksM1 + mod] = new TH2FMean(Form("NLedPeaksM%d", mod + 1), Form("Number of LED peaks, mod %d", mod + 1), 64, 0., 64., 56, 0., 56.); + mHist2DMean[kLEDNpeaksM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2DMean[kLEDNpeaksM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2DMean[kLEDNpeaksM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2DMean[kLEDNpeaksM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2DMean[kLEDNpeaksM1 + mod]->SetStats(0); + mHist2DMean[kLEDNpeaksM1 + mod]->SetMinimum(0); + getObjectsManager()->startPublishing(mHist2DMean[kLEDNpeaksM1 + mod]); + } else { + mHist2DMean[kLEDNpeaksM1 + mod]->Reset(); + } + } + // Prepare internal array of histos and final plot with number of peaks per channel + mSpSearcher = std::make_unique(20); + for (unsigned int absId = 1793; absId <= o2::phos::Mapping::NCHANNELS; absId++) { + mSpectra.emplace_back(Form("SpChannel%d", absId), "", 487, 50., 1024.); + } +} +void RawQcTask::CreateTRUHistograms() +{ + // Prepare historams for TRU QA + for (Int_t mod = 0; mod < 4; mod++) { + if (!mHist2D[kTRUSTOccupM1 + mod]) { + mHist2D[kTRUSTOccupM1 + mod] = new TH2F(Form("TRUSumTableOccupancyM%d", mod + 1), Form("TRU summary table occupancy, mod %d", mod + 1), 32, 0., 64., 28, 0., 56.); + mHist2D[kTRUSTOccupM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kTRUSTOccupM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kTRUSTOccupM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kTRUSTOccupM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kTRUSTOccupM1 + mod]->SetStats(0); + mHist2D[kTRUSTOccupM1 + mod]->SetMinimum(0); + // mHist2D[kTRUSTOccupM1+mod]->SetMaximum(100) ; + getObjectsManager()->startPublishing(mHist2D[kTRUSTOccupM1 + mod]); + } else { + mHist2D[kTRUSTOccupM1 + mod]->Reset(); + } + if (!mHist2D[kTRUDGOccupM1 + mod]) { + mHist2D[kTRUDGOccupM1 + mod] = new TH2F(Form("TRUDigOccupancyM%d", mod + 1), Form("TRU digits occupancy, mod %d", mod + 1), 32, 0., 64., 28, 0., 56.); + mHist2D[kTRUDGOccupM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kTRUDGOccupM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kTRUDGOccupM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kTRUDGOccupM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kTRUDGOccupM1 + mod]->SetStats(0); + mHist2D[kTRUDGOccupM1 + mod]->SetMinimum(0); + // mHist2D[kTRUDGOccupM1+mod]->SetMaximum(100) ; + getObjectsManager()->startPublishing(mHist2D[kTRUDGOccupM1 + mod]); + } else { + mHist2D[kTRUDGOccupM1 + mod]->Reset(); + } + if (!mHist2D[kTRUSTMatchM1 + mod]) { + mHist2D[kTRUSTMatchM1 + mod] = new TH2F(Form("TRUMatchedOccupancyM%d", mod + 1), Form("TRU ST+dig matched, mod %d", mod + 1), 32, 0., 64., 28, 0., 56.); + mHist2D[kTRUSTMatchM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kTRUSTMatchM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kTRUSTMatchM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kTRUSTMatchM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kTRUSTMatchM1 + mod]->SetStats(0); + mHist2D[kTRUSTMatchM1 + mod]->SetMinimum(0); + // mHist2D[kTRUSTMatchM1+mod]->SetMaximum(100) ; + getObjectsManager()->startPublishing(mHist2D[kTRUSTMatchM1 + mod]); + } else { + mHist2D[kTRUSTMatchM1 + mod]->Reset(); + } + if (!mHist2D[kTRUSTFakeM1 + mod]) { + mHist2D[kTRUSTFakeM1 + mod] = new TH2F(Form("TRUFakeSTOccupancyM%d", mod + 1), Form("TRU ST without digit, mod %d", mod + 1), 32, 0., 64., 28, 0., 56.); + mHist2D[kTRUSTFakeM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kTRUSTFakeM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kTRUSTFakeM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kTRUSTFakeM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kTRUSTFakeM1 + mod]->SetStats(0); + mHist2D[kTRUSTFakeM1 + mod]->SetMinimum(0); + // mHist2D[kTRUSTFakeM1+mod]->SetMaximum(100) ; + getObjectsManager()->startPublishing(mHist2D[kTRUSTFakeM1 + mod]); + } else { + mHist2D[kTRUSTFakeM1 + mod]->Reset(); + } + + if (!mHist2D[kTRUDGFakeM1 + mod]) { + mHist2D[kTRUDGFakeM1 + mod] = new TH2F(Form("TRUFakeDGOccupancyM%d", mod + 1), Form("TRU dig without ST, mod %d", mod + 1), 32, 0., 64., 28, 0., 56.); + mHist2D[kTRUDGFakeM1 + mod]->GetXaxis()->SetNdivisions(508, kFALSE); + mHist2D[kTRUDGFakeM1 + mod]->GetYaxis()->SetNdivisions(514, kFALSE); + mHist2D[kTRUDGFakeM1 + mod]->GetXaxis()->SetTitle("x, cells"); + mHist2D[kTRUDGFakeM1 + mod]->GetYaxis()->SetTitle("z, cells"); + mHist2D[kTRUDGFakeM1 + mod]->SetStats(0); + mHist2D[kTRUDGFakeM1 + mod]->SetMinimum(0); + // mHist2D[kTRUDGFakeM1+mod]->SetMaximum(100) ; + getObjectsManager()->startPublishing(mHist2D[kTRUDGFakeM1 + mod]); + } else { + mHist2D[kTRUDGFakeM1 + mod]->Reset(); + } + } +} + +} // namespace o2::quality_control_modules::phos diff --git a/Modules/PHOS/src/TH1Fraction.cxx b/Modules/PHOS/src/TH1Fraction.cxx new file mode 100644 index 0000000000..54767265ef --- /dev/null +++ b/Modules/PHOS/src/TH1Fraction.cxx @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1Fraction.cxx +/// \author Sergey Evdokimov +/// + +#include "PHOS/TH1Fraction.h" + +namespace o2::quality_control_modules::phos +{ +TH1Fraction::TH1Fraction(const char* name, const char* title, Int_t nbinsx, Double_t xlow, Double_t xup) : TH1D(name, title, nbinsx, xlow, xup) +{ + Bool_t bStatus = TH1::AddDirectoryStatus(); + TH1::AddDirectory(kFALSE); + mUnderlyingCounts = new TH1D(Form("%s_underlying", name), title, nbinsx, xlow, xup); + TH1::AddDirectory(bStatus); + + if (!mUnderlyingCounts) { + return; + } + mUnderlyingCounts->Sumw2(); +} + +TH1Fraction::TH1Fraction(TH1Fraction const& copymerge) : TH1D(copymerge.GetName(), copymerge.GetTitle(), + copymerge.GetXaxis()->GetNbins(), + copymerge.GetXaxis()->GetXmin(), + copymerge.GetXaxis()->GetXmax()), + o2::mergers::MergeInterface(), + mEventCounter(copymerge.getEventCounter()) +{ + Bool_t bStatus = TH1::AddDirectoryStatus(); + TH1::AddDirectory(kFALSE); + mUnderlyingCounts = (TH1D*)copymerge.getUnderlyingCounts()->Clone(); + TH1::AddDirectory(bStatus); + + if (!mUnderlyingCounts) { + return; + } + mUnderlyingCounts->Sumw2(); + update(); +} + +void TH1Fraction::update() +{ + if (mEventCounter) { + for (int i = 1; i <= GetXaxis()->GetNbins(); i++) { + SetBinContent(i, mUnderlyingCounts->GetBinContent(i) / mEventCounter); + SetBinError(i, mUnderlyingCounts->GetBinError(i) / mEventCounter); + } + } +} + +void TH1Fraction::merge(MergeInterface* const other) +{ + // special merge method to approximately combine two histograms + auto otherHisto = dynamic_cast(other); + if (otherHisto) { + if (mUnderlyingCounts->Add(otherHisto->getUnderlyingCounts())) { + mEventCounter += otherHisto->getEventCounter(); + } + } + update(); +} + +void TH1Fraction::Reset(Option_t* option) +{ + if (mUnderlyingCounts) { + mUnderlyingCounts->Reset(option); + } + mEventCounter = 0; + TH1D::Reset(option); +} + +} // namespace o2::quality_control_modules::phos diff --git a/Modules/PHOS/src/TH2FMean.cxx b/Modules/PHOS/src/TH2FMean.cxx new file mode 100644 index 0000000000..9520c9f3d6 --- /dev/null +++ b/Modules/PHOS/src/TH2FMean.cxx @@ -0,0 +1,37 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2FMean.cxx +/// \author Dmitri Peresunko +/// + +#include "PHOS/TH2FMean.h" + +namespace o2::quality_control_modules::phos +{ + +void TH2FMean::merge(MergeInterface* const other) +{ + // special merge method to approximately combine two histograms + auto otherHisto = dynamic_cast(other); + if (otherHisto) { + // Make approximate averaging with weights proportional to total number of entries + double sum = this->GetEntries() + otherHisto->GetEntries(); + if (sum > 0) { + double w1 = this->GetEntries() / sum; + double w2 = otherHisto->GetEntries() / sum; + this->Scale(w1); + this->Add(otherHisto, w2); + } + } +} +} // namespace o2::quality_control_modules::phos diff --git a/Modules/PHOS/src/TH2Fraction.cxx b/Modules/PHOS/src/TH2Fraction.cxx new file mode 100644 index 0000000000..a993dfbece --- /dev/null +++ b/Modules/PHOS/src/TH2Fraction.cxx @@ -0,0 +1,90 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2Fraction.cxx +/// \author Sergey Evdokimov +/// + +#include "PHOS/TH2Fraction.h" + +namespace o2::quality_control_modules::phos +{ +TH2Fraction::TH2Fraction(const char* name, const char* title, Int_t nbinsx, Double_t xlow, Double_t xup, + Int_t nbinsy, Double_t ylow, Double_t yup) : TH2D(name, title, nbinsx, xlow, xup, nbinsy, ylow, yup) +{ + Bool_t bStatus = TH2::AddDirectoryStatus(); + TH2::AddDirectory(kFALSE); + mUnderlyingCounts = new TH2D(Form("%s_underlying", name), title, nbinsx, xlow, xup, nbinsy, ylow, yup); + TH2::AddDirectory(bStatus); + + if (!mUnderlyingCounts) { + return; + } + mUnderlyingCounts->Sumw2(); +} + +TH2Fraction::TH2Fraction(TH2Fraction const& copymerge) : TH2D(copymerge.GetName(), copymerge.GetTitle(), + copymerge.GetXaxis()->GetNbins(), + copymerge.GetXaxis()->GetXmin(), + copymerge.GetXaxis()->GetXmax(), + copymerge.GetYaxis()->GetNbins(), + copymerge.GetYaxis()->GetXmin(), + copymerge.GetYaxis()->GetXmax()), + o2::mergers::MergeInterface(), + mEventCounter(copymerge.getEventCounter()) +{ + Bool_t bStatus = TH2::AddDirectoryStatus(); + TH2::AddDirectory(kFALSE); + mUnderlyingCounts = (TH2D*)copymerge.getUnderlyingCounts()->Clone(); + TH2::AddDirectory(bStatus); + + if (!mUnderlyingCounts) { + return; + } + mUnderlyingCounts->Sumw2(); + update(); +} + +void TH2Fraction::update() +{ + if (mEventCounter) { + for (int i = 1; i <= GetXaxis()->GetNbins(); i++) { + for (int j = 1; j <= GetYaxis()->GetNbins(); j++) { + SetBinContent(i, j, mUnderlyingCounts->GetBinContent(i, j) / mEventCounter); + SetBinError(i, j, mUnderlyingCounts->GetBinError(i, j) / mEventCounter); + } + } + } +} + +void TH2Fraction::merge(MergeInterface* const other) +{ + // special merge method to approximately combine two histograms + auto otherHisto = dynamic_cast(other); + if (otherHisto) { + if (mUnderlyingCounts->Add(otherHisto->getUnderlyingCounts())) { + mEventCounter += otherHisto->getEventCounter(); + } + } + update(); +} + +void TH2Fraction::Reset(Option_t* option) +{ + if (mUnderlyingCounts) { + mUnderlyingCounts->Reset(option); + } + mEventCounter = 0; + TH2D::Reset(option); +} + +} // namespace o2::quality_control_modules::phos diff --git a/Modules/PHOS/src/TH2SBitmask.cxx b/Modules/PHOS/src/TH2SBitmask.cxx new file mode 100644 index 0000000000..6b02b02db9 --- /dev/null +++ b/Modules/PHOS/src/TH2SBitmask.cxx @@ -0,0 +1,35 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2SBitmask.cxx +/// \author Dmitri Peresunko +/// + +#include "PHOS/TH2SBitmask.h" + +namespace o2::quality_control_modules::phos +{ +void TH2SBitmask::merge(MergeInterface* const other) +{ + // combine two histograms representing bitmasks + auto otherHisto = dynamic_cast(other); + if (otherHisto) { + for (unsigned int ix = 1; ix <= this->GetNbinsX(); ix++) { + for (unsigned int iz = 1; iz <= this->GetNbinsY(); iz++) { + int cont = this->GetBinContent(ix, iz); + cont |= int(otherHisto->GetBinContent(ix, iz)); + this->SetBinContent(ix, iz, cont); + } + } + } +} +} // namespace o2::quality_control_modules::phos diff --git a/Modules/PID/CMakeLists.txt b/Modules/PID/CMakeLists.txt new file mode 100644 index 0000000000..94ee035a85 --- /dev/null +++ b/Modules/PID/CMakeLists.txt @@ -0,0 +1,86 @@ +# ---- Library ---- + +add_library(O2QcPID) + +target_sources(O2QcPID + # Tasks + PRIVATE src/TaskFT0TOF.cxx + # PostProcessing + # Trending + # Trending config + # Utilities + ) + +target_include_directories( + O2QcPID + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + +target_link_libraries(O2QcPID PUBLIC O2QualityControl + O2::TOFBase + O2::DataFormatsTOF + O2::TOFCompression + O2::TOFReconstruction + O2::DataFormatsGlobalTracking + O2::DataFormatsFT0 + O2::FT0Base + O2::FITCalibration) + +install(TARGETS O2QcPID + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/PID + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- ROOT dictionary ---- + +add_root_dictionary(O2QcPID + # Tasks + HEADERS include/PID/TaskFT0TOF.h + # Checkers + # PostProcessing + # Trending + # Trending config + # Utilities + LINKDEF include/PID/LinkDef.h) + +# ---- Test(s) ---- + +#add_executable(testQcPID test/testPID.cxx) +#target_link_libraries(testQcPID PRIVATE O2QcPID Boost::unit_test_framework) +#add_test(NAME testQcPID COMMAND testQcPID) +#set_property(TARGET ${test_name} +# PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) +#set_tests_properties(testQcPID PROPERTIES TIMEOUT 20) + +# ---- Executables ---- + +#set(EXE_SRCS src/runPID.cxx) +# +#set(EXE_NAMES o2-qc-run-pid) +# +#list(LENGTH EXE_SRCS count) +#math(EXPR count "${count}-1") +#foreach(i RANGE ${count}) +# list(GET EXE_SRCS ${i} src) +# list(GET EXE_NAMES ${i} name) +# add_executable(${name} ${src}) +# target_link_libraries(${name} PRIVATE O2QualityControl CURL::libcurl) +#endforeach() + +# ---- Extra scripts ---- + +install(FILES pidft0tof.json + pidtof.json + DESTINATION etc) + +get_property(dirs + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + PROPERTY INCLUDE_DIRECTORIES) +foreach(dir ${dirs}) + message(STATUS "dir='${dir}'") +endforeach() diff --git a/Modules/PID/include/PID/LinkDef.h b/Modules/PID/include/PID/LinkDef.h new file mode 100644 index 0000000000..7dda0c79d8 --- /dev/null +++ b/Modules/PID/include/PID/LinkDef.h @@ -0,0 +1,12 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +// Tasks +#pragma link C++ class o2::quality_control_modules::pid::TaskFT0TOF + ; +// Checks +// PostProcessing +// Trending +// Utilities +#endif diff --git a/Modules/PID/include/PID/TaskFT0TOF.h b/Modules/PID/include/PID/TaskFT0TOF.h new file mode 100644 index 0000000000..6489ec867e --- /dev/null +++ b/Modules/PID/include/PID/TaskFT0TOF.h @@ -0,0 +1,201 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TaskFT0TOF.h +/// \author Francesca Ercolessi +/// \brief Header task to monitor TOF PID performance +/// \since 13/01/2022 +/// + +#ifndef QC_MODULE_PID_TASKFT0TOF_H +#define QC_MODULE_PID_TASKFT0TOF_H + +#include "QualityControl/TaskInterface.h" + +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include "ReconstructionDataFormats/MatchInfoTOFReco.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "DataFormatsTPC/TrackTPC.h" +#include "ReconstructionDataFormats/TrackTPCITS.h" +#include "DataFormatsTRD/TrackTRD.h" +#include "TOFBase/Geo.h" +#include "DataFormatsFT0/RecPoints.h" + +class TH1F; +class TH1D; +class TH1I; +class TH2F; +class TProfile; + +namespace o2::quality_control_modules::pid +{ + +using namespace o2::quality_control::core; +using GID = o2::dataformats::GlobalTrackID; +using trkType = o2::dataformats::MatchInfoTOFReco::TrackType; + +struct MyTrack { + o2::tpc::TrackTPC trk; + o2::dataformats::MatchInfoTOF match; + int source = -1; + static float t0maxp; + MyTrack(const o2::dataformats::MatchInfoTOF& m, const o2::tpc::TrackTPC& t, const int s) : match(m), trk(t), source(s) {} + MyTrack() {} + float tofSignal() const { return match.getSignal(); } + double tofSignalDouble() const { return match.getSignal(); } + float tofExpSignalPi() const { return match.getLTIntegralOut().getTOF(2); } + float tofExpSignalKa() const { return match.getLTIntegralOut().getTOF(3); } + float tofExpSignalPr() const { return match.getLTIntegralOut().getTOF(4); } + float tofExpSigmaPi() const { return sigmaexp[0]; } // FIX ME + float tofExpSigmaKa() const { return sigmaexp[1]; } // FIX ME + float tofExpSigmaPr() const { return sigmaexp[2]; } // FIX ME + void setSigmaPi(float val) { sigmaexp[0] = val; } + void setSigmaKa(float val) { sigmaexp[1] = val; } + void setSigmaPr(float val) { sigmaexp[2] = val; } + float getEta() const { return trk.getEta(); } + float getP() const { return p; } + float getPt() const { return pt; } + void setP(float val) { p = val; } + void setPt(float val) { pt = val; } + float getL() const + { + const o2::track::TrackLTIntegral& info = match.getLTIntegralOut(); + return info.getL(); + } + static float getT0MaxP() { return t0maxp; } + static void setT0MaxP(float pmax) { t0maxp = pmax; } + const o2::tpc::TrackTPC& getTrack() { return trk; } + float p = 0.; + float pt = 0.; + float sigmaexp[3] = { 200., 250., 300. }; +}; + +class TaskFT0TOF final : public TaskInterface +{ + public: + enum trackType : int8_t { TPC = 0, + ITSTPC, + ITSTPCTRD, + TPCTRD, + SIZE }; + + enum evTimeType : int8_t { TOF = 0, + FT0AC, + FT0A, + FT0C, + SIZEt0 }; + + /// \brief Constructor + TaskFT0TOF() = default; + /// Destructor + ~TaskFT0TOF() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + void processEvent(const std::vector& tracks, const std::vector& ft0Cand); + // track selection + bool selectTrack(o2::tpc::TrackTPC const& track); + void setMinPtCut(float v) { mMinPtCut = v; } + void setEtaCut(float v) { mEtaCut = v; } + void setMinNTPCClustersCut(float v) { mNTPCClustersCut = v; } + void setMinDCAtoBeamPipeCut(std::array v) + { + setMinDCAtoBeamPipeCut(v[0]); + setMinDCAtoBeamPipeYCut(v[1]); + } + void setMinDCAtoBeamPipeCut(float v) { mMinDCAtoBeamPipeCut = v; } + void setMinDCAtoBeamPipeYCut(float v) { mMinDCAtoBeamPipeCutY = v; } + + private: + std::shared_ptr mDataRequest; + o2::globaltracking::RecoContainer mRecoCont; + GID::mask_t mSrc = GID::getSourcesMask("ITS-TPC"); + GID::mask_t mAllowedSources = GID::getSourcesMask("TPC,TPC-TOF,ITS-TPC,ITS-TPC-TOF,TPC-TRD,TPC-TRD-TOF,ITS-TPC-TRD,ITS-TPC-TRD-TOF"); + // TPC-TOF + gsl::span mTPCTracks; + gsl::span mTPCTOFMatches; + // ITS-TPC-TOF + gsl::span mITSTPCTracks; + gsl::span mITSTPCTOFMatches; + // TPC-TRD-TOF + gsl::span mTPCTRDTracks; + gsl::span mTPCTRDTOFMatches; + // TPC-TRD-TOF + gsl::span mITSTPCTRDTracks; + gsl::span mITSTPCTRDTOFMatches; + // + std::vector mMyTracks; + + // for track selection + float mMinPtCut = 0.1f; + float mEtaCut = 0.8f; + int32_t mNTPCClustersCut = 40; + float mMinDCAtoBeamPipeCut = 100.f; + float mMinDCAtoBeamPipeCutY = 10.f; + float mBz = 0; ///< nominal Bz + int mTF = -1; // to count the number of processed TFs + const float cinv = 33.35641; + bool mUseFT0 = false; + float mEvTimeTracksMaxMomentum = 1.5; + // Bin limits + int mPtBins = 100; + float mPtBinsMin = 0.f; + float mPtBinsMax = 10.f; + + int mDeltaBins = 500; + float mDeltaBinsMin = -5000.f; + float mDeltaBinsMax = 5000.f; + + int mEvTimeBins = 200; + float mEvTimeBinsMin = -2000.f; + float mEvTimeBinsMax = 2000.f; + + TH1F* mHistDeltatPi[trackType::SIZE][evTimeType::SIZEt0] = {}; + TH1F* mHistDeltatKa[trackType::SIZE][evTimeType::SIZEt0] = {}; + TH1F* mHistDeltatPr[trackType::SIZE][evTimeType::SIZEt0] = {}; + TH2F* mHistDeltatPiPt[trackType::SIZE][evTimeType::SIZEt0] = {}; + TH2F* mHistDeltatKaPt[trackType::SIZE][evTimeType::SIZEt0] = {}; + TH2F* mHistDeltatPrPt[trackType::SIZE][evTimeType::SIZEt0] = {}; + TH1F* mHistMass[trackType::SIZE][evTimeType::SIZEt0] = {}; + TH2F* mHistBetavsP[trackType::SIZE][evTimeType::SIZEt0] = {}; + TH2F* mHistMassvsP[trackType::SIZE][evTimeType::SIZEt0] = {}; + TH2F* mHistDeltatPiEvTimeRes[trackType::SIZE] = {}; + TH2F* mHistDeltatPiEvTimeMult[trackType::SIZE] = {}; + TH2F* mHistEvTimeResEvTimeMult = 0x0; + TH1F* mHistEvTimeTOF = 0x0; + TH2F* mHistEvTimeTOFVsFT0AC = 0x0; + TH2F* mHistEvTimeTOFVsFT0A = 0x0; + TH2F* mHistEvTimeTOFVsFT0C = 0x0; + TH1F* mHistDeltaEvTimeTOFVsFT0AC = 0x0; + TH1F* mHistDeltaEvTimeTOFVsFT0A = 0x0; + TH1F* mHistDeltaEvTimeTOFVsFT0C = 0x0; + TH2F* mHistEvTimeTOFVsFT0ACSameBC = 0x0; + TH2F* mHistEvTimeTOFVsFT0ASameBC = 0x0; + TH2F* mHistEvTimeTOFVsFT0CSameBC = 0x0; + TH1F* mHistDeltaEvTimeTOFVsFT0ACSameBC = 0x0; + TH1F* mHistDeltaEvTimeTOFVsFT0ASameBC = 0x0; + TH1F* mHistDeltaEvTimeTOFVsFT0CSameBC = 0x0; + TH1D* mHistDeltaBCTOFFT0 = 0x0; + TH2F* mHistMismatchVsEta = 0x0; + TProfile* mProfLoverCvsEta = 0x0; +}; + +} // namespace o2::quality_control_modules::pid + +#endif // QC_MODULE_PID_TASKFT0TOF_H diff --git a/Modules/PID/pidft0tof.json b/Modules/PID/pidft0tof.json new file mode 100644 index 0000000000..aa7f8cb2c4 --- /dev/null +++ b/Modules/PID/pidft0tof.json @@ -0,0 +1,69 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "infologger": { + "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", + "": "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel": "21", + "": "Message at this level or above are discarded (default: 21 - Trace)" + } + }, + "tasks": { + "TaskFT0TOF": { + "active": "true", + "className": "o2::quality_control_modules::pid::TaskFT0TOF", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query_comment": "checking every matched track", + "query": "matchITSTPCTOF:TOF/MTC_ITSTPC/0;trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;tofcluster:TOF/CLUSTERS/0;trackTPC:TPC/TRACKS/0;trackTPCClRefs:TPC/CLUSREFS/0;recpoints:FT0/RECPOINTS/0" + }, + "taskParameters": { + "GID": "ITS-TPC,TPC,ITS-TPC-TOF", + "verbose": "false", + "minPtCut": "0.3f", + "etaCut": "0.8f", + "useFT0": "true", + "minNTPCClustersCut": "60", + "minDCACut": "100.f", + "minDCACutY": "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "Aligned", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "": "For debugging, path to the file where to save. If empty or missing it won't save." + } + } + }, + "dataSamplingPolicies": [] +} diff --git a/Modules/PID/pidtof.json b/Modules/PID/pidtof.json new file mode 100644 index 0000000000..cc1638674b --- /dev/null +++ b/Modules/PID/pidtof.json @@ -0,0 +1,84 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "infologger": { + "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", + "": "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel": "21", + "": "Message at this level or above are discarded (default: 21 - Trace)" + } + }, + "tasks": { + "TaskFT0TOF": { + "active": "true", + "className": "o2::quality_control_modules::pid::TaskFT0TOF", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query_comment": "checking every matched track", + "query": "matchITSTPCTOF:TOF/MTC_ITSTPC/0;matchTPCTOF:TOF/MTC_TPC/0;trackTPCTOF:TOF/TOFTRACKS_TPC/0;trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS/0;trackTPCClRefs:TPC/CLUSREFS/0;tofcluster:TOF/CLUSTERS/0" + }, + "taskParameters": { + "GID": "ITS-TPC,ITS-TPC-TOF,TPC,TPC-TOF", + "verbose": "false", + "minPtCut": "0.3f", + "etaCut": "0.8f", + "minNTPCClustersCut": "60", + "minDCACut": "100.f", + "minDCACutY": "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "Aligned", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location": "remote", + "saveObjectsToFile": "TOFPID.root", + "": "For debugging, path to the file where to save. If empty or missing it won't save." + } + }, + "checks": { + "QcCheck": { + "active": "false", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [{ + "type": "Task", + "name": "QcTask", + "MOs": ["example"] + }] + } + } + }, + "dataSamplingPolicies": [] +} diff --git a/Modules/PID/src/TaskFT0TOF.cxx b/Modules/PID/src/TaskFT0TOF.cxx new file mode 100644 index 0000000000..2811725ed3 --- /dev/null +++ b/Modules/PID/src/TaskFT0TOF.cxx @@ -0,0 +1,792 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TaskFT0TOF.cxx +/// \author Francesca Ercolessi +/// \brief Task to monitor TOF PID performance +/// \since 13/01/2022 +/// + +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "PID/TaskFT0TOF.h" +#include +#include +#include +#include "GlobalTrackingWorkflowHelpers/InputHelper.h" +#include "ReconstructionDataFormats/TrackTPCITS.h" +#include "DataFormatsTPC/TrackTPC.h" +#include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" +#include "ReconstructionDataFormats/TrackParametrization.h" +#include "DetectorsBase/Propagator.h" +#include "TOFBase/EventTimeMaker.h" +#include "GlobalTrackingWorkflow/TOFEventTimeChecker.h" +#include "DetectorsRaw/HBFUtils.h" + +// from TOF +#include "TOFBase/Geo.h" +#include "TOFBase/Utils.h" +#include "DataFormatsTOF/Cluster.h" +#include "TOFBase/EventTimeMaker.h" + +using GTrackID = o2::dataformats::GlobalTrackID; +using namespace o2::tof; + +namespace o2::quality_control_modules::pid +{ + +float MyTrack::t0maxp = 1.5; // default p threshold for tracks for event time computation + +bool MyFilter(const MyTrack& tr) +{ + return (tr.getP() < MyTrack::getT0MaxP()); +} + +TaskFT0TOF::~TaskFT0TOF() +{ + // delete + for (int i = 0; i < trackType::SIZE; ++i) { + for (int j = 0; j < evTimeType::SIZEt0; ++j) { + delete mHistDeltatPi[i][j]; + delete mHistDeltatKa[i][j]; + delete mHistDeltatPr[i][j]; + delete mHistDeltatPiPt[i][j]; + delete mHistDeltatKaPt[i][j]; + delete mHistDeltatPrPt[i][j]; + delete mHistMass[i][j]; + delete mHistMassvsP[i][j]; + delete mHistBetavsP[i][j]; + } + delete mHistDeltatPiEvTimeRes[i]; + delete mHistDeltatPiEvTimeMult[i]; + } + delete mHistEvTimeResEvTimeMult; + delete mHistEvTimeTOF; + delete mHistEvTimeTOFVsFT0AC; + delete mHistEvTimeTOFVsFT0A; + delete mHistEvTimeTOFVsFT0C; + delete mHistDeltaEvTimeTOFVsFT0AC; + delete mHistDeltaEvTimeTOFVsFT0A; + delete mHistDeltaEvTimeTOFVsFT0C; + delete mHistEvTimeTOFVsFT0ACSameBC; + delete mHistEvTimeTOFVsFT0ASameBC; + delete mHistEvTimeTOFVsFT0CSameBC; + delete mHistDeltaEvTimeTOFVsFT0ACSameBC; + delete mHistDeltaEvTimeTOFVsFT0ASameBC; + delete mHistDeltaEvTimeTOFVsFT0CSameBC; + delete mHistDeltaBCTOFFT0; + delete mHistMismatchVsEta; + delete mProfLoverCvsEta; +} + +void TaskFT0TOF::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Info, Support) << " Initializing... " << ENDM; + // track selection + if (auto param = mCustomParameters.find("minPtCut"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - minPtCut (for track selection): " << param->second << ENDM; + setMinPtCut(atof(param->second.c_str())); + } + if (auto param = mCustomParameters.find("etaCut"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - etaCut (for track selection): " << param->second << ENDM; + setEtaCut(atof(param->second.c_str())); + } + if (auto param = mCustomParameters.find("minNTPCClustersCut"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - minNTPCClustersCut (for track selection): " << param->second << ENDM; + setMinNTPCClustersCut(atoi(param->second.c_str())); + } + if (auto param = mCustomParameters.find("minDCACut"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - minDCACut (for track selection): " << param->second << ENDM; + setMinDCAtoBeamPipeCut(atof(param->second.c_str())); + } + if (auto param = mCustomParameters.find("minDCACutY"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - minDCACutY (for track selection): " << param->second << ENDM; + setMinDCAtoBeamPipeYCut(atof(param->second.c_str())); + } + if (auto param = mCustomParameters.find("useFT0"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - useFT0: " << param->second << ENDM; + if (param->second == "true" || param->second == "True" || param->second == "TRUE") { + mUseFT0 = true; + } + } + if (auto param = mCustomParameters.find("evTimeTracksMaxMomentum"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - EvTimeTracksMaxMomentum (for ev time computation): " << param->second << ENDM; + mEvTimeTracksMaxMomentum = atof(param->second.c_str()); + MyTrack::setT0MaxP(mEvTimeTracksMaxMomentum); + } + + // for track type selection + if (auto param = mCustomParameters.find("GID"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - GID (= sources by user): " << param->second << ENDM; + ILOG(Info, Devel) << "Allowed Sources = " << mAllowedSources << ENDM; + mSrc = mAllowedSources & GID::getSourcesMask(param->second); + ILOG(Info, Devel) << "Final requested sources = " << mSrc << ENDM; + } + + // Binning + if (auto param = mCustomParameters.find("PtBins"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - PtBins: " << param->second << ENDM; + mPtBins = atoi(param->second.c_str()); + } + if (auto param = mCustomParameters.find("PtBinsMin"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - PtBinsMin: " << param->second << ENDM; + mPtBinsMin = atof(param->second.c_str()); + } + if (auto param = mCustomParameters.find("PtBinsMax"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - PtBinsMax: " << param->second << ENDM; + mPtBinsMax = atof(param->second.c_str()); + } + + if (auto param = mCustomParameters.find("DeltaBins"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - DeltaBins: " << param->second << ENDM; + mDeltaBins = atoi(param->second.c_str()); + } + if (auto param = mCustomParameters.find("DeltaBinsMin"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - DeltaBinsMin: " << param->second << ENDM; + mDeltaBinsMin = atof(param->second.c_str()); + } + if (auto param = mCustomParameters.find("DeltaBinsMax"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - DeltaBinsMax: " << param->second << ENDM; + mDeltaBinsMax = atof(param->second.c_str()); + } + + if (auto param = mCustomParameters.find("EvTimeBins"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - EvTimeBins: " << param->second << ENDM; + mEvTimeBins = atoi(param->second.c_str()); + } + if (auto param = mCustomParameters.find("EvTimeBinsMin"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - EvTimeBinsMin: " << param->second << ENDM; + mEvTimeBinsMin = atof(param->second.c_str()); + } + if (auto param = mCustomParameters.find("EvTimeBinsMax"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - EvTimeBinsMax: " << param->second << ENDM; + mEvTimeBinsMax = atof(param->second.c_str()); + } + + // TPC tracks + if ((mSrc[GID::Source::TPCTOF] == 1 && mSrc[GID::Source::TPC] == 0) || (mSrc[GID::Source::TPCTOF] == 0 && mSrc[GID::Source::TPC] == 1)) { + ILOG(Fatal, Support) << "Check the requested sources: TPCTOF = " << mSrc[GID::Source::TPCTOF] << ", TPC = " << mSrc[GID::Source::TPC] << ENDM; + } + + // ITS-TPC tracks + if ((mSrc[GID::Source::ITSTPCTOF] == 1 && mSrc[GID::Source::ITSTPC] == 0) || (mSrc[GID::Source::ITSTPCTOF] == 0 && mSrc[GID::Source::ITSTPC] == 1)) { + ILOG(Fatal, Support) << "Check the requested sources: ITSTPCTOF = " << mSrc[GID::Source::ITSTPCTOF] << ", ITSTPC = " << mSrc[GID::Source::ITSTPC] << ENDM; + } + + // TPC-TRD tracks + if ((mSrc[GID::Source::TPCTRDTOF] == 1 && mSrc[GID::Source::TPCTRD] == 0) || (mSrc[GID::Source::TPCTRDTOF] == 0 && mSrc[GID::Source::TPCTRD] == 1)) { + ILOG(Fatal, Support) << "Check the requested sources: TPCTRDTOF = " << mSrc[GID::Source::TPCTRDTOF] << ", TPCTRD = " << mSrc[GID::Source::TPCTRD] << ENDM; + } + + // ITS-TPC-TRD tracks + if ((mSrc[GID::Source::ITSTPCTRDTOF] == 1 && mSrc[GID::Source::ITSTPCTRD] == 0) || (mSrc[GID::Source::ITSTPCTRDTOF] == 0 && mSrc[GID::Source::ITSTPCTRD] == 1)) { + ILOG(Fatal, Support) << "Check the requested sources: ITSTPCTRDTOF = " << mSrc[GID::Source::ITSTPCTRDTOF] << ", ITSTPCTRD = " << mSrc[GID::Source::ITSTPCTRD] << ENDM; + } + + // initialize histgrams + std::array title{ "TPC", "ITSTPC", "ITSTPCTRD", "TPCTRD" }; + std::array evtimetitle{ "TOF", "FT0AC", "FT0A", "FT0C" }; + for (int i = 0; i < trackType::SIZE; ++i) { + for (int j = 0; j < evTimeType::SIZEt0; ++j) { + mHistDeltatPi[i][j] = new TH1F(Form("DeltatPi_%s_t0%s", title[i].c_str(), evtimetitle[j].c_str()), Form("tracks: %s , evTime: %s;t_{TOF} - t_{exp}^{#pi} (ps)", title[i].c_str(), evtimetitle[j].c_str()), mDeltaBins, mDeltaBinsMin, mDeltaBinsMax); + mHistDeltatKa[i][j] = new TH1F(Form("DeltatKa_%s_t0%s", title[i].c_str(), evtimetitle[j].c_str()), Form("tracks: %s , evTime: %s;t_{TOF} - t_{exp}^{K} (ps)", title[i].c_str(), evtimetitle[j].c_str()), mDeltaBins, mDeltaBinsMin, mDeltaBinsMax); + mHistDeltatPr[i][j] = new TH1F(Form("DeltatPr_%s_t0%s", title[i].c_str(), evtimetitle[j].c_str()), Form("tracks: %s , evTime: %s;t_{TOF} - t_{exp}^{p} (ps)", title[i].c_str(), evtimetitle[j].c_str()), mDeltaBins, mDeltaBinsMin, mDeltaBinsMax); + mHistDeltatPiPt[i][j] = new TH2F(Form("DeltatPi_Pt_%s_t0%s", title[i].c_str(), evtimetitle[j].c_str()), Form("tracks: %s , evTime: %s;#it{p}_{T} (GeV/#it{c});t_{TOF} - t_{exp}^{#pi} (ps)", title[i].c_str(), evtimetitle[j].c_str()), mPtBins, mPtBinsMin, mPtBinsMax, mDeltaBins, mDeltaBinsMin, mDeltaBinsMax); + mHistDeltatKaPt[i][j] = new TH2F(Form("DeltatKa_Pt_%s_t0%s", title[i].c_str(), evtimetitle[j].c_str()), Form("tracks: %s , evTime: %s;#it{p}_{T} (GeV/#it{c});t_{TOF} - t_{exp}^{#pi} (ps)", title[i].c_str(), evtimetitle[j].c_str()), mPtBins, mPtBinsMin, mPtBinsMax, mDeltaBins, mDeltaBinsMin, mDeltaBinsMax); + mHistDeltatPrPt[i][j] = new TH2F(Form("DeltatPr_Pt_%s_t0%s", title[i].c_str(), evtimetitle[j].c_str()), Form("tracks: %s , evTime: %s;#it{p}_{T} (GeV/#it{c});t_{TOF} - t_{exp}^{#pi} (ps)", title[i].c_str(), evtimetitle[j].c_str()), mPtBins, mPtBinsMin, mPtBinsMax, mDeltaBins, mDeltaBinsMin, mDeltaBinsMax); + mHistMass[i][j] = new TH1F(Form("HadronMasses_%s_t0%s", title[i].c_str(), evtimetitle[j].c_str()), Form("tracks: %s , evTime: %s;M (GeV/#it{c}^{2})", title[i].c_str(), evtimetitle[j].c_str()), 1000, 0, 3.); + mHistMassvsP[i][j] = new TH2F(Form("HadronMassesvsP_%s_t0%s", title[i].c_str(), evtimetitle[j].c_str()), Form("tracks: %s , evTime: %s;#it{p} (GeV/#it{c});M (GeV/#it{c}^{2})", title[i].c_str(), evtimetitle[j].c_str()), 100, 0., 5, 500, 0, 3.); + mHistBetavsP[i][j] = new TH2F(Form("BetavsP_%s_t0%s", title[i].c_str(), evtimetitle[j].c_str()), Form("tracks: %s , evTime: %s;#it{p} (GeV/#it{c});TOF #beta", title[i].c_str(), evtimetitle[j].c_str()), 100, 0., 5, 500, 0., 1.5); + } + mHistDeltatPiEvTimeRes[i] = new TH2F(Form("DeltatPiEvtimeRes_%s", title[i].c_str()), Form("tracks %s, 1.5 < p < 1.6 GeV/#it{c};TOF event time resolution (ps);t_{TOF} - t_{exp}^{#pi} (ps)", title[i].c_str()), 100, 0., 200, mDeltaBins, mDeltaBinsMin, mDeltaBinsMax); + mHistDeltatPiEvTimeMult[i] = new TH2F(Form("DeltatPiEvTimeMult_%s", title[i].c_str()), Form("tracks %s, 1.5 < p < 1.6 GeV/#it{c};TOF multiplicity; t_{TOF} - t_{exp}^{#pi} (ps)", title[i].c_str()), 200, 0., 200, mDeltaBins, mDeltaBinsMin, mDeltaBinsMax); + } + mHistEvTimeResEvTimeMult = new TH2F("EvTimeResEvTimeMult", "1.5 < p < 1.6 GeV/#it{c};TOF multiplicity;TOF event time resolution (ps)", 100, 0., 100, 200, 0, 200); + mHistEvTimeTOF = new TH1F("EvTimeTOF", "t_{0}^{TOF};t_{0}^{TOF} (ps);Counts", 1000, -5000., +5000); + mHistEvTimeTOFVsFT0AC = new TH2F("EvTimeTOFVsFT0AC", "t_{0}^{FT0AC} vs t_{0}^{TOF} w.r.t. BC;t_{0}^{TOF} w.r.t. BC (ps);t_{0}^{FT0AC} w.r.t. BC (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax, mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistEvTimeTOFVsFT0A = new TH2F("EvTimeTOFVsFT0A", "t_{0}^{FT0A} vs t_{0}^{TOF} w.r.t. BC;t_{0}^{TOF} w.r.t. BC (ps);t_{0}^{FT0A} w.r.t. BC (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax, mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistEvTimeTOFVsFT0C = new TH2F("EvTimeTOFVsFT0C", "t_{0}^{FT0C} vs t_{0}^{TOF} w.r.t. BC;t_{0}^{TOF} w.r.t. BC (ps);t_{0}^{FT0C} w.r.t. BC (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax, mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistDeltaEvTimeTOFVsFT0AC = new TH1F("DeltaEvTimeTOFVsFT0AC", ";t_{0}^{TOF} - t_{0}^{FT0AC} (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistDeltaEvTimeTOFVsFT0A = new TH1F("DeltaEvTimeTOFVsFT0A", ";t_{0}^{TOF} - t_{0}^{FT0A} (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistDeltaEvTimeTOFVsFT0C = new TH1F("DeltaEvTimeTOFVsFT0C", ";t_{0}^{TOF} - t_{0}^{FT0C} (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistEvTimeTOFVsFT0ACSameBC = new TH2F("EvTimeTOFVsFT0ACSameBC", "t_{0}^{FT0AC} vs t_{0}^{TOF} w.r.t. BC;t_{0}^{TOF} w.r.t. BC (ps);t_{0}^{FT0AC} w.r.t. BC (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax, mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistEvTimeTOFVsFT0ASameBC = new TH2F("EvTimeTOFVsFT0ASameBC", "t_{0}^{FT0A} vs t_{0}^{TOF} w.r.t. BC;t_{0}^{TOF} w.r.t. BC (ps);t_{0}^{FT0A} w.r.t. BC (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax, mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistEvTimeTOFVsFT0CSameBC = new TH2F("EvTimeTOFVsFT0CSameBC", "t_{0}^{FT0C} vs t_{0}^{TOF} w.r.t. BC;t_{0}^{TOF} w.r.t. BC (ps);t_{0}^{FT0C} w.r.t. BC (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax, mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistDeltaEvTimeTOFVsFT0ACSameBC = new TH1F("DeltaEvTimeTOFVsFT0ACSameBC", ";t_{0}^{TOF} - t_{0}^{FT0AC} (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistDeltaEvTimeTOFVsFT0ASameBC = new TH1F("DeltaEvTimeTOFVsFT0ASameBC", ";t_{0}^{TOF} - t_{0}^{FT0A} (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistDeltaEvTimeTOFVsFT0CSameBC = new TH1F("DeltaEvTimeTOFVsFT0CSameBC", ";t_{0}^{TOF} - t_{0}^{FT0C} (ps)", mEvTimeBins, mEvTimeBinsMin, mEvTimeBinsMax); + mHistDeltaBCTOFFT0 = new TH1D("DeltaBCTOFFT0", "#Delta BC (TOF-FT0 evt time);#Delta BC", 16, -8, +8); + mHistMismatchVsEta = new TH2F("mHistMismatchVsEta", ";#eta;t_{TOF}-t_{0}^{FT0AC}-L_{ch}/c", 21, -1., +1., 6500, -30000, +100000); + mProfLoverCvsEta = new TProfile("LoverCvsEta", ";#eta;L_{ch}/c", 21, -1., +1.); + + // publish histgrams + getObjectsManager()->startPublishing(mHistEvTimeResEvTimeMult); + getObjectsManager()->startPublishing(mHistEvTimeTOF); + getObjectsManager()->startPublishing(mHistDeltaBCTOFFT0); + getObjectsManager()->startPublishing(mHistEvTimeTOFVsFT0AC); + getObjectsManager()->startPublishing(mHistEvTimeTOFVsFT0A); + getObjectsManager()->startPublishing(mHistEvTimeTOFVsFT0C); + getObjectsManager()->startPublishing(mHistDeltaEvTimeTOFVsFT0AC); + getObjectsManager()->startPublishing(mHistDeltaEvTimeTOFVsFT0A); + getObjectsManager()->startPublishing(mHistDeltaEvTimeTOFVsFT0C); + getObjectsManager()->startPublishing(mHistEvTimeTOFVsFT0ACSameBC); + getObjectsManager()->startPublishing(mHistEvTimeTOFVsFT0ASameBC); + getObjectsManager()->startPublishing(mHistEvTimeTOFVsFT0CSameBC); + getObjectsManager()->startPublishing(mHistDeltaEvTimeTOFVsFT0ACSameBC); + getObjectsManager()->startPublishing(mHistDeltaEvTimeTOFVsFT0ASameBC); + getObjectsManager()->startPublishing(mHistDeltaEvTimeTOFVsFT0CSameBC); + getObjectsManager()->startPublishing(mHistMismatchVsEta); + getObjectsManager()->startPublishing(mProfLoverCvsEta); + + // Use FT0? + int evTimeMax = 1; // if not use only TOF (evTimeType::TOF == 0) + if (mUseFT0) { + evTimeMax = evTimeType::SIZEt0; + } + + // Is track TPC-TOF? + if (mSrc[GID::Source::ITSTPCTOF] == 1) { + for (int j = 0; j < evTimeMax; ++j) { + getObjectsManager()->startPublishing(mHistDeltatPi[trackType::TPC][j]); + getObjectsManager()->startPublishing(mHistDeltatKa[trackType::TPC][j]); + getObjectsManager()->startPublishing(mHistDeltatPr[trackType::TPC][j]); + getObjectsManager()->startPublishing(mHistDeltatPiPt[trackType::TPC][j]); + getObjectsManager()->startPublishing(mHistDeltatKaPt[trackType::TPC][j]); + getObjectsManager()->startPublishing(mHistDeltatPrPt[trackType::TPC][j]); + getObjectsManager()->startPublishing(mHistMass[trackType::TPC][j]); + getObjectsManager()->startPublishing(mHistBetavsP[trackType::TPC][j]); + getObjectsManager()->startPublishing(mHistMassvsP[trackType::TPC][j]); + } + getObjectsManager()->startPublishing(mHistDeltatPiEvTimeRes[trackType::TPC]); + getObjectsManager()->startPublishing(mHistDeltatPiEvTimeMult[trackType::TPC]); + } + + // Is track TPC-TRD-TOF? + if (mSrc[GID::Source::TPCTRDTOF] == 1) { + for (int j = 0; j < evTimeMax; ++j) { + getObjectsManager()->startPublishing(mHistDeltatPi[trackType::TPCTRD][j]); + getObjectsManager()->startPublishing(mHistDeltatKa[trackType::TPCTRD][j]); + getObjectsManager()->startPublishing(mHistDeltatPr[trackType::TPCTRD][j]); + getObjectsManager()->startPublishing(mHistDeltatPiPt[trackType::TPCTRD][j]); + getObjectsManager()->startPublishing(mHistDeltatKaPt[trackType::TPCTRD][j]); + getObjectsManager()->startPublishing(mHistDeltatPrPt[trackType::TPCTRD][j]); + getObjectsManager()->startPublishing(mHistMass[trackType::TPCTRD][j]); + getObjectsManager()->startPublishing(mHistBetavsP[trackType::TPCTRD][j]); + getObjectsManager()->startPublishing(mHistMassvsP[trackType::TPCTRD][j]); + } + getObjectsManager()->startPublishing(mHistDeltatPiEvTimeRes[trackType::TPCTRD]); + getObjectsManager()->startPublishing(mHistDeltatPiEvTimeMult[trackType::TPCTRD]); + } + // Is track ITS-TPC-TOF + if (mSrc[GID::Source::ITSTPCTOF] == 1) { + for (int j = 0; j < evTimeMax; ++j) { + getObjectsManager()->startPublishing(mHistDeltatPi[trackType::ITSTPC][j]); + getObjectsManager()->startPublishing(mHistDeltatKa[trackType::ITSTPC][j]); + getObjectsManager()->startPublishing(mHistDeltatPr[trackType::ITSTPC][j]); + getObjectsManager()->startPublishing(mHistDeltatPiPt[trackType::ITSTPC][j]); + getObjectsManager()->startPublishing(mHistDeltatKaPt[trackType::ITSTPC][j]); + getObjectsManager()->startPublishing(mHistDeltatPrPt[trackType::ITSTPC][j]); + getObjectsManager()->startPublishing(mHistMass[trackType::ITSTPC][j]); + getObjectsManager()->startPublishing(mHistBetavsP[trackType::ITSTPC][j]); + getObjectsManager()->startPublishing(mHistMassvsP[trackType::ITSTPC][j]); + } + getObjectsManager()->startPublishing(mHistDeltatPiEvTimeRes[trackType::ITSTPC]); + getObjectsManager()->startPublishing(mHistDeltatPiEvTimeMult[trackType::ITSTPC]); + } + // Is track ITS-TPC-TRD-TOF + if (mSrc[GID::Source::ITSTPCTRDTOF] == 1) { + for (int j = 0; j < evTimeMax; ++j) { + getObjectsManager()->startPublishing(mHistDeltatPi[trackType::ITSTPCTRD][j]); + getObjectsManager()->startPublishing(mHistDeltatKa[trackType::ITSTPCTRD][j]); + getObjectsManager()->startPublishing(mHistDeltatPr[trackType::ITSTPCTRD][j]); + getObjectsManager()->startPublishing(mHistDeltatPiPt[trackType::ITSTPCTRD][j]); + getObjectsManager()->startPublishing(mHistDeltatKaPt[trackType::ITSTPCTRD][j]); + getObjectsManager()->startPublishing(mHistDeltatPrPt[trackType::ITSTPCTRD][j]); + getObjectsManager()->startPublishing(mHistMass[trackType::ITSTPCTRD][j]); + getObjectsManager()->startPublishing(mHistBetavsP[trackType::ITSTPCTRD][j]); + getObjectsManager()->startPublishing(mHistMassvsP[trackType::ITSTPCTRD][j]); + } + getObjectsManager()->startPublishing(mHistDeltatPiEvTimeRes[trackType::ITSTPCTRD]); + getObjectsManager()->startPublishing(mHistDeltatPiEvTimeMult[trackType::ITSTPCTRD]); + } + ILOG(Info, Support) << " Initialized!!!! " << ENDM; + + mDataRequest = std::make_shared(); + mDataRequest->requestTracks(mSrc, 0 /*mUseMC*/); +} + +void TaskFT0TOF::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + reset(); +} + +void TaskFT0TOF::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TaskFT0TOF::monitorData(o2::framework::ProcessingContext& ctx) +{ + ++mTF; + ILOG(Info, Support) << " Processing TF: " << mTF << ENDM; + + // Getting the B field + mBz = o2::base::Propagator::Instance()->getNominalBz(); + + mRecoCont.collectData(ctx, *mDataRequest.get()); + + mMyTracks.clear(); + + // Get FT0 RecPointss + std::vector ft0Sorted; + if (mUseFT0) { + auto& obj = ctx.inputs().get>("recpoints"); + for (const auto& o : obj) { + // ILOG(Info, Support) << "cand -> " << o.mIntRecord.orbit <(); + mTPCTRDTOFMatches = mRecoCont.getTPCTRDTOFMatches(); + + // loop over TOF MatchInfo + for (const auto& matchTOF : mTPCTRDTOFMatches) { + + GTrackID gTrackId = matchTOF.getTrackRef(); + const auto& trk = mTPCTRDTracks[gTrackId.getIndex()]; + const auto& trkTPC = mTPCTracks[trk.getRefGlobalTrackId()]; + + if (!selectTrack(trkTPC)) { // Discard tracks according to configurable cuts + continue; + } + + mMyTracks.push_back(MyTrack(matchTOF, trkTPC, trackType::TPCTRD)); + auto& mytrk = mMyTracks[mMyTracks.size() - 1]; + mytrk.setP(trk.getP()); + mytrk.setPt(trk.getPt()); + } // END loop on TOF matches + } // END if track is TPC-TRD-TOF + + // ITS-TPC-TRD-TOF + if (mRecoCont.isTrackSourceLoaded(GID::ITSTPCTRDTOF)) { // this is enough to know that also ITSTPC was loades, see "initialize" + + mITSTPCTRDTracks = mRecoCont.getITSTPCTRDTracks(); + mITSTPCTRDTOFMatches = mRecoCont.getITSTPCTRDTOFMatches(); + + // loop over TOF MatchInfo + for (const auto& matchTOF : mITSTPCTRDTOFMatches) { + + GTrackID gTrackId = matchTOF.getTrackRef(); + const auto& trk = mITSTPCTRDTracks[gTrackId.getIndex()]; + const auto& trkITSTPC = mITSTPCTracks[trk.getRefGlobalTrackId()]; + const auto& trkTPC = mTPCTracks[trkITSTPC.getRefTPC()]; + + if (!selectTrack(trkTPC)) { // Discard tracks according to configurable cuts + continue; + } + + mMyTracks.push_back(MyTrack(matchTOF, trkTPC, trackType::ITSTPCTRD)); + auto& mytrk = mMyTracks[mMyTracks.size() - 1]; + mytrk.setP(trk.getP()); + mytrk.setPt(trk.getPt()); + } // END loop on TOF matches + } // END if track is ITS-TPC-TRD-TOF + + std::vector tracks; + + std::vector ft0Cand; + + // sorting matching in time + std::sort(mMyTracks.begin(), mMyTracks.end(), + [](MyTrack a, MyTrack b) { return a.tofSignalDouble() < b.tofSignalDouble(); }); + + std::sort(ft0Sorted.begin(), ft0Sorted.end(), + [](o2::ft0::RecPoints a, o2::ft0::RecPoints b) { return a.mIntRecord.bc2ns() < b.mIntRecord.bc2ns(); }); + + int ift0 = 0; + + uint32_t ft0firstOrbit = ctx.services().get().firstTForbit; + + // ILOG(Info, Support) << "ft0firstOrbit = " << ft0firstOrbit << " - BC = " << mRecoCont.startIR.bc < 100E3) { + i--; + break; + } + tracks.emplace_back(mMyTracks[i]); + ntrk++; + } + if (ntrk > 0) { // good candidate with time + + // select FT0 candidates within 8 BCs + if (mUseFT0) { + double firstTime = tracks[0].tofSignalDouble() - 8 * o2::tof::Geo::BC_TIME_INPS; + double lastTime = tracks[ntrk - 1].tofSignalDouble() + 8 * o2::tof::Geo::BC_TIME_INPS; + for (int j = ift0; j < ft0Sorted.size(); j++) { + const auto& obj = ft0Sorted[j]; + if (obj.mIntRecord.orbit < ft0firstOrbit) { + ILOG(Info, Support) << "FT0 orbit comes first the first sampled one -> " << obj.mIntRecord.orbit << " - " << ft0firstOrbit << ENDM; + continue; // skip FT0 objects from previous orbits + } + uint32_t orbit = obj.mIntRecord.orbit - ft0firstOrbit; + double BCtimeFT0 = ((orbit)*o2::constants::lhc::LHCMaxBunches + obj.mIntRecord.bc) * o2::tof::Geo::BC_TIME_INPS; + + // ILOG(Info, Support) << "orbit " << orbit << "-> time (ps) " << BCtimeFT0 < lastTime) { + break; + } + // ILOG(Info, Support) << " TOF first=" << firstTime << " < " << BCtimeFT0 << " < TOF last=" << lastTime << ENDM; + // ILOG(Info, Support) << " orbit=" << obj.mIntRecord.orbit << " " << obj.mIntRecord.orbit - ft0firstOrbit << " BC=" << obj.mIntRecord.bc << ENDM; + + std::array collTimes = { obj.getCollisionTime(0), obj.getCollisionTime(1), obj.getCollisionTime(2), obj.getCollisionTime(3) }; + + int pos = ft0Cand.size(); + ft0Cand.emplace_back(collTimes, 0, 0, o2::InteractionRecord{ obj.mIntRecord.bc, orbit }, obj.getTrigger()); + } + } + + processEvent(tracks, ft0Cand); + } + } // end loop on tracks + + const auto clusters = mRecoCont.getTOFClusters(); // get the TOF clusters + std::vector clsIndex; // use vector of clusters indices + clsIndex.resize(clusters.size()); + for (int i = 0; i < clsIndex.size(); i++) { + clsIndex[i] = i; + } + // sort clusters indices in time + std::sort(clsIndex.begin(), clsIndex.end(), [&clusters](int a, int b) { return clusters[a].getTime() < clusters[b].getTime(); }); + + int icls = 0; + static TLorentzVector v; + + for (auto& ft0 : ft0Sorted) { + if (!ft0.isValidTime(0)) { + continue; // skip invalid FT0AC times + } + if (ft0firstOrbit > ft0.mIntRecord.orbit) { + continue; // skip FT0 objects from previous orbits + } + if (abs(ft0.getCollisionTime(0)) > 1000) { + continue; // skip bad FT0 time (not from collisions) + } + + double ft0time = ((ft0.mIntRecord.orbit - ft0firstOrbit) * o2::constants::lhc::LHCMaxBunches + ft0.mIntRecord.bc) * o2::tof::Geo::BC_TIME_INPS + ft0.getCollisionTime(0); + double timemax = ft0time + 100E3; + double timemin = ft0time - 30E3; + + for (int j = icls; j < clusters.size(); j++) { + auto& obj = clusters[clsIndex[j]]; + + if (obj.getTime() < timemin) { + icls = j + 1; + continue; + } + if (obj.getTime() > timemax) { + break; + } + + int channel = obj.getMainContributingChannel(); // channel index + int det[5]; + o2::tof::Geo::getVolumeIndices(channel, det); // detector index + float pos[3]; + o2::tof::Geo::getPos(det, pos); // detector position + v.SetXYZT(pos[0], pos[1], pos[2], 0); + float LoverC = v.P() * 33.3564095; // L/c + float eta = v.Eta(); + + mHistMismatchVsEta->Fill(eta, obj.getTime() - ft0time - LoverC); + mProfLoverCvsEta->Fill(eta, LoverC); + } + } + + ILOG(Info, Support) << " Processed! " << ENDM; + return; +} + +void TaskFT0TOF::endOfCycle() +{ + + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void TaskFT0TOF::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TaskFT0TOF::reset() +{ + // clean all the monitor objects here + for (int i = 0; i < trackType::SIZE; ++i) { + for (int j = 0; j < evTimeType::SIZEt0; ++j) { + mHistDeltatPi[i][j]->Reset(); + mHistDeltatKa[i][j]->Reset(); + mHistDeltatPr[i][j]->Reset(); + mHistDeltatPiPt[i][j]->Reset(); + mHistDeltatKaPt[i][j]->Reset(); + mHistDeltatPrPt[i][j]->Reset(); + mHistMass[i][j]->Reset(); + mHistBetavsP[i][j]->Reset(); + mHistMassvsP[i][j]->Reset(); + } + mHistDeltatPiEvTimeRes[i]->Reset(); + mHistDeltatPiEvTimeMult[i]->Reset(); + } + mHistEvTimeResEvTimeMult->Reset(); + mHistEvTimeTOF->Reset(); + mHistEvTimeTOFVsFT0AC->Reset(); + mHistEvTimeTOFVsFT0A->Reset(); + mHistEvTimeTOFVsFT0C->Reset(); + mHistDeltaEvTimeTOFVsFT0AC->Reset(); + mHistDeltaEvTimeTOFVsFT0A->Reset(); + mHistDeltaEvTimeTOFVsFT0C->Reset(); + mHistEvTimeTOFVsFT0ACSameBC->Reset(); + mHistEvTimeTOFVsFT0ASameBC->Reset(); + mHistEvTimeTOFVsFT0CSameBC->Reset(); + mHistDeltaEvTimeTOFVsFT0ACSameBC->Reset(); + mHistDeltaEvTimeTOFVsFT0ASameBC->Reset(); + mHistDeltaEvTimeTOFVsFT0CSameBC->Reset(); +} + +void TaskFT0TOF::processEvent(const std::vector& tracks, const std::vector& ft0Cand) +{ + auto evtime = o2::tof::evTimeMaker, MyTrack, MyFilter>(tracks); + // if event time is 0 + if (evtime.mEventTimeMultiplicity <= 2) { + int nBC = (tracks[0].tofSignal()) * o2::tof::Geo::BC_TIME_INPS_INV; + evtime.mEventTime = nBC * o2::tof::Geo::BC_TIME_INPS; + evtime.mEventTimeError = 200; + } + bool isTOFst = evtime.mEventTimeError < 150; + int nBC = (evtime.mEventTime + 5000.) * o2::tof::Geo::BC_TIME_INPS_INV; // 5 ns offset to get the correct number of BC (int) + float mEvTime_BC = evtime.mEventTime - nBC * o2::tof::Geo::BC_TIME_INPS; // Event time in ps wrt BC + // + if (TMath::Abs(mEvTime_BC) > 800) { + mEvTime_BC = 0; + isTOFst = false; + evtime.mEventTime = nBC * o2::tof::Geo::BC_TIME_INPS; + evtime.mEventTimeMultiplicity = 0; + } + + float FT0evTimes[3] = {}; + for (auto& obj : ft0Cand) { // fill histo for FT0 + bool isAC = obj.isValidTime(0); + bool isA = obj.isValidTime(1); + bool isC = obj.isValidTime(2); + // t0 times w.r.t. BC + float times[4] = { mEvTime_BC, isAC ? obj.getCollisionTime(0) : 0.f, isC ? obj.getCollisionTime(1) : 0.f, isC ? obj.getCollisionTime(2) : 0.f }; // TOF, FT0-AC, FT0-A, FT0-C + if (isTOFst) { + // no condition for same BC + mHistEvTimeTOFVsFT0AC->Fill(times[0], times[1]); + mHistEvTimeTOFVsFT0A->Fill(times[0], times[2]); + mHistEvTimeTOFVsFT0C->Fill(times[0], times[3]); + mHistDeltaEvTimeTOFVsFT0AC->Fill(times[0] - times[1]); + mHistDeltaEvTimeTOFVsFT0A->Fill(times[0] - times[2]); + mHistDeltaEvTimeTOFVsFT0C->Fill(times[0] - times[3]); + + // if same BC + if (nBC % o2::constants::lhc::LHCMaxBunches == obj.mIntRecord.bc) { + // no need for condition on orbit we select FT0 candidates within 8 BCs + FT0evTimes[0] = obj.mIntRecord.bc2ns() * 1E3 + times[1]; + FT0evTimes[1] = obj.mIntRecord.bc2ns() * 1E3 + times[2]; + FT0evTimes[2] = obj.mIntRecord.bc2ns() * 1E3 + times[3]; + mHistEvTimeTOFVsFT0ACSameBC->Fill(times[0], times[1]); + mHistEvTimeTOFVsFT0ASameBC->Fill(times[0], times[2]); + mHistEvTimeTOFVsFT0CSameBC->Fill(times[0], times[3]); + mHistDeltaEvTimeTOFVsFT0ACSameBC->Fill(times[0] - times[1]); + mHistDeltaEvTimeTOFVsFT0ASameBC->Fill(times[0] - times[2]); + mHistDeltaEvTimeTOFVsFT0CSameBC->Fill(times[0] - times[3]); + } + + mHistDeltaBCTOFFT0->Fill(nBC % o2::constants::lhc::LHCMaxBunches - obj.mIntRecord.bc); + } + } + + int nt = 0; + for (auto& track : tracks) { + + float mEvTime = evtime.mEventTime; + float mEvTimeRes = evtime.mEventTimeError; + const auto mMultiplicity = evtime.mEventTimeMultiplicity; + + if (mMultiplicity > 2) { + evtime.removeBias(track, nt, mEvTime, mEvTimeRes); + } + + // Delta t Pion + float deltatpi[evTimeType::SIZEt0] = {}; + // Delta t Kaon + float deltatka[evTimeType::SIZEt0] = {}; + // Delta t Proton + float deltatpr[evTimeType::SIZEt0] = {}; + // Beta + float beta[evTimeType::SIZEt0] = {}; + // Mass + float mass[evTimeType::SIZEt0] = {}; + + deltatpi[0] = track.tofSignal() - mEvTime - track.tofExpSignalPi(); + deltatka[0] = track.tofSignal() - mEvTime - track.tofExpSignalKa(); + deltatpr[0] = track.tofSignal() - mEvTime - track.tofExpSignalPr(); + beta[0] = track.getL() / (track.tofSignal() - mEvTime) * cinv; + mass[0] = track.getP() / beta[0] * TMath::Sqrt(TMath::Abs(1 - beta[0] * beta[0])); + // Use FT0 times + if (mUseFT0) { + for (int j = 1; j < evTimeType::SIZEt0; j++) { + deltatpi[j] = track.tofSignal() - FT0evTimes[j - 1] - track.tofExpSignalPi(); + deltatka[j] = track.tofSignal() - FT0evTimes[j - 1] - track.tofExpSignalKa(); + deltatpr[j] = track.tofSignal() - FT0evTimes[j - 1] - track.tofExpSignalPr(); + beta[j] = track.getL() / (track.tofSignal() - FT0evTimes[j - 1]) * cinv; + mass[j] = track.getP() / beta[j] * TMath::Sqrt(TMath::Abs(1 - beta[j] * beta[j])); + } + } + + for (int i = 0; i < trackType::SIZE; ++i) { + if (track.source == i) { + for (int j = 0; j < evTimeType::SIZEt0; ++j) { + mHistDeltatPi[i][j]->Fill(deltatpi[j]); + mHistDeltatKa[i][j]->Fill(deltatka[j]); + mHistDeltatPr[i][j]->Fill(deltatpr[j]); + mHistDeltatPiPt[i][j]->Fill(track.getPt(), deltatpi[j]); + mHistDeltatKaPt[i][j]->Fill(track.getPt(), deltatka[j]); + mHistDeltatPrPt[i][j]->Fill(track.getPt(), deltatpr[j]); + mHistMass[i][j]->Fill(mass[j]); + mHistBetavsP[i][j]->Fill(track.getP(), beta[j]); + mHistMassvsP[i][j]->Fill(track.getP(), mass[j]); + } + if (track.getPt() > 1.5 && track.getPt() < 1.6) { + mHistDeltatPiEvTimeRes[i]->Fill(mEvTimeRes, deltatpi[0]); + if (mMultiplicity < 200) { + mHistDeltatPiEvTimeMult[i]->Fill(mMultiplicity, deltatpi[0]); + } else { + mHistDeltatPiEvTimeMult[i]->Fill(199, deltatpi[0]); + } + } + } + } + + mHistEvTimeTOF->Fill(mEvTime_BC); + if (track.getPt() > 1.5 && track.getPt() < 1.6) { + mHistEvTimeResEvTimeMult->Fill(mMultiplicity, mEvTimeRes); + } + } +} + +//__________________________________________________________ + +bool TaskFT0TOF::selectTrack(o2::tpc::TrackTPC const& track) +{ + if (track.getPt() < mMinPtCut) { + return false; + } + if (std::abs(track.getEta()) > mEtaCut) { + return false; + } + if (track.getNClusters() < mNTPCClustersCut) { + return false; + } + + math_utils::Point3D v{}; + std::array dca; + o2::track::TrackPar trTmp(track); + if (!trTmp.propagateParamToDCA(v, mBz, &dca, mMinDCAtoBeamPipeCut) || std::abs(dca[0]) > mMinDCAtoBeamPipeCutY) { + return false; + } + + return true; +} + +} // namespace o2::quality_control_modules::pid diff --git a/Modules/Skeleton/.CMakeListsEmpty.txt b/Modules/Skeleton/.CMakeListsEmpty.txt index 9fe56ffeae..073a7fd8f3 100644 --- a/Modules/Skeleton/.CMakeListsEmpty.txt +++ b/Modules/Skeleton/.CMakeListsEmpty.txt @@ -1,48 +1,45 @@ -set(MODULE_NAME "QcSkeleton") - -# ---- Files ---- - -set(SRCS -) - -set(HEADERS -) - # ---- Library ---- -add_library(${MODULE_NAME} SHARED ${SRCS} ${MODULE_NAME}Dict.cxx) +add_library(O2QcSkeleton) + +target_sources(O2QcSkeleton PRIVATE) target_include_directories( - ${MODULE_NAME} - PUBLIC $ $ - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src -) + O2QcSkeleton + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) -target_link_libraries(${MODULE_NAME} PUBLIC QualityControl) +target_link_libraries(O2QcSkeleton PUBLIC O2QualityControl) -install( - TARGETS ${MODULE_NAME} +install(TARGETS O2QcSkeleton LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -# ---- ROOT dictionary ---- +add_root_dictionary(O2QcSkeleton + HEADERS + LINKDEF include/Skeleton/LinkDef.h + BASENAME O2QcSkeleton) -generate_root_dict(MODULE_NAME ${MODULE_NAME} LINKDEF "include/Skeleton/LinkDef.h" DICT_CLASS "${MODULE_NAME}Dict") +# Install headers +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/QualityControl/Skeleton + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") -# ---- Tests ---- +# ---- Test(s) ---- -set( - TEST_SRCS -) +set(TEST_SRCS test/testQcSkeleton.cxx) foreach(test ${TEST_SRCS}) get_filename_component(test_name ${test} NAME) string(REGEX REPLACE ".cxx" "" test_name ${test_name}) add_executable(${test_name} ${test}) - target_link_libraries(${test_name} PRIVATE ${MODULE_NAME} Boost::unit_test_framework) + target_link_libraries(${test_name} + PRIVATE O2QcSkeleton Boost::unit_test_framework) add_test(NAME ${test_name} COMMAND ${test_name}) - set_tests_properties(${test_name} PROPERTIES TIMEOUT 60) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) endforeach() + diff --git a/Modules/Skeleton/CMakeLists.txt b/Modules/Skeleton/CMakeLists.txt index c3c7cbd7ff..cd5dd32324 100644 --- a/Modules/Skeleton/CMakeLists.txt +++ b/Modules/Skeleton/CMakeLists.txt @@ -1,41 +1,34 @@ -set(MODULE_NAME "QcSkeleton") - -# ---- Files ---- - -set( - SRCS - src/SkeletonTask.cxx - src/SkeletonCheck.cxx -) - -set( - HEADERS - include/Skeleton/SkeletonTask.h - include/Skeleton/SkeletonCheck.h -) - # ---- Library ---- -add_library(${MODULE_NAME} SHARED ${SRCS} ${MODULE_NAME}Dict.cxx) +add_library(O2QcSkeleton) + +target_sources(O2QcSkeleton PRIVATE src/SkeletonTask.cxx src/SkeletonCheck.cxx src/SkeletonAggregator.cxx src/SkeletonPostProcessing.cxx) target_include_directories( - ${MODULE_NAME} - PUBLIC $ $ - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src -) + O2QcSkeleton + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) -target_link_libraries(${MODULE_NAME} PUBLIC QualityControl) +target_link_libraries(O2QcSkeleton PUBLIC O2QualityControl) -install( - TARGETS ${MODULE_NAME} +install(TARGETS O2QcSkeleton LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +# Install headers +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/Skeleton + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") # ---- ROOT dictionary ---- -generate_root_dict(MODULE_NAME ${MODULE_NAME} LINKDEF "include/Skeleton/LinkDef.h" DICT_CLASS "${MODULE_NAME}Dict") +add_root_dictionary(O2QcSkeleton + HEADERS include/Skeleton/SkeletonTask.h + include/Skeleton/SkeletonCheck.h + include/Skeleton/SkeletonAggregator.h + include/Skeleton/SkeletonPostProcessing.h + LINKDEF "include/Skeleton/LinkDef.h") # ---- Tests ---- @@ -46,7 +39,10 @@ foreach(test ${TEST_SRCS}) string(REGEX REPLACE ".cxx" "" test_name ${test_name}) add_executable(${test_name} ${test}) - target_link_libraries(${test_name} PRIVATE ${MODULE_NAME} Boost::unit_test_framework) + target_link_libraries(${test_name} + PRIVATE O2QcSkeleton Boost::unit_test_framework) add_test(NAME ${test_name} COMMAND ${test_name}) - set_tests_properties(${test_name} PROPERTIES TIMEOUT 60) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) endforeach() diff --git a/Modules/Skeleton/include/Skeleton/LinkDef.h b/Modules/Skeleton/include/Skeleton/LinkDef.h index c090ef53fb..56abd1507b 100644 --- a/Modules/Skeleton/include/Skeleton/LinkDef.h +++ b/Modules/Skeleton/include/Skeleton/LinkDef.h @@ -5,4 +5,6 @@ #pragma link C++ class o2::quality_control_modules::skeleton::SkeletonTask + ; #pragma link C++ class o2::quality_control_modules::skeleton::SkeletonCheck + ; +#pragma link C++ class o2::quality_control_modules::skeleton::SkeletonPostProcessing + ; +#pragma link C++ class o2::quality_control_modules::skeleton::SkeletonAggregator + ; #endif diff --git a/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h b/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h new file mode 100644 index 0000000000..7575e55b73 --- /dev/null +++ b/Modules/Skeleton/include/Skeleton/SkeletonAggregator.h @@ -0,0 +1,43 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SkeletonAggregator.h +/// \author My Name +/// + +#ifndef QUALITYCONTROL_SKELETONAGGREGATOR_H +#define QUALITYCONTROL_SKELETONAGGREGATOR_H + +// ROOT +#include +// QC +#include "QualityControl/AggregatorInterface.h" +#include "QualityControl/QCInputs.h" + +namespace o2::quality_control_modules::skeleton +{ + +/// \brief Example QC quality aggregator +/// \author My Name +class SkeletonAggregator : public o2::quality_control::checker::AggregatorInterface +{ + public: + // Override interface + void configure() override; + std::map aggregate(const o2::quality_control::core::QCInputs& data) override; + + ClassDefOverride(SkeletonAggregator, 1); +}; + +} // namespace o2::quality_control_modules::skeleton + +#endif // QUALITYCONTROL_SKELETONAGGREGATOR_H diff --git a/Modules/Skeleton/include/Skeleton/SkeletonCheck.h b/Modules/Skeleton/include/Skeleton/SkeletonCheck.h index 643362c224..a15ba7713f 100644 --- a/Modules/Skeleton/include/Skeleton/SkeletonCheck.h +++ b/Modules/Skeleton/include/Skeleton/SkeletonCheck.h @@ -1,36 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file SkeletonCheck.h -/// \author Piotr Konopka +/// \author My Name /// #ifndef QC_MODULE_SKELETON_SKELETONCHECK_H #define QC_MODULE_SKELETON_SKELETONCHECK_H #include "QualityControl/CheckInterface.h" -#include "QualityControl/MonitorObject.h" -#include "QualityControl/Quality.h" namespace o2::quality_control_modules::skeleton { -/// \brief Check whether a plot is empty or not. -/// -/// \author Barthelemy von Haller +/// \brief Example QC Check +/// \author My Name class SkeletonCheck : public o2::quality_control::checker::CheckInterface { public: /// Default constructor - SkeletonCheck(); + SkeletonCheck() = default; /// Destructor - ~SkeletonCheck() override; + ~SkeletonCheck() override = default; // Override interface - void configure(std::string name) override; - Quality check(const MonitorObject* mo) override; - void beautify(MonitorObject* mo, Quality checkResult = Quality::Null) override; - std::string getAcceptedType() override; + void configure() override; + Quality check(const quality_control::core::QCInputs& data) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void reset() override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + private: + std::shared_ptr mActivity; - ClassDefOverride(SkeletonCheck, 1); + ClassDefOverride(SkeletonCheck, 3); }; } // namespace o2::quality_control_modules::skeleton diff --git a/Modules/Skeleton/include/Skeleton/SkeletonPostProcessing.h b/Modules/Skeleton/include/Skeleton/SkeletonPostProcessing.h new file mode 100644 index 0000000000..f269152711 --- /dev/null +++ b/Modules/Skeleton/include/Skeleton/SkeletonPostProcessing.h @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SkeletonPostProcessing.h +/// \author My Name +/// + +#ifndef QUALITYCONTROL_SKELETONPOSTPROCESSING_H +#define QUALITYCONTROL_SKELETONPOSTPROCESSING_H + +#include "QualityControl/PostProcessingInterface.h" +#include +#include + +namespace o2::quality_control_modules::skeleton +{ + +/// \brief Example Quality Control Postprocessing Task +/// \author My Name +class SkeletonPostProcessing final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + SkeletonPostProcessing() = default; + /// \brief Destructor + ~SkeletonPostProcessing() override; + + /// \brief Configuration a post-processing task. + /// Configuration of a post-processing task. The task may create variables that shall live throughout its lifetime. + /// \param config boost property with the full QC configuration file + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + std::unique_ptr mHistogramA = nullptr; // lives through the whole task lifetime + std::unique_ptr mHistogramB = nullptr; // lives until the end of each run + std::unique_ptr mHistogramC = nullptr; // lives for one-time publications +}; + +} // namespace o2::quality_control_modules::skeleton + +#endif //QUALITYCONTROL_SKELETONPOSTPROCESSING_H \ No newline at end of file diff --git a/Modules/Skeleton/include/Skeleton/SkeletonTask.h b/Modules/Skeleton/include/Skeleton/SkeletonTask.h index b11a375663..8d315d994c 100644 --- a/Modules/Skeleton/include/Skeleton/SkeletonTask.h +++ b/Modules/Skeleton/include/Skeleton/SkeletonTask.h @@ -1,13 +1,24 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file SkeletonTask.h -/// \author Barthelemy von Haller -/// \author Piotr Konopka +/// \author My Name /// #ifndef QC_MODULE_SKELETON_SKELETONTASK_H #define QC_MODULE_SKELETON_SKELETONTASK_H #include "QualityControl/TaskInterface.h" +#include class TH1F; @@ -16,29 +27,28 @@ using namespace o2::quality_control::core; namespace o2::quality_control_modules::skeleton { -/// \brief Example Quality Control DPL Task -/// It is final because there is no reason to derive from it. Just remove it if needed. -/// \author Barthelemy von Haller -/// \author Piotr Konopka -class SkeletonTask /*final*/ : public TaskInterface // todo add back the "final" when doxygen is fixed +/// \brief Example Quality Control Task +/// \author My Name +class SkeletonTask final : public TaskInterface { public: /// \brief Constructor - SkeletonTask(); + SkeletonTask() = default; /// Destructor ~SkeletonTask() override; // Definition of the methods for the template method pattern void initialize(o2::framework::InitContext& ctx) override; - void startOfActivity(Activity& activity) override; + void startOfActivity(const Activity& activity) override; void startOfCycle() override; void monitorData(o2::framework::ProcessingContext& ctx) override; void endOfCycle() override; - void endOfActivity(Activity& activity) override; + void endOfActivity(const Activity& activity) override; void reset() override; private: - TH1F* mHistogram; + std::shared_ptr mHistogramA = nullptr; + std::shared_ptr mHistogramB = nullptr; }; } // namespace o2::quality_control_modules::skeleton diff --git a/Modules/Skeleton/src/SkeletonAggregator.cxx b/Modules/Skeleton/src/SkeletonAggregator.cxx new file mode 100644 index 0000000000..396936eed0 --- /dev/null +++ b/Modules/Skeleton/src/SkeletonAggregator.cxx @@ -0,0 +1,62 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SkeletonCheck.cxx +/// \author My Name +/// + +#include "Skeleton/SkeletonAggregator.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/QCInputsAdapters.h" + +using namespace std; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::skeleton +{ + +void SkeletonAggregator::configure() +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + // This method is called whenever CustomParameters are set. + + // Example of retrieving a custom parameter + std::string parameter = mCustomParameters.atOrDefaultValue("myOwnKey", "fallback value"); +} + +std::map SkeletonAggregator::aggregate(const o2::quality_control::core::QCInputs& data) +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + std::map result; + + ILOG(Info, Devel) << "Entered SkeletonAggregator::aggregate" << ENDM; + ILOG(Info, Devel) << " received a data of size : " << data.size() << ENDM; + + // we return the worse quality of all the objects we receive + Quality current = Quality::Good; + for (const auto& qo : iterateQualityObjects(data)) { + ILOG(Info, Devel) << "Object: " << qo << ENDM; + if (qo.getQuality().isWorseThan(current)) { + current = qo.getQuality(); + } + } + + ILOG(Info, Devel) << " result: " << current << ENDM; + result["newQuality"] = current; + + // add one more + Quality plus = Quality::Medium; + result["another"] = plus; + + return result; +} +} // namespace o2::quality_control_modules::skeleton diff --git a/Modules/Skeleton/src/SkeletonCheck.cxx b/Modules/Skeleton/src/SkeletonCheck.cxx index 92d8c18aa3..cc49ec628a 100644 --- a/Modules/Skeleton/src/SkeletonCheck.cxx +++ b/Modules/Skeleton/src/SkeletonCheck.cxx @@ -1,65 +1,133 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file SkeletonCheck.cxx -/// \author Piotr Konopka +/// \author My Name /// #include "Skeleton/SkeletonCheck.h" - +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "Skeleton/SkeletonTask.h" +#include "QualityControl/QCInputs.h" +#include "QualityControl/QCInputsAdapters.h" // ROOT -#include #include -#include + +#include +#include using namespace std; +using namespace o2::quality_control; namespace o2::quality_control_modules::skeleton { -SkeletonCheck::SkeletonCheck() {} - -SkeletonCheck::~SkeletonCheck() {} +void SkeletonCheck::configure() +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + // This method is called whenever CustomParameters are set. -void SkeletonCheck::configure(std::string) {} + // Example of retrieving a custom parameter + std::string parameter = mCustomParameters.atOrDefaultValue("myOwnKey1", "default"); +} -Quality SkeletonCheck::check(const MonitorObject* mo) +Quality SkeletonCheck::check(const quality_control::core::QCInputs& data) { + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. Quality result = Quality::Null; - if (mo->getName() == "example") { - auto* h = dynamic_cast(mo->getObject()); + // You can get details about the activity via the object mActivity: + ILOG(Debug, Devel) << "Run " << mActivity->mId << ", type: " << mActivity->mType << ", beam: " << mActivity->mBeamType << ENDM; + // and you can get your custom parameters: + ILOG(Debug, Devel) << "custom param physics.pp.myOwnKey1 : " << mCustomParameters.atOrDefaultValue("myOwnKey1", "default_value", "physics", "pp") << ENDM; + + constexpr static auto name = "example"; + // get MonitorObject with a given name from generic data object and converts it into requested type (TH1 here) + const auto histOpt = getMonitorObject(data, name); + if (!histOpt.has_value()) { + ILOG(Warning, Support) << "Data object does not contain any MonitorObject with a name: " << name << ", or it couldn't be transformed into TH1" << ENDM; + return result; + } - result = Quality::Good; + // histOpt contains reference_wrapper, it can be accesed by .get() or by implicit type conversion operator like in this example + const TH1& histogram = histOpt.value(); + // unless we find issues, we assume the quality is good + result = Quality::Good; - for (int i = 0; i < h->GetNbinsX(); i++) { - if (i > 0 && i < 8 && h->GetBinContent(i) == 0) { - result = Quality::Bad; - break; - } else if ((i == 0 || i > 7) && h->GetBinContent(i) > 0) { - result = Quality::Medium; - } + // an example of a naive quality check: we want bins 1-7 to be non-empty and bins 0 and >7 to be empty. + for (int i = 0; i < histogram.GetNbinsX(); i++) { + if (i > 0 && i < 8 && histogram.GetBinContent(i) == 0) { + result = Quality::Bad; + // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. + result.addFlag(FlagTypeFactory::BadPID(), "It is bad because there is nothing in bin " + std::to_string(i)); + break; + } else if ((i == 0 || i > 7) && histogram.GetBinContent(i) > 0) { + result = Quality::Medium; + // optionally, we can add flags indicating the effect on data and a comment explaining why it was assigned. + result.addFlag(FlagTypeFactory::Unknown(), "It is medium because bin " + std::to_string(i) + " is not empty"); + result.addFlag(FlagTypeFactory::BadTracking(), "We can assign more than one Flag to a Quality"); } } + // optionally, we can associate some custom metadata to a Quality + result.addMetadata("mykey", "myvalue"); + return result; } -std::string SkeletonCheck::getAcceptedType() { return "TH1"; } - -void SkeletonCheck::beautify(MonitorObject* mo, Quality checkResult) +void SkeletonCheck::beautify(std::shared_ptr mo, Quality checkResult) { + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + + // This method lets you decorate the checked object according to the computed Quality if (mo->getName() == "example") { auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "Could not cast `example` to TH1*, skipping" << ENDM; + return; + } if (checkResult == Quality::Good) { h->SetFillColor(kGreen); } else if (checkResult == Quality::Bad) { - LOG(INFO) << "Quality::Bad, setting to red"; + ILOG(Debug, Devel) << "Quality::Bad, setting to red" << ENDM; h->SetFillColor(kRed); } else if (checkResult == Quality::Medium) { - LOG(INFO) << "Quality::medium, setting to orange"; + ILOG(Debug, Devel) << "Quality::medium, setting to orange" << ENDM; h->SetFillColor(kOrange); } h->SetLineColor(kBlack); } } +void SkeletonCheck::reset() +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "SkeletonCheck::reset" << ENDM; + // please reset the state of the check here to allow for reuse between consecutive runs. +} + +void SkeletonCheck::startOfActivity(const Activity& activity) +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "SkeletonCheck::start : " << activity.mId << ENDM; + mActivity = make_shared(activity); +} + +void SkeletonCheck::endOfActivity(const Activity& activity) +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "SkeletonCheck::end : " << activity.mId << ENDM; +} + } // namespace o2::quality_control_modules::skeleton diff --git a/Modules/Skeleton/src/SkeletonPostProcessing.cxx b/Modules/Skeleton/src/SkeletonPostProcessing.cxx new file mode 100644 index 0000000000..ab23f92f42 --- /dev/null +++ b/Modules/Skeleton/src/SkeletonPostProcessing.cxx @@ -0,0 +1,80 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingInterface.cxx +/// \author My Name +/// + +#include "Skeleton/SkeletonPostProcessing.h" +#include "QualityControl/QcInfoLogger.h" + +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::skeleton +{ + +SkeletonPostProcessing::~SkeletonPostProcessing() +{ +} + +void SkeletonPostProcessing::configure(const boost::property_tree::ptree& config) +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + + // A histogram created here can be reused throughout the task lifetime + mHistogramA = std::make_unique("exampleA", "exampleA", 20, 0, 30000); + // We request that the histogram A is published until the end of the task lifetime + getObjectsManager()->startPublishing(mHistogramA.get(), PublicationPolicy::Forever); +} + +void SkeletonPostProcessing::initialize(Trigger, framework::ServiceRegistryRef) +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + + // Resetting the histogram A, so it is empty at each new START + mHistogramA->Reset(); + + // We want the histogram B to be recreated at each START, so first we delete the existing one from the possible + // previous run, then create the new one. ROOT may crash if there are two objects with the same name at the same time. + mHistogramB.reset(); + mHistogramB = std::make_unique("exampleB", "exampleB", 20, 0, 30000); + // We request that the histogram B is published after each update(), up to and including finalize() + // The framework will stop publishing it after finalize() + getObjectsManager()->startPublishing(mHistogramB.get(), PublicationPolicy::ThroughStop); +} + +void SkeletonPostProcessing::update(Trigger t, framework::ServiceRegistryRef) +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + + ILOG(Info, Support) << "Trigger type is: " << t.triggerType << ", the timestamp is " << t.timestamp << ENDM; + + // Histogram C is recreated at each update and is expected to be published just once. We delete the previous + // histogram and create the new one, then we start publishing it. + mHistogramC.reset(); + mHistogramC = std::make_unique("exampleC", "exampleC", 20, 0, 30000); + getObjectsManager()->startPublishing(mHistogramC.get(), PublicationPolicy::Once); + + // We fill the histograms + mHistogramA->Fill(t.timestamp % 30000); + mHistogramB->Fill(t.timestamp % 30000); + mHistogramC->Fill(t.timestamp % 30000); +} + +void SkeletonPostProcessing::finalize(Trigger, framework::ServiceRegistryRef) +{ +} + +} // namespace o2::quality_control_modules::skeleton diff --git a/Modules/Skeleton/src/SkeletonTask.cxx b/Modules/Skeleton/src/SkeletonTask.cxx index 49aed524b4..3f2c507a1a 100644 --- a/Modules/Skeleton/src/SkeletonTask.cxx +++ b/Modules/Skeleton/src/SkeletonTask.cxx @@ -1,7 +1,17 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file SkeletonTask.cxx -/// \author Barthelemy von Haller -/// \author Piotr Konopka +/// \author My Name /// #include @@ -9,102 +19,136 @@ #include "QualityControl/QcInfoLogger.h" #include "Skeleton/SkeletonTask.h" +#include +#include namespace o2::quality_control_modules::skeleton { -SkeletonTask::SkeletonTask() : TaskInterface(), mHistogram(nullptr) { mHistogram = nullptr; } - -SkeletonTask::~SkeletonTask() { - if (mHistogram) { - delete mHistogram; - } +SkeletonTask::~SkeletonTask() +{ } -void SkeletonTask::initialize(o2::framework::InitContext& ctx) +void SkeletonTask::initialize(o2::framework::InitContext& /*ctx*/) { - QcInfoLogger::GetInstance() << "initialize SkeletonTask" << AliceO2::InfoLogger::InfoLogger::endm; - - mHistogram = new TH1F("example", "example", 20, 0, 30000); - getObjectsManager()->startPublishing(mHistogram); - getObjectsManager()->addCheck(mHistogram, "checkFromSkeleton", "o2::quality_control_modules::skeleton::SkeletonCheck", - "QcSkeleton"); + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + + // This is how logs are created. QcInfoLogger is used. In production, FairMQ logs will go to InfoLogger as well. + ILOG(Debug, Devel) << "initialize SkeletonTask" << ENDM; + ILOG(Debug, Support) << "A debug targeted for support" << ENDM; + ILOG(Info, Ops) << "An Info log targeted for operators" << ENDM; + + // This creates and registers a histogram for publication at the end of each cycle, until the end of the task lifetime + mHistogramA = std::make_unique("example", "example", 20, 0, 30000); + getObjectsManager()->startPublishing(mHistogramA.get(), PublicationPolicy::Forever); + + try { + getObjectsManager()->addMetadata(mHistogramA->GetName(), "custom", "34"); + } catch (...) { + // some methods can throw exceptions, it is indicated in their doxygen. + // In case it is recoverable, it is recommended to catch them and do something meaningful. + // Here we don't care that the metadata was not added and just log the event. + ILOG(Warning, Support) << "Metadata could not be added to " << mHistogramA->GetName() << ENDM; + } } -void SkeletonTask::startOfActivity(Activity& activity) +void SkeletonTask::startOfActivity(const Activity& activity) { - QcInfoLogger::GetInstance() << "startOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; - mHistogram->Reset(); + // THIS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + + // We clean any histograms that could have been filled in previous runs. + mHistogramA->Reset(); + + // This creates and registers a histogram for publication at the end of each cycle until and including the end of run. + // Typically you may create and register a histogram here if you require Activity information for binning or decoration. + // Since ROOT does not respond well to having two histograms with the same name in the memory, + // we invoke the reset() to first delete the object that could remained from a previous run. + mHistogramB.reset(); + mHistogramB = std::make_unique("example2", "example2", 256, 0, 255); + getObjectsManager()->startPublishing(mHistogramB.get(), PublicationPolicy::ThroughStop); + + // Example of retrieving a custom parameter based on the run type + beam type available in Activity + std::string parameter; + // first we try for the current activity. It returns an optional. + if (auto param = mCustomParameters.atOptional("myOwnKey", activity)) { + parameter = param.value(); // we got a value + } else { + // we did not get a value. We ask for the "default" runtype and beamtype and we specify a default return value. + parameter = mCustomParameters.atOrDefaultValue("myOwnKey", "some default"); + } } void SkeletonTask::startOfCycle() { - QcInfoLogger::GetInstance() << "startOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "startOfCycle" << ENDM; } void SkeletonTask::monitorData(o2::framework::ProcessingContext& ctx) { - // In this function you can access data inputs specified in the JSON config file, for example: - // { - // "binding": "random", - // "dataOrigin": "ITS", - // "dataDescription": "RAWDATA" - // } - - // Use Framework/DataRefUtils.h or Framework/InputRecord.h to access and unpack inputs (both are documented) - // One can find additional examples at: - // https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md#using-inputs---the-inputrecord-api - - // Some examples: - - // 1. In a loop - for (auto&& input : ctx.inputs()) { - // get message header - const auto* header = header::get(input.header); - // get payload of a specific input, which is a char array. -// const char* payload = input.payload; - - // for the sake of an example, let's fill the histogram with payload sizes - mHistogram->Fill(header->payloadSize); + // THIS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + + // In this function you can access data inputs specified in the JSON config file. + // Typically, you should have asked for data inputs as a direct data source or as a data sampling policy. + // In both cases you probably used a query such as: + // "query": "data:TST/RAWDATA/0" + // which follows the format :/[/>("channels"); + // auto digits = ctx.inputs().get>("digits"); + + // In our case we will access the header and the payload as a raw array of bytes + const auto* header = o2::framework::DataRefUtils::getHeader(randomData); + auto payloadSize = header->payloadSize; + + // as an example, let's fill the histogram A with payload sizes and histogram B with the value in the first byte + mHistogramA->Fill(payloadSize); + + if (payloadSize > 0) { + auto firstByte = static_cast(randomData.payload[0]); + mHistogramB->Fill(firstByte); } - // 2. Using get("") - - // get the payload of a specific input, which is a char array. "random" is the binding specified in the config file. -// auto payload = ctx.inputs().get("random").payload; - - // get payload of a specific input, which is a structure array: -// const auto* header = header::get(ctx.inputs().get("random").header); -// struct s {int a; double b;}; -// auto array = ctx.inputs().get("random"); -// for (int j = 0; j < header->payloadSize / sizeof(s); ++j) { -// int i = array.get()[j].a; -// } - - // get payload of a specific input, which is a root object -// auto h = ctx.inputs().get("histos"); -// Double_t stats[4]; -// h->GetStats(stats); -// auto s = ctx.inputs().get("string"); -// LOG(INFO) << "String is " << s->GetString().Data(); + // One can find some examples of using InputRecord at: + // https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md#using-inputs---the-inputrecord-api + // + // One can also iterate over all inputs in a loop: + for (const framework::DataRef& input : framework::InputRecordWalker(ctx.inputs())) { + // do something with input + } + // To know how to access CCDB objects, please refer to: + // https://github.com/AliceO2Group/QualityControl/blob/master/doc/Advanced.md#accessing-objects-in-ccdb + // for GRP objects in particular: + // https://github.com/AliceO2Group/QualityControl/blob/master/doc/Advanced.md#access-grp-objects-with-grp-geom-helper } void SkeletonTask::endOfCycle() { - QcInfoLogger::GetInstance() << "endOfCycle" << AliceO2::InfoLogger::InfoLogger::endm; + // THIS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "endOfCycle" << ENDM; } -void SkeletonTask::endOfActivity(Activity& activity) +void SkeletonTask::endOfActivity(const Activity& /*activity*/) { - QcInfoLogger::GetInstance() << "endOfActivity" << AliceO2::InfoLogger::InfoLogger::endm; + // THIS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "endOfActivity" << ENDM; } void SkeletonTask::reset() { - // clean all the monitor objects here + // THIS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. - QcInfoLogger::GetInstance() << "Resetting the histogram" << AliceO2::InfoLogger::InfoLogger::endm; - mHistogram->Reset(); + // Clean all the monitor objects here. + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + if (mHistogramA) { + mHistogramA->Reset(); + } + if (mHistogramB) { + mHistogramB->Reset(); + } } } // namespace o2::quality_control_modules::skeleton diff --git a/Modules/Skeleton/test/.testEmpty.cxx b/Modules/Skeleton/test/.testEmpty.cxx index 828c94dba4..d6b39f3234 100644 --- a/Modules/Skeleton/test/.testEmpty.cxx +++ b/Modules/Skeleton/test/.testEmpty.cxx @@ -1,6 +1,17 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file .testEmpty.cxx -/// \author +/// \author My Name /// #include "QualityControl/TaskFactory.h" diff --git a/Modules/Skeleton/test/testQcSkeleton.cxx b/Modules/Skeleton/test/testQcSkeleton.cxx index 011d1f8257..af04a503ed 100644 --- a/Modules/Skeleton/test/testQcSkeleton.cxx +++ b/Modules/Skeleton/test/testQcSkeleton.cxx @@ -1,13 +1,24 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + /// /// \file testQcSkeleton.cxx /// \author Barthelemy von Haller /// \author Piotr Konopka /// -#include "Framework/InitContext.h" -#include "QualityControl/TaskFactory.h" #include "Skeleton/SkeletonTask.h" -#include + +//#include "Framework/InitContext.h" +#include "QualityControl/TaskFactory.h" #define BOOST_TEST_MODULE Publisher test #define BOOST_TEST_MAIN @@ -15,8 +26,6 @@ #include -#include - using namespace std; namespace o2::quality_control_modules::skeleton @@ -25,8 +34,11 @@ namespace o2::quality_control_modules::skeleton BOOST_AUTO_TEST_CASE(instantiate_task) { SkeletonTask task; - TaskConfig config; - auto manager = make_shared(config); + TaskRunnerConfig config; + config.consulUrl = ""; + config.name = "qcSkeletonTest"; + config.detectorName = "TST"; + auto manager = make_shared(config.name, "SkeletonTask", config.detectorName, 0); task.setObjectsManager(manager); // o2::framework::InitContext ctx; // task.initialize(ctx); diff --git a/Modules/TOF/CMakeLists.txt b/Modules/TOF/CMakeLists.txt new file mode 100644 index 0000000000..3aa4cb4cff --- /dev/null +++ b/Modules/TOF/CMakeLists.txt @@ -0,0 +1,149 @@ +# ---- Library ---- + +add_library(O2QcTOF) + +target_sources(O2QcTOF + # Tasks + PRIVATE src/TaskDigits.cxx + src/TaskCosmics.cxx + src/TaskRaw.cxx + src/TOFMatchedTracks.cxx + # Checkers + src/CheckDiagnostics.cxx + src/CheckDRMDiagnostics.cxx + src/CheckCompressedData.cxx + src/CheckRawMultiplicity.cxx + src/CheckRawTime.cxx + src/CheckRawToT.cxx + src/CheckHitMap.cxx + src/CheckNoise.cxx + src/CheckSlotPartMask.cxx + src/CheckLostOrbits.cxx + # PostProcessing + src/PostProcessDiagnosticPerCrate.cxx + src/PostProcessHitMap.cxx + src/PostProcessingLostOrbits.cxx + src/PostProcessingLuminometer.cxx + # Trending + src/TrendingHits.cxx + src/TrendingRate.cxx + src/TrendingCalibLHCphase.cxx + src/TrendingCalibDiagnostics.cxx + # Trending config + src/TrendingConfigTOF.cxx + # Reductors + src/TH1ReductorTOF.cxx + # Utilities + ) + +target_include_directories( + O2QcTOF + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + +target_link_libraries(O2QcTOF PUBLIC O2QualityControl + O2::TOFBase + O2::DataFormatsTOF + O2::TOFCompression + O2::TOFReconstruction + O2::DataFormatsGlobalTracking) + +install(TARGETS O2QcTOF + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/TOF + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- ROOT dictionary ---- + +add_root_dictionary(O2QcTOF + # Tasks + HEADERS include/TOF/TaskDigits.h + include/TOF/TaskCosmics.h + include/TOF/TaskRaw.h + include/TOF/TOFMatchedTracks.h + # Checkers + include/TOF/CheckCompressedData.h + include/TOF/CheckDiagnostics.h + include/TOF/CheckDRMDiagnostics.h + include/TOF/CheckRawMultiplicity.h + include/TOF/CheckRawTime.h + include/TOF/CheckRawToT.h + include/TOF/CheckHitMap.h + include/TOF/CheckNoise.h + include/TOF/CheckSlotPartMask.h + include/TOF/CheckLostOrbits.h + # PostProcessing + include/TOF/PostProcessDiagnosticPerCrate.h + include/TOF/PostProcessHitMap.h + include/TOF/PostProcessingLostOrbits.h + include/TOF/PostProcessingLuminometer.h + # Trending + include/TOF/TrendingHits.h + include/TOF/TrendingRate.h + include/TOF/TrendingCalibLHCphase.h + include/TOF/TrendingCalibDiagnostics.h + # Trending config + include/TOF/TrendingConfigTOF.h + # Reductors + include/TOF/TH1ReductorTOF.h + # Utilities + include/Base/Counter.h + include/Base/MessagePad.h + LINKDEF include/TOF/LinkDef.h) + +# ---- Test(s) ---- + +add_executable(testQcTOF test/testTOF.cxx) +target_link_libraries(testQcTOF PRIVATE O2QcTOF Boost::unit_test_framework) +add_test(NAME testQcTOF COMMAND testQcTOF) +set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) +set_tests_properties(testQcTOF PROPERTIES TIMEOUT 20) + +# ---- Executables ---- + +#set(EXE_SRCS src/runTOF.cxx) +# +#set(EXE_NAMES o2-qc-run-tof) +# +#list(LENGTH EXE_SRCS count) +#math(EXPR count "${count}-1") +#foreach(i RANGE ${count}) +# list(GET EXE_SRCS ${i} src) +# list(GET EXE_NAMES ${i} name) +# add_executable(${name} ${src}) +# target_link_libraries(${name} PRIVATE O2QualityControl CURL::libcurl) +#endforeach() + +# ---- Extra scripts ---- + +install(FILES tofdigits.json + tofdigits_multinode.json + toffull.json + toffull_multinode.json + tofpostprocessdiagnosticpercrate.json + tofraw.json + tofraw_multinode.json + tofcosmics.json + toftrending.json + toftrendingrate.json + toftrendingcalib.json + tofMatchedTracks_ITSTPCTOF.json + tofMatchedTracks_ITSTPCTOF_TPCTOF.json + tofMatchedTracks_ITSTPCTOF_TPCTOF_MC.json + tofMatchedTracks_ITSTPCTOF_TPCTOF_direct.json + tofMatchedTracks_ITSTPCTOF_TPCTOF_direct_MC.json + tofMatchedTracks_AllTypes_direct_MC.json + DESTINATION etc) + +get_property(dirs + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + PROPERTY INCLUDE_DIRECTORIES) +foreach(dir ${dirs}) + message(STATUS "dir='${dir}'") +endforeach() diff --git a/Modules/TOF/README.md b/Modules/TOF/README.md new file mode 100644 index 0000000000..7d8f8c199f --- /dev/null +++ b/Modules/TOF/README.md @@ -0,0 +1,61 @@ +# This is the documentation of the TOF QC + +## List of available JSON configurations +`tofdigits.json` +- Monitoring TOF digits + +`tofdigits_multinode.json` +- Monitoring TOF digits with the remote machine configuration + +`toffull.json` +- Monitoring both TOF RAW data and TOF digits + +`toffull_multinode.json` +- Monitoring both TOF RAW data and TOF digits with the remote machine configuration + +`tofpostprocessdiagnosticpercrate.json` +- Postprocessing the diagnostic words from the cards to the crate view + +`tofraw.json` +- Monitoring both TOF RAW data + +`tofraw_multinode.json` +- Monitoring both TOF RAW data with the remote machine configuration + + +## Useful information +Here are some various tips to run QC + +### Multinode QC i.e. running QC with separate data merging +The multinode QC configuration is useful when running QC on EPN/FLP/DCS machines i.e. when several machines are observing different fractions of the TOF data. +- The local machines are machines that run the QC directly on data usuall there are more than one. +- The remote machine is responsible for the merging and checking of the data +- On the QCDB the merged output will be under the name of the LOCAL tasks! +- QC on local machines should have specified the name of the machine. +- It's very important to specify `--local --host localnode1` for local machines and `--remote` for remote machine. +`localnode1` is the alias of the machine defined inside the configuration JSON. + +An example is the following: +```bash +# First local QC process +o2-raw-file-reader-workflow -b --input-conf TOFraw.cfg --loop 1000 | \ + o2-tof-compressor -b --shm-segment-size 10000000000 | \ + o2-tof-reco-workflow -b --input-type raw --output-type digits | \ + o2-qc -b --config json://${$QUALITYCONTROL_ROOT}/toffull_multinode.json --local --host localnode1 & + +# Second local QC process +o2-raw-file-reader-workflow -b --input-conf TOFraw.cfg --loop 1000 | \ + o2-tof-compressor -b --shm-segment-size 10000000000 | \ + o2-tof-reco-workflow -b --input-type raw --output-type digits | \ + o2-qc -b --config json://${$QUALITYCONTROL_ROOT}/toffull_multinode.json --local --host localnode2 & + +# Third local QC process +o2-raw-file-reader-workflow -b --input-conf TOFraw.cfg --loop 1000 | \ + o2-tof-compressor -b --shm-segment-size 10000000000 | \ + o2-tof-reco-workflow -b --input-type raw --output-type digits | \ + o2-qc -b --config json://${$QUALITYCONTROL_ROOT}/toffull_multinode.json --local --host localnode3 & + +# Remote QC process +o2-qc -b --config json://${$QUALITYCONTROL_ROOT}/toffull_multinode.json --remote +``` + diff --git a/Modules/TOF/include/Base/Counter.h b/Modules/TOF/include/Base/Counter.h new file mode 100644 index 0000000000..bfc7322f08 --- /dev/null +++ b/Modules/TOF/include/Base/Counter.h @@ -0,0 +1,396 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Counter.h +/// \author Nicolo' Jacazio +/// \since 24/03/2020 +/// \brief Utilities to count events and fill histograms at the end of the main processing loops. +/// Can be used to count labelled and non labelled events and fill histograms only once. +/// + +#ifndef QC_MODULE_TOF_COUNTER_H +#define QC_MODULE_TOF_COUNTER_H + +// ROOT includes +#include "TH1.h" +#include "TMath.h" + +// QC includes +#include "QualityControl/QcInfoLogger.h" + +// Fairlogger includes +#include + +namespace o2::quality_control_modules::tof +{ + +/// \brief Class to count events +/// \author Nicolo' Jacazio +template +class Counter +{ + public: + /// \brief Constructor + Counter() = default; + + /// Destructor + ~Counter() = default; + + /// Functions to increment a counter + /// @param index Index in the counter array to increment + /// @param weight weight to add to the array element + void Add(const unsigned int& index, const uint32_t& weight); + + /// Functions to count a single event + /// @param index Index in the counter array to increment by one + void Count(const unsigned int& index) { Add(index, 1); } + + /// Function to reset counters to zero + void Reset(); + + /// Function to print the counter content + void Print(); + + /// Function to get the total number of counts + uint32_t Total(); + + /// Function to get the total number of counts added since last call + uint32_t TotalNew(); + + /// Function to get the total number of counts and reset the counts + uint32_t TotalAndReset(); + + /// Function to get how many counts where observed + /// @return Returns the number of counts observed for a particular index + uint32_t HowMany(const unsigned int& index) const { return counter[index]; } + + /// Function to check if the counter has a label at position index + /// @param index Index in the counter array to be checked + /// @return Returns if the counter has label corresponding to a particular index. Returns false if the label is not there or is empty. + constexpr bool HasLabel(const unsigned int& index) const; + + /// Function to make a histogram out of the counters. If a counter has labels defined these are used as axis labels, if not this will not be done. + /// @param histogram histogram to shape in order to have room for the counter size + /// @returns Returns 0 if everything went OK + int MakeHistogram(TH1* histogram) const; + + /// Function to fill a histogram with the counters + /// @param histogram The histogram to fill + /// @param biny Y offset to fill to histogram, useful for TH2 and TH3 + /// @param binz Z offset to fill to histogram, useful for TH3 + /// @returns Returns 0 if everything went OK + int FillHistogram(TH1* histogram, const unsigned int& biny = 0, const unsigned int& binz = 0) const; + + /// Function to fill a histogram with the counters + /// @param histogram The histogram to fill + /// @param weight Weight to apply to second histogram (optional) + /// @returns Returns 0 if everything went OK + int AddHistogram(TH1* histogram, const float& weight = 1) const; + + /// Getter for the size + /// @return Returns the size of the counter + unsigned int Size() const { return size; } + + private: + static_assert(size > 0, "size of the counter cannot be 0!"); + // static_assert(((labels == nullptr) || (sizeof(labels) / sizeof(const char*)) == size), "size of the counter and the one of the labels must coincide"); + /// Containers to fill + std::array counter = { 0 }; + uint32_t mTotal = 0; +}; + +//////////////////// +// Implementation // +//////////////////// + +// #define ENABLE_BIN_SHIFT // Flag used to enable different binning in counter and histograms + +template +void Counter::Add(const unsigned int& index, const uint32_t& weight) +{ + if (index > size) { + LOG(fatal) << "Incrementing counter too far! " << index << "/" << size; + } + LOG(debug) << "Incrementing " << index << "/" << size << " of " << weight << " to " << counter[index]; + counter[index] += weight; +} + +template +void Counter::Reset() +{ + LOG(debug) << "Resetting Counter"; + for (unsigned int i = 0; i < size; i++) { + counter[i] = 0; + } +} + +template +constexpr bool Counter::HasLabel(const unsigned int& index) const +{ + if constexpr (labels != nullptr) { + return (labels[index] && labels[index][0]); + } + return false; +} + +template +void Counter::Print() +{ + for (unsigned int i = 0; i < size; i++) { + if (labels != nullptr) { + LOG(info) << "Bin " << i << "/" << size - 1 << " '" << labels[i] << "' = " << HowMany(i); + } else { + LOG(info) << "Bin " << i << "/" << size - 1 << " = " << HowMany(i); + } + } +} + +template +uint32_t Counter::Total() +{ + uint32_t sum = 0; + for (unsigned int i = 0; i < size; i++) { + sum += counter[i]; + } + mTotal = sum; + return sum; +} + +template +uint32_t Counter::TotalNew() +{ + uint32_t sum = mTotal; + return sum - Total(); +} + +template +uint32_t Counter::TotalAndReset() +{ + uint32_t sum = Total(); + Reset(); + return sum; +} + +template +int Counter::MakeHistogram(TH1* histogram) const +{ + LOG(debug) << "Making Histogram " << histogram->GetName() << " to accomodate counter of size " << size; + TAxis* axis = histogram->GetXaxis(); + if (static_cast(axis->GetNbins()) < size) { + LOG(fatal) << "The histogram size (" << axis->GetNbins() << ") is not large enough to accomodate the counter size (" << size << ")"; + return 1; + } + histogram->Reset(); + +#ifndef ENABLE_BIN_SHIFT + LOG(debug) << "Asked to produce a histogram with size " << size << " out of the size " << size << " due to " << size - size << " empty labels"; + axis->Set(size, 0, size); + for (unsigned int i = 0; i < size; i++) { + if (!HasLabel(i)) { // If label at position i is empty + continue; + } + LOG(debug) << "Setting bin " << i + 1 << "/" << size << " to contain counter for '" << labels[i] << "' (index " << i << "/" << size - 1 << ")"; + axis->SetBinLabel(i + 1, labels[i]); + } +#else + unsigned int histo_size = size; + if (labels != nullptr) { // Only if labels are defined + for (unsigned int i = 0; i < size; i++) { + if (!HasLabel(i)) { // If label at position i is empty or does not exist + LOG(debug) << "Skipping label '" << labels[i] << "' at position " << i << "/" << size - 1; + histo_size--; + } + } + } + if (histo_size == 0) { + LOG(fatal) << "Asked to produce a histogram with size " << histo_size << ", check counter bin labels"; + return 1; + } else { + LOG(debug) << "Asked to produce a histogram with size " << histo_size << " out of the size " << size << " due to " << size - histo_size << " empty labels"; + } + + axis->Set(histo_size, 0, histo_size); + if (labels != nullptr) { // Only if labels are defined + unsigned int binx = 1; + for (unsigned int i = 0; i < size; i++) { + if (!HasLabel(i)) { // If label at position i is empty + continue; + } + if (histo_size < binx) { + LOG(fatal) << "Making bin outside of histogram limits!"; + return 1; + } + LOG(debug) << "Setting bin " << binx << "/" << histo_size << " to contain counter for '" << labels[i] << "' (index " << i << "/" << size - 1 << ")"; + axis->SetBinLabel(binx++, labels[i]); + } + } +#endif + histogram->Reset(); + return 0; +} + +template +int Counter::FillHistogram(TH1* histogram, const unsigned int& biny, const unsigned int& binz) const +{ + auto fillIt = [&](const unsigned int& bin, const unsigned int& index) { + if (counter[index] > 0) { + if (biny > 0) { + if (binz > 0) { + histogram->SetBinContent(bin, biny, binz, counter[index]); + histogram->SetBinError(bin, biny, binz, TMath::Sqrt(counter[index])); + } else { + histogram->SetBinContent(bin, biny, counter[index]); + histogram->SetBinError(bin, biny, TMath::Sqrt(counter[index])); + } + } else { + histogram->SetBinContent(bin, counter[index]); + histogram->SetBinError(bin, TMath::Sqrt(counter[index])); + } + } + }; + + LOG(debug) << "Filling Histogram " << histogram->GetName() << " with counter contents"; +#ifndef ENABLE_BIN_SHIFT + if (size != (histogram->GetNbinsX())) { + LOG(fatal) << "Counter of size " << size << " does not fit in histogram " << histogram->GetName() << " with size " << histogram->GetNbinsX() - 1; + return 1; + } + for (unsigned int i = 0; i < size; i++) { + LOG(debug) << "Filling bin " << i + 1 << " with counter at position " << i; + if (HasLabel(i) && strcmp(labels[i], histogram->GetXaxis()->GetBinLabel(i + 1)) != 0) { // If it has a label check its consistency! + LOG(fatal) << "Bin " << i + 1 << " does not have the expected label '" << histogram->GetXaxis()->GetBinLabel(i + 1) << "' vs '" << labels[i] << "'"; + return 1; + } + fillIt(i + 1, i); + } +#else + const unsigned int nbinsx = histogram->GetNbinsX(); + if constexpr (labels == nullptr) { // Fill without labels + if (nbinsx < size) { + LOG(fatal) << "Counter size " << size << " is too large to fit in histogram " << histogram->GetName() << " with size " << nbinsx; + return 1; + } + for (unsigned int i = 0; i < size; i++) { + LOG(debug) << "Filling bin " << i + 1 << " with position " << i << " with " << counter[i]; + fillIt(i + 1, i); + } + } else { // Fill with labels + unsigned int binx = 1; + for (unsigned int i = 0; i < size; i++) { + if (!HasLabel(i)) { // Labels are defined and label is empty + if (counter[i] > 0) { + LOG(fatal) << "Counter at position " << i << " was non empty (" << counter[i] << ") but was discarded because of empty labels"; + return 1; + } + continue; + } + LOG(debug) << "Filling bin " << binx << " with position " << i << " of label " << labels[i] << " with " << counter[i]; + if (binx > nbinsx) { + LOG(fatal) << "Filling histogram " << histogram->GetName() << " at position " << binx << " i.e. past its size (" << nbinsx << ")!"; + return 1; + } + const char* bin_label = histogram->GetXaxis()->GetBinLabel(binx); + if (!HasLabel(i) && counter[i] > 0) { + LOG(fatal) << "Label at position " << i << " does not exist for axis label '" << bin_label << "' but counter is *non* empty!"; + return 1; + } else if (strcmp(labels[i], bin_label) != 0) { + LOG(fatal) << "Bin " << binx << " does not have the expected label '" << bin_label << "' vs '" << labels[i] << "'"; + return 1; + } + fillIt(binx, i); + binx++; + } + if (binx != nbinsx + 1) { + LOG(fatal) << "Did not fully fill histogram " << histogram->GetName() << ", filled " << binx << " out of " << nbinsx; + return 1; + } + } +#endif + + return 0; +} + +template +int Counter::AddHistogram(TH1* histogram, const float& weight) const +{ //Works only for 1D histos + + auto fillIt = [&](const unsigned int& bin, const unsigned int& index) { + if (counter[index] > 0) { + histogram->AddBinContent(bin, counter[index] * weight); + //Errors not propagated, not crucial + } + }; + + LOG(debug) << "Filling Histogram " << histogram->GetName() << " with counter contents"; +#ifndef ENABLE_BIN_SHIFT + if (size != (histogram->GetNbinsX())) { + LOG(fatal) << "Counter of size " << size << " does not fit in histogram " << histogram->GetName() << " with size " << histogram->GetNbinsX() - 1; + return 1; + } + for (unsigned int i = 0; i < size; i++) { + LOG(debug) << "Filling bin " << i + 1 << " with counter at position " << i; + if (HasLabel(i) && strcmp(labels[i], histogram->GetXaxis()->GetBinLabel(i + 1)) != 0) { // If it has a label check its consistency! + LOG(fatal) << "Bin " << i + 1 << " does not have the expected label '" << histogram->GetXaxis()->GetBinLabel(i + 1) << "' vs '" << labels[i] << "'"; + return 1; + } + fillIt(i + 1, i); + } +#else + const unsigned int nbinsx = histogram->GetNbinsX(); + if constexpr (labels == nullptr) { // Fill without labels + if (nbinsx < size) { + LOG(fatal) << "Counter size " << size << " is too large to fit in histogram " << histogram->GetName() << " with size " << nbinsx; + return 1; + } + for (unsigned int i = 0; i < size; i++) { + LOG(debug) << "Filling bin " << i + 1 << " with position " << i << " with " << counter[i]; + fillIt(i + 1, i); + } + } else { // Fill with labels + unsigned int binx = 1; + for (unsigned int i = 0; i < size; i++) { + if (!HasLabel(i)) { // Labels are defined and label is empty + if (counter[i] > 0) { + LOG(fatal) << "Counter at position " << i << " was non empty (" << counter[i] << ") but was discarded because of empty labels"; + return 1; + } + continue; + } + LOG(debug) << "Filling bin " << binx << " with position " << i << " of label " << labels[i] << " with " << counter[i]; + if (binx > nbinsx) { + LOG(fatal) << "Filling histogram " << histogram->GetName() << " at position " << binx << " i.e. past its size (" << nbinsx << ")!"; + return 1; + } + const char* bin_label = histogram->GetXaxis()->GetBinLabel(binx); + if (!HasLabel(i) && counter[i] > 0) { + LOG(fatal) << "Label at position " << i << " does not exist for axis label '" << bin_label << "' but counter is *non* empty!"; + return 1; + } else if (strcmp(labels[i], bin_label) != 0) { + LOG(fatal) << "Bin " << binx << " does not have the expected label '" << bin_label << "' vs '" << labels[i] << "'"; + return 1; + } + fillIt(binx, i); + binx++; + } + if (binx != nbinsx + 1) { + LOG(fatal) << "Did not fully fill histogram " << histogram->GetName() << ", filled " << binx << " out of " << nbinsx; + return 1; + } + } +#endif + + return 0; +} + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_COUNTER_H diff --git a/Modules/TOF/include/Base/MessagePad.h b/Modules/TOF/include/Base/MessagePad.h new file mode 100644 index 0000000000..d310a25999 --- /dev/null +++ b/Modules/TOF/include/Base/MessagePad.h @@ -0,0 +1,187 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file MessagePad.h +/// \author Nicolo' Jacazio +/// \since 13/10/2021 +/// \brief Utilities to contain TOF messages and to display them on QCG +/// + +#ifndef QC_MODULE_TOF_MESSAGEPAD_H +#define QC_MODULE_TOF_MESSAGEPAD_H + +// QC includes +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "TOF/Utils.h" + +// ROOT includes +#include "TPaveText.h" +#include "TList.h" +#include "TH1F.h" +#include "TH2F.h" + +namespace o2::quality_control_modules::tof +{ + +struct MessagePad { + float mPadLowX = 0.6; /// Position of the message PAD in low x + float mPadLowY = 0.5; /// Position of the message PAD in low y + float mPadHighX = 0.9; /// Position of the message PAD in high x + float mPadHighY = 0.75; /// Position of the message PAD in high y + std::vector mMessages{}; /// Message to print on the pad, this is reset at each call of MakeMessagePad + TPaveText* mMessagePad = nullptr; /// Text pad with the messages + int mEnabledFlag = 1; /// Flag to enable or disable the pad + const std::string mName = "msg"; /// Name of the message pad, can be used to identify the pad if multiple are used + + // Messages to print based on quality + std::string mMessageWhenNull = "No quality established"; /// Message to print when quality is Null + std::string mMessageWhenGood = "OK!"; /// Message to print when quality is Good + std::string mMessageWhenMedium = "Email TOF on-call"; /// Message to print when quality is Medium + std::string mMessageWhenBad = ""; /// Message to print when quality is Bad + + MessagePad(const std::string name = "", + const float padLowX = 0.6, const float padLowY = 0.5, + const float padHighX = 0.9, const float padHighY = 0.75) : mName(name) + { + ILOG(Info, Support) << "Making new message pad " << mName << ", " << mPadLowX << padLowY << ", " << padHighX << ", " << padHighY << ENDM; + setPosition(padLowX, padLowY, padHighX, padHighY); + } + + /// Function configure the message pad based on the input configuration + template + void configure(const T& CustomParameters) + { + // Setting position + if (utils::parseFloatParameter(CustomParameters, mName + "PadLowX", mPadLowX)) { + ILOG(Info, Support) << "Setting message pad " << mName << " mPadLowX to " << mPadLowX << ENDM; + } + if (utils::parseFloatParameter(CustomParameters, mName + "PadLowY", mPadLowY)) { + ILOG(Info, Support) << "Setting message pad " << mName << " mPadLowY to " << mPadLowY << ENDM; + } + if (utils::parseFloatParameter(CustomParameters, mName + "PadHighX", mPadHighX)) { + ILOG(Info, Support) << "Setting message pad " << mName << " mPadHighX to " << mPadHighX << ENDM; + } + if (utils::parseFloatParameter(CustomParameters, mName + "PadHighY", mPadHighY)) { + ILOG(Info, Support) << "Setting message pad " << mName << " mPadHighY to " << mPadHighY << ENDM; + } + // Setting standard messages + if (utils::parseStrParameter(CustomParameters, mName + "MessageWhenNull", mMessageWhenNull)) { + ILOG(Info, Support) << "Setting message pad " << mName << " mMessageWhenNull to " << mMessageWhenNull << ENDM; + } + if (utils::parseStrParameter(CustomParameters, mName + "MessageWhenGood", mMessageWhenGood)) { + ILOG(Info, Support) << "Setting message pad " << mName << " mMessageWhenGood to " << mMessageWhenGood << ENDM; + } + if (utils::parseStrParameter(CustomParameters, mName + "MessageWhenMedium", mMessageWhenMedium)) { + ILOG(Info, Support) << "Setting message pad " << mName << " mMessageWhenMedium to " << mMessageWhenMedium << ENDM; + } + if (utils::parseStrParameter(CustomParameters, mName + "MessageWhenBad", mMessageWhenBad)) { + ILOG(Info, Support) << "Setting message pad " << mName << " mMessageWhenBad to " << mMessageWhenBad << ENDM; + } + // Setting flags + configureEnabledFlag(CustomParameters); + } + + /// Function to set the enabled flag based on the input configuration + template + void configureEnabledFlag(const T& CustomParameters) + { + if (auto param = CustomParameters.find(mName + "EnabledFlag"); param != CustomParameters.end()) { + mEnabledFlag = ::atoi(param->second.c_str()); + ILOG(Info, Support) << "Setting message pad " << mName << " mEnabledFlag to " << mEnabledFlag << ENDM; + } + } + + /// Function configure the message pad position + void setPosition(const float& padLowX, const float& padLowY, const float& padHighX, const float& padHighY) + { + mPadLowX = padLowX; + mPadLowY = padLowY; + mPadHighX = padHighX; + mPadHighY = padHighY; + } + + /// Function to reset the standard quality messages + void clearQualityMessages() + { + mMessageWhenNull = ""; + mMessageWhenGood = ""; + mMessageWhenMedium = ""; + mMessageWhenBad = ""; + } + + /// Function to add a message that will be reported in the pad, will only add the message if the flag mEnabledFlag is on + void AddMessage(const std::string& message) + { + if (!mEnabledFlag) { + return; + } + mMessages.push_back(message); + } + + /// Function to add the message pad to the histogram. Returns nullptr if the message pad is disabled + template + TPaveText* MakeMessagePad(T* histogram, const Quality& quality, Option_t* padOpt = "blNDC") + { + if (!mEnabledFlag) { + mMessages.clear(); + ILOG(Info, Devel) << "Message pad '" << mName << "' is disabled" << ENDM; + return nullptr; + } + ILOG(Info, Devel) << "Message pad '" << mName << "' is enabled" << ENDM; + + mMessagePad = new TPaveText(mPadLowX, mPadLowY, mPadHighX, mPadHighY, padOpt); + mMessagePad->SetName(Form("%s_%s", histogram->GetName(), mName.c_str())); + histogram->GetListOfFunctions()->Add(mMessagePad); + mMessagePad->SetBorderSize(1); + mMessagePad->SetTextColor(kBlack); + if (quality == Quality::Good) { + mMessagePad->SetFillColor(kGreen); + } else if (quality == Quality::Medium) { + mMessagePad->SetFillColor(kYellow); + } else if (quality == Quality::Bad) { + mMessagePad->SetFillColor(kRed); + } else if (quality == Quality::Null) { + mMessagePad->SetTextColor(kWhite); + mMessagePad->SetFillStyle(3001); + mMessagePad->SetFillColor(kBlack); + } + // Add all lines + for (const auto& line : mMessages) { + mMessagePad->AddText(line.c_str()); + } + // Last line: message based on quality + std::string qualityMessage = ""; + if (quality == Quality::Good) { + qualityMessage = mMessageWhenGood; + } else if (quality == Quality::Medium) { + qualityMessage = mMessageWhenMedium; + } else if (quality == Quality::Bad) { + qualityMessage = mMessageWhenBad; + } else if (quality == Quality::Null) { + qualityMessage = mMessageWhenNull; + } else { + qualityMessage = "Quality undefined"; + } + if (qualityMessage != "") { + mMessagePad->AddText(qualityMessage.c_str()); + } + + // Clear the messages for next usage + mMessages.clear(); + return mMessagePad; + } +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_MESSAGEPAD_H diff --git a/Modules/TOF/include/TOF/CheckCompressedData.h b/Modules/TOF/include/TOF/CheckCompressedData.h new file mode 100644 index 0000000000..00c2f0ea16 --- /dev/null +++ b/Modules/TOF/include/TOF/CheckCompressedData.h @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckCompressedData.h +/// \author Nicolo' Jacazio +/// \brief Checker for the raw compressed data for TOF +/// + +#ifndef QC_MODULE_TOF_CHECKCOMPRESSEDDATA_H +#define QC_MODULE_TOF_CHECKCOMPRESSEDDATA_H + +#include "QualityControl/CheckInterface.h" +#include "Base/MessagePad.h" + +namespace o2::quality_control_modules::tof +{ + +/// \brief Checker for the data produced by the TOF compressor (i.e. checking raw data) +/// +/// \author Nicolo' Jacazio +class CheckCompressedData : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckCompressedData() = default; + /// Destructor + ~CheckCompressedData() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + float mDiagnosticThresholdPerSlot = 0; + + /// Messages to print on the output PAD + MessagePad mShifterMessages{ "CompressedData", 0.9, 0.1, 1.0, 0.5 }; + + ClassDefOverride(CheckCompressedData, 2); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_CHECKCOMPRESSEDDATA_H diff --git a/Modules/TOF/include/TOF/CheckDRMDiagnostics.h b/Modules/TOF/include/TOF/CheckDRMDiagnostics.h new file mode 100644 index 0000000000..76ffa4a42a --- /dev/null +++ b/Modules/TOF/include/TOF/CheckDRMDiagnostics.h @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckDRMDiagnostics.h +/// \author Nicolo' Jacazio, Pranjal Sarma +/// \brief Checker dedicated to the study of low level raw data diagnostics words +/// + +#ifndef QC_MODULE_TOF_CHECKDRMDIAGNOSTICS_H +#define QC_MODULE_TOF_CHECKDRMDIAGNOSTICS_H + +#include "QualityControl/CheckInterface.h" +#include "Base/MessagePad.h" + +namespace o2::quality_control_modules::tof +{ + +/// \brief Checker for diagnostic histogram of TOF Raw data +/// +/// \author Nicolo' Jacazio, Pranjal Sarma +class CheckDRMDiagnostics : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckDRMDiagnostics() = default; + /// Destructor + ~CheckDRMDiagnostics() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + /// Messages to print on the output PAD + MessagePad mShifterMessages; + + ClassDefOverride(CheckDRMDiagnostics, 2); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_CHECKDRMDIAGNOSTICS_H diff --git a/Modules/TOF/include/TOF/CheckDiagnostics.h b/Modules/TOF/include/TOF/CheckDiagnostics.h new file mode 100644 index 0000000000..55b856e36e --- /dev/null +++ b/Modules/TOF/include/TOF/CheckDiagnostics.h @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckDiagnostics.h +/// \author Nicolo' Jacazio +/// \brief Checker dedicated to the study of low level raw data diagnostics words +/// + +#ifndef QC_MODULE_TOF_CHECKDIAGNOSTICS_H +#define QC_MODULE_TOF_CHECKDIAGNOSTICS_H + +#include "QualityControl/CheckInterface.h" +#include "Base/MessagePad.h" + +namespace o2::quality_control_modules::tof +{ + +/// \brief Checker for diagnostic histogram of TOF Raw data +/// +/// \author Nicolo' Jacazio +class CheckDiagnostics : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckDiagnostics() = default; + /// Destructor + ~CheckDiagnostics() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + /// Messages to print on the output PAD + MessagePad mShifterMessages; + + ClassDefOverride(CheckDiagnostics, 2); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_CHECKDIAGNOSTICS_H diff --git a/Modules/TOF/include/TOF/CheckHitMap.h b/Modules/TOF/include/TOF/CheckHitMap.h new file mode 100644 index 0000000000..5cc94c3b77 --- /dev/null +++ b/Modules/TOF/include/TOF/CheckHitMap.h @@ -0,0 +1,71 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckHitMap.h +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// \brief Checker for the hit map hit obtained with the TaskDigits +/// + +#ifndef QC_MODULE_TOF_CHECKHITMAP_H +#define QC_MODULE_TOF_CHECKHITMAP_H + +#include "QualityControl/CheckInterface.h" +#include "Base/MessagePad.h" +#include "TPad.h" + +namespace o2::quality_control_modules::tof +{ + +class CheckHitMap : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckHitMap() = default; + /// Destructor + ~CheckHitMap() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + /// Reference hit map taken from the CCDB and translated into QC binning + std::shared_ptr mHistoRefHitMap = nullptr; /// TOF reference hit map + std::shared_ptr mHistoBinaryHitMap = nullptr; /// TOF binary (yes or no) hit map + std::vector> mHitMoreThanRef; + std::vector> mRefMoreThanHit; + + /// Messages to print on the output PAD + MessagePad mShifterMessages; + /// Message regarding the PHOS module (hole) + MessagePad mPhosModuleMessage{ "PHOS", 13.f, 38.f, 16.f, 53.f }; // Values corresponding to the PHOS hole + /// Flag to enable or disable the check with respect to the reference map + bool mEnableReferenceHitMap = true; + /// Name of the Path to get on CCDB for the ref. map + std::string mRefMapCcdbPath = "/TOF/Calib/FEELIGHT"; + /// Timestamp to get on CCDB for the ref. map + int mRefMapTimestamp = -1; + int mNWithHits = 0; /// Number of half strips with hits + int mNEnabled = 0; /// Number of enabled half strips + int mMaxHitMoreThanRef = 2; /// Maximum number of Hits more than Ref that is accepted + int mMaxRefMoreThanHit = 317; /// Maximum number of Refs more than Hits that is accepted (usual 5%of enabled channels) + bool mEnablePadPerMismatch = false; /// Flag to enable showing where the mismatch happens in the plot with TPads + /// Accepted type for this check + static constexpr char mAcceptedType[] = "TH2F"; + + ClassDefOverride(CheckHitMap, 2); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_CHECKHITMAP_H diff --git a/Modules/TOF/include/TOF/CheckLostOrbits.h b/Modules/TOF/include/TOF/CheckLostOrbits.h new file mode 100644 index 0000000000..f8b4def582 --- /dev/null +++ b/Modules/TOF/include/TOF/CheckLostOrbits.h @@ -0,0 +1,50 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckLostOrbits.h +/// \author Francesca Ercolessi +/// \brief Checker for lost orbits +/// + +#ifndef QC_MODULE_TOF_CHECKLOSTORBITS_H +#define QC_MODULE_TOF_CHECKLOSTORBITS_H + +#include "QualityControl/CheckInterface.h" +#include "Base/MessagePad.h" + +namespace o2::quality_control_modules::tof +{ + +class CheckLostOrbits : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckLostOrbits() = default; + /// Destructor + ~CheckLostOrbits() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + MessagePad mShifterMessages{ "", 0.15, 0.65, 0.4, 0.85 }; + /// To select to check link inefficiencies (if recovery does not work) + double mFractionThr = 0.9; + + ClassDefOverride(CheckLostOrbits, 2); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_CHECKLOSTORBITS_H diff --git a/Modules/TOF/include/TOF/CheckNoise.h b/Modules/TOF/include/TOF/CheckNoise.h new file mode 100644 index 0000000000..3f031f510b --- /dev/null +++ b/Modules/TOF/include/TOF/CheckNoise.h @@ -0,0 +1,76 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckNoise.h +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// \brief Checker for the noise levels obtained with the TaskRaw +/// + +#ifndef QC_MODULE_TOF_CHECKNOISE_H +#define QC_MODULE_TOF_CHECKNOISE_H + +#include "QualityControl/CheckInterface.h" +#include "Base/MessagePad.h" +#include "TPad.h" + +namespace o2::quality_control_modules::tof +{ + +class CheckNoise : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckNoise() = default; + /// Destructor + ~CheckNoise() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + /// Messages to print on the output PAD + MessagePad mShifterMessages; + /// Name of the accepted MO + static constexpr char mAcceptedName[] = "hIndexEOHitRate"; + /// Accepted type for this check + static constexpr char mAcceptedType[] = "TH1F"; + /// Maximum rate allowed before declaring a channel noisy + float mMaxNoiseRate = 50.f; // Hz + + /// Utility to locate the channel in the TOF + int locatedCrate = -1; /// Located crate corresponding to channel + int locatedTrm = -1; /// Located TRM corresponding to channel + int locatedSupermodule = -1; /// Located Supermodule corresponding to channel + int locatedLink = -1; /// Located Link corresponding to channel + int locatedChain = -1; /// Located Chain corresponding to channel + int locatedTdc = -1; /// Located Tdc corresponding to channel + int locatedChannel = -1; /// Located Channel (in alternative scheme) corresponding to channel + /// Function to locate a channel in the TOF geometry + void locateChannel(const int channel = 69461) + { + locatedCrate = channel / 2400; + locatedTrm = (channel - locatedCrate * 2400) / 240 + 3; + locatedSupermodule = locatedCrate / 4; + locatedLink = locatedCrate % 4; + locatedChain = (channel - locatedCrate * 2400 - (locatedTrm - 3) * 240) / 120; + locatedTdc = (channel - 120 * locatedChain - 240 * (locatedTrm - 3) - 2400 * locatedCrate) / 8; + locatedChannel = channel - locatedCrate * 2400 - (locatedTrm - 3) * 240 - locatedChain * 120 - locatedTdc * 8; + } + + ClassDefOverride(CheckNoise, 1); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_CHECKNOISE_H diff --git a/Modules/TOF/include/TOF/CheckRaw.h b/Modules/TOF/include/TOF/CheckRaw.h new file mode 100644 index 0000000000..0ced914eb2 --- /dev/null +++ b/Modules/TOF/include/TOF/CheckRaw.h @@ -0,0 +1,51 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRaw.h +/// \author Nicolo' Jacazio +/// \brief Checker for the raw compressed data for TOF +/// + +#ifndef QC_MODULE_TOF_CHECKCOMPRESSEDDATA_H +#define QC_MODULE_TOF_CHECKCOMPRESSEDDATA_H + +#include "QualityControl/CheckInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" + +namespace o2::quality_control_modules::tof +{ + +/// \brief Checker for the data produced by the TOF compressor (i.e. checking raw data) +/// +/// \author Nicolo' Jacazio +class CheckRaw : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckRaw() = default; + /// Destructor + ~CheckRaw() override = default; + + // Override interface + void configure(std::string name) override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + float mDiagnosticThresholdPerSlot = 0; + + ClassDefOverride(CheckRaw, 1); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_CHECKCOMPRESSEDDATA_H diff --git a/Modules/TOF/include/TOF/CheckRawMultiplicity.h b/Modules/TOF/include/TOF/CheckRawMultiplicity.h new file mode 100644 index 0000000000..3a11590aed --- /dev/null +++ b/Modules/TOF/include/TOF/CheckRawMultiplicity.h @@ -0,0 +1,70 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRawMultiplicity.h +/// \author Nicolò Jacazio +/// \brief Checker for the hit multiplicity obtained with the TaskDigits +/// + +#ifndef QC_MODULE_TOF_TOFCHECKRAWSMULTI_H +#define QC_MODULE_TOF_TOFCHECKRAWSMULTI_H + +#include "QualityControl/CheckInterface.h" +#include "Base/MessagePad.h" + +namespace o2::quality_control_modules::tof +{ + +class CheckRawMultiplicity : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckRawMultiplicity() = default; + /// Destructor + ~CheckRawMultiplicity() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + /// Running modes available + static constexpr int kModeCollisions = 0; /// Standard running mode with collisions + static constexpr int kModeCosmics = 1; /// Running mode with cosmics + + private: + // Running configurable parameters + /// Minimum number of entries in MO before message can be printed + double mMinEntriesBeforeMessage = -1.0; + /// Running mode, cosmics or collisions + int mRunningMode = kModeCollisions; + /// Minimum value of TOF raw hit multiplicity + float mMinRawHits = 10; + /// Maximum value of TOF raw hit multiplicity + float mMaxRawHits = 5000; + /// Fraction of the total integral which are considered Ok at 0 mult + float mMaxFractAtZeroMult = 0.75; + /// Fraction of the total integral which are considered Ok at low mult + float mMaxFractAtLowMult = 0.75; + + // User variables + /// Messages to print on the output PAD + MessagePad mShifterMessages; + /// Accepted type for this check + static constexpr char mAcceptedType[] = "TH1I"; + + ClassDefOverride(CheckRawMultiplicity, 2); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TOFCHECKRAWSMULTI_H diff --git a/Modules/TOF/include/TOF/CheckRawTime.h b/Modules/TOF/include/TOF/CheckRawTime.h new file mode 100644 index 0000000000..63749d5b21 --- /dev/null +++ b/Modules/TOF/include/TOF/CheckRawTime.h @@ -0,0 +1,69 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRawTime.h +/// \author Nicolò Jacazio +/// \brief Checker for the meassured time obtained with the TaskDigits +/// + +#ifndef QC_MODULE_TOF_TOFCHECKRAWSTIME_H +#define QC_MODULE_TOF_TOFCHECKRAWSTIME_H + +#include "QualityControl/CheckInterface.h" +#include "Base/MessagePad.h" + +namespace o2::quality_control_modules::tof +{ + +class CheckRawTime : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckRawTime() = default; + /// Destructor + ~CheckRawTime() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + // Running configurable parameters + /// Minimum number of entries in MO before message can be printed + double mMinEntriesBeforeMessage = -1.0; + /// Minimum value for TOF average raw time + float mMinAllowedTime = -1.f; + /// Maximum value for TOF average raw time + float mMaxAllowedTime = 300000.f; + /// Minimum value for the ratio between value the integral in the peak and the one outside for TOF raw time + float mMinPeakRatioIntegral = 0.20; + + // User variables + /// Mean of the TOF raw time distribution + float mRawTimeMean = 0.f; + /// Integral of the TOF raw time distribution in the peak region i.e. within minTOFrawTime and maxTOFrawTime + float mRawTimePeakIntegral = 0.f; + /// Integral of the TOF raw time distribution in the whole histogram range + float mRawTimeIntegral = 0.f; + + /// Messages to print on the output PAD + MessagePad mShifterMessages; + /// Accepted type for this check + static constexpr char mAcceptedType[] = "TH1F"; + + ClassDefOverride(CheckRawTime, 2); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TOFCHECKRAWSTIME_H diff --git a/Modules/TOF/include/TOF/CheckRawToT.h b/Modules/TOF/include/TOF/CheckRawToT.h new file mode 100644 index 0000000000..6ab2d57bba --- /dev/null +++ b/Modules/TOF/include/TOF/CheckRawToT.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRawToT.h +/// \author Nicolò Jacazio +/// \brief Checker for TOF Raw data on ToT +/// + +#ifndef QC_MODULE_TOF_TOFCHECKRAWSTOT_H +#define QC_MODULE_TOF_TOFCHECKRAWSTOT_H + +#include "QualityControl/CheckInterface.h" +#include "Base/MessagePad.h" + +namespace o2::quality_control_modules::tof +{ + +class CheckRawToT : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckRawToT() = default; + /// Destructor + ~CheckRawToT() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + // Running configurable parameters + /// Minimum number of entries in MO before message can be printed + double mMinEntriesBeforeMessage = -1.0; + /// Minimum ToT allowed for the mean in ns + float mMinAllowedToT = 10.; // ns + /// Maximum ToT allowed for the mean in ns + float mMaxAllowedToT = 15.; // ns + + // User variables + /// Messages to print on the output PAD + MessagePad mShifterMessages; + /// Accepted type for this check + static constexpr char mAcceptedType[] = "TH1F"; + + ClassDefOverride(CheckRawToT, 2); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TOFCHECKRAWSTOT_H diff --git a/Modules/TOF/include/TOF/CheckSlotPartMask.h b/Modules/TOF/include/TOF/CheckSlotPartMask.h new file mode 100644 index 0000000000..ec819dc421 --- /dev/null +++ b/Modules/TOF/include/TOF/CheckSlotPartMask.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckSlotPartMask.h +/// \author Francesca Ercolessi +/// \brief Checker for slot partecipating +/// + +#ifndef QC_MODULE_TOF_CHECKSLOTPARTMASK_H +#define QC_MODULE_TOF_CHECKSLOTPARTMASK_H + +#include "QualityControl/CheckInterface.h" +#include "Base/MessagePad.h" + +namespace o2::quality_control_modules::tof +{ + +class CheckSlotPartMask : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + CheckSlotPartMask() = default; + /// Destructor + ~CheckSlotPartMask() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult) override; + + private: + // Threshold number of crates missing + int mNCrates = 36; + // Threshold number of inefficient links + int mNCrateIneff = 36; + // Fraction of entries w.r.t. mean of all crates to decide if a link is inefficient + double mIneffThreshold = 0.8; + /// Messages to print on the output PAD + MessagePad mShifterMessages{ "", 50.f, 13.f, 72.f, 14.5 }; + /// To select to check link inefficiencies (if recovery does not work) + int mMaxNumberIneffientSlotPerCrate = 7; + int mMinNhitsPerSlot = 0; + + ClassDefOverride(CheckSlotPartMask, 2); +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_CHECKDIAGNOSTICS_H diff --git a/Modules/TOF/include/TOF/LinkDef.h b/Modules/TOF/include/TOF/LinkDef.h new file mode 100644 index 0000000000..495a8d2e71 --- /dev/null +++ b/Modules/TOF/include/TOF/LinkDef.h @@ -0,0 +1,35 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +// Tasks +#pragma link C++ class o2::quality_control_modules::tof::TaskDigits + ; +#pragma link C++ class o2::quality_control_modules::tof::TaskCosmics + ; +#pragma link C++ class o2::quality_control_modules::tof::TaskRaw + ; +#pragma link C++ class o2::quality_control_modules::tof::TOFMatchedTracks + ; +// Checks +#pragma link C++ class o2::quality_control_modules::tof::CheckDiagnostics + ; +#pragma link C++ class o2::quality_control_modules::tof::CheckDRMDiagnostics + ; +#pragma link C++ class o2::quality_control_modules::tof::CheckCompressedData + ; +#pragma link C++ class o2::quality_control_modules::tof::CheckRawMultiplicity + ; +#pragma link C++ class o2::quality_control_modules::tof::CheckRawTime + ; +#pragma link C++ class o2::quality_control_modules::tof::CheckRawToT + ; +#pragma link C++ class o2::quality_control_modules::tof::CheckHitMap + ; +#pragma link C++ class o2::quality_control_modules::tof::CheckNoise + ; +#pragma link C++ class o2::quality_control_modules::tof::CheckSlotPartMask + ; +#pragma link C++ class o2::quality_control_modules::tof::CheckLostOrbits + ; +// PostProcessing +#pragma link C++ class o2::quality_control_modules::tof::PostProcessDiagnosticPerCrate + ; +#pragma link C++ class o2::quality_control_modules::tof::PostProcessHitMap + ; +#pragma link C++ class o2::quality_control_modules::tof::PostProcessingLostOrbits + ; +#pragma link C++ class o2::quality_control_modules::tof::PostProcessingLuminometer + ; +// Trending +#pragma link C++ class o2::quality_control_modules::tof::TrendingHits + ; +#pragma link C++ class o2::quality_control_modules::tof::TrendingRate + ; +#pragma link C++ class o2::quality_control_modules::tof::TrendingCalibLHCphase + ; +#pragma link C++ class o2::quality_control_modules::tof::TrendingCalibDiagnostics + ; +// Reductors +#pragma link C++ class o2::quality_control_modules::tof::TH1ReductorTOF + ; +// Utilities +#endif diff --git a/Modules/TOF/include/TOF/PostProcessDiagnosticPerCrate.h b/Modules/TOF/include/TOF/PostProcessDiagnosticPerCrate.h new file mode 100644 index 0000000000..e09b1766a9 --- /dev/null +++ b/Modules/TOF/include/TOF/PostProcessDiagnosticPerCrate.h @@ -0,0 +1,85 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessDiagnosticPerCrate.h +/// \brief Post processing to rearrange TOF information at the level of the crate (maybe we should do the opposite..) +/// \author Nicolo' Jacazio and Francesca Ercolessi +/// \since 11/09/2020 +/// + +#ifndef QUALITYCONTROL_POSTPROCESSDIAGNOSTICPERCRATE_H +#define QUALITYCONTROL_POSTPROCESSDIAGNOSTICPERCRATE_H + +// QC includes +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/DatabaseInterface.h" + +#include +#include +#include +#include + +class TH1F; +class TH2F; + +namespace o2::quality_control_modules::tof +{ + +/// \brief Post processing to rearrange TOF information at the level of the crate (maybe we should do the opposite..) +/// \author Nicolo' Jacazio and Francesca Ercolessi +class PostProcessDiagnosticPerCrate final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + PostProcessDiagnosticPerCrate() = default; + /// \brief Destructor + ~PostProcessDiagnosticPerCrate() override; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + virtual void configure(const boost::property_tree::ptree& config) override; + + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + static const int mNWords; + static const int mNSlots; + + std::array, 72> mCrates; + o2::quality_control::repository::DatabaseInterface* mDatabase = nullptr; + std::string mCCDBPath; /// CCDB path of the MO (initialized from the configure method) + std::string mCCDBPathObjectDRM; /// CCDB name of the MO for the DRM (initialized from the configure method) + std::string mCCDBPathObjectLTM; /// CCDB name of the MO for the LTM (initialized from the configure method) + std::string mCCDBPathObjectTRM; /// CCDB name of the MO for the TRM (initialized from the configure method) +}; + +} // namespace o2::quality_control_modules::tof + +#endif //QUALITYCONTROL_POSTPROCESSDIAGNOSTICPERCRATE_H diff --git a/Modules/TOF/include/TOF/PostProcessHitMap.h b/Modules/TOF/include/TOF/PostProcessHitMap.h new file mode 100644 index 0000000000..64f5949fe4 --- /dev/null +++ b/Modules/TOF/include/TOF/PostProcessHitMap.h @@ -0,0 +1,86 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessHitMap.h +/// \brief Post processing to produce a plot of the TOF hit map with the reference enabled channels +/// \author Nicolò Jacazio +/// \since 09/07/2022 +/// + +#ifndef QUALITYCONTROL_TOF_POSTPROCESSHITMAP_H +#define QUALITYCONTROL_TOF_POSTPROCESSHITMAP_H + +// QC includes +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/DatabaseInterface.h" + +#include +#include +#include + +class TH1F; +class TH2F; +class TCanvas; +class TPaveText; + +namespace o2::quality_control_modules::tof +{ + +class PostProcessHitMap final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + PostProcessHitMap() = default; + /// \brief Destructor + ~PostProcessHitMap() override = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + virtual void configure(const boost::property_tree::ptree& config) override; + + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + o2::quality_control::repository::DatabaseInterface* mDatabase = nullptr; + int mRefMapTimestamp; /// Timestamp of the hitmap to fetch (initialized from the configure method) + std::string mCCDBPath; /// CCDB path of the MO (initialized from the configure method) + std::string mCCDBPathObject; /// CCDB name of the MO (initialized from the configure method) + std::string mRefMapCcdbPath; /// CCDB path of the RefMap (initialized from the configure method) + /// Reference hit map taken from the CCDB and translated into QC binning + std::shared_ptr mHistoRefHitMap = nullptr; /// TOF reference hit map + std::shared_ptr mHistoHitMap = nullptr; /// TOF hit map + std::shared_ptr mCanvasMo = nullptr; /// Canvas with hit map and ref. map + std::shared_ptr mPhosPad = nullptr; /// PHOS pad to draw + bool mDrawRefOnTop; /// flag to enable the drawing of the refmap on top of the hit map. if false drawing on top the hitmap (initialized from the configure method) +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QUALITYCONTROL_TOF_POSTPROCESSHITMAP_H diff --git a/Modules/TOF/include/TOF/PostProcessingLostOrbits.h b/Modules/TOF/include/TOF/PostProcessingLostOrbits.h new file mode 100644 index 0000000000..accb7522f7 --- /dev/null +++ b/Modules/TOF/include/TOF/PostProcessingLostOrbits.h @@ -0,0 +1,72 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingLostOrbits.h +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +/// + +#ifndef QUALITYCONTROL_POSTPROCESSINGLOSTORBITS_H +#define QUALITYCONTROL_POSTPROCESSINGLOSTORBITS_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/DatabaseInterface.h" + +class TH1F; + +namespace o2::quality_control_modules::tof +{ + +/// \brief Post processing to monitor lost orbits in TimeFrame +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +class PostProcessingLostOrbits final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + PostProcessingLostOrbits() = default; + /// \brief Destructor + ~PostProcessingLostOrbits() override; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + virtual void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + o2::quality_control::repository::DatabaseInterface* mDatabase = nullptr; + std::string mCCDBPath = "TOF/MO/TaskDigits/"; + std::string mMOName = "OrbitVsCrate"; + std::shared_ptr mHistoOrbitsInTFEfficiency = nullptr; + int mBins = 1100; + float mMaxRange = 1.1; +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QUALITYCONTROL_POSTPROCESSINGLOSTORBITS_H \ No newline at end of file diff --git a/Modules/TOF/include/TOF/PostProcessingLuminometer.h b/Modules/TOF/include/TOF/PostProcessingLuminometer.h new file mode 100644 index 0000000000..aedacdfb68 --- /dev/null +++ b/Modules/TOF/include/TOF/PostProcessingLuminometer.h @@ -0,0 +1,77 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingLuminometer.h +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +/// + +#ifndef QUALITYCONTROL_POSTPROCESSINGLUMINOMETER_H +#define QUALITYCONTROL_POSTPROCESSINGLUMINOMETER_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/DatabaseInterface.h" + +class TH1F; + +namespace o2::quality_control_modules::tof +{ + +/// \brief Post processing to monitor lost orbits in TimeFrame +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +class PostProcessingLuminometer final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + PostProcessingLuminometer() = default; + /// \brief Destructor + ~PostProcessingLuminometer() override; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + virtual void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + o2::quality_control::repository::DatabaseInterface* mDatabase = nullptr; + std::string mCCDBPath = "TOF/MO/TaskDigits/"; + std::string mMOEfficiency = "OrbitVsCrate"; + std::string mMOActiveChannels = "HitMap"; + std::string mMOdecodingErrors = "DecodingErrors"; + std::string mMOMultiplicity = "Multiplicity/Integrated"; + std::shared_ptr mHistoOrbitsInTFEfficiency = nullptr; + std::shared_ptr mHistoLuminometer = nullptr; + int mBins = 1100; + float mMaxRange = 1.1; + float mActiveThr = 1; +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QUALITYCONTROL_POSTPROCESSINGLuminometer_H diff --git a/Modules/TOF/include/TOF/TH1ReductorTOF.h b/Modules/TOF/include/TOF/TH1ReductorTOF.h new file mode 100644 index 0000000000..3be7e07934 --- /dev/null +++ b/Modules/TOF/include/TOF/TH1ReductorTOF.h @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1ReductorTOF.h +/// \author Francesca Ercolessi +/// +#ifndef QUALITYCONTROL_TH1REDUCTORTOF_H +#define QUALITYCONTROL_TH1REDUCTORTOF_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::tof +{ + +/// \brief A Reductor which obtains the most popular characteristics of TH1. +/// +/// A Reductor which obtains the most popular characteristics of TH1. +/// It produces a branch in the format: "mean/D:stddev:entries" +class TH1ReductorTOF : public quality_control::postprocessing::ReductorTObject +{ + public: + TH1ReductorTOF() = default; + ~TH1ReductorTOF() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + Double_t mean; + Double_t stddev; + Double_t entries; + Double_t maxpeak; + } mStats; +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QUALITYCONTROL_TH1REDUCTORTOF_H diff --git a/Modules/TOF/include/TOF/TOFMatchedTracks.h b/Modules/TOF/include/TOF/TOFMatchedTracks.h new file mode 100644 index 0000000000..8301035d38 --- /dev/null +++ b/Modules/TOF/include/TOF/TOFMatchedTracks.h @@ -0,0 +1,152 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TOFMatchedTracks.h +/// \author Chiara Zampolli +/// \brief Task to monitor TOF matching efficiency +/// \since 03/08/2021 +/// + +#ifndef QC_MODULE_TOF_TOFTOFMATCHEDTRACKS_H +#define QC_MODULE_TOF_TOFTOFMATCHEDTRACKS_H + +#include "QualityControl/TaskInterface.h" + +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include "ReconstructionDataFormats/MatchInfoTOFReco.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "DataFormatsTRD/TrackTRD.h" + +class TH1F; +class TH2F; +class TEfficiency; + +namespace o2::quality_control_modules::tof +{ + +using namespace o2::quality_control::core; +using GID = o2::dataformats::GlobalTrackID; + +/// \brief Task for the control of the TOF matching efficiency +/// \author Chiara Zampolli +class TOFMatchedTracks final : public TaskInterface +{ + public: + enum matchType : int8_t { TPC = 0, + ITSTPC_ITSTPCTRD, + TPCTRD, + SIZE }; + + /// \brief Constructor + TOFMatchedTracks() = default; + /// Destructor + ~TOFMatchedTracks() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + // track selection + bool selectTrack(o2::tpc::TrackTPC const& track); + void setPtCut(float v) { mPtCut = v; } + void setEtaCut(float v) { mEtaCut = v; } + void setMinNTPCClustersCut(float v) { mNTPCClustersCut = v; } + void setMinDCAtoBeamPipeCut(std::array v) + { + setMinDCAtoBeamPipeDistanceCut(v[0]); + setMinDCAtoBeamPipeYCut(v[1]); + } + void setMinDCAtoBeamPipeDistanceCut(float v) { mDCACut = v; } + void setMinDCAtoBeamPipeYCut(float v) { mDCACutY = v; } + + private: + std::shared_ptr mDataRequest; + o2::globaltracking::RecoContainer mRecoCont; + GID::mask_t mSrc = GID::getSourcesMask("ITS-TPC"); + GID::mask_t mAllowedSources = GID::getSourcesMask("TPC,ITS-TPC,TPC-TOF,ITS-TPC-TOF,TPC-TRD,ITS-TPC-TRD-TOF,TPC-TRD-TOF,ITS-TPC-TRD"); + // TPC-TOF + gsl::span mTPCTracks; + gsl::span mTPCTOFMatches; + // ITS-TPC-TOF + gsl::span mITSTPCTracks; + gsl::span mITSTPCTOFMatches; + // TPC-TRD-TOF + gsl::span mTPCTRDTracks; + gsl::span mTPCTRDTOFMatches; + // TPC-TRD-TOF + gsl::span mITSTPCTRDTracks; + gsl::span mITSTPCTRDTOFMatches; + + void fillDenominatorForExperts(float eta, float phi); + void fillNumeratorForExperts(float eta, float phi, int channel, int nhit, float dx, float dz, float chi2); + float mThresholdPtForExperts = 2.0; + static constexpr Float_t ETAFROMSTRIP[91] = { 0.852408, 0.833555, 0.814697, 0.795648, 0.777002, 0.758214, 0.739368, 0.720569, 0.701740, 0.682949, 0.664247, 0.645404, 0.626585, 0.607850, 0.589007, 0.570191, 0.551303, 0.532452, 0.514311, 0.493487, 0.474700, 0.455910, 0.437032, 0.418148, 0.399490, 0.380784, 0.362024, 0.343193, 0.324490, 0.305825, 0.286971, 0.268118, 0.249384, 0.230666, 0.211854, 0.193103, 0.174354, 0.155913, 0.131256, 0.113258, 0.096227, 0.077911, 0.059520, 0.041240, 0.022849, 0.004604, -0.013771, -0.032039, -0.050439, -0.068710, -0.087143, -0.104045, -0.122299, -0.146704, -0.165218, -0.184033, -0.202654, -0.221536, -0.240324, -0.258915, -0.277828, -0.296733, -0.315451, -0.334002, -0.352892, -0.371696, -0.390449, -0.408962, -0.427876, -0.446766, -0.465565, -0.484362, -0.505619, -0.523295, -0.542173, -0.561072, -0.579892, -0.598741, -0.617481, -0.636304, -0.655142, -0.673841, -0.692634, -0.711465, -0.730266, -0.749117, -0.767894, -0.786554, -0.805589, -0.824465, -0.843304 }; + + TH1F* mHistoExpTrackedStrip = nullptr; + TH1F* mHistoExpMatchedStrip = nullptr; + TH1F* mHistoExpMatchedStripFromCh = nullptr; + TH2F* mHistoExpMatchedStripFromChDx = nullptr; + TH2F* mHistoExpMatchedStripFromChDz = nullptr; + TH2F* mHistoExpMatchedStripFromChR = nullptr; + TH2F* mHistoExpMatchedNhit = nullptr; + + bool mUseMC = false; + bool mVerbose = false; + TH1F* mInTracksPt[matchType::SIZE] = {}; + TH1F* mInTracksEta[matchType::SIZE] = {}; + TH2F* mInTracks2DPtEta[matchType::SIZE] = {}; + TH1F* mMatchedTracksPt[matchType::SIZE] = {}; + TH1F* mMatchedTracksEta[matchType::SIZE] = {}; + TH2F* mMatchedTracks2DPtEta[matchType::SIZE] = {}; + TH1F* mFakeMatchedTracksPt[matchType::SIZE] = {}; + TH1F* mFakeMatchedTracksEta[matchType::SIZE] = {}; + TH2F* mDeltaZEta[matchType::SIZE] = {}; + TH2F* mDeltaZPhi[matchType::SIZE] = {}; + TH2F* mDeltaZPt[matchType::SIZE] = {}; + TH2F* mDeltaXEta[matchType::SIZE] = {}; + TH2F* mDeltaXPhi[matchType::SIZE] = {}; + TH2F* mDeltaXPt[matchType::SIZE] = {}; + TH1F* mTOFChi2[matchType::SIZE] = {}; + TH2F* mTOFChi2Pt[matchType::SIZE] = {}; + TH2F* mDTimeTrk[18] = {}; + TH2F* mDTimeTrkTPC[18] = {}; + TH2F* mDTimeTrkTRD[18] = {}; + TH1F* mDeltaTwMC; + TH2F* mExpTimesPiMC[matchType::SIZE] = {}; + TH2F* mExpTimesKaMC[matchType::SIZE] = {}; + TH2F* mExpTimesPrMC[matchType::SIZE] = {}; + + TEfficiency* mEffPt[matchType::SIZE] = {}; + TEfficiency* mEffEta[matchType::SIZE] = {}; + TEfficiency* mEff2DPtEta[matchType::SIZE] = {}; + TEfficiency* mFakeFractionTracksPt[matchType::SIZE] = {}; // fraction of fakes among the matched tracks vs pT + TEfficiency* mFakeFractionTracksEta[matchType::SIZE] = {}; // fraction of fakes among the matched tracks vs Eta + + // for track selection + float mPtCut = 0.1f; + float mEtaCut = 0.8f; + int32_t mNTPCClustersCut = 40; + float mDCACut = 100.f; + float mDCACutY = 10.f; + float mBz = 0; ///< nominal Bz + + int mTF = -1; // to count the number of processed TFs +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TOFTOFMATCHEDTRACKS_H diff --git a/Modules/TOF/include/TOF/TaskCosmics.h b/Modules/TOF/include/TOF/TaskCosmics.h new file mode 100644 index 0000000000..b05f93f643 --- /dev/null +++ b/Modules/TOF/include/TOF/TaskCosmics.h @@ -0,0 +1,81 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TaskCosmics.h +/// \author Nicolo' Jacazio +/// \brief Task to monitor TOF data collected in events from cosmics +/// + +#ifndef QC_MODULE_TOF_TASK_COSMICS_H +#define QC_MODULE_TOF_TASK_COSMICS_H + +#include "QualityControl/TaskInterface.h" +#include "Base/Counter.h" +#include "TOFBase/Geo.h" + +class TH1F; +class TH2F; +class TH1I; +class TH2I; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::tof +{ + +/// \brief TOF Quality Control DPL Task +/// \author Nicolo' Jacazio +class TaskCosmics final : public TaskInterface +{ + public: + /// \brief Constructor + TaskCosmics(); + /// Destructor + ~TaskCosmics() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + // Parameters + const float nrow = 256 * 3; + const float mTFDuration = ((nrow - 1) / nrow) * o2::tof::Geo::BC_TIME * o2::tof::Geo::BC_IN_ORBIT * 256 * 1E-9; /// Duration of a TF used to compute the rate of cosmics + float mSelDeltaTSignalRegion = 50000.f; /// Cut on the DeltaT to select signal + float mSelDeltaTBackgroundRegion = 100000.f; /// Cut on the DeltaT to select background + float mSelMinLength = 500.f; /// Cut on the minimum length that a track mush have [cm] + + // Histograms + std::shared_ptr mHistoCrate1 = nullptr; /// Crates of the first hit + std::shared_ptr mHistoCrate2 = nullptr; /// Crates of the second hit + std::shared_ptr mHistoCrate1VsCrate2 = nullptr; /// Crates of the second hit + std::shared_ptr mHistoDeltaT = nullptr; /// DeltaT + std::shared_ptr mHistoToT1 = nullptr; /// ToT1 + std::shared_ptr mHistoToT2 = nullptr; /// ToT2 + std::shared_ptr mHistoLength = nullptr; /// Length + std::shared_ptr mHistoDeltaTLength = nullptr; /// DeltaT vs Length + std::shared_ptr mHistoCosmicRate = nullptr; /// Rate of cosmics per crate + + // Counters + Counter<2, nullptr> mCounterTF; /// Counter for the number of TF seen + Counter<72, nullptr> mCounterPeak; /// Counter for coincidence between signals in the peak region + Counter<72, nullptr> mCounterBkg; /// Counter for coincidence between signals in the bkg region +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TASK_COSMICS_H diff --git a/Modules/TOF/include/TOF/TaskDigits.h b/Modules/TOF/include/TOF/TaskDigits.h new file mode 100644 index 0000000000..9c4d28c301 --- /dev/null +++ b/Modules/TOF/include/TOF/TaskDigits.h @@ -0,0 +1,180 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TaskDigits.h +/// \author Nicolò Jacazio +/// \brief Task to monitor quantities in TOF digits in both data and MC +/// + +#ifndef QC_MODULE_TOF_TASK_DIGITS_H +#define QC_MODULE_TOF_TASK_DIGITS_H + +#include "QualityControl/TaskInterface.h" +#include "Base/Counter.h" +#include "TOF/TaskRaw.h" +#include "CommonConstants/LHCConstants.h" +#include "DataFormatsTOF/CalibTimeSlewingParamTOF.h" +#include "DataFormatsTOF/CalibLHCphaseTOF.h" + +class TH1F; +class TH2F; +class TH1I; +class TH2I; +class TH2S; +class TProfile; +class TProfile2D; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::tof +{ + +/// \brief TOF Quality Control DPL Task for digits. Monitoring multiplicity, time, ToT and readout errors +/// \author Nicolò Jacazio +class TaskDigits final : public TaskInterface +{ + public: + /// \brief Constructor + TaskDigits(); + /// Destructor + ~TaskDigits() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + //////////////////////// + // Histogram binnings // + //////////////////////// + + // Orbit + static constexpr int mBinsOrbitId = 1024; /// Number of bins for the orbitID axis + static constexpr int mRangeMaxOrbitId = 1048576; /// Max range in the orbitID axis + // BC + static constexpr int mBinsBC = 594; /// Number of bins for the BC axis + static constexpr float mRangeMaxBC = o2::constants::lhc::LHCMaxBunches; /// Max range in the BC axis + // Event counter + static constexpr int mBinsEventCounter = 1000; /// Number of bins for the EventCounter axis + static constexpr int mRangeMaxEventCounter = mBinsEventCounter; /// Max range in the EventCounter axis + // Orbit in the Time Frame + static constexpr int mRangeMaxOrbitPerTimeFrame = 256; /// Number of bins for the OrbitPerTimeFrame axis. + static constexpr int mBinsOrbitPerTimeFrame = mRangeMaxOrbitPerTimeFrame * 3; /// Max range in the OrbitPerTimeFrame axis. 3 orbits are recorded per time frame + // Multiplicity + int mBinsMultiplicity = 2000; /// Number of bins in multiplicity plot + int mBinsMultiplicity2D = 2000; /// Number of bins in multiplicity plot + int mBinsMultiplicityOrbit = 6000; /// Number of bins in multiplicity plot + static constexpr int mRangeMinMultiplicity = 0; /// Min range in multiplicity plot + int mRangeMaxMultiplicity = mBinsMultiplicity; /// Max range in multiplicity plot + int mRangeMaxMultiplicityOrbit = mBinsMultiplicityOrbit; /// Max range in multiplicity plot + static constexpr int mBinsBCForMultiplicity = mRangeMaxBC; /// Number of bins for the BC axis in the multiplicity vs BC plot + // Time + int mBinsTime = 300; /// Number of bins in time plot + float fgkNbinsWidthTime = 2.44; /// Width of bins in time plot + float mRangeMinTime = 0.f; /// Range min in time plot + float mRangeMaxTime = o2::constants::lhc::LHCOrbitNS; /// Range max in time plot + // ToT + int mBinsToT = 100; /// Number of bins in ToT plot + float mRangeMinToT = 0; /// Range min in ToT plot + float mRangeMaxToT = 48.8; /// Range max in ToT plot + + /////////// + // Flags // + /////////// + + bool mFlagEnableDiagnostic = false; /// Flag to enable or disable diagnostic plots + bool mFlagEnableOrphanPerChannel = false; /// Flag to enable the histogram of the orphan counter per channel + + //////////////// + // Parameters // + //////////////// + + int mNoiseClassSelection = -1; /// Index to discard classes of noisy channels -1 no discarding, 0 first class are discarded, 1 second class, 2 third class + + o2::dataformats::CalibTimeSlewingParamTOF* mCalChannel = nullptr; + o2::dataformats::CalibLHCphaseTOF* mLHCphase = nullptr; + bool mApplyCalib = false; + + //////////////// + // Histograms // + //////////////// + + // Event info + std::shared_ptr mHistoOrbitID = nullptr; /// Orbits seen + std::shared_ptr mHistoBCID = nullptr; /// Bunch crossings seen + std::shared_ptr mHistoEventCounter = nullptr; /// Event counters seen + std::shared_ptr mHistoHitMap = nullptr; /// TOF hit map (1 bin = 1 FEA = 24 channels) + std::shared_ptr mHistoHitMapNoiseFiltered = nullptr; /// TOF hit map (1 bin = 1 FEA = 24 channels) filtered with the noise + std::shared_ptr mHistoTimeVsBCID = nullptr; /// TOF time vs BCID + std::shared_ptr mHistoOrbitVsCrate = nullptr; /// Orbits per crate + std::shared_ptr mHistoROWSize = nullptr; /// Readout window size + std::shared_ptr mHistoDecodingCrate[10]; /// mult vs TRM + std::shared_ptr mHistoDecodingErrors = nullptr; /// Decoding error monitoring + std::shared_ptr mHistoOrphanPerChannel = nullptr; /// Orphans per channel + std::shared_ptr mHistoNoisyChannels = nullptr; /// Channel flagged as noise (divided per flagged rate class) + + // Multiplicity + std::shared_ptr mHistoMultiplicity = nullptr; /// TOF raw hit multiplicity per event - all rw + std::shared_ptr mHistoMultiplicityIA = nullptr; /// TOF raw hit multiplicity per event - I/A side + std::shared_ptr mHistoMultiplicityOA = nullptr; /// TOF raw hit multiplicity per event - O/A side + std::shared_ptr mHistoMultiplicityIC = nullptr; /// TOF raw hit multiplicity per event - I/C side + std::shared_ptr mHistoMultiplicityOC = nullptr; /// TOF raw hit multiplicity per event - O/C side + std::shared_ptr mHistoMultiplicityOrbit = nullptr; /// TOF raw hit multiplicity per event - orbit + std::shared_ptr mHistoMultiplicityRW[3]; /// TOF raw hit multiplicity per event - RW1 + std::shared_ptr mHitMultiplicityVsCrate = nullptr; /// TOF raw hit multiplicity per event vs Crate + std::shared_ptr mHitMultiplicityVsCratepro = nullptr; /// TOF raw hit multiplicity per event vs Crate (TProfile) + std::shared_ptr mHitMultiplicityVsBC = nullptr; /// TOF raw hit multiplicity per event vs BC + std::shared_ptr mHitMultiplicityVsBCpro = nullptr; /// TOF raw hit multiplicity per event vs BC (TProfile) + + // Time + std::shared_ptr mHistoTime = nullptr; /// TOF hit time (ns) + std::shared_ptr mHistoTimeIA = nullptr; /// TOF hit time (ns) - I/A side + std::shared_ptr mHistoTimeOA = nullptr; /// TOF hit time (ns) - O/A side + std::shared_ptr mHistoTimeIC = nullptr; /// TOF hit time (ns) - I/C side + std::shared_ptr mHistoTimeOC = nullptr; /// TOF hit time (ns) - O/C side + std::shared_ptr mHistoTimeOrphans = nullptr; /// TOF hit time (ns) for orphan hits (missing trailer word) + + // ToT + std::shared_ptr mHistoToT = nullptr; /// TOF hit ToT (ns) + std::shared_ptr mHistoToTIA = nullptr; /// TOF hit ToT (ns) - I/A side + std::shared_ptr mHistoToTOA = nullptr; /// TOF hit ToT (ns) - O/A side + std::shared_ptr mHistoToTIC = nullptr; /// TOF hit ToT (ns) - I/C side + std::shared_ptr mHistoToTOC = nullptr; /// TOF hit ToT (ns) - O/C side + + // std::shared_ptr mTOFRawTimeVsTRM035 = nullptr; /// TOF raws - Hit time vs TRM - crates 0 to 35 + // std::shared_ptr mTOFRawTimeVsTRM3671 = nullptr; /// TOF raws - Hit time vs TRM - crates 36 to 72 + // std::shared_ptr mTOFTimeVsStrip = nullptr; /// TOF raw hit time vs. MRPC (along z axis) + // std::shared_ptr mTOFchannelEfficiencyMap = nullptr; /// TOF channels (HWok && efficient && !noisy && !problematic) + // std::shared_ptr mTOFhitsCTTM = nullptr; /// Map of hit pads according to CTTM numbering + // std::shared_ptr mTOFmacropadCTTM = nullptr; /// Map of hit macropads according to CTTM numbering + // std::shared_ptr mTOFmacropadDeltaPhiTime = nullptr; /// #Deltat vs #Delta#Phi of hit macropads + // std::shared_ptr mBXVsCttmBit = nullptr; /// BX ID in TOF matching window vs trg channel + // std::shared_ptr mTimeVsCttmBit = nullptr; /// TOF raw time vs trg channel + + // Counters + static constexpr unsigned int nchannels = RawDataDecoder::ncrates * RawDataDecoder::nstrips * 24; /// Number of channels + Counter mCounterHitsPerStrip[RawDataDecoder::nstrips]; /// Hit map counter in the crate, one per strip + Counter mCounterHitsPerStripNoiseFiltered[RawDataDecoder::nstrips]; /// Hit map counter in the crate, one per strip. With noise filtered + Counter mCounterHitsPerChannel; /// Hit map counter in the single channel + Counter mCounterOrphansPerChannel; /// Orphan counter in the single channel + static constexpr unsigned int nNoiseClasses = 3; /// Number of noise classes + Counter mCounterNoisyChannels[nNoiseClasses]; /// Noise flagged channel counter +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TASK_DIGITS_H diff --git a/Modules/TOF/include/TOF/TaskRaw.h b/Modules/TOF/include/TOF/TaskRaw.h new file mode 100644 index 0000000000..0a854568f2 --- /dev/null +++ b/Modules/TOF/include/TOF/TaskRaw.h @@ -0,0 +1,185 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TaskRaw.h +/// \author Nicolo' Jacazio and Francesca Ercolessi +/// \brief Task To monitor data converted from TOF compressor, and check the diagnostic words of TOF crates received trough the TOF compressor. +/// Here are defined the counters to check the diagnostics words of the TOF crates obtained from the compressor. +/// This is why the class derives from DecoderBase: it reads data from the decoder. +/// This tasks also perform a basic noise monitoring to check the fraction of noisy channels +/// \since 20-11-2020 +/// + +#ifndef QC_MODULE_TOF_TASK_RAW_H +#define QC_MODULE_TOF_TASK_RAW_H + +// O2 includes +#include "TOFReconstruction/DecoderBase.h" +#include "DataFormatsTOF/CompressedDataFormat.h" +#include "TOFBase/Geo.h" +using namespace o2::tof::compressed; + +// QC includes +#include "QualityControl/TaskInterface.h" +#include "Base/Counter.h" +using namespace o2::quality_control::core; + +class TH1; +class TH1F; +class TH2F; +class TEfficiency; + +namespace o2::quality_control_modules::tof +{ + +/// \brief TOF Quality Control class for Decoding Compressed data for TOF Compressed data QC Task +/// \author Nicolo' Jacazio and Francesca Ercolessi +class RawDataDecoder final : public DecoderBase +{ + public: + /// \brief Constructor + RawDataDecoder() = default; + /// Destructor + ~RawDataDecoder() = default; + + /// Function to run decoding of raw data + void decode() { DecoderBase::run(); }; + + /// Counters to fill + static constexpr unsigned int ncrates = 72; /// Number of crates + static constexpr unsigned int ntrms = 10; /// Number of TRMs per crate + static constexpr unsigned int ntrmschains = 2; /// Number of TRMChains per TRM + static constexpr unsigned int nsectors = 18; /// Number of sectors + static constexpr unsigned int nstrips = 91; /// Number of strips per sector + static constexpr unsigned int nwords = 32; /// Number of diagnostic words of a slot card + static constexpr unsigned int nslots = 12; /// Number of slots in a crate + static constexpr unsigned int nequipments = 172800; /// Number of equipment in the electronic indexing scheme + static constexpr unsigned int nRDHwords = 3; /// Number of diagnostic words for RDH + + /// Set parameters for noise analysis + void setTimeWindowMin(std::string min) { mTimeMin = atoi(min.c_str()); } + void setTimeWindowMax(std::string max) { mTimeMax = atoi(max.c_str()); } + void setNoiseThreshold(std::string thresholdnoise) { mNoiseThreshold = atof(thresholdnoise.c_str()); } + void setDebugCrateMultiplicity(bool debug) { mDebugCrateMultiplicity = debug; } + const bool& isDebugCrateMultiplicity() const { return mDebugCrateMultiplicity; } + + // Names of diagnostic counters + static const char* RDHDiagnosticsName[nRDHwords]; /// RDH Counter names + static const char* DRMDiagnosticName[nwords]; /// DRM Counter names + static const char* LTMDiagnosticName[nwords]; /// LTM Counter names + static const char* TRMDiagnosticName[nwords]; /// TRM Counter names + // Diagnostic counters + Counter mCounterRDH[ncrates]; /// RDH Counters + Counter mCounterDRM[ncrates]; /// DRM Counters + Counter mCounterLTM[ncrates]; /// LTM Counters + Counter mCounterTRM[ncrates][ntrms]; /// TRM Counters + // Global counters + Counter mCounterIndexEO; /// Counter for the single electronic index + Counter mCounterIndexEOInTimeWin; /// Counter for the single electronic index for noise analysis + Counter mCounterNoisyChannels; /// Counter for noisy channels + Counter<1024, nullptr> mCounterTimeBC; /// Counter for the Bunch Crossing Time + Counter mCounterNoiseMap[ncrates][4]; /// Counter for the Noise Hit Map, counts per crate and per FEA (4 per strip) + Counter mCounterRDHTriggers[2]; /// Counter for RDH triggers, one counts the triggers served to TDCs and one counts the triggers received + Counter mCounterRDHOpen; /// Counter for RDH open + Counter<800, nullptr> mCounterOrbitsPerCrate[ncrates]; /// Counter for orbits per crate + + /// Function to init histograms + void initHistograms(); + + /// Function to reset histograms + void resetHistograms(); + + // Function for noise estimation + void estimateNoise(std::shared_ptr hIndexEOIsNoise); + + // Histograms filled in the decoder to be kept to the bare bone so as to increase performance + std::shared_ptr mHistoHits; /// Number of TOF hits + std::shared_ptr mHistoHitsCrate[ncrates]; /// Number of TOF hits in TRMs per Crate + std::shared_ptr mHistoTime; /// Time + std::shared_ptr mHistoTOT; /// Time-Over-Threshold + std::shared_ptr mHistoDiagnostic; /// Diagnostic words + std::shared_ptr mHistoNErrors; /// Number of errors + std::shared_ptr mHistoErrorBits; /// Bits of errors + std::shared_ptr mHistoError; /// Errors in slot and TDC + std::shared_ptr mHistoNTests; /// Number of tests + std::shared_ptr mHistoTest; /// Tests in slot and TDC + std::shared_ptr mHistoOrbitID; /// Orbit ID for the header and trailer words + std::shared_ptr mHistoNoiseMap; /// Noise map, one bin corresponds to one FEA card + std::shared_ptr mHistoIndexEOHitRate; /// Noise rate x channel + std::shared_ptr mHistoPayload; /// Time + + private: + /** decoding handlers **/ + void rdhHandler(const o2::header::RAWDataHeader* /*rdh*/) override; + void headerHandler(const CrateHeader_t* crateHeader, const CrateOrbit_t* crateOrbit) override; + void frameHandler(const CrateHeader_t* crateHeader, const CrateOrbit_t* crateOrbit, + const FrameHeader_t* frameHeader, const PackedHit_t* packedHits) override; + void trailerHandler(const CrateHeader_t* crateHeader, const CrateOrbit_t* crateOrbit, + const CrateTrailer_t* crateTrailer, const Diagnostic_t* diagnostics, + const Error_t* errors) override; + + // Decoder parameters + /// Noise analysis variables + int mTimeMin = 0; /// Start of the time window in bins of the TDC + int mTimeMax = -1; /// End of the time window in bins of the TDC + static constexpr double mTDCWidth = 24.3660e-12; /// Width of the TDC bins in [s] + double mNoiseThreshold = 1.e+3; /// Threshold used to define noisy channels [Hz] + bool mDebugCrateMultiplicity = false; /// Save 72 histo with multiplicity per crate +}; + +/// \brief TOF Quality Control DPL Task for TOF Compressed data +/// \author Nicolo' Jacazio and Francesca Ercolessi +class TaskRaw final : public TaskInterface +{ + public: + /// \brief Constructor + TaskRaw() = default; + /// Destructor + ~TaskRaw() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + // Histograms + // Diagnostic words + std::shared_ptr mHistoRDH; /// Words per RDH + std::shared_ptr mHistoDRM; /// Words per DRM + std::shared_ptr mHistoLTM; /// Words per LTM + std::shared_ptr mHistoTRM[RawDataDecoder::ntrms]; /// Words per TRM + std::shared_ptr mHistoCrate[RawDataDecoder::ncrates]; /// Words of each slot in a crate + std::shared_ptr mHistoSlotParticipating; /// Participating slot per crate + + // Indices in the electronic scheme + std::shared_ptr mHistoIndexEO; /// Index in electronic + std::shared_ptr mHistoIndexEOInTimeWin; /// Index in electronic for noise analysis + std::shared_ptr mHistoIndexEOIsNoise; /// Noise hit map x channel + std::shared_ptr mHistoRDHServed; /// RDH triggers served per crate + std::shared_ptr mHistoRDHReceived; /// RDH triggers received per crate + TEfficiency* mEffRDHTriggers; /// RDH trigger efficiency, ratio of total triggers served to total triggers received per crate + std::shared_ptr mHistoOrbitsPerCrate; /// Orbit per crate + + // Other observables + std::shared_ptr mHistoTimeBC; /// Time in Bunch Crossing + + RawDataDecoder mDecoderRaw; /// Decoder for TOF Compressed data useful for the Task and filler of histograms for compressed raw data +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TASK_RAW_H diff --git a/Modules/TOF/include/TOF/TrendingCalibDiagnostics.h b/Modules/TOF/include/TOF/TrendingCalibDiagnostics.h new file mode 100644 index 0000000000..f63a115aab --- /dev/null +++ b/Modules/TOF/include/TOF/TrendingCalibDiagnostics.h @@ -0,0 +1,74 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingCalibDiagnostics.h +/// \author Sofia Tomassini +/// \author Francesca Ercolessi + +#ifndef QC_MODULE_TOF_TRENDING_CALIB_DIAGNOSTICS_H +#define QC_MODULE_TOF_TRENDING_CALIB_DIAGNOSTICS_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" +#include "CCDB/CcdbApi.h" + +#include "TOF/TrendingConfigTOF.h" + +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::tof +{ + +class TrendingCalibDiagnostics : public PostProcessingInterface +{ + public: + TrendingCalibDiagnostics() = default; + ~TrendingCalibDiagnostics() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, repository::DatabaseInterface&); + void generatePlots(); + + TrendingConfigTOF mConfig; + MetaData mMetaData; + UInt_t mTime; + float mCrateEff = 0.; + float mTRMEff = 0.; + o2::ccdb::CcdbApi mCdbApi; + std::string mHost; + + std::unique_ptr mTrend; + std::map mPlots; + std::unordered_map> mReductors; +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TRENDING_CALIB_DIAGNOSTICS_H diff --git a/Modules/TOF/include/TOF/TrendingCalibLHCphase.h b/Modules/TOF/include/TOF/TrendingCalibLHCphase.h new file mode 100644 index 0000000000..0c0f7cc1a5 --- /dev/null +++ b/Modules/TOF/include/TOF/TrendingCalibLHCphase.h @@ -0,0 +1,74 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingCalibLHCphase.h +/// \author Francesca Ercolessi + +#ifndef QC_MODULE_TOF_TRENDING_CALIB_LHCPHASE_H +#define QC_MODULE_TOF_TRENDING_CALIB_LHCPHASE_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" +#include "CCDB/CcdbApi.h" + +#include "TOF/TrendingConfigTOF.h" + +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::tof +{ + +class TrendingCalibLHCphase : public PostProcessingInterface +{ + public: + TrendingCalibLHCphase() = default; + ~TrendingCalibLHCphase() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, repository::DatabaseInterface&); + void generatePlots(); + + TrendingConfigTOF mConfig; + MetaData mMetaData; + UInt_t mTime; + float mPhase = 0.; + double mStartValidity = 0; + double mEndValidity = 0; + o2::ccdb::CcdbApi mCdbApi; + std::string mHost; + + std::unique_ptr mTrend; + std::map mPlots; + std::unordered_map> mReductors; +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TRENDING_CALIB_LHCPHASE_H diff --git a/Modules/TOF/include/TOF/TrendingConfigTOF.h b/Modules/TOF/include/TOF/TrendingConfigTOF.h new file mode 100644 index 0000000000..9e4b361aed --- /dev/null +++ b/Modules/TOF/include/TOF/TrendingConfigTOF.h @@ -0,0 +1,65 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingConfigTOF.h +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +/// \brief Header file for the trending task configuration for the number of hits in TOF +/// \since 05/10/2021 +/// + +#ifndef QC_MODULE_TOF_TRENDING_CONF_H +#define QC_MODULE_TOF_TRENDING_CONF_H + +#include +#include +#include "QualityControl/PostProcessingConfig.h" + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::tof +{ + +/// \brief TOF trending configuration structure +struct TrendingConfigTOF : PostProcessingConfig { + TrendingConfigTOF() = default; + TrendingConfigTOF(std::string name, const boost::property_tree::ptree& config); + ~TrendingConfigTOF() = default; + + struct Plot { + std::string name; + std::string title; + std::string varexp; + std::string selection; + std::string option; + std::string graphErrors; + }; + + struct DataSource { + std::string type; + std::string path; + std::string name; + std::string reductorName; + std::string moduleName; + }; + + struct ConfigTrendingRate { + float thresholdSignal = 0.3; + float thresholdBackground = 0.1; + } mConfigTrendingRate; + + std::vector plots; + std::vector dataSources; +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TRENDING_CONF_H diff --git a/Modules/TOF/include/TOF/TrendingHits.h b/Modules/TOF/include/TOF/TrendingHits.h new file mode 100644 index 0000000000..12fc004300 --- /dev/null +++ b/Modules/TOF/include/TOF/TrendingHits.h @@ -0,0 +1,72 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingHits.h +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +/// \brief Header file for the trending task for the number of hits in TOF +/// \since 05/10/2021 +/// + +#ifndef QC_MODULE_TOF_TRENDING_HITS_H +#define QC_MODULE_TOF_TRENDING_HITS_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" + +#include "TOF/TrendingConfigTOF.h" + +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::tof +{ + +/// \brief A post-processing task which trends TOF hits and stores them in a TTree and produces plots. +class TrendingHits : public PostProcessingInterface +{ + public: + TrendingHits() = default; + ~TrendingHits() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, repository::DatabaseInterface&); + void generatePlots(); + + TrendingConfigTOF mConfig; + MetaData mMetaData; + UInt_t mTime; + std::unique_ptr mTrend; + std::map mPlots; + std::unordered_map> mReductors; +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TRENDING_HITS_H diff --git a/Modules/TOF/include/TOF/TrendingRate.h b/Modules/TOF/include/TOF/TrendingRate.h new file mode 100644 index 0000000000..9abe1b14da --- /dev/null +++ b/Modules/TOF/include/TOF/TrendingRate.h @@ -0,0 +1,96 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingRate.h +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// \author Francesco Noferini francesco.noferini@cern.ch +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +/// \brief Trending of the TOF interaction rate +/// \since 06/06/2022 +/// + +#ifndef QC_MODULE_TOF_TRENDING_RATE_H +#define QC_MODULE_TOF_TRENDING_RATE_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" + +#include "TOF/TrendingConfigTOF.h" + +#include +#include +#include + +class TH2F; +class TProfile; + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::tof +{ + +/// \brief A post-processing task which trends TOF hits and stores them in a TTree and produces plots. +class TrendingRate : public PostProcessingInterface +{ + public: + TrendingRate() = default; + ~TrendingRate() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + std::string nameHitMap = "HitMap"; + std::string nameMultVsBC = "Multiplicity/VsBC"; + std::string nameMultVsBCpro = "Multiplicity/VsBCpro"; + + private: + struct MetaData { + Int_t runNumber = 0; + }; + + void trendValues(const Trigger& t, repository::DatabaseInterface&); + void generatePlots(); + + void computeTOFRates(TH2F* h, std::vector& bcInt, std::vector& bcRate, std::vector& bcPileup); + + TrendingConfigTOF mConfig; + MetaData mMetaData; + UInt_t mTime; + // Extra values to trend + float mNoiseRatePerChannel = 0.f; /// Noise rate + float mCollisionRate = 0.f; /// Collision rate + float mPileupRate = 0.f; /// Pileup + int mActiveChannels = 0; /// Active channels + int mNIBC = 0; /// n interction bunches + + std::unique_ptr mTrend; + std::map mPlots; + std::unordered_map> mReductors; + + TH2F* mPreviousPlot = nullptr; /// to keep memory of previous plot to work only with updates + + static constexpr float orbit_lenght = 88.924596E-6; + // These are initialized from the TrendingConfigTOF.h + float mThresholdSgn = 0.f; + float mThresholdBkg = 0.f; +}; + +} // namespace o2::quality_control_modules::tof + +#endif // QC_MODULE_TOF_TRENDING_HITS_H diff --git a/Modules/TOF/include/TOF/Utils.h b/Modules/TOF/include/TOF/Utils.h new file mode 100644 index 0000000000..5a3641142f --- /dev/null +++ b/Modules/TOF/include/TOF/Utils.h @@ -0,0 +1,110 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Utils.cxx +/// \author Nicolò Jacazio +/// \brief Set of common utilities for Tasks and Checkers +/// + +#ifndef QC_MODULE_TOF_UTILS_H +#define QC_MODULE_TOF_UTILS_H + +namespace o2::quality_control_modules::tof::utils +{ + +/// Utility methods to fetch float options from the custom parameters. +/// @param parametersIn container for the input parameters +/// @param name name of the option as in the mCustomParameters and JSON file +/// @param parameterOut will be set accordingly if the 'name' element is in mCustomParameters +/// @return true if the option was found, false otherwise +template +bool parseBooleanParameter(const ParameterType& parametersIn, const std::string& name, bool& parameterOut) +{ + if (auto param = parametersIn.find(name); param != parametersIn.end()) { + ILOG(Debug, Devel) << "Custom parameter - " << name << " " << param->second << ENDM; + if (param->second == "true" || param->second == "True" || param->second == "TRUE") { + parameterOut = true; + } else if (param->second == "false" || param->second == "False" || param->second == "FALSE") { + parameterOut = false; + } + return true; + } + return false; +} + +/// Utility methods to fetch float options from the custom parameters. +/// @param parametersIn container for the input parameters +/// @param name name of the option as in the mCustomParameters and JSON file +/// @param parameterOut will be set accordingly if the 'name' element is in mCustomParameters +/// @return true if the option was found, false otherwise +template +bool parseDoubleParameter(const ParameterType& parametersIn, const std::string& name, double& parameterOut) +{ + if (auto param = parametersIn.find(name); param != parametersIn.end()) { + ILOG(Debug, Devel) << "Custom parameter - " << name << " " << param->second << ENDM; + parameterOut = ::atof(param->second.c_str()); + return true; + } + return false; +} + +/// Utility methods to fetch float options from the custom parameters. +/// @param parametersIn container for the input parameters +/// @param name name of the option as in the mCustomParameters and JSON file +/// @param parameterOut will be set accordingly if the 'name' element is in mCustomParameters +/// @return true if the option was found, false otherwise +template +bool parseFloatParameter(const ParameterType& parametersIn, const std::string& name, float& parameterOut) +{ + if (auto param = parametersIn.find(name); param != parametersIn.end()) { + ILOG(Debug, Devel) << "Custom parameter - " << name << " " << param->second << ENDM; + parameterOut = ::atof(param->second.c_str()); + return true; + } + return false; +} + +/// Utility methods to fetch float options from the custom parameters. +/// @param parametersIn container for the input parameters +/// @param name name of the option as in the mCustomParameters and JSON file +/// @param parameter will be set accordingly if the 'name' element is in mCustomParameters +/// @return true if the option was found, false otherwise +template +bool parseIntParameter(const ParameterType& parametersIn, const std::string& name, int& parameterOut) +{ + if (auto param = parametersIn.find(name); param != parametersIn.end()) { + ILOG(Debug, Devel) << "Custom parameter - " << name << " " << param->second << ENDM; + parameterOut = ::atoi(param->second.c_str()); + return true; + } + return false; +} + +/// Utility methods to fetch float options from the custom parameters. +/// @param parametersIn container for the input parameters +/// @param name name of the option as in the mCustomParameters and JSON file +/// @param parameter will be set accordingly if the 'name' element is in mCustomParameters +/// @return true if the option was found, false otherwise +template +bool parseStrParameter(const ParameterType& parametersIn, const std::string& name, std::string& parameterOut) +{ + if (auto param = parametersIn.find(name); param != parametersIn.end()) { + ILOG(Debug, Devel) << "Custom parameter - " << name << " " << param->second << ENDM; + parameterOut = param->second; + return true; + } + return false; +} + +} // namespace o2::quality_control_modules::tof::utils + +#endif diff --git a/Modules/TOF/src/CheckCompressedData.cxx b/Modules/TOF/src/CheckCompressedData.cxx new file mode 100644 index 0000000000..cbb2eb3946 --- /dev/null +++ b/Modules/TOF/src/CheckCompressedData.cxx @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckCompressedData.cxx +/// \author Nicolo' Jacazio +/// \brief Checker for the raw compressed data for TOF +/// + +// QC +#include "TOF/CheckCompressedData.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" + +using namespace std; + +namespace o2::quality_control_modules::tof +{ + +void CheckCompressedData::configure() +{ + mDiagnosticThresholdPerSlot = 0; + if (auto param = mCustomParameters.find("DiagnosticThresholdPerSlot"); param != mCustomParameters.end()) { + mDiagnosticThresholdPerSlot = ::atof(param->second.c_str()); + } + mShifterMessages.configure(mCustomParameters); +} + +Quality CheckCompressedData::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + ILOG(Info, Support) << "Checking quality of compressed data" << ENDM; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "hDiagnostic") { + auto* h = dynamic_cast(mo->getObject()); + result = Quality::Good; + for (int i = 1; i < h->GetNbinsX(); i++) { + for (int j = 1; j < h->GetNbinsY(); j++) { + const float content = h->GetBinContent(i, j); + if (content > mDiagnosticThresholdPerSlot) { // If above threshold + result = Quality::Bad; + } else if (content > 0) { // If larger than zero + result = Quality::Medium; + } + } + } + } + } + return result; +} + +void CheckCompressedData::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "hDiagnostic") { + auto* h = dynamic_cast(mo->getObject()); + auto msg = mShifterMessages.MakeMessagePad(h, checkResult); + if (!msg) { + return; + } + if (checkResult == Quality::Good) { + msg->AddText("OK!"); + } else if (checkResult == Quality::Bad) { + msg->AddText("Diagnostics"); + msg->AddText("above"); + msg->AddText(Form("threshold (%.0f)", mDiagnosticThresholdPerSlot)); + } else if (checkResult == Quality::Medium) { + msg->AddText("Diagnostics above zero"); + } + } else { + ILOG(Error, Support) << "Did not get correct histo from " << mo->GetName() << ENDM; + } +} +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/CheckDRMDiagnostics.cxx b/Modules/TOF/src/CheckDRMDiagnostics.cxx new file mode 100644 index 0000000000..9403313a4b --- /dev/null +++ b/Modules/TOF/src/CheckDRMDiagnostics.cxx @@ -0,0 +1,89 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckDRMDiagnostics.cxx +/// \author Nicolo' Jacazio, Pranjal Sarma +/// \brief Checker dedicated to the study of low level raw data diagnostics words +/// + +// QC +#include "TOF/CheckDRMDiagnostics.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace std; + +namespace o2::quality_control_modules::tof +{ + +void CheckDRMDiagnostics::configure() +{ + mShifterMessages.configure(mCustomParameters); +} + +Quality CheckDRMDiagnostics::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + ILOG(Info, Support) << "Checking quality of diagnostic words" << ENDM; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "DRMCounter") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + continue; + } + for (int i = 2; i < h->GetNbinsX(); i++) { + for (int j = 2; j < h->GetNbinsY(); j++) { + if (h->GetBinContent(i, j) > 0) { // If larger than zero + result = Quality::Bad; + break; + } + } + if (result == Quality::Bad) { + break; + } + } + result = Quality::Good; + } + } + return result; +} + +void CheckDRMDiagnostics::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "DRMCounter") { + auto* h = dynamic_cast(mo->getObject()); + if (!h) { + ILOG(Warning, Support) << "Did not get MO for DRMCounter" << ENDM; + return; + } + auto msg = mShifterMessages.MakeMessagePad(h, checkResult); + if (!msg) { + return; + } + if (checkResult == Quality::Good) { + msg->AddText("OK!"); + } else if (checkResult == Quality::Bad) { + msg->AddText("DRM reporting error words"); + } else if (checkResult == Quality::Medium) { + msg->AddText("No entries. IF TOF IN RUN"); + msg->AddText("email TOF on-call."); + } + } else + ILOG(Error, Support) << "Did not get correct histo from " << mo->GetName() << ENDM; +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/CheckDiagnostics.cxx b/Modules/TOF/src/CheckDiagnostics.cxx new file mode 100644 index 0000000000..199fe8aae2 --- /dev/null +++ b/Modules/TOF/src/CheckDiagnostics.cxx @@ -0,0 +1,68 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckDiagnostics.cxx +/// \author Nicolo' Jacazio +/// \brief Checker dedicated to the study of low level raw data diagnostics words +/// + +// QC +#include "TOF/CheckDiagnostics.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace std; + +namespace o2::quality_control_modules::tof +{ + +void CheckDiagnostics::configure() +{ + mShifterMessages.configure(mCustomParameters); +} + +Quality CheckDiagnostics::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + ILOG(Info, Support) << "Checking quality of diagnostic words" << ENDM; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "RDHCounter") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + mShifterMessages.AddMessage("No entries"); + } + } + } + return result; +} + +void CheckDiagnostics::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "RDHCounter") { + auto* h = dynamic_cast(mo->getObject()); + auto msg = mShifterMessages.MakeMessagePad(h, checkResult); + if (!msg) { + return; + } + if (checkResult == Quality::Bad) { + msg->AddText("No TOF hits for all events."); + } + } else + ILOG(Error, Support) << "Did not get correct histo from " << mo->GetName() << ENDM; +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/CheckHitMap.cxx b/Modules/TOF/src/CheckHitMap.cxx new file mode 100644 index 0000000000..523f75a22c --- /dev/null +++ b/Modules/TOF/src/CheckHitMap.cxx @@ -0,0 +1,194 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckHitMap.cxx +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// \brief Checker for the hit map hit obtained with the TaskDigits +/// + +// QC +#include "TOF/CheckHitMap.h" +#include "TOF/Utils.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" +#include "CCDB/BasicCCDBManager.h" +#include "DataFormatsTOF/TOFFEElightInfo.h" +#include "TOFBase/Geo.h" + +using namespace std; + +namespace o2::quality_control_modules::tof +{ + +void CheckHitMap::configure() +{ + utils::parseBooleanParameter(mCustomParameters, "EnableReferenceHitMap", mEnableReferenceHitMap); + utils::parseStrParameter(mCustomParameters, "RefMapCcdbPath", mRefMapCcdbPath); + utils::parseIntParameter(mCustomParameters, "RefMapTimestamp", mRefMapTimestamp); + utils::parseIntParameter(mCustomParameters, "MaxHitMoreThanRef", mMaxHitMoreThanRef); + utils::parseIntParameter(mCustomParameters, "MaxRefMoreThanHit", mMaxRefMoreThanHit); /// by defoult it is to 317 = 5 % of the total enabled channels + utils::parseBooleanParameter(mCustomParameters, "EnablePadPerMismatch", mEnablePadPerMismatch); + mPhosModuleMessage.clearQualityMessages(); + mPhosModuleMessage.configureEnabledFlag(mCustomParameters); + mShifterMessages.mMessageWhenBad = "Call TOF on-call"; + mShifterMessages.configure(mCustomParameters); +} + +Quality CheckHitMap::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + if (!mo->encapsulatedInheritsFrom(mAcceptedType)) { + ILOG(Error, Support) << "Cannot check MO " << mo->getName() << " " << moName << " which is not of type " << mAcceptedType << ENDM; + continue; + } + ILOG(Debug, Devel) << "Checking " << mo->getName() << ENDM; + const auto* h = static_cast(mo->getObject()); + if (h->GetEntries() == 0) { // Histogram is empty + result = Quality::Medium; + mShifterMessages.AddMessage("No counts!"); + } else if (mEnableReferenceHitMap) { // Histogram is non empty. Here we should check that it is in agreement with the reference from CCDB + // Getting the reference map + const auto* refmap = o2::ccdb::BasicCCDBManager::instance().getForTimeStamp(mRefMapCcdbPath, mRefMapTimestamp); + if (!mHistoRefHitMap) { // Check that binning is compatible with Monitoring Object? + ILOG(Debug, Devel) << "making new refmap " << refmap << ENDM; + mHistoRefHitMap.reset(static_cast(h->Clone("ReferenceHitMap"))); + } + mHistoRefHitMap->Reset(); + int det[5] = { 0 }; // Coordinates + int strip = 0; // Strip + + for (auto i = 0; i < refmap->NCHANNELS; i++) { + if (!refmap->getChannelEnabled(i)) { + continue; + } + o2::tof::Geo::getVolumeIndices(i, det); + strip = o2::tof::Geo::getStripNumberPerSM(det[1], det[2]); // Strip index in the SM + const int istrip = mHistoRefHitMap->GetYaxis()->FindBin(strip); + const int crate = det[0] * 4 + det[4] / 12; + const int icrate = mHistoRefHitMap->GetXaxis()->FindBin(0.25f * crate); + mHistoRefHitMap->SetBinContent(icrate, istrip, 1); + } + + mNWithHits = 0; + mNEnabled = 0; + mHitMoreThanRef.clear(); + mRefMoreThanHit.clear(); + bool hasHit = false; + bool hasRef = false; + for (int i = 1; i <= h->GetNbinsX(); i++) { + for (int j = 1; j <= h->GetNbinsY(); j++) { + hasHit = false; + hasRef = false; + if (h->GetBinContent(i, j) > 0.0) { // Yes hit + mNWithHits++; + hasHit = true; + } + if (mHistoRefHitMap->GetBinContent(i, j) > 0.0) { // Ch. enabled + mNEnabled++; + hasRef = true; + } + if (hasHit && !hasRef) { + mHitMoreThanRef.push_back(std::pair(i, j)); + } + if (!hasHit && hasRef) { + mRefMoreThanHit.push_back(std::pair(i, j)); + } + } + } + + result = Quality::Good; + const auto mNMishmatching_HitMoreThanRef = mHitMoreThanRef.size(); + const auto mNMishmatching_RefMoreThanHit = mRefMoreThanHit.size(); + const auto mNMishmatching = mRefMoreThanHit.size() + mHitMoreThanRef.size(); + if (mNMishmatching_HitMoreThanRef > mMaxHitMoreThanRef || mNMishmatching_RefMoreThanHit > mMaxRefMoreThanHit) { + result = Quality::Bad; + mShifterMessages.AddMessage(Form("Hits %i, enabled %i, %zu total Mismatch", mNWithHits, mNEnabled, mNMishmatching)); + if (mNMishmatching_HitMoreThanRef > mMaxHitMoreThanRef) { + mShifterMessages.AddMessage(Form("%zu has hit but not ref > %i Thr.", mNMishmatching_HitMoreThanRef, mMaxHitMoreThanRef)); + } + if (mNMishmatching_RefMoreThanHit > mMaxRefMoreThanHit) { + mShifterMessages.AddMessage(Form("%zu has ref but not hit > %i Thr.", mNMishmatching_RefMoreThanHit, mMaxRefMoreThanHit)); + } + } + + return result; + } + } + return result; +} + +void CheckHitMap::beautify(std::shared_ptr mo, Quality checkResult) +{ + ILOG(Debug, Devel) << "Beautifying " << mo->getName() << ENDM; + if (!mo->encapsulatedInheritsFrom(mAcceptedType)) { + ILOG(Error, Support) << "Cannot beautify MO " << mo->getName() << " which is not of type " << mAcceptedType << ENDM; + return; + } + if (1) { + auto* h = static_cast(mo->getObject()); + if (checkResult != Quality::Good) { + auto msg = mShifterMessages.MakeMessagePad(h, checkResult); + } + auto msgPhos = mPhosModuleMessage.MakeMessagePad(h, Quality::Good, "bl"); + if (!msgPhos) { + return; + } + msgPhos->SetFillStyle(3004); + msgPhos->SetTextSize(30); + msgPhos->AddText("PHOS"); + if (mEnablePadPerMismatch) { // Adding pads to highlight mismatches + for (const auto p : mHitMoreThanRef) { // Pads for when hits are more than the reference map + float xl = h->GetXaxis()->GetBinLowEdge(p.first); + float xu = h->GetXaxis()->GetBinUpEdge(p.first); + float yl = h->GetYaxis()->GetBinLowEdge(p.second); + float yu = h->GetYaxis()->GetBinUpEdge(p.second); + + TPaveText* pad = new TPaveText(xl, yl, xu, yu); + + pad->SetName(Form("hits more than ref_%i_%i", p.first, p.second)); + h->GetListOfFunctions()->Add(pad); + pad->SetBorderSize(1); + pad->SetFillColor(kMagenta); + pad->AddText(" "); + + ILOG(Warning, Support) << "Adding extra pad to " << mo->GetName() << " in position along x: " << xl << " - " << xu << " and y: " << yl << " - " << yu << ENDM; + } + + for (const auto p : mRefMoreThanHit) { // Pads for when hits are less than the reference map + float xl = h->GetXaxis()->GetBinLowEdge(p.first); + float xu = h->GetXaxis()->GetBinUpEdge(p.first); + float yl = h->GetYaxis()->GetBinLowEdge(p.second); + float yu = h->GetYaxis()->GetBinUpEdge(p.second); + + TPaveText* pad = new TPaveText(xl, yl, xu, yu); + + pad->SetName(Form("ref more than hit_%i_%i", p.first, p.second)); + h->GetListOfFunctions()->Add(pad); + pad->SetBorderSize(1); + pad->SetFillColor(kRed); + // pad->SetFillStyle(3004); + pad->AddText(" "); + + ILOG(Warning, Support) << "Adding extra pad to " << mo->GetName() << " in position along x: " << xl << " - " << xu << " and y: " << yl << " - " << yu << ENDM; + } + } + + } else { + ILOG(Error, Support) << "Did not get correct histo from " << mo->GetName() << ENDM; + return; + } +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/CheckLostOrbits.cxx b/Modules/TOF/src/CheckLostOrbits.cxx new file mode 100644 index 0000000000..eafc4e88e8 --- /dev/null +++ b/Modules/TOF/src/CheckLostOrbits.cxx @@ -0,0 +1,71 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckLostOrbits.cxx +/// \author Francesca Ercolessi +/// \brief Checker for lost orbits +/// + +// QC +#include "TOF/CheckLostOrbits.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "TOF/Utils.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace std; + +namespace o2::quality_control_modules::tof +{ + +void CheckLostOrbits::configure() +{ + mShifterMessages.configure(mCustomParameters); + + utils::parseDoubleParameter(mCustomParameters, "FractionThr", mFractionThr); +} + +Quality CheckLostOrbits::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + ILOG(Info, Support) << "Checking fraction of lost orbits" << ENDM; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "OrbitsInTFEfficiency") { + auto* h = dynamic_cast(mo->getObject()); + + if (h->GetBinCenter((h->GetMaximumBin())) > mFractionThr) { + result = Quality::Good; + } else { + result = Quality::Bad; + } + } + } + return result; +} + +void CheckLostOrbits::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "OrbitsInTFEfficiency") { + auto* h = dynamic_cast(mo->getObject()); + auto msg = mShifterMessages.MakeMessagePad(h, checkResult, "NDC"); + if (!msg) { + return; + } + msg->AddText(Form("Max peak position = %.3f", h->GetBinCenter(h->GetMaximumBin()))); + msg->AddText(Form("Mean = %.3f", h->GetMean())); + } +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/CheckNoise.cxx b/Modules/TOF/src/CheckNoise.cxx new file mode 100644 index 0000000000..e4d73ed6e2 --- /dev/null +++ b/Modules/TOF/src/CheckNoise.cxx @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckNoise.cxx +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// \brief Checker for the hit map hit obtained with the TaskDigits +/// + +// QC +#include "TOF/CheckNoise.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" + +using namespace std; + +namespace o2::quality_control_modules::tof +{ + +void CheckNoise::configure() +{ + mShifterMessages.configure(mCustomParameters); +} + +Quality CheckNoise::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + if (!mo->encapsulatedInheritsFrom(mAcceptedType)) { + ILOG(Error, Support) << "Cannot check MO " << mo->getName() << " " << moName << " which is not of type " << mAcceptedType << ENDM; + continue; + } + if (mo->getName() != mAcceptedName) { + ILOG(Error, Support) << "Cannot check MO " << mo->getName() << " " << moName << " which does not have name " << mAcceptedName << ENDM; + continue; + } + + ILOG(Debug, Devel) << "Checking " << mo->getName() << ENDM; + const auto* h = static_cast(mo->getObject()); + + for (int i = 0; i < h->GetNbinsX(); i++) { + if (h->GetBinContent(i + 1) <= mMaxNoiseRate) { + continue; + } + result = Quality::Medium; + locateChannel(i); + std::string channelFormat = Form("%i %i %i %i %i %i", locatedSupermodule, locatedLink, locatedTrm, locatedChain, locatedTdc, locatedChannel); + mo->addMetadata(Form("noisyChannel%i", i), channelFormat); + mShifterMessages.AddMessage(channelFormat); + } + } + return result; +} + +void CheckNoise::beautify(std::shared_ptr mo, Quality checkResult) +{ + ILOG(Debug, Devel) << "Beautifying " << mo->getName() << ENDM; + if (!mo->encapsulatedInheritsFrom(mAcceptedType)) { + ILOG(Error, Support) << "Cannot beautify MO " << mo->getName() << " which is not of type " << mAcceptedType << ENDM; + return; + } + if (mo->getName() == mAcceptedName) { + auto* h = static_cast(mo->getObject()); + auto msg = mShifterMessages.MakeMessagePad(h, checkResult); + if (!msg) { + return; + } + } else { + ILOG(Error, Support) << "Did not get correct histo from " << mo->GetName() << ENDM; + return; + } +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/CheckRaw.cxx b/Modules/TOF/src/CheckRaw.cxx new file mode 100644 index 0000000000..85192f10b5 --- /dev/null +++ b/Modules/TOF/src/CheckRaw.cxx @@ -0,0 +1,113 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRaw.cxx +/// \author Nicolo' Jacazio +/// \brief Checker for the raw compressed data for TOF +/// + +// QC +#include "TOF/CheckRaw.h" +#include "QualityControl/QcInfoLogger.h" + +// ROOT +#include +#include +#include +#include + +using namespace std; + +namespace o2::quality_control_modules::tof +{ + +void CheckRaw::configure(std::string) +{ + mDiagnosticThresholdPerSlot = 0; + if (auto param = mCustomParameters.find("DiagnosticThresholdPerSlot"); param != mCustomParameters.end()) { + mDiagnosticThresholdPerSlot = ::atof(param->second.c_str()); + } +} + +Quality CheckRaw::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + ILOG(Info, Support) << "Checking quality of raw data" << ENDM; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "hDiagnostic") { + auto* h = dynamic_cast(mo->getObject()); + result = Quality::Good; + for (int i = 1; i < h->GetNbinsX(); i++) { + for (int j = 1; j < h->GetNbinsY(); j++) { + const float content = h->GetBinContent(i, j); + if (content > mDiagnosticThresholdPerSlot) { // If above threshold + result = Quality::Bad; + } else if (content > 0) { // If larger than zero + result = Quality::Medium; + } + } + } + } else if (mo->getName() == "DRMCounter") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } + } else if (mo->getName() == "LTMCounter") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + } + } + } + return result; +} + +void CheckRaw::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "hDiagnostic") { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.9, 0.1, 1.0, 0.5, "blNDC"); + h->GetListOfFunctions()->Add(msg); + msg->SetBorderSize(1); + msg->SetTextColor(kWhite); + msg->SetFillColor(kBlack); + msg->AddText("Default message for hDiagnostic"); + msg->SetName(Form("%s_msg", mo->GetName())); + + if (checkResult == Quality::Good) { + ILOG(Info, Support) << "Quality::Good, setting to green" << ENDM; + msg->Clear(); + msg->AddText("OK!"); + msg->SetFillColor(kGreen); + msg->SetTextColor(kBlack); + } else if (checkResult == Quality::Bad) { + ILOG(Info, Support) << "Quality::Bad, setting to red" << ENDM; + msg->Clear(); + msg->AddText("Diagnostics"); + msg->AddText("above"); + msg->AddText(Form("threshold (%.0f)", mDiagnosticThresholdPerSlot)); + msg->SetFillColor(kRed); + msg->SetTextColor(kBlack); + } else if (checkResult == Quality::Medium) { + ILOG(Info, Support) << "Quality::medium, setting to yellow" << ENDM; + msg->Clear(); + msg->AddText("Diagnostics above zero"); + msg->SetFillColor(kYellow); + msg->SetTextColor(kBlack); + } + } else { + ILOG(Error, Support) << "Did not get correct histo from " << mo->GetName() << ENDM; + } +} +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/CheckRawMultiplicity.cxx b/Modules/TOF/src/CheckRawMultiplicity.cxx new file mode 100644 index 0000000000..cf99b6b185 --- /dev/null +++ b/Modules/TOF/src/CheckRawMultiplicity.cxx @@ -0,0 +1,220 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRawMultiplicity.cxx +/// \author Nicolò Jacazio +/// \brief Checker for the raw hit multiplicity obtained with the TaskDigits +/// + +// QC +#include "TOF/CheckRawMultiplicity.h" +#include "TOF/Utils.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::tof +{ + +void CheckRawMultiplicity::configure() +{ + utils::parseDoubleParameter(mCustomParameters, "MinEntriesBeforeMessage", mMinEntriesBeforeMessage); + utils::parseIntParameter(mCustomParameters, "RunningMode", mRunningMode); + switch (mRunningMode) { + case kModeCollisions: + case kModeCosmics: + break; + default: + ILOG(Fatal, Support) << "Run mode not correct " << mRunningMode << ENDM; + break; + } + + utils::parseFloatParameter(mCustomParameters, "MinRawHits", mMinRawHits); + utils::parseFloatParameter(mCustomParameters, "MaxRawHits", mMaxRawHits); + + utils::parseFloatParameter(mCustomParameters, "MaxFractAtZeroMult", mMaxFractAtZeroMult); + utils::parseFloatParameter(mCustomParameters, "MaxFractAtLowMult", mMaxFractAtLowMult); + + mShifterMessages.configure(mCustomParameters); +} + +Quality CheckRawMultiplicity::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + + /// Mean of the TOF hit multiplicity histogram + float hitsMean = 0.f; + /// Number of events with 0 TOF hits + float hitsZeroMultIntegral = 0.f; + /// Number of events with low TOF hits multiplicity + float hitsLowMultIntegral = 0.f; + /// Number of events with TOF hits multiplicity > 0 + float hitsIntegral = 0.f; + + for (auto& [moName, mo] : *moMap) { + if (!mo->encapsulatedInheritsFrom(mAcceptedType)) { + ILOG(Error, Support) << "Cannot check MO " << mo->getName() << " " << moName << " which is not of type " << mAcceptedType << ENDM; + continue; + } + ILOG(Debug, Devel) << "Checking " << mo->getName() << ENDM; + if (mo->getName() == "Multiplicity/Integrated") { + const auto* h = static_cast(mo->getObject()); + if (h->GetEntries() == 0) { // Histogram is empty + result = Quality::Medium; + result.addFlag(FlagTypeFactory::NoDetectorData(), + "Empty histogram (no counts)"); + mShifterMessages.AddMessage("Empty histogram, no counts!"); + } else { // Histogram is non empty + + // Computing variables to check + hitsMean = h->GetMean(); + hitsZeroMultIntegral = h->Integral(1, 1); + hitsLowMultIntegral = h->Integral(1, 10); + hitsIntegral = h->Integral(2, h->GetNbinsX()); + + mo->addMetadata("mean", Form("%5.2f", hitsMean)); + mo->addMetadata("integ0mult", Form("%f", hitsZeroMultIntegral)); + mo->addMetadata("integlowmult", Form("%f", hitsLowMultIntegral)); + mo->addMetadata("integ", Form("%f", hitsIntegral)); + + if (hitsIntegral > 0) { + mo->addMetadata("frac0mult", Form("%5.2f%%", hitsZeroMultIntegral * 100. / (hitsIntegral + hitsZeroMultIntegral))); + } else { + mo->addMetadata("frac0mult", "UNDEF"); + } + + if (hitsIntegral == 0) { // if only "0 hits per event" bin is filled -> error + if (h->GetBinContent(1) > 0) { + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), + "Only events with 0 multiplicity"); + mShifterMessages.AddMessage("Only events with 0 multiplicity!"); + } + } else { + const bool isZeroBinContentHigh = (hitsZeroMultIntegral > (mMaxFractAtZeroMult * (hitsIntegral + hitsZeroMultIntegral))); + const bool isLowMultContentHigh = (hitsLowMultIntegral > (mMaxFractAtLowMult * hitsIntegral)); + const bool isAverageLow = (hitsMean < mMinRawHits); + const bool isAverageHigh = (hitsMean > mMaxRawHits); + switch (mRunningMode) { + case kModeCollisions: // Collisions + if (isZeroBinContentHigh) { + result = Quality::Medium; + mShifterMessages.AddMessage("Zero-multiplicity counts are high"); + // mShifterMessages.AddMessage(Form("(%.2f higher than total)!", mMaxFractAtZeroMult)); + } /*else if (isLowMultContentHigh) { + result = Quality::Medium; + mShifterMessages.AddMessage("Low-multiplicity counts are high"); + mShifterMessages.AddMessage(Form("(%.2f higher than total)!", mMaxFractAtLowMult)); + }*/ + else if (isAverageLow) { + result = Quality::Medium; + mShifterMessages.AddMessage(Form("Average lower than expected (%.2f)!", mMinRawHits)); + } else if (isAverageHigh) { + result = Quality::Medium; + mShifterMessages.AddMessage(Form("Average higher than expected (%.2f)!", mMaxRawHits)); + } else { + result = Quality::Good; + mShifterMessages.AddMessage("Average within limits"); + } + break; + case kModeCosmics: // Cosmics + if (hitsMean < 200.) { + result = Quality::Good; + mShifterMessages.AddMessage("Average within limits"); + } else { + result = Quality::Medium; + mShifterMessages.AddMessage("Average outside limits!"); + } + break; + default: + ILOG(Fatal, Support) << "Not running in correct mode " << mRunningMode << ENDM; + break; + } + } + } + } + } + return result; +} + +void CheckRawMultiplicity::beautify(std::shared_ptr mo, Quality checkResult) +{ + ILOG(Debug, Devel) << "Beautifying " << mo->getName() << ENDM; + if (!mo->encapsulatedInheritsFrom(mAcceptedType)) { + ILOG(Error, Support) << "Cannot beautify MO " << mo->getName() << " which is not of type " << mAcceptedType << ENDM; + return; + } + if (mo->getName() == "Multiplicity/Integrated") { + auto* h = static_cast(mo->getObject()); + auto msg = mShifterMessages.MakeMessagePad(h, checkResult); + if (!msg) { + return; + } + const auto& meta = mo->getMetadataMap(); + auto getMetaData = [&meta, &mo](const char* key) { + if (meta.find(key) == meta.end()) { + ILOG(Warning, Support) << "Looking for key '" << key << "' in metadata of " << mo->getName() << ", not found!" << ENDM; + return static_cast(Form("'Key %s not found'", key)); + } + return meta.at(key).c_str(); + }; + msg->AddText(Form("Mean value = %s", getMetaData("mean"))); + switch (mRunningMode) { + case kModeCollisions: + msg->AddText(Form("Reference range: [%5.2f-%5.2f]", mMinRawHits, mMaxRawHits)); + msg->AddText(Form("Events with 0 hits = %s", getMetaData("frac0mult"))); + break; + case kModeCosmics: // Cosmics + msg->AddText("Reference range: [0-200]"); + break; + default: + ILOG(Fatal, Support) << "Not running in correct mode " << mRunningMode << ENDM; + break; + } + + if (h->GetEntries() < mMinEntriesBeforeMessage) { // Checking that the histogram has enough entries before printing messages + msg->AddText("Cannot establish quality yet"); + msg->SetTextColor(kWhite); + return; + } + + if (checkResult == Quality::Good) { + msg->AddText("OK!"); + } else if (checkResult == Quality::Bad) { + msg->AddText("Call TOF on-call."); + } else if (checkResult == Quality::Medium) { + ILOG(Info, Support) << "Quality::medium, setting to yellow" << ENDM; + msg->AddText("IF TOF in run email TOF on-call."); + } + } else if (mo->getName() == "Multiplicity/SectorIA" || + mo->getName() == "Multiplicity/SectorOA" || + mo->getName() == "Multiplicity/SectorIC" || + mo->getName() == "Multiplicity/SectorOC") { + auto* h = static_cast(mo->getObject()); + auto msg = mShifterMessages.MakeMessagePad(h, checkResult); + if (!msg) { + return; + } + msg->AddText(Form("Mean value = %5.2f", h->GetMean())); + } else { + ILOG(Error, Support) << "Did not get correct histo from " << mo->GetName() << ENDM; + return; + } +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/CheckRawTime.cxx b/Modules/TOF/src/CheckRawTime.cxx new file mode 100644 index 0000000000..95fbea806d --- /dev/null +++ b/Modules/TOF/src/CheckRawTime.cxx @@ -0,0 +1,127 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRawTime.cxx +/// \author Nicolò Jacazio +/// \brief Checker for the meassured time obtained with the TaskDigits +/// + +// QC +#include "TOF/CheckRawTime.h" +#include "TOF/Utils.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::tof +{ + +void CheckRawTime::configure() +{ + utils::parseDoubleParameter(mCustomParameters, "MinEntriesBeforeMessage", mMinEntriesBeforeMessage); + utils::parseFloatParameter(mCustomParameters, "MinAllowedTime", mMinAllowedTime); + utils::parseFloatParameter(mCustomParameters, "MaxAllowedTime", mMaxAllowedTime); + utils::parseFloatParameter(mCustomParameters, "MinPeakRatioIntegral", mMinPeakRatioIntegral); + mShifterMessages.configure(mCustomParameters); +} + +Quality CheckRawTime::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + if (!mo->encapsulatedInheritsFrom(mAcceptedType)) { + ILOG(Error, Support) << "Cannot check MO " << mo->getName() << " " << moName << " which is not of type " << mAcceptedType << ENDM; + continue; + } + ILOG(Debug, Devel) << "Checking " << mo->getName() << ENDM; + + if (mo->getName().find("Time/") != std::string::npos) { + auto* h = static_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + result.addFlag(FlagTypeFactory::NoDetectorData(), + "Empty histogram (no counts)"); + } else { + mRawTimeMean = h->GetMean(); + static const int lowBinId = h->GetXaxis()->FindBin(mMinAllowedTime); + static const int highBinId = h->GetXaxis()->FindBin(mMaxAllowedTime); + mRawTimePeakIntegral = h->Integral(lowBinId, highBinId); + mRawTimeIntegral = h->Integral(1, h->GetNbinsX()); + if ((mRawTimeMean > mMinAllowedTime) && (mRawTimeMean < mMaxAllowedTime)) { + result = Quality::Good; + } else { + if (mRawTimePeakIntegral / mRawTimeIntegral > mMinPeakRatioIntegral) { + ILOG(Warning, Support) << Form("Raw time: peak/total integral = %5.2f, mean = %5.2f ns -> Check filling scheme...", mRawTimePeakIntegral / mRawTimeIntegral, mRawTimeMean) << ENDM; + result = Quality::Medium; + result.addFlag(FlagTypeFactory::Unknown(), + "Peak over total outside of allowed range"); + } else { + ILOG(Warning, Support) << Form("Raw time peak/total integral = %5.2f, mean = %5.2f ns", mRawTimePeakIntegral / mRawTimeIntegral, mRawTimeMean) << ENDM; + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), + "Time mean out of expected range"); + } + } + } + } + } + return result; +} + +void CheckRawTime::beautify(std::shared_ptr mo, Quality checkResult) +{ + ILOG(Debug, Devel) << "Beautifying " << mo->getName() << ENDM; + if (!mo->encapsulatedInheritsFrom(mAcceptedType)) { + ILOG(Error, Support) << "Cannot beautify MO " << mo->getName() << " which is not of type " << mAcceptedType << ENDM; + return; + } + if (mo->getName().find("Time/") != std::string::npos) { + auto* h = static_cast(mo->getObject()); + if (h->GetEntries() < mMinEntriesBeforeMessage) { // Checking that the histogram has enough entries before printing messages + return; + } + + auto msg = mShifterMessages.MakeMessagePad(h, checkResult); + if (!msg) { + return; + } + if (checkResult == Quality::Good) { + msg->AddText("Mean inside limits: OK"); + msg->AddText(Form("Allowed range: %3.0f-%3.0f ns", mMinAllowedTime, mMaxAllowedTime)); + } else if (checkResult == Quality::Bad) { + msg->AddText("Call TOF on-call."); + msg->AddText(Form("Mean outside limits (%3.0f-%3.0f ns)", mMinAllowedTime, mMaxAllowedTime)); + msg->AddText(Form("Raw time peak/total integral = %5.2f%%", mRawTimePeakIntegral * 100. / mRawTimeIntegral)); + msg->AddText(Form("Mean = %5.2f ns", mRawTimeMean)); + } else if (checkResult == Quality::Medium) { + msg->AddText("No entries. If TOF in the run"); + msg->AddText("email TOF on-call."); + // text->AddText(Form("Raw time peak/total integral = %5.2f%%", mRawTimePeakIntegral * 100. / mRawTimeIntegral)); + // text->AddText(Form("Mean = %5.2f ns", mRawTimeMean)); + // text->AddText(Form("Allowed range: %3.0f-%3.0f ns", mMinAllowedTime, mMaxAllowedTime)); + // text->AddText("If multiple peaks, check filling scheme"); + // text->AddText("See TOF TWiki."); + // text->SetFillColor(kYellow); + } + } else { + ILOG(Error, Support) << "Did not get correct histo from " << mo->GetName() << ENDM; + return; + } +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/CheckRawToT.cxx b/Modules/TOF/src/CheckRawToT.cxx new file mode 100644 index 0000000000..932674d726 --- /dev/null +++ b/Modules/TOF/src/CheckRawToT.cxx @@ -0,0 +1,141 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckRawToT.cxx +/// \author Nicolò Jacazio +/// \brief Checker for the ToT obtained in the TaskDigits +/// + +// QC +#include "TOF/CheckRawToT.h" +#include "TOF/Utils.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/MonitorObject.h" + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::tof +{ + +void CheckRawToT::configure() +{ + utils::parseDoubleParameter(mCustomParameters, "MinEntriesBeforeMessage", mMinEntriesBeforeMessage); + utils::parseFloatParameter(mCustomParameters, "MinAllowedToT", mMinAllowedToT); + utils::parseFloatParameter(mCustomParameters, "MaxAllowedToT", mMaxAllowedToT); + mShifterMessages.configure(mCustomParameters); +} + +Quality CheckRawToT::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + + /// Mean of the TOF ToT histogram + float ToTMean = 0.f; + /// Number of events with ToT==0 (orphans) + float ToTOrphanIntegral = 0.f; + /// Number of events with ToT > 0 (excluding orphans) + float ToTIntegral = 0.f; + + for (auto& [moName, mo] : *moMap) { + if (!mo->encapsulatedInheritsFrom(mAcceptedType)) { + ILOG(Error, Support) << "Cannot check MO " << mo->getName() << " " << moName << " which is not of type " << mAcceptedType << ENDM; + continue; + } + ILOG(Debug, Devel) << "Checking " << mo->getName() << ENDM; + + if (mo->getName().find("ToT/") != std::string::npos) { + auto* h = static_cast(mo->getObject()); + if (h->GetEntries() == 0) { + result = Quality::Medium; + result.addFlag(FlagTypeFactory::NoDetectorData(), + "Empty histogram (no counts)"); + } else { + // Set range to compute the average without the orphans + h->GetXaxis()->SetRange(2, h->GetNbinsX()); + ToTMean = h->GetMean(); + // Reset the range + h->GetXaxis()->SetRange(); + // Computing fractions + ToTOrphanIntegral = h->Integral(1, 1); + ToTIntegral = h->Integral(1, h->GetNbinsX()); + mo->addMetadata("mean", Form("%5.2f", ToTMean)); + if (ToTIntegral > 0) { + mo->addMetadata("orphanfraction", Form("%5.2f%%", ToTOrphanIntegral * 100. / ToTIntegral)); + } else { + mo->addMetadata("orphanfraction", "UNDEF"); + } + + if ((ToTMean > mMinAllowedToT) && (ToTMean < mMaxAllowedToT)) { + result = Quality::Good; + } else { + ILOG(Warning, Support) << Form("ToT mean = %5.2f ns", ToTMean) << ENDM; + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), + "ToT mean out of expected range"); + } + } + } + } + return result; +} + +void CheckRawToT::beautify(std::shared_ptr mo, Quality checkResult) +{ + ILOG(Debug, Devel) << "Beautifying " << mo->getName() << ENDM; + if (!mo->encapsulatedInheritsFrom(mAcceptedType)) { + ILOG(Error, Support) << "Cannot beautify MO " << mo->getName() << " which is not of type " << mAcceptedType << ENDM; + return; + } + if (mo->getName().find("ToT/") != std::string::npos) { + auto* h = static_cast(mo->getObject()); + auto msg = mShifterMessages.MakeMessagePad(h, checkResult); + if (!msg) { + return; + } + const auto& meta = mo->getMetadataMap(); + auto getMetaData = [&meta, &mo](const char* key) { + if (meta.find(key) == meta.end()) { + ILOG(Warning, Support) << "Looking for key '" << key << "' in metadata of " << mo->getName() << ", not found!" << ENDM; + return static_cast(Form("'Key %s not found'", key)); + } + return meta.at(key).c_str(); + }; + msg->AddText(Form("Mean value = %s", getMetaData("mean"))); + msg->AddText(Form("Allowed range: %3.1f-%3.1f ns", mMinAllowedToT, mMaxAllowedToT)); + msg->AddText(Form("Orphan fraction = %s", getMetaData("orphanfraction"))); + + if (h->GetEntries() < mMinEntriesBeforeMessage) { // Checking that the histogram has enough entries before printing messages + msg->AddText("Cannot establish quality yet"); + msg->SetTextColor(kWhite); + return; + } + + if (checkResult == Quality::Good) { + msg->AddText("OK!"); + } else if (checkResult == Quality::Bad) { + msg->AddText("Call TOF on-call."); + } else if (checkResult == Quality::Medium) { + ILOG(Info, Support) << "Quality::medium, setting to yellow" << ENDM; + msg->AddText("IF TOF IN RUN email TOF on-call."); + } + } else { + ILOG(Error, Support) << "Did not get correct histo from " << mo->GetName() << ENDM; + return; + } +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/CheckSlotPartMask.cxx b/Modules/TOF/src/CheckSlotPartMask.cxx new file mode 100644 index 0000000000..4ef54cce53 --- /dev/null +++ b/Modules/TOF/src/CheckSlotPartMask.cxx @@ -0,0 +1,115 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckSlotPartMask.cxx +/// \author Francesca Ercolessi +/// \brief Checker for slot partecipating +/// + +// QC +#include "TOF/CheckSlotPartMask.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "TOF/Utils.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace std; + +namespace o2::quality_control_modules::tof +{ + +void CheckSlotPartMask::configure() +{ + utils::parseIntParameter(mCustomParameters, "NCrates", mNCrates); + utils::parseIntParameter(mCustomParameters, "MinNhitsPerSlot", mMinNhitsPerSlot); + utils::parseIntParameter(mCustomParameters, "MaxNumberIneffientSlotPerCrate", mMaxNumberIneffientSlotPerCrate); + utils::parseDoubleParameter(mCustomParameters, "IneffThreshold", mIneffThreshold); + utils::parseIntParameter(mCustomParameters, "NCrateIneff", mNCrateIneff); + + mShifterMessages.configure(mCustomParameters); +} + +Quality CheckSlotPartMask::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + ILOG(Info, Support) << "Checking slot partecipating" << ENDM; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "hSlotPartMask") { + auto* h = dynamic_cast(mo->getObject()); + double hitsxcrate[72] = {}; + int ncrate = 0; + double maxhitsxcrate = 0.; + + for (int xbin = 1; xbin <= h->GetNbinsX(); xbin++) { // loop over crates + int nSlotsBelowThr = 0; // define couter variable for the slots below the threshold + for (int ybin = 1; ybin <= h->GetNbinsY(); ybin++) { // loop over slots + hitsxcrate[xbin - 1] += h->GetBinContent(xbin, ybin); + if (h->GetBinContent(xbin, ybin) <= mMinNhitsPerSlot) { + nSlotsBelowThr++; // number of inefficient slots in each crate + } + } + if (nSlotsBelowThr > mMaxNumberIneffientSlotPerCrate) { + ncrate++; // if the number of inefficient slots in a crate is above a maximun, the crate is inefficient + } + if (maxhitsxcrate <= hitsxcrate[xbin - 1]) + maxhitsxcrate = hitsxcrate[xbin - 1]; // Finding the maximum number of hits in a crate + } + // look for inefficient crates + int ncrate_ineff = 0; + for (int ncrate = 0; ncrate < 72; ncrate++) { // loop over crates + if (hitsxcrate[ncrate] < mIneffThreshold * maxhitsxcrate) { + ncrate_ineff++; + } + } + + Quality partialResult = Quality::Null; + if (ncrate > mNCrates) { + partialResult = Quality::Bad; + } else if ((ncrate_ineff > mNCrateIneff)) { + partialResult = Quality::Medium; + } else { + partialResult = Quality::Good; + } + if (partialResult.isWorseThan(result) || result == Quality::Null) { + result = partialResult; + } + } + } + return result; +} + +void CheckSlotPartMask::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "hSlotPartMask") { + auto* h = dynamic_cast(mo->getObject()); + auto msg = mShifterMessages.MakeMessagePad(h, checkResult, "bl"); + msg->SetTextSize(30); + if (!msg) { + return; + } + if (checkResult == Quality::Good) { + msg->AddText("OK!"); + } else if (checkResult == Quality::Bad) { + msg->AddText("Many crates have many inefficient slots."); + msg->AddText("Call TOF on-call."); + } else if (checkResult == Quality::Medium) { + msg->AddText("Many inefficient crates."); + // msg->AddText(""); + } + } else + ILOG(Error, Support) << "Did not get correct histo from " << mo->GetName() << ENDM; +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/PostProcessDiagnosticPerCrate.cxx b/Modules/TOF/src/PostProcessDiagnosticPerCrate.cxx new file mode 100644 index 0000000000..930988f518 --- /dev/null +++ b/Modules/TOF/src/PostProcessDiagnosticPerCrate.cxx @@ -0,0 +1,118 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessDiagnosticPerCrate.cxx +/// \brief Post processing to rearrange TOF information at the level of the crate (maybe we should do the opposite..) +/// \author Nicolo' Jacazio and Francesca Ercolessi +/// \since 11/09/2020 +/// + +// QC includes +#include "TOF/PostProcessDiagnosticPerCrate.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" + +// ROOT includes +#include +#include + +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::tof +{ + +const int PostProcessDiagnosticPerCrate::mNWords = 32; +const int PostProcessDiagnosticPerCrate::mNSlots = 14; + +PostProcessDiagnosticPerCrate::~PostProcessDiagnosticPerCrate() +{ +} + +void PostProcessDiagnosticPerCrate::configure(const boost::property_tree::ptree& config) +{ + const std::string baseJsonPath = "qc.postprocessing." + getID() + ".customization."; + mCCDBPath = config.get(baseJsonPath + "CCDBPath", "TOF/MO/TaskRaw/"); + ILOG(Info, Support) << "Setting CCDBPath to " << mCCDBPath << ENDM; + mCCDBPathObjectDRM = config.get(baseJsonPath + "CCDBPathObjectDRM", "DRMCounter"); + ILOG(Info, Support) << "Setting mCCDBPathObjectDRM to " << mCCDBPathObjectDRM << ENDM; + mCCDBPathObjectLTM = config.get(baseJsonPath + "CCDBPathObjectLTM", "LTMCounter"); + ILOG(Info, Support) << "Setting mCCDBPathObjectLTM to " << mCCDBPathObjectLTM << ENDM; + mCCDBPathObjectTRM = config.get(baseJsonPath + "CCDBPathObjectTRM", "TRMCounter"); + ILOG(Info, Support) << "Setting mCCDBPathObjectTRM to " << mCCDBPathObjectTRM << ENDM; +} + +void PostProcessDiagnosticPerCrate::initialize(Trigger, framework::ServiceRegistryRef services) +{ + int counter = 0; + for (auto& i : mCrates) { + i.reset(); + i.reset(new TH2F(Form("hCrate%02i", counter), + Form("Crate%02i;Word;Slot", counter), + mNWords, 0, mNWords, mNSlots, 0, mNSlots)); + counter++; + } + + // Setting up services + mDatabase = &services.get(); +} + +void PostProcessDiagnosticPerCrate::update(Trigger t, framework::ServiceRegistryRef) +{ + ILOG(Debug) << "UPDATING !" << ENDM; + for (int slot = 0; slot < mNSlots; slot++) { // Loop over slots + std::string moName = mCCDBPathObjectDRM; + if (slot == 1) { + moName = mCCDBPathObjectLTM; + } else if (slot > 1) { + moName = Form("%s%i", mCCDBPathObjectTRM.c_str(), slot); + } + ILOG(Debug) << "Processing slot " << slot << " from " << moName << ENDM; + auto mo = mDatabase->retrieveMO(mCCDBPath, moName, t.timestamp, t.activity); + TH2F* moH = static_cast(mo ? mo->getObject() : nullptr); + if (moH) { + for (int crate = 0; crate < moH->GetNbinsY(); crate++) { // Loop over crates + ILOG(Debug) << "Processing crate " << crate << ENDM; + if (static_cast(crate) > mCrates.size()) { + ILOG(Fatal) << "Crate counter is too large " << ENDM; + } + for (int word = 0; word < moH->GetNbinsX(); word++) { // Loop over words + if (word > mNWords) { + ILOG(Fatal) << "Word counter is too large " << ENDM; + } + mCrates[crate]->SetBinContent(word + 1, slot + 1, moH->GetBinContent(word + 1, crate + 1)); + } + } + } else { + ILOG(Warning) << "Did not find MO " << moName << " in path " << mCCDBPath << ENDM; + } + } + ILOG(Debug) << "DONE UPDATING !" << ENDM; +} + +void PostProcessDiagnosticPerCrate::finalize(Trigger t, framework::ServiceRegistryRef) +{ + ILOG(Info, Support) << "FINALIZING !" << ENDM; + + for (auto& i : mCrates) { + TCanvas* c = new TCanvas(i->GetName(), i->GetName()); + i->Draw(); + auto mo = std::make_shared(c, "PostProcessDiagnosticPerCrate", "o2::quality_control_modules::tof::PostProcessDiagnosticPerCreate", "TOF"); + mo->setIsOwner(false); + mDatabase->storeMO(mo); + + // It should delete everything inside. Confirmed by trying to delete histo after and getting a segfault. + delete c; + } + ILOG(Info, Support) << "DONE FINALIZING !" << ENDM; +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/PostProcessHitMap.cxx b/Modules/TOF/src/PostProcessHitMap.cxx new file mode 100644 index 0000000000..fd03731a90 --- /dev/null +++ b/Modules/TOF/src/PostProcessHitMap.cxx @@ -0,0 +1,179 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessHitMap.cxx +/// \brief Post processing to produce a plot of the TOF hit map with the reference enabled channels +/// \author Nicolò Jacazio +/// \since 09/07/2022 +/// + +// O2 includes +#include "DataFormatsTOF/TOFFEElightInfo.h" +#include "TOFBase/Geo.h" + +// ROOT includes +#include +#include +#include +#include +#include + +// QualityControl includes +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "TOF/PostProcessHitMap.h" +#include "QualityControl/DatabaseInterface.h" + +using namespace std; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control::core; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::tof +{ + +void PostProcessHitMap::configure(const boost::property_tree::ptree& config) +{ + const std::string baseJsonPath = "qc.postprocessing." + getID() + ".customization."; + mCCDBPath = config.get(baseJsonPath + "CCDBPath", "TOF/MO/TaskDigits/"); + ILOG(Info, Support) << "Setting CCDBPath to " << mCCDBPath << ENDM; + mCCDBPathObject = config.get(baseJsonPath + "CCDBPathObject", "HitMapNoiseFiltered"); + ILOG(Info, Support) << "Setting CCDBPathObject to " << mCCDBPathObject << ENDM; + mRefMapCcdbPath = config.get(baseJsonPath + "RefMapCcdbPath", "/TOF/Calib/FEELIGHT"); + ILOG(Info, Support) << "Setting RefMapCcdbPath to " << mRefMapCcdbPath << ENDM; + mRefMapTimestamp = config.get(baseJsonPath + "RefMapTimestamp", -1); + ILOG(Info, Support) << "Setting RefMapTimestamp to " << mRefMapTimestamp << ENDM; + mDrawRefOnTop = config.get(baseJsonPath + "DrawRefOnTop", false); + ILOG(Info, Support) << "Setting DrawRefOnTop to " << (mDrawRefOnTop ? "true" : "false") << ENDM; +} + +void PostProcessHitMap::initialize(Trigger, framework::ServiceRegistryRef services) +{ + // Setting up services + mDatabase = &services.get(); + + mCanvasMo.reset(); + mCanvasMo = std::make_shared("defaultMap", "defaultMap"); + getObjectsManager()->startPublishing(mCanvasMo.get()); + + mPhosPad.reset(); + mPhosPad = std::make_shared(13.f, 38.f, 16.f, 53.f, "bl"); + mPhosPad->SetTextSize(0.05); + mPhosPad->SetBorderSize(1); + mPhosPad->SetTextColor(kBlack); + mPhosPad->SetFillColor(kGreen); + mPhosPad->SetFillStyle(3004); + mPhosPad->AddText("Red: No Match"); + mPhosPad->AddText("Blu: RefMap"); + mPhosPad->AddText("Green: HitMap"); +} + +void PostProcessHitMap::update(Trigger t, framework::ServiceRegistryRef services) +{ + // Getting the hit map + const auto mo = mDatabase->retrieveMO(mCCDBPath, mCCDBPathObject, t.timestamp, t.activity); + TH2F* h = static_cast(mo ? mo->getObject() : nullptr); + if (!h) { + ILOG(Warning, Devel) << "MO '" << mCCDBPathObject << "' not found in path " << mCCDBPath << " with timestamp " << t.timestamp << ENDM; + return; + } + // Getting the reference map + const o2::tof::TOFFEElightInfo* refmap = nullptr; + refmap = o2::quality_control::core::UserCodeInterface::retrieveConditionAny(mRefMapCcdbPath); + if (!mHistoRefHitMap) { + ILOG(Debug, Devel) << "making new refmap from " << mRefMapCcdbPath << " and timestamp " << mRefMapTimestamp << ENDM; + mHistoRefHitMap.reset(static_cast(h->Clone("ReferenceHitMap"))); + mHistoRefHitMap->SetTitle(Form("%s (With reference)", mHistoRefHitMap->GetTitle())); + mHistoRefHitMap->SetFillColor(kRed); + if (mDrawRefOnTop) { + mHistoRefHitMap->SetFillColor(kBlue); + } + } + mHistoRefHitMap->Reset(); + int det[5] = { 0 }; // Coordinates + int strip = 0; // Strip + + for (auto i = 0; i < refmap->NCHANNELS; i++) { + if (!refmap->getChannelEnabled(i)) { + continue; + } + o2::tof::Geo::getVolumeIndices(i, det); + strip = o2::tof::Geo::getStripNumberPerSM(det[1], det[2]); // Strip index in the SM + const int istrip = mHistoRefHitMap->GetYaxis()->FindBin(strip); + const int crate = det[0] * 4 + det[4] / 12; + const int icrate = mHistoRefHitMap->GetXaxis()->FindBin(0.25f * crate); + mHistoRefHitMap->SetBinContent(icrate, istrip, 1); + } + + if (!mHistoHitMap) { + ILOG(Debug, Devel) << "making new hit map" << ENDM; + mHistoHitMap.reset(static_cast(h->Clone("WithReferenceHitMap"))); + mHistoHitMap->SetFillColor(kGreen); + if (mDrawRefOnTop) { + mHistoHitMap->SetFillColor(kRed); + } + } + mHistoHitMap->Reset(); + for (int i = 1; i <= h->GetNbinsX(); i++) { + for (int j = 1; j <= h->GetNbinsY(); j++) { + mHistoHitMap->SetBinContent(i, j, 0); + if (h->GetBinContent(i, j) > 0) { + mHistoHitMap->SetBinContent(i, j, 1); + } + } + } + + // Finalizing the plot and updating the MO on the CCDB + if (!mHistoRefHitMap) { + ILOG(Warning, Support) << "mHistoRefHitMap undefined, can't finalize" << ENDM; + return; + } + if (!mHistoHitMap) { + ILOG(Warning, Support) << "mHistoHitMap undefined, can't finalize" << ENDM; + return; + } + + mCanvasMo->SetName(mHistoHitMap->GetName()); + mCanvasMo->SetTitle(mHistoHitMap->GetName()); + mCanvasMo->cd(); + mCanvasMo->Clear(); + + mHistoRefHitMap->GetZaxis()->SetRangeUser(0, 1); + mHistoHitMap->GetZaxis()->SetRangeUser(0, 1); + + if (!mDrawRefOnTop) { + mHistoRefHitMap->GetListOfFunctions()->Clear(); + mHistoRefHitMap->Draw("BOX"); + mHistoHitMap->GetListOfFunctions()->Clear(); + mHistoHitMap->Draw("BOXsame"); + + } else { + mHistoHitMap->GetListOfFunctions()->Clear(); + mHistoHitMap->Draw("BOX"); + mHistoRefHitMap->GetListOfFunctions()->Clear(); + mHistoRefHitMap->Draw("BOXsame"); + } + mPhosPad->Draw(); + + // Draw the shifter message + if (mHistoHitMap->GetListOfFunctions()->FindObject(Form("%s_msg", mHistoHitMap->GetName()))) { + mHistoHitMap->GetListOfFunctions()->FindObject(Form("%s_msg", mHistoHitMap->GetName()))->Draw("same"); + } +} + +void PostProcessHitMap::finalize(Trigger, framework::ServiceRegistryRef) +{ + // Only if you don't want it to be published after finalisation. + getObjectsManager()->stopPublishing(mCanvasMo.get()); +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/PostProcessingLostOrbits.cxx b/Modules/TOF/src/PostProcessingLostOrbits.cxx new file mode 100644 index 0000000000..0ae36f34a2 --- /dev/null +++ b/Modules/TOF/src/PostProcessingLostOrbits.cxx @@ -0,0 +1,101 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingLostOrbits.cxx +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +/// + +#include "TOF/PostProcessingLostOrbits.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" + +#include "TOF/Utils.h" + +#include +#include +#include + +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::tof +{ + +PostProcessingLostOrbits::~PostProcessingLostOrbits() +{ +} + +void PostProcessingLostOrbits::configure(const boost::property_tree::ptree& config) +{ + if (const auto& customConfigs = config.get_child_optional("qc.postprocessing." + getID() + ".customization"); customConfigs.has_value()) { + for (const auto& customConfig : customConfigs.value()) { // Plot configuration + if (const auto& customNames = customConfig.second.get_child_optional("name"); customNames.has_value()) { + if (customConfig.second.get("name") == "Nbins") { + mBins = customConfig.second.get("value"); + ILOG(Info, Support) << "Setting Nbins to " << mBins << ENDM; + } else if (customConfig.second.get("name") == "MaxValue") { + mMaxRange = customConfig.second.get("value"); + ILOG(Info, Support) << "Setting Nbins to " << mMaxRange << ENDM; + } else if (customConfig.second.get("name") == "CCDBPath") { + mCCDBPath = customConfig.second.get("value"); + ILOG(Info, Support) << "Setting CCDBPath to " << mCCDBPath << ENDM; + } else if (customConfig.second.get("name") == "MOName") { + mMOName = customConfig.second.get("value"); + ILOG(Info, Support) << "Setting MOName to " << mMOName << ENDM; + } + } + } + } +} + +void PostProcessingLostOrbits::initialize(Trigger, framework::ServiceRegistryRef services) +{ + float bin_width = mMaxRange / mBins; + mHistoOrbitsInTFEfficiency.reset(); + mHistoOrbitsInTFEfficiency = std::make_shared("OrbitsInTFEfficiency", "Fraction of orbits in TF;Fraction of orbits in TF; Counts x n_{crates} ", mBins, bin_width / 2, mMaxRange + bin_width / 2); + getObjectsManager()->startPublishing(mHistoOrbitsInTFEfficiency.get()); + + mDatabase = &services.get(); +} + +void PostProcessingLostOrbits::update(Trigger t, framework::ServiceRegistryRef) +{ + ILOG(Info, Support) << "Trigger type is: " << t.triggerType << ", the timestamp is " << t.timestamp << ENDM; + + if (mHistoOrbitsInTFEfficiency) { + mHistoOrbitsInTFEfficiency->Reset(); + } + + TH1F* tempPerCrateHisto[72]; // 72 crates + for (int i = 0; i < 72; i++) { // loop over crates + tempPerCrateHisto[i] = nullptr; + } + + auto mo = mDatabase->retrieveMO(mCCDBPath, mMOName, t.timestamp, t.activity); + TH2F* moH = static_cast(mo ? mo->getObject() : nullptr); + if (moH) { + ILOG(Info, Support) << "Found MO " << mMOName << " in path " << mCCDBPath << ENDM; + for (int i = 0; i < 72; i++) { // loop over crates + tempPerCrateHisto[i] = (TH1F*)moH->ProjectionY(Form("hPerCrate%i", i), i + 1, i + 1); + mHistoOrbitsInTFEfficiency->Fill(tempPerCrateHisto[i]->Integral() / (32.f * 3.f)); // 32 orbits in Time Frame, 3 readout windows per orbit + } + } else { + ILOG(Warning) << "Did not find MO " << mMOName << " in path " << mCCDBPath << ENDM; + } +} + +void PostProcessingLostOrbits::finalize(Trigger, framework::ServiceRegistryRef) +{ + // Only if you don't want it to be published after finalisation. + getObjectsManager()->stopPublishing(mHistoOrbitsInTFEfficiency.get()); +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/PostProcessingLuminometer.cxx b/Modules/TOF/src/PostProcessingLuminometer.cxx new file mode 100644 index 0000000000..6df1268ada --- /dev/null +++ b/Modules/TOF/src/PostProcessingLuminometer.cxx @@ -0,0 +1,182 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingLuminometer.cxx +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +/// + +#include "TOF/PostProcessingLuminometer.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" + +#include "TOF/Utils.h" + +#include +#include +#include + +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::tof +{ + +PostProcessingLuminometer::~PostProcessingLuminometer() +{ +} + +void PostProcessingLuminometer::configure(const boost::property_tree::ptree& config) +{ + if (const auto& customConfigs = config.get_child_optional("qc.postprocessing." + getID() + ".customization"); customConfigs.has_value()) { + for (const auto& customConfig : customConfigs.value()) { // Plot configuration + if (const auto& customNames = customConfig.second.get_child_optional("name"); customNames.has_value()) { + if (customConfig.second.get("name") == "Nbins") { + mBins = customConfig.second.get("value"); + ILOG(Info, Support) << "Setting Nbins to " << mBins << ENDM; + } else if (customConfig.second.get("name") == "MaxValue") { + mMaxRange = customConfig.second.get("value"); + ILOG(Info, Support) << "Setting Nbins to " << mMaxRange << ENDM; + } else if (customConfig.second.get("name") == "CCDBPath") { + mCCDBPath = customConfig.second.get("value"); + ILOG(Info, Support) << "Setting CCDBPath to " << mCCDBPath << ENDM; + } else if (customConfig.second.get("name") == "ActiveThr") { + mActiveThr = customConfig.second.get("value"); + ILOG(Info, Support) << "Setting mActiveThr to " << mActiveThr << ENDM; + } + } + } + } +} + +void PostProcessingLuminometer::initialize(Trigger, framework::ServiceRegistryRef services) +{ + float bin_width = mMaxRange / mBins; + mHistoOrbitsInTFEfficiency.reset(); + mHistoOrbitsInTFEfficiency = std::make_shared("OrbitsInTFEfficiency", "Fraction of orbits in TF;Fraction of orbits in TF; Counts x n_{crates} ", mBins, bin_width / 2, mMaxRange + bin_width / 2); + getObjectsManager()->startPublishing(mHistoOrbitsInTFEfficiency.get()); + + mHistoLuminometer.reset(); + mHistoLuminometer = std::make_shared("Luminometer", "Luminometer; ; hit_{TOF}/(eff_{RO}f_{active}) ", 1000, 0., 10000); + getObjectsManager()->startPublishing(mHistoLuminometer.get()); + + mDatabase = &services.get(); +} + +void PostProcessingLuminometer::update(Trigger t, framework::ServiceRegistryRef) +{ + ILOG(Info, Support) << "Trigger type is: " << t.triggerType << ", the timestamp is " << t.timestamp << ENDM; + + if (mHistoOrbitsInTFEfficiency) { + mHistoOrbitsInTFEfficiency->Reset(); + } + if (mHistoLuminometer) { + mHistoLuminometer->Reset(); + } + + TH1F* tempPerCrateHisto[72]; // 72 crates + for (int i = 0; i < 72; i++) { // loop over crates + tempPerCrateHisto[i] = nullptr; + } + + auto moEfficiency = mDatabase->retrieveMO(mCCDBPath, mMOEfficiency, t.timestamp, t.activity); + auto moActiveChannels = mDatabase->retrieveMO(mCCDBPath, mMOActiveChannels, t.timestamp, t.activity); + auto moMultiplicity = mDatabase->retrieveMO(mCCDBPath, mMOMultiplicity, t.timestamp, t.activity); + auto moDecodingErrors = mDatabase->retrieveMO(mCCDBPath, mMOdecodingErrors, t.timestamp, t.activity); + + float ROeff = 1.; + float hitMult = 0.; + float activeCh = 1.; + float decodingEff = 1.; + + // Decoding Errors + if (moDecodingErrors) { + TH2D* htemp = static_cast(moDecodingErrors->getObject()); + TH1D* hproj = (TH1D*)htemp->ProjectionY(); + hproj->SetName("decodErr_pro"); + if (hproj->GetBinContent(1) > 0) { + hproj->Scale(0.1 / hproj->GetBinContent(1)); // normalize to the first bin content and divide by 10 (TRMs) + for (int ibin = 3; ibin < hproj->GetNbinsX(); ibin++) { // count on TRM errors (skip last bin = DRM errors) + decodingEff -= hproj->GetBinContent(ibin); + } + if (decodingEff < 1E-2) { + ILOG(Warning) << "decodingEff = " << decodingEff << " -> it is too low? Why? ... skipping such a correction " << ENDM; + decodingEff = 1.; + } + } + } else { + ILOG(Warning) << "Did not find MO " << moDecodingErrors << " in path " << mCCDBPath << ENDM; + } + + // Readout efficiency + TH2F* moHEfficiency = static_cast(moEfficiency ? moEfficiency->getObject() : nullptr); + if (moHEfficiency) { + ILOG(Info, Support) << "Found MO " << mMOEfficiency << " in path " << mCCDBPath << ENDM; + for (int i = 0; i < 72; i++) { // loop over crates + tempPerCrateHisto[i] = (TH1F*)moHEfficiency->ProjectionY(Form("hPerCrate%i", i), i + 1, i + 1); + mHistoOrbitsInTFEfficiency->Fill(tempPerCrateHisto[i]->Integral() / (32.f * 3.f)); // 32 orbits in Time Frame, 3 readout windows per orbit + } + } else { + ILOG(Warning) << "Did not find MO " << mMOEfficiency << " in path " << mCCDBPath << ENDM; + } + + if (mHistoOrbitsInTFEfficiency) { + ROeff = mHistoOrbitsInTFEfficiency->GetMean(); + } + + // Active channels + TH2F* moHActiveChannels = static_cast(moActiveChannels ? moActiveChannels->getObject() : nullptr); + if (moHActiveChannels) { + ILOG(Info, Support) << "Found MO " << mMOActiveChannels << " in path " << mCCDBPath << ENDM; + + int counterAll = 0; + int counterActive = 0; + + for (int i = 1; i <= moHActiveChannels->GetNbinsX(); i++) { + for (int j = 1; j <= moHActiveChannels->GetNbinsY(); j++) { + counterAll++; + + if (moHActiveChannels->GetBinContent(i, j) > mActiveThr) { + counterActive++; + } + } + } + + activeCh = (float)counterActive / counterAll; + } else { + ILOG(Warning) << "Did not find MO " << mMOActiveChannels << " in path " << mCCDBPath << ENDM; + } + + // Multiplicity + TH1F* moHMultiplicity = static_cast(moMultiplicity ? moMultiplicity->getObject() : nullptr); + if (moHMultiplicity) { + ILOG(Info, Support) << "Found MO " << mMOMultiplicity << " in path " << mCCDBPath << ENDM; + hitMult = moHMultiplicity->GetMean(); + } else { + ILOG(Warning) << "Did not find MO " << mMOMultiplicity << " in path " << mCCDBPath << ENDM; + } + + mHistoLuminometer->Fill(hitMult / (activeCh * ROeff * decodingEff)); + ILOG(Info) << "____________________" << ENDM; + ILOG(Info) << "Luminometer summary " << ENDM; + ILOG(Info) << "decodingEff = " << decodingEff << ENDM; + ILOG(Info) << "activeCh = " << activeCh << ENDM; + ILOG(Info) << "ROeff = " << ROeff << ENDM; + ILOG(Info) << "hitMult = " << hitMult << ENDM; + ILOG(Info) << "____________________" << ENDM; +} + +void PostProcessingLuminometer::finalize(Trigger, framework::ServiceRegistryRef) +{ + // Only if you don't want it to be published after finalisation. + getObjectsManager()->stopPublishing(mHistoLuminometer.get()); +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/TH1ReductorTOF.cxx b/Modules/TOF/src/TH1ReductorTOF.cxx new file mode 100644 index 0000000000..3fda3d2569 --- /dev/null +++ b/Modules/TOF/src/TH1ReductorTOF.cxx @@ -0,0 +1,45 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1ReductorTOFTOF.cxx +/// \author Francesca Ercolessi +/// + +#include +#include "TOF/TH1ReductorTOF.h" + +namespace o2::quality_control_modules::tof +{ + +void* TH1ReductorTOF::getBranchAddress() +{ + return &mStats; +} + +const char* TH1ReductorTOF::getBranchLeafList() +{ + return "mean/D:stddev:entries:maxpeak"; +} + +void TH1ReductorTOF::update(TObject* obj) +{ + // todo: use GetStats() instead? + auto histo = dynamic_cast(obj); + if (histo) { + mStats.entries = histo->GetEntries(); + mStats.stddev = histo->GetStdDev(); + mStats.mean = histo->GetMean(); + mStats.maxpeak = histo->GetBinCenter(histo->GetMaximumBin()); + } +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/TOFMatchedTracks.cxx b/Modules/TOF/src/TOFMatchedTracks.cxx new file mode 100644 index 0000000000..7eda6bade4 --- /dev/null +++ b/Modules/TOF/src/TOFMatchedTracks.cxx @@ -0,0 +1,959 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TOFMatchedTracks.cxx +/// \author Chiara Zampolli +/// \brief Task to monitor TOF matching efficiency +/// \since 03/08/2021 +/// + +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "TOF/TOFMatchedTracks.h" +#include +#include +#include +#include "GlobalTrackingWorkflowHelpers/InputHelper.h" +#include "ReconstructionDataFormats/TrackTPCITS.h" +#include "ReconstructionDataFormats/TrackTPCITS.h" +#include "DataFormatsTPC/TrackTPC.h" +#include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" +#include "ReconstructionDataFormats/TrackParametrization.h" +#include "DetectorsBase/Propagator.h" + +#include "TOF/Utils.h" +#include "TOFBase/Geo.h" +#include "DataFormatsTOF/Cluster.h" + +using GTrackID = o2::dataformats::GlobalTrackID; + +namespace o2::quality_control_modules::tof +{ + +TOFMatchedTracks::~TOFMatchedTracks() +{ + if (mUseMC) { + delete mDeltaTwMC; + } + + for (int i = 0; i < matchType::SIZE; ++i) { + delete mMatchedTracksPt[i]; + delete mMatchedTracksEta[i]; + delete mMatchedTracks2DPtEta[i]; + if (mUseMC) { + delete mFakeMatchedTracksPt[i]; + delete mFakeMatchedTracksEta[i]; + delete mExpTimesPiMC[i]; + delete mExpTimesKaMC[i]; + delete mExpTimesPrMC[i]; + } + delete mInTracksPt[i]; + delete mInTracksEta[i]; + delete mInTracks2DPtEta[i]; + delete mEffPt[i]; + delete mEffEta[i]; + delete mEff2DPtEta[i]; + delete mDeltaZEta[i]; + delete mDeltaZPhi[i]; + delete mDeltaZPt[i]; + delete mDeltaXEta[i]; + delete mDeltaXPhi[i]; + delete mDeltaXPt[i]; + delete mTOFChi2[i]; + delete mTOFChi2Pt[i]; + } + for (int isec = 0; isec < 18; isec++) { + delete mDTimeTrk[isec]; + delete mDTimeTrkTPC[isec]; + delete mDTimeTrkTRD[isec]; + } +} + +void TOFMatchedTracks::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TOFMatchedTracks" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + + utils::parseBooleanParameter(mCustomParameters, "isMC", mUseMC); + utils::parseBooleanParameter(mCustomParameters, "verbose", mVerbose); + + // for track selection + if (auto param = mCustomParameters.find("minPtCut"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - minPtCut (for track selection): " << param->second << ENDM; + setPtCut(atof(param->second.c_str())); + } + if (auto param = mCustomParameters.find("etaCut"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - etaCut (for track selection): " << param->second << ENDM; + setEtaCut(atof(param->second.c_str())); + } + if (auto param = mCustomParameters.find("minNTPCClustersCut"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - minNTPCClustersCut (for track selection): " << param->second << ENDM; + setMinNTPCClustersCut(atoi(param->second.c_str())); + } + if (auto param = mCustomParameters.find("minDCACut"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - minDCACut (for track selection): " << param->second << ENDM; + setMinDCAtoBeamPipeDistanceCut(atof(param->second.c_str())); + } + if (auto param = mCustomParameters.find("minDCACutY"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - minDCACutY (for track selection): " << param->second << ENDM; + setMinDCAtoBeamPipeYCut(atof(param->second.c_str())); + } + if (auto param = mCustomParameters.find("minPtExpert"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - minPtExpert (for filling extra histos): " << param->second << ENDM; + mThresholdPtForExperts = atof(param->second.c_str()); + if (mThresholdPtForExperts < 1.0) { + ILOG(Debug, Devel) << "!!! - minPtExpert too low... forced to 1 GeV/c " << ENDM; + mThresholdPtForExperts = 1.0; + } + } + + // for track type selection + if (auto param = mCustomParameters.find("GID"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - GID (= sources by user): " << param->second << ENDM; + ILOG(Info, Devel) << "Allowed Sources = " << mAllowedSources << ENDM; + mSrc = mAllowedSources & GID::getSourcesMask(param->second); + ILOG(Info, Devel) << "Final requested sources = " << mSrc << ENDM; + } + + mDataRequest = std::make_shared(); + mDataRequest->requestTracks(mSrc, mUseMC); + + if ((mSrc[GID::Source::TPCTOF] == 1 && mSrc[GID::Source::TPC] == 0) || (mSrc[GID::Source::TPCTOF] == 0 && mSrc[GID::Source::TPC] == 1)) { + ILOG(Fatal, Support) << "Check the requested sources: TPCTOF = " << mSrc[GID::Source::TPCTOF] << ", TPC = " << mSrc[GID::Source::TPC] << ENDM; + } + + if ((mSrc[GID::Source::ITSTPCTOF] == 1 && mSrc[GID::Source::ITSTPC] == 0) || (mSrc[GID::Source::ITSTPCTOF] == 0 && mSrc[GID::Source::ITSTPC] == 1)) { + ILOG(Fatal, Support) << "Check the requested sources: ITSTPCTOF = " << mSrc[GID::Source::ITSTPCTOF] << ", ITSTPC = " << mSrc[GID::Source::ITSTPC] << ENDM; + } + + if ((mSrc[GID::Source::TPCTRDTOF] == 1 && mSrc[GID::Source::TPCTRD] == 0) || (mSrc[GID::Source::TPCTRDTOF] == 0 && mSrc[GID::Source::TPCTRD] == 1)) { + ILOG(Fatal, Support) << "Check the requested sources: TPCTRDTOF = " << mSrc[GID::Source::TPCTRDTOF] << ", TPCTRD = " << mSrc[GID::Source::TPCTRD] << ENDM; + } + + if ((mSrc[GID::Source::ITSTPCTRDTOF] == 1 && mSrc[GID::Source::ITSTPCTRD] == 0) || (mSrc[GID::Source::ITSTPCTRDTOF] == 0 && mSrc[GID::Source::ITSTPCTRD] == 1)) { + ILOG(Fatal, Support) << "Check the requested sources: ITSTPCTRDTOF = " << mSrc[GID::Source::ITSTPCTRDTOF] << ", ITSTPCTRD = " << mSrc[GID::Source::ITSTPCTRD] << ENDM; + } + + std::array title{ "TPC", "ITSTPC-ITSTPCTRD", "TPCTRD" }; + + if (mUseMC) { + mDeltaTwMC = new TH1F("mDeltaTwMC", "all types;t_{TOF} - t^{0}_{MC} - t_{geant} (ps)", 100, -500, 500); + } + + for (int i = 0; i < matchType::SIZE; ++i) { + mInTracksPt[i] = new TH1F(Form("mInTracksPt_%s", title[i].c_str()), Form("mInTracksPt (matchType: %s); #it{p}_{T}; counts", title[i].c_str()), 100, 0.f, 20.f); + mInTracksEta[i] = new TH1F(Form("mInTracksEta_%s", title[i].c_str()), Form("mInTracksEta (matchType: %s); #eta; counts", title[i].c_str()), 100, -1.0f, 1.0f); + mInTracks2DPtEta[i] = new TH2F(Form("mInTracks2DPtEta_%s", title[i].c_str()), Form("mInTracks2DPtEta (matchType: %s); #it{p}_{T}; #eta; counts", title[i].c_str()), 100, 0.f, 20.f, 100, -1.0f, 1.0f); + mMatchedTracksPt[i] = new TH1F(Form("mMatchedTracksPt_%s", title[i].c_str()), Form("mMatchedTracksPt (matchType: %s); #it{p}_{T}; counts", title[i].c_str()), 100, 0.f, 20.f); + mMatchedTracksEta[i] = new TH1F(Form("mMatchedTracksEta_%s", title[i].c_str()), Form("mMatchedTracksEta (matchType: %s); #eta; counts", title[i].c_str()), 100, -1.0f, 1.0f); + mMatchedTracks2DPtEta[i] = new TH2F(Form("mMatchedTracks2DPtEta_%s", title[i].c_str()), Form("mMatchedTracks2DPtEta (matchType: %s); #it{p}_{T}; #eta; counts", title[i].c_str()), 100, 0.f, 20.f, 100, -1.0f, 1.0f); + if (mUseMC) { + mFakeMatchedTracksPt[i] = new TH1F(Form("mFakeMatchedTracksPt_%s", title[i].c_str()), Form("mFakeMatchedTracksPt (matchType: %s); #it{p}_{T}; counts", title[i].c_str()), 100, 0.f, 20.f); + mFakeMatchedTracksEta[i] = new TH1F(Form("mFakeMatchedTracksEta_%s", title[i].c_str()), Form("mFakeMatchedTracksEta (matchType: %s); #eta; counts", title[i].c_str()), 100, -1.0f, 1.0f); + mFakeFractionTracksPt[i] = new TEfficiency(Form("mFakeFractionPt_%s", title[i].c_str()), Form("Fraction of fake matches vs Pt (matchType: %s); #it{p}_{T}; Eff", title[i].c_str()), 100, 0.f, 20.f); + mFakeFractionTracksEta[i] = new TEfficiency(Form("mFakeFractionEta_%s", title[i].c_str()), Form("Fraction of fake matches vs Eta (matchType: %s); #eta; Eff", title[i].c_str()), 100, -1.0f, 1.0f); + mExpTimesPiMC[i] = new TH2F(Form("mExpTimesPiMC_%s", title[i].c_str()), ";p_{T} (GeV/c);t_{geant} - t_{exp}^{#pi} (ps)", 10, 0, 2, 100, -1000, 1000); + mExpTimesKaMC[i] = new TH2F(Form("mExpTimesKaMC_%s", title[i].c_str()), ";p_{T} (GeV/c);t_{geant} - t_{exp}^{K} (ps)", 10, 0, 2, 100, -1000, 1000); + mExpTimesPrMC[i] = new TH2F(Form("mExpTimesPrMC_%s", title[i].c_str()), ";p_{T} (GeV/c);t_{geant} - t_{exp}^{p} (ps)", 10, 0, 2, 100, -1000, 1000); + } + mEffPt[i] = new TEfficiency(Form("mEffPt_%s", title[i].c_str()), Form("Efficiency vs Pt (matchType: %s); #it{p}_{T}; Eff", title[i].c_str()), 100, 0.f, 20.f); + mEffEta[i] = new TEfficiency(Form("mEffEta_%s", title[i].c_str()), Form("Efficiency vs Eta (matchType: %s); #eta; Eff", title[i].c_str()), 100, -1.f, 1.f); + mEff2DPtEta[i] = new TEfficiency(Form("mEff2DPtEta_%s", title[i].c_str()), Form("Efficiency vs Pt vs Eta (matchType: %s); #it{p}_{T}; #eta; Eff", title[i].c_str()), 100, 0.f, 20.f, 100, -1.0f, 1.0f); + mDeltaZEta[i] = new TH2F(Form("mDeltaZEta%s", title[i].c_str()), Form("mDeltaZEta (matchType: %s); #eta; #Delta z (cm); counts", title[i].c_str()), 100, -1.0f, 1.0f, 100, -10.f, 10.f); + mDeltaZPhi[i] = new TH2F(Form("mDeltaZPhi%s", title[i].c_str()), Form("mDeltaZPhi (matchType: %s); #phi; #Delta z (cm); counts", title[i].c_str()), 100, .0f, 6.3f, 100, -10.f, 10.f); + mDeltaZPt[i] = new TH2F(Form("mDeltaZPt%s", title[i].c_str()), Form("mDeltaZPt (matchType: %s); #it{p}_{T}; #Delta z (cm); counts", title[i].c_str()), 100, 0.f, 20.f, 100, -10.f, 10.f); + mDeltaXEta[i] = new TH2F(Form("mDeltaXEta%s", title[i].c_str()), Form("mDeltaXEta (matchType: %s); #eta; #Delta x (cm); counts", title[i].c_str()), 100, -1.0f, 1.0f, 100, -10.f, 10.f); + mDeltaXPhi[i] = new TH2F(Form("mDeltaXPhi%s", title[i].c_str()), Form("mDeltaXPhi (matchType: %s); #phi; #Delta x (cm); counts", title[i].c_str()), 100, .0f, 6.3f, 100, -10.f, 10.f); + mDeltaXPt[i] = new TH2F(Form("mDeltaXPt%s", title[i].c_str()), Form("mDeltaXPt (matchType: %s); #it{p}_{T}; #Delta z (cm); counts", title[i].c_str()), 100, 0.f, 20.f, 100, -10.f, 10.f); + mTOFChi2[i] = new TH1F(Form("mTOFChi2%s", title[i].c_str()), Form("mTOFChi2 (matchType: %s); #chi^{2}; counts", title[i].c_str()), 100, 0.f, 10.f); + mTOFChi2Pt[i] = new TH2F(Form("mTOFChi2Pt%s", title[i].c_str()), Form("mTOFChi2Pt (matchType: %s); #it{p}_{T}; #chi^{2}; counts", title[i].c_str()), 100, 0.f, 20.f, 100, 0.f, 10.f); + } + + for (int isec = 0; isec < 18; isec++) { + mDTimeTrk[isec] = new TH2F(Form("DTimeTrk_sec%02d", isec), Form("Sector %d: ITS-TPC track-tof #Deltat vs #eta; #eta; #Deltat (# BC)", isec), 50, -1.0f, 1.0f, 500, -200, 200); + getObjectsManager()->startPublishing(mDTimeTrk[isec]); + mDTimeTrkTPC[isec] = new TH2F(Form("DTimeTrkTPC_sec%02d", isec), Form("Sector %d: TPC track-tof #Deltat vs #eta; #eta; #Deltat (# BC)", isec), 50, -1.0f, 1.0f, 500, -200, 200); + if ((mSrc & o2::dataformats::GlobalTrackID::getSourcesMask("TPC")).any()) { + getObjectsManager()->startPublishing(mDTimeTrkTPC[isec]); + } + mDTimeTrkTRD[isec] = new TH2F(Form("DTimeTrkTRD_sec%02d", isec), Form("Sector %d: ITS-TPC-TRD track-tof #Deltat vs #eta; #eta; #Deltat (# BC)", isec), 100, -1.0f, 1.0f, 200, -5, 5); + if ((mSrc & o2::dataformats::GlobalTrackID::getSourcesMask("ITS-TPC-TRD")).any()) { + getObjectsManager()->startPublishing(mDTimeTrkTRD[isec]); + } + } + + if (mUseMC) { + getObjectsManager()->startPublishing(mDeltaTwMC); + } + + if (mSrc[GID::Source::TPCTOF] == 1) { + getObjectsManager()->startPublishing(mInTracksPt[matchType::TPC]); + getObjectsManager()->startPublishing(mInTracksEta[matchType::TPC]); + getObjectsManager()->startPublishing(mInTracks2DPtEta[matchType::TPC]); + getObjectsManager()->startPublishing(mMatchedTracksPt[matchType::TPC]); + getObjectsManager()->startPublishing(mMatchedTracksEta[matchType::TPC]); + getObjectsManager()->startPublishing(mMatchedTracks2DPtEta[matchType::TPC]); + if (mUseMC) { + getObjectsManager()->startPublishing(mFakeMatchedTracksPt[matchType::TPC]); + getObjectsManager()->startPublishing(mFakeMatchedTracksEta[matchType::TPC]); + getObjectsManager()->startPublishing(mFakeFractionTracksPt[matchType::TPC]); + getObjectsManager()->startPublishing(mFakeFractionTracksEta[matchType::TPC]); + getObjectsManager()->startPublishing(mExpTimesPiMC[matchType::TPC]); + getObjectsManager()->startPublishing(mExpTimesKaMC[matchType::TPC]); + getObjectsManager()->startPublishing(mExpTimesPrMC[matchType::TPC]); + } + getObjectsManager()->startPublishing(mEffPt[matchType::TPC]); + getObjectsManager()->startPublishing(mEffEta[matchType::TPC]); + getObjectsManager()->startPublishing(mEff2DPtEta[matchType::TPC]); + getObjectsManager()->startPublishing(mDeltaZEta[matchType::TPC]); + getObjectsManager()->startPublishing(mDeltaZPhi[matchType::TPC]); + getObjectsManager()->startPublishing(mDeltaZPt[matchType::TPC]); + getObjectsManager()->startPublishing(mDeltaXEta[matchType::TPC]); + getObjectsManager()->startPublishing(mDeltaXPhi[matchType::TPC]); + getObjectsManager()->startPublishing(mDeltaXPt[matchType::TPC]); + getObjectsManager()->startPublishing(mTOFChi2[matchType::TPC]); + getObjectsManager()->startPublishing(mTOFChi2Pt[matchType::TPC]); + } + + if (mSrc[GID::Source::TPCTRDTOF] == 1) { + getObjectsManager()->startPublishing(mInTracksPt[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mInTracksEta[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mInTracks2DPtEta[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mMatchedTracksPt[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mMatchedTracksEta[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mMatchedTracks2DPtEta[matchType::TPCTRD]); + if (mUseMC) { + getObjectsManager()->startPublishing(mFakeMatchedTracksPt[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mFakeMatchedTracksEta[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mFakeFractionTracksPt[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mFakeFractionTracksEta[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mExpTimesPiMC[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mExpTimesKaMC[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mExpTimesPrMC[matchType::TPCTRD]); + } + getObjectsManager()->startPublishing(mEffPt[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mEffEta[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mEff2DPtEta[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mDeltaZEta[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mDeltaZPhi[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mDeltaZPt[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mDeltaXEta[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mDeltaXPhi[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mDeltaXPt[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mTOFChi2[matchType::TPCTRD]); + getObjectsManager()->startPublishing(mTOFChi2Pt[matchType::TPCTRD]); + } + + if (mSrc[GID::Source::ITSTPCTOF] == 1 || mSrc[GID::Source::ITSTPCTRDTOF] == 1) { + getObjectsManager()->startPublishing(mInTracksPt[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mInTracksEta[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mInTracks2DPtEta[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mMatchedTracksEta[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mMatchedTracks2DPtEta[matchType::ITSTPC_ITSTPCTRD]); + if (mUseMC) { + getObjectsManager()->startPublishing(mFakeMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mFakeMatchedTracksEta[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mFakeFractionTracksPt[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mFakeFractionTracksEta[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mExpTimesPiMC[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mExpTimesKaMC[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mExpTimesPrMC[matchType::ITSTPC_ITSTPCTRD]); + } + getObjectsManager()->startPublishing(mEffPt[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mEffEta[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mEff2DPtEta[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mDeltaZEta[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mDeltaZPhi[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mDeltaZPt[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mDeltaXEta[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mDeltaXPhi[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mDeltaXPt[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mTOFChi2[matchType::ITSTPC_ITSTPCTRD]); + getObjectsManager()->startPublishing(mTOFChi2Pt[matchType::ITSTPC_ITSTPCTRD]); + } + + mHistoExpTrackedStrip = new TH1F("mHistoExpTrackedStrip", "Tracked vs Strips from (eta,phi);strip; entries", 91 * 18, 0, 91 * 18); + getObjectsManager()->startPublishing(mHistoExpTrackedStrip); + mHistoExpMatchedStrip = new TH1F("mHistoExpMatchedStrip", "Matched vs Strips from (eta,phi);strip; entries", 91 * 18, 0, 91 * 18); + getObjectsManager()->startPublishing(mHistoExpMatchedStrip); + mHistoExpMatchedStripFromCh = new TH1F("mHistoExpMatchedStripFromCh", "Matched vs Strips from channel;strip; entries", 91 * 18, 0, 91 * 18); + getObjectsManager()->startPublishing(mHistoExpMatchedStripFromCh); + + mHistoExpMatchedStripFromChDx = new TH2F("mHistoExpMatchedStripFromChDx", "Matched vs Strips from channel;strip; #DeltaX (cm)", 91 * 18, 0, 91 * 18, 100, -10, 10); + getObjectsManager()->startPublishing(mHistoExpMatchedStripFromChDx); + mHistoExpMatchedStripFromChDz = new TH2F("mHistoExpMatchedStripFromChDz", "Matched vs Strips from channel;strip; #DeltaZ (cm)", 91 * 18, 0, 91 * 18, 100, -10, 10); + getObjectsManager()->startPublishing(mHistoExpMatchedStripFromChDz); + mHistoExpMatchedStripFromChR = new TH2F("mHistoExpMatchedStripFromChR", "Matched vs Strips from channel;strip; Residual (cm)", 91 * 18, 0, 91 * 18, 100, 0, 10); + getObjectsManager()->startPublishing(mHistoExpMatchedStripFromChR); + + mHistoExpMatchedNhit = new TH2F("mHistoExpMatchedNhit", "N hits in TOF cluster, per strip from channel; strip; n_{hit}", 91 * 18, 0, 91 * 18, 6, 0, 6); + getObjectsManager()->startPublishing(mHistoExpMatchedNhit); +} + +void TOFMatchedTracks::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + reset(); +} + +void TOFMatchedTracks::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TOFMatchedTracks::monitorData(o2::framework::ProcessingContext& ctx) +{ + + ++mTF; + LOG(debug) << " ************************ "; + LOG(debug) << " *** Processing TF " << mTF << " *** "; + LOG(debug) << " ************************ "; + mRecoCont.collectData(ctx, *mDataRequest.get()); + + // Getting the B field + mBz = o2::base::Propagator::Instance()->getNominalBz(); + + // TOF + gsl::span tofClusArray = mRecoCont.getTOFClusters(); + + // TPC + if (mRecoCont.isTrackSourceLoaded(GID::TPC) || mRecoCont.isTrackSourceLoaded(GID::TPCTOF)) { // this is enough to know that also TPC was loades, see "initialize" + mTPCTracks = mRecoCont.getTPCTracks(); + } + + // TPC-TOF + if (mRecoCont.isTrackSourceLoaded(GID::TPCTOF)) { // this is enough to know that also TPC was loades, see "initialize" + mTPCTOFMatches = mRecoCont.getTPCTOFMatches(); + LOG(debug) << "We found " << mTPCTracks.size() << " TPC-only tracks"; + LOG(debug) << "We found " << mRecoCont.getTPCTOFTracks().size() << " TPC-TOF tracks"; + LOG(debug) << "We found " << mTPCTOFMatches.size() << " TPC-only tracks matched to TOF"; + if (mRecoCont.getTPCTOFTracks().size() != mTPCTOFMatches.size()) { + ILOG(Fatal, Support) << "Number of TPCTOF tracks (" << mRecoCont.getTPCTOFTracks().size() << ") differs from number of TPCTOF matches (" << mTPCTOFMatches.size() << ")" << ENDM; + } + // loop over TOF MatchInfo + for (const auto& matchTOF : mTPCTOFMatches) { + GTrackID gTrackId = matchTOF.getTrackRef(); + const auto& trk = mTPCTracks[gTrackId.getIndex()]; + const auto& trkDz = matchTOF.getDZatTOF(); + const auto& trkDx = matchTOF.getDXatTOF(); + const auto& trkchi2 = matchTOF.getChi2(); + if (!selectTrack(trk)) { + LOG(debug) << "NUM UNCONS: track with eta " << trk.getEta() << " and pt " << trk.getPt() << " DISCARDED for numerator, UNCONS"; + continue; + } + LOG(debug) << "NUM UNCONS: track with eta " << trk.getEta() << " and pt " << trk.getPt() << " ACCEPTED for numerator, UNCONS"; + mMatchedTracksPt[matchType::TPC]->Fill(trk.getPt()); + mMatchedTracksEta[matchType::TPC]->Fill(trk.getEta()); + mMatchedTracks2DPtEta[matchType::TPC]->Fill(trk.getPt(), trk.getEta()); + mDeltaZEta[matchType::TPC]->Fill(trk.getEta(), trkDz); + mDeltaZPhi[matchType::TPC]->Fill(trk.getPhi(), trkDz); + mDeltaZPt[matchType::TPC]->Fill(trk.getPt(), trkDz); + mDeltaXEta[matchType::TPC]->Fill(trk.getEta(), trkDx); + mDeltaXPhi[matchType::TPC]->Fill(trk.getPhi(), trkDx); + mDeltaXPt[matchType::TPC]->Fill(trk.getPt(), trkDx); + mTOFChi2[matchType::TPC]->Fill(trkchi2); + mTOFChi2Pt[matchType::TPC]->Fill(trk.getPt(), trkchi2); + + if (trk.getPt() > 1.0) { + const double bcTimeInvInMus = o2::tof::Geo::BC_TIME_INV * 1E3; + float deltaTrackTimeInBC = -matchTOF.getDeltaT() * bcTimeInvInMus; // track time - tof time in number of BC + auto& tofCl = tofClusArray[matchTOF.getTOFClIndex()]; + int isec = tofCl.getMainContributingChannel() / 8736; + if (isec >= 0 && isec < 18) { + mDTimeTrkTPC[isec]->Fill(trk.getEta(), deltaTrackTimeInBC); + } + } + + if (mUseMC) { + auto lbl = mRecoCont.getTrackMCLabel(gTrackId); + if (lbl.isFake()) { + mFakeMatchedTracksPt[matchType::TPC]->Fill(trk.getPt()); + mFakeMatchedTracksEta[matchType::TPC]->Fill(trk.getEta()); + } else { + mDeltaTwMC->Fill(matchTOF.getSignal() - matchTOF.getT0true() - matchTOF.getTgeant() * 1E3); + mExpTimesPiMC[matchType::TPC]->Fill(trk.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(2)); + mExpTimesKaMC[matchType::TPC]->Fill(trk.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(3)); + mExpTimesPrMC[matchType::TPC]->Fill(trk.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(4)); + } + } + } + } + + // ITS-TPC-TOF + if (mRecoCont.isTrackSourceLoaded(GID::ITSTPCTOF)) { // this is enough to know that also ITSTPC was loades, see "initialize" + mITSTPCTracks = mRecoCont.getTPCITSTracks(); + mITSTPCTOFMatches = mRecoCont.getITSTPCTOFMatches(); + LOG(debug) << "We found " << mITSTPCTracks.size() << " ITS-TPC tracks"; + LOG(debug) << "We found " << mITSTPCTOFMatches.size() << " ITS-TPC tracks matched to TOF"; + // loop over TOF MatchInfo + for (const auto& matchTOF : mITSTPCTOFMatches) { + GTrackID gTrackId = matchTOF.getTrackRef(); + const auto& trk = mITSTPCTracks[gTrackId.getIndex()]; + const auto& trkTPC = mTPCTracks[trk.getRefTPC()]; + const auto& trkDz = matchTOF.getDZatTOF(); + const auto& trkDx = matchTOF.getDXatTOF(); + const auto& trkchi2 = matchTOF.getChi2(); + if (!selectTrack(trkTPC)) { + LOG(debug) << "NUM ITSTPC CONSTR: track with eta " << trkTPC.getEta() << " and pT " << trkTPC.getPt() << " DISCARDED for numerator, ITSTPC CONSTR"; + continue; + } + LOG(debug) << "NUM ITSTPC CONSTR: track with eta " << trkTPC.getEta() << " and pT " << trkTPC.getPt() << " ACCEPTED for numerator, ITSTPC CONSTR" + << " gid: " << gTrackId << " TPC gid =" << trk.getRefTPC(); + mMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt()); + + if (trkTPC.getPt() > 1.0) { + const double bcTimeInvInMus = o2::tof::Geo::BC_TIME_INV * 1E3; + float deltaTrackTimeInBC = -matchTOF.getDeltaT() * bcTimeInvInMus; // track time - tof time in number of BC + auto& tofCl = tofClusArray[matchTOF.getTOFClIndex()]; + int isec = tofCl.getMainContributingChannel() / 8736; + if (isec >= 0 && isec < 18) { + mDTimeTrk[isec]->Fill(trkTPC.getEta(), deltaTrackTimeInBC); + } + if (trkTPC.getPt() > mThresholdPtForExperts) { + std::array globalPos; + trk.getParamOut().getXYZGlo(globalPos); + + float eta = trk.getEta(); + float phi = trk.getPhi(); + phi = TMath::ATan2(globalPos[1], globalPos[0]); + + fillNumeratorForExperts(eta, phi, tofCl.getMainContributingChannel(), tofCl.getNumOfContributingChannels(), matchTOF.getDXatTOF(), matchTOF.getDZatTOF(), matchTOF.getChi2()); + } + } + + mMatchedTracksEta[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getEta()); + mMatchedTracks2DPtEta[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), trkTPC.getEta()); + mDeltaZEta[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getEta(), trkDz); + mDeltaZPhi[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPhi(), trkDz); + mDeltaZPt[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), trkDz); + mDeltaXEta[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getEta(), trkDx); + mDeltaXPhi[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPhi(), trkDx); + mDeltaXPt[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), trkDx); + mTOFChi2[matchType::ITSTPC_ITSTPCTRD]->Fill(trkchi2); + mTOFChi2Pt[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), trkchi2); + if (mUseMC) { + auto lbl = mRecoCont.getTrackMCLabel(gTrackId); + if (lbl.isFake()) { + mFakeMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt()); + mFakeMatchedTracksEta[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getEta()); + } else { + mDeltaTwMC->Fill(matchTOF.getSignal() - matchTOF.getT0true() - matchTOF.getTgeant() * 1E3); + mExpTimesPiMC[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(2)); + mExpTimesKaMC[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(3)); + mExpTimesPrMC[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(4)); + } + } + } + } + + // TPC-TRD-TOF + if (mRecoCont.isTrackSourceLoaded(GID::TPCTRDTOF)) { // this is enough to know that also TPCTRD was loades, see "initialize" + mTPCTRDTracks = mRecoCont.getTPCTRDTracks(); + mTPCTRDTOFMatches = mRecoCont.getTPCTRDTOFMatches(); + LOG(debug) << "We found " << mTPCTRDTracks.size() << " TPC-TRD tracks"; + LOG(debug) << "We found " << mTPCTRDTOFMatches.size() << " TPC-TRD tracks matched to TOF"; + // loop over TOF MatchInfo + for (const auto& matchTOF : mTPCTRDTOFMatches) { + GTrackID gTrackId = matchTOF.getTrackRef(); + const auto& trktpctrd = mTPCTRDTracks[gTrackId.getIndex()]; + const auto& trk = mTPCTracks[trktpctrd.getRefGlobalTrackId()]; + const auto& trkDz = matchTOF.getDZatTOF(); + const auto& trkDx = matchTOF.getDXatTOF(); + const auto& trkchi2 = matchTOF.getChi2(); + if (!selectTrack(trk)) { + LOG(debug) << "NUM TPCTRD CONSTR: track with eta " << trk.getEta() << " and pt " << trk.getPt() << " DISCARDED for numerator, TPCTRD CONSTR"; + continue; + } + LOG(debug) << "NUM TPCTRD CONSTR: track with eta " << trk.getEta() << " and pt " << trk.getPt() << " ACCEPTED for numerator, TPCTRD CONSTR"; + mMatchedTracksPt[matchType::TPCTRD]->Fill(trk.getPt()); + mMatchedTracksEta[matchType::TPCTRD]->Fill(trk.getEta()); + mMatchedTracks2DPtEta[matchType::TPCTRD]->Fill(trk.getPt(), trk.getEta()); + mDeltaZEta[matchType::TPCTRD]->Fill(trk.getEta(), trkDz); + mDeltaZPhi[matchType::TPCTRD]->Fill(trk.getPhi(), trkDz); + mDeltaZPt[matchType::TPCTRD]->Fill(trk.getPt(), trkDz); + mDeltaXEta[matchType::TPCTRD]->Fill(trk.getEta(), trkDx); + mDeltaXPhi[matchType::TPCTRD]->Fill(trk.getPhi(), trkDx); + mDeltaXPt[matchType::TPCTRD]->Fill(trk.getPt(), trkDx); + mTOFChi2[matchType::TPCTRD]->Fill(trkchi2); + mTOFChi2Pt[matchType::TPCTRD]->Fill(trk.getPt(), trkchi2); + if (mUseMC) { + auto lbl = mRecoCont.getTrackMCLabel(gTrackId); + if (lbl.isFake()) { + mFakeMatchedTracksPt[matchType::TPCTRD]->Fill(trk.getPt()); + mFakeMatchedTracksEta[matchType::TPCTRD]->Fill(trk.getEta()); + } else { + mDeltaTwMC->Fill(matchTOF.getSignal() - matchTOF.getT0true() - matchTOF.getTgeant() * 1E3); + mExpTimesPiMC[matchType::TPCTRD]->Fill(trk.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(2)); + mExpTimesKaMC[matchType::TPCTRD]->Fill(trk.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(3)); + mExpTimesPrMC[matchType::TPCTRD]->Fill(trk.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(4)); + } + } + } + } + + // ITS-TPC-TRD-TOF + if (mRecoCont.isTrackSourceLoaded(GID::ITSTPCTRDTOF)) { // this is enough to know that also ITSTPC was loades, see "initialize" + mITSTPCTRDTracks = mRecoCont.getITSTPCTRDTracks(); + mITSTPCTRDTOFMatches = mRecoCont.getITSTPCTRDTOFMatches(); + LOG(debug) << "We found " << mITSTPCTRDTracks.size() << " ITS-TPC-TRD tracks"; + LOG(debug) << "We found " << mITSTPCTRDTOFMatches.size() << " ITS-TPC-TRD tracks matched to TOF"; + // loop over TOF MatchInfo + for (const auto& matchTOF : mITSTPCTRDTOFMatches) { + GTrackID gTrackId = matchTOF.getTrackRef(); + const auto& trk = mITSTPCTRDTracks[gTrackId.getIndex()]; + const auto& trkITSTPC = mITSTPCTracks[trk.getRefGlobalTrackId()]; + const auto& trkTPC = mTPCTracks[trkITSTPC.getRefTPC()]; // check! + const auto& trkDz = matchTOF.getDZatTOF(); + const auto& trkDx = matchTOF.getDXatTOF(); + const auto& trkchi2 = matchTOF.getChi2(); + if (!selectTrack(trkTPC)) { + LOG(debug) << "NUM ITSTPCTRD CONSTR: track with eta " << trkTPC.getEta() << " and pT " << trkTPC.getPt() << " DISCARDED for numerator, ITSTPCTRD CONSTR"; + continue; + } + LOG(debug) << "NUM ITSTPCTRD CONSTR: track with eta " << trkTPC.getEta() << " and pT " << trkTPC.getPt() << " ACCEPTED for numerator, ITSTPCTRD CONSTR" + << " gid: " << gTrackId << " TPC gid =" << trkITSTPC.getRefTPC(); + mMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt()); + mMatchedTracksEta[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getEta()); + mMatchedTracks2DPtEta[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), trkTPC.getEta()); + mDeltaZEta[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getEta(), trkDz); + mDeltaZPhi[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPhi(), trkDz); + mDeltaZPt[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), trkDz); + mDeltaXEta[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getEta(), trkDx); + mDeltaXPhi[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPhi(), trkDx); + mDeltaXPt[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), trkDx); + mTOFChi2[matchType::ITSTPC_ITSTPCTRD]->Fill(trkchi2); + mTOFChi2Pt[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), trkchi2); + + if (trkTPC.getPt() > 1.0) { + const double bcTimeInvInMus = o2::tof::Geo::BC_TIME_INV * 1E3; + float deltaTrackTimeInBC = -matchTOF.getDeltaT() * bcTimeInvInMus; // track time - tof time in number of BC + auto& tofCl = tofClusArray[matchTOF.getTOFClIndex()]; + int isec = tofCl.getMainContributingChannel() / 8736; + if (isec >= 0 && isec < 18) { + mDTimeTrkTRD[isec]->Fill(trkTPC.getEta(), deltaTrackTimeInBC); + } + + if (trkTPC.getPt() > mThresholdPtForExperts) { + std::array globalPos; + trk.getOuterParam().getXYZGlo(globalPos); + + float eta = trk.getEta(); + float phi = trk.getPhi(); + phi = TMath::ATan2(globalPos[1], globalPos[0]); + + fillNumeratorForExperts(eta, phi, tofCl.getMainContributingChannel(), tofCl.getNumOfContributingChannels(), matchTOF.getDXatTOF(), matchTOF.getDZatTOF(), matchTOF.getChi2()); + } + } + + if (mUseMC) { + auto lbl = mRecoCont.getTrackMCLabel(gTrackId); + if (lbl.isFake()) { + mFakeMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt()); + mFakeMatchedTracksEta[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getEta()); + } else { + mDeltaTwMC->Fill(matchTOF.getSignal() - matchTOF.getT0true() - matchTOF.getTgeant() * 1E3); + mExpTimesPiMC[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(2)); + mExpTimesKaMC[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(3)); + mExpTimesPrMC[matchType::ITSTPC_ITSTPCTRD]->Fill(trkTPC.getPt(), matchTOF.getTgeant() * 1E3 - matchTOF.getLTIntegralOut().getTOF(4)); + } + } + } + } + + auto creator = [this](auto& trk, GID gid, float, float) { + // Getting the tracks for the denominator of the efficiencies for TPC-TOF tracks; + // The RecoContainer will provide as TPCtracks only those not matched to TOF (lower + // quality), so we need to ask also for the TPCTOF to have the full set in the denominator + if constexpr (isTPCTrack() || isTPCTOFTrack()) { + // some quality cuts should be applied on the track. + // they might depend on the track type, so in case one needs to acces + // the correct track. For now, using an "if-statement" approach + // but it should be replaced with the official TrackCut class in O2. + // E.g. (till the TrackCut class is used): + if constexpr (isTPCTrack()) { + if (!selectTrack(trk)) { + LOG(debug) << "DEN UNCONS: track with eta " << trk.getEta() << " and pT " << trk.getPt() << " DISCARDED for denominator UNCONS, TPC track"; + return true; + } + LOG(debug) << "DEN UNCONS: track with eta " << trk.getEta() << " and pT " << trk.getPt() << " ACCEPTED for denominator UNCONS, TPC track" + << " gid: " << gid; + this->mInTracksPt[matchType::TPC]->Fill(trk.getPt()); + this->mInTracksEta[matchType::TPC]->Fill(trk.getEta()); + this->mInTracks2DPtEta[matchType::TPC]->Fill(trk.getPt(), trk.getEta()); + } else if constexpr (isTPCTOFTrack()) { + const auto& tpcTrack = mTPCTracks[mTPCTOFMatches[trk.getRefMatch()].getTrackRef().getIndex()]; + if (!selectTrack(tpcTrack)) { + LOG(debug) << "DEN UNCONS: track with eta " << tpcTrack.getEta() << " and pT " << tpcTrack.getPt() << " DISCARDED for denominator UNCONS, TPCTOF track"; + return true; + } + LOG(debug) << "DEN UNCONS: track with eta " << tpcTrack.getEta() << " and pT " << tpcTrack.getPt() << " ACCEPTED for denominator UNCONS, TPCTOF track"; + this->mInTracksPt[matchType::TPC]->Fill(tpcTrack.getPt()); + this->mInTracksEta[matchType::TPC]->Fill(tpcTrack.getEta()); + this->mInTracks2DPtEta[matchType::TPC]->Fill(tpcTrack.getPt(), tpcTrack.getEta()); + } + } + // In case of ITS-TPC-TOF, the ITS-TPC tracks contain also the ITS-TPC-TOF + if constexpr (isTPCITSTrack()) { + const auto& tpcTrack = mTPCTracks[trk.getRefTPC().getIndex()]; + if (!selectTrack(tpcTrack)) { + LOG(debug) << "DEN CONSTR: track with eta " << tpcTrack.getEta() << " and pT " << tpcTrack.getPt() << " DISCARDED for denominator CONSTR, ITSTPC track"; + return true; + } + LOG(debug) << "DEN CONSTR: track with eta " << tpcTrack.getEta() << " and pT " << tpcTrack.getPt() << " ACCEPTED for denominator CONSTR, ITSTPC track"; + this->mInTracksPt[matchType::ITSTPC_ITSTPCTRD]->Fill(tpcTrack.getPt()); + this->mInTracksEta[matchType::ITSTPC_ITSTPCTRD]->Fill(tpcTrack.getEta()); + this->mInTracks2DPtEta[matchType::ITSTPC_ITSTPCTRD]->Fill(tpcTrack.getPt(), tpcTrack.getEta()); + + if (tpcTrack.getPt() > mThresholdPtForExperts) { + std::array globalPos; + trk.getParamOut().getXYZGlo(globalPos); + + float phi = trk.getPhi(); + phi = TMath::ATan2(globalPos[1], globalPos[0]); + + fillDenominatorForExperts(trk.getEta(), phi); + } + } + // In case of ITS-TPC-TRD-TOF, TPC-TRD, ITS-TPC-TRD-TOF, TPC-TRD-TOF, it is always a TRD track + if constexpr (isTRDTrack()) { + // now we need to check if this is a track with or without the ITS + if (gid.includesDet(GID::DetID::ITS)) { + const auto& trkITSTPC = mITSTPCTracks[trk.getRefGlobalTrackId()]; + const auto& tpcTrack = mTPCTracks[trkITSTPC.getRefTPC()]; // check! + if (!selectTrack(tpcTrack)) { + LOG(debug) << "DEN CONSTR: track with eta " << tpcTrack.getEta() << " and pT " << tpcTrack.getPt() << " DISCARDED for denominator CONSTR, ITSTPC track"; + return true; + } + LOG(debug) << "DEN CONSTR: track with eta " << tpcTrack.getEta() << " and pT " << tpcTrack.getPt() << " ACCEPTED for denominator CONSTR, ITSTPC track"; + this->mInTracksPt[matchType::ITSTPC_ITSTPCTRD]->Fill(tpcTrack.getPt()); + this->mInTracksEta[matchType::ITSTPC_ITSTPCTRD]->Fill(tpcTrack.getEta()); + this->mInTracks2DPtEta[matchType::ITSTPC_ITSTPCTRD]->Fill(tpcTrack.getPt(), tpcTrack.getEta()); + + if (tpcTrack.getPt() > mThresholdPtForExperts) { + std::array globalPos; + trk.getOuterParam().getXYZGlo(globalPos); + + float phi = trk.getPhi(); + phi = TMath::ATan2(globalPos[1], globalPos[0]); + + fillDenominatorForExperts(trk.getEta(), phi); + } + } else { + const auto& tpcTrack = mTPCTracks[trk.getRefGlobalTrackId()]; + if (!selectTrack(tpcTrack)) { + LOG(debug) << "DEN CONSTR: track with eta " << tpcTrack.getEta() << " and pT " << tpcTrack.getPt() << " DISCARDED for denominator CONSTR, ITSTPC track"; + return true; + } + LOG(debug) << "DEN CONSTR: track with eta " << tpcTrack.getEta() << " and pT " << tpcTrack.getPt() << " ACCEPTED for denominator CONSTR, ITSTPC track"; + this->mInTracksPt[matchType::TPCTRD]->Fill(tpcTrack.getPt()); + this->mInTracksEta[matchType::TPCTRD]->Fill(tpcTrack.getEta()); + this->mInTracks2DPtEta[matchType::TPCTRD]->Fill(tpcTrack.getPt(), tpcTrack.getEta()); + } + } + return true; + }; + mRecoCont.createTracksVariadic(creator); + + int nTPCOnlytracks = mRecoCont.getTPCTracks().size() > 0 ? (mRecoCont.getTPCTracks().size() - mITSTPCTracks.size()) : 0; + + LOG(debug) << "We have " << mInTracksPt[matchType::TPC]->GetEntries() << " unconstrained tracks at denominator (should be " << nTPCOnlytracks << "), and " << mInTracksPt[matchType::ITSTPC_ITSTPCTRD]->GetEntries() << " constrained tracks at denominator (should be " << mRecoCont.getTPCITSTracks().size() << " but *before any quality cut!!*)"; + LOG(debug) << "We have " << mMatchedTracksPt[matchType::TPC]->GetEntries() << " TOF matches from unconstrained tracks (should be " << mTPCTOFMatches.size() << "), and " << mMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD]->GetEntries() << " TOF matches from constrained tracks (should be " << mITSTPCTOFMatches.size() << " but *before any quality cut!!*))"; + + LOG(debug) << "We have " << mInTracksPt[matchType::TPC]->GetEntries() << " unconstrained tracks at denominator, and " << mInTracksPt[matchType::ITSTPC_ITSTPCTRD]->GetEntries() << " ITS-constrained (ITSTPC + ITSTPCTRD) tracks at denominator, and " << mInTracksPt[matchType::TPCTRD]->GetEntries() << " TRD-constrained tracks at denominator"; + LOG(debug) << "We have " << mMatchedTracksPt[matchType::TPC]->GetEntries() << " TOF matches from unconstrained tracks and " << mMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD]->GetEntries() << " TOF matches from ITS-constrained (ITSTPC + ITSTPCTRD) tracks, and" << mMatchedTracksPt[matchType::TPCTRD]->GetEntries() << " TOF matches from TRD-constrained tracks"; + + // logging in case denominator has less tracks than numerator + for (int i = 0; i < matchType::SIZE; ++i) { + for (int ibin = 1; ibin <= mMatchedTracksPt[i]->GetNbinsX(); ++ibin) { + LOG(debug) << "check: ibin " << ibin << ": mInTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mInTracksPt[i]->GetBinContent(ibin) << ", mMatchedTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksPt[i]->GetBinContent(ibin); + if (mInTracksPt[i]->GetBinContent(ibin) < mMatchedTracksPt[i]->GetBinContent(ibin)) { + LOG(error) << "issue spotted: ibin " << ibin << ": mInTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mInTracksPt[i]->GetBinContent(ibin) << ", mMatchedTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksPt[i]->GetBinContent(ibin); + } + if (mUseMC) { + if (mMatchedTracksPt[i]->GetBinContent(ibin) < mFakeMatchedTracksPt[i]->GetBinContent(ibin)) { + LOG(error) << "issue spotted: ibin " << ibin << ": mMatchedTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksPt[i]->GetBinContent(ibin) << ", mFakeMatchedTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mFakeMatchedTracksPt[i]->GetBinContent(ibin); + } + } + } + for (int ibin = 1; ibin <= mMatchedTracksEta[i]->GetNbinsX(); ++ibin) { + LOG(debug) << "check: ibin " << ibin << ": mInTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mInTracksEta[i]->GetBinContent(ibin) << ", mMatchedTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksEta[i]->GetBinContent(ibin); + if (mInTracksEta[i]->GetBinContent(ibin) < mMatchedTracksEta[i]->GetBinContent(ibin)) { + LOG(error) << "issue spotted: ibin " << ibin << ": mInTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mInTracksEta[i]->GetBinContent(ibin) << ", mMatchedTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksEta[i]->GetBinContent(ibin); + } + if (mUseMC) { + if (mMatchedTracksEta[i]->GetBinContent(ibin) < mFakeMatchedTracksEta[i]->GetBinContent(ibin)) { + LOG(error) << "issue spotted: ibin " << ibin << ": mMatchedTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksEta[i]->GetBinContent(ibin) << ", mFakeMatchedTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mFakeMatchedTracksEta[i]->GetBinContent(ibin); + } + } + } + } +} + +void TOFMatchedTracks::fillDenominatorForExperts(float eta, float phi) +{ + int stripFromTrack = 90; + while (ETAFROMSTRIP[stripFromTrack] < eta && stripFromTrack > 0) { + stripFromTrack--; + } + + int iSect = o2::math_utils::angle2Sector(phi); + stripFromTrack += iSect * 91; + + // fill histo with residualts (x,z) vs strip extrapolated from track + mHistoExpTrackedStrip->Fill(stripFromTrack); +} + +void TOFMatchedTracks::fillNumeratorForExperts(float eta, float phi, int channel, int nhit, float dx, float dz, float chi2) +{ + int stripFromTrack = 90; + while (ETAFROMSTRIP[stripFromTrack] < eta && stripFromTrack > 0) { + stripFromTrack--; + } + + // ILOG(Info, Devel) << "eta = " << eta << ", strip = " << stripFromTrack<< ENDM; + + int iSect = o2::math_utils::angle2Sector(phi); + stripFromTrack += iSect * 91; + + int det[5]; + o2::tof::Geo::getVolumeIndices(channel, det); + int strip = channel / 96; + + // fill histo with residualts (x,z) vs strip extrapolated from track and vs strip from matching + + // ILOG(Info, Devel) << "phi = " << phi << ENDM; + // ILOG(Info, Devel) << "strip from eta,phi = " << stripFromTrack % 91 << ", sector = " << iSect << ENDM; + // ILOG(Info, Devel) << "strip from channel = " << strip % 91 << ", sector = " << strip / 91 << ENDM; + + mHistoExpMatchedNhit->Fill(strip, nhit); + mHistoExpMatchedStrip->Fill(stripFromTrack); + mHistoExpMatchedStripFromCh->Fill(strip); + + mHistoExpMatchedStripFromChDx->Fill(strip, dx); + mHistoExpMatchedStripFromChDz->Fill(strip, dz); + mHistoExpMatchedStripFromChR->Fill(strip, chi2); +} + +void TOFMatchedTracks::endOfCycle() +{ + + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + + // Logging in case any denominator has less entries than the corresponding numerator + for (int i = 0; i < matchType::SIZE; ++i) { + for (int ibin = 1; ibin <= mMatchedTracksPt[i]->GetNbinsX(); ++ibin) { + if (mInTracksPt[i]->GetBinContent(ibin) < mMatchedTracksPt[i]->GetBinContent(ibin)) { + LOG(error) << "End Of Cycle issue spotted: ibin " << ibin << ": mInTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mInTracksPt[i]->GetBinContent(ibin) << ", mMatchedTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksPt[i]->GetBinContent(ibin); + } + if (mInTracksPt[i]->GetBinContent(ibin) != 0 || mMatchedTracksPt[i]->GetBinContent(ibin) != 0) { + LOG(debug) << "End Of Cycle - histo filled : ibin " << ibin << ": mInTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mInTracksPt[i]->GetBinContent(ibin) << ", mMatchedTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksPt[i]->GetBinContent(ibin); + } + if (mUseMC) { + if (mMatchedTracksPt[i]->GetBinContent(ibin) < mFakeMatchedTracksPt[i]->GetBinContent(ibin)) { + LOG(error) << "End Of Cycle issue spotted: ibin " << ibin << ": mMatchedTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksPt[i]->GetBinContent(ibin) << ", mFakeMatchedTracksPt[" << i << "]->GetBinContent(" << ibin << ") = " << mFakeMatchedTracksPt[i]->GetBinContent(ibin); + } + } + } + for (int ibin = 1; ibin <= mMatchedTracksEta[i]->GetNbinsX(); ++ibin) { + if (mInTracksEta[i]->GetBinContent(ibin) < mMatchedTracksEta[i]->GetBinContent(ibin)) { + LOG(error) << "End Of Cycle issue spotted: ibin " << ibin << ": mInTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mInTracksEta[i]->GetBinContent(ibin) << ", mMatchedTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksEta[i]->GetBinContent(ibin); + } + if (mInTracksEta[i]->GetBinContent(ibin) != 0 || mMatchedTracksEta[i]->GetBinContent(ibin) != 0) { + LOG(debug) << "End Of Cycle - histo filled: ibin " << ibin << ": mInTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mInTracksEta[i]->GetBinContent(ibin) << ", mMatchedTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksEta[i]->GetBinContent(ibin); + } + if (mUseMC) { + if (mMatchedTracksEta[i]->GetBinContent(ibin) < mFakeMatchedTracksEta[i]->GetBinContent(ibin)) { + LOG(error) << "End Of Cycle issue spotted: ibin " << ibin << ": mMatchedTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mMatchedTracksEta[i]->GetBinContent(ibin) << ", mFakeMatchedTracksEta[" << i << "]->GetBinContent(" << ibin << ") = " << mFakeMatchedTracksEta[i]->GetBinContent(ibin); + } + } + } + for (int ibinx = 1; ibinx <= mMatchedTracks2DPtEta[i]->GetNbinsX(); ++ibinx) { + for (int ibiny = 1; ibiny <= mMatchedTracks2DPtEta[i]->GetNbinsY(); ++ibiny) { + if (mInTracks2DPtEta[i]->GetBinContent(ibinx, ibiny) < mMatchedTracks2DPtEta[i]->GetBinContent(ibinx, ibiny)) { + LOG(error) << "End Of Cycle issue spotted: ibinx " << ibinx << ", ibiny " << ibiny << ": mInTracks2DPtEta[" << i << "]->GetBinContent(" << ibinx << ", " << ibiny << ") = " << mInTracks2DPtEta[i]->GetBinContent(ibinx, ibiny) << ", mMatchedTracks2DPtEta[" << i << "]->GetBinContent(" << ibinx << ", " << ibiny << ") = " << mMatchedTracks2DPtEta[i]->GetBinContent(ibinx, ibiny); + } + } + } + } + + if (mRecoCont.isTrackSourceLoaded(GID::TPCTOF)) { + if (!mEffPt[matchType::TPC]->SetTotalHistogram(*mInTracksPt[matchType::TPC], "f") || // all total have to be forcely replaced, or the passed from previous processing may have incompatible number of entries + !mEffPt[matchType::TPC]->SetPassedHistogram(*mMatchedTracksPt[matchType::TPC], "") || + !mEffEta[matchType::TPC]->SetTotalHistogram(*mInTracksEta[matchType::TPC], "f") || + !mEffEta[matchType::TPC]->SetPassedHistogram(*mMatchedTracksEta[matchType::TPC], "") || + !mEff2DPtEta[matchType::TPC]->SetTotalHistogram(*mInTracks2DPtEta[matchType::TPC], "f") || + !mEff2DPtEta[matchType::TPC]->SetPassedHistogram(*mMatchedTracks2DPtEta[matchType::TPC], "")) { + ILOG(Fatal, Support) << "Something went wrong in defining the efficiency histograms, UNCONS!!" << ENDM; + } + if (mUseMC) { + if (!mFakeFractionTracksPt[matchType::TPC]->SetTotalHistogram(*mMatchedTracksPt[matchType::TPC], "f") || + !mFakeFractionTracksPt[matchType::TPC]->SetPassedHistogram(*mFakeMatchedTracksPt[matchType::TPC], "") || + !mFakeFractionTracksEta[matchType::TPC]->SetTotalHistogram(*mMatchedTracksEta[matchType::TPC], "f") || + !mFakeFractionTracksEta[matchType::TPC]->SetPassedHistogram(*mFakeMatchedTracksEta[matchType::TPC], "")) { + ILOG(Fatal, Support) << "Something went wrong in defining the efficiency histograms for MC, UNCONS!!" << ENDM; + } + } + } + if (mRecoCont.isTrackSourceLoaded(GID::ITSTPCTOF) || mRecoCont.isTrackSourceLoaded(GID::ITSTPCTRDTOF)) { + if (!mEffPt[matchType::ITSTPC_ITSTPCTRD]->SetTotalHistogram(*mInTracksPt[matchType::ITSTPC_ITSTPCTRD], "f") || + !mEffPt[matchType::ITSTPC_ITSTPCTRD]->SetPassedHistogram(*mMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD], "") || + !mEffEta[matchType::ITSTPC_ITSTPCTRD]->SetTotalHistogram(*mInTracksEta[matchType::ITSTPC_ITSTPCTRD], "f") || + !mEffEta[matchType::ITSTPC_ITSTPCTRD]->SetPassedHistogram(*mMatchedTracksEta[matchType::ITSTPC_ITSTPCTRD], "") || + !mEff2DPtEta[matchType::ITSTPC_ITSTPCTRD]->SetTotalHistogram(*mInTracks2DPtEta[matchType::ITSTPC_ITSTPCTRD], "f") || + !mEff2DPtEta[matchType::ITSTPC_ITSTPCTRD]->SetPassedHistogram(*mMatchedTracks2DPtEta[matchType::ITSTPC_ITSTPCTRD], "")) { + ILOG(Fatal, Support) << "Something went wrong in defining the efficiency histograms, ITS-CONSTR (ITSTPC + ITSTPCTRD)!!" << ENDM; + } + if (mUseMC) { + if (!mFakeFractionTracksPt[matchType::ITSTPC_ITSTPCTRD]->SetTotalHistogram(*mMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD], "f") || + !mFakeFractionTracksPt[matchType::ITSTPC_ITSTPCTRD]->SetPassedHistogram(*mFakeMatchedTracksPt[matchType::ITSTPC_ITSTPCTRD], "") || + !mFakeFractionTracksEta[matchType::ITSTPC_ITSTPCTRD]->SetTotalHistogram(*mMatchedTracksEta[matchType::ITSTPC_ITSTPCTRD], "f") || + !mFakeFractionTracksEta[matchType::ITSTPC_ITSTPCTRD]->SetPassedHistogram(*mFakeMatchedTracksEta[matchType::ITSTPC_ITSTPCTRD], "")) { + ILOG(Fatal, Support) << "Something went wrong in defining the efficiency histograms for MC, ITS-CONSTR (ITSTPC + ITSTPCTRD)!!" << ENDM; + } + } + } + if (mRecoCont.isTrackSourceLoaded(GID::TPCTRDTOF)) { + if (!mEffPt[matchType::TPCTRD]->SetTotalHistogram(*mInTracksPt[matchType::TPCTRD], "f") || + !mEffPt[matchType::TPCTRD]->SetPassedHistogram(*mMatchedTracksPt[matchType::TPCTRD], "") || + !mEffEta[matchType::TPCTRD]->SetTotalHistogram(*mInTracksEta[matchType::TPCTRD], "f") || + !mEffEta[matchType::TPCTRD]->SetPassedHistogram(*mMatchedTracksEta[matchType::TPCTRD], "") || + !mEff2DPtEta[matchType::TPCTRD]->SetTotalHistogram(*mInTracks2DPtEta[matchType::TPCTRD], "f") || + !mEff2DPtEta[matchType::TPCTRD]->SetPassedHistogram(*mMatchedTracks2DPtEta[matchType::TPCTRD], "")) { + ILOG(Fatal, Support) << "Something went wrong in defining the efficiency histograms, TRD-CONSTR (TPCTRD)!!" << ENDM; + } + if (mUseMC) { + if (!mFakeFractionTracksPt[matchType::TPCTRD]->SetTotalHistogram(*mMatchedTracksPt[matchType::TPCTRD], "f") || + !mFakeFractionTracksPt[matchType::TPCTRD]->SetPassedHistogram(*mFakeMatchedTracksPt[matchType::TPCTRD], "") || + !mFakeFractionTracksEta[matchType::TPCTRD]->SetTotalHistogram(*mMatchedTracksEta[matchType::TPCTRD], "f") || + !mFakeFractionTracksEta[matchType::TPCTRD]->SetPassedHistogram(*mFakeMatchedTracksEta[matchType::TPCTRD], "")) { + ILOG(Fatal, Support) << "Something went wrong in defining the efficiency histograms for MC, TRD-CONSTR (TPCTRD)!!" << ENDM; + } + } + } + + // Printing, for checks + if (mVerbose) { + for (int i = 0; i < matchType::SIZE; ++i) { + ILOG(Debug, Support) << "Type " << i << ENDM; + ILOG(Debug, Support) << "Pt plot" << ENDM; + for (int ibin = 0; ibin < mMatchedTracksPt[i]->GetNbinsX(); ++ibin) { + ILOG(Debug, Support) << "mMatchedTracksPt[" << i << "]->GetBinContent(" << ibin + 1 << ") = " << mMatchedTracksPt[i]->GetBinContent(ibin + 1) << ", with error = " << mMatchedTracksPt[i]->GetBinError(ibin + 1); + ILOG(Debug, Support) << ", mInTracksPt[" << i << "]->GetBinContent(" << ibin + 1 << ") = " << mInTracksPt[i]->GetBinContent(ibin + 1) << ", with error = " << mInTracksPt[i]->GetBinError(ibin + 1); + ILOG(Debug, Support) << ", mEffPt[" << i << "]->GetEfficiency(" << ibin + 1 << ") = " << mEffPt[i]->GetEfficiency(ibin + 1) << ", with error low = " << mEffPt[i]->GetEfficiencyErrorLow(ibin + 1) << " and error up = " << mEffPt[i]->GetEfficiencyErrorUp(ibin + 1); + if (mInTracksPt[i]->GetBinContent(ibin + 1) > 0) { + ILOG(Debug, Support) << " (should be " << mMatchedTracksPt[i]->GetBinContent(ibin + 1) / mInTracksPt[i]->GetBinContent(ibin + 1) << ")" << ENDM; + } else { + ILOG(Debug, Support) << ENDM; + } + } + ILOG(Debug, Support) << ENDM; + ILOG(Debug, Support) << "Eta plot" << ENDM; + for (int ibin = 0; ibin < mMatchedTracksEta[i]->GetNbinsX(); ++ibin) { + ILOG(Debug, Support) << "mMatchedTracksEta[" << i << "]->GetBinContent(" << ibin + 1 << ") = " << mMatchedTracksEta[i]->GetBinContent(ibin + 1) << ", with error = " << mMatchedTracksEta[i]->GetBinError(ibin + 1); + ILOG(Debug, Support) << ", mInTracksEta[" << i << "]->GetBinContent(" << ibin + 1 << ") = " << mInTracksEta[i]->GetBinContent(ibin + 1) << ", with error = " << mInTracksEta[i]->GetBinError(ibin + 1); + ILOG(Debug, Support) << ", mEffEta[" << i << "]->GetEfficiency(" << ibin + 1 << ") = " << mEffEta[i]->GetEfficiency(ibin + 1) << ", with error low = " << mEffEta[i]->GetEfficiencyErrorLow(ibin + 1) << " and error up = " << mEffEta[i]->GetEfficiencyErrorUp(ibin + 1); + if (mInTracksEta[i]->GetBinContent(ibin + 1) > 0) { + ILOG(Debug, Support) << " (should be " << mMatchedTracksEta[i]->GetBinContent(ibin + 1) / mInTracksEta[i]->GetBinContent(ibin + 1) << ")" << ENDM; + } else { + ILOG(Debug, Support) << ENDM; + } + } + ILOG(Debug, Support) << ENDM; + } + } +} + +void TOFMatchedTracks::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TOFMatchedTracks::reset() +{ + // clean all the monitor objects here + + if (mUseMC) { + mDeltaTwMC->Reset(); + } + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + for (int i = 0; i < matchType::SIZE; ++i) { + mMatchedTracksPt[i]->Reset(); + mMatchedTracksEta[i]->Reset(); + mMatchedTracks2DPtEta[i]->Reset(); + if (mUseMC) { + mFakeMatchedTracksPt[i]->Reset(); + mFakeMatchedTracksEta[i]->Reset(); + mExpTimesPiMC[i]->Reset(); + mExpTimesKaMC[i]->Reset(); + mExpTimesPrMC[i]->Reset(); + } + mInTracksPt[i]->Reset(); + mInTracksEta[i]->Reset(); + mInTracks2DPtEta[i]->Reset(); + mDeltaZEta[i]->Reset(); + mDeltaZPhi[i]->Reset(); + mDeltaZPt[i]->Reset(); + mDeltaXEta[i]->Reset(); + mDeltaXPhi[i]->Reset(); + mDeltaXPt[i]->Reset(); + mTOFChi2[i]->Reset(); + mTOFChi2Pt[i]->Reset(); + } + + for (int isec = 0; isec < 18; isec++) { + mDTimeTrk[isec]->Reset(); + mDTimeTrkTPC[isec]->Reset(); + mDTimeTrkTRD[isec]->Reset(); + } + + mHistoExpTrackedStrip->Reset(); + mHistoExpMatchedStrip->Reset(); + mHistoExpMatchedStripFromCh->Reset(); + mHistoExpMatchedStripFromChDx->Reset(); + mHistoExpMatchedStripFromChDz->Reset(); + mHistoExpMatchedStripFromChR->Reset(); + mHistoExpMatchedNhit->Reset(); +} + +//__________________________________________________________ + +bool TOFMatchedTracks::selectTrack(o2::tpc::TrackTPC const& track) +{ + + if (track.getPt() <= mPtCut) { + return false; + } + if (std::abs(track.getEta()) >= mEtaCut) { + return false; + } + if (track.getNClusters() < mNTPCClustersCut) { + return false; + } + + math_utils::Point3D v{}; + std::array dca; + o2::track::TrackPar trTmp(track); + if (!trTmp.propagateParamToDCA(v, mBz, &dca, mDCACut) || std::abs(dca[0]) > mDCACutY) { + return false; + } + + return true; +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/TaskCosmics.cxx b/Modules/TOF/src/TaskCosmics.cxx new file mode 100644 index 0000000000..3d275da996 --- /dev/null +++ b/Modules/TOF/src/TaskCosmics.cxx @@ -0,0 +1,168 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TaskCosmics.cxx +/// \author Nicolo' Jacazio +/// \brief Task to monitor TOF data collected in events from cosmics +/// + +// ROOT includes +#include +#include +#include +#include +#include +#include + +// O2 includes +#include "TOFBase/Digit.h" +#include "TOFBase/Geo.h" +#include "DataFormatsTOF/CosmicInfo.h" +#include + +// Fairlogger includes +#include + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TOF/TaskCosmics.h" +#include "TOF/Utils.h" + +namespace o2::quality_control_modules::tof +{ + +TaskCosmics::TaskCosmics() : TaskInterface() +{ +} + +void TaskCosmics::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TaskCosmics" << ENDM; + + // Set task parameters from JSON + if (utils::parseFloatParameter(mCustomParameters, "mSelDeltaTSignalRegion", mSelDeltaTSignalRegion)) { + ILOG(Info, Support) << "Set SelDeltaTSignalRegion to " << mSelDeltaTSignalRegion << " ps" << ENDM; + } + if (utils::parseFloatParameter(mCustomParameters, "SelDeltaTBackgroundRegion", mSelDeltaTBackgroundRegion)) { + ILOG(Info, Support) << "Set SelDeltaTBackgroundRegion to " << mSelDeltaTBackgroundRegion << " ps" << ENDM; + } + if (utils::parseFloatParameter(mCustomParameters, "SelMinLength", mSelMinLength)) { + ILOG(Info, Support) << "Set SelMinLength to " << mSelMinLength << " cm" << ENDM; + } + + mHistoCrate1.reset(new TH1F("Crate1", "Crate1;Crate of first hit;Counts", 72, 0, 72)); + getObjectsManager()->startPublishing(mHistoCrate1.get()); + mHistoCrate2.reset(new TH1F("Crate2", "Crate2;Crate of second hit;Counts", 72, 0, 72)); + getObjectsManager()->startPublishing(mHistoCrate2.get()); + mHistoCrate1VsCrate2.reset(new TH2F("Crate1VsCrate2", "Crate1;Crate of first hit;Crate of second hit;Counts", 72, 0, 72, 72, 0, 72)); + getObjectsManager()->startPublishing(mHistoCrate1VsCrate2.get()); + mHistoDeltaT.reset(new TH1F("DeltaT", "DeltaT;#DeltaT (ps);Counts", 1000, -1e6, 1e6)); + getObjectsManager()->startPublishing(mHistoDeltaT.get()); + mHistoToT1.reset(new TH1F("ToT1", "ToT1;ToT (ns);Counts", 100, 0., 48.8)); + getObjectsManager()->startPublishing(mHistoToT1.get()); + mHistoToT2.reset(new TH1F("ToT2", "ToT2;ToT (ns);Counts", 100, 0., 48.8)); + getObjectsManager()->startPublishing(mHistoToT2.get()); + mHistoLength.reset(new TH1F("Length", "Length;Length (cm);Counts", 100, mSelMinLength, 1000.f)); + getObjectsManager()->startPublishing(mHistoLength.get()); + mHistoDeltaTLength.reset(new TH2F("DeltaTLength", "DeltaT vs Length;Length (cm);#DeltaT (ps);Counts", 100, mSelMinLength, 1000.f, 1000, -1e6, 1e6)); + getObjectsManager()->startPublishing(mHistoDeltaTLength.get()); + mHistoCosmicRate.reset(new TH1F("CosmicRate", "CosmicRate;Crate;Cosmic rate (Hz)", 72, 0., 72)); + mCounterPeak.MakeHistogram(mHistoCosmicRate.get()); + getObjectsManager()->startPublishing(mHistoCosmicRate.get()); +} + +void TaskCosmics::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + reset(); +} + +void TaskCosmics::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TaskCosmics::monitorData(o2::framework::ProcessingContext& ctx) +{ + mCounterTF.Count(0); + const auto cosmics = ctx.inputs().get>("infocosmics"); + + for (unsigned int i = 0; i < cosmics.size(); i++) { + if (cosmics.size() > i + 2) { + if (fabs(cosmics[i].getT1() - cosmics[i + 2].getT1()) < 2E3) { + continue; + } + } + + const o2::tof::CosmicInfo cosmic = cosmics[i]; + if (cosmic.getL() < mSelMinLength) { + continue; + } + const int crate1 = o2::tof::Geo::getCrateFromECH(o2::tof::Geo::getECHFromCH(cosmic.getCH1())); + const int crate2 = o2::tof::Geo::getCrateFromECH(o2::tof::Geo::getECHFromCH(cosmic.getCH2())); + if (crate1 == crate2) { + continue; + } + + mHistoCrate1->Fill(crate1); + mHistoCrate2->Fill(crate2); + mHistoCrate1VsCrate2->Fill(crate1, crate2); + mHistoDeltaT->Fill(cosmic.getDeltaTime()); + mHistoToT1->Fill(cosmic.getTOT1()); + mHistoToT2->Fill(cosmic.getTOT2()); + mHistoLength->Fill(cosmic.getL()); + mHistoDeltaTLength->Fill(cosmic.getL(), cosmic.getDeltaTime()); + + if (abs(cosmic.getDeltaTime()) < mSelDeltaTSignalRegion) { + mCounterPeak.Count(crate1); + mCounterPeak.Count(crate2); + } else if (abs(cosmic.getDeltaTime()) < mSelDeltaTBackgroundRegion) { + mCounterBkg.Count(crate1); + mCounterBkg.Count(crate2); + } + } +} + +void TaskCosmics::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + mCounterPeak.FillHistogram(mHistoCosmicRate.get()); + mCounterBkg.AddHistogram(mHistoCosmicRate.get(), -1); + if (mCounterTF.HowMany(0) > 0) { + mHistoCosmicRate->Scale(1. / (mCounterTF.HowMany(0) * mTFDuration)); + } else { + ILOG(Warning, Support) << "TF read are 0" << ENDM; + } +} + +void TaskCosmics::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TaskCosmics::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mHistoCrate1->Reset(); + mHistoCrate2->Reset(); + mHistoCrate1VsCrate2->Reset(); + mHistoDeltaT->Reset(); + mHistoToT1->Reset(); + mHistoToT2->Reset(); + mHistoLength->Reset(); + mHistoDeltaTLength->Reset(); + mHistoCosmicRate->Reset(); +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/TaskDigits.cxx b/Modules/TOF/src/TaskDigits.cxx new file mode 100644 index 0000000000..e6fda47538 --- /dev/null +++ b/Modules/TOF/src/TaskDigits.cxx @@ -0,0 +1,585 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TaskDigits.cxx +/// \author Nicolò Jacazio +/// \brief Task to monitor quantities in TOF digits in both data and MC +/// + +// ROOT includes +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// O2 includes +#include "TOFBase/Digit.h" +#include "TOFBase/Geo.h" +#include +#include "CommonConstants/LHCConstants.h" +#include "DataFormatsTOF/Diagnostic.h" +#include "CCDB/BasicCCDBManager.h" +#include "Framework/TimingInfo.h" +#include "DetectorsBase/GeometryManager.h" + +// Fairlogger includes +#include + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TOF/TaskDigits.h" +#include "TOF/Utils.h" + +namespace o2::quality_control_modules::tof +{ + +TaskDigits::TaskDigits() : TaskInterface() +{ +} + +void TaskDigits::initialize(o2::framework::InitContext& /*ctx*/) +{ + // Define parameters + utils::parseIntParameter(mCustomParameters, "NbinsMultiplicity", mBinsMultiplicity); + utils::parseIntParameter(mCustomParameters, "NbinsMultiplicity2D", mBinsMultiplicity2D); + utils::parseIntParameter(mCustomParameters, "RangeMaxMultiplicity", mRangeMaxMultiplicity); + utils::parseIntParameter(mCustomParameters, "NbinsMultiplicityOrbit", mBinsMultiplicityOrbit); + utils::parseIntParameter(mCustomParameters, "RangeMaxMultiplicityOrbit", mRangeMaxMultiplicityOrbit); + + utils::parseIntParameter(mCustomParameters, "NbinsTime", mBinsTime); + utils::parseFloatParameter(mCustomParameters, "kNbinsWidthTime", fgkNbinsWidthTime); + utils::parseFloatParameter(mCustomParameters, "RangeMinTime", mRangeMinTime); + utils::parseFloatParameter(mCustomParameters, "RangeMaxTime", mRangeMaxTime); + + utils::parseIntParameter(mCustomParameters, "NbinsToT", mBinsToT); + utils::parseFloatParameter(mCustomParameters, "RangeMinTime", mRangeMinToT); + utils::parseFloatParameter(mCustomParameters, "RangeMaxTime", mRangeMaxToT); + + if (utils::parseIntParameter(mCustomParameters, "NoiseClassSelection", mNoiseClassSelection)) { + if (mNoiseClassSelection < -1 || mNoiseClassSelection >= nNoiseClasses) { + ILOG(Error, Support) << "Asked to discard noise class " << mNoiseClassSelection << " but it is invalid, use -1, 0, 1, 2. Setting it to -1 (no selection)" << ENDM; + mNoiseClassSelection = -1; + } + } + if (auto param = mCustomParameters.find("applyCalib"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - applyCalib: " << param->second << ENDM; + if (param->second == "true" || param->second == "True" || param->second == "TRUE") { + mApplyCalib = true; + } + } + utils::parseBooleanParameter(mCustomParameters, "Diagnostic", mFlagEnableDiagnostic); + utils::parseBooleanParameter(mCustomParameters, "PerChannel", mFlagEnableOrphanPerChannel); + + // Define histograms + ILOG(Debug, Devel) << "initialize TaskDigits" << ENDM; + + // Event info + mHistoOrbitID = std::make_shared("OrbitID", Form("TOF OrbitID;OrbitID %% %i;Crate", mRangeMaxOrbitId), mBinsOrbitId, 0, mRangeMaxOrbitId, RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates); + getObjectsManager()->startPublishing(mHistoOrbitID.get()); + + mHistoBCID = std::make_shared("TimeBC", "TOF readout window BC ID;BC ID in orbit (~25 ns);Crate", mBinsBC, 0., mRangeMaxBC, RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates); + getObjectsManager()->startPublishing(mHistoBCID.get()); + + mHistoEventCounter = std::make_shared("EventCounter", Form("TOF event Counter;Event counter %% %i;Crate", mRangeMaxEventCounter), mRangeMaxEventCounter, 0., mRangeMaxEventCounter, RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates); + getObjectsManager()->startPublishing(mHistoEventCounter.get()); + + mHistoHitMap = std::make_shared("HitMap", "TOF hit map;Sector;Strip", RawDataDecoder::ncrates, 0., RawDataDecoder::nsectors, RawDataDecoder::nstrips, 0., RawDataDecoder::nstrips); + mHistoHitMap->SetBit(TH1::kNoStats); + getObjectsManager()->startPublishing(mHistoHitMap.get()); + getObjectsManager()->setDefaultDrawOptions(mHistoHitMap.get(), "colz logz"); + getObjectsManager()->setDisplayHint(mHistoHitMap.get(), "colz logz"); + + mHistoHitMapNoiseFiltered = std::make_shared("HitMapNoiseFiltered", "#splitline{TOF hit map}{(noise filtered)};Sector;Strip", RawDataDecoder::ncrates, 0., RawDataDecoder::nsectors, RawDataDecoder::nstrips, 0., RawDataDecoder::nstrips); + mHistoHitMapNoiseFiltered->SetBit(TH1::kNoStats); + getObjectsManager()->startPublishing(mHistoHitMapNoiseFiltered.get()); + getObjectsManager()->setDefaultDrawOptions(mHistoHitMapNoiseFiltered.get(), "colz logz"); + getObjectsManager()->setDisplayHint(mHistoHitMapNoiseFiltered.get(), "colz logz"); + + mHistoTimeVsBCID = std::make_shared("TimeVsBCID", "TOF time vs BC ID;BC ID in orbit (~25 ns);time (ns)", mBinsBC, 0., mRangeMaxBC, mBinsTime, mRangeMinTime, mRangeMaxTime); + getObjectsManager()->startPublishing(mHistoTimeVsBCID.get()); + + mHistoOrbitVsCrate = std::make_shared("OrbitVsCrate", "TOF Orbits in TF vs Crate;Crate;Orbits in TF;Fraction", RawDataDecoder::ncrates, 0., RawDataDecoder::ncrates, mBinsOrbitPerTimeFrame, 0, mRangeMaxOrbitPerTimeFrame); + getObjectsManager()->startPublishing(mHistoOrbitVsCrate.get()); + + mHistoROWSize = std::make_shared("ReadoutWindowSize", "N Orbits in TF;Orbits in TF", 300, 0., 300.); + getObjectsManager()->startPublishing(mHistoROWSize.get()); + + if (mFlagEnableDiagnostic) { + mHistoDecodingErrors = std::make_shared("DecodingErrors", "TOF decoding error;Crate;Error", RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates, 13, 1, 14); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(1, "DRM Header"); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(2, "LTM Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(3, "TRM 3 Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(4, "TRM 4 Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(5, "TRM 5 Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(6, "TRM 6 Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(7, "TRM 7 Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(8, "TRM 8 Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(9, "TRM 9 Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(10, "TRM 10 Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(11, "TRM 11 Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(12, "TRM 12 Err."); + mHistoDecodingErrors->GetYaxis()->SetBinLabel(13, "DRM Error"); + getObjectsManager()->startPublishing(mHistoDecodingErrors.get()); + } + + if (mFlagEnableOrphanPerChannel) { + mHistoOrphanPerChannel = std::make_shared("OrphanPerChannel", "TOF orphans vs channel;Channel;Counts", nchannels, 0., nchannels); + getObjectsManager()->startPublishing(mHistoOrphanPerChannel.get()); + } + + mHistoNoisyChannels = std::make_shared("NoisyChannels", "TOF orphans vs channel;Channel;Counts", nchannels, 0., nchannels, nNoiseClasses, 0, nNoiseClasses); + for (int i = 0; i < nNoiseClasses; i++) { + mHistoNoisyChannels->GetYaxis()->SetBinLabel(1 + i, Form("Class %i", i)); + } + getObjectsManager()->startPublishing(mHistoNoisyChannels.get()); + + // Multiplicity + mHistoMultiplicity = std::make_shared("Multiplicity/Integrated", "TOF hit multiplicity;TOF hits;Events ", mBinsMultiplicity, mRangeMinMultiplicity, mRangeMaxMultiplicity); + getObjectsManager()->startPublishing(mHistoMultiplicity.get()); + + for (int i = 0; i < 3; i++) { + mHistoMultiplicityRW[i] = std::make_shared(Form("Multiplicity/ReadoutWindow%i", i + 1), Form("TOF hit multiplicity in rw%i;TOF hits;Events ", i + 1), mBinsMultiplicity, mRangeMinMultiplicity, mRangeMaxMultiplicity); + getObjectsManager()->startPublishing(mHistoMultiplicityRW[i].get()); + } + + mHistoMultiplicityOrbit = std::make_shared("Multiplicity/IntegratedOrbit", "TOF hit multiplicity in orbit;TOF hits;Events ", mBinsMultiplicity, mRangeMinMultiplicity, mRangeMaxMultiplicityOrbit); + getObjectsManager()->startPublishing(mHistoMultiplicityOrbit.get()); + + mHistoMultiplicityIA = std::make_shared("Multiplicity/SectorIA", "TOF hit multiplicity - I/A side;TOF hits;Events ", mBinsMultiplicity, mRangeMinMultiplicity, mRangeMaxMultiplicity); + getObjectsManager()->startPublishing(mHistoMultiplicityIA.get()); + + mHistoMultiplicityOA = std::make_shared("Multiplicity/SectorOA", "TOF hit multiplicity - O/A side;TOF hits;Events ", mBinsMultiplicity, mRangeMinMultiplicity, mRangeMaxMultiplicity); + getObjectsManager()->startPublishing(mHistoMultiplicityOA.get()); + + mHistoMultiplicityIC = std::make_shared("Multiplicity/SectorIC", "TOF hit multiplicity - I/C side;TOF hits;Events ", mBinsMultiplicity, mRangeMinMultiplicity, mRangeMaxMultiplicity); + getObjectsManager()->startPublishing(mHistoMultiplicityIC.get()); + + mHistoMultiplicityOC = std::make_shared("Multiplicity/SectorOC", "TOF hit multiplicity - O/C side;TOF hits;Events ", mBinsMultiplicity, mRangeMinMultiplicity, mRangeMaxMultiplicity); + getObjectsManager()->startPublishing(mHistoMultiplicityOC.get()); + + mHitMultiplicityVsCrate = std::make_shared("Multiplicity/VsCrate", "TOF hit multiplicity vs Crate;Crate;TOF hits", RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates, mBinsMultiplicity2D, mRangeMinMultiplicity, mRangeMaxMultiplicity); + getObjectsManager()->startPublishing(mHitMultiplicityVsCrate.get()); + + mHitMultiplicityVsCratepro = std::make_shared("Multiplicity/VsCratepro", "TOF hit multiplicity vs Crate;Crate;#LT TOF hits #GT", RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates); + getObjectsManager()->startPublishing(mHitMultiplicityVsCratepro.get()); + + mHitMultiplicityVsBC = std::make_shared("Multiplicity/VsBC", "TOF hit multiplicity vs BC;BC;#TOF hits;Events", mBinsBCForMultiplicity, 0, mRangeMaxBC, mBinsMultiplicity2D, mRangeMinMultiplicity, mRangeMaxMultiplicity); + getObjectsManager()->startPublishing(mHitMultiplicityVsBC.get()); + + mHitMultiplicityVsBCpro = std::make_shared("Multiplicity/VsBCpro", "TOF hit multiplicity vs BC;BC;#TOF hits;Events", mBinsBCForMultiplicity, 0, mRangeMaxBC); + getObjectsManager()->startPublishing(mHitMultiplicityVsBCpro.get()); + + // Time + mHistoTime = std::make_shared("Time/Integrated", "TOF hit time;Hit time (ns);Hits", mBinsTime, mRangeMinTime, mRangeMaxTime); + getObjectsManager()->startPublishing(mHistoTime.get()); + + mHistoTimeIA = std::make_shared("Time/SectorIA", "TOF hit time - I/A side;Hit time (ns);Hits", mBinsTime, mRangeMinTime, mRangeMaxTime); + getObjectsManager()->startPublishing(mHistoTimeIA.get()); + + mHistoTimeOA = std::make_shared("Time/SectorOA", "TOF hit time - O/A side;Hit time (ns);Hits", mBinsTime, mRangeMinTime, mRangeMaxTime); + getObjectsManager()->startPublishing(mHistoTimeOA.get()); + + mHistoTimeIC = std::make_shared("Time/SectorIC", "TOF hit time - I/C side;Hit time (ns);Hits", mBinsTime, mRangeMinTime, mRangeMaxTime); + getObjectsManager()->startPublishing(mHistoTimeIC.get()); + + mHistoTimeOC = std::make_shared("Time/SectorOC", "TOF hit time - O/C side;Hit time (ns);Hits", mBinsTime, mRangeMinTime, mRangeMaxTime); + getObjectsManager()->startPublishing(mHistoTimeOC.get()); + + mHistoTimeOrphans = std::make_shared("Time/Orphans", "TOF hit time - orphans;Hit time (ns);Hits", mBinsTime, mRangeMinTime, mRangeMaxTime); + getObjectsManager()->startPublishing(mHistoTimeOrphans.get()); + + // ToT + mHistoToT = std::make_shared("ToT/Integrated", "TOF hit ToT;Hit ToT (ns);Hits", mBinsToT, mRangeMinToT, mRangeMaxToT); + getObjectsManager()->startPublishing(mHistoToT.get()); + + mHistoToTIA = std::make_shared("ToT/SectorIA", "TOF hit ToT - I/A side;Hit ToT (ns);Hits", mBinsToT, mRangeMinToT, mRangeMaxToT); + getObjectsManager()->startPublishing(mHistoToTIA.get()); + + mHistoToTOA = std::make_shared("ToT/SectorOA", "TOF hit ToT - O/A side;Hit ToT (ns);Hits", mBinsToT, mRangeMinToT, mRangeMaxToT); + getObjectsManager()->startPublishing(mHistoToTOA.get()); + + mHistoToTIC = std::make_shared("ToT/SectorIC", "TOF hit ToT - I/C side;Hit ToT (ns);Hits", mBinsToT, mRangeMinToT, mRangeMaxToT); + getObjectsManager()->startPublishing(mHistoToTIC.get()); + + mHistoToTOC = std::make_shared("ToT/SectorOC", "TOF hit ToT - O/C side;Hit ToT (ns);Hits", mBinsToT, mRangeMinToT, mRangeMaxToT); + getObjectsManager()->startPublishing(mHistoToTOC.get()); + + // mBXVsCttmBit = std::make_shared("BXVsCttmBit", "BX ID in TOF matching window vs trg channel; trg channel; BX", 1728, 0, 1728, 24, 0, 24); + // getObjectsManager()->startPublishing(mBXVsCttmBit.get()); + + // mTimeVsCttmBit = std::make_shared("TimeVsCttmBit", "TOF raw time vs trg channel; trg channel; raw time (ns)", 1728, 0., 1728., mBinsTime, mRangeMinTime, mRangeMaxTime); + // getObjectsManager()->startPublishing(mTimeVsCttmBit.get()); + + // Decoding Error Checkers + for (int j = 0; j < 10; j++) { + mHistoDecodingCrate[j] = std::make_shared(Form("DecodingTRM_%02i", j + 3), Form("Mult per Decoding error trm %d;bit error;Crate;hit multiplicity", j + 3), 29, 0, 29, 72, 0, 72); + getObjectsManager()->startPublishing(mHistoDecodingCrate[j].get()); + } +} + +void TaskDigits::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + reset(); +} + +void TaskDigits::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TaskDigits::monitorData(o2::framework::ProcessingContext& ctx) +{ + if (mApplyCalib && !mCalChannel) { + auto creationTime = ctx.services().get().creation; + mCalChannel = o2::ccdb::BasicCCDBManager::instance().getForTimeStamp("TOF/Calib/ChannelCalib", creationTime); + mLHCphase = o2::ccdb::BasicCCDBManager::instance().getForTimeStamp("TOF/Calib/LHCphase", creationTime); + } + + // Get TOF digits + const auto& digits = ctx.inputs().get>("tofdigits"); + // Get TOF Readout window + const auto& rows = ctx.inputs().get>("readoutwin"); + // Get Diagnostic frequency to check noisy channels in the current TF + const auto& diafreq = ctx.inputs().get("diafreq"); + + int nent = mHitMultiplicityVsBC->GetEntries(); + + int eta, phi; // Eta and phi indices + int det[5] = { 0 }; // Coordinates + int strip = 0; // Strip + float tdc_time = 0.f; + float tot_time = 0.f; + // SM in side I: 14-17, 0-4 -> 4 + 5 + // SM in side O: 5-13 -> 9 + // phi is counted every pad starting from SM 0. + // There are 48 pads per SM. Side I is from phi 0:48*4 and 48*14:48*18 + constexpr int phi_I1 = 48 * 4; + constexpr int phi_I2 = 48 * 14; + // eta is counted every half strip starting from strip 0. + // Halves strips in side A 0-90, in side C RawDataDecoder::nstrips-181 + constexpr int half_eta = RawDataDecoder::nstrips; + bool isSectorI = false; + std::array ndigitsPerQuater = { 0 }; // Number of digits per side I/A,O/A,I/C,O/C + std::array ndigitsPerCrate = { 0 }; // Number of hits in one event per crate + constexpr int nOrbits = 128; // Number of orbits + int ndigitsPerBC[nOrbits][mBinsBCForMultiplicity] = {}; // number of digit per oribit, BC/18 + + mHistoROWSize->Fill(rows.size() / 3.0); + + int currentReadoutWindow = 0; + int currentDiagnostics = 0; + int orbitMultiplicity = 0; + // Loop on readout windows + for (const auto& row : rows) { + const auto& digits_in_row = row.getBunchChannelData(digits); // Digits inside a readout window + + for (unsigned int crate = 0; crate < RawDataDecoder::ncrates; crate++) { // Loop on all crates + mHistoOrbitVsCrate->Fill(crate, currentReadoutWindow / 3.0, !row.isEmptyCrate(crate)); + // + if (row.isEmptyCrate(crate)) { // Only for active crates + continue; + } + mHistoOrbitID->Fill(row.mFirstIR.orbit % mRangeMaxOrbitId, crate); + mHistoBCID->Fill(row.mFirstIR.bc, crate); + mHistoEventCounter->Fill(row.mEventCounter % mRangeMaxEventCounter, crate); + } + + // check patterns + int trmMult[72][10] = { 0 }; // multiplicity in TRM and ROW + for (auto const& digit : digits_in_row) { + if (digit.getChannel() < 0) { + LOG(error) << "No valid channel"; + continue; + } + int ech = o2::tof::Geo::getECHFromCH(digit.getChannel()); + int crate = o2::tof::Geo::getCrateFromECH(ech); + int trm = o2::tof::Geo::getTRMFromECH(ech) - 3; + trmMult[crate][trm]++; + } + + for (int crate = 0; crate < 72; crate++) { + if (row.isEmptyCrate(crate)) { // Only for active crates + continue; + } + for (int trm = 0; trm < 10; trm++) { + mHistoDecodingCrate[trm]->Fill(0., crate, trmMult[crate][trm]); + } + + if (mFlagEnableDiagnostic) { + // Get TOF Diagnostic words + const auto& diagnostics = ctx.inputs().get>("patterns"); + + int nDia = row.getDiagnosticInCrate(crate); + + mHistoDecodingErrors->Fill(crate, 1); + + int slot = -1; + int lastslot = -1; + for (int idia = currentDiagnostics; idia < currentDiagnostics + nDia; idia++) { + const uint8_t& el = diagnostics[idia]; + + if (el > 28) { // new slot + slot = el - 28; + } else if (slot > 1 && lastslot != slot) { // fill only one time per TRM and row + // fill error + mHistoDecodingErrors->Fill(crate, slot); + lastslot = slot; + } else if (slot == 1 && lastslot != slot) { + mHistoDecodingErrors->Fill(crate, 13); // DRM error + lastslot = slot; + } + if (el <= 28 && slot > 2 && slot < 13) { // fill TRM mult for the current bit error for this TRM + mHistoDecodingCrate[slot - 3]->Fill(el, crate, trmMult[crate][slot - 3]); + } + } + currentDiagnostics += nDia; + } + } + + int windowIndex = currentReadoutWindow % 3; + int ndigits_in_row = digits_in_row.size(); + + mHistoMultiplicityRW[windowIndex]->Fill(ndigits_in_row); + orbitMultiplicity += ndigits_in_row; + + if (windowIndex == 2) { // End of readout windows, fill orbit multiplicity + + mHistoMultiplicityOrbit->Fill(orbitMultiplicity); + orbitMultiplicity = 0; // Reset + } + + // Loop on digits + for (auto const& digit : digits_in_row) { + if (digit.getChannel() < 0) { + LOG(error) << "No valid channel"; + continue; + } + + for (int i = 0; i < nNoiseClasses; i++) { + if (!diafreq->isNoisyChannel(digit.getChannel(), i)) { + continue; + } + mCounterNoisyChannels[i].Count(digit.getChannel()); + } + + // Fill hit map counter no matter the selection + o2::tof::Geo::getVolumeIndices(digit.getChannel(), det); + strip = o2::tof::Geo::getStripNumberPerSM(det[1], det[2]); // Strip index in the SM + mCounterHitsPerStrip[strip].Count(det[0] * 4 + det[4] / 12); + + if (mNoiseClassSelection >= 0 && + diafreq->isNoisyChannel(digit.getChannel(), mNoiseClassSelection)) { + // LOG(info) << "noisy channel " << digit.getChannel(); + continue; + } + // LOG(info) << "good channel " << digit.getChannel(); + + // Correct BC index + int bcCorr = digit.getIR().bc - o2::tof::Geo::LATENCYWINDOW_IN_BC; + if (bcCorr < 0) { + bcCorr += o2::constants::lhc::LHCMaxBunches; + } + + int ech = o2::tof::Geo::getECHFromCH(digit.getChannel()); + int crateECH = o2::tof::Geo::getCrateFromECH(ech); + int slotECH = o2::tof::Geo::getTRMFromECH(ech); + int chainECH = o2::tof::Geo::getChainFromECH(ech); + int tdcECH = o2::tof::Geo::getTDCFromECH(ech); + int bcCorrCable = bcCorr; + float pos[3]; + o2::tof::Geo::getPos(det, pos); + float length = sqrt(pos[0] * pos[0] + pos[1] * pos[1] + pos[2] * pos[2]); + + if (mCalChannel) { // calibration + float timeTDCcorr = digit.getTDC() * o2::tof::Geo::TDCBIN; // in ps + timeTDCcorr -= mCalChannel->evalTimeSlewing(digit.getChannel(), 0.0); + timeTDCcorr -= mLHCphase->getLHCphase(0); + timeTDCcorr -= length * 33.356410 - 1000; // subract path (1ns margin) + bcCorrCable += int(o2::constants::lhc::LHCMaxBunches + timeTDCcorr * o2::tof::Geo::BC_TIME_INPS_INV) - o2::constants::lhc::LHCMaxBunches; // to truncate in the proper way + } else { + bcCorrCable -= (o2::tof::Geo::getCableTimeShiftBin(crateECH, slotECH, chainECH, tdcECH) - digit.getTDC()) / 1024; // just cable length + } + + if (bcCorrCable < 0) { + bcCorrCable += o2::constants::lhc::LHCMaxBunches; + } + + if (bcCorrCable >= o2::constants::lhc::LHCMaxBunches) { + bcCorrCable -= o2::constants::lhc::LHCMaxBunches; + } + + ndigitsPerBC[row.mFirstIR.orbit % nOrbits][bcCorrCable]++; + + ndigitsPerCrate[crateECH]++; + mCounterHitsPerStripNoiseFiltered[strip].Count(det[0] * 4 + det[4] / 12); + mCounterHitsPerChannel.Count(digit.getChannel()); + // TDC time and ToT time + constexpr float TDCBIN_NS = o2::tof::Geo::TDCBIN * 0.001; + tdc_time = (digit.getTDC() + bcCorr * 1024) * TDCBIN_NS; + tot_time = digit.getTOT() * o2::tof::Geo::TOTBIN_NS; + mHistoTimeVsBCID->Fill(row.mFirstIR.bc, tdc_time); + mHistoTime->Fill(tdc_time); + if (tot_time <= 0.f) { + mHistoTimeOrphans->Fill(tdc_time); + if (mFlagEnableOrphanPerChannel) { + mCounterOrphansPerChannel.Count(digit.getChannel()); + } + } + mHistoToT->Fill(tot_time); + digit.getPhiAndEtaIndex(phi, eta); + isSectorI = phi < phi_I1 || phi > phi_I2; + if (eta < half_eta) { // Sector A + if (isSectorI) { // Sector I/A + mHistoTimeIA->Fill(tdc_time); + mHistoToTIA->Fill(tot_time); + ndigitsPerQuater[0]++; + } else { // Sector O/A + mHistoTimeOA->Fill(tdc_time); + mHistoToTOA->Fill(tot_time); + ndigitsPerQuater[1]++; + } + } else { // Sector C + if (isSectorI) { // Sector I/C + mHistoTimeIC->Fill(tdc_time); + mHistoToTIC->Fill(tot_time); + ndigitsPerQuater[2]++; + } else { // Sector O/C + mHistoTimeOC->Fill(tdc_time); + mHistoToTOC->Fill(tot_time); + ndigitsPerQuater[3]++; + } + } + } + // Filling histograms of hit multiplicity + mHistoMultiplicity->Fill(ndigitsPerQuater[0] + ndigitsPerQuater[1] + ndigitsPerQuater[2] + ndigitsPerQuater[3]); // Number of digits inside a readout window + // Filling quarter histograms + mHistoMultiplicityIA->Fill(ndigitsPerQuater[0]); + mHistoMultiplicityOA->Fill(ndigitsPerQuater[1]); + mHistoMultiplicityIC->Fill(ndigitsPerQuater[2]); + mHistoMultiplicityOC->Fill(ndigitsPerQuater[3]); + + for (int crate = 0; crate < RawDataDecoder::ncrates; crate++) { + mHitMultiplicityVsCratepro->Fill(crate, ndigitsPerCrate[crate]); + mHitMultiplicityVsCrate->Fill(crate, ndigitsPerCrate[crate]); + ndigitsPerCrate[crate] = 0; + } + // + ndigitsPerQuater[0] = 0; + ndigitsPerQuater[1] = 0; + ndigitsPerQuater[2] = 0; + ndigitsPerQuater[3] = 0; + currentReadoutWindow++; + } + + for (int iorb = 0; iorb < nOrbits; iorb++) { + for (int ibc = 0; ibc < mBinsBCForMultiplicity; ibc++) { + mHitMultiplicityVsBC->Fill(ibc, ndigitsPerBC[iorb][ibc]); + mHitMultiplicityVsBCpro->Fill(ibc, ndigitsPerBC[iorb][ibc]); + } + } + + // To complete the second TF in case it receives orbits + for (; currentReadoutWindow < 768; currentReadoutWindow++) { + for (unsigned int i = 0; i < RawDataDecoder::ncrates; i++) { // Loop on all crates + mHistoOrbitVsCrate->Fill(i, currentReadoutWindow / 3.0, 0); + } + } +} + +void TaskDigits::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + for (unsigned int i = 0; i < RawDataDecoder::nstrips; i++) { + mCounterHitsPerStrip[i].FillHistogram(mHistoHitMap.get(), i + 1); + mCounterHitsPerStripNoiseFiltered[i].FillHistogram(mHistoHitMapNoiseFiltered.get(), i + 1); + } + if (mFlagEnableOrphanPerChannel) { + mCounterOrphansPerChannel.FillHistogram(mHistoOrphanPerChannel.get()); + } + for (unsigned int i = 0; i < nNoiseClasses; i++) { + mCounterNoisyChannels[i].FillHistogram(mHistoNoisyChannels.get(), i + 1); + } +} + +void TaskDigits::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TaskDigits::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the counters" << ENDM; + for (unsigned int i = 0; i < RawDataDecoder::nstrips; i++) { + mCounterHitsPerStrip[i].Reset(); + mCounterHitsPerStripNoiseFiltered[i].Reset(); + } + mCounterHitsPerChannel.Reset(); + mCounterOrphansPerChannel.Reset(); + for (unsigned int i = 0; i < nNoiseClasses; i++) { + mCounterNoisyChannels[i].Reset(); + } + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + // Event info + mHistoOrbitID->Reset(); + mHistoBCID->Reset(); + mHistoEventCounter->Reset(); + mHistoHitMap->Reset(); + mHistoTimeVsBCID->Reset(); + mHistoOrbitVsCrate->Reset(); + mHistoROWSize->Reset(); + for (int j = 0; j < 10; j++) { + mHistoDecodingCrate[j]->Reset(); + } + if (mFlagEnableDiagnostic) { + mHistoDecodingErrors->Reset(); + } + if (mFlagEnableOrphanPerChannel) { + mHistoOrphanPerChannel->Reset(); + } + // Multiplicity + mHistoMultiplicity->Reset(); + mHistoMultiplicityIA->Reset(); + mHistoMultiplicityOA->Reset(); + mHistoMultiplicityIC->Reset(); + mHistoMultiplicityOC->Reset(); + mHistoMultiplicityOrbit->Reset(); + for (int i = 0; i < 3; i++) { + mHistoMultiplicityRW[i]->Reset(); + } + mHitMultiplicityVsCrate->Reset(); + mHitMultiplicityVsCratepro->Reset(); + mHitMultiplicityVsBC->Reset(); + mHitMultiplicityVsBCpro->Reset(); + // Time + mHistoTime->Reset(); + mHistoTimeIA->Reset(); + mHistoTimeOA->Reset(); + mHistoTimeIC->Reset(); + mHistoTimeOC->Reset(); + mHistoTimeOrphans->Reset(); + // ToT + mHistoToT->Reset(); + mHistoToTIA->Reset(); + mHistoToTOA->Reset(); + mHistoToTIC->Reset(); + mHistoToTOC->Reset(); +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/TaskRaw.cxx b/Modules/TOF/src/TaskRaw.cxx new file mode 100644 index 0000000000..99dff1671e --- /dev/null +++ b/Modules/TOF/src/TaskRaw.cxx @@ -0,0 +1,721 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TaskRaw.cxx +/// \author Nicolo' Jacazio and Francesca Ercolessi +/// \brief Task To monitor data converted from TOF compressor, and check the diagnostic words of TOF crates received trough the TOF compressor. +/// Here are defined the counters to check the diagnostics words of the TOF crates obtained from the compressor. +/// This is why the class derives from DecoderBase: it reads data from the decoder. +/// This tasks also perform a basic noise monitoring to check the fraction of noisy channels +/// \since 20-11-2020 +/// + +// ROOT includes +#include +#include +#include +#include + +// O2 includes +#include "DataFormatsTOF/CompressedDataFormat.h" +#include +#include "Headers/RAWDataHeader.h" +#include "DetectorsRaw/HBFUtils.h" +#include +#include +#include "DetectorsRaw/RDHUtils.h" + +using namespace o2::framework; +using namespace o2::tof; +using RDHUtils = o2::raw::RDHUtils; + +// Fairlogger includes +#include + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TOF/TaskRaw.h" +#include "TOF/Utils.h" + +namespace o2::quality_control_modules::tof +{ + +void RawDataDecoder::rdhHandler(const o2::header::RAWDataHeader* rdh) +{ + // auto orbit = RDHUtils::getHeartBeatOrbit(rdh); + bool isValidRDH = RDHUtils::checkRDH(rdh, false); + + if (!isValidRDH) { + LOG(debug) << "No valid RDH... skipped"; + return; + } + + mHistoPayload->Fill(rdh->feeId, TMath::Log2((rdh->memorySize - rdh->headerSize) + 1)); + + constexpr auto rdhCrateWord = 0xFF; + if (RDHUtils::getPageCounter(rdh) == 0) { // if RDH open + mCounterRDHOpen.Count(rdh->feeId & rdhCrateWord); + } + + mCounterRDH[rdh->feeId & rdhCrateWord].Count(0); + + // Case for the RDH word "fatal" + if ((rdh->detectorField & 0x00001000) != 0) { + mCounterRDH[rdh->feeId & rdhCrateWord].Count(1); + // LOG(warn) << "RDH flag \"fatal\" error occurred in crate " << static_cast(rdh->feeId & rdhCrateWord); + } + + if (rdh->stop) { // if RDH close + // Triggers served and received (3 are expected) + const int triggerserved = ((rdh->detectorField >> 24) & rdhCrateWord); + const int triggerreceived = ((rdh->detectorField >> 16) & rdhCrateWord); + if (triggerserved < triggerreceived) { + // RDH word "trigger error": served < received + mCounterRDH[rdh->feeId & rdhCrateWord].Count(2); + } + // Numerator and denominator for the trigger efficiency + mCounterRDHTriggers[0].Add(rdh->feeId & rdhCrateWord, triggerserved); + mCounterRDHTriggers[1].Add(rdh->feeId & rdhCrateWord, triggerreceived); + } +} + +void RawDataDecoder::headerHandler(const CrateHeader_t* crateHeader, const CrateOrbit_t* crateOrbit) +{ + + // DRM Counter + mCounterDRM[crateHeader->drmID].Count(0); + + // LTM Counter + if (crateHeader->slotPartMask & (1 << 0)) { + mCounterLTM[crateHeader->drmID].Count(0); + } + + // Participating slot + for (int ibit = 1; ibit < 11; ++ibit) { + if (crateHeader->slotPartMask & (1 << ibit)) { + // TRM Counter + mCounterTRM[crateHeader->drmID][ibit - 1].Count(0); + } + } + + // Orbit ID + mHistoOrbitID->Fill(crateOrbit->orbitID % 1048576, crateHeader->drmID); +} + +void RawDataDecoder::frameHandler(const CrateHeader_t* crateHeader, const CrateOrbit_t* /*crateOrbit*/, + const FrameHeader_t* frameHeader, const PackedHit_t* packedHits) +{ + const auto& drmID = crateHeader->drmID; // [0-71] + const auto& trmID = frameHeader->trmID; // [3-12] + // Number of hits + mHistoHits->Fill(frameHeader->numberOfHits); + // Number of hits in TRM slot per crate + if (mDebugCrateMultiplicity) { + mHistoHitsCrate[drmID]->Fill(frameHeader->numberOfHits); + } + for (int i = 0; i < frameHeader->numberOfHits; ++i) { + const auto packedHit = packedHits + i; + const auto chain = packedHit->chain; // [0-1] + const auto tdcID = packedHit->tdcID; // [0-14] + const auto channel = packedHit->channel; // [0-7] + const auto indexE = channel + 8 * tdcID + 120 * chain + 240 * (trmID - 3) + 2400 * drmID; // [0-172799] + const int time = packedHit->time + (frameHeader->frameID << 13); // [24.4 ps] + const int timebc = time % 1024; + + // Equipment index (Electronics Oriented) + mCounterIndexEO.Count(indexE); + // Raw time + mHistoTime->Fill(time); + // BC time + mCounterTimeBC.Count(timebc); + // ToT + mHistoTOT->Fill(packedHit->tot); + // Equipment index for noise analysis (Electronics Oriented) + if (time < mTimeMin || time >= mTimeMax) { + continue; + } + mCounterIndexEOInTimeWin.Count(indexE); + } +} + +void RawDataDecoder::trailerHandler(const CrateHeader_t* crateHeader, const CrateOrbit_t* /*crateOrbit*/, + const CrateTrailer_t* crateTrailer, const Diagnostic_t* diagnostics, + const Error_t* errors) +{ + // Diagnostic word per slot + const int drmID = crateHeader->drmID; + constexpr unsigned int reserved_words = 4; // First 4 bits are reserved + constexpr unsigned int words_to_check = nwords - reserved_words; // Words to check + for (int i = 0; i < crateTrailer->numberOfDiagnostics; ++i) { + auto diagnostic = diagnostics + i; + const int slotID = diagnostic->slotID; + if (slotID == 1) { // Here we have a DRM + for (unsigned int j = 0; j < words_to_check; j++) { + if (diagnostic->faultBits & 1 << j) { + mCounterDRM[drmID].Count(j + reserved_words); + } + } + } else if (slotID == 2) { // Here we have a LTM + for (unsigned int j = 0; j < words_to_check; j++) { + if (diagnostic->faultBits & 1 << j) { + mCounterLTM[drmID].Count(j + reserved_words); + } + } + } else { // Here we have a TRM + for (unsigned int j = 0; j < words_to_check; j++) { + if (diagnostic->faultBits & 1 << j) { + mCounterTRM[drmID][slotID - 3].Count(j + reserved_words); + } + } + } + } + + // Number of diagnostics per crate + for (int i = 0; i < crateTrailer->numberOfDiagnostics; ++i) { + auto diagnostic = diagnostics + i; + mHistoDiagnostic->Fill(crateHeader->drmID, diagnostic->slotID); + } + // Errors in the TDCs + int nError = 0, nTest = 0; + for (int i = 0; i < crateTrailer->numberOfErrors; ++i) { + auto error = errors + i; + if (error->undefined) { + nTest++; + mHistoTest->Fill(error->slotID + 0.5 * error->chain, error->tdcID); + } else { + nError++; + mHistoError->Fill(error->slotID + 0.5 * error->chain, error->tdcID); + for (int ibit = 0; ibit < 15; ++ibit) { + if (error->errorFlags & (1 << ibit)) { + mHistoErrorBits->Fill(ibit); + } + } + } + } + mHistoNErrors->Fill(nError); + mHistoNTests->Fill(nTest); +} + +void RawDataDecoder::initHistograms() // Initialization of histograms in Decoder +{ + mHistoHits = std::make_shared("hHits", "Raw Hits;Hits per event", 1000, 0., 1000.); + if (mDebugCrateMultiplicity) { + for (unsigned int i = 0; i < ncrates; i++) { + mHistoHitsCrate[i] = std::make_shared(Form("CrateMultiplicity/hHitsCrate%02i", i), Form("Hits in TRMs per Crate %i ;Hits per event", i), 1000, 0., 1000.); + } + } + mHistoTime = std::make_shared("hTime", "Raw Time;Time (24.4 ps)", 2097152, 0., 2097152.); + mHistoTOT = std::make_shared("hTOT", "Raw ToT;ToT (48.8 ps)", 2048, 0., 2048.); + mHistoDiagnostic = std::make_shared("hDiagnostic", "hDiagnostic;Crate;Slot", ncrates, 0., ncrates, nslots, 1, nslots + 1); + mHistoDiagnostic.get()->GetYaxis()->SetBinLabel(1, "DRM"); + mHistoDiagnostic.get()->GetYaxis()->SetBinLabel(2, "LTM"); + for (int k = 0; k < 10; k++) { + mHistoDiagnostic.get()->GetYaxis()->SetBinLabel(3 + k, Form("TRMSlot%i", 3 + k)); + } + mHistoNErrors = std::make_shared("hNErrors", "Error numbers;Number of errors", 1000, 0., 1000.); + mHistoErrorBits = std::make_shared("hErrorBit", "Error Bit;TDC error bit", 15, 0., 15.); + mHistoError = std::make_shared("hError", "Errors;slot;TDC", 24, 1., 13., 15, 0., 15.); + mHistoNTests = std::make_shared("hNTests", "Test numbers;Number of errors", 1000, 0., 1000.); + mHistoTest = std::make_shared("hTest", "Tests;slot;TDC", 24, 1., 13., 15, 0., 15.); + mHistoOrbitID = std::make_shared("hOrbitID", "OrbitID;OrbitID % 1048576;Crate", 1024, 0, 1048576, ncrates, 0, ncrates); + mHistoNoiseMap = std::make_shared("hNoiseMap", "Noise Map (1 bin = 1 FEA = 24 channels); crate; Fea x strip", ncrates, 0., ncrates, 364, 0., nstrips); + mHistoIndexEOHitRate = std::make_shared("hIndexEOHitRate", "Hit Rate (Hz); index EO; Rate (Hz)", nequipments, 0., nequipments); + mHistoPayload = std::make_shared("hPayload", "hPayload;Crate;Log_{2}(payload + 1)", ncrates, 0., ncrates, 30, 0, 30); // up to 1 GB 2^30 +} + +void RawDataDecoder::resetHistograms() // Reset of histograms in Decoder +{ + // Reset counters + mCounterIndexEO.Reset(); + mCounterIndexEOInTimeWin.Reset(); + mCounterNoisyChannels.Reset(); + mCounterTimeBC.Reset(); + for (unsigned int i = 0; i < ncrates; i++) { + mCounterOrbitsPerCrate[i].Reset(); + for (unsigned int j = 0; j < 4; j++) { + mCounterNoiseMap[i][j].Reset(); + } + } + mCounterRDHTriggers[0].Reset(); + mCounterRDHTriggers[1].Reset(); + mCounterRDHOpen.Reset(); + + // Reset histograms + mHistoHits->Reset(); + if (mDebugCrateMultiplicity) { + for (unsigned int i = 0; i < ncrates; i++) { + mHistoHitsCrate[i]->Reset(); + } + } + mHistoTime->Reset(); + mHistoTOT->Reset(); + mHistoDiagnostic->Reset(); + mHistoNErrors->Reset(); + mHistoErrorBits->Reset(); + mHistoError->Reset(); + mHistoNTests->Reset(); + mHistoTest->Reset(); + mHistoOrbitID->Reset(); + mHistoNoiseMap->Reset(); + mHistoIndexEOHitRate->Reset(); + mHistoPayload->Reset(); +} + +void RawDataDecoder::estimateNoise(std::shared_ptr hIndexEOIsNoise) +{ + double IntegratedTimeFea[nstrips][ncrates][4] = { { { 0. } } }; + double IntegratedTime[nstrips][ncrates] = { { 0. } }; + + for (unsigned int i = 0; i < nequipments; ++i) { + const auto indexcounter = mCounterIndexEOInTimeWin.HowMany(i); + const unsigned int crate = i / 2400; + const int crate_ = i % 2400; + const int slot = crate_ / 240; + const double time_window = mTDCWidth * (mTimeMax - mTimeMin); + const double time = mCounterTRM[crate][slot].HowMany(0) * time_window; + + // start measure time from 1 micro second + if (time < 1.e-6) { + continue; + } + // check if this channel was active + if (indexcounter == 0) { + continue; + } + + const double rate = (double)indexcounter / time; + + // Fill noise rate histogram + mHistoIndexEOHitRate->SetBinContent(i + 1, rate); + + // Noise condition + if (rate < mNoiseThreshold) { + continue; + } + + const int slot_ = crate_ % 240; + const int chain = slot_ / 120; + const int chain_ = slot_ % 120; + const int tdc = chain_ / 8; + const int tdc_ = chain_ % 8; + const int channel = tdc_; + const auto eIndex = o2::tof::Geo::getECHFromIndexes(crate, slot + 3, chain, tdc, channel); + const auto dIndex = o2::tof::Geo::getCHFromECH(eIndex); + if (dIndex < 0) { + continue; + } + const auto sector_ = dIndex % 8736; + const auto strip = sector_ / 96; + const auto strip_ = sector_ % 96; + const auto strrow_ = strip_ % 48; + const auto fea = strrow_ / 12; + + mCounterNoisyChannels.Add(i, indexcounter); + IntegratedTime[strip][crate] += time; + mCounterNoiseMap[crate][fea].Add(strip, indexcounter); + IntegratedTimeFea[strip][crate][fea] += time; + } // end loop over index + + // Fill noisy channels histogram + mCounterNoisyChannels.FillHistogram(hIndexEOIsNoise.get()); + + for (unsigned int icrate = 0; icrate < ncrates; icrate++) { + for (unsigned int istrip = 0; istrip < nstrips; istrip++) { + const auto itime = IntegratedTime[istrip][icrate]; + + // start measure time from 1 micro second + if (itime < 1.e-6) { + continue; + } + + for (int iFea = 0; iFea < 4; iFea++) { + const auto indexcounterFea = mCounterNoiseMap[icrate][iFea].HowMany(istrip); + const auto timeFea = IntegratedTimeFea[istrip][icrate][iFea]; + + // start measure time from 1 micro second + if (timeFea < 1.e-6) { + continue; + } + + // Fill noise map + mHistoNoiseMap->SetBinContent(icrate + 1, istrip * 4 + (3 - iFea) + 1, indexcounterFea); + } // end loop over Feas + } // end loop over strips + } // end loop over sectors +} + +// Implement the Task +void TaskRaw::initialize(o2::framework::InitContext& /*ctx*/) +{ + // Set task parameters from JSON + bool useConetMode = false; + if (utils::parseBooleanParameter(mCustomParameters, "DecoderCONET", useConetMode)) { + ILOG(Info, Support) << "Set DecoderCONET to " << useConetMode << ENDM; + mDecoderRaw.setDecoderCONET(useConetMode); + } + if (auto param = mCustomParameters.find("TimeWindowMin"); param != mCustomParameters.end()) { + mDecoderRaw.setTimeWindowMin(param->second); + } + if (auto param = mCustomParameters.find("TimeWindowMax"); param != mCustomParameters.end()) { + mDecoderRaw.setTimeWindowMax(param->second); + } + if (auto param = mCustomParameters.find("NoiseThreshold"); param != mCustomParameters.end()) { + mDecoderRaw.setNoiseThreshold(param->second); + } + bool usePerCrateHistograms = false; + if (utils::parseBooleanParameter(mCustomParameters, "DebugCrateMultiplicity", usePerCrateHistograms)) { + ILOG(Info, Support) << "Set DebugCrateMultiplicity to " << usePerCrateHistograms << ENDM; + mDecoderRaw.setDebugCrateMultiplicity(usePerCrateHistograms); + } + + // RDH + mHistoRDH = std::make_shared("RDHCounter", "RDH Diagnostics;RDH Word;Crate;Words", + RawDataDecoder::nwords, 0, RawDataDecoder::nwords, + RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates); + mDecoderRaw.mCounterRDH[0].MakeHistogram(mHistoRDH.get()); + getObjectsManager()->startPublishing(mHistoRDH.get()); + // DRM + mHistoDRM = std::make_shared("DRMCounter", "DRM Diagnostics;DRM Word;Crate;Words", + RawDataDecoder::nwords, 0, RawDataDecoder::nwords, + RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates); + mDecoderRaw.mCounterDRM[0].MakeHistogram(mHistoDRM.get()); + getObjectsManager()->startPublishing(mHistoDRM.get()); + // LTM + mHistoLTM = std::make_shared("LTMCounter", "LTM Diagnostics;LTM Word;Crate;Words", + RawDataDecoder::nwords, 0, RawDataDecoder::nwords, + RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates); + mDecoderRaw.mCounterLTM[0].MakeHistogram(mHistoLTM.get()); + getObjectsManager()->startPublishing(mHistoLTM.get()); + // TRMs + for (unsigned int j = 0; j < RawDataDecoder::ntrms; j++) { + mHistoTRM[j] = std::make_shared(Form("TRMCounterSlot%02i", j + 3), Form("TRM Slot %i Diagnostics;TRM Word;Crate;Words", j + 3), + RawDataDecoder::nwords, 0, RawDataDecoder::nwords, + RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates); + mDecoderRaw.mCounterTRM[0][j].MakeHistogram(mHistoTRM[j].get()); + getObjectsManager()->startPublishing(mHistoTRM[j].get()); + } + // Whole Crates + for (unsigned int j = 0; j < RawDataDecoder::ncrates; j++) { + mHistoCrate[j] = std::make_shared(Form("CrateCounter%02i", j), + Form("Crate%02i Diagnostics;Word;Slot", j), + RawDataDecoder::nwords, 0, RawDataDecoder::nwords, + RawDataDecoder::nslots + 1, 0, RawDataDecoder::nslots + 1); + mHistoCrate[j].get()->GetYaxis()->SetBinLabel(1, "RDH"); + mHistoCrate[j].get()->GetYaxis()->SetBinLabel(2, "DRM"); + mHistoCrate[j].get()->GetYaxis()->SetBinLabel(3, "LTM"); + for (int k = 0; k < 10; k++) { + mHistoCrate[j].get()->GetYaxis()->SetBinLabel(4 + k, Form("TRMSlot%02i", k + 3)); + } + getObjectsManager()->startPublishing(mHistoCrate[j].get()); + } + // Slot participating in all crates + mHistoSlotParticipating = std::make_shared("hSlotPartMask", + "Slot participating;Crate;Slot", + RawDataDecoder::ncrates, 0., RawDataDecoder::ncrates, + RawDataDecoder::nslots + 1, 0, RawDataDecoder::nslots + 1); + mHistoSlotParticipating->SetBit(TH1::kNoStats); + mHistoSlotParticipating.get()->GetYaxis()->SetBinLabel(1, "RDH"); + mHistoSlotParticipating.get()->GetYaxis()->SetBinLabel(2, "DRM"); + mHistoSlotParticipating.get()->GetYaxis()->SetBinLabel(3, "LTM"); + for (int k = 0; k < 10; k++) { + mHistoSlotParticipating.get()->GetYaxis()->SetBinLabel(4 + k, Form("TRMSlot%02i", k + 3)); + } + getObjectsManager()->startPublishing(mHistoSlotParticipating.get()); + + mHistoIndexEO = std::make_shared("hIndexEO", "Index Electronics Oriented;index EO;Counts", RawDataDecoder::nequipments, 0., RawDataDecoder::nequipments); + mDecoderRaw.mCounterIndexEO.MakeHistogram(mHistoIndexEO.get()); + getObjectsManager()->startPublishing(mHistoIndexEO.get()); + mHistoIndexEOInTimeWin = std::make_shared("hIndexEOInTimeWin", "Index Electronics Oriented for noise analysis;index EO;Counts", RawDataDecoder::nequipments, 0., RawDataDecoder::nequipments); + mDecoderRaw.mCounterIndexEOInTimeWin.MakeHistogram(mHistoIndexEOInTimeWin.get()); + getObjectsManager()->startPublishing(mHistoIndexEOInTimeWin.get()); + mHistoTimeBC = std::make_shared("hTimeBC", "Raw BC Time;BC time (24.4 ps);Counts", 1024, 0., 1024.); + mDecoderRaw.mCounterTimeBC.MakeHistogram(mHistoTimeBC.get()); + getObjectsManager()->startPublishing(mHistoTimeBC.get()); + mHistoIndexEOIsNoise = std::make_shared("hIndexEOIsNoise", "Noisy Channels; index EO;Counts", RawDataDecoder::nequipments, 0., RawDataDecoder::nequipments); + mDecoderRaw.mCounterNoisyChannels.MakeHistogram(mHistoIndexEOIsNoise.get()); + getObjectsManager()->startPublishing(mHistoIndexEOIsNoise.get()); + mHistoRDHReceived = std::make_shared("hRDHTriggersReceived", "RDH Trigger Received;Crate;Triggers_{received}", RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates + 1); + mDecoderRaw.mCounterRDHTriggers[1].MakeHistogram(mHistoRDHReceived.get()); + getObjectsManager()->startPublishing(mHistoRDHReceived.get()); + mHistoRDHServed = std::make_shared("hRDHTriggersServed", "RDH Trigger Served;Crate;Triggers_{served}", RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates + 1); + mDecoderRaw.mCounterRDHTriggers[0].MakeHistogram(mHistoRDHServed.get()); + getObjectsManager()->startPublishing(mHistoRDHServed.get()); + mEffRDHTriggers = new TEfficiency("hEffRDHTriggers", "RDH Efficiency vs Crate; Crate; Eff(Served/Received)", RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates + 1); + getObjectsManager()->startPublishing(mEffRDHTriggers); + mHistoOrbitsPerCrate = std::make_shared("hOrbitsPerCrate", "Orbits per Crate;Orbits;Crate;Events", 800, 0, 800., RawDataDecoder::ncrates, 0, RawDataDecoder::ncrates + 1); + mDecoderRaw.mCounterOrbitsPerCrate[0].MakeHistogram(mHistoOrbitsPerCrate.get()); + getObjectsManager()->startPublishing(mHistoOrbitsPerCrate.get()); + + mDecoderRaw.initHistograms(); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoHits.get()); + if (mDecoderRaw.isDebugCrateMultiplicity()) { + for (unsigned int i = 0; i < RawDataDecoder::ncrates; i++) { + getObjectsManager()->startPublishing(mDecoderRaw.mHistoHitsCrate[i].get()); + } + } + getObjectsManager()->startPublishing(mDecoderRaw.mHistoTime.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoTOT.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoDiagnostic.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoNErrors.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoErrorBits.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoError.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoNTests.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoTest.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoOrbitID.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoNoiseMap.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoIndexEOHitRate.get()); + getObjectsManager()->startPublishing(mDecoderRaw.mHistoPayload.get()); +} + +void TaskRaw::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + reset(); + mDecoderRaw.resetHistograms(); +} + +void TaskRaw::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TaskRaw::monitorData(o2::framework::ProcessingContext& ctx) +{ + // Reset counter before decode() call + mDecoderRaw.mCounterRDHOpen.Reset(); + // + { + /** loop over input parts **/ + for (auto const& input : o2::framework::InputRecordWalker(ctx.inputs())) { + /** input **/ + const auto* headerIn = o2::framework::DataRefUtils::getHeader(input); + const auto payloadIn = input.payload; + const auto payloadInSize = o2::framework::DataRefUtils::getPayloadSize(input); + // TODO: better use InputRecord::get(input) to extract a span and pass + // either the span or its data pointer and size + mDecoderRaw.setDecoderBuffer(payloadIn); + mDecoderRaw.setDecoderBufferSize(payloadInSize); + + // + mDecoderRaw.decode(); + } + } + // Count number of orbits per crate + for (unsigned int ncrate = 0; ncrate < RawDataDecoder::ncrates; ncrate++) { // loop over crates + if (mDecoderRaw.mCounterRDHOpen.HowMany(ncrate) <= 799) { + mDecoderRaw.mCounterOrbitsPerCrate[ncrate].Count(mDecoderRaw.mCounterRDHOpen.HowMany(ncrate)); + } else { + mDecoderRaw.mCounterOrbitsPerCrate[ncrate].Count(799); + } + } +} + +void TaskRaw::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + for (unsigned int crate = 0; crate < RawDataDecoder::ncrates; crate++) { // Filling histograms only at the end of the cycle + mDecoderRaw.mCounterRDH[crate].FillHistogram(mHistoRDH.get(), crate + 1); + mDecoderRaw.mCounterDRM[crate].FillHistogram(mHistoDRM.get(), crate + 1); + mDecoderRaw.mCounterLTM[crate].FillHistogram(mHistoLTM.get(), crate + 1); + mHistoSlotParticipating->SetBinContent(crate + 1, 2, mDecoderRaw.mCounterDRM[crate].HowMany(0)); + mHistoSlotParticipating->SetBinContent(crate + 1, 3, mDecoderRaw.mCounterLTM[crate].HowMany(0)); + mDecoderRaw.mCounterOrbitsPerCrate[crate].FillHistogram(mHistoOrbitsPerCrate.get(), crate + 1); + for (unsigned int j = 0; j < RawDataDecoder::ntrms; j++) { + mDecoderRaw.mCounterTRM[crate][j].FillHistogram(mHistoTRM[j].get(), crate + 1); + mHistoSlotParticipating->SetBinContent(crate + 1, j + 4, mDecoderRaw.mCounterTRM[crate][j].HowMany(0)); + } + mHistoSlotParticipating->SetBinContent(crate + 1, 1, mDecoderRaw.mCounterRDH[crate].HowMany(0)); + mDecoderRaw.mCounterRDHTriggers[1].FillHistogram(mHistoRDHReceived.get()); + mDecoderRaw.mCounterRDHTriggers[0].FillHistogram(mHistoRDHServed.get()); + } + mEffRDHTriggers->SetTotalHistogram(*mHistoRDHReceived, "f"); + mEffRDHTriggers->SetPassedHistogram(*mHistoRDHServed, ""); + mDecoderRaw.mCounterIndexEO.FillHistogram(mHistoIndexEO.get()); + mDecoderRaw.mCounterIndexEOInTimeWin.FillHistogram(mHistoIndexEOInTimeWin.get()); + mDecoderRaw.mCounterTimeBC.FillHistogram(mHistoTimeBC.get()); + mDecoderRaw.estimateNoise(mHistoIndexEOIsNoise); + + // Reshuffling information from the cards to the whole crate + for (unsigned int slot = 0; slot < RawDataDecoder::nslots; slot++) { // Loop over slots + TH2F* diagnosticHisto = nullptr; + if (slot == 0) { // We have a DRM! + diagnosticHisto = mHistoDRM.get(); + } else if (slot == 1) { // We have a LTM! + diagnosticHisto = mHistoLTM.get(); + } else { // We have a TRM! + diagnosticHisto = mHistoTRM[slot - 2].get(); + } + if (diagnosticHisto) { + for (int crate = 0; crate < diagnosticHisto->GetNbinsY(); crate++) { // Loop over crates + for (int word = 0; word < diagnosticHisto->GetNbinsX(); word++) { // Loop over words + mHistoCrate[crate]->SetBinContent(word + 1, slot + 2, // Shift position 1 to make room for the RDH + diagnosticHisto->GetBinContent(word + 1, crate + 1)); + } + } + } else { + LOG(warn) << "Did not find diagnostic histogram for slot " << slot << " for reshuffling"; + } + } + for (unsigned int crate = 0; crate < RawDataDecoder::ncrates; crate++) { // Loop over crates for how many RDH read + for (unsigned int word = 0; word < mDecoderRaw.mCounterRDH[crate].Size(); word++) { // Loop over words + mHistoCrate[crate]->SetBinContent(word + 1, 1, mDecoderRaw.mCounterRDH[crate].HowMany(word)); + } + } +} + +void TaskRaw::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TaskRaw::reset() +{ + // clean all the monitor objects here + + for (unsigned int crate = 0; crate < RawDataDecoder::ncrates; crate++) { + mDecoderRaw.mCounterRDH[crate].Reset(); + mDecoderRaw.mCounterDRM[crate].Reset(); + mDecoderRaw.mCounterLTM[crate].Reset(); + for (unsigned int j = 0; j < RawDataDecoder::ntrms; j++) { + mDecoderRaw.mCounterTRM[crate][j].Reset(); + } + } + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mHistoRDH->Reset(); + mHistoDRM->Reset(); + mHistoLTM->Reset(); + for (unsigned int j = 0; j < RawDataDecoder::ntrms; j++) { + mHistoTRM[j]->Reset(); + } + for (unsigned int crate = 0; crate < RawDataDecoder::ncrates; crate++) { + mHistoCrate[crate]->Reset(); + } + mHistoSlotParticipating->Reset(); + mHistoIndexEO->Reset(); + mHistoIndexEOInTimeWin->Reset(); + mHistoTimeBC->Reset(); + mHistoIndexEOIsNoise->Reset(); + mHistoRDHServed->Reset(); + mHistoRDHReceived->Reset(); + + mDecoderRaw.resetHistograms(); +} + +const char* RawDataDecoder::RDHDiagnosticsName[RawDataDecoder::nRDHwords] = { "RDH_HAS_DATA", "RDH_DECODER_FATAL", "RDH_TRIGGER_ERROR" }; + +const char* RawDataDecoder::DRMDiagnosticName[RawDataDecoder::nwords] = { + diagnostic::DRMDiagnosticName[0], + diagnostic::DRMDiagnosticName[1], + diagnostic::DRMDiagnosticName[2], + diagnostic::DRMDiagnosticName[3], + diagnostic::DRMDiagnosticName[4], + diagnostic::DRMDiagnosticName[5], + diagnostic::DRMDiagnosticName[6], + diagnostic::DRMDiagnosticName[7], + diagnostic::DRMDiagnosticName[8], + diagnostic::DRMDiagnosticName[9], + diagnostic::DRMDiagnosticName[10], + diagnostic::DRMDiagnosticName[11], + diagnostic::DRMDiagnosticName[12], + diagnostic::DRMDiagnosticName[13], + diagnostic::DRMDiagnosticName[14], + diagnostic::DRMDiagnosticName[15], + diagnostic::DRMDiagnosticName[16], + diagnostic::DRMDiagnosticName[17], + diagnostic::DRMDiagnosticName[18], + diagnostic::DRMDiagnosticName[19], + diagnostic::DRMDiagnosticName[20], + diagnostic::DRMDiagnosticName[21], + diagnostic::DRMDiagnosticName[22], + diagnostic::DRMDiagnosticName[23], + diagnostic::DRMDiagnosticName[24], + diagnostic::DRMDiagnosticName[25], + diagnostic::DRMDiagnosticName[26], + diagnostic::DRMDiagnosticName[27], + diagnostic::DRMDiagnosticName[28], + diagnostic::DRMDiagnosticName[29], + diagnostic::DRMDiagnosticName[30], + diagnostic::DRMDiagnosticName[31] +}; + +const char* RawDataDecoder::LTMDiagnosticName[RawDataDecoder::nwords] = { + diagnostic::LTMDiagnosticName[0], + diagnostic::LTMDiagnosticName[1], + diagnostic::LTMDiagnosticName[2], + diagnostic::LTMDiagnosticName[3], + diagnostic::LTMDiagnosticName[4], + diagnostic::LTMDiagnosticName[5], + diagnostic::LTMDiagnosticName[6], + diagnostic::LTMDiagnosticName[7], + diagnostic::LTMDiagnosticName[8], + diagnostic::LTMDiagnosticName[9], + diagnostic::LTMDiagnosticName[10], + diagnostic::LTMDiagnosticName[11], + diagnostic::LTMDiagnosticName[12], + diagnostic::LTMDiagnosticName[13], + diagnostic::LTMDiagnosticName[14], + diagnostic::LTMDiagnosticName[15], + diagnostic::LTMDiagnosticName[16], + diagnostic::LTMDiagnosticName[17], + diagnostic::LTMDiagnosticName[18], + diagnostic::LTMDiagnosticName[19], + diagnostic::LTMDiagnosticName[20], + diagnostic::LTMDiagnosticName[21], + diagnostic::LTMDiagnosticName[22], + diagnostic::LTMDiagnosticName[23], + diagnostic::LTMDiagnosticName[24], + diagnostic::LTMDiagnosticName[25], + diagnostic::LTMDiagnosticName[26], + diagnostic::LTMDiagnosticName[27], + diagnostic::LTMDiagnosticName[28], + diagnostic::LTMDiagnosticName[29], + diagnostic::LTMDiagnosticName[30], + diagnostic::LTMDiagnosticName[31] +}; + +const char* RawDataDecoder::TRMDiagnosticName[RawDataDecoder::nwords] = { + diagnostic::TRMDiagnosticName[0], + diagnostic::TRMDiagnosticName[1], + diagnostic::TRMDiagnosticName[2], + diagnostic::TRMDiagnosticName[3], + diagnostic::TRMDiagnosticName[4], + diagnostic::TRMDiagnosticName[5], + diagnostic::TRMDiagnosticName[6], + diagnostic::TRMDiagnosticName[7], + diagnostic::TRMDiagnosticName[8], + diagnostic::TRMDiagnosticName[9], + diagnostic::TRMDiagnosticName[10], + diagnostic::TRMDiagnosticName[11], + diagnostic::TRMDiagnosticName[12], + diagnostic::TRMDiagnosticName[13], + diagnostic::TRMDiagnosticName[14], + diagnostic::TRMDiagnosticName[15], + diagnostic::TRMDiagnosticName[16], + diagnostic::TRMDiagnosticName[17], + diagnostic::TRMDiagnosticName[18], + diagnostic::TRMDiagnosticName[19], + diagnostic::TRMDiagnosticName[20], + diagnostic::TRMDiagnosticName[21], + diagnostic::TRMDiagnosticName[22], + diagnostic::TRMDiagnosticName[23], + diagnostic::TRMDiagnosticName[24], + diagnostic::TRMDiagnosticName[25], + diagnostic::TRMDiagnosticName[26], + diagnostic::TRMDiagnosticName[27], + diagnostic::TRMDiagnosticName[28], + diagnostic::TRMDiagnosticName[29], + diagnostic::TRMDiagnosticName[30], + diagnostic::TRMDiagnosticName[31] +}; + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/TrendingCalibDiagnostics.cxx b/Modules/TOF/src/TrendingCalibDiagnostics.cxx new file mode 100644 index 0000000000..fc912c8816 --- /dev/null +++ b/Modules/TOF/src/TrendingCalibDiagnostics.cxx @@ -0,0 +1,248 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingCalibDiagnostics.cxx +/// \author Sofia Tomassini +/// \author Francesca Ercolessi + +#include "TOF/TrendingCalibDiagnostics.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/ActivityHelpers.h" +#include "CCDB/BasicCCDBManager.h" +#include "QualityControl/UserCodeInterface.h" +#include "DetectorsCalibration/Utils.h" +#include "TOFBase/Utils.h" +#include "TOFCalibration/LHCClockCalibrator.h" +#include "TOFBase/Geo.h" +#include "CCDB/CcdbApi.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::tof; + +using Diagnostics = o2::tof::Diagnostic; + +void TrendingCalibDiagnostics::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingConfigTOF(getID(), config); + mHost = config.get("qc.postprocessing." + getID() + ".dataSourceURL"); +} + +void TrendingCalibDiagnostics::initialize(Trigger, framework::ServiceRegistryRef) +{ + mCdbApi.init(mHost); + + // Preparing data structure of TTree + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("time", &mTime); + mTrend->Branch("crateEfficiency", &mCrateEff); + mTrend->Branch("TRMEfficiency", &mTRMEff); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create(source.moduleName, source.reductorName)); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } + getObjectsManager()->startPublishing(mTrend.get()); +} + +void TrendingCalibDiagnostics::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& ccdb = services.get(); + + trendValues(t, ccdb); + generatePlots(); +} + +void TrendingCalibDiagnostics::finalize(Trigger t, framework::ServiceRegistryRef) +{ + generatePlots(); +} + +void TrendingCalibDiagnostics::trendValues(const Trigger& t, repository::DatabaseInterface& ccdb) +{ + mTime = t.activity.mValidity.getMax() / 1000; + mMetaData.runNumber = t.activity.mId; + mCrateEff = 0.; + mTRMEff = 0.; + + float crate[72] = {}; + float trm[72][10] = {}; + float row = 0.; + float nrowall = 0; + map metadata; // can be empty + + for (auto& dataSource : mConfig.dataSources) { + + if (dataSource.type == "ccdb") { + + auto calib_object = UserCodeInterface::retrieveConditionAny(dataSource.path, metadata, t.timestamp); + + if (!calib_object) { + ILOG(Error, Support) << "Could not retrieve calibration file '" << dataSource.path << "'." << ENDM; + } else { + ILOG(Info, Support) << "Retrieved calibration file '" << dataSource.path << "'." << ENDM; + row = calib_object->getFrequencyROW(); + if (row <= 0) { + ILOG(Info, Support) << "Readout window size " << row << "." << ENDM; + continue; + } + + const auto& vec = calib_object->getVector(); + for (const auto& obj : vec) { + int ic = calib_object->getCrate(obj.first); + int itrm = calib_object->getSlot(obj.first); + if (itrm > 2 && itrm < 13) { + itrm -= 3; + trm[ic][itrm] += obj.second; + } + } + + for (int ic = 0; ic < 72; ic++) { + crate[ic] = calib_object->getFrequencyEmptyCrate(ic); + mCrateEff += crate[ic]; + nrowall += (row - crate[ic]); + for (int itrm = 0; itrm < 10; itrm++) { + mTRMEff += trm[ic][itrm]; + } + } + + mCrateEff = (row - mCrateEff / 72) / row; + if (nrowall > 0) { + mTRMEff = (nrowall - mTRMEff / 10) / nrowall; + } else { + mTRMEff = 0; + } + } + } else { + ILOG(Error, Support) << "Unknown type of data source '" << dataSource.type << "'. Expected: ccdb" << ENDM; + } + } + + mTrend->Fill(); +} + +void TrendingCalibDiagnostics::generatePlots() +{ + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, won't generate any plots." << ENDM; + return; + } + + ILOG(Info, Support) << "Generating " << mConfig.plots.size() << " plots." << ENDM; + + for (const auto& plot : mConfig.plots) { + + // Before we generate any new plots, we have to delete existing under the same names. + // It seems that ROOT cannot handle an existence of two canvases with a common name in the same process. + TCanvas* c; + if (!mPlots.count(plot.name)) { + c = new TCanvas(plot.name.c_str(), plot.title.c_str()); + mPlots[plot.name] = c; + getObjectsManager()->startPublishing(c); + } else { + c = (TCanvas*)mPlots[plot.name]; + c->cd(); + } + + // we determine the order of the plot, i.e. if it is a histogram (1), graph (2), or any higher dimension. + const size_t plotOrder = std::count(plot.varexp.begin(), plot.varexp.end(), ':') + 1; + // we have to delete the graph errors after the plot is saved, unfortunately the canvas does not take its ownership + TGraphErrors* graphErrors = nullptr; + + mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), plot.option.c_str()); + + // For graphs we allow to draw errors if they are specified. + if (!plot.graphErrors.empty()) { + if (plotOrder != 2) { + ILOG(Error, Support) << "Non empty graphErrors seen for the plot '" << plot.name << "', which is not a graph, ignoring." << ENDM; + } else { + // We generate some 4-D points, where 2 dimensions represent graph points and 2 others are the error bars + std::string varexpWithErrors(plot.varexp + ":" + plot.graphErrors); + mTrend->Draw(varexpWithErrors.c_str(), plot.selection.c_str(), "goff"); + graphErrors = new TGraphErrors(mTrend->GetSelectedRows(), mTrend->GetVal(1), mTrend->GetVal(0), mTrend->GetVal(2), mTrend->GetVal(3)); + // We draw on the same plot as the main graph, but only error bars + graphErrors->Draw("SAME E"); + // We try to convince ROOT to delete graphErrors together with the rest of the canvas. + if (auto* pad = c->GetPad(0)) { + if (auto* primitives = pad->GetListOfPrimitives()) { + primitives->Add(graphErrors); + } + } + } + } + + // Postprocessing the plot - adding specified titles, configuring time-based plots, flushing buffers. + // Notice that axes and title are drawn using a histogram, even in the case of graphs. + if (auto histo = dynamic_cast(c->GetPrimitive("htemp"))) { + // The title of histogram is printed, not the title of canvas => we set it as well. + histo->SetTitle(plot.title.c_str()); + // We have to update the canvas to make the title appear. + c->Update(); + + // After the update, the title has a different size and it is not in the center anymore. We have to fix that. + if (auto title = dynamic_cast(c->GetPrimitive("title"))) { + title->SetBBoxCenterX(c->GetBBoxCenter().fX); + c->Modified(); + c->Update(); + } else { + ILOG(Error, Devel) << "Could not get the title TPaveText of the plot '" << plot.name << "'." << ENDM; + } + + // We have to explicitly configure showing time on x axis. + // I hope that looking for ":time" is enough here and someone doesn't come with an exotic use-case. + if (plot.varexp.find(":time") != std::string::npos || plot.varexp.find(":startValidity") != std::string::npos || plot.varexp.find(":endValidity") != std::string::npos) { + histo->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + histo->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + histo->GetXaxis()->SetTimeOffset(0.0); + histo->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + if (plot.varexp.find("startValidity:") != std::string::npos || plot.varexp.find("endValidity:") != std::string::npos) { + histo->GetYaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + histo->GetYaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + histo->GetYaxis()->SetTimeOffset(0.0); + histo->GetYaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + // QCG doesn't empty the buffers before visualizing the plot, nor does ROOT when saving the file, + // so we have to do it here. + histo->BufferEmpty(); + } else { + ILOG(Error, Devel) << "Could not get the htemp histogram of the plot '" << plot.name << "'." << ENDM; + } + } +} diff --git a/Modules/TOF/src/TrendingCalibLHCphase.cxx b/Modules/TOF/src/TrendingCalibLHCphase.cxx new file mode 100644 index 0000000000..2b73942b4d --- /dev/null +++ b/Modules/TOF/src/TrendingCalibLHCphase.cxx @@ -0,0 +1,218 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingCalibLHCphase.cxx +/// \author Francesca Ercolessi + +#include "TOF/TrendingCalibLHCphase.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/ActivityHelpers.h" +#include "CCDB/BasicCCDBManager.h" +#include "QualityControl/UserCodeInterface.h" +#include "DetectorsCalibration/Utils.h" +#include "TOFBase/Utils.h" +#include "TOFCalibration/LHCClockCalibrator.h" +#include "TOFBase/Geo.h" +#include "CCDB/CcdbApi.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::tof; + +using LHCphase = o2::dataformats::CalibLHCphaseTOF; + +void TrendingCalibLHCphase::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingConfigTOF(getID(), config); + mHost = config.get("qc.postprocessing." + getID() + ".dataSourceURL"); +} + +void TrendingCalibLHCphase::initialize(Trigger, framework::ServiceRegistryRef) +{ + mCdbApi.init(mHost); + + // Preparing data structure of TTree + mTrend.reset(); + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("time", &mTime); + mTrend->Branch("phase", &mPhase); + mTrend->Branch("startValidity", &mStartValidity); + mTrend->Branch("endValidity", &mEndValidity); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create(source.moduleName, source.reductorName)); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } + getObjectsManager()->startPublishing(mTrend.get(), PublicationPolicy::ThroughStop); +} + +void TrendingCalibLHCphase::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& ccdb = services.get(); + + trendValues(t, ccdb); + generatePlots(); +} + +void TrendingCalibLHCphase::finalize(Trigger t, framework::ServiceRegistryRef) +{ + generatePlots(); +} + +void TrendingCalibLHCphase::trendValues(const Trigger& t, repository::DatabaseInterface& ccdb) +{ + mTime = t.activity.mValidity.getMax() / 1000; + mMetaData.runNumber = t.activity.mId; + + mPhase = 0.; + mStartValidity = 0; + mEndValidity = 0; + + map metadata; // can be empty + + for (auto& dataSource : mConfig.dataSources) { + + if (dataSource.type == "ccdb") { + + auto calib_object = UserCodeInterface::retrieveConditionAny(dataSource.path, metadata, t.timestamp); + + if (!calib_object) { + ILOG(Error, Support) << "Could not retrieve calibration file '" << dataSource.path << "'." << ENDM; + } else { + ILOG(Info, Support) << "Retrieved calibration file '" << dataSource.path << "'." << ENDM; + mPhase = calib_object->getLHCphase(0); + mStartValidity = (double)calib_object->getStartValidity() * 0.001; + mEndValidity = (double)calib_object->getEndValidity() * 0.001; + } + } else { + ILOG(Error, Support) << "Unknown type of data source '" << dataSource.type << "'. Expected: ccdb" << ENDM; + } + } + + mTrend->Fill(); +} + +void TrendingCalibLHCphase::generatePlots() +{ + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, won't generate any plots." << ENDM; + return; + } + + ILOG(Info, Support) << "Generating " << mConfig.plots.size() << " plots." << ENDM; + + for (const auto& plot : mConfig.plots) { + + // Before we generate any new plots, we have to delete existing under the same names. + // It seems that ROOT cannot handle an existence of two canvases with a common name in the same process. + TCanvas* c; + if (!mPlots.count(plot.name)) { + c = new TCanvas(plot.name.c_str(), plot.title.c_str()); + mPlots[plot.name] = c; + getObjectsManager()->startPublishing(c, PublicationPolicy::Forever); + } else { + c = (TCanvas*)mPlots[plot.name]; + c->cd(); + } + + // we determine the order of the plot, i.e. if it is a histogram (1), graph (2), or any higher dimension. + const size_t plotOrder = std::count(plot.varexp.begin(), plot.varexp.end(), ':') + 1; + // we have to delete the graph errors after the plot is saved, unfortunately the canvas does not take its ownership + TGraphErrors* graphErrors = nullptr; + + mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), plot.option.c_str()); + + // For graphs we allow to draw errors if they are specified. + if (!plot.graphErrors.empty()) { + if (plotOrder != 2) { + ILOG(Error, Support) << "Non empty graphErrors seen for the plot '" << plot.name << "', which is not a graph, ignoring." << ENDM; + } else { + // We generate some 4-D points, where 2 dimensions represent graph points and 2 others are the error bars + std::string varexpWithErrors(plot.varexp + ":" + plot.graphErrors); + mTrend->Draw(varexpWithErrors.c_str(), plot.selection.c_str(), "goff"); + graphErrors = new TGraphErrors(mTrend->GetSelectedRows(), mTrend->GetVal(1), mTrend->GetVal(0), mTrend->GetVal(2), mTrend->GetVal(3)); + // We draw on the same plot as the main graph, but only error bars + graphErrors->Draw("SAME E"); + // We try to convince ROOT to delete graphErrors together with the rest of the canvas. + if (auto* pad = c->GetPad(0)) { + if (auto* primitives = pad->GetListOfPrimitives()) { + primitives->Add(graphErrors); + } + } + } + } + + // Postprocessing the plot - adding specified titles, configuring time-based plots, flushing buffers. + // Notice that axes and title are drawn using a histogram, even in the case of graphs. + if (auto histo = dynamic_cast(c->GetPrimitive("htemp"))) { + // The title of histogram is printed, not the title of canvas => we set it as well. + histo->SetTitle(plot.title.c_str()); + // We have to update the canvas to make the title appear. + c->Update(); + + // After the update, the title has a different size and it is not in the center anymore. We have to fix that. + if (auto title = dynamic_cast(c->GetPrimitive("title"))) { + title->SetBBoxCenterX(c->GetBBoxCenter().fX); + c->Modified(); + c->Update(); + } else { + ILOG(Error, Devel) << "Could not get the title TPaveText of the plot '" << plot.name << "'." << ENDM; + } + + // We have to explicitly configure showing time on x axis. + // I hope that looking for ":time" is enough here and someone doesn't come with an exotic use-case. + if (plot.varexp.find(":time") != std::string::npos || plot.varexp.find(":startValidity") != std::string::npos || plot.varexp.find(":endValidity") != std::string::npos) { + histo->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + histo->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + histo->GetXaxis()->SetTimeOffset(0.0); + histo->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + if (plot.varexp.find("startValidity:") != std::string::npos || plot.varexp.find("endValidity:") != std::string::npos) { + histo->GetYaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + histo->GetYaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + histo->GetYaxis()->SetTimeOffset(0.0); + histo->GetYaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + // QCG doesn't empty the buffers before visualizing the plot, nor does ROOT when saving the file, + // so we have to do it here. + histo->BufferEmpty(); + } else { + ILOG(Error, Devel) << "Could not get the htemp histogram of the plot '" << plot.name << "'." << ENDM; + } + } +} diff --git a/Modules/TOF/src/TrendingConfigTOF.cxx b/Modules/TOF/src/TrendingConfigTOF.cxx new file mode 100644 index 0000000000..35aa6485e1 --- /dev/null +++ b/Modules/TOF/src/TrendingConfigTOF.cxx @@ -0,0 +1,75 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingConfigTOF.cxx +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +/// \brief File for the trending task configuration for the number of hits in TOF +/// \since 05/10/2021 +/// + +#include "TOF/TrendingConfigTOF.h" +#include "QualityControl/QcInfoLogger.h" +#include + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::tof +{ + +TrendingConfigTOF::TrendingConfigTOF(std::string id, const boost::property_tree::ptree& config) + : PostProcessingConfig(id, config) +{ + if (const auto& customConfigs = config.get_child_optional("qc.postprocessing." + id + ".customization"); customConfigs.has_value()) { + for (const auto& customConfig : customConfigs.value()) { // Plot configuration + ILOG(Info, Support) << "Reading configuration " << customConfig.second.get("name") << ENDM; + if (const auto& customNames = customConfig.second.get_child_optional("name"); customNames.has_value()) { + if (customConfig.second.get("name") == "ThresholdSgn") { + mConfigTrendingRate.thresholdSignal = customConfig.second.get("value"); + ILOG(Info, Support) << "Setting thresholdSignal to " << mConfigTrendingRate.thresholdSignal << ENDM; + } else if (customConfig.second.get("name") == "ThresholdBkg") { + mConfigTrendingRate.thresholdBackground = customConfig.second.get("value"); + ILOG(Info, Support) << "Setting thresholdBackground to " << mConfigTrendingRate.thresholdBackground << ENDM; + } + } + } + } + for (const auto& plotConfig : config.get_child("qc.postprocessing." + id + ".plots")) { // Plot configuration + plots.push_back({ plotConfig.second.get("name"), + plotConfig.second.get("title", ""), + plotConfig.second.get("varexp"), + plotConfig.second.get("selection", ""), + plotConfig.second.get("option", ""), + plotConfig.second.get("graphErrors", "") }); + } + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + id + ".dataSources")) { // Data source configuration + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + sourceName.second.data(), + dataSourceConfig.second.get("reductorName"), + dataSourceConfig.second.get("moduleName") }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name"), + dataSourceConfig.second.get("reductorName"), + dataSourceConfig.second.get("moduleName") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + id + ".dataSources'"); + } + } +} + +} // namespace o2::quality_control_modules::tof diff --git a/Modules/TOF/src/TrendingHits.cxx b/Modules/TOF/src/TrendingHits.cxx new file mode 100644 index 0000000000..f6d0f09bd6 --- /dev/null +++ b/Modules/TOF/src/TrendingHits.cxx @@ -0,0 +1,180 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingHits.cxx +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +/// \brief Trending task for the number of hits in TOF +/// \since 05/10/2021 +/// + +#include "TOF/TrendingHits.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorHelpers.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/ActivityHelpers.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::tof; + +void TrendingHits::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingConfigTOF(getID(), config); +} + +void TrendingHits::initialize(Trigger, framework::ServiceRegistryRef) +{ + // Preparing data structure of TTree + mTrend.reset(); + mTrend = std::make_unique(); // todo: retrieve last TTree, so we continue trending. maybe do it optionally? + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + // mTrend->Branch("ntreeentries", &ntreeentries); + mTrend->Branch("time", &mTime); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create(source.moduleName, source.reductorName)); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } + getObjectsManager()->startPublishing(mTrend.get(), PublicationPolicy::ThroughStop); +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TrendingHits::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + trendValues(t, qcdb); + generatePlots(); +} + +void TrendingHits::finalize(Trigger t, framework::ServiceRegistryRef) +{ + generatePlots(); +} + +void TrendingHits::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + mTime = activity_helpers::isLegacyValidity(t.activity.mValidity) + ? t.timestamp / 1000 + : t.activity.mValidity.getMax() / 1000; // ROOT expects seconds since epoch. + mMetaData.runNumber = t.activity.mId; + + for (auto& dataSource : mConfig.dataSources) { + if (!reductor_helpers::updateReductor(mReductors[dataSource.name].get(), t, dataSource, qcdb, *this)) { + ILOG(Error, Support) << "Failed to update reductor for data sources with path '" << dataSource.path + << "', name '" << dataSource.name + << "', type '" << dataSource.type << "'." << ENDM; + } + } + + mTrend->Fill(); +} + +void TrendingHits::generatePlots() +{ + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, won't generate any plots." << ENDM; + return; + } + + ILOG(Info, Support) << "Generating " << mConfig.plots.size() << " plots." << ENDM; + + for (const auto& plot : mConfig.plots) { + + // Before we generate any new plots, we have to delete existing under the same names. + // It seems that ROOT cannot handle an existence of two canvases with a common name in the same process. + TCanvas* c; + if (!mPlots.count(plot.name)) { + c = new TCanvas(plot.name.c_str(), plot.title.c_str()); + mPlots[plot.name] = c; + getObjectsManager()->startPublishing(c, PublicationPolicy::Forever); + } else { + c = (TCanvas*)mPlots[plot.name]; + c->cd(); + } + + // we determine the order of the plot, i.e. if it is a histogram (1), graph (2), or any higher dimension. + const size_t plotOrder = std::count(plot.varexp.begin(), plot.varexp.end(), ':') + 1; + // we have to delete the graph errors after the plot is saved, unfortunately the canvas does not take its ownership + TGraphErrors* graphErrors = nullptr; + + mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), plot.option.c_str()); + + // For graphs we allow to draw errors if they are specified. + if (!plot.graphErrors.empty()) { + if (plotOrder != 2) { + ILOG(Error, Support) << "Non empty graphErrors seen for the plot '" << plot.name << "', which is not a graph, ignoring." << ENDM; + } else { + // We generate some 4-D points, where 2 dimensions represent graph points and 2 others are the error bars + std::string varexpWithErrors(plot.varexp + ":" + plot.graphErrors); + mTrend->Draw(varexpWithErrors.c_str(), plot.selection.c_str(), "goff"); + graphErrors = new TGraphErrors(mTrend->GetSelectedRows(), mTrend->GetVal(1), mTrend->GetVal(0), mTrend->GetVal(2), mTrend->GetVal(3)); + // We draw on the same plot as the main graph, but only error bars + graphErrors->Draw("SAME E"); + // We try to convince ROOT to delete graphErrors together with the rest of the canvas. + if (auto* pad = c->GetPad(0)) { + if (auto* primitives = pad->GetListOfPrimitives()) { + primitives->Add(graphErrors); + } + } + } + } + + // Postprocessing the plot - adding specified titles, configuring time-based plots, flushing buffers. + // Notice that axes and title are drawn using a histogram, even in the case of graphs. + if (auto histo = dynamic_cast(c->GetPrimitive("htemp"))) { + // The title of histogram is printed, not the title of canvas => we set it as well. + histo->SetTitle(plot.title.c_str()); + // We have to update the canvas to make the title appear. + c->Update(); + + // After the update, the title has a different size and it is not in the center anymore. We have to fix that. + if (auto title = dynamic_cast(c->GetPrimitive("title"))) { + title->SetBBoxCenterX(c->GetBBoxCenter().fX); + // It will have an effect only after invoking Draw again. + title->Draw(); + } else { + ILOG(Error, Devel) << "Could not get the title TPaveText of the plot '" << plot.name << "'." << ENDM; + } + + // We have to explicitly configure showing time on x axis. + // I hope that looking for ":time" is enough here and someone doesn't come with an exotic use-case. + if (plot.varexp.find(":time") != std::string::npos) { + histo->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + histo->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + histo->GetXaxis()->SetTimeOffset(0.0); + histo->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + // QCG doesn't empty the buffers before visualizing the plot, nor does ROOT when saving the file, + // so we have to do it here. + histo->BufferEmpty(); + } else { + ILOG(Error, Devel) << "Could not get the htemp histogram of the plot '" << plot.name << "'." << ENDM; + } + } +} diff --git a/Modules/TOF/src/TrendingRate.cxx b/Modules/TOF/src/TrendingRate.cxx new file mode 100644 index 0000000000..98d32cca16 --- /dev/null +++ b/Modules/TOF/src/TrendingRate.cxx @@ -0,0 +1,385 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingRate.cxx +/// \author Nicolò Jacazio nicolo.jacazio@cern.ch +/// \author Francesco Noferini francesco.noferini@cern.ch +/// \author Francesca Ercolessi francesca.ercolessi@cern.ch +/// \brief Trending of the TOF interaction rate +/// \since 06/06/2022 +/// + +#include "TOF/TrendingRate.h" +#include "TOFBase/Geo.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/ActivityHelpers.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::tof; +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::tof; + +void TrendingRate::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingConfigTOF(getID(), config); + mThresholdSgn = mConfig.mConfigTrendingRate.thresholdSignal; + mThresholdBkg = mConfig.mConfigTrendingRate.thresholdBackground; +} + +void TrendingRate::computeTOFRates(TH2F* h, std::vector& bcInt, std::vector& bcRate, std::vector& bcPileup) +{ + + mCollisionRate = 0.; + mPileupRate = 0.; + mNoiseRatePerChannel = 0.; + mNIBC = 0; + + if (!h) { + ILOG(Warning, Support) << "Got no histogram, skipping" << ENDM; + return; + } + TH1D* hback = nullptr; + + if (!mPreviousPlot) { + // create object for comparing with the past since + mPreviousPlot = new TH2F(*h); + mPreviousPlot->SetName("hTOFprevIRmap"); + mPreviousPlot->Reset(); + } + + TH2F hDiffGlobal(*h); + hDiffGlobal.SetName("hTOFhitDiff"); + hDiffGlobal.Add(mPreviousPlot, -1); + + TProfile* hpdiff = hDiffGlobal.ProfileX("hProTOFhitDiff"); + + // Counting background + int nb = 0; + float ybin_width = hDiffGlobal.GetYaxis()->GetBinWidth(1) * 0.5; + for (int i = 1; i <= hpdiff->GetNbinsX(); i++) { + if (hpdiff->GetBinContent(i) - ybin_width < mThresholdBkg) { + if (!hback) { + hback = hDiffGlobal.ProjectionY("back", i, i); + } else { + hback->Add(hDiffGlobal.ProjectionY("tmp", i, i)); + } + nb++; + } + } + + // update previous object status + mPreviousPlot->Reset(); + mPreviousPlot->Add(h); + + if (nb == 0) { // threshold too low? return since hback was not created + ILOG(Warning, Support) << "Counted 0 background events, BKG threshold might be too low!" << ENDM; + delete hpdiff; + bcInt.push_back(0.); + bcRate.push_back(0.); + bcPileup.push_back(0.); + return; + } + if (hback->Integral() < 0 || hback->GetMean() < ybin_width) { + return; + } + + if (mActiveChannels > 0) { + mNoiseRatePerChannel = (hback->GetMean() - ybin_width) / orbit_lenght * h->GetNbinsX() / mActiveChannels; + } + + std::vector signals; + + float sumw = 0.f; + float pilup = 0.f; + float ratetot = 0.f; + + if (nb) { + for (int i = 1; i <= hDiffGlobal.GetNbinsX(); i++) { + if (hpdiff->GetBinContent(i) - ybin_width > mThresholdSgn) { + signals.push_back(i); + mNIBC++; + } + } + + for (int i = 0; i < signals.size(); i++) { + TH1D* hb = new TH1D(*hback); + hb->SetName(Form("hback_%d", i)); + const int ibc = signals[i]; + const int bcmin = (ibc - 1) * 18; + const int bcmax = ibc * 18; + TH1D* hs = hDiffGlobal.ProjectionY(Form("sign_%d_%d", bcmin, bcmax), ibc, ibc); + hs->SetTitle(Form("%d < BC < %d", bcmin, bcmax)); + if (hb->GetBinContent(1)) { + hb->Scale(hs->GetBinContent(1) / hb->GetBinContent(1)); + } else { + delete hb; + delete hs; + + continue; + } + const float overall = hs->Integral(); + if (overall <= 0.f) { + ILOG(Warning, Support) << "no signal for BC index " << ibc << ENDM; + delete hb; + delete hs; + + continue; + } + const float background = hb->Integral(); + if (background <= 0.f) { + ILOG(Warning, Support) << "no background for BC index " << ibc << ENDM; + delete hb; + delete hs; + + continue; + } + const float prob = (overall - background) / overall; + if ((1.f - prob) < 0.f) { + ILOG(Warning, Support) << "Probability is 1, can't comute mu" << ENDM; + delete hb; + delete hs; + + continue; + } + const float mu = TMath::Log(1.f / (1.f - prob)); + const float rate = mu / orbit_lenght; + bcInt.push_back(ibc); + bcRate.push_back(rate); + if (prob <= 0.f) { + ILOG(Warning, Support) << "Probability is 0, can't compute pileup" << ENDM; + delete hb; + delete hs; + + continue; + } + bcPileup.push_back(mu / prob); + sumw += rate; + pilup += mu / prob * rate; + ratetot += rate; + //ILOG(Info, Support) << "interaction prob = " << mu << ", rate=" << rate << " Hz, mu=" << mu / prob << ENDM; + delete hb; + delete hs; + } + + if (sumw > 0) { + mCollisionRate = ratetot; + mPileupRate = pilup / sumw - 1; + } + delete hback; + } + + delete hpdiff; +} + +void TrendingRate::initialize(Trigger, framework::ServiceRegistryRef) +{ + // Setting parameters + + // Preparing data structure of TTree + mTrend.reset(); + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + mTrend->Branch("time", &mTime); + mTrend->Branch("noiseRate", &mNoiseRatePerChannel); + mTrend->Branch("collisionRate", &mCollisionRate); + mTrend->Branch("activeChannels", &mActiveChannels); + mTrend->Branch("pileup", &mPileupRate); + mTrend->Branch("nIBC", &mNIBC); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create(source.moduleName, source.reductorName)); + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } + getObjectsManager()->startPublishing(mTrend.get(), PublicationPolicy::ThroughStop); +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TrendingRate::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + trendValues(t, qcdb); + generatePlots(); +} + +void TrendingRate::finalize(Trigger t, framework::ServiceRegistryRef) +{ + generatePlots(); +} + +void TrendingRate::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + mTime = activity_helpers::isLegacyValidity(t.activity.mValidity) + ? t.timestamp / 1000 + : t.activity.mValidity.getMax() / 1000; // ROOT expects seconds since epoch. + mMetaData.runNumber = t.activity.mId; + + mActiveChannels = o2::tof::Geo::NCHANNELS; + std::shared_ptr moHistogramMultVsBC = nullptr; + + std::vector bcInt; + std::vector bcRate; + std::vector bcPileup; + + bool foundHitMap = false; + bool foundVsBC = false; + + for (auto& dataSource : mConfig.dataSources) { + auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name, t.timestamp, t.activity); + TObject* obj = mo ? mo->getObject() : nullptr; + if (!obj) { + ILOG(Error, Support) << "No MO retrieved from qcdb, name: " << dataSource.name << " - path:" << dataSource.path << " - timestamp" << t.timestamp << ENDM; + continue; + } + ILOG(Debug, Support) << "Got MO " << mo << ENDM; + if (dataSource.name == "HitMap") { + foundHitMap = true; + TH2F* hmap = dynamic_cast(obj); + if (hmap) { + TH2F hcopy(*hmap); + hcopy.Divide(hmap); + mActiveChannels = hcopy.Integral() * 24; + // ILOG(Info, Support) << "N channels = " << mActiveChannels << ENDM; + } + } else if (dataSource.name == "Multiplicity/VsBC") { + moHistogramMultVsBC = mo; + foundVsBC = true; + } + } + + if (!foundHitMap) { + ILOG(Info, Support) << "HitMap not found"; + return; + } + + if (!foundVsBC) { + ILOG(Info, Support) << "Multiplicity/VsBC not found"; + return; + } + + if (mActiveChannels < 1) { + ILOG(Info, Support) << "No active channels or empty objects"; + return; + } + + computeTOFRates(dynamic_cast(moHistogramMultVsBC->getObject()), bcInt, bcRate, bcPileup); + + /*ILOG(Info, Support) << "In " << mActiveChannels << " channels, noise rate per channel= " << mNoiseRatePerChannel << " Hz - collision rate = " << mCollisionRate << " Hz - mu-pilup = " << mPileupRate << ENDM; + for (int i = 0; i < bcInt.size(); i++) { + ILOG(Info, Support) << "bc = " << bcInt[i] * 18 - 9 << ") rate = " << bcRate[i] << ", pilup = " << bcPileup[i] - 1 << ENDM; + }*/ + + mTrend->Fill(); +} + +void TrendingRate::generatePlots() +{ + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, won't generate any plots." << ENDM; + return; + } + + ILOG(Info, Support) << "Generating " << mConfig.plots.size() << " plots." << ENDM; + + for (const auto& plot : mConfig.plots) { + + // Before we generate any new plots, we have to delete existing under the same names. + // It seems that ROOT cannot handle an existence of two canvases with a common name in the same process. + TCanvas* c; + if (!mPlots.count(plot.name)) { + c = new TCanvas(plot.name.c_str(), plot.title.c_str()); + mPlots[plot.name] = c; + getObjectsManager()->startPublishing(c, PublicationPolicy::Forever); + } else { + c = (TCanvas*)mPlots[plot.name]; + c->cd(); + } + + // we determine the order of the plot, i.e. if it is a histogram (1), graph (2), or any higher dimension. + const size_t plotOrder = std::count(plot.varexp.begin(), plot.varexp.end(), ':') + 1; + // we have to delete the graph errors after the plot is saved, unfortunately the canvas does not take its ownership + TGraphErrors* graphErrors = nullptr; + + mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), plot.option.c_str()); + + // For graphs we allow to draw errors if they are specified. + if (!plot.graphErrors.empty()) { + if (plotOrder != 2) { + ILOG(Error, Support) << "Non empty graphErrors seen for the plot '" << plot.name << "', which is not a graph, ignoring." << ENDM; + } else { + // We generate some 4-D points, where 2 dimensions represent graph points and 2 others are the error bars + std::string varexpWithErrors(plot.varexp + ":" + plot.graphErrors); + mTrend->Draw(varexpWithErrors.c_str(), plot.selection.c_str(), "goff"); + graphErrors = new TGraphErrors(mTrend->GetSelectedRows(), mTrend->GetVal(1), mTrend->GetVal(0), mTrend->GetVal(2), mTrend->GetVal(3)); + // We draw on the same plot as the main graph, but only error bars + graphErrors->Draw("SAME E"); + // We try to convince ROOT to delete graphErrors together with the rest of the canvas. + if (auto* pad = c->GetPad(0)) { + if (auto* primitives = pad->GetListOfPrimitives()) { + primitives->Add(graphErrors); + } + } + } + } + + // Postprocessing the plot - adding specified titles, configuring time-based plots, flushing buffers. + // Notice that axes and title are drawn using a histogram, even in the case of graphs. + if (auto histo = dynamic_cast(c->GetPrimitive("htemp"))) { + // The title of histogram is printed, not the title of canvas => we set it as well. + histo->SetTitle(plot.title.c_str()); + // We have to update the canvas to make the title appear. + c->Update(); + + // After the update, the title has a different size and it is not in the center anymore. We have to fix that. + if (auto title = dynamic_cast(c->GetPrimitive("title"))) { + title->SetBBoxCenterX(c->GetBBoxCenter().fX); + // It will have an effect only after invoking Draw again. + title->Draw(); + } else { + ILOG(Error, Devel) << "Could not get the title TPaveText of the plot '" << plot.name << "'." << ENDM; + } + + // We have to explicitly configure showing time on x axis. + // I hope that looking for ":time" is enough here and someone doesn't come with an exotic use-case. + if (plot.varexp.find(":time") != std::string::npos) { + histo->GetXaxis()->SetTimeDisplay(1); + // It deals with highly congested dates labels + histo->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + histo->GetXaxis()->SetTimeOffset(0.0); + histo->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + } + // QCG doesn't empty the buffers before visualizing the plot, nor does ROOT when saving the file, + // so we have to do it here. + histo->BufferEmpty(); + } else { + ILOG(Error, Devel) << "Could not get the htemp histogram of the plot '" << plot.name << "'." << ENDM; + } + } +} diff --git a/Modules/TOF/test/testTOF.cxx b/Modules/TOF/test/testTOF.cxx new file mode 100644 index 0000000000..d677989747 --- /dev/null +++ b/Modules/TOF/test/testTOF.cxx @@ -0,0 +1,102 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testTOF.cxx +/// \author +/// + +#include "QualityControl/TaskFactory.h" +#include "Base/Counter.h" +#include "DataFormatsTOF/CompressedDataFormat.h" +#include "TH1F.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::tof +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +BOOST_AUTO_TEST_CASE(check_tof_counter) +{ + BOOST_TEST_CHECKPOINT("Starting"); + + TH1F* hFull = new TH1F("hFull", "hFull;DRM Word;Crate;Words", 32, 0, 32); + Counter<32, o2::tof::diagnostic::DRMDiagnosticName> counterFull; + BOOST_CHECK_MESSAGE(counterFull.MakeHistogram(hFull) == 0, "Did not correctly make the histogram for Full labels"); + int nFull = 0; + for (int i = 0; i < 32; i++) { + if (o2::tof::diagnostic::DRMDiagnosticName[i] && o2::tof::diagnostic::DRMDiagnosticName[i][0]) { + nFull++; + } + } + + TH1F* hEmpty = new TH1F("hEmpty", "hEmpty;DRM Word;Crate;Words", 32, 0, 32); + Counter<32, nullptr> counterEmpty; + BOOST_CHECK_MESSAGE(counterEmpty.MakeHistogram(hEmpty) == 0, "Did not correctly make the histogram for Empty labels"); + BOOST_CHECK_MESSAGE((hFull->GetNbinsX() == 32) || (hFull->GetNbinsX() + hEmpty->GetNbinsX()) == (32 + nFull), + "Sum of histogram size does not match the number of possible words " << hFull->GetNbinsX() + hEmpty->GetNbinsX() << " vs " << (32 + nFull)); + + const unsigned int n = 1000; + for (unsigned int i = 0; i < n; i++) { + for (unsigned int j = 0; j < 32; j++) { + if (o2::tof::diagnostic::DRMDiagnosticName[j] && o2::tof::diagnostic::DRMDiagnosticName[j][0]) { + counterFull.Count(j); + } else { + counterEmpty.Count(j); + } + } + } + + LOG(info) << "Printing counter of full labels"; + counterFull.Print(); + for (unsigned int j = 0; j < 32; j++) { + unsigned int expected = 0; + if (o2::tof::diagnostic::DRMDiagnosticName[j] && o2::tof::diagnostic::DRMDiagnosticName[j][0]) { + expected = n; + } + BOOST_CHECK_MESSAGE(counterFull.HowMany(j) == expected, + "Issue with the counts in " << o2::tof::diagnostic::DRMDiagnosticName[j] << ": " << counterFull.HowMany(j) << " should be " << expected); + } + BOOST_CHECK_MESSAGE(counterFull.FillHistogram(hFull) == 0, "Did not correctly fill the histogram for Full labels"); + BOOST_CHECK_MESSAGE(hFull->Integral() == n * nFull, + "Issue with the counts in histogram of full counter, expected " << n * nFull << " entries and got " << hFull->Integral()); + for (int j = 0; j < hFull->GetNbinsX(); j++) { + if (hFull->GetXaxis()->GetBinLabel(j + 1)[0]) { + BOOST_CHECK_MESSAGE(hFull->GetBinContent(j + 1) == n, + "Issue with the counts in bin " << j + 1 << " they must be " << n << " and instead are: " << hFull->GetBinContent(j + 1)); + } + LOG(info) << "in: " << j + 1 << "/" << hFull->GetNbinsX() + 1 << " (bin '" << hFull->GetXaxis()->GetBinLabel(j + 1) << "') there are " << hFull->GetBinContent(j + 1) << " counts"; + } + + LOG(info) << "Printing counter of empty labels"; + counterEmpty.Print(); + for (unsigned int j = 0; j < 32; j++) { + unsigned int expected = n; + if (o2::tof::diagnostic::DRMDiagnosticName[j] && o2::tof::diagnostic::DRMDiagnosticName[j][0]) { + expected = 0; + } + BOOST_CHECK_MESSAGE(counterEmpty.HowMany(j) == expected, + "Issue with the counts in " << o2::tof::diagnostic::DRMDiagnosticName[j] << ": " << counterEmpty.HowMany(j) << " should be " << expected); + } + BOOST_CHECK_MESSAGE(counterEmpty.FillHistogram(hEmpty) == 0, "Did not correctly fill the histogram for Empty labels"); + BOOST_CHECK_MESSAGE(hEmpty->Integral() == n * (32 - nFull), + "Issue with the counts in histogram of empty counter, expected " << n * (32 - nFull) << " entries and got " << hEmpty->Integral()); + + BOOST_TEST_CHECKPOINT("Ending"); + BOOST_CHECK(true); +} +} // namespace o2::quality_control_modules::tof \ No newline at end of file diff --git a/Modules/TOF/tofMatchedTracks_AllTypes_direct_MC.json b/Modules/TOF/tofMatchedTracks_AllTypes_direct_MC.json new file mode 100644 index 0000000000..eaf27d5bc8 --- /dev/null +++ b/Modules/TOF/tofMatchedTracks_AllTypes_direct_MC.json @@ -0,0 +1,70 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MTCTOFAllTypes" : { + "active" : "true", + "className" : "o2::quality_control_modules::tof::TOFMatchedTracks", + "moduleName" : "QcTOF", + "detectorName" : "TOF", + "cycleDurationSeconds" : "10", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "direct", + "query_comment" : "checking every matched track", + "query" : "matchITSTPCTOF:TOF/MTC_ITSTPC/0;matchTPCTOF:TOF/MTC_TPC/0;trackTPCTOF:TOF/TOFTRACKS_TPC/0;trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS/0;trackTPCClRefs:TPC/CLUSREFS/0;tofcluster:TOF/CLUSTERS/0;trackTPCMCTR:TPC/TRACKSMCLBL;trackITSTPCMCTR:GLO/TPCITS_MC;trackITSTPCABMCTR:GLO/TPCITSAB_MC;clsTOF_TPC_MCTR:TOF/MCMTC_TPC;clsTOF_GLO_MCTR:TOF/MCMTC_ITSTPC;trackITSTPCTRD:TRD/MATCH_ITSTPC/0;trackITSTPCTRDMCTR:TRD/MCLB_ITSTPC/0;trackITSTPCTRDSAMCTR:TRD/MCLB_ITSTPC_TRD/0;trackTPCTRD:TRD/MATCH_TPC/0;trackTPCTRDMCTR:TRD/MCLB_TPC/0;trackTPCTRDSAMCTR:TRD/MCLB_TPC_TRD/0;trigITSTPCTRD:TRD/TRGREC_ITSTPC/0;trigTPCTRD:TRD/TRGREC_TPC/0;matchITSTPCTRDTOF:TOF/MTC_ITSTPCTRD/0;clsTOF_GLO3_MCTR:TOF/MCMTC_ITSTPCTRD/0;matchTPCTRDTOF:TOF/MTC_TPCTRD/0;clsTOF_GLO2_MCTR:TOF/MCMTC_TPCTRD/0" + }, + "taskParameters" : { + "GID" : "ITS-TPC,TPC,ITS-TPC-TOF,TPC-TOF,TPC-TRD,ITS-TPC-TRD,ITS-TPC-TRD-TOF,TPC-TRD-TOF", + "verbose" : "false", + "isMC" : "true", + "minPtCut" : "0.3f", + "etaCut" : "0.8f", + "minNTPCClustersCut" : "60", + "minDCACut" : "100.f", + "minDCACutY" : "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "remote", + "saveObjectsToFile" : "TOFmatchedTracks_AllTypes_MC.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + } + }, + "dataSamplingPolicies" : [] +} diff --git a/Modules/TOF/tofMatchedTracks_ITSTPCTOF.json b/Modules/TOF/tofMatchedTracks_ITSTPCTOF.json new file mode 100644 index 0000000000..6f4586c4fb --- /dev/null +++ b/Modules/TOF/tofMatchedTracks_ITSTPCTOF.json @@ -0,0 +1,69 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MatchedTracksITSTPCTOF" : { + "active" : "true", + "className" : "o2::quality_control_modules::tof::TOFMatchedTracks", + "moduleName" : "QcTOF", + "detectorName" : "TOF", + "cycleDurationSeconds" : "10", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "direct", + "query_comment" : "checking every matched track", + "query" : "matchITSTPCTOF:TOF/MTC_ITSTPC/0;trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;tofcluster:TOF/CLUSTERS/0;trackTPC:TPC/TRACKS/0;trackTPCClRefs:TPC/CLUSREFS/0" + }, + "taskParameters" : { + "GID" : "ITS-TPC,ITS-TPC-TOF", + "verbose" : "false", + "minPtCut" : "0.3f", + "etaCut" : "0.8f", + "minNTPCClustersCut" : "60", + "minDCACut" : "100.f", + "minDCACutY" : "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "remote", + "saveObjectsToFile" : "TOFmatchedITSTPCTOF.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + } + }, + "dataSamplingPolicies" : [] +} diff --git a/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF.json b/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF.json new file mode 100644 index 0000000000..47bae53d9f --- /dev/null +++ b/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF.json @@ -0,0 +1,85 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MTCITSTPCTOF" : { + "taskName" : "MatchTOF_ITSTPCTOF_TPCTOF", + "active" : "true", + "className" : "o2::quality_control_modules::tof::TOFMatchedTracks", + "moduleName" : "QcTOF", + "detectorName" : "TOF", + "cycleDurationSeconds" : "10", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "tof-match-samp" + }, + "taskParameters" : { + "GID" : "ITS-TPC,TPC,ITS-TPC-TOF,TPC-TOF", + "verbose" : "false", + "minPtCut" : "0.3f", + "etaCut" : "0.8f", + "minNTPCClustersCut" : "60", + "minDCACut" : "100.f", + "minDCACutY" : "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "remote", + "saveObjectsToFile" : "TOFmatchedITSTPCTOF_TPCTOF.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "tof-match-samp", + "active" : "true", + "machines" : [], + "query_comment" : "checking every 10% matched track", + "query" : "matchITSTPCTOF:TOF/MTC_ITSTPC/0;matchTPCTOF:TOF/MTC_TPC/0;trackTPCTOF:TOF/TOFTRACKS_TPC/0;trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS/0;trackTPCClRefs:TPC/CLUSREFS/0;tofcluster:TOF/CLUSTERS/0", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.1", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF_MC.json b/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF_MC.json new file mode 100644 index 0000000000..71817b9c11 --- /dev/null +++ b/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF_MC.json @@ -0,0 +1,85 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MatchedTracksITSTPCTOF_TPCTOF" : { + "active" : "true", + "className" : "o2::quality_control_modules::tof::TOFMatchedTracks", + "moduleName" : "QcTOF", + "detectorName" : "TOF", + "cycleDurationSeconds" : "10", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "dataSamplingPolicy", + "name" : "tof-match-samp" + }, + "taskParameters" : { + "GID" : "ITS-TPC,TPC,ITS-TPC-TOF,TPC-TOF", + "verbose" : "false", + "isMC" : "true", + "minPtCut" : "0.3f", + "etaCut" : "0.8f", + "minNTPCClustersCut" : "60", + "minDCACut" : "100.f", + "minDCACutY" : "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "remote", + "saveObjectsToFile" : "TOFmatchedITSTPCTOF_TPCTOF_MC.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + } + }, + "dataSamplingPolicies" : [ + { + "id" : "tof-match-samp", + "active" : "true", + "machines" : [], + "query_comment" : "checking every 10% matched track", + "query" : "matchITSTPCTOF:TOF/MTC_ITSTPC/0;matchTPCTOF:TOF/MTC_TPC/0;trackTPCTOF:TOF/TOFTRACKS_TPC/0;trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS/0;trackTPCClRefs:TPC/CLUSREFS/0;tofcluster:TOF/CLUSTERS/0;trackTPCMCTR:TPC/TRACKSMCLBL;trackITSTPCMCTR:GLO/TPCITS_MC;trackITSTPCABMCTR:GLO/TPCITSAB_MC;clsTOF_TPC_MCTR:TOF/MCMTC_TPC;clsTOF_GLO_MCTR:TOF/MCMTC_ITSTPC", + "samplingConditions" : [ + { + "condition" : "random", + "fraction" : "0.1", + "seed" : "1234" + } + ], + "blocking" : "false" + } + ] +} diff --git a/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF_direct.json b/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF_direct.json new file mode 100644 index 0000000000..715df58e82 --- /dev/null +++ b/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF_direct.json @@ -0,0 +1,69 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MatchedTracksITSTPCTOF_TPCTOF" : { + "active" : "true", + "className" : "o2::quality_control_modules::tof::TOFMatchedTracks", + "moduleName" : "QcTOF", + "detectorName" : "TOF", + "cycleDurationSeconds" : "10", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "direct", + "query_comment" : "checking every matched track", + "query" : "matchITSTPCTOF:TOF/MTC_ITSTPC/0;matchTPCTOF:TOF/MTC_TPC/0;trackTPCTOF:TOF/TOFTRACKS_TPC/0;trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS/0;trackTPCClRefs:TPC/CLUSREFS/0;tofcluster:TOF/CLUSTERS/0" + }, + "taskParameters" : { + "GID" : "ITS-TPC,TPC,ITS-TPC-TOF,TPC-TOF", + "verbose" : "false", + "minPtCut" : "0.3f", + "etaCut" : "0.8f", + "minNTPCClustersCut" : "60", + "minDCACut" : "100.f", + "minDCACutY" : "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "remote", + "saveObjectsToFile" : "TOFmatchedITSTPCTOF_TPCTOF.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + } + }, + "dataSamplingPolicies" : [] +} diff --git a/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF_direct_MC.json b/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF_direct_MC.json new file mode 100644 index 0000000000..80aad2ed42 --- /dev/null +++ b/Modules/TOF/tofMatchedTracks_ITSTPCTOF_TPCTOF_direct_MC.json @@ -0,0 +1,71 @@ +{ + "qc" : { + "config" : { + "database" : { + "implementation" : "CCDB", + "host" : "ccdb-test.cern.ch:8080", + "username" : "not_applicable", + "password" : "not_applicable", + "name" : "not_applicable" + }, + "Activity" : { + "number" : "42", + "type" : "2" + }, + "monitoring" : { + "url" : "infologger:///debug?qc" + }, + "consul" : { + "url" : "" + }, + "conditionDB" : { + "url" : "ccdb-test.cern.ch:8080" + }, + "infologger" : { "" : "Configuration of the Infologger (optional).", + "filterDiscardDebug" : "false", + "" : "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel" : "21", + "" : "Message at this level or above are discarded (default: 21 - Trace)" } + }, + "tasks" : { + "MTCITSTPCTOF" : { + "taskName" : "MatchTOF_ITSTPCTOF_TPCTOF", + "active" : "true", + "className" : "o2::quality_control_modules::tof::TOFMatchedTracks", + "moduleName" : "QcTOF", + "detectorName" : "TOF", + "cycleDurationSeconds" : "10", + "maxNumberCycles" : "-1", + "dataSource" : { + "type" : "direct", + "query_comment" : "checking every matched track", + "query" : "matchITSTPCTOF:TOF/MTC_ITSTPC/0;matchTPCTOF:TOF/MTC_TPC/0;trackTPCTOF:TOF/TOFTRACKS_TPC/0;trackITSTPC:GLO/TPCITS/0;trackITSTPCABREFS:GLO/TPCITSAB_REFS/0;trackITSTPCABCLID:GLO/TPCITSAB_CLID/0;trackTPC:TPC/TRACKS/0;trackTPCClRefs:TPC/CLUSREFS/0;tofcluster:TOF/CLUSTERS/0;trackTPCMCTR:TPC/TRACKSMCLBL;trackITSTPCMCTR:GLO/TPCITS_MC;trackITSTPCABMCTR:GLO/TPCITSAB_MC;clsTOF_TPC_MCTR:TOF/MCMTC_TPC;clsTOF_GLO_MCTR:TOF/MCMTC_ITSTPC" + }, + "taskParameters" : { + "GID" : "ITS-TPC,TPC,ITS-TPC-TOF,TPC-TOF", + "verbose" : "false", + "isMC" : "true", + "minPtCut" : "0.3f", + "etaCut" : "0.8f", + "minNTPCClustersCut" : "60", + "minDCACut" : "100.f", + "minDCACutY" : "10.f" + }, + "grpGeomRequest" : { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location" : "remote", + "saveObjectsToFile" : "TOFmatchedITSTPCTOF_TPCTOF_MC.root", + "" : "For debugging, path to the file where to save. If empty or missing it won't save." + } + } + }, + "dataSamplingPolicies" : [] +} diff --git a/Modules/TOF/tofcosmics.json b/Modules/TOF/tofcosmics.json new file mode 100644 index 0000000000..562446a8e6 --- /dev/null +++ b/Modules/TOF/tofcosmics.json @@ -0,0 +1,80 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "TaskCosmics": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskCosmics", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tof-cosmics" + }, + "taskParameters": { + "SelDeltaTSignalRegion": "50000", + "SelDeltaTBackgroundRegion": "100000", + "SelMinLength": "500" + }, + "location": "remote" + } + }, + "checks": { + "TOFRawsMulti": { + "active": "false", + "className": "o2::quality_control_modules::tof::CheckRawMultiplicity", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskCosmics", + "MOs": [ + "Crate1" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tof-cosmics", + "active": "true", + "machines": [], + "query": "infocosmics:TOF/INFOCOSMICS/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/TOF/tofdigits.json b/Modules/TOF/tofdigits.json new file mode 100644 index 0000000000..a7b589cba7 --- /dev/null +++ b/Modules/TOF/tofdigits.json @@ -0,0 +1,163 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "TaskDigits": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskDigits", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tof-digits" + }, + "taskParameters": { + "Diagnostic": "false", + "NoiseClassSelection": "0" + }, + "grpGeomRequest" : { + "geomRequest": "Aligned", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location": "remote" + } + }, + "checks": { + "TOFRawsMulti": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckRawMultiplicity", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "Multiplicity/Integrated", + "Multiplicity/SectorIA", + "Multiplicity/SectorOA", + "Multiplicity/SectorIC", + "Multiplicity/SectorOC" + ] + } + ] + }, + "TOFRawsTime": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckRawTime", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "Time/Integrated", + "Time/SectorIA", + "Time/SectorOA", + "Time/SectorIC", + "Time/SectorOC" + ] + } + ] + }, + "TOFRawsToT": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckRawToT", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "ToT/Integrated", + "ToT/SectorIA", + "ToT/SectorOA", + "ToT/SectorIC", + "ToT/SectorOC" + ] + } + ] + }, + "TOFRawHitMap": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckHitMap", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "HitMap" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tof-digits", + "active": "true", + "machines": [], + "query": "tofdigits:TOF/DIGITS/0;readoutwin:TOF/READOUTWINDOW/0;diafreq:TOF/DIAFREQ/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + }, + { + "id": "tof-digits-dia", + "active": "true", + "machines": [], + "query": "tofdigits:TOF/DIGITS/0;readoutwin:TOF/READOUTWINDOW/0;patterns:TOF/PATTERNS/0;diafreq:TOF/DIAFREQ/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/TOF/tofdigits_multinode.json b/Modules/TOF/tofdigits_multinode.json new file mode 100644 index 0000000000..ecc375ac5b --- /dev/null +++ b/Modules/TOF/tofdigits_multinode.json @@ -0,0 +1,149 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "TaskDigits": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskDigits", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "digi-local" + }, + "taskParameters": { + "nothing": "rien" + }, + "grpGeomRequest" : { + "geomRequest": "Aligned", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location": "local", + "localMachines": [ + "localnode1", + "localnode2", + "localnode3" + ], + "remoteMachine": "localhost", + "remotePort": "30132", + "mergingMode": "delta" + }, + "TaskDigitsRemote": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskDigits", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "digi-remote" + }, + "taskParameters": { + "nothing": "rien" + }, + "location": "remote" + } + }, + "checks": { + "MultiNodeLocalCheck": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckRawMultiplicity", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "TOFRawsMulti" + ] + } + ] + }, + "MultiNodeRemoteCheck": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckRawMultiplicity", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigitsRemote", + "MOs": [ + "TOFRawsMulti" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "digi-local", + "active": "true", + "machines": [ + "localnode1", + "localnode2", + "localnode3" + ], + "query": "tofdigits:TOF/DIGITS/0;readoutwin:TOF/READOUTWINDOW/0;diafreq:TOF/DIAFREQ/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + }, + { + "id": "digi-remote", + "active": "true", + "machines": [ + "localhost" + ], + "port": "30333", + "query": "tofdigits:TOF/DIGITS/0;readoutwin:TOF/READOUTWINDOW/0;diafreq:TOF/DIAFREQ/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/TOF/toffull.json b/Modules/TOF/toffull.json new file mode 100644 index 0000000000..8b5101ca10 --- /dev/null +++ b/Modules/TOF/toffull.json @@ -0,0 +1,239 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "TaskRaw": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskRaw", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "60", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tof-rawdata" + }, + "taskParameters": { + "DecoderCONET": "False", + "TimeWindowMin": "4096", + "TimeWindowMax": "36864", + "NoiseThreshold": "1000" + }, + "location": "remote" + }, + "TaskDigits": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskDigits", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "60", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tof-digits" + }, + "taskParameters": { + "nothing": "rien" + }, + "grpGeomRequest" : { + "geomRequest": "Aligned", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location": "remote" + } + }, + "checks": { + "CheckDiagnostics": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckDiagnostics", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "RDHCounterCrate0" + ] + } + ] + }, + "CheckDRMDiagnostics": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckDRMDiagnostics", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "DRMCounter" + ] + } + ] + }, + "CheckCompressedData": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckCompressedData", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "checkParameters": { + "DiagnosticThresholdPerSlot": "10" + }, + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "hDiagnostic" + ] + } + ] + }, + "TOFRawsMulti": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckRawMultiplicity", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "TOFRawsMulti" + ] + } + ] + }, + "TOFRawsTime": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckRawTime", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "TOFRawsTime" + ] + } + ] + }, + "TOFRawsToT": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckRawToT", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "TOFRawsToT" + ] + } + ] + }, + "TOFRawHitMap": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckHitMap", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "TOFRawHitMap" + ] + } + ] + } + }, + "postprocessing": { + "PostProcessDiagnosticPerCrate": { + "active": "true", + "className": "o2::quality_control_modules::tof::PostProcessDiagnosticPerCrate", + "moduleName": "QualityControl", + "detectorName": "TOF", + "dataSources": [], + "plots": [], + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "5 seconds" + ], + "stopTrigger": [ + "once" + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tof-rawdata", + "active": "true", + "machines": [], + "query": "dataframe:TOF/CRAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + }, + { + "id": "tof-digits", + "active": "true", + "machines": [], + "query": "tofdigits:TOF/DIGITS/0;readoutwin:TOF/READOUTWINDOW/0;diafreq:TOF/DIAFREQ/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/TOF/toffull_multinode.json b/Modules/TOF/toffull_multinode.json new file mode 100644 index 0000000000..9948186344 --- /dev/null +++ b/Modules/TOF/toffull_multinode.json @@ -0,0 +1,194 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "TaskRaw": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskRaw", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-local" + }, + "taskParameters": { + "nothing": "rien" + }, + "location": "local", + "localMachines": [ + "localnode1", + "localnode2", + "localnode3" + ], + "remoteMachine": "localhost", + "remotePort": "30132", + "mergingMode": "delta" + }, + "TaskDigits": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskDigits", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "digi-local" + }, + "taskParameters": { + "nothing": "rien" + }, + "grpGeomRequest" : { + "geomRequest": "Aligned", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location": "local", + "localMachines": [ + "localnode1", + "localnode2", + "localnode3" + ], + "remoteMachine": "localhost", + "remotePort": "30132", + "mergingMode": "delta" + } + }, + "checks": { + "CheckDiagnostics": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckDiagnostics", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "RDHCounterCrate0" + ] + } + ] + }, + "CheckCompressedData": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckCompressedData", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "checkParameters": { + "DiagnosticThresholdPerSlot": "10" + }, + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "hDiagnostic" + ] + } + ] + }, + "CheckRawMultiplicity": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckRawMultiplicity", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "TOFRawsMulti" + ] + } + ] + }, + "TOFRawHitMap": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckHitMap", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [ + { + "type": "Task", + "name": "TaskDigits", + "MOs": [ + "TOFRawHitMap" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "raw-local", + "active": "true", + "machines": [ + "localnode1", + "localnode2", + "localnode3" + ], + "query": "dataframe:TOF/CRAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + }, + { + "id": "digi-local", + "active": "true", + "machines": [ + "localnode1", + "localnode2", + "localnode3" + ], + "port": "30333", + "query": "tofdigits:TOF/DIGITS/0;readoutwin:TOF/READOUTWINDOW/0;diafreq:TOF/DIAFREQ/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/TOF/tofpostprocessdiagnosticpercrate.json b/Modules/TOF/tofpostprocessdiagnosticpercrate.json new file mode 100644 index 0000000000..85c4223be4 --- /dev/null +++ b/Modules/TOF/tofpostprocessdiagnosticpercrate.json @@ -0,0 +1,68 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "PostProcessDiagnosticPerCrate": { + "active": "false", + "className": "o2::quality_control_modules::tof::PostProcessDiagnosticPerCrate", + "moduleName": "QualityControl", + "detectorName": "TOF", + "dataSources": [], + "plots": [], + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "5 seconds" + ], + "stopTrigger": [ + "10 seconds" + ] + }, + "PostProcessHitMap": { + "active": "true", + "className": "o2::quality_control_modules::tof::PostProcessHitMap", + "moduleName": "QualityControl", + "detectorName": "TOF", + "dataSources": [], + "plots": [], + "customization": [ + { + "name": "CCDBPath", + "value": "TOF/MO/TaskDigitsNew/" + } + ], + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "1 seconds" + ], + "stopTrigger": [ + "2 seconds" + ] + } + } + } +} \ No newline at end of file diff --git a/Modules/TOF/tofraw.json b/Modules/TOF/tofraw.json new file mode 100644 index 0000000000..815aba77b1 --- /dev/null +++ b/Modules/TOF/tofraw.json @@ -0,0 +1,147 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "TaskRaw": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskRaw", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "60", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tof-rawdata" + }, + "taskParameters": { + "DecoderCONET": "False", + "TimeWindowMin": "4096", + "TimeWindowMax": "1227112", + "NoiseThreshold": "1000" + }, + "location": "remote" + } + }, + "checks": { + "CheckNoise": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckNoise", + "moduleName": "QcTOF", + "policy": "OnAny", + "detectorName": "TOF", + "dataSource": [{ + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "hIndexEOHitRate" + ] + }] + }, + "CheckDiagnostics": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckDiagnostics", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "RDHCounterCrate0" + ] + } + ] + }, + "CheckDRMDiagnostics": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckDRMDiagnostics", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "DRMCounter" + ] + } + ] + }, + "CheckCompressedData": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckCompressedData", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "checkParameters": { + "DiagnosticThresholdPerSlot": "10" + }, + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "hDiagnostic" + ] + } + ] + }, + "CheckSlotPartMask": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckSlotPartMask", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "checkParameters": {}, + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "hSlotPartMask" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tof-rawdata", + "active": "true", + "machines": [], + "query": "dataframe:TOF/CRAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/TOF/tofraw_multinode.json b/Modules/TOF/tofraw_multinode.json new file mode 100644 index 0000000000..ae944055b8 --- /dev/null +++ b/Modules/TOF/tofraw_multinode.json @@ -0,0 +1,177 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "TaskRaw": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskRaw", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-local" + }, + "taskParameters": { + "nothing": "rien" + }, + "location": "local", + "localMachines": [ + "localnode1", + "localnode2", + "localnode3" + ], + "remoteMachine": "localhost", + "remotePort": "30132", + "mergingMode": "delta" + }, + "TaskRawRemote": { + "active": "true", + "className": "o2::quality_control_modules::tof::TaskRaw", + "moduleName": "QcTOF", + "detectorName": "TOF", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-remote" + }, + "taskParameters": { + "nothing": "rien" + }, + "location": "remote" + } + }, + "checks": { + "CheckDiagnosticsLocal": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckDiagnostics", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "RDHCounterCrate0" + ] + } + ] + }, + "CheckCompressedDataLocal": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckCompressedData", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "checkParameters": { + "DiagnosticThresholdPerSlot": "10" + }, + "dataSource": [ + { + "type": "Task", + "name": "TaskRaw", + "MOs": [ + "hDiagnostic" + ] + } + ] + }, + "CheckDiagnosticsRemote": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckDiagnostics", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "dataSource": [ + { + "type": "Task", + "name": "TaskRawRemote", + "MOs": [ + "RDHCounterCrate0" + ] + } + ] + }, + "CheckCompressedDataRemote": { + "active": "true", + "className": "o2::quality_control_modules::tof::CheckCompressedData", + "moduleName": "QcTOF", + "detectorName": "TOF", + "policy": "OnAny", + "checkParameters": { + "DiagnosticThresholdPerSlot": "10" + }, + "dataSource": [ + { + "type": "Task", + "name": "TaskRawRemote", + "MOs": [ + "hDiagnostic" + ] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "raw-local", + "active": "true", + "machines": [ + "localnode1", + "localnode2", + "localnode3" + ], + "query": "dataframe:TOF/CRAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + }, + { + "id": "raw-remote", + "active": "true", + "machines": [ + "localhost" + ], + "port": "30333", + "query": "dataframe:TOF/CRAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} \ No newline at end of file diff --git a/Modules/TOF/toftrending.json b/Modules/TOF/toftrending.json new file mode 100644 index 0000000000..b02a854357 --- /dev/null +++ b/Modules/TOF/toftrending.json @@ -0,0 +1,64 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "TOFTrendingHits": { + "active": "true", + "className": "o2::quality_control_modules::tof::TrendingHits", + "moduleName": "QcTOF", + "detectorName": "TOF", + "dataSources": [ + { + "type": "repository", + "path": "TOF/MO/TaskDigits/Multiplicity", + "names": [ + "Integrated" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_hits", + "title": "Mean trend of TOF hits", + "varexp": "Integrated.mean:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:TOF/MO/TaskDigits/Multiplicity/Integrated" + ], + "stopTrigger": [ + "userorcontrol", + "10 minutes" + ] + } + } + } +} diff --git a/Modules/TOF/toftrendingcalib.json b/Modules/TOF/toftrendingcalib.json new file mode 100644 index 0000000000..8c362fdab5 --- /dev/null +++ b/Modules/TOF/toftrendingcalib.json @@ -0,0 +1,66 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "0" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://alice-ccdb.cern.ch" + } + }, + "postprocessing": { + "TOFTrendingCalibLHCphase": { + "active": "true", + "className": "o2::quality_control_modules::tof::TrendingCalibLHCphase", + "moduleName": "QcTOF", + "detectorName": "TOF", + "dataSourceURL": "http://alice-ccdb.cern.ch", + "dataSources": [ + { + "type": "ccdb", + "path": "/TOF/Calib/LHCphase/", + "names": [ + "LHC_phase" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "LHC_phase", + "title": "LHC phase", + "varexp": "phase:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "once" + ], + "updateTrigger": [ + "newobject:ccdb:TOF/Calib/LHCphase/" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + }, + "dataSamplingPolicies": [ + {} + ] +} \ No newline at end of file diff --git a/Modules/TOF/toftrendingrate.json b/Modules/TOF/toftrendingrate.json new file mode 100644 index 0000000000..e5df295eb3 --- /dev/null +++ b/Modules/TOF/toftrendingrate.json @@ -0,0 +1,97 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "TOFTrendingRate": { + "active": "true", + "className": "o2::quality_control_modules::tof::TrendingRate", + "moduleName": "QcTOF", + "detectorName": "TOF", + "customization": [ + { + "name": "ThresholdSgn", + "value": "0.15" + }, + { + "name": "ThresholdBkg", + "value": "0.04" + } + ], + "dataSources": [ + { + "type": "repository", + "path": "TOF/MO/TaskDigits", + "names": [ + "HitMap", + "Multiplicity/VsBC", + "Multiplicity/VsBCpro" + ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "interaction_rate", + "title": "IR from TOF hits", + "varexp": "collisionRate:time", + "selection": "", + "option": "L" + }, + { + "name": "noise_rate", + "title": "Noise Rate per channel", + "varexp": "noiseRate:time", + "selection": "", + "option": "L" + }, + { + "name": "active_channels", + "title": "Active channels", + "varexp": "activeChannels:time", + "selection": "", + "option": "*L" + }, + { + "name": "pile_up", + "title": "Pileup", + "varexp": "pileup:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:TOF/MO/TaskDigits/Multiplicity/VsBC" + ], + "stopTrigger": [ + "userorcontrol", + "10 minutes" + ] + } + } + } +} diff --git a/Modules/TOF/tools/README.md b/Modules/TOF/tools/README.md new file mode 100644 index 0000000000..1a8557b23b --- /dev/null +++ b/Modules/TOF/tools/README.md @@ -0,0 +1,6 @@ +# checkEff.C +# void checkEff(const char *fn1="QC.root", const char *fn2="QC.root", long ts=1660430519000); +# this macro allow to check on QC the efficiency strip by strip using tracks with pT > 2 GeV/c +# strip efficiencies are also corrected for all the features anchored in MC: acceptance/dead channels, decoding errors, problematic channels from calibrations +# after correction we expect efficiency > 60% for all strips enabled -> this would guarantee we are able to anchor +# ARGUMENTS: file with digit QC, file with matching QC (different only in MC), SOR timestamp for the calibration object diff --git a/Modules/TOF/tools/checkEff.C b/Modules/TOF/tools/checkEff.C new file mode 100644 index 0000000000..2599d110be --- /dev/null +++ b/Modules/TOF/tools/checkEff.C @@ -0,0 +1,251 @@ +void checkEff(const char *fn1="QC.root", const char *fn2="QC.root", long ts=1660430519000){ + // get TOF calibrations + system(Form("o2-ccdb-downloadccdbfile -p TOF/Calib/ChannelCalib -t %ld",ts)); + + TFile *fcal = new TFile("TOF/Calib/ChannelCalib/snapshot.root"); + o2::dataformats::CalibTimeSlewingParamTOF *cal = (o2::dataformats::CalibTimeSlewingParamTOF *) fcal->Get("ccdb_object"); + + int rebin = 1; + + TFile *f1 = new TFile(fn1); + TFile *f2 = new TFile(fn2); + + o2::quality_control::core::MonitorObjectCollection *mon = (o2::quality_control::core::MonitorObjectCollection *) f1->Get("TOF/Digits"); + o2::quality_control::core::MonitorObject *obj = (o2::quality_control::core::MonitorObject *) mon->FindObject("HitMapNoiseFiltered"); // TH2F + TH2F *map = (TH2F *) obj->getObject(); + + obj = (o2::quality_control::core::MonitorObject *) mon->FindObject("DecodingErrors"); // TH2I + TH2I *decode = (TH2I *) obj->getObject(); + + obj = (o2::quality_control::core::MonitorObject *) mon->FindObject("OrbitVsCrate"); // TH2I + TProfile2D *orbit = (TProfile2D *) obj->getObject(); + + obj = (o2::quality_control::core::MonitorObject *) mon->FindObject("EventCounter"); // TH2F + TH2F *ec = (TH2F *) obj->getObject(); + + int nCounts = 0; + for(int ib=1; ib < 72; ib++){ + if(ec->GetBinContent(ib, 1) > nCounts){ + nCounts = ec->GetBinContent(ib, 1); + } + } + + TH1D *hpro = (TH1D *) orbit->ProfileY(); + int norb = 0; + hpro->Divide(hpro); + for(int ib=1; ib <= hpro->GetNbinsX(); ib++){ + if(hpro->GetBinContent(ib) == 0){ + break; + } + norb = ib; + } + + + printf("n orbits = %d - nCounts = %d\n",norb, nCounts); + + mon = (o2::quality_control::core::MonitorObjectCollection *) f2->Get("TOF/MatchTrAll"); + + obj = (o2::quality_control::core::MonitorObject *) mon->FindObject("mHistoExpTrackedStrip"); // TH1F + TH1F *trk = (TH1F *) obj->getObject(); + obj = (o2::quality_control::core::MonitorObject *) mon->FindObject("mHistoExpMatchedStrip"); // TH1F + TH1F *mtch = (TH1F *) obj->getObject(); + obj = (o2::quality_control::core::MonitorObject *) mon->FindObject("mHistoExpMatchedStripFromCh"); // TH1F + TH1F *mtchFromCh = (TH1F *) obj->getObject(); + + TH1F *hRefMatching = mtch; // reference to strip for matching: using channel info or traking kinematics + + trk->Add(mtch, -1); + trk->Add(hRefMatching, 1); + + TH2F *actv = new TH2F(*map); + actv->SetName("hActiveMap"); + actv->Reset(); + + TH2F *hEff2D = new TH2F(*map); + hEff2D->SetName("hEff2D"); + hEff2D->Reset(); + hEff2D->SetStats(0); + hEff2D->SetTitle("Eff 2D"); + + float thr = map->Integral() / map->GetNbinsX() / map->GetNbinsX() * 0.05; + for(int i=1; i<=map->GetNbinsX(); i++){ + for(int j=1; j<=map->GetNbinsY(); j++){ + if(map->GetBinContent(i,j) > thr){ + actv->SetBinContent(i,j,1); + } + } + } + + TCanvas *cMap = new TCanvas; + cMap->Divide(5,1); + cMap->cd(1); + actv->SetTitle("Active map"); + actv->Draw("col"); + cMap->cd(2); + TH1F *hEffStrip = new TH1F(*hRefMatching); + hEffStrip->SetName("hEffStrip"); + hEffStrip->SetTitle("hAcceptance"); + hEffStrip->GetYaxis()->SetTitle("#varepsilon"); + hEffStrip->SetStats(0); + hEffStrip->Reset(); + TH1F *hEffDAQ = new TH1F(*hEffStrip); + hEffDAQ->SetName("hEffDAQ"); + hEffDAQ->SetTitle("hEffDAQ"); + TH1F *hEffProb = new TH1F(*hEffStrip); + hEffProb->SetName("hEffProb"); + hEffProb->SetTitle("hEffProblematicCh"); + hEffStrip->Draw(); + + for(int i=1; i<=map->GetNbinsX(); i++){ + for(int j=1; j<=map->GetNbinsY(); j++){ + hEffStrip->AddBinContent(((i-1)/4)*91 + j, actv->GetBinContent(i,j)*0.25); + } + } + for(int i=1; i<=map->GetNbinsX(); i++){ + for(int j=1; j<=map->GetNbinsY(); j++){ + hEffStrip->SetBinError(((i-1)/4)*91 + j, 0); + } + } + hEffStrip->SetLineColor(2); + + cMap->cd(3); + hEffDAQ->Draw(); + cMap->cd(4); + hEffProb->Draw(); + + TCanvas *cEff = new TCanvas; + cEff->Divide(2,1); + cEff->cd(1); + TH1F *hEff = new TH1F(*hRefMatching); + hEff->SetMarkerStyle(20); + hEff->SetName("hEff"); + hEff->Divide(hRefMatching,trk,1,1,"B"); + hEff->GetYaxis()->SetTitle("#varepsilon"); + hEff->Draw("P"); + hEff->SetStats(0); + + TH1F *hEffN = new TH1F(*hEff); + hEffN->SetName("hEffN"); + cEff->cd(2); + hEffN->Draw("P"); + hEffN->GetYaxis()->SetTitle("normalized #varepsilon"); + + new TCanvas; + hEffN->Draw("P"); + + for(int i=1; i < 18; i++){ + TLine *l = new TLine(i*91,0,i*91,1); + l->SetLineColor(2); + l->Draw("SAME"); + } + + for(int isec=0; isec < 18; isec++){ + for(int istrip=0; istrip<91;istrip++){ + int nact = 0; + float nerr = 0; + float goodorb = 0; + float goodch = 0; + for(int ich=0; ich<96; ich++){ + int channel = (91*isec + istrip)*96 + ich; + int det[5]; + o2::tof::Geo::getVolumeIndices(channel, det); + int isec = det[0] * 4 + det[4] / 12 + 1; + if(actv->GetBinContent(isec, istrip+1)){ + nact++; + + int ech = o2::tof::Geo::getECHFromCH(channel); + int crate = o2::tof::Geo::getCrateFromECH(ech); + int trm = o2::tof::Geo::getTRMFromECH(ech); + float derr = 0; + if(decode->GetBinContent(crate+1,trm+1)){ + if(decode->GetBinContent(crate+1, 1)){ + int norm = decode->GetBinContent(crate+1, 1); + if(norm > nCounts){ + norm = nCounts; + } + derr = decode->GetBinContent(crate+1,trm+1)/norm; + nerr += derr; + } + } + float localorb = 0; + for(int iorb=1; iorb <= norb;iorb++){ + localorb += orbit->GetBinContent(crate+1, iorb) / norb; + goodorb += orbit->GetBinContent(crate+1, iorb) / norb; + } + if(!cal->isProblematic(channel)){ + goodch++; + hEff2D->SetBinContent(isec, istrip+1, hEff2D->GetBinContent(isec, istrip+1) + localorb*(1- derr)/24); + } + } + } + if(nact > 0){ + hEffDAQ->SetBinContent(istrip+1+isec*91, goodorb/nact*(1 - nerr/nact)); + hEffProb->SetBinContent(istrip+1+isec*91, goodch/nact); + } else { + hEffDAQ->SetBinContent(istrip+1+isec*91, 1); + } + hEffDAQ->SetBinError(istrip+1+isec*91, 0); + hEffProb->SetBinError(istrip+1+isec*91, 0); + } + } + + TH1F *hEffTot = new TH1F(*hEffStrip); + hEffTot->SetName("hEffTot"); + hEffTot->SetTitle("hEffTot"); + + hEffTot->Multiply(hEffProb); + hEffTot->Multiply(hEffDAQ); + + hEffTot->RebinX(rebin); + hEffTot->Scale(1./rebin); + hEffDAQ->RebinX(rebin); + hEffDAQ->Scale(1./rebin); + hEffStrip->RebinX(rebin); + hEffStrip->Scale(1./rebin); + hEffProb->RebinX(rebin); + hEffProb->Scale(1./rebin); + hEff->RebinX(rebin); + hEff->Scale(1./rebin); + hEffN->RebinX(rebin); + hEffN->Scale(1./rebin); + + hEffN->Divide(hEffTot); + + hEff->SetMaximum(1.0); + hEffN->SetMaximum(1.0); + + hEffStrip->SetMaximum(1.0); + hEffStrip->SetMinimum(0); + hEffDAQ->SetMaximum(1.0); + hEffDAQ->SetMinimum(0); + hEffProb->SetMaximum(1.0); + hEffProb->SetMinimum(0); + hEffTot->SetMaximum(1.0); + hEffTot->SetMinimum(0); + + cMap->cd(5); + hEffTot->Draw(); + + TCanvas *cDistr = new TCanvas; + TH1F *hDistr = new TH1F("hDistr","#varepsilon distribution (2.7% PHOS HOLES); #varepsilon; fraction",101,0,1.01); + hDistr->SetLineColor(2); + TH1F *hDistrN = new TH1F("hDistrN","normalized #varepsilon distribution (2.7% PHOS HOLES); normalized #varepsilon; fraction",101,0,1.01); + for(int i=1; i<=hEffN->GetNbinsX(); i++){ + hDistrN->Fill(hEffN->GetBinContent(i)); + hDistr->Fill(hEff->GetBinContent(i)); + } + hDistrN->Draw(); + hDistr->Draw("SAME"); + hDistr->SetStats(0); + hDistrN->Scale(1./91/18); + hDistr->Scale(1./91/18); + hDistrN->SetStats(1); + + TCanvas *cCheck = new TCanvas; + cCheck->Divide(2,1); + cCheck->cd(1); + map->Draw("colz"); + cCheck->cd(2); + hEff2D->Draw("colz"); + +} diff --git a/Modules/TPC/CMakeLists.txt b/Modules/TPC/CMakeLists.txt new file mode 100644 index 0000000000..56a7051d1a --- /dev/null +++ b/Modules/TPC/CMakeLists.txt @@ -0,0 +1,192 @@ +# ---- Library ---- + +add_library(O2QcTPC) + +target_sources(O2QcTPC PRIVATE src/PID.cxx + src/Tracking.cxx + src/Tracks.cxx + src/ROCReductor.cxx + src/Clusters.cxx + src/CalDetPublisher.cxx + src/PadCalibrationCheck.cxx + src/Utility.cxx + src/RawDigits.cxx + src/CheckOfTrendings.cxx + src/LaserTracks.cxx + src/LtrCalibReductor.cxx + src/ClusterVisualizer.cxx + src/TrendingTaskTPC.cxx + src/TrendingTaskConfigTPC.cxx + src/TH1ReductorTPC.cxx + src/TH2ReductorTPC.cxx + src/CheckForEmptyPads.cxx + src/QualityReductorTPC.cxx + src/DCSPTemperature.cxx + src/IDCs.cxx + src/IDCsVsSACs.cxx + src/QualityObserver.cxx + src/RatioGeneratorTPC.cxx + src/CheckOfSlices.cxx + src/GenericHistogramCheck.cxx + src/JunkDetection.cxx + src/CheckOfPads.cxx + src/CalPadClusterReductor.cxx + src/IDCScaleReductor.cxx + src/SACs.cxx + src/TPCAggregator.cxx + src/SACZeroScaleReductor.cxx + src/TrackClusters.cxx + src/VDriftCalibReductor.cxx + src/SeparationPowerReductor.cxx + src/TimeGainCalibReductor.cxx + src/DCSPTempReductor.cxx + src/AtmosPressureReductor.cxx + src/GPUErrorQA.cxx) + +target_include_directories( + O2QcTPC + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src + ${O2_ROOT}/include/GPU) + +target_link_libraries(O2QcTPC + PUBLIC O2QualityControl + O2::TPCQC + O2::TPCWorkflow + O2::TPCCalibration + O2QcCommon) + + + +add_root_dictionary(O2QcTPC + HEADERS include/TPC/PID.h + include/TPC/Tracking.h + include/TPC/Tracks.h + include/TPC/ROCReductor.h + include/TPC/Clusters.h + include/TPC/CalDetPublisher.h + include/TPC/PadCalibrationCheck.h + include/TPC/Utility.h + include/TPC/RawDigits.h + include/TPC/ClustersData.h + include/TPC/CheckOfTrendings.h + include/TPC/LaserTracks.h + include/TPC/LtrCalibReductor.h + include/TPC/ClusterVisualizer.h + include/TPC/SliceInfo.h + include/TPC/TrendingTaskTPC.h + include/TPC/TrendingTaskConfigTPC.h + include/TPC/ReductorTPC.h + include/TPC/TH1ReductorTPC.h + include/TPC/TH2ReductorTPC.h + include/TPC/CheckForEmptyPads.h + include/TPC/QualityReductorTPC.h + include/TPC/DCSPTemperature.h + include/TPC/IDCs.h + include/TPC/IDCsVsSACs.h + include/TPC/QualityObserver.h + include/TPC/RatioGeneratorTPC.h + include/TPC/CheckOfSlices.h + include/TPC/GenericHistogramCheck.h + include/TPC/JunkDetection.h + include/TPC/CheckOfPads.h + include/TPC/CalPadClusterReductor.h + include/TPC/IDCScaleReductor.h + include/TPC/SACs.h + include/TPC/TPCAggregator.h + include/TPC/SACZeroScaleReductor.h + include/TPC/TrackClusters.h + include/TPC/VDriftCalibReductor.h + include/TPC/SeparationPowerReductor.h + include/TPC/TimeGainCalibReductor.h + include/TPC/DCSPTempReductor.h + include/TPC/AtmosPressureReductor.h + include/TPC/GPUErrorQA.h + LINKDEF include/TPC/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/TPC + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcTPC.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PUBLIC O2QcTPC + PRIVATE Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 60) +endforeach() + +# ---- Executables ---- + +set(EXE_SRCS + run/runTPCQCTrackReader.cxx) + +set(EXE_NAMES + o2-qc-run-tpctrackreader) + +list(LENGTH EXE_SRCS count) +math(EXPR count "${count}-1") +foreach(i RANGE ${count}) + list(GET EXE_SRCS ${i} src) + list(GET EXE_NAMES ${i} name) + add_executable(${name} ${src}) + target_link_libraries(${name} PRIVATE O2QcTPC ROOT::Tree) +endforeach() + +# ---- Install ---- + +install(TARGETS O2QcTPC ${EXE_NAMES} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +# ---- Install config files ---- + +install(FILES run/tpcQCPID_sampled.json + run/tpcQCPID_direct.json + run/tpcQCTracks_sampled.json + run/tpcQCTracks_direct.json + run/tpcQCTracking_direct.json + run/tpcQCSimpleTrending.json + run/tpcQCTrending.json + run/tpcQCClusters_direct.json + run/tpcQCTrackingFromExternal_direct.json + run/tpcQCCalDetPublisher.json + run/tpcQCPadCalibration.json + run/tpcQCRawDigits_direct.json + run/tpcQCTasks_multinode.json + run/tpcQCCheckTrending.json + run/tpcQCLaserTracks.json + run/tpcQCTrending_laserCalib.json + run/tpcQCClusterVisualizer.json + run/tpcQCRawDigitVisualizer.json + run/tpcQCTrending_slicer.json + run/tpcQCTrending_canvas.json + run/tpcQCDCSPTemperature.json + run/tpcQCIDCs.json + run/tpcQCIDCsVsSACs.json + run/tpcQCQualityObserver.json + run/tpcQCRatio_generator.json + run/tpcQCCheckSlices.json + run/tpcQCTracks_Generic.json + run/tpcQCJunkDetection.json + run/tpcQCPadCalibration_GenericPad.json + run/tpcQCROCTrending.json + run/tpcQCSACs.json + run/tpcQCSACScaleTrend.json + run/tpcQCTrackClusters.json + run/tpcQCvDriftTrending.json + run/tpcQCTrending_separationpower.json + run/tpcQCTimeGainCalibTrending.json + run/tpcDCSPTempTrending.json + run/tpcQCAtmosPressureTrending.json + run/tpcQCGPUErrorQA_direct.json + DESTINATION etc) diff --git a/Modules/TPC/include/TPC/AtmosPressureReductor.h b/Modules/TPC/include/TPC/AtmosPressureReductor.h new file mode 100644 index 0000000000..29a3a782e6 --- /dev/null +++ b/Modules/TPC/include/TPC/AtmosPressureReductor.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AtmosPressureReductor.h +/// \author Marcel Lesch +/// + +#ifndef QUALITYCONTROL_ATMOSPRESSUREREDUCTOR_H +#define QUALITYCONTROL_ATMOSPRESSUREREDUCTOR_H + +#include "QualityControl/ReductorConditionAny.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief A reductor for atmospheric pressure +/// +/// A reductor for atmospheric pressure +/// It produces a branch in the format: "cavernPressure1/F:errCavernPressure1:cavernPressure2:errCavernPressure2:surfacePressure:errSurfacePressure" + +class AtmosPressureReductor : public quality_control::postprocessing::ReductorConditionAny +{ + public: + AtmosPressureReductor() = default; + ~AtmosPressureReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + bool update(ConditionRetriever& retriever) override; + + private: + struct { + Float_t cavernPressure1; + Float_t errCavernPressure1; + Float_t cavernPressure2; + Float_t errCavernPressure2; + Float_t surfacePressure; + Float_t errSurfacePressure; + } mStats; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_ATMOSPRESSUREREDUCTOR_H diff --git a/Modules/TPC/include/TPC/CalDetPublisher.h b/Modules/TPC/include/TPC/CalDetPublisher.h new file mode 100644 index 0000000000..98b3e93783 --- /dev/null +++ b/Modules/TPC/include/TPC/CalDetPublisher.h @@ -0,0 +1,83 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalDetPublisher.h +/// \author Thomas Klemenz +/// + +#ifndef QUALITYCONTROL_CALDETPUBLISHER_H +#define QUALITYCONTROL_CALDETPUBLISHER_H + +// QC includes +#include "QualityControl/PostProcessingInterface.h" + +#include +#include + +class TCanvas; +class TPaveText; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Quality Control task for the calibration data of the TPC +/// \author Thomas Klemenz +class CalDetPublisher final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + CalDetPublisher() = default; + /// \brief Destructor + ~CalDetPublisher() = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + std::vector mOutputList{}; ///< list of CalDet objects to be processed + std::vector mOutputListMap{}; ///< list of vectors of CalDet objects to be processed + std::vector>> mCalDetCanvasVec{}; ///< vector containing a vector of summary canvases for every CalDet object + std::vector mTimestamps{}; ///< timestamps to look for specific data in the CCDB + std::vector> mLookupMaps{}; ///< meta data to look for data in the CCDB + std::vector> mStoreMaps{}; ///< meta data to be stored with the output in the QCDB + bool mCheckZSCalib; ///< shall the calib data used for ZS be compared to the latest pedestal and noise files + bool mCheckZSPrereq = false; ///< is pedestal and noise in the outputList in the config file + std::unique_ptr> mRefPedestal; ///< reference pedestal file used for ZS at the moment + std::unique_ptr> mRefNoise; ///< reference noise file used for ZS at the moment + long mInitRefCalibTimestamp; ///< timestamp of the pedestal/noise map used at init of the task + TPaveText* mNewZSCalibMsg = nullptr; ///< badge to indicate the necessity to upload new calibration data for ZS + std::unordered_map> mRanges; ///< histogram ranges configurable via config file +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_CALDETPUBLISHER_H diff --git a/Modules/TPC/include/TPC/CalPadClusterReductor.h b/Modules/TPC/include/TPC/CalPadClusterReductor.h new file mode 100644 index 0000000000..59969a6ad5 --- /dev/null +++ b/Modules/TPC/include/TPC/CalPadClusterReductor.h @@ -0,0 +1,67 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// file CalPadClusterReductor.h +// author Marcel Lesch +// +#ifndef QC_MODULE_TPC_CALPADCLUSTERDUCTOR_H +#define QC_MODULE_TPC_CALPADCLUSTERDUCTOR_H + +#include "QualityControl/ReductorTObject.h" +#if __has_include("TPCBase/CalDet.h") +#include "TPCBase/CalDet.h" +#else +#include "TPCBaseRecSim/CalDet.h" +#endif +#include "TPCBase/CalArray.h" +#include "TPCQC/Clusters.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief A Reductor of cluster data stored as CalPads. +/// +/// A Reductor of cluster data stored as CalPads. Stores number of entries, mean, standard deviation, median and rms +/// for each NClusters, QMax, QTot, SigmaTime, SigmaPad and TimeBin individually. +/// It produces a branch in the format: "NClusters[4][72]/F:QMax[4][72]:QTot[4][72]:SigmaTime[4][72]:SigmaPad[4][72]:TimeBin[4][72]" +/// First iterator holds entries [0], mean [1], standard deviation [2] and median [3] +/// Second iterator runs over all 72 ROCs + +class CalPadClusterReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + CalPadClusterReductor() = default; + ~CalPadClusterReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + Float_t NClusters[4][72]; + Float_t QMax[4][72]; + Float_t QTot[4][72]; + Float_t SigmaTime[4][72]; + Float_t SigmaPad[4][72]; + Float_t TimeBin[4][72]; + } mCalPad; + + o2::tpc::CalPad& GetCalPad(o2::tpc::qc::Clusters& clusters, int dataType); + typedef Float_t (*pointer_to_arrays)[72]; + pointer_to_arrays getArrayPointer(int dataType); + +}; // class CalPadClusterReductor : public quality_control::postprocessing::Reductor + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_CALPADCLUSTERDUCTOR_H diff --git a/Modules/TPC/include/TPC/CheckForEmptyPads.h b/Modules/TPC/include/TPC/CheckForEmptyPads.h new file mode 100644 index 0000000000..99d7ab9534 --- /dev/null +++ b/Modules/TPC/include/TPC/CheckForEmptyPads.h @@ -0,0 +1,65 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckForEmptyPads.h +/// \author Laura Serksnyte +/// + +#ifndef QC_MODULE_TPC_CheckForEmptyPads_H +#define QC_MODULE_TPC_CheckForEmptyPads_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Check whether the cluster number for a track is smaller than 40 or 20 in Track task. +/// +/// \author Laura Serksnyte +class CheckForEmptyPads : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + CheckForEmptyPads() = default; + /// Destructor + ~CheckForEmptyPads() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + ClassDefOverride(CheckForEmptyPads, 1); + std::string summarizeMetaData(Quality quality); + std::vector mSectorsName; + std::vector mSectorsQuality; + std::vector mMOsToCheck2D; + double mMediumQualityLimit; + double mBadQualityLimit; + + std::string mBadString = ""; + std::string mMediumString = ""; + std::string mGoodString = ""; + std::string mNullString = ""; + + std::string mBadStringMeta = ""; + std::string mMediumStringMeta = ""; + std::string mGoodStringMeta = ""; + std::string mNullStringMeta = ""; + + std::string mMetadataComment = ""; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_CheckForEmptyPads_H \ No newline at end of file diff --git a/Modules/TPC/include/TPC/CheckOfPads.h b/Modules/TPC/include/TPC/CheckOfPads.h new file mode 100644 index 0000000000..452dc82043 --- /dev/null +++ b/Modules/TPC/include/TPC/CheckOfPads.h @@ -0,0 +1,74 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckOfPads.h +/// \author Maximilian Horst +/// + +#ifndef QC_MODULE_TPC_CheckOfPads_H +#define QC_MODULE_TPC_CheckOfPads_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief +/// +/// \author Maximilian Horst +class CheckOfPads : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + CheckOfPads() = default; + /// Destructor + ~CheckOfPads() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + ClassDefOverride(CheckOfPads, 1); + static constexpr std::string_view CheckChoiceMean = "Mean"; + static constexpr std::string_view CheckChoiceExpectedValue = "ExpectedValue"; + static constexpr std::string_view CheckChoiceBoth = "Both"; + std::vector mSectorsNameEV; + std::vector mSectorsQualityEV; + std::vector mSectorsNameMean; + std::vector mSectorsQualityMean; + std::vector mSectorsQualityEmpty; + std::vector mSectorsName; + std::vector mSectorsQuality; + std::vector mMOsToCheck2D; + std::string mCheckChoice = "NULL"; + std::vector mPadMeans; + std::vector mPadStdev; + std::vector mEmptyPadPercent; + float mMediumQualityLimit; + float mBadQualityLimit; + float mExpectedValue; + float mExpectedValueMediumSigmas; + float mExpectedValueBadSigmas; + float mMeanMediumSigmas; + float mMeanBadSigmas; + float mTotalMean; + float mTotalStdev; + bool mEmptyCheck = false; + bool mExpectedValueCheck = false; + bool mMeanCheck = false; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_CheckOfPads_H \ No newline at end of file diff --git a/Modules/TPC/include/TPC/CheckOfSlices.h b/Modules/TPC/include/TPC/CheckOfSlices.h new file mode 100644 index 0000000000..138df05a60 --- /dev/null +++ b/Modules/TPC/include/TPC/CheckOfSlices.h @@ -0,0 +1,77 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckOfSlices.h +/// \author Maximilian Horst +/// \author Laura Serksnyte +/// \author Marcel Lesch +/// + +#ifndef QC_MODULE_TPC_CHECKOFSLICES_H +#define QC_MODULE_TPC_CHECKOFSLICES_H +#include +#include +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Check if all the slices in a Trending (e.g. as a function of TPC sector) are within their uncertainty compatible with the mean or a predefined physical value +/// \author Maximilian Horst +/// \author Laura Serksnyte +/// \author Marcel Lesch +class CheckOfSlices : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + CheckOfSlices() = default; + /// Destructor + ~CheckOfSlices() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + ClassDefOverride(CheckOfSlices, 3); + std::string createMetaData(const std::vector& pointMetaData); + std::string mCheckChoice; + double mExpectedPhysicsValue; + double mNSigmaExpectedPhysicsValue; + double mNSigmaBadExpectedPhysicsValue; + double mNSigmaMean; + double mNSigmaBadMean; + double mRangeMedium; + double mRangeBad; + bool mSliceTrend; + std::vector mMaskedPoints; + + double mMean = 0; + double mStdev; + + std::string mBadString = ""; + std::string mMediumString = ""; + std::string mGoodString = ""; + std::string mNullString = ""; + + std::string mMetadataComment; + + bool mRangeCheck = false; + bool mExpectedValueCheck = false; + bool mMeanCheck = false; + bool mZeroCheck = false; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_CHECKOFSLICES_H \ No newline at end of file diff --git a/Modules/TPC/include/TPC/CheckOfTrendings.h b/Modules/TPC/include/TPC/CheckOfTrendings.h new file mode 100644 index 0000000000..8fca751011 --- /dev/null +++ b/Modules/TPC/include/TPC/CheckOfTrendings.h @@ -0,0 +1,80 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckOfTrendings.h +/// \author Laura Serksnyte +/// \author Marcel Lesch +/// + +#ifndef QC_MODULE_TPC_CHECKOFTRENDINGS_H +#define QC_MODULE_TPC_CHECKOFTRENDINGS_H + +#include "QualityControl/CheckInterface.h" + +class TGraph; +class TCanvas; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Check if the a new data point in trend is not 3sigma or 6 sigma away from the average value +/// +/// \author Laura Serksnyte +/// \author Marcel Lesch +class CheckOfTrendings : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + CheckOfTrendings() = default; + /// Destructor + ~CheckOfTrendings() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + ClassDefOverride(CheckOfTrendings, 2); + void getGraphs(TCanvas* canv, std::vector& graphs, const std::string moName); + std::string createMetaData(const std::vector& pointMetaData); + std::string mCheckChoice; + float mExpectedPhysicsValue; + float mNSigmaExpectedPhysicsValue; + float mNSigmaBadExpectedPhysicsValue; + float mNSigmaMean; + float mNSigmaBadMean; + float mRangeMedium; + float mRangeBad; + bool mSliceTrend; + + std::vector mStdev; + + int mPointToTakeForExpectedValueCheck; + int mPointToTakeForMeanCheck; + int mPointToTakeForRangeCheck; + int mPointToTakeForZeroCheck; + + std::unordered_map> mPadMetaData; + std::vector mPadQualities; + + std::string mMetadataComment; + + bool mRangeCheck = false; + bool mExpectedValueCheck = false; + bool mMeanCheck = false; + bool mZeroCheck = false; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_CHECKOFTRENDINGS_H \ No newline at end of file diff --git a/Modules/TPC/include/TPC/ClusterVisualizer.h b/Modules/TPC/include/TPC/ClusterVisualizer.h new file mode 100644 index 0000000000..7fd9340af1 --- /dev/null +++ b/Modules/TPC/include/TPC/ClusterVisualizer.h @@ -0,0 +1,90 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterVisualizer.h +/// \author Thomas Klemenz +/// + +#ifndef QUALITYCONTROL_CLUSTERVISUALIZER_H +#define QUALITYCONTROL_CLUSTERVISUALIZER_H + +// O2 includes +#include "CCDB/CcdbApi.h" + +// QC includes +#include "QualityControl/PostProcessingInterface.h" + +#include +#include + +class TCanvas; +class TPaveText; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Quality Control task for the calibration data of the TPC +/// \author Thomas Klemenz +class ClusterVisualizer final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + ClusterVisualizer() = default; + /// \brief Destructor + ~ClusterVisualizer() = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + template + void makeRadialProfile(o2::tpc::CalDet& calDet, TCanvas* canv, int nbinsY, float yMin, float yMax); + + template + void fillRadialHisto(TH2D& h2D, const o2::tpc::CalDet& calDet, const o2::tpc::Side side); + + private: + int mNHBFPerTF = 32; + o2::ccdb::CcdbApi mCdbApi; + std::string mHost; + std::vector>> mCalDetCanvasVec{}; ///< vector containing a vector of summary canvases for every CalDet object + std::vector mTimestamps{}; ///< timestamps to look for specific data in the CCDB + std::vector> mLookupMaps{}; ///< meta data to look for data in the CCDB + std::vector> mStoreMaps{}; ///< meta data to be stored with the output in the QCDB + std::unordered_map> mRanges; ///< histogram ranges configurable via config file + std::vector mObservables{}; + std::string mPath; + bool mIsClusters; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_CLUSTERVISUALIZER_H diff --git a/Modules/TPC/include/TPC/Clusters.h b/Modules/TPC/include/TPC/Clusters.h new file mode 100644 index 0000000000..97741af41b --- /dev/null +++ b/Modules/TPC/include/TPC/Clusters.h @@ -0,0 +1,75 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Clusters.h +/// \author Jens Wiechula +/// \author Thomas Klemenz +/// + +#ifndef QC_MODULE_TPC_CLUSTERS_H +#define QC_MODULE_TPC_CLUSTERS_H + +// O2 includes +#include "TPCQC/CalPadWrapper.h" + +// QC includes +#include "QualityControl/TaskInterface.h" +#include "TPC/ClustersData.h" + +class TCanvas; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Example Quality Control DPL Task +/// It is final because there is no reason to derive from it. Just remove it if needed. +/// \author Barthelemy von Haller +/// \author Piotr Konopka +class Clusters /*final*/ : public TaskInterface // todo add back the "final" when doxygen is fixed +{ + public: + /// \brief Constructor + Clusters(); + /// \brief Destructor + ~Clusters() = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + bool mIsMergeable = true; + int mNHBFPerTF = 32; + ClustersData mQCClusters{}; ///< O2 Cluster task to perform actions on cluster objects + std::vector mWrapperVector{}; ///< vector holding CalPad objects wrapped as TObjects; published on QCG; will be non-wrapped CalPad objects in the future + std::vector> mNClustersCanvasVec{}; ///< summary canvases of the NClusters object + std::vector> mQMaxCanvasVec{}; ///< summary canvases of the QMax object + std::vector> mQTotCanvasVec{}; ///< summary canvases of the QTot object + std::vector> mSigmaTimeCanvasVec{}; ///< summary canvases of the SigmaTime object + std::vector> mSigmaPadCanvasVec{}; ///< summary canvases of the SigmaPad object + std::vector> mTimeBinCanvasVec{}; ///< summary canvases of the TimeBin object + std::vector> mOccupancyCanvasVec{}; ///< summary canvases of the Occupancy object + + void processClusterNative(o2::framework::InputRecord& inputs); + void processKrClusters(o2::framework::InputRecord& inputs); +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_CLUSTERS_H diff --git a/Modules/TPC/include/TPC/ClustersData.h b/Modules/TPC/include/TPC/ClustersData.h new file mode 100644 index 0000000000..c4e45e7206 --- /dev/null +++ b/Modules/TPC/include/TPC/ClustersData.h @@ -0,0 +1,74 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClustersData.h +/// \author Jens Wiechula +/// \author Thomas Klemenz +/// + +#ifndef QC_MODULE_TPC_CLUSTERSDATA_H +#define QC_MODULE_TPC_CLUSTERSDATA_H + +// ROOT includes +#include "TObject.h" + +// O2 includes +#include +#include +#include "TPCQC/Clusters.h" + +using o2::mergers::MergeInterface; + +namespace o2::quality_control_modules::tpc +{ + +class ClustersData final : public TObject, public MergeInterface +{ + public: + ClustersData() + : o2::mergers::MergeInterface(), TObject() + { + } + + ClustersData(std::string_view nclName) + : o2::mergers::MergeInterface(), TObject(), mClusters{ nclName } + { + } + + ~ClustersData() final = default; + + virtual void merge(MergeInterface* other) final; + + o2::tpc::qc::Clusters& getClusters() { return mClusters; } + + void setName(std::string_view name) { mName = name.data(); } + + virtual const char* GetName() const override { return mName.data(); } + + private: + o2::tpc::qc::Clusters mClusters; + std::string mName; + + ClassDefOverride(ClustersData, 1); +}; + +inline void ClustersData::merge(MergeInterface* other) +{ + auto otherCl = dynamic_cast(other); + if (otherCl) { + mClusters.merge(otherCl->mClusters); + } +} + +} // namespace o2::quality_control_modules::tpc + +#endif diff --git a/Modules/TPC/include/TPC/DCSPTempReductor.h b/Modules/TPC/include/TPC/DCSPTempReductor.h new file mode 100644 index 0000000000..0cdad5f1d6 --- /dev/null +++ b/Modules/TPC/include/TPC/DCSPTempReductor.h @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DCSPTempReductor.h +/// \author Marcel Lesch +/// + +#ifndef QUALITYCONTROL_DCSPTEMPREDUCTOR_H +#define QUALITYCONTROL_DCSPTEMPREDUCTOR_H + +#include "QualityControl/ReductorConditionAny.h" +#include + +namespace o2::quality_control_modules::tpc +{ + +/// \brief A Reductor for calibration objects of the TPC DCS temperatures +/// +/// A Reductor for TPC DCS temperatures. +/// It produces a branch in the format: +/// "tempSensor[18]/F:tempSensorErr[18]:tempMeanPerSide[2]:tempMeanPerSideErr[2]:tempGradXPerSide[2]:tempGradXPerSideErr[2]:tempGradYPerSide[2]:tempGradYPerSideErr[2]" +/// tempSensor[i] is the raw sensor temperature for each of the 18 sensores +/// tempMeanPerSide[i] is the mean temperature per TPC-Side (0: A-Side, 1: C-Side) +/// tempGradXPerSide[i] is the temperature gradient in x direction per TPC-Side (0: A-Side, 1: C-Side) +/// tempGradYPerSide[i] is the temperature gradient in y direction per TPC-Side (0: A-Side, 1: C-Side) + +class DCSPTempReductor : public quality_control::postprocessing::ReductorConditionAny +{ + public: + DCSPTempReductor() = default; + ~DCSPTempReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + bool update(ConditionRetriever& retriever) override; + + private: + struct { + Float_t tempSensor[18]; + Float_t tempSensorErr[18]; // uncertainties + + Float_t tempMeanPerSide[2]; + Float_t tempMeanPerSideErr[2]; // uncertainties + + Float_t tempGradXPerSide[2]; + Float_t tempGradXPerSideErr[2]; // uncertainties + + Float_t tempGradYPerSide[2]; + Float_t tempGradYPerSideErr[2]; // uncertainties + } mStats; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_DCSPTEMPREDUCTOR_H diff --git a/Modules/TPC/include/TPC/DCSPTemperature.h b/Modules/TPC/include/TPC/DCSPTemperature.h new file mode 100644 index 0000000000..5377b042ad --- /dev/null +++ b/Modules/TPC/include/TPC/DCSPTemperature.h @@ -0,0 +1,79 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DCSPTemperature.h +/// \author Thomas Klemenz +/// + +#ifndef QUALITYCONTROL_DCSPTEMPERATURE_H +#define QUALITYCONTROL_DCSPTEMPERATURE_H + +// O2 includes +#include "CCDB/CcdbApi.h" +#include "TPCQC/DCSPTemperature.h" + +// QC includes +#include "QualityControl/PostProcessingInterface.h" + +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Quality Control task for the IDC data of the TPC +/// \author Thomas Klemenz +class DCSPTemperature : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + DCSPTemperature() = default; + /// \brief Destructor + ~DCSPTemperature() = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + o2::tpc::qc::DCSPTemperature mDCSPTemp; + o2::ccdb::CcdbApi mCdbApi; + std::string mHost; + int mNFiles; + std::vector> mData; + long mTimestamp; ///< timestamp to look for specific data in the CCDB + std::map mLookupMap; ///< meta data to look for data in the CCDB + std::map mStoreMap; ///< meta data to be stored with the output in the QCDB +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_IDCS_H diff --git a/Modules/TPC/include/TPC/GPUErrorQA.h b/Modules/TPC/include/TPC/GPUErrorQA.h new file mode 100644 index 0000000000..1ee46ebcd1 --- /dev/null +++ b/Modules/TPC/include/TPC/GPUErrorQA.h @@ -0,0 +1,57 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GPUErrorQA.h +/// \author Anton Riedel, anton.riedel@cern.ch +/// + +#ifndef QC_MODULE_TPC_GPUERRORQA_H +#define QC_MODULE_TPC_GPUERRORQA_H + +// O2 includes +#include "TPCQC/GPUErrorQA.h" + +// QC includes +#include "QualityControl/TaskInterface.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Quality Control DPL Task for QC Module TPC GPU errors +/// \author Anton Riedel + +class GPUErrorQA final : public TaskInterface +{ + public: + /// \brief Constructor + GPUErrorQA() = default; + /// Destructor + ~GPUErrorQA() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + o2::tpc::qc::GPUErrorQA mGPUErrorQA; ///< TPC QC class from o2 +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_GPUERRORQA_H diff --git a/Modules/TPC/include/TPC/GenericHistogramCheck.h b/Modules/TPC/include/TPC/GenericHistogramCheck.h new file mode 100644 index 0000000000..2999480bad --- /dev/null +++ b/Modules/TPC/include/TPC/GenericHistogramCheck.h @@ -0,0 +1,72 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GenericHistogramCheck.h +/// \author Maximilian Horst +/// + +#ifndef QC_MODULE_TPC_GENERICHISTOGRAMCHECK_H +#define QC_MODULE_TPC_GENERICHISTOGRAMCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Checks any 1D and 2D Histogram for their mean in X and Y agains an expected Value with a range or their StdDeviation +/// \author Maximilian Horst +class GenericHistogramCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + GenericHistogramCheck() = default; + /// Destructor + ~GenericHistogramCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + ClassDefOverride(GenericHistogramCheck, 2); + std::string mAxisName; + float mMeanX = 0; + float mMeanY = 0; + float mStdevX = 0; + float mStdevY = 0; + int mHistDimension = 0; + bool mCheckXAxis = false; + bool mCheckYAxis = false; + bool mCheckRange = false; + bool mCheckStdDev = false; + float mExpectedValueX; + float mRangeX; + float mExpectedValueY; + float mRangeY; + + std::string mBadString = ""; + std::string mMediumString = ""; + std::string mGoodString = ""; + std::string mNullString = ""; + + std::string mBadStringMeta = ""; + std::string mMediumStringMeta = ""; + std::string mGoodStringMeta = ""; + std::string mNullStringMeta = ""; + + std::string mMetadataComment = ""; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_GENERICHISTOGRAMCHECK_H diff --git a/Modules/TPC/include/TPC/IDCScaleReductor.h b/Modules/TPC/include/TPC/IDCScaleReductor.h new file mode 100644 index 0000000000..58975506ed --- /dev/null +++ b/Modules/TPC/include/TPC/IDCScaleReductor.h @@ -0,0 +1,46 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// file IDCScaleReductor.h +// author Marcel Lesch +// +#ifndef QC_MODULE_TPC_IDCSCALEREDUCTOR_H +#define QC_MODULE_TPC_IDCSCALEREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief A Reductor of IDC 0 scale factors for TPC A and C side. + +class IDCScaleReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + IDCScaleReductor() = default; + ~IDCScaleReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + Float_t ScaleFactorASide; + Float_t ScaleFactorCSide; + } mIDC; + +}; // class IDCScaleReductor : public quality_control::postprocessing::Reductor + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_IDCSCALEREDUCTOR_H diff --git a/Modules/TPC/include/TPC/IDCs.h b/Modules/TPC/include/TPC/IDCs.h new file mode 100644 index 0000000000..7dcc6e442a --- /dev/null +++ b/Modules/TPC/include/TPC/IDCs.h @@ -0,0 +1,98 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file IDCs.h +/// \author Thomas Klemenz +/// + +#ifndef QUALITYCONTROL_IDCS_H +#define QUALITYCONTROL_IDCS_H + +// O2 includes +#include "TPCCalibration/IDCContainer.h" +#include "TPCCalibration/IDCCCDBHelper.h" +#include "TPCCalibration/IDCGroupHelperSector.h" +#include "CCDB/CcdbApi.h" + +// QC includes +#include "QualityControl/PostProcessingInterface.h" + +// ROOT includes +#include "TCanvas.h" + +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Quality Control task for the IDC data of the TPC +/// \author Thomas Klemenz +class IDCs : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + IDCs() = default; + /// \brief Destructor + ~IDCs() = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + o2::tpc::IDCCCDBHelper mCCDBHelper; + o2::ccdb::CcdbApi mCdbApi; + std::string mHost; + bool mDoIDCDelta = false; + bool mDoIDC1 = false; + bool mDoFourier = false; + std::unique_ptr mIDCZeroScale; + std::unique_ptr mIDCZerOverview; + std::unique_ptr mIDCZeroSides; + std::unique_ptr mIDCZeroRadialProf; + std::unique_ptr mIDCZeroStacksA; + std::unique_ptr mIDCZeroStacksC; + std::unique_ptr mIDCDeltaStacksA; + std::unique_ptr mIDCDeltaStacksC; + std::unique_ptr mIDCOneSides1D; + std::unique_ptr mFourierCoeffsA; + std::unique_ptr mFourierCoeffsC; + + std::unordered_map mTimestamps; ///< timestamps to look for specific data in the CCDB + std::vector> mLookupMaps{}; ///< meta data to look for data in the CCDB + std::vector> mStoreMaps{}; ///< meta data to be stored with the output in the QCDB + std::unordered_map> mRanges; ///< histogram ranges configurable via config file +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_IDCS_H diff --git a/Modules/TPC/include/TPC/IDCsVsSACs.h b/Modules/TPC/include/TPC/IDCsVsSACs.h new file mode 100644 index 0000000000..4ffba201ea --- /dev/null +++ b/Modules/TPC/include/TPC/IDCsVsSACs.h @@ -0,0 +1,91 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file IDCsVsSACs.h +/// \author Bhawani Singh +/// + +#ifndef QUALITYCONTROL_IDCSVSSACS_H +#define QUALITYCONTROL_IDCSVSSACS_H + +// O2 includes +#include "TPCCalibration/IDCContainer.h" +#include "TPCCalibration/IDCCCDBHelper.h" +#include "TPCCalibration/SACCCDBHelper.h" +#include "TPCCalibration/IDCGroupHelperSector.h" +#include "CCDB/CcdbApi.h" +#include "TPCQC/IDCsVsSACs.h" + +// QC includes +#include "QualityControl/PostProcessingInterface.h" + +// ROOT includes +#include "TCanvas.h" + +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Quality Control task for the IDC data of the TPC comparision +/// \author Bhawani Singh +class IDCsVsSACs : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + IDCsVsSACs() = default; + /// \brief Destructor + ~IDCsVsSACs() = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param name Name of the task + /// \param config ConfigurationInterface with prefix set to "" + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + o2::tpc::IDCCCDBHelper mCCDBHelper; + /// to have comparision plots of SACs and IDCs + o2::tpc::SACCCDBHelper mSACs; + o2::tpc::qc::IDCsVsSACs mIDCsVsSACs; + o2::ccdb::CcdbApi mCdbApi; + std::string mHost; + std::unique_ptr mCompareIDC0andSAC0; + + std::unordered_map mTimestamps; ///< timestamps to look for specific data in the CCDB + std::vector> mLookupMaps{}; ///< meta data to look for data in the CCDB + std::vector> mStoreMaps{}; ///< meta data to be stored with the output in the QCDB + std::unordered_map> mRanges; ///< histogram ranges configurable via config file +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_IDCSVSSACS_H diff --git a/Modules/TPC/include/TPC/JunkDetection.h b/Modules/TPC/include/TPC/JunkDetection.h new file mode 100644 index 0000000000..e5baa69249 --- /dev/null +++ b/Modules/TPC/include/TPC/JunkDetection.h @@ -0,0 +1,67 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file JunkDetection.h +/// \author Thomas Klemenz +/// + +#ifndef QC_MODULE_TPC_JUNKDETECTION_H +#define QC_MODULE_TPC_JUNKDETECTION_H + +// QC includes +#include "QualityControl/TaskInterface.h" + +// root includes +#include "TH2.h" + +class TCanvas; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief TPC JunkDetection QC Task +/// \author Thomas Klemenz +class JunkDetection final : public TaskInterface +{ + public: + /// \brief Constructor + JunkDetection() = default; + /// Destructor + ~JunkDetection() + { + for (auto hist : mJDHistos) { + delete hist; + } + } + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + /// fills the output canvas with the relevant info + TCanvas* makeCanvas(const TObjArray* data, TCanvas* outputCanvas); + + private: + bool mIsMergeable = false; + std::unique_ptr mJDCanv; + std::vector mJDHistos{}; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_JUNKDETECTION_H diff --git a/Modules/TPC/include/TPC/LaserTracks.h b/Modules/TPC/include/TPC/LaserTracks.h new file mode 100644 index 0000000000..0c79e4ba80 --- /dev/null +++ b/Modules/TPC/include/TPC/LaserTracks.h @@ -0,0 +1,76 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file LaserTracks.h +/// \author Thomas Klemenz +/// + +#ifndef QUALITYCONTROL_LASERTRACKS_H +#define QUALITYCONTROL_LASERTRACKS_H + +// QC includes +#include "QualityControl/PostProcessingInterface.h" + +#include +#include + +class TCanvas; +class TPaveText; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Quality Control task for the laser track calibration data of the TPC +/// \author Thomas Klemenz +class LaserTracks final : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + LaserTracks() = default; + /// \brief Destructor + ~LaserTracks() = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + std::vector> mLaserTracksCanvasVec{}; ///< vector containing a vector of summary canvases for every CalDet object + long mTimestamp = -1; ///< timestamps to look for specific data in the CCDB + std::map mLookupMap{}; ///< meta data to look for data in the CCDB + std::map mStoreMap{}; ///< meta data to be stored with the output in the QCDB + + TPaveText* mNewZSCalibMsg = nullptr; ///< badge to indicate the necessity to upload new calibration data for ZS +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_LASERTRACKS_H diff --git a/Modules/TPC/include/TPC/LinkDef.h b/Modules/TPC/include/TPC/LinkDef.h new file mode 100644 index 0000000000..4c5277a709 --- /dev/null +++ b/Modules/TPC/include/TPC/LinkDef.h @@ -0,0 +1,53 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; +#pragma link C++ class o2::quality_control_modules::tpc::PID + ; +#pragma link C++ class o2::quality_control_modules::tpc::Tracking + ; +#pragma link C++ class o2::quality_control_modules::tpc::Tracks + ; +#pragma link C++ class o2::quality_control_modules::tpc::ROCReductor + ; +#pragma link C++ class o2::quality_control_modules::tpc::Clusters + ; +#pragma link C++ class o2::quality_control_modules::tpc::CalDetPublisher + ; +#pragma link C++ class o2::quality_control_modules::tpc::RawDigits + ; +#pragma link C++ class o2::quality_control_modules::tpc::CheckOfTrendings + ; +#pragma link C++ class o2::quality_control_modules::tpc::PadCalibrationCheck + ; +#pragma link C++ class o2::quality_control_modules::tpc::LaserTracks + ; +#pragma link C++ class o2::quality_control_modules::tpc::LtrCalibReductor + ; +#pragma link C++ class o2::quality_control_modules::tpc::ClustersData + ; +#pragma link C++ class o2::quality_control_modules::tpc::ClusterVisualizer + ; +#pragma link C++ class o2::quality_control_modules::tpc::SliceInfo + ; +#pragma link C++ class std::vector < o2::quality_control_modules::tpc::SliceInfo> + ; +#pragma link C++ class o2::quality_control_modules::tpc::TrendingTaskTPC + ; +#pragma link C++ class o2::quality_control_modules::tpc::TrendingTaskConfigTPC + ; +#pragma link C++ class o2::quality_control_modules::tpc::ReductorTPC + ; +#pragma link C++ class o2::quality_control_modules::tpc::TH1ReductorTPC + ; +#pragma link C++ class o2::quality_control_modules::tpc::TH2ReductorTPC + ; +#pragma link C++ class o2::quality_control_modules::tpc::CheckForEmptyPads + ; +#pragma link C++ class o2::quality_control_modules::tpc::SliceInfoQuality + ; +#pragma link C++ class o2::quality_control_modules::tpc::QualityReductorTPC + ; +#pragma link C++ class o2::quality_control_modules::tpc::DCSPTemperature + ; +#pragma link C++ class o2::quality_control_modules::tpc::IDCs + ; +#pragma link C++ class o2::quality_control_modules::tpc::IDCsVsSACs + ; +#pragma link C++ class o2::quality_control_modules::tpc::QualityObserver + ; +#pragma link C++ class o2::quality_control_modules::tpc::RatioGeneratorTPC + ; +#pragma link C++ class o2::quality_control_modules::tpc::CheckOfSlices + ; +#pragma link C++ class o2::quality_control_modules::tpc::GenericHistogramCheck + ; +#pragma link C++ class o2::quality_control_modules::tpc::JunkDetection+; +#pragma link C++ class o2::quality_control_modules::tpc::CheckOfPads + ; +#pragma link C++ class o2::quality_control_modules::tpc::CalPadClusterReductor + ; +#pragma link C++ class o2::quality_control_modules::tpc::IDCScaleReductor + ; +#pragma link C++ class o2::quality_control_modules::tpc::SACs + ; +#pragma link C++ class o2::quality_control_modules::tpc::TPCAggregator + ; +#pragma link C++ class o2::quality_control_modules::tpc::SACZeroScaleReductor + ; +#pragma link C++ class o2::quality_control_modules::tpc::TrackClusters + ; +#pragma link C++ class o2::quality_control_modules::tpc::VDriftCalibReductor + ; +#pragma link C++ class o2::quality_control_modules::tpc::SeparationPowerReductor + ; +#pragma link C++ class o2::quality_control_modules::tpc::TimeGainCalibReductor + ; +#pragma link C++ class o2::quality_control_modules::tpc::DCSPTempReductor + ; +#pragma link C++ class o2::quality_control_modules::tpc::AtmosPressureReductor + ; +#pragma link C++ class o2::quality_control_modules::tpc::GPUErrorQA + ; + +#pragma link C++ function o2::quality_control_modules::tpc::addAndPublish + ; +#pragma link C++ function o2::quality_control_modules::tpc::toVector + ; +#pragma link C++ function o2::quality_control_modules::tpc::clusterHandler + ; +#endif diff --git a/Modules/TPC/include/TPC/LtrCalibReductor.h b/Modules/TPC/include/TPC/LtrCalibReductor.h new file mode 100644 index 0000000000..ff414e0aa2 --- /dev/null +++ b/Modules/TPC/include/TPC/LtrCalibReductor.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// \file LtrCalibReductor.h +// \author Cindy Mordasini +// \author Marcel Lesch +// +#ifndef QC_MODULE_TPC_LTRCALIBREDUCTOR_H +#define QC_MODULE_TPC_LTRCALIBREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" +#include + +namespace o2::quality_control_modules::tpc +{ + +/// \brief +/// +/// +class LtrCalibReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + LtrCalibReductor() = default; + ~LtrCalibReductor() = default; + + void* getBranchAddress() final; + const char* getBranchLeafList() final; + void update(TObject* obj) final; + + private: + struct { + double processedTFs; + double dvCorrectionA; + double dvCorrectionC; + double dvCorrection; + double dvOffsetA; + double dvOffsetC; + double t0A; + double t0C; + double nTracksA; + double nTracksC; + double dvAbsolute; + } mLtrCalib; + + double getValue(TText* line); +}; + +} // namespace o2::quality_control_modules::tpc +#endif // QC_MODULE_TPC_LTRCALIBREDUCTOR_H diff --git a/Modules/TPC/include/TPC/PID.h b/Modules/TPC/include/TPC/PID.h new file mode 100644 index 0000000000..53fad42b1b --- /dev/null +++ b/Modules/TPC/include/TPC/PID.h @@ -0,0 +1,64 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PID.h +/// \author Jens Wiechula +/// + +#ifndef QC_MODULE_TPC_PID_H +#define QC_MODULE_TPC_PID_H + +// O2 includes +#include "TPCQC/PID.h" + +// QC includes +#include "QualityControl/TaskInterface.h" + +//ROOT includes +#include + +class TH1F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief TPC PID QC Task +/// It is final because there is no reason to derive from it. Just remove it if needed. +/// \author Jens Wiechula +class PID final : public TaskInterface +{ + public: + /// \brief Constructor + PID(); + /// Destructor + ~PID() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + o2::tpc::qc::PID mQCPID{}; + std::unique_ptr mSeparationPower{}; + const int nPars = 8; //6 fit parameters + 2 external (seperation power + chi²/ndf) +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_PID_H diff --git a/Modules/TPC/include/TPC/PadCalibrationCheck.h b/Modules/TPC/include/TPC/PadCalibrationCheck.h new file mode 100644 index 0000000000..685482aa45 --- /dev/null +++ b/Modules/TPC/include/TPC/PadCalibrationCheck.h @@ -0,0 +1,54 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PadCalibrationCheck.h +/// \author Laura Serksnyte +/// + +#ifndef QC_MODULE_TPC_PadCalibrationCheck_H +#define QC_MODULE_TPC_PadCalibrationCheck_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Check whether the cluster number for a track is smaller than 40 or 20 in Track task. +/// +/// \author Laura Serksnyte +class PadCalibrationCheck : public o2::quality_control::checker::CheckInterface +{ + + public: + /// Default constructor + PadCalibrationCheck() = default; + /// Destructor + ~PadCalibrationCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + + private: + ClassDefOverride(PadCalibrationCheck, 2); + std::vector mSectorsName; + std::vector mSectorsQuality; + std::vector mNoiseMean; + std::vector mNoiseStdDev; + std::vector mNoiseNonZeroEntries; + double mMediumQualityLimitNoiseMean; + double mBadQualityLimitNoiseMean; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_PadCalibrationCheck_H \ No newline at end of file diff --git a/Modules/TPC/include/TPC/QualityObserver.h b/Modules/TPC/include/TPC/QualityObserver.h new file mode 100644 index 0000000000..f09846ab8f --- /dev/null +++ b/Modules/TPC/include/TPC/QualityObserver.h @@ -0,0 +1,89 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityObserver.h +/// \author Marcel Lesch +/// + +#ifndef QUALITYCONTROL_QUALITYOBSERVER_H +#define QUALITYCONTROL_QUALITYOBSERVER_H + +#include "QualityControl/PostProcessingInterface.h" +#include +#include +#include +#include +class TCanvas; + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} // namespace o2::quality_control::repository + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::tpc +{ +/// \brief A post-processing task to generate an overview of multiple groups of qualities +/// +/// A post-processing task which generates an overview panel for multiple groups of qualities. +/// It extracts the quality of the tasks/QOs passed to the object and creates a TPaveText +/// as ouput. +/// + +class QualityObserver : public PostProcessingInterface +{ + public: + /// \brief Constructor. + QualityObserver() = default; + /// \brief Destructor. + ~QualityObserver() final = default; + + /// \brief Post-processing methods inherited from 'PostProcessingInterface'. + void configure(const boost::property_tree::ptree& config) final; + void initialize(Trigger, framework::ServiceRegistryRef) final; + void update(Trigger, framework::ServiceRegistryRef) final; + void finalize(Trigger, framework::ServiceRegistryRef) final; + + struct Config { + std::string groupTitle; + std::string path; + std::vector qo; + std::vector qoTitle; + }; + + private: + /// \brief Method to get the qualities of the QOs + void getQualities(const Trigger& t, o2::quality_control::repository::DatabaseInterface&); + /// \brief Method to create and publish the overview panel + void generatePanel(); + /// \brief Method to add text to the TPaveText + void generateText(TPaveText* pt, bool isReason, std::string QOMetaText); + /// \brief Method to break Text into smaller pieces + void breakText(TPaveText* pt, std::string infoType, std::string textUnbroken); + + std::vector mConfig; + std::string mObserverName; + std::unordered_map> mQualities; + std::unordered_map mColors; + TCanvas* mCanvas = nullptr; + size_t mLineLength = 70; + + bool mViewDetails; + std::unordered_map> mFlags; + std::unordered_map> mComments; + std::string mQualityDetailChoice; + std::unordered_map mQualityDetails; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_QUALITYOBSERVER_H diff --git a/Modules/TPC/include/TPC/QualityReductorTPC.h b/Modules/TPC/include/TPC/QualityReductorTPC.h new file mode 100644 index 0000000000..2e3d9af585 --- /dev/null +++ b/Modules/TPC/include/TPC/QualityReductorTPC.h @@ -0,0 +1,45 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1ReductorTPC.h +/// \author Marcel Lesch +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_QUALITYREDUCTORTPC_H +#define QUALITYCONTROL_QUALITYREDUCTORTPC_H + +#include "TPC/ReductorTPC.h" + +namespace o2::quality_control_modules::tpc +{ +/// \brief A reductor of quality objects for the trending of TPC objects. +/// +/// A Reductor for the quality objects used for the TPC quantities. It receives +/// a struct of 'SliceInfoQuality' +/// + +class QualityReductorTPC : public quality_control_modules::tpc::ReductorTPC +{ + public: + /// \brief Constructor. + QualityReductorTPC() = default; + /// \brief Destructor. + ~QualityReductorTPC() = default; + + /// \brief Methods from the reductor class adapted for the needs of the TPC. + void updateQuality(const TObject* obj, SliceInfoQuality& reducedQualitySource) final; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_QUALITYREDUCTORTPC_H diff --git a/Modules/TPC/include/TPC/ROCReductor.h b/Modules/TPC/include/TPC/ROCReductor.h new file mode 100644 index 0000000000..a8bd9b9a18 --- /dev/null +++ b/Modules/TPC/include/TPC/ROCReductor.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// file ROCReductor.h +// author Cindy Mordasini +// author Marcel Lesch +// +#ifndef QC_MODULE_TPC_ROCREDUCTOR_H +#define QC_MODULE_TPC_ROCREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" +#if __has_include("TPCBase/CalDet.h") +#include "TPCBase/CalDet.h" +#else +#include "TPCBaseRecSim/CalDet.h" +#endif +//#include "CCDB/TObjectWrapper.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief A Reductor of ROC, stores mean, standard deviation and median for each ROC. +/// +/// A Reductor of ROC, stores entries, mean, standard deviation, median and rms for each ROC. +/// It produces a branch in the format: "entries[72]/I:mean[72]/F:stddev[72]:median[72]:rms[72]" +class ROCReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + ROCReductor() = default; + ~ROCReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + Int_t entries[72]; + Float_t mean[72]; + Float_t stddev[72]; + Float_t median[72]; + Float_t rms[72]; + } mCalPad; + +}; // class ROCReductor : public quality_control::postprocessing::Reductor + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_ROCREDUCTOR_H diff --git a/Modules/TPC/include/TPC/RatioGeneratorTPC.h b/Modules/TPC/include/TPC/RatioGeneratorTPC.h new file mode 100644 index 0000000000..e040cc5b43 --- /dev/null +++ b/Modules/TPC/include/TPC/RatioGeneratorTPC.h @@ -0,0 +1,74 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RatioGeneratorTPC.h +/// \author Marcel Lesch +/// + +#ifndef QUALITYCONTROL_RATIOGENERATORTPC_H +#define QUALITYCONTROL_RATIOGENERATORTPC_H + +#include "QualityControl/PostProcessingInterface.h" +#include +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} // namespace o2::quality_control::repository + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::tpc +{ +/// \brief A post-processing task which generates ratios of two merged histogram for post-processing +/// +/// A post-processing task which generates ratios of two histogram for post-processing. +/// It takes two TH1 objects as input, calculates the ratio and stores the ratio TH1 on ccdb/qcg. +/// + +class RatioGeneratorTPC : public PostProcessingInterface +{ + public: + /// \brief Constructor. + RatioGeneratorTPC() = default; + /// \brief Destructor. + ~RatioGeneratorTPC() final = default; + + /// \brief Post-processing methods inherited from 'PostProcessingInterface'. + void configure(const boost::property_tree::ptree& config) final; + void initialize(Trigger, framework::ServiceRegistryRef) final{}; + void update(Trigger, framework::ServiceRegistryRef) final; + void finalize(Trigger, framework::ServiceRegistryRef) final; + + struct DataSource { + std::string path; + std::string nameInputObjects[2]; + std::string nameOutputObject; + std::string plotTitle; + std::string axisTitle; + }; + + private: + /// \brief Method to calculate the ratio between two TH1. + void generateRatios(const Trigger& t, o2::quality_control::repository::DatabaseInterface&); + /// \brief Method to create and publish plot. + void generatePlots(); + + std::unordered_map mRatios; + std::vector mConfig; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_RATIOGENERATORTPC_H diff --git a/Modules/TPC/include/TPC/RawDigits.h b/Modules/TPC/include/TPC/RawDigits.h new file mode 100644 index 0000000000..bd4199c2e5 --- /dev/null +++ b/Modules/TPC/include/TPC/RawDigits.h @@ -0,0 +1,73 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawDigits.h +/// \author Jens Wiechula +/// \author Thomas Klemenz +/// + +#ifndef QC_MODULE_TPC_RAWDIGITS_H +#define QC_MODULE_TPC_RAWDIGITS_H + +// O2 includes +#include "TPCQC/CalPadWrapper.h" +#include "TPCReconstruction/RawReaderCRU.h" +#include "TPCWorkflow/CalibProcessingHelper.h" + +// QC includes +#include "QualityControl/TaskInterface.h" +#include "TPC/ClustersData.h" + +class TCanvas; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Example Quality Control DPL Task +/// It is final because there is no reason to derive from it. Just remove it if needed. +/// \author Barthelemy von Haller +/// \author Piotr Konopka +class RawDigits /*final*/ : public TaskInterface // todo add back the "final" when doxygen is fixed +{ + public: + /// \brief Constructor + RawDigits(); + /// \brief Destructor + ~RawDigits() = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + bool mIsMergeable = true; + ClustersData mRawDigitQC{ "N_RawDigits" }; ///< O2 Cluster task to perform actions on cluster objects + std::vector mWrapperVector{}; ///< vector holding CalPad objects wrapped as TObjects; published on QCG; will be non-wrapped CalPad objects in the future + std::vector> mNRawDigitsCanvasVec{}; ///< summary canvases of the NRawDigits object + std::vector> mQMaxCanvasVec{}; ///< summary canvases of the QMax object + std::vector> mQTotCanvasVec{}; ///< summary canvases of the QTot object + std::vector> mSigmaTimeCanvasVec{}; ///< summary canvases of the SigmaTime object + std::vector> mSigmaPadCanvasVec{}; ///< summary canvases of the SigmaPad object + std::vector> mTimeBinCanvasVec{}; ///< summary canvases of the TimeBin object + o2::tpc::rawreader::RawReaderCRUManager mRawReader; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_RawDigits_H diff --git a/Modules/TPC/include/TPC/ReductorTPC.h b/Modules/TPC/include/TPC/ReductorTPC.h new file mode 100644 index 0000000000..8202d6ae0f --- /dev/null +++ b/Modules/TPC/include/TPC/ReductorTPC.h @@ -0,0 +1,69 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ReductorTPC.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_REDUCTORTPC_H +#define QUALITYCONTROL_REDUCTORTPC_H + +#include "TPC/SliceInfo.h" +#include +#include +#include "TAxis.h" + +namespace o2::quality_control_modules::tpc +{ +/// \brief An interface for storing data derived from QC objects into a TTree. +/// +/// A TPC-specific reductor class from which each reductor used for the trending +/// of the TPC-related quantities inherit. +/// + +class ReductorTPC +{ + public: + /// \brief Constructor. + ReductorTPC() = default; + /// \brief Destructor. + virtual ~ReductorTPC() = default; + + /// \brief Methods from the reductor class adapted for the needs of the TPC. + virtual void update(TObject* obj, std::vector& reducedSource, + std::vector>& axis, + int& finalNumberPads){}; + + /// \brief Methods from the reductor class adapted for the needs of the TPC QO. + virtual void updateQuality(const TObject* obj, SliceInfoQuality& reducedSource){}; + + /// \brief Function to return proper bin numbers to avoid double counting if slicing is used + void getBinSlices(TAxis* histAxis, const float sliceLow, const float sliceUp, int& binLow, int& binUp, float& sliceLabel) + { + binLow = histAxis->FindBin(sliceLow); + if (sliceLow > histAxis->GetBinCenter(binLow)) { + binLow += 1; + } // Lower slice boundary is above bin center. Start at next higher bin + binUp = histAxis->FindBin(sliceUp); + if (sliceUp <= histAxis->GetBinCenter(binUp)) { + binUp -= 1; + } // Upper slice boundary is smaller equal bin center. Stop at next lower bin + + sliceLabel = (sliceLow + sliceUp) / 2.; + } +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_REDUCTORTPC_H diff --git a/Modules/TPC/include/TPC/SACZeroScaleReductor.h b/Modules/TPC/include/TPC/SACZeroScaleReductor.h new file mode 100644 index 0000000000..d1c370ffc9 --- /dev/null +++ b/Modules/TPC/include/TPC/SACZeroScaleReductor.h @@ -0,0 +1,46 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// file SACZeroScaleReductor.h +// author Marcel Lesch +// +#ifndef QC_MODULE_TPC_SACZEROSCALEREDUCTOR_H +#define QC_MODULE_TPC_SACZEROSCALEREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief A Reductor of SAC 0 scale factors for TPC A and C side. + +class SACZeroScaleReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + SACZeroScaleReductor() = default; + ~SACZeroScaleReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + void update(TObject* obj) override; + + private: + struct { + Float_t ScaleFactorASide; + Float_t ScaleFactorCSide; + } mSACZero; + +}; // class SACZeroScaleReductor : public quality_control::postprocessing::Reductor + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_SACZEROSCALEREDUCTOR_H diff --git a/Modules/TPC/include/TPC/SACs.h b/Modules/TPC/include/TPC/SACs.h new file mode 100644 index 0000000000..815b53c1c8 --- /dev/null +++ b/Modules/TPC/include/TPC/SACs.h @@ -0,0 +1,94 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SACs.h +/// \author Thomas Klemenz, Marcel Lesch +/// + +#ifndef QUALITYCONTROL_SACS_H +#define QUALITYCONTROL_SACS_H + +// O2 includes +#include "CCDB/CcdbApi.h" +#include "TPCQC/SACs.h" + +// QC includes +#include "QualityControl/PostProcessingInterface.h" + +// ROOT includes +#include "TCanvas.h" + +#include + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Quality Control task for the IDC data of the TPC +/// \author Thomas Klemenz +class SACs : public quality_control::postprocessing::PostProcessingInterface +{ + public: + /// \brief Constructor + SACs() = default; + /// \brief Destructor + ~SACs() = default; + + /// \brief Configuration of a post-processing task. + /// Configuration of a post-processing task. Can be overridden if user wants to retrieve the configuration of the task. + /// \param config ConfigurationInterface with prefix set to "" + void configure(const boost::property_tree::ptree& config) override; + /// \brief Initialization of a post-processing task. + /// Initialization of a post-processing task. User receives a Trigger which caused the initialization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::SOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void initialize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Update of a post-processing task. + /// Update of a post-processing task. User receives a Trigger which caused the update and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::Period + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void update(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + /// \brief Finalization of a post-processing task. + /// Finalization of a post-processing task. User receives a Trigger which caused the finalization and a service + /// registry with singleton interfaces. + /// \param trigger Trigger which caused the initialization, for example Trigger::EOR + /// \param services Interface containing optional interfaces, for example DatabaseInterface + void finalize(quality_control::postprocessing::Trigger, framework::ServiceRegistryRef) override; + + private: + o2::tpc::qc::SACs mSACs; + o2::ccdb::CcdbApi mCdbApi; + std::string mHost; + bool mDoLatest = false; + std::unique_ptr mSACZeroSides; + std::unique_ptr mSACOneSides; + std::unique_ptr mSACDeltaSides; + std::unique_ptr mFourierCoeffsA; + std::unique_ptr mFourierCoeffsC; + std::unique_ptr mSACZeroSidesScaled; + std::unique_ptr mSACZeroScale; + std::unique_ptr mSACZeroOutliers; + + std::unordered_map mTimestamps; ///< timestamps to look for specific data in the CCDB + std::vector> mLookupMaps{}; ///< meta data to look for data in the CCDB + std::vector> mStoreMaps{}; ///< meta data to be stored with the output in the QCDB + std::unordered_map> mRanges; ///< histogram ranges configurable via config file + + bool mRejectOutliersSACZeroScale; + float mSACZeroMaxDeviation; + bool mDoSACFourierCoeffs; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_IDCS_H \ No newline at end of file diff --git a/Modules/TPC/include/TPC/SeparationPowerReductor.h b/Modules/TPC/include/TPC/SeparationPowerReductor.h new file mode 100644 index 0000000000..cc0f97d277 --- /dev/null +++ b/Modules/TPC/include/TPC/SeparationPowerReductor.h @@ -0,0 +1,51 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// \file SeparationPowerReductor.h +// \author Marcel Lesch +// +#ifndef QC_MODULE_TPC_SEPARATIONPOWERREDUCTOR_H +#define QC_MODULE_TPC_SEPARATIONPOWERREDUCTOR_H + +#include "QualityControl/ReductorTObject.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Reductor for TPC PID separation power values +/// +/// Reductor for TPC PID separation power values +class SeparationPowerReductor : public quality_control::postprocessing::ReductorTObject +{ + public: + SeparationPowerReductor() = default; + ~SeparationPowerReductor() = default; + + void* getBranchAddress() final; + const char* getBranchLeafList() final; + void update(TObject* obj) final; + + private: + struct { + float amplitudePi; + float meanPi; + float sigmaPi; + float amplitudeEl; + float meanEl; + float sigmaEl; + float separationPower; + float chiSquareOverNdf; + } mSeparationPower; +}; + +} // namespace o2::quality_control_modules::tpc +#endif // QC_MODULE_TPC_SEPARATIONPOWERREDUCTOR_H diff --git a/Modules/TPC/include/TPC/SliceInfo.h b/Modules/TPC/include/TPC/SliceInfo.h new file mode 100644 index 0000000000..660a07f8ec --- /dev/null +++ b/Modules/TPC/include/TPC/SliceInfo.h @@ -0,0 +1,113 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SliceInfo.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// + +#ifndef QUALITYCONTROL_SLICEINFO_H +#define QUALITYCONTROL_SLICEINFO_H + +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include + +namespace o2::quality_control_modules::tpc +{ +/// \brief Structure for the reductor quantities for a single pad of the TPC. +/// +/// Structure gathering all the reductor quantities related to the trending of +/// the 'pads' (ROCs, sectors, slices,...) of the TPC. The reductor receives a +/// vector of SliceInfo with one element per slice, and fills it accordingly to +/// the json configuration. +/// + +struct SliceInfo { + double entries = 0.; // Number of entries in the slice/canvas. + double meanX = 0.; // Standard mean for a given range in X. + double stddevX = 0.; // Standard deviation for the range in X. + double errMeanX = 0.; // Error on the mean along X. + double meanY = 0.; // Standard mean in Y. + double stddevY = 0.; // Standard deviation in Y. + double errMeanY = 0.; // Error on the mean along Y. + double sliceLabelX = 0.; // Stores numerical center of slice along X in case of slicing or pad number in case of canvas + double sliceLabelY = 0.; // Stores numerical center of slice along Y in case of slicing or pad number in case of canvas + std::string title = ""; + + /// \brief Check if the argument is a floating number or a string. + bool isStringFloating(std::string var) + { + std::istringstream iss(var); + float f; + iss >> std::noskipws >> f; + return iss.eof() && !iss.fail(); + } + + /// \brief Return the struct member/float corresponding to the argument. + double RetrieveValue(std::string varType) + { + if (isStringFloating(varType)) { + return std::stod(varType); + } else { + if (varType == "entries") { + return entries; + } else if (varType == "meanX") { + return meanX; + } else if (varType == "stddevX") { + return stddevX; + } else if (varType == "errMeanX") { + return errMeanX; + } else if (varType == "meanY") { + return meanY; + } else if (varType == "stddevY") { + return stddevY; + } else if (varType == "errMeanY") { + return errMeanY; + } else if (varType == "sliceLabelX") { + return sliceLabelX; + } else if (varType == "sliceLabelY") { + return sliceLabelY; + } else { + ILOG(Error, Support) << "TPC SliceInfo.h: 'varType' " << varType.data() + << " in 'RetrieveValue' unknown. Breaking." << ENDM; + exit(0); + } + } + } + + ClassDefNV(SliceInfo, 1); +}; + +struct SliceInfoQuality { + UInt_t qualitylevel = 0; + std::string title = ""; + + /// \brief Return the struct member/float corresponding to the argument. + double RetrieveValue(const std::string& varType) const + { + if (varType == "qualitylevel") { + return (double)qualitylevel; + } else { + ILOG(Error, Support) << "TPC SliceInfoQuality: 'varType' " << varType.data() + << " in 'RetrieveValue' unknown. Breaking." << ENDM; + exit(0); + } + } + + ClassDefNV(SliceInfoQuality, 1); +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_SLICEINFO_H diff --git a/Modules/TPC/include/TPC/TH1ReductorTPC.h b/Modules/TPC/include/TPC/TH1ReductorTPC.h new file mode 100644 index 0000000000..1d841e65fb --- /dev/null +++ b/Modules/TPC/include/TPC/TH1ReductorTPC.h @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1ReductorTPC.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TH1REDUCTORTPC_H +#define QUALITYCONTROL_TH1REDUCTORTPC_H + +#include "TPC/ReductorTPC.h" +#include "TH1.h" + +namespace o2::quality_control_modules::tpc +{ +/// \brief A reductor of TH1 histograms for the trending of TPC objects. +/// +/// A Reductor for the TH1 histograms used for the TPC quantities. It receives +/// a vector of 'SliceInfo' which size corresponds to the number of slices or +/// pads which need to be trended. +/// + +class TH1ReductorTPC : public quality_control_modules::tpc::ReductorTPC +{ + public: + /// \brief Constructor. + TH1ReductorTPC() = default; + /// \brief Destructor. + ~TH1ReductorTPC() = default; + + /// \brief Methods from the reductor class adapted for the needs of the TPC. + void update(TObject* obj, std::vector& reducedSource, + std::vector>& axis, int& finalNumberPads) final; + + private: + void GetTH1StatsY(TH1* hist, float stats[3], const int lowerBin, const int upperBin); +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_TH1REDUCTORTPC_H diff --git a/Modules/TPC/include/TPC/TH2ReductorTPC.h b/Modules/TPC/include/TPC/TH2ReductorTPC.h new file mode 100644 index 0000000000..142aae3a17 --- /dev/null +++ b/Modules/TPC/include/TPC/TH2ReductorTPC.h @@ -0,0 +1,48 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2ReductorTPC.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TH2REDUCTORTPC_H +#define QUALITYCONTROL_TH2REDUCTORTPC_H + +#include "TPC/ReductorTPC.h" + +namespace o2::quality_control_modules::tpc +{ +/// \brief A reductor of TH2 histograms for the trending of TPC objects. +/// +/// A Reductor for the TH2 histograms used for the TPC quantities. It receives +/// a vector of 'SliceInfo' which size corresponds to the number of slices or +/// pads which need to be trended. +/// + +class TH2ReductorTPC : public quality_control_modules::tpc::ReductorTPC +{ + public: + /// \brief Constructor. + TH2ReductorTPC() = default; + /// \brief Destructor. + ~TH2ReductorTPC() = default; + + /// \brief Methods from the reductor class adapted for the needs of the TPC. + void update(TObject* obj, std::vector& reducedSource, + std::vector>& axis, int& finalNumberPads) final; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_TH2REDUCTORTPC_H diff --git a/Modules/TPC/include/TPC/TPCAggregator.h b/Modules/TPC/include/TPC/TPCAggregator.h new file mode 100644 index 0000000000..1c2ae76fa2 --- /dev/null +++ b/Modules/TPC/include/TPC/TPCAggregator.h @@ -0,0 +1,46 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TPCAggregator.h +/// \author Marcel Lesch +/// + +#ifndef QUALITYCONTROL_TPCAGGREGATOR_H +#define QUALITYCONTROL_TPCAGGREGATOR_H + +// ROOT +#include +// QC +#include "QualityControl/AggregatorInterface.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Aggregator which selects the worst Quality and aggregates MetaData of TPC QOs +/// \author Marcel Lesch +class TPCAggregator : public o2::quality_control::checker::AggregatorInterface +{ + public: + // Override interface + void configure() override; + std::map + aggregate(o2::quality_control::core::QualityObjectsMapType& qoMap) override; + + ClassDefOverride(TPCAggregator, 1); + + private: + void insertQOName(std::string& MetaData, std::string& insertTitle); +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_TPCAGGREGATOR_H diff --git a/Modules/TPC/include/TPC/TimeGainCalibReductor.h b/Modules/TPC/include/TPC/TimeGainCalibReductor.h new file mode 100644 index 0000000000..6f71971630 --- /dev/null +++ b/Modules/TPC/include/TPC/TimeGainCalibReductor.h @@ -0,0 +1,54 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TimeGainCalibReductor.h +/// \author Marcel Lesch +/// + +#ifndef QUALITYCONTROL_TIMEGAINCALIBREDUCTOR_H +#define QUALITYCONTROL_TIMEGAINCALIBREDUCTOR_H + +#include "QualityControl/ReductorConditionAny.h" +#include + +namespace o2::quality_control_modules::tpc +{ + +/// \brief A Reductor for calibration objects of the TPC drift velocity +/// +/// A Reductor for calibration objects of the TPC time gain. +/// It produces a branch in the format: "meanEntries[2][5]/F:stddevEntries[2][5]:meanGain[2][5]:diffCorrectionTgl[2][5]" +/// Format Details: +/// [2][5] charge type (Max = 0, Tot = 1) per type (IROCgem = 0, OROC1gem = 1, OROC2gem = 2, OROC3gem = 3, All Stacks = 4) + +class TimeGainCalibReductor : public quality_control::postprocessing::ReductorConditionAny +{ + public: + TimeGainCalibReductor() = default; + ~TimeGainCalibReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + bool update(ConditionRetriever& retriever) override; + + private: + struct { + Float_t meanEntries[o2::tpc::CHARGETYPES][o2::tpc::GEMSTACKSPERSECTOR + 1]; + Float_t stddevEntries[o2::tpc::CHARGETYPES][o2::tpc::GEMSTACKSPERSECTOR + 1]; + Float_t meanGain[o2::tpc::CHARGETYPES][o2::tpc::GEMSTACKSPERSECTOR + 1]; + Float_t diffCorrectionTgl[o2::tpc::CHARGETYPES][o2::tpc::GEMSTACKSPERSECTOR + 1]; // diff of getCorrection() for tgl(0)-tgl(1) + } mStats; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_TIMEGAINCALIBREDUCTOR_H diff --git a/Modules/TPC/include/TPC/TrackClusters.h b/Modules/TPC/include/TPC/TrackClusters.h new file mode 100644 index 0000000000..8ef039407f --- /dev/null +++ b/Modules/TPC/include/TPC/TrackClusters.h @@ -0,0 +1,60 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrackClusters.h +/// \author Laura Serksnyte +/// + +#ifndef QC_MODULE_TPC_TRACKCLUSTERS_H +#define QC_MODULE_TPC_TRACKCLUSTERS_H + +// O2 includes +#include "TPCQC/TrackClusters.h" + +// QC includes +#include "QualityControl/TaskInterface.h" + +// ROOT includes +#include + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Quality Control task for the shared clusters and crossed rows distribution +class TrackClusters : public TaskInterface +{ + public: + /// \brief Constructor + TrackClusters(); + /// \brief Destructor + ~TrackClusters() = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + o2::tpc::qc::TrackClusters mQCTrackClusters{}; ///< TPC QC class from o2 + TRandom3* mRandomGenerator; + float mSamplingFraction; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_TRACKCLUSTERS_H diff --git a/Modules/TPC/include/TPC/Tracking.h b/Modules/TPC/include/TPC/Tracking.h new file mode 100644 index 0000000000..0a795be9f3 --- /dev/null +++ b/Modules/TPC/include/TPC/Tracking.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Tracking.h +/// \author David Rohr +/// + +#ifndef QC_MODULE_TPC_TRACKING_H +#define QC_MODULE_TPC_TRACKING_H + +// O2 includes +#include "TPCQC/Tracking.h" + +// QC includes +#include "QualityControl/TaskInterface.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief TPC Tracking QC Task +/// It is final because there is no reason to derive from it. Just remove it if needed. +/// \author Jens Wiechula +class Tracking final : public TaskInterface +{ + public: + /// \brief Constructor + Tracking(); + /// Destructor + ~Tracking() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + o2::tpc::qc::Tracking mQCTracking{}; + o2::tpc::qc::Tracking::outputModes mOutputMode; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_TRACKING_H diff --git a/Modules/TPC/include/TPC/Tracks.h b/Modules/TPC/include/TPC/Tracks.h new file mode 100644 index 0000000000..08d234c585 --- /dev/null +++ b/Modules/TPC/include/TPC/Tracks.h @@ -0,0 +1,58 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Tracks.h +/// \author Stefan Heckel, sheckel@cern.ch +/// + +#ifndef QC_MODULE_TPC_Tracks_H +#define QC_MODULE_TPC_Tracks_H + +// O2 includes +#include "TPCQC/Tracks.h" + +// QC includes +#include "QualityControl/TaskInterface.h" + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::tpc +{ + +/// \brief Quality Control DPL Task for QC Module TPC for track related observables +/// \author Stefan Heckel + +class Tracks final : public TaskInterface +{ + public: + /// \brief Constructor + Tracks() = default; + /// Destructor + ~Tracks() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + o2::tpc::qc::Tracks mQCTracks{}; ///< TPC QC class from o2 + bool usePVfromCCDB = false; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QC_MODULE_TPC_Tracks_H diff --git a/Modules/TPC/include/TPC/TrendingTaskConfigTPC.h b/Modules/TPC/include/TPC/TrendingTaskConfigTPC.h new file mode 100644 index 0000000000..b20f148f22 --- /dev/null +++ b/Modules/TPC/include/TPC/TrendingTaskConfigTPC.h @@ -0,0 +1,70 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskConfigTPC.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKCONFIGTPC_H +#define QUALITYCONTROL_TRENDINGTASKCONFIGTPC_H + +#include "QualityControl/PostProcessingConfig.h" + +namespace o2::quality_control_modules::tpc +{ +/// \brief TrendingTask configuration structure, tuned for the need of the TPC +/// trending. +/// +/// Configuration structure for the trending objects: the data sources to trend +/// and the plots to produce and publish on the QCG. +/// This configuration structure is specific to the TPC trending, as it allows +/// to input/output canvases, and slice TH objects along any axis. + +struct TrendingTaskConfigTPC : public quality_control::postprocessing::PostProcessingConfig { + /// \brief Constructors. + TrendingTaskConfigTPC() = default; + TrendingTaskConfigTPC(const std::string& name, const boost::property_tree::ptree& config); + /// \brief Destructor. + ~TrendingTaskConfigTPC() = default; + + struct Plot { + std::string name; + std::string title; + std::string varexp; + std::string selection; + std::string option; + std::string graphErrors; + std::string graphYRange; + std::string graphXRange; + std::string graphAxisLabel; + }; + + struct DataSource { + std::string type; + std::string path; + std::string name; + std::string reductorName; + std::vector> axisDivision; + std::string moduleName; + }; + + bool producePlotsOnUpdate; + bool resumeTrend; + std::vector plots; + std::vector dataSources; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_TRENDINGTASKCONFIGTPC_H diff --git a/Modules/TPC/include/TPC/TrendingTaskTPC.h b/Modules/TPC/include/TPC/TrendingTaskTPC.h new file mode 100644 index 0000000000..6849e798db --- /dev/null +++ b/Modules/TPC/include/TPC/TrendingTaskTPC.h @@ -0,0 +1,100 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskTPC.h +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKTPC_H +#define QUALITYCONTROL_TRENDINGTASKTPC_H + +#include "QualityControl/PostProcessingInterface.h" +#include "TPC/ReductorTPC.h" +#include "TPC/SliceInfo.h" +#include "TPC/TrendingTaskConfigTPC.h" + +#include +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} // namespace o2::quality_control::repository + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::tpc +{ +/// \brief A post-processing task tuned for the needs of the trending of the TPC. +/// +/// A post-processing task which trends TPC related objects inside QC database (QCDB). +/// It extracts some values of one or multiple objects using the Reductor classes, +/// then stores them inside a TTree. +/// This class is specific to the TPC: a subrange slicer is available in the json, +/// and input/output canvas can be dealt with alongside normal histograms. +/// + +class TrendingTaskTPC : public PostProcessingInterface +{ + public: + /// \brief Constructor. + TrendingTaskTPC() = default; + /// \brief Destructor. + ~TrendingTaskTPC() final = default; + + /// \brief Post-processing methods inherited from 'PostProcessingInterface'. + void configure(const boost::property_tree::ptree& config) final; + void initialize(Trigger, framework::ServiceRegistryRef) final; + void update(Trigger, framework::ServiceRegistryRef) final; + void finalize(Trigger, framework::ServiceRegistryRef) final; + + private: + struct { + Long64_t runNumber = 0; + static const char* getBranchLeafList() + { + return "runNumber/L"; + } + } mMetaData; + + /// \brief Methods specific to the trending itself. + void trendValues(const Trigger& t, o2::quality_control::repository::DatabaseInterface&); + void generatePlots(); + void drawCanvasMO(TCanvas* thisCanvas, const std::string& var, + const std::string& name, const std::string& opt, const std::string& err, const std::vector>& axis); + void drawCanvasQO(TCanvas* thisCanvas, const std::string& var, const std::string& name, const std::string& opt); + void getUserAxisRange(const std::string graphAxisRange, float& limitLow, float& limitUp); + void setUserAxisLabel(TAxis* xAxis, TAxis* yAxis, const std::string graphAxisLabel); + void getTrendVariables(const std::string& inputvar, std::string& sourceName, std::string& variableName, std::string& trend); + void getTrendErrors(const std::string& inputvar, std::string& errorX, std::string& errorY); + + template + void beautifyGraph(T& graph, const TrendingTaskConfigTPC::Plot& plotconfig, TCanvas* canv); // beautify function for TGraphs and TMultiGraphs + + TrendingTaskConfigTPC mConfig; + UInt_t mTime; + std::unique_ptr mTrend; + std::map mPlots; + std::unordered_map> mReductors; + std::unordered_map*> mSources; + std::unordered_map mSourcesQuality; + std::unordered_map mIsMoObject; + std::unordered_map mNumberPads; + std::unordered_map>> mAxisDivision; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_TRENDINGTASKTPC_H diff --git a/Modules/TPC/include/TPC/Utility.h b/Modules/TPC/include/TPC/Utility.h new file mode 100644 index 0000000000..0b13dfa47d --- /dev/null +++ b/Modules/TPC/include/TPC/Utility.h @@ -0,0 +1,121 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Utility.h +/// \author Thomas Klemenz +/// + +#ifndef QUALITYCONTROL_TPCUTILITY_H +#define QUALITYCONTROL_TPCUTILITY_H + +#include "QualityControl/ObjectsManager.h" +#include "QualityControl/CustomParameters.h" + +#if __has_include("TPCBase/CalDet.h") +#include "TPCBase/CalDet.h" +#else +#include "TPCBaseRecSim/CalDet.h" +#endif +#include "DataFormatsTPC/ClusterNative.h" +#include "DataFormatsTPC/WorkflowHelper.h" +#include "Framework/ProcessingContext.h" +#include "CCDB/CcdbApi.h" + +#include + +namespace o2::quality_control_modules::tpc +{ +/// \brief Get boolean configurable from json file +/// This function checks if a configurable is available in the json file and makes sure that different versions of it are accepted (true, TRUE, 1, etc) +/// \param config ConfigurationInterface with prefix set to "" +/// \param id Task id +/// \param property Property name to be looked for in json +bool getPropertyBool(const boost::property_tree::ptree& config, const std::string& id, const std::string property); + +/// \brief Prepare canvases to publish DalPad data +/// This function creates canvases for CalPad data and registers them to be published on the QCG. +/// \param objectsManager ObjectsManager of the underlying PostProcessingInterface +/// \param canVec Vector which holds TCanvas pointers persisting for the entire runtime of the task +/// \param canvNames Names of the canvases +/// \param metaData Optional std::map to set meta data for the publishing +void addAndPublish(std::shared_ptr objectsManager, std::vector>& canVec, std::vector canvNames, const std::map& metaData = std::map()); + +/// \brief Converts std::vector> to std::vector +/// \param input std::vector> to be converted to std::vector +/// \return std::vector +std::vector toVector(std::vector>& input); + +/// \brief Fills std::vector> with data from calDet +/// This is a convenience function to call o2::tpc::painter::makeSummaryCanvases in QC tasks to visualize the content of a CalDet object. +/// \param calDet Object to be displayed in the canvases +/// \param canvases Vector containing three std::unique_ptr, will be filled +/// \param params Information about the ranges of the histograms that will be drawn on the canvases. The params can be set via 'taskParameters' in the config file of corresponding the task. +/// \param paramName Name of the observable that is stored in calDet +void fillCanvases(const o2::tpc::CalDet& calDet, std::vector>& canvases, const quality_control::core::CustomParameters& params, const std::string paramName); + +/// \brief Clears all canvases +/// \param canvases Contains the canvases that will be cleared +void clearCanvases(std::vector>& canvases); + +/// \brief Converts CLUSTERNATIVE from InputRecord to getWorkflowTPCInput_ret +/// Convenience funtion to make native clusters accessible when receiving them from the DPL +/// \param input InputReconrd from the ProcessingContext +/// \return getWorkflowTPCInput_ret object for easy cluster access +std::unique_ptr clusterHandler(o2::framework::InputRecord& inputs, int verbosity = 0, unsigned long tpcSectorMask = 0xFFFFFFFFF); + +/// \brief Extracts the "Valid from" timestamp from metadata +void getTimestamp(const std::string& metaInfo, std::vector& timeStamps); + +/// \brief Gives a vector of timestamps for data to be processed +/// Gives a vector of time stamps of x files (x=nFiles) in path which are older than a given time stamp (limit) +/// \param url CCDB URL +/// \param path File path in the CCDB +/// \param nFiles Number of files that shall be processed +/// \param limit Most recent timestamp to be processed +std::vector getDataTimestamps(const o2::ccdb::CcdbApi& cdbApi, const std::string_view path, const unsigned int nFiles, const long limit); + +/// \brief Calculates mean and stddev from yValues of a TGraph. Overloaded function, actual calculation in retrieveStatistics +/// \param yValues const double* pointer to yValues of TGraph (via TGraph->GetY()) +/// \param yErrors const double* pointer to y uncertainties of TGraph (via TGraph->GetEY()) +/// \param useErrors bool whether uncertainties should be used in calculation of mean and stddev of mean +/// \param firstPoint const int, first point of yValues to include in calculation +/// \param lastPoint const int, last point of yValues to include in calculation +/// \param mean double&, reference to double that should store mean +/// \param stddevOfMean double&, reference to double that should store stddev of mean +void calculateStatistics(const double* yValues, const double* yErrors, bool useErrors, const int firstPoint, const int lastPoint, double& mean, double& stddevOfMean); + +/// \brief Calculates mean and stddev from yValues of a TGraph. Overloaded function +/// \param yValues const double* pointer to yValues of TGraph (via TGraph->GetY()) +/// \param yErrors const double* pointer to y uncertainties of TGraph (via TGraph->GetEY()) +/// \param useErrors bool whether uncertainties should be used in calculation of mean and stddev of mean +/// \param firstPoint const int, first point of yValues to include in calculation +/// \param lastPoint const int, last point of yValues to include in calculation +/// \param mean double&, reference to double that should store mean +/// \param stddevOfMean double&, reference to double that should store stddev of mean +/// \param maskPoints std::vector&, points of the selected TGraph-points that should be masked +void calculateStatistics(const double* yValues, const double* yErrors, bool useErrors, const int firstPoint, const int lastPoint, double& mean, double& stddevOfMean, std::vector& maskPoints); + +/// \brief Calculates mean and stddev from yValues of a TGraph. Overloaded function, actual calculation in retrieveStatistics +/// \param values std::vector& vector that contains the data points +/// \param errors std::vector& vector that contains the data errors +/// \param useErrors bool whether uncertainties should be used in calculation of mean and stddev of mean +/// \param mean double&, reference to double that should store mean +/// \param stddevOfMean double&, reference to double that should store stddev of mean +void retrieveStatistics(std::vector& values, std::vector& errors, bool useErrors, double& mean, double& stddevOfMean); + +/// \brief Calculates mean and stddev from a vector +/// \param values std::vector& vector that contains the data points +/// \param mean float&, reference to float that should store mean +/// \param stddev float&, reference to float that should store stddev of mean +void calcMeanAndStddev(const std::vector& values, float& mean, float& stddev); +} // namespace o2::quality_control_modules::tpc +#endif // QUALITYCONTROL_TPCUTILITY_H diff --git a/Modules/TPC/include/TPC/VDriftCalibReductor.h b/Modules/TPC/include/TPC/VDriftCalibReductor.h new file mode 100644 index 0000000000..823fccd946 --- /dev/null +++ b/Modules/TPC/include/TPC/VDriftCalibReductor.h @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file VDriftCalibReductor.h +/// \author Marcel Lesch +/// + +#ifndef QUALITYCONTROL_VDRIFTCALIBREDUCTOR_H +#define QUALITYCONTROL_VDRIFTCALIBREDUCTOR_H + +#include "QualityControl/ReductorConditionAny.h" + +namespace o2::quality_control_modules::tpc +{ + +/// \brief A Reductor for calibration objects of the TPC drift velocity +/// +/// A Reductor for calibration objects of the TPC drift velocity. +/// It produces a branch in the format: "vdrift/F:vdrifterror" + +class VDriftCalibReductor : public quality_control::postprocessing::ReductorConditionAny +{ + public: + VDriftCalibReductor() = default; + ~VDriftCalibReductor() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + bool update(ConditionRetriever& retriever) override; + + private: + struct { + Float_t vdrift; + Float_t vdrifterror; + } mStats; +}; + +} // namespace o2::quality_control_modules::tpc + +#endif // QUALITYCONTROL_VDRIFTCALIBREDUCTOR_H diff --git a/Modules/TPC/run/runTPCQCTrackReader.cxx b/Modules/TPC/run/runTPCQCTrackReader.cxx new file mode 100644 index 0000000000..1bd4f8c7c3 --- /dev/null +++ b/Modules/TPC/run/runTPCQCTrackReader.cxx @@ -0,0 +1,111 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file runTPCQCPID.cxx +/// \author Jens Wiechula +/// +/// \brief run TPC PID QC task, reading tracks from file for the moment +/// + +// IMPORTANT: +// for some reason, if the 'customize' functions are below the other includes, +// the options are not added to the workflow. +// So the structure should be kept as it is +#include +#include "QualityControl/InfrastructureGenerator.h" + +using namespace o2; +using namespace o2::framework; +using namespace o2::utilities; + +void customize(std::vector& policies) +{ + DataSampling::CustomizeInfrastructure(policies); + quality_control::customizeInfrastructure(policies); +} + +void customize(std::vector& policies) +{ + DataSampling::CustomizeInfrastructure(policies); +} + +void customize(std::vector& workflowOptions) +{ + workflowOptions.push_back(ConfigParamSpec{ "input-file", VariantType::String, "tpctracks.root", { "Input file name for TPC tracks" } }); + workflowOptions.push_back(ConfigParamSpec{ "tree-name", VariantType::String, "tpcrec", { "Name of the tree containing the TPC tracks vector" } }); + workflowOptions.push_back(ConfigParamSpec{ "branch-name", VariantType::String, "TPCTracks", { "Name of the branch of the TPC tracks vector" } }); +} + +// c++ includes +#include +#include + +// ROOT includes +#include + +// o2 included +#include "Framework/runDataProcessing.h" +#include "DPLUtils/RootTreeReader.h" + +// qc includes +#include "QualityControl/CheckRunner.h" +#include "QualityControl/runnerUtils.h" + +WorkflowSpec defineDataProcessing(const ConfigContext& config) +{ + WorkflowSpec specs; + + // ===| workflow options |==================================================== + // + auto inputFile = config.options().get("input-file"); + auto treeName = config.options().get("tree-name"); + auto branchName = config.options().get("branch-name"); + + // ===| tree reader |========================================================= + // + // The tree reader will read tpc tracks from file crated via the o2 sim/rec + // workflow + DataProcessorSpec producer{ + "tpc-track-reader", + Inputs{}, + Outputs{ + { "TPC", "TRACKS", 0, Lifetime::Timeframe } }, + AlgorithmSpec{ + (AlgorithmSpec::InitCallback)[inputFile, treeName, branchName](InitContext&){ + + // root tree reader + auto reader = std::make_shared(treeName.data(), // tree name + inputFile.data(), // input file name + RootTreeReader::PublishingMode::Loop, // loop over + Output{ "TPC", "TRACKS", 0 }, + branchName.data() // name of the branch + ); + + return (AlgorithmSpec::ProcessCallback)[reader](ProcessingContext & processingContext) mutable + { + //(++(*reader))(processingContext); + if (reader->next()) { + (*reader)(processingContext); + // LOG(info) << "Call producer AlgorithmSpec::ProcessCallback: has data " << reader->getCount(); + } else { + // LOG(info) << "Call producer AlgorithmSpec::ProcessCallback: no next data" << reader->getCount(); + } + }; +} +} +} +; + +specs.push_back(producer); + +return specs; +} diff --git a/Modules/TPC/run/tpcDCSPTempTrending.json b/Modules/TPC/run/tpcDCSPTempTrending.json new file mode 100644 index 0000000000..943d8dc8c8 --- /dev/null +++ b/Modules/TPC/run/tpcDCSPTempTrending.json @@ -0,0 +1,116 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "", + "type": "", + "start": "", + "end": "" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "postprocessing": { + "periodSeconds": "10" + } + }, + "postprocessing": { + "TemperatureQC": { + "active": "true", + "resumeTrend": "false", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "producePlotsOnUpdate": "true", + "dataSources": [ + { + "type": "condition", + "path": "TPC/Calib/", + "names": [ "Temperature" ], + "reductorName": "o2::quality_control_modules::tpc::DCSPTempReductor", + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "Temp_Mean_ASide", + "title": "Mean Temperature A Side", + "varexp": "Temperature.tempMeanPerSide[0]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean temp A Side:time", + "graphErrors": "Temperature.tempMeanPerSideErr[0]:0" + }, + { + "name": "Temp_GradX_ASide", + "title": "GradX Temperature A Side", + "varexp": "Temperature.tempGradXPerSide[0]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "gradX temp A Side:time", + "graphErrors": "Temperature.tempGradXPerSideErr[0]:0" + }, + { + "name": "Temp_GradY_ASide", + "title": "GradY Temperature A Side", + "varexp": "Temperature.tempGradYPerSide[0]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "gradY temp A Side:time", + "graphErrors": "Temperature.tempGradYPerSideErr[0]:0" + }, + { + "name": "Temp_Mean_CSide", + "title": "Mean Temperature C Side", + "varexp": "Temperature.tempMeanPerSide[1]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean temp C Side:time", + "graphErrors": "Temperature.tempMeanPerSideErr[1]:0" + }, + { + "name": "Temp_GradX_CSide", + "title": "GradX Temperature C Side", + "varexp": "Temperature.tempGradXPerSide[1]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "gradX temp C Side:time", + "graphErrors": "Temperature.tempGradXPerSideErr[1]:0" + }, + { + "name": "Temp_GradY_CSide", + "title": "GradY Temperature C Side", + "varexp": "Temperature.tempGradYPerSide[1]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "gradY temp C Side:time", + "graphErrors": "Temperature.tempGradYPerSideErr[1]:0" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "foreachlatest:ccdb:TPC/Calib/Temperature/" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } + } + \ No newline at end of file diff --git a/Modules/TPC/run/tpcQCAtmosPressureTrending.json b/Modules/TPC/run/tpcQCAtmosPressureTrending.json new file mode 100644 index 0000000000..e71d48c091 --- /dev/null +++ b/Modules/TPC/run/tpcQCAtmosPressureTrending.json @@ -0,0 +1,80 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "", + "type": "", + "start": "", + "end": "" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "postprocessing": { + "periodSeconds": "10" + } + }, + "postprocessing": { + "AtmosPressure": { + "active": "true", + "resumeTrend": "false", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "producePlotsOnUpdate": "true", + "dataSources": [ + { + "type": "condition", + "path": "GLO/Config/", + "names": [ "EnvVars" ], + "reductorName": "o2::quality_control_modules::tpc::AtmosPressureReductor", + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "atmosPressure1_Trending", + "title": "Trend of atmospheric cavern pressure 1 over time", + "varexp": "EnvVars.cavernPressure1:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "atmospheric Cavern Pressure:time", + "graphErrors": "EnvVars.errCavernPressure1:0" + }, + { + "name": "atmosPressure2_Trending", + "title": "Trend of atmospheric cavern pressure 2 over time", + "varexp": "EnvVars.cavernPressure2:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "atmospheric Cavern Pressure:time", + "graphErrors": "EnvVars.errCavernPressure2:0" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "foreachlatest:ccdb:GLO/Config/EnvVars/" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } + } + \ No newline at end of file diff --git a/Modules/TPC/run/tpcQCCalDetPublisher.json b/Modules/TPC/run/tpcQCCalDetPublisher.json new file mode 100644 index 0000000000..9c0febd181 --- /dev/null +++ b/Modules/TPC/run/tpcQCCalDetPublisher.json @@ -0,0 +1,103 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "PadCalibration": { + "active": "true", + "className": "o2::quality_control_modules::tpc::CalDetPublisher", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSourceURL": "ccdb-test.cern.ch:8080", + "outputCalPadMaps_comment" : [ "CalDet objects that are stored in std::unordered_map> need to go here.", + "This needs to be the last part of the CCDB path, e.g. Pulser or CE." ], + "outputCalPadMaps": [ + "CE", + "PedestalNoise" + ], + "outputCalPads_comment" : [ "CalDet objects that are stored as plain o2::tpc::CalDet objects need to go here.", + "This needs to be the last part of the CCDB path, e.g. Pedestal or Noise." ], + "outputCalPads": [ + ], + "timestamps_comment": [ "Put the timestamp of the corresponding file you want to look for in the timestamps array.", + "You can either put a timestamp for every object or leave the array empty to take the latest file from the CCDB.", + "An empty array to get the the latest version will be the main use case.", + "The array is mapped to the output objects sequentially", + "If you want to pick the latest file in the CCDB manually, you can use -1." + ], + "timestamps": [ + ], + "lookupMetaData_comment": [ "With this array you can filter your search via meta data.", + "The array is mapped sequentially to the output objects.", + "If you leave only one entry in the array this is used for all objects in outputCalPadMaps and outputCalPads.", + "If you want no meta data simply remove 'keys' and 'values' completely and leave only {}", + "Every entry above (outputCalPads.size() + outputCalPadMaps.size()) is ignored.", + "The keys and values that are set by default are only there to serve as an example." + ], + "lookupMetaData": [ + { + } + ], + "storeMetaData_comment": "For how-to, see 'lookupMetaData_comment'.", + "storeMetaData": [ + { + }, + { + "keys": [ "key1", "key2" ], + "values": [ "value1", "value2" ] + }, + { + "keys": [ "key" ], + "values": [ "value" ] + }, + { + } + ], + "histogramRanges_comment" : [ "nBins", "min", "max" ], + "histogramRanges": [ + { "Pedestals" : [ "240", "0", "120" ] }, + { "Noise" : [ "200", "0", "2" ] }, + { "Qtot" : [ "600", "0", "300" ] }, + { "T0" : [ "100", "239", "240" ] }, + { "Width" : [ "100", "0", "1" ] } + ], + "checkZSCalibration": { + "check": "false", + "initRefCalibTimestamp": "-1" + }, + "initTrigger": [ + "once" + ], + "updateTrigger_comment": "To trigger on a specific file being updated, use e.g. 'newobject:ccdb:TPC/Calib/Noise'", + "updateTrigger": [ + "once" + ], + "stopTrigger_comment": [ "To keep the task running until it is stopped manually set the trigger on the update of a non-existing object, e.g. 'newobject:ccdb:TPC/ThisDoesNotExist'", + "There will be a end of run trigger implemented so the above workaround can be abandoned later." ], + "stopTrigger": [ + "once" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCCheckSlices.json b/Modules/TPC/run/tpcQCCheckSlices.json new file mode 100644 index 0000000000..f5cefaea65 --- /dev/null +++ b/Modules/TPC/run/tpcQCCheckSlices.json @@ -0,0 +1,270 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "", + "type": "" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "checks" : { + "CheckOfSlices" : { + "active" : "true", + "className" : "o2::quality_control_modules::tpc::CheckOfSlices", + "moduleName" : "QcTPC", + "policy" : "OnEachSeparately", + "detectorName" : "TPC", + "dataSource" : [ { + "type" : "PostProcessing", + "name" : "TestTPCTH1Slicer", + "MOs" : ["hPhiAsideRatio_StatMeanY_Trend_Slices"] + } ], + + "stopTrigger_comment": [ "Parameters are needed to choose if check should be performed on the mean (Mean) of all points or as a comparison to an expected physics value (ExpectedPhysicsValue). Or both (Both)." ], + "checkParameters": { + "CheckChoice" : "Mean, ExpectedValue, Zero", + "expectedPhysicsValue" : "1.0", + "allowedNSigmaForExpectation" : "3", + "badNSigmaForExpectation" : "6", + "allowedNSigmaForMean" : "3", + "badNSigmaForMean" : "6", + "allowedRange" : "0.2", + "badRange" : "0.3", + "MetadataComment" : "TestComment2" + } + } + }, + "postprocessing": { + "TestTPCTH1Slicer": { + "active": "true", + "className": "o2::quality_control_modules::tpc::TrendingTaskTPC", + "moduleName": "QcTPC", + "detectorName": "TPC", + "producePlotsOnUpdate": "true", + "resumeTrend": "false", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "hEtaRatio" ], + "reductorName": "o2::quality_control_modules::tpc::TH1ReductorTPC", + "axisDivision": [ [ "-2.0", "-1.0", "0.0", "1.0", "2.0" ] ], + "moduleName": "QcTPC" + }, + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "hEta" ], + "reductorName": "o2::quality_control_modules::tpc::TH1ReductorTPC", + "axisDivision": [[ "-1.0", "0.0", "1.0" ]], + "moduleName": "QcTPC" + }, + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "hPhiAside", "hPhiAsideRatio", "hPhiCside", "hPhiCsideRatio" ], + "reductorName": "o2::quality_control_modules::tpc::TH1ReductorTPC", + "axisDivision": [[ "0.0", "0.349066", "0.698132", "1.0472", "1.39626", "1.74533", "2.0944", "2.44346", "2.79253", "3.14159", "3.49066", "3.83972", "4.18879", "4.53786", "4.88692", "5.23599", "5.58505", "5.93412", "6.28319" ]], + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "hEtaRatio_StatMean_Trend", + "title": "Mean of the #eta ratio", + "varexp": "hEtaRatio.meanX:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanX:0.5", + "graphYRange": "-2.0:2.0", + "graphXRange": "", + "graphAxisLabel": "Mean #eta:time" + }, + { + "name": "hEtaRatio_StatMeanY_Trend", + "title": "Mean Y of the #eta ratio", + "varexp": "hEtaRatio.meanY:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanY:0.5", + "graphYRange": "-0.1:2.0", + "graphXRange": "", + "graphAxisLabel": "Mean #eta ratio:time" + }, + { + "name": "hEtaRatio_StatMeanY_Trend_Slices", + "title": "Mean Y of the #eta ratio vs slices", + "varexp": "hEtaRatio.meanY:slices", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanY:0", + "graphYRange": "-0.1:2.0", + "graphXRange": "-2.0:2.0", + "graphAxisLabel": "Mean #eta:#eta" + }, + { + "name": "hEta_StatMean_Trend", + "title": "Mean of #eta", + "varexp": "hEta.meanX:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanX:0.5", + "graphYRange": "-1.0:1.0", + "graphXRange": "", + "graphAxisLabel": "Mean #eta:time" + }, + { + "name": "hPhiAsideRatio_StatMeanY_Trend", + "title": "Mean of #phi ratio - A side", + "varexp": "hPhiAsideRatio.meanY:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanY:0.0", + "graphYRange": "0.0:1.5", + "graphXRange": "", + "graphAxisLabel": "Mean #phi ratio A side:time" + }, + { + "name": "hPhiAsideRatio_StatMeanY_Trend_Slices", + "title": "Mean of #phi ratio - A side", + "varexp": "hPhiAsideRatio.meanY:slices", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:0.0", + "graphYRange": "-5.0:5.0", + "graphXRange": "-0.5:6.5", + "graphAxisLabel": "Mean #phi ratio A side:#phi" + }, + { + "name": "hPhiCsideRatio_StatMeanY_Trend", + "title": "Mean of #phi ratio - C side", + "varexp": "hPhiCsideRatio.meanY:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:0.", + "graphYRange": "0.0:1.5", + "graphXRange": "", + "graphAxisLabel": "Mean #phi ratio C side:time" + }, + { + "name": "hPhiCsideRatio_StatMeanY_Trend_Slices", + "title": "Mean of #phi ratio - C side", + "varexp": "hPhiCsideRatio.meanY:slices", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:0.0", + "graphYRange": "0.0:1.5", + "graphXRange": "-0.5:6.5", + "graphAxisLabel": "Mean #phi ratio C side:#phi" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "20 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "TestTPCTH2Slicer": { + "active": "false", + "className": "o2::quality_control_modules::tpc::TrendingTaskTPC", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "h2DEtaPhi" ], + "reductorName": "o2::quality_control_modules::tpc::TH2ReductorTPC", + "axisDivision": [ [ "0.0", "0.349066", "0.698132", "1.0472", "1.39626", "1.74533", "2.0944", "2.44346", "2.79253", "3.14159", "3.49066", "3.83972", "4.18879", "4.53786", "4.88692", "5.23599", "5.58505", "5.93412", "6.28319" ] , [ "-1.0", "0.0", "1.0" ] ], + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "h2DEtaPhi_StatMeanX_Trend", + "title": "Mean of #phi for #eta-#phi slices", + "varexp": "h2DEtaPhi.meanX:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanX:0.5", + "graphYRange": "-2.0:8.0", + "graphXRange": "", + "graphAxisLabel": "Mean #phi:time" + }, + { + "name": "h2DEtaPhi_StatMeanY_Trend", + "title": "Mean of #eta for #eta-#phi slices", + "varexp": "h2DEtaPhi.meanY:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanY:0.5", + "graphYRange": "-2.0:2.0", + "graphXRange": "", + "graphAxisLabel": "Mean #eta:time" + }, + { + "name": "h2DEtaPhi_StatMeanY_Trend_Slices", + "title": "Mean of the #eta vs slices in #eta and #phi", + "varexp": "h2DEtaPhi.meanY:slices2D", + "selection": "", + "option": "colz text", + "graphErrors": "errMeanY:0.5", + "graphYRange": "-1.0:1.0", + "graphXRange": "", + "graphAxisLabel": "#eta:#phi" + }, + { + "name": "h2DEtaPhi_StatMeanX_Trend_Slices", + "title": "Mean of the #phi vs slices in #eta and #phi", + "varexp": "h2DEtaPhi.meanX:slices2D", + "selection": "", + "option": "colz text", + "graphErrors": "errMeanX:0.5", + "graphYRange": "0.0:6.5", + "graphXRange": "", + "graphAxisLabel": "#eta:#phi" + }, + { + "name": "h2DEtaPhi_NumberEntries_Trend_Slices", + "title": "Number of entries per slices in #eta and #phi", + "varexp": "h2DEtaPhi.entries:slices2D", + "selection": "", + "option": "colz text", + "graphErrors": "", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "#eta:#phi" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "20 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCCheckTrending.json b/Modules/TPC/run/tpcQCCheckTrending.json new file mode 100644 index 0000000000..6ec5a70b0b --- /dev/null +++ b/Modules/TPC/run/tpcQCCheckTrending.json @@ -0,0 +1,536 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "aggregators": { + "TPCTestAggregator1": { + "active": "true", + "className": "o2::quality_control_modules::tpc::TPCAggregator", + "moduleName": "QcTPC", + "policy": "OnAny", + "detectorName": "TPC", + "dataSource": [{ + "type": "Check", + "name": "CheckOfTrack_Trending", + "QOs" : ["Tracks_Trending/hEta_StatMean_Trend", "Tracks_Trending/hNClustersAfterCuts_StatMean_Trend" ] + }] + }, + "TPCTestAggregator2": { + "active": "true", + "className": "o2::quality_control_modules::tpc::TPCAggregator", + "moduleName": "QcTPC", + "policy": "OnAny", + "detectorName": "TPC", + "dataSource": [{ + "type": "Aggregator", + "name": "TPCTestAggregator1", + "QOs" : ["TPCTestAggregator1" ] + }, + { + "type": "Check", + "name": "CheckOfTrack_Trending", + "QOs" : ["Tracks_Trending/hNClustersBeforeCuts_StatMean_Trend" ] + }] + } + }, + "checks" : { + "CheckOfPID_Trending" : { + "active" : "false", + "className" : "o2::quality_control_modules::tpc::CheckOfTrendings", + "moduleName" : "QcTPC", + "policy" : "OnEachSeparately", + "detectorName" : "TPC", + "dataSource" : [ { + "type" : "PostProcessing", + "name" : "PID_Trending", + "MOs" : [ "hNClusters_StatMean_Trend", "hdEdxTot_StatMean_Trend", "hdEdxMax_StatMean_Trend", "hTgl_StatMean_Trend", "hSnp_StatMean_Trend", "hdEdxEles_StatMean_Trend", "hdEdxEles_StatStddev_Trend", "hdEdxMips_StatMean_Trend", "hdEdxMips_StatStddev_Trend", "hdEdxElesMips_Separ_Trend", "hdEdxElesMips_SeparPower_Trend", "hdEdxVsncls_StatMean_Trend_dEdx", "hdEdxVsp_StatMean_Trend_p", "hdEdxVsPhiMipsAside_StatMean_Trend_dEdx", "hdEdxVsPhiMipsCside_StatMean_Trend_dEdx", "hdEdxVsPhiElesAside_StatMean_Trend_dEdx", "hdEdxVsPhiElesCside_StatMean_Trend_dEdx" ] + } ], + + "stopTrigger_comment": [ "Parameters are needed to choose if check should be performed only as last point comparison to average or average comparison to expected value." ], + "checkParameters": { + "CheckChoice" : "Mean", + "SliceTrending" : "false", + "expectedPhysicsValue" : "65", + "allowedNSigmaForExpectation" : "3", + "badNSigmaForExpectation" : "6", + "allowedNSigmaForMean" : "3", + "badNSigmaForMean" : "6", + "pointsToTakeForExpectedValueCheck" : "10", + "pointsToTakeForMeanCheck" : "10", + "allowedRange" : "0.2", + "badRange" : "2", + "pointsToTakeForRangeCheck" : "10", + "MetadataComment" : "TestComment2", + "pointsToTakeForZeroCheck" : "8" + } + }, + "CheckOfTrack_Trending" : { + "active" : "true", + "className" : "o2::quality_control_modules::tpc::CheckOfTrendings", + "moduleName" : "QcTPC", + "policy" : "OnEachSeparately", + "detectorName" : "TPC", + "dataSource" : [ { + "type" : "PostProcessing", + "name" : "Tracks_Trending", + "MOs" : [ "hEta_StatMean_Trend", "hNClustersAfterCuts_StatMean_Trend", "hNClustersBeforeCuts_StatMean_Trend" ] + } ], + + "stopTrigger_comment": [ "Parameters are needed to choose if check should be performed only as last point comparison to average or average comparison to expected value." ], + "checkParameters": { + "CheckChoice" : "Range", + "SliceTrending" : "false", + "expectedPhysicsValue" : "0.1", + "EPV_comment" : "This value is used for the Expected Physics Value and the Range Check", + "allowedNSigmaForExpectation" : "3", + "badNSigmaForExpectation" : "6", + "allowedNSigmaForMean" : "1", + "badNSigmaForMean" : "2", + "pointsToTakeForExpectedValueCheck" : "10", + "pointsToTakeForMeanCheck" : "10", + "allowedRange" : "0.05", + "badRange" : "0.1", + "pointsToTakeForRangeCheck" : "1", + "MetadataComment" : "TestComment", + "pointsToTakeForZeroCheck" : "4" + + } + }, + "CheckOfTrack_Trending_Slice" : { + "active" : "true", + "className" : "o2::quality_control_modules::tpc::CheckOfTrendings", + "moduleName" : "QcTPC", + "policy" : "OnEachSeparately", + "detectorName" : "TPC", + "dataSource" : [ { + "type" : "PostProcessing", + "name" : "Tracks_Trending_Slice", + "MOs" : [ "hEta_StatMean_Trend" ] + } ], + + "stopTrigger_comment": [ "Parameters are needed to choose if check should be performed only as last point comparison to average or average comparison to expected value." ], + "checkParameters": { + "CheckChoice" : "Zero, Mean", + "SliceTrending" : "true", + "expectedPhysicsValue" : "0.1", + "EPV_comment" : "This value is used for the Expected Physics Value and the Range Check", + "allowedNSigmaForExpectation" : "3", + "badNSigmaForExpectation" : "6", + "allowedNSigmaForMean" : "1", + "badNSigmaForMean" : "2", + "pointsToTakeForExpectedValueCheck" : "10", + "pointsToTakeForMeanCheck" : "10", + "allowedRange" : "0.05", + "badRange" : "0.1", + "pointsToTakeForRangeCheck" : "1", + "MetadataComment" : "TestComment", + "pointsToTakeForZeroCheck" : "4" + + } + } + }, + "postprocessing": { + "TestQualityCheckerTrending": { + "active": "true", + "className": "o2::quality_control_modules::tpc::QualityObserver", + "moduleName": "QcTPC", + "detectorName": "TPC", + "qualityObserverName": "TestQualityCheckerTrending", + "observeDetails": "true", + "qualityDetailChoice": "Null, Good, Medium, Bad, Comment", + "qualityObserverConfig": [ + { + "groupTitle": "Tracks Trending", + "path": "TPC/QO/", + "inputObjects": [ "CheckOfTrack_Trending/Tracks_Trending/hEta_StatMean_Trend", "CheckOfTrack_Trending_Slice/Tracks_Trending_Slice/hEta_StatMean_Trend" ], + "inputObjectTitles": [ "Eta, Mean Trend, Framework Trend", "Eta, Mean Trend, Slice Trend" ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "TestTPCAggregatorObserver": { + "active": "true", + "className": "o2::quality_control_modules::tpc::QualityObserver", + "moduleName": "QcTPC", + "detectorName": "TPC", + "qualityObserverName": "TestQualityCheckerTrending", + "observeDetails": "true", + "qualityDetailChoice": "Null, Good, Medium, Bad", + "lineLength": "70", + "qualityObserverConfig": [ + { + "groupTitle": "Tracks Trending", + "path": "TPC/QO/", + "inputObjects": [ "CheckOfTrack_Trending/Tracks_Trending/hEta_StatMean_Trend", "CheckOfTrack_Trending/Tracks_Trending/hNClustersAfterCuts_StatMean_Trend", "CheckOfTrack_Trending/Tracks_Trending/hNClustersBeforeCuts_StatMean_Trend", "TPCTestAggregator1/TPCTestAggregator1", "TPCTestAggregator2/TPCTestAggregator2" ], + "inputObjectTitles": [ "Eta", "NClusters after", "NClusters before", "Aggregated 1", "Aggregated 2" ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "PID_Trending": { + "active": "false", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/PID", + "names": [ "hNClusters", "hdEdxTot", "hdEdxMax", "hTgl", "hSnp", "hdEdxEles", "hdEdxMips" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + }, + { + "type": "repository", + "path": "TPC/MO/PID", + "names": [ "hdEdxVsncls", "hdEdxVsp", "hdEdxVsPhiMipsAside", "hdEdxVsPhiMipsCside", "hdEdxVsPhiElesAside", "hdEdxVsPhiElesCside" ], + "reductorName": "o2::quality_control_modules::common::TH2Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "hNClusters_StatMean_Trend", + "title": "Mean trend of the number of TPC clusters", + "varexp": "hNClusters.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hNClusters.stddev/(sqrt(hNClusters.entries)))" + }, + { + "name": "hdEdxTot_StatMean_Trend", + "title": "Mean trend of dEdxTot", + "varexp": "hdEdxTot.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hdEdxTot.stddev/(sqrt(hdEdxTot.entries)))" + }, + { + "name": "hdEdxMax_StatMean_Trend", + "title": "Mean trend of dEdxMax", + "varexp": "hdEdxMax.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hdEdxMax.stddev/(sqrt(hdEdxMax.entries)))" + }, + { + "name": "hTgl_StatMean_Trend", + "title": "Mean trend of tan(#lambda)", + "varexp": "hTgl.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hTgl.stddev/(sqrt(hTgl.entries)))" + }, + { + "name": "hSnp_StatMean_Trend", + "title": "Mean trend of sin(p)", + "varexp": "hSnp.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hSnp.stddev/(sqrt(hSnp.entries)))" + }, + { + "name": "hdEdxEles_StatMean_Trend", + "title": "Mean trend of dEdx for electrons", + "varexp": "hdEdxEles.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hdEdxEles.stddev/(sqrt(hdEdxEles.entries)))" + }, + { + "name": "hdEdxEles_StatStddev_Trend", + "title": "Stddev trend of dEdx for electrons", + "varexp": "hdEdxEles.stddev:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxMips_StatMean_Trend", + "title": "Mean trend of dEdx for MIPS", + "varexp": "hdEdxMips.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hdEdxMips.stddev/(sqrt(hdEdxMips.entries)))" + }, + { + "name": "hdEdxMips_StatStddev_Trend", + "title": "Stddev trend of dEdx for MIPS", + "varexp": "hdEdxMips.stddev:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxElesMips_Separ_Trend", + "title": "Separation trend of dEdx between electrons and MIPS", + "varexp": "(hdEdxEles.mean-hdEdxMips.mean):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxElesMips_SeparPower_Trend", + "title": "Separation power trend of dEdx between electrons and MIPS", + "varexp": "((hdEdxEles.mean-hdEdxMips.mean)/(0.5*(hdEdxEles.stddev+hdEdxMips.stddev))):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsncls_StatMean_Trend_dEdx", + "title": "Trend mean dEdx from dEdx vs Number of Clusters", + "varexp": "(hdEdxVsncls.sumwy/hdEdxVsncls.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsp_StatMean_Trend_p", + "title": "Trend mean p from dEdx vs Momentum", + "varexp": "(hdEdxVsp.sumwx/hdEdxVsp.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsPhiMipsAside_StatMean_Trend_dEdx", + "title": "Trend mean dEdx from hdEdxVsPhiMipsAside", + "varexp": "(hdEdxVsPhiMipsAside.sumwy/hdEdxVsPhiMipsAside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsPhiMipsCside_StatMean_Trend_dEdx", + "title": "Trend mean dEdx from hdEdxVsPhiMipsCside", + "varexp": "(hdEdxVsPhiMipsCside.sumwy/hdEdxVsPhiMipsCside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsPhiElesAside_StatMean_Trend_dEdx", + "title": "Trend mean dEdx from hdEdxVsPhiElesAside", + "varexp": "(hdEdxVsPhiElesAside.sumwy/hdEdxVsPhiElesAside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsPhiElesCside_StatMean_Trend_dEdx", + "title": "Trend mean dEdx from hdEdxVsPhiElesCside", + "varexp": "(hdEdxVsPhiElesCside.sumwy/hdEdxVsPhiElesCside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "30 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "Tracks_Trending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "hNClustersBeforeCuts", "hNClustersAfterCuts", "hEta", "hPt", "hSign", "hPtPos", "hPtNeg" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + }, + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "h2DNClustersPhiAside", "h2DNClustersPhiCside" ], + "reductorName": "o2::quality_control_modules::common::TH2Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "hNClustersBeforeCuts_StatMean_Trend", + "title": "Mean trend of the number of TPC clusters (Before the cuts)", + "varexp": "hNClustersBeforeCuts.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hNClustersBeforeCuts.stddev)/(sqrt(hNClustersBeforeCuts.entries))" + }, + { + "name": "hNClustersAfterCuts_StatMean_Trend", + "title": "Mean trend of the number of TPC clusters (After the cuts)", + "varexp": "hNClustersAfterCuts.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hNClustersAfterCuts.stddev)/(sqrt(hNClustersAfterCuts.entries))" + }, + { + "name": "hEta_StatMean_Trend", + "title": "Mean trend of the pseudorapidity", + "varexp": "hEta.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hEta.stddev)/(sqrt(hEta.entries))" + }, + { + "name": "hPt_StatMean_Trend", + "title": "Mean trend of the transverse momentum", + "varexp": "hPt.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hPt.stddev)/(sqrt(hPt.entries))" + }, + { + "name": "hSign_StatMean_Trend", + "title": "Mean trend of the sign of electric charge", + "varexp": "hSign.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hSign.stddev)/(sqrt(hSign.entries))" + }, + { + "name": "hPtNeg_StatMean_Trend", + "title": "Mean trend of the transverse momentum of negative charges", + "varexp": "hPtNeg.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hPtNeg.stddev)/(sqrt(hPtNeg.entries))" + }, + { + "name": "hPtPos_StatMean_Trend", + "title": "Mean trend of the transverse momentum of positive charges", + "varexp": "hPtPos.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hPtPos.stddev)/(sqrt(hPtPos.entries))" + }, + { + "name": "h2DNClustersPhiAside_StatMean_Trend_Phi", + "title": "Mean trend of Phi from h2DNClustersPhiAside", + "varexp": "(h2DNClustersPhiAside.sumwx/h2DNClustersPhiAside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "h2DNClustersPhiAside_StatMean_Trend_Nclusters", + "title": "Mean trend of the number of clusters from h2DNClustersPhiAside", + "varexp": "(h2DNClustersPhiAside.sumwy/h2DNClustersPhiAside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "h2DNClustersPhiCside_StatMean_Trend_Phi", + "title": "Mean trend of Phi from h2DNClustersPhiCside", + "varexp": "(h2DNClustersPhiCside.sumwx/h2DNClustersPhiCside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "h2DNClustersPhiCside_StatMean_Trend_Nclusters", + "title": "Mean trend of the number of clusters from h2DNClustersPhiCside", + "varexp": "(h2DNClustersPhiCside.sumwy/h2DNClustersPhiCside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "Tracks_Trending_Slice": { + "active": "true", + "className": "o2::quality_control::postprocessing::SliceTrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "producePlotsOnUpdate": "true", + "resumeTrend": "false", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "hEta" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ [ "-1.0", "-0.5", "0.0", "0.5", "1.0" ] ], + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "hEta_StatMean_Trend", + "title": "Mean trend of the pseudorapidity", + "varexp": "hEta.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.", + "graphYRange": "-1.0:1.0", + "graphXRange": "", + "graphAxisLabel": "Mean Eta X:time" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} \ No newline at end of file diff --git a/Modules/TPC/run/tpcQCCheckTrending_laserCalib.json b/Modules/TPC/run/tpcQCCheckTrending_laserCalib.json new file mode 100644 index 0000000000..2c910b0375 --- /dev/null +++ b/Modules/TPC/run/tpcQCCheckTrending_laserCalib.json @@ -0,0 +1,132 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "checks" : { + "CheckOfLaserCalibration_Trending" : { + "active" : "true", + "className" : "o2::quality_control_modules::tpc::CheckOfTrendings", + "moduleName" : "QcTPC", + "policy" : "OnEachSeparately", + "detectorName" : "TPC", + "dataSource" : [ { + "type" : "PostProcessing", + "name" : "LaserCalibration_Trending", + "MOs" : [ "processedTFs_Trend", "dvCorrectionA_Trend","dvCorrectionC_Trend", "dvCorrection_Trend", "t0A_Trend", "t0C_Trend", "nTracksA_Trend","nTracksC_Trend"] + } ] + } + }, + "postprocessing": { + "LaserCalibration_Trending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "qc/TPC/MO/LaserCalibration", + "name": "Calib_Values", + "reductorName": "o2::quality_control_modules::tpc::LtrCalibReductor", + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "processedTFs_Trend", + "title": "Trend of the number of processed TFs", + "varexp": "Calib_Values.processedTFs:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "dvCorrectionA_Trend", + "title": "Trend of the drift velocity correction factor (A side)", + "varexp": "Calib_Values.dvCorrectionA:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "dvCorrectionC_Trend", + "title": "Trend of the drift velocity correction factor (C side)", + "varexp": "Calib_Values.dvCorrectionC:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "dvCorrection_Trend", + "title": "Trend of the drift velocity correction factor", + "varexp": "Calib_Values.dvCorrection:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "t0A_Trend", + "title": "Trend of the laser trigger t0 offset (A side)", + "varexp": "Calib_Values.t0A:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "t0C_Trend", + "title": "Trend of the laser trigger t0 offset (C side)", + "varexp": "Calib_Values.t0C:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "nTracksA_Trend", + "title": "Trend of the number of tracks (A side)", + "varexp": "Calib_Values.nTracksA:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "nTracksC_Trend", + "title": "Trend of the number of tracks (C side)", + "varexp": "Calib_Values.nTracksC:time", + "selection": "", + "option": "*L", + "graphErrors": "" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "30 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCClusterVisualizer.json b/Modules/TPC/run/tpcQCClusterVisualizer.json new file mode 100644 index 0000000000..b04a48a9f8 --- /dev/null +++ b/Modules/TPC/run/tpcQCClusterVisualizer.json @@ -0,0 +1,83 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "Clusters": { + "active": "true", + "className": "o2::quality_control_modules::tpc::ClusterVisualizer", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSourceURL": "ccdb-test.cern.ch:8080", + "timestamps_comment": [ "Put the timestamp of the corresponding file you want to look for in the timestamps array.", + "You can either put a timestamp for every object or leave the array empty to take the latest file from the CCDB.", + "An empty array to get the the latest version will be the main use case.", + "The array is mapped to the output objects sequentially", + "If you want to pick the latest file in the CCDB manually, you can use -1." + ], + "timestamps": [ + ], + "lookupMetaData_comment": [ "With this array you can filter your search via meta data.", + "The array is mapped sequentially to the output objects.", + "If you leave only one entry in the array this is used for all objects in outputCalPadMaps and outputCalPads.", + "If you want no meta data simply remove 'keys' and 'values' completely and leave only {}", + "Every entry above (outputCalPads.size() + outputCalPadMaps.size()) is ignored.", + "The keys and values that are set by default are only there to serve as an example." + ], + "lookupMetaData": [ + { + } + ], + "storeMetaData_comment": "For how-to, see 'lookupMetaData_comment'.", + "storeMetaData": [ + { + } + ], + "histogramRanges_comment" : [ "nBins", "min", "max" ], + "histogramRanges": [ + { "N_Clusters" : [ "100", "0", "100" ] }, + { "Q_Max" : [ "200", "0", "200" ] }, + { "Q_Tot" : [ "600", "0", "600" ] }, + { "Sigma_Time" : [ "200", "0", "2" ] }, + { "Sigma_Pad" : [ "200", "0", "2" ] }, + { "Time_Bin" : [ "1000", "0", "100000" ] }, + { "Occupancy" : [ "1000", "0", "1" ] } + ], + "path_comment": "This is the path of the ClustersData object that shall be visualized.", + "path": "qc/TPC/MO/Clusters/ClusterData", + "dataType_comment": "This is the switch for 'RawDigits' or 'Clusters' task. Choose 'raw' or 'clusters'.", + "dataType": "clusters", + "initTrigger": [ + "once" + ], + "updateTrigger_comment": "To trigger on a specific file being updated, use e.g. 'newobject:qcdb:TPC/Calib/Noise'", + "updateTrigger": [ + "newobject:ccdb:qc/TPC/MO/Clusters/ClusterData" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCClusters_direct.json b/Modules/TPC/run/tpcQCClusters_direct.json new file mode 100644 index 0000000000..45988e217c --- /dev/null +++ b/Modules/TPC/run/tpcQCClusters_direct.json @@ -0,0 +1,60 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "Clusters": { + "active": "true", + "className": "o2::quality_control_modules::tpc::Clusters", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "60", + "maxNumberCycles": "-1", + "resetAfterCycles": "5", + "dataSource": { + "type": "direct", + "query" : "input:TPC/CLUSTERNATIVE" + }, + "taskParameters": { + "mergeableOutput": "true", + "NClustersNBins": "100", "NClustersXMin": "0", "NClustersXMax": "100", + "QmaxNBins": "200", "QmaxXMin": "0", "QmaxXMax": "200", + "QtotNBins": "600", "QtotXMin": "0", "QtotXMax": "600", + "SigmaPadNBins": "200", "SigmaPadXMin": "0", "SigmaPadXMax": "2", + "SigmaTimeNBins": "200", "SigmaTimeXMin": "0", "SigmaTimeXMax": "2", + "TimeBinNBins": "1000", "TimeBinXMin": "0", "TimeBinXMax": "100000", + "OccupancyNBins": "1000", "OccupancyXMin": "0", "OccupancyXMax": "1" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "localhost", + "remotePort": "32626", + "mergingMode": "delta" + } + } + }, + "dataSamplingPolicies": [ + ] +} diff --git a/Modules/TPC/run/tpcQCDCSPTemperature.json b/Modules/TPC/run/tpcQCDCSPTemperature.json new file mode 100644 index 0000000000..e82ac5082d --- /dev/null +++ b/Modules/TPC/run/tpcQCDCSPTemperature.json @@ -0,0 +1,72 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "DCS_Temperature": { + "active": "true", + "className": "o2::quality_control_modules::tpc::DCSPTemperature", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSourceURL": "ccdb-test.cern.ch:8080", + "timestamps_comment": [ + "Use the 'Valid from' timestamp", + "The timestamp is used as a limit of how recent the files may be for visualization.", + "i.e. no file younger than timestamp is visualized.", + "-1 gets the file with the longest 'Valid until' timestamp, which should be the most recent file in production." + ], + "timestamp": "-1", + "nFiles_comment": [ + "The number of files to be combined in the visualization", + "nFiles older than timestamp will be visualized." + ], + "nFiles": "12", + "lookupMetaData_comment": [ + "Write the keys and values to filter the search in the CCDB.", + "Keys and values can be given comma separated.", + "The first value corresponds to the first value etc." + ], + "lookupMetaData": { + "keys": [], + "values": [] + }, + "storeMetaData_comment": [ + "Metadata to be stored in the QC output in the QCDB.", + "For how-to, see 'lookupMetaData_comment'." + ], + "storeMetaData": { + "keys": [], + "values": [] + }, + "initTrigger": [ + "once" + ], + "updateTrigger_comment": "To trigger on a specific file being updated, use e.g. 'newobject:ccdb:TPC/Calib/Noise'", + "updateTrigger": [ + "60min" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCGPUErrorQA_direct.json b/Modules/TPC/run/tpcQCGPUErrorQA_direct.json new file mode 100644 index 0000000000..76861f3c5d --- /dev/null +++ b/Modules/TPC/run/tpcQCGPUErrorQA_direct.json @@ -0,0 +1,38 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": {}, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "GPUErrorQA": { + "active": "true", + "className": "o2::quality_control_modules::tpc::GPUErrorQA", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query": "error-qa:GPU/ERRORQA/0" + }, + "location": "remote" + } + } + }, + "dataSamplingPolicies": [] +} diff --git a/Modules/TPC/run/tpcQCIDCs.json b/Modules/TPC/run/tpcQCIDCs.json new file mode 100644 index 0000000000..bef735f59b --- /dev/null +++ b/Modules/TPC/run/tpcQCIDCs.json @@ -0,0 +1,73 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://alice-ccdb.cern.ch" + } + }, + "postprocessing": { + "IDCs": { + "active": "true", + "className": "o2::quality_control_modules::tpc::IDCs", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSourceURL": "http://alice-ccdb.cern.ch", + "timestamps_comment": [ "Put the timestamp of the corresponding file you want to look for in the timestamps array.", + "You can either put a timestamp for every object or leave the array empty to take the latest file from the CCDB.", + "An empty array to get the the latest version will be the main use case.", + "The array is mapped to the output objects sequentially", + "If you want to pick the latest file in the CCDB manually, you can use -1." + ], + "timestamps": [ + { "IDCZero":"-1" }, + { "IDCOne": "-1" }, + { "IDCDelta": "-1" }, + { "FourierCoeffs": "-1" } + ], + "lookupMetaData_comment": [ "Not used in the current version" ], + "lookupMetaData": [ + { + } + ], + "storeMetaData_comment": "Not used in the current version", + "storeMetaData": [ + { + } + ], + "histogramRanges_comment" : [ "nBins", "min", "max" ], + "histogramRanges": [ + { "IDCZero" : [ "250", "0", "2.5" ] }, + { "IDCZeroOveview" : [ "250", "-20", "20" ] }, + { "IDCOne" : [ "250", "0", "0.5" ] }, + { "IDCDelta" : [ "100", "-1.02", "-0.94" ] }, + { "FourierCoeffs" : [ "600", "-20", "20" ] } + ], + "initTrigger": [ + "once" + ], + "updateTrigger_comment": "To trigger on a specific file being updated, use e.g. 'newobject:ccdb:TPC/Calib/Noise'", + "updateTrigger": [ + "once" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCIDCsVsSACs.json b/Modules/TPC/run/tpcQCIDCsVsSACs.json new file mode 100644 index 0000000000..2bc4975648 --- /dev/null +++ b/Modules/TPC/run/tpcQCIDCsVsSACs.json @@ -0,0 +1,68 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://alice-ccdb.cern.ch" + } + }, + "postprocessing": { + "IDCsVsSACs": { + "active": "true", + "className": "o2::quality_control_modules::tpc::IDCsVsSACs", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSourceURL": "http://alice-ccdb.cern.ch", + "timestamps_comment": [ "Put the timestamp of the corresponding file you want to look for in the timestamps array.", + "You can either put a timestamp for every object or leave the array empty to take the latest file from the CCDB.", + "An empty array to get the the latest version will be the main use case.", + "The array is mapped to the output objects sequentially", + "If you want to pick the latest file in the CCDB manually, you can use -1." + ], + "timestamps": [ + { "IDCZero":"-1" }, + { "SACZero":"-1" } + ], + "lookupMetaData_comment": [ "Not used in the current version" ], + "lookupMetaData": [ + { + } + ], + "storeMetaData_comment": "Not used in the current version", + "storeMetaData": [ + { + } + ], + "histogramRanges_comment" : [ "nBins", "min", "max" ], + "histogramRanges": [ + { "IDCZero" : [ "250", "0", "2.5"] }, + { "SACZero" : [ "250", "-100", "100" ] } + ], + "initTrigger": [ + "once" + ], + "updateTrigger_comment": "To trigger on a specific file being updated, use e.g. 'newobject:ccdb:TPC/Calib/Noise'", + "updateTrigger": [ + "once" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } + } diff --git a/Modules/TPC/run/tpcQCJunkDetection.json b/Modules/TPC/run/tpcQCJunkDetection.json new file mode 100644 index 0000000000..8c79db0d03 --- /dev/null +++ b/Modules/TPC/run/tpcQCJunkDetection.json @@ -0,0 +1,48 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "JunkDetection": { + "active": "true", + "className": "o2::quality_control_modules::tpc::JunkDetection", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query" : "JunkDetection:TPC/TRACKINGQA/0" + }, + "taskParameters": { + "mergeableOutput": "false", "": "With non-mergeable output a canvas is published in addition to the histograms to show the numbers in a nicer way." + }, + "comment": "The following options need to be changed according to the P2 config.", + "location": "local", + "localMachines": ["localhost"], + "remoteMachine": "localhost", + "mergingMode": "delta", + "remotePort": "32625" + } + } + }, + "dataSamplingPolicies": [ + ] +} diff --git a/Modules/TPC/run/tpcQCKrClusters_direct.json b/Modules/TPC/run/tpcQCKrClusters_direct.json new file mode 100644 index 0000000000..a9287398fd --- /dev/null +++ b/Modules/TPC/run/tpcQCKrClusters_direct.json @@ -0,0 +1,52 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "KrClusters": { + "active": "true", + "className": "o2::quality_control_modules::tpc::Clusters", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "10", + "resetAfterCycles": "5", + "dataSource": { + "type": "direct", + "query" : "krClusters:TPC/KRCLUSTERS" + }, + "taskParameters": { + "NClustersNBins": "100", "NClustersXMin": "0", "NClustersXMax": "100", + "QmaxNBins": "200", "QmaxXMin": "0", "QmaxXMax": "1024", + "QtotNBins": "600", "QtotXMin": "0", "QtotXMax": "6000", + "SigmaPadNBins": "200", "SigmaPadXMin": "0", "SigmaPadXMax": "3.8", + "SigmaTimeNBins": "200", "SigmaTimeXMin": "0", "SigmaTimeXMax": "2.5", + "TimeBinNBins": "1000", "TimeBinXMin": "0", "TimeBinXMax": "100000", + "Occupancy": "1000", "OccupancyXMin": "0", "OccupancyXMax": "1" + }, + "location": "remote" + } + } + }, + "dataSamplingPolicies": [ + ] +} diff --git a/Modules/TPC/run/tpcQCLaserTracks.json b/Modules/TPC/run/tpcQCLaserTracks.json new file mode 100644 index 0000000000..9b9bb5d0ca --- /dev/null +++ b/Modules/TPC/run/tpcQCLaserTracks.json @@ -0,0 +1,59 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "LaserCalibration": { + "active": "true", + "className": "o2::quality_control_modules::tpc::LaserTracks", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSourceURL": "ccdb-test.cern.ch:8080", + "timestamps_comment": [ "Select specific timestamp, -1 is default." + ], + "timestamp": [ + ], + "lookupMetaData": [ + { + } + ], + "storeMetaData": [ + { + } + ], + "initTrigger": [ + "once" + ], + "updateTrigger_comment": "To trigger on a specific file being updated, use e.g. 'newobject:ccdb:TPC/Calib/LaserTracks'", + "updateTrigger": [ + "once" + ], + "stopTrigger_comment": [ "To keep the task running until it is stopped manually set the trigger on the update of a non-existing object, e.g. 'newobject:ccdb:TPC/ThisDoesNotExist'", + "There will be a end of run trigger implemented so the above workaround can be abandoned later." ], + "stopTrigger": [ + "once" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCPID_direct.json b/Modules/TPC/run/tpcQCPID_direct.json new file mode 100644 index 0000000000..a09db8b4f1 --- /dev/null +++ b/Modules/TPC/run/tpcQCPID_direct.json @@ -0,0 +1,60 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "PID": { + "active": "true", + "className": "o2::quality_control_modules::tpc::PID", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query" : "inputTracks:TPC/TRACKS/0" + }, + "taskParameters": { + "cutMinNCluster": "60", "cutAbsTgl": "1.", "cutMindEdxTot": "20.", "cutMaxdEdxTot": "20.", "cutMinpTPC": "20.", "cutMaxpTPC": "20.", "cutMinpTPCMIPs": "0.45", "cutMaxpTPCMIPs": "0.55" + }, + "location": "remote" + } + }, + "checks": { + "QcCheckPID": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TPC", + "dataSource": [{ + "type": "Task", + "name": "PID", + "MOs": ["example"] + }] + } + } + }, + "dataSamplingPolicies": [ + + ] +} diff --git a/Modules/TPC/run/tpcQCPID_sampled.json b/Modules/TPC/run/tpcQCPID_sampled.json new file mode 100644 index 0000000000..095b2f3cf0 --- /dev/null +++ b/Modules/TPC/run/tpcQCPID_sampled.json @@ -0,0 +1,74 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "PID": { + "active": "true", + "className": "o2::quality_control_modules::tpc::PID", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tpc-tracks" + }, + "taskParameters": { + "cutMinNCluster": "60", "cutAbsTgl": "1.", "cutMindEdxTot": "10.", "cutMaxdEdxTot": "70.", "cutMinpTPC": "0.05", "cutMaxpTPC": "20.", "cutMinpTPCMIPs": "0.45", "cutMaxpTPCMIPs": "0.55" , "createCanvas" : "1" + }, + "location": "remote" + } + }, + "checks": { + "QcCheckPIDSampled": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TPC", + "dataSource": [{ + "type": "Task", + "name": "PID", + "MOs": ["example"] + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tpc-tracks", + "active": "true", + "machines": [], + "query" : "inputTracks:TPC/TRACKS/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.9", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/TPC/run/tpcQCPadCalibration.json b/Modules/TPC/run/tpcQCPadCalibration.json new file mode 100644 index 0000000000..ed51a3b5cc --- /dev/null +++ b/Modules/TPC/run/tpcQCPadCalibration.json @@ -0,0 +1,138 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable", + "maxObjectSize": "100000000" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "Calibration": { + "active": "true", + "className": "o2::quality_control_modules::tpc::CalDetPublisher", + "moduleName": "QcTPC", + "detectorName": "TPC", + "outputCalPadMaps_comment" : [ "CalDet objects that are stored in std::unordered_map> need to go here.", + "This needs to be the last part of the CCDB path, e.g. Pulser or CE." ], + "outputCalPadMaps": [ + "PedestalNoise" + ], + "outputCalPads_comment" : [ "CalDet objects that are stored as plain o2::tpc::CalDet objects need to go here.", + "This needs to be the last part of the CCDB path, e.g. Pedestal or Noise." ], + "outputCalPads": [ + ], + "timestamps_comment": [ "Put the timestamp of the corresponding file you want to look for in the timestamps array.", + "You can either put a timestamp for every object or leave the array empty to take the latest file from the CCDB.", + "An empty array to get the the latest version will be the main use case.", + "The array is mapped to the output objects sequentially", + "If you want to pick the latest file in the CCDB manually, you can use -1." + ], + "timestamps": [ + ], + "lookupMetaData_comment": [ "With this array you can filter your search via meta data.", + "The array is mapped sequentially to the output objects.", + "If you leave only one entry in the array this is used for all objects in outputCalPadMaps and outputCalPads.", + "If you want no meta data simply remove 'keys' and 'values' completely and leave only {}", + "Every entry above (outputCalPads.size() + outputCalPadMaps.size()) is ignored.", + "The keys and values that are set by default are only there to serve as an example." + ], + "lookupMetaData": [ + { + } + ], + "storeMetaData_comment": "For how-to, see 'lookupMetaData_comment'.", + "storeMetaData": [ + { + }, + { + "keys": [ "key1", "key2" ], + "values": [ "value1", "value2" ] + }, + { + "keys": [ "key" ], + "values": [ "value" ] + }, + { + } + ], + "histogramRanges_comment" : [ "nBins", "min", "max" ], + "histogramRanges": [ + { "Pedestals" : [ "240", "0", "120" ] }, + { "Noise" : [ "200", "0", "2" ] } + ], + "checkZSCalibration": { + "check": "false", + "initRefCalibTimestamp": "-1", + "initRefPedestalTimestamp": "-1", + "initRefNoiseTimestamp": "-1" + }, + "initTrigger": [ + "once" + ], + "updateTrigger_comment": "To trigger on a specific file being updated, use e.g. 'newobject:ccdb:TPC/Calib/Noise'", + "updateTrigger": [ + "once" + ], + "stopTrigger_comment": [ "To keep the task running until it is stopped manually set the trigger on the update of a non-existing object, e.g. 'newobject:ccdb:TPC/ThisDoesNotExist'", + "There will be a end of run trigger implemented so the above workaround can be abandoned later." ], + "stopTrigger": [ + "once" + ] + } + }, + "checks":{ + "PadCalibrationCheck": { + "active" : "true", + "className" : "o2::quality_control_modules::tpc::PadCalibrationCheck", + "moduleName" : "QcTPC", + "policy" : "OnEachSeparately", + "detectorName" : "TPC", + "dataSource" : [{ + "type" : "PostProcessing", + "name" : "Calibration", + "MOs" : ["c_ROCs_Noise_1D","c_Sides_Noise"] + }], + "checkParameters": { + "mediumQualityNoiseMean" : "1.25", + "badQualityNoiseMean" : "1.5" + } + }, + + "CheckForEmptyPads": { + "active" : "true", + "className" : "o2::quality_control_modules::tpc::CheckForEmptyPads", + "moduleName" : "QcTPC", + "policy" : "OnEachSeparately", + "detectorName" : "TPC", + "dataSource" : [{ + "type" : "PostProcessing", + "name" : "Calibration", + "MOs" : ["c_ROCs_Pedestals_2D","c_ROCs_Noise_2D"] + }], + "stopTrigger_comment": [ "MOsNames are required for code to work, runs check of empty pads, should be the same as MOs, the MOs must be separated by comma only, no spaces" ], + "checkParameters": { + "mediumQualityPercentageOfWorkingPads" : "0.7", + "badQualityPercentageOfWorkingPads" : "0.4", + "MOsNames2D" : "c_ROCs_Pedestals_2D,c_ROCs_Noise_2D" + } + } + } + } +} diff --git a/Modules/TPC/run/tpcQCPadCalibration_GenericPad.json b/Modules/TPC/run/tpcQCPadCalibration_GenericPad.json new file mode 100644 index 0000000000..c06af5dc01 --- /dev/null +++ b/Modules/TPC/run/tpcQCPadCalibration_GenericPad.json @@ -0,0 +1,153 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable", + "maxObjectSize": "100000000" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "Calibration_Generic": { + "active": "true", + "className": "o2::quality_control_modules::tpc::CalDetPublisher", + "moduleName": "QcTPC", + "detectorName": "TPC", + "outputCalPadMaps_comment" : [ "CalDet objects that are stored in std::unordered_map> need to go here.", + "This needs to be the last part of the CCDB path, e.g. Pulser or CE." ], + "outputCalPadMaps": [ + "PedestalNoise" + ], + "outputCalPads_comment" : [ "CalDet objects that are stored as plain o2::tpc::CalDet objects need to go here.", + "This needs to be the last part of the CCDB path, e.g. Pedestal or Noise." ], + "outputCalPads": [ + ], + "timestamps_comment": [ "Put the timestamp of the corresponding file you want to look for in the timestamps array.", + "You can either put a timestamp for every object or leave the array empty to take the latest file from the CCDB.", + "An empty array to get the the latest version will be the main use case.", + "The array is mapped to the output objects sequentially", + "If you want to pick the latest file in the CCDB manually, you can use -1." + ], + "timestamps": [ + ], + "lookupMetaData_comment": [ "With this array you can filter your search via meta data.", + "The array is mapped sequentially to the output objects.", + "If you leave only one entry in the array this is used for all objects in outputCalPadMaps and outputCalPads.", + "If you want no meta data simply remove 'keys' and 'values' completely and leave only {}", + "Every entry above (outputCalPads.size() + outputCalPadMaps.size()) is ignored.", + "The keys and values that are set by default are only there to serve as an example." + ], + "lookupMetaData": [ + { + } + ], + "storeMetaData_comment": "For how-to, see 'lookupMetaData_comment'.", + "storeMetaData": [ + { + }, + { + "keys": [ "key1", "key2" ], + "values": [ "value1", "value2" ] + }, + { + "keys": [ "key" ], + "values": [ "value" ] + }, + { + } + ], + "histogramRanges_comment" : [ "nBins", "min", "max" ], + "histogramRanges": [ + { "Pedestals" : [ "240", "0", "120" ] }, + { "Noise" : [ "200", "0", "2" ] } + ], + "checkZSCalibration": { + "check": "false", + "initRefCalibTimestamp": "-1", + "initRefPedestalTimestamp": "-1", + "initRefNoiseTimestamp": "-1" + }, + "initTrigger": [ + "once" + ], + "updateTrigger_comment": "To trigger on a specific file being updated, use e.g. 'newobject:ccdb:TPC/Calib/Noise'", + "updateTrigger": [ + "once" + ], + "stopTrigger_comment": [ "To keep the task running until it is stopped manually set the trigger on the update of a non-existing object, e.g. 'newobject:ccdb:TPC/ThisDoesNotExist'", + "There will be a end of run trigger implemented so the above workaround can be abandoned later." ], + "stopTrigger": [ + "once" + ] + } + }, + "checks":{ + + "CheckOfPads_Noise": { + "active" : "true", + "className" : "o2::quality_control_modules::tpc::CheckOfPads", + "moduleName" : "QcTPC", + "policy" : "OnEachSeparately", + "detectorName" : "TPC", + "dataSource" : [{ + "type" : "PostProcessing", + "name" : "Calibration_Generic", + "MOs" : ["c_ROCs_Noise_2D"] + }], + "stopTrigger_comment": [ "MOsNames are required for code to work, runs check of empty pads, should be the same as MOs, the MOs must be separated by comma only, no spaces" ], + "checkParameters": { + "mediumQualityPercentageOfWorkingPads" : "0.9", + "badQualityPercentageOfWorkingPads" : "0.8", + "CheckChoice" : "ExpectedValue", + "ExpectedValueSigmaMedium" : "3", + "ExpectedValueSigmaBad" : "6", + "MeanSigmaMedium" : "1", + "MeanSigmaBad" : "3", + "ExpectedValue" : "1", + "MOsNames2D" : "c_ROCs_Noise_2D" + } + }, + "CheckOfPads_Pedestrals": { + "active" : "true", + "className" : "o2::quality_control_modules::tpc::CheckOfPads", + "moduleName" : "QcTPC", + "policy" : "OnEachSeparately", + "detectorName" : "TPC", + "dataSource" : [{ + "type" : "PostProcessing", + "name" : "Calibration_Generic", + "MOs" : ["c_ROCs_Pedestals_2D"] + }], + "stopTrigger_comment": [ "MOsNames are required for code to work, runs check of empty pads, should be the same as MOs, the MOs must be separated by comma only, no spaces" ], + "checkParameters": { + "mediumQualityPercentageOfWorkingPads" : "0.9", + "badQualityPercentageOfWorkingPads" : "0.7", + "CheckChoice" : "Empty,Mean", + "ExpectedValueSigmaMedium" : "3", + "ExpectedValueSigmaBad" : "6", + "MeanSigmaMedium" : "1", + "MeanSigmaBad" : "3", + "MOsNames2D" : "c_ROCs_Pedestals_2D" + } + } + + } + } + } + \ No newline at end of file diff --git a/Modules/TPC/run/tpcQCQualityObserver.json b/Modules/TPC/run/tpcQCQualityObserver.json new file mode 100644 index 0000000000..04bc198398 --- /dev/null +++ b/Modules/TPC/run/tpcQCQualityObserver.json @@ -0,0 +1,66 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "", + "type": "" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "TestQuality": { + "active": "true", + "className": "o2::quality_control_modules::tpc::QualityObserver", + "moduleName": "QcTPC", + "detectorName": "TPC", + "qualityObserverName": "TestObserver", + "observeDetails": "true", + "qualityDetailChoice": "Null, Good, Medium, Bad", + "qualityObserverConfig": [ + { + "groupTitle": "Tracks Trending", + "path": "TPC/QO/CheckOfTrack_Trending/Tracks_Trending", + "inputObjects": [ "hEta_StatMean_Trend", "hNClustersAfterCuts_StatMean_Trend" ], + "inputObjectTitles": [ "Eta, Mean Trend", "Clusters after Cut, Mean Trend" ] + }, + { + "groupTitle": "Calibration", + "path": "TPC/QO/Calibration/Calibration", + "inputObjects": [ "c_ROCs_Noise_1D", "c_ROCs_Pedestal_2D", "c_Sides_Noise" ], + "inputObjectTitles": [ "ROCs Noise 1D", "ROCs Pedestal 2D", "Sides Noise" ] + }, + { + "groupTitle": "Calibration Emtpy Pads", + "path": "TPC/QO/CheckForEmptyPads/Calibration", + "inputObjects": [ "c_ROCs_Noise_2D", "c_ROCs_Pedestals_2D" ], + "inputObjectTitles": [ "Empty Pads in ROCs Noise 2D", "Empty Pads in ROCs Pedestal 2D" ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCROCTrending.json b/Modules/TPC/run/tpcQCROCTrending.json new file mode 100644 index 0000000000..639d7b335e --- /dev/null +++ b/Modules/TPC/run/tpcQCROCTrending.json @@ -0,0 +1,115 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "", + "type": "" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "http://consul-test.cern.ch:8500" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "TrendingROC": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Clusters", + "names": [ "N_Clusters", "Q_Max", "Q_Tot", "Sigma_Pad", "Sigma_Time", "Time_Bin" ], + "reductorName": "o2::quality_control_modules::tpc::ROCReductor", + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "temp_mean_N_Clusters_0", + "title": "Mean value of N_Clusters (ROC 0) over time", + "varexp": "N_Clusters.mean[]:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "TrendingCalPadCluster": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Clusters", + "names": [ "ClusterData" ], + "reductorName": "o2::quality_control_modules::tpc::CalPadClusterReductor", + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "NClusters_numberEntries_allROC_trending", + "title": "Number of Entries NClusters (all ROC) over time", + "varexp": "ClusterData.NClusters[0][ ]:time", + "selection": "", + "option": "*L" + }, + { + "name": "NClusters_mean_allROC_trending", + "title": "Mean NClusters (all ROC) over time", + "varexp": "ClusterData.NClusters[1][ ]:time", + "selection": "", + "option": "*L" + }, + { + "name": "NClusters_stddev_allROC_trending", + "title": "Stddev NClusters (all ROC) over time", + "varexp": "ClusterData.NClusters[2][ ]:time", + "selection": "", + "option": "*L" + }, + { + "name": "NClusters_median_allROC_trending", + "title": "Median NClusters (all ROC) over time", + "varexp": "ClusterData.NClusters[3][ ]:time", + "selection": "", + "option": "*L" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCRatio_generator.json b/Modules/TPC/run/tpcQCRatio_generator.json new file mode 100644 index 0000000000..fcb91d4cf6 --- /dev/null +++ b/Modules/TPC/run/tpcQCRatio_generator.json @@ -0,0 +1,59 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "", + "type": "" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "Tracks": { + "active": "true", + "className": "o2::quality_control_modules::tpc::RatioGeneratorTPC", + "moduleName": "QcTPC", + "detectorName": "TPC", + "ratioConfig": [ + { + "path": "TPC/MO/Tracks", + "inputObjects": [ "hEtaNeg", "hEtaPos" ], + "outputName": "hEtaRatio", + "plotTitle": "Ratio neg/pos tracks vs #eta", + "axisTitle": "Ratio neg/pos:#eta" + }, + { + "path": "TPC/MO/Tracks", + "inputObjects": [ "hPhiAsideNeg", "hPhiAsidePos" ], + "outputName": "hPhiAsideRatio", + "plotTitle": "Ratio neg/pos tracks vs #phi", + "axisTitle": "Ratio neg/pos:#phi" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCRawDigitVisualizer.json b/Modules/TPC/run/tpcQCRawDigitVisualizer.json new file mode 100644 index 0000000000..e0a94e0d25 --- /dev/null +++ b/Modules/TPC/run/tpcQCRawDigitVisualizer.json @@ -0,0 +1,101 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "RawDigits": { + "active": "true", + "className": "o2::quality_control_modules::tpc::ClusterVisualizer", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSourceURL": "ccdb-test.cern.ch:8080", + "timestamps_comment": [ "Put the timestamp of the corresponding file you want to look for in the timestamps array.", + "You can either put a timestamp for every object or leave the array empty to take the latest file from the CCDB.", + "An empty array to get the the latest version will be the main use case.", + "The array is mapped to the output objects sequentially", + "If you want to pick the latest file in the CCDB manually, you can use -1." + ], + "timestamps": [ + ], + "lookupMetaData_comment": [ "With this array you can filter your search via meta data.", + "The array is mapped sequentially to the output objects.", + "If you leave only one entry in the array this is used for all objects in outputCalPadMaps and outputCalPads.", + "If you want no meta data simply remove 'keys' and 'values' completely and leave only {}", + "Every entry above (outputCalPads.size() + outputCalPadMaps.size()) is ignored.", + "The keys and values that are set by default are only there to serve as an example." + ], + "lookupMetaData": [ + { + } + ], + "storeMetaData_comment": "For how-to, see 'lookupMetaData_comment'.", + "storeMetaData": [ + { + } + ], + "histogramRanges_comment" : [ "nBins", "min", "max" ], + "histogramRanges": [ + { "N_Clusters" : [ "100", "0", "100" ] }, + { "N_RawDigits" : [ "100", "0", "100" ] }, + { "Q_Max" : [ "200", "0", "200" ] }, + { "Time_Bin" : [ "1000", "0", "100000" ] }, + { "Occupancy" : [ "1000", "0", "1" ] } + ], + "path_comment": "This is the path of the RawDigitData object that shall be visualized.", + "path": "qc/TPC/MO/RawDigits/RawDigitData", + "dataType_comment": "This is the switch for 'RawDigits' or 'Clusters' task. Choose 'raw' or 'clusters'.", + "dataType": "raw", + "initTrigger": [ + "once" + ], + "updateTrigger_comment": "To trigger on a specific file being updated, use e.g. 'newobject:qcdb:TPC/Calib/Noise'", + "updateTrigger": [ + "newobject:qcdb:TPC/MO/RawDigits/RawDigitData" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks":{ + "CheckForEmptyPads": { + "active" : "true", + "className" : "o2::quality_control_modules::tpc::CheckForEmptyPads", + "moduleName" : "QcTPC", + "policy" : "OnEachSeparately", + "detectorName" : "TPC", + "dataSource" : [{ + "type" : "PostProcessing", + "name" : "RawDigits", + "MOs" : ["c_ROCs_N_RawDigits_2D","c_ROCs_Q_Max_2D"] + }], + "stopTrigger_comment": [ "MOsNames are required for code to work, runs check of empty pads, should be the same as MOs, the MOs must be separated by comma only, no spaces" ], + "checkParameters": { + "mediumQualityPercentageOfWorkingPads" : "0.7", + "badQualityPercentageOfWorkingPads" : "0.4", + "MOsNames2D" : "c_ROCs_N_RawDigits_2D,c_ROCs_Q_Max_2D" + } + } + } + } +} diff --git a/Modules/TPC/run/tpcQCRawDigits_direct.json b/Modules/TPC/run/tpcQCRawDigits_direct.json new file mode 100644 index 0000000000..efe5c04c4e --- /dev/null +++ b/Modules/TPC/run/tpcQCRawDigits_direct.json @@ -0,0 +1,56 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "RawDigits": { + "active": "true", + "className": "o2::quality_control_modules::tpc::RawDigits", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "10", + "resetAfterCycles": "5", + "dataSource": { + "type": "direct", + "query" : "input:TPC/RAWDATA" + }, + "taskParameters": { + "mergeableOutput": "true", + "NRawDigitsNBins": "100", "NRawDigitsXMin": "0", "NRawDigitsXMax": "100", + "QmaxNBins": "200", "QmaxXMin": "0", "QmaxXMax": "200", + "TimeBinNBins": "1000", "TimeBinXMin": "0", "TimeBinXMax": "100000", + "OccupancyNBins": "1000", "OccupancyXMin": "0", "OccupancyXMax": "1" + }, + "location": "local", + "localMachines": [ + "localhost" + ], + "remoteMachine": "localhost", + "remotePort": "32627", + "mergingMode": "delta" + } + } + }, + "dataSamplingPolicies": [ + ] +} diff --git a/Modules/TPC/run/tpcQCSACScaleTrend.json b/Modules/TPC/run/tpcQCSACScaleTrend.json new file mode 100644 index 0000000000..a24a9dbbe4 --- /dev/null +++ b/Modules/TPC/run/tpcQCSACScaleTrend.json @@ -0,0 +1,68 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "SACZero_Scale_Trending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/SACs", + "names": [ "c_sides_SACZero_ScaleFactor" ], + "reductorName": "o2::quality_control_modules::tpc::SACZeroScaleReductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "c_sides_SACZero_ScaleFactor_ASide_Trend", + "title": "Trend SAC Zero Scale Factor - A Side", + "varexp": "c_sides_SACZero_ScaleFactor.SACZeroScaleFactorASide:time", + "selection": "", + "option": "*L", + "graphErrors": "0:0" + }, + { + "name": "c_sides_SACZero_ScaleFactor_CSide_Trend", + "title": "Trend SAC Zero Scale Factor - C Side", + "varexp": "c_sides_SACZero_ScaleFactor.SACZeroScaleFactorCSide:time", + "selection": "", + "option": "*L", + "graphErrors": "0:0" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} \ No newline at end of file diff --git a/Modules/TPC/run/tpcQCSACs.json b/Modules/TPC/run/tpcQCSACs.json new file mode 100644 index 0000000000..571a30e671 --- /dev/null +++ b/Modules/TPC/run/tpcQCSACs.json @@ -0,0 +1,78 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://ccdb-test.cern.ch:8080" + }, + "postprocessing": { + "matchAnyRunNumber": "true" + } + }, + "postprocessing": { + "SACs": { + "active": "true", + "className": "o2::quality_control_modules::tpc::SACs", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSourceURL": "http://ccdb-test.cern.ch:8080", + "doLatest": "true", + "rejectOutliersSACZeroScale": "true", + "maxDeviationOutlierSACZero": "2.", + "doSACFourierCoeffs": "false", + "timestamps_comment": [ "Put the timestamp of the corresponding file you want to look for in the timestamps array.", + "You can either put a timestamp for every object or leave the array empty to take the latest file from the CCDB.", + "An empty array to get the the latest version will be the main use case.", + "The array is mapped to the output objects sequentially", + "If you want to pick the latest file in the CCDB manually, you can use -1." + ], + "timestamps": [ + { "SACContainer":"-1" }, + { "SACFourierCoeffs":"-1" } + ], + "lookupMetaData_comment": [ "Not used in the current version" ], + "lookupMetaData": [ + { + } + ], + "storeMetaData_comment": "Not used in the current version", + "storeMetaData": [ + { + } + ], + "histogramRanges_comment" : [ "nBins", "min", "max" ], + "histogramRanges": [ + { "SACZero" : [ "250", "-50", "-50" ] }, + { "SACZeroScaled" : [ "250", "-5", "2" ] }, + { "SACOne" : [ "250", "-100", "100" ] }, + { "SACDelta" : [ "250", "-10", "10" ] }, + { "SACFourierCoeffs" : [ "250", "-10", "10" ] } + ], + "initTrigger": [ + "once" + ], + "updateTrigger_comment": "To trigger on a specific file being updated, use e.g. 'newobject:ccdb:TPC/Calib/Noise'", + "updateTrigger": [ + "foreachlatest:ccdb:TPC/Calib/SAC" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} \ No newline at end of file diff --git a/Modules/TPC/run/tpcQCSimpleTrending.json b/Modules/TPC/run/tpcQCSimpleTrending.json new file mode 100644 index 0000000000..c703b930e5 --- /dev/null +++ b/Modules/TPC/run/tpcQCSimpleTrending.json @@ -0,0 +1,171 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "localhost:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "ExampleTrendingPID": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/PID", + "names": [ "hNClusters", "hPhi" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + }, + { + "type": "repository-quality", + "path": "TPC/QO", + "names": [ "PIDClusterCheck" ], + "reductorName": "o2::quality_control_modules::common::QualityReductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_hPhi", + "title": "Mean trend of hPhi", + "varexp": "hPhi.mean:time", + "selection": "", + "option": "*L" + }, + { + "name": "mean_of_hNClusters", + "title": "Mean trend of the number of clusters", + "varexp": "hNClusters.mean:time", + "selection": "", + "option": "*L" + }, + { + "name": "histogram_of_means_hNClusters", + "title": "Distribution of mean values in the number of clusters histograms", + "varexp": "hNClusters.mean", + "selection": "", + "option": "" + }, + { + "name": "correlation_mean_stddev_hNClusters", + "title": "Correlation between the mean and stddev of the number of clusters", + "varexp": "hNClusters.mean:hNClusters.stddev", + "selection": "", + "option": "*" + }, + { + "name": "correlation_stddev_entries_hNClusters", + "title": "Correlation between the stddev and entries of the number of clusters", + "varexp": "hNClusters.stddev:hNClusters.entries", + "selection": "", + "option": "*" + }, + { + "name": "PIDClusterCheck_quality", + "title": "Trend of the example histogram's quality", + "varexp": "PIDClusterCheck.name:time", + "selection": "", + "option": "*" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "ExampleTrendingTracks": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "hNClustersBeforeCuts" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + }, + { + "type": "repository-quality", + "path": "TPC/QO", + "names": [ "TrackClusterCheck" ], + "reductorName": "o2::quality_control_modules::common::QualityReductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "mean_of_hNClustersBeforeCuts", + "title": "Mean trend of hNClustersBeforeCuts", + "varexp": "hNClustersBeforeCuts.mean:time", + "selection": "", + "option": "*L" + }, + { + "name": "histogram_of_means_hNClustersBeforeCuts", + "title": "Distribution of mean values of hNClustersBeforeCuts", + "varexp": "hNClustersBeforeCuts.mean", + "selection": "", + "option": "" + }, + { + "name": "correlation_mean_stddev_hNClustersBeforeCuts", + "title": "Correlation between the mean and stddev of hNClustersBeforeCuts", + "varexp": "hNClustersBeforeCuts.mean:hNClustersBeforeCuts.stddev", + "selection": "", + "option": "*" + }, + { + "name": "correlation_stddev_entries_hNClustersBeforeCuts", + "title": "Correlation between the stddev and entries of hNClustersBeforeCuts", + "varexp": "hNClustersBeforeCuts.stddev:hNClustersBeforeCuts.entries", + "selection": "", + "option": "*" + }, + { + "name": "TrackClusterCheck_quality", + "title": "Trend of the quality of TrackClusterCheck", + "varexp": "TrackClusterCheck.name:time", + "selection": "", + "option": "*" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCTasks_multinode.json b/Modules/TPC/run/tpcQCTasks_multinode.json new file mode 100644 index 0000000000..2f6857b2a0 --- /dev/null +++ b/Modules/TPC/run/tpcQCTasks_multinode.json @@ -0,0 +1,141 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "RawDigits_EPN": { + "active": "true", + "className": "o2::quality_control_modules::tpc::RawDigits", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "random-rawdata" + }, + "taskParameters": { + "NRawDigitsNBins": "100", "NRawDigitsXMin": "0", "NRawDigitsXMax": "100", + "QmaxNBins": "200", "QmaxXMin": "0", "QmaxXMax": "200", + "TimeBinNBins": "600", "TimeBinXMin": "0", "TimeBinXMax": "600" + }, + "location": "remote" + }, + "Clusters_EPN": { + "active": "true", + "className": "o2::quality_control_modules::tpc::Clusters", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "random-clusters" + }, + "taskParameters": { + "NClustersNBins": "100", "NClustersXMin": "0", "NClustersXMax": "100", + "QmaxNBins": "200", "QmaxXMin": "0", "QmaxXMax": "200", + "QtotNBins": "600", "QtotXMin": "0", "QtotXMax": "600", + "SigmaPadNBins": "200", "SigmaPadXMin": "0", "SigmaPadXMax": "2", + "SigmaTimeNBins": "200", "SigmaTimeXMin": "0", "SigmaTimeXMax": "2", + "TimeBinNBins": "1000", "TimeBinXMin": "0", "TimeBinXMax": "100000" + }, + "location": "remote" + }, + "PID_EPN": { + "active": "true", + "className": "o2::quality_control_modules::tpc::PID", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query" : "inputTracks:TPC/TRACKS/0" + }, + "taskParameters": {}, + "location": "local", + "localMachines": [ + "epn160-ib", "epn161-ib", "epn162-ib", "epn163-ib", "epn164-ib", "epn165-ib", "epn166-ib", "epn167-ib", "epn168-ib", "epn169-ib", "epn170-ib", "epn171-ib", "epn172-ib", "epn173-ib", "epn174-ib", "epn175-ib", "epn176-ib", "epn177-ib", "epn178-ib", "epn179-ib", "epn180-ib", "epn181-ib", "epn182-ib", "epn183-ib", "epn184-ib", "epn185-ib", "epn186-ib", "epn187-ib", "epn188-ib", "epn189-ib", "epn190-ib", "epn191-ib", "epn192-ib", "epn193-ib", "epn194-ib", "epn195-ib", "epn196-ib", "epn197-ib", "epn198-ib", "epn199-ib", "epn200-ib", "epn201-ib", "epn202-ib", "epn203-ib", "epn204-ib", "epn205-ib", "epn206-ib", "epn207-ib", "epn208-ib", "epn209-ib", "epn210-ib", "epn211-ib", "epn212-ib", "epn213-ib", "epn214-ib", "epn215-ib", "epn216-ib", "epn217-ib", "epn218-ib", "epn219-ib", "epn220-ib", "epn221-ib", "epn222-ib", "epn223-ib", "epn224-ib", "epn225-ib", "epn226-ib", "epn227-ib", "epn228-ib", "epn229-ib", "epn230-ib", "epn231-ib", "epn232-ib", "epn233-ib", "epn234-ib", "epn235-ib", "epn236-ib", "epn237-ib", "epn238-ib", "epn239-ib", "epn240-ib", "epn241-ib", "epn242-ib", "epn243-ib", "epn244-ib", "epn245-ib", "epn246-ib", "epn247-ib", "epn248-ib" + ], + "remoteMachine": "epn160-ib", + "remotePort": "32625", + "mergingMode": "delta" + }, + "Tracks_EPN": { + "active": "true", + "className": "o2::quality_control_modules::tpc::Tracks", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "30", + "dataSource": { + "type": "direct", + "query" : "inputTracks:TPC/TRACKS/0" + }, + "taskParameters": {}, + "location": "local", + "localMachines": [ + "epn160-ib", "epn161-ib", "epn162-ib", "epn163-ib", "epn164-ib", "epn165-ib", "epn166-ib", "epn167-ib", "epn168-ib", "epn169-ib", "epn170-ib", "epn171-ib", "epn172-ib", "epn173-ib", "epn174-ib", "epn175-ib", "epn176-ib", "epn177-ib", "epn178-ib", "epn179-ib", "epn180-ib", "epn181-ib", "epn182-ib", "epn183-ib", "epn184-ib", "epn185-ib", "epn186-ib", "epn187-ib", "epn188-ib", "epn189-ib", "epn190-ib", "epn191-ib", "epn192-ib", "epn193-ib", "epn194-ib", "epn195-ib", "epn196-ib", "epn197-ib", "epn198-ib", "epn199-ib", "epn200-ib", "epn201-ib", "epn202-ib", "epn203-ib", "epn204-ib", "epn205-ib", "epn206-ib", "epn207-ib", "epn208-ib", "epn209-ib", "epn210-ib", "epn211-ib", "epn212-ib", "epn213-ib", "epn214-ib", "epn215-ib", "epn216-ib", "epn217-ib", "epn218-ib", "epn219-ib", "epn220-ib", "epn221-ib", "epn222-ib", "epn223-ib", "epn224-ib", "epn225-ib", "epn226-ib", "epn227-ib", "epn228-ib", "epn229-ib", "epn230-ib", "epn231-ib", "epn232-ib", "epn233-ib", "epn234-ib", "epn235-ib", "epn236-ib", "epn237-ib", "epn238-ib", "epn239-ib", "epn240-ib", "epn241-ib", "epn242-ib", "epn243-ib", "epn244-ib", "epn245-ib", "epn246-ib", "epn247-ib", "epn248-ib" + ], + "remoteMachine": "epn160-ib", + "remotePort": "32626", + "mergingMode": "delta" + } + } + }, + "dataSamplingPolicies": [ + { + "id": "random-clusters", + "active": "true", + "machines": [ + "epn160-ib", "epn161-ib", "epn162-ib", "epn163-ib", "epn164-ib", "epn165-ib", "epn166-ib", "epn167-ib", "epn168-ib", "epn169-ib", "epn170-ib", "epn171-ib", "epn172-ib", "epn173-ib", "epn174-ib", "epn175-ib", "epn176-ib", "epn177-ib", "epn178-ib", "epn179-ib", "epn180-ib", "epn181-ib", "epn182-ib", "epn183-ib", "epn184-ib", "epn185-ib", "epn186-ib", "epn187-ib", "epn188-ib", "epn189-ib", "epn190-ib", "epn191-ib", "epn192-ib", "epn193-ib", "epn194-ib", "epn195-ib", "epn196-ib", "epn197-ib", "epn198-ib", "epn199-ib", "epn200-ib", "epn201-ib", "epn202-ib", "epn203-ib", "epn204-ib", "epn205-ib", "epn206-ib", "epn207-ib", "epn208-ib", "epn209-ib", "epn210-ib", "epn211-ib", "epn212-ib", "epn213-ib", "epn214-ib", "epn215-ib", "epn216-ib", "epn217-ib", "epn218-ib", "epn219-ib", "epn220-ib", "epn221-ib", "epn222-ib", "epn223-ib", "epn224-ib", "epn225-ib", "epn226-ib", "epn227-ib", "epn228-ib", "epn229-ib", "epn230-ib", "epn231-ib", "epn232-ib", "epn233-ib", "epn234-ib", "epn235-ib", "epn236-ib", "epn237-ib", "epn238-ib", "epn239-ib", "epn240-ib", "epn241-ib", "epn242-ib", "epn243-ib", "epn244-ib", "epn245-ib", "epn246-ib", "epn247-ib", "epn248-ib" + ], + "port": "32627", + "query": "inputClus:TPC/CLUSTERNATIVE", + "outputs": "sampled-clusters:DS/CLUSTERNATIVE", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.5", + "seed": "1234" + } + ], + "blocking": "false" + }, + { + "id": "random-rawdata", + "active": "true", + "machines": [ + "epn160-ib", "epn161-ib", "epn162-ib", "epn163-ib", "epn164-ib", "epn165-ib", "epn166-ib", "epn167-ib", "epn168-ib", "epn169-ib", "epn170-ib", "epn171-ib", "epn172-ib", "epn173-ib", "epn174-ib", "epn175-ib", "epn176-ib", "epn177-ib", "epn178-ib", "epn179-ib", "epn180-ib", "epn181-ib", "epn182-ib", "epn183-ib", "epn184-ib", "epn185-ib", "epn186-ib", "epn187-ib", "epn188-ib", "epn189-ib", "epn190-ib", "epn191-ib", "epn192-ib", "epn193-ib", "epn194-ib", "epn195-ib", "epn196-ib", "epn197-ib", "epn198-ib", "epn199-ib", "epn200-ib", "epn201-ib", "epn202-ib", "epn203-ib", "epn204-ib", "epn205-ib", "epn206-ib", "epn207-ib", "epn208-ib", "epn209-ib", "epn210-ib", "epn211-ib", "epn212-ib", "epn213-ib", "epn214-ib", "epn215-ib", "epn216-ib", "epn217-ib", "epn218-ib", "epn219-ib", "epn220-ib", "epn221-ib", "epn222-ib", "epn223-ib", "epn224-ib", "epn225-ib", "epn226-ib", "epn227-ib", "epn228-ib", "epn229-ib", "epn230-ib", "epn231-ib", "epn232-ib", "epn233-ib", "epn234-ib", "epn235-ib", "epn236-ib", "epn237-ib", "epn238-ib", "epn239-ib", "epn240-ib", "epn241-ib", "epn242-ib", "epn243-ib", "epn244-ib", "epn245-ib", "epn246-ib", "epn247-ib", "epn248-ib" + ], + "port": "32628", + "query": "inputRaw:TPC/RAWDATA", + "outputs": "sampled-rawdata:DS/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.5", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/TPC/run/tpcQCTimeGainCalibTrending.json b/Modules/TPC/run/tpcQCTimeGainCalibTrending.json new file mode 100644 index 0000000000..1463fb2e39 --- /dev/null +++ b/Modules/TPC/run/tpcQCTimeGainCalibTrending.json @@ -0,0 +1,222 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "", + "type": "", + "start": "", + "end": "" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "postprocessing": { + "periodSeconds": "10" + } + }, + "postprocessing": { + "CalibQC": { + "active": "true", + "resumeTrend": "false", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "producePlotsOnUpdate": "true", + "dataSources": [ + { + "type": "condition", + "path": "TPC/Calib/", + "names": [ "TimeGain" ], + "reductorName": "o2::quality_control_modules::tpc::TimeGainCalibReductor", + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "Gain_QMax_IROC_Trend", + "title": "QMax mean gain over IROC vs time", + "varexp": "TimeGain.meanGain[0][0]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean gain:time" + }, + { + "name": "Gain_QMax_OROC1_Trend", + "title": "QMax mean gain over OROC1 vs time", + "varexp": "TimeGain.meanGain[0][1]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean gain:time" + }, + { + "name": "Gain_QMax_OROC2_Trend", + "title": "QMax mean gain over OROC2 vs time", + "varexp": "TimeGain.meanGain[0][2]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean gain:time" + }, + { + "name": "Gain_QMax_OROC3_Trend", + "title": "QMax mean gain over OROC3 vs time", + "varexp": "TimeGain.meanGain[0][3]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean gain:time" + }, + { + "name": "Gain_QMax_AllStacks_Trend", + "title": "QMax mean gain over all stacks vs time", + "varexp": "TimeGain.meanGain[0][4]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean gain:time" + }, + { + "name": "Gain_QTot_IROC_Trend", + "title": "QTot mean gain over IROC vs time", + "varexp": "TimeGain.meanGain[1][0]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean gain:time" + }, + { + "name": "Gain_QTot_OROC1_Trend", + "title": "QTot mean gain over OROC1 vs time", + "varexp": "TimeGain.meanGain[1][1]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean gain:time" + }, + { + "name": "Gain_QTot_OROC2_Trend", + "title": "QTot mean gain over OROC2 vs time", + "varexp": "TimeGain.meanGain[1][2]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean gain:time" + }, + { + "name": "Gain_QTot_OROC3_Trend", + "title": "QTot mean gain over OROC3 vs time", + "varexp": "TimeGain.meanGain[1][3]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean gain:time" + }, + { + "name": "Gain_QTot_AllStacks_Trend", + "title": "QTot mean gain over all stacks vs time", + "varexp": "TimeGain.meanGain[1][4]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "mean gain:time" + }, + { + "name": "DiffCorrectionTgl_QMax_IROC_Trend", + "title": "QMax diff correction tgl(1) - tgl(0) over IROC vs time", + "varexp": "TimeGain.diffCorrectionTgl[0][0]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "diff correction tgl(1) - tgl(0):time" + }, + { + "name": "DiffCorrectionTgl_QMax_OROC1_Trend", + "title": "QMax diff correction tgl(1) - tgl(0) over OROC1 vs time", + "varexp": "TimeGain.diffCorrectionTgl[0][1]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "diff correction tgl(1) - tgl(0):time" + }, + { + "name": "DiffCorrectionTgl_QMax_OROC2_Trend", + "title": "QMax diff correction tgl(1) - tgl(0) over OROC2 vs time", + "varexp": "TimeGain.diffCorrectionTgl[0][2]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "diff correction tgl(1) - tgl(0):time" + }, + { + "name": "DiffCorrectionTgl_QMax_OROC3_Trend", + "title": "QMax diff correction tgl(1) - tgl(0) over OROC3 vs time", + "varexp": "TimeGain.diffCorrectionTgl[0][3]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "diff correction tgl(1) - tgl(0):time" + }, + { + "name": "DiffCorrectionTgl_QMax_AllStacks_Trend", + "title": "QMax diff correction tgl(1) - tgl(0) over all stacks vs time", + "varexp": "TimeGain.diffCorrectionTgl[0][4]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "diff correction tgl(1) - tgl(0):time" + }, + { + "name": "DiffCorrectionTgl_QTot_IROC_Trend", + "title": "QTot diff correction tgl(1) - tgl(0) over IROC vs time", + "varexp": "TimeGain.diffCorrectionTgl[1][0]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "diff correction tgl(1) - tgl(0):time" + }, + { + "name": "DiffCorrectionTgl_QTot_OROC1_Trend", + "title": "QTot diff correction tgl(1) - tgl(0) over OROC1 vs time", + "varexp": "TimeGain.diffCorrectionTgl[1][1]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "diff correction tgl(1) - tgl(0):time" + }, + { + "name": "DiffCorrectionTgl_QTot_OROC2_Trend", + "title": "QTot diff correction tgl(1) - tgl(0) over OROC2 vs time", + "varexp": "TimeGain.diffCorrectionTgl[1][2]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "diff correction tgl(1) - tgl(0):time" + }, + { + "name": "DiffCorrectionTgl_QTot_OROC3_Trend", + "title": "QTot diff correction tgl(1) - tgl(0) over OROC3 vs time", + "varexp": "TimeGain.diffCorrectionTgl[1][3]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "diff correction tgl(1) - tgl(0):time" + }, + { + "name": "DiffCorrectionTgl_QTot_AllStacks_Trend", + "title": "QTot diff correction tgl(1) - tgl(0) over all stacks vs time", + "varexp": "TimeGain.diffCorrectionTgl[1][4]:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "diff correction tgl(1) - tgl(0):time" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "foreachobject:ccdb:TPC/Calib/TimeGain/" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } + } + \ No newline at end of file diff --git a/Modules/TPC/run/tpcQCTrackClusters.json b/Modules/TPC/run/tpcQCTrackClusters.json new file mode 100644 index 0000000000..edef559250 --- /dev/null +++ b/Modules/TPC/run/tpcQCTrackClusters.json @@ -0,0 +1,45 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "TrackClusters": { + "active": "true", + "className": "o2::quality_control_modules::tpc::TrackClusters", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "200", + "dataSource": { + "type": "direct", + "query": "inputTracks:TPC/TRACKS/0;inputClusters:TPC/CLUSTERNATIVE/0;inputClusRefs:TPC/CLUSREFS/0" + }, + "taskParameters": { + "cutAbsEta": "1.", "cutMinNCluster": "60", "cutMindEdxTot": "20.", + "seed": "0", "samplingFraction": "1" + }, + "location": "remote" + } + } + }, + "dataSamplingPolicies": [ + + ] +} diff --git a/Modules/TPC/run/tpcQCTrackingFromExternal_direct.json b/Modules/TPC/run/tpcQCTrackingFromExternal_direct.json new file mode 100644 index 0000000000..b343e0917d --- /dev/null +++ b/Modules/TPC/run/tpcQCTrackingFromExternal_direct.json @@ -0,0 +1,38 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + }, + "externalTasks": { + "TPCTrackingQA": { + "active": "true", + "query": "TPCTrackingQA:TPC/TRACKINGQA/0", "": "Use the task name as binding (encouraged)" + } + }, + "checks": { + } + }, + "dataSamplingPolicies": [ + ] +} diff --git a/Modules/TPC/run/tpcQCTracking_direct.json b/Modules/TPC/run/tpcQCTracking_direct.json new file mode 100644 index 0000000000..62b9d35fe3 --- /dev/null +++ b/Modules/TPC/run/tpcQCTracking_direct.json @@ -0,0 +1,48 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "TPCTrackingQA": { + "active": "true", + "className": "o2::quality_control_modules::tpc::Tracking", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "direct", + "query": "inputTracks:TPC/TRACKS/0;inputTrackLabels:TPC/TRACKSMCLBL/0;inputClusRefs:TPC/CLUSREFS/0;inputClusters:TPC/CLUSTERNATIVE/0;inputClusterLabels:TPC/CLNATIVEMCLBL/0" + }, + "taskParameters": { + "myOwnKey": "myOwnValue" + }, + "location": "remote" + } + }, + "checks": { + } + }, + "dataSamplingPolicies": [ + + ] +} diff --git a/Modules/TPC/run/tpcQCTracks_Generic.json b/Modules/TPC/run/tpcQCTracks_Generic.json new file mode 100644 index 0000000000..afc36561a2 --- /dev/null +++ b/Modules/TPC/run/tpcQCTracks_Generic.json @@ -0,0 +1,87 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "Tracks": { + "active": "true", + "className": "o2::quality_control_modules::tpc::Tracks", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query": "inputTracks:TPC/TRACKS/0" + }, + "taskParameters": { + "myOwnKey": "myOwnValue" + }, + "location": "remote" + } + }, + "checks": { + "GenericHistogramCheck": { + "active": "true", + "className": "o2::quality_control_modules::tpc::GenericHistogramCheck", + "moduleName": "QcTPC", + "policy": "OnEachSeparately", + "detectorName": "TPC", + "dataSource": [ + { + "type": "Task", + "name": "Tracks", + "Noneed": ["a"], + "MOs": [ + "h2DEtaPhiBeforeCuts", + "hEtaRatio_copy_copy", + "hNClustersBeforeCuts", + "hPhiAside", + "hEtaBeforeCuts", + "h2DNClustersPtBeforeCuts", + "h2DQOverPtPhiCside", + "h2DQOverPtPhiAside", + "h2DNClustersPt", + "h2DNClustersPhiCside", + "h2DNClustersEtaBeforeCuts", + "hPtBeforeCuts" + ] + } + ], + + "stopTrigger_comment": [ + "Parameters are needed to choose if check should be performed only as last point comparison to average or average comparison to expected value." + ], + "checkParameters": { + "ExpectedValueX": "3.14", + "RangeX": "0.006", + "ExpectedValueY": "0", + "RangeY": "0.006", + "checks": "StdDev, Range", + "axis": "X,Y", + "MetadataComment": "TestComment" + } + } + } + }, + "dataSamplingPolicies": [] +} diff --git a/Modules/TPC/run/tpcQCTracks_direct.json b/Modules/TPC/run/tpcQCTracks_direct.json new file mode 100644 index 0000000000..93c65dcf74 --- /dev/null +++ b/Modules/TPC/run/tpcQCTracks_direct.json @@ -0,0 +1,58 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "Tracks": { + "active": "true", + "className": "o2::quality_control_modules::tpc::Tracks", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query": "inputTracks:TPC/TRACKS/0" + }, + "taskParameters": { + "cutAbsEta": "1.", "cutMinNCluster": "60", "cutMindEdxTot": "20." + }, + "location": "remote" + } + }, + "checks": { + "QcCheckTracks": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TPC", + "dataSource": [{ + "type": "Task", + "name": "Tracks", + "MOs": ["example"] + }] + } + } + }, + "dataSamplingPolicies": [ + + ] +} diff --git a/Modules/TPC/run/tpcQCTracks_sampled.json b/Modules/TPC/run/tpcQCTracks_sampled.json new file mode 100644 index 0000000000..a246300343 --- /dev/null +++ b/Modules/TPC/run/tpcQCTracks_sampled.json @@ -0,0 +1,91 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "560401" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + } + }, + "tasks": { + "Tracks": { + "active": "true", + "className": "o2::quality_control_modules::tpc::Tracks", + "moduleName": "QcTPC", + "detectorName": "TPC", + "cycleDurationSeconds": "10", + "dataSource_comment": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSources": [ + { "type": "dataSamplingPolicy", "name": "tpc-tracks" }, + { + "type": "direct", + "query": "meanvertex:GLO/MEANVERTEX/0?lifetime=condition&ccdb-path=GLO/Calib/MeanVertex" + } + ], + "taskParameters": { + "cutAbsEta": "1.", + "cutMinNCluster": "60", + "cutMindEdxTot": "20.", + "usePVfromCCDB": "true" + }, + "grpGeomRequest": { + "geomRequest": "None", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "true", + "askMatLUT": "true", + "askTime": "false", + "askOnceAllButField": "true", + "needPropagatorD": "false" + }, + "location": "remote" + } + }, + "checks": { + "QcCheckTracksSampled": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TPC", + "dataSource": [ + { + "type": "Task", + "name": "Tracks", + "MOs": ["example"] + } + ] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "tpc-tracks", + "active": "true", + "machines": [], + "query": "inputTracks:TPC/TRACKS/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.9", + "seed": "1234" + } + ], + "blocking": "false" + } + ] +} diff --git a/Modules/TPC/run/tpcQCTrending.json b/Modules/TPC/run/tpcQCTrending.json new file mode 100644 index 0000000000..63b5756093 --- /dev/null +++ b/Modules/TPC/run/tpcQCTrending.json @@ -0,0 +1,318 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "PID_Trending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/PID", + "names": [ "hNClusters", "hdEdxTot", "hdEdxMax", "hTgl", "hSnp", "hdEdxEles", "hdEdxMips" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + }, + { + "type": "repository", + "path": "TPC/MO/PID", + "names": [ "hdEdxVsncls", "hdEdxVsp", "hdEdxVsPhiMipsAside", "hdEdxVsPhiMipsCside", "hdEdxVsPhiElesAside", "hdEdxVsPhiElesCside" ], + "reductorName": "o2::quality_control_modules::common::TH2Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "hNClusters_StatMean_Trend", + "title": "Mean trend of the number of TPC clusters", + "varexp": "hNClusters.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hNClusters.stddev/(sqrt(hNClusters.entries)))" + }, + { + "name": "hdEdxTot_StatMean_Trend", + "title": "Mean trend of dEdxTot", + "varexp": "hdEdxTot.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hdEdxTot.stddev/(sqrt(hdEdxTot.entries)))" + }, + { + "name": "hdEdxMax_StatMean_Trend", + "title": "Mean trend of dEdxMax", + "varexp": "hdEdxMax.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hdEdxMax.stddev/(sqrt(hdEdxMax.entries)))" + }, + { + "name": "hTgl_StatMean_Trend", + "title": "Mean trend of tan(#lambda)", + "varexp": "hTgl.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hTgl.stddev/(sqrt(hTgl.entries)))" + }, + { + "name": "hSnp_StatMean_Trend", + "title": "Mean trend of sin(p)", + "varexp": "hSnp.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hSnp.stddev/(sqrt(hSnp.entries)))" + }, + { + "name": "hdEdxEles_StatMean_Trend", + "title": "Mean trend of dEdx for electrons", + "varexp": "hdEdxEles.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hdEdxEles.stddev/(sqrt(hdEdxEles.entries)))" + }, + { + "name": "hdEdxEles_StatStddev_Trend", + "title": "Stddev trend of dEdx for electrons", + "varexp": "hdEdxEles.stddev:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxMips_StatMean_Trend", + "title": "Mean trend of dEdx for MIPS", + "varexp": "hdEdxMips.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hdEdxMips.stddev/(sqrt(hdEdxMips.entries)))" + }, + { + "name": "hdEdxMips_StatStddev_Trend", + "title": "Stddev trend of dEdx for MIPS", + "varexp": "hdEdxMips.stddev:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxElesMips_Separ_Trend", + "title": "Separation trend of dEdx between electrons and MIPS", + "varexp": "(hdEdxEles.mean-hdEdxMips.mean):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxElesMips_SeparPower_Trend", + "title": "Separation power trend of dEdx between electrons and MIPS", + "varexp": "((hdEdxEles.mean-hdEdxMips.mean)/(0.5*(hdEdxEles.stddev+hdEdxMips.stddev))):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsncls_StatMean_Trend_dEdx", + "title": "Trend mean dEdx from dEdx vs Number of Clusters", + "varexp": "(hdEdxVsncls.sumwy/hdEdxVsncls.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsp_StatMean_Trend_p", + "title": "Trend mean p from dEdx vs Momentum", + "varexp": "(hdEdxVsp.sumwx/hdEdxVsp.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsPhiMipsAside_StatMean_Trend_dEdx", + "title": "Trend mean dEdx from hdEdxVsPhiMipsAside", + "varexp": "(hdEdxVsPhiMipsAside.sumwy/hdEdxVsPhiMipsAside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsPhiMipsCside_StatMean_Trend_dEdx", + "title": "Trend mean dEdx from hdEdxVsPhiMipsCside", + "varexp": "(hdEdxVsPhiMipsCside.sumwy/hdEdxVsPhiMipsCside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsPhiElesAside_StatMean_Trend_dEdx", + "title": "Trend mean dEdx from hdEdxVsPhiElesAside", + "varexp": "(hdEdxVsPhiElesAside.sumwy/hdEdxVsPhiElesAside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "hdEdxVsPhiElesCside_StatMean_Trend_dEdx", + "title": "Trend mean dEdx from hdEdxVsPhiElesCside", + "varexp": "(hdEdxVsPhiElesCside.sumwy/hdEdxVsPhiElesCside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "30 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "Tracks_Trending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "hNClustersBeforeCuts", "hNClustersAfterCuts", "hEta", "hPt", "hSign", "hPtPos", "hPtNeg" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + }, + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "h2DNClustersPhiAside", "h2DNClustersPhiCside" ], + "reductorName": "o2::quality_control_modules::common::TH2Reductor", + "moduleName": "QcCommon" + } + ], + "plots": [ + { + "name": "hNClustersBeforeCuts_StatMean_Trend", + "title": "Mean trend of the number of TPC clusters (Before the cuts)", + "varexp": "hNClustersBeforeCuts.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hNClustersBeforeCuts.stddev)/(sqrt(hNClustersBeforeCuts.entries))" + }, + { + "name": "hNClustersAfterCuts_StatMean_Trend", + "title": "Mean trend of the number of TPC clusters (After the cuts)", + "varexp": "hNClustersAfterCuts.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hNClustersAfterCuts.stddev)/(sqrt(hNClustersAfterCuts.entries))" + }, + { + "name": "hEta_StatMean_Trend", + "title": "Mean trend of the pseudorapidity", + "varexp": "hEta.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hEta.stddev)/(sqrt(hEta.entries))" + }, + { + "name": "hPt_StatMean_Trend", + "title": "Mean trend of the transverse momentum", + "varexp": "hPt.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hPt.stddev)/(sqrt(hPt.entries))" + }, + { + "name": "hSign_StatMean_Trend", + "title": "Mean trend of the sign of electric charge", + "varexp": "hSign.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hSign.stddev)/(sqrt(hSign.entries))" + }, + { + "name": "hPtNeg_StatMean_Trend", + "title": "Mean trend of the transverse momentum of negative charges", + "varexp": "hPtNeg.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hPtNeg.stddev)/(sqrt(hPtNeg.entries))" + }, + { + "name": "hPtPos_StatMean_Trend", + "title": "Mean trend of the transverse momentum of positive charges", + "varexp": "hPtPos.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "0:(hPtPos.stddev)/(sqrt(hPtPos.entries))" + }, + { + "name": "h2DNClustersPhiAside_StatMean_Trend_Phi", + "title": "Mean trend of Phi from h2DNClustersPhiAside", + "varexp": "(h2DNClustersPhiAside.sumwx/h2DNClustersPhiAside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "h2DNClustersPhiAside_StatMean_Trend_Nclusters", + "title": "Mean trend of the number of clusters from h2DNClustersPhiAside", + "varexp": "(h2DNClustersPhiAside.sumwy/h2DNClustersPhiAside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "h2DNClustersPhiCside_StatMean_Trend_Phi", + "title": "Mean trend of Phi from h2DNClustersPhiCside", + "varexp": "(h2DNClustersPhiCside.sumwx/h2DNClustersPhiCside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "h2DNClustersPhiCside_StatMean_Trend_Nclusters", + "title": "Mean trend of the number of clusters from h2DNClustersPhiCside", + "varexp": "(h2DNClustersPhiCside.sumwy/h2DNClustersPhiCside.sumw):time", + "selection": "", + "option": "*L", + "graphErrors": "" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "30 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCTrending_canvas.json b/Modules/TPC/run/tpcQCTrending_canvas.json new file mode 100644 index 0000000000..00d6d1d6f1 --- /dev/null +++ b/Modules/TPC/run/tpcQCTrending_canvas.json @@ -0,0 +1,106 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "", + "type": "" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "TestTPCTH1Canvas": { + "active": "true", + "className": "o2::quality_control_modules::tpc::TrendingTaskTPC", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Clusters", + "names": [ "c_ROCs_N_Clusters_1D" ], + "reductorName": "o2::quality_control_modules::tpc::TH1ReductorTPC", + "axisDivision": [ [ "0.0", "100.0" ] ], + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "clusters_StatMean_Trend", + "title": "MeanX of N_Clusters", + "varexp": "c_ROCs_N_Clusters_1D.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.5", + "graphYRange": "0.0:10.0" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "TestIDCScaleFactorTrending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QcTPC", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/IDCs", + "names": [ "c_sides_IDC0_scale" ], + "reductorName": "o2::quality_control_modules::tpc::IDCScaleReductor", + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "IDC0_ScaleFactor_ASide_Trend", + "title": "IDC 0 Scale Factor - A Side", + "varexp": "c_sides_IDC0_scale.IDCScaleFactorASide:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "IDC0 Scale Factor - A Side:time" + }, + { + "name": "IDC0_ScaleFactor_CSide_Trend", + "title": "IDC 0 Scale Factor - C Side", + "varexp": "c_sides_IDC0_scale.IDCScaleFactorCSide:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "IDC0 Scale Factor - C Side:time" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} \ No newline at end of file diff --git a/Modules/TPC/run/tpcQCTrending_laserCalib.json b/Modules/TPC/run/tpcQCTrending_laserCalib.json new file mode 100644 index 0000000000..b8f6420950 --- /dev/null +++ b/Modules/TPC/run/tpcQCTrending_laserCalib.json @@ -0,0 +1,126 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "LaserCalibration_Trending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/LaserCalibration", + "name": "Calib_Values", + "reductorName": "o2::quality_control_modules::tpc::LtrCalibReductor", + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "processedTFs_Trend", + "title": "Trend of the number of processed TFs", + "varexp": "Calib_Values.processedTFs:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "dvCorrectionA_Trend", + "title": "Trend of the drift velocity correction factor (A side)", + "varexp": "Calib_Values.dvCorrectionA:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "dvCorrectionC_Trend", + "title": "Trend of the drift velocity correction factor (C side)", + "varexp": "Calib_Values.dvCorrectionC:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "dvCorrection_Trend", + "title": "Trend of the drift velocity correction factor", + "varexp": "Calib_Values.dvCorrection:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "t0A_Trend", + "title": "Trend of the laser trigger t0 offset (A side)", + "varexp": "Calib_Values.t0A:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "t0C_Trend", + "title": "Trend of the laser trigger t0 offset (C side)", + "varexp": "Calib_Values.t0C:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "nTracksA_Trend", + "title": "Trend of the number of tracks (A side)", + "varexp": "Calib_Values.nTracksA:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "nTracksC_Trend", + "title": "Trend of the number of tracks (C side)", + "varexp": "Calib_Values.nTracksC:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "dvAbsolute_Trend", + "title": "Trend of the dvAbsolute", + "varexp": "Calib_Values.dvAbsolute:time", + "selection": "", + "option": "*L", + "graphErrors": "" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "30 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCTrending_separationpower.json b/Modules/TPC/run/tpcQCTrending_separationpower.json new file mode 100644 index 0000000000..80da763323 --- /dev/null +++ b/Modules/TPC/run/tpcQCTrending_separationpower.json @@ -0,0 +1,87 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "2" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "PID_SepPower_Trend": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "resumeTrend": "false", + "detectorName": "TPC", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/PID", + "name": "pSeparationPower", + "reductorName": "o2::quality_control_modules::tpc::SeparationPowerReductor", + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "sepPower_MeanPi", + "title": "Trend of Separation Power Mean Pion", + "varexp": "pSeparationPower.meanPi:time", + "selection": "", + "option": "*L", + "graphErrors": "0:sigmaPi" + }, + { + "name": "sepPower_El", + "title": "Trend of Separation Power Mean Electron", + "varexp": "pSeparationPower.meanEl:time", + "selection": "", + "option": "*L", + "graphErrors": "0:sigmaEl" + }, + { + "name": "sepPower_SeparationPower", + "title": "Trend of SeparationPower", + "varexp": "pSeparationPower.separationPower:time", + "selection": "", + "option": "*L", + "graphErrors": "" + }, + { + "name": "sepPower_ChiSquareOverNDF", + "title": "Trend of SeparationPower ChiSquare/NDF fit", + "varexp": "pSeparationPower.chiSquareOverNdf:time", + "selection": "", + "option": "*L", + "graphErrors": "" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "foreachlatest:ccdb:qc/TPC/MO/PID/pSeparationPower" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCTrending_slicer.json b/Modules/TPC/run/tpcQCTrending_slicer.json new file mode 100644 index 0000000000..6ce835c582 --- /dev/null +++ b/Modules/TPC/run/tpcQCTrending_slicer.json @@ -0,0 +1,337 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "", + "type": "" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "postprocessing": { + "TestTPCTH1Slicer": { + "active": "true", + "className": "o2::quality_control_modules::tpc::TrendingTaskTPC", + "moduleName": "QcTPC", + "detectorName": "TPC", + "producePlotsOnUpdate": "true", + "resumeTrend": "true", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "hEtaRatio" ], + "reductorName": "o2::quality_control_modules::tpc::TH1ReductorTPC", + "axisDivision": [ [ "-2.0", "-1.0", "0.0", "1.0", "2.0" ] ], + "moduleName": "QcTPC" + }, + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "hEta" ], + "reductorName": "o2::quality_control_modules::tpc::TH1ReductorTPC", + "axisDivision": [[ "-1.0", "0.0", "1.0" ]], + "moduleName": "QcTPC" + }, + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "hPhiAside", "hPhiAsideRatio", "hPhiCside", "hPhiCsideRatio" ], + "reductorName": "o2::quality_control_modules::tpc::TH1ReductorTPC", + "axisDivision": [[ "0.0", "0.349066", "0.698132", "1.0472", "1.39626", "1.74533", "2.0944", "2.44346", "2.79253", "3.14159", "3.49066", "3.83972", "4.18879", "4.53786", "4.88692", "5.23599", "5.58505", "5.93412", "6.28319" ]], + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "hEtaRatio_StatMean_Trend", + "title": "Mean of the #eta ratio", + "varexp": "hEtaRatio.meanX:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanX:0.5", + "graphYRange": "-2.0:2.0", + "graphXRange": "", + "graphAxisLabel": "Mean #eta:time" + }, + { + "name": "hEtaRatio_StatMeanY_Trend", + "title": "Mean Y of the #eta ratio", + "varexp": "hEtaRatio.meanY:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanY:0.5", + "graphYRange": "-0.1:2.0", + "graphXRange": "", + "graphAxisLabel": "Mean #eta ratio:time" + }, + { + "name": "hEtaRatio_StatMeanY_Trend_Slices", + "title": "Mean Y of the #eta ratio vs slices", + "varexp": "hEtaRatio.meanY:slices", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanY:0", + "graphYRange": "-0.1:2.0", + "graphXRange": "-2.0:2.0", + "graphAxisLabel": "Mean #eta:#eta" + }, + { + "name": "hEtaRatio_StatMeanY_Trend_MultiGraph", + "title": "Mean Y of the #eta ratio vs time for slices in #eta (x-axis)", + "varexp": "hEtaRatio.meanY:multigraphtime", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanY:0", + "graphYRange": "-0.1:2.0", + "graphXRange": "", + "graphAxisLabel": "Mean #eta:time" + }, + { + "name": "hEta_StatMean_Trend", + "title": "Mean of #eta", + "varexp": "hEta.meanX:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanX:0.5", + "graphYRange": "-1.0:1.0", + "graphXRange": "", + "graphAxisLabel": "Mean #eta:time" + }, + { + "name": "hPhiAsideRatio_StatMeanY_Trend", + "title": "Mean of #phi ratio - A side", + "varexp": "hPhiAsideRatio.meanY:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanY:0.0", + "graphYRange": "0.0:1.5", + "graphXRange": "", + "graphAxisLabel": "Mean #phi ratio A side:time" + }, + { + "name": "hPhiAsideRatio_StatMeanY_Trend_Slices", + "title": "Mean of #phi ratio - A side", + "varexp": "hPhiAsideRatio.meanY:slices", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:0.0", + "graphYRange": "0.0:1.5", + "graphXRange": "-0.5:6.5", + "graphAxisLabel": "Mean #phi ratio A side:#phi" + }, + { + "name": "hPhiAsideRatio_StatMeanY_Trend_MultiGraph", + "title": "Mean of #phi ratio - A side - slices along #phi (x-axis)", + "varexp": "hPhiAsideRatio.meanY:multigraphtime", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:0.0", + "graphYRange": "0.0:1.5", + "graphXRange": "", + "graphAxisLabel": "Mean #phi ratio A side:time" + }, + { + "name": "hPhiCsideRatio_StatMeanY_Trend", + "title": "Mean of #phi ratio - C side", + "varexp": "hPhiCsideRatio.meanY:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:0.", + "graphYRange": "0.0:1.5", + "graphXRange": "", + "graphAxisLabel": "Mean #phi ratio C side:time" + }, + { + "name": "hPhiCsideRatio_StatMeanY_Trend_Slices", + "title": "Mean of #phi ratio - C side", + "varexp": "hPhiCsideRatio.meanY:slices", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:0.0", + "graphYRange": "0.0:1.5", + "graphXRange": "-0.5:6.5", + "graphAxisLabel": "Mean #phi ratio C side:#phi" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "TestTPCTH2Slicer": { + "active": "true", + "className": "o2::quality_control_modules::tpc::TrendingTaskTPC", + "moduleName": "QcTPC", + "detectorName": "TPC", + "producePlotsOnUpdate": "true", + "resumeTrend": "true", + "dataSources": [ + { + "type": "repository", + "path": "TPC/MO/Tracks", + "names": [ "h2DEtaPhi" ], + "reductorName": "o2::quality_control_modules::tpc::TH2ReductorTPC", + "axisDivision": [ [ "0.0", "0.349066", "0.698132", "1.0472", "1.39626", "1.74533", "2.0944", "2.44346", "2.79253", "3.14159", "3.49066", "3.83972", "4.18879", "4.53786", "4.88692", "5.23599", "5.58505", "5.93412", "6.28319" ] , [ "-1.0", "0.0", "1.0" ] ], + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "h2DEtaPhi_StatMeanX_Trend", + "title": "Mean of #phi for #eta-#phi slices", + "varexp": "h2DEtaPhi.meanX:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanX:0.5", + "graphYRange": "-2.0:8.0", + "graphXRange": "", + "graphAxisLabel": "Mean #phi:time" + }, + { + "name": "h2DEtaPhi_StatMeanY_Trend", + "title": "Mean of #eta for #eta-#phi slices", + "varexp": "h2DEtaPhi.meanY:time", + "selection": "", + "option": "*LE", + "graphErrors": "errMeanY:0.5", + "graphYRange": "-2.0:2.0", + "graphXRange": "", + "graphAxisLabel": "Mean #eta:time" + }, + { + "name": "h2DEtaPhi_StatMeanY_Trend_Slices", + "title": "Mean of the #eta vs slices in #eta and #phi", + "varexp": "h2DEtaPhi.meanY:slices2D", + "selection": "", + "option": "colz text", + "graphErrors": "errMeanY:0.5", + "graphYRange": "-1.0:1.0", + "graphXRange": "", + "graphAxisLabel": "#eta:#phi" + }, + { + "name": "h2DEtaPhi_StatMeanX_Trend_Slices", + "title": "Mean of the #phi vs slices in #eta and #phi", + "varexp": "h2DEtaPhi.meanX:slices2D", + "selection": "", + "option": "colz text", + "graphErrors": "errMeanX:0.5", + "graphYRange": "0.0:6.5", + "graphXRange": "", + "graphAxisLabel": "#eta:#phi" + }, + { + "name": "h2DEtaPhi_NumberEntries_Trend_Slices", + "title": "Number of entries per slices in #eta and #phi", + "varexp": "h2DEtaPhi.entries:slices2D", + "selection": "", + "option": "colz text", + "graphErrors": "", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "#eta:#phi" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + }, + "TestQuality": { + "active": "true", + "className": "o2::quality_control_modules::tpc::TrendingTaskTPC", + "moduleName": "QcTPC", + "detectorName": "TPC", + "producePlotsOnUpdate": "true", + "resumeTrend": "true", + "dataSources": [ + { + "type": "repository-quality", + "path": "TPC/QO/CheckOfTrack_Trending/Tracks_Trending", + "names": [ "hEta_StatMean_Trend", "hNClustersAfterCuts_StatMean_Trend" ], + "reductorName": "o2::quality_control_modules::tpc::QualityReductorTPC", + "axisDivision": [ ], + "moduleName": "QcTPC" + }, + { + "type": "repository-quality", + "path": "TPC/QO/CheckOfPt/Trending", + "names": [ "hPt_Trend" ], + "reductorName": "o2::quality_control_modules::tpc::QualityReductorTPC", + "axisDivision": [ ], + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "hEta_StatMean_Quality_Trend", + "title": "Quality trending", + "varexp": "hEta_StatMean_Trend.qualitylevel:time", + "selection": "", + "option": "*L", + "graphErrors": "", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "qualitylevel:time" + }, + { + "name": "hhNClustersAfterCuts_StatMean_Quality_Trend", + "title": "Quality trending", + "varexp": "hNClustersAfterCuts_StatMean_Trend.qualitylevel:time", + "selection": "", + "option": "*L", + "graphErrors": "", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "qualitylevel:time" + }, + { + "name": "hPt_Trend_StatMean_Quality_Trend", + "title": "Quality trending", + "varexp": "hPt_Trend.qualitylevel:time", + "selection": "", + "option": "*L", + "graphErrors": "", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "qualitylevel:time" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TPC/run/tpcQCvDriftTrending.json b/Modules/TPC/run/tpcQCvDriftTrending.json new file mode 100644 index 0000000000..f04198a746 --- /dev/null +++ b/Modules/TPC/run/tpcQCvDriftTrending.json @@ -0,0 +1,70 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "", + "type": "", + "start": "", + "end": "" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "postprocessing": { + "periodSeconds": "10" + } + }, + "postprocessing": { + "CalibQC": { + "active": "true", + "resumeTrend": "false", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TPC", + "producePlotsOnUpdate": "true", + "dataSources": [ + { + "type": "condition", + "path": "TPC/Calib/", + "names": [ "VDriftTgl" ], + "reductorName": "o2::quality_control_modules::tpc::VDriftCalibReductor", + "moduleName": "QcTPC" + } + ], + "plots": [ + { + "name": "vDrift_Trending", + "title": "Trend of vDrift over time", + "varexp": "VDriftTgl.vdrift:time", + "selection": "", + "option": "*L", + "graphAxisLabel": "v_Drift:time" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:ccdb:TPC/Calib/VDriftTgl/" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } + } + \ No newline at end of file diff --git a/Modules/TPC/src/AtmosPressureReductor.cxx b/Modules/TPC/src/AtmosPressureReductor.cxx new file mode 100644 index 0000000000..8f51796658 --- /dev/null +++ b/Modules/TPC/src/AtmosPressureReductor.cxx @@ -0,0 +1,63 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file AtmosPressureReductor.cxx +/// \author Marcel Lesch +/// + +#include "TPC/AtmosPressureReductor.h" +#include "GRPCalibration/GRPDCSDPsProcessor.h" +#include "TPC/Utility.h" + +namespace o2::quality_control_modules::tpc +{ + +void* AtmosPressureReductor::getBranchAddress() +{ + return &mStats; +} + +const char* AtmosPressureReductor::getBranchLeafList() +{ + return "cavernPressure1/F:errCavernPressure1:cavernPressure2:errCavernPressure2:surfacePressure:errSurfacePressure"; +} + +bool AtmosPressureReductor::update(ConditionRetriever& retriever) +{ + if (auto env = retriever.retrieve()) { + std::vector pressureValues; + + // Cavern pressure 1 + for ([[maybe_unused]] const auto& [time, p] : env->mEnvVars["CavernAtmosPressure"]) { + pressureValues.emplace_back((float)p); + } + calcMeanAndStddev(pressureValues, mStats.cavernPressure1, mStats.errCavernPressure1); + pressureValues.clear(); + + // Cavern pressure 2 + for ([[maybe_unused]] const auto& [time, p] : env->mEnvVars["CavernAtmosPressure2"]) { + pressureValues.emplace_back((float)p); + } + calcMeanAndStddev(pressureValues, mStats.cavernPressure2, mStats.errCavernPressure2); + pressureValues.clear(); + + // Surface pressure + for ([[maybe_unused]] const auto& [time, p] : env->mEnvVars["SurfaceAtmosPressure"]) { + pressureValues.emplace_back((float)p); + } + calcMeanAndStddev(pressureValues, mStats.surfacePressure, mStats.errSurfacePressure); + return true; + } + return false; +} + +} // namespace o2::quality_control_modules::tpc \ No newline at end of file diff --git a/Modules/TPC/src/CalDetPublisher.cxx b/Modules/TPC/src/CalDetPublisher.cxx new file mode 100644 index 0000000000..0f33388e07 --- /dev/null +++ b/Modules/TPC/src/CalDetPublisher.cxx @@ -0,0 +1,251 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalDetPublisher.cxx +/// \author Thomas Klemenz +/// + +// O2 includes +#if __has_include("TPCBase/Painter.h") +#include "TPCBase/Painter.h" +#include "TPCBase/CDBInterface.h" +#else +#include "TPCBaseRecSim/Painter.h" +#include "TPCBaseRecSim/CDBInterface.h" +#endif +#include "TPCQC/Helpers.h" + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/CalDetPublisher.h" +#include "TPC/Utility.h" + +// root includes +#include "TCanvas.h" +#include "TPaveText.h" + +#include +#include +#include + +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::tpc +{ + +void CalDetPublisher::configure(const boost::property_tree::ptree& config) +{ + o2::tpc::CDBInterface::instance().setURL(config.get("qc.config.conditionDB.url")); + + auto& id = getID(); + for (const auto& output : config.get_child("qc.postprocessing." + id + ".outputCalPadMaps")) { + mOutputListMap.emplace_back(output.second.data()); + } + + for (const auto& output : config.get_child("qc.postprocessing." + id + ".outputCalPads")) { + mOutputList.emplace_back(output.second.data()); + } + + for (const auto& timestamp : config.get_child("qc.postprocessing." + id + ".timestamps")) { + mTimestamps.emplace_back(std::stol(timestamp.second.data())); + } + + std::vector keyVec{}; + std::vector valueVec{}; + for (const auto& data : config.get_child("qc.postprocessing." + id + ".lookupMetaData")) { + mLookupMaps.emplace_back(std::map()); + if (const auto& keys = data.second.get_child_optional("keys"); keys.has_value()) { + for (const auto& key : keys.value()) { + keyVec.emplace_back(key.second.data()); + } + } + if (const auto& values = data.second.get_child_optional("values"); values.has_value()) { + for (const auto& value : values.value()) { + valueVec.emplace_back(value.second.data()); + } + } + auto vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mLookupMaps.back().insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for lookupMetaData are not matching" << ENDM; + } + keyVec.clear(); + valueVec.clear(); + } + + for (const auto& data : config.get_child("qc.postprocessing." + id + ".storeMetaData")) { + mStoreMaps.emplace_back(std::map()); + if (const auto& keys = data.second.get_child_optional("keys"); keys.has_value()) { + for (const auto& key : keys.value()) { + keyVec.emplace_back(key.second.data()); + } + } + if (const auto& values = data.second.get_child_optional("values"); values.has_value()) { + for (const auto& value : values.value()) { + valueVec.emplace_back(value.second.data()); + } + } + auto vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mStoreMaps.back().insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for storeMetaData are not matching" << ENDM; + } + keyVec.clear(); + valueVec.clear(); + } + + if ((mTimestamps.size() != mOutputList.size()) && (mTimestamps.size() > 0)) { + ILOG(Error, Support) << "You need to set a timestamp for every CalPad object or none at all" << ENDM; + } + + if (std::find(mOutputListMap.begin(), mOutputListMap.end(), "PedestalNoise") != mOutputListMap.end()) { + mCheckZSPrereq = true; + } + + for (const auto& entry : config.get_child("qc.postprocessing." + id + ".histogramRanges")) { + for (const auto& type : entry.second) { + for (const auto& value : type.second) { + mRanges[type.first].emplace_back(std::stof(value.second.data())); + } + } + } + + std::string checkZSCalibration = config.get("qc.postprocessing." + id + ".checkZSCalibration.check"); + if (checkZSCalibration == "true" && mCheckZSPrereq == true) { + mCheckZSCalib = true; + mInitRefCalibTimestamp = std::stol(config.get("qc.postprocessing." + id + ".checkZSCalibration.initRefCalibTimestamp")); + } else if (checkZSCalibration == "false") { + mCheckZSCalib = false; + } else if (checkZSCalibration == "true" && mCheckZSPrereq == false) { + ILOG(Error, Support) << "'PedestalNoise' needs to be in the 'outputCalPadMaps' to make the Zero Suppression calibration check" << ENDM; + } else { + ILOG(Error, Support) << "No valid value for 'checkZSCalibration.check' set. Has to be 'true' or 'false'" << ENDM; + } + + o2::tpc::CDBInterface::instance().setURL(config.get("qc.postprocessing." + id + ".dataSourceURL")); +} + +void CalDetPublisher::initialize(Trigger, framework::ServiceRegistryRef) +{ + auto calDetIter = 0; + for (const auto& type : mOutputListMap) { + auto& calMap = o2::tpc::CDBInterface::instance().getSpecificObjectFromCDB>>(fmt::format("TPC/Calib/{}", type).data(), + -1, + std::map()); + for (const auto& item : calMap) { + mCalDetCanvasVec.emplace_back(std::vector>()); + addAndPublish(getObjectsManager(), + mCalDetCanvasVec.back(), + { fmt::format("c_Sides_{}", item.second.getName()).data(), + fmt::format("c_ROCs_{}_1D", item.second.getName()).data(), + fmt::format("c_ROCs_{}_2D", item.second.getName()).data() }, + mStoreMaps.size() > 1 ? mStoreMaps.at(calDetIter) : mStoreMaps.at(0)); + } + calDetIter++; + } + + for (const auto& type : mOutputList) { + mCalDetCanvasVec.emplace_back(std::vector>()); + addAndPublish(getObjectsManager(), + mCalDetCanvasVec.back(), + { fmt::format("c_Sides_{}", type).data(), + fmt::format("c_ROCs_{}_1D", type).data(), + fmt::format("c_ROCs_{}_2D", type).data() }, + mStoreMaps.size() > 1 ? mStoreMaps.at(calDetIter) : mStoreMaps.at(0)); + calDetIter++; + } + + if (mCheckZSCalib) { + auto& calMap = o2::tpc::CDBInterface::instance().getSpecificObjectFromCDB>>("TPC/Calib/PedestalNoise", + mInitRefCalibTimestamp, + std::map()); + mRefPedestal = std::make_unique>(calMap["Pedestals"]); + mRefNoise = std::make_unique>(calMap["Noise"]); + + mNewZSCalibMsg = new TPaveText(0.5, 0.5, 0.9, 0.75, "NDC"); + mNewZSCalibMsg->AddText("Upload new calib data for ZS!"); + mNewZSCalibMsg->SetFillColor(kRed); + } +} + +void CalDetPublisher::update(Trigger t, framework::ServiceRegistryRef) +{ + ILOG(Info, Support) << "Trigger type is: " << t.triggerType << ", the timestamp is " << t.timestamp << ENDM; + + auto calDetIter = 0; + auto calVecIter = 0; + for (const auto& type : mOutputListMap) { + auto& calMap = o2::tpc::CDBInterface::instance().getSpecificObjectFromCDB>>( + fmt::format("TPC/Calib/{}", type).data(), + mTimestamps.size() > 0 ? mTimestamps.at(calVecIter) : -1, + mLookupMaps.size() > 1 ? mLookupMaps.at(calVecIter) : mLookupMaps.at(0)); + for (const auto& item : calMap) { + auto vecPtr = toVector(mCalDetCanvasVec.at(calDetIter)); + o2::tpc::painter::makeSummaryCanvases(item.second, int(mRanges[item.second.getName()].at(0)), mRanges[item.second.getName()].at(1), mRanges[item.second.getName()].at(2), false, &vecPtr); + calDetIter++; + } + calVecIter++; + + if (mCheckZSCalib) { + if (type == std::string("PedestalNoise")) { + if (o2::tpc::qc::helpers::newZSCalib(*(mRefPedestal.get()), *(mRefNoise.get()), calMap["Pedestals"])) { + mRefPedestal.reset(nullptr); + mRefPedestal = std::make_unique>(calMap["Pedestals"]); + ILOG(Info, Support) << "New reference pedestal file set!" << ENDM; + mRefNoise.reset(nullptr); + mRefNoise = std::make_unique>(calMap["Noise"]); + ILOG(Info, Support) << "New reference noise file set!" << ENDM; + + for (const auto& canvasVec : mCalDetCanvasVec) { + for (auto& canvas : canvasVec) { + if (canvas->GetName() == std::string("c_Sides_Pedestals")) { + canvas->cd(3); + mNewZSCalibMsg->Draw(); + } + } + } + } + } + } + } + + for (const auto& type : mOutputList) { + auto& calDet = o2::tpc::CDBInterface::instance().getSpecificObjectFromCDB>(fmt::format("TPC/Calib/{}", type).data(), + mTimestamps.size() > 0 ? mTimestamps.at(calDetIter) : -1, + mLookupMaps.size() > 1 ? mLookupMaps.at(calDetIter) : mLookupMaps.at(0)); + auto vecPtr = toVector(mCalDetCanvasVec.at(calDetIter)); + o2::tpc::painter::makeSummaryCanvases(calDet, int(mRanges[calDet.getName()].at(0)), mRanges[calDet.getName()].at(1), mRanges[calDet.getName()].at(2), false, &vecPtr); + calDetIter++; + } +} + +void CalDetPublisher::finalize(Trigger t, framework::ServiceRegistryRef) +{ + for (const auto& calDetCanvasVec : mCalDetCanvasVec) { + for (const auto& canvas : calDetCanvasVec) { + getObjectsManager()->stopPublishing(canvas.get()); + } + } + mCalDetCanvasVec.clear(); +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/CalPadClusterReductor.cxx b/Modules/TPC/src/CalPadClusterReductor.cxx new file mode 100644 index 0000000000..5036a06e7c --- /dev/null +++ b/Modules/TPC/src/CalPadClusterReductor.cxx @@ -0,0 +1,110 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// file CalPadClusterReductor.cxx +// author Marcel Lesch +// + +#include "TPC/CalPadClusterReductor.h" +#include "TPC/ClustersData.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +void* CalPadClusterReductor::getBranchAddress() +{ + return &mCalPad; +} // void* CalPadClusterReductor::getBranchAddress() + +const char* CalPadClusterReductor::getBranchLeafList() +{ + return "NClusters[4][72]/F:QMax[4][72]:QTot[4][72]:SigmaTime[4][72]:SigmaPad[4][72]:TimeBin[4][72]"; +} // const char* CalPadClusterReductor::getBranchLeafList() + +void CalPadClusterReductor::update(TObject* obj) +{ + if (obj) { + auto qcClusters = dynamic_cast(obj); + + if (qcClusters) { + auto& clusters = qcClusters->getClusters(); + + for (int iType = 0; iType < 6; iType++) { + + auto& calDet = GetCalPad(clusters, iType); + + for (size_t iROC = 0; iROC < calDet.getData().size(); ++iROC) { + auto& calArray = calDet.getCalArray(iROC); + auto& data = calArray.getData(); + + // Remove pads which are empty from any calculations + data.erase(std::remove_if(data.begin(), data.end(), [](const auto& value) { return (std::isnan(value) || value <= 0); }), data.end()); + + Float_t(*Quantity)[72] = getArrayPointer(iType); + if ((*Quantity)[0]) { + Quantity[0][iROC] = static_cast(data.size()); + Quantity[1][iROC] = TMath::Mean(data.begin(), data.end()); + Quantity[2][iROC] = TMath::StdDev(data.begin(), data.end()); + Quantity[3][iROC] = TMath::Median(data.size(), data.data()); + } + } // for (size_t iROC = 0; iROC < calDet.getData().size(); ++iROC) + } // for(int iType = 0; iType < 6; iType++) + } // if (qcClusters) + } // if(obj) +} // void CalPadClusterReductor::update(TObject* obj) + +o2::tpc::CalPad& CalPadClusterReductor::GetCalPad(o2::tpc::qc::Clusters& clusters, int dataType) +{ + if (dataType == 0) { + return clusters.getNClusters(); + } else if (dataType == 1) { + return clusters.getQMax(); + } else if (dataType == 2) { + return clusters.getQTot(); + } else if (dataType == 3) { + return clusters.getSigmaTime(); + } else if (dataType == 4) { + return clusters.getSigmaPad(); + } else if (dataType == 5) { + return clusters.getTimeBin(); + } else { + ILOG(Error, Support) << "Error: Datatype not supported in CalPadClusterReductor" << ENDM; + return clusters.getNClusters(); // return NClusters as dummy. + } +} // o2::tpc::CalPad& CalPadClusterReductor::GetCalPad(o2::tpc::qc::Clusters& clusters, int dataType) + +CalPadClusterReductor::pointer_to_arrays CalPadClusterReductor::getArrayPointer(int dataType) +{ + if (dataType == 0) { + return mCalPad.NClusters; + } else if (dataType == 1) { + return mCalPad.QMax; + } else if (dataType == 2) { + return mCalPad.QTot; + } else if (dataType == 3) { + return mCalPad.SigmaTime; + } else if (dataType == 4) { + return mCalPad.SigmaPad; + } else if (dataType == 5) { + return mCalPad.TimeBin; + } else { + ILOG(Error, Support) << "Error: Datatype not supported in CalPadClusterReductor" << ENDM; + return NULL; + } +} // CalPadClusterReductor::pointer_to_arrays CalPadClusterReductor::getArrayPointer(int dataType) + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/CheckForEmptyPads.cxx b/Modules/TPC/src/CheckForEmptyPads.cxx new file mode 100644 index 0000000000..44220062dd --- /dev/null +++ b/Modules/TPC/src/CheckForEmptyPads.cxx @@ -0,0 +1,302 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckForEmptyPads.cxx +/// \author Laura Serksnyte +/// + +#include "TPC/CheckForEmptyPads.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include "Common/Utils.h" + +// ROOT +#include +#include +#include +#include +#include +#include + +#include + +namespace o2::quality_control_modules::tpc +{ +void CheckForEmptyPads::configure() +{ + mMetadataComment = common::getFromConfig(mCustomParameters, "MetadataComment", ""); + if (auto param = mCustomParameters.find("mediumQualityPercentageOfWorkingPads"); param != mCustomParameters.end()) { + mMediumQualityLimit = std::atof(param->second.c_str()); + } else { + mMediumQualityLimit = 0.7; + ILOG(Warning, Support) << "Chosen check requires mediumQualityPercentageOfWorkingPads which is not given. Setting to default 0.7 ." << ENDM; + } + if (auto param = mCustomParameters.find("badQualityPercentageOfWorkingPads"); param != mCustomParameters.end()) { + mBadQualityLimit = std::atof(param->second.c_str()); + } else { + mBadQualityLimit = 0.3; + ILOG(Warning, Support) << "Chosen check requires badQualityPercentageOfWorkingPads which is not given. Setting to default 0.3 ." << ENDM; + } + if (auto param = mCustomParameters.find("MOsNames2D"); param != mCustomParameters.end()) { + auto temp = param->second.c_str(); + std::istringstream ss(temp); + std::string token; + while (std::getline(ss, token, ',')) { + mMOsToCheck2D.emplace_back(token); + } + } +} + +//______________________________________________________________________________ +Quality CheckForEmptyPads::check(std::map>* moMap) +{ + std::vector checkMessage; + std::string message; + + Quality result = Quality::Null; + for (auto const& moObj : *moMap) { + auto mo = moObj.second; + if (!mo) { + continue; + } + auto moName = mo->getName(); + std::string histName, histNameS; + int padsTotal = 0, padsstart = 1000; + if (auto it = std::find(mMOsToCheck2D.begin(), mMOsToCheck2D.end(), moName); it != mMOsToCheck2D.end()) { + size_t end = moName.find("_2D"); + auto histSubName = moName.substr(7, end - 7); + result = Quality::Good; + auto* canv = dynamic_cast(mo->getObject()); + if (!canv) + continue; + // Check all histograms in the canvas + for (int tpads = 1; tpads <= 72; tpads++) { + const auto padName = fmt::format("{:s}_{:d}", moName, tpads); + const auto histName = fmt::format("h_{:s}_ROC_{:02d}", histSubName, tpads - 1); + TPad* pad = (TPad*)canv->GetListOfPrimitives()->FindObject(padName.data()); + if (!pad) { + mSectorsName.push_back("notitle"); + mSectorsQuality.push_back(Quality::Null); + message = "TPad has no title!"; + checkMessage.push_back(message); + continue; + } + TH2F* h = (TH2F*)pad->GetListOfPrimitives()->FindObject(histName.data()); + if (!h) { + mSectorsName.push_back("notitle"); + mSectorsQuality.push_back(Quality::Null); + message = "THist has no title!"; + checkMessage.push_back(message); + continue; + } + const std::string titleh = h->GetTitle(); + + // check if we are dealing with IROC or OROC + int totalPads = 0; + if (titleh.find("IROC") != std::string::npos) { + totalPads = 5280; + } else if (titleh.find("OROC") != std::string::npos) { + totalPads = 9280; + } else { + return Quality::Null; + } + const int NX = h->GetNbinsX(); + const int NY = h->GetNbinsY(); + // Check how many of the pads are non zero + int sum = 0; + for (int i = 1; i <= NX; i++) { + for (int j = 1; j <= NY; j++) { + float val = h->GetBinContent(i, j); + if (val > 0.) { + sum += 1; + } + } + } + // Check how many are off + if (sum > mBadQualityLimit * totalPads && sum < mMediumQualityLimit * totalPads) { + if (result == Quality::Good) { + result = Quality::Medium; + } + mSectorsName.push_back(titleh); + mSectorsQuality.push_back(Quality::Medium); + } else if (sum < mBadQualityLimit * totalPads) { + result = Quality::Bad; + mSectorsName.push_back(titleh); + mSectorsQuality.push_back(Quality::Bad); + } else { + mSectorsName.push_back(titleh); + mSectorsQuality.push_back(Quality::Good); + } + message = fmt::format("Fraction of filled pads: {:.2f}", (float)sum / (float)totalPads); + checkMessage.push_back(message); + } + } + } // end of loop over moMap + + // For writing to metadata and drawing later + mBadString = ""; + mMediumString = ""; + mGoodString = ""; + mNullString = ""; + + // Aggregation of quality strings used for MO + for (int qualityindex = 0; qualityindex < mSectorsQuality.size(); qualityindex++) { + Quality q = mSectorsQuality.at(qualityindex); + if (q == Quality::Bad) { + mBadString = mBadString + checkMessage.at(qualityindex) + "\n"; + } else if (q == Quality::Medium) { + mMediumString = mMediumString + checkMessage.at(qualityindex) + "\n"; + } else if (q == Quality::Good) { + mGoodString = mGoodString + checkMessage.at(qualityindex) + "\n"; + } else { + mNullString = mNullString + checkMessage.at(qualityindex) + "\n"; + } + } + + // Condensing the quality in concise strings used for QO + mBadStringMeta = ""; + mMediumStringMeta = ""; + mGoodStringMeta = ""; + mNullStringMeta = ""; + + mBadStringMeta = summarizeMetaData(Quality::Bad); + mMediumStringMeta = summarizeMetaData(Quality::Medium); + mGoodStringMeta = summarizeMetaData(Quality::Good); + mNullStringMeta = summarizeMetaData(Quality::Null); + + result.addMetadata(Quality::Bad.getName(), mBadStringMeta); + result.addMetadata(Quality::Medium.getName(), mMediumStringMeta); + result.addMetadata(Quality::Good.getName(), mGoodStringMeta); + result.addMetadata(Quality::Null.getName(), mNullStringMeta); + result.addMetadata("Comment", mMetadataComment); + + return result; +} + +//______________________________________________________________________________ +std::string CheckForEmptyPads::summarizeMetaData(Quality quality) +{ + std::string sumMetaData = ""; + + // reset + std::string mBadStringtemp = ""; + std::string mMediumStringtemp = ""; + std::string mGoodStringtemp = ""; + std::string mNullStringtemp = ""; + + for (int qualityindex = 0; qualityindex < mSectorsQuality.size(); qualityindex++) { + Quality q = mSectorsQuality.at(qualityindex); + if (q == Quality::Bad) { + mBadStringtemp = mBadStringtemp + fmt::format("{:d} ", qualityindex); + } else if (q == Quality::Medium) { + mMediumStringtemp = mMediumStringtemp + fmt::format("{:d} ", qualityindex); + } else if (q == Quality::Good) { + mGoodStringtemp = mGoodStringtemp + fmt::format("{:d} ", qualityindex); + } else { + mNullStringtemp = mNullStringtemp + fmt::format("{:d} ", qualityindex); + } + } + if (quality == Quality::Bad) { + sumMetaData += "Pads are empty: " + mBadStringtemp + "\n"; + } else if (quality == Quality::Medium) { + sumMetaData += "Pads have medium quality: " + mMediumStringtemp + "\n"; + } else if (quality == Quality::Good) { + sumMetaData += "Pads have good quality: " + mGoodStringtemp + "\n"; + } else { + sumMetaData += "Pads have no quality status: " + mNullStringtemp + "\n"; + } + + return sumMetaData; +} + +void CheckForEmptyPads::beautify(std::shared_ptr mo, Quality) +{ + std::string checkMessage; + + auto moName = mo->getName(); + if (auto it = std::find(mMOsToCheck2D.begin(), mMOsToCheck2D.end(), moName); it != mMOsToCheck2D.end()) { + int padsTotal = 0, padsstart = 1000; + auto* tcanv = dynamic_cast(mo->getObject()); + std::string histNameS, histName; + padsstart = 1; + padsTotal = 72; + size_t end = moName.find("_2D"); + auto histSubName = moName.substr(7, end - 7); + histNameS = fmt::format("h_{:s}_ROC", histSubName); + for (int tpads = padsstart; tpads <= padsTotal; tpads++) { + const std::string padName = fmt::format("{:s}_{:d}", moName, tpads); + TPad* pad = (TPad*)tcanv->GetListOfPrimitives()->FindObject(padName.data()); + if (!pad) { + continue; + } + pad->cd(); + TH1F* h = nullptr; + histName = fmt::format("{:s}_{:02d}", histNameS, tpads - 1); + h = (TH1F*)pad->GetListOfPrimitives()->FindObject(histName.data()); + if (!h) { + continue; + } + const std::string titleh = h->GetTitle(); + auto it = std::find(mSectorsName.begin(), mSectorsName.end(), titleh); + if (it == mSectorsName.end()) { + continue; + } + const int index = std::distance(mSectorsName.begin(), it); + TPaveText* msgQuality = new TPaveText(0.1, 0.82, 0.5, 0.9, "NDC"); + msgQuality->SetBorderSize(1); + Quality qualitySpecial = mSectorsQuality[index]; + msgQuality->SetName(Form("%s_msg", mo->GetName())); + if (qualitySpecial == Quality::Good) { + msgQuality->Clear(); + msgQuality->AddText(Quality::Good.getName().data()); + msgQuality->SetFillColor(kGreen); + checkMessage = mGoodString; + } else if (qualitySpecial == Quality::Bad) { + msgQuality->Clear(); + msgQuality->AddText(Quality::Bad.getName().data()); + msgQuality->SetFillColor(kRed); + checkMessage = mBadString; + } else if (qualitySpecial == Quality::Medium) { + msgQuality->Clear(); + msgQuality->AddText(Quality::Medium.getName().data()); + msgQuality->SetFillColor(kOrange); + checkMessage = mMediumString; + } else if (qualitySpecial == Quality::Null) { + msgQuality->AddText(Quality::Null.getName().data()); + h->SetFillColor(0); + checkMessage = mNullString; + } + + h->SetLineColor(kBlack); + + // Split lines by hand as \n does not work with TPaveText + std::string delimiter = "\n"; + size_t pos = 0; + std::string subText; + pos = checkMessage.find(delimiter); + subText = checkMessage.substr(0, pos); + msgQuality->AddText(subText.c_str()); + checkMessage.erase(0, pos + delimiter.length()); + msgQuality->AddText(qualitySpecial.getMetadata("Comment", "").c_str()); + + msgQuality->SetTextSize(32); + msgQuality->Draw("same"); + } + auto savefileNAme = fmt::format("/home/ge56luj/Desktop/ThisIsBeautifyObject{:s}.pdf", moName); + tcanv->SaveAs(savefileNAme.c_str()); + mSectorsName.clear(); + mSectorsQuality.clear(); + } +} + +} // namespace o2::quality_control_modules::tpc \ No newline at end of file diff --git a/Modules/TPC/src/CheckOfPads.cxx b/Modules/TPC/src/CheckOfPads.cxx new file mode 100644 index 0000000000..2379612459 --- /dev/null +++ b/Modules/TPC/src/CheckOfPads.cxx @@ -0,0 +1,327 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckOfPads.cxx +/// \author Laura Serksnyte, Maximilian Horst +/// + +#include "TPC/CheckOfPads.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" + +// ROOT +#include +#include +#include +#include +#include +#include + +#include + +namespace o2::quality_control_modules::tpc +{ +void CheckOfPads::configure() +{ + mMediumQualityLimit = common::getFromConfig(mCustomParameters, "mediumQualityPercentageOfWorkingPads", 0.7); + mBadQualityLimit = common::getFromConfig(mCustomParameters, "badQualityPercentageOfWorkingPads", 0.3); + const std::string checkChoiceString = common::getFromConfig(mCustomParameters, "CheckChoice", "Mean"); + + if (size_t finder = checkChoiceString.find("ExpectedValue"); finder != std::string::npos) { + mExpectedValueCheck = true; + } + if (size_t finder = checkChoiceString.find("Mean"); finder != std::string::npos) { + mMeanCheck = true; + } + if (size_t finder = checkChoiceString.find("Empty"); finder != std::string::npos) { + mEmptyCheck = true; + } + + if (mExpectedValueCheck) { + // load expectedValue and allowed standard deviations for medium/bad quality + mExpectedValue = common::getFromConfig(mCustomParameters, "ExpectedValue", 1.0); + mExpectedValueMediumSigmas = common::getFromConfig(mCustomParameters, "ExpectedValueSigmaMedium", 3.0); + mExpectedValueBadSigmas = common::getFromConfig(mCustomParameters, "ExpectedValueSigmaBad", 6.0); + } + // Check if Mean comparison is wished for: + if (mMeanCheck) { + // load Mean Sigma Medium + mMeanMediumSigmas = common::getFromConfig(mCustomParameters, "MeanSigmaMedium", 3.0); + mMeanBadSigmas = common::getFromConfig(mCustomParameters, "MeanSigmaBad", 6.0); + } + + if (auto param = mCustomParameters.find("MOsNames2D"); param != mCustomParameters.end()) { + auto temp = param->second.c_str(); + std::istringstream ss(temp); + std::string token; + while (std::getline(ss, token, ',')) { + mMOsToCheck2D.emplace_back(token); + } + } +} + +//______________________________________________________________________________ +Quality CheckOfPads::check(std::map>* moMap) +{ + Quality resultEV = Quality::Null; + Quality resultMean = Quality::Null; + Quality resultGlobal = Quality::Null; + Quality resultEmpty = Quality::Null; + for (auto const& moObj : *moMap) { + auto mo = moObj.second; + if (!mo) { + continue; + } + const auto moName = mo->getName(); + if (auto it = std::find(mMOsToCheck2D.begin(), mMOsToCheck2D.end(), moName); it != mMOsToCheck2D.end()) { + const size_t end = moName.find("_2D"); + const auto histSubName = moName.substr(7, end - 7); + resultEV = Quality::Good; + resultMean = Quality::Good; + + auto* canv = dynamic_cast(mo->getObject()); + if (!canv) { + continue; + } + // Check all histograms in the canvas + + mTotalMean = 0.; + + for (int tpads = 1; tpads <= 72; tpads++) { + const auto padName = fmt::format("{:s}_{:d}", moName, tpads); + const auto histName = fmt::format("h_{:s}_ROC_{:02d}", histSubName, tpads - 1); + TPad* pad = (TPad*)canv->GetListOfPrimitives()->FindObject(padName.data()); + if (!pad) { + mSectorsName.push_back("notitle"); + mSectorsQualityEV.push_back(Quality::Null); + continue; + } + TH2F* h = (TH2F*)pad->GetListOfPrimitives()->FindObject(histName.data()); + if (!h) { + mSectorsName.push_back("notitle"); + mSectorsQualityEV.push_back(Quality::Null); + continue; + } + const std::string titleh = h->GetTitle(); + + mSectorsName.push_back(titleh); + // check if we are dealing with IROC or OROC + float totalPads = 0; + const int MaximumXBin = h->GetNbinsX(); + const int MaximumYBin = h->GetNbinsY(); + if (titleh.find("IROC") != std::string::npos) { + totalPads = 5280; + } else if (titleh.find("OROC") != std::string::npos) { + totalPads = 9280; + } else { + return Quality::Null; + } + + float padSum = 0.; + int padsCount = 0; + float padStdev = 0.; + float padMean = 0.; + + // Run twice to get the mean and the standard deviation + for (int runNo = 1; runNo <= 2; runNo++) { + // Run1: calculate single Pad total->Mean + // Run2: calculate standardDeviation from mean + for (int xBin = 1; xBin <= MaximumXBin; xBin++) { + for (int yBin = 1; yBin <= MaximumYBin; yBin++) { + const float binvalue = h->GetBinContent(xBin, yBin); + if (binvalue != 0) { + if (runNo == 1) { + padSum += binvalue; + padsCount++; + } else { + padStdev += pow(binvalue - padMean, 2); + } + } + } + } + + if (runNo == 1 && padSum > 0.) { // no need for div by 0 check, because if padSum > 0 -> padsCount>0 + padMean = padSum / padsCount; + } + } + + if (padsCount == 0) { + ILOG(Error, Support) << "The Pad provided (Pad: " << tpads << ") is empty." << ENDM; + return Quality::Null; + } + + if (mEmptyCheck) { + if (padsCount > mMediumQualityLimit * totalPads) { + resultEmpty = Quality::Good; + } else if (padsCount < mBadQualityLimit * totalPads) { + resultEmpty = Quality::Bad; + } else { + resultEmpty = Quality::Medium; + } + mSectorsQualityEmpty.push_back(resultEmpty); + mEmptyPadPercent.push_back(1. - (float)padsCount / totalPads); + } + + padStdev = sqrt(padStdev / (padsCount - 1)); + mPadMeans.push_back(padMean); + mPadStdev.push_back(padStdev); + } + float sumOfWeights = 0.; + for (size_t it = 0; it < mPadMeans.size(); it++) { // loop over all pads + // calculate the total mean and standard deviation + mTotalMean += mPadMeans[it] / mPadStdev[it]; + sumOfWeights += 1 / mPadStdev[it]; + } + mTotalStdev = sqrt(1 / sumOfWeights); // standard deviation of the weighted average. + mTotalMean /= sumOfWeights; // Weighted average (by standard deviation) of the total mean + // calculate the Qualities: + + for (size_t it = 0; it < mPadMeans.size(); it++) { // loop over all pads + + if (mExpectedValueCheck) { + if (std::abs(mPadMeans[it] - mExpectedValue) < mPadStdev[it] * mExpectedValueMediumSigmas) { + resultEV = Quality::Good; + } else if (std::abs(mPadMeans[it] - mExpectedValue) >= mPadStdev[it] * mExpectedValueMediumSigmas && std::abs(mPadMeans[it] - mExpectedValue) < mPadStdev[it] * mExpectedValueBadSigmas) { + resultEV = Quality::Medium; + } else { + resultEV = Quality::Bad; + } + } + + mSectorsQualityEV.push_back(resultEV); + if (mMeanCheck) { + if (std::abs(mPadMeans[it] - mTotalMean) < mPadStdev[it] * mMeanMediumSigmas) { + resultMean = Quality::Good; + } else if (std::abs(mPadMeans[it] - mTotalMean) >= mPadStdev[it] * mMeanMediumSigmas && std::abs(mPadMeans[it] - mTotalMean) < mPadStdev[it] * mMeanBadSigmas) { + resultMean = Quality::Medium; + } else { + resultMean = Quality::Bad; + } + } + + mSectorsQualityMean.push_back(resultMean); + + std::vector qualities; + if (mMeanCheck) { + qualities.push_back(resultMean); + } + + if (mExpectedValueCheck) { + qualities.push_back(resultEV); + } + + if (mEmptyCheck) { + qualities.push_back(mSectorsQualityEmpty[it]); + } + + if (qualities.size() > 0) { + Quality worst = Quality::Good; + for (int Quality = 0; Quality < qualities.size(); Quality++) { + if (qualities[Quality].isWorseThan(worst)) { + worst = qualities[Quality]; + } + } + mSectorsQuality.push_back(worst); + } else { + ILOG(Error, Support) << "No Qualities were found!" << ENDM; + } + //// Quality aggregation: + if (mMeanCheck && mExpectedValueCheck) { // compare the total mean to the expected value. This is returned as the quality object + if (std::abs(mTotalMean - mExpectedValue) < mTotalStdev * mExpectedValueMediumSigmas) { + resultGlobal = Quality::Good; + } else if (std::abs(mTotalMean - mExpectedValue) > mTotalStdev * mExpectedValueBadSigmas) { + resultGlobal = Quality::Bad; + } else { + resultGlobal = Quality::Medium; + } + } + + } // for it in vectors (pads) + + } // if MO exists + } // MO-map loop + return resultGlobal; +} // end of loop over moMap + +//______________________________________________________________________________ + +//______________________________________________________________________________ +void CheckOfPads::beautify(std::shared_ptr mo, Quality) +{ + auto moName = mo->getName(); + if (auto it = std::find(mMOsToCheck2D.begin(), mMOsToCheck2D.end(), moName); it != mMOsToCheck2D.end()) { + + auto* tcanv = dynamic_cast(mo->getObject()); + const int padsstart = 1; + const int padsTotal = 72; + const size_t end = moName.find("_2D"); + const auto histSubName = moName.substr(7, end - 7); + const auto histNameS = fmt::format("h_{}_ROC", histSubName); + for (int tpads = padsstart; tpads <= padsTotal; tpads++) { + + const std::string padName = fmt::format("{:s}_{:d}", moName, tpads); + TPad* pad = (TPad*)tcanv->GetListOfPrimitives()->FindObject(padName.data()); + if (!pad) { + continue; + } + pad->cd(); + const auto histName = fmt::format("{}_{:02d}", histNameS, tpads - 1); + const auto h = (TH1F*)pad->GetListOfPrimitives()->FindObject(histName.data()); + if (!h) { + continue; + } + const std::string titleh = h->GetTitle(); + auto it = std::find(mSectorsName.begin(), mSectorsName.end(), titleh); + if (it == mSectorsName.end()) { + continue; + } + + const int index = std::distance(mSectorsName.begin(), it); + TPaveText* msgQuality = new TPaveText(0.1, 0.85, 0.81, 0.95, "NDC"); + msgQuality->SetBorderSize(1); + Quality qualitySpecial = mSectorsQuality[index]; + msgQuality->SetName(Form("%s_msg", mo->GetName())); + if (qualitySpecial == Quality::Good) { + msgQuality->Clear(); + msgQuality->AddText("Good"); + msgQuality->SetFillColor(kGreen); + } else if (qualitySpecial == Quality::Bad) { + msgQuality->Clear(); + msgQuality->AddText("Bad"); + msgQuality->SetFillColor(kRed); + } else if (qualitySpecial == Quality::Medium) { + msgQuality->Clear(); + msgQuality->AddText("Medium"); + msgQuality->SetFillColor(kOrange); + } else if (qualitySpecial == Quality::Null) { + h->SetFillColor(0); + } + + if (mMeanCheck) { + msgQuality->AddText(fmt::format("Pad Mean: {:1.4f}+-{:1.4f}, Global Mean: {:1.4f}", mPadMeans[index], mPadStdev[index], mTotalMean).data()); + } + if (mExpectedValueCheck) { + msgQuality->AddText(fmt::format("Pad Mean: {:1.4}+-{:1.4f}, Expected Value: {:1.4f}", mPadMeans[index], mPadStdev[index], mExpectedValue).data()); + } + if (mEmptyCheck) { + msgQuality->AddText(fmt::format("{:1.3f} % Empty pads.", mEmptyPadPercent[index] * 100).data()); + } + h->SetLineColor(kBlack); + msgQuality->Draw("same"); + } + mSectorsName.clear(); + mSectorsQuality.clear(); + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/CheckOfSlices.cxx b/Modules/TPC/src/CheckOfSlices.cxx new file mode 100644 index 0000000000..2fe0dbd17a --- /dev/null +++ b/Modules/TPC/src/CheckOfSlices.cxx @@ -0,0 +1,544 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckOfSlices.cxx +/// \author Maximilian Horst +/// \author Marcel Lesch +/// + +#include "TPC/CheckOfSlices.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include "Common/Utils.h" +#include "TPC/Utility.h" +#include "Algorithm/RangeTokenizer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +void CheckOfSlices::configure() +{ + // Backwards compability for old choice + const std::string oldCheckChoiceString = common::getFromConfig(mCustomParameters, "chooseCheckMeanOrExpectedPhysicsValueOrBoth", ""); + if (oldCheckChoiceString != "") { + ILOG(Warning, Support) << "json using CheckOfTrendings needs to be updated! chooseCheckMeanOrExpectedPhysicsValueOrBoth was replaced by CheckChoice. Available options: Mean, ExpectedValue, Range, Zero" << ENDM; + + if (size_t finder = oldCheckChoiceString.find("Both"); finder != std::string::npos) { + mMeanCheck = true; + mExpectedValueCheck = true; + } else if (size_t finder = oldCheckChoiceString.find("Mean"); finder != std::string::npos) { + mMeanCheck = true; + } else if (size_t finder = oldCheckChoiceString.find("ExpectedPhysicsValue"); finder != std::string::npos) { + mExpectedValueCheck = true; + } + } + + const std::string checkChoiceString = common::getFromConfig(mCustomParameters, "CheckChoice", "Mean"); + + if (size_t finder = checkChoiceString.find("ExpectedValue"); finder != std::string::npos) { + mExpectedValueCheck = true; + } + if (size_t finder = checkChoiceString.find("Mean"); finder != std::string::npos) { + mMeanCheck = true; + } + if (size_t finder = checkChoiceString.find("Range"); finder != std::string::npos) { + mRangeCheck = true; + } + if (size_t finder = checkChoiceString.find("Zero"); finder != std::string::npos) { + mZeroCheck = true; + } + + if (!mExpectedValueCheck && !mMeanCheck && !mRangeCheck && !mZeroCheck) { // A choice was provided but does not overlap with any of the provided options + ILOG(Warning, Support) << "The chosen check option does not exist. Available options: Mean, ExpectedValue, Range, Zero. Multiple options can be chosen in parallel. Default Mean is now selected." << ENDM; + } + + mMetadataComment = common::getFromConfig(mCustomParameters, "MetadataComment", ""); + if (mExpectedValueCheck) { + mNSigmaExpectedPhysicsValue = common::getFromConfig(mCustomParameters, "allowedNSigmaForExpectation", 3); + mNSigmaBadExpectedPhysicsValue = common::getFromConfig(mCustomParameters, "badNSigmaForExpectation", 6); + if (mNSigmaBadExpectedPhysicsValue < mNSigmaExpectedPhysicsValue) { // if bad < medium flip them. + std::swap(mNSigmaBadExpectedPhysicsValue, mNSigmaExpectedPhysicsValue); + } + mExpectedPhysicsValue = common::getFromConfig(mCustomParameters, "expectedPhysicsValue", 1); + } + + if (mMeanCheck) { + mNSigmaMean = common::getFromConfig(mCustomParameters, "allowedNSigmaForMean", 3); + mNSigmaBadMean = common::getFromConfig(mCustomParameters, "badNSigmaForMean", 6); + if (mNSigmaBadMean < mNSigmaMean) { // if bad < medium flip them. + std::swap(mNSigmaBadMean, mNSigmaMean); + } + } + + if (mRangeCheck) { + mRangeMedium = common::getFromConfig(mCustomParameters, "allowedRange", 1); + mRangeBad = common::getFromConfig(mCustomParameters, "badRange", 2); + if (mRangeBad < mRangeMedium) { // if bad < medium flip them. + std::swap(mRangeBad, mRangeMedium); + } + mExpectedPhysicsValue = common::getFromConfig(mCustomParameters, "expectedPhysicsValue", 1); + } + + std::string maskingValues = common::getFromConfig(mCustomParameters, "maskedPoints", ""); + mMaskedPoints.clear(); + if (maskingValues != "") { + mMaskedPoints = RangeTokenizer::tokenize(maskingValues); + } +} + +Quality CheckOfSlices::check(std::map>* moMap) +{ + Quality totalQuality = Quality::Null; + totalQuality.addMetadata("Comment", mMetadataComment); + + std::vector qualities; + std::unordered_map> checks; + checks[Quality::Bad.getName()] = std::vector(); + checks[Quality::Medium.getName()] = std::vector(); + checks[Quality::Good.getName()] = std::vector(); + + auto mo = moMap->begin()->second; + if (!mo) { + ILOG(Error, Support) << "Monitoring object not found" << ENDM; + totalQuality.addMetadata(Quality::Null.getName(), "Monitoring object not found \n"); + return totalQuality; + } + auto* canv = dynamic_cast(mo->getObject()); + if (!canv) { + ILOG(Error, Support) << "Canvas not found" << ENDM; + totalQuality.addMetadata(Quality::Null.getName(), "Canvas not found \n"); + return totalQuality; + } + TList* padList = (TList*)canv->GetListOfPrimitives(); + padList->SetOwner(kTRUE); + if (padList->GetEntries() > 1) { + ILOG(Error, Support) << "CheckOfSlices does not support multiple pads from SliceTrending" << ENDM; + } + auto pad = static_cast(padList->At(0)); + if (!pad) { + ILOG(Error, Support) << "Could not retrieve pad containing slice graph" << ENDM; + totalQuality.addMetadata(Quality::Null.getName(), "Could not retrieve pad containing slice graph \n"); + return totalQuality; + } + + TGraphErrors* g = nullptr; + g = static_cast(pad->GetPrimitive("Graph")); + if (!g) { + ILOG(Error, Support) << "No Graph object found" << ENDM; + totalQuality.addMetadata(Quality::Null.getName(), "No Graph object found \n"); + return totalQuality; + } + + const int NBins = g->GetN(); + if (NBins == 0) { + totalQuality.addMetadata(Quality::Null.getName(), "Graph has no data points \n"); + mNullString = "Graph has no data points \n"; + return totalQuality; + } + + const double* yValues = g->GetY(); + const double* yErrors = g->GetEY(); + bool useErrors = true; + if (yErrors == nullptr) { + useErrors = false; + ILOG(Info, Support) << "NO ERRORS" << ENDM; + } + const std::vector v(yValues, yValues + NBins); // use all points + std::vector vErr; + if (useErrors) { + const std::vector vErrTemp(yErrors, yErrors + NBins); // use all points + vErr = vErrTemp; + } else { + for (int i = 0; i < NBins; ++i) { + vErr.push_back(0.); + } + } + + calculateStatistics(yValues, yErrors, useErrors, 0, NBins, mMean, mStdev, mMaskedPoints); + + for (size_t i = 0; i < v.size(); ++i) { + Quality totalQualityPoint = Quality::Null; + std::vector qualityPoints; + const auto yvalue = v[i]; + const auto yError = vErr[i]; + + std::string badStringPoint = ""; + std::string mediumStringPoint = ""; + std::string goodStringPoint = ""; + + if (std::find(mMaskedPoints.begin(), mMaskedPoints.end(), i) != mMaskedPoints.end()) { // i is in the masked points + // if point is masked -> push back empty string to not mess with ordering and skip calculation + checks[Quality::Bad.getName()].push_back(badStringPoint); + checks[Quality::Medium.getName()].push_back(mediumStringPoint); + checks[Quality::Good.getName()].push_back(goodStringPoint); + continue; + } + + if (mMeanCheck) { + const double totalError = sqrt(mStdev * mStdev + yError * yError); + if (std::abs(yvalue - mMean) < totalError * mNSigmaMean) { + qualityPoints.push_back(Quality::Good); + goodStringPoint += "MeanCheck \n"; + } else if (std::abs(yvalue - mMean) > totalError * mNSigmaBadMean) { + qualityPoints.push_back(Quality::Bad); + badStringPoint += "MeanCheck \n"; + } else { + qualityPoints.push_back(Quality::Medium); + mediumStringPoint += "MeanCheck \n"; + } + } // if (mMeanCheck) + + if (mExpectedValueCheck) { + if (std::abs(yvalue - mExpectedPhysicsValue) < yError * mNSigmaExpectedPhysicsValue) { + qualityPoints.push_back(Quality::Good); + goodStringPoint += "ExpectedValueCheck \n"; + } else if (std::abs(yvalue - mExpectedPhysicsValue) > yError * mNSigmaBadExpectedPhysicsValue) { + qualityPoints.push_back(Quality::Bad); + badStringPoint += "ExpectedValueCheck \n"; + } else { + qualityPoints.push_back(Quality::Medium); + mediumStringPoint += "ExpectedValueCheck \n"; + } + } // if (mExpectedValueCheck) + + if (mRangeCheck) { + if (std::abs(yvalue - mExpectedPhysicsValue) > mRangeBad) { + qualityPoints.push_back(Quality::Bad); + badStringPoint += "RangeCheck \n"; + } else if (std::abs(yvalue - mExpectedPhysicsValue) < mRangeMedium) { + qualityPoints.push_back(Quality::Good); + goodStringPoint += "RangeCheck \n"; + } else { + qualityPoints.push_back(Quality::Medium); + mediumStringPoint += "RangeCheck \n"; + } + } // if (mRangeCheck) { + + checks[Quality::Bad.getName()].push_back(badStringPoint); + checks[Quality::Medium.getName()].push_back(mediumStringPoint); + checks[Quality::Good.getName()].push_back(goodStringPoint); + + // aggregate qualities of this point between mean, expected and range into result + auto Worst_Quality = std::max_element(qualityPoints.begin(), qualityPoints.end(), + [](const Quality& q1, const Quality& q2) { + return q1.isBetterThan(q2); + }); + qualities.push_back(*Worst_Quality); + + } // for (size_t i = 0; i < v.size(); ++i) + + // Quality aggregation from previous checks + if (qualities.size() >= 1) { + auto Worst_Quality = std::max_element(qualities.begin(), qualities.end(), + [](const Quality& q1, const Quality& q2) { + return q1.isBetterThan(q2); + }); + totalQuality = *Worst_Quality; + + // MetaData aggregation from previous checks + mBadString = createMetaData(checks[Quality::Bad.getName()]); + mMediumString = createMetaData(checks[Quality::Medium.getName()]); + mGoodString = createMetaData(checks[Quality::Good.getName()]); + } + + // Zeros Check: + Quality qualityZeroCheck = Quality::Null; + if (mZeroCheck) { + const bool allZeros = std::all_of(v.begin(), v.end(), [](double i) { return std::abs(i) == 0.0; }); + if (allZeros) { + mBadString += "ZeroCheck \n"; + totalQuality = Quality::Bad; + } else { + mGoodString += "ZeroCheck \n"; + } + } + + if (totalQuality == Quality::Null) { + mNullString = "No check performed \n"; + } + + totalQuality.addMetadata(Quality::Bad.getName(), mBadString); + totalQuality.addMetadata(Quality::Medium.getName(), mMediumString); + totalQuality.addMetadata(Quality::Good.getName(), mGoodString); + totalQuality.addMetadata(Quality::Null.getName(), mNullString); + + return totalQuality; +} + +void CheckOfSlices::beautify(std::shared_ptr mo, Quality checkResult) +{ + auto* canv = dynamic_cast(mo->getObject()); + if (!canv) { + ILOG(Error, Support) << "Canvas not found (beautify function)" << ENDM; + return; + } + TList* padList = (TList*)canv->GetListOfPrimitives(); + padList->SetOwner(kTRUE); + if (padList->GetEntries() > 1) { + ILOG(Error, Support) << "CheckOfSlices does not support multiple pads from SliceTrending" << ENDM; + } + auto pad = static_cast(padList->At(0)); + if (!pad) { + ILOG(Error, Support) << "Could not retrieve pad containing slice graph (beautify function)" << ENDM; + return; + } + TGraphErrors* h = nullptr; + h = static_cast(pad->GetPrimitive("Graph")); + if (!h) { + ILOG(Error, Support) << "No Graph object found (beautify function)" << ENDM; + return; + } + + const double* yValues = h->GetY(); + const double* yErrors = h->GetEY(); // returns nullptr for TGraph (no errors) + bool useErrors = true; + if (yErrors == nullptr) { + useErrors = false; + } + + TPaveText* msg = new TPaveText(0.5, 0.75, 0.9, 0.9, "NDC"); + msg->SetName(fmt::format("{}_msg", mo->GetName()).data()); + h->GetListOfFunctions()->Add(msg); + + std::string checkMessage; + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen - 2); + msg->Clear(); + msg->AddText("Quality::Good"); + msg->SetFillColor(kGreen - 2); + } else if (checkResult == Quality::Bad) { + h->SetFillColor(kRed); + msg->Clear(); + msg->AddText("Quality::Bad. Failed checks:"); + checkMessage = mBadString; + msg->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + h->SetFillColor(kOrange); + msg->Clear(); + msg->AddText("Quality::Medium. Failed checks:"); + checkMessage = mMediumString; + msg->SetFillColor(kOrange); + } else if (checkResult == Quality::Null) { + h->SetFillColor(0); + msg->AddText("Quality::Null. Failed checks:"); + checkMessage = mNullString; + } + + // Split lines by hand as \n does not work with TPaveText + const std::string delimiter = "\n"; + size_t pos = 0; + std::string subText; + while ((pos = checkMessage.find(delimiter)) != std::string::npos) { + subText = checkMessage.substr(0, pos); + msg->AddText(subText.c_str()); + checkMessage.erase(0, pos + delimiter.length()); + } + msg->AddText(checkResult.getMetadata("Comment", "").c_str()); + + std::string mMaskedPointsString = "Masked Points:"; + for (int iMasked = 0; iMasked < mMaskedPoints.size(); iMasked++) { + mMaskedPointsString += " " + std::to_string(mMaskedPoints[iMasked]) + ","; + } + mMaskedPointsString.pop_back(); + msg->AddText(mMaskedPointsString.c_str()); + + const int nPoints = h->GetN(); + if (nPoints == 0) { + h->AddPoint(0., 0.); // have to add dummy point, else msg is not shown + h->SetMarkerColor(kWhite); // hide the dummy point + return; + } + + const double xMin = h->GetPointX(0); + const double xMax = h->GetPointX(nPoints - 1); + + if (mRangeCheck) { + TLine* lineUpperRangeMed = new TLine(xMin, mExpectedPhysicsValue + mRangeMedium, xMax, mExpectedPhysicsValue + mRangeMedium); + lineUpperRangeMed->SetLineColor(kBlack); + lineUpperRangeMed->SetLineStyle(kDashed); + lineUpperRangeMed->SetLineWidth(1); + h->GetListOfFunctions()->Add(lineUpperRangeMed); + TLine* lineLowerRangeMed = new TLine(xMin, mExpectedPhysicsValue - mRangeMedium, xMax, mExpectedPhysicsValue - mRangeMedium); + lineLowerRangeMed->SetLineColor(kBlack); + lineLowerRangeMed->SetLineStyle(kDashed); + lineLowerRangeMed->SetLineWidth(1); + h->GetListOfFunctions()->Add(lineLowerRangeMed); + TLine* lineUpperRangeBad = new TLine(xMin, mExpectedPhysicsValue + mRangeBad, xMax, mExpectedPhysicsValue + mRangeBad); + lineUpperRangeBad->SetLineColor(kBlack); + lineUpperRangeBad->SetLineWidth(3); + h->GetListOfFunctions()->Add(lineUpperRangeBad); + TLine* lineLowerRangeBad = new TLine(xMin, mExpectedPhysicsValue - mRangeBad, xMax, mExpectedPhysicsValue - mRangeBad); + lineLowerRangeBad->SetLineColor(kBlack); + lineLowerRangeBad->SetLineWidth(3); + h->GetListOfFunctions()->Add(lineLowerRangeBad); + } // if (mRangeCheck) + + if (mExpectedValueCheck) { + TLine* expectedValueLine = new TLine(xMin, mExpectedPhysicsValue, xMax, mExpectedPhysicsValue); + + TGraph* stddevGraphMediumUp = new TGraph(); + TGraph* stddevGraphMediumDown = new TGraph(); + TGraph* stddevGraphBadUp = new TGraph(); + TGraph* stddevGraphBadDown = new TGraph(); + + for (int nBins = 0; nBins < nPoints; nBins++) { + + double PointError = 0.; + if (useErrors) { + PointError = *(yErrors + nBins); + } + if (PointError < 0.) { + ILOG(Warning, Support) << "Point for check of mean has negative error" << ENDM; + } + + stddevGraphMediumUp->AddPoint(h->GetPointX(nBins), mExpectedPhysicsValue + PointError * mNSigmaExpectedPhysicsValue); + stddevGraphMediumDown->AddPoint(h->GetPointX(nBins), mExpectedPhysicsValue - PointError * mNSigmaExpectedPhysicsValue); + stddevGraphBadUp->AddPoint(h->GetPointX(nBins), mExpectedPhysicsValue + PointError * mNSigmaBadExpectedPhysicsValue); + stddevGraphBadDown->AddPoint(h->GetPointX(nBins), mExpectedPhysicsValue - PointError * mNSigmaBadExpectedPhysicsValue); + } // for (int nBins = 1; nBins < nPoints; nBins++) + + expectedValueLine->SetLineWidth(2); + expectedValueLine->SetLineColor(kGreen - 7); + expectedValueLine->SetLineStyle(kDashed); + + stddevGraphMediumUp->SetLineWidth(2); + stddevGraphMediumUp->SetLineColor(kOrange - 3); + stddevGraphMediumUp->SetMarkerColor(kOrange - 3); + stddevGraphMediumUp->SetLineStyle(kDashed); + + stddevGraphMediumDown->SetLineWidth(2); + stddevGraphMediumDown->SetLineColor(kOrange - 3); + stddevGraphMediumDown->SetMarkerColor(kOrange - 3); + stddevGraphMediumDown->SetLineStyle(kDashed); + + stddevGraphBadUp->SetLineWidth(2); + stddevGraphBadUp->SetLineColor(kRed - 3); + stddevGraphBadUp->SetMarkerColor(kRed - 3); + stddevGraphBadUp->SetLineStyle(kDashed); + + stddevGraphBadDown->SetLineWidth(2); + stddevGraphBadDown->SetLineColor(kRed - 3); + stddevGraphBadDown->SetMarkerColor(kRed - 3); + stddevGraphBadDown->SetLineStyle(kDashed); + + h->GetListOfFunctions()->Add(expectedValueLine); + h->GetListOfFunctions()->Add(stddevGraphMediumUp); + h->GetListOfFunctions()->Add(stddevGraphMediumDown); + h->GetListOfFunctions()->Add(stddevGraphBadUp); + h->GetListOfFunctions()->Add(stddevGraphBadDown); + } // if (mExpectedValueCheck) + + if (mMeanCheck) { + TLine* meanGraph = new TLine(xMin, mMean, xMax, mMean); + TGraph* stddevGraphMediumUp = new TGraph(); + TGraph* stddevGraphMediumDown = new TGraph(); + TGraph* stddevGraphBadUp = new TGraph(); + TGraph* stddevGraphBadDown = new TGraph(); + + for (int nBins = 0; nBins < nPoints; nBins++) { + + double PointError = 0.; + if (useErrors) { + PointError = *(yErrors + nBins); + } + if (PointError < 0.) { + ILOG(Warning, Support) << "Last point for check of mean has negative error" << ENDM; + } + + double totalError = sqrt(mStdev * mStdev + PointError * PointError); + + stddevGraphMediumUp->AddPoint(h->GetPointX(nBins), mMean + totalError * mNSigmaMean); + stddevGraphMediumDown->AddPoint(h->GetPointX(nBins), mMean - totalError * mNSigmaMean); + stddevGraphBadUp->AddPoint(h->GetPointX(nBins), mMean + totalError * mNSigmaBadMean); + stddevGraphBadDown->AddPoint(h->GetPointX(nBins), mMean - totalError * mNSigmaBadMean); + } // for (int nBins = 1; nBins < nPoints; nBins++) + + meanGraph->SetLineWidth(2); + meanGraph->SetLineColor(kGreen - 2); + + stddevGraphMediumUp->SetLineWidth(2); + stddevGraphMediumUp->SetLineColor(kOrange); + stddevGraphMediumUp->SetMarkerColor(kOrange); + + stddevGraphMediumDown->SetLineWidth(2); + stddevGraphMediumDown->SetLineColor(kOrange); + stddevGraphMediumDown->SetMarkerColor(kOrange); + + stddevGraphBadUp->SetLineWidth(2); + stddevGraphBadUp->SetLineColor(kRed); + stddevGraphBadUp->SetMarkerColor(kRed); + + stddevGraphBadDown->SetLineWidth(2); + stddevGraphBadDown->SetLineColor(kRed); + stddevGraphBadDown->SetMarkerColor(kRed); + + h->GetListOfFunctions()->Add(meanGraph); + h->GetListOfFunctions()->Add(stddevGraphMediumUp); + h->GetListOfFunctions()->Add(stddevGraphMediumDown); + h->GetListOfFunctions()->Add(stddevGraphBadUp); + h->GetListOfFunctions()->Add(stddevGraphBadDown); + } // if (mMeanCheck) +} // beautify function + +std::string CheckOfSlices::createMetaData(const std::vector& pointMetaData) +{ + + std::string meanString = ""; + std::string expectedValueString = ""; + std::string rangeString = ""; + + for (int i = 0; i < pointMetaData.size(); i++) { + if (pointMetaData.at(i).find("MeanCheck") != std::string::npos) { + meanString += " " + std::to_string(i) + ","; + } + if (pointMetaData.at(i).find("ExpectedValueCheck") != std::string::npos) { + expectedValueString += " " + std::to_string(i) + ","; + } + if (pointMetaData.at(i).find("RangeCheck") != std::string::npos) { + rangeString += " " + std::to_string(i) + ","; + } + } + + std::string totalString = ""; + if (meanString != "") { + meanString.pop_back(); + meanString = "MeanCheck (solid, coloured lines) for Points:" + meanString + "\n"; + totalString += meanString; + } + if (expectedValueString != "") { + expectedValueString.pop_back(); + expectedValueString = "ExpectedValueCheck (dashed, coloured lines) for Points:" + expectedValueString + "\n"; + totalString += expectedValueString; + } + if (rangeString != "") { + rangeString.pop_back(); + rangeString = "RangeCheck (black lines) for Points:" + rangeString + "\n"; + totalString += rangeString; + } + + return totalString; +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/CheckOfTrendings.cxx b/Modules/TPC/src/CheckOfTrendings.cxx new file mode 100644 index 0000000000..c60642ea0b --- /dev/null +++ b/Modules/TPC/src/CheckOfTrendings.cxx @@ -0,0 +1,703 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CheckOfTrendings.cxx +/// \author Laura Serksnyte +/// \author Marcel Lesch +/// + +#include "TPC/CheckOfTrendings.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" +#include "TPC/Utility.h" +#include +#include "TROOT.h" +#include "TRandom.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +void CheckOfTrendings::configure() +{ + // Backwards compability for old choice + const std::string oldCheckChoiceString = common::getFromConfig(mCustomParameters, "chooseCheckMeanOrExpectedPhysicsValueOrBoth", ""); + if (oldCheckChoiceString != "") { + ILOG(Warning, Support) << "json using CheckOfTrendings needs to be updated! chooseCheckMeanOrExpectedPhysicsValueOrBoth was replaced by CheckChoice. Available options: Mean, ExpectedValue, Range, Zero" << ENDM; + + if (size_t finder = oldCheckChoiceString.find("Both"); finder != std::string::npos) { + mMeanCheck = true; + mExpectedValueCheck = true; + } else if (size_t finder = oldCheckChoiceString.find("Mean"); finder != std::string::npos) { + mMeanCheck = true; + } else if (size_t finder = oldCheckChoiceString.find("ExpectedPhysicsValue"); finder != std::string::npos) { + mExpectedValueCheck = true; + } + } + + const std::string checkChoiceString = common::getFromConfig(mCustomParameters, "CheckChoice", "Mean"); + + if (size_t finder = checkChoiceString.find("ExpectedValue"); finder != std::string::npos) { + mExpectedValueCheck = true; + } + if (size_t finder = checkChoiceString.find("Mean"); finder != std::string::npos) { + mMeanCheck = true; + } + if (size_t finder = checkChoiceString.find("Range"); finder != std::string::npos) { + mRangeCheck = true; + } + if (size_t finder = checkChoiceString.find("Zero"); finder != std::string::npos) { + mZeroCheck = true; + } + + if (!mExpectedValueCheck && !mMeanCheck && !mRangeCheck && !mZeroCheck) { // A choice was provided but does not overlap with any of the provided options + ILOG(Warning, Support) << "The chosen check option does not exist. Available options: Mean, ExpectedValue, Range, Zero. Multiple options can be chosen in parallel. Default Mean is now selected." << ENDM; + } + + mMetadataComment = common::getFromConfig(mCustomParameters, "MetadataComment", ""); + if (mExpectedValueCheck) { + mNSigmaExpectedPhysicsValue = common::getFromConfig(mCustomParameters, "allowedNSigmaForExpectation", 3); + mNSigmaBadExpectedPhysicsValue = common::getFromConfig(mCustomParameters, "badNSigmaForExpectation", 6); + if (mNSigmaBadExpectedPhysicsValue < mNSigmaExpectedPhysicsValue) { // if bad < medium flip them. + std::swap(mNSigmaBadExpectedPhysicsValue, mNSigmaExpectedPhysicsValue); + } + mPointToTakeForExpectedValueCheck = std::abs(common::getFromConfig(mCustomParameters, "pointsToTakeForExpectedValueCheck", 10)); + mExpectedPhysicsValue = common::getFromConfig(mCustomParameters, "expectedPhysicsValue", 1); + } + + if (mMeanCheck) { + mNSigmaMean = common::getFromConfig(mCustomParameters, "allowedNSigmaForMean", 3); + mNSigmaBadMean = common::getFromConfig(mCustomParameters, "badNSigmaForMean", 6); + if (mNSigmaBadMean < mNSigmaMean) { // if bad < medium flip them. + std::swap(mNSigmaBadMean, mNSigmaMean); + } + mPointToTakeForMeanCheck = std::abs(common::getFromConfig(mCustomParameters, "pointsToTakeForMeanCheck", 10)); + } + + if (mRangeCheck) { + mRangeMedium = common::getFromConfig(mCustomParameters, "allowedRange", 1); + mRangeBad = common::getFromConfig(mCustomParameters, "badRange", 2); + if (mRangeBad < mRangeMedium) { // if bad < medium flip them. + std::swap(mRangeBad, mRangeMedium); + } + mPointToTakeForRangeCheck = std::abs(common::getFromConfig(mCustomParameters, "pointsToTakeForRangeCheck", 1)); + mExpectedPhysicsValue = common::getFromConfig(mCustomParameters, "expectedPhysicsValue", 1); + } + + if (mZeroCheck) { + mPointToTakeForZeroCheck = std::abs(common::getFromConfig(mCustomParameters, "pointsToTakeForZeroCheck", 1)); + } + + mSliceTrend = common::getFromConfig(mCustomParameters, "SliceTrending", true); +} + +Quality CheckOfTrendings::check(std::map>* moMap) +{ + mPadQualities.clear(); + mPadMetaData.clear(); + mPadMetaData[Quality::Null.getName()] = std::vector(); + mPadMetaData[Quality::Bad.getName()] = std::vector(); + mPadMetaData[Quality::Medium.getName()] = std::vector(); + mPadMetaData[Quality::Good.getName()] = std::vector(); + + std::vector graphs; + Quality totalQuality = Quality::Null; + totalQuality.addMetadata("Comment", mMetadataComment); + + auto mo = moMap->begin()->second; + if (!mo) { + ILOG(Error, Support) << "Monitoring object not found" << ENDM; + totalQuality.addMetadata(Quality::Null.getName(), "Monitoring object not found"); + return totalQuality; + } + auto* canv = dynamic_cast(mo->getObject()); + if (!canv) { + ILOG(Error, Support) << "Canvas not found" << ENDM; + totalQuality.addMetadata(Quality::Null.getName(), "Canvas not found"); + return totalQuality; + } + getGraphs(canv, graphs, mo->getName()); + + if (graphs.size() == 0) { + ILOG(Error, Support) << "Could not retrieve any TGraph for CheckOfTrendings" << ENDM; + totalQuality.addMetadata(Quality::Null.getName(), "Could not retrieve any TGraph for CheckOfTrendings"); + return totalQuality; + } + for (size_t iGraph = 0; iGraph < graphs.size(); iGraph++) { + if (!graphs[iGraph]) { // if there is no TGraph, give an error and break + ILOG(Error, Support) << "TGraph number " << iGraph << " is NULL." << ENDM; + totalQuality.addMetadata(Quality::Null.getName(), "Could not retrieve all TGraphs"); + return totalQuality; + } + } + if (!mSliceTrend && graphs.size() > 1) { + ILOG(Error, Support) << "Multiple Graphs found even though this is not a slice trending" << ENDM; + totalQuality.addMetadata(Quality::Null.getName(), "Multiple Graphs found even though this is not a slice trending"); + return totalQuality; + } + + for (size_t iGraph = 0; iGraph < graphs.size(); iGraph++) { + std::string padNullString = ""; + std::string padBadString = ""; + std::string padMediumString = ""; + std::string padGoodString = ""; + std::string checkMessage = ""; + std::vector qualitiesOfPad; + + const int nBins = graphs[iGraph]->GetN(); + + if (nBins == 0) { + mPadQualities.push_back(Quality::Null); + padNullString += "Graph has no data points for pad " + std::to_string(iGraph) + " \n"; + + mPadMetaData[Quality::Null.getName()].push_back(padNullString); + mPadMetaData[Quality::Bad.getName()].push_back(padBadString); + mPadMetaData[Quality::Medium.getName()].push_back(padMediumString); + mPadMetaData[Quality::Good.getName()].push_back(padGoodString); + + continue; + } + + const double* yValues = graphs[iGraph]->GetY(); + const double* yErrors = graphs[iGraph]->GetEY(); // returns nullptr for TGraph (no errors) + + bool useErrors = true; + if (yErrors == nullptr) { + useErrors = false; + ILOG(Info, Support) << "NO ERRORS" << ENDM; + } else { + const std::vector vErr(yErrors, yErrors + nBins); + if (std::find(vErr.begin(), vErr.end(), 0.0) != vErr.end()) { + useErrors = false; + ILOG(Info, Support) << "Cannot take uncertainties of points into account for check of trending. At least one uncertainty is zero" << ENDM; + } + } + + double mean = 0.; + double stddevOfMean = 0.; + + // Mean Check: + if (mMeanCheck) { + if (nBins <= 1) { // If only one data point available, don't check quality for mean + qualitiesOfPad.push_back(Quality::Null); + padNullString += "MeanCheck: Could not perform check due as there is only one data point or less \n"; + } else { + + checkMessage = ""; + double x_last = 0., y_last = 0.; + graphs[iGraph]->GetPoint(nBins - 1, x_last, y_last); + int pointNumberForMean = mPointToTakeForMeanCheck; + if ((nBins - 1) < mPointToTakeForMeanCheck) { + pointNumberForMean = nBins - 1; + } + + calculateStatistics(yValues, yErrors, useErrors, nBins - 1 - pointNumberForMean, nBins - 1, mean, stddevOfMean); + + double lastPointError = 0.; + if (useErrors) { + lastPointError = *(yErrors + nBins - 1); // Error of last point + } + if (lastPointError < 0.) { + ILOG(Warning, Support) << "Last point for check of mean has negative error" << ENDM; + } + + const double totalError = sqrt(stddevOfMean * stddevOfMean + lastPointError * lastPointError); + double nSigma = -1.; + if (totalError != 0.) { + nSigma = std::abs(y_last - mean) / totalError; + } + if (nSigma < 0.) { + qualitiesOfPad.push_back(Quality::Bad); + padBadString += "MeanCheck: Final uncertainty of mean is zero \n"; + } else { + checkMessage = fmt::format("MeanCheck: {:.1f}#sigma difference to mean({:.2f}#pm{:.2f}) \n", nSigma, mean, totalError); + + if (std::abs(y_last - mean) < totalError * mNSigmaMean) { + qualitiesOfPad.push_back(Quality::Good); + padGoodString += checkMessage; + } else if (std::abs(y_last - mean) > totalError * mNSigmaBadMean) { + qualitiesOfPad.push_back(Quality::Bad); + padBadString += checkMessage; + } else { + qualitiesOfPad.push_back(Quality::Medium); + padMediumString += checkMessage; + } + } + } + } // if (mMeanCheck) + + // ExpectedValue Check: + if (mExpectedValueCheck) { + checkMessage = ""; + int pointNumber = mPointToTakeForExpectedValueCheck; + if (!useErrors && pointNumber == 1) { // if we only have one point without errrors, the stddev = 0 and the check would not make any sense + ILOG(Warning, Support) << "Expected Value Check: Cannot use 1 point without errors. Switching to two points" << ENDM; + pointNumber = 2; + } + if (nBins < pointNumber) { + pointNumber = nBins; + } + + if (!useErrors && pointNumber == 1) { // changing from 1 to 2 points failed because only 1 point is available -> no quality + qualitiesOfPad.push_back(Quality::Null); + padNullString += "ExpectedValueCheck: Only one data point without errors \n"; + } else { + calculateStatistics(yValues, yErrors, useErrors, nBins - pointNumber, nBins, mean, stddevOfMean); + mStdev.push_back(stddevOfMean); + + double nSigma = -1.; + if (stddevOfMean != 0.) { + nSigma = std::abs(mean - mExpectedPhysicsValue) / stddevOfMean; + } + if (nSigma < 0.) { + qualitiesOfPad.push_back(Quality::Bad); + padBadString += "ExpectedValueCheck: Final uncertainty of mean is zero \n"; + } else { + checkMessage = fmt::format("ExpectedValueCheck: {:.1f}#sigma difference to ExpectedValue({:.2f}) \n", nSigma, mExpectedPhysicsValue); + + if (std::abs(mean - mExpectedPhysicsValue) > mNSigmaBadExpectedPhysicsValue * stddevOfMean) { + qualitiesOfPad.push_back(Quality::Bad); + padBadString += checkMessage; + } else if (std::abs(mean - mExpectedPhysicsValue) < mNSigmaExpectedPhysicsValue * stddevOfMean) { + qualitiesOfPad.push_back(Quality::Good); + padGoodString += checkMessage; + } else { + qualitiesOfPad.push_back(Quality::Medium); + padMediumString += checkMessage; + } + } + } + } // if (mExpectedValueCheck) + + // Range Check: + if (mRangeCheck) { + int pointNumber = mPointToTakeForRangeCheck; + if (nBins < pointNumber) { + pointNumber = nBins; + } + + calculateStatistics(yValues, yErrors, useErrors, nBins - pointNumber, nBins, mean, stddevOfMean); + + if (std::abs(mean - mExpectedPhysicsValue) > mRangeBad) { + qualitiesOfPad.push_back(Quality::Bad); + padBadString += fmt::format("RangeCheck: Out of range of ExpectedValue({:.2f}) by more than #pm{:.2f} \n", mExpectedPhysicsValue, mRangeBad); + } else if (std::abs(mean - mExpectedPhysicsValue) < mRangeMedium) { + qualitiesOfPad.push_back(Quality::Good); + padGoodString += fmt::format("RangeCheck: In range of ExpectedValue({:.2f}) within #pm{:.2f} \n", mExpectedPhysicsValue, mRangeMedium); + } else { + qualitiesOfPad.push_back(Quality::Medium); + padMediumString += fmt::format("RangeCheck: Out of range of ExpectedValue({:.2f}) between #pm{:.2f} and #pm{:.2f} \n", mExpectedPhysicsValue, mRangeMedium, mRangeBad); + } + } // if(mRangeCheck) + + // Zeros Check: + if (mZeroCheck) { + int pointNumber = mPointToTakeForZeroCheck; + if (nBins < pointNumber) { + pointNumber = nBins; + } + + const std::vector v(yValues + nBins - pointNumber, yValues + nBins); + const bool allZeros = std::all_of(v.begin(), v.end(), [](double i) { return std::abs(i) == 0.0; }); + if (allZeros) { + qualitiesOfPad.push_back(Quality::Bad); + padBadString += fmt::format("ZeroCheck: Last {} Points zero \n", pointNumber); + } else { + qualitiesOfPad.push_back(Quality::Good); + padGoodString += "ZeroCheck: Not all Points zero \n"; + } + } // if (mZeroCheck) + + // Quality aggregation + if (qualitiesOfPad.size() >= 1) { + auto worst_Quality_Pad = std::max_element(qualitiesOfPad.begin(), qualitiesOfPad.end(), + [](const Quality& q1, const Quality& q2) { + return q1.isBetterThan(q2); + }); + mPadQualities.push_back(*worst_Quality_Pad); + } else { + mPadQualities.push_back(Quality::Null); + padNullString += "No Checks performed for pad " + std::to_string(iGraph) + " \n"; + } + + mPadMetaData[Quality::Null.getName()].push_back(padNullString); + mPadMetaData[Quality::Bad.getName()].push_back(padBadString); + mPadMetaData[Quality::Medium.getName()].push_back(padMediumString); + mPadMetaData[Quality::Good.getName()].push_back(padGoodString); + } // for(size_t iGraph = 0; iGraph < graphs.size(); iGraph++) + + // Final Aggregation of qualities and metadata of all Pads + std::string totalBadString = ""; + std::string totalMediumString = ""; + std::string totalGoodString = ""; + std::string totalNullString = ""; + + if (mPadQualities.size() >= 1) { + auto worst_Quality = std::max_element(mPadQualities.begin(), mPadQualities.end(), + [](const Quality& q1, const Quality& q2) { + return q1.isBetterThan(q2); + }); + totalQuality = *worst_Quality; + + // MetaData aggregation from all pads + totalBadString = createMetaData(mPadMetaData[Quality::Bad.getName()]); + totalMediumString = createMetaData(mPadMetaData[Quality::Medium.getName()]); + totalGoodString = createMetaData(mPadMetaData[Quality::Good.getName()]); + totalNullString = createMetaData(mPadMetaData[Quality::Null.getName()]); + } else { + totalNullString = "No Check was performed \n"; + } + + totalQuality.addMetadata(Quality::Bad.getName(), totalBadString); + totalQuality.addMetadata(Quality::Medium.getName(), totalMediumString); + totalQuality.addMetadata(Quality::Good.getName(), totalGoodString); + totalQuality.addMetadata(Quality::Null.getName(), totalNullString); + + return totalQuality; +} + +void CheckOfTrendings::beautify(std::shared_ptr mo, Quality checkResult) +{ + auto* canv = dynamic_cast(mo->getObject()); + if (!canv) { + ILOG(Error, Support) << "Canvas not found (beautify function)" << ENDM; + return; + } + + std::vector graphs; + getGraphs(canv, graphs, mo->getName()); + + if (graphs.size() == 0) { + ILOG(Error, Support) << "Could not retrieve any TGraph for CheckOfTrendings (beautify function)" << ENDM; + return; + } + for (size_t iGraph = 0; iGraph < graphs.size(); iGraph++) { + if (!graphs[iGraph]) { // if there is no TGraph, give an error and break + ILOG(Error, Support) << "TGraph number " << iGraph << " is NULL. (beautify function)" << ENDM; + return; + } + } + if (!mSliceTrend && graphs.size() > 1) { + ILOG(Error, Support) << "Multiple Graphs found even though this is not a slice trending (beautify function)" << ENDM; + return; + } + + for (size_t iGraph = 0; iGraph < graphs.size(); iGraph++) { + + // InfoBox + std::string checkMessage; + TPaveText* msg = new TPaveText(0.5, 0.75, 0.9, 0.9, "NDC"); + msg->SetName(Form("%s_msg_%zu", mo->GetName(), iGraph)); + graphs[iGraph]->GetListOfFunctions()->Add(msg); + + if (mPadQualities[iGraph] == Quality::Good) { + graphs[iGraph]->SetFillColor(kGreen - 2); + msg->Clear(); + msg->AddText("Quality::Good"); + msg->SetFillColor(kGreen - 2); + } else if (mPadQualities[iGraph] == Quality::Bad) { + graphs[iGraph]->SetFillColor(kRed); + msg->Clear(); + msg->AddText("Quality::Bad. Failed checks:"); + checkMessage = mPadMetaData[Quality::Bad.getName()][iGraph]; + msg->SetFillColor(kRed); + } else if (mPadQualities[iGraph] == Quality::Medium) { + graphs[iGraph]->SetFillColor(kOrange); + msg->Clear(); + msg->AddText("Quality::Medium. Failed checks:"); + checkMessage = mPadMetaData[Quality::Medium.getName()][iGraph]; + msg->SetFillColor(kOrange); + } else if (mPadQualities[iGraph] == Quality::Null) { + graphs[iGraph]->SetFillColor(0); + msg->AddText("Quality::Null. Failed checks:"); + checkMessage = mPadMetaData[Quality::Null.getName()][iGraph]; + } + + // Split lines by hand as \n does not work with TPaveText + const std::string delimiter = "\n"; + size_t pos = 0; + std::string subText; + while ((pos = checkMessage.find(delimiter)) != std::string::npos) { + subText = checkMessage.substr(0, pos); + msg->AddText(subText.c_str()); + checkMessage.erase(0, pos + delimiter.length()); + } + msg->AddText(checkResult.getMetadata("Comment", "").c_str()); + + const int nDataPoints = graphs[iGraph]->GetN(); + if (nDataPoints == 0) { + graphs[iGraph]->AddPoint(0., 0.); // have to add dummy point, else msg is not shown + graphs[iGraph]->SetMarkerColor(kWhite); // hide the dummy point + continue; + } + + graphs[iGraph]->SetLineColor(kBlack); + double xMin = graphs[iGraph]->GetXaxis()->GetXmin(); + double xMax = graphs[iGraph]->GetXaxis()->GetXmax(); + double yMin = 0.; + double yMax = 0.; + if (mSliceTrend) { + yMin = graphs[iGraph]->GetYaxis()->GetXmin(); + yMax = graphs[iGraph]->GetYaxis()->GetXmax(); + } else { + TH2* htemp = (TH2*)canv->GetListOfPrimitives()->FindObject("htemp"); + if (htemp) { + xMin = htemp->GetXaxis()->GetXmin(); + xMax = htemp->GetXaxis()->GetXmax(); + yMin = htemp->GetYaxis()->GetXmin(); + yMax = htemp->GetYaxis()->GetXmax(); + } + } + if (mMeanCheck) { + int nn = graphs[iGraph]->GetN(); + xMin = graphs[iGraph]->GetPointX(nn - 1) + (xMax - graphs[iGraph]->GetPointX(nn - 1)) / 4.; + } + + if (mExpectedValueCheck) { + TBox* badBox = new TBox(xMin, yMin, xMax, yMax); + badBox->SetFillColor(kRed); + badBox->SetFillStyle(1001); + badBox->SetLineWidth(0); + TBox* mediumBox = new TBox(xMin, mExpectedPhysicsValue - mNSigmaBadExpectedPhysicsValue * mStdev[iGraph], xMax, mExpectedPhysicsValue + mNSigmaBadExpectedPhysicsValue * mStdev[iGraph]); + mediumBox->SetFillColor(kOrange); + mediumBox->SetFillStyle(1001); + mediumBox->SetLineWidth(0); + TBox* goodBox = new TBox(xMin, mExpectedPhysicsValue - mNSigmaExpectedPhysicsValue * mStdev[iGraph], xMax, mExpectedPhysicsValue + mNSigmaExpectedPhysicsValue * mStdev[iGraph]); + goodBox->SetFillColor(kGreen - 2); + goodBox->SetFillStyle(1001); + goodBox->SetLineWidth(0); + graphs[iGraph]->GetListOfFunctions()->Add(badBox); + graphs[iGraph]->GetListOfFunctions()->Add(mediumBox); + graphs[iGraph]->GetListOfFunctions()->Add(goodBox); + } + + if (mRangeCheck) { + TLine* lineUpperRangeMed = new TLine(xMin, mExpectedPhysicsValue + mRangeMedium, xMax, mExpectedPhysicsValue + mRangeMedium); + lineUpperRangeMed->SetLineColor(kBlack); + lineUpperRangeMed->SetLineStyle(kDashed); + lineUpperRangeMed->SetLineWidth(1); + graphs[iGraph]->GetListOfFunctions()->Add(lineUpperRangeMed); + TLine* lineLowerRangeMed = new TLine(xMin, mExpectedPhysicsValue - mRangeMedium, xMax, mExpectedPhysicsValue - mRangeMedium); + lineLowerRangeMed->SetLineColor(kBlack); + lineLowerRangeMed->SetLineStyle(kDashed); + lineLowerRangeMed->SetLineWidth(1); + graphs[iGraph]->GetListOfFunctions()->Add(lineLowerRangeMed); + TLine* lineUpperRangeBad = new TLine(xMin, mExpectedPhysicsValue + mRangeBad, xMax, mExpectedPhysicsValue + mRangeBad); + lineUpperRangeBad->SetLineColor(kBlack); + lineUpperRangeBad->SetLineWidth(3); + graphs[iGraph]->GetListOfFunctions()->Add(lineUpperRangeBad); + TLine* lineLowerRangeBad = new TLine(xMin, mExpectedPhysicsValue - mRangeBad, xMax, mExpectedPhysicsValue - mRangeBad); + lineLowerRangeBad->SetLineColor(kBlack); + lineLowerRangeBad->SetLineWidth(3); + graphs[iGraph]->GetListOfFunctions()->Add(lineLowerRangeBad); + } + + // Draw the mean+stdev each point got compared against + if (mMeanCheck) { + TGraph* meanGraph = new TGraph(); + TGraph* stddevGraphMediumUp = new TGraph(); + TGraph* stddevGraphMediumDown = new TGraph(); + TGraph* stddevGraphBadUp = new TGraph(); + TGraph* stddevGraphBadDown = new TGraph(); + + const int nPoints = graphs[iGraph]->GetN(); + const double* yValues = graphs[iGraph]->GetY(); + const double* yErrors = graphs[iGraph]->GetEY(); // returns nullptr for TGraph (no errors) + bool useErrors = true; + if (yErrors == nullptr) { + useErrors = false; + } else { + const std::vector vErr(yErrors, yErrors + nPoints); + if (std::find(vErr.begin(), vErr.end(), 0.0) != vErr.end()) { + useErrors = false; + ILOG(Info, Support) << "Cannot take uncertainties of points into account for check of trending. At least one uncertainty is zero" << ENDM; + } + } + + if (nPoints > 2) { + for (int nBins = 1; nBins < nPoints; nBins++) { + int pointNumberForMean = mPointToTakeForMeanCheck; + if ((nBins) < mPointToTakeForMeanCheck) { + pointNumberForMean = nBins; + } + + double mean = 0.; + double stdevMean = 0.; + calculateStatistics(yValues, yErrors, useErrors, nBins - pointNumberForMean, nBins, mean, stdevMean); + + double lastPointError = 0.; + if (useErrors) { + lastPointError = *(yErrors + nBins); // Error of last point + } + if (lastPointError < 0.) { + ILOG(Warning, Support) << "Last point for check of mean has negative error" << ENDM; + } + + double stdev = sqrt(stdevMean * stdevMean + lastPointError * lastPointError); + + meanGraph->AddPoint(graphs[iGraph]->GetPointX(nBins), mean); + stddevGraphMediumUp->AddPoint(graphs[iGraph]->GetPointX(nBins), mean + stdev * mNSigmaMean); + stddevGraphMediumDown->AddPoint(graphs[iGraph]->GetPointX(nBins), mean - stdev * mNSigmaMean); + stddevGraphBadUp->AddPoint(graphs[iGraph]->GetPointX(nBins), mean + stdev * mNSigmaBadMean); + stddevGraphBadDown->AddPoint(graphs[iGraph]->GetPointX(nBins), mean - stdev * mNSigmaBadMean); + } // for (int nBins = 1; nBins < nPoints; nBins++) + + meanGraph->SetLineWidth(2); + meanGraph->SetLineColor(kGreen - 2); + meanGraph->SetMarkerColor(kGreen - 2); + + stddevGraphMediumUp->SetLineWidth(2); + stddevGraphMediumUp->SetLineColor(kOrange); + stddevGraphMediumUp->SetMarkerColor(kOrange); + + stddevGraphMediumDown->SetLineWidth(2); + stddevGraphMediumDown->SetLineColor(kOrange); + stddevGraphMediumDown->SetMarkerColor(kOrange); + + stddevGraphBadUp->SetLineWidth(2); + stddevGraphBadUp->SetLineColor(kRed); + stddevGraphBadUp->SetMarkerColor(kRed); + + stddevGraphBadDown->SetLineWidth(2); + stddevGraphBadDown->SetLineColor(kRed); + stddevGraphBadDown->SetMarkerColor(kRed); + + graphs[iGraph]->GetListOfFunctions()->Add(meanGraph); + graphs[iGraph]->GetListOfFunctions()->Add(stddevGraphMediumUp); + graphs[iGraph]->GetListOfFunctions()->Add(stddevGraphMediumDown); + graphs[iGraph]->GetListOfFunctions()->Add(stddevGraphBadUp); + graphs[iGraph]->GetListOfFunctions()->Add(stddevGraphBadDown); + } + } + } // for(size_t iGraph = 0; iGraph < graphs.size(); iGraph++) +} + +void CheckOfTrendings::getGraphs(TCanvas* canv, std::vector& graphs, const std::string moName) +{ + if (mSliceTrend) { + TList* padList = (TList*)canv->GetListOfPrimitives(); + padList->SetOwner(kTRUE); + const int numberPads = padList->GetEntries(); + for (int iPad = 0; iPad < numberPads; iPad++) { + auto pad = static_cast(padList->At(iPad)); + graphs.push_back(static_cast(pad->GetPrimitive("Graph"))); // SliceTrendingTask saves things as Graph + } + } else { + // If we have a standard trending with a TGraphErrors, then there will be a TGraph and a TGraphErrors with the same name ("Graph") + // with the current configuration the TGraphErrors will always be added after the TGraph + // loop from the back and find the last element with the name "Graph" + TGraph* g; + int jList = canv->GetListOfPrimitives()->LastIndex(); + for (; jList > 0; jList--) { + if (!strcmp(canv->GetListOfPrimitives()->At(jList)->GetName(), (moName + "_errors").c_str())) { // Trending Task saves things under the MO name + g = (TGraph*)canv->GetListOfPrimitives()->At(jList); + break; + } else if (!strcmp(canv->GetListOfPrimitives()->At(jList)->GetName(), moName.c_str())) { // Trending Task saves things under the MO name + g = (TGraph*)canv->GetListOfPrimitives()->At(jList); + break; + } + } + if (!g) { + // if the upper loop somehow fails, log a warning and get the object the old fashioned way + ILOG(Warning, Support) << "No TGraph found in the List of primitives." << ENDM; + g = (TGraph*)canv->GetListOfPrimitives()->FindObject("Graph"); + } + graphs.push_back(g); + } +} + +std::string CheckOfTrendings::createMetaData(const std::vector& pointMetaData) +{ + + std::string totalString = ""; + + if (pointMetaData.size() > 1) { + std::string meanString = ""; + std::string expectedValueString = ""; + std::string rangeString = ""; + std::string zeroString = ""; + std::string noDataPointsString = ""; + std::string noChecksString = ""; + + for (size_t i = 0; i < pointMetaData.size(); i++) { + if (pointMetaData.at(i).find("MeanCheck") != std::string::npos) { + meanString += " " + std::to_string(i) + ","; + } + if (pointMetaData.at(i).find("ExpectedValueCheck") != std::string::npos) { + expectedValueString += " " + std::to_string(i) + ","; + } + if (pointMetaData.at(i).find("RangeCheck") != std::string::npos) { + rangeString += " " + std::to_string(i) + ","; + } + if (pointMetaData.at(i).find("ZeroCheck") != std::string::npos) { + zeroString += " " + std::to_string(i) + ","; + } + if (pointMetaData.at(i).find("Graph has no data points") != std::string::npos) { + noDataPointsString += " " + std::to_string(i) + ","; + } + if (pointMetaData.at(i).find("No Checks performed") != std::string::npos) { + noChecksString += " " + std::to_string(i) + ","; + } + } + + if (meanString != "") { + meanString.pop_back(); + meanString = "MeanCheck for Graphs:" + meanString + "\n"; + totalString += meanString; + } + if (expectedValueString != "") { + expectedValueString.pop_back(); + expectedValueString = "ExpectedValueCheck for Graphs:" + expectedValueString + "\n"; + totalString += expectedValueString; + } + if (rangeString != "") { + rangeString.pop_back(); + rangeString = "RangeCheck for Graphs:" + rangeString + "\n"; + totalString += rangeString; + } + if (zeroString != "") { + zeroString.pop_back(); + zeroString = "ZeroCheck for Graphs:" + zeroString + "\n"; + totalString += zeroString; + } + if (noDataPointsString != "") { + noDataPointsString.pop_back(); + noDataPointsString = "No data points for Graphs:" + noDataPointsString + "\n"; + totalString += noDataPointsString; + } + if (noChecksString != "") { + noChecksString.pop_back(); + noChecksString = "No checks performed for Graphs:" + noChecksString + "\n"; + totalString += noChecksString; + } + } else { + totalString = pointMetaData[0]; + } + + return totalString; +} + +} // namespace o2::quality_control_modules::tpc \ No newline at end of file diff --git a/Modules/TPC/src/ClusterVisualizer.cxx b/Modules/TPC/src/ClusterVisualizer.cxx new file mode 100644 index 0000000000..4853b0dfc2 --- /dev/null +++ b/Modules/TPC/src/ClusterVisualizer.cxx @@ -0,0 +1,306 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ClusterVisualizer.cxx +/// \author Thomas Klemenz +/// + +// O2 includes +#if __has_include("TPCBase/Painter.h") +#include "TPCBase/Painter.h" +#include "TPCBase/CDBInterface.h" +#else +#include "TPCBaseRecSim/Painter.h" +#include "TPCBaseRecSim/CDBInterface.h" +#endif +#include "TPCQC/Helpers.h" +#include "DetectorsBase/GRPGeomHelper.h" + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/ClusterVisualizer.h" +#include "TPC/Utility.h" +#include "TPC/ClustersData.h" + +// root includes +#include "TCanvas.h" +#include "TPaveText.h" + +#include +#include +#include + +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::tpc +{ + +void ClusterVisualizer::configure(const boost::property_tree::ptree& config) +{ + auto& id = getID(); + + for (const auto& timestamp : config.get_child("qc.postprocessing." + id + ".timestamps")) { + mTimestamps.emplace_back(std::stol(timestamp.second.data())); + } + + std::vector keyVec{}; + std::vector valueVec{}; + for (const auto& data : config.get_child("qc.postprocessing." + id + ".lookupMetaData")) { + mLookupMaps.emplace_back(std::map()); + if (const auto& keys = data.second.get_child_optional("keys"); keys.has_value()) { + for (const auto& key : keys.value()) { + keyVec.emplace_back(key.second.data()); + } + } + if (const auto& values = data.second.get_child_optional("values"); values.has_value()) { + for (const auto& value : values.value()) { + valueVec.emplace_back(value.second.data()); + } + } + auto vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mLookupMaps.back().insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for lookupMetaData are not matching" << ENDM; + } + keyVec.clear(); + valueVec.clear(); + } + + for (const auto& data : config.get_child("qc.postprocessing." + id + ".storeMetaData")) { + mStoreMaps.emplace_back(std::map()); + if (const auto& keys = data.second.get_child_optional("keys"); keys.has_value()) { + for (const auto& key : keys.value()) { + keyVec.emplace_back(key.second.data()); + } + } + if (const auto& values = data.second.get_child_optional("values"); values.has_value()) { + for (const auto& value : values.value()) { + valueVec.emplace_back(value.second.data()); + } + } + auto vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mStoreMaps.back().insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for storeMetaData are not matching" << ENDM; + } + keyVec.clear(); + valueVec.clear(); + } + + for (const auto& entry : config.get_child("qc.postprocessing." + id + ".histogramRanges")) { + for (const auto& type : entry.second) { + for (const auto& value : type.second) { + mRanges[type.first].emplace_back(std::stof(value.second.data())); + } + } + } + + mPath = config.get("qc.postprocessing." + id + ".path"); + mHost = config.get("qc.postprocessing." + id + ".dataSourceURL"); + + const auto type = config.get("qc.postprocessing." + id + ".dataType"); + if (type == "clusters") { + mIsClusters = true; + mObservables = { + "N_Clusters", + "Q_Max", + "Q_Tot", + "Sigma_Pad", + "Sigma_Time", + "Time_Bin", + "Occupancy" + }; + } else if (type == "raw") { + mIsClusters = false; + mObservables = { + "N_RawDigits", + "Q_Max", + "Time_Bin", + "Occupancy" + }; + } else { + ILOG(Error, Support) << "No valid data type given. 'dataType' has to be either 'clusters' or 'raw'." << ENDM; + } +} + +void ClusterVisualizer::initialize(Trigger, framework::ServiceRegistryRef) +{ + mCdbApi.init(mHost); + + mNHBFPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + + if (mCalDetCanvasVec.size() > 0) { + mCalDetCanvasVec.clear(); + } + + auto calDetIter = 0; + for (const auto& type : mObservables) { + mCalDetCanvasVec.emplace_back(std::vector>()); + addAndPublish(getObjectsManager(), + mCalDetCanvasVec.back(), + { fmt::format("c_Sides_{}", type).data(), + fmt::format("c_ROCs_{}_1D", type).data(), + fmt::format("c_ROCs_{}_2D", type).data() }, + mStoreMaps.size() > 1 ? mStoreMaps.at(calDetIter) : mStoreMaps.at(0)); + calDetIter++; + } + mCalDetCanvasVec.emplace_back(std::vector>()); + addAndPublish(getObjectsManager(), + mCalDetCanvasVec.back(), + { "c_radial_profile_Occupancy" }, + mStoreMaps.size() > 1 ? mStoreMaps.at(calDetIter) : mStoreMaps.at(0)); +} + +void ClusterVisualizer::update(Trigger t, framework::ServiceRegistryRef) +{ + ILOG(Info, Support) << "Trigger type is: " << t.triggerType << ", the timestamp is " << t.timestamp << ENDM; + + for (auto& vec : mCalDetCanvasVec) { + for (auto& canvas : vec) { + canvas.get()->Clear(); + } + } + + auto calDetIter = 0; + + std::unique_ptr clusterData(mCdbApi.retrieveFromTFileAny(mPath, + mLookupMaps.size() > 1 ? mLookupMaps.at(calDetIter) : mLookupMaps.at(0), + mTimestamps.size() > 0 ? mTimestamps.at(calDetIter) : t.timestamp)); + + auto& clusters = clusterData->getClusters(); + + auto& calDet = clusters.getNClusters(); + auto vecPtr = toVector(mCalDetCanvasVec.at(calDetIter)); + + o2::tpc::painter::makeSummaryCanvases(calDet, int(mRanges[calDet.getName()].at(0)), mRanges[calDet.getName()].at(1), mRanges[calDet.getName()].at(2), false, &vecPtr); + calDetIter++; + + calDet = clusters.getQMax(); + vecPtr = toVector(mCalDetCanvasVec.at(calDetIter)); + o2::tpc::painter::makeSummaryCanvases(calDet, int(mRanges[calDet.getName()].at(0)), mRanges[calDet.getName()].at(1), mRanges[calDet.getName()].at(2), false, &vecPtr); + calDetIter++; + + if (mIsClusters) { + calDet = clusters.getQTot(); + vecPtr = toVector(mCalDetCanvasVec.at(calDetIter)); + o2::tpc::painter::makeSummaryCanvases(calDet, int(mRanges[calDet.getName()].at(0)), mRanges[calDet.getName()].at(1), mRanges[calDet.getName()].at(2), false, &vecPtr); + calDetIter++; + + calDet = clusters.getSigmaPad(); + vecPtr = toVector(mCalDetCanvasVec.at(calDetIter)); + o2::tpc::painter::makeSummaryCanvases(calDet, int(mRanges[calDet.getName()].at(0)), mRanges[calDet.getName()].at(1), mRanges[calDet.getName()].at(2), false, &vecPtr); + calDetIter++; + + calDet = clusters.getSigmaTime(); + vecPtr = toVector(mCalDetCanvasVec.at(calDetIter)); + o2::tpc::painter::makeSummaryCanvases(calDet, int(mRanges[calDet.getName()].at(0)), mRanges[calDet.getName()].at(1), mRanges[calDet.getName()].at(2), false, &vecPtr); + calDetIter++; + } + + calDet = clusters.getTimeBin(); + vecPtr = toVector(mCalDetCanvasVec.at(calDetIter)); + o2::tpc::painter::makeSummaryCanvases(calDet, int(mRanges[calDet.getName()].at(0)), mRanges[calDet.getName()].at(1), mRanges[calDet.getName()].at(2), false, &vecPtr); + calDetIter++; + + auto occupancy = clusters.getOccupancy(mNHBFPerTF); + vecPtr = toVector(mCalDetCanvasVec.at(calDetIter)); + o2::tpc::painter::makeSummaryCanvases(occupancy, int(mRanges[occupancy.getName()].at(0)), mRanges[occupancy.getName()].at(1), mRanges[occupancy.getName()].at(2), false, &vecPtr); + calDetIter++; + vecPtr = toVector(mCalDetCanvasVec.at(calDetIter)); + makeRadialProfile(occupancy, vecPtr.at(0), int(mRanges[occupancy.getName()].at(0)), mRanges[occupancy.getName()].at(1), mRanges[occupancy.getName()].at(2)); + calDetIter++; +} + +void ClusterVisualizer::finalize(Trigger t, framework::ServiceRegistryRef) +{ + for (const auto& calDetCanvasVec : mCalDetCanvasVec) { + for (const auto& canvas : calDetCanvasVec) { + getObjectsManager()->stopPublishing(canvas.get()); + } + } + + if (mCalDetCanvasVec.size() > 0) { + mCalDetCanvasVec.clear(); + } +} + +template +void ClusterVisualizer::makeRadialProfile(o2::tpc::CalDet& calDet, TCanvas* canv, int nbinsY, float yMin, float yMax) +{ + const std::string_view calName = calDet.getName(); + const auto radialBinning = o2::tpc::painter::getRowBinningCM(); + + auto hAside2D = new TH2D(fmt::format("h_{}_radialProfile_Aside", calName).data(), fmt::format("{}: Radial profile (A-Side)", calName).data(), radialBinning.size() - 1, radialBinning.data(), nbinsY, yMin, yMax); + hAside2D->GetXaxis()->SetTitle("x (cm)"); + hAside2D->GetYaxis()->SetTitle(fmt::format("{}", calName).data()); + hAside2D->SetTitleOffset(1.05, "XY"); + hAside2D->SetTitleSize(0.05, "XY"); + hAside2D->SetStats(0); + + auto hCside2D = new TH2D(fmt::format("h_{}_radialProfile_Cside", calName).data(), fmt::format("{}: Radial profile (C-Side)", calName).data(), radialBinning.size() - 1, radialBinning.data(), nbinsY, yMin, yMax); + hCside2D->GetXaxis()->SetTitle("x (cm)"); + hCside2D->GetYaxis()->SetTitle(fmt::format("{}", calName).data()); + hCside2D->SetTitleOffset(1.05, "XY"); + hCside2D->SetTitleSize(0.05, "XY"); + hCside2D->SetStats(0); + + fillRadialHisto(*hAside2D, calDet, o2::tpc::Side::A); + fillRadialHisto(*hCside2D, calDet, o2::tpc::Side::C); + + canv->Divide(1, 2); + canv->cd(1); + hAside2D->Draw("colz"); + hAside2D->SetStats(0); + hAside2D->ProfileX("profile_ASide", 1, -1, "d,same"); + + canv->cd(2); + hCside2D->Draw("colz"); + hCside2D->ProfileX("profile_CSide", 1, -1, "d,same"); + hAside2D->SetStats(0); + + hAside2D->SetBit(TObject::kCanDelete); + hCside2D->SetBit(TObject::kCanDelete); +} + +template +void ClusterVisualizer::fillRadialHisto(TH2D& h2D, const o2::tpc::CalDet& calDet, const o2::tpc::Side side) +{ + const o2::tpc::Mapper& mapper = o2::tpc::Mapper::instance(); + + for (o2::tpc::ROC roc; !roc.looped(); ++roc) { + if (roc.side() != side) { + continue; + } + const int nrows = mapper.getNumberOfRowsROC(roc); + for (int irow = 0; irow < nrows; ++irow) { + const int npads = mapper.getNumberOfPadsInRowROC(roc, irow); + const int globalRow = irow + (roc >= o2::tpc::Mapper::getNumberOfIROCs()) * o2::tpc::Mapper::getNumberOfRowsInIROC(); + for (int ipad = 0; ipad < npads; ++ipad) { + const auto val = calDet.getValue(roc, irow, ipad); + const o2::tpc::LocalPosition2D pos = mapper.getPadCentre(o2::tpc::PadPos(globalRow, ipad)); + h2D.Fill(pos.X(), val); + } + } + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/Clusters.cxx b/Modules/TPC/src/Clusters.cxx new file mode 100644 index 0000000000..17a5627d36 --- /dev/null +++ b/Modules/TPC/src/Clusters.cxx @@ -0,0 +1,204 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Clusters.cxx +/// \author Thomas Klemenz +/// + +// O2 includes +#include "Framework/ProcessingContext.h" +#include "DataFormatsTPC/KrCluster.h" +#include "DataFormatsTPC/ClusterNative.h" +#include "Framework/InputRecordWalker.h" +#include "DetectorsBase/GRPGeomHelper.h" + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/Clusters.h" +#include "TPC/Utility.h" + +using namespace o2::framework; +using namespace o2::tpc; + +namespace o2::quality_control_modules::tpc +{ + +Clusters::Clusters() : TaskInterface() +{ +} + +void Clusters::initialize(InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TPC Clusters QC task" << ENDM; + + mQCClusters.setName("ClusterData"); + + mNHBFPerTF = o2::base::GRPGeomHelper::instance().getNHBFPerTF(); + + const auto last = mCustomParameters.end(); + const auto itMergeable = mCustomParameters.find("mergeableOutput"); + std::string mergeable; + + if (itMergeable == last) { + LOGP(warning, "missing parameter 'mergeableOutput'"); + LOGP(warning, "Please add 'mergeableOutput': '' to the 'taskParameters'."); + } else { + mergeable = itMergeable->second; + } + + if (mergeable == "true") { + mIsMergeable = true; + ILOG(Info, Support) << "Using mergeable output for Clusters Task." << ENDM; + } else if (mergeable == "false") { + mIsMergeable = false; + ILOG(Info, Support) << "Using non-mergeable output for Clusters Task." << ENDM; + } else { + mIsMergeable = false; + LOGP(warning, "No valid value for 'mergeableOutput'. Set it as 'true' or 'false'. Falling back to non-mergeable output."); + } + + if (mIsMergeable) { + getObjectsManager()->startPublishing(&mQCClusters); + } else { + mWrapperVector.emplace_back(&mQCClusters.getClusters().getNClusters()); + mWrapperVector.emplace_back(&mQCClusters.getClusters().getQMax()); + mWrapperVector.emplace_back(&mQCClusters.getClusters().getQTot()); + mWrapperVector.emplace_back(&mQCClusters.getClusters().getSigmaTime()); + mWrapperVector.emplace_back(&mQCClusters.getClusters().getSigmaPad()); + mWrapperVector.emplace_back(&mQCClusters.getClusters().getTimeBin()); + auto occupancy = mQCClusters.getClusters().getOccupancy(mNHBFPerTF); + mWrapperVector.emplace_back(&occupancy); + + addAndPublish(getObjectsManager(), mNClustersCanvasVec, { "c_Sides_N_Clusters", "c_ROCs_N_Clusters_1D", "c_ROCs_N_Clusters_2D" }); + addAndPublish(getObjectsManager(), mQMaxCanvasVec, { "c_Sides_Q_Max", "c_ROCs_Q_Max_1D", "c_ROCs_Q_Max_2D" }); + addAndPublish(getObjectsManager(), mQTotCanvasVec, { "c_Sides_Q_Tot", "c_ROCs_Q_Tot_1D", "c_ROCs_Q_Tot_2D" }); + addAndPublish(getObjectsManager(), mSigmaTimeCanvasVec, { "c_Sides_Sigma_Time", "c_ROCs_Sigma_Time_1D", "c_ROCs_Sigma_Time_2D" }); + addAndPublish(getObjectsManager(), mSigmaPadCanvasVec, { "c_Sides_Sigma_Pad", "c_ROCs_Sigma_Pad_1D", "c_ROCs_Sigma_Pad_2D" }); + addAndPublish(getObjectsManager(), mTimeBinCanvasVec, { "c_Sides_Time_Bin", "c_ROCs_Time_Bin_1D", "c_ROCs_Time_Bin_2D" }); + addAndPublish(getObjectsManager(), mOccupancyCanvasVec, { "c_Sides_Occupancy", "c_ROCs_Occupancy_1D", "c_ROCs_Occupancy_2D" }); + + for (auto& wrapper : mWrapperVector) { + getObjectsManager()->startPublishing(&wrapper); + } + } +} + +void Clusters::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + + mQCClusters.getClusters().reset(); + if (!mIsMergeable) { + clearCanvases(mNClustersCanvasVec); + clearCanvases(mQMaxCanvasVec); + clearCanvases(mQTotCanvasVec); + clearCanvases(mSigmaTimeCanvasVec); + clearCanvases(mSigmaPadCanvasVec); + clearCanvases(mTimeBinCanvasVec); + } +} + +void Clusters::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void Clusters::processClusterNative(InputRecord& inputs) +{ + const auto& inputsTPCclusters = clusterHandler(inputs); + if (!inputsTPCclusters->clusterIndex.nClustersTotal) { + return; + } + + for (int isector = 0; isector < o2::tpc::constants::MAXSECTOR; ++isector) { + for (int irow = 0; irow < o2::tpc::constants::MAXGLOBALPADROW; ++irow) { + const int nClusters = inputsTPCclusters->clusterIndex.nClusters[isector][irow]; + for (int icl = 0; icl < nClusters; ++icl) { + const auto& cl = *(inputsTPCclusters->clusterIndex.clusters[isector][irow] + icl); + mQCClusters.getClusters().processCluster(cl, Sector(isector), irow); + } + } + } + mQCClusters.getClusters().endTF(); +} + +void Clusters::processKrClusters(InputRecord& inputs) +{ + std::vector filterKr = { + { "krClusters", ConcreteDataTypeMatcher{ "TPC", "KRCLUSTERS" }, Lifetime::Timeframe }, + { "sampled-krClusters", ConcreteDataTypeMatcher{ "DS", "KRCLUSTERS" }, Lifetime::Timeframe }, + }; + + for (auto const& inputRef : InputRecordWalker(inputs, filterKr)) { + auto krClusters = inputs.get>(inputRef); + for (const auto& cl : krClusters) { + mQCClusters.getClusters().processCluster(cl, Sector(cl.sector), int(cl.meanRow)); + } + } + mQCClusters.getClusters().endTF(); +} + +void Clusters::monitorData(ProcessingContext& ctx) +{ + mQCClusters.getClusters().denormalize(); + + processClusterNative(ctx.inputs()); + processKrClusters(ctx.inputs()); + + if (!mIsMergeable) { + mQCClusters.getClusters().normalize(); + + fillCanvases(mQCClusters.getClusters().getNClusters(), mNClustersCanvasVec, mCustomParameters, "NClusters"); + fillCanvases(mQCClusters.getClusters().getQMax(), mQMaxCanvasVec, mCustomParameters, "Qmax"); + fillCanvases(mQCClusters.getClusters().getQTot(), mQTotCanvasVec, mCustomParameters, "Qtot"); + fillCanvases(mQCClusters.getClusters().getSigmaTime(), mSigmaTimeCanvasVec, mCustomParameters, "SigmaPad"); + fillCanvases(mQCClusters.getClusters().getSigmaPad(), mSigmaPadCanvasVec, mCustomParameters, "SigmaTime"); + fillCanvases(mQCClusters.getClusters().getTimeBin(), mTimeBinCanvasVec, mCustomParameters, "TimeBin"); + fillCanvases(mQCClusters.getClusters().getTimeBin(), mOccupancyCanvasVec, mCustomParameters, "Occupancy"); + } +} + +void Clusters::endOfCycle() +{ + ILOG(Info, Support) << "endOfCycle" << ENDM; + ILOG(Info, Support) << "Processed TFs: " << mQCClusters.getClusters().getProcessedTFs() << ENDM; + + if (mIsMergeable) { + mQCClusters.getClusters().normalize(); + } +} + +void Clusters::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void Clusters::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the data" << ENDM; + + mQCClusters.getClusters().reset(); + + if (!mIsMergeable) { + clearCanvases(mNClustersCanvasVec); + clearCanvases(mQMaxCanvasVec); + clearCanvases(mQTotCanvasVec); + clearCanvases(mSigmaTimeCanvasVec); + clearCanvases(mSigmaPadCanvasVec); + clearCanvases(mTimeBinCanvasVec); + clearCanvases(mOccupancyCanvasVec); + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/DCSPTempReductor.cxx b/Modules/TPC/src/DCSPTempReductor.cxx new file mode 100644 index 0000000000..717f98510f --- /dev/null +++ b/Modules/TPC/src/DCSPTempReductor.cxx @@ -0,0 +1,83 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DCSPTempReductor.cxx +/// \author Marcel Lesch +/// + +#include "TPC/DCSPTempReductor.h" +#include "DataFormatsTPC/DCS.h" +#include "TPC/Utility.h" + +namespace o2::quality_control_modules::tpc +{ + +void* DCSPTempReductor::getBranchAddress() +{ + return &mStats; +} + +const char* DCSPTempReductor::getBranchLeafList() +{ + return "tempSensor[18]/F:tempSensorErr[18]:tempMeanPerSide[2]:tempMeanPerSideErr[2]:tempGradXPerSide[2]:tempGradXPerSideErr[2]:tempGradYPerSide[2]:tempGradYPerSideErr[2]"; +} + +bool DCSPTempReductor::update(ConditionRetriever& retriever) +{ + if (auto dcstemp = retriever.retrieve()) { + + int sensorCounter = 0; + std::vector sensorData[18]; + for (const auto& sensor : dcstemp->raw) { + for (const auto& value : sensor.data) { + sensorData[sensorCounter].emplace_back(value.value); + } + calcMeanAndStddev(sensorData[sensorCounter], mStats.tempSensor[sensorCounter], mStats.tempSensorErr[sensorCounter]); + sensorCounter++; + if (sensorCounter > 17) + break; + } + + std::vector sideData[3]; // 0 mean, 1 gradX, 2 gradY + + // A-Side + for (const auto& value : dcstemp->statsA.data) { + sideData[0].emplace_back(value.value.mean); + sideData[1].emplace_back(value.value.gradX); + sideData[2].emplace_back(value.value.gradY); + } + + calcMeanAndStddev(sideData[0], mStats.tempMeanPerSide[0], mStats.tempMeanPerSideErr[0]); + calcMeanAndStddev(sideData[1], mStats.tempGradXPerSide[0], mStats.tempGradXPerSideErr[0]); + calcMeanAndStddev(sideData[2], mStats.tempGradYPerSide[0], mStats.tempGradYPerSideErr[0]); + + for (int iCount = 0; iCount < 3; iCount++) { + sideData[iCount].clear(); + } + + // C-Side + for (const auto& value : dcstemp->statsC.data) { + sideData[0].emplace_back(value.value.mean); + sideData[1].emplace_back(value.value.gradX); + sideData[2].emplace_back(value.value.gradY); + } + + calcMeanAndStddev(sideData[0], mStats.tempMeanPerSide[1], mStats.tempMeanPerSideErr[1]); + calcMeanAndStddev(sideData[1], mStats.tempGradXPerSide[1], mStats.tempGradXPerSideErr[1]); + calcMeanAndStddev(sideData[2], mStats.tempGradYPerSide[1], mStats.tempGradYPerSideErr[1]); + + return true; + } + return false; +} + +} // namespace o2::quality_control_modules::tpc \ No newline at end of file diff --git a/Modules/TPC/src/DCSPTemperature.cxx b/Modules/TPC/src/DCSPTemperature.cxx new file mode 100644 index 0000000000..bd09fc60ec --- /dev/null +++ b/Modules/TPC/src/DCSPTemperature.cxx @@ -0,0 +1,115 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DCSPTemperature.cxx +/// \author Thomas Klemenz +/// + +// O2 includes +#include "DataFormatsTPC/DCS.h" + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/DCSPTemperature.h" +#include "TPC/Utility.h" + +// root includes +#include "TCanvas.h" +#include "TGraph.h" +#include "TStyle.h" + +#include +#include + +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::tpc +{ + +void DCSPTemperature::configure(const boost::property_tree::ptree& config) +{ + auto& id = getID(); + std::vector keyVec{}; + std::vector valueVec{}; + for (const auto& key : config.get_child("qc.postprocessing." + id + ".lookupMetaData.keys")) { + keyVec.emplace_back(key.second.data()); + } + for (const auto& value : config.get_child("qc.postprocessing." + id + ".lookupMetaData.values")) { + valueVec.emplace_back(value.second.data()); + } + + int vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mLookupMap.insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for lookupMetaData are not matching" << ENDM; + } + + keyVec.clear(); + valueVec.clear(); + + for (const auto& key : config.get_child("qc.postprocessing." + id + ".storeMetaData.keys")) { + keyVec.emplace_back(key.second.data()); + } + for (const auto& value : config.get_child("qc.postprocessing." + id + ".storeMetaData.values")) { + valueVec.emplace_back(value.second.data()); + } + + vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mStoreMap.insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + + mTimestamp = std::stol(config.get("qc.postprocessing." + id + ".timestamp")); + mNFiles = std::stoi(config.get("qc.postprocessing." + id + ".nFiles")); + mHost = config.get("qc.postprocessing." + id + ".dataSourceURL"); +} + +void DCSPTemperature::initialize(Trigger, framework::ServiceRegistryRef) +{ + mCdbApi.init(mHost); + mDCSPTemp.initializeCanvases(); + + for (auto& canv : mDCSPTemp.getCanvases()) { + getObjectsManager()->startPublishing(canv); + for (const auto& [key, value] : mStoreMap) { + getObjectsManager()->addMetadata(canv->GetName(), key, value); + } + } +} + +void DCSPTemperature::update(Trigger, framework::ServiceRegistryRef) +{ + std::vector usedTimestamps = getDataTimestamps(mCdbApi, "TPC/Calib/Temperature", mNFiles, mTimestamp); + for (auto& timestamp : usedTimestamps) { + mData.emplace_back(mCdbApi.retrieveFromTFileAny("TPC/Calib/Temperature", mLookupMap, timestamp)); + } + + mDCSPTemp.processData(mData); +} + +void DCSPTemperature::finalize(Trigger, framework::ServiceRegistryRef) +{ + for (auto& canv : mDCSPTemp.getCanvases()) { + getObjectsManager()->stopPublishing(canv); + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/GPUErrorQA.cxx b/Modules/TPC/src/GPUErrorQA.cxx new file mode 100644 index 0000000000..af76a60487 --- /dev/null +++ b/Modules/TPC/src/GPUErrorQA.cxx @@ -0,0 +1,72 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GPUErrorQA.cxx +/// \author Anton Riede, anton.riedel@cern.ch +/// + +// root includes +#include + +// O2 includes +#include "Framework/ProcessingContext.h" +#include + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/GPUErrorQA.h" + +namespace o2::quality_control_modules::tpc +{ + +void GPUErrorQA::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TPC GPUErrorQA QC task" << ENDM; + mGPUErrorQA.initializeHistograms(); +} + +void GPUErrorQA::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Support) << "startOfActivity" << ENDM; + mGPUErrorQA.resetHistograms(); +} + +void GPUErrorQA::startOfCycle() +{ + ILOG(Debug, Support) << "startOfCycle" << ENDM; +} + +void GPUErrorQA::monitorData(o2::framework::ProcessingContext& ctx) +{ + ILOG(Debug, Devel) << "monitorData" << ENDM; + auto errors = ctx.inputs().get>>("error-qa"); + mGPUErrorQA.processErrors(errors); +} + +void GPUErrorQA::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void GPUErrorQA::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void GPUErrorQA::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mGPUErrorQA.resetHistograms(); +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/GenericHistogramCheck.cxx b/Modules/TPC/src/GenericHistogramCheck.cxx new file mode 100644 index 0000000000..702e988162 --- /dev/null +++ b/Modules/TPC/src/GenericHistogramCheck.cxx @@ -0,0 +1,366 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file GenericHistogramCheck.cxx +/// \author Maximilian Horst +/// + +#include "TPC/GenericHistogramCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" + +// ROOT +#include +#include +#include +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::tpc +{ +Quality CheckQuality(double Mean, double Comparison, double Offset, double nMed, double nBad, std::string& message) +{ + message = ""; + Quality result = Quality::Null; + double deviation = std::abs(Mean - Comparison); + + if (deviation < nMed * Offset) { + result = Quality::Good; + } else if (deviation > nBad * Offset) { + result = Quality::Bad; + } else { + result = Quality::Medium; + } + message = "Deviation to expected Value: " + std::to_string(deviation); + + return result; +} +void GenericHistogramCheck::configure() +{ + mMetadataComment = common::getFromConfig(mCustomParameters, "MetadataComment", ""); + + // ILOG(Warning, Support) << "Config started....?" << ENDM; + if (const auto param = mCustomParameters.find("checks"); param != mCustomParameters.end()) { + const std::string checkString = param->second.c_str(); + if (const auto param1 = checkString.find("Range"); param1 != string::npos) { + mCheckRange = true; + } + if (const auto param1 = checkString.find("StdDev"); param1 != string::npos) { + mCheckStdDev = true; + } + if (!mCheckRange && !mCheckStdDev) { + + mCheckStdDev = true; + ILOG(Warning, Support) << "The given value for check was not readable. The options are Range and StdDev. As a default StdDev was chosen." << ENDM; + } + } else { + mCheckStdDev = true; + ILOG(Warning, Support) << "No Check was given. The options are Range and StdDev. As a default StdDev was chosen." << ENDM; + } + + if (const auto param = mCustomParameters.find("axis"); param != mCustomParameters.end()) { + const std::string axisString = param->second.c_str(); + if (const auto param1 = axisString.find("X"); param1 != string::npos) { + mCheckXAxis = true; + } + if (const auto param1 = axisString.find("Y"); param1 != string::npos) { + mCheckYAxis = true; + } + if (!mCheckXAxis && !mCheckYAxis) { + + mCheckXAxis = true; + ILOG(Warning, Support) << "The given value for axis was not readable. The options are X and Y. As a default X was chosen." << ENDM; + } + } else { + mCheckStdDev = true; + ILOG(Warning, Support) << "No axis was given. The options are X and Y. As a default X was chosen." << ENDM; + } + + if (const auto param = mCustomParameters.find("ExpectedValueX"); param != mCustomParameters.end()) { + mExpectedValueX = std::atof(param->second.c_str()); + } else { + mExpectedValueX = 0; + ILOG(Warning, Support) << "No ExpectedValueX was given. Please always give an expected value. As a default 0 was chosen. " << ENDM; + } + if (const auto param = mCustomParameters.find("ExpectedValueY"); param != mCustomParameters.end()) { + mExpectedValueY = std::atof(param->second.c_str()); + } else { + mExpectedValueY = 0; + ILOG(Warning, Support) << "No ExpectedValueY was given. Please always give an expected value. As a default 0 was chosen. " << ENDM; + } + + if (mCheckRange) { + if (const auto param = mCustomParameters.find("RangeX"); param != mCustomParameters.end()) { + mRangeX = std::atof(param->second.c_str()); + } else { + mRangeX = 0.1 * mExpectedValueX; + ILOG(Warning, Support) << "No RangeX was given even though it was requested as a check. As a default " << 0.1 * mExpectedValueX << " was chosen. " << ENDM; + } + if (const auto param = mCustomParameters.find("RangeY"); param != mCustomParameters.end()) { + mRangeY = std::atof(param->second.c_str()); + } else { + mRangeY = 0.1 * mExpectedValueY; + ILOG(Warning, Support) << "No RangeY was given even though it was requested as a check. As a default " << 0.1 * mExpectedValueY << " was chosen. " << ENDM; + } + } +} +Quality GenericHistogramCheck::check(std::map>* moMap) +{ + std::vector mosQuality; + + std::vector checkMessage; + std::string message; + + Quality result = Quality::Null; + result.addMetadata("Comment", mMetadataComment); + + for (auto const& moObj : *moMap) { + auto mo = moObj.second; + if (!mo) { + continue; + ILOG(Error, Support) << "No MO found" << ENDM; + result.addMetadata(Quality::Null.getName(), "No MO found"); + return result; + } + auto h = dynamic_cast(mo->getObject()); + if (!h) { + ILOG(Error, Support) << "No Histogram found" << ENDM; + result.addMetadata(Quality::Null.getName(), "No Histogram found"); + return result; + } + + mHistDimension = h->GetDimension(); + if (mCheckXAxis) { + mMeanX = h->GetMean(1); + mStdevX = h->GetStdDev(1); + } else if (mHistDimension == 1) { + ILOG(Error, Support) << "a 1D Histogram was given, but the X-axis is not assigned to be checked. No Check was performed." << ENDM; + mMeanX = 999999999; // set it to some number so that the math does not break + mStdevX = 999999999; + } + if (mHistDimension == 2) { + if (mCheckYAxis) { + mMeanY = h->GetMean(2); + mStdevY = h->GetStdDev(2); + } + } else if (mHistDimension != 1) { + ILOG(Warning, Support) << "This check only supports 1 and 2 dimensional histograms." << ENDM; + // Brick. + } + + Quality resultRangeX = Quality::Null; + Quality resultStdDevX = Quality::Null; + Quality resultRangeY = Quality::Null; + Quality resultStdDevY = Quality::Null; + + // make function: + // CheckQuality(Axis,mean,Compare,offset,nMed,nBad) + + if (mCheckXAxis) { + if (mCheckRange) { + resultRangeX = CheckQuality(mMeanX, mExpectedValueX, mRangeX, 1, 2, message); + checkMessage.push_back("RangeCheck: " + message); + mosQuality.push_back(resultRangeX); + } + if (mCheckStdDev) { + resultStdDevX = CheckQuality(mMeanX, mExpectedValueX, mStdevX, 3, 6, message); + checkMessage.push_back("StdDevCheck: " + message); + mosQuality.push_back(resultStdDevX); + } + } + if (mCheckYAxis) { + if (mHistDimension == 2) { + if (mCheckRange) { + resultRangeY = CheckQuality(mMeanY, mExpectedValueY, mRangeY, 1, 2, message); + checkMessage.push_back("RangeCheck: " + message); + mosQuality.push_back(resultRangeY); + } + + if (mCheckStdDev) { + resultStdDevY = CheckQuality(mMeanX, mExpectedValueX, mStdevY, 3, 6, message); + checkMessage.push_back("StdDevCheck: " + message); + mosQuality.push_back(resultStdDevY); + } + } + } + // find the worst quality that is NOT ("Null") + std::vector quals = { resultRangeX, resultRangeY, resultStdDevX, resultStdDevY }; + std::sort(quals.begin(), quals.end(), [](Quality a, Quality b) { return a.isWorseThan(b); }); + auto result_iter = std::lower_bound(quals.begin(), quals.end(), Quality::Bad, [](Quality a, Quality b) { return a.isWorseThan(b); }); + if (result_iter != quals.end()) { + result = quals[std::distance(quals.begin(), result_iter)]; + } else { + ILOG(Warning, Support) << "There is a problem with the Qualities. All Qualities might be Null. Is there a check requested?" << ENDM; + } + + } // for Mo map + + // For writing to metadata and drawing later + mBadString = ""; + mMediumString = ""; + mGoodString = ""; + mNullString = ""; + + // Aggregation of quality strings used for MO + for (int qualityindex = 0; qualityindex < mosQuality.size(); qualityindex++) { + Quality q = mosQuality.at(qualityindex); + if (q == Quality::Bad) { + mBadString = mBadString + checkMessage.at(qualityindex) + "\n"; + } else if (q == Quality::Medium) { + mMediumString = mMediumString + checkMessage.at(qualityindex) + "\n"; + } else if (q == Quality::Good) { + mGoodString = mGoodString + checkMessage.at(qualityindex) + "\n"; + } else { + mNullString = mNullString + checkMessage.at(qualityindex) + "\n"; + } + } + mosQuality.clear(); + + result.addMetadata(Quality::Bad.getName(), mBadString); + result.addMetadata(Quality::Medium.getName(), mMediumString); + result.addMetadata(Quality::Good.getName(), mGoodString); + result.addMetadata(Quality::Null.getName(), mNullString); + + return result; +} + +void GenericHistogramCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + TPaveText* msg = new TPaveText(0.11, 0.85, 0.9, 0.95, "NDC"); + msg->SetBorderSize(1); + TText* txt = new TText(0, 0, "Quality::Null"); + TText* txt2 = new TText(0, 0, "Quality::Null"); + double xText = 0; + double yText = 0; + double yText2 = 0; + + if (mHistDimension == 1) { + + auto h1 = dynamic_cast((mo->getObject())); + if (!h1) { + ILOG(Warning, Support) << "h1 not found in D= 1" << ENDM; + return; + } + TLine* lineX = new TLine(mMeanX, h1->GetMinimum() * 1.1, mMeanX, h1->GetMaximum() * 1.1); + TLine* lineXEV = new TLine(mExpectedValueX, h1->GetMinimum() * 1.1, mExpectedValueX, h1->GetMaximum() * 1.1); + lineX->SetLineWidth(3); + lineX->SetLineColor(kRed); + lineXEV->SetLineWidth(3); + lineXEV->SetLineStyle(kDashed); + + h1->GetListOfFunctions()->Add(lineX); + h1->GetListOfFunctions()->Add(lineXEV); + + h1->SetLineColor(kBlack); + + msg->SetName(Form("%s_msg", mo->GetName())); + + h1->GetListOfFunctions()->Add(msg); + } + if (mHistDimension == 2) { + + auto h2 = dynamic_cast((mo->getObject())); + if (!h2) { + ILOG(Warning, Support) << "h2 not found in D= 2" << ENDM; + return; + } + xText = h2->GetXaxis()->GetXmin() + std::abs(h2->GetXaxis()->GetXmax() - h2->GetXaxis()->GetXmin()) * 0.01; + yText = h2->GetYaxis()->GetXmax() * 0.9; + yText2 = h2->GetYaxis()->GetXmax() * 0.9 - std::abs(h2->GetYaxis()->GetXmax() - h2->GetYaxis()->GetXmin()) * 0.05; + + TLine* lineX = new TLine(mMeanX, h2->GetYaxis()->GetXmin(), mMeanX, h2->GetYaxis()->GetXmax()); + TLine* lineY = new TLine(h2->GetXaxis()->GetXmin(), mMeanY, h2->GetXaxis()->GetXmax(), mMeanY); + TLine* lineXEV = new TLine(mExpectedValueX, h2->GetYaxis()->GetXmin(), mExpectedValueX, h2->GetYaxis()->GetXmax()); + TLine* lineYEV = new TLine(h2->GetXaxis()->GetXmin(), mExpectedValueY, h2->GetXaxis()->GetXmax(), mExpectedValueY); + + lineY->SetLineWidth(3); + lineY->SetLineColor(kOrange); + lineYEV->SetLineWidth(3); + lineYEV->SetLineColor(kOrange); + lineYEV->SetLineStyle(kDashed); + lineX->SetLineWidth(3); + lineX->SetLineColor(kRed); + lineXEV->SetLineWidth(3); + lineXEV->SetLineColor(kRed); + lineXEV->SetLineStyle(kDashed); + + h2->GetListOfFunctions()->Add(lineX); + h2->GetListOfFunctions()->Add(lineY); + h2->GetListOfFunctions()->Add(lineXEV); + h2->GetListOfFunctions()->Add(lineYEV); + h2->GetListOfFunctions()->Add(txt); + h2->GetListOfFunctions()->Add(txt2); + + h2->SetLineColor(kBlack); + + msg->SetName(Form("%s_msg", mo->GetName())); + + h2->GetListOfFunctions()->Add(msg); + + // make generic text for the 2D + if (mCheckXAxis && mHistDimension == 2) { + if (mCheckYAxis) { + txt->SetText(xText, yText, fmt::format("MeanX: {:.3}, ExpectedX: {:.3}", mMeanX, mExpectedValueX).data()); + txt2->SetText(xText, yText2, fmt::format("MeanY: {:.3}, ExpectedY: {:.3}", mMeanY, mExpectedValueY).data()); + } else { + txt->SetText(xText, yText, fmt::format("MeanX: {:.3}, ExpectedX: {:.3}", mMeanX, mExpectedValueX).data()); + } + } else { + txt->SetText(xText, yText, fmt::format("MeanY: {:.3}, ExpectedY: {:.3}", mMeanY, mExpectedValueY).data()); + } + } + if (checkResult == Quality::Good) { + msg->Clear(); + msg->AddText("Quality::Good"); + msg->AddText(checkResult.getMetadata(Quality::Good.getName(), "").c_str()); + msg->SetFillColor(kGreen); + + txt->SetTextColor(kGreen); + txt2->SetTextColor(kGreen); + } else if (checkResult == Quality::Bad) { + + msg->Clear(); + msg->AddText("Quality::Bad"); + msg->AddText(checkResult.getMetadata(Quality::Bad.getName(), "").c_str()); + + msg->SetFillColor(kRed); + + txt->SetTextColor(kRed); + txt2->SetTextColor(kRed); + } else if (checkResult == Quality::Medium) { + + msg->Clear(); + msg->AddText("Quality::Medium"); + msg->AddText(checkResult.getMetadata(Quality::Medium.getName(), "").c_str()); + msg->SetFillColor(kOrange); + + txt->SetTextColor(kOrange); + txt2->SetTextColor(kOrange); + } + if (checkResult == Quality::Null) { + msg->AddText("No Check was performed."); + msg->AddText(checkResult.getMetadata(Quality::Null.getName(), "").c_str()); + } else { + msg->AddText(fmt::format("X-Mean: {:.3}, Expected: {:.3}", mMeanX, mExpectedValueX).data()); + msg->AddText(checkResult.getMetadata("Comment", "").c_str()); + if (mHistDimension == 2) { + msg->AddText(fmt::format("Y-Mean: {:.3}, Expected: {:.3}", mMeanY, mExpectedValueY).data()); + } + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/IDCScaleReductor.cxx b/Modules/TPC/src/IDCScaleReductor.cxx new file mode 100644 index 0000000000..9c32ef00b1 --- /dev/null +++ b/Modules/TPC/src/IDCScaleReductor.cxx @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// file IDCScaleReductor.cxx +// author Marcel Lesch +// + +#include "TPC/IDCScaleReductor.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +void* IDCScaleReductor::getBranchAddress() +{ + return &mIDC; +} // void* IDCScaleReductor::getBranchAddress() + +const char* IDCScaleReductor::getBranchLeafList() +{ + return "IDCScaleFactorASide/F:IDCScaleFactorCSide"; +} // const char* IDCScaleReductor::getBranchLeafList() + +void IDCScaleReductor::update(TObject* obj) +{ + if (obj) { + auto canvas = dynamic_cast(obj); + + if (canvas) { + auto g = dynamic_cast(canvas->GetListOfPrimitives()->FindObject("g_IDC0ScaleFactor")); + + if (g) { + mIDC.ScaleFactorASide = g->GetPointY(0); + mIDC.ScaleFactorCSide = g->GetPointY(1); + + } // if (g) + } // if (canvas) + } // if(obj) +} // void IDCScaleReductor::update(TObject* obj) + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/IDCs.cxx b/Modules/TPC/src/IDCs.cxx new file mode 100644 index 0000000000..c90f4eb201 --- /dev/null +++ b/Modules/TPC/src/IDCs.cxx @@ -0,0 +1,342 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file IDCs.cxx +/// \author Thomas Klemenz +/// + +// O2 includes +#include "TPCBase/CalArray.h" +#if __has_include("TPCBase/Painter.h") +#include "TPCBase/Painter.h" +#include "TPCBase/CDBInterface.h" +#else +#include "TPCBaseRecSim/Painter.h" +#include "TPCBaseRecSim/CDBInterface.h" +#endif +#include "TPCBase/CalDet.h" + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/IDCs.h" +#include "TPC/Utility.h" + +// root includes +#include "TCanvas.h" + +#include +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::tpc; + +namespace o2::quality_control_modules::tpc +{ + +void IDCs::configure(const boost::property_tree::ptree& config) +{ + auto& id = getID(); + std::vector keyVec{}; + std::vector valueVec{}; + for (const auto& data : config.get_child("qc.postprocessing." + id + ".lookupMetaData")) { + mLookupMaps.emplace_back(std::map()); + if (const auto& keys = data.second.get_child_optional("keys"); keys.has_value()) { + for (const auto& key : keys.value()) { + keyVec.emplace_back(key.second.data()); + } + } + if (const auto& values = data.second.get_child_optional("values"); values.has_value()) { + for (const auto& value : values.value()) { + valueVec.emplace_back(value.second.data()); + } + } + auto vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mLookupMaps.back().insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for lookupMetaData are not matching" << ENDM; + } + keyVec.clear(); + valueVec.clear(); + } + + for (const auto& data : config.get_child("qc.postprocessing." + id + ".storeMetaData")) { + mStoreMaps.emplace_back(std::map()); + if (const auto& keys = data.second.get_child_optional("keys"); keys.has_value()) { + for (const auto& key : keys.value()) { + keyVec.emplace_back(key.second.data()); + } + } + if (const auto& values = data.second.get_child_optional("values"); values.has_value()) { + for (const auto& value : values.value()) { + valueVec.emplace_back(value.second.data()); + } + } + auto vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mStoreMaps.back().insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for storeMetaData are not matching" << ENDM; + } + keyVec.clear(); + valueVec.clear(); + } + + for (const auto& entry : config.get_child("qc.postprocessing." + id + ".histogramRanges")) { + for (const auto& type : entry.second) { + for (const auto& value : type.second) { + mRanges[type.first].emplace_back(std::stof(value.second.data())); + } + } + } + + for (const auto& entry : config.get_child("qc.postprocessing." + id + ".timestamps")) { + for (const auto& type : entry.second) { + mTimestamps[type.first] = std::stol(type.second.data()); + } + } + + mHost = config.get("qc.postprocessing." + id + ".dataSourceURL"); + + mDoIDCDelta = getPropertyBool(config, id, "doIDCDelta"); + mDoIDC1 = getPropertyBool(config, id, "doIDC1"); + mDoFourier = getPropertyBool(config, id, "doFourier"); +} + +void IDCs::initialize(Trigger, framework::ServiceRegistryRef) +{ + mCdbApi.init(mHost); + + mIDCZeroScale.reset(); + mIDCZerOverview.reset(); + mIDCZeroRadialProf.reset(); + mIDCZeroStacksA.reset(); + mIDCZeroStacksC.reset(); + + mIDCZeroScale = std::make_unique("c_sides_IDC0_scale"); + mIDCZerOverview = std::make_unique("c_sides_IDC0_overview"); + mIDCZeroRadialProf = std::make_unique("c_sides_IDC0_radialProfile"); + mIDCZeroStacksA = std::make_unique("c_GEMStacks_IDC0_1D_ASide"); + mIDCZeroStacksC = std::make_unique("c_GEMStacks_IDC0_1D_CSide"); + + getObjectsManager()->startPublishing(mIDCZeroScale.get()); + getObjectsManager()->startPublishing(mIDCZerOverview.get()); + getObjectsManager()->startPublishing(mIDCZeroRadialProf.get()); + getObjectsManager()->startPublishing(mIDCZeroStacksA.get()); + getObjectsManager()->startPublishing(mIDCZeroStacksC.get()); + + if (mDoIDCDelta) { + mIDCDeltaStacksA.reset(); + mIDCDeltaStacksC.reset(); + mIDCDeltaStacksA = std::make_unique("c_GEMStacks_IDCDelta_1D_ASide"); + mIDCDeltaStacksC = std::make_unique("c_GEMStacks_IDCDelta_1D_CSide"); + getObjectsManager()->startPublishing(mIDCDeltaStacksA.get()); + getObjectsManager()->startPublishing(mIDCDeltaStacksC.get()); + } + + if (mDoIDC1) { + mIDCOneSides1D.reset(); + mIDCOneSides1D = std::make_unique("c_sides_IDC1_1D"); + getObjectsManager()->startPublishing(mIDCOneSides1D.get()); + } + + if (mDoFourier) { + mFourierCoeffsA.reset(); + mFourierCoeffsC.reset(); + mFourierCoeffsA = std::make_unique("c_FourierCoefficients_1D_ASide"); + mFourierCoeffsC = std::make_unique("c_FourierCoefficients_1D_CSide"); + getObjectsManager()->startPublishing(mFourierCoeffsA.get()); + getObjectsManager()->startPublishing(mFourierCoeffsC.get()); + } +} + +void IDCs::update(Trigger, framework::ServiceRegistryRef) +{ + std::vector availableTimestampsIDCZeroA = getDataTimestamps(mCdbApi, CDBTypeMap.at(CDBType::CalIDC0A), 1, mTimestamps["IDCZero"]); + std::vector availableTimestampsIDCZeroC = getDataTimestamps(mCdbApi, CDBTypeMap.at(CDBType::CalIDC0C), 1, mTimestamps["IDCZero"]); + std::vector availableTimestampsIDCDeltaA{ 0 }; + std::vector availableTimestampsIDCDeltaC{ 0 }; + std::vector availableTimestampsIDCOneA{ 0 }; + std::vector availableTimestampsIDCOneC{ 0 }; + std::vector availableTimestampsFFTA{ 0 }; + std::vector availableTimestampsFFTC{ 0 }; + bool timestampFoundForIDCZero = false; + if (availableTimestampsIDCZeroA.size() == 0 || availableTimestampsIDCZeroC.size() == 0) { + ILOG(Warning, Support) << fmt::format("No timstemp found for '{}' produced in the last day.", "IDCZero") << ENDM; + } else { + timestampFoundForIDCZero = true; + } + + bool timestampFoundForIDCDelta = false; + if (mDoIDCDelta) { + availableTimestampsIDCDeltaA = getDataTimestamps(mCdbApi, CDBTypeMap.at(CDBType::CalIDCDeltaA), 1, mTimestamps["IDCDelta"]); + availableTimestampsIDCDeltaC = getDataTimestamps(mCdbApi, CDBTypeMap.at(CDBType::CalIDCDeltaC), 1, mTimestamps["IDCDelta"]); + if (availableTimestampsIDCDeltaA.size() == 0 || availableTimestampsIDCDeltaC.size() == 0) { + ILOG(Warning, Support) << fmt::format("No timstemp found for '{}' produced in the last day.", "IDCDelta") << ENDM; + } else { + timestampFoundForIDCDelta = true; + } + mIDCDeltaStacksA.get()->Clear(); + mIDCDeltaStacksC.get()->Clear(); + } + + bool timestampFoundForIDCOne = false; + if (mDoIDC1) { + availableTimestampsIDCOneA = getDataTimestamps(mCdbApi, CDBTypeMap.at(CDBType::CalIDC1A), 1, mTimestamps["IDCOne"]); + availableTimestampsIDCOneC = getDataTimestamps(mCdbApi, CDBTypeMap.at(CDBType::CalIDC1C), 1, mTimestamps["IDCOne"]); + if (availableTimestampsIDCOneA.size() == 0 || availableTimestampsIDCOneC.size() == 0) { + ILOG(Warning, Support) << fmt::format("No timstemp found for '{}' produced in the last day.", "IDCOne") << ENDM; + } else { + timestampFoundForIDCOne = true; + } + mIDCOneSides1D.get()->Clear(); + } + + bool timestampFoundForFourier = false; + if (mDoFourier) { + availableTimestampsFFTA = getDataTimestamps(mCdbApi, CDBTypeMap.at(CDBType::CalIDCFourierA), 1, mTimestamps["FourierCoeffs"]); + availableTimestampsFFTC = getDataTimestamps(mCdbApi, CDBTypeMap.at(CDBType::CalIDCFourierC), 1, mTimestamps["FourierCoeffs"]); + if (availableTimestampsFFTA.size() == 0 || availableTimestampsFFTC.size() == 0) { + ILOG(Warning, Support) << fmt::format("No timstemp found for '{}' produced in the last day.", "FourierCoeffs") << ENDM; + } else { + timestampFoundForFourier = true; + } + mFourierCoeffsA.get()->Clear(); + mFourierCoeffsC.get()->Clear(); + } + + mIDCZeroScale.get()->Clear(); + mIDCZerOverview.get()->Clear(); + mIDCZeroRadialProf.get()->Clear(); + mIDCZeroStacksA.get()->Clear(); + mIDCZeroStacksC.get()->Clear(); + + o2::tpc::IDCZero* idcZeroA = nullptr; + o2::tpc::IDCZero* idcZeroC = nullptr; + o2::tpc::IDCDelta* idcDeltaA = nullptr; + o2::tpc::IDCDelta* idcDeltaC = nullptr; + o2::tpc::IDCOne* idcOneA = nullptr; + o2::tpc::IDCOne* idcOneC = nullptr; + o2::tpc::FourierCoeff* idcFFTA = nullptr; + o2::tpc::FourierCoeff* idcFFTC = nullptr; + + if (timestampFoundForIDCZero) { + idcZeroA = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalIDC0A), std::map{}, availableTimestampsIDCZeroA[0]); + idcZeroC = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalIDC0C), std::map{}, availableTimestampsIDCZeroC[0]); + } + + if (mDoIDCDelta && timestampFoundForIDCDelta) { + idcDeltaA = mCdbApi.retrieveFromTFileAny>(CDBTypeMap.at(CDBType::CalIDCDeltaA), std::map{}, availableTimestampsIDCDeltaA[0]); + idcDeltaC = mCdbApi.retrieveFromTFileAny>(CDBTypeMap.at(CDBType::CalIDCDeltaC), std::map{}, availableTimestampsIDCDeltaC[0]); + } + if (mDoIDC1 && timestampFoundForIDCOne) { + idcOneA = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalIDC1A), std::map{}, availableTimestampsIDCOneA[0]); + idcOneC = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalIDC1C), std::map{}, availableTimestampsIDCOneC[0]); + } + + if (mDoFourier && timestampFoundForFourier) { + idcFFTA = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalIDCFourierA), std::map{}, availableTimestampsFFTA[0]); + idcFFTC = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalIDCFourierC), std::map{}, availableTimestampsFFTC[0]); + } + + if (idcZeroA && idcZeroC) { + mCCDBHelper.setIDCZero(idcZeroA, Side::A); + mCCDBHelper.setIDCZero(idcZeroC, Side::C); + // scale IDCZero to the sum of IDCZeros + mCCDBHelper.setIDCZeroScale(true); + mCCDBHelper.drawIDCZeroScale(mIDCZeroScale.get(), true); + mCCDBHelper.drawIDCZeroStackCanvas(mIDCZeroStacksA.get(), Side::A, "IDC0", mRanges["IDCZero"].at(0), mRanges["IDCZero"].at(1), mRanges["IDCZero"].at(2)); + mCCDBHelper.drawIDCZeroStackCanvas(mIDCZeroStacksC.get(), Side::C, "IDC0", mRanges["IDCZero"].at(0), mRanges["IDCZero"].at(1), mRanges["IDCZero"].at(2)); + mCCDBHelper.drawIDCZeroRadialProfile(mIDCZeroRadialProf.get(), mRanges["IDCZero"].at(0), mRanges["IDCZero"].at(1), mRanges["IDCZero"].at(2)); + + const auto& calDet = mCCDBHelper.getIDCZeroCalDet(); + o2::tpc::painter::draw(calDet, mRanges["IDCZeroOveview"].at(0), mRanges["IDCZeroOveview"].at(1), mRanges["IDCZeroOveview"].at(2), mIDCZerOverview.get()); + } + + if (idcDeltaA) { + mCCDBHelper.setIDCDelta(idcDeltaA, Side::A); + mCCDBHelper.drawIDCZeroStackCanvas(mIDCDeltaStacksA.get(), Side::A, "IDCDelta", mRanges["IDCDelta"].at(0), mRanges["IDCDelta"].at(1), mRanges["IDCDelta"].at(2)); + } + + if (idcDeltaC) { + mCCDBHelper.setIDCDelta(idcDeltaC, Side::C); + mCCDBHelper.drawIDCZeroStackCanvas(mIDCDeltaStacksC.get(), Side::C, "IDCDelta", mRanges["IDCDelta"].at(0), mRanges["IDCDelta"].at(1), mRanges["IDCDelta"].at(2)); + } + + if (idcOneA && idcOneC) { + mCCDBHelper.setIDCOne(idcOneA, Side::A); + mCCDBHelper.setIDCOne(idcOneC, Side::C); + mCCDBHelper.drawIDCOneCanvas(mIDCOneSides1D.get(), mRanges["IDCOne"].at(0), mRanges["IDCOne"].at(1), mRanges["IDCOne"].at(2)); + } + + if (idcFFTA) { + mCCDBHelper.setFourierCoeffs(idcFFTA, Side::A); + mCCDBHelper.drawFourierCoeff(mFourierCoeffsA.get(), Side::A, mRanges["FourierCoeffs"].at(0), mRanges["FourierCoeffs"].at(1), mRanges["FourierCoeffs"].at(2)); + } + + if (idcFFTC) { + mCCDBHelper.setFourierCoeffs(idcFFTC, Side::C); + mCCDBHelper.drawFourierCoeff(mFourierCoeffsC.get(), Side::C, mRanges["IDCZeroOveview"].at(0), mRanges["IDCZeroOveview"].at(1), mRanges["IDCZeroOveview"].at(2)); + } + + delete idcZeroA; + delete idcZeroC; + delete idcDeltaA; + delete idcDeltaC; + delete idcOneA; + delete idcOneC; + delete idcFFTA; + delete idcFFTC; + + mCCDBHelper.setIDCZero(nullptr, Side::A); + mCCDBHelper.setIDCZero(nullptr, Side::C); + mCCDBHelper.setIDCDelta(nullptr, Side::A); + mCCDBHelper.setIDCDelta(nullptr, Side::C); + mCCDBHelper.setIDCOne(nullptr, Side::A); + mCCDBHelper.setIDCOne(nullptr, Side::C); + mCCDBHelper.setFourierCoeffs(nullptr, Side::A); + mCCDBHelper.setFourierCoeffs(nullptr, Side::C); +} + +void IDCs::finalize(Trigger, framework::ServiceRegistryRef) +{ + getObjectsManager()->stopPublishing(mIDCZeroScale.get()); + getObjectsManager()->stopPublishing(mIDCZerOverview.get()); + getObjectsManager()->stopPublishing(mIDCZeroRadialProf.get()); + getObjectsManager()->stopPublishing(mIDCZeroStacksA.get()); + getObjectsManager()->stopPublishing(mIDCZeroStacksC.get()); + + if (mDoIDC1) { + getObjectsManager()->stopPublishing(mIDCOneSides1D.get()); + } + + if (mDoFourier) { + getObjectsManager()->stopPublishing(mFourierCoeffsA.get()); + getObjectsManager()->stopPublishing(mFourierCoeffsC.get()); + } + + if (mDoIDCDelta) { + getObjectsManager()->stopPublishing(mIDCDeltaStacksA.get()); + getObjectsManager()->stopPublishing(mIDCDeltaStacksC.get()); + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/IDCsVsSACs.cxx b/Modules/TPC/src/IDCsVsSACs.cxx new file mode 100644 index 0000000000..50fbba447d --- /dev/null +++ b/Modules/TPC/src/IDCsVsSACs.cxx @@ -0,0 +1,153 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file IDCsVsSACs.cxx +/// \author Bhawani Singh +/// + +// O2 includes +#include "TPCBase/CalArray.h" +#if __has_include("TPCBase/Painter.h") +#include "TPCBase/Painter.h" +#include "TPCBase/CDBInterface.h" +#else +#include "TPCBaseRecSim/Painter.h" +#include "TPCBaseRecSim/CDBInterface.h" +#endif +#include "TPCBase/CalDet.h" + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/IDCsVsSACs.h" + +// root includes +#include "TCanvas.h" + +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::tpc; + +namespace o2::quality_control_modules::tpc +{ + +void IDCsVsSACs::configure(const boost::property_tree::ptree& config) +{ + std::vector keyVec{}; + std::vector valueVec{}; + auto& id = getID(); + for (const auto& data : config.get_child("qc.postprocessing." + id + ".lookupMetaData")) { + mLookupMaps.emplace_back(std::map()); + if (const auto& keys = data.second.get_child_optional("keys"); keys.has_value()) { + for (const auto& key : keys.value()) { + keyVec.emplace_back(key.second.data()); + } + } + if (const auto& values = data.second.get_child_optional("values"); values.has_value()) { + for (const auto& value : values.value()) { + valueVec.emplace_back(value.second.data()); + } + } + auto vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mLookupMaps.back().insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for lookupMetaData are not matching" << ENDM; + } + keyVec.clear(); + valueVec.clear(); + } + + for (const auto& data : config.get_child("qc.postprocessing." + id + ".storeMetaData")) { + mStoreMaps.emplace_back(std::map()); + if (const auto& keys = data.second.get_child_optional("keys"); keys.has_value()) { + for (const auto& key : keys.value()) { + keyVec.emplace_back(key.second.data()); + } + } + if (const auto& values = data.second.get_child_optional("values"); values.has_value()) { + for (const auto& value : values.value()) { + valueVec.emplace_back(value.second.data()); + } + } + auto vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mStoreMaps.back().insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for storeMetaData are not matching" << ENDM; + } + keyVec.clear(); + valueVec.clear(); + } + + for (const auto& entry : config.get_child("qc.postprocessing." + id + ".histogramRanges")) { + for (const auto& type : entry.second) { + for (const auto& value : type.second) { + mRanges[type.first].emplace_back(std::stof(value.second.data())); + } + } + } + + for (const auto& entry : config.get_child("qc.postprocessing." + id + ".timestamps")) { + for (const auto& type : entry.second) { + mTimestamps[type.first] = std::stol(type.second.data()); + } + } + + mHost = config.get("qc.postprocessing." + id + ".dataSourceURL"); +} + +void IDCsVsSACs::initialize(Trigger, framework::ServiceRegistryRef) +{ + mCdbApi.init(mHost); + mCompareIDC0andSAC0 = std::make_unique("c_mCompareIDC0andSAC0"); + getObjectsManager()->startPublishing(mCompareIDC0andSAC0.get()); +} + +void IDCsVsSACs::update(Trigger, framework::ServiceRegistryRef) +{ + mCompareIDC0andSAC0.get()->Clear(); + auto idcZeroA = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalIDC0A), std::map{}, mTimestamps["IDCZero"]); + auto idcZeroC = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalIDC0C), std::map{}, mTimestamps["IDCZero"]); + mCCDBHelper.setIDCZero(idcZeroA, Side::A); + mCCDBHelper.setIDCZero(idcZeroC, Side::C); + mCCDBHelper.setIDCZeroScale(true); + + auto sacZero = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalSAC0), std::map{}, mTimestamps["SACZero"]); + mSACs.setSACZero(sacZero); + mIDCsVsSACs = o2::tpc::qc::IDCsVsSACs(&mCCDBHelper, &mSACs); + mIDCsVsSACs.drawComparisionSACandIDCZero(mCompareIDC0andSAC0.get(), mRanges["IDCZero"].at(0), mRanges["IDCZero"].at(1), mRanges["IDCZero"].at(2), mRanges["SACZero"].at(0), mRanges["SACZero"].at(1), mRanges["SACZero"].at(2)); + delete idcZeroA; + delete idcZeroC; + delete sacZero; + + mCCDBHelper.setIDCZero(nullptr, Side::A); + mCCDBHelper.setIDCZero(nullptr, Side::C); + mSACs.setSACZero(nullptr); +} + +void IDCsVsSACs::finalize(Trigger, framework::ServiceRegistryRef) +{ + getObjectsManager()->stopPublishing(mCompareIDC0andSAC0.get()); + mCompareIDC0andSAC0.reset(); +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/JunkDetection.cxx b/Modules/TPC/src/JunkDetection.cxx new file mode 100644 index 0000000000..b89f2f2c97 --- /dev/null +++ b/Modules/TPC/src/JunkDetection.cxx @@ -0,0 +1,148 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file JunkDetection.cxx +/// \author Thomas Klemenz +/// + +// O2 includes +#include "Framework/ProcessingContext.h" +#include + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/JunkDetection.h" +#include "Common/Utils.h" + +// root includes +#include "TCanvas.h" +#include "TObjArray.h" +#include "TH2F.h" +#include "TPaveText.h" + +namespace o2::quality_control_modules::tpc +{ + +void JunkDetection::initialize(o2::framework::InitContext&) +{ + ILOG(Debug, Devel) << "initialize JunkDetection QC task" << ENDM; + + mIsMergeable = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "mergeableOutput"); + + if (!mIsMergeable) { + mJDCanv = std::make_unique("c_junk_detection", "Junk Detection", 1000, 1000); + } + mJDHistos.emplace_back(new TH2F("h_removed_Strategy_A", "Removed Strategy (A)", 1, 0, 1, 1, 0, 1)); // dummy for the objectsManager + mJDHistos.emplace_back(new TH2F("h_removed_Strategy_B", "Removed Strategy (B)", 1, 0, 1, 1, 0, 1)); // dummy for the objectsManager + + if (!mIsMergeable) { + getObjectsManager()->startPublishing(mJDCanv.get()); + } + for (const auto& hist : mJDHistos) { + getObjectsManager()->startPublishing(hist); + } +} + +void JunkDetection::startOfActivity(const Activity&) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + + if (!mIsMergeable) { + mJDCanv->Clear(); + } +} + +void JunkDetection::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void JunkDetection::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto jdHistos = ctx.inputs().get("JunkDetection"); + + *mJDHistos[0] = *(TH2F*)jdHistos->At(4); + *mJDHistos[1] = *(TH2F*)jdHistos->At(5); + + mJDHistos[0]->SetName("h_removed_Strategy_A"); + mJDHistos[1]->SetName("h_removed_Strategy_B"); + + if (!mIsMergeable) { + makeCanvas(jdHistos.get(), mJDCanv.get()); + } +} + +void JunkDetection::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void JunkDetection::endOfActivity(const Activity&) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; + + if (!mIsMergeable) { + getObjectsManager()->stopPublishing(mJDCanv.get()); + } + for (auto& hist : mJDHistos) { + getObjectsManager()->stopPublishing(hist); + delete hist; + hist = nullptr; + } +} + +void JunkDetection::reset() +{ + ILOG(Debug, Devel) << "Resetting the histogramss" << ENDM; + + if (!mIsMergeable) { + mJDCanv->Clear(); + } +} + +TCanvas* JunkDetection::makeCanvas(const TObjArray* data, TCanvas* outputCanvas) +{ + auto c = outputCanvas; + if (!c) { + c = new TCanvas("c_junk_detection", "Junk Detection", 1000, 1000); + } + + c->Clear(); + + auto strA = (TH2F*)data->At(4); + auto strB = (TH2F*)data->At(5); + + double statsA[7]; + double statsB[7]; + + strA->GetStats(statsA); + strB->GetStats(statsB); + + auto junkDetectionMsg = new TPaveText(0.1, 0.1, 0.9, 0.9, "NDC"); + junkDetectionMsg->SetFillColor(0); + junkDetectionMsg->SetBorderSize(0); + junkDetectionMsg->AddText("Removal Strategy A"); + junkDetectionMsg->AddText(fmt::format("Number of Clusters before Removal: {}", statsA[2]).data()); + junkDetectionMsg->AddText(fmt::format("Removed Fraction: {:.2f}%", statsA[4]).data()); + junkDetectionMsg->AddLine(.0, .5, 1., .5); + junkDetectionMsg->AddText("Removal Strategy B"); + junkDetectionMsg->AddText(fmt::format("Number of Clusters before Removal: {}", statsB[2]).data()); + junkDetectionMsg->AddText(fmt::format("Removed Fraction: {:.2f}%", statsB[4]).data()); + + c->cd(); + junkDetectionMsg->SetBit(TObject::kCanDelete); + junkDetectionMsg->Draw(); + + return c; +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/LaserTracks.cxx b/Modules/TPC/src/LaserTracks.cxx new file mode 100644 index 0000000000..a767435acf --- /dev/null +++ b/Modules/TPC/src/LaserTracks.cxx @@ -0,0 +1,71 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file LaserTracks.cxx +/// \author Thomas Klemenz +/// + +// O2 includes +#if __has_include("TPCBase/Painter.h") +#include "TPCBase/Painter.h" +#include "TPCBase/CDBInterface.h" +#else +#include "TPCBaseRecSim/Painter.h" +#include "TPCBaseRecSim/CDBInterface.h" +#endif + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/LaserTracks.h" +#include "TPC/Utility.h" + +// root includes +#include "TCanvas.h" +#include + +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::tpc +{ + +void LaserTracks::configure(const boost::property_tree::ptree& config) +{ + o2::tpc::CDBInterface::instance().setURL(config.get("qc.postprocessing." + getID() + ".dataSourceURL")); +} + +void LaserTracks::initialize(Trigger, framework::ServiceRegistryRef) +{ + addAndPublish(getObjectsManager(), + mLaserTracksCanvasVec, + { "Ltr_Coverage", "Calib_Values", "Ltr_dEdx" }, + mStoreMap); +} + +void LaserTracks::update(Trigger t, framework::ServiceRegistryRef) +{ + ILOG(Info, Support) << "Trigger type is: " << t.triggerType << ", the timestamp is " << t.timestamp << ENDM; + + const auto& calibData = o2::tpc::CDBInterface::instance().getSpecificObjectFromCDB("TPC/Calib/LaserTracks", mTimestamp, mLookupMap); + + auto vecPtr = toVector(mLaserTracksCanvasVec); + o2::tpc::painter::makeSummaryCanvases(calibData, &vecPtr); +} + +void LaserTracks::finalize(Trigger t, framework::ServiceRegistryRef) +{ + for (const auto& canvas : mLaserTracksCanvasVec) { + getObjectsManager()->stopPublishing(canvas.get()); + } + mLaserTracksCanvasVec.clear(); +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/LtrCalibReductor.cxx b/Modules/TPC/src/LtrCalibReductor.cxx new file mode 100644 index 0000000000..21be9c256b --- /dev/null +++ b/Modules/TPC/src/LtrCalibReductor.cxx @@ -0,0 +1,80 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// \file LtrCalibReductor.cxx +// \author Cindy Mordasini +// \author Marcel Lesch +// +#include "TPC/LtrCalibReductor.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +void* LtrCalibReductor::getBranchAddress() +{ + return &mLtrCalib; +} + +const char* LtrCalibReductor::getBranchLeafList() +{ + // return "processedTFs/g:dvCorrectionA/F:dvCorrectionC:dvOffsetA:dvOffsetC:nTracksA/s:nTracksC"; + return "processedTFs/D:dvCorrectionA:dvCorrectionC:dvCorrection:dvOffsetA:dvOffsetC:t0A:t0C:nTracksA:nTracksC:dvAbsolute"; +} + +void LtrCalibReductor::update(TObject* obj) +{ + // The 'Calib_Values' for the Laser Calibration are saved in a TPaveText inside a TCanvas 'obj'. + if (obj) { + // ILOG(Info, Support) << "'obj' has been passed to the reductor." << ENDM; + if (auto canvas = static_cast(obj)) { + if (auto blocText = static_cast(canvas->GetPrimitive("TPave"))) { + + mLtrCalib.processedTFs = getValue((TText*)blocText->GetLineWith("processedTFs:")); + mLtrCalib.dvCorrectionA = getValue((TText*)blocText->GetLineWith("dvCorrectionA:")); + mLtrCalib.dvCorrectionC = getValue((TText*)blocText->GetLineWith("dvCorrectionC:")); + mLtrCalib.dvCorrection = getValue((TText*)blocText->GetLineWith("dvCorrection:")); + mLtrCalib.dvOffsetA = getValue((TText*)blocText->GetLineWith("dvOffsetA:")); + mLtrCalib.dvOffsetC = getValue((TText*)blocText->GetLineWith("dvOffsetC:")); + mLtrCalib.t0A = getValue((TText*)blocText->GetLineWith("t0A:")); + mLtrCalib.t0C = getValue((TText*)blocText->GetLineWith("t0C:")); + mLtrCalib.nTracksA = getValue((TText*)blocText->GetLineWith("nTracksA:")); + mLtrCalib.nTracksC = getValue((TText*)blocText->GetLineWith("nTracksC:")); + mLtrCalib.dvAbsolute = getValue((TText*)blocText->GetLineWith("dvAbsolute:")); + } + } + } else { + ILOG(Error, Support) << "No 'obj' found." << ENDM; + } +} + +double LtrCalibReductor::getValue(TText* line) +{ + if (!line) { + return 0.; + } + + std::string text = static_cast(line->GetTitle()); + + std::string title; + double value; + std::stringstream sStream(text); + sStream >> title >> value; + return value; +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/PID.cxx b/Modules/TPC/src/PID.cxx new file mode 100644 index 0000000000..e4920a2496 --- /dev/null +++ b/Modules/TPC/src/PID.cxx @@ -0,0 +1,152 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PID.cxx +/// \author Jens Wiechula +/// + +// root includes +#include +#include +#include +#include + +// O2 includes +#include "Framework/ProcessingContext.h" +#include "DataFormatsTPC/TrackTPC.h" +#include "TPCQC/Helpers.h" +#include + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/PID.h" +#include "Common/Utils.h" + +namespace o2::quality_control_modules::tpc +{ + +PID::PID() : TaskInterface() {} + +PID::~PID() +{ +} + +void PID::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TPC PID QC task" << ENDM; + // elementary cuts for PID from json file + const int cutMinNCluster = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMinNCluster"); + const float cutAbsTgl = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutAbsTgl"); + const float cutMindEdxTot = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMindEdxTot"); + const float cutMaxdEdxTot = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMaxdEdxTot"); + const float cutMinpTPC = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMinpTPC"); + const float cutMaxpTPC = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMaxpTPC"); + const float cutMinpTPCMIPs = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMinpTPCMIPs"); + const float cutMaxpTPCMIPs = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMaxpTPCMIPs"); + const bool runAsyncAndTurnOffSomeHistos = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "turnOffHistosForAsync"); + const bool getdEdxVspHypoHist = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "getdEdxVspHypoHist"); + + // set track cuts defaults are (AbsEta = 1.0, nCluster = 60, MindEdxTot = 20) + mQCPID.setPIDCuts(cutMinNCluster, cutAbsTgl, cutMindEdxTot, cutMaxdEdxTot, cutMinpTPC, cutMaxpTPC, cutMinpTPCMIPs, cutMaxpTPCMIPs, runAsyncAndTurnOffSomeHistos, getdEdxVspHypoHist); + mQCPID.initializeHistograms(); + //mSeparationPower = new TProfile("mSeparationPower", "mSeparationPower", nPars, 0., (float)nPars); + mSeparationPower.reset(new TProfile("mSeparationPower", "mSeparationPower", nPars, 0., (float)nPars)); + getObjectsManager()->startPublishing(mSeparationPower.get()); + // pass map of vectors of histograms to be beautified! + + o2::tpc::qc::helpers::setStyleHistogramsInMap(mQCPID.getMapOfHisto()); + for (auto const& pair : mQCPID.getMapOfHisto()) { + for (auto& hist : pair.second) { + getObjectsManager()->startPublishing(hist.get()); + } + } +} + +void PID::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + mQCPID.resetHistograms(); +} + +void PID::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void PID::monitorData(o2::framework::ProcessingContext& ctx) +{ + using TrackType = std::vector; + auto tracks = ctx.inputs().get("inputTracks"); + // ILOG(Info, Support) << "monitorData: " << tracks.size() << ENDM; + + for (auto const& track : tracks) { + mQCPID.processTrack(track, tracks.size()); + } +} + +void PID::endOfCycle() +{ + // ===| Fitting Histogram for separation Power |============================================================ + // std::unique_ptr fitFunc = std::make_unique("fitFunc", "[0]*exp(-0.5*((x-[1])/[2])^2) + [3]*exp(-0.5*((x-[4])/[5])^2)", 0, 100); + TF1 fitFunc("fitFunc", "[0]*exp(-0.5*((x-[1])/[2])^2) + [3]*exp(-0.5*((x-[4])/[5])^2)", 0, 100); + + for (auto const& pair : mQCPID.getMapOfHisto()) { + for (auto& hist : pair.second) { + if (pair.first.compare("hdEdxTotMIP") == 0) { + // Define fitting function: sum of two Gaussians with an offset + // Set initial parameters for the fit + fitFunc.SetParameter(0, 3000); // Amplitude of the first Gaussian + fitFunc.SetParameter(1, 50); // Mean of the first Gaussian + fitFunc.SetParLimits(1, 45, 55); + fitFunc.SetParameter(2, 2); // Sigma of the first Gaussian + fitFunc.SetParameter(3, 100); // Amplitude of the second Gaussian + fitFunc.SetParameter(4, 75); // Mean of the second Gaussian + fitFunc.SetParLimits(4, 60, 90); + fitFunc.SetParameter(5, 10); // Sigma of the second Gaussian + + // Fit the histogram with the fitting function + hist->Fit(&fitFunc, "QRNM"); + + const TString binLabels[8] = { "Amplitude Pi", "Mean Pi", "Sigma Pi", "Amplitude El", "Mean El", "Sigma El", "Separation Power", "chiSquare/ndf" }; + + for (int iPar = 0; iPar < nPars - 2; iPar++) { + mSeparationPower->GetXaxis()->SetBinLabel(iPar + 1, binLabels[iPar]); + mSeparationPower->Fill((float)iPar + 0.5, fitFunc.GetParameter(iPar)); + } + // Retrieve parameters of the fitted function + + const double sepPow = (fitFunc.GetParameter(4) - fitFunc.GetParameter(1)) / (fitFunc.GetParameter(2) / 2. + fitFunc.GetParameter(5) / 2.); //separation power + mSeparationPower->GetXaxis()->SetBinLabel(7, binLabels[6]); + mSeparationPower->GetXaxis()->SetBinLabel(8, binLabels[7]); + mSeparationPower->Fill(6.5, sepPow); + mSeparationPower->Fill(7.5, fitFunc.GetChisquare() / fitFunc.GetNDF()); + } + } + } + + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void PID::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void PID::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mQCPID.resetHistograms(); +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/PadCalibrationCheck.cxx b/Modules/TPC/src/PadCalibrationCheck.cxx new file mode 100644 index 0000000000..875e77cc58 --- /dev/null +++ b/Modules/TPC/src/PadCalibrationCheck.cxx @@ -0,0 +1,209 @@ +// Copyright CERN and copyright holders of ALICE O2. This software is +// distributed under the terms of the GNU General Public License v3 (GPL +// Version 3), copied verbatim in the file "COPYING". +// +// See http://alice-o2.web.cern.ch/license for full licensing information. +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PadCalibrationCheck.cxx +/// \author Laura Serksnyte +/// + +#include "TPC/PadCalibrationCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" + +#include +// ROOT +#include +#include +#include +#include +#include +#include + +#include + +namespace o2::quality_control_modules::tpc +{ +void PadCalibrationCheck::configure() +{ + if (auto param = mCustomParameters.find("mediumQualityNoiseMean"); param != mCustomParameters.end()) { + mMediumQualityLimitNoiseMean = std::atof(param->second.c_str()); + } + if (auto param = mCustomParameters.find("badQualityNoiseMean"); param != mCustomParameters.end()) { + mBadQualityLimitNoiseMean = std::atof(param->second.c_str()); + } +} + +//______________________________________________________________________________ +Quality PadCalibrationCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + for (auto const& moObj : *moMap) { + auto mo = moObj.second; + if (!mo) { + continue; + } + auto moName = mo->getName(); + std::string histName, histNameS; + int padsTotal = 0, padsstart = 1000; + // If it is a noise histogram, not only the quality but also the counts, mean and standart deviation excluding 0 bin must be stored + if (moName == "c_Sides_Noise" || moName == "c_ROCs_Noise_1D") { + result = Quality::Good; + if (moName == "c_Sides_Noise") { + padsstart = 3; + padsTotal = 4; + } + if (moName == "c_ROCs_Noise_1D") { + padsstart = 1; + padsTotal = 72; + } + auto* canv = dynamic_cast(mo->getObject()); + if (!canv) { + continue; + } + // Check all histograms in the canvas + for (int tpads = padsstart; tpads <= padsTotal; tpads++) { + const auto padName = fmt::format("{:s}_{:d}", moName, tpads); + TPad* pad = (TPad*)canv->GetListOfPrimitives()->FindObject(padName.data()); + if (!pad) { + continue; + } + TH1F* h = nullptr; + if (moName == "c_Sides_Noise") { + if (tpads == 3) { + histName = "h_Aside_1D_Noise"; + } + if (tpads == 4) { + histName = "h_Cside_1D_Noise"; + } + } else if (moName == "c_ROCs_Noise_1D") { + histName = fmt::format("h1_Noise_{:02d}", tpads - 1); + } + h = (TH1F*)pad->GetListOfPrimitives()->FindObject(histName.data()); + if (!h) { + continue; + } + const std::string titleh = h->GetTitle(); + // Set histogram range to correct one and then obtain information about total counts, mean, standard deviation + const int NX = h->GetNbinsX(); + h->GetXaxis()->SetRangeUser(h->GetXaxis()->GetBinCenter(2), h->GetXaxis()->GetBinCenter(NX)); + auto mean = h->GetMean(); + auto stdDev = h->GetStdDev(); + auto nonZeroEntries = h->Integral(); + mNoiseMean.push_back(mean); + mNoiseStdDev.push_back(stdDev); + mNoiseNonZeroEntries.push_back(nonZeroEntries); + // check quality + if (mean > mMediumQualityLimitNoiseMean && mean < mBadQualityLimitNoiseMean) { + if (result == Quality::Good) { + result = Quality::Medium; + } + mSectorsName.push_back(titleh); + mSectorsQuality.push_back(Quality::Medium); + } else if (mean > mBadQualityLimitNoiseMean) { + result = Quality::Bad; + mSectorsName.push_back(titleh); + mSectorsQuality.push_back(Quality::Bad); + } else { + mSectorsName.push_back(titleh); + mSectorsQuality.push_back(Quality::Good); + } + } + } + } // end of loop over moMap + + return result; +} + +void PadCalibrationCheck::beautify(std::shared_ptr mo, Quality) +{ + auto moName = mo->getName(); + if (moName == "c_Sides_Noise" || moName == "c_ROCs_Noise_1D") { + int padsTotal = 0, padsstart = 1000; + auto* tcanv = dynamic_cast(mo->getObject()); + std::string histNameS, histName; + if (moName == "c_Sides_Noise") { + padsstart = 3; + padsTotal = 4; + } else if (moName == "c_ROCs_Noise_1D") { + padsstart = 1; + padsTotal = 72; + histNameS = "h1_Noise"; + } + for (int tpads = padsstart; tpads <= padsTotal; tpads++) { + const std::string padName = fmt::format("{:s}_{:d}", moName, tpads); + TPad* pad = (TPad*)tcanv->GetListOfPrimitives()->FindObject(padName.data()); + if (!pad) { + continue; + } + pad->cd(); + TH1F* h = nullptr; + // In case of working on noise information + if (moName == "c_Sides_Noise") { + if (tpads == 3) { + histName = "h_Aside_1D_Noise"; + } + if (tpads == 4) { + histName = "h_Cside_1D_Noise"; + } + } else { + histName = fmt::format("{:s}_{:02d}", histNameS, tpads - 1); + } + h = (TH1F*)pad->GetListOfPrimitives()->FindObject(histName.data()); + if (!h) { + continue; + } + const std::string titleh = h->GetTitle(); + auto it = std::find(mSectorsName.begin(), mSectorsName.end(), titleh); + if (it == mSectorsName.end()) { + continue; + } + const int index = std::distance(mSectorsName.begin(), it); + TPaveText* msg = new TPaveText(0.7, 0.8, 0.898, 0.9, "NDC"); + h->SetStats(0); + msg->SetBorderSize(1); + msg->SetName(Form("%s_msg", mo->GetName())); + msg->Clear(); + msg->AddText(fmt::format("Entries: {:d}", mNoiseNonZeroEntries[index]).data()); + msg->AddText(fmt::format("Mean: {:.4f}", mNoiseMean[index]).data()); + msg->AddText(fmt::format("Std Dev: {:.4f}", mNoiseStdDev[index]).data()); + msg->Draw("same"); + + // In case of all histograms + TPaveText* msgQuality = new TPaveText(0.1, 0.9, 0.9, 0.95, "NDC"); + msgQuality->SetBorderSize(1); + Quality qualitySpecial = mSectorsQuality[index]; + msgQuality->SetName(Form("%s_msg", mo->GetName())); + if (qualitySpecial == Quality::Good) { + msgQuality->Clear(); + msgQuality->AddText("Good"); + msgQuality->SetFillColor(kGreen); + } else if (qualitySpecial == Quality::Bad) { + msgQuality->Clear(); + msgQuality->AddText("Bad"); + msgQuality->SetFillColor(kRed); + } else if (qualitySpecial == Quality::Medium) { + msgQuality->Clear(); + msgQuality->AddText("Medium"); + msgQuality->SetFillColor(kOrange); + } else if (qualitySpecial == Quality::Null) { + h->SetFillColor(0); + } + h->SetLineColor(kBlack); + msgQuality->Draw("same"); + } + mSectorsName.clear(); + mSectorsQuality.clear(); + mNoiseMean.clear(); + mNoiseStdDev.clear(); + mNoiseNonZeroEntries.clear(); + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/QualityObserver.cxx b/Modules/TPC/src/QualityObserver.cxx new file mode 100644 index 0000000000..780fb2bae9 --- /dev/null +++ b/Modules/TPC/src/QualityObserver.cxx @@ -0,0 +1,279 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityObserver.cxx +/// \author Marcel Lesch +/// + +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/QualityObject.h" +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::tpc; + +void QualityObserver::configure(const boost::property_tree::ptree& config) +{ + auto& id = getID(); + mObserverName = config.get("qc.postprocessing." + id + ".qualityObserverName"); + mViewDetails = config.get("qc.postprocessing." + id + ".observeDetails", true); + mQualityDetailChoice = config.get("qc.postprocessing." + id + ".qualityDetailChoice", "Null, Good, Medium, Bad"); + mLineLength = config.get("qc.postprocessing." + id + ".lineLength", 70); + + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + id + ".qualityObserverConfig")) { + Config dataConfig; + + dataConfig.groupTitle = dataSourceConfig.second.get("groupTitle"); + dataConfig.path = dataSourceConfig.second.get("path"); + + std::vector inputQO; + std::vector inputQOTitle; + + if (const auto& qoSources = dataSourceConfig.second.get_child_optional("inputObjects"); qoSources.has_value()) { + for (const auto& QOsource : qoSources.value()) { + inputQO.push_back(QOsource.second.data()); + } + } + if (const auto& qoTitlesources = dataSourceConfig.second.get_child_optional("inputObjectTitles"); qoTitlesources.has_value()) { + for (const auto& QOTitlesource : qoTitlesources.value()) { + inputQOTitle.push_back(QOTitlesource.second.data()); + } + } + if (inputQO.size() != inputQOTitle.size()) { + ILOG(Error, Devel) << "in config of group" << dataConfig.groupTitle << ": Number of QOs does not match number of qo titles!" << ENDM; + } + dataConfig.qo = inputQO; + dataConfig.qoTitle = inputQOTitle; + mConfig.push_back(dataConfig); + + inputQO.clear(); + inputQOTitle.clear(); + } // for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".ratioConfig")) +} + +void QualityObserver::initialize(Trigger, framework::ServiceRegistryRef) +{ + for (const auto& config : mConfig) { + mQualities[config.groupTitle] = std::vector(); + mFlags[config.groupTitle] = std::vector(); + mComments[config.groupTitle] = std::vector(); + } + mColors[Quality::Bad.getName()] = kRed; + mColors[Quality::Medium.getName()] = kOrange - 3; + mColors[Quality::Good.getName()] = kGreen + 2; + mColors[Quality::Null.getName()] = kViolet - 6; + + mQualityDetails[Quality::Bad.getName()] = false; + mQualityDetails[Quality::Medium.getName()] = false; + mQualityDetails[Quality::Good.getName()] = false; + mQualityDetails[Quality::Null.getName()] = false; + + if (size_t finder = mQualityDetailChoice.find("Bad"); finder != std::string::npos) { + mQualityDetails[Quality::Bad.getName()] = true; + } + if (size_t finder = mQualityDetailChoice.find("Medium"); finder != std::string::npos) { + mQualityDetails[Quality::Medium.getName()] = true; + } + if (size_t finder = mQualityDetailChoice.find("Good"); finder != std::string::npos) { + mQualityDetails[Quality::Good.getName()] = true; + } + if (size_t finder = mQualityDetailChoice.find("Null"); finder != std::string::npos) { + mQualityDetails[Quality::Null.getName()] = true; + } +} + +void QualityObserver::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + getQualities(t, qcdb); + generatePanel(); +} + +void QualityObserver::finalize(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + getQualities(t, qcdb); + if (mQualities.size() > 0 && mFlags.size() > 0 && mComments.size() > 0) { + generatePanel(); + } +} + +void QualityObserver::getQualities(const Trigger& t, + repository::DatabaseInterface& qcdb) +{ + for (const auto& config : mConfig) { + + if (mQualities[config.groupTitle].size() > 0) { + mQualities[config.groupTitle].clear(); + mFlags[config.groupTitle].clear(); + mComments[config.groupTitle].clear(); + } + for (const auto& qualityobject : config.qo) { + const auto qo = qcdb.retrieveQO(config.path + "/" + qualityobject, t.timestamp, t.activity); + if (qo) { + const auto quality = qo->getQuality(); + mQualities[config.groupTitle].push_back(quality.getName()); + mFlags[config.groupTitle].push_back(quality.getMetadata(quality.getName(), "")); + mComments[config.groupTitle].push_back(quality.getMetadata("Comment", "")); + } else { + mQualities[config.groupTitle].push_back(Quality::Null.getName()); + mFlags[config.groupTitle].push_back(""); + mComments[config.groupTitle].push_back(""); + } + } + } +} // void QualityObserver::getQualities(const Trigger& t, repository::DatabaseInterface& qcdb) + +void QualityObserver::generatePanel() +{ + // Delete the existing plots before regenerating them. + if (mCanvas) { + getObjectsManager()->stopPublishing(mObserverName); + delete mCanvas; + mCanvas = nullptr; + } + + // Draw the ratio on a new canvas. + TCanvas* c = new TCanvas(); + c->SetName(mObserverName.c_str()); + c->SetTitle(mObserverName.c_str()); + c->cd(1); + + TPaveText* pt = new TPaveText(0.05, 0.05, .95, .95); + pt->SetLineColor(0); + pt->SetFillColor(0); + pt->SetBorderSize(1); + + for (const auto& config : mConfig) { + pt->AddText(""); // Emtpy line needed. AddLine() places the line to the first entry of the for loop. Check late + TText* GroupText = pt->AddText(config.groupTitle.data()); + ((TText*)pt->GetListOfLines()->Last())->SetTextAlign(22); + for (int i = 0; i < config.qoTitle.size(); i++) { + pt->AddText(Form("%s = #color[%d]{%s}", config.qoTitle.at(i).data(), mColors[mQualities[config.groupTitle].at(i).data()], mQualities[config.groupTitle].at(i).data())); + ((TText*)pt->GetListOfLines()->Last())->SetTextAlign(12); + + if (mViewDetails && mQualityDetails[mQualities[config.groupTitle].at(i).data()]) { + generateText(pt, true, mFlags[config.groupTitle].at(i)); // print flags + generateText(pt, false, mComments[config.groupTitle].at(i)); // print comments + } + } + + pt->AddLine(); + ((TLine*)pt->GetListOfLines()->Last())->SetLineWidth(1); + ((TLine*)pt->GetListOfLines()->Last())->SetLineStyle(9); + } + + pt->Draw(); + mCanvas = c; + getObjectsManager()->startPublishing(c); + +} // void QualityObserver::generatePanel() + +void QualityObserver::generateText(TPaveText* pt, bool isFlag, std::string qoMetaText) +{ + std::string infoType = "Flag"; + if (!isFlag) { + infoType = "Comment"; + } + if (qoMetaText != "") { + std::string delimiter = "\n"; + + if (qoMetaText.find(delimiter) != std::string::npos) { + size_t pos = 0; + std::string subText; + while ((pos = qoMetaText.find(delimiter)) != std::string::npos) { // cut string into the different reasons/comments + subText = qoMetaText.substr(0, pos); + qoMetaText.erase(0, pos + delimiter.length()); + breakText(pt, infoType, subText); // break up reason/comment for better visualisation on qcg + } + } else { + breakText(pt, infoType, qoMetaText); + } + } // if (qoMetaText != "") +} + +void QualityObserver::breakText(TPaveText* pt, std::string infoType, std::string textUnbroken) +{ + std::string subLine = ""; + std::string subLineDelimiter = " "; + size_t subpos = 0; + + if (textUnbroken.length() < mLineLength) { + pt->AddText(Form("#color[%d]{#rightarrow %s: %s}", kGray + 2, infoType.data(), textUnbroken.data())); + ((TText*)pt->GetListOfLines()->Last())->SetTextAlign(12); + return; + } + + if (textUnbroken.find(subLineDelimiter) != std::string::npos) { + bool firstOccurance = true; + int colorInfoType = kGray + 2; + + while ((subpos = textUnbroken.find(subLineDelimiter)) != std::string::npos) { + std::string textFragmant = textUnbroken.substr(0, subpos); + + if (subLine == "") { + subLine += textFragmant + " "; + textUnbroken.erase(0, subpos + subLineDelimiter.length()); + if (subLine.length() > mLineLength) { + pt->AddText(Form("#color[%d]{#rightarrow %s:} #color[%d]{%s}", colorInfoType, infoType.data(), kGray + 2, subLine.data())); + ((TText*)pt->GetListOfLines()->Last())->SetTextAlign(12); + + if (firstOccurance) { + colorInfoType = kWhite; + firstOccurance = false; + } + subLine = ""; + } + } else { + if (subLine.length() + textFragmant.length() + 1 <= mLineLength) { + subLine += textFragmant + " "; + textUnbroken.erase(0, subpos + subLineDelimiter.length()); + } else { + pt->AddText(Form("#color[%d]{#rightarrow %s:} #color[%d]{%s}", colorInfoType, infoType.data(), kGray + 2, subLine.data())); + ((TText*)pt->GetListOfLines()->Last())->SetTextAlign(12); + + if (firstOccurance) { + colorInfoType = kWhite; + firstOccurance = false; + } + subLine = ""; + } + } + } // while ((subpos = textUnbroken.find(subLineDelimiter)) != std::string::npos) + + if (subLine.length() > 0) { + pt->AddText(Form("#color[%d]{#rightarrow %s:} #color[%d]{%s}", colorInfoType, infoType.data(), kGray + 2, subLine.data())); + ((TText*)pt->GetListOfLines()->Last())->SetTextAlign(12); + if (firstOccurance) { + colorInfoType = kWhite; + firstOccurance = false; + } + } + if (textUnbroken.length() > 0) { + pt->AddText(Form("#color[%d]{#rightarrow %s:} #color[%d]{%s}", colorInfoType, infoType.data(), kGray + 2, textUnbroken.data())); + ((TText*)pt->GetListOfLines()->Last())->SetTextAlign(12); + } + + } else { // string longer than mLineLength but does not contain subLineDelimiter + pt->AddText(Form("#color[%d]{#rightarrow %s: %s}", kGray + 2, infoType.data(), textUnbroken.data())); + ((TText*)pt->GetListOfLines()->Last())->SetTextAlign(12); + return; + } +} \ No newline at end of file diff --git a/Modules/TPC/src/QualityReductorTPC.cxx b/Modules/TPC/src/QualityReductorTPC.cxx new file mode 100644 index 0000000000..bf1d658323 --- /dev/null +++ b/Modules/TPC/src/QualityReductorTPC.cxx @@ -0,0 +1,36 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file QualityReductorTPC.cxx +/// \author Marcel Lesch +/// \author Based on the work from Piotr Konopka +/// + +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/QualityObject.h" +#include "TPC/QualityReductorTPC.h" + +namespace o2::quality_control_modules::tpc +{ +void QualityReductorTPC::updateQuality(const TObject* obj, SliceInfoQuality& reducedQualitySource) +{ + auto qo = dynamic_cast(obj); + if (qo) { + const auto quality = qo->getQuality(); + reducedQualitySource.qualitylevel = quality.getLevel(); + reducedQualitySource.title = qo->getPath(); + } else { + ILOG(Error, Support) << "Error: 'Qualityobject' not found." << ENDM; + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/ROCReductor.cxx b/Modules/TPC/src/ROCReductor.cxx new file mode 100644 index 0000000000..ef2dda9ab1 --- /dev/null +++ b/Modules/TPC/src/ROCReductor.cxx @@ -0,0 +1,70 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// file ROCReductor.cxx +// author Cindy Mordasini +// author Marcel Lesch +// + +#include "TPC/ROCReductor.h" +#if __has_include("TPCBase/CalDet.h") +#include "TPCBase/CalDet.h" +#else +#include "TPCBaseRecSim/CalDet.h" +#endif +#include "TPCBase/CalArray.h" +#include "TPCQC/CalPadWrapper.h" +#include +#include +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +void* ROCReductor::getBranchAddress() +{ + return &mCalPad; +} // void* ROCReductor::getBranchAddress() + +const char* ROCReductor::getBranchLeafList() +{ + return "entries[72]/I:mean[72]/F:stddev[72]:median[72]:rms[72]"; +} // const char* ROCReductor::getBranchLeafList() + +void ROCReductor::update(TObject* obj) +{ + if (obj) { + auto wrappedCalDet = dynamic_cast(obj); + if (wrappedCalDet) { + o2::tpc::CalPad* pcalDet = wrappedCalDet->getObj(); + auto& calDet = *pcalDet; + + for (size_t iROC = 0; iROC < calDet.getData().size(); ++iROC) { + auto& calArray = calDet.getCalArray(iROC); + auto& data = calArray.getData(); + + // Remove pads which are empty from consideration + data.erase(std::remove_if(data.begin(), data.end(), [](const auto& value) { return (std::isnan(value) || value <= 0); }), data.end()); + + mCalPad.entries[iROC] = static_cast(data.size()); + mCalPad.mean[iROC] = TMath::Mean(data.begin(), data.end()); + mCalPad.stddev[iROC] = TMath::StdDev(data.begin(), data.end()); + mCalPad.median[iROC] = TMath::Median(data.size(), data.data()); + mCalPad.rms[iROC] = TMath::RMS(data.size(), data.data()); + + } // for (size_t iROC = 0; iROC < calDet.getData().size(); ++iROC) + } // if (wrappedCalDet) + } // if(obj) +} // void ROCReductor::update(TObject* obj) + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/RatioGeneratorTPC.cxx b/Modules/TPC/src/RatioGeneratorTPC.cxx new file mode 100644 index 0000000000..5b199159ab --- /dev/null +++ b/Modules/TPC/src/RatioGeneratorTPC.cxx @@ -0,0 +1,120 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RatioGeneratorTPC.cxx +/// \author Marcel Lesch +/// + +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::tpc; + +void RatioGeneratorTPC::configure(const boost::property_tree::ptree& config) +{ + auto& id = getID(); + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + id + ".ratioConfig")) { + std::string inputNames[2]; + int counter = 0; + + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("inputObjects"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + if (counter > 1) { + ILOG(Error, Support) << "Ratio plots should not have more than two input objects!" << ENDM; + break; + } + inputNames[counter] = sourceName.second.data(); + counter++; + } + } + mConfig.push_back({ dataSourceConfig.second.get("path"), + { inputNames[0], inputNames[1] }, + dataSourceConfig.second.get("outputName"), + dataSourceConfig.second.get("plotTitle", ""), + dataSourceConfig.second.get("axisTitle", "") }); + + } // for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".ratioConfig")) +} + +void RatioGeneratorTPC::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + generateRatios(t, qcdb); + generatePlots(); +} + +void RatioGeneratorTPC::finalize(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + generateRatios(t, qcdb); + if (mRatios.size() > 0) { + generatePlots(); + } + + for (const auto& source : mConfig) { + if (mRatios.count(source.nameOutputObject) > 0 && mRatios[source.nameOutputObject]) { + getObjectsManager()->stopPublishing(source.nameOutputObject); + delete mRatios[source.nameOutputObject]; + mRatios[source.nameOutputObject] = nullptr; + } + } +} + +void RatioGeneratorTPC::generateRatios(const Trigger& t, + repository::DatabaseInterface& qcdb) +{ + for (const auto& source : mConfig) { + // Delete the existing ratios before regenerating them. + if (mRatios.count(source.nameOutputObject) > 0 && mRatios[source.nameOutputObject]) { + getObjectsManager()->stopPublishing(source.nameOutputObject); + delete mRatios[source.nameOutputObject]; + mRatios[source.nameOutputObject] = nullptr; + } + auto moNumerator = qcdb.retrieveMO(source.path, source.nameInputObjects[0], t.timestamp, t.activity); + TH1* histoNumerator = moNumerator ? dynamic_cast(moNumerator->getObject()) : nullptr; + auto moDenominator = qcdb.retrieveMO(source.path, source.nameInputObjects[1], t.timestamp, t.activity); + TH1* histoDenominator = moDenominator ? dynamic_cast(moDenominator->getObject()) : nullptr; + + if (histoNumerator && histoDenominator) { + mRatios[source.nameOutputObject] = (TH1*)histoNumerator->Clone(fmt::format("{}_over_{}", histoNumerator->GetName(), histoDenominator->GetName()).data()); + mRatios[source.nameOutputObject]->Divide(histoDenominator); + } + } +} // void RatioGeneratorTPC::trendValues(uint64_t timestamp, repository::DatabaseInterface& qcdb) + +void RatioGeneratorTPC::generatePlots() +{ + for (const auto& source : mConfig) { + // Beautify and publish the ratios + const std::size_t posDivider = source.axisTitle.find(":"); + const std::string yLabel(source.axisTitle.substr(0, posDivider)); + const std::string xLabel(source.axisTitle.substr(posDivider + 1)); + + if (mRatios.count(source.nameOutputObject) > 0 && mRatios[source.nameOutputObject]) { + mRatios[source.nameOutputObject]->SetName(source.nameOutputObject.c_str()); + mRatios[source.nameOutputObject]->GetXaxis()->SetTitle(xLabel.data()); + mRatios[source.nameOutputObject]->GetYaxis()->SetTitle(yLabel.data()); + mRatios[source.nameOutputObject]->SetTitle(source.plotTitle.data()); + getObjectsManager()->startPublishing(mRatios[source.nameOutputObject]); + } + } +} // void RatioGeneratorTPC::generatePlots() diff --git a/Modules/TPC/src/RawDigits.cxx b/Modules/TPC/src/RawDigits.cxx new file mode 100644 index 0000000000..f49b7a3cf6 --- /dev/null +++ b/Modules/TPC/src/RawDigits.cxx @@ -0,0 +1,153 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawDigits.cxx +/// \author Thomas Klemenz +/// + +// O2 includes +#include "Framework/ProcessingContext.h" +#include "DataFormatsTPC/ClusterNative.h" +#if __has_include("TPCBase/Painter.h") +#include "TPCBase/Painter.h" +#else +#include "TPCBaseRecSim/Painter.h" +#endif + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/RawDigits.h" +#include "TPC/Utility.h" + +namespace o2::quality_control_modules::tpc +{ + +RawDigits::RawDigits() : TaskInterface() +{ +} + +void RawDigits::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TPC RawDigits QC task" << ENDM; + + mRawDigitQC.setName("RawDigitData"); + + const auto last = mCustomParameters.end(); + const auto itMergeable = mCustomParameters.find("mergeableOutput"); + std::string mergeable; + + if (itMergeable == last) { + LOGP(warning, "missing parameter 'mergeableOutput'"); + LOGP(warning, "Please add 'mergeableOutput': '' to the 'taskParameters'."); + } else { + mergeable = itMergeable->second; + } + + if (mergeable == "true") { + mIsMergeable = true; + ILOG(Info, Support) << "Using mergeable output for RawDigits Task." << ENDM; + } else if (mergeable == "false") { + mIsMergeable = false; + ILOG(Info, Support) << "Using non-mergeable output for RawDigits Task." << ENDM; + } else { + mIsMergeable = false; + LOGP(warning, "No valid value for 'mergeableOutput'. Set it as 'true' or 'false'. Falling back to non-mergeable output."); + } + + mRawReader.createReader(""); + + if (mIsMergeable) { + getObjectsManager()->startPublishing(&mRawDigitQC); + } else { + mWrapperVector.emplace_back(&mRawDigitQC.getClusters().getNClusters()); + mWrapperVector.emplace_back(&mRawDigitQC.getClusters().getQMax()); + mWrapperVector.emplace_back(&mRawDigitQC.getClusters().getTimeBin()); + + addAndPublish(getObjectsManager(), mNRawDigitsCanvasVec, { "c_Sides_N_RawDigits", "c_ROCs_N_RawDigits_1D", "c_ROCs_N_RawDigits_2D" }); + addAndPublish(getObjectsManager(), mQMaxCanvasVec, { "c_Sides_Q_Max", "c_ROCs_Q_Max_1D", "c_ROCs_Q_Max_2D" }); + addAndPublish(getObjectsManager(), mTimeBinCanvasVec, { "c_Sides_Time_Bin", "c_ROCs_Time_Bin_1D", "c_ROCs_Time_Bin_2D" }); + + for (auto& wrapper : mWrapperVector) { + getObjectsManager()->startPublishing(&wrapper); + } + } + + mRawReader.setLinkZSCallback([this](int cru, int rowInSector, int padInRow, int timeBin, float adcValue) -> bool { + mRawDigitQC.getClusters().fillADCValue(cru, rowInSector, padInRow, timeBin, adcValue); + return true; + }); +} + +void RawDigits::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + + mRawDigitQC.getClusters().reset(); + + if (!mIsMergeable) { + clearCanvases(mNRawDigitsCanvasVec); + clearCanvases(mQMaxCanvasVec); + clearCanvases(mTimeBinCanvasVec); + } +} + +void RawDigits::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void RawDigits::monitorData(o2::framework::ProcessingContext& ctx) +{ + mRawDigitQC.getClusters().denormalize(); + + auto& reader = mRawReader.getReaders()[0]; + o2::tpc::calib_processing_helper::processRawData(ctx.inputs(), reader, false); + + if (!mIsMergeable) { + mRawDigitQC.getClusters().normalize(); + + fillCanvases(mRawDigitQC.getClusters().getNClusters(), mNRawDigitsCanvasVec, mCustomParameters, "NRawDigits"); + fillCanvases(mRawDigitQC.getClusters().getQMax(), mQMaxCanvasVec, mCustomParameters, "Qmax"); + fillCanvases(mRawDigitQC.getClusters().getTimeBin(), mTimeBinCanvasVec, mCustomParameters, "TimeBin"); + } +} + +void RawDigits::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; + + if (mIsMergeable) { + mRawDigitQC.getClusters().normalize(); + } +} + +void RawDigits::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void RawDigits::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the data" << ENDM; + + mRawDigitQC.getClusters().reset(); + + if (!mIsMergeable) { + clearCanvases(mNRawDigitsCanvasVec); + clearCanvases(mQMaxCanvasVec); + clearCanvases(mTimeBinCanvasVec); + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/SACZeroScaleReductor.cxx b/Modules/TPC/src/SACZeroScaleReductor.cxx new file mode 100644 index 0000000000..310f12ca56 --- /dev/null +++ b/Modules/TPC/src/SACZeroScaleReductor.cxx @@ -0,0 +1,52 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// file SACZeroScaleReductor.cxx +// author Marcel Lesch +// + +#include "TPC/SACZeroScaleReductor.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +void* SACZeroScaleReductor::getBranchAddress() +{ + return &mSACZero; +} // void* SACZeroScaleReductor::getBranchAddress() + +const char* SACZeroScaleReductor::getBranchLeafList() +{ + return "SACZeroScaleFactorASide/F:SACZeroScaleFactorCSide"; +} // const char* SACZeroScaleReductor::getBranchLeafList() + +void SACZeroScaleReductor::update(TObject* obj) +{ + if (obj) { + auto canvas = dynamic_cast(obj); + + if (canvas) { + auto g = dynamic_cast(canvas->GetListOfPrimitives()->FindObject("g_SAC0ScaleFactor")); + + if (g) { + mSACZero.ScaleFactorASide = g->GetPointY(0); + mSACZero.ScaleFactorCSide = g->GetPointY(1); + + } // if (g) + } // if (canvas) + } // if(obj) +} // void SACZeroScaleReductor::update(TObject* obj) + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/SACs.cxx b/Modules/TPC/src/SACs.cxx new file mode 100644 index 0000000000..e6ad4b7fa3 --- /dev/null +++ b/Modules/TPC/src/SACs.cxx @@ -0,0 +1,268 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file SACs.cxx +/// \author Thomas Klemenz, Marcel Lesch +/// + +// O2 includes +#if __has_include("TPCBase/CDBInterface.h") +#include "TPCBase/CDBInterface.h" +#else +#include "TPCBaseRecSim/CDBInterface.h" +#endif + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/SACs.h" +#include "TPC/Utility.h" + +// root includes +#include "TCanvas.h" + +#include +#include +#include + +using namespace o2::quality_control::postprocessing; +using namespace o2::tpc; + +namespace o2::quality_control_modules::tpc +{ + +void SACs::configure(const boost::property_tree::ptree& config) +{ + auto& id = getID(); + std::vector keyVec{}; + std::vector valueVec{}; + for (const auto& data : config.get_child("qc.postprocessing." + id + ".lookupMetaData")) { + mLookupMaps.emplace_back(std::map()); + if (const auto& keys = data.second.get_child_optional("keys"); keys.has_value()) { + for (const auto& key : keys.value()) { + keyVec.emplace_back(key.second.data()); + } + } + if (const auto& values = data.second.get_child_optional("values"); values.has_value()) { + for (const auto& value : values.value()) { + valueVec.emplace_back(value.second.data()); + } + } + auto vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mLookupMaps.back().insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for lookupMetaData are not matching" << ENDM; + } + keyVec.clear(); + valueVec.clear(); + } + + for (const auto& data : config.get_child("qc.postprocessing." + id + ".storeMetaData")) { + mStoreMaps.emplace_back(std::map()); + if (const auto& keys = data.second.get_child_optional("keys"); keys.has_value()) { + for (const auto& key : keys.value()) { + keyVec.emplace_back(key.second.data()); + } + } + if (const auto& values = data.second.get_child_optional("values"); values.has_value()) { + for (const auto& value : values.value()) { + valueVec.emplace_back(value.second.data()); + } + } + auto vecIter = 0; + if ((keyVec.size() > 0) && (keyVec.size() == valueVec.size())) { + for (const auto& key : keyVec) { + mStoreMaps.back().insert(std::pair(key, valueVec.at(vecIter))); + vecIter++; + } + } + if (keyVec.size() != valueVec.size()) { + ILOG(Error, Support) << "Number of keys and values for storeMetaData are not matching" << ENDM; + } + keyVec.clear(); + valueVec.clear(); + } + + for (const auto& entry : config.get_child("qc.postprocessing." + id + ".histogramRanges")) { + for (const auto& type : entry.second) { + for (const auto& value : type.second) { + mRanges[type.first].emplace_back(std::stof(value.second.data())); + } + } + } + + for (const auto& entry : config.get_child("qc.postprocessing." + id + ".timestamps")) { + for (const auto& type : entry.second) { + mTimestamps[type.first] = std::stol(type.second.data()); + } + } + + mHost = config.get("qc.postprocessing." + id + ".dataSourceURL"); + + boost::optional doLatestExists = config.get_child_optional("qc.postprocessing." + id + ".doLatest"); + if (doLatestExists) { + auto doLatest = config.get("qc.postprocessing." + id + ".doLatest"); + if (doLatest == "1" || doLatest == "true" || doLatest == "True" || doLatest == "TRUE" || doLatest == "yes") { + mDoLatest = true; + } else if (doLatest == "0" || doLatest == "false" || doLatest == "False" || doLatest == "FALSE" || doLatest == "no") { + mDoLatest = false; + } else { + mDoLatest = false; + ILOG(Warning, Support) << "No valid input for 'doLatest'. Using default value 'false'." << ENDM; + } + } else { + mDoLatest = false; + ILOG(Warning, Support) << "Option 'doLatest' is missing. Using default value 'false'." << ENDM; + } + + mRejectOutliersSACZeroScale = config.get("qc.postprocessing." + id + ".rejectOutliersSACZeroScale", true); + if (!mRejectOutliersSACZeroScale) { + ILOG(Warning, Support) << "No rejection for outliers in SAC Zero Scale!" << ENDM; + } + mSACZeroMaxDeviation = config.get("qc.postprocessing." + id + ".maxDeviationOutlierSACZero", 3.); + mDoSACFourierCoeffs = config.get("qc.postprocessing." + id + ".doSACFourierCoeffs", false); + if (mDoSACFourierCoeffs) { + ILOG(Warning, Support) << "SAC Fourier Coeffs activated, these are currently not in use!" << ENDM; + } +} + +void SACs::initialize(Trigger, framework::ServiceRegistryRef) +{ + mCdbApi.init(mHost); + + mSACZeroSides = std::make_unique("c_sides_SACZero"); + mSACDeltaSides = std::make_unique("c_sides_SACDelta"); + mSACOneSides = std::make_unique("c_sides_SACOne"); + mFourierCoeffsA = std::make_unique("c_FourierCoefficients_1D_ASide"); + mFourierCoeffsC = std::make_unique("c_FourierCoefficients_1D_CSide"); + mSACZeroSidesScaled = std::make_unique("c_sides_SACZero_Scaled"); + mSACZeroScale = std::make_unique("c_sides_SACZero_ScaleFactor"); + mSACZeroOutliers = std::make_unique("c_sides_SACZero_Outliers"); + + getObjectsManager()->startPublishing(mSACZeroSides.get()); + getObjectsManager()->startPublishing(mSACDeltaSides.get()); + getObjectsManager()->startPublishing(mSACOneSides.get()); + getObjectsManager()->startPublishing(mSACZeroSidesScaled.get()); + getObjectsManager()->startPublishing(mSACZeroScale.get()); + getObjectsManager()->startPublishing(mSACZeroOutliers.get()); + if (mDoSACFourierCoeffs) { + getObjectsManager()->startPublishing(mFourierCoeffsA.get()); + getObjectsManager()->startPublishing(mFourierCoeffsC.get()); + } +} + +void SACs::update(Trigger, framework::ServiceRegistryRef) +{ + mSACZeroSides.get()->Clear(); + mSACDeltaSides.get()->Clear(); + mSACOneSides.get()->Clear(); + mFourierCoeffsA.get()->Clear(); + mFourierCoeffsC.get()->Clear(); + mSACZeroSidesScaled.get()->Clear(); + mSACZeroScale.get()->Clear(); + mSACZeroOutliers.get()->Clear(); + + o2::tpc::SAC* sacContainer = nullptr; + o2::tpc::FourierCoeffSAC* sacFFT = nullptr; + + if (mDoLatest) { + std::vector availableTimestampsSACContainer = getDataTimestamps(mCdbApi, CDBTypeMap.at(CDBType::CalSAC), 1, mTimestamps["SACContainer"]); + std::vector availableTimestampsSACFourierCoeffs = getDataTimestamps(mCdbApi, CDBTypeMap.at(CDBType::CalSACFourier), 1, mTimestamps["SACFourierCoeffs"]); + + sacContainer = mCdbApi.retrieveFromTFileAny>(CDBTypeMap.at(CDBType::CalSAC), std::map{}, availableTimestampsSACContainer[0]); + if (mDoSACFourierCoeffs) { + sacFFT = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalSACFourier), std::map{}, availableTimestampsSACFourierCoeffs[0]); + } + } else { + sacContainer = mCdbApi.retrieveFromTFileAny>(CDBTypeMap.at(CDBType::CalSAC), std::map{}, mTimestamps["SACContainer"]); + if (mDoSACFourierCoeffs) { + sacFFT = mCdbApi.retrieveFromTFileAny(CDBTypeMap.at(CDBType::CalSACFourier), std::map{}, mTimestamps["SACFourierCoeffs"]); + } + } + + o2::tpc::SACZero* sacZero = nullptr; + o2::tpc::SACDelta* sacDelta = nullptr; + o2::tpc::SACOne* sacOne = nullptr; + + if (!sacContainer) { + ILOG(Error, Support) << "No SAC Container fetched" << ENDM; + } else { + sacZero = &(sacContainer->mSACZero); + sacDelta = &(sacContainer->mSACDelta); + sacOne = &(sacContainer->mSACOne); + } + + if (sacZero) { + mSACs.setSACZero(sacZero); + mSACs.drawSACTypeSides(o2::tpc::SACType::IDCZero, 0, mRanges["SACZero"].at(1), mRanges["SACZero"].at(2), mSACZeroSides.get()); // draw unscaled + mSACs.setSACZeroMaxDeviation(mSACZeroMaxDeviation); + mSACs.setSACZeroScale(mRejectOutliersSACZeroScale); + mSACs.drawSACZeroScale(mSACZeroScale.get()); + mSACs.drawSACTypeSides(o2::tpc::SACType::IDCZero, 0, mRanges["SACZeroScaled"].at(1), mRanges["SACZeroScaled"].at(2), mSACZeroSidesScaled.get()); // draw scaled + mSACs.drawSACTypeSides(o2::tpc::SACType::IDCOutlier, 0, -2, 2, mSACZeroOutliers.get()); // draw SACZero outlier map + } + if (sacDelta) { + mSACs.setSACDelta(sacDelta); + mSACs.drawSACTypeSides(o2::tpc::SACType::IDCDelta, 0, mRanges["SACDelta"].at(1), mRanges["SACDelta"].at(2), mSACDeltaSides.get()); + } + if (sacOne) { + mSACs.setSACOne(sacOne, Side::A); + mSACs.setSACOne(sacOne, Side::C); + mSACs.drawSACOneCanvas(mRanges["SACOne"].at(0), mRanges["SACOne"].at(1), mRanges["SACOne"].at(2), 0, mSACOneSides.get()); + } + if (sacFFT) { + mSACs.setFourierCoeffSAC(sacFFT); + mSACs.drawFourierCoeffSAC(Side::A, mRanges["SACFourierCoeffs"].at(0), mRanges["SACFourierCoeffs"].at(1), mRanges["SACFourierCoeffs"].at(2), mFourierCoeffsA.get()); + mSACs.drawFourierCoeffSAC(Side::C, mRanges["SACFourierCoeffs"].at(0), mRanges["SACFourierCoeffs"].at(1), mRanges["SACFourierCoeffs"].at(2), mFourierCoeffsC.get()); + } + + delete sacContainer; + delete sacFFT; + + mSACs.setSACZero(nullptr); + mSACs.setSACDelta(nullptr); + mSACs.setSACOne(nullptr, Side::A); + mSACs.setSACOne(nullptr, Side::C); + mSACs.setFourierCoeffSAC(nullptr); +} + +void SACs::finalize(Trigger, framework::ServiceRegistryRef) +{ + getObjectsManager()->stopPublishing(mSACZeroSides.get()); + getObjectsManager()->stopPublishing(mSACOneSides.get()); + getObjectsManager()->stopPublishing(mSACDeltaSides.get()); + getObjectsManager()->stopPublishing(mSACZeroSidesScaled.get()); + getObjectsManager()->stopPublishing(mSACZeroScale.get()); + getObjectsManager()->stopPublishing(mSACZeroOutliers.get()); + + if (mDoSACFourierCoeffs) { + getObjectsManager()->stopPublishing(mFourierCoeffsA.get()); + getObjectsManager()->stopPublishing(mFourierCoeffsC.get()); + } + + mSACZeroSides.reset(); + mSACOneSides.reset(); + mSACDeltaSides.reset(); + mSACZeroSidesScaled.reset(); + mSACZeroScale.reset(); + mSACZeroOutliers.reset(); + if (mDoSACFourierCoeffs) { + mFourierCoeffsA.reset(); + mFourierCoeffsC.reset(); + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/SeparationPowerReductor.cxx b/Modules/TPC/src/SeparationPowerReductor.cxx new file mode 100644 index 0000000000..43a8545e4b --- /dev/null +++ b/Modules/TPC/src/SeparationPowerReductor.cxx @@ -0,0 +1,56 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +// +// \file SeparationPowerReductor.cxx +// \author Marcel Lesch +// +#include "TPC/SeparationPowerReductor.h" +#include "QualityControl/QcInfoLogger.h" +#include +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +void* SeparationPowerReductor::getBranchAddress() +{ + return &mSeparationPower; +} + +const char* SeparationPowerReductor::getBranchLeafList() +{ + return "amplitudePi/F:meanPi:sigmaPi:amplitudeEl:meanEl:sigmaEl:separationPower:chiSquareOverNdf"; +} + +void SeparationPowerReductor::update(TObject* obj) +{ + // The values for the separation power are saved in a TProfile + if (obj) { + if (auto profile = static_cast(obj)) { + mSeparationPower.amplitudePi = profile->GetBinContent(1); + mSeparationPower.meanPi = profile->GetBinContent(2); + mSeparationPower.sigmaPi = profile->GetBinContent(3); + + mSeparationPower.amplitudeEl = profile->GetBinContent(4); + mSeparationPower.meanEl = profile->GetBinContent(5); + mSeparationPower.sigmaEl = profile->GetBinContent(6); + + mSeparationPower.separationPower = profile->GetBinContent(7); + mSeparationPower.chiSquareOverNdf = profile->GetBinContent(8); + } + } else { + ILOG(Error, Support) << "No 'obj' found." << ENDM; + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/TH1ReductorTPC.cxx b/Modules/TPC/src/TH1ReductorTPC.cxx new file mode 100644 index 0000000000..5759741b55 --- /dev/null +++ b/Modules/TPC/src/TH1ReductorTPC.cxx @@ -0,0 +1,177 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH1Reductor.cxx +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#include "QualityControl/QcInfoLogger.h" +#include "TPC/TH1ReductorTPC.h" +#include +#include +#include + +namespace o2::quality_control_modules::tpc +{ +void TH1ReductorTPC::update(TObject* obj, std::vector& reducedSource, + std::vector>& axis, + int& finalNumberPads) +{ + // Define the local variables in the default case: 1 single pad + // (no multipad canvas, nor slicer), and slicer axes size set to 1 (no slicing). + TList* padList = nullptr; // List of TPads if input TCanvas. + TH1* histo = nullptr; // Pointer to the histogram to trend. + int numberPads = 1; // Number of input objects. + int numberSlices = 1; // Default value for the inner slicer axis. + bool useSlicing = false; + + // GANESHA add protection that axisSize == 1. But, do we allow that the json contains also y or z axis i.e. or protection would be if( (int)axis.size() < 1 ) + if ((int)axis.size() != 1) { + ILOG(Error, Support) << "Error: 'axisDivision' in json not configured properly for TH1Reductor. Should contain exactly one axis." << ENDM; + } + + // Get the number of pads, and their list in case of an input canvas. + const bool isCanvas = (obj->IsA() == TCanvas::Class()); + if (isCanvas) { + auto canvas = static_cast(obj); + padList = static_cast(canvas->GetListOfPrimitives()); + padList->SetOwner(kTRUE); + numberPads = padList->GetEntries(); + } else { // Non-canvas case: number of input pads always 1, i.e. only one histogram passed. + if (axis[0].size() > 1) { // Ensure the slicer config is valid, either to select a certain range, or obtain multiple slices. + // Otherwise, full range of histo will be used. + numberSlices = (int)axis[0].size() - 1; // axis[0].size() = number of boundaries. + useSlicing = true; // Enable the use of custom boundaries. + } else { + ILOG(Info, Support) << "Not enough axis boundaries for slicing. Will use full histogram range." << ENDM; + } + } + ILOG(Info, Support) << "Number of input histograms for the trending of " + << obj->GetName() << ": " << numberPads << ENDM; + + // Access the histograms embedded in 'obj'. + for (int iPad = 0; iPad < numberPads; iPad++) { + if (isCanvas) { + auto pad = static_cast(padList->At(iPad)); + histo = static_cast(pad->GetListOfPrimitives()->At(0)); + } else { // 'obj' is already a single histogram. + histo = static_cast(obj); + } + + if (histo) { + // Bin Numbers for correctly getting the statistical properties + int binXLow = 0; + int binXUp = 0; + + // Get the trending quantities defined in 'SlicerInfo'. + for (int j = 0; j < numberSlices; j++) { + std::string thisRange; + float sliceLabel = 0.; + + if (useSlicing) { + getBinSlices(histo->GetXaxis(), axis[0][j], axis[0][j + 1], binXLow, binXUp, sliceLabel); + histo->GetXaxis()->SetRange(binXLow, binXUp); + thisRange = fmt::format("{0:s} - RangeX: [{1:.1f}, {2:.1f}]", histo->GetTitle(), axis[0][j], axis[0][j + 1]); + } else { + if (isCanvas) { + thisRange = fmt::format("{0:s}", histo->GetTitle()); + sliceLabel = (float)(j); + } else { + thisRange = fmt::format("{0:s} - RangeX (default): [{1:.1f}, {2:.1f}]", histo->GetTitle(), histo->GetXaxis()->GetXmin(), histo->GetXaxis()->GetXmax()); + sliceLabel = (histo->GetXaxis()->GetXmin() + histo->GetXaxis()->GetXmax()) / 2.; + } + binXLow = 1; + binXUp = histo->GetNbinsX(); + } + + finalNumberPads++; + SliceInfo mySlice; + mySlice.entries = histo->Integral(binXLow, binXUp); + mySlice.meanX = histo->GetMean(1); + mySlice.stddevX = histo->GetStdDev(1); + if (mySlice.entries != 0) { + mySlice.errMeanX = mySlice.stddevX / (sqrt(mySlice.entries)); + } else { + mySlice.errMeanX = 0.; + } + + float StatsY[3]; // 0 Mean, 1 Stddev, 2 Error + if (useSlicing) { + GetTH1StatsY(histo, StatsY, binXLow, binXUp); + } else { // We don't slice and take the full histo as defined. + GetTH1StatsY(histo, StatsY, binXLow, binXUp); + } + + mySlice.meanY = StatsY[0]; + mySlice.stddevY = StatsY[1]; + mySlice.errMeanY = StatsY[2]; + mySlice.sliceLabelX = sliceLabel; + mySlice.sliceLabelY = 0.; + mySlice.title = thisRange; + + reducedSource.emplace_back(mySlice); + } + + } else { + ILOG(Error, Support) << "Error: 'histo' not found." << ENDM; + } + } // All the vector elements have been updated. +} + +void TH1ReductorTPC::GetTH1StatsY(TH1* hist, float stats[3], + const int lowerBin, const int upperBin) +{ + const int nTotalBins = hist->GetNbinsX(); + const int iterateBins = upperBin - lowerBin + 1; // Amount of bins included in the calculation. + // Includes lowerBin and upperBin. + + // Safety measures. + if (lowerBin <= 0 || upperBin <= 0) { + ILOG(Error, Support) << "Error: Negative bin in TH1ReducterTPC::GetTH1StatsY" << ENDM; + exit(0); + } + if (upperBin <= lowerBin) { + ILOG(Error, Support) << "Error: Upper bin smaller than lower bin in TH1ReducterTPC::GetTH1StatsY" << ENDM; + exit(0); + } + if (nTotalBins < (upperBin - lowerBin)) { + ILOG(Error, Support) << "Error: Bin region bigger than total amount of bins TH1ReducterTPC::GetTH1StatsY" << ENDM; + exit(0); + } + + float meanY = 0.; + float stddevY = 0.; + float errMeanY = 0.; + + for (int i = lowerBin; i <= upperBin; i++) { + meanY += hist->GetBinContent(i); + } + + meanY /= (float)iterateBins; + + for (int i = lowerBin; i <= upperBin; i++) { + stddevY += pow(meanY - hist->GetBinContent(i), 2.); + } + + stddevY /= ((float)iterateBins - 1.); + errMeanY = stddevY / ((float)iterateBins); + stddevY = sqrt(stddevY); + errMeanY = sqrt(errMeanY); + + stats[0] = meanY; + stats[1] = stddevY; + stats[2] = errMeanY; +} // TH1ReductorTPC::GetTH1StatsY(TH1* hist, float stats[3], float LowerBoundary, float UpperBoundary) + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/TH2ReductorTPC.cxx b/Modules/TPC/src/TH2ReductorTPC.cxx new file mode 100644 index 0000000000..0d3a8b0d60 --- /dev/null +++ b/Modules/TPC/src/TH2ReductorTPC.cxx @@ -0,0 +1,164 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TH2Reductor.cxx +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#include "QualityControl/QcInfoLogger.h" +#include "TPC/TH2ReductorTPC.h" +#include +#include +#include +#include + +namespace o2::quality_control_modules::tpc +{ +void TH2ReductorTPC::update(TObject* obj, std::vector& reducedSource, + std::vector>& axis, + int& finalNumberPads) +{ + // Define the local variables in the default case: 1 single pad + // (no multipad canvas, nor slicer), and slicer axes size set to 1 (no slicing). + TList* padList = nullptr; // List of TPads if input TCanvas. + TH2* histo = nullptr; // Pointer to the histogram to trend. + int numberPads = 1; // Number of input objects. + int numberSlicesX = 1; // Default value for the X axis slicer. + int numberSlicesY = 1; // Default value for the Y axis slicer. + bool useSlicingX = false; + bool useSlicingY = false; + + // GANESHA add protection that number of axes == 2. + if ((int)axis.size() != 2) { + ILOG(Error, Support) << "Error: 'axisDivision' in json not configured properly for TH2Reductor. Should contain exactly two axes." << ENDM; + } + + // Get the number of pads, and their list in case of an input canvas. + const bool isCanvas = (obj->IsA() == TCanvas::Class()); + if (isCanvas) { + auto canvas = static_cast(obj); + padList = static_cast(canvas->GetListOfPrimitives()); + padList->SetOwner(kTRUE); + numberPads = padList->GetEntries(); + } else { // Non-canvas case: number of input pads always 1, i.e. only one histogram passed. + if (axis[0].size() > 1) { // Ensure the slicer config X is valid, either to select a certain range, or obtain multiple slices. + // Otherwise, full range of histo will be used. + numberSlicesX = (int)axis[0].size() - 1; // axis[0].size() = number of boundaries. + useSlicingX = true; // Enable the use of custom boundaries. + } else { + ILOG(Info, Support) << "Not enough axis boundaries for slicing on X. Will use full histogram range along X." << ENDM; + } + + if (axis[1].size() > 1) { // Ensure the slicer config Y is valid, either to select a certain range, or obtain multiple slices. + // Otherwise, full range of histo will be used. + numberSlicesY = (int)axis[1].size() - 1; // axis[1].size() = number of boundaries. + useSlicingY = true; // Enable the use of custom boundaries. + } else { + ILOG(Info, Support) << "Not enough axis boundaries for slicing on Y. Will use full histogram range along Y." << ENDM; + } + } + + ILOG(Info, Support) << "Number of input histograms for the trending of " + << obj->GetName() << ": " << numberPads << ENDM; + + double labelIterator = 0; + + // Access the histograms embedded in 'obj'. + for (int iPad = 0; iPad < numberPads; iPad++) { + if (isCanvas) { + auto pad = static_cast(padList->At(iPad)); + histo = static_cast(pad->GetListOfPrimitives()->At(0)); + } else { // 'obj' is already a single histogram. + histo = static_cast(obj); + } + + if (histo) { + + // Bin Numbers for correctly getting the statistical properties + int binXLow = 0; + int binXUp = 0; + int binYLow = 0; + int binYUp = 0; + + // Get the trending quantities defined in 'SlicerInfo'. + // The two for-loop do only one pass if we have an input canvas. + + for (int iX = 0; iX < numberSlicesX; iX++) { + std::string thisRange; + float sliceLabelX = 0.; + + if (useSlicingX) { + getBinSlices(histo->GetXaxis(), axis[0][iX], axis[0][iX + 1], binXLow, binXUp, sliceLabelX); + histo->GetXaxis()->SetRange(binXLow, binXUp); + thisRange = fmt::format("{0:s} - RangeX: [{1:.1f}, {2:.1f}]", histo->GetTitle(), axis[0][iX], axis[0][iX + 1]); + } else { + if (isCanvas) { + thisRange = fmt::format("{0:s}", histo->GetTitle()); + sliceLabelX = (float)(iX); + } else { + thisRange = fmt::format("{0:s} - RangeX (default): [{1:.1f}, {2:.1f}]", histo->GetTitle(), histo->GetXaxis()->GetXmin(), histo->GetXaxis()->GetXmax()); + sliceLabelX = (histo->GetXaxis()->GetXmin() + histo->GetXaxis()->GetXmax()) / 2.; + } + binXLow = 1; + binXUp = histo->GetNbinsX(); + } + + for (int jY = 0; jY < numberSlicesY; jY++) { + float sliceLabelY = 0.; + + if (useSlicingY) { + getBinSlices(histo->GetYaxis(), axis[1][jY], axis[1][jY + 1], binYLow, binYUp, sliceLabelY); + histo->GetYaxis()->SetRange(binYLow, binYUp); + thisRange += fmt::format(" and RangeY: [{0:.1f}, {1:.1f}]", axis[1][jY], axis[1][jY + 1]); + } else { + thisRange += fmt::format(" and RangeY (default): [{0:.1f}, {1:.1f}]", histo->GetYaxis()->GetXmin(), histo->GetYaxis()->GetXmax()); + sliceLabelY = (histo->GetYaxis()->GetXmin() + histo->GetYaxis()->GetXmax()) / 2.; + binYLow = 1; + binYUp = histo->GetNbinsY(); + } + + finalNumberPads++; + SliceInfo mySlice; + mySlice.entries = histo->Integral(binXLow, binXUp, binYLow, binYUp, ""); + mySlice.meanX = histo->GetMean(1); + mySlice.stddevX = histo->GetStdDev(1); + if (mySlice.entries != 0) { + mySlice.errMeanX = mySlice.stddevX / (sqrt(mySlice.entries)); + } else { + mySlice.errMeanX = 0.; + } + + mySlice.meanY = histo->GetMean(2); + mySlice.stddevY = histo->GetStdDev(2); + if (mySlice.entries != 0) { + mySlice.errMeanY = mySlice.stddevY / (sqrt(mySlice.entries)); + } else { + mySlice.errMeanY = 0.; + } + + mySlice.sliceLabelX = sliceLabelX; + mySlice.sliceLabelY = sliceLabelY; + mySlice.title = thisRange; + + reducedSource.emplace_back(mySlice); + } + } + + } else { + ILOG(Error, Support) << "Error: 'histo' not found." << ENDM; + } + } // All the vector elements have been updated. +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/TPCAggregator.cxx b/Modules/TPC/src/TPCAggregator.cxx new file mode 100644 index 0000000000..3c426b162d --- /dev/null +++ b/Modules/TPC/src/TPCAggregator.cxx @@ -0,0 +1,116 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TPCAggregator.cxx +/// \author Marcel Lesch +/// + +#include "TPC/TPCAggregator.h" +#include "QualityControl/QcInfoLogger.h" + +#include + +using namespace o2::quality_control::core; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::tpc +{ + +void TPCAggregator::configure() +{ +} + +std::map TPCAggregator::aggregate(QualityObjectsMapType& qoMap) +{ + if (qoMap.empty()) { + Quality null = Quality::Null; + std::string NullReason = "QO map given to the aggregator '" + mName + "' is empty."; + null.addFlag(FlagTypeFactory::UnknownQuality(), NullReason); + null.addMetadata(Quality::Null.getName(), NullReason); + return { { mName, null } }; + } + + std::unordered_map AggregatorMetaData; + AggregatorMetaData[Quality::Null.getName()] = ""; + AggregatorMetaData[Quality::Bad.getName()] = ""; + AggregatorMetaData[Quality::Medium.getName()] = ""; + AggregatorMetaData[Quality::Good.getName()] = ""; + + std::unordered_map AggregatorComment; + AggregatorComment[Quality::Null.getName()] = ""; + AggregatorComment[Quality::Bad.getName()] = ""; + AggregatorComment[Quality::Medium.getName()] = ""; + AggregatorComment[Quality::Good.getName()] = ""; + + // we return the worse quality of all the objects we receive, but we preserve all FlagTypes + Quality current = Quality::Good; + for (const auto& [qoName, qo] : qoMap) { + (void)qoName; + for (const auto& flag : qo->getFlags()) { + current.addFlag(flag.first, flag.second); + } + + std::string qoTitle = qo->getName(); + std::string qoMetaData = qo->getQuality().getMetadata(qo->getQuality().getName(), ""); + std::string qoMetaDataComment = qo->getQuality().getMetadata("Comment", ""); + std::string insertTitle = qoTitle + ": "; + + insertQOName(qoMetaData, insertTitle); + insertQOName(qoMetaDataComment, insertTitle); + + AggregatorMetaData[qo->getQuality().getName()] += qoMetaData; + AggregatorComment[qo->getQuality().getName()] += qoMetaDataComment; + + if (qo->getQuality().isWorseThan(current)) { + current.set(qo->getQuality()); + } + } + ILOG(Info, Devel) << "Aggregated Quality: " << current << ENDM; + + current.addMetadata(Quality::Bad.getName(), AggregatorMetaData[Quality::Bad.getName()]); + current.addMetadata(Quality::Medium.getName(), AggregatorMetaData[Quality::Medium.getName()]); + current.addMetadata(Quality::Good.getName(), AggregatorMetaData[Quality::Good.getName()]); + current.addMetadata(Quality::Null.getName(), AggregatorMetaData[Quality::Null.getName()]); + current.addMetadata("Comment", AggregatorComment[current.getName()]); + + return { { mName, current } }; +} + +void TPCAggregator::insertQOName(std::string& metaData, std::string& insertTitle) +{ + + std::string delimiter = "\n"; + size_t pos = 0; + + if (metaData != "") { + if (metaData.find(delimiter) != std::string::npos) { + while ((pos = metaData.find("\n", pos)) != std::string::npos) { + // metaData.replace(pos, delimiter.size(), insertTitle); + metaData.insert(pos + delimiter.size(), insertTitle); + pos += insertTitle.size() + delimiter.size(); + } + + metaData.erase(metaData.length() - insertTitle.size()); + } // else { + // metaData = " " + insertTitle; + metaData = insertTitle + metaData; + // } + + if (metaData.substr(metaData.length() - delimiter.size(), delimiter.size()) != "\n") { + metaData += " \n"; + } + } + + return; +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/TimeGainCalibReductor.cxx b/Modules/TPC/src/TimeGainCalibReductor.cxx new file mode 100644 index 0000000000..d5af99f1aa --- /dev/null +++ b/Modules/TPC/src/TimeGainCalibReductor.cxx @@ -0,0 +1,101 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TimeGainCalibReductor.cxx +/// \author Marcel Lesch +/// + +#include "TPC/TimeGainCalibReductor.h" +#include "QualityControl/QcInfoLogger.h" +#include + +namespace o2::quality_control_modules::tpc +{ + +void* TimeGainCalibReductor::getBranchAddress() +{ + return &mStats; +} + +const char* TimeGainCalibReductor::getBranchLeafList() +{ + return "meanEntries[2][5]/F:stddevEntries[2][5]:meanGain[2][5]:diffCorrectionTgl[2][5]"; +} + +bool TimeGainCalibReductor::update(ConditionRetriever& retriever) +{ + // Protection in case something changes in the enums, else getBranchLeafList() is not properly set up + if (o2::tpc::CHARGETYPES != 2 || o2::tpc::GEMSTACKSPERSECTOR != 4) { + ILOG(Error, Support) << "Error in TimeGainCalibReductor: TPC setups not matching expected default" << ENDM; + } + + if (auto timeGainCalib = retriever.retrieve()) { + + constexpr std::array charges = { o2::tpc::Max, o2::tpc::Tot }; + constexpr std::array stacks = { o2::tpc::IROCgem, o2::tpc::OROC1gem, o2::tpc::OROC2gem, o2::tpc::OROC3gem }; + + for (int iCharge = 0; iCharge < o2::tpc::CHARGETYPES; iCharge++) { + auto charge = charges[iCharge]; + + // Mean and stddev of entries over all sectors and stacks + float sum[o2::tpc::GEMSTACKSPERSECTOR + 1] = { 0. }; + float sumSquare[o2::tpc::GEMSTACKSPERSECTOR + 1] = { 0. }; + float counter[o2::tpc::GEMSTACKSPERSECTOR + 1] = { 0. }; + + for (int sector = 0; sector < o2::tpc::SECTORSPERSIDE * o2::tpc::SIDES; ++sector) { + for (int iStack = 0; iStack < o2::tpc::GEMSTACKSPERSECTOR; iStack++) { + auto stack = stacks[iStack]; + float entry = (float)(timeGainCalib->getEntries(o2::tpc::StackID{ sector, stack }, charge)); + + // Quantity per stack type + sum[iStack] += entry; + sumSquare[iStack] += entry * entry; + counter[iStack] += 1.; + + // Quantity averaged over all stack types + sum[o2::tpc::GEMSTACKSPERSECTOR] += entry; + sumSquare[o2::tpc::GEMSTACKSPERSECTOR] += entry * entry; + counter[o2::tpc::GEMSTACKSPERSECTOR] += 1.; + } + } + + for (int iStack = 0; iStack < o2::tpc::GEMSTACKSPERSECTOR + 1; iStack++) { + if (counter[iStack] != 0) { + mStats.meanEntries[iCharge][iStack] = sum[iStack] / counter[iStack]; + } else { + mStats.meanEntries[iCharge][iStack] = 0.; + } + if (counter[iStack] > 1) { + mStats.stddevEntries[iCharge][iStack] = (sumSquare[iStack] - (sum[iStack] * sum[iStack]) / counter[iStack]) / (counter[iStack] - 1.); + } else { + mStats.stddevEntries[iCharge][iStack] = 0.; + } + } + + const double tgl = 1.; + // Gain averaged over all sectors for a stack type + for (int iStack = 0; iStack < o2::tpc::GEMSTACKSPERSECTOR; iStack++) { + auto stack = stacks[iStack]; + mStats.meanGain[iCharge][iStack] = timeGainCalib->getMeanParam(stack, charge, 0); + mStats.diffCorrectionTgl[iCharge][iStack] = tgl * (timeGainCalib->getMeanParam(stack, charge, 1) + tgl * (timeGainCalib->getMeanParam(stack, charge, 2) + tgl * (timeGainCalib->getMeanParam(stack, charge, 3) + tgl * timeGainCalib->getMeanParam(stack, charge, 4)))); + } + + // Gain averaged over all sectors for a stack type + mStats.meanGain[iCharge][o2::tpc::GEMSTACKSPERSECTOR] = timeGainCalib->getMeanParam(charge, 0); + mStats.diffCorrectionTgl[iCharge][o2::tpc::GEMSTACKSPERSECTOR] = tgl * (timeGainCalib->getMeanParam(charge, 1) + tgl * (timeGainCalib->getMeanParam(charge, 2) + tgl * (timeGainCalib->getMeanParam(charge, 3) + tgl * timeGainCalib->getMeanParam(charge, 4)))); + } + return true; + } + return false; +} + +} // namespace o2::quality_control_modules::tpc \ No newline at end of file diff --git a/Modules/TPC/src/TrackClusters.cxx b/Modules/TPC/src/TrackClusters.cxx new file mode 100644 index 0000000000..7407133866 --- /dev/null +++ b/Modules/TPC/src/TrackClusters.cxx @@ -0,0 +1,106 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrackClusters.cxx +/// \author Laura Serksnyte +/// + +// O2 includes +#include "Framework/ProcessingContext.h" +#include +#include "DataFormatsTPC/TrackTPC.h" +#include "TPCQC/Helpers.h" +#include "DataFormatsTPC/ClusterNative.h" +#include "DataFormatsTPC/WorkflowHelper.h" + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/TrackClusters.h" +#include "Common/Utils.h" + +using namespace o2::framework; +using namespace o2::tpc; + +namespace o2::quality_control_modules::tpc +{ + +TrackClusters::TrackClusters() : TaskInterface() +{ +} + +void TrackClusters::initialize(InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TPC TrackClusters QC task" << ENDM; + + // do random generator + const int seed = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "seed"); + mRandomGenerator = new TRandom3(seed); + mSamplingFraction = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "samplingFraction"); + + const int cutMinNCluster = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMinNCluster"); + const float cutMindEdxTot = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMindEdxTot"); + const float cutAbsEta = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutAbsEta"); + + mQCTrackClusters.setTrackClustersCuts(cutMinNCluster, cutMindEdxTot, cutAbsEta); + mQCTrackClusters.initializeHistograms(); + + o2::tpc::qc::helpers::setStyleHistogramsInMap(mQCTrackClusters.getMapOfHisto()); + for (auto const& pair : mQCTrackClusters.getMapOfHisto()) { + for (auto& hist : pair.second) { + getObjectsManager()->startPublishing(hist.get()); + } + } +} + +void TrackClusters::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + mQCTrackClusters.resetHistograms(); +} + +void TrackClusters::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TrackClusters::monitorData(ProcessingContext& ctx) +{ + if (mRandomGenerator->Uniform(0., 1.) < mSamplingFraction) { + + using TrackType = std::vector; + using ClusterRefType = std::vector; + + auto tracks = ctx.inputs().get("inputTracks"); + const auto& inputsTPCclusters = o2::tpc::getWorkflowTPCInput(ctx, 0, false); + auto clusRefs = ctx.inputs().get("inputClusRefs"); + + mQCTrackClusters.processTrackAndClusters(&tracks, &inputsTPCclusters->clusterIndex, &clusRefs); + } +} + +void TrackClusters::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void TrackClusters::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TrackClusters::reset() +{ + ILOG(Debug, Devel) << "Resetting the data" << ENDM; + mQCTrackClusters.resetHistograms(); +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/Tracking.cxx b/Modules/TPC/src/Tracking.cxx new file mode 100644 index 0000000000..079822042f --- /dev/null +++ b/Modules/TPC/src/Tracking.cxx @@ -0,0 +1,123 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Tracking.cxx +/// \author David Rohr +/// + +// root includes +#include +#include +#include +#include +#include + +// O2 includes +#include "Framework/ProcessingContext.h" +#include "Framework/DataRefUtils.h" +#include +#include "Framework/InputRecordWalker.h" +#include "DataFormatsTPC/TrackTPC.h" +#include "SimulationDataFormat/MCCompLabel.h" +#include "SimulationDataFormat/MCTruthContainer.h" +#include "SimulationDataFormat/ConstMCTruthContainer.h" +#include "DataFormatsTPC/ClusterNative.h" +#include "DataFormatsTPC/WorkflowHelper.h" +#include "TPCQC/Helpers.h" +#include "GPUO2InterfaceQA.h" + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/Tracking.h" + +using namespace o2::tpc; +using namespace o2::dataformats; +using namespace o2::framework; +using namespace o2::header; + +namespace o2::quality_control_modules::tpc +{ + +Tracking::Tracking() : TaskInterface() {} + +Tracking::~Tracking() +{ +} + +void Tracking::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TPC Tracking QC task" << ENDM; + mOutputMode = o2::tpc::qc::Tracking::outputMergeable; + mQCTracking.initialize(mOutputMode); + + if (mOutputMode == o2::tpc::qc::Tracking::outputMergeable) { + const std::vector* h1; + const std::vector* h2; + const std::vector* h3; + const std::vector* h4; + mQCTracking.getHists(h1, h2, h3, h4); + for (auto& hist : *h1) { + getObjectsManager()->startPublishing((TObject*)&hist); + } + for (auto& hist : *h2) { + getObjectsManager()->startPublishing((TObject*)&hist); + } + for (auto& hist : *h3) { + getObjectsManager()->startPublishing((TObject*)&hist); + } + for (auto& hist : *h4) { + getObjectsManager()->startPublishing((TObject*)&hist); + } + } +} + +void Tracking::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + mQCTracking.resetHistograms(); +} + +void Tracking::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void Tracking::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto tracks = ctx.inputs().get>("inputTracks"); + auto trackLabels = ctx.inputs().get>("inputTrackLabels"); + auto clusRefs = ctx.inputs().get>("inputClusRefs"); + const auto& inputsTPCclusters = o2::tpc::getWorkflowTPCInput(ctx, 0, true); + + LOG(info) << "RECEIVED tracks " << tracks.size() << " (MC " << trackLabels.size() << ", ClusRefs " << clusRefs.size() << ") clusters " << inputsTPCclusters->clusterIndex.nClustersTotal << " (MC " << inputsTPCclusters->clusterIndex.clustersMCTruth->getNElements() << ")"; + mQCTracking.processTracks(&tracks, &trackLabels, &inputsTPCclusters->clusterIndex); +} + +void Tracking::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void Tracking::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void Tracking::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mQCTracking.resetHistograms(); +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/Tracks.cxx b/Modules/TPC/src/Tracks.cxx new file mode 100644 index 0000000000..ffbb1d055b --- /dev/null +++ b/Modules/TPC/src/Tracks.cxx @@ -0,0 +1,113 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Tracks.cxx +/// \author Stefan Heckel, sheckel@cern.ch +/// + +// root includes +#include +#include +#include + +// O2 includes +#include "Framework/ProcessingContext.h" +#include "DataFormatsTPC/TrackTPC.h" +#include "DataFormatsCalibration/MeanVertexObject.h" +#include "TPCQC/Helpers.h" +#include + +// QC includes +#include "QualityControl/QcInfoLogger.h" +#include "TPC/Tracks.h" +#include "Common/Utils.h" + +namespace o2::quality_control_modules::tpc +{ + +void Tracks::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TPC Tracks QC task" << ENDM; + + const float cutMindEdxTot = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMindEdxTot"); + const float cutAbsEta = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutAbsEta"); + const int cutMinNCluster = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMinNCluster"); + const float cutPtForDCAr = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutPtForDCAr", 1.5); // min pt cut only for the DCA + const float samplingFractionDCAr = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "samplingFractionDCAr", 0.1); + const bool runAsyncAndTurnOffSomeHistos = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "turnOffHistosForAsync"); + const float cutMaxAbsDCAr = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "cutMaxAbsDCAr", 0.1); + const bool useCutMaxAbsDCArOnHistos = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "useCutMaxAbsDCArOnHistos"); + + usePVfromCCDB = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "usePVfromCCDB"); + + // set track cuts defaults are (AbsEta = 1.0, nCluster = 60, MindEdxTot = 20) + mQCTracks.setTrackCuts(cutAbsEta, cutMinNCluster, cutMindEdxTot, cutPtForDCAr, samplingFractionDCAr, runAsyncAndTurnOffSomeHistos, cutMaxAbsDCAr, useCutMaxAbsDCArOnHistos); + + mQCTracks.initializeHistograms(); + // pass map of vectors of histograms to be beutified! + o2::tpc::qc::helpers::setStyleHistogramsInMap(mQCTracks.getMapHist()); + for (auto const& pair : mQCTracks.getMapHist()) { + getObjectsManager()->startPublishing(pair.second.get()); + } +} + +void Tracks::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; + mQCTracks.resetHistograms(); +} + +void Tracks::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void Tracks::monitorData(o2::framework::ProcessingContext& ctx) +{ + // set the coordinates of the PV (extracted from CCDB) + if (usePVfromCCDB) { + auto coordinatesOfPV = ctx.inputs().get("meanvertex"); + if (!coordinatesOfPV) { + LOGP(error, "Failed to retrieve MeanVertexObject, using default (0,0,0) instead!"); + } else { + mQCTracks.setPVposition(coordinatesOfPV->getPos()); + } + } + + using TrackType = std::vector; + auto tracks = ctx.inputs().get("inputTracks"); + + for (auto const& track : tracks) { + mQCTracks.processTrack(track); + } +} + +void Tracks::endOfCycle() +{ + mQCTracks.processEndOfCycle(); + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void Tracks::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void Tracks::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mQCTracks.resetHistograms(); +} + +} // namespace o2::quality_control_modules::tpc \ No newline at end of file diff --git a/Modules/TPC/src/TrendingTaskConfigTPC.cxx b/Modules/TPC/src/TrendingTaskConfigTPC.cxx new file mode 100644 index 0000000000..fbce5d7b03 --- /dev/null +++ b/Modules/TPC/src/TrendingTaskConfigTPC.cxx @@ -0,0 +1,85 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskConfigTPC.cxx +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#include "TPC/TrendingTaskConfigTPC.h" +#include + +namespace o2::quality_control_modules::tpc +{ + +TrendingTaskConfigTPC::TrendingTaskConfigTPC(const std::string& id, + const boost::property_tree::ptree& config) + : PostProcessingConfig(id, config) +{ + producePlotsOnUpdate = config.get("qc.postprocessing." + id + ".producePlotsOnUpdate", true); + resumeTrend = config.get("qc.postprocessing." + id + ".resumeTrend", false); + for (const auto& plotConfig : config.get_child("qc.postprocessing." + id + ".plots")) { + plots.push_back({ plotConfig.second.get("name"), + plotConfig.second.get("title", ""), + plotConfig.second.get("varexp"), + plotConfig.second.get("selection", ""), + plotConfig.second.get("option", ""), + plotConfig.second.get("graphErrors", ""), + plotConfig.second.get("graphYRange", ""), + plotConfig.second.get("graphXRange", ""), + plotConfig.second.get("graphAxisLabel", "") }); + } + + // Loop over all the data sources to trend. + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + id + ".dataSources")) { + // Prepare the vector(vector) for the slicing. + std::vector> axisBoundaries; + std::vector singleAxis; + + if (const auto& multiAxisValues = dataSourceConfig.second.get_child_optional("axisDivision"); multiAxisValues.has_value()) { + for (const auto& multiAxisValue : multiAxisValues.value()) { + for (const auto& axis : multiAxisValue.second) { + singleAxis.push_back(std::stof(axis.second.data())); + } + axisBoundaries.push_back(singleAxis); + singleAxis.clear(); + } + } + + // Parse the vector of "names" or just get the "name" of sources. + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + sourceName.second.data(), + dataSourceConfig.second.get("reductorName"), + axisBoundaries, + dataSourceConfig.second.get("moduleName") }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here. + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name"), + dataSourceConfig.second.get("reductorName"), + axisBoundaries, + dataSourceConfig.second.get("moduleName") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + id + ".dataSources'"); + } + + axisBoundaries.clear(); + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/TrendingTaskTPC.cxx b/Modules/TPC/src/TrendingTaskTPC.cxx new file mode 100644 index 0000000000..86e453054e --- /dev/null +++ b/Modules/TPC/src/TrendingTaskTPC.cxx @@ -0,0 +1,666 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskTPC.cxx +/// \author Marcel Lesch +/// \author Cindy Mordasini +/// \author Based on the work from Piotr Konopka +/// + +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/ActivityHelpers.h" +#include "TPC/TrendingTaskTPC.h" +#include "QualityControl/RepoPathUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::postprocessing; +using namespace o2::quality_control_modules::tpc; + +void TrendingTaskTPC::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingTaskConfigTPC(getID(), config); +} + +void TrendingTaskTPC::initialize(Trigger, framework::ServiceRegistryRef services) +{ + // Prepare the data structure of the trending TTree. + if (mConfig.resumeTrend) { + ILOG(Info, Support) << "Trying to retrieve an existing TTree for this task to continue the trend." << ENDM; + auto& qcdb = services.get(); + auto path = RepoPathUtils::getMoPath(mConfig.detectorName, PostProcessingInterface::getName(), "", "", false); + auto mo = qcdb.retrieveMO(path, PostProcessingInterface::getName(), -1, mConfig.activity); + if (mo && mo->getObject()) { + auto tree = dynamic_cast(mo->getObject()); + if (tree) { + mTrend = std::unique_ptr(tree); + mo->setIsOwner(false); + } + } else { + ILOG(Warning, Support) << "Could not retrieve an existing TTree for this task, maybe there is none which match these Activity settings" << ENDM; + } + } + if (mTrend == nullptr) { + ILOG(Info, Support) << "Generating new TTree for SliceTrending" << ENDM; + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + + mTrend->Branch("meta", &mMetaData, mMetaData.getBranchLeafList()); + mTrend->Branch("time", &mTime); + for (const auto& source : mConfig.dataSources) { + mSources[source.name] = new std::vector(); + mSourcesQuality[source.name] = new SliceInfoQuality(); + if (source.type == "repository") { + mTrend->Branch(source.name.c_str(), &mSources[source.name]); + } else if (source.type == "repository-quality") { + mTrend->Branch(source.name.c_str(), &mSourcesQuality[source.name]); + } + } + } else { // we picked up an older TTree + mTrend->SetBranchAddress("meta", &mMetaData); + mTrend->SetBranchAddress("time", &mTime); + for (const auto& source : mConfig.dataSources) { + const bool existingBranch = mTrend->GetBranchStatus(source.name.c_str()); + mSources[source.name] = new std::vector(); + mSourcesQuality[source.name] = new SliceInfoQuality(); + if (existingBranch) { + if (source.type == "repository") { + mTrend->SetBranchAddress(source.name.c_str(), &mSources[source.name]); + } else if (source.type == "repository-quality") { + mTrend->SetBranchAddress(source.name.c_str(), &mSourcesQuality[source.name]); + } + } else { + if (source.type == "repository") { + mTrend->Branch(source.name.c_str(), &mSources[source.name]); + } else if (source.type == "repository-quality") { + mTrend->Branch(source.name.c_str(), &mSourcesQuality[source.name]); + } + } + } + } + // Reductors + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create( + source.moduleName, source.reductorName)); + mReductors[source.name] = std::move(reductor); + if (source.type == "repository") { + mIsMoObject[source.name] = true; + } else if (source.type == "repository-quality") { + mIsMoObject[source.name] = false; + } + } + + if (mConfig.producePlotsOnUpdate) { + getObjectsManager()->startPublishing(mTrend.get(), PublicationPolicy::ThroughStop); + } +} + +void TrendingTaskTPC::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + trendValues(t, qcdb); + if (mConfig.producePlotsOnUpdate) { + generatePlots(); + } +} + +void TrendingTaskTPC::finalize(Trigger t, framework::ServiceRegistryRef) +{ + if (!mConfig.producePlotsOnUpdate) { + getObjectsManager()->startPublishing(mTrend.get()); + } + generatePlots(); + for (const auto& source : mConfig.dataSources) { + delete mSources[source.name]; + mSources[source.name] = nullptr; + delete mSourcesQuality[source.name]; + mSourcesQuality[source.name] = nullptr; + } +} + +void TrendingTaskTPC::trendValues(const Trigger& t, + repository::DatabaseInterface& qcdb) +{ + mTime = activity_helpers::isLegacyValidity(t.activity.mValidity) + ? t.timestamp / 1000 + : t.activity.mValidity.getMax() / 1000; // ROOT expects seconds since epoch. + mMetaData.runNumber = t.activity.mId; + + for (auto& dataSource : mConfig.dataSources) { + mNumberPads[dataSource.name] = 0; + if (dataSource.type == "repository") { + mSources[dataSource.name]->clear(); // reset + auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name, t.timestamp, t.activity); + TObject* obj = mo ? mo->getObject() : nullptr; + + mAxisDivision[dataSource.name] = dataSource.axisDivision; + if (obj) { + mReductors[dataSource.name]->update(obj, *mSources[dataSource.name], + dataSource.axisDivision, mNumberPads[dataSource.name]); + } + } else if (dataSource.type == "repository-quality") { + // reset + mSourcesQuality[dataSource.name]->qualitylevel = 0; + mSourcesQuality[dataSource.name]->title = ""; + if (auto qo = qcdb.retrieveQO(dataSource.path + "/" + dataSource.name, t.timestamp, t.activity)) { + mReductors[dataSource.name]->updateQuality(qo.get(), *mSourcesQuality[dataSource.name]); + mNumberPads[dataSource.name] = 1; + } + } else { + ILOG(Error, Support) << "Data source '" << dataSource.type << "' unknown." << ENDM; + } + } + + mTrend->Fill(); +} // void TrendingTaskTPC::trendValues(uint64_t timestamp, repository::DatabaseInterface& qcdb) + +void TrendingTaskTPC::generatePlots() +{ + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, no plot generated." << ENDM; + return; + } + + ILOG(Info, Support) << "Generating " << mConfig.plots.size() << " plots." << ENDM; + for (const auto& plot : mConfig.plots) { + // Delete the existing plots before regenerating them. + if (mPlots.count(plot.name)) { + delete mPlots[plot.name]; + mPlots[plot.name] = nullptr; + } + + // Postprocess each pad (titles, axes, flushing buffers). + const std::size_t posEndVar = plot.varexp.find("."); // Find the end of the dataSource. + const std::string varName(plot.varexp.substr(0, posEndVar)); + + // Draw the trending on a new canvas. + TCanvas* c = new TCanvas(); + c->SetName(plot.name.c_str()); + c->SetTitle(plot.title.c_str()); + + if (mIsMoObject[varName]) { + drawCanvasMO(c, plot.varexp, plot.name, plot.option, plot.graphErrors, mAxisDivision[varName]); + } else { + drawCanvasQO(c, plot.varexp, plot.name, plot.option); + } + + int NumberPlots = 1; + if (plot.varexp.find(":time") != std::string::npos || plot.varexp.find(":run") != std::string::npos) { // we plot vs time, multiple plots on canvas possible + NumberPlots = mNumberPads[varName]; + } + for (int p = 0; p < NumberPlots; p++) { + c->cd(p + 1); + if (auto histo = dynamic_cast(c->cd(p + 1)->GetPrimitive("Graph"))) { + beautifyGraph(histo, plot, c); + } else if (auto multigraph = dynamic_cast(c->cd(p + 1)->GetPrimitive("MultiGraph"))) { + if (auto legend = dynamic_cast(c->cd(2)->GetPrimitive("MultiGraphLegend"))) { + c->cd(1); + beautifyGraph(multigraph, plot, c); + c->cd(1)->SetLeftMargin(0.15); + c->cd(1)->SetRightMargin(0.01); + c->cd(2)->SetLeftMargin(0.01); + c->cd(2)->SetRightMargin(0.01); + } else { + ILOG(Error, Support) << "No legend in multigraph-time" << ENDM; + c->cd(1); + beautifyGraph(multigraph, plot, c); + } + c->Update(); + } else if (auto histo = dynamic_cast(c->cd(p + 1)->GetPrimitive("Graph2D"))) { + + const std::string thisTitle = fmt::format("{0:s}", plot.title.data()); + histo->SetTitle(thisTitle.data()); + + if (!plot.graphAxisLabel.empty()) { + setUserAxisLabel(histo->GetXaxis(), histo->GetYaxis(), plot.graphAxisLabel); + c->Modified(); + c->Update(); + } + + if (!plot.graphYRange.empty()) { + float yMin, yMax; + getUserAxisRange(plot.graphYRange, yMin, yMax); + histo->SetMinimum(yMin); + histo->SetMaximum(yMax); + c->Modified(); + c->Update(); + } + + gStyle->SetPalette(kBird); + histo->SetStats(kFALSE); + c->Modified(); + c->Update(); + + } else { + ILOG(Error, Devel) << "Could not get the 'Graph' of the plot '" + << plot.name << "'." << ENDM; + } + } + + mPlots[plot.name] = c; + getObjectsManager()->startPublishing(c, PublicationPolicy::Once); + } +} // void TrendingTaskTPC::generatePlots() + +void TrendingTaskTPC::drawCanvasMO(TCanvas* thisCanvas, const std::string& var, + const std::string& name, const std::string& opt, const std::string& err, const std::vector>& axis) +{ + // Determine the order of the plot (1 - histo, 2 - graph, ...) + const size_t plotOrder = std::count(var.begin(), var.end(), ':') + 1; + + // Prepare the strings for the dataSource and its trending quantity. + std::string varName, typeName, trendType; + getTrendVariables(var, varName, typeName, trendType); + + std::string errXName, errYName; + getTrendErrors(err, errXName, errYName); + + // Divide the canvas into the correct number of pads. + if (trendType == "time" || trendType == "run") { + thisCanvas->DivideSquare(mNumberPads[varName]); // trending vs time: multiple plots per canvas possible + } else if (trendType == "multigraphtime" || trendType == "multigraphrun") { + thisCanvas->Divide(2, 1); + } else { + thisCanvas->DivideSquare(1); + } + + // Delete the graph errors after the plot is saved. //To-Do check if ownership is now taken + // Unfortunately the canvas does not take its ownership. + TGraphErrors* graphErrors = nullptr; + + // Setup the tree reader with the needed values. + TTreeReader myReader(mTrend.get()); + TTreeReaderValue retrieveTime(myReader, "time"); + TTreeReaderValue retrieveRun(myReader, "meta.runNumber"); + TTreeReaderValue> dataRetrieveVector(myReader, varName.data()); + + const int nuPa = mNumberPads[varName]; + const int nEntries = mTrend->GetEntriesFast(); + const int nEntriesTime = mTrend->GetBranch("time")->GetEntries(); + const int nEntriesRuns = mTrend->GetBranch("meta")->GetEntries(); + const int nEntriesData = mTrend->GetBranch(varName.data())->GetEntries(); + + // Fill the graph(errors) to be published. + if (trendType == "time" || trendType == "run") { + const int nEffectiveEntries = (trendType == "time") ? std::min(nEntriesTime, nEntriesData) : std::min(nEntriesRuns, nEntriesData); + const int startPoint = (trendType == "time") ? nEntriesTime - nEffectiveEntries : nEntriesRuns - nEffectiveEntries; + + for (int p = 0; p < nuPa; p++) { + thisCanvas->cd(p + 1); + int iEntry = 0; + + graphErrors = new TGraphErrors(nEffectiveEntries); + myReader.SetEntry(startPoint - 1); // startPoint-1 as myReader.Next() increments by one so that we then start at startPoint + + while (myReader.Next()) { + const double xVal = (trendType == "time") ? (double)(*retrieveTime) : (double)(*retrieveRun); + const double dataPoint = (dataRetrieveVector->at(p)).RetrieveValue(typeName); + double errorX = 0.; + double errorY = 0.; + + if (!err.empty()) { + errorX = (dataRetrieveVector->at(p)).RetrieveValue(errXName); + errorY = (dataRetrieveVector->at(p)).RetrieveValue(errYName); + } + + graphErrors->SetPoint(iEntry, xVal, dataPoint); + graphErrors->SetPointError(iEntry, errorX, errorY); // Add Error to the last added point + + iEntry++; + } + graphErrors->SetTitle((dataRetrieveVector->at(p)).title.data()); + myReader.Restart(); + + if (!err.empty()) { + if (plotOrder != 2) { + ILOG(Info, Support) << "Non empty graphErrors seen for the plot '" << name + << "', which is not a graph, ignoring." << ENDM; + } else { + graphErrors->Draw(opt.data()); + } + } + } + } // Trending vs time + else if (trendType == "multigraphtime" || trendType == "multigraphrun") { + + auto multigraph = new TMultiGraph(); + multigraph->SetName("MultiGraph"); + + const int nEffectiveEntries = (trendType == "multigraphtime") ? std::min(nEntriesTime, nEntriesData) : std::min(nEntriesRuns, nEntriesData); + const int startPoint = (trendType == "multigraphtime") ? nEntriesTime - nEffectiveEntries : nEntriesRuns - nEffectiveEntries; + + for (int p = 0; p < nuPa; p++) { + int iEntry = 0; + auto gr = new TGraphErrors(nEffectiveEntries); + myReader.SetEntry(startPoint - 1); // startPoint-1 as myReader.Next() increments by one so that we then start at startPoint + + while (myReader.Next()) { + const double xVal = (trendType == "multigraphtime") ? (double)(*retrieveTime) : (double)(*retrieveRun); + const double dataPoint = (dataRetrieveVector->at(p)).RetrieveValue(typeName); + double errorX = 0.; + double errorY = 0.; + + if (!err.empty()) { + errorX = (dataRetrieveVector->at(p)).RetrieveValue(errXName); + errorY = (dataRetrieveVector->at(p)).RetrieveValue(errYName); + } + + gr->SetPoint(iEntry, xVal, dataPoint); + gr->SetPointError(iEntry, errorX, errorY); // Add Error to the last added point + iEntry++; + } + + const std::string_view title = (dataRetrieveVector->at(p)).title; + const auto posDivider = title.find("RangeX"); + if (posDivider != title.npos) { + gr->SetName(title.substr(posDivider, -1).data()); + } else { + gr->SetName(title.data()); + } + + myReader.Restart(); + multigraph->Add(gr); + } // for (int p = 0; p < nuPa; p++) + + thisCanvas->cd(1); + multigraph->Draw("A pmc plc"); + + auto legend = new TLegend(0., 0.1, 0.95, 0.9); + legend->SetName("MultiGraphLegend"); + legend->SetNColumns(2); + legend->SetTextSize(2.0); + for (auto obj : *multigraph->GetListOfGraphs()) { + legend->AddEntry(obj, obj->GetName(), "lpf"); + } + thisCanvas->cd(2); + legend->Draw(); + } // Trending vs Time as Multigraph + else if (trendType == "slices") { + + graphErrors = new TGraphErrors(nuPa); + thisCanvas->cd(1); + + myReader.SetEntry(nEntries - 1); // set event to last entry with index nEntries-1 + + int iEntry = 0; + for (int p = 0; p < nuPa; p++) { + + const double dataPoint = (dataRetrieveVector->at(p)).RetrieveValue(typeName); + double errorX = 0.; + double errorY = 0.; + if (!err.empty()) { + errorX = (dataRetrieveVector->at(p)).RetrieveValue(errXName); + errorY = (dataRetrieveVector->at(p)).RetrieveValue(errYName); + } + const double xLabel = (dataRetrieveVector->at(p)).RetrieveValue("sliceLabelX"); + + graphErrors->SetPoint(iEntry, xLabel, dataPoint); + graphErrors->SetPointError(iEntry, errorX, errorY); // Add Error to the last added point + + iEntry++; + } + + if (myReader.Next()) { + ILOG(Error, Devel) << "Entry beyond expected last entry" << ENDM; + } + + myReader.Restart(); + + if (!err.empty()) { + if (plotOrder != 2) { + ILOG(Info, Support) << "Non empty graphErrors seen for the plot '" << name + << "', which is not a graph, ignoring." << ENDM; + } else { + graphErrors->Draw(opt.data()); + } + } + } // Trending vs Slices + else if (trendType == "slices2D") { + + thisCanvas->cd(1); + const int xBins = axis[0].size(); + float xBoundaries[xBins]; + for (int i = 0; i < xBins; i++) { + xBoundaries[i] = axis[0][i]; + } + const int yBins = axis[1].size(); + float yBoundaries[yBins]; + for (int i = 0; i < yBins; i++) { + yBoundaries[i] = axis[1][i]; + } + + TH2F* graph2D = new TH2F("", "", xBins - 1, xBoundaries, yBins - 1, yBoundaries); + graph2D->SetName("Graph2D"); + thisCanvas->cd(1); + myReader.SetEntry(nEntries - 1); // set event to last entry with index nEntries-1 + + int iEntry = 0; + for (int p = 0; p < nuPa; p++) { + + const double dataPoint = (double)(dataRetrieveVector->at(p)).RetrieveValue(typeName); + double error = 0.; + if (!err.empty()) { + error = (double)(dataRetrieveVector->at(p)).RetrieveValue(errYName); + } + const double xLabel = (double)(dataRetrieveVector->at(p)).RetrieveValue("sliceLabelX"); + const double yLabel = (double)(dataRetrieveVector->at(p)).RetrieveValue("sliceLabelY"); + + graph2D->Fill(xLabel, yLabel, dataPoint); + graph2D->SetBinError(graph2D->GetXaxis()->FindBin(xLabel), graph2D->GetYaxis()->FindBin(yLabel), error); + + iEntry++; + } + + if (myReader.Next()) { + ILOG(Error, Devel) << "Entry beyond expected last entry" << ENDM; + } + + myReader.Restart(); + gStyle->SetPalette(kBird); + graph2D->Draw(opt.data()); + } // Trending vs Slices2D +} + +void TrendingTaskTPC::drawCanvasQO(TCanvas* thisCanvas, const std::string& var, + const std::string& name, const std::string& opt) +{ + // Determine the order of the plot (1 - histo, 2 - graph, ...) + const size_t plotOrder = std::count(var.begin(), var.end(), ':') + 1; + + // Prepare the strings for the dataSource and its trending quantity. + std::string varName, typeName, trendType; + getTrendVariables(var, varName, typeName, trendType); + + // Divide the canvas into the correct number of pads. + if (trendType != "time" && trendType != "run") { + ILOG(Error, Devel) << "Error in trending of Quality Object '" << name + << "'Trending only possible vs time or run, break." << ENDM; + } + thisCanvas->DivideSquare(1); + thisCanvas->cd(1); + + // Delete the graph errors after the plot is saved. //To-Do check if ownership is now taken + // Unfortunately the canvas does not take its ownership. + TGraphErrors* graphErrors = nullptr; + + // Setup the tree reader with the needed values. + TTreeReader myReader(mTrend.get()); + TTreeReaderValue retrieveTime(myReader, "time"); + TTreeReaderValue retrieveRun(myReader, "meta.runNumber"); + TTreeReaderValue qualityRetrieveVector(myReader, varName.data()); + + if (mNumberPads[varName] != 1) + ILOG(Error, Devel) << "Error in trending of Quality Object '" << name + << "' Quality trending should not have slicing, break." << ENDM; + + const int nEntries = mTrend->GetEntriesFast(); + const int nEntriesTime = mTrend->GetBranch("time")->GetEntries(); + const int nEntriesRuns = mTrend->GetBranch("meta")->GetEntries(); + const int nEntriesData = mTrend->GetBranch(varName.data())->GetEntries(); + const double errorX = 0.; + const double errorY = 0.; + + const int nEffectiveEntries = (trendType == "time") ? std::min(nEntriesTime, nEntriesData) : std::min(nEntriesRuns, nEntriesData); + const int startPoint = (trendType == "time") ? nEntriesTime - nEffectiveEntries : nEntriesRuns - nEffectiveEntries; + + int iEntry = 0; + graphErrors = new TGraphErrors(nEffectiveEntries); + myReader.SetEntry(startPoint - 1); // startPoint-1 as myReader.Next() increments by one so that we then start at startPoint + + while (myReader.Next()) { + const double xVal = (trendType == "time") ? (double)(*retrieveTime) : (double)(*retrieveRun); + double dataPoint = 0.; + + dataPoint = qualityRetrieveVector->RetrieveValue(typeName); + + if (dataPoint < 1. || dataPoint > 3.) { // if quality is outside standard good, medium, bad -> set to 0 + dataPoint = 0.; + } + + graphErrors->SetPoint(iEntry, xVal, dataPoint); + graphErrors->SetPointError(iEntry, errorX, errorY); // Add Error to the last added point + + iEntry++; + } + graphErrors->SetTitle(qualityRetrieveVector->title.data()); + myReader.Restart(); + + if (plotOrder != 2) { + ILOG(Info, Support) << "Non empty graphErrors seen for the plot '" << name + << "', which is not a graph, ignoring." << ENDM; + } else { + graphErrors->Draw(opt.data()); + } +} + +void TrendingTaskTPC::getUserAxisRange(const std::string graphAxisRange, float& limitLow, float& limitUp) +{ + const std::size_t posDivider = graphAxisRange.find(":"); + const std::string minString(graphAxisRange.substr(0, posDivider)); + const std::string maxString(graphAxisRange.substr(posDivider + 1)); + + limitLow = std::stof(minString); + limitUp = std::stof(maxString); +} + +void TrendingTaskTPC::setUserAxisLabel(TAxis* xAxis, TAxis* yAxis, const std::string graphAxisLabel) +{ + const std::size_t posDivider = graphAxisLabel.find(":"); + const std::string yLabel(graphAxisLabel.substr(0, posDivider)); + const std::string xLabel(graphAxisLabel.substr(posDivider + 1)); + + xAxis->SetTitle(xLabel.data()); + yAxis->SetTitle(yLabel.data()); +} + +void TrendingTaskTPC::getTrendVariables(const std::string& inputvar, std::string& sourceName, std::string& variableName, std::string& trend) +{ + const std::size_t posEndVar = inputvar.find("."); // Find the end of the dataSource. + const std::size_t posEndType = inputvar.find(":"); // Find the end of the quantity. + sourceName = inputvar.substr(0, posEndVar); + variableName = inputvar.substr(posEndVar + 1, posEndType - posEndVar - 1); + trend = inputvar.substr(posEndType + 1, -1); +} + +void TrendingTaskTPC::getTrendErrors(const std::string& inputvar, std::string& errorX, std::string& errorY) +{ + const std::size_t posEndType_err = inputvar.find(":"); // Find the end of the error. + errorX = inputvar.substr(posEndType_err + 1); + errorY = inputvar.substr(0, posEndType_err); +} + +template +void TrendingTaskTPC::beautifyGraph(T& graph, const TrendingTaskConfigTPC::Plot& plotconfig, TCanvas* canv) +{ + + // Set the title of the graph in a proper way. + std::string thisTitle; + if (plotconfig.varexp.find(":time") != std::string::npos) { + thisTitle = fmt::format("{0:s} - {1:s}", plotconfig.title.data(), graph->GetTitle()); // for plots vs time slicing might be applied for the title + } else { + thisTitle = fmt::format("{0:s}", plotconfig.title.data()); + } + graph->SetTitle(thisTitle.data()); + + // Set the user-defined range on the y axis if needed. + if (!plotconfig.graphYRange.empty()) { + float yMin, yMax; + getUserAxisRange(plotconfig.graphYRange, yMin, yMax); + graph->SetMinimum(yMin); + graph->SetMaximum(yMax); + canv->Modified(); + canv->Update(); + } + + if (!plotconfig.graphXRange.empty()) { + float xMin, xMax; + getUserAxisRange(plotconfig.graphXRange, xMin, xMax); + graph->GetXaxis()->SetLimits(xMin, xMax); + canv->Modified(); + canv->Update(); + } + + if (!plotconfig.graphAxisLabel.empty()) { + setUserAxisLabel(graph->GetXaxis(), graph->GetYaxis(), plotconfig.graphAxisLabel); + canv->Modified(); + canv->Update(); + } + + // Configure the time for the x axis. + if (plotconfig.varexp.find(":time") != std::string::npos || plotconfig.varexp.find(":multigraphtime") != std::string::npos) { + graph->GetXaxis()->SetTimeDisplay(1); + graph->GetXaxis()->SetNdivisions(505); + graph->GetXaxis()->SetTimeOffset(0.0); + graph->GetXaxis()->SetLabelOffset(0.02); + graph->GetXaxis()->SetTimeFormat("#splitline{%d.%m.%y}{%H:%M}"); + } else if (plotconfig.varexp.find(":meta.runNumber") != std::string::npos || plotconfig.varexp.find(":run") != std::string::npos || plotconfig.varexp.find(":multigraphrun") != std::string::npos) { + graph->GetXaxis()->SetNoExponent(true); + } + + if (plotconfig.varexp.find("quality") != std::string::npos) { + graph->SetMinimum(-0.5); + graph->SetMaximum(3.5); + + graph->GetYaxis()->Set(4, -0.5, 3.5); + graph->GetYaxis()->SetNdivisions(3); + graph->GetYaxis()->SetBinLabel(1, "No Quality"); + graph->GetYaxis()->SetBinLabel(2, "Good"); + graph->GetYaxis()->SetBinLabel(3, "Medium"); + graph->GetYaxis()->SetBinLabel(4, "Bad"); + graph->GetYaxis()->ChangeLabel(2, -1., -1., -1., kGreen + 2, -1, "Good"); + graph->GetYaxis()->ChangeLabel(3, -1., -1., -1., kOrange - 3, -1, "Medium"); + graph->GetYaxis()->ChangeLabel(4, -1., -1., -1., kRed, -1, "Bad"); + + canv->Modified(); + canv->Update(); + } +} diff --git a/Modules/TPC/src/Utility.cxx b/Modules/TPC/src/Utility.cxx new file mode 100644 index 0000000000..cdf51a3fad --- /dev/null +++ b/Modules/TPC/src/Utility.cxx @@ -0,0 +1,371 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file Utility.cxx +/// \author Thomas Klemenz +/// + +// O2 includes +#include "Framework/ProcessingContext.h" +#include "Framework/InputRecordWalker.h" +#include "DataFormatsTPC/ClusterNativeHelper.h" +#include "DataFormatsTPC/TPCSectorHeader.h" +#include "TPCBase/CalDet.h" +#if __has_include("TPCBase/Painter.h") +#include "TPCBase/Painter.h" +#else +#include "TPCBaseRecSim/Painter.h" +#endif +#include "CommonUtils/StringUtils.h" + +// QC includes +#include "TPC/Utility.h" +#include "QualityControl/QcInfoLogger.h" + +// external includes +#include +#include +#include +#include + +namespace o2::quality_control_modules::tpc +{ + +bool getPropertyBool(const boost::property_tree::ptree& config, const std::string& id, const std::string property) +{ + const auto propertyFullName = fmt::format("qc.postprocessing.{}.{}", id, property); + const boost::optional propertyExists = config.get_child_optional(propertyFullName); + if (propertyExists) { + const auto doProperty = config.get(propertyFullName); + if (doProperty == "1" || doProperty == "true" || doProperty == "True" || doProperty == "TRUE" || doProperty == "yes") { + return true; + } else if (doProperty == "0" || doProperty == "false" || doProperty == "False" || doProperty == "FALSE" || doProperty == "no") { + return false; + } else { + ILOG(Warning, Support) << fmt::format("No valid input for '{}'. Using default value 'false'.", property) << ENDM; + } + } else { + ILOG(Warning, Support) << fmt::format("Option '{}' is missing. Using default value 'false'.", property) << ENDM; + } + return false; +} + +void addAndPublish(std::shared_ptr objectsManager, std::vector>& canVec, std::vector canvNames, const std::map& metaData) +{ + for (const auto& canvName : canvNames) { + canVec.emplace_back(std::make_unique(canvName.data())); + auto canvas = canVec.back().get(); + objectsManager->startPublishing(canvas); + if (metaData.size() != 0) { + for (const auto& [key, value] : metaData) { + objectsManager->addMetadata(canvas->GetName(), key, value); + } + } + } +} + +std::vector toVector(std::vector>& input) +{ + std::vector output; + for (auto& in : input) { + output.emplace_back(in.get()); + } + return output; +} + +void fillCanvases(const o2::tpc::CalDet& calDet, std::vector>& canvases, const quality_control::core::CustomParameters& params, const std::string paramName) +{ + const std::string parNBins = paramName + "NBins"; + const std::string parXMin = paramName + "XMin"; + const std::string parXMax = paramName + "XMax"; + int nbins = 300; + float xmin = 0; + float xmax = 0; + const auto last = params.end(); + const auto itNBins = params.find(parNBins); + const auto itXMin = params.find(parXMin); + const auto itXMax = params.find(parXMax); + if ((itNBins == last) || (itXMin == last) || (itXMax == last)) { + LOGP(warning, "missing parameter {}, {} or {}, falling back to auto scaling", parNBins, parXMin, parXMax); + LOGP(warning, "Please add '{}': '', '{}': '', '{}': '' to the 'taskParameters'.", parNBins, parXMin, parXMax); + } else { + nbins = std::stoi(itNBins->second); + xmin = std::stof(itXMin->second); + xmax = std::stof(itXMax->second); + } + auto vecPtr = toVector(canvases); + o2::tpc::painter::makeSummaryCanvases(calDet, nbins, xmin, xmax, false, &vecPtr); +} + +void clearCanvases(std::vector>& canvases) +{ + for (const auto& canvas : canvases) { + canvas->Clear(); + } +} + +std::unique_ptr clusterHandler(o2::framework::InputRecord& inputs, int verbosity, unsigned long tpcSectorMask) +{ + auto retVal = std::make_unique(); + + std::vector filter = { + { "input", o2::framework::ConcreteDataTypeMatcher{ "TPC", "CLUSTERNATIVE" }, o2::framework::Lifetime::Timeframe }, + }; + bool sampledData = true; + for ([[maybe_unused]] auto const& ref : o2::framework::InputRecordWalker(inputs, filter)) { + sampledData = false; + break; + } + if (sampledData) { + filter = { + { "sampled-data", o2::framework::ConcreteDataTypeMatcher{ "DS", "CLUSTERNATIVE" }, o2::framework::Lifetime::Timeframe }, + }; + LOG(info) << "Using sampled data."; + } + + unsigned long recvMask = 0; + bool hasData = false; + for (auto const& ref : o2::framework::InputRecordWalker(inputs, filter)) { + auto const* sectorHeader = o2::framework::DataRefUtils::getHeader(ref); + if (sectorHeader == nullptr) { + throw std::runtime_error("sector header missing on header stack"); + } + const int sector = sectorHeader->sector(); + if (sector < 0) { + continue; + } + if (recvMask & sectorHeader->sectorBits) { + throw std::runtime_error("can only have one cluster data set per sector"); + } + recvMask |= (sectorHeader->sectorBits & tpcSectorMask); + retVal->internal.inputrefs[sector].data = ref; + hasData = true; + } + if (hasData && (recvMask != tpcSectorMask)) { + throw std::runtime_error("Incomplete set of clusters/digits received"); + } + + for (auto const& refentry : retVal->internal.inputrefs) { + auto& sector = refentry.first; + auto& ref = refentry.second.data; + if (ref.payload == nullptr) { + // skip zero-length message + continue; + } + if (!(tpcSectorMask & (1ul << sector))) { + continue; + } + if (refentry.second.labels.header != nullptr && refentry.second.labels.payload != nullptr) { + retVal->internal.mcInputs.emplace_back(o2::dataformats::ConstMCLabelContainerView(inputs.get>(refentry.second.labels))); + } + retVal->internal.inputs.emplace_back(gsl::span(ref.payload, o2::framework::DataRefUtils::getPayloadSize(ref))); + if (verbosity > 1) { + LOG(info) << "received " << *(ref.spec) << ", size " << o2::framework::DataRefUtils::getPayloadSize(ref) << " for sector " << sector; + } + } + + memset(&retVal->clusterIndex, 0, sizeof(retVal->clusterIndex)); + o2::tpc::ClusterNativeHelper::Reader::fillIndex(retVal->clusterIndex, retVal->internal.clusterBuffer, retVal->internal.clustersMCBuffer, retVal->internal.inputs, retVal->internal.mcInputs, tpcSectorMask); + + return std::move(retVal); +} + +void getTimestamp(const std::string& metaInfo, std::vector& timeStamps) +{ + std::string result_str; + long result; + std::string token = "Validity: "; + if (metaInfo.find(token) != std::string::npos) { + int start = metaInfo.find(token) + token.size(); + int end = metaInfo.find(" -", start); + result_str = metaInfo.substr(start, end - start); + std::string::size_type sz; + result = std::stol(result_str, &sz); + timeStamps.emplace_back(result); + } +} + +std::vector getDataTimestamps(const o2::ccdb::CcdbApi& cdbApi, const std::string_view path, const unsigned int nFiles, const long limit) +{ + std::vector outVec{}; + std::vector tmpVec{}; + std::vector tmpVec2{}; + + if (limit == -1) { + + // get the list of files for the latest timestamps up to 1 day ago from the moment the code is running + // added some seconds to upper timestamp limit as the ccdbapi uses creation timestamp, not validity! + const auto to = std::chrono::duration_cast((std::chrono::system_clock::now() + std::chrono::minutes(1)).time_since_epoch()).count(); + const auto from = std::chrono::duration_cast((std::chrono::system_clock::now() + std::chrono::weeks(-2)).time_since_epoch()).count(); + std::vector fileList = o2::utils::Str::tokenize(cdbApi.list(path.data(), false, "text/plain", to, from), '\n'); + for (const auto& metaData : fileList) { + getTimestamp(metaData, outVec); + if (outVec.size() == nFiles) { + break; + } + } + } else { + // get file list for requested timestamp minus three days + // added some seconds to upper timestamp limit as the ccdbapi uses creation timestamp, not validity! + std::vector fileList = o2::utils::Str::tokenize(cdbApi.list(path.data(), false, "text/plain", limit + 600000, limit - 86400000), '\n'); + for (const auto& metaData : fileList) { + if (outVec.size() < nFiles) { + getTimestamp(metaData, tmpVec); + if (tmpVec.size() > 0 && tmpVec.back() <= limit) { + if (outVec.size() == 0 || tmpVec.back() != outVec.back()) { + outVec.emplace_back(tmpVec.back()); + } + } + } else { + break; + } + } + } + std::sort(outVec.begin(), outVec.end()); + + return std::move(outVec); +} + +void calculateStatistics(const double* yValues, const double* yErrors, bool useErrors, const int firstPoint, const int lastPoint, double& mean, double& stddevOfMean) +{ + // yErrors returns nullptr for TGraph (no errors) + if (lastPoint - firstPoint <= 0) { + ILOG(Error, Support) << "In calculateStatistics(), the first and last point of the range have to differ!" << ENDM; + return; + } + + if (useErrors && !yErrors) { + ILOG(Error, Support) << "In calculateStatistics(): requested to use errors of data but TGraph does not contain errors." << ENDM; + useErrors = false; + } + + std::vector v(yValues + firstPoint, yValues + lastPoint); + std::vector vErr; + + if (useErrors) { + const std::vector vErr_temp(yErrors + firstPoint, yErrors + lastPoint); + for (int i = 0; i < vErr_temp.size(); i++) { + vErr.push_back(vErr_temp[i]); + } + } + + retrieveStatistics(v, vErr, useErrors, mean, stddevOfMean); +} + +void calculateStatistics(const double* yValues, const double* yErrors, bool useErrors, const int firstPoint, const int lastPoint, double& mean, double& stddevOfMean, std::vector& maskPoints) +{ + // yErrors returns nullptr for TGraph (no errors) + if (lastPoint - firstPoint <= 0) { + ILOG(Error, Support) << "In calculateStatistics(), the first and last point of the range have to differ!" << ENDM; + return; + } + + if (useErrors && !yErrors) { + ILOG(Error, Support) << "In calculateStatistics(): requested to use errors of data but TGraph does not contain errors." << ENDM; + useErrors = false; + } + + std::vector v; + const std::vector v_temp(yValues + firstPoint, yValues + lastPoint); + for (int i = 0; i < v_temp.size(); i++) { + if (std::find(maskPoints.begin(), maskPoints.end(), i) == maskPoints.end()) { // i is not in the masked points + v.push_back(v_temp[i]); + } + } + + std::vector vErr; + if (useErrors) { + const std::vector vErr_temp(yErrors + firstPoint, yErrors + lastPoint); + for (int i = 0; i < vErr_temp.size(); i++) { + if (std::find(maskPoints.begin(), maskPoints.end(), i) == maskPoints.end()) { // i is not in the masked points + vErr.push_back(vErr_temp[i]); + } + } + } + + retrieveStatistics(v, vErr, useErrors, mean, stddevOfMean); +} + +void retrieveStatistics(std::vector& values, std::vector& errors, bool useErrors, double& mean, double& stddevOfMean) +{ + if ((errors.size() != values.size()) && useErrors) { + ILOG(Error, Support) << "In retrieveStatistics(): errors do not match data points, omitting errors" << ENDM; + useErrors = false; + } + + double sum = 0.; + double sumSquare = 0.; + double sumOfWeights = 0.; // sum w_i + double sumOfSquaredWeights = 0.; // sum (w_i)^2 + double weight = 0.; + + if (!useErrors) { + // In case of no errors, we set our weights equal to 1 + sum = std::accumulate(values.begin(), values.end(), 0.0); + sumOfWeights = values.size(); + sumOfSquaredWeights = values.size(); + } else { + // In case of errors, we set our weights equal to 1/sigma_i^2 + for (size_t i = 0; i < values.size(); i++) { + weight = 1. / std::pow(errors[i], 2.); + sum += values[i] * weight; + sumSquare += values[i] * values[i] * weight; + sumOfWeights += weight; + sumOfSquaredWeights += weight * weight; + } + } + + mean = sum / sumOfWeights; + + if (values.size() == 1) { // we only have one point, we keep it's uncertainty + if (!useErrors) { + stddevOfMean = 0.; + } else { + stddevOfMean = sqrt(1. / sumOfWeights); + } + } else { // for >= 2 points, we calculate the spread + if (!useErrors) { + std::vector diff(values.size()); + std::transform(values.begin(), values.end(), diff.begin(), [mean](double x) { return x - mean; }); + double sq_sum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0); + stddevOfMean = std::sqrt(sq_sum / (values.size() * (values.size() - 1.))); + } else { + double ratioSumWeight = sumOfSquaredWeights / (sumOfWeights * sumOfWeights); + stddevOfMean = sqrt((sumSquare / sumOfWeights - mean * mean) * (1. / (1. - ratioSumWeight)) * ratioSumWeight); + } + } +} + +void calcMeanAndStddev(const std::vector& values, float& mean, float& stddev) +{ + if (values.size() == 0) { + mean = 0.; + stddev = 0.; + return; + } + + // Mean + const float sum = std::accumulate(values.begin(), values.end(), 0.0); + mean = sum / values.size(); + + // Stddev + if (values.size() == 1) { // we only have one point -> no stddev + stddev = 0.; + } else { // for >= 2 points, we calculate the spread + std::vector diff(values.size()); + std::transform(values.begin(), values.end(), diff.begin(), [mean](auto x) { return x - mean; }); + const auto sq_sum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.f); + stddev = std::sqrt(sq_sum / (values.size() * (values.size() - 1.))); + } +} + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TPC/src/VDriftCalibReductor.cxx b/Modules/TPC/src/VDriftCalibReductor.cxx new file mode 100644 index 0000000000..9e57e9b56d --- /dev/null +++ b/Modules/TPC/src/VDriftCalibReductor.cxx @@ -0,0 +1,44 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file VDriftCalibReductor.cxx +/// \author Marcel Lesch +/// + +#include "TPC/VDriftCalibReductor.h" + +#include + +namespace o2::quality_control_modules::tpc +{ + +void* VDriftCalibReductor::getBranchAddress() +{ + return &mStats; +} + +const char* VDriftCalibReductor::getBranchLeafList() +{ + return "vdrift/F:vdrifterror"; +} + +bool VDriftCalibReductor::update(ConditionRetriever& retriever) +{ + if (auto vdriftCalib = retriever.retrieve()) { + mStats.vdrift = vdriftCalib->getVDrift(); + mStats.vdrifterror = vdriftCalib->getVDriftError(); + return true; + } + return false; +} + +} // namespace o2::quality_control_modules::tpc \ No newline at end of file diff --git a/Modules/TPC/test/testQcTPC.cxx b/Modules/TPC/test/testQcTPC.cxx new file mode 100644 index 0000000000..ff649e25fc --- /dev/null +++ b/Modules/TPC/test/testQcTPC.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testTPC.cxx +/// \author +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::tpc +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::tpc diff --git a/Modules/TRD/CMakeLists.txt b/Modules/TRD/CMakeLists.txt new file mode 100644 index 0000000000..3762b4d331 --- /dev/null +++ b/Modules/TRD/CMakeLists.txt @@ -0,0 +1,85 @@ +# ---- Library ---- + +add_library(O2QcTRD) + +target_sources(O2QcTRD PRIVATE + src/CalibReductorTRD.cxx + src/TrackletCountCheck.cxx + src/PulsePositionCheck.cxx + src/TrackingTask.cxx + src/PulseHeightTrackMatch.cxx + src/TrackletsTask.cxx + src/PulseHeightCheck.cxx + src/RawData.cxx + src/RawDataCheck.cxx + src/DigitsTask.cxx + src/TRDTrending.cxx + src/TrendingTaskConfigTRD.cxx + src/PulseHeightPostProcessing.cxx + src/TRDHelpers.cxx) + +target_include_directories( + O2QcTRD + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcTRD PUBLIC O2QualityControl O2QcCommon) + +install(TARGETS O2QcTRD + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcTRD HEADERS + include/TRD/CalibReductorTRD.h + include/TRD/TrackletCountCheck.h + include/TRD/PulsePositionCheck.h + include/TRD/TrackingTask.h + include/TRD/PulseHeightTrackMatch.h + include/TRD/TrackletsTask.h + include/TRD/TRDHelpers.h + include/TRD/PulseHeightCheck.h + include/TRD/RawData.h + include/TRD/RawDataCheck.h + include/TRD/PulseHeightPostProcessing.h + include/TRD/DigitsTask.h + include/TRD/TRDTrending.h + include/TRD/TrendingTaskConfigTRD.h + LINKDEF include/TRD/LinkDef.h) + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcTRD.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcTRD Boost::unit_test_framework) + + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + + +# ---- Install ---- + +install(TARGETS O2QcTRD ${EXE_NAMES} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/TRD + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Install config files ---- + +install(FILES TRDQC.json + DESTINATION etc) diff --git a/Modules/TRD/README.md b/Modules/TRD/README.md new file mode 100644 index 0000000000..4e8192f49c --- /dev/null +++ b/Modules/TRD/README.md @@ -0,0 +1,59 @@ +# Quality Control for the TRD + + +The QC code covers Tasks, Checks, Aggregators and Trending. +The configuration is done via json files. For running online at P2 we are using a single json file only: + + consul://o2/components/qc/ANY/any/trd-full-qcmn + +For tests, both on staging and in production we can use another json file which we can select both for the QC merger node and for the EPN nodes: + + consul://o2/components/qc/ANY/any/trd-full-qcmn-test + +For asynchronous production we are currently running basically the same thing as in synchronous, only without data sampling. + +For both synch and asynch productions we urgently need + +- tracking efficiency as function of pT and eta-phi +- trending of calibration parameters (gain, vDrift, ExB, t0) +- trending of basic observables, such as triggers per TF, tracklet and digit count per trigger, ... +- checks for the available plots, especially the ones foreseen for the shifters layout +- the chamber status from DCS in order to properly mask half chambers which are not expected to send data +- certainly others I cannot think of at the moment... + + +## Current Status + +### Tasks + +| class name | task name | synch reco | asynch reco | +|---|---|---|---| +| RawDataTask | RawData | enabled | disabled | +| DigitsTask | Digits | enabled | enabled | +| TrackletsTask | Tracklets | enabled | enabled | +| PulseHeightTrackMatch | PHTrackMatch | enabled, if ITSTPCTRD matching | enabled, if ITSTPCTRD matching | +| TrackingTask | Tracking | enabled, if ITSTPCTRD matching | enabled, if ITSTPCTRD matching | + + +In case new tasks are added please make sure that it uses a unique port number. The port range assigned to TRD can be found in . It goes from 29850 to 29899. + +### Checks + +TODO + +### Aggregators + +TODO + +### Trending + +TODO + + +## Quickstart + +For local tests you can append the `o2-qc` binary to any workflow in O2 with the following parameters: + + o2-qc --config json://$HOME/alice/QualityControl/Modules/TRD/TRDQC.json + +Adjust the TRDQC.json file according to your needs, e.g. add additional checks, disable some tasks depending on the inputs you have available etc. diff --git a/Modules/TRD/TRDQC.json b/Modules/TRD/TRDQC.json new file mode 100644 index 0000000000..947d64e845 --- /dev/null +++ b/Modules/TRD/TRDQC.json @@ -0,0 +1,217 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "123456", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "http://alice-ccdb.cern.ch" + }, + "infologger": { + "filterDiscardDebug": "false", + "filterDiscardLevel": "11" + } + }, + "tasks": { + "RawData": { + "active": "false", + "className": "o2::quality_control_modules::trd::RawData", + "moduleName": "QcTRD", + "detectorName": "TRD", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query": "rawstats:TRD/RAWSTATS" + } + }, + "Digits": { + "active": "true", + "className": "o2::quality_control_modules::trd::DigitsTask", + "moduleName": "QcTRD", + "detectorName": "TRD", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query": "digits:TRD/DIGITS;triggers:TRD/TRKTRGRD;noiseMap:TRD/NOISEMAP/0?lifetime=condition&ccdb-path=TRD/Calib/NoiseMapMCM;chamberStatus:TRD/CHSTATUS/0?lifetime=condition&ccdb-path=TRD/Calib/HalfChamberStatusQC" + } + }, + "Tracklets": { + "active": "true", + "className": "o2::quality_control_modules::trd::TrackletsTask", + "moduleName": "QcTRD", + "detectorName": "TRD", + "cycleDurationSeconds": "15", + "dataSource": { + "type": "direct", + "query": "tracklets:TRD/TRACKLETS;triggers:TRD/TRKTRGRD;noiseMap:TRD/NOISEMAP/0?lifetime=condition&ccdb-path=TRD/Calib/NoiseMapMCM;chamberStatus:TRD/CHSTATUS/0?lifetime=condition&ccdb-path=TRD/Calib/HalfChamberStatusQC" + } + }, + "PHTrackMatch": { + "active": "true", + "className": "o2::quality_control_modules::trd::PulseHeightTrackMatch", + "moduleName": "QcTRD", + "detectorName": "TRD", + "cycleDurationSeconds": "15", + "dataSource": { + "type": "direct", + "query": "phValues:TRD/PULSEHEIGHT" + } + }, + "Tracking": { + "active": "true", + "className": "o2::quality_control_modules::trd::TrackingTask", + "moduleName": "QcTRD", + "detectorName": "TRD", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query": "trackITSTPCTRD:TRD/MATCH_ITSTPC;trigITSTPCTRD:TRD/TRGREC_ITSTPC" + }, + "taskParameters": { + "detailedQC": "false", + "trackSources": "ITS-TPC-TRD" + } + } + }, + "checks": { + "TrackletCountCheck": { + "active": "true", + "className": "o2::quality_control_modules::trd::TrackletCountCheck", + "moduleName": "QcTRD", + "policy": "OnAny", + "detectorName": "TRD", + "dataSource": [{ + "type": "Task", + "name": "Tracklets", + "MOs": [ "trackletsperevent" ] + }], + "checkParameters":{ + "LowerthresholdPerTrigger":"500.0", + "UpperthresholdPerTrigger":"520.0", + "StatThresholdPerTrigger": "100" + } + } + }, + "aggregators": { + "TRDQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QualityControl", + "policy": "OnAll", + "detectorName": "TRD", + "dataSource": [ + { + "type": "Check", + "name": "TrackletPerTriggerCheck" + } + ] + } + }, + "postprocessing": { + "TRDTrending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TRDTrending", + "moduleName": "QualityControl", + "detectorName": "TRD", + "dataSources": [ + { + "type": "repository", + "path": "TRD/MO/Tracklets/", + "names": [ "trackletspertimeframe","trackletsperevent" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcTRD" + } + ], + "plots": [ + { + "names": ["mean_distribution_trackletpertimeframe","mean_distribution_trackletperevent"], + "title": ["mean distribution of tracklet per timeframe","mean distribution of tracklet per event"], + "varexp": ["trackletspertimeframe.mean","trackletsperevent.mean"], + "selection": "", + "option": "" + }, + { + "names": ["mean_trend_trackletspertimeframe", "mean_trend_trackletperevent"], + "title": ["Mean trend of the Tracklet per time frame","Mean trend of tracklet per event"], + "varexp": ["trackletspertimeframe.mean:time","trackletsperevent.mean:time"], + "selection": "", + "option": "PL" + } + ], + "initTrigger": [ + "newobject:ccdb:TRD/MO/Tracklets/trackletsperevent" + ], + "updateTrigger": [ + "newobject:ccdb:TRD/MO/Tracklets/trackletsperevent","20 seconds" + ], + "stopTrigger": [ + "usercontrol" + ] + }, + "Quality": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QualityControl", + "detectorName": "TRD", + "qualityGroups": [ + { + "name" : "global", + "title" : "GLOBAL TRD QUALITY", + "path": "TRD/QO", + "ignoreQualitiesDetails" : ["Null", "Good", "Medium", "Bad"], + "inputObjects": [ + { + "name" : "TRDQuality/TRDQuality", + "title" : "TRD Quality", + "messageBad" : "Inform TRD on-call immediately", + "messageMedium": "Add bookkeeping entry", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty!!!" + } + ] + }, + { + "name" : "TrackletQuality", + "title" : "TRD Tracklet Quality", + "path": "TRD/QO", + "ignoreQualitiesDetails" : [], + "inputObjects": [ + { + "name" : "TrackletPerTriggerCheck", + "title" : "Check on Mean of Tracklet per Event", + "messageBad" : "Inform TRD on-call immediately", + "messageMedium": "Add bookkeeping entry", + "messageGood": "TrackletPerTriggerCheck check is OK", + "messageNull": "Some histograms are empty!!!" + } + ] + } + ], + "initTrigger": [ + "newobject:ccdb:TRD/QO/TrackletPerTriggerCheck" + ], + "updateTrigger": [ + "newobject:ccdb:TRD/QO/TrackletPerTriggerCheck","20 seconds" + ], + "stopTrigger": [ + "userorcontrol","EOR" + ] + } + }, + "dataSamplingPolicies": [] + } +} diff --git a/Modules/TRD/TRDpostprocessing.json b/Modules/TRD/TRDpostprocessing.json new file mode 100644 index 0000000000..5617f2ab31 --- /dev/null +++ b/Modules/TRD/TRDpostprocessing.json @@ -0,0 +1,91 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE", + "passName": "", + "periodName" : "" + + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + + "postprocessing": { + "TRDTrending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TRDTrending", + "moduleName": "QualityControl", + "detectorName": "TRD", + "dataSources": [ + { + "type": "repository", + "path": "TRD/MO/TrackletsTask/", + "names": [ "trackletspertimeframe", + "trackletsperevent" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcTRD" + } + ], + "plots": [ + + { + "names": ["mean_distribution_trackletpertimeframe","mean_distribution_trackletperevent"], + "title": ["mean distribution of tracklet per timeframe","mean distribution of tracklet per event"], + "varexp": ["trackletspertimeframe.mean","trackletsperevent.mean"], + "selection": "", + "option": "" + }, + { + "names": ["mean_trend_trackletspertimeframe", "mean_trend_trackletperevent"], + "title": ["Mean trend of the Tracklet per time frame","Mean trend of tracklet per event"], + "varexp": ["trackletspertimeframe.mean:time","trackletsperevent.mean:time"], + "selection": "", + "option": "PL" + } + ], + "initTrigger": [ + "usercontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "usercontrol" + ] + }, + "PulseHeightPerChamber2D": { + "active": "true", + "className": "o2::quality_control::postprocessing::PulseHeightPostProcessing", + "moduleName": "QualityControl", + "detectorName": "TRD", + "path": "qc/TRD/MO/DigitTask/PulseHeight/mPulseHeightperchamber", + "timestamps":"-1", + "initTrigger": [ + "usercontrol" + ], + "updateTrigger": [ + "once" + ], + "stopTrigger": [ + "usercontrol" + ] + } + } + } +} diff --git a/Modules/TRD/TrackletPerTriggerCheck.json b/Modules/TRD/TrackletPerTriggerCheck.json new file mode 100644 index 0000000000..123ffecfb3 --- /dev/null +++ b/Modules/TRD/TrackletPerTriggerCheck.json @@ -0,0 +1,186 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable", + "maxObjectSize": "2097152", "": "[Bytes, default=2MB] Maximum size allowed, larger objects are rejected." + }, + "Activity": { + "number": "42", + "type": "NONE", + "periodName": "", "": "Period name - e.g. LHC22c, LHC22c1b_test", + "passName": "", "": "Pass type - e.g. spass, cpass1", + "provenance": "qc", "": "Provenance - qc or qc_mc depending whether it is normal data or monte carlo data" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", "": "Set to true to discard debug and trace messages (default: false)", + "filterDiscardLevel": "11", "": "Message at this level or above are discarded (default: 21 - Trace)", + "filterDiscardFile": "/tmp/_ID_.txt", "": ["If set, the messages discarded because of filterDiscardLevel", + "will go to this file (default: ); The keyword _ID_ is replaced by the device id. Discarded Debug ", + "messages won't go there."] + }, + "bookkeeping": { + "url": "" + }, + "postprocessing": { + "periodSeconds": "60" + } + }, + "tasks": { + "DTrackletsTask": { + "active": "true", + "className": "o2::quality_control_modules::trd::TrackletsTask", + "moduleName": "QcTRD", + "detectorName": "TRD", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "direct", + "query": "tracklets:TRD/TRACKLETS;digits:TRD/DIGITS;triggers:TRD/TRKTRGRD" ,"":"digits:TRD/DIGITS;tracklets:TRD/TRACKLETS;triggers:TRD/TRKTRGRD;rawstats:TRD/RAWSTATS" + }, + "taskParameters": {}, + "location": "remote", + "saveObjectsToFile": "TrackletPerTriggerCheck.root" + } + }, + "checks": { + "DTrackletPerTriggerCheck": { + "active": "true", + "className": "o2::quality_control_modules::trd::TrackletPerTriggerCheck", + "moduleName": "QcTRD", + "policy": "OnAny", + "detectorName": "TRD", + "dataSource": [{ + "type": "Task", + "name": "DTrackletsTask", + "MOs": [ "trackletsperevent" ] + }], + "checkParameters":{ + "Lowerthreshold":"500.0", + "Upperthreshold":"520.0", + "StatThreshold": "1" + } + } + }, + "aggregators": { + "TRDQuality": { + "active": "true", + "className": "o2::quality_control_modules::common::WorstOfAllAggregator", + "moduleName": "QualityControl", + "policy": "OnAll", + "detectorName": "TRD", + "dataSource": [ + { + "type": "Check", + "name": "DTrackletPerTriggerCheck" + } + ] + } + }, + "postprocessing": { + "TRDTrending": { + "active": "true", + "className": "o2::quality_control::postprocessing::TRDTrending", + "moduleName": "QualityControl", + "detectorName": "TRD", + "dataSources": [ + { + "type": "repository", + "path": "TRD/MO/DTrackletsTask/", + "names": [ "trackletspertimeframe","trackletsperevent" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcTRD" + } + ], + "plots": [ + { + "names": ["mean_distribution_trackletpertimeframe","mean_distribution_trackletperevent"], + "title": ["mean distribution of tracklet per timeframe","mean distribution of tracklet per event"], + "varexp": ["trackletspertimeframe.mean","trackletsperevent.mean"], + "selection": "", + "option": "" + }, + { + "names": ["mean_trend_trackletspertimeframe", "mean_trend_trackletperevent"], + "title": ["Mean trend of the Tracklet per time frame","Mean trend of tracklet per event"], + "varexp": ["trackletspertimeframe.mean:time","trackletsperevent.mean:time"], + "selection": "", + "option": "PL" + } + ], + "initTrigger": [ + "usercontrol" + ], + "updateTrigger": [ + "10 seconds" + ], + "stopTrigger": [ + "usercontrol" + ] + }, + "Quality": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QualityControl", + "detectorName": "TRD", + "qualityGroups": [ + { + "name" : "global", + "title" : "GLOBAL TRD QUALITY", + "path": "TRD/QO", + "ignoreQualitiesDetails" : ["Null", "Good", "Medium", "Bad"], + "inputObjects": [ + { + "name" : "TRDQuality/TRDQuality", + "title" : "TRD Quality", + "messageBad" : "Inform TRD on-call immediately", + "messageMedium": "Add bookkeeping entry", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty!!!" + } + ] + }, + { + "name" : "TrackletQuality", + "title" : "TRD Tracklet Quality", + "path": "TRD/QO", + "ignoreQualitiesDetails" : [], + "inputObjects": [ + { + "name" : "DTrackletPerTriggerCheck", + "title" : "Check on Mean of Tracklet per Event", + "messageBad" : "Inform TRD on-call immediately", + "messageMedium": "Add bookkeeping entry", + "messageGood": "TrackletPerTriggerCheck check is OK", + "messageNull": "Some histograms are empty!!!" + } + ] + } + ], + "initTrigger": [ + "userorcontrol","60sec","SOR" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol","EOR" + ] + } + } + } + } + + \ No newline at end of file diff --git a/Modules/TRD/calibParamTRD.json b/Modules/TRD/calibParamTRD.json new file mode 100644 index 0000000000..f2616ef896 --- /dev/null +++ b/Modules/TRD/calibParamTRD.json @@ -0,0 +1,79 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080" + }, + "Activity": { + "number": "526486" + }, + "conditionDB": { + "url": "alice-ccdb.cern.ch" + }, + "postprocessing": { + "matchAnyRunNumber": "true" + } + }, + "postprocessing": { + "CalibTrendTRD": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TRD", + "producePlotsOnUpdate": "false", + "dataSources": [ + { + "type": "condition", + "path": "TRD/Calib", + "names": [ + "CalVdriftExB" + ], + "reductorName": "o2::quality_control_modules::trd::CalibReductorTRD", + "moduleName": "QcTRD" + } + ], + "plots": [ + { + "name": "vdrift_trend", + "title": "vDrift mean trend", + "varexp": "CalVdriftExB.vdriftmean:time", + "selection": "", + "option": "*L", + "graphYRange": "0:3.5", + "graphXRange": "", + "graphAxisLabel": "vdrift:time", + "graphErrors": "0:CalVdriftExB.vdrifterr" + }, + { + "name": "vdrift_trend2D", + "title": "vDrift trend", + "varexp": "CalVdriftExB.vdrift:time", + "selection": "", + "option": "colz logz" + }, + { + "name": "exb_trend", + "title": "ExB mean trend", + "varexp": "CalVdriftExB.exbmean:time", + "selection": "", + "option": "*L", + "graphYRange": "-2.0:2.0", + "graphXRange": "", + "graphAxisLabel": "ExB:time", + "graphErrors": "0:CalVdriftExB.exberr" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:ccdb:TRD/Calib/CalVdriftExB" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} diff --git a/Modules/TRD/include/TRD/CalibReductorTRD.h b/Modules/TRD/include/TRD/CalibReductorTRD.h new file mode 100644 index 0000000000..a8c13c3b05 --- /dev/null +++ b/Modules/TRD/include/TRD/CalibReductorTRD.h @@ -0,0 +1,51 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibReductorTRD.h +/// \author Salman Malik +/// + +#ifndef QUALITYCONTROL_CALIBREDUCTORTRD_H +#define QUALITYCONTROL_CALIBREDUCTORTRD_H + +#include "QualityControl/ReductorConditionAny.h" + +#include + +namespace o2::quality_control_modules::trd +{ + +/// \brief A Reductor which obtains the calibration parameters of TRD from ccdb +/// +class CalibReductorTRD : public quality_control::postprocessing::ReductorConditionAny +{ + public: + CalibReductorTRD() = default; + ~CalibReductorTRD() = default; + + void* getBranchAddress() override; + const char* getBranchLeafList() override; + bool update(ConditionRetriever& retriever) override; + + private: + struct { + float vdrift[o2::trd::constants::MAXCHAMBER]; + float vdriftmean; + float vdrifterr; + float exbmean; + float exberr; + } mStats; +}; + +} // namespace o2::quality_control_modules::trd + +#endif // QUALITYCONTROL_CALIBREDUCTORTRD_H diff --git a/Modules/TRD/include/TRD/DigitsTask.h b/Modules/TRD/include/TRD/DigitsTask.h new file mode 100644 index 0000000000..e1ad5565d0 --- /dev/null +++ b/Modules/TRD/include/TRD/DigitsTask.h @@ -0,0 +1,92 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file DigitsTask.h +/// + +#ifndef QC_MODULE_TRD_DIGITSTASK_H +#define QC_MODULE_TRD_DIGITSTASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include "DataFormatsTRD/NoiseCalibration.h" +#include "TRDQC/StatusHelper.h" + +class TH1F; +class TH2F; +class TProfile; +class TProfile2D; +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::trd +{ + +/// \brief Example Quality Control DPL Task +/// \author My Name +class DigitsTask final : public TaskInterface +{ + public: + DigitsTask() = default; + ~DigitsTask() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) override; + void reset() override; + void buildHistograms(); + void drawLinesOnPulseHeight(TH1F* h); + void buildChamberIgnoreBP(); + + private: + // user settings + unsigned int mPulseHeightThreshold; + bool mDoClusterize{ false }; + std::pair mPulseHeightPeakRegion; + std::string mChambersToIgnore; + std::bitset mChambersToIgnoreBP; + int mClsCutoff = 1000; + int mAdcBaseline = 10; + + // histograms + std::shared_ptr mDigitsPerEvent; + std::shared_ptr mDigitsSizevsTrackletSize; + std::shared_ptr mDigitHCID; + std::shared_ptr mADCvalue; + + // histograms for clusterizer are not published by default + std::shared_ptr mNCls; + std::shared_ptr mClsTb; + std::shared_ptr mClsAmp; + std::shared_ptr mClsChargeTb; + std::shared_ptr mClsNTb; + + std::array, o2::trd::constants::NSECTOR> mHCMCM; + + std::shared_ptr mPulseHeight = nullptr; + std::shared_ptr mTotalPulseHeight2D = nullptr; + std::array, o2::trd::constants::NSECTOR> mPulseHeight2DperSM; + std::shared_ptr mPulseHeightpro = nullptr; + std::shared_ptr mPulseHeightperchamber = nullptr; + std::array, o2::trd::constants::NLAYER> mLayers; + + // CCDB objects + const o2::trd::NoiseStatusMCM* mNoiseMap = nullptr; +}; + +} // namespace o2::quality_control_modules::trd + +#endif // QC_MODULE_TRD_DIGITSTASK_H diff --git a/Modules/TRD/include/TRD/LinkDef.h b/Modules/TRD/include/TRD/LinkDef.h new file mode 100644 index 0000000000..5e697aba9a --- /dev/null +++ b/Modules/TRD/include/TRD/LinkDef.h @@ -0,0 +1,21 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::trd::DigitsTask + ; + +#pragma link C++ class o2::quality_control_modules::trd::RawData + ; +#pragma link C++ class o2::quality_control_modules::trd::PulseHeightCheck + ; +#pragma link C++ class o2::quality_control_modules::trd::TrackletsTask + ; +#pragma link C++ class o2::quality_control_modules::trd::TRDHelpers + ; +#pragma link C++ class o2::quality_control::postprocessing::TRDTrending + ; +#pragma link C++ class o2::quality_control::postprocessing::PulseHeightPostProcessing + ; +#pragma link C++ class o2::quality_control_modules::trd::PulseHeightTrackMatch + ; +#pragma link C++ class o2::quality_control_modules::trd::TrackingTask + ; +#pragma link C++ class o2::quality_control_modules::trd::PulsePositionCheck + ; +#pragma link C++ class o2::quality_control_modules::trd::TrackletCountCheck + ; +#pragma link C++ class o2::quality_control_modules::trd::RawDataCheckStats + ; +#pragma link C++ class o2::quality_control_modules::trd::RawDataCheckSizes + ; +#pragma link C++ class o2::quality_control_modules::trd::CalibReductorTRD + ; +#endif diff --git a/Modules/TRD/include/TRD/PulseHeightCheck.h b/Modules/TRD/include/TRD/PulseHeightCheck.h new file mode 100644 index 0000000000..88612da0c4 --- /dev/null +++ b/Modules/TRD/include/TRD/PulseHeightCheck.h @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PulseHeightCheck.h +/// \author My Name +/// + +#ifndef QC_MODULE_TRD_TRDPULSEHEIGHTCHECK_H +#define QC_MODULE_TRD_TRDPULSEHEIGHTCHECK_H + +#include "QualityControl/CheckInterface.h" +#include "DataFormatsTRD/Constants.h" + +namespace o2::quality_control_modules::trd +{ + +/// \brief Example QC Check +/// \author My Name +class PulseHeightCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + PulseHeightCheck() = default; + /// Destructor + ~PulseHeightCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + std::pair mDriftRegion; + std::pair mPulseHeightPeakRegion; + unsigned int mPulseHeightMinSum; + float mPulseHeightRatio; + ClassDefOverride(PulseHeightCheck, 1); +}; + +} // namespace o2::quality_control_modules::trd + +#endif // QC_MODULE_TRD_TRDPULSEHEIGHTCHECK_H diff --git a/Modules/TRD/include/TRD/PulseHeightPostProcessing.h b/Modules/TRD/include/TRD/PulseHeightPostProcessing.h new file mode 100644 index 0000000000..643ae19165 --- /dev/null +++ b/Modules/TRD/include/TRD/PulseHeightPostProcessing.h @@ -0,0 +1,63 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TRDTrending.h +/// \author based on Piotr Konopka work +/// + +#ifndef QUALITYCONTROL_PULSEHEIGHTPOSTPROCESSING_H +#define QUALITYCONTROL_PULSEHEIGHTPOSTPROCESSING_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "CCDB/CcdbApi.h" +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +class PulseHeightPostProcessing : public PostProcessingInterface +{ + public: + PulseHeightPostProcessing() = default; + ~PulseHeightPostProcessing() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + void PlotPulseHeightPerChamber(); + + o2::ccdb::CcdbApi mCdbph; + long mTimestamps; + std::string mHost; + std::string mPath; + std::array, 18> cc; + TProfile* h2[540]; + UInt_t mTime; + std::vector runlist; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRDTRENDING_H diff --git a/Modules/TRD/include/TRD/PulseHeightTrackMatch.h b/Modules/TRD/include/TRD/PulseHeightTrackMatch.h new file mode 100644 index 0000000000..03a1725efb --- /dev/null +++ b/Modules/TRD/include/TRD/PulseHeightTrackMatch.h @@ -0,0 +1,70 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PulseHeightTrackMatch.h +/// \author Vikash Sumberia +/// + +#ifndef QC_MODULE_TRD_TRDPULSEHEIGHTTRACKMATCH_H +#define QC_MODULE_TRD_TRDPULSEHEIGHTTRACKMATCH_H + +#include "QualityControl/TaskInterface.h" +#include +#include "DataFormatsTRD/NoiseCalibration.h" +#include "DataFormatsTRD/Digit.h" +#include "DataFormatsTRD/Constants.h" +#include "TRDQC/StatusHelper.h" + +class TH1F; +class TH2F; +class TH1D; +class TH2D; +class TLine; +class TProfile; +class TProfile2D; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::trd +{ + +class PulseHeightTrackMatch final : public TaskInterface +{ + public: + /// \brief Constructor + PulseHeightTrackMatch() = default; + /// Destructor + ~PulseHeightTrackMatch() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + void buildHistograms(); + void drawLinesOnPulseHeight(TProfile* h); + + private: + // json configurable parameters + std::pair mDriftRegion; + std::pair mPulseHeightPeakRegion; + std::bitset<4> mTrackType = 0xf; // bitset to select one or a combination of track types 0: ITSTPCTRD, 1: TPCTRD, 2: TRACKLET, 3: OTHERS. Default is 0xf: all tracks + std::shared_ptr mPulseHeightpro = nullptr; + std::shared_ptr mPulseHeightperchamber = nullptr; +}; + +} // namespace o2::quality_control_modules::trd + +#endif // QC_MODULE_TRD_TRDPULSEHEIGHTTRACKMATCH_H diff --git a/Modules/TRD/include/TRD/PulsePositionCheck.h b/Modules/TRD/include/TRD/PulsePositionCheck.h new file mode 100644 index 0000000000..bd3cbd84b6 --- /dev/null +++ b/Modules/TRD/include/TRD/PulsePositionCheck.h @@ -0,0 +1,53 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PulsePositionCheck.h +/// \author Deependra (deependra.sharma@cern.ch) +/// + +#ifndef QC_MODULE_TRD_TRDPULSEPOSITIONCHECK_H +#define QC_MODULE_TRD_TRDPULSEPOSITIONCHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::trd +{ + +/// \brief Example QC Check +/// \author My Name +class PulsePositionCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + PulsePositionCheck() = default; + /// Destructor + ~PulsePositionCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + std::pair mPulseHeightPeakRegion; + double chi2byNDF_threshold; + double FitParam0; + double FitParam1; + double FitParam2; + double FitParam3; + double FunctionRange[2]; + double FitRange[2]; + + ClassDefOverride(PulsePositionCheck, 2); +}; + +} // namespace o2::quality_control_modules::trd + +#endif // QC_MODULE_TRD_TRDPULSEPOSITIONCHECK_H diff --git a/Modules/TRD/include/TRD/RawData.h b/Modules/TRD/include/TRD/RawData.h new file mode 100644 index 0000000000..485778e915 --- /dev/null +++ b/Modules/TRD/include/TRD/RawData.h @@ -0,0 +1,69 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawData.h +/// \author Sean Murray +/// + +#ifndef QC_MODULE_TRD_TRDRAWDATA_H +#define QC_MODULE_TRD_TRDRAWDATA_H + +#include "QualityControl/TaskInterface.h" +#include "DataFormatsTRD/RawDataStats.h" +#include + +class TH1F; +class TH2F; +class TProfile; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::trd +{ + +class RawData final : public TaskInterface +{ + public: + RawData() = default; + ~RawData() override; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + void buildHistograms(); + void resetHistograms(); + + private: + TH1F* mStats = nullptr; + TH1F* mDataAcceptance = nullptr; + TH2F* mDataVolumePerHalfChamber = nullptr; + TH2F* mDataVolumePerSector = nullptr; + TH1F* mTimeFrameTime = nullptr; + TH1F* mTrackletParsingTime = nullptr; + TH1F* mDigitParsingTime = nullptr; + TH1F* mDataVersionsMajor = nullptr; + TH1F* mParsingErrors = nullptr; + TProfile* mDataVolumePerSectorProf = nullptr; + std::array mLinkErrors; + std::array mParsingErrors2d; + bool mCheckDigitHCHeaderVersion = false; +}; + +} // namespace o2::quality_control_modules::trd + +#endif // QC_MODULE_TRD_TRDRAWDATA_H diff --git a/Modules/TRD/include/TRD/RawDataCheck.h b/Modules/TRD/include/TRD/RawDataCheck.h new file mode 100644 index 0000000000..ef5f7054ec --- /dev/null +++ b/Modules/TRD/include/TRD/RawDataCheck.h @@ -0,0 +1,84 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawDataCheck.h +/// \author Ole Schmidt +/// + +#ifndef QC_TRD_RAWDATA_CHECK_H +#define QC_TRD_RAWDATA_CHECK_H + +// Quality Control +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::trd +{ + +/// \brief TRD raw data checks +/// + +class RawDataCheckStats : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + RawDataCheckStats() = default; + /// Destructor + ~RawDataCheckStats() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void reset() override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + private: + std::shared_ptr mActivity; + float mCalTriggerRate; + float mReadoutRate; + float mMaxReadoutRate; + float mMinCalTriggerRate; + int mNHBFperTF; + + ClassDefOverride(RawDataCheckStats, 1); +}; + +class RawDataCheckSizes : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + RawDataCheckSizes() = default; + /// Destructor + ~RawDataCheckSizes() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void reset() override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + private: + std::shared_ptr mActivity; + float mMeanDataSize; + float mStdDevDataSize; + float mWarningThreshold; + float mErrorThreshold; + + ClassDefOverride(RawDataCheckSizes, 1); +}; + +} // namespace o2::quality_control_modules::trd + +#endif // QC_TRD_RAWDATA_CHECK_H diff --git a/Modules/TRD/include/TRD/TRDHelpers.h b/Modules/TRD/include/TRD/TRDHelpers.h new file mode 100644 index 0000000000..1ae8af7861 --- /dev/null +++ b/Modules/TRD/include/TRD/TRDHelpers.h @@ -0,0 +1,49 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TRDHelpers.h +/// + +#ifndef QC_MODULE_TRD_TRDHELPER_H +#define QC_MODULE_TRD_TRDHELPER_H + +#include "QualityControl/TaskInterface.h" +#include "TRDQC/StatusHelper.h" + +class TH2F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::trd +{ + +class TRDHelpers +{ + public: + TRDHelpers() = default; + ~TRDHelpers() = default; + + static void addChamberGridToHistogram(std::shared_ptr histogram, int unitsPerSection); + static void drawChamberStatusOnHistograms(const std::array* ptrChamber, std::shared_ptr chamberMap, std::array, o2::trd::constants::NLAYER> ptrLayersArray, int unitsPerSection); + static void drawHalfChamberMask(int halfChamberStatus, std::pair xCoords, std::pair yCoords, std::shared_ptr histogram); + static bool isHalfChamberMasked(int halfChamberId, const std::array* ptrChamber); + + private: + // Chamber status values definitions, used for masking + static const int mConfiguredChamberStatus = 3; + static const int mEmptyChamberStatus = 0; + static const int mErrorChamberStatus = 99; +}; + +} // namespace o2::quality_control_modules::trd + +#endif diff --git a/Modules/TRD/include/TRD/TRDTrending.h b/Modules/TRD/include/TRD/TRDTrending.h new file mode 100644 index 0000000000..252ef085b9 --- /dev/null +++ b/Modules/TRD/include/TRD/TRDTrending.h @@ -0,0 +1,67 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TRDTrending.h +/// \author based on Piotr Konopka work +/// + +#ifndef QUALITYCONTROL_TRDTRENDING_H +#define QUALITYCONTROL_TRDTRENDING_H + +#include "QualityControl/PostProcessingInterface.h" +#include "QualityControl/Reductor.h" +#include "TRD/TrendingTaskConfigTRD.h" + +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +namespace o2::quality_control::postprocessing +{ + +class TRDTrending : public PostProcessingInterface +{ + public: + TRDTrending() = default; + ~TRDTrending() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + struct { + Long64_t runNumber = 0; + + } mMetaData; + + void trendValues(const Trigger& t, repository::DatabaseInterface& qcdb); + void generatePlots(repository::DatabaseInterface& qcdb); + + TrendingTaskConfigTRD mConfig; + Int_t ntreeentries = 0; + UInt_t mTime; + std::vector runlist; + std::unique_ptr mTrend; + std::map mPlots; + std::unordered_map> mReductors; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRDTRENDING_H diff --git a/Modules/TRD/include/TRD/TrackingTask.h b/Modules/TRD/include/TRD/TrackingTask.h new file mode 100644 index 0000000000..a92724cfeb --- /dev/null +++ b/Modules/TRD/include/TRD/TrackingTask.h @@ -0,0 +1,105 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrackingTask.h +/// \author Salman Malik +/// + +#ifndef QC_MODULE_TRD_TRDTRACKINGTASK_H +#define QC_MODULE_TRD_TRDTRACKINGTASK_H + +// ROOT includes +#include "TH1D.h" +#include "TH2D.h" +#include "TProfile2D.h" +#include +#include +// O2 includes +#include "DataFormatsTRD/TrackTRD.h" +#include "DataFormatsTRD/TrackTriggerRecord.h" +#include "DataFormatsTRD/Constants.h" +#include "ReconstructionDataFormats/GlobalTrackID.h" +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include +// QC includes +#include "QualityControl/TaskInterface.h" + +class TH1D; +class TH2D; +class TProfile2D; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::trd +{ + +class TrackingTask final : public TaskInterface +{ + public: + TrackingTask() = default; + ~TrackingTask() = default; + + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + + private: + void buildHistograms(); + void axisConfig(TH1* h, const char* xTitle, const char* yTitle, const char* zTitle, bool stat, float xOffset = 1., float yOffset = 1.); + void publishObject(TObject* aObject, const char* drawOpt = "", const char* dispayOpt = ""); + void drawLayers(TH2* hist); + void setEfficiency(TEfficiency* eff, TH1* hNum, TH1* hDen); + bool mDetailedTrackQC = false; // flag whether or not to expect o2::trd::TrackQC input + std::shared_ptr mDataRequest; // specify which input to use + o2::globaltracking::RecoContainer mRecoCont; // helper to acess input from reconstruction + o2::dataformats::GlobalTrackID::mask_t mSrcSelected; // the selected track sources from allowed ITS-TPC-TRD and TPC-TRD + // the input data spans + gsl::span mITSTPCTRDTracks; + gsl::span mTrigITSTPCTRD; + gsl::span mTPCTRDTracks; + gsl::span mTrigTPCTRD; + gsl::span mTPCITSTracks; + // + float mPtMin = 0.0; // minimum pT of tracks + TString mChargeLabel[3] = { "Pos", "Neg", "All" }; // charge of tracks + TH1D* mNtracks = nullptr; // number of ITS-TPC-TRD tracks per event + TH1D* mNtracklets = nullptr; // number of TRD tracklets per track + TH2D* mTrackEtaPhi = nullptr; // eta-phi distribution of ITS-TPC-TRD tracks + TH1D* mTrackEta = nullptr; // eta of ITS-TPC-TRD tracks + TH1D* mTrackPhi = nullptr; // phi of ITS-TPC-TRD tracks + TH1D* mTrackPt = nullptr; // pt of ITS-TPC-TRD tracks + TH1D* mTrackPtTPCITS = nullptr; // pt of ITS-TPC tracks + TH1D* mTrackEtaTPCITS = nullptr; // eta of ITS-TPC tracks + TH1D* mTrackPhiTPCITS = nullptr; // phi of ITS-TPC tracks + TH1D* mTrackChi2 = nullptr; // reduced chi2 of ITS-TPC-TRD tracks + TH1D* mDeltaY = nullptr; // residual in y direction (trackposiny - trackletposiny) + TH1D* mDeltaZ = nullptr; // residual in z direction (trackposinz - trackletposinz) + TH2D* mDeltaYDet = nullptr; // residual in y direction vs. 540 detectors + TH2D* mDeltaZDet = nullptr; // residual in z direction vs. 540 detectors + TH2D* mDeltaYvsSphi = nullptr; // residual in y direction vs. sin(phi) track seed + TH2D* mTrackletDef = nullptr; // tracklet slope vs. tracklet position + std::array mTrackletsEtaPhi; // eta-phi distribution of average number of tracklets per track + std::array, o2::trd::constants::NLAYER> mTracksEtaPhiPerLayer; // eta vs. phi of tracks per layer + std::array mDeltaYinEtaPerLayer; // residual in y direction vs. eta per layer + std::array mDeltaYinPhiPerLayer; // residual in y direction vs. phi per layer + TEfficiency* mEfficiencyPt = nullptr; // efficiency vs. pt + TEfficiency* mEfficiencyEta = nullptr; // efficiency vs. eta + TEfficiency* mEfficiencyPhi = nullptr; // efficiency vs. phi +}; + +} // namespace o2::quality_control_modules::trd + +#endif // QC_MODULE_TRD_TRDTRACKINGTASK_H diff --git a/Modules/TRD/include/TRD/TrackletCountCheck.h b/Modules/TRD/include/TRD/TrackletCountCheck.h new file mode 100644 index 0000000000..2685d7962b --- /dev/null +++ b/Modules/TRD/include/TRD/TrackletCountCheck.h @@ -0,0 +1,75 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrackletCountCheck.h +/// \author Deependra Sharma @IITB +/// + +#ifndef QC_MODULE_TRD_TRDTRACKLETCOUNTCHECK_H +#define QC_MODULE_TRD_TRDTRACKLETCOUNTCHECK_H + +#include +#include +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::trd +{ +class TrackletCountCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + TrackletCountCheck() = default; + /// Destructor + ~TrackletCountCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void reset() override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + private: + float mThresholdMeanLowPerTrigger, mThresholdMeanHighPerTrigger, mThresholdMeanLowPerTimeFrame, mThresholdMeanHighPerTimeFrame; + int mStatThresholdPerTrigger; + + float mTrackletPerTimeFrameThreshold, mRatioThreshold, mZeroBinRatioThreshold; + + Quality mResultPertrigger; + Quality mResultPerTimeFrame; + Quality mFinalResult; + + std::shared_ptr mTrackletPerTriggerMessage; + std::shared_ptr mTrackletPerTimeFrameMessage; + + /// @brief This function do comparsion between number of TimeFrame with lower and higher tracklets + /// @param trackletPerTimeFrameThreshold The deciding boundary between Timeframes of lower and higher tracklets + /// @param acceptedRatio accepted ratio (Timeframe with lower tracklets/ Timeframe with higher tracklets) + /// @param zeroTrackletTfRation accepted ratio (Timeframe without tracklets/ Timeframe with Tracklets) + /// @param hist TrackletPerTimeFrame distribution + /// @return true if ratio (Timeframe with lower tracklets/ Timeframe with higher tracklets) is below than accepted ratio + bool isTrackletDistributionAccepeted(float trackletPerTimeFrameThreshold, float acceptedRatio, float zeroTrackletTfRatio, TH1F* hist); + + /// @brief This function checks if ration of TF without tracklets to TF with tracklets is higher than a threshold + /// @param zeroTrackletTfRation accepted ratio (Timeframe without tracklets/ Timeframe with Tracklets) + /// @param hist TrackletPerTimeFrame distribution + /// @return true if ratio (TF WO tracklets / TF W tracklets) is lower than a threshold + bool isTimeframeRatioWOTrackletAccepted(float zeroTrackletTfRatio, TH1F* hist); + + std::shared_ptr mActivity; + ClassDefOverride(TrackletCountCheck, 2); +}; + +} // namespace o2::quality_control_modules::trd + +#endif // QC_MODULE_TRD_TRDTRACKLETCOUNTCHECK_H \ No newline at end of file diff --git a/Modules/TRD/include/TRD/TrackletsTask.h b/Modules/TRD/include/TRD/TrackletsTask.h new file mode 100644 index 0000000000..35e200eb5f --- /dev/null +++ b/Modules/TRD/include/TRD/TrackletsTask.h @@ -0,0 +1,72 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrackletsTask.h +/// + +#ifndef QC_MODULE_TRD_TRDTRACKLETSTASK_H +#define QC_MODULE_TRD_TRDTRACKLETSTASK_H + +#include "QualityControl/TaskInterface.h" +#include "QualityControl/DatabaseInterface.h" +#include "DataFormatsTRD/NoiseCalibration.h" +#include "TRDQC/StatusHelper.h" + +class TH1F; +class TH2F; +class TCanvas; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::trd +{ + +class TrackletsTask final : public TaskInterface +{ + public: + TrackletsTask() = default; + ~TrackletsTask() override = default; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) override; + void reset() override; + void buildHistograms(); + + private: + // settings + bool mRemoveNoise{ false }; + // histograms + std::array, 3> mTrackletQ; + std::shared_ptr mTrackletSlope = nullptr; + std::shared_ptr mTrackletHCID = nullptr; + std::shared_ptr mTrackletPosition = nullptr; + std::shared_ptr mTrackletsPerEvent = nullptr; + std::shared_ptr mTrackletsPerEventPP = nullptr; + std::shared_ptr mTrackletsPerEventPbPb = nullptr; + std::shared_ptr mTrackletsPerHC2D = nullptr; + std::shared_ptr mTrackletsPerTimeFrame = nullptr; + std::shared_ptr mTriggersPerTimeFrame = nullptr; + std::array, o2::trd::constants::NLAYER> mLayers; + + // data to pull from CCDB + const o2::trd::NoiseStatusMCM* mNoiseMap = nullptr; +}; + +} // namespace o2::quality_control_modules::trd + +#endif // QC_MODULE_TRD_TRDTRACKLETSTASK_H diff --git a/Modules/TRD/include/TRD/TrendingTaskConfigTRD.h b/Modules/TRD/include/TRD/TrendingTaskConfigTRD.h new file mode 100644 index 0000000000..7d86105cd1 --- /dev/null +++ b/Modules/TRD/include/TRD/TrendingTaskConfigTRD.h @@ -0,0 +1,57 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskConfigTRD.h +/// \author based on Piotr Konopkan work +/// + +#ifndef QUALITYCONTROL_TRENDINGTASKCONFIGTRD_H +#define QUALITYCONTROL_TRENDINGTASKCONFIGTRD_H + +#include +#include +#include "QualityControl/PostProcessingConfig.h" + +namespace o2::quality_control::postprocessing +{ + +struct TrendingTaskConfigTRD : PostProcessingConfig { + TrendingTaskConfigTRD() = default; + TrendingTaskConfigTRD(std::string name, const boost::property_tree::ptree& config); + ~TrendingTaskConfigTRD() = default; + + struct Plot { + std::string name; + std::string title; + std::string varexp; + std::string selection; + std::string option; + std::string graphErrors; + }; + + struct DataSource { + std::string type; + std::string path; + std::string name; + std::string reductorName; + std::string moduleName; + }; + + bool producePlotsOnUpdate{}; + bool resumeTrend{}; + std::vector plots; + std::vector dataSources; +}; + +} // namespace o2::quality_control::postprocessing + +#endif // QUALITYCONTROL_TRENDINGTASKCONFIG_H diff --git a/Modules/TRD/src/CalibReductorTRD.cxx b/Modules/TRD/src/CalibReductorTRD.cxx new file mode 100644 index 0000000000..d75039114b --- /dev/null +++ b/Modules/TRD/src/CalibReductorTRD.cxx @@ -0,0 +1,59 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file CalibReductorTRD.cxx +/// \author Salman Malik +/// + +#include "TRD/CalibReductorTRD.h" + +#include + +namespace o2::quality_control_modules::trd +{ + +void* CalibReductorTRD::getBranchAddress() +{ + return &mStats; +} + +const char* CalibReductorTRD::getBranchLeafList() +{ + return Form("vdrift[%i]:vdriftmean/F:vdrifterr:exbmean/F:exberr", o2::trd::constants::MAXCHAMBER); +} + +bool CalibReductorTRD::update(ConditionRetriever& retriever) +{ + if (auto retvdrift = retriever.retrieve()) { + double sumVdrift = 0, sumSqVdrift = 0; + double sumExb = 0, sumSqExb = 0; + + for (int i = 0; i < o2::trd::constants::MAXCHAMBER; i++) { + mStats.vdrift[i] = retvdrift->getVdrift(i); + sumVdrift += retvdrift->getVdrift(i); + sumSqVdrift += retvdrift->getVdrift(i) * retvdrift->getVdrift(i); + sumExb += retvdrift->getExB(i); + sumSqExb += retvdrift->getExB(i) * retvdrift->getExB(i); + } + + mStats.vdriftmean = sumVdrift / o2::trd::constants::MAXCHAMBER; + mStats.vdrifterr = std::sqrt(sumSqVdrift / o2::trd::constants::MAXCHAMBER - mStats.vdriftmean * mStats.vdriftmean); + + mStats.exbmean = sumExb / o2::trd::constants::MAXCHAMBER; + mStats.exberr = std::sqrt(sumSqExb / o2::trd::constants::MAXCHAMBER - mStats.exbmean * mStats.exbmean); + + return true; + } + return false; +} + +} // namespace o2::quality_control_modules::trd diff --git a/Modules/TRD/src/DigitsTask.cxx b/Modules/TRD/src/DigitsTask.cxx new file mode 100644 index 0000000000..1cb02d553e --- /dev/null +++ b/Modules/TRD/src/DigitsTask.cxx @@ -0,0 +1,373 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TRD/DigitsTask.h" +#include "TRD/TRDHelpers.h" +#include "DataFormatsTRD/Constants.h" +#include "DataFormatsTRD/Digit.h" +#include "DataFormatsTRD/HelperMethods.h" +#include "DataFormatsTRD/TriggerRecord.h" +#include "QualityControl/ObjectsManager.h" +#include "QualityControl/TaskInterface.h" +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" +#include +#include +#include +#include +#include + +using namespace o2::quality_control_modules::common; +using namespace o2::trd::constants; +using Helper = o2::trd::HelperMethods; + +namespace o2::quality_control_modules::trd +{ + +void DigitsTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TRDDigitQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // this is how to get access to custom parameters defined in the config file at qc.tasks..taskParameters + mDoClusterize = getFromConfig(mCustomParameters, "doClusterize", false); + mPulseHeightPeakRegion.first = getFromConfig(mCustomParameters, "peakRegionStart", 0.f); + mPulseHeightPeakRegion.second = getFromConfig(mCustomParameters, "peakRegionEnd", 5.f); + mPulseHeightThreshold = getFromConfig(mCustomParameters, "phThreshold", 400u); + mChambersToIgnore = getFromConfig(mCustomParameters, "ignoreChambers", "16_3_0"); + mClsCutoff = getFromConfig(mCustomParameters, "clsCutoff", 1000); + mAdcBaseline = getFromConfig(mCustomParameters, "adcBaseline", 10); + + buildChamberIgnoreBP(); + buildHistograms(); +} + +void DigitsTask::buildHistograms() +{ + constexpr int nLogBins = 100; + float xBins[nLogBins + 1]; + float xBinLogMin = 0.f; + float xBinLogMax = 8.f; + float logBinWidth = (xBinLogMax - xBinLogMin) / nLogBins; + for (int iBin = 0; iBin <= nLogBins; ++iBin) { + xBins[iBin] = TMath::Power(10, xBinLogMin + iBin * logBinWidth); + } + mDigitsPerEvent.reset(new TH1F("digitsperevent", "Number of digits per Event", nLogBins, xBins)); + getObjectsManager()->startPublishing(mDigitsPerEvent.get()); + + mDigitHCID.reset(new TH1F("digithcid", "Digit distribution over Halfchambers", 1080, -0.5, 1079.5)); + getObjectsManager()->startPublishing(mDigitHCID.get()); + + mDigitsSizevsTrackletSize.reset(new TH1F("digitsvstracklets", "Digit count vs. Tracklet count; N_Digits / N_Tracklets;counts", 100, 0, 100)); + getObjectsManager()->startPublishing(mDigitsSizevsTrackletSize.get()); + + mADCvalue.reset(new TH1F("ADCvalue", "ADC values for all digits;ADC value;Counts", 1024, -0.5, 1023.5)); + getObjectsManager()->startPublishing(mADCvalue.get()); + + for (Int_t iSM = 0; iSM < NSECTOR; ++iSM) { + mHCMCM[iSM].reset(new TH2F(Form("DigitsPerMCM_%i", iSM), Form("Digits per MCM in sector %i;pad row; MCM column", iSM), 76, -0.5, 75.5, 8, -0.5, 7.5)); + getObjectsManager()->startPublishing(mHCMCM[iSM].get()); + getObjectsManager()->setDefaultDrawOptions(mHCMCM[iSM]->GetName(), "COLZ"); + } + + if (mDoClusterize) { + mClsNTb.reset(new TH1F("Cluster/ClsNTb", "ClsNTb", 30, -0.5, 29.5)); + mClsNTb->SetTitle("Clusters per time bin;Number of clusters;Timebin"); + getObjectsManager()->startPublishing(mClsNTb.get()); + mClsChargeTb.reset(new TH1F("Cluster/ClsChargeTb", "ClsChargeTb;", 30, -0.5, 29.5)); + getObjectsManager()->startPublishing(mClsChargeTb.get()); + mClsAmp.reset(new TH1F("Cluster/ClsAmp", "Amplitude of clusters;Amplitdu(ADC);Counts", 200, -0.5, 1999.5)); + getObjectsManager()->startPublishing(mClsAmp.get()); + mNCls.reset(new TH1F("Cluster/NCls", "Total number of clusters per sector", 18, -0.5, 17.5)); + mNCls->SetTitle("Total number of clusters per sector;Sector;Counts"); + getObjectsManager()->startPublishing(mNCls.get()); + mClsTb.reset(new TH2F("Cluster/ChargeTB", "Cluster charge;time bin;cluster charge", 30, -0.5, 29.5, 200, 0, 2000)); + getObjectsManager()->startPublishing(mClsTb.get()); + } + + mPulseHeight.reset(new TH1F("PulseHeight1D", "PH spectrum 1D;time bin;ADC sum", 30, -0.5, 29.5)); + drawLinesOnPulseHeight(mPulseHeight.get()); + getObjectsManager()->startPublishing(mPulseHeight.get()); + mPulseHeight.get()->GetYaxis()->SetTickSize(0.01); + + mTotalPulseHeight2D.reset(new TH2F("PulseHeight2D", "PH spectrum 2D;time bin;ADC sum", 30, 0., 30., 200, 0., 200.)); + getObjectsManager()->startPublishing(mTotalPulseHeight2D.get()); + getObjectsManager()->setDefaultDrawOptions(mTotalPulseHeight2D->GetName(), "COLZ"); + + mPulseHeightpro.reset(new TProfile("PulseHeightProfile", "PH spectrum for all chambers combined;time bin;ADC sum", 30, -0.5, 29.5)); + mPulseHeightpro.get()->Sumw2(); + getObjectsManager()->startPublishing(mPulseHeightpro.get()); + + mPulseHeightperchamber.reset(new TProfile2D("PulseHeightPerChamber", "PH spectrum for all chambers;time bin;chamber", 30, -0.5, 29.5, 540, -0.5, 539.5)); + mPulseHeightperchamber.get()->Sumw2(); + getObjectsManager()->startPublishing(mPulseHeightperchamber.get()); + getObjectsManager()->setDefaultDrawOptions(mPulseHeightperchamber.get()->GetName(), "colz"); + + for (int iSec = 0; iSec < NSECTOR; ++iSec) { + mPulseHeight2DperSM[iSec].reset(new TH1F(Form("PulseHeight_%i", iSec), Form("PH spectrum for sector %i;time bin;ADC sum count", iSec), 30, -0.5, 29.5)); + getObjectsManager()->startPublishing(mPulseHeight2DperSM[iSec].get()); + } + + // Build digits layers + int unitsPerSection = NCOLUMN; + for (int iLayer = 0; iLayer < NLAYER; ++iLayer) { + mLayers[iLayer].reset(new TH2F(Form("DigitsPerLayer_%i", iLayer), Form("Digit count per pad in layer %i;glb pad row;glb pad col", iLayer), + 76, -0.5, 75.5, unitsPerSection * NSECTOR, -0.5, unitsPerSection * NSECTOR - 0.5)); + mLayers[iLayer]->SetStats(0); + TRDHelpers::addChamberGridToHistogram(mLayers[iLayer], unitsPerSection); + getObjectsManager()->startPublishing(mLayers[iLayer].get()); + getObjectsManager()->setDefaultDrawOptions(mLayers[iLayer]->GetName(), "COLZ"); + getObjectsManager()->setDisplayHint(mLayers[iLayer].get(), "logz"); + } +} + +void DigitsTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + // Load CCDB objects (needs to be done only once) + if (!mNoiseMap) { + auto ptr = ctx.inputs().get("noiseMap"); + mNoiseMap = ptr.get(); + } + + auto ptr = ctx.inputs().get*>("fedChamberStatus"); + + // fill histograms + auto digits = ctx.inputs().get>("digits"); + auto triggerrecords = ctx.inputs().get>("triggers"); + for (auto& trigger : triggerrecords) { + auto nDigits = trigger.getNumberOfDigits(); + auto iDigitFirst = trigger.getFirstDigit(); + int iDigitLast = iDigitFirst + nDigits; + if (nDigits == 0) { + continue; + } + mDigitsPerEvent->Fill(nDigits); + if (trigger.getNumberOfTracklets() > 0) { + mDigitsSizevsTrackletSize->Fill(nDigits / trigger.getNumberOfTracklets()); + } + + for (int iDigit = iDigitFirst; iDigit < iDigitLast; ++iDigit) { + const auto& digit = digits[iDigit]; + if (digit.isSharedDigit()) { + continue; + } + + int detector = digit.getDetector(); + if (detector >= MAXCHAMBER) { + // stupid fix to avoid crashes below on corrupted digits recorded in 2022 + // observed e.g. in run 523308 + continue; + } + int sector = Helper::getSector(detector); + int layer = Helper::getLayer(detector); + int stack = Helper::getStack(detector); + int row = digit.getPadRow(); + int col = digit.getPadCol(); + int rowGlb = FIRSTROW[stack] + row; + int colGlb = col + sector * NCOLUMN; + mLayers[layer]->Fill(rowGlb, colGlb); + mHCMCM[sector]->Fill(rowGlb, digit.getMCMCol()); + mDigitHCID->Fill(digit.getHCId()); + for (auto adc : digit.getADC()) { + mADCvalue->Fill(adc); + } + if (iDigit == iDigitFirst || iDigit == iDigitLast - 1) { + // above histograms are filled for all digits, now we are looking for clusters + continue; + } + + const auto* digitLeft = &digits[iDigit + 1]; + const auto* digitRight = &digits[iDigit - 1]; + // due to shared digits the neighbouring element might still be in the same pad column + if (digitLeft->getPadCol() == col && iDigit < iDigitLast - 2) { + digitLeft = &(digits[iDigit + 2]); + } + if (digitRight->getPadCol() == col && iDigit > iDigitFirst + 2) { + digitRight = &(digits[iDigit - 2]); + } + if (!digitLeft->isNeighbour(digit) || !digitRight->isNeighbour(digit)) { + continue; + } + + if (mDoClusterize) { + for (int tb = 1; tb < TIMEBINS - 1; ++tb) { + auto adc = digit.getADC()[tb]; + if (adc > digitLeft->getADC()[tb] && adc > digitRight->getADC()[tb]) { + // local maximum + int valueLU = digitLeft->getADC()[tb - 1] > mAdcBaseline ? digitLeft->getADC()[tb - 1] - mAdcBaseline : 0; + int valueRU = digitRight->getADC()[tb - 1] > mAdcBaseline ? digitRight->getADC()[tb - 1] - mAdcBaseline : 0; + int valueU = digit.getADC()[tb - 1] - mAdcBaseline; // why not protect against negative values here? + + int valueLD = digitLeft->getADC()[tb + 1] > mAdcBaseline ? digitLeft->getADC()[tb + 1] - mAdcBaseline : 0; + int valueRD = digitRight->getADC()[tb + 1] > mAdcBaseline ? digitRight->getADC()[tb + 1] - mAdcBaseline : 0; + int valueD = digit.getADC()[tb + 1] - mAdcBaseline; // same question as above + + int valueL = digitLeft->getADC()[tb] > mAdcBaseline ? digitLeft->getADC()[tb] - mAdcBaseline : 0; + int valueR = digitRight->getADC()[tb] > mAdcBaseline ? digitRight->getADC()[tb] - mAdcBaseline : 0; + + int sum = adc + valueL + valueR; + int sumU = valueU + valueLU + valueRU; + int sumD = valueD + valueLD + valueRD; + + if (sumU < mAdcBaseline || sumD < mAdcBaseline) { + continue; + } + if (TMath::Abs(1. * sum / sumU - 1) < 0.01) { + continue; + } + if (TMath::Abs(1. * sum / sumD - 1) < 0.01) { + continue; + } + if (TMath::Abs(1. * sumU / sumD - 1) < 0.01) { + continue; + } + mNCls->Fill(sector); + mClsTb->Fill(tb, sum); + mClsAmp->Fill(sum); + if (sum > mAdcBaseline && sum < mClsCutoff) { + mClsChargeTb->Fill(tb, sum); + mClsNTb->Fill(tb); + } + } // local maximum + } // loop over time bins + } + + auto sumLeft = digitLeft->getADCsum(); + auto sumCenter = digit.getADCsum(); + auto sumRight = digitRight->getADCsum(); + if (!mChambersToIgnoreBP.test(detector)) { + if (sumCenter > sumLeft && sumCenter > sumRight) { + auto lowestSum = (sumLeft > sumRight) ? sumRight : sumLeft; + if (lowestSum > mPulseHeightThreshold) { + for (int tb = 0; tb < TIMEBINS; tb++) { + int phVal = (digit.getADC()[tb] + digitLeft->getADC()[tb] + digitRight->getADC()[tb]); + mPulseHeight->Fill(tb, phVal); + mTotalPulseHeight2D->Fill(tb, phVal); + mPulseHeight2DperSM[sector]->Fill(tb, phVal); + mPulseHeightpro->Fill(tb, phVal); + mPulseHeightperchamber->Fill(tb, detector, phVal); + } // loop over time bins + } // lower ADC sum above threshold + } // local ADC maximum + } // chamber OK + } // for loop over digits + } // loop over triggers +} + +void DigitsTask::drawLinesOnPulseHeight(TH1F* h) +{ + TLine* lmin = new TLine(mPulseHeightPeakRegion.first, 0, mPulseHeightPeakRegion.first, 1e9); + TLine* lmax = new TLine(mPulseHeightPeakRegion.second, 0, mPulseHeightPeakRegion.second, 1e9); + lmin->SetLineStyle(2); + lmax->SetLineStyle(2); + lmin->SetLineColor(kRed); + lmax->SetLineColor(kRed); + h->GetListOfFunctions()->Add(lmin); + h->GetListOfFunctions()->Add(lmax); +} + +void DigitsTask::buildChamberIgnoreBP() +{ + mChambersToIgnoreBP.reset(); + // Vector of string to save tokens + std::vector tokens; + // stringstream class check1 + std::stringstream check1(mChambersToIgnore); + std::string intermediate; + // Tokenizing w.r.t. space ',' + while (getline(check1, intermediate, ',')) { + tokens.push_back(intermediate); + } + // Printing the token vector + for (auto& token : tokens) { + // token now holds something like 16_3_0 + std::vector parts; + std::stringstream indexcheck(token); + std::string tokenpart; + // Tokenizing w.r.t. space ',' + while (getline(indexcheck, tokenpart, '_')) { + parts.push_back(tokenpart); + } + // now flip the bit related to the sector:stack:layer stored in parts. + int sector = std::stoi(parts[0]); + int stack = std::stoi(parts[1]); + int layer = std::stoi(parts[2]); + mChambersToIgnoreBP.set(Helper::getDetector(sector, stack, layer)); + } +} + +void DigitsTask::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; +} // set stats/stacs + +void DigitsTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void DigitsTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void DigitsTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void DigitsTask::finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) +{ + if (matcher == o2::framework::ConcreteDataMatcher("TRD", "FCHSTATUS", 0)) { + // LB: no half chamber distribution map for Digits, pass it as null pointer + TRDHelpers::drawChamberStatusOnHistograms(static_cast*>(obj), nullptr, mLayers, NCOLUMN); + } +} + +void DigitsTask::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histogram" << ENDM; + mDigitsPerEvent->Reset(); + mDigitHCID->Reset(); + mADCvalue->Reset(); + mDigitsSizevsTrackletSize->Reset(); + mTotalPulseHeight2D->Reset(); + mPulseHeight->Reset(); + mPulseHeightpro->Reset(); + mPulseHeightperchamber->Reset(); + for (auto& h : mPulseHeight2DperSM) { + h->Reset(); + } + for (auto& h : mHCMCM) { + h->Reset(); + } + for (auto& h : mLayers) { + h->Reset(); + } + if (mDoClusterize) { + mNCls->Reset(); + mClsTb->Reset(); + mClsChargeTb->Reset(); + mClsNTb->Reset(); + mClsAmp->Reset(); + } +} +} // namespace o2::quality_control_modules::trd diff --git a/Modules/TRD/src/PulseHeightCheck.cxx b/Modules/TRD/src/PulseHeightCheck.cxx new file mode 100644 index 0000000000..421388d43d --- /dev/null +++ b/Modules/TRD/src/PulseHeightCheck.cxx @@ -0,0 +1,175 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PulseHeightCheck.cxx +/// \author My Name +/// + +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TRDQC/StatusHelper.h" +#include "TRD/PulseHeightCheck.h" + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::trd +{ + +void PulseHeightCheck::configure() +{ + ILOG(Debug, Devel) << "initialize PulseHeight" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + if (auto param = mCustomParameters.find("driftregionstart"); param != mCustomParameters.end()) { + mDriftRegion.first = stof(param->second); + ILOG(Debug, Support) << "configure() : using driftregionstart = " << mDriftRegion.first << ENDM; + } else { + mDriftRegion.first = 7.0; + ILOG(Debug, Support) << "configure() : using default dritfregionstart = " << mDriftRegion.first << ENDM; + } + if (auto param = mCustomParameters.find("driftregionend"); param != mCustomParameters.end()) { + mDriftRegion.second = stof(param->second); + ILOG(Debug, Support) << "configure() : using dritftregionend = " << mDriftRegion.second << ENDM; + } else { + mDriftRegion.second = 20.0; + ILOG(Debug, Support) << "configure() : using default dritfregionend = " << mDriftRegion.second << ENDM; + } + if (auto param = mCustomParameters.find("peakregionstart"); param != mCustomParameters.end()) { + mPulseHeightPeakRegion.first = stof(param->second); + ILOG(Debug, Support) << "configure() : using peakregionstart " << mPulseHeightPeakRegion.first << ENDM; + } else { + mPulseHeightPeakRegion.first = 1.0; + ILOG(Debug, Support) << "configure() : using default peakregionstart = " << mPulseHeightPeakRegion.first << ENDM; + } + if (auto param = mCustomParameters.find("peakregionend"); param != mCustomParameters.end()) { + mPulseHeightPeakRegion.second = stof(param->second); + ILOG(Debug, Support) << "configure() : using peak region ends = " << mPulseHeightPeakRegion.second << ENDM; + } else { + mPulseHeightPeakRegion.second = 5.0; + ILOG(Debug, Support) << "configure() : using default peak region end = " << mPulseHeightPeakRegion.second << ENDM; + } + if (auto param = mCustomParameters.find("pulseheightminsum"); param != mCustomParameters.end()) { + mPulseHeightMinSum = stoi(param->second); + ILOG(Debug, Support) << "configure() : using pulseheight min sum before checking = " << mPulseHeightMinSum << ENDM; + } else { + mPulseHeightMinSum = 1500; + ILOG(Debug, Support) << "configure() : using pulseheight min sum before checking = " << mPulseHeightMinSum << ENDM; + } + if (auto param = mCustomParameters.find("pulseheightratio"); param != mCustomParameters.end()) { + mPulseHeightRatio = stoi(param->second); + ILOG(Debug, Support) << "configure() : using pulseheight ratio, peak/drift = " << mPulseHeightRatio << ENDM; + } else { + mPulseHeightRatio = 1.1; + ILOG(Debug, Support) << "configure() : using pulseheight ratio, peak/drift = " << mPulseHeightRatio << ENDM; + } +} + +Quality PulseHeightCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + + (void)moName; + if ((mo->getName() == "PulseHeight/mPulseHeight") || (mo->getName() == "PulseHeight/mPulseHeightpro")) { + auto* h = dynamic_cast(mo->getObject()); + + result = Quality::Good; + + // check max bin is in the spike on left. + auto max = h->GetMaximum(); + auto maxbin = h->GetMaximumBin(); + auto average = 0.0; + for (int i = (int)mDriftRegion.first; i < (int)mDriftRegion.second; ++i) { + average += h->GetBinContent(i); + } + if (mDriftRegion.first != mDriftRegion.second) { + average = average / (mDriftRegion.second - mDriftRegion.first); + } + + if (maxbin < mPulseHeightPeakRegion.first || maxbin > mPulseHeightPeakRegion.second) { + // is the peak in the peak region. + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Invalid(), + "Peak is in the wrong position " + std::to_string(maxbin)); + return result; + } + + // check the drift region is sufficiently below the left hand peak. + if (average > 0) { + if (max / average > mPulseHeightRatio) { + // peak is sufficiently high relative to the drift region. + result = Quality::Good; + return result; + } else { + + result = Quality::Medium; + result.addFlag(FlagTypeFactory::Invalid(), + "Peak is too low relative to the drift region max : " + std::to_string(max) + " average of drift:" + std::to_string(average)); + return result; + } + if (max < average) { + // if the peak maximum is below the average height of the drift region, we have a problem. + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Invalid(), + "Peak is below the drift region average peak : " + std::to_string(max) + " average of drift:" + std::to_string(average)); + return result; + } + } else { + result = Quality::Medium; + result.addFlag(FlagTypeFactory::Invalid(), + "Drift region average is " + std::to_string(average)); + return result; + } + } + } + return result; +} + +void PulseHeightCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if ((mo->getName() == "PulseHeight/mPulseHeight") || (mo->getName() == "PulseHeight/mPulseHeightpro")) { + auto* h = dynamic_cast(mo->getObject()); + TPaveText* msg = new TPaveText(0.3, 0.9, 0.7, 0.95, "NDC"); + h->GetListOfFunctions()->Add(msg); + // std::string message = fmt::format("Pulseheight message"); + std::string message = "Pulseheight message"; + msg->SetName(message.c_str()); + + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + h->SetLineColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Info, Support) << "Quality::Bad, something wrong with the pulseheight spectrum" << ENDM; + h->SetFillColor(kRed); + h->SetLineColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Info, Support) << "Quality::medium, pusleheight spectrum is a bit suspect" << ENDM; + h->SetFillColor(kOrange); + h->SetLineColor(kOrange); + } + // h->SetLineColor(kBlack); + h->Draw(); + } +} + +} // namespace o2::quality_control_modules::trd diff --git a/Modules/TRD/src/PulseHeightPostProcessing.cxx b/Modules/TRD/src/PulseHeightPostProcessing.cxx new file mode 100644 index 0000000000..fb5c28599f --- /dev/null +++ b/Modules/TRD/src/PulseHeightPostProcessing.cxx @@ -0,0 +1,121 @@ + +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TRDTrending.cxx +/// \author based on Piotr Konopka +/// + +#include "TRD/PulseHeightPostProcessing.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/RepoPathUtils.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; +using namespace o2::quality_control::postprocessing; + +void PulseHeightPostProcessing::configure(const boost::property_tree::ptree& config) +{ + + mHost = config.get("qc.config.conditionDB.url"); + + mPath = config.get("qc.postprocessing.PulseHeightPerChamber2D.path"); + mTimestamps = config.get("qc.postprocessing.PulseHeightPerChamber2D.timestamps"); +} + +void PulseHeightPostProcessing::initialize(Trigger, framework::ServiceRegistryRef services) +{ + + mCdbph.init(mHost); + + for (Int_t sm = 0; sm < 18; sm++) { + + cc[sm] = std::make_shared(Form("cc%.2i", sm), Form("sm-%.2i", sm)); + + cc[sm].get()->Divide(5, 6); + + getObjectsManager()->startPublishing(cc[sm].get(), PublicationPolicy::ThroughStop); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void PulseHeightPostProcessing::update(Trigger t, framework::ServiceRegistryRef) +{ + PlotPulseHeightPerChamber(); +} + +void PulseHeightPostProcessing::finalize(Trigger, framework::ServiceRegistryRef services) +{ + for (Int_t sm = 0; sm < 18; sm++) { + getObjectsManager()->stopPublishing(cc[sm].get()); + cc[sm].reset(); + } +} + +void PulseHeightPostProcessing::PlotPulseHeightPerChamber() +{ + std::map meta; + auto hPulseHeight2D = mCdbph.retrieveFromTFileAny(mPath, meta, mTimestamps); + + Int_t cn = 0; + Int_t sm = 0; + Int_t cm = 0; + Int_t c = 1; + + for (Int_t i = 0; i < 540; i++) { + + sm = i / 30; + + h2[i] = (TProfile*)hPulseHeight2D->ProfileX(Form("%i_pfx", i), i, i + 1); + h2[i]->SetName(Form("phpersm_%.2i", i)); + h2[i]->SetTitle(Form("%.2i_%i_%i", sm, cn / 6, cn % 6)); ////SM_Stack_Layer + h2[i]->SetXTitle("timebin"); + h2[i]->SetYTitle("pulseheight"); + TProfile* h3 = (TProfile*)h2[i]->Clone(); + + cc[sm].get()->cd(cm * 5 + c); + + h3->Draw("p"); + cc[sm].get()->Update(); + cn++; + // cout << sm << endl; + if (cn > 29) { + cn = 0; + c = 0; + } + cm++; + if (cm > 5) { + cm = 0; + c++; + } + } +} diff --git a/Modules/TRD/src/PulseHeightTrackMatch.cxx b/Modules/TRD/src/PulseHeightTrackMatch.cxx new file mode 100644 index 0000000000..3c6a210809 --- /dev/null +++ b/Modules/TRD/src/PulseHeightTrackMatch.cxx @@ -0,0 +1,152 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PulseHeightTrackMatch.cxx +/// \author Vikash Sumberia +/// \author Salman Malik + +// ROOT includes +#include +#include +#include +#include + +// O2 includes +#include "DataFormatsTRD/TrackTRD.h" +#include "QualityControl/QcInfoLogger.h" +#include "TRD/PulseHeightTrackMatch.h" +#include "TRDCalibration/PulseHeight.h" +#include "DataFormatsTRD/PHData.h" +#include "TRDWorkflowIO/TRDCalibWriterSpec.h" +#include "DataFormatsGlobalTracking/RecoContainer.h" +#include + +namespace o2::quality_control_modules::trd +{ +PulseHeightTrackMatch::~PulseHeightTrackMatch() +{ +} + +void PulseHeightTrackMatch::buildHistograms() +{ + mPulseHeightpro.reset(new TProfile("PulseHeight/mPulseHeightpro", "PulseHeight;Timebins;ADC Counts", 30, -0.5, 29.5)); + drawLinesOnPulseHeight(mPulseHeightpro.get()); + mPulseHeightpro.get()->Sumw2(); + getObjectsManager()->startPublishing(mPulseHeightpro.get()); + + mPulseHeightperchamber.reset(new TProfile2D("PulseHeight/mPulseHeightperchamber", "PulseHeight per chamber;Timebin;Chamber", 30, -0.5, 29.5, 540, 0, 540)); + mPulseHeightperchamber.get()->Sumw2(); + getObjectsManager()->startPublishing(mPulseHeightperchamber.get()); + getObjectsManager()->setDefaultDrawOptions(mPulseHeightperchamber.get()->GetName(), "colz"); +} + +void PulseHeightTrackMatch::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TRDPulseHeightQcTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + + // values configurable from json + if (auto param = mCustomParameters.find("driftRegionStart"); param != mCustomParameters.end()) { + mDriftRegion.first = stof(param->second); + ILOG(Debug, Devel) << "configure() : using driftRegionStart = " << mDriftRegion.first << ENDM; + } else { + mDriftRegion.first = 7.0; + ILOG(Debug, Devel) << "configure() : using default driftRegionStart = " << mDriftRegion.first << ENDM; + } + if (auto param = mCustomParameters.find("driftRegionEnd"); param != mCustomParameters.end()) { + mDriftRegion.second = stof(param->second); + ILOG(Debug, Devel) << "configure() : using driftRegionEnd = " << mDriftRegion.second << ENDM; + } else { + mDriftRegion.second = 20.0; + ILOG(Debug, Devel) << "configure() : using default driftRegionEnd = " << mDriftRegion.second << ENDM; + } + if (auto param = mCustomParameters.find("peakRegionStart"); param != mCustomParameters.end()) { + mPulseHeightPeakRegion.first = stof(param->second); + ILOG(Debug, Devel) << "configure() : using peakRegionStart = " << mPulseHeightPeakRegion.first << ENDM; + } else { + mPulseHeightPeakRegion.first = 1.0; + ILOG(Debug, Devel) << "configure() : using default peakRegionStart = " << mPulseHeightPeakRegion.first << ENDM; + } + if (auto param = mCustomParameters.find("peakRegionEnd"); param != mCustomParameters.end()) { + mPulseHeightPeakRegion.second = stof(param->second); + ILOG(Debug, Devel) << "configure() : using peakRegionEnd = " << mPulseHeightPeakRegion.second << ENDM; + } else { + mPulseHeightPeakRegion.second = 5.0; + ILOG(Debug, Devel) << "configure() : using default peakRegionEnd = " << mPulseHeightPeakRegion.second << ENDM; + } + if (auto param = mCustomParameters.find("trackType"); param != mCustomParameters.end()) { + mTrackType = std::stof(param->second); + if (mTrackType == 0x1) { + ILOG(Debug, Devel) << "configure() : using trackType = " << mTrackType << " ITS-TPC-TRD tracks" << ENDM; + } + if (mTrackType == 0x2) { + ILOG(Debug, Devel) << "configure() : using trackType = " << mTrackType << " TPC-TRD tracks" << ENDM; + } + } else { + mTrackType = 0xf; + ILOG(Debug, Devel) << "configure() : using default trackType = " << mTrackType << " all the tracks" << ENDM; + } + + buildHistograms(); +} + +void PulseHeightTrackMatch::startOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "startOfActivity" << ENDM; +} // set stats/stacs + +void PulseHeightTrackMatch::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void PulseHeightTrackMatch::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto phDataArr = ctx.inputs().get>("phValues"); + + for (const auto& phData : phDataArr) { + if (mTrackType[phData.getType()]) { + mPulseHeightpro->Fill(phData.getTimebin(), phData.getADC()); + mPulseHeightperchamber->Fill(phData.getTimebin(), phData.getDetector(), phData.getADC()); + } + } +} + +void PulseHeightTrackMatch::drawLinesOnPulseHeight(TProfile* h) +{ + TLine* lmin = new TLine(mPulseHeightPeakRegion.first, -10, mPulseHeightPeakRegion.first, 1e9); + TLine* lmax = new TLine(mPulseHeightPeakRegion.second, -10, mPulseHeightPeakRegion.second, 1e9); + lmin->SetLineStyle(2); + lmax->SetLineStyle(2); + lmin->SetLineColor(kRed); + lmax->SetLineColor(kRed); + h->GetListOfFunctions()->Add(lmin); + h->GetListOfFunctions()->Add(lmax); +} + +void PulseHeightTrackMatch::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void PulseHeightTrackMatch::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void PulseHeightTrackMatch::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histogram" << ENDM; + mPulseHeightpro->Reset(); + mPulseHeightperchamber->Reset(); +} +} // namespace o2::quality_control_modules::trd diff --git a/Modules/TRD/src/PulsePositionCheck.cxx b/Modules/TRD/src/PulsePositionCheck.cxx new file mode 100644 index 0000000000..e8cf93ca98 --- /dev/null +++ b/Modules/TRD/src/PulsePositionCheck.cxx @@ -0,0 +1,236 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PulsePositionCheck.cxx +/// \author Deependra (deependra.sharma@cern.ch) +/// + +#include "TRD/PulsePositionCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include + +#include +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::trd +{ + +void PulsePositionCheck::configure() +{ + ILOG(Debug, Devel) << "initialize PulseHeight" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + if (auto param = mCustomParameters.find("pulseheightpeaklower"); param != mCustomParameters.end()) { + mPulseHeightPeakRegion.first = stof(param->second); + ILOG(Debug, Support) << "configure() : using pulseheightpeaklower " << mPulseHeightPeakRegion.first << ENDM; + } else { + mPulseHeightPeakRegion.first = 0.5; + ILOG(Debug, Support) << "configure() : using default pulseheightpeaklower = " << mPulseHeightPeakRegion.first << ENDM; + } + if (auto param = mCustomParameters.find("pulseheightpeakupper"); param != mCustomParameters.end()) { + mPulseHeightPeakRegion.second = stof(param->second); + ILOG(Debug, Support) << "configure() : using pulseheightpeakupper = " << mPulseHeightPeakRegion.second << ENDM; + } else { + mPulseHeightPeakRegion.second = 4.0; + ILOG(Debug, Support) << "configure() : using default pulseheightpeakupper = " << mPulseHeightPeakRegion.second << ENDM; + } + + // peak region should be well inside the fitting range(0_4) + // safe Gaurd + if (mPulseHeightPeakRegion.second > 4.0) { + mPulseHeightPeakRegion.second = 4.0; + } + + if (auto param = mCustomParameters.find("Chi2byNDF_threshold"); param != mCustomParameters.end()) { + chi2byNDF_threshold = stod(param->second); + ILOG(Debug, Support) << "configure() : using chi2/NDF threshold = " << chi2byNDF_threshold << ENDM; + } else { + chi2byNDF_threshold = 0.22; + ILOG(Debug, Support) << "configure() : using chi2/NDF threshold = " << chi2byNDF_threshold << ENDM; + } + // Fit param 0 + if (auto param = mCustomParameters.find("FitParameter0"); param != mCustomParameters.end()) { + FitParam0 = stod(param->second); + ILOG(Debug, Support) << "configure() : using FitParameter0= " << FitParam0 << ENDM; + } else { + FitParam0 = 100000.0; + ILOG(Debug, Support) << "configure() : using FitParameter0= " << FitParam0 << ENDM; + } + // Fit param 1 + if (auto param = mCustomParameters.find("FitParameter1"); param != mCustomParameters.end()) { + FitParam1 = stod(param->second); + ILOG(Debug, Support) << "configure() : using FitParameter1= " << FitParam1 << ENDM; + } else { + FitParam1 = 100000.0; + ILOG(Debug, Support) << "configure() : using FitParameter1= " << FitParam1 << ENDM; + } + // Fit param 2 + if (auto param = mCustomParameters.find("FitParameter2"); param != mCustomParameters.end()) { + FitParam2 = stod(param->second); + ILOG(Debug, Support) << "configure() : using FitParameter2= " << FitParam2 << ENDM; + } else { + FitParam2 = 1.48; + ILOG(Debug, Support) << "configure() : using FitParameter2= " << FitParam2 << ENDM; + } + // Fit param 3 + if (auto param = mCustomParameters.find("FitParameter3"); param != mCustomParameters.end()) { + FitParam3 = stod(param->second); + ILOG(Debug, Support) << "configure() : using FitParameter3= " << FitParam3 << ENDM; + } else { + FitParam3 = 1.09; + ILOG(Debug, Support) << "configure() : using FitParameter3= " << FitParam3 << ENDM; + } + // Defined function range + if (auto param = mCustomParameters.find("DefinedFunctionRangeL"); param != mCustomParameters.end()) { + FunctionRange[0] = stod(param->second); + } else { + FunctionRange[0] = 0.0; + } + if (auto param = mCustomParameters.find("DefinedFunctionRangeU"); param != mCustomParameters.end()) { + FunctionRange[1] = stod(param->second); + } else { + FunctionRange[1] = 6.0; + } + ILOG(Debug, Support) << "configure() : using defined function range = " << FunctionRange[0] << " to " << FunctionRange[1] << ENDM; + + // Fiiting range lower value + if (auto param = mCustomParameters.find("FitRangeL"); param != mCustomParameters.end()) { + FitRange[0] = stod(param->second); + } else { + FitRange[0] = 0.0; + } + // Fiiting range upper value + if (auto param = mCustomParameters.find("FitRangeU"); param != mCustomParameters.end()) { + FitRange[1] = stod(param->second); + } else { + FitRange[1] = 4.0; + } + ILOG(Debug, Support) << "config() : using fit range= " << FitRange[0] << " to " << FitRange[1] << ENDM; +} + +Quality PulsePositionCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + // ILOG(Debug, Trace) << "Check function called"<< ENDM; + // LOG(info)<<"Check function called"; + + for (auto& [moName, mo] : *moMap) { + + (void)moName; + if (mo->getName() == "mPulseHeight") { + + // LOG(info) << "mPulseHeight object found"; + + auto* h = dynamic_cast(mo->getObject()); + + // Defining Fit function + TF1* f1 = new TF1("landaufit", "((x<2) ? ROOT::Math::erf(x)*[0]:[0]) + [1]*TMath::Landau(x,[2],[3])", FunctionRange[0], FunctionRange[1]); + f1->SetParameters(FitParam0, FitParam1, FitParam2, FitParam3); + + // LOGF(info,"p0: %f, p1: %f, p2: %f, p3: %f",FitParam0, FitParam1, FitParam2, FitParam3); + // LOGF(info,"FitRang[0]: %f, FitRange[1]: %f",FitRange[0],FitRange[1]); + // LOGF(info,"FunctionRange[0]: %f, FunctionRange[1]: %f",FunctionRange[0],FunctionRange[1]); + + // Fitting Pulse Distribution with defined fit function + auto FitResult = h->Fit(f1, "S", "", FitRange[0], FitRange[1]); + // LOGF(info,"Status: %d, Cov Matrix Status: %d",FitResult->Status(),FitResult->CovMatrixStatus()); + + if (FitResult->CovMatrixStatus() != 3) // if covMatrix is not accurate + { + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), "Covariance matrix is not accurate."); + return result; + } + + double_t peak_value_x = f1->GetMaximumX(mPulseHeightPeakRegion.first, mPulseHeightPeakRegion.second); + + // LOGF(info,"peak_value_x %f",peak_value_x); + + double_t chi2_value = f1->GetChisquare(); + Int_t NDF = f1->GetNDF(); + double_t Chi2byNDF = chi2_value / NDF; + + // LOGF(info,"chi2/ndf %f",Chi2byNDF); + + if (Chi2byNDF > chi2byNDF_threshold) { + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), "chi2/ndf is very large"); + return result; + } + + auto x0 = h->GetXaxis()->GetBinLowEdge(0); + auto x1 = h->GetXaxis()->GetBinLowEdge(1); + auto x2 = h->GetXaxis()->GetBinLowEdge(2); + auto x3 = h->GetXaxis()->GetBinLowEdge(3); + + if ((peak_value_x < x1) && (peak_value_x > x0)) { + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), "pulse peak is in first bin"); + // LOG(info)<<"peak is in the first bin from left"; + return result; + } else if ((peak_value_x < x2) && (peak_value_x > x1)) { + result = Quality::Good; + // LOG(info)<<"peak is in the second bin from left"; + } else if ((peak_value_x < x3) && (peak_value_x > x2)) { + result = Quality::Good; + // LOG(info)<<"peak is in the third bin from left"; + } else { + // LOG(info)<<"peak is not in Good region"; + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), "amplification peak is not in good position. Out of [" + std::to_string(x0) + " " + std::to_string(x3) + "] range"); + return result; + } + + result.addMetadata("Peak_in", "third_Bin"); + } else { + // LOG(info) << "PulseHeight object not found"; + } + } + return result; +} + +void PulsePositionCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + + // ILOG(Debug, Trace) << "beautify function called"<< ENDM; + // LOG(info)<< "beautify function called"; + + if (mo->getName() == "mPulseHeight") { + auto* h = dynamic_cast(mo->getObject()); + + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red" << ENDM; + h->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange" << ENDM; + h->SetFillColor(kOrange); + } else if (checkResult == Quality::Null) { + ILOG(Debug, Devel) << "Quality::Null, setting to Blue" << ENDM; + h->SetFillColor(kBlue); + } + h->SetLineColor(kBlack); + h->Draw(); + } +} + +} // namespace o2::quality_control_modules::trd diff --git a/Modules/TRD/src/RawData.cxx b/Modules/TRD/src/RawData.cxx new file mode 100644 index 0000000000..42df71de4f --- /dev/null +++ b/Modules/TRD/src/RawData.cxx @@ -0,0 +1,330 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawData.cxx +/// \author Sean Murray +/// + +#include "TCanvas.h" +#include "TH1F.h" +#include "TH2F.h" +#include "TProfile.h" +#include "TMath.h" + +#include "QualityControl/QcInfoLogger.h" +#include "TRD/RawData.h" +#include "DataFormatsTRD/RawDataStats.h" +#include "DataFormatsTRD/Digit.h" +#include "DataFormatsTRD/Tracklet64.h" +#include "DataFormatsTRD/TriggerRecord.h" +#include "DataFormatsTRD/HelperMethods.h" +#include +#include +#include + +using namespace o2::trd; +using namespace o2::trd::constants; + +namespace o2::quality_control_modules::trd +{ + +RawData::~RawData() +{ +} + +void RawData::buildHistograms() +{ + std::array linkerrortitles = { "Count of Link had no errors during tf", + "Count of # times Linkerrors 0x1 seen per tf", + "Count of # time Linkerrors 0x2 seen per tf", + "Count of any Linkerror seen during tf", + "Link was seen with no data (empty) in a tf", + "Link was seen with data during a tf", + "Links seen with corrupted data during tf", + "Links seen with out corrupted data during tf", + "Accepted Data volume on link", + "Rejected Data volume on link" }; + + mStats = new TH1F("stats", "Data reader statistics;;counts", 5, 0, 5); + getObjectsManager()->startPublishing(mStats); + getObjectsManager()->setDefaultDrawOptions(mStats->GetName(), "logy"); + mStats->GetXaxis()->SetBinLabel(1, "nTF"); + mStats->GetXaxis()->SetBinLabel(2, "nTrig"); + mStats->GetXaxis()->SetBinLabel(3, "nCalTrig"); + mStats->GetXaxis()->SetBinLabel(4, "nTrklts"); + mStats->GetXaxis()->SetBinLabel(5, "nDigits"); + mStats->LabelsOption("v"); + mDataAcceptance = new TH1F("dataacceptance", "Data Accepted and Rejected;;MBytes", 2, -0.5, 1.5); + getObjectsManager()->startPublishing(mDataAcceptance); + mDataAcceptance->GetXaxis()->SetBinLabel(1, "Accepted"); + mDataAcceptance->GetXaxis()->SetBinLabel(2, "Rejected"); + constexpr int nLogBins = 100; + float xBins[nLogBins + 1]; + float xBinLogMin = 1.f; + float xBinLogMax = 6.f; + float logBinWidth = (xBinLogMax - xBinLogMin) / nLogBins; + for (int iBin = 0; iBin <= nLogBins; ++iBin) { + xBins[iBin] = TMath::Power(10, xBinLogMin + iBin * logBinWidth); + } + mTimeFrameTime = new TH1F("timeframetime", "Time taken per time frame;Time taken [us];Counts", nLogBins, xBins); + getObjectsManager()->startPublishing(mTimeFrameTime); + getObjectsManager()->setDefaultDrawOptions(mTimeFrameTime->GetName(), "logx"); + mTrackletParsingTime = new TH1F("tracklettime", "Time taken per tracklet block;Time taken [us];Counts", nLogBins, xBins); + getObjectsManager()->startPublishing(mTrackletParsingTime); + getObjectsManager()->setDefaultDrawOptions(mTrackletParsingTime->GetName(), "logx"); + mDigitParsingTime = new TH1F("digittime", "Time taken per digit block;Time taken [us];Counts", nLogBins, xBins); + getObjectsManager()->startPublishing(mDigitParsingTime); + getObjectsManager()->setDefaultDrawOptions(mDigitParsingTime->GetName(), "logx"); + mDataVersionsMajor = new TH1F("dataversionsmajor", "Data versions major seen in the data (half chamber header required);Version;Counts", 256, -0.5, 255.5); + getObjectsManager()->startPublishing(mDataVersionsMajor); + mParsingErrors = new TH1F("parseerrors", "Parsing Errors seen in data;;Counts", TRDLastParsingError, 0, TRDLastParsingError); + getObjectsManager()->startPublishing(mParsingErrors); + getObjectsManager()->setDefaultDrawOptions(mParsingErrors->GetName(), "logy"); + + mDataVolumePerHalfChamber = new TH2F("datavolumeperhalfchamber", "Data sizes from HalfCRU header;Half Chamber ID;Data Volume [Bytes/TF]", 1080, -0.5, 1079.5, 1000, 0, 100); + getObjectsManager()->startPublishing(mDataVolumePerHalfChamber); + getObjectsManager()->setDefaultDrawOptions("datavolumeperhalfchamber", "COLZ"); + getObjectsManager()->setDisplayHint(mDataVolumePerHalfChamber->GetName(), "logz"); + + mDataVolumePerSector = new TH2F("datavolumepersector", "Data sizes from HalfCRU header;Sector;Data Volume [Bytes/TF]", 18, -0.5, 17.5, 1000, 0, 4000); + mDataVolumePerSector->SetStats(0); + getObjectsManager()->startPublishing(mDataVolumePerSector); + getObjectsManager()->setDefaultDrawOptions("datavolumepersector", "COLZ"); + getObjectsManager()->setDisplayHint(mDataVolumePerSector->GetName(), "logz"); + + mDataVolumePerSectorProf = new TProfile("datavolumepersectorProfile", "Data volume by sector;Sector;Data Volume [Bytes/TF]", 18, -0.5, 17.5); + mDataVolumePerSectorProf->Sumw2(); + getObjectsManager()->startPublishing(mDataVolumePerSectorProf); + + for (int count = 0; count < o2::trd::TRDLastParsingError; ++count) { + std::string label = fmt::format("parsingerrors_{0}", count); + std::string title = ParsingErrorsString.at(count); + TH2F* h = new TH2F(label.c_str(), title.c_str(), 36, 0, 36, 30, 0, 30); + mParsingErrors2d[count] = h; + getObjectsManager()->startPublishing(h); + getObjectsManager()->setDefaultDrawOptions(h->GetName(), "COLZ"); + getObjectsManager()->setDisplayHint(h->GetName(), "logz"); + } + for (int count = 0; count < 10; ++count) { + std::string label = fmt::format("linkstatus_{0}", count); + std::string title = linkerrortitles[count]; + TH2F* h = new TH2F(label.c_str(), title.c_str(), 36, 0, 36, 30, 0, 30); + mLinkErrors[count] = h; + getObjectsManager()->startPublishing(h); + getObjectsManager()->setDefaultDrawOptions(h->GetName(), "COLZ"); + getObjectsManager()->setDisplayHint(h->GetName(), "logz"); + } + + for (int i = 0; i < TRDLastParsingError; ++i) { + std::string label = fmt::format("{1:.3}_{0}", i, ParsingErrorsString.at(i)); + mParsingErrors->GetXaxis()->SetBinLabel(i + 1, label.c_str()); + } + mParsingErrors->LabelsOption("v"); + for (int count = 0; count < o2::trd::TRDLastParsingError; ++count) { + TH2F* h = mParsingErrors2d[count]; + h->GetXaxis()->SetTitle("Sector_Side"); + h->GetXaxis()->CenterTitle(kTRUE); + h->GetYaxis()->SetTitle("Stack_Layer"); + h->GetYaxis()->CenterTitle(kTRUE); + for (int s = 0; s < NSTACK; ++s) { + for (int l = 0; l < NLAYER; ++l) { + std::string label = fmt::format("{0}_{1}", s, l); + int pos = s * NLAYER + l + 1; + h->GetYaxis()->SetBinLabel(pos, label.c_str()); + } + getObjectsManager()->setDisplayHint(h->GetName(), "logz"); + } + for (int sm = 0; sm < NSECTOR; ++sm) { + for (int side = 0; side < 2; ++side) { + std::string label = fmt::format("{0}_{1}", sm, side == 0 ? "A" : "B"); + int pos = sm * 2 + side + 1; + h->GetXaxis()->SetBinLabel(pos, label.c_str()); + } + } + } + for (int count = 0; count < 10; ++count) { + TH2F* h = mLinkErrors[count]; + h->GetXaxis()->SetTitle("Sector_Side"); + h->GetXaxis()->CenterTitle(kTRUE); + h->GetYaxis()->SetTitle("Stack_Layer"); + h->GetYaxis()->CenterTitle(kTRUE); + getObjectsManager()->setDisplayHint(h->GetName(), "logz"); + for (int s = 0; s < NSTACK; ++s) { + for (int l = 0; l < NLAYER; ++l) { + std::string label = fmt::format("{0}_{1}", s, l); + int pos = s * NLAYER + l + 1; + h->GetYaxis()->SetBinLabel(pos, label.c_str()); + } + } + for (int sm = 0; sm < NSECTOR; ++sm) { + for (int side = 0; side < 2; ++side) { + std::string label = fmt::format("{0}_{1}", sm, side == 0 ? "A" : "B"); + int pos = sm * 2 + side + 1; + h->GetXaxis()->SetBinLabel(pos, label.c_str()); + } + } + } +} + +void RawData::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TRD RawData QC" << ENDM; + if (auto param = mCustomParameters.find("fillHeaderVersionHist"); param != mCustomParameters.end()) { + mCheckDigitHCHeaderVersion = std::stoi(param->second); + } + buildHistograms(); + ILOG(Info, Support) << "TRD RawData QC histograms built" << ENDM; +} + +void RawData::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + resetHistograms(); +} + +void RawData::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + mStats->Reset(); +} + +void RawData::monitorData(o2::framework::ProcessingContext& ctx) +{ + auto rawdatastats = ctx.inputs().get("rawstats"); + mStats->AddBinContent(1, 1); // count number of TFs seen + mStats->AddBinContent(2, rawdatastats->mNTriggersTotal); // count total number of triggers seen + mStats->AddBinContent(3, rawdatastats->mNTriggersCalib); // count total number of calibration triggers seen + mStats->AddBinContent(4, rawdatastats->mTrackletsFound); // count total number of tracklets seen + mStats->AddBinContent(5, rawdatastats->mDigitsFound); // count total number of digits seen + + // data per TF per link. + std::array dataSizePerSector{ 0U }; + for (int hcid = 0; hcid < MAXHALFCHAMBER; ++hcid) { + if (rawdatastats->mLinkWords[hcid] > 0) { + int sec = hcid / NHCPERSEC; + mDataVolumePerHalfChamber->Fill(hcid, rawdatastats->mLinkWords[hcid] / 32.f); // one link word is 32 bytes, we want to display in units of kB per TF + dataSizePerSector[sec] += rawdatastats->mLinkWords[hcid] / 32.f; + } + } + for (int iSec = 0; iSec < NSECTOR; ++iSec) { + mDataVolumePerSector->Fill(iSec, dataSizePerSector[iSec]); + mDataVolumePerSectorProf->Fill(iSec, dataSizePerSector[iSec]); + } + + // parsing errors + for (int error = 0; error < TRDLastParsingError; ++error) { + mParsingErrors->AddBinContent(error + 1, rawdatastats->mParsingErrors[error]); + } + for (auto e : rawdatastats->mParsingErrorsByLink) { + int hcid = e / TRDLastParsingError; + int errorIdx = e % TRDLastParsingError; + int stackLayer = HelperMethods::getStack(hcid / 2) * NLAYER + HelperMethods::getLayer(hcid / 2); + int sectorSide = (hcid / NHCPERSEC) * 2 + (hcid % 2); + mParsingErrors2d[errorIdx]->Fill(sectorSide, stackLayer); + } + for (int hcid = 0; hcid < MAXHALFCHAMBER; ++hcid) { + if (rawdatastats->mParsingOK[hcid] > 0) { + int stackLayer = HelperMethods::getStack(hcid / 2) * NLAYER + HelperMethods::getLayer(hcid / 2); + int sectorSide = (hcid / NHCPERSEC) * 2 + (hcid % 2); + mParsingErrors2d[0]->SetBinContent(sectorSide + 1, stackLayer + 1, mParsingErrors2d[0]->GetBinContent(sectorSide + 1, stackLayer + 1) + rawdatastats->mParsingOK[hcid]); + } + } + + // link statistics + for (int hcid = 0; hcid < MAXHALFCHAMBER; ++hcid) { + int stackLayer = HelperMethods::getStack(hcid / 2) * NLAYER + HelperMethods::getLayer(hcid / 2); + int sectorSide = (hcid / NHCPERSEC) * 2 + (hcid % 2); + if (rawdatastats->mLinkErrorFlag[hcid] == 0) { //"Count of Link had no errors during tf", + mLinkErrors[0]->Fill(sectorSide, stackLayer); + } + if (rawdatastats->mLinkErrorFlag[hcid] & 0x1) { //"Count of # times Linkerrors 0x1 seen per tf", + mLinkErrors[1]->Fill(sectorSide, stackLayer); + } + if (rawdatastats->mLinkErrorFlag[hcid] & 0x2) { //"Count of # time Linkerrors 0x2 seen per tf", + mLinkErrors[2]->Fill(sectorSide, stackLayer); + } + if (rawdatastats->mLinkErrorFlag[hcid] != 0) { //"Count of any Linkerror seen during tf", + mLinkErrors[3]->Fill(sectorSide, stackLayer); + } + if (rawdatastats->mLinkWordsRejected[hcid] + rawdatastats->mLinkWordsRead[hcid] == 0) { + mLinkErrors[4]->Fill(sectorSide, stackLayer); + } + if (rawdatastats->mLinkWordsRead[hcid] > 0) { //"Link was seen with data during a tf", + mLinkErrors[5]->Fill(sectorSide, stackLayer); + } + if (rawdatastats->mLinkWordsRejected[hcid] > 0) { //"Links seen with corrupted data during tf" + mLinkErrors[6]->Fill(sectorSide, stackLayer); + } + if (rawdatastats->mLinkWordsRejected[hcid] == 0) { //"Links seen without corrupted data during tf" + mLinkErrors[7]->Fill(sectorSide, stackLayer); + } + if (rawdatastats->mLinkWordsRead[hcid] != 0) { + ILOG(Debug, Devel) << "Accepted Data volume on link: " << rawdatastats->mLinkWordsRead[hcid] << ENDM; + mLinkErrors[8]->Fill((double)sectorSide, (double)stackLayer, rawdatastats->mLinkWordsRead[hcid]); + mDataAcceptance->AddBinContent(1, (4.f * rawdatastats->mLinkWordsRead[hcid]) / (1024.f * 1024.f)); // each word is 32 bits + } + if (rawdatastats->mLinkWordsRejected[hcid] != 0) { + ILOG(Debug, Devel) << "Rejected Data volume on link: " << rawdatastats->mLinkWordsRejected[hcid] << ENDM; + mLinkErrors[9]->Fill((double)sectorSide, (double)stackLayer, rawdatastats->mLinkWordsRejected[hcid]); + mDataAcceptance->AddBinContent(2, (4.f * rawdatastats->mLinkWordsRejected[hcid]) / (1024.f * 1024.f)); // each word is 32 bits + } + } + // time graphs + mTimeFrameTime->Fill(rawdatastats->mTimeTaken); + mDigitParsingTime->Fill(rawdatastats->mTimeTakenForDigits); + mTrackletParsingTime->Fill(rawdatastats->mTimeTakenForTracklets); + + if (mCheckDigitHCHeaderVersion) { + for (int i = 0; i < rawdatastats->mDataFormatRead.size(); ++i) { + mDataVersionsMajor->Fill(i, rawdatastats->mDataFormatRead[i]); + } + } +} + +void RawData::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void RawData::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void RawData::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + resetHistograms(); +} +void RawData::resetHistograms() +{ + ILOG(Debug, Devel) << "Resetting the histograms " << ENDM; + for (auto hist : mLinkErrors) { + hist->Reset(); + } + for (auto hist : mParsingErrors2d) { + hist->Reset(); + } + mStats->Reset(); + mDataAcceptance->Reset(); + mTimeFrameTime->Reset(); + mTrackletParsingTime->Reset(); + mDigitParsingTime->Reset(); + mDataVersionsMajor->Reset(); + mParsingErrors->Reset(); + mDataVolumePerHalfChamber->Reset(); + mDataVolumePerSector->Reset(); + mDataVolumePerSectorProf->Reset(); +} + +} // namespace o2::quality_control_modules::trd diff --git a/Modules/TRD/src/RawDataCheck.cxx b/Modules/TRD/src/RawDataCheck.cxx new file mode 100644 index 0000000000..2bffd33a39 --- /dev/null +++ b/Modules/TRD/src/RawDataCheck.cxx @@ -0,0 +1,226 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file RawDataCheck.cxx +/// \author Ole Schmidt + +// C++ +#include +#include +// Fair +#include +// ROOT +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// O2 +#include +#include +#include +#include +#include + +// Quality Control +#include "TRD/RawDataCheck.h" +#include "Common/Utils.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/UserCodeInterface.h" + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::trd +{ + +void RawDataCheckStats::configure() +{ + // parameters which don't depend on activity can be initialized here + mNHBFperTF = o2::quality_control_modules::common::getFromConfig(mCustomParameters, "nHBFperTF", 32); +} + +Quality RawDataCheckStats::check(std::map>* moMap) +{ + Quality result = Quality::Null; + // For QC checks the correct timestamp is not available from the framework + // Thus, for synch. reconstruction of real data the code below would work, + // but for SYNTHETIC data different timestamps are used from MC which cannot + // be accessed in here. This we keep the number of HBFs per TF as a configurable + // parameter + /* + int runNumber = mActivity->mId; + auto& ccdbmgr = o2::ccdb::BasicCCDBManager::instance(); + auto runDuration = ccdbmgr.getRunDuration(runNumber, false); + map metaData; + metaData.emplace(std::pair("runNumber", to_string(runNumber))); + auto calib = UserCodeInterface::retrieveConditionAny("GLO/Config/GRPECS", metaData, runDuration.first); + calib->getNHBFPerTF() + */ + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "stats") { + auto* h = dynamic_cast(mo->getObject()); + result.set(Quality::Good); + if (h->GetBinContent(1) < 1 || h->GetBinContent(2) < 1) { + result.set(Quality::Bad); // no readout triggers or histogram is completely empty + } else { + mReadoutRate = h->GetBinContent(2) / h->GetBinContent(1); // number of triggers per TF + mReadoutRate *= 1. / (mNHBFperTF * o2::constants::lhc::LHCOrbitMUS * 1e-6); + mCalTriggerRate = h->GetBinContent(3) / h->GetBinContent(2); + } + if (mReadoutRate > mMaxReadoutRate) { + result.set(Quality::Bad); // too high rate (should be triggered only for COSMICS run) TOF noisy? + } + if (mCalTriggerRate < mMinCalTriggerRate) { + result.set(Quality::Medium); // no calibration triggers configured? + } + result.addMetadata("readoutRate", to_string(mReadoutRate)); + result.addMetadata("calTriggerRate", to_string(mCalTriggerRate)); + } + } + return result; +} + +void RawDataCheckStats::beautify(std::shared_ptr mo, Quality checkResult) +{ + + if (mo->getName() == "stats") { + auto* h = dynamic_cast(mo->getObject()); + if (h->GetMaximum() < 1) { + return; + } + auto msg = new TPaveText(0.5, h->GetMaximum() / 1e2, 2.5, h->GetMaximum()); + TText* txtPtr = nullptr; + txtPtr = msg->AddText(Form("Readout rate: %.3f kHz", mReadoutRate / 1000.)); + txtPtr->SetTextAlign(12); + txtPtr = msg->AddText(Form("Calibration trigger rate: %.4f", mCalTriggerRate)); + txtPtr->SetTextAlign(12); + static_cast(msg->GetListOfLines()->Last())->SetTextAlign(12); + h->GetListOfFunctions()->Add(msg); + if (checkResult == Quality::Good) { + h->SetFillColor(kGreen); + msg->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red" << ENDM; + h->SetFillColor(kRed); + msg->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange" << ENDM; + h->SetFillColor(kOrange); + msg->SetFillColor(kOrange); + } + h->SetLineColor(kBlack); + } +} + +void RawDataCheckStats::reset() +{ + ILOG(Debug, Devel) << "RawDataCheckStats::reset" << ENDM; +} + +void RawDataCheckStats::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "RawDataCheckStats::start : " << activity.mId << ENDM; + mActivity = make_shared(activity); + // we cannot move the parameter initialization to configure(), as there the activity is not yet initialized + mMaxReadoutRate = std::stof(mCustomParameters.atOptional("maxReadoutRate", *mActivity).value_or("1e6")); + mMinCalTriggerRate = std::stof(mCustomParameters.atOptional("minCalTriggerRate", *mActivity).value_or("1e-5")); +} + +void RawDataCheckStats::endOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "RawDataCheckStats::end : " << activity.mId << ENDM; +} + +void RawDataCheckSizes::configure() {} + +Quality RawDataCheckSizes::check(std::map>* moMap) +{ + Quality result = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "datavolumepersector") { + auto* h = dynamic_cast(mo->getObject()); + result.set(Quality::Good); + mMeanDataSize = h->GetMean(2); + mStdDevDataSize = h->GetRMS(2); + for (int iBin = 1; iBin <= 18; ++iBin) { + auto sectorMean = h->ProjectionY("py", iBin, iBin + 1)->GetMean(); + if (TMath::Abs(sectorMean - mMeanDataSize) > mWarningThreshold * mStdDevDataSize) { + ILOG(Debug, Support) << fmt::format("Found outlier in sector {} with mean value of {}", iBin - 1, sectorMean) << ENDM; + result.set(Quality::Medium); + } + if (TMath::Abs(sectorMean - mMeanDataSize) > mErrorThreshold * mStdDevDataSize) { + ILOG(Debug, Support) << fmt::format("Found strong outlier in sector {} with mean value of {}", iBin - 1, sectorMean) << ENDM; + result.set(Quality::Bad); + } + } + ILOG(Debug, Support) << fmt::format("Data size plot: Mean({}), StdDev({}).", mMeanDataSize, mStdDevDataSize) << ENDM; + result.addMetadata("dataSizeMean", to_string(mMeanDataSize)); + result.addMetadata("dataSizeStdDev", to_string(mStdDevDataSize)); + } + } + return result; +} + +void RawDataCheckSizes::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "datavolumepersector") { + ILOG(Debug, Support) << "Found the meta data \"foo\": " << checkResult.getMetadata("foo", "ramtam") << ENDM; + auto* h = dynamic_cast(mo->getObject()); + auto msg = new TPaveText(4, 3000, 10, 3800); + msg->AddText(Form("Average volume per sector: %.2f Bytes/TF", mMeanDataSize)); + static_cast(msg->GetListOfLines()->Last())->SetTextAlign(12); + msg->AddText(Form("sigma: %.3f", mStdDevDataSize)); + static_cast(msg->GetListOfLines()->Last())->SetTextAlign(12); + h->GetListOfFunctions()->Add(msg); + if (checkResult == Quality::Good) { + msg->SetFillColor(kGreen); + } else if (checkResult == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red" << ENDM; + msg->SetFillColor(kRed); + } else if (checkResult == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange" << ENDM; + msg->SetFillColor(kOrange); + } + } +} + +void RawDataCheckSizes::reset() +{ + ILOG(Debug, Devel) << "RawDataCheckSizes::reset" << ENDM; +} + +void RawDataCheckSizes::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "RawDataCheckSizes::start : " << activity.mId << ENDM; + mActivity = make_shared(activity); + // we cannot move the parameter initialization to configure(), as there the activity is not yet initialized + mWarningThreshold = std::stof(mCustomParameters.atOptional("warningThreshold", *mActivity).value_or("3")); + mErrorThreshold = std::stof(mCustomParameters.atOptional("errorThreshold", *mActivity).value_or("5")); +} + +void RawDataCheckSizes::endOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "RawDataCheckSizes::end : " << activity.mId << ENDM; +} + +} // namespace o2::quality_control_modules::trd diff --git a/Modules/TRD/src/TRDHelpers.cxx b/Modules/TRD/src/TRDHelpers.cxx new file mode 100644 index 0000000000..565c2371e2 --- /dev/null +++ b/Modules/TRD/src/TRDHelpers.cxx @@ -0,0 +1,117 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TRDHelpers.cxx +/// + +#include +#include +#include +#include "TRD/TRDHelpers.h" +#include "DataFormatsTRD/HelperMethods.h" + +using namespace o2::trd::constants; +using Helper = o2::trd::HelperMethods; + +namespace o2::quality_control_modules::trd +{ +bool TRDHelpers::isHalfChamberMasked(int hcid, const std::array* ptrChamber) +{ + // LB: defined as array in case other chamber status values should be set as not masked + int GoodStatus[] = { mConfiguredChamberStatus }; + int hcstat = (*ptrChamber)[hcid / 2]; + return (std::find(std::begin(GoodStatus), std::end(GoodStatus), hcstat) == std::end(GoodStatus)); +} + +void TRDHelpers::drawChamberStatusOnHistograms(const std::array* ptrChamber, std::shared_ptr chamberMap, std::array, NLAYER> ptrLayersArray, int unitsPerSection) +{ + std::pair x, y; + for (int hcid = 0; hcid < MAXCHAMBER * 2; ++hcid) { + if (isHalfChamberMasked(hcid, ptrChamber)) { + // Chamber properties + int hcstat = (*ptrChamber)[hcid / 2]; + int side = hcid % 2; + int sec = hcid / NHCPERSEC; + int stack = Helper::getStack(hcid / 2); + int layer = Helper::getLayer(hcid / 2); + + if (chamberMap) { + // Coordinates for half chamber distribution + int stackLayer = stack * NLAYER + layer; + int sectorSide = sec * 2 + side; + x.first = sectorSide; + x.second = sectorSide + 1; + y.first = stackLayer; + y.second = stackLayer + 1; + + drawHalfChamberMask(hcstat, x, y, chamberMap); + } + + if (ptrLayersArray[layer]) { + // Chamber stack of type C0 or C1 condition + int rowstart = FIRSTROW[stack]; + int rowend = stack == 2 ? rowstart + NROWC0 : rowstart + NROWC1; + + // Coordinates for layer map + x.first = rowstart - 0.5; + x.second = rowend - 0.5; + y.first = (sec * 2 + side) * (unitsPerSection / 2) - 0.5; + y.second = y.first + (unitsPerSection / 2); + + drawHalfChamberMask(hcstat, x, y, ptrLayersArray[layer]); + } + } + } +} + +void TRDHelpers::drawHalfChamberMask(int hcstat, std::pair xCoord, std::pair yCoord, std::shared_ptr histogram) +{ + TLine* box[6]; + box[0] = new TLine(xCoord.first, yCoord.first, xCoord.second, yCoord.first); // bottom + box[1] = new TLine(xCoord.first, yCoord.second, xCoord.second, yCoord.second); // top + box[2] = new TLine(xCoord.first, yCoord.first, xCoord.first, yCoord.second); // left + box[3] = new TLine(xCoord.second, yCoord.first, xCoord.second, yCoord.second); // right + box[4] = new TLine(xCoord.first, yCoord.second, xCoord.second, yCoord.first); // backslash + box[5] = new TLine(xCoord.first, yCoord.first, xCoord.second, yCoord.second); // forwardslash + + for (int line = 0; line < 6; ++line) { + if (hcstat == mEmptyChamberStatus) { + box[line]->SetLineColor(kGray + 1); + } else if (hcstat == mErrorChamberStatus) { + box[line]->SetLineColor(kRed); + } else { + box[line]->SetLineColor(kBlack); + } + box[line]->SetLineWidth(2); + histogram->GetListOfFunctions()->Add(box[line]); + } +} + +void TRDHelpers::addChamberGridToHistogram(std::shared_ptr histogram, int unitsPerSection) +{ + TLine* line; + for (int iStack = 0; iStack < NSTACK; ++iStack) { + line = new TLine(FIRSTROW[iStack] - 0.5, 0, FIRSTROW[iStack] - 0.5, NSECTOR * unitsPerSection - 0.5); + line->SetLineStyle(kDashed); + line->SetLineColor(kBlack); + histogram->GetListOfFunctions()->Add(line); + } + + for (int iSec = 1; iSec < NSECTOR; ++iSec) { + float yPos = iSec * unitsPerSection - 0.5; + line = new TLine(-0.5, yPos, FIRSTROW[NSTACK - 1] + NROWC1 - 0.5, yPos); + line->SetLineStyle(kDashed); + line->SetLineColor(kBlack); + histogram->GetListOfFunctions()->Add(line); + } +} +} // namespace o2::quality_control_modules::trd diff --git a/Modules/TRD/src/TRDTrending.cxx b/Modules/TRD/src/TRDTrending.cxx new file mode 100644 index 0000000000..f8ec06e8d4 --- /dev/null +++ b/Modules/TRD/src/TRDTrending.cxx @@ -0,0 +1,176 @@ + +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TRDTrending.cxx +/// \author based on Piotr Konopka +/// + +#include "TRD/TRDTrending.h" +#include "QualityControl/QcInfoLogger.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Reductor.h" +#include "QualityControl/ReductorTObject.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/RootClassFactory.h" +#include "QualityControl/RepoPathUtils.h" +#include "QualityControl/ActivityHelpers.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace o2::quality_control; +using namespace o2::quality_control::core; +using namespace o2::quality_control::repository; +using namespace o2::quality_control::postprocessing; + +void TRDTrending::configure(const boost::property_tree::ptree& config) +{ + mConfig = TrendingTaskConfigTRD(getID(), config); +} + +void TRDTrending::initialize(Trigger, framework::ServiceRegistryRef services) +{ + // Preparing data structure of TTree + mTrend.reset(); + mTrend = std::make_unique(); + mTrend->SetName(PostProcessingInterface::getName().c_str()); + mTrend->Branch("runNumber", &mMetaData.runNumber); + + mTrend->Branch("ntreeentries", &ntreeentries); + mTrend->Branch("time", &mTime); + + for (const auto& source : mConfig.dataSources) { + std::unique_ptr reductor(root_class_factory::create(source.moduleName, source.reductorName)); + + mTrend->Branch(source.name.c_str(), reductor->getBranchAddress(), reductor->getBranchLeafList()); + mReductors[source.name] = std::move(reductor); + } +} + +// todo: see if OptimizeBaskets() indeed helps after some time +void TRDTrending::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + trendValues(t, qcdb); + generatePlots(qcdb); +} + +void TRDTrending::finalize(Trigger, framework::ServiceRegistryRef services) +{ + + auto& qcdb = services.get(); + auto mo = std::make_shared(mTrend.get(), getName(), "o2::quality_control_modules::trd::TRDTrending", mConfig.detectorName); + mo->setIsOwner(false); + qcdb.storeMO(mo); + generatePlots(qcdb); +} + +void TRDTrending::trendValues(const Trigger& t, repository::DatabaseInterface& qcdb) +{ + mTime = TDatime().Convert(); // ROOT expects seconds since epoch. + mMetaData.runNumber = t.activity.mId; + int count = 0; + + for (auto& dataSource : mConfig.dataSources) { + + // todo: make it agnostic to MOs, QOs or other objects. Let the reductor cast to whatever it needs. + if (dataSource.type == "repository") { + auto mo = qcdb.retrieveMO(dataSource.path, dataSource.name, t.timestamp); + if (mo == nullptr) { + ILOG(Warning, Devel) << "Could not retrieve MO '" << dataSource.name << "' from QCDB, skipping this data source" << ENDM; + continue; + } + + if (!count) { + std::map entryMetadata = mo->getMetadataMap(); // full list of metadata as a map + mMetaData.runNumber = std::stoi(entryMetadata[metadata_keys::runNumber]); // get and set run number + ntreeentries = (Int_t)mTrend->GetEntries() + 1; + runlist.push_back(std::to_string(mMetaData.runNumber)); + } + TObject* obj = mo ? mo->getObject() : nullptr; + auto reductor = dynamic_cast(mReductors[dataSource.name].get()); + if (obj && reductor) { + reductor->update(obj); + } + } else { + ILOG(Error, Support) << "Unknown type of data source '" << dataSource.type << "'." << ENDM; + } + count++; + } + + mTrend->Fill(); +} + +void TRDTrending::generatePlots(repository::DatabaseInterface& qcdb) +{ + if (mTrend->GetEntries() < 1) { + ILOG(Info, Support) << "No entries in the trend so far, won't generate any plots." << ENDM; + return; + } + + ILOG(Info, Support) << "Generating " << mConfig.plots.size() << " plots." << ENDM; + + std::vector> mCanvasTRD; + int mPlot = 0; + for (const auto& plot : mConfig.plots) { + + mCanvasTRD.push_back(std::make_unique(plot.name.c_str(), plot.title.c_str())); + mCanvasTRD[mPlot].get()->cd(); + + mTrend->Draw(plot.varexp.c_str(), plot.selection.c_str(), plot.option.c_str()); + + if (auto histo = dynamic_cast(mCanvasTRD[mPlot].get()->GetPrimitive("htemp"))) { + + histo->SetTitle(plot.title.c_str()); + mCanvasTRD[mPlot]->Update(); + if (plot.varexp.find(":time") != std::string::npos) { + histo->GetXaxis()->SetTimeDisplay(1); + + // It deals with highly congested dates labels + histo->GetXaxis()->SetNdivisions(505); + // Without this it would show dates in order of 2044-12-18 on the day of 2019-12-19. + histo->GetXaxis()->SetTimeOffset(0.0); + histo->GetXaxis()->SetTimeFormat("%Y-%m-%d %H:%M"); + + } + + else if (plot.varexp.find(":runNumber") != std::string::npos) { + histo->GetXaxis()->SetNdivisions(505); + + for (int ir = 0; ir < (int)runlist.size(); ir++) + histo->GetXaxis()->SetBinLabel(ir + 1, runlist[ir].c_str()); + } + + histo->BufferEmpty(); + + } else { + ILOG(Error, Devel) << "Could not get the processing histogram of the plot '" << plot.name << "'." << ENDM; + } + + mPlots[plot.name] = mCanvasTRD[mPlot].get(); + + auto mo_trd = std::make_shared(mCanvasTRD[mPlot].get(), mConfig.taskName, "o2::quality_control_modules::trd::TRDTrending", mConfig.detectorName); + + mo_trd->setIsOwner(false); + qcdb.storeMO(mo_trd); + + ++mPlot; + } +} diff --git a/Modules/TRD/src/TrackingTask.cxx b/Modules/TRD/src/TrackingTask.cxx new file mode 100644 index 0000000000..09a9984008 --- /dev/null +++ b/Modules/TRD/src/TrackingTask.cxx @@ -0,0 +1,394 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrackingTask.cxx +/// \author Salman Malik +/// + +// ROOT includes +#include "TLine.h" +#include "TCanvas.h" +#include "TMath.h" +// O2 includes +#include "QualityControl/QcInfoLogger.h" +#include "TRDQC/Tracking.h" +#include +// QC includes +#include "TRD/TrackingTask.h" +#include "Common/Utils.h" + +using namespace o2::trd; +using namespace o2::trd::constants; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::trd +{ +using GID = o2::dataformats::GlobalTrackID; + +void TrackingTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TRD TrackingTask" << ENDM; + + mPtMin = getFromConfig(mCustomParameters, "pTminvalue", 0.f); + mDetailedTrackQC = getFromConfig(mCustomParameters, "detailedQC"); + std::string trackSources = getFromConfig(mCustomParameters, "trackSources", "all"); + GID::mask_t allowedSources = GID::getSourcesMask("ITS-TPC,TPC-TRD,ITS-TPC-TRD"); + mSrcSelected = allowedSources & GID::getSourcesMask(trackSources); + mDataRequest = std::make_shared(); + mDataRequest->requestTracks(mSrcSelected, false); + + buildHistograms(); +} + +void TrackingTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; +} + +void TrackingTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TrackingTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + ILOG(Debug, Devel) << "monitorData" << ENDM; + + mRecoCont.collectData(ctx, *mDataRequest.get()); + std::vector sources; + if (mSrcSelected[GID::Source::ITSTPCTRD] == 1) { + mITSTPCTRDTracks = mRecoCont.getITSTPCTRDTracks(); + mTrigITSTPCTRD = mRecoCont.getITSTPCTRDTriggers(); + sources.push_back(GID::Source::ITSTPCTRD); + } + if (mSrcSelected[GID::Source::TPCTRD] == 1) { + mTPCTRDTracks = mRecoCont.getTPCTRDTracks(); + mTrigTPCTRD = mRecoCont.getTPCTRDTriggers(); + sources.push_back(GID::Source::TPCTRD); + } + if (mSrcSelected[GID::Source::ITSTPC] == 1) { + mTPCITSTracks = mRecoCont.getTPCITSTracks(); + sources.push_back(GID::Source::ITSTPC); + } + + const gsl::span* trackQcPtr = nullptr; + using trkQcType = std::decay_t>(""))>; + std::optional trackQc; + if (mDetailedTrackQC) { + trackQc.emplace(ctx.inputs().get>("tracksqc")); + trackQcPtr = &trackQc.value(); + } + + std::vector trdTrigTimes = {}; + for (auto src : sources) { + const gsl::span* trackTriggers = (src == GID::Source::ITSTPCTRD) ? &mTrigITSTPCTRD : &mTrigTPCTRD; + const gsl::span* tracks = (src == GID::Source::ITSTPCTRD) ? &mITSTPCTRDTracks : &mTPCTRDTracks; + for (const auto& tracktrig : *trackTriggers) { + int start = tracktrig.getFirstTrack(); + int end = start + tracktrig.getNumberOfTracks(); + mNtracks->Fill(tracktrig.getNumberOfTracks()); + trdTrigTimes.push_back(tracktrig.getBCData().differenceInBC(mRecoCont.startIR) * o2::constants::lhc::LHCBunchSpacingMUS); + for (int itrack = start; itrack < end; ++itrack) { + const auto& trackTRD = (*tracks)[itrack]; + // remove tracks with pt below threshold + if (trackTRD.getPt() < mPtMin) { + continue; + } + // filling track information + mTrackPt->Fill(trackTRD.getPt()); + mTrackPhi->Fill(trackTRD.getOuterParam().getPhiPos()); + mNtracklets->Fill(trackTRD.getNtracklets()); + mTrackChi2->Fill(trackTRD.getReducedChi2()); + mTrackEta->Fill(trackTRD.getOuterParam().getEta()); + mTrackEtaPhi->Fill(trackTRD.getOuterParam().getEta(), trackTRD.getOuterParam().getPhiPos()); + // find charge bin of the track + int charge = trackTRD.getCharge() > 0 ? 0 : 1; + // eta-phi distribution of tracklets per layer + mTrackletsEtaPhi[charge]->Fill(trackTRD.getOuterParam().getEta(), trackTRD.getOuterParam().getPhiPos(), trackTRD.getNtracklets()); + mTrackletsEtaPhi[2]->Fill(trackTRD.getOuterParam().getEta(), trackTRD.getOuterParam().getPhiPos(), trackTRD.getNtracklets()); + for (int iLayer = 0; iLayer < NLAYER; iLayer++) { + // skip layers with no tracklet + if (trackTRD.getTrackletIndex(iLayer) < 0) { + continue; + } + // eta-phi distribution per layer + mTracksEtaPhiPerLayer[iLayer][charge]->Fill(trackTRD.getOuterParam().getEta(), trackTRD.getOuterParam().getPhiPos()); + } // end of loop over layers + } // end of loop over tracks + } // end of loop over track trigger records + + if (src == GID::Source::ITSTPC) { + for (size_t itrk = 0; itrk < mTPCITSTracks.size(); ++itrk) { + const auto& track = mTPCITSTracks[itrk]; + double trackTime = track.getTimeMUS().getTimeStamp(); + double trackTimeError = track.getTimeMUS().getTimeStampError(); + + bool hasTRDTrigger = std::any_of(trdTrigTimes.begin(), trdTrigTimes.end(), [&](double trdTime) { + return (trackTime - trackTimeError <= trdTime) && (trackTime + trackTimeError >= trdTime); + }); + if ((!hasTRDTrigger) || (abs(track.getEta()) > 0.84)) { + continue; + } + + // filling track information + mTrackPtTPCITS->Fill(track.getPt()); + if (track.getPt() > mPtMin) { + mTrackEtaTPCITS->Fill(track.getEta()); + mTrackPhiTPCITS->Fill(track.getPhi()); + } + } + } + setEfficiency(mEfficiencyPt, mTrackPt, mTrackPtTPCITS); + setEfficiency(mEfficiencyEta, mTrackEta, mTrackEtaTPCITS); + setEfficiency(mEfficiencyPhi, mTrackPhi, mTrackPhiTPCITS); + } + trdTrigTimes.clear(); + + if (mDetailedTrackQC && trackQcPtr) { + // Residuals in y and z using TRD/TRACKINGQC + for (const auto& trackqc : *trackQcPtr) { + // match only ITS-TPC tracks + auto trackType = trackqc.refGlobalTrackId.getSource(); + if (!(trackType == GID::Source::ITSTPC)) { + continue; + } + // remove tracks with pt below threshold + if (trackqc.trackTRD.getPt() < mPtMin) { + continue; + } + for (int iLayer = 0; iLayer < NLAYER; iLayer++) { + // skip layers with no tracklet + if (trackqc.trackTRD.getTrackletIndex(iLayer) < 0) { + continue; + } + // residuals information + mDeltaY->Fill(trackqc.trackProp[iLayer].getY() - trackqc.trackletY[iLayer]); + mDeltaZ->Fill(trackqc.trackProp[iLayer].getZ() - trackqc.trackletZ[iLayer]); + mDeltaYDet->Fill(trackqc.trklt64[iLayer].getDetector(), trackqc.trackProp[iLayer].getY() - trackqc.trackletY[iLayer]); + mDeltaZDet->Fill(trackqc.trklt64[iLayer].getDetector(), trackqc.trackProp[iLayer].getZ() - trackqc.trackletZ[iLayer]); + mDeltaYvsSphi->Fill(trackqc.trackProp[iLayer].getSnp(), trackqc.trackProp[iLayer].getY() - trackqc.trackletY[iLayer]); + mDeltaYinEtaPerLayer[iLayer]->Fill(trackqc.trackProp[iLayer].getEta(), trackqc.trackProp[iLayer].getY() - trackqc.trackletY[iLayer]); + mDeltaYinPhiPerLayer[iLayer]->Fill(trackqc.trackProp[iLayer].getPhiPos(), trackqc.trackProp[iLayer].getY() - trackqc.trackletY[iLayer]); + mTrackletDef->Fill(trackqc.trackProp[iLayer].getSnp(), trackqc.trkltCalib[iLayer].getDy()); + } // end of loop over layers + } // end of loop over tracks + } +} + +void TrackingTask::setEfficiency(TEfficiency* eff, TH1* hNum, TH1* hDen) +{ + if (!eff || !hNum || !hDen) { + ILOG(Debug, Devel) << "Something went wrong when defining the efficiency " << eff->GetName() << " from " << hNum->GetName() << " and " << hDen->GetName() << ENDM; + } + eff->SetTotalHistogram(*hDen, "f"); + eff->SetPassedHistogram(*hNum, "f"); +} + +void TrackingTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void TrackingTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TrackingTask::reset() +{ + ILOG(Debug, Devel) << "Resetting the histogram" << ENDM; + mNtracks->Reset(); + mNtracklets->Reset(); + mTrackEta->Reset(); + mTrackPhi->Reset(); + mTrackEtaPhi->Reset(); + mTrackPt->Reset(); + mTrackPtTPCITS->Reset(); + mTrackEtaTPCITS->Reset(); + mTrackPhiTPCITS->Reset(); + mTrackChi2->Reset(); + if (mDetailedTrackQC) { + mDeltaY->Reset(); + mDeltaZ->Reset(); + mDeltaYDet->Reset(); + mDeltaZDet->Reset(); + mDeltaYvsSphi->Reset(); + mTrackletDef->Reset(); + for (auto h : mDeltaYinEtaPerLayer) { + h->Reset(); + } + for (auto h : mDeltaYinPhiPerLayer) { + h->Reset(); + } + } + for (auto h : mTrackletsEtaPhi) { + h->Reset(); + } + for (auto h : mTracksEtaPhiPerLayer) { + for (auto hh : h) { + hh->Reset(); + } + } +} + +void TrackingTask::buildHistograms() +{ + mNtracks = new TH1D("Ntracks", "Number of matched tracks", 100, 0.0, 100.0); + axisConfig(mNtracks, "# of tracks", "", "", 1, 1.0, 1.3); + publishObject(mNtracks); + + mNtracklets = new TH1D("Ntracklets", "Number of Tracklets per Track", 7, -0.5, 6.5); + axisConfig(mNtracklets, "# of tracklets", "", "", 1, 1.0, 1.1); + publishObject(mNtracklets); + + mTrackEta = new TH1D("TrackEta", "Eta Distribution", 20, -1.0, 1.0); + axisConfig(mTrackEta, "#eta", "Counts", "", 1, 1.0, 1.1); + publishObject(mTrackEta); + + mTrackPhi = new TH1D("TrackPhi", "Phi Distribution", 60, 0, TMath::TwoPi()); + axisConfig(mTrackPhi, "#phi", "Counts", "", 1, 1.0, 1.1); + publishObject(mTrackPhi); + + mTrackEtaPhi = new TH2D("TrackEtaPhi", "Number of TRD matched tracks;track #eta;track #phi;counts", 100, -1., 1., 180, 0, TMath::TwoPi()); + publishObject(mTrackEtaPhi, "colz"); + + mTrackPt = new TH1D("TrackPt", "p_{T} Distribution", 100, 0.0, 10.0); + axisConfig(mTrackPt, "p_{T} (GeV)", "Counts", "", 1, 1.0, 1.1); + publishObject(mTrackPt, "", "logx"); + + mTrackPtTPCITS = new TH1D("TrackPtTPCITS", "p_{T} Distribution for ITS-TPC tracks", 100, 0.0, 10.0); + axisConfig(mTrackPtTPCITS, "p_{T} (GeV)", "Counts", "", 1, 1.0, 1.1); + publishObject(mTrackPtTPCITS, "", "logx"); + + mTrackEtaTPCITS = new TH1D("TrackEtaTPCITS", "Eta Distribution for ITS-TPC tracks", 20, -1.0, 1.0); + axisConfig(mTrackEtaTPCITS, "#eta", "Counts", "", 1, 1.0, 1.1); + publishObject(mTrackEtaTPCITS); + + mTrackPhiTPCITS = new TH1D("TrackPhiTPCITS", "Phi Distribution for ITS-TPC tracks", 60, 0, TMath::TwoPi()); + axisConfig(mTrackPhiTPCITS, "#phi", "Counts", "", 1, 1.0, 1.1); + publishObject(mTrackPhiTPCITS); + + mEfficiencyPt = new TEfficiency("EfficiencyPt", "Efficiency of matched tracks in p_{T}", 100, 0.0, 10.0); + publishObject(mEfficiencyPt); + + mEfficiencyEta = new TEfficiency("EfficiencyEta", "Efficiency of matched tracks in #eta", 20, -1.0, 1.0); + publishObject(mEfficiencyEta); + + mEfficiencyPhi = new TEfficiency("EfficiencyPhi", "Efficiency of matched tracks in #phi", 60, 0, TMath::TwoPi()); + publishObject(mEfficiencyPhi); + + mTrackChi2 = new TH1D("TrackChi2", "Reduced Chi2Distribution", 100, 0.0, 10.0); + axisConfig(mTrackChi2, "reduced #chi^{2}", "Counts", "", 1, 1.0, 1.1); + publishObject(mTrackChi2); + + if (mDetailedTrackQC) { + mDeltaY = new TH1D("DeltaY", "Residuals in Y", 100, -10.0, 10.0); + axisConfig(mDeltaY, "track Y - tracklet Y (cm)", "Counts", "", 1, 1.0, 1.1); + publishObject(mDeltaY); + + mDeltaZ = new TH1D("DeltaZ", "Residuals in Z", 100, -25.0, 25.0); + axisConfig(mDeltaZ, "track Z - tracklet Z (cm)", "Counts", "", 1, 1.0, 1.1); + publishObject(mDeltaZ); + + mDeltaYDet = new TH2D("DeltaYvsDet", "YResiduals per chamber", 540, -0.5, 539.5, 50, -10, 10); + axisConfig(mDeltaYDet, "chamber number", "track Y - tracklet Y (cm)", "Counts", 0, 1.0, 1.1); + publishObject(mDeltaYDet, "colz", "logz"); + + mDeltaZDet = new TH2D("DeltaZvsDet", "ZResiduals per chamber", 540, -0.5, 539.5, 50, -25, 25); + axisConfig(mDeltaZDet, "chamber number", "track Z - tracklet Z (cm)", "Counts", 0, 1.0, 1.1); + publishObject(mDeltaZDet, "colz", "logz"); + + mDeltaYvsSphi = new TH2D("DeltaYsnphi", "YResiduals vs Track sin(#phi)", 50, -0.4, 0.4, 50, -10, 10); + axisConfig(mDeltaYvsSphi, "track #it{sin(#phi)}", "track Y - tracklet Y (cm)", "Counts", 0, 1.0, 1.1); + publishObject(mDeltaYvsSphi, "colz", "logz"); + + mTrackletDef = new TH2D("TrackletDeflection", "Tracklet slope vs Track sin(#phi)", 100, -0.5, 0.5, 100, -1.5, 1.5); + axisConfig(mTrackletDef, "track #it{sin(#phi)}", "tracklet D_{y}", "Counts", 0, 1.0, 1.1); + publishObject(mTrackletDef, "colz", "logz"); + } + + for (int i = 0; i < NLAYER; ++i) { + for (int j = 0; j < 2; ++j) { + mTracksEtaPhiPerLayer[i][j] = new TH2D(Form("EtaPhi%sTrackPerLayer/layer%i", mChargeLabel[j].Data(), i), Form("EtaPhi for %s tracks in layer %i", mChargeLabel[j].Data(), i), 100, -0.856, 0.856, 180, 0, TMath::TwoPi()); + axisConfig(mTracksEtaPhiPerLayer[i][j], "#eta", "#phi", "Counts", 0, 1.0, 1.1); + drawLayers(mTracksEtaPhiPerLayer[i][j]); + publishObject(mTracksEtaPhiPerLayer[i][j], "colz", ""); + } + if (mDetailedTrackQC) { + mDeltaYinEtaPerLayer[i] = new TH2D(Form("YResinEtaPerLayer/layer%i", i), Form("YResiduals in eta for layer %i", i), 100, -0.856, 0.856, 100, -10, 10); + axisConfig(mDeltaYinEtaPerLayer[i], "#eta", "track Y - tracklet Y (cm)", "Counts", 0, 1.0, 1.1); + publishObject(mDeltaYinEtaPerLayer[i], "colz", "logz"); + + mDeltaYinPhiPerLayer[i] = new TH2D(Form("YResinPhiPerLayer/layer%i", i), Form("YResiduals in phi for layer %i", i), 180, 0, TMath::TwoPi(), 100, -10, 10); + axisConfig(mDeltaYinPhiPerLayer[i], "#phi", "track Y - tracklet Y (cm)", "Counts", 0, 1.0, 1.1); + publishObject(mDeltaYinPhiPerLayer[i], "colz", "logz"); + } + } + for (int i = 0; i < 3; ++i) { + mTrackletsEtaPhi[i] = new TProfile2D(Form("EtaPhiTracklets/%sTracks", mChargeLabel[i].Data()), Form("Av. # of tracklets for %s TRD tracks", mChargeLabel[i].Data()), 100, -0.856, 0.856, 180, 0, TMath::TwoPi(), 0, 6); + axisConfig(mTrackletsEtaPhi[i], "track #eta", "track #phi", "", 0, 1.0, 1.1); + drawLayers(mTrackletsEtaPhi[i]); + publishObject(mTrackletsEtaPhi[i], "colz", ""); + } +} + +void TrackingTask::drawLayers(TH2* hist) +{ + TLine* lineEta[4]; + TLine* linePhi[17]; + double etabins[6] = { -0.85, -0.54, -0.16, 0.16, 0.54, 0.85 }; + double phibins[18]; + for (int i = 0; i < 18; i++) { + phibins[i] = (TMath::TwoPi() / 18) * i; + } + for (int iSec = 0; iSec < 4; ++iSec) { + lineEta[iSec] = new TLine(etabins[iSec + 1], 0, etabins[iSec + 1], TMath::TwoPi()); + lineEta[iSec]->SetLineStyle(2); + hist->GetListOfFunctions()->Add(lineEta[iSec]); + } + for (int iStack = 0; iStack < 17; ++iStack) { + linePhi[iStack] = new TLine(-0.85, phibins[iStack + 1], 0.85, phibins[iStack + 1]); + linePhi[iStack]->SetLineStyle(2); + hist->GetListOfFunctions()->Add(linePhi[iStack]); + } +} + +void TrackingTask::axisConfig(TH1* h, const char* xTitle, const char* yTitle, const char* zTitle, bool stat, float xOffset, float yOffset) +{ + h->GetXaxis()->SetTitle(xTitle); + h->GetYaxis()->SetTitle(yTitle); + h->GetXaxis()->SetTitleOffset(xOffset); + h->GetYaxis()->SetTitleOffset(yOffset); + h->SetStats(stat); + if (zTitle) { + h->GetZaxis()->SetTitle(zTitle); + } +} + +void TrackingTask::publishObject(TObject* aObject, const char* drawOpt, const char* displayOpt) +{ + if (!aObject) { + ILOG(Debug, Devel) << " ERROR: trying to publish a non-existent histogram " << ENDM; + return; + } else { + getObjectsManager()->startPublishing(aObject); + if (drawOpt) { + getObjectsManager()->setDefaultDrawOptions(aObject->GetName(), drawOpt); + } + if (displayOpt) { + getObjectsManager()->setDisplayHint(aObject->GetName(), displayOpt); + } + ILOG(Debug, Devel) << " Object will be published: " << aObject->GetName() << ENDM; + } +} + +} // namespace o2::quality_control_modules::trd diff --git a/Modules/TRD/src/TrackletCountCheck.cxx b/Modules/TRD/src/TrackletCountCheck.cxx new file mode 100644 index 0000000000..19a216faa2 --- /dev/null +++ b/Modules/TRD/src/TrackletCountCheck.cxx @@ -0,0 +1,283 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrackletCountCheck.cxx +/// \author Deependra Sharma @IITB +/// + +#include "TRD/TrackletCountCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" +// ROOT +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; +using namespace o2::quality_control_modules::common; + +namespace o2::quality_control_modules::trd +{ + +void TrackletCountCheck::configure() +{ +} + +Quality TrackletCountCheck::check(std::map>* moMap) +{ + mResultPertrigger = Quality::Null; + mResultPerTimeFrame = Quality::Null; + + for (auto& [moName, mo] : *moMap) { + ILOG(Debug, Support) << "moName =" << moName << ENDM; + (void)moName; + if (mo->getName() == "trackletsperevent") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + // ILOG(Debug, Support) << "Requested Histogram type does not match with the Histogram in source" << ENDM; + continue; + } + // warning about statistics available + mTrackletPerTriggerMessage.reset(); + if (!mTrackletPerTriggerMessage) { + mTrackletPerTriggerMessage = std::make_shared(0.3, 0.7, 0.7, 0.9, "NDC"); + } + // mTrackletPerTriggerMessage = std::make_shared(0.3, 0.7, 0.7, 0.9, "NDC"); + // mTrackletPerTriggerMessage->DeleteText(); + mTrackletPerTriggerMessage->SetTextSize(10); + int entriesInQcHist = h->GetEntries(); + if (entriesInQcHist > mStatThresholdPerTrigger) { + mTrackletPerTriggerMessage->AddText(TString::Format("Hist Can't be ignored. Stat is enough. entriesInQcHist: %d > Threshold: %d", entriesInQcHist, mStatThresholdPerTrigger)); + // mTrackletPerTriggerMessage->SetTextColor(kGreen); + } else if (entriesInQcHist > 0) { + mTrackletPerTriggerMessage->AddText(TString::Format("Hist Can be ignored. Stat is low. entriesInQcHist: %d < Threshold: %d", entriesInQcHist, mStatThresholdPerTrigger)); + // mTrackletPerTriggerMessage->SetTextColor(kYellow); + } else if (entriesInQcHist == 0) { + mTrackletPerTriggerMessage->AddText(TString::Format("Hist is empty. entriesInQcHist: %d < Threshold: %d", entriesInQcHist, mStatThresholdPerTrigger)); + mTrackletPerTriggerMessage->SetTextColor(kRed); + } + + // Warning about triggers without any tracklets + int underFlowTrackletPerTrigger = h->GetBinContent(0); + if (underFlowTrackletPerTrigger > 0.) { + mTrackletPerTriggerMessage->AddText(TString::Format("Number of Triggers without Tracklets: %d", underFlowTrackletPerTrigger)); + } + + // applying check + float meanTrackletPerTrigger = h->GetMean(); + if (meanTrackletPerTrigger > mThresholdMeanLowPerTrigger && meanTrackletPerTrigger < mThresholdMeanHighPerTrigger) { + TText* checkMessagePerTriggerPtr = mTrackletPerTriggerMessage->AddText(TString::Format("Mean Per Trigger: %f is found in bound region [%f, %f]", meanTrackletPerTrigger, mThresholdMeanLowPerTrigger, mThresholdMeanHighPerTrigger)); + checkMessagePerTriggerPtr->SetTextColor(kGreen); + mResultPertrigger = Quality::Good; + } else { + mResultPertrigger = Quality::Bad; + TText* checkMessagePerTriggerPtr = mTrackletPerTriggerMessage->AddText(TString::Format("Mean Per Trigger: %f is not found in bound region [%f, %f]", meanTrackletPerTrigger, mThresholdMeanLowPerTrigger, mThresholdMeanHighPerTrigger)); + checkMessagePerTriggerPtr->SetTextColor(kRed); + mResultPertrigger.addFlag(FlagTypeFactory::Unknown(), "meanTrackletPerTrigger is not in bound region"); + } + h->GetListOfFunctions()->Add(mTrackletPerTriggerMessage->Clone()); + } + if (mo->getName() == "trackletspertimeframe") { + auto* h2 = dynamic_cast(mo->getObject()); + if (h2 == nullptr) { + // ILOG(Debug, Support) << "Requested Histogram type does not match with the Histogram in source" << ENDM; + continue; + } + mTrackletPerTimeFrameMessage.reset(); + if (!mTrackletPerTimeFrameMessage) { + mTrackletPerTimeFrameMessage = std::make_shared(0.3, 0.7, 0.7, 0.9, "NDC"); + } + // mTrackletPerTimeFrameMessage = std::make_shared(0.3, 0.7, 0.7, 0.9, "NDC"); + // mTrackletPerTimeFrameMessage->DeleteText(); + mTrackletPerTimeFrameMessage->SetTextSize(10); + + // Warning about TimeFrame without any tracklets + int underFlowTrackletPerTimeFrame = h2->GetBinContent(0); + if (underFlowTrackletPerTimeFrame > 0.) { + mTrackletPerTimeFrameMessage->AddText(TString::Format("Number of TimeFrames without Tracklets: %d", underFlowTrackletPerTimeFrame)); + } + + // applying check on mean of TrackletPerTimeFrame + float meanTrackletPerTimeframe = h2->GetMean(); + if (meanTrackletPerTimeframe > mThresholdMeanLowPerTimeFrame && meanTrackletPerTimeframe < mThresholdMeanHighPerTimeFrame) { + TText* checkMessagePerTimeframePtr = mTrackletPerTimeFrameMessage->AddText(TString::Format("Mean Per Timeframe: %f is found in bound region [%f, %f]", meanTrackletPerTimeframe, mThresholdMeanLowPerTimeFrame, mThresholdMeanHighPerTimeFrame)); + checkMessagePerTimeframePtr->SetTextColor(kGreen); + + bool isDistributionAccepted = isTrackletDistributionAccepeted(mTrackletPerTimeFrameThreshold, mRatioThreshold, mZeroBinRatioThreshold, h2); + if (isDistributionAccepted) { + mResultPerTimeFrame = Quality::Good; + } else { + TText* checkMessagePerTimeframePtr2 = mTrackletPerTimeFrameMessage->AddText("TrackletPerTimeFrame distribution in not accepeted as per given config"); + checkMessagePerTimeframePtr2->SetTextColor(kRed); + mResultPerTimeFrame = Quality::Bad; + mResultPerTimeFrame.addFlag(FlagTypeFactory::Unknown(), "TrackletPerTimeFrame distribution in not accepet"); + } + + } else { + mResultPerTimeFrame = Quality::Bad; + TText* checkMessagePerTimeframePtr = mTrackletPerTimeFrameMessage->AddText(TString::Format("Mean per Timeframe: %f is not found in bound region[%f, %f]", meanTrackletPerTimeframe, mThresholdMeanLowPerTimeFrame, mThresholdMeanHighPerTimeFrame)); + checkMessagePerTimeframePtr->SetTextColor(kRed); + mResultPerTimeFrame.addFlag(FlagTypeFactory::Unknown(), "meanTrackletPerTimeframe is not in bound region"); + } + h2->GetListOfFunctions()->Add(mTrackletPerTimeFrameMessage->Clone()); + } + } + if (mResultPertrigger == Quality::Null && mResultPerTimeFrame == Quality::Null) { + mFinalResult = Quality::Null; + mFinalResult.addFlag(FlagTypeFactory::Unknown(), "Quality of both trackletspertimeframe and trackletsperevent is unknown"); + } else if ((mResultPertrigger == Quality::Null && mResultPerTimeFrame == Quality::Good) || (mResultPertrigger == Quality::Good && mResultPerTimeFrame == Quality::Null)) { + mFinalResult = Quality::Medium; + mFinalResult.addFlag(FlagTypeFactory::Unknown(), "Quality of any of trackletspertimeframe and trackletsperevent is unknown"); + } else if (mResultPertrigger == Quality::Bad || mResultPerTimeFrame == Quality::Bad) { + mFinalResult = Quality::Bad; + mFinalResult.addFlag(FlagTypeFactory::Unknown(), "Quality of both or any of trackletspertimeframe and trackletsperevent is bad"); + } else { + mFinalResult = Quality::Good; + } + return mFinalResult; +} + +void TrackletCountCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "trackletsperevent") { + auto* h1 = dynamic_cast(mo->getObject()); + if (h1 == nullptr) { + // ILOG(Debug, Support) << "Requested Histogram type does not match with the Histogram in source" << ENDM; + return; + } + if (mResultPertrigger == Quality::Good) { + h1->SetFillColor(kGreen); + } else if (mResultPertrigger == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red" << ENDM; + h1->SetFillColor(kRed); + } else if (mResultPertrigger == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange" << ENDM; + h1->SetFillColor(kOrange); + } + h1->SetLineColor(kBlack); + } + if (mo->getName() == "trackletspertimeframe") { + auto* h2 = dynamic_cast(mo->getObject()); + if (h2 == nullptr) { + // ILOG(Debug, Support) << "Requested Histogram type does not match with the Histogram in source" << ENDM; + return; + } + if (mResultPerTimeFrame == Quality::Good) { + h2->SetFillColor(kGreen); + } else if (mResultPerTimeFrame == Quality::Bad) { + ILOG(Debug, Devel) << "Quality::Bad, setting to red" << ENDM; + h2->SetFillColor(kRed); + } else if (mResultPerTimeFrame == Quality::Medium) { + ILOG(Debug, Devel) << "Quality::medium, setting to orange" << ENDM; + h2->SetFillColor(kOrange); + } + h2->SetLineColor(kBlack); + } +} + +void TrackletCountCheck::reset() +{ + ILOG(Debug, Devel) << "TrackletCountCheck::reset" << ENDM; +} + +void TrackletCountCheck::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "TrackletCountCheck::start : " << activity.mId << ENDM; + mActivity = make_shared(activity); + + ILOG(Debug, Devel) << "initializing TrackletCountCheck" << ENDM; + + mThresholdMeanHighPerTimeFrame = std::stof(mCustomParameters.atOptional("UpperthresholdPerTimeFrame", *mActivity).value_or("6.0e5")); + ILOG(Debug, Support) << "using Upperthreshold Per Timeframe= " << mThresholdMeanHighPerTimeFrame << ENDM; + + mThresholdMeanLowPerTimeFrame = std::stof(mCustomParameters.atOptional("LowerthresholdPerTimeFrame", *mActivity).value_or("5.0e5")); + ILOG(Debug, Support) << "using Lowerthreshold Per Timeframe= " << mThresholdMeanLowPerTimeFrame << ENDM; + + mThresholdMeanHighPerTrigger = std::stof(mCustomParameters.atOptional("UpperthresholdPerTrigger", *mActivity).value_or("2.0e4")); + ILOG(Debug, Support) << "using Upperthreshold Per Trigger= " << mThresholdMeanHighPerTrigger << ENDM; + + mThresholdMeanLowPerTrigger = std::stof(mCustomParameters.atOptional("LowerthresholdPerTrigger", *mActivity).value_or("1.0e4")); + ILOG(Debug, Support) << "using Lowerthreshold Per Trigger= " << mThresholdMeanLowPerTrigger << ENDM; + + mStatThresholdPerTrigger = std::stof(mCustomParameters.atOptional("StatThresholdPerTrigger", *mActivity).value_or("1.0e3")); + ILOG(Debug, Support) << "using StatThreshold Per Trigger= " << mStatThresholdPerTrigger << ENDM; + + // to check if TimeFrames with lower tracklets are higher in number wrt TimeFrames with higher tracklets + // (the boundary is "mTrackletPerTimeFrameThreshold") + mTrackletPerTimeFrameThreshold = std::stof(mCustomParameters.atOptional("trackletPerTimeFrameThreshold", *mActivity).value_or("5.5e5")); + + // 90% of counts must be above the threshold + mRatioThreshold = std::stof(mCustomParameters.atOptional("ratiothreshold", *mActivity).value_or("0.9")); + + // 1% of counts can be in this bin + mZeroBinRatioThreshold = std::stof(mCustomParameters.atOptional("zerobinratiotheshold", *mActivity).value_or("0.1")); +} + +void TrackletCountCheck::endOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "TrackletCountCheck::end : " << activity.mId << ENDM; +} + +bool TrackletCountCheck::isTrackletDistributionAccepeted(float trackletPerTimeFrameThreshold, float acceptedRatio, float zeroTrackletTfRatio, TH1F* hist) +{ + float ratio = -999.; + + int nBins = hist->GetNbinsX(); + float xMax = hist->GetXaxis()->GetXmax(); + float xMin = hist->GetXaxis()->GetXmin(); + float binWidth = (xMax - xMin) / nBins; + + int trackletPerTimeFrameThresholdBin = int(trackletPerTimeFrameThreshold / binWidth); + float integralOnleftSide = hist->Integral(0, trackletPerTimeFrameThresholdBin); + float integralOnRightSide = hist->Integral(trackletPerTimeFrameThresholdBin, hist->GetXaxis()->GetXmax()); + ILOG(Debug, Support) << "integralOnleftSide: " << integralOnleftSide << " integralOnRightSide: " << integralOnRightSide << ENDM; + + if (integralOnleftSide * integralOnRightSide > 0.) { + ratio = integralOnleftSide / integralOnRightSide; + ILOG(Debug, Support) << "ratio (TFs with lower tracklets/ TFs with higher tracklets): " << ratio << " Threshold limit: " << acceptedRatio << ENDM; + } + bool zeroTrackletRatio = isTimeframeRatioWOTrackletAccepted(zeroTrackletTfRatio, hist); + if (!(ratio < 0.) && (ratio <= acceptedRatio)) { + if (zeroTrackletRatio) { + return true; + } else { + ILOG(Warning, Support) << "TFs with lower tracklets are still within threshold but TFs without tracklets are higher" << ENDM; + return false; + } + } else if (!(ratio < 0) && (ratio > acceptedRatio)) { + ILOG(Warning, Support) << "TFs with lower tracklets are not within threshold " << ENDM; + return false; + } + ILOG(Warning, Support) << "ratio is still not updated, Some region is still empty in TrackletPerTimeFrame object" << ENDM; + return false; +} + +bool TrackletCountCheck::isTimeframeRatioWOTrackletAccepted(float zeroTrackletTfRatio, TH1F* hist) +{ + auto integral = hist->Integral(1, hist->GetXaxis()->GetXmax()); + if (integral <= 0.0) { + integral = 1; + } + auto tFsWOTracklets = hist->GetBinContent(0); + if ((tFsWOTracklets / integral) <= zeroTrackletTfRatio) { + return true; + } + ILOG(Warning, Support) << "Ratio of No. of TFs without trackets to the total TFs: " << (tFsWOTracklets / integral) << " is higher than the threshold limit: " << zeroTrackletTfRatio << ENDM; + return false; +} + +} // namespace o2::quality_control_modules::trd \ No newline at end of file diff --git a/Modules/TRD/src/TrackletsTask.cxx b/Modules/TRD/src/TrackletsTask.cxx new file mode 100644 index 0000000000..c9201794a0 --- /dev/null +++ b/Modules/TRD/src/TrackletsTask.cxx @@ -0,0 +1,226 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrackletsTask.cxx +/// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "Common/Utils.h" +#include "TRD/TrackletsTask.h" +#include "TRD/TRDHelpers.h" +#include "TRDQC/StatusHelper.h" +#include +#include +#include "DataFormatsTRD/Tracklet64.h" +#include "DataFormatsTRD/HelperMethods.h" +#include "DataFormatsTRD/Digit.h" +#include "DataFormatsTRD/NoiseCalibration.h" +#include "DataFormatsTRD/TriggerRecord.h" + +using namespace o2::quality_control_modules::common; +using namespace o2::trd::constants; +using Helper = o2::trd::HelperMethods; + +namespace o2::quality_control_modules::trd +{ + +void TrackletsTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize TrackletsTask" << ENDM; + mRemoveNoise = getFromConfig(mCustomParameters, "removeNoise", false); + buildHistograms(); +} + +void TrackletsTask::buildHistograms() +{ + constexpr int nLogBins = 100; + float xBins[nLogBins + 1]; + float xBinLogMin = 0.f; + float xBinLogMax = 8.f; + float logBinWidth = (xBinLogMax - xBinLogMin) / nLogBins; + for (int iBin = 0; iBin <= nLogBins; ++iBin) { + xBins[iBin] = TMath::Power(10, xBinLogMin + iBin * logBinWidth); + } + mTrackletSlope.reset(new TH1F("trackletslope", "Tracklet inclination in natural units;pads per time bin;counts", 100, -0.15, 0.15)); + getObjectsManager()->startPublishing(mTrackletSlope.get()); + + mTrackletHCID.reset(new TH1F("tracklethcid", "Tracklet distribution over Halfchambers;HalfChamber ID;counts", 1080, -0.5, 1079.5)); + getObjectsManager()->startPublishing(mTrackletHCID.get()); + + mTrackletPosition.reset(new TH1F("trackletpos", "Tracklet position relative to MCM center;number of pads;counts", 200, -30, 30)); + getObjectsManager()->startPublishing(mTrackletPosition.get()); + + mTrackletsPerEvent.reset(new TH1F("trackletsperevent", "Number of Tracklets per event;Tracklets in Event;Counts", nLogBins, xBins)); + getObjectsManager()->startPublishing(mTrackletsPerEvent.get()); + getObjectsManager()->setDefaultDrawOptions(mTrackletsPerEvent->GetName(), "logx"); + + mTrackletsPerEventPP.reset(new TH1F("trackletspereventPP", "Number of Tracklets per event;Tracklets in Event;Counts", 1000, 0, 5000)); + getObjectsManager()->startPublishing(mTrackletsPerEventPP.get()); + + mTrackletsPerEventPbPb.reset(new TH1F("trackletspereventPbPb", "Number of Tracklets per event;Tracklets in Event;Counts", 1000, 0, 100000)); + getObjectsManager()->startPublishing(mTrackletsPerEventPbPb.get()); + + mTrackletsPerHC2D.reset(new TH2F("trackletsperHC2D", "Tracklets distribution in half-chambers;Sector_Side;Stack_Side", 36, 0, 36, 30, 0, 30)); + mTrackletsPerHC2D->SetStats(0); + mTrackletsPerHC2D->GetXaxis()->SetTitle("Sector_Side"); + mTrackletsPerHC2D->GetXaxis()->CenterTitle(kTRUE); + mTrackletsPerHC2D->GetYaxis()->SetTitle("Stack_Layer"); + mTrackletsPerHC2D->GetYaxis()->CenterTitle(kTRUE); + for (int s = 0; s < NSTACK; ++s) { + for (int l = 0; l < NLAYER; ++l) { + std::string label = fmt::format("{0}_{1}", s, l); + int pos = s * NLAYER + l + 1; + mTrackletsPerHC2D->GetYaxis()->SetBinLabel(pos, label.c_str()); + } + } + + for (int sm = 0; sm < NSECTOR; ++sm) { + for (int side = 0; side < 2; ++side) { + std::string label = fmt::format("{0}_{1}", sm, side == 0 ? "A" : "B"); + int pos = sm * 2 + side + 1; + mTrackletsPerHC2D->GetXaxis()->SetBinLabel(pos, label.c_str()); + } + } + + getObjectsManager()->startPublishing(mTrackletsPerHC2D.get()); + getObjectsManager()->setDefaultDrawOptions(mTrackletsPerHC2D->GetName(), "COLZ"); + getObjectsManager()->setDisplayHint(mTrackletsPerHC2D->GetName(), "logz"); + + for (int chargeWindow = 0; chargeWindow < 3; ++chargeWindow) { + mTrackletQ[chargeWindow].reset(new TH1F(Form("TrackletQ%i", chargeWindow), Form("Tracklet Q%i;charge (a.u.);counts", chargeWindow), 256, -0.5, 255.5)); + getObjectsManager()->startPublishing(mTrackletQ[chargeWindow].get()); + getObjectsManager()->setDefaultDrawOptions(mTrackletQ[chargeWindow]->GetName(), "logy"); + } + + mTrackletsPerTimeFrame.reset(new TH1F("trackletspertimeframe", "Number of Tracklets per timeframe;Tracklets in TimeFrame;Counts", nLogBins, xBins)); + getObjectsManager()->startPublishing(mTrackletsPerTimeFrame.get()); + getObjectsManager()->setDefaultDrawOptions(mTrackletsPerTimeFrame->GetName(), "logx"); + + mTriggersPerTimeFrame.reset(new TH1F("triggerspertimeframe", "Number of Triggers per timeframe;Triggers in TimeFrame;Counts", 1000, 0, 1000)); + getObjectsManager()->startPublishing(mTriggersPerTimeFrame.get()); + + // Build tracklet layers + int unitsPerSection = NCOLUMN / NSECTOR; + for (int iLayer = 0; iLayer < NLAYER; ++iLayer) { + mLayers[iLayer].reset(new TH2F(Form("TrackletsPerMCM_Layer%i", iLayer), Form("Tracklet count per MCM in layer %i;glb pad row;glb MCM col", iLayer), + 76, -0.5, 75.5, NCOLUMN, -0.5, NCOLUMN - 0.5)); + mLayers[iLayer]->SetStats(0); + TRDHelpers::addChamberGridToHistogram(mLayers[iLayer], unitsPerSection); + getObjectsManager()->startPublishing(mLayers[iLayer].get()); + getObjectsManager()->setDefaultDrawOptions(mLayers[iLayer]->GetName(), "COLZ"); + getObjectsManager()->setDisplayHint(mLayers[iLayer].get(), "logz"); + } +} + +void TrackletsTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + // Load CCDB objects (needs to be done only once) + if (!mNoiseMap) { + auto ptr = ctx.inputs().get("noiseMap"); + mNoiseMap = ptr.get(); + } + + auto ptr = ctx.inputs().get*>("fedChamberStatus"); + + // Fill histograms + auto tracklets = ctx.inputs().get>("tracklets"); + auto triggerrecords = ctx.inputs().get>("triggers"); + mTrackletsPerTimeFrame->Fill(tracklets.size()); + mTriggersPerTimeFrame->Fill(triggerrecords.size()); + + for (const auto& trigger : triggerrecords) { + mTrackletsPerEvent->Fill(trigger.getNumberOfTracklets()); + mTrackletsPerEventPP->Fill(trigger.getNumberOfTracklets()); + mTrackletsPerEventPbPb->Fill(trigger.getNumberOfTracklets()); + for (int currenttracklet = trigger.getFirstTracklet(); currenttracklet < trigger.getFirstTracklet() + trigger.getNumberOfTracklets(); ++currenttracklet) { + const auto& trklt = tracklets[currenttracklet]; + if (mNoiseMap != nullptr && mRemoveNoise && mNoiseMap->isTrackletFromNoisyMCM(trklt)) { + continue; + } + int hcid = trklt.getHCID(); + int layer = Helper::getLayer(hcid / 2); + int stack = Helper::getStack(hcid / 2); + int sector = Helper::getSector(hcid / 2); + int stackLayer = stack * NLAYER + layer; + int sectorSide = sector * 2 + (hcid % 2); + int rowGlb = FIRSTROW[stack] + trklt.getPadRow(); + int colGlb = trklt.getMCMCol() + sector * 8; + mTrackletsPerHC2D->Fill(sectorSide, stackLayer); + mTrackletSlope->Fill(trklt.getSlopeFloat()); + mTrackletPosition->Fill(trklt.getPositionFloat()); + mTrackletHCID->Fill(hcid); + mTrackletQ[0]->Fill(trklt.getQ0()); + mTrackletQ[1]->Fill(trklt.getQ1()); + mTrackletQ[2]->Fill(trklt.getQ2()); + mLayers[layer]->Fill(rowGlb, colGlb); + } + } +} + +void TrackletsTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; +} + +void TrackletsTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void TrackletsTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void TrackletsTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void TrackletsTask::finaliseCCDB(o2::framework::ConcreteDataMatcher& matcher, void* obj) +{ + if (matcher == o2::framework::ConcreteDataMatcher("TRD", "FCHSTATUS", 0)) { + TRDHelpers::drawChamberStatusOnHistograms(static_cast*>(obj), mTrackletsPerHC2D, mLayers, NCOLUMN / NSECTOR); + } +} + +void TrackletsTask::reset() +{ + // clean all the monitor objects here + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + mTrackletSlope->Reset(); + mTrackletHCID->Reset(); + mTrackletPosition->Reset(); + mTrackletsPerEvent->Reset(); + mTrackletsPerEventPP->Reset(); + mTrackletsPerEventPbPb->Reset(); + mTrackletsPerHC2D->Reset(); + mTrackletsPerTimeFrame->Reset(); + mTriggersPerTimeFrame->Reset(); + for (auto h : mLayers) { + h->Reset(); + } + for (auto h : mTrackletQ) { + h->Reset(); + } +} + +} // namespace o2::quality_control_modules::trd diff --git a/Modules/TRD/src/TrendingTaskConfigTRD.cxx b/Modules/TRD/src/TrendingTaskConfigTRD.cxx new file mode 100644 index 0000000000..181e7c26ae --- /dev/null +++ b/Modules/TRD/src/TrendingTaskConfigTRD.cxx @@ -0,0 +1,79 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file TrendingTaskConfigTRD.cxx +/// \author based on Piotr Konopka work +/// + +#include "TRD/TrendingTaskConfigTRD.h" +#include +using boost::property_tree::ptree; +namespace o2::quality_control::postprocessing +{ + +TrendingTaskConfigTRD::TrendingTaskConfigTRD(std::string id, const boost::property_tree::ptree& config) + : PostProcessingConfig(id, config) +{ + + for (const auto& plotConfig : config.get_child("qc.postprocessing." + id + ".plots")) { + + if (const auto& sourceNames = plotConfig.second.get_child_optional("names"); + sourceNames.has_value()) { + const auto& sourceVarexps = + plotConfig.second.get_child_optional("varexp"); // take all varexps + const auto& sourceTitles = + plotConfig.second.get_child_optional("title"); // take all titles + + ptree::const_iterator itname = sourceNames.value().begin(); + ptree::const_iterator itexp = sourceVarexps.value().begin(); + ptree::const_iterator ittitle = sourceTitles.value().begin(); + + while (itname != sourceNames.value().end() || + itexp != sourceVarexps.value().end() || + ittitle != sourceTitles.value().end()) { + plots.push_back({ itname->second.data(), ittitle->second.data(), + itexp->second.data(), + plotConfig.second.get("selection", ""), + plotConfig.second.get("option", ""), + plotConfig.second.get("graphErrors", "") }); + + itname++; + itexp++; + ittitle++; + } + } + } + + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + id + ".dataSources")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + + for (const auto& sourceName : sourceNames.value()) { + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + sourceName.second.data(), + dataSourceConfig.second.get("reductorName"), + dataSourceConfig.second.get("moduleName") }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSources.push_back({ dataSourceConfig.second.get("type", "repository"), + dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name"), + dataSourceConfig.second.get("reductorName"), + dataSourceConfig.second.get("moduleName") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + id + ".dataSources'"); + } + } +} + +} // namespace o2::quality_control::postprocessing diff --git a/Modules/TRD/test/testQcTRD.cxx b/Modules/TRD/test/testQcTRD.cxx new file mode 100644 index 0000000000..4bfdc86568 --- /dev/null +++ b/Modules/TRD/test/testQcTRD.cxx @@ -0,0 +1,14 @@ +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::trd +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::trd diff --git a/Modules/ZDC/CMakeLists.txt b/Modules/ZDC/CMakeLists.txt new file mode 100644 index 0000000000..ce600bef8c --- /dev/null +++ b/Modules/ZDC/CMakeLists.txt @@ -0,0 +1,49 @@ +# ---- Library ---- + +add_library(O2QcZDC) + +target_sources(O2QcZDC PRIVATE src/ZDCRecBeautifyPlots.cxx src/ZDCRecDataCheck.cxx src/ZDCRecDataTask.cxx src/ZDCRecDataPostProcessing.cxx src/PostProcessingConfigZDC.cxx src/ZDCRawDataCheck.cxx src/ZDCRawDataTask.cxx ) + +target_include_directories( + O2QcZDC + PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +target_link_libraries(O2QcZDC PUBLIC O2QualityControl O2::DataFormatsZDC) + +install(TARGETS O2QcZDC + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_root_dictionary(O2QcZDC + HEADERS + include/ZDC/ZDCRecBeautifyPlots.h + include/ZDC/ZDCRecDataCheck.h + include/ZDC/ZDCRecDataTask.h + include/ZDC/ZDCRecDataPostProcessing.h + include/ZDC/ZDCRawDataCheck.h + include/ZDC/ZDCRawDataTask.h + LINKDEF include/ZDC/LinkDef.h) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ZDC + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/QualityControl") + +# ---- Test(s) ---- + +#set(TEST_SRCS test/testQcZDC.cxx) # uncomment to reenable the test which was empty + +foreach(test ${TEST_SRCS}) + get_filename_component(test_name ${test} NAME) + string(REGEX REPLACE ".cxx" "" test_name ${test_name}) + + add_executable(${test_name} ${test}) + target_link_libraries(${test_name} + PRIVATE O2QcZDC Boost::unit_test_framework) + add_test(NAME ${test_name} COMMAND ${test_name}) + set_property(TARGET ${test_name} + PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + set_tests_properties(${test_name} PROPERTIES TIMEOUT 20) +endforeach() + diff --git a/Modules/ZDC/etc/zdcTaskRawData.json b/Modules/ZDC/etc/zdcTaskRawData.json new file mode 100755 index 0000000000..8b1c140279 --- /dev/null +++ b/Modules/ZDC/etc/zdcTaskRawData.json @@ -0,0 +1,151 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "42", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "Raw": { + "active": "true", + "className": "o2::quality_control_modules::zdc::ZDCRawDataTask", + "moduleName": "QcZDC", + "detectorName": "ZDC", + "cycleDurationSeconds": "60", + "": "The other type of dataSource is \"direct\", see basic-no-sampling.json.", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "zdc-raw" + }, + "taskParameters": { + "BASELINE": "4096;-2048.5;2047.5", + "COUNTS": "30;-0.5;29.5", + "SIGNAL": "60;-36.5;23.5;4096;-2048.5;2047.5", + "BUNCH": "100;-0.5;99.5;36;-35.5;0.5", + "TRASMITTEDCHANNEL": "8;-0.5;7.5;4;-0.5;3.5", + "FIREDCHANNEL": "8;-0.5;7.5;4;-0.5;3.5", + "DATALOSS": "8;-0.5;7.5;4;-0.5;3.5", + "TRIGGER_BIT": "32;-0.5;31.5;10;-0.5; 9.5", + "TRIGGER_BIT_HIT": "32;-0.5;31.5;10;-0.5; 9.5", + "OVER_BC": "32;-0.5;31.5", + "SUMMARYBASELINE": "26;-0.5;25.5", + "SUMMARYRATE": "26;-0.5;25.5", + "SUMMARY_ALIGN": "26;0.5; 26.5;12;-0.5;11.5", + "ALIGN_NUM_CYCLE": "1", + "ALIGN_NUM_ENTRIES": "1000", + "ERROR_NUM_CYCLE": "5" + }, + "location": "remote", + "saveObjectsToFile": "QcZDCRawData.root", "": "For debugging, path to the file where to save. If empty it won't save." + } + }, + "checks": { + "QcZDCRawCheck": { + "active": "true", + "className": "o2::quality_control_modules::zdc::ZDCRawDataCheck", + "moduleName": "QcZDC", + "detectorName" : "ZDC", + "policy": "OnAny", + "checkParameters" : { + "PED_ZNAC" : "1118.48;10;20", + "PED_ZNA1" : "270.66;10;20", + "PED_ZNA2" : "262.84;10;20", + "PED_ZNAS" : "1074.55;10;20", + "PED_ZNA3" : "256.30;10;20", + "PED_ZNA4" : "255.04;10;20", + "PED_ZNCC" : "1086.07;10;20", + "PED_ZNC1" : "262.46;10;20", + "PED_ZNC2" : "230.21;10;20", + "PED_ZNCS" : "1058.64;10;20", + "PED_ZNC3" : "194.54;10;20", + "PED_ZNC4" : "180.66;10;20", + "PED_ZPAC" : "1741;10;20", + "PED_ZEM1" : "1104.89;10;20", + "PED_ZPA1" : "356.3;10;20", + "PED_ZPA2" : "301.7;10;20", + "PED_ZPAS" : "1688;10;20", + "PED_ZPA3" : "355.6;10;20", + "PED_ZPA4" : "1703;10;20", + "PED_ZPCC" : "1787;10;20", + "PED_ZEM2" : "1062.12;10;20", + "PED_ZPC3" : "343.9;10;20", + "PED_ZPC4" : "335.5;10;20", + "PED_ZPCS" : "1686;10;20", + "PED_ZPC1" : "1742;10;20", + "PED_ZPC2" : "316.1;10;20", + "PED_POS_MSG_X": "0.01", + "PED_POS_MSG_Y": "0.92", + "ALIGN_ZNAC" : "6;1;2", + "ALIGN_ZNA1" : "6;1;2", + "ALIGN_ZNA2" : "6;1;2", + "ALIGN_ZNAS" : "6;1;2", + "ALIGN_ZNA3" : "6;1;2", + "ALIGN_ZNA4" : "6;1;2", + "ALIGN_ZNCC" : "6;1;2", + "ALIGN_ZNC1" : "6;1;2", + "ALIGN_ZNC2" : "6;1;2", + "ALIGN_ZNCS" : "6;1;2", + "ALIGN_ZNC3" : "6;1;2", + "ALIGN_ZNC4" : "6;1;2", + "ALIGN_ZPAC" : "6;1;2", + "ALIGN_ZEM1" : "6;1;2", + "ALIGN_ZPA1" : "6;1;2", + "ALIGN_ZPA2" : "6;1;2", + "ALIGN_ZPAS" : "6;1;2", + "ALIGN_ZPA3" : "6;1;2", + "ALIGN_ZPA4" : "6;1;2", + "ALIGN_ZPCC" : "6;1;2", + "ALIGN_ZEM2" : "6;1;2", + "ALIGN_ZPC3" : "6;1;2", + "ALIGN_ZPC4" : "6;1;2", + "ALIGN_ZPCS" : "6;1;2", + "ALIGN_ZPC1" : "6;1;2", + "ALIGN_ZPC2" : "6;1;2", + "ALIGN_POS_MSG_X": "0.01", + "ALIGN_POS_MSG_Y": "0.92", + "ERROR_POS_MSG_X": "0.01", + "ERROR_POS_MSG_Y": "0.92" + }, + "dataSource": [{ + "type": "Task", + "name": "Raw", + "MOs": ["hpedSummary", "hAlignPlotShift", "herrorSummary"] + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "zdc-raw", + "active": "true", + "machines": [], + "query": "random:ROUT/RAWDATA", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + "seed": "0" + } + ], + "blocking": "false" + } + ] +} + diff --git a/Modules/ZDC/etc/zdcTaskRecData.json b/Modules/ZDC/etc/zdcTaskRecData.json new file mode 100755 index 0000000000..bc3897664a --- /dev/null +++ b/Modules/ZDC/etc/zdcTaskRecData.json @@ -0,0 +1,275 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "50", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "Rec": { + "active": "true", + "className": "o2::quality_control_modules::zdc::ZDCRecDataTask", + "moduleName": "QcZDC", + "detectorName": "ZDC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "zdc-rec" + }, + "taskParameters": { + "ADC": "3600; -3;357", + "ADCZEM": "3025;-100;12000", + "ADCH": "2300;-3;20", + "TDCT": "2400;-13.5;11.45", + "TDCA": "3600; -3;357", + "TDCAZEM": "2050;-0.5;4099.5", + "TDCAH": "2300;-3;20", + "ADCSUMvsTC": "144;-3;357;144;-3;357", + "ADCZEMvsADCZEM": "121;-100;12000;121;-100;12000", + "ADCZEMvsTC": "144;-3;357;121;-100;12000", + "ADCvsTDCT": "120;-13.5;11.45;144;-3;357", + "ADCZEMvsTDCT": "120;-13.5;11.45;121;-100;12000", + "TDCDIFF": "240;-27;22.90;240;-27;22.90", + "TDCAvsTDCT": "120;-13.5;11.45;144;-3;357", + "TDCAZEMvsTDCT": "120;-13.5;11.45;125;-1;3999", + "TDCAvsTDCA": "144;-3;357;144;-3;357", + "TDCAZEMvsTDCAZEM": "125;-1;3999;125;-1;3999", + "TDCAZEMvsTDCA": "144;-3;357;125;-1;3999", + "CENTR_ZNA": "200;-2;2;200;-2;2", + "CENTR_ZNC": "200;-2;2;200;-2;2", + "CENTR_ZPA": "2240;0;22.4", + "CENTR_ZPC": "2240;-22.4;0" + }, + "location": "remote", + "saveObjectsToFile": "QcZDCRecData.root", "": "For debugging, path to the file where to save. If empty it won't save." + } + }, + "postprocessing": { + "RecPP": { + "active": "true", + "className": "o2::quality_control_modules::zdc::ZDCRecDataPostProcessing", + "moduleName": "QcZDC", + "detectorName": "ZDC", + "customization": [ + ], + "dataSourcesADC": [ + { + "type": "repository", + "path": "ZDC/MO/Rec", + "names": [ + "ZNAC:h_ADC_ZNA_TC", + "ZNA1:h_ADC_ZNA_T1", + "ZNA2:h_ADC_ZNA_T2", + "ZNA3:h_ADC_ZNA_T3", + "ZNA4:h_ADC_ZNA_T4", + "ZNAS:h_ADC_ZNA_SUM", + "ZPAC:h_ADC_ZPA_TC", + "ZPA1:h_ADC_ZPA_T1", + "ZPA2:h_ADC_ZPA_T2", + "ZPA3:h_ADC_ZPA_T3", + "ZPA4:h_ADC_ZPA_T4", + "ZPAS:h_ADC_ZPA_SUM", + "ZEM1:h_ADC_ZEM1", + "ZEM2:h_ADC_ZEM2", + "ZNCC:h_ADC_ZNC_TC", + "ZNC1:h_ADC_ZNC_T1", + "ZNC2:h_ADC_ZNC_T2", + "ZNC3:h_ADC_ZNC_T3", + "ZNC4:h_ADC_ZNC_T4", + "ZNCS:h_ADC_ZNC_SUM", + "ZPCC:h_ADC_ZPC_TC", + "ZPC1:h_ADC_ZPC_T1", + "ZPC2:h_ADC_ZPC_T2", + "ZPC3:h_ADC_ZPC_T3", + "ZPC4:h_ADC_ZPC_T4", + "ZPCS:h_ADC_ZPC_SUM" + ] + } + ], + "dataSourcesTDC": [ + { + "type": "repository", + "path": "ZDC/MO/Rec", + "names": [ + "ZNAC:h_TDC_ZNA_TC_V", + "ZNAS:h_TDC_ZNA_SUM_V", + "ZPAC:h_TDC_ZPA_TC_V", + "ZPAS:h_TDC_ZPA_SUM_V", + "ZEM1:h_TDC_ZEM1_V", + "ZEM2:h_TDC_ZEM2_V", + "ZNCC:h_TDC_ZNC_TC_V", + "ZNCS:h_TDC_ZNC_SUM_V", + "ZPCC:h_TDC_ZPC_TC_V", + "ZPCS:h_TDC_ZPC_SUM_V" + ] + } + ], + "dataSourcesTDCA": [ + { + "type": "repository", + "path": "ZDC/MO/Rec", + "names": [ + "ZNAC:h_TDC_ZNA_TC_A", + "ZNAS:h_TDC_ZNA_SUM_A", + "ZPAC:h_TDC_ZPA_TC_A", + "ZPAS:h_TDC_ZPA_SUM_A", + "ZEM1:h_TDC_ZEM1_A", + "ZEM2:h_TDC_ZEM2_A", + "ZNCC:h_TDC_ZNC_TC_A", + "ZNCS:h_TDC_ZNC_SUM_A", + "ZPCC:h_TDC_ZPC_TC_A", + "ZPCS:h_TDC_ZPC_SUM_A" + ] + } + ], + "dataSourcesPeak1n": [ + { + "type": "repository", + "path": "ZDC/MO/Rec", + "names": [ + "ZNAC:h_TDC_ZNA_TC_A", + "ZNAS:h_TDC_ZNA_SUM_A", + "ZNCC:h_TDC_ZNC_TC_A", + "ZNCS:h_TDC_ZNC_SUM_A" + ] + } + ], + "dataSourcesPeak1p": [ + { + "type": "repository", + "path": "ZDC/MO/Rec", + "names": [ + "ZPAC:h_TDC_ZPA_TC_A", + "ZPAS:h_TDC_ZPA_SUM_A", + "ZPCC:h_TDC_ZPC_TC_A", + "ZPCS:h_TDC_ZPC_SUM_A" + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:ZDC/MO/Rec/h_ADC_ZNA_TC" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks": { + "RecCheck": { + "active": "true", + "className": "o2::quality_control_modules::zdc::ZDCRecDataCheck", + "moduleName": "QcZDC", + "detectorName" : "ZDC", + "policy": "OnAll", + "checkParameters" : { + "ADC_ZNAC" : "40.2;10;20", + "ADC_ZNA1" : "10.6;10;20", + "ADC_ZNA2" : "11.8;10;20", + "ADC_ZNA3" : "8.6;10;20", + "ADC_ZNA4" : "6.9;10;20", + "ADC_ZNAS" : "41.7;10;20", + "ADC_ZPAC" : "17.7;10;20", + "ADC_ZPA1" : "1.63;10;20", + "ADC_ZPA2" : "2.86;10;20", + "ADC_ZPA3" : "5.30;10;20", + "ADC_ZPA4" : "12.25;10;20", + "ADC_ZPAS" : "20.2;10;20", + "ADC_ZEM1" : "803.2;10;20", + "ADC_ZEM2" : "891.2;10;20", + "ADC_ZNCC" : "40.7;10;20", + "ADC_ZNC1" : "9.7;10;20", + "ADC_ZNC2" : "15.8;10;20", + "ADC_ZNC3" : "6.2;10;20", + "ADC_ZNC4" : "8.6;10;20", + "ADC_ZNCS" : "42.3;10;20", + "ADC_ZPCC" : "19.6;10;20", + "ADC_ZPC1" : "14.19;10;20", + "ADC_ZPC2" : "7.63;10;20", + "ADC_ZPC3" : "2.96;10;20", + "ADC_ZPC4" : "1.21;10;20", + "ADC_ZPCS" : "20.9;10;20", + "ADC_POS_MSG_X": "0.15", + "ADC_POS_MSG_Y": "0.92", + "TDC_ZNAC" : "0.14;0.10;0.20", + "TDC_ZNAS" : "0.05;0.10;0.20", + "TDC_ZPAC" : "-0.39;0.10;0.20", + "TDC_ZPAS" : "-0.11;0.10;0.20", + "TDC_ZEM1" : "0.07;0.10;0.20", + "TDC_ZEM2" : "0.02;0.10;0.20", + "TDC_ZNCC" : "0.19;0.10;0.20", + "TDC_ZNCS" : "0.08;0.10;0.20", + "TDC_ZPCC" : "-0.29;0.10;0.20", + "TDC_ZPCS" : "-0.20;0.10;0.20", + "TDC_POS_MSG_X": "0.01", + "TDC_POS_MSG_Y": "0.92", + "TDCA_ZNAC" : "326;10;20", + "TDCA_ZNAS" : "331;10;20", + "TDCA_ZPAC" : "130;10;20", + "TDCA_ZPAS" : "146;10;20", + "TDCA_ZEM1" : "1203;10;20", + "TDCA_ZEM2" : "1265;10;20", + "TDCA_ZNCC" : "329;10;20", + "TDCA_ZNCS" : "339;10;20", + "TDCA_ZPCC" : "146;10;20", + "TDCA_ZPCS" : "150;10;20", + "TDCA_POS_MSG_X": "0.01", + "TDCA_POS_MSG_Y": "0.92", + "PEAK1N_ZNAC" : "40;10;20", + "PEAK1N_ZNAS" : "40;10;20", + "PEAK1N_ZNCC" : "40;10;20", + "PEAK1N_ZNCS" : "40;10;20", + "PEAK1N_POS_MSG_X": "0.01", + "PEAK1N_POS_MSG_Y": "0.92", + "PEAK1P_ZPAC" : "52;10;20", + "PEAK1P_ZPAS" : "52;10;20", + "PEAK1P_ZPCC" : "52;10;20", + "PEAK1P_ZPCS" : "52;10;20", + "PEAK1P_POS_MSG_X": "0.01", + "PEAK1P_POS_MSG_Y": "0.92" + }, + "dataSource": [{ + "type": "PostProcessing", + "name": "RecPP", + "MOs": ["h_summary_ADC" , "h_summary_TDC", "h_summary_TDCA", "h_summary_Peak1n", "h_summary_Peak1p"] + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "zdc-rec", + "active": "true", + "machines": [], + "query": "zdc-bcrec:ZDC/BCREC/0;zdc-energyrec:ZDC/ENERGY/0;zdc-tdcrec:ZDC/TDCDATA/0;zdc-inforec:ZDC/INFO/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.3", + "seed": "0" + } + ], + "blocking": "false" + } + ] +} + diff --git a/Modules/ZDC/etc/zdcTaskRecDataPbPb.json b/Modules/ZDC/etc/zdcTaskRecDataPbPb.json new file mode 100755 index 0000000000..1cca5be14c --- /dev/null +++ b/Modules/ZDC/etc/zdcTaskRecDataPbPb.json @@ -0,0 +1,275 @@ +{ + "qc": { + "config": { + "database": { + "implementation": "CCDB", + "host": "ccdb-test.cern.ch:8080", + "username": "not_applicable", + "password": "not_applicable", + "name": "not_applicable" + }, + "Activity": { + "number": "59", + "type": "NONE" + }, + "monitoring": { + "url": "infologger:///debug?qc" + }, + "consul": { + "url": "" + }, + "conditionDB": { + "url": "ccdb-test.cern.ch:8080" + } + }, + "tasks": { + "Rec": { + "active": "true", + "className": "o2::quality_control_modules::zdc::ZDCRecDataTask", + "moduleName": "QcZDC", + "detectorName": "ZDC", + "cycleDurationSeconds": "10", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "zdc-rec" + }, + "taskParameters": { + "ADC": "1440; -3;357", + "ADCZEM": "3025;-100;12000", + "ADCH": "115;-3;20", + "TDCT": "2400;-13.5;11.45", + "TDCA": "1440; -3;357", + "TDCAZEM": "2050;-0.5;4099.5", + "TDCAH": "230;-3;20", + "ADCSUMvsTC": "144;-3;357;144;-3;357", + "ADCZEMvsADCZEM": "121;-100;12000;121;-100;12000", + "ADCZEMvsTC": "121;-100;12000;144;-3;357", + "ADCvsTDCT": "120;-13.5;11.45;144;-3;357", + "ADCZEMvsTDCT": "120;-13.5;11.45;121;-100;12000", + "TDCDIFF": "240;-27;22.90;240;-27;22.90", + "TDCAvsTDCT": "120;-13.5;11.45;144;-3;357", + "TDCAZEMvsTDCT": ";125;-1;3999;120;-13.5;11.45", + "TDCAvsTDCA": "144;-3;357;144;-3;357", + "TDCAZEMvsTDCAZEM": "125;-1;3999;125;-1;3999", + "TDCAZEMvsTDCA": "125;-1;3999;144;-3;357;", + "CENTR_ZNA": "200;-2;2;200;-2;2", + "CENTR_ZNC": "200;-2;2;200;-2;2", + "CENTR_ZPA": "2240;0;22.4", + "CENTR_ZPC": "2240;-22.4;0" + }, + "location": "remote", + "saveObjectsToFile": "QcZDCRecData.root", "": "For debugging, path to the file where to save. If empty it won't save." + } + }, + "postprocessing": { + "RecPP": { + "active": "true", + "className": "o2::quality_control_modules::zdc::ZDCRecDataPostProcessing", + "moduleName": "QcZDC", + "detectorName": "ZDC", + "customization": [ + ], + "dataSourcesADC": [ + { + "type": "repository", + "path": "ZDC/MO/Rec", + "names": [ + "ZNAC:h_ADC_ZNA_TC", + "ZNA1:h_ADC_ZNA_T1", + "ZNA2:h_ADC_ZNA_T2", + "ZNA3:h_ADC_ZNA_T3", + "ZNA4:h_ADC_ZNA_T4", + "ZNAS:h_ADC_ZNA_SUM", + "ZPAC:h_ADC_ZPA_TC", + "ZPA1:h_ADC_ZPA_T1", + "ZPA2:h_ADC_ZPA_T2", + "ZPA3:h_ADC_ZPA_T3", + "ZPA4:h_ADC_ZPA_T4", + "ZPAS:h_ADC_ZPA_SUM", + "ZEM1:h_ADC_ZEM1", + "ZEM2:h_ADC_ZEM2", + "ZNCC:h_ADC_ZNC_TC", + "ZNC1:h_ADC_ZNC_T1", + "ZNC2:h_ADC_ZNC_T2", + "ZNC3:h_ADC_ZNC_T3", + "ZNC4:h_ADC_ZNC_T4", + "ZNCS:h_ADC_ZNC_SUM", + "ZPCC:h_ADC_ZPC_TC", + "ZPC1:h_ADC_ZPC_T1", + "ZPC2:h_ADC_ZPC_T2", + "ZPC3:h_ADC_ZPC_T3", + "ZPC4:h_ADC_ZPC_T4", + "ZPCS:h_ADC_ZPC_SUM" + ] + } + ], + "dataSourcesTDC": [ + { + "type": "repository", + "path": "ZDC/MO/Rec", + "names": [ + "ZNAC:h_TDC_ZNA_TC_V", + "ZNAS:h_TDC_ZNA_SUM_V", + "ZPAC:h_TDC_ZPA_TC_V", + "ZPAS:h_TDC_ZPA_SUM_V", + "ZEM1:h_TDC_ZEM1_V", + "ZEM2:h_TDC_ZEM2_V", + "ZNCC:h_TDC_ZNC_TC_V", + "ZNCS:h_TDC_ZNC_SUM_V", + "ZPCC:h_TDC_ZPC_TC_V", + "ZPCS:h_TDC_ZPC_SUM_V" + ] + } + ], + "dataSourcesTDCA": [ + { + "type": "repository", + "path": "ZDC/MO/Rec", + "names": [ + "ZNAC:h_TDC_ZNA_TC_A", + "ZNAS:h_TDC_ZNA_SUM_A", + "ZPAC:h_TDC_ZPA_TC_A", + "ZPAS:h_TDC_ZPA_SUM_A", + "ZEM1:h_TDC_ZEM1_A", + "ZEM2:h_TDC_ZEM2_A", + "ZNCC:h_TDC_ZNC_TC_A", + "ZNCS:h_TDC_ZNC_SUM_A", + "ZPCC:h_TDC_ZPC_TC_A", + "ZPCS:h_TDC_ZPC_SUM_A" + ] + } + ], + "dataSourcesPeak1n": [ + { + "type": "repository", + "path": "ZDC/MO/Rec", + "names": [ + "ZNAC:h_TDC_ZNA_TC_A", + "ZNAS:h_TDC_ZNA_SUM_A", + "ZNCC:h_TDC_ZNC_TC_A", + "ZNCS:h_TDC_ZNC_SUM_A" + ] + } + ], + "dataSourcesPeak1p": [ + { + "type": "repository", + "path": "ZDC/MO/Rec", + "names": [ + "ZPAC:h_TDC_ZPA_TC_A", + "ZPAS:h_TDC_ZPA_SUM_A", + "ZPCC:h_TDC_ZPC_TC_A", + "ZPCS:h_TDC_ZPC_SUM_A" + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "newobject:qcdb:ZDC/MO/Rec/h_ADC_ZNA_TC" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks": { + "RecCheck": { + "active": "true", + "className": "o2::quality_control_modules::zdc::ZDCRecDataCheck", + "moduleName": "QcZDC", + "detectorName" : "ZDC", + "policy": "OnAll", + "checkParameters" : { + "ADC_ZNAC" : "21.2;10;20", + "ADC_ZNA1" : "5.6;10;20", + "ADC_ZNA2" : "6.8;10;20", + "ADC_ZNA3" : "4.6;10;20", + "ADC_ZNA4" : "3.9;10;20", + "ADC_ZNAS" : "21.7;10;20", + "ADC_ZPAC" : "8.7;10;20", + "ADC_ZPA1" : "1.63;10;20", + "ADC_ZPA2" : "2.86;10;20", + "ADC_ZPA3" : "6.30;10;20", + "ADC_ZPA4" : "9.25;10;20", + "ADC_ZPAS" : "10.2;10;20", + "ADC_ZEM1" : "870.2;100;200", + "ADC_ZEM2" : "870.2;100;200", + "ADC_ZNCC" : "21.7;10;20", + "ADC_ZNC1" : "4.7;10;20", + "ADC_ZNC2" : "8.8;10;20", + "ADC_ZNC3" : "3.2;10;20", + "ADC_ZNC4" : "4.6;10;20", + "ADC_ZNCS" : "22.3;10;20", + "ADC_ZPCC" : "9.6;10;20", + "ADC_ZPC1" : "7.19;10;20", + "ADC_ZPC2" : "3.63;10;20", + "ADC_ZPC3" : "1.96;10;20", + "ADC_ZPC4" : "1.21;10;20", + "ADC_ZPCS" : "10.9;10;20", + "ADC_POS_MSG_X": "0.15", + "ADC_POS_MSG_Y": "0.92", + "TDC_ZNAC" : "0.14;0.10;0.20", + "TDC_ZNAS" : "0.05;0.10;0.20", + "TDC_ZPAC" : "-0.39;0.10;0.20", + "TDC_ZPAS" : "-0.11;0.10;0.20", + "TDC_ZEM1" : "0.07;0.10;0.20", + "TDC_ZEM2" : "0.02;0.10;0.20", + "TDC_ZNCC" : "0.19;0.10;0.20", + "TDC_ZNCS" : "0.08;0.10;0.20", + "TDC_ZPCC" : "-0.29;0.10;0.20", + "TDC_ZPCS" : "-0.20;0.10;0.20", + "TDC_POS_MSG_X": "0.01", + "TDC_POS_MSG_Y": "0.92", + "TDCA_ZNAC" : "21;10;20", + "TDCA_ZNAS" : "21;10;20", + "TDCA_ZPAC" : "8;10;20", + "TDCA_ZPAS" : "9;10;20", + "TDCA_ZEM1" : "252;10;20", + "TDCA_ZEM2" : "258;10;20", + "TDCA_ZNCC" : "21;10;20", + "TDCA_ZNCS" : "22;10;20", + "TDCA_ZPCC" : "9;10;20", + "TDCA_ZPCS" : "9;10;20", + "TDCA_POS_MSG_X": "0.01", + "TDCA_POS_MSG_Y": "0.92", + "PEAK1N_ZNAC" : "2;2;4", + "PEAK1N_ZNAS" : "2;2;4", + "PEAK1N_ZNCC" : "2;2;4", + "PEAK1N_ZNCS" : "2;2;4", + "PEAK1N_POS_MSG_X": "0.01", + "PEAK1N_POS_MSG_Y": "0.92", + "PEAK1P_ZPAC" : "3;10;20", + "PEAK1P_ZPAS" : "3;10;20", + "PEAK1P_ZPCC" : "3;10;20", + "PEAK1P_ZPCS" : "3;10;20", + "PEAK1P_POS_MSG_X": "0.01", + "PEAK1P_POS_MSG_Y": "0.92" + }, + "dataSource": [{ + "type": "PostProcessing", + "name": "RecPP", + "MOs": ["h_summary_ADC" , "h_summary_TDC", "h_summary_TDCA", "h_summary_Peak1n", "h_summary_Peak1p"] + }] + } + } + }, + "dataSamplingPolicies": [ + { + "id": "zdc-rec", + "active": "true", + "machines": [], + "query": "zdc-bcrec:ZDC/BCREC/0;zdc-energyrec:ZDC/ENERGY/0;zdc-tdcrec:ZDC/TDCDATA/0;zdc-inforec:ZDC/INFO/0", + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.3", + "seed": "0" + } + ], + "blocking": "false" + } + ] +} + diff --git a/Modules/ZDC/include/ZDC/LinkDef.h b/Modules/ZDC/include/ZDC/LinkDef.h new file mode 100644 index 0000000000..6f06bdbd12 --- /dev/null +++ b/Modules/ZDC/include/ZDC/LinkDef.h @@ -0,0 +1,12 @@ +#ifdef __CLING__ +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class o2::quality_control_modules::zdc::ZDCRawDataTask + ; +#pragma link C++ class o2::quality_control_modules::zdc::ZDCRawDataCheck + ; +#pragma link C++ class o2::quality_control_modules::zdc::ZDCRecDataTask + ; +#pragma link C++ class o2::quality_control_modules::zdc::ZDCRecDataCheck + ; +#pragma link C++ class o2::quality_control_modules::zdc::ZDCRecDataPostProcessing + ; +#pragma link C++ class o2::quality_control_modules::zdc::ZDCRecBeautifyPlots + ; +#endif diff --git a/Modules/ZDC/include/ZDC/PostProcessingConfigZDC.h b/Modules/ZDC/include/ZDC/PostProcessingConfigZDC.h new file mode 100644 index 0000000000..0ebdaf702f --- /dev/null +++ b/Modules/ZDC/include/ZDC/PostProcessingConfigZDC.h @@ -0,0 +1,59 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingConfigZDC.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Header file for the configuration of ZDC post-processing tasks +/// \since 30/08/2023 +/// + +#ifndef QC_MODULE_ZDC_PPCONFIG_H +#define QC_MODULE_ZDC_PPCONFIG_H + +#include "QualityControl/PostProcessingConfig.h" +#include +#include +#include +#include + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::zdc +{ + +/// \brief MCH trending configuration structure +struct PostProcessingConfigZDC : PostProcessingConfig { + PostProcessingConfigZDC() = default; + PostProcessingConfigZDC(std::string name, const boost::property_tree::ptree& config); + ~PostProcessingConfigZDC() = default; + + const bool hasParameter(std::string name) const + { + auto entry = parameters.find(name); + return (entry != parameters.end()); + } + + struct DataSource { + std::string path; + std::string name; + }; + + std::map parameters; + std::vector dataSourcesADC; + std::vector dataSourcesTDC; + std::vector dataSourcesTDCA; + std::vector dataSourcesPeak1n; + std::vector dataSourcesPeak1p; +}; + +} // namespace o2::quality_control_modules::zdc + +#endif // QC_MODULE_ZDC_PPCONFIG_H diff --git a/Modules/ZDC/include/ZDC/ZDCRawDataCheck.h b/Modules/ZDC/include/ZDC/ZDCRawDataCheck.h new file mode 100644 index 0000000000..fc50ec39d9 --- /dev/null +++ b/Modules/ZDC/include/ZDC/ZDCRawDataCheck.h @@ -0,0 +1,87 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRawDataCheck.h +/// \author Carlo Puggioni +/// + +#ifndef QC_MODULE_ZDC_ZDCZDCRAWDATACHECK_H +#define QC_MODULE_ZDC_ZDCZDCRAWDATACHECK_H + +#include "QualityControl/CheckInterface.h" +#include +#include + +namespace o2::quality_control_modules::zdc +{ + +/// \brief QC Check Data Raw. Check baseline mean values of each ZDC channel. +/// \author Carlo Puggioni +class ZDCRawDataCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + ZDCRawDataCheck() = default; + /// Destructor + ~ZDCRawDataCheck() override = default; + + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(ZDCRawDataCheck, 2); + struct sCheck { + std::string ch; + float minW; + float maxW; + float minE; + float maxE; + std::string param; + }; + + struct sHistoCheck { + std::string nameHisto; + std::string typecheck; + std::string typeHisto; + std::string paramPosMsgX; + std::string paramPosMsgY; + float posMsgX; + float posMsgY; + int numW = 0; + int numE = 0; + int quality = 0; // 1 Good 2 warning 3 bad + std::string stringW = "List channels Warning Quality: "; + std::string stringE = "List channels Bad Quality: "; + std::vector paramch; + }; + void startOfActivity(const Activity& activity) override; + void init(const Activity& activity); + void setChName(std::string channel); + void setChCheck(std::string histoName, std::string typeHisto, std::string typeCheck, std::string paramPosMsgX, std::string paramPosMsgY, const Activity& activity); + std::vector tokenLine(std::string Line, std::string Delimiter); + void dumpVecParam(int id_histo, int numBinHisto, int num_ch); + std::string getCurrentDataTime(); + void dumpStruct(); + + private: + std::vector mVectHistoCheck; + std::vector mVectch; + float NOISE_LEVEL_LOW = 0.0; + float NOISE_LEVEL_HIGH = 2.0; + bool COMPARATOR_ARRAY[12]; + int REFERENCE_BIN = 7; + + // std::string mStringW = "List channels Warning Quality: "; + // std::string mStringE = "List channels Bad Quality: "; +}; + +} // namespace o2::quality_control_modules::zdc + +#endif // QC_MODULE_ZDC_ZDCZDCRAWDATACHECK_H diff --git a/Modules/ZDC/include/ZDC/ZDCRawDataTask.h b/Modules/ZDC/include/ZDC/ZDCRawDataTask.h new file mode 100644 index 0000000000..507c345f6e --- /dev/null +++ b/Modules/ZDC/include/ZDC/ZDCRawDataTask.h @@ -0,0 +1,188 @@ +// Copyright 2019-2022 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +/// +/// \file ZDCRawDataTask.h +/// \author Carlo Puggioni +/// + +#ifndef QC_MODULE_ZDC_ZDCZDCRAWDATATASK_H +#define QC_MODULE_ZDC_ZDCZDCRAWDATATASK_H + +#include "QualityControl/TaskInterface.h" +#include +#include +#include +#include "ZDCBase/Constants.h" +#include "ZDCSimulation/ZDCSimParam.h" +#include "DataFormatsZDC/RawEventData.h" +#include +#include + +class TH1F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::zdc +{ +/// \brief Quality Control Raw Data Task +/// \author Carlo Puggioni +class ZDCRawDataTask final : public TaskInterface +{ + public: + /// \brief Constructor + ZDCRawDataTask() = default; + /// Destructor + ~ZDCRawDataTask() override; + + // Definition structures + struct infoHisto { + int idHisto; + std::vector condHisto; + }; + + struct infoHisto1D { + TH1* histo; + std::vector condHisto; + }; + struct infoHisto2D { + TH2* histo; + std::vector condHisto; + }; + struct sSample { + int id_sample; + int num_entry; + int sum; + double mean; + }; + + struct sMinSample { + int id_min_sample; + double min_mean; + int num_entry; + std::vector vSamples; + }; + + struct sAlignment { + std::string name_ch; + int bin; + sMinSample minSample; + }; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + void init(); + void initHisto(); + int process(const o2::zdc::EventData& ev); + int process(const o2::zdc::EventChData& ch); + int processWord(const uint32_t* word); + int getHPos(uint32_t board, uint32_t ch, int matrix[o2::zdc::NModules][o2::zdc::NChPerModule]); + std::string getNameChannel(int imod, int ich); + void setNameChannel(int imod, int ich, std::string namech, int bin); + void setBinHisto1D(int numBinX, double minBinX, double maxBinX); + void setBinHisto2D(int numBinX, double minBinX, double maxBinX, int numBinY, double minBinY, double maxBinY); + void setNumBinX(int nbin) { fNumBinX = nbin; }; + void setMinBinX(double min) { fMinBinX = min; }; + void setMaxBinX(double max) { fMaxBinX = max; }; + void setNumBinY(int nbin) { fNumBinY = nbin; }; + void setMinBinY(double min) { fMinBinY = min; }; + void setMaxBinY(double max) { fMaxBinY = max; }; + int getNumBinX() { return fNumBinX; }; + int getMinBinX() { return fMinBinX; }; + int getMaxBinX() { return fMaxBinX; }; + int getNumBinY() { return fNumBinY; }; + int getMinBinY() { return fMinBinY; }; + int getMaxBinY() { return fMaxBinY; }; + bool getModAndCh(std::string chName, int* module, int* channel); + bool addNewHisto(std::string type, std::string name, std::string title, std::string chName, std::string condition); + std::string removeSpaces(std::string s); + std::vector tokenLine(std::string Line, std::string Delimiter); + bool configureRawDataTask(); + bool checkCondition(std::string cond); + bool decodeConfLine(std::vector tokenString, int lineNumber); + bool decodeModule(std::vector tokenString, int lineNumber); + bool decodeBinHistogram(std::vector tokenString, int lineNumber); + bool decodeBaseline(std::vector tokenString, int lineNumber); + bool decodeCounts(std::vector tokenString, int lineNumber); + bool decodeSignal(std::vector tokenString, int lineNumber); + bool decodeBunch(std::vector tokenString, int lineNumber); + bool decodeFireChannel(std::vector tokenString, int lineNumber); + bool decodeDataLoss(std::vector tokenString, int lineNumber); + bool decodeOverBc(std::vector tokenString, int lineNumber); + bool decodeTrasmittedChannel(std::vector tokenString, int lineNumber); + bool decodeTriggerBitChannel(std::vector tokenString, int lineNumber); + bool decodeTriggerBitHitChannel(std::vector tokenString, int lineNumber); + bool decodeSummary(std::vector tokenString, int lineNumber); + void dumpHistoStructure(); + void resetAlign(); + void setVerbosity(int v) + { + mVerbosity = v; + } + int getVerbosity() const { return mVerbosity; } + + private: + int mVerbosity = 1; + + o2::zdc::EventChData mCh; + std::string fNameChannel[o2::zdc::NModules][o2::zdc::NChPerModule]; + std::vector fMatrixHistoBaseline[o2::zdc::NModules][o2::zdc::NChPerModule]; + std::vector fMatrixHistoCounts[o2::zdc::NModules][o2::zdc::NChPerModule]; + std::vector fMatrixHistoCounts_a[o2::zdc::NModules][o2::zdc::NChPerModule]; + std::vector fMatrixHistoSignal[o2::zdc::NModules][o2::zdc::NChPerModule]; + std::vector fMatrixHistoBunch[o2::zdc::NModules][o2::zdc::NChPerModule]; + + TH2* fFireChannel; + TH2* fTrasmChannel; + TH2* fDataLoss; + TH2* fTriggerBits; + TH2* fTriggerBitsHits; + TH1* fSummaryPedestal; + TH1* fSummaryRate; + TH2* fSummaryAlign; + TH2* fSummaryAlignShift; + TH2* fSummaryError; + TH1* fOverBc; + // Begin Stefan addition + TH2* fBCalignment; + // End Stefan addition + + std::vector fNameHisto; + std::map fMapBinNameIdSummaryHisto; + + std::map> fMapChNameModCh; + + int fNumBinX = 0; + double fMinBinX = 0; + double fMaxBinX = 0; + int fNumBinY = 0; + double fMinBinY = 0; + double fMaxBinY = 0; + int fNumCycle = 0; + int fNumCycleErr = 0; + int fAlignCycle = 1; // param + int fErrorCycle = 1; // param + int fAlignNumEntries = 2000; + // Begin Stefan addition + int FirstEventBC = 0; + // End Stefan addition + + sAlignment fMatrixAlign[o2::zdc::NModules][o2::zdc::NChPerModule]; +}; + +} // namespace o2::quality_control_modules::zdc + +#endif // QC_MODULE_ZDC_ZDCZDCRAWDATATASK_H diff --git a/Modules/ZDC/include/ZDC/ZDCRecBeautifyPlots.h b/Modules/ZDC/include/ZDC/ZDCRecBeautifyPlots.h new file mode 100644 index 0000000000..4dd4f47ae9 --- /dev/null +++ b/Modules/ZDC/include/ZDC/ZDCRecBeautifyPlots.h @@ -0,0 +1,51 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRecBeautifyPlots.h +/// \author Stefan Cristi Zugravel +/// + +#ifndef QC_MODULE_ZDC_ZDCZDCRECBEAUTIFYPLOTS_H +#define QC_MODULE_ZDC_ZDCZDCRECBEAUTIFYPLOTS_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::zdc +{ + +/// \brief ZDC Beautify for centroids plots +/// \author Stefan Cristi Zugravel +class ZDCRecBeautifyPlots : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + ZDCRecBeautifyPlots() = default; + /// Destructor + ~ZDCRecBeautifyPlots() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + void reset() override; + void startOfActivity(const Activity& activity) override; + void endOfActivity(const Activity& activity) override; + + private: + std::shared_ptr mActivity; + + ClassDefOverride(ZDCRecBeautifyPlots, 3); +}; + +} // namespace o2::quality_control_modules::zdc + +#endif // QC_MODULE_ZDC_ZDCZDCRECBEAUTIFYPLOTS_H diff --git a/Modules/ZDC/include/ZDC/ZDCRecDataCheck.h b/Modules/ZDC/include/ZDC/ZDCRecDataCheck.h new file mode 100644 index 0000000000..94c0390422 --- /dev/null +++ b/Modules/ZDC/include/ZDC/ZDCRecDataCheck.h @@ -0,0 +1,107 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRecDataCheck.h +/// \author Carlo Puggioni +/// + +#ifndef QC_MODULE_ZDC_ZDCZDCRECDATACHECK_H +#define QC_MODULE_ZDC_ZDCZDCRECDATACHECK_H + +#include "QualityControl/CheckInterface.h" + +namespace o2::quality_control_modules::zdc +{ + +/// \brief QC Check Data Rec. Check ADC and TDC mean values of each ZDC channel. +/// \author alienv enter Readout/latest +class ZDCRecDataCheck : public o2::quality_control::checker::CheckInterface +{ + public: + /// Default constructor + ZDCRecDataCheck() = default; + /// Destructor + ~ZDCRecDataCheck() override = default; + + // Override interface + void configure() override; + Quality check(std::map>* moMap) override; + void beautify(std::shared_ptr mo, Quality checkResult = Quality::Null) override; + ClassDefOverride(ZDCRecDataCheck, 2); + + struct sCheck { + std::string ch; + float minW; + float maxW; + float minE; + float maxE; + std::string typech; + }; + void startOfActivity(const Activity& activity) override; + void init(const Activity& activity); + void setChName(std::string channel, std::string type); + void setChCheck(int i, std::string type, const Activity& activity); + std::vector tokenLine(std::string Line, std::string Delimiter); + void dumpVecParam(int numBinHisto, int num_ch); + void setQualityInfo(std::shared_ptr mo, int color, std::string text); + std::string getCurrentDataTime(); + + private: + std::vector mVectParamADC; + std::vector mVectParamTDC; + std::vector mVectParamTDCA; + std::vector mVectParamPeak1n; + std::vector mVectParamPeak1p; + + int mNumWADC = 0; + int mNumEADC = 0; + int mNumWTDC = 0; + int mNumETDC = 0; + int mNumWTDCA = 0; + int mNumETDCA = 0; + int mNumWPeak1n = 0; + int mNumEPeak1n = 0; + int mNumWPeak1p = 0; + int mNumEPeak1p = 0; + + float mPosMsgADCX; + float mPosMsgADCY; + float mPosMsgTDCX; + float mPosMsgTDCY; + float mPosMsgTDCAX; + float mPosMsgTDCAY; + float mPosMsgPeak1nX; + float mPosMsgPeak1nY; + float mPosMsgPeak1pX; + float mPosMsgPeak1pY; + + int mQADC = 0; + int mQTDC = 0; + int mQTDCA = 0; + int mQPeak1n = 0; + int mQPeak1p = 0; + + std::string mStringWADC = ""; + std::string mStringEADC = ""; + std::string mStringWTDC = ""; + std::string mStringETDC = ""; + std::string mStringWTDCA = ""; + std::string mStringETDCA = ""; + std::string mStringWPeak1n = ""; + std::string mStringEPeak1n = ""; + std::string mStringWPeak1p = ""; + std::string mStringEPeak1p = ""; +}; + +} // namespace o2::quality_control_modules::zdc + +#endif // QC_MODULE_ZDC_ZDCZDCRECDATACHECK_H diff --git a/Modules/ZDC/include/ZDC/ZDCRecDataPostProcessing.h b/Modules/ZDC/include/ZDC/ZDCRecDataPostProcessing.h new file mode 100644 index 0000000000..54c2473213 --- /dev/null +++ b/Modules/ZDC/include/ZDC/ZDCRecDataPostProcessing.h @@ -0,0 +1,138 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRecDataPostProcessing.h +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Post-processing of the ZDC ADC and TDC (Time and Amplitude) plots +/// \since 30/08/2023 +/// + +#ifndef QC_MODULE_ZDC_ZDCZDCRECDATAPP_H +#define QC_MODULE_ZDC_ZDCZDCRECDATAPP_H + +#include "QualityControl/PostProcessingInterface.h" + +#include +#include +#include + +namespace o2::quality_control::repository +{ +class DatabaseInterface; +} + +using namespace o2::quality_control; +using namespace o2::quality_control::postprocessing; + +namespace o2::quality_control_modules::zdc +{ + +struct MOHelper { + MOHelper(); + MOHelper(std::string p, std::string n); + + bool update(o2::quality_control::repository::DatabaseInterface* qcdb, + long timeStamp = -1, + const o2::quality_control::core::Activity& activity = {}); + void setStartIme(); + long getTimeStamp() { return mTimeStamp; } + template + T* get() + { + if (!mObject) { + return nullptr; + } + if (!mObject->getObject()) { + return nullptr; + } + + // Get histogram object + T* h = dynamic_cast(mObject->getObject()); + return h; + } + + std::shared_ptr mObject; + std::string mPath; + std::string mName; + uint64_t mTimeStart{ 0 }; + uint64_t mTimeStamp{ 0 }; +}; + +/// \brief A post-processing task which processes the ADC and TDC plots from ZDC +class ZDCRecDataPostProcessing : public PostProcessingInterface +{ + public: + ZDCRecDataPostProcessing() = default; + ~ZDCRecDataPostProcessing() override = default; + + void configure(const boost::property_tree::ptree& config) override; + void initialize(Trigger, framework::ServiceRegistryRef) override; + void update(Trigger, framework::ServiceRegistryRef) override; + void finalize(Trigger, framework::ServiceRegistryRef) override; + + private: + template + void publishHisto(T* h, bool statBox = false, + const char* drawOptions = "", + const char* displayHints = ""); + void createSummaryADCHistos(Trigger t, repository::DatabaseInterface* qcdb); + void createSummaryTDCHistos(Trigger t, repository::DatabaseInterface* qcdb); + void createSummaryTDCAHistos(Trigger t, repository::DatabaseInterface* qcdb); + void createSummaryPeak1nHistos(Trigger t, repository::DatabaseInterface* qcdb); + void createSummaryPeak1pHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateSummaryADCHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateSummaryTDCHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateSummaryTDCAHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateSummaryPeak1nHistos(Trigger t, repository::DatabaseInterface* qcdb); + void updateSummaryPeak1pHistos(Trigger t, repository::DatabaseInterface* qcdb); + + // CCDB object accessors + std::map mMOsADC; + std::map mMOsTDC; + std::map mMOsTDCA; + std::map mMOsPeak1n; + std::map mMOsPeak1p; + // Hit rate histograms =============================================== + std::vector mBinLabelsADC; + std::vector mBinLabelsTDC; + std::vector mBinLabelsTDCA; + std::vector mBinLabelsPeak1n; + std::vector mBinLabelsPeak1p; + std::unique_ptr mSummaryADCHisto; + std::unique_ptr mSummaryTDCHisto; + std::unique_ptr mSummaryTDCAHisto; + std::unique_ptr mSummaryPeak1nHisto; + std::unique_ptr mSummaryPeak1pHisto; +}; + +template +void ZDCRecDataPostProcessing::publishHisto(T* h, bool statBox, + const char* drawOptions, + const char* displayHints) +{ + h->LabelsOption("v"); + h->SetLineColor(kBlack); + if (!statBox) { + h->SetStats(0); + } + getObjectsManager()->startPublishing(h, quality_control::core::PublicationPolicy::ThroughStop); + if (drawOptions) { + getObjectsManager()->setDefaultDrawOptions(h, drawOptions); + } + if (displayHints) { + getObjectsManager()->setDisplayHint(h, displayHints); + } +} + +} // namespace o2::quality_control_modules::zdc + +#endif // QC_MODULE_ZDC_ZDCZDCRECDATAPP_H diff --git a/Modules/ZDC/include/ZDC/ZDCRecDataTask.h b/Modules/ZDC/include/ZDC/ZDCRecDataTask.h new file mode 100644 index 0000000000..e726de3f5b --- /dev/null +++ b/Modules/ZDC/include/ZDC/ZDCRecDataTask.h @@ -0,0 +1,147 @@ +// Copyright 2019-2022 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRecDataTask.h +/// \author Carlo Puggioni +/// + +#ifndef QC_MODULE_ZDC_ZDCZDCRECDATATASK_H +#define QC_MODULE_ZDC_ZDCZDCRECDATATASK_H + +#include "QualityControl/TaskInterface.h" +#include "DataFormatsZDC/BCRecData.h" +#include "DataFormatsZDC/RecEventFlat.h" +#include "DataFormatsZDC/ZDCEnergy.h" +#include "DataFormatsZDC/ZDCTDCData.h" +#include "SimulationDataFormat/DigitizationContext.h" +#include "ZDCBase/Constants.h" +#include "ZDCSimulation/Digitizer.h" +#include +#include +#include +#include + +class TH1F; + +using namespace o2::quality_control::core; + +namespace o2::quality_control_modules::zdc +{ + +/// \brief Quality Control ZDC Rec Task +/// \author Carlo Puggioni +class ZDCRecDataTask final : public TaskInterface +{ + public: + /// \brief Constructor + ZDCRecDataTask() = default; + /// Destructor + ~ZDCRecDataTask() override; + + struct sHisto1D { + TH1* histo; + std::string ch; + std::string typeh; + std::string typech; + int bin; + }; + struct sHisto2D { + TH2* histo; + std::string typeh; + std::string ch1; + std::string ch2; + std::string typech1; + std::string typech2; + }; + + // Definition of the methods for the template method pattern + void initialize(o2::framework::InitContext& ctx) override; + void startOfActivity(const Activity& activity) override; + void startOfCycle() override; + void monitorData(o2::framework::ProcessingContext& ctx) override; + void endOfCycle() override; + void endOfActivity(const Activity& activity) override; + void reset() override; + void init(); + void initVecCh(); + void initVecType(); + void initHisto(); + void insertChVec(std::string ch); + void insertTypeVec(std::string type); + void setBinHisto1D(int numBinX, double minBinX, double maxBinX); + void setBinHisto2D(int numBinX, double minBinX, double maxBinX, int numBinY, double minBinY, double maxBinY); + void setNumBinX(int nbin) { fNumBinX = nbin; }; + void setMinBinX(double min) { fMinBinX = min; }; + void setMaxBinX(double max) { fMaxBinX = max; }; + void setNumBinY(int nbin) { fNumBinY = nbin; }; + void setMinBinY(double min) { fMinBinY = min; }; + void setMaxBinY(double max) { fMaxBinY = max; }; + int getNumBinX() { return fNumBinX; }; + int getMinBinX() { return fMinBinX; }; + int getMaxBinX() { return fMaxBinX; }; + int getNumBinY() { return fNumBinY; }; + int getMinBinY() { return fMinBinY; }; + int getMaxBinY() { return fMaxBinY; }; + float getADCRecValue(std::string typech, std::string ch); + int getIdTDCch(std::string typech, std::string ch); + // int getTDCRecValue(int tdcid); + bool addNewHisto(std::string typeH, std::string name, std::string title, std::string typeCh1, std::string ch1, std::string typeCh2, std::string ch2, int bin); + bool add1DHisto(std::string typeH, std::string name, std::string title, std::string typeCh1, std::string ch, int bin); + bool add2DHisto(std::string typeH, std::string name, std::string title, std::string typeCh1, std::string ch1, std::string typeCh2, std::string ch2); + void dumpHistoStructure(); + int process(const gsl::span& RecBC, const gsl::span& Energy, const gsl::span& TDCData, const gsl::span& Info); + bool FillTDCValueHisto(); + std::vector tokenLine(std::string Line, std::string Delimiter); + // Begin Stefan addition + bool IsEventCentral(); + void SetConfigCentralEvent(float tdcLimit, int centraleventconfigvalue); + void settdcLimit(float tdcv) { ftdcLimit = tdcv; }; + void setcentraleventconfigvalue(int centrentcfg) { fcentraleventconfigvalue = centrentcfg; }; + float gettdcLimit() { return ftdcLimit; }; + int getcentraleventconfigvalue() { return fcentraleventconfigvalue; }; + // End Stefan addition + + private: + std::vector mVecCh; + std::vector mVecType; + std::vector mNameHisto; + std::vector mVecTDC{ + "ZNAC", + "ZNAS", + "ZPAC", + "ZPAS", + "ZEM1", + "ZEM2", + "ZNCC", + "ZNCS", + "ZPCC", + "ZPCS", + }; + std::vector mHisto1D; + std::vector mHisto2D; + o2::zdc::RecEventFlat mEv; + int fNumBinX = 0; + double fMinBinX = 0; + double fMaxBinX = 0; + int fNumBinY = 0; + double fMinBinY = 0; + double fMaxBinY = 0; + // Begin Stefan addition + float ftdcLimit = 0; + int fcentraleventconfigvalue = 0; + // End Stefan addition + // TH1F* mHistogram = nullptr; +}; + +} // namespace o2::quality_control_modules::zdc + +#endif // QC_MODULE_ZDC_ZDCZDCRECDATATASK_H diff --git a/Modules/ZDC/src/PostProcessingConfigZDC.cxx b/Modules/ZDC/src/PostProcessingConfigZDC.cxx new file mode 100644 index 0000000000..ce6114e567 --- /dev/null +++ b/Modules/ZDC/src/PostProcessingConfigZDC.cxx @@ -0,0 +1,116 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file PostProcessingConfigZDC.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief File for the configuration of ZDC post-processing tasks +/// \since 30/08/2023 +/// + +#include "ZDC/PostProcessingConfigZDC.h" +#include + +using namespace o2::quality_control::postprocessing; +namespace o2::quality_control_modules::zdc +{ + +PostProcessingConfigZDC::PostProcessingConfigZDC(std::string name, const boost::property_tree::ptree& config) + : PostProcessingConfig(name, config) +{ + // parameters + if (const auto& customConfigs = config.get_child_optional("qc.postprocessing." + name + ".customization"); customConfigs.has_value()) { + for (const auto& customConfig : customConfigs.value()) { + if (const auto& customNames = customConfig.second.get_child_optional("name"); customNames.has_value()) { + parameters.insert(std::make_pair(customConfig.second.get("name"), customConfig.second.get("value"))); + } + } + } + + // Data source configuration + // ADC + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".dataSourcesADC")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSourcesADC.push_back({ dataSourceConfig.second.get("path"), + sourceName.second.data() }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSourcesADC.push_back({ dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + name + ".dataSourcesADC'"); + } + } + // TDC Time + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".dataSourcesTDC")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSourcesTDC.push_back({ dataSourceConfig.second.get("path"), + sourceName.second.data() }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSourcesTDC.push_back({ dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + name + ".dataSourcesTDC'"); + } + } + // TDC Amplitude + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".dataSourcesTDCA")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSourcesTDCA.push_back({ dataSourceConfig.second.get("path"), + sourceName.second.data() }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSourcesTDCA.push_back({ dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + name + ".dataSourcesTDCA'"); + } + } + // Peak 1n TDC Amplitude + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".dataSourcesPeak1n")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSourcesPeak1n.push_back({ dataSourceConfig.second.get("path"), + sourceName.second.data() }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSourcesPeak1n.push_back({ dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + name + ".dataSourcesPeak1n'"); + } + } + // Peak 1p TDC Amplitude + for (const auto& dataSourceConfig : config.get_child("qc.postprocessing." + name + ".dataSourcesPeak1p")) { + if (const auto& sourceNames = dataSourceConfig.second.get_child_optional("names"); sourceNames.has_value()) { + for (const auto& sourceName : sourceNames.value()) { + dataSourcesPeak1p.push_back({ dataSourceConfig.second.get("path"), + sourceName.second.data() }); + } + } else if (!dataSourceConfig.second.get("name").empty()) { + // "name" : [ "something" ] would return an empty string here + dataSourcesPeak1p.push_back({ dataSourceConfig.second.get("path"), + dataSourceConfig.second.get("name") }); + } else { + throw std::runtime_error("No 'name' value or a 'names' vector in the path 'qc.postprocessing." + name + ".dataSourcesPeak1p'"); + } + } +} + +} // namespace o2::quality_control_modules::zdc diff --git a/Modules/ZDC/src/ZDCRawDataCheck.cxx b/Modules/ZDC/src/ZDCRawDataCheck.cxx new file mode 100644 index 0000000000..5239a82f5e --- /dev/null +++ b/Modules/ZDC/src/ZDCRawDataCheck.cxx @@ -0,0 +1,471 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRawDataCheck.cxx +/// \author Carlo Puggioni +/// + +#include "ZDC/ZDCRawDataCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" + +#include // chrono::system_clock +#include // localtime +#include // stringstream +#include // put_time +#include // string + +// ROOT +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::zdc +{ +void ZDCRawDataCheck::startOfActivity(const Activity& activity) +{ + // ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + init(activity); +} +Quality ZDCRawDataCheck::check(std::map>* moMap) +{ + + Quality result = Quality::Null; + int ib = 0; + for (auto& [moName, mo] : *moMap) { + (void)moName; + // For -> Histo to check + for (int ih = 0; ih < (int)mVectHistoCheck.size(); ih++) { + if (mo->getName() == mVectHistoCheck.at(ih).nameHisto) { + if ((mo->getName() == "hpedSummary")) { + mVectHistoCheck.at(ih).numE = 0; + mVectHistoCheck.at(ih).numW = 0; + mVectHistoCheck.at(ih).stringW = ""; + mVectHistoCheck.at(ih).stringE = ""; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast hpedSummary to TH1*" << ENDM; + return Quality::Null; + } + if ((int)h->GetNbinsX() != (int)mVectHistoCheck.at(ih).paramch.size()) { + return Quality::Null; + } + // check all channels + for (int i = 0; i < h->GetNbinsX(); i++) { + ib = i + 1; + if ((((float)h->GetBinContent(ib) < (float)mVectHistoCheck.at(ih).paramch.at(i).minW && (float)h->GetBinContent(ib) >= (float)mVectHistoCheck.at(ih).paramch.at(i).minE)) || ((float)h->GetBinContent(ib) > (float)mVectHistoCheck.at(ih).paramch.at(i).maxW && (float)h->GetBinContent(ib) < (float)mVectHistoCheck.at(ih).paramch.at(i).maxE)) { + mVectHistoCheck.at(ih).numW += 1; + mVectHistoCheck.at(ih).stringW = mVectHistoCheck.at(ih).stringW + mVectHistoCheck.at(ih).paramch.at(i).ch + " "; + // ILOG(Warning, Support) << "Baseline Warning in " << mVectHistoCheck.at(ih).paramch.at(i).param << " intervall: " << mVectHistoCheck.at(ih).paramch.at(i).minW << " - " << mVectHistoCheck.at(ih).paramch.at(i).maxW << " Value: " << h->GetBinContent(ib) << ENDM; + } + if (((float)h->GetBinContent(ib) < (float)mVectHistoCheck.at(ih).paramch.at(i).minE) || ((float)h->GetBinContent(ib) > (float)mVectHistoCheck.at(ih).paramch.at(i).maxE)) { + mVectHistoCheck.at(ih).numE += 1; + mVectHistoCheck.at(ih).stringE = mVectHistoCheck.at(ih).stringE + mVectHistoCheck.at(ih).paramch.at(i).ch + " "; + // ILOG(Error, Support) << "Baseline Error in " << mVectHistoCheck.at(ih).paramch.at(i).param << " intervall: " << mVectHistoCheck.at(ih).paramch.at(i).minE << " - " << mVectHistoCheck.at(ih).paramch.at(i).maxE << " Value: " << h->GetBinContent(ib) << ENDM; + } + } + } + + if ((mo->getName() == "hAlignPlot") || (mo->getName() == "hAlignPlotShift")) { + mVectHistoCheck.at(ih).numE = 0; + mVectHistoCheck.at(ih).numW = 0; + mVectHistoCheck.at(ih).stringW = ""; + mVectHistoCheck.at(ih).stringE = ""; + int flag_ch_empty = 1; + int flag_all_ch_empty = 1; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast hAlignPlot to TH2*" << ENDM; + return Quality::Null; + } + if ((int)h->GetNbinsX() != (int)mVectHistoCheck.at(ih).paramch.size()) { + return Quality::Null; + } + for (int x = 0; x < h->GetNbinsX(); x++) { + for (int y = 0; y < h->GetNbinsY(); y++) { + if (h->GetBinContent(x + 1, y + 1) > 0) { + flag_ch_empty = 0; + flag_all_ch_empty = 0; + if ((((float)y < (float)mVectHistoCheck.at(ih).paramch.at(x).minW && (float)y >= (float)mVectHistoCheck.at(ih).paramch.at(x).minE)) || ((float)y > (float)mVectHistoCheck.at(ih).paramch.at(x).maxW && (float)y < (float)mVectHistoCheck.at(ih).paramch.at(x).maxE)) { + mVectHistoCheck.at(ih).numW += 1; + mVectHistoCheck.at(ih).stringW = mVectHistoCheck.at(ih).stringW + mVectHistoCheck.at(ih).paramch.at(x).ch + " "; + // ILOG(Warning, Support) << "Alignment warning:" << mVectHistoCheck.at(ih).paramch.at(x).param << " intervall: " << mVectHistoCheck.at(ih).paramch.at(x).minW << " - " << mVectHistoCheck.at(ih).paramch.at(x).maxW << " Value: " << y << ENDM; + } + if (((float)y < (float)mVectHistoCheck.at(ih).paramch.at(x).minE) || ((float)y > (float)mVectHistoCheck.at(ih).paramch.at(x).maxE)) { + mVectHistoCheck.at(ih).numE += 1; + mVectHistoCheck.at(ih).stringE = mVectHistoCheck.at(ih).stringE + mVectHistoCheck.at(ih).paramch.at(x).ch + " "; + // ILOG(Error, Support) << "Alignment error:" << mVectHistoCheck.at(ih).paramch.at(x).param << " intervall: " << mVectHistoCheck.at(ih).paramch.at(x).minE << " - " << mVectHistoCheck.at(ih).paramch.at(x).maxE << " Value: " << y << ENDM; + } + } + } + if (flag_ch_empty == 1) { + mVectHistoCheck.at(ih).numE += 1; + mVectHistoCheck.at(ih).stringE = mVectHistoCheck.at(ih).stringE + mVectHistoCheck.at(ih).paramch.at(x).ch + "(Empty) "; + // ILOG(Error, Support) << "Alignment error: Channel" << mVectHistoCheck.at(ih).paramch.at(x).ch << " is empty. Ignore if is the first cycle " << ENDM; + } + flag_ch_empty = 1; + } + if (flag_all_ch_empty == 1) { + mVectHistoCheck.at(ih).numE = 0; + mVectHistoCheck.at(ih).stringE = ""; + } + } + + if (mo->getName() == "herrorSummary") { + mVectHistoCheck.at(ih).numE = 0; + mVectHistoCheck.at(ih).numW = 0; + mVectHistoCheck.at(ih).stringW = ""; + mVectHistoCheck.at(ih).stringE = ""; + int flag_ch_empty = 1; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast herrorSummary to TH2*" << ENDM; + return Quality::Null; + } + if ((int)h->GetNbinsX() != (int)mVectHistoCheck.at(ih).paramch.size()) { + return Quality::Null; + } + for (int x = 0; x < h->GetNbinsX(); x++) { + for (int y = 0; y < h->GetNbinsY(); y++) { + if (h->GetBinContent(x + 1, y + 1) > 0) { + flag_ch_empty = 0; + if (y == 0) { + mVectHistoCheck.at(ih).numE += 1; + mVectHistoCheck.at(ih).stringE = mVectHistoCheck.at(ih).stringE + mVectHistoCheck.at(ih).paramch.at(x).ch + " "; + // ILOG(Error, Support) << "Error Bit:" << mVectHistoCheck.at(ih).paramch.at(x).ch << " Value: " << h->GetBinContent(x + 1, y + 1) << ENDM; + } + if (y == 1) { + mVectHistoCheck.at(ih).numW += 1; + mVectHistoCheck.at(ih).stringW = mVectHistoCheck.at(ih).stringW + mVectHistoCheck.at(ih).paramch.at(x).ch + " "; + // ILOG(Warning, Support) << "Data Loss warning:" << mVectHistoCheck.at(ih).paramch.at(x).ch << " Value: " << h->GetBinContent(x + 1, y + 1) << ENDM; + } + if (y == 2) { + mVectHistoCheck.at(ih).numW += 1; + mVectHistoCheck.at(ih).stringW = mVectHistoCheck.at(ih).stringW + mVectHistoCheck.at(ih).paramch.at(x).ch + " "; + // ILOG(Warning, Support) << "Data Corrupted warning:" << mVectHistoCheck.at(ih).paramch.at(x).ch << " Value: " << h->GetBinContent(x + 1, y + 1) << ENDM; + } + } + } + flag_ch_empty = 1; + } + } + if (mo->getName() == "hBCAlignPlot") { + mVectHistoCheck.at(ih).numE = 0; + mVectHistoCheck.at(ih).numW = 0; + mVectHistoCheck.at(ih).stringW = ""; + mVectHistoCheck.at(ih).stringE = ""; + bool ratio_array[12]; + float ratio = 0.0; + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast herrorSummary to TH2*" << ENDM; + return Quality::Null; + } + if ((int)h->GetNbinsX() != (int)mVectHistoCheck.at(ih).paramch.size()) { + return Quality::Null; + } + for (int x = 0; x < h->GetNbinsX(); x++) { + for (int y = 0; y < h->GetNbinsY(); y++) { + ratio = (h->GetBinContent(x + 1, y + 1)) / (h->GetBinContent(x + 1, REFERENCE_BIN)); + if ((ratio > NOISE_LEVEL_LOW) && (ratio < NOISE_LEVEL_HIGH)) { + ratio_array[y] = true; + } else { + ratio_array[y] = false; + } + } + if (!std::equal(std::begin(ratio_array), std::end(ratio_array), std::begin(COMPARATOR_ARRAY))) { + mVectHistoCheck.at(ih).numE += 1; + mVectHistoCheck.at(ih).stringE = mVectHistoCheck.at(ih).stringE + mVectHistoCheck.at(ih).paramch.at(x).ch + " "; + } + } + } + // check result check + if (mVectHistoCheck.at(ih).numW == 0 && mVectHistoCheck.at(ih).numE == 0) { + result = Quality::Good; + mVectHistoCheck.at(ih).quality = 1; + } + if (mVectHistoCheck.at(ih).numW > 0) { + result = Quality::Medium; + result.addFlag(FlagTypeFactory::Unknown(), + "It is medium because " + std::to_string(mVectHistoCheck.at(ih).numW) + " channels:" + mVectHistoCheck.at(ih).stringW + "have a value in the medium range"); + mVectHistoCheck.at(ih).quality = 2; + } + if (mVectHistoCheck.at(ih).numE > 0) { + result = Quality::Bad; + result.addFlag(FlagTypeFactory::Unknown(), + "It is bad because " + std::to_string(mVectHistoCheck.at(ih).numE) + " channels:" + mVectHistoCheck.at(ih).stringE + "have a value in the bad range"); + mVectHistoCheck.at(ih).quality = 3; + } + } + } + } + return result; +} + +void ZDCRawDataCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + for (int ih = 0; ih < (int)mVectHistoCheck.size(); ih++) { + + if (mo->getName() == mVectHistoCheck.at(ih).nameHisto) { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast '" << mo->getName() << "' to TH1*" << ENDM; + return; + } + if (mo->getName() == "hBCAlignPlot") { + float ratio = 0.0; + for (int x = 0; x < h->GetNbinsX(); x++) { + for (int y = 0; y < h->GetNbinsY(); y++) { + ratio = (h->GetBinContent(x + 1, y + 1)) / (h->GetBinContent(x + 1, REFERENCE_BIN)); + float xpos = h->GetXaxis()->GetBinCenter(x + 1); + float ypos = h->GetYaxis()->GetBinCenter(y + 1); + std::string strValue = std::format("{:.2f}", ratio); + TLatex* msgr = new TLatex(xpos - 0.35, ypos - 0.15, strValue.c_str()); + msgr->SetTextSize(9); + if ((ratio > NOISE_LEVEL_LOW) && (ratio < NOISE_LEVEL_HIGH)) { + msgr->SetTextColor(kGreen); + } else { + msgr->SetTextColor(kRed); + } + h->GetListOfFunctions()->Add(msgr); + } + } + } + if (mVectHistoCheck.at(ih).quality == 1) { + std::string errorSt = getCurrentDataTime() + " Ok"; + TLatex* msg = new TLatex(mVectHistoCheck.at(ih).posMsgX, mVectHistoCheck.at(ih).posMsgY, errorSt.c_str()); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kGreen); + h->GetListOfFunctions()->Add(msg); + h->SetFillColor(kGreen); + msg->Draw(); + } else if (mVectHistoCheck.at(ih).quality == 3) { + std::string errorSt = getCurrentDataTime() + " Errors --> Call the expert. " + mVectHistoCheck.at(ih).stringE; + TLatex* msg = new TLatex(mVectHistoCheck.at(ih).posMsgX, mVectHistoCheck.at(ih).posMsgY, errorSt.c_str()); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kRed); + h->GetListOfFunctions()->Add(msg); + h->SetFillColor(kRed); + msg->Draw(); + } else if (mVectHistoCheck.at(ih).quality == 2) { + std::string errorSt = getCurrentDataTime() + " Warning --> Send mail to the expert." + mVectHistoCheck.at(ih).stringW; + TLatex* msg = new TLatex(mVectHistoCheck.at(ih).posMsgX, mVectHistoCheck.at(ih).posMsgY, errorSt.c_str()); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + msg->SetTextColor(kOrange); + h->GetListOfFunctions()->Add(msg); + h->SetFillColor(kOrange); + msg->Draw(); + } + h->SetLineColor(kBlack); + } + } +} + +void ZDCRawDataCheck::init(const Activity& activity) +{ + mVectch.clear(); + mVectHistoCheck.clear(); + setChName("ZNAC"); + setChName("ZNA1"); + setChName("ZNA2"); + setChName("ZNAS"); + setChName("ZNA3"); + setChName("ZNA4"); + setChName("ZNCC"); + setChName("ZNC1"); + setChName("ZNC2"); + setChName("ZNCS"); + setChName("ZNC3"); + setChName("ZNC4"); + setChName("ZPAC"); + setChName("ZEM1"); + setChName("ZPA1"); + setChName("ZPA2"); + setChName("ZPAS"); + setChName("ZPA3"); + setChName("ZPA4"); + setChName("ZPCC"); + setChName("ZEM2"); + setChName("ZPC3"); + setChName("ZPC4"); + setChName("ZPCS"); + setChName("ZPC1"); + setChName("ZPC2"); + setChCheck("hpedSummary", "TH1F", "PED", "PED_POS_MSG_X", "PED_POS_MSG_Y", activity); + setChCheck("hAlignPlotShift", "TH2F", "ALIGN", "ALIGN_POS_MSG_X", "ALIGN_POS_MSG_Y", activity); + setChCheck("herrorSummary", "TH2F", "ERROR", "ERROR_POS_MSG_X", "ERROR_POS_MSG_Y", activity); + setChCheck("hBCAlignPlot", "TH2F", "PED", "PED_POS_MSG_X", "PED_POS_MSG_Y", activity); + + std::vector tokenString; + if (auto param = mCustomParameters.find("REFERENCE_BIN"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - REFERENCE_BIN: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + REFERENCE_BIN = atoi(param->second.c_str()); + } else { + REFERENCE_BIN = 7; + } + if (auto param = mCustomParameters.find("NOISE_LEVEL_LOW"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - NOISE_LEVEL_LOW: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + NOISE_LEVEL_LOW = atof(param->second.c_str()); + } else { + NOISE_LEVEL_LOW = 0.0; + } + if (auto param = mCustomParameters.find("NOISE_LEVEL_HIGH"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - NOISE_LEVEL_HIGH: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + NOISE_LEVEL_HIGH = atof(param->second.c_str()); + } else { + NOISE_LEVEL_HIGH = 2.0; + } + if (auto param = mCustomParameters.find("COMPARATOR_ARRAY"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - COMPARATOR_ARRAY: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + for (int i = 0; i < 12; i++) { + if (atoi(tokenString.at(i).c_str()) == 1) { + COMPARATOR_ARRAY[i] = true; + } else { + COMPARATOR_ARRAY[i] = false; + } + } + } else { + for (int i = 0; i < 12; i++) { + COMPARATOR_ARRAY[i] = false; + } + } +} + +void ZDCRawDataCheck::setChName(std::string channel) +{ + mVectch.push_back(channel); +} + +void ZDCRawDataCheck::setChCheck(std::string histoName, std::string typeHisto, std::string typeCheck, std::string paramPosMsgX, std::string paramPosMsgY, const Activity& activity) +{ + sCheck chCheck; + sHistoCheck histoCheck; + std::vector tokenString; + // General info check and histo + histoCheck.nameHisto = histoName; + histoCheck.typeHisto = typeHisto; + histoCheck.typecheck = typeCheck; + histoCheck.paramPosMsgX = paramPosMsgX; + histoCheck.paramPosMsgY = paramPosMsgY; + if (auto param = mCustomParameters.atOptional(histoCheck.paramPosMsgX, activity)) { + histoCheck.posMsgX = atof(param.value().c_str()); + } + if (auto param = mCustomParameters.atOptional(histoCheck.paramPosMsgY, activity)) { + histoCheck.posMsgY = atof(param.value().c_str()); + } + // For each ZDC Channel + for (int i = 0; i < (int)mVectch.size(); i++) { + chCheck.ch = mVectch.at(i); + chCheck.param = histoCheck.typecheck + "_" + chCheck.ch; + if (auto param = mCustomParameters.atOptional(chCheck.param, activity)) { + tokenString = tokenLine(param.value(), ";"); + chCheck.minW = atof(tokenString.at(0).c_str()) - atof(tokenString.at(1).c_str()); + chCheck.maxW = atof(tokenString.at(0).c_str()) + atof(tokenString.at(1).c_str()); + chCheck.minE = atof(tokenString.at(0).c_str()) - atof(tokenString.at(2).c_str()); + chCheck.maxE = atof(tokenString.at(0).c_str()) + atof(tokenString.at(2).c_str()); + } + histoCheck.paramch.push_back(chCheck); + } + mVectHistoCheck.push_back(histoCheck); +} + +std::vector ZDCRawDataCheck::tokenLine(std::string Line, std::string Delimiter) +{ + std::string token; + size_t pos = 0; + int i = 0; + std::vector stringToken; + while ((pos = Line.find(Delimiter)) != std::string::npos) { + token = Line.substr(i, pos); + stringToken.push_back(token); + Line.erase(0, pos + Delimiter.length()); + } + stringToken.push_back(Line); + return stringToken; +} + +void ZDCRawDataCheck::dumpVecParam(int id_histo, int numBinHisto, int num_ch) +{ + std::ofstream dumpFile; + dumpFile.open("dumpStructuresRawCheck.txt"); + if (dumpFile.good()) { + dumpFile << "\n Vector Param\n"; + for (int i = 0; i < (int)mVectHistoCheck.at(id_histo).paramch.size(); i++) { + dumpFile << mVectHistoCheck.at(id_histo).paramch.at(i).param << " \t" << std::to_string(mVectHistoCheck.at(id_histo).paramch.at(i).minW) << " \t" << std::to_string(mVectHistoCheck.at(id_histo).paramch.at(i).maxW) << " \t" << std::to_string(mVectHistoCheck.at(id_histo).paramch.at(i).minE) << " \t" << std::to_string(mVectHistoCheck.at(id_histo).paramch.at(i).maxE) << " \n"; + } + dumpFile << "Num Bin Histo " << std::to_string(numBinHisto) << " \n"; + dumpFile << "Num ch " << std::to_string(num_ch) << " \n"; + dumpFile.close(); + } +} + +std::string ZDCRawDataCheck::getCurrentDataTime() +{ + auto now = std::chrono::system_clock::now(); + auto in_time_t = std::chrono::system_clock::to_time_t(now); + + std::stringstream ss; + ss << std::put_time(std::localtime(&in_time_t), "%d-%m-%Y %X"); + return ss.str(); +} + +void ZDCRawDataCheck::dumpStruct() +{ + std::ofstream dumpFile; + dumpFile.open("dumpStructuresRawCheck2.txt"); + if (dumpFile.good()) { + dumpFile << "\n Vector Param\n"; + dumpFile << "NumHisto " << std::to_string((int)mVectHistoCheck.size()) << " \n"; + for (int id_histo = 0; id_histo < (int)mVectHistoCheck.size(); id_histo++) { + dumpFile << "Name Histo " << mVectHistoCheck.at(id_histo).nameHisto << " \n"; + dumpFile << "Type Histo " << mVectHistoCheck.at(id_histo).typeHisto << " \n"; + dumpFile << "Type Check " << mVectHistoCheck.at(id_histo).typecheck << " \n"; + dumpFile << "paramPosMsgX " << mVectHistoCheck.at(id_histo).paramPosMsgX << " \n"; + dumpFile << "paramPosMsgY " << mVectHistoCheck.at(id_histo).paramPosMsgY << " \n"; + dumpFile << "posMsgX " << std::to_string(mVectHistoCheck.at(id_histo).posMsgX) << " \n"; + dumpFile << "posMsgY " << std::to_string(mVectHistoCheck.at(id_histo).posMsgY) << " \n"; + dumpFile << "numW " << std::to_string(mVectHistoCheck.at(id_histo).numW) << " \n"; + dumpFile << "numE " << std::to_string(mVectHistoCheck.at(id_histo).numE) << " \n"; + dumpFile << "stringW " << mVectHistoCheck.at(id_histo).stringW << " \n"; + dumpFile << "stringE " << mVectHistoCheck.at(id_histo).stringE << " \n"; + dumpFile << "quality " << mVectHistoCheck.at(id_histo).quality << " \n"; + for (int i = 0; i < (int)mVectHistoCheck.at(id_histo).paramch.size(); i++) { + dumpFile << mVectHistoCheck.at(id_histo).paramch.at(i).param << " \t" << std::to_string(mVectHistoCheck.at(id_histo).paramch.at(i).minW) << " \t" << std::to_string(mVectHistoCheck.at(id_histo).paramch.at(i).maxW) << " \t" << std::to_string(mVectHistoCheck.at(id_histo).paramch.at(i).minE) << " \t" << std::to_string(mVectHistoCheck.at(id_histo).paramch.at(i).maxE) << " \n"; + } + } + + dumpFile.close(); + } +} + +} // namespace o2::quality_control_modules::zdc diff --git a/Modules/ZDC/src/ZDCRawDataTask.cxx b/Modules/ZDC/src/ZDCRawDataTask.cxx new file mode 100644 index 0000000000..03119b3c74 --- /dev/null +++ b/Modules/ZDC/src/ZDCRawDataTask.cxx @@ -0,0 +1,1777 @@ +// Copyright 2019-2022 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRawDataTask.cxx +/// \author Carlo Puggioni +/// + +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "ZDC/ZDCRawDataTask.h" +#include +#include "DPLUtils/DPLRawParser.h" +#include +#include +#include +#include +#include +#include "CommonConstants/LHCConstants.h" +#include "ZDCSimulation/Digits2Raw.h" +#include "ZDCSimulation/ZDCSimParam.h" +#include +#include +#include +#include +#include +#include +#include +#include +using namespace o2::zdc; + +namespace o2::quality_control_modules::zdc +{ + +ZDCRawDataTask::~ZDCRawDataTask() +{ + delete fFireChannel; + delete fTrasmChannel; + delete fDataLoss; + delete fTriggerBits; + delete fTriggerBitsHits; + delete fSummaryPedestal; + delete fSummaryRate; + delete fSummaryAlign; + delete fSummaryAlignShift; + delete fSummaryError; + delete fOverBc; +} + +void ZDCRawDataTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize ZDCRawDataTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + init(); +} + +void ZDCRawDataTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity" << activity.mId << ENDM; + // reset for all object + fNumCycle = 0; + fNumCycleErr = 0; + resetAlign(); + reset(); +} + +void ZDCRawDataTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; + + // Reset at each QC cycle of the trending plot data for the rate measurement + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + for (int k = 0; k < (int)fMatrixHistoCounts_a[i][j].size(); k++) { + fMatrixHistoCounts_a[i][j].at(k).histo->Reset(); + } + } + } + fNumCycle++; + fNumCycleErr++; +} + +void ZDCRawDataTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + o2::framework::DPLRawParser parser(ctx.inputs()); + int PayloadPerGBTW = 10; + int dataFormat; + const uint32_t* gbtw; + uint64_t count = 0; + size_t payloadSize; + size_t offset; + static uint64_t nErr[3] = { 0 }; + + for (auto it = parser.begin(), end = parser.end(); it != end; ++it) { + auto rdhPtr = reinterpret_cast(it.raw()); + if (rdhPtr == nullptr || !o2::raw::RDHUtils::checkRDH(rdhPtr, true)) { + nErr[0]++; + + } else { + if (it.data() == nullptr) { + nErr[1]++; + } else if (it.size() == 0) { + nErr[2]++; + } else { + // retrieving payload pointer of the page + auto const* payload = it.data(); + // size of payload + payloadSize = it.size(); + // offset of payload in the raw page + offset = it.offset(); + dataFormat = o2::raw::RDHUtils::getDataFormat(rdhPtr); + if (dataFormat == 2) { + for (int32_t ip = 0; (ip + PayloadPerGBTW) <= payloadSize; ip += PayloadPerGBTW) { + gbtw = (const uint32_t*)&payload[ip]; + if (gbtw[0] != 0xffffffff || gbtw[1] != 0xffffffff || (gbtw[2] & 0xffff) != 0xffff) { + processWord(gbtw); + } + } + } else if (dataFormat == 0) { + for (int32_t ip = 0; ip < (int32_t)payloadSize; ip += 16) { + processWord((const uint32_t*)&payload[ip]); + } + } + } + } + } +} + +void ZDCRawDataTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ZDCRawDataTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ZDCRawDataTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + for (int k = 0; k < (int)fMatrixHistoBaseline[i][j].size(); k++) { + fMatrixHistoBaseline[i][j].at(k).histo->Reset(); + } + } + } + + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + for (int k = 0; k < (int)fMatrixHistoCounts[i][j].size(); k++) { + fMatrixHistoCounts[i][j].at(k).histo->Reset(); + } + } + } + + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + for (int k = 0; k < (int)fMatrixHistoSignal[i][j].size(); k++) { + fMatrixHistoSignal[i][j].at(k).histo->Reset(); + } + } + } + + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + for (int k = 0; k < (int)fMatrixHistoBunch[i][j].size(); k++) { + fMatrixHistoBunch[i][j].at(k).histo->Reset(); + } + } + } + if (fFireChannel) { + fFireChannel->Reset(); + } + if (fTrasmChannel) { + fTrasmChannel->Reset(); + } + if (fDataLoss) { + fDataLoss->Reset(); + } + if (fTriggerBits) { + fTriggerBits->Reset(); + } + if (fTriggerBitsHits) { + fTriggerBitsHits->Reset(); + } + if (fSummaryPedestal) { + fSummaryPedestal->Reset(); + } + if (fSummaryRate) { + fSummaryRate->Reset(); + } + if (fSummaryAlign) { + fSummaryAlign->Reset(); + } + if (fSummaryAlignShift) { + fSummaryAlignShift->Reset(); + } + if (fSummaryError) { + fSummaryError->Reset(); + } + if (fOverBc) { + fOverBc->Reset(); + } + // Begin Stefan addition + if (fBCalignment) { + fBCalignment->Reset(); + } + // End Stefan addition +} + +void ZDCRawDataTask::initHisto() +{ + ILOG(Debug, Devel) << "initialize ZDC RAW DATA HISTOGRAMS" << ENDM; + setNameChannel(0, 0, "ZNA_TC_TR", 1); + setNameChannel(0, 1, "ZNA_SUM_SP", -1); + setNameChannel(0, 2, "ZNA_T1", 2); + setNameChannel(0, 3, "ZNA_T2", 3); + setNameChannel(1, 0, "ZNA_TC_OTR", -1); + setNameChannel(1, 1, "ZNA_SUM", 4); + setNameChannel(1, 2, "ZNA_T3", 5); + setNameChannel(1, 3, "ZNA_T4", 6); + setNameChannel(2, 0, "ZNC_TC_TR", 7); + setNameChannel(2, 1, "ZNC_SUM_SP", -1); + setNameChannel(2, 2, "ZNC_T1", 8); + setNameChannel(2, 3, "ZNC_T2", 9); + setNameChannel(3, 0, "ZNC_TC_OTR", -1); + setNameChannel(3, 1, "ZNC_SUM", 10); + setNameChannel(3, 2, "ZNC_T3", 11); + setNameChannel(3, 3, "ZNC_T4", 12); + setNameChannel(4, 0, "ZPA_TC_TR", 13); + setNameChannel(4, 1, "ZEM1_TR", 14); + setNameChannel(4, 2, "ZPA_T1", 15); + setNameChannel(4, 3, "ZPA_T2", 16); + setNameChannel(5, 0, "ZPA_TC_OTR", -1); + setNameChannel(5, 1, "ZPA_SUM", 17); + setNameChannel(5, 2, "ZPA_T3", 18); + setNameChannel(5, 3, "ZPA_T4", 19); + setNameChannel(6, 0, "ZPC_TC_TR", 20); + setNameChannel(6, 1, "ZEM2_TR", 21); + setNameChannel(6, 2, "ZPC_T3", 22); + setNameChannel(6, 3, "ZPC_T4", 23); + setNameChannel(7, 0, "ZPC_TC_OTR", -1); + setNameChannel(7, 1, "ZPC_SUM", 24); + setNameChannel(7, 2, "ZPC_T1", 25); + setNameChannel(7, 3, "ZPC_T2", 26); + + std::vector tokenString; + // Histograms Baseline + // setBinHisto1D(16378, -0.125, o2::zdc::ADCMax + 0.125); + if (auto param = mCustomParameters.find("BASELINE"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - BASELINE: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(4096, -2048.5, 2047.5); + } + addNewHisto("BASELINE", "hped-ZNA_TC_TR", "Baseline ZNA TC", "ZNA_TC_TR", "LBC"); + addNewHisto("BASELINE", "hped-ZNA_T1", "Baseline ZNA T1", "ZNA_T1", "LBC"); + addNewHisto("BASELINE", "hped-ZNA_T2", "Baseline ZNA T2", "ZNA_T2", "LBC"); + addNewHisto("BASELINE", "hped-ZNA_T3", "Baseline ZNA T3", "ZNA_T3", "LBC"); + addNewHisto("BASELINE", "hped-ZNA_T4", "Baseline ZNA T4", "ZNA_T4", "LBC"); + addNewHisto("BASELINE", "hped-ZNA_SUM", "Baseline ZNA SUM", "ZNA_SUM", "LBC"); + + addNewHisto("BASELINE", "hped-ZNC_TC_TR", "Baseline ZNC TC", "ZNC_TC_TR", "LBC"); + addNewHisto("BASELINE", "hped-ZNC_T1", "Baseline ZNC T1", "ZNC_T1", "LBC"); + addNewHisto("BASELINE", "hped-ZNC_T2", "Baseline ZNC T2", "ZNC_T2", "LBC"); + addNewHisto("BASELINE", "hped-ZNC_T3", "Baseline ZNC T3", "ZNC_T3", "LBC"); + addNewHisto("BASELINE", "hped-ZNC_T4", "Baseline ZNC T4", "ZNC_T4", "LBC"); + addNewHisto("BASELINE", "hped-ZNC_SUM", "Baseline ZNC SUM", "ZNC_SUM", "LBC"); + + addNewHisto("BASELINE", "hped-ZPA_TC_TR", "Baseline ZPA TC", "ZPA_TC_TR", "LBC"); + addNewHisto("BASELINE", "hped-ZPA_T1", "Baseline ZPA T1", "ZPA_T1", "LBC"); + addNewHisto("BASELINE", "hped-ZPA_T2", "Baseline ZPA T2", "ZPA_T2", "LBC"); + addNewHisto("BASELINE", "hped-ZPA_T3", "Baseline ZPA T3", "ZPA_T3", "LBC"); + addNewHisto("BASELINE", "hped-ZPA_T4", "Baseline ZPA T4", "ZPA_T4", "LBC"); + addNewHisto("BASELINE", "hped-ZPA_SUM", "Baseline ZPA SUM", "ZPA_SUM", "LBC"); + + addNewHisto("BASELINE", "hped-ZPC_TC_TR", "Baseline ZPC TC", "ZPC_TC_TR", "LBC"); + addNewHisto("BASELINE", "hped-ZPC_T1", "Baseline ZPC T1", "ZPC_T1", "LBC"); + addNewHisto("BASELINE", "hped-ZPC_T2", "Baseline ZPC T2", "ZPC_T2", "LBC"); + addNewHisto("BASELINE", "hped-ZPC_T3", "Baseline ZPC T3", "ZPC_T3", "LBC"); + addNewHisto("BASELINE", "hped-ZPC_T4", "Baseline ZPC T4", "ZPC_T4", "LBC"); + addNewHisto("BASELINE", "hped-ZPC_SUM", "Baseline ZPC SUM", "ZPC_SUM", "LBC"); + + addNewHisto("BASELINE", "hped-ZEM1_TR", "Baseline ZEM1", "ZEM1_TR", "LBC"); + addNewHisto("BASELINE", "hped-ZEM2_TR", "Baseline ZEM2", "ZEM2_TR", "LBC"); + + // Histograms Counts + // setBinHisto1D(o2::constants::lhc::LHCMaxBunches + 6, -5.5, o2::constants::lhc::LHCMaxBunches+0.5); + if (auto param = mCustomParameters.find("COUNTS"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - COUNTS: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(10, -0.5, 9.5); + } + addNewHisto("COUNTS", "hcounts-ZNA_TC_TR", "Counts ZNA TC", "ZNA_TC_TR", "LBC"); + addNewHisto("COUNTS", "hcounts-ZNA_T1", "Counts ZNA T1", "ZNA_T1", "LBC"); + addNewHisto("COUNTS", "hcounts-ZNA_T2", "Counts ZNA T2", "ZNA_T2", "LBC"); + addNewHisto("COUNTS", "hcounts-ZNA_T3", "Counts ZNA T3", "ZNA_T3", "LBC"); + addNewHisto("COUNTS", "hcounts-ZNA_T4", "Counts ZNA T4", "ZNA_T4", "LBC"); + addNewHisto("COUNTS", "hcounts-ZNA_SUM", "Counts ZNA SUM", "ZNA_SUM", "LBC"); + + addNewHisto("COUNTS", "hcounts-ZNC_TC_TR", "Counts ZNC TC", "ZNC_TC_TR", "LBC"); + addNewHisto("COUNTS", "hcounts-ZNC_T1", "Counts ZNC T1", "ZNC_T1", "LBC"); + addNewHisto("COUNTS", "hcounts-ZNC_T2", "Counts ZNC T2", "ZNC_T2", "LBC"); + addNewHisto("COUNTS", "hcounts-ZNC_T3", "Counts ZNC T3", "ZNC_T3", "LBC"); + addNewHisto("COUNTS", "hcounts-ZNC_T4", "Counts ZNC T4", "ZNC_T4", "LBC"); + addNewHisto("COUNTS", "hcounts-ZNC_SUM", "Counts ZNC SUM", "ZNC_SUM", "LBC"); + + addNewHisto("COUNTS", "hcounts-ZPA_TC_TR", "Counts ZPA TC", "ZPA_TC_TR", "LBC"); + addNewHisto("COUNTS", "hcounts-ZPA_T1", "Counts ZPA T1", "ZPA_T1", "LBC"); + addNewHisto("COUNTS", "hcounts-ZPA_T2", "Counts ZPA T2", "ZPA_T2", "LBC"); + addNewHisto("COUNTS", "hcounts-ZPA_T3", "Counts ZPA T3", "ZPA_T3", "LBC"); + addNewHisto("COUNTS", "hcounts-ZPA_T4", "Counts ZPA T4", "ZPA_T4", "LBC"); + addNewHisto("COUNTS", "hcounts-ZPA_SUM", "Counts ZPA SUM", "ZPA_SUM", "LBC"); + + addNewHisto("COUNTS", "hcounts-ZPC_TC_TR", "Counts ZPC TC", "ZPC_TC_TR", "LBC"); + addNewHisto("COUNTS", "hcounts-ZPC_T1", "Counts ZPC T1", "ZPC_T1", "LBC"); + addNewHisto("COUNTS", "hcounts-ZPC_T2", "Counts ZPC T2", "ZPC_T2", "LBC"); + addNewHisto("COUNTS", "hcounts-ZPC_T3", "Counts ZPC T3", "ZPC_T3", "LBC"); + addNewHisto("COUNTS", "hcounts-ZPC_T4", "Counts ZPC T4", "ZPC_T4", "LBC"); + addNewHisto("COUNTS", "hcounts-ZPC_SUM", "Counts ZPC SUM", "ZPC_SUM", "LBC"); + + addNewHisto("COUNTS", "hcounts-ZEM1_TR", "Counts ZEM1", "ZEM1_TR", "LBC"); + addNewHisto("COUNTS", "hcounts-ZEM2_TR", "Counts ZEM2", "ZEM2_TR", "LBC"); + + addNewHisto("COUNTSA", "hcounts_ZNA_TC_TR", "Counts ZNA TC istantaneous", "ZNA_TC_TR", "LBC"); + addNewHisto("COUNTSA", "hcounts_ZNA_SUM", "Counts ZNA SUM istantaneous", "ZNA_SUM", "LBC"); + addNewHisto("COUNTSA", "hcounts_ZNC_TC_TR", "Counts ZNC TC istantaneous", "ZNC_TC_TR", "LBC"); + addNewHisto("COUNTSA", "hcounts_ZNC_SUM", "Counts ZNC SUM istantaneous", "ZNC_SUM", "LBC"); + addNewHisto("COUNTSA", "hcounts_ZPA_TC_TR", "Counts ZPA TC istantaneous", "ZPA_TC_TR", "LBC"); + addNewHisto("COUNTSA", "hcounts_ZPA_SUM", "Counts ZPA SUM istantaneous", "ZPA_SUM", "LBC"); + addNewHisto("COUNTSA", "hcounts_ZPC_TC_TR", "Counts ZPC TC istantaneous", "ZPC_TC_TR", "LBC"); + addNewHisto("COUNTSA", "hcounts_ZPC_SUM", "Counts ZPC SUM istantaneous", "ZPC_SUM", "LBC"); + addNewHisto("COUNTSA", "hcounts_ZEM1_TR", "Counts ZEM1 istantaneous", "ZEM1_TR", "LBC"); + addNewHisto("COUNTSA", "hcounts_ZEM2_TR", "Counts ZEM2 istantaneous", "ZEM2_TR", "LBC"); + + // Histograms Signal + int nBCAheadTrig = 3; + int nbx = (nBCAheadTrig + 1 + 12) * o2::zdc::NTimeBinsPerBC; + double xmin = -nBCAheadTrig * o2::zdc::NTimeBinsPerBC - 0.5; + double xmax = o2::zdc::NTimeBinsPerBC - 0.5 + 12; + // setBinHisto2D(nbx, xmin, xmax, o2::zdc::ADCRange, o2::zdc::ADCMin - 0.5, o2::zdc::ADCMax + 0.5); + if (auto param = mCustomParameters.find("SIGNAL"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - SIGNAL: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(60, -36.5, 23.5, 4096, -2048.5, 2047.5); + } + // Alice Trigger Or Auto Trigger + addNewHisto("SIGNAL", "hsignal-ZNA_TC_TR_AoT", "Signal ZNA TC Trigger Alice OR Auto Trigger", "ZNA_TC_TR", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZNA_T1_AoT", "Signal ZNA T1 Trigger Alice OR Auto Trigger", "ZNA_T1", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZNA_T2_AoT", "Signal ZNA T2 Trigger Alice OR Auto Trigger", "ZNA_T2", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZNA_T3_AoT", "Signal ZNA T3 Trigger Alice OR Auto Trigger", "ZNA_T3", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZNA_T4_AoT", "Signal ZNA T4 Trigger Alice OR Auto Trigger", "ZNA_T4", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZNA_SUM_AoT", "Signal ZNA SUM Trigger Alice OR Auto Trigger", "ZNA_SUM", "AoT"); + + addNewHisto("SIGNAL", "hsignal-ZNC_TC_TR_AoT", "Signal ZNC TC Trigger Alice OR Auto Trigger", "ZNC_TC_TR", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZNC_T1_AoT", "Signal ZNC T1 Trigger Alice OR Auto Trigger", "ZNC_T1", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZNC_T2_AoT", "Signal ZNC T2 Trigger Alice OR Auto Trigger", "ZNC_T2", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZNC_T3_AoT", "Signal ZNC T3 Trigger Alice OR Auto Trigger", "ZNC_T3", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZNC_T4_AoT", "Signal ZNC T4 Trigger Alice OR Auto Trigger", "ZNC_T4", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZNC_SUM_AoT", "Signal ZNC SUM Trigger Alice OR Auto Trigger", "ZNC_SUM", "AoT"); + + addNewHisto("SIGNAL", "hsignal-ZPA_TC_TR_AoT", "Signal ZPA TC Trigger Alice OR Auto Trigger", "ZPA_TC_TR", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZPA_T1_AoT", "Signal ZPA T1 Trigger Alice OR Auto Trigger", "ZPA_T1", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZPA_T2_AoT", "Signal ZPA T2 Trigger Alice OR Auto Trigger", "ZPA_T2", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZPA_T3_AoT", "Signal ZPA T3 Trigger Alice OR Auto Trigger", "ZPA_T3", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZPA_T4_AoT", "Signal ZPA T4 Trigger Alice OR Auto Trigger", "ZPA_T4", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZPA_SUM_AoT", "Signal ZPA SUM Trigger Alice OR Auto Trigger", "ZPA_SUM", "AoT"); + + addNewHisto("SIGNAL", "hsignal-ZPC_TC_TR_AoT", "Signal ZPC TC Trigger Alice OR Auto Trigger", "ZPC_TC_TR", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZPC_T1_AoT", "Signal ZPC T1 Trigger Alice OR Auto Trigger", "ZPC_T1", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZPC_T2_AoT", "Signal ZPC T2 Trigger Alice OR Auto Trigger", "ZPC_T2", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZPC_T3_AoT", "Signal ZPC T3 Trigger Alice OR Auto Trigger", "ZPC_T3", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZPC_T4_AoT", "Signal ZPC T4 Trigger Alice OR Auto Trigger", "ZPC_T4", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZPC_SUM_AoT", "Signal ZPC SUM Trigger Alice OR Auto Trigger", "ZPC_SUM", "AoT"); + + addNewHisto("SIGNAL", "hsignal-ZEM1_TR_AoT", "Signal ZEM1 Trigger Alice OR Auto Trigger", "ZEM1_TR", "AoT"); + addNewHisto("SIGNAL", "hsignal-ZEM2_TR_AoT", "Signal ZEM2 Trigger Alice OR Auto Trigger", "ZEM2_TR", "AoT"); + + // Histograms Bunch Crossing Maps + if (auto param = mCustomParameters.find("BUNCH"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - BUNCH: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(100, -0.5, 99.5, 36, -35.5, 0.5); + } + addNewHisto("BUNCH", "hbunch-ZNA_TC_TR_A0oT0", "Bunch ZNA TC Ali Trigger OR AutoTrigger", "ZNA_TC_TR", "A0oT0"); + addNewHisto("BUNCH", "hbunch-ZNA_SUM_A0oT0", "Bunch ZNA SUM Ali Trigger OR AutoTrigger", "ZNA_SUM", "A0oT0"); + addNewHisto("BUNCH", "hbunch-ZNC_TC_TR_A0oT0", "Bunch ZNC TC Ali Trigger OR AutoTrigger", "ZNC_TC_TR", "A0oT0"); + addNewHisto("BUNCH", "hbunch-ZNC_SUM_A0oT0", "Bunch ZNC SUM Ali Trigger OR AutoTrigger", "ZNC_SUM", "A0oT0"); + addNewHisto("BUNCH", "hbunch-ZPA_TC_TR_A0oT0", "Bunch ZPA TC Ali Trigger OR AutoTrigger", "ZPA_TC_TR", "A0oT0"); + addNewHisto("BUNCH", "hbunch-ZPA_SUM_A0oT0", "Bunch ZPA SUM Ali Trigger OR AutoTrigger", "ZPA_SUM", "A0oT0"); + addNewHisto("BUNCH", "hbunch-ZPC_TC_TR_A0oT0", "Bunch ZPC TC Ali Trigger OR AutoTrigger", "ZPC_TC_TR", "A0oT0"); + addNewHisto("BUNCH", "hbunch-ZPC_SUM_A0oT0", "Bunch ZPC SUM Ali Trigger OR AutoTrigger", "ZPC_SUM", "A0oT0"); + addNewHisto("BUNCH", "hbunch-ZEM1_A0oT0", "Bunch ZEM1 Ali Trigger OR AutoTrigger", "ZEM1_TR", "A0oT0"); + addNewHisto("BUNCH", "hbunch-ZEM2_A0oT0", "Bunch ZEM2 Ali Trigger OR AutoTrigger", "ZEM2_TR", "A0oT0"); + + addNewHisto("BUNCH", "hbunch-ZNA_TC_TR_A0", "Bunch ZNA TC Trigger Alice", "ZNA_TC_TR", "A0"); + addNewHisto("BUNCH", "hbunch-ZNA_SUM_A0", "Bunch ZNA SUM Trigger Alice", "ZNA_SUM", "A0"); + addNewHisto("BUNCH", "hbunch-ZNC_TC_TR_A0", "Bunch ZNC TC Trigger Alice", "ZNC_TC_TR", "A0"); + addNewHisto("BUNCH", "hbunch-ZNC_SUM_A0", "Bunch ZNC SUM Trigger Alice", "ZNC_SUM", "A0"); + addNewHisto("BUNCH", "hbunch-ZPA_TC_TR_A0", "Bunch ZPA TC Trigger Alice", "ZPA_TC_TR", "A0"); + addNewHisto("BUNCH", "hbunch-ZPA_SUM_A0", "Bunch ZPA SUM Trigger Alice", "ZPA_SUM", "A0"); + addNewHisto("BUNCH", "hbunch-ZPC_TC_TR_A0", "Bunch ZPC TC Trigger Alice", "ZPC_TC_TR", "A0"); + addNewHisto("BUNCH", "hbunch-ZPC_SUM_A0", "Bunch ZPC SUM Trigger Alice", "ZPC_SUM", "A0"); + addNewHisto("BUNCH", "hbunch-ZEM1_A0", "Bunch ZEM1 Trigger Alice", "ZEM1_TR", "A0"); + addNewHisto("BUNCH", "hbunch-ZEM2_A0", "Bunch ZEM2 Trigger Alice", "ZEM2_TR", "A0"); + + addNewHisto("BUNCH", "hbunch-ZNA_TC_TR_T0", "Bunch ZNA TC Auto Trigger", "ZNA_TC_TR", "T0"); + addNewHisto("BUNCH", "hbunch-ZNA_SUM_T0", "Bunch ZNA SUM Auto Trigger", "ZNA_SUM", "T0"); + addNewHisto("BUNCH", "hbunch-ZNC_TC_TR_T0", "Bunch ZNC TC Auto Trigger", "ZNC_TC_TR", "T0"); + addNewHisto("BUNCH", "hbunch-ZNC_SUM_T0", "Bunch ZNC SUM Auto Trigger", "ZNC_SUM", "T0"); + addNewHisto("BUNCH", "hbunch-ZPA_TC_TR_T0", "Bunch ZPA TC Auto Trigger", "ZPA_TC_TR", "T0"); + addNewHisto("BUNCH", "hbunch-ZPA_SUM_T0", "Bunch ZPA SUM Auto Trigger", "ZPA_SUM", "T0"); + addNewHisto("BUNCH", "hbunch-ZPC_TC_TR_T0", "Bunch ZPC TC Auto Trigger", "ZPC_TC_TR", "T0"); + addNewHisto("BUNCH", "hbunch-ZPC_SUM_T0", "Bunch ZPC SUM Auto Trigger", "ZPC_SUM", "T0"); + addNewHisto("BUNCH", "hbunch-ZEM1_T0", "Bunch ZEM1 Auto Trigger", "ZEM1_TR", "T0"); + addNewHisto("BUNCH", "hbunch-ZEM2_T0", "Bunch ZEM2 Auto Trigger", "ZEM2_TR", "T0"); + + if (auto param = mCustomParameters.find("TRASMITTEDCHANNEL"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TRASMITTEDCHANNEL: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(8, -0.5, 7.5, 4, -0.5, 3.5); + } + addNewHisto("TRASMITTEDCHANNEL", "hchTrasmitted", "Channels Trasmitted", "NONE", "ALL"); + + if (auto param = mCustomParameters.find("FIRECHANNEL"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - FIRECHANNELL: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(8, -0.5, 7.5, 4, -0.5, 3.5); + } + addNewHisto("FIRECHANNEL", "hchFired", "Channels Fired", "NONE", "ALL"); + + if (auto param = mCustomParameters.find("DATALOSS"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - DATALOSS: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(8, -0.5, 7.5, 4, -0.5, 3.5); + } + addNewHisto("DATALOSS", "hchDataLoss", "Data Loss", "NONE", "ALL"); + + if (auto param = mCustomParameters.find("TRIGGER_BIT"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TRIGGER_BIT: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(32, -0.5, 31.5, 10, -0.5, 9.5); + } + addNewHisto("TRIGGER_BIT", "hchTriggerBits", "Trigger Bits", "NONE", "ALL"); + + if (auto param = mCustomParameters.find("TRIGGER_BIT_HIT"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TRIGGER_BIT_HIT: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(32, -0.5, 31.5, 10, -0.5, 9.5); + } + addNewHisto("TRIGGER_BIT_HIT", "hchTriggerBitsHits", "Trigger Bits Hit", "NONE", "ALL"); + + if (auto param = mCustomParameters.find("OVER_BC"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - OVER_BC: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(32, -0.5, 31.5); + } + addNewHisto("OVER_BC", "hbcOver", "BC Overflow", "NONE", "ALL"); + + if (auto param = mCustomParameters.find("SUMMARYBASELINE"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - SUMMARYBASELINE: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(26, -0.5, 25.5); + } + addNewHisto("SUMMARYBASELINE", "hpedSummary", "Baseline Summary", "NONE", "LBC"); + + if (auto param = mCustomParameters.find("SUMMARYRATE"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - SUMMARYRATE: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(26, -0.5, 25.5); + } + addNewHisto("SUMMARYRATE", "hrateSummary", "Rate Summary (KHz)", "NONE", "LBC"); + + if (auto param = mCustomParameters.find("SUMMARY_ERROR"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - SUMMARY_ERROR: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto2D(26, -0.5, 25.5, 3, -0.5, 2.5); + } + addNewHisto("SUMMARY_ERROR", "herrorSummary", "Raw Data Error (Instantaneous)", "NONE", "ALL"); + if (auto param = mCustomParameters.find("ERROR_NUM_CYCLE"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter ERROR_NUM_CYCLE: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + fErrorCycle = atoi(param->second.c_str()); + } else { + fErrorCycle = 1; + } + if (auto param = mCustomParameters.find("SUMMARY_ALIGN"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - SUMMARY_ALIGN: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(26, 0.5, 26.5, 12, -0.5, 11.5); + } + addNewHisto("SUMMARY_ALIGN", "hAlignPlot", "Alignment Plot (History)", "NONE", "A0oT0"); + addNewHisto("SUMMARY_ALIGN_SHIFT", "hAlignPlotShift", "Alignment Plot (Instantaneous)", "NONE", "A0oT0"); + + if (auto param = mCustomParameters.find("ALIGN_NUM_CYCLE"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter -ALIGN_CYCLE: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + fAlignCycle = atoi(param->second.c_str()); + } else { + fAlignCycle = 1; + } + + if (auto param = mCustomParameters.find("ALIGN_NUM_ENTRIES"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter -ALIGN_NUM_ENTRIES: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + fAlignNumEntries = atoi(param->second.c_str()); + } else { + fAlignNumEntries = 2000; + } + + // Begin Stefan addition + if (auto param = mCustomParameters.find("CONFIG_BC_ALIGN"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - CONFIG_BC_ALIGN: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + FirstEventBC = atoi(param->second.c_str()); + } else { + FirstEventBC = 0; + } + + if (auto param = mCustomParameters.find("BC_ALIGN_PLOT"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - BC_ALIGN_PLOT: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(26, 0.5, 26.5, 12, -0.5, 11.5); + } + addNewHisto("BC_ALIGN_PLOT", "hBCAlignPlot", "BC alignment Plot", "NONE", "NONE"); + // End Stefan addition +} + +void ZDCRawDataTask::init() +{ + gROOT->SetBatch(); + configureRawDataTask(); + // Word id not present in payload + mCh.f.fixed_0 = o2::zdc::Id_wn; + mCh.f.fixed_1 = o2::zdc::Id_wn; + mCh.f.fixed_2 = o2::zdc::Id_wn; +} + +inline int ZDCRawDataTask::getHPos(uint32_t board, uint32_t ch, int matrix[o2::zdc::NModules][o2::zdc::NChPerModule]) +{ + if (board < o2::zdc::NModules && ch < o2::zdc::NChPerModule) { + return matrix[board][ch]; + } else { + LOG(error) << "Wrong board " << board << " ch " << ch; + return -1; + } + return -1; +} + +int ZDCRawDataTask::processWord(const uint32_t* word) +{ + if (word == nullptr) { + printf("NULL\n"); + return 1; + } + if ((word[0] & 0x3) == o2::zdc::Id_w0) { + for (int32_t iw = 0; iw < o2::zdc::NWPerGBTW; iw++) { + mCh.w[0][iw] = word[iw]; + } + } else if ((word[0] & 0x3) == o2::zdc::Id_w1) { + if (mCh.f.fixed_0 == o2::zdc::Id_w0) { + for (int32_t iw = 0; iw < o2::zdc::NWPerGBTW; iw++) { + mCh.w[1][iw] = word[iw]; + } + } else { + LOG(error) << "Wrong word sequence"; + mCh.f.fixed_0 = o2::zdc::Id_wn; + mCh.f.fixed_1 = o2::zdc::Id_wn; + mCh.f.fixed_2 = o2::zdc::Id_wn; + } + } else if ((word[0] & 0x3) == o2::zdc::Id_w2) { + if (mCh.f.fixed_0 == o2::zdc::Id_w0 && mCh.f.fixed_1 == o2::zdc::Id_w1) { + for (int32_t iw = 0; iw < o2::zdc::NWPerGBTW; iw++) { + mCh.w[2][iw] = word[iw]; + } + process(mCh); + } else { + LOG(error) << "Wrong word sequence"; + } + mCh.f.fixed_0 = o2::zdc::Id_wn; + mCh.f.fixed_1 = o2::zdc::Id_wn; + mCh.f.fixed_2 = o2::zdc::Id_wn; + } else { + // Word not present in payload + LOG(error) << "Event format error"; + return 1; + } + return 0; +} + +int ZDCRawDataTask::process(const o2::zdc::EventChData& ch) +{ + int flag_reset = 0; + static constexpr int last_bc = o2::constants::lhc::LHCMaxBunches - 1; + union { + uint16_t uns; + int16_t sig; + } word16; + int flag = 0; + // Not empty event + auto f = ch.f; + uint16_t us[12]; + int16_t s[12]; + us[0] = f.s00; + us[1] = f.s01; + us[2] = f.s02; + us[3] = f.s03; + us[4] = f.s04; + us[5] = f.s05; + us[6] = f.s06; + us[7] = f.s07; + us[8] = f.s08; + us[9] = f.s09; + us[10] = f.s10; + us[11] = f.s11; + int itb = 4 * (int)f.board + (int)f.ch; + if (f.Hit == 1 && fFireChannel) { + fFireChannel->Fill(f.board, f.ch); + } + if (fTrasmChannel) { + fTrasmChannel->Fill(f.board, f.ch); + } + + if (f.Alice_0 || f.Auto_0 || f.Alice_1 || f.Auto_1 || f.Alice_2 || f.Auto_2 || f.Alice_3 || f.Auto_3) { + for (int j = 0; j < (int)fMatrixHistoSignal[f.board][f.ch].size(); j++) { + for (int32_t i = 0; i < 12; i++) { + if (us[i] > o2::zdc::ADCMax) { + s[i] = us[i] - o2::zdc::ADCRange; + } else { + s[i] = us[i]; + } + if ((fMatrixHistoSignal[f.board][f.ch].at(j).condHisto.at(0) == "AoT") && (f.Alice_3 || f.Auto_3)) { + fMatrixHistoSignal[f.board][f.ch].at(j).histo->Fill(i - 36., double(s[i])); + } + if ((fMatrixHistoSignal[f.board][f.ch].at(j).condHisto.at(0) == "AoT") && (f.Alice_2 || f.Auto_2)) { + fMatrixHistoSignal[f.board][f.ch].at(j).histo->Fill(i - 24., double(s[i])); + } + if ((fMatrixHistoSignal[f.board][f.ch].at(j).condHisto.at(0) == "AoT") && (f.Alice_1 || f.Auto_1)) { + fMatrixHistoSignal[f.board][f.ch].at(j).histo->Fill(i - 12., double(s[i])); + } + if ((fMatrixHistoSignal[f.board][f.ch].at(j).condHisto.at(0) == "AoT") && (f.Alice_0 || f.Auto_0)) { + fMatrixHistoSignal[f.board][f.ch].at(j).histo->Fill(i + 0., double(s[i])); + if (f.Auto_0) { + fMatrixAlign[f.board][f.ch].minSample.vSamples[i].num_entry += 1; + fMatrixAlign[f.board][f.ch].minSample.vSamples[i].sum += (int)s[i]; + fMatrixAlign[f.board][f.ch].minSample.vSamples[i].mean = (double)fMatrixAlign[f.board][f.ch].minSample.vSamples[i].sum / (double)fMatrixAlign[f.board][f.ch].minSample.vSamples[i].num_entry; + if (fMatrixAlign[f.board][f.ch].minSample.vSamples[0].num_entry > fAlignNumEntries && fMatrixAlign[f.board][f.ch].minSample.vSamples[i].mean < fMatrixAlign[f.board][f.ch].minSample.min_mean) { + fMatrixAlign[f.board][f.ch].minSample.id_min_sample = i; + fMatrixAlign[f.board][f.ch].minSample.min_mean = fMatrixAlign[f.board][f.ch].minSample.vSamples[i].mean; + fMatrixAlign[f.board][f.ch].minSample.num_entry = fMatrixAlign[f.board][f.ch].minSample.vSamples[i].num_entry; + } + } + } + } + } + } + + if (fNumCycle == fAlignCycle) { + fSummaryAlignShift->Reset(); + for (int i_mod = 0; i_mod < o2::zdc::NModules; i_mod++) { + for (int i_ch = 0; i_ch < o2::zdc::NChPerModule; i_ch++) { + if (fMatrixAlign[i_mod][i_ch].minSample.vSamples[0].num_entry > 0) { + fSummaryAlign->Fill(fMatrixAlign[i_mod][i_ch].bin, fMatrixAlign[i_mod][i_ch].minSample.id_min_sample); + fSummaryAlignShift->Fill(fMatrixAlign[i_mod][i_ch].bin, fMatrixAlign[i_mod][i_ch].minSample.id_min_sample); + } + } + } + resetAlign(); + fNumCycle = 0; + } + + // Begin Stefan addiiton + if (fBCalignment && (f.bc > (FirstEventBC - 7)) && (f.bc < (FirstEventBC + 6))) { + if (f.Hit) { + fBCalignment->Fill(fMatrixAlign[f.board][f.ch].bin, f.bc); + // fBCalignment->Fill(fMatrixAlign[f.board][f.ch].bin -1, f.bc); + } + } + // End Stefan addition + + if ((f.Alice_0 || f.Auto_0 || f.Alice_1 || f.Auto_1 || f.Alice_2 || f.Auto_2 || f.Alice_3 || f.Auto_3 || f.Auto_m) && fTriggerBits && fTriggerBitsHits) { + if (f.Alice_3) { + fTriggerBits->Fill(itb, 9); + if (f.Hit) { + if (f.Hit) { + fTriggerBitsHits->Fill(itb, 9); + } + } + } + if (f.Alice_2) { + fTriggerBits->Fill(itb, 8); + if (f.Hit) { + fTriggerBitsHits->Fill(itb, 8); + } + } + if (f.Alice_1) { + fTriggerBits->Fill(itb, 7); + if (f.Hit) { + fTriggerBitsHits->Fill(itb, 7); + } + } + if (f.Alice_0) { + fTriggerBits->Fill(itb, 6); + if (f.Hit) { + fTriggerBitsHits->Fill(itb, 6); + } + } + if (f.Auto_3) { + fTriggerBits->Fill(itb, 5); + if (f.Hit) { + fTriggerBitsHits->Fill(itb, 5); + } + } + if (f.Auto_2) { + fTriggerBits->Fill(itb, 4); + if (f.Hit) { + fTriggerBitsHits->Fill(itb, 4); + } + } + if (f.Auto_1) { + fTriggerBits->Fill(itb, 3); + if (f.Hit) { + fTriggerBitsHits->Fill(itb, 3); + } + } + if (f.Auto_0) { + fTriggerBits->Fill(itb, 2); + if (f.Hit) { + fTriggerBitsHits->Fill(itb, 2); + } + } + if (f.Auto_m) { + fTriggerBits->Fill(itb, 1); + if (f.Hit) { + fTriggerBitsHits->Fill(itb, 1); + } + } + } + if (!(f.Alice_3 || f.Alice_2 || f.Alice_1 || f.Alice_0 || f.Alice_1 || f.Auto_3 || f.Auto_2 || f.Auto_1 || f.Auto_0 || f.Auto_m)) { + if (fTriggerBits && fTriggerBitsHits) { + fTriggerBits->Fill(itb, 0); + if (f.Hit) + fTriggerBitsHits->Fill(itb, 0); + } + } + // Bunch + if (fNumCycleErr == fErrorCycle) { + fSummaryError->Reset(); + fNumCycleErr = 0; + } + if ((fOverBc) && f.bc >= o2::constants::lhc::LHCMaxBunches) { + fOverBc->Fill(itb); + if (fSummaryError) { + fSummaryError->Fill(fMatrixAlign[f.board][f.ch].bin - 1, 2); + } + } + if (f.Alice_0 || f.Auto_0) { + double bc_d = uint32_t(f.bc / 100); + double bc_m = uint32_t(f.bc % 100); + for (int i = 0; i < (int)fMatrixHistoBunch[f.board][f.ch].size(); i++) { + if (fMatrixHistoBunch[f.board][f.ch].at(i).condHisto.at(0) == "A0oT0") { + fMatrixHistoBunch[f.board][f.ch].at(i).histo->Fill(bc_m, -bc_d); + } + if (f.Alice_0 && fMatrixHistoBunch[f.board][f.ch].at(i).condHisto.at(0) == "A0") { + fMatrixHistoBunch[f.board][f.ch].at(i).histo->Fill(bc_m, -bc_d); + } + if (f.Auto_0 && fMatrixHistoBunch[f.board][f.ch].at(i).condHisto.at(0) == "T0") { + fMatrixHistoBunch[f.board][f.ch].at(i).histo->Fill(bc_m, -bc_d); + } + } + } + if (f.bc == last_bc) { + // Fill Baseline + // int32_t offset =(int32_t) f.offset; + // if (offset > 32768) offset = offset - 32768; + word16.uns = f.offset; + // if (word16.sig < 0) word16.sig = word16.sig + 32768; + for (int i = 0; i < (int)fMatrixHistoBaseline[f.board][f.ch].size(); i++) { + fMatrixHistoBaseline[f.board][f.ch].at(i).histo->Fill(word16.sig / 12.); + } + + // Fill Data Loss + if (fDataLoss && (f.dLoss)) { + fDataLoss->Fill(f.board, f.ch); + if (fSummaryError) { + fSummaryError->Fill(fMatrixAlign[f.board][f.ch].bin - 1, 1); + } + } + + // Fill Bit Error + if (fSummaryError && (f.error)) { + fSummaryError->Fill(fMatrixAlign[f.board][f.ch].bin - 1, 0); + } + + // Fill counts + for (int i = 0; i < (int)fMatrixHistoCounts[f.board][f.ch].size(); i++) { + fMatrixHistoCounts[f.board][f.ch].at(i).histo->Fill(f.hits & 0xfff); + } + + // Fill counts for trending + for (int i = 0; i < (int)fMatrixHistoCounts_a[f.board][f.ch].size(); i++) { + fMatrixHistoCounts_a[f.board][f.ch].at(i).histo->Fill(f.hits & 0xfff); + } + + // Fill Summary + if (fMapBinNameIdSummaryHisto.find(getNameChannel(f.board, f.ch)) != fMapBinNameIdSummaryHisto.end()) { + if (fMatrixHistoBaseline[f.board][f.ch].size() > 0) { + if (fSummaryPedestal && fMatrixHistoBaseline[f.board][f.ch].at(0).histo) { + fSummaryPedestal->SetBinContent(fMapBinNameIdSummaryHisto[getNameChannel(f.board, f.ch)], fMatrixHistoBaseline[f.board][f.ch].at(0).histo->GetMean()); + fSummaryPedestal->SetBinError(fMapBinNameIdSummaryHisto[getNameChannel(f.board, f.ch)], fMatrixHistoBaseline[f.board][f.ch].at(0).histo->GetMeanError()); + } + if (fSummaryRate && fMatrixHistoBaseline[f.board][f.ch].at(0).histo) { + fSummaryRate->SetBinContent(fMapBinNameIdSummaryHisto[getNameChannel(f.board, f.ch)], fMatrixHistoCounts[f.board][f.ch].at(0).histo->GetMean() * 11.2455); + fSummaryRate->SetBinError(fMapBinNameIdSummaryHisto[getNameChannel(f.board, f.ch)], fMatrixHistoCounts[f.board][f.ch].at(0).histo->GetMeanError()); + } + } + } + } + return 0; +} + +int ZDCRawDataTask::process(const o2::zdc::EventData& ev) +{ + for (int32_t im = 0; im < o2::zdc::NModules; im++) { + for (int32_t ic = 0; ic < o2::zdc::NChPerModule; ic++) { + if (ev.data[im][ic].f.fixed_0 == o2::zdc::Id_w0 && ev.data[im][ic].f.fixed_1 == o2::zdc::Id_w1 && ev.data[im][ic].f.fixed_2 == o2::zdc::Id_w2) { + process(ev.data[im][ic]); + } else if (ev.data[im][ic].f.fixed_0 == 0 && ev.data[im][ic].f.fixed_1 == 0 && ev.data[im][ic].f.fixed_2 == 0) { + // Empty channel + } else { + LOG(error) << "Data format error"; + } + } + } + return 0; +} + +std::string ZDCRawDataTask::getNameChannel(int imod, int ich) +{ + return fNameChannel[imod][ich]; +} + +void ZDCRawDataTask::setNameChannel(int imod, int ich, std::string namech, int bin) +{ + sSample sample; + fNameChannel[imod][ich] = namech; + std::vector coord; + coord.push_back(imod); + coord.push_back(ich); + fMapChNameModCh.insert(std::pair>(namech, coord)); + + // init structure alignment + fMatrixAlign[imod][ich].name_ch = namech; + fMatrixAlign[imod][ich].bin = bin; + fMatrixAlign[imod][ich].minSample.id_min_sample = -1; + fMatrixAlign[imod][ich].minSample.min_mean = 2048.0; + fMatrixAlign[imod][ich].minSample.num_entry = 0; + for (int i = 0; i < 12; i++) { + sample.id_sample = i; + sample.num_entry = 0; + sample.mean = 0.0; + fMatrixAlign[imod][ich].minSample.vSamples.push_back(sample); + } +} + +bool ZDCRawDataTask::getModAndCh(std::string chName, int* module, int* channel) +{ + if (chName == "NONE") { + return true; + } + if (fMapChNameModCh.find(chName) != fMapChNameModCh.end()) { + *module = fMapChNameModCh[chName].at(0); + *channel = fMapChNameModCh[chName].at(1); + return true; + } + return false; +} + +void ZDCRawDataTask::setBinHisto1D(int numBinX, double minBinX, double maxBinX) +{ + setNumBinX(numBinX); + setMinBinX(minBinX); + setMaxBinX(maxBinX); +} + +void ZDCRawDataTask::setBinHisto2D(int numBinX, double minBinX, double maxBinX, int numBinY, double minBinY, double maxBinY) +{ + setNumBinX(numBinX); + setMinBinX(minBinX); + setMaxBinX(maxBinX); + setNumBinY(numBinY); + setMinBinY(minBinY); + setMaxBinY(maxBinY); +} + +bool ZDCRawDataTask::addNewHisto(std::string type, std::string name, std::string title, std::string chName, std::string condition) +{ + int mod; + int ch; + int ih; // index Histogram + infoHisto1D h1d; + infoHisto2D h2d; + // Check if the channel name defined + if (getModAndCh(chName, &mod, &ch) == true) { + // Check if channel selected produced data. (_SP spare _OTR only trigger) + if (chName.find("_SP") != std::string::npos || chName.find("_OTR") != std::string::npos) { + return false; + } + TString hname = TString::Format("%s", name.c_str()); + TString htit = TString::Format("%s", title.c_str()); + + // BASELINE + if (type == "BASELINE") { + // Check if Histogram Exist + if (std::find(fNameHisto.begin(), fNameHisto.end(), name) == fNameHisto.end()) { + fNameHisto.push_back(name); + h1d.histo = new TH1F(hname, htit, fNumBinX, fMinBinX, fMaxBinX); + h1d.condHisto.push_back(condition); + ih = (int)fMatrixHistoBaseline[mod][ch].size(); + fMatrixHistoBaseline[mod][ch].push_back(h1d); + + if (ih < (int)fMatrixHistoBaseline[mod][ch].size()) { + getObjectsManager()->startPublishing(fMatrixHistoBaseline[mod][ch].at(ih).histo); + try { + getObjectsManager()->addMetadata(fMatrixHistoBaseline[mod][ch].at(ih).histo->GetName(), fMatrixHistoBaseline[mod][ch].at(ih).histo->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fMatrixHistoBaseline[mod][ch].at(ih).histo->GetName() << ENDM; + return false; + } + + delete h1d.histo; + h1d.condHisto.clear(); + } + return true; + } else { + for (int i = 0; i < (int)fMatrixHistoBaseline[mod][ch].size(); i++) { + fMatrixHistoBaseline[mod][ch].at(i).histo->Reset(); + } + return true; + } + } + // COUNTS + if (type == "COUNTS") { + // Check if Histogram Exist + if (std::find(fNameHisto.begin(), fNameHisto.end(), name) == fNameHisto.end()) { + fNameHisto.push_back(name); + h1d.histo = new TH1F(hname, htit, fNumBinX, fMinBinX, fMaxBinX); + h1d.condHisto.push_back(condition); + ih = (int)fMatrixHistoCounts[mod][ch].size(); + fMatrixHistoCounts[mod][ch].push_back(h1d); + + if (ih < (int)fMatrixHistoCounts[mod][ch].size()) { + getObjectsManager()->startPublishing(fMatrixHistoCounts[mod][ch].at(ih).histo); + try { + getObjectsManager()->addMetadata(fMatrixHistoCounts[mod][ch].at(ih).histo->GetName(), fMatrixHistoCounts[mod][ch].at(ih).histo->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fMatrixHistoCounts[mod][ch].at(ih).histo->GetName() << ENDM; + return false; + } + + delete h1d.histo; + h1d.condHisto.clear(); + } + return true; + } else { + for (int i = 0; i < (int)fMatrixHistoCounts[mod][ch].size(); i++) { + fMatrixHistoCounts[mod][ch].at(i).histo->Reset(); + } + return true; + } + } + + if (type == "COUNTSA") { + // Check if Histogram Exist + if (std::find(fNameHisto.begin(), fNameHisto.end(), name) == fNameHisto.end()) { + fNameHisto.push_back(name); + h1d.histo = new TH1F(hname, htit, fNumBinX, fMinBinX, fMaxBinX); + h1d.condHisto.push_back(condition); + ih = (int)fMatrixHistoCounts_a[mod][ch].size(); + fMatrixHistoCounts_a[mod][ch].push_back(h1d); + + if (ih < (int)fMatrixHistoCounts_a[mod][ch].size()) { + getObjectsManager()->startPublishing(fMatrixHistoCounts_a[mod][ch].at(ih).histo); + try { + getObjectsManager()->addMetadata(fMatrixHistoCounts_a[mod][ch].at(ih).histo->GetName(), fMatrixHistoCounts_a[mod][ch].at(ih).histo->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fMatrixHistoCounts_a[mod][ch].at(ih).histo->GetName() << ENDM; + return false; + } + + delete h1d.histo; + h1d.condHisto.clear(); + } + return true; + } else { + for (int i = 0; i < (int)fMatrixHistoCounts_a[mod][ch].size(); i++) { + fMatrixHistoCounts_a[mod][ch].at(i).histo->Reset(); + } + return true; + } + } + + // SIGNAL + if (type == "SIGNAL") { + // Check if Histogram Exist + if (std::find(fNameHisto.begin(), fNameHisto.end(), name) == fNameHisto.end()) { + fNameHisto.push_back(name); + h2d.histo = new TH2F(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + h2d.histo->GetXaxis()->SetTitle("Sample number"); + h2d.histo->GetYaxis()->SetTitle("ADC units"); + h2d.condHisto.push_back(condition); + ih = (int)fMatrixHistoSignal[mod][ch].size(); + fMatrixHistoSignal[mod][ch].push_back(h2d); + + if (ih < (int)fMatrixHistoSignal[mod][ch].size()) { + getObjectsManager()->startPublishing(fMatrixHistoSignal[mod][ch].at(ih).histo); + try { + getObjectsManager()->addMetadata(fMatrixHistoSignal[mod][ch].at(ih).histo->GetName(), fMatrixHistoSignal[mod][ch].at(ih).histo->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fMatrixHistoSignal[mod][ch].at(ih).histo->GetName() << ENDM; + return false; + } + + delete h2d.histo; + h2d.condHisto.clear(); + } + return true; + } else { + for (int i = 0; i < (int)fMatrixHistoSignal[mod][ch].size(); i++) { + fMatrixHistoSignal[mod][ch].at(i).histo->Reset(); + } + return true; + } + } + // BUNCH + if (type == "BUNCH") { + // Check if Histogram Exist + if (std::find(fNameHisto.begin(), fNameHisto.end(), name) == fNameHisto.end()) { + fNameHisto.push_back(name); + h2d.histo = new TH2F(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + h2d.condHisto.push_back(condition); + h2d.histo->SetStats(0); + ih = (int)fMatrixHistoBunch[mod][ch].size(); + fMatrixHistoBunch[mod][ch].push_back(h2d); + + if (ih < (int)fMatrixHistoBunch[mod][ch].size()) { + getObjectsManager()->startPublishing(fMatrixHistoBunch[mod][ch].at(ih).histo); + try { + getObjectsManager()->addMetadata(fMatrixHistoBunch[mod][ch].at(ih).histo->GetName(), fMatrixHistoBunch[mod][ch].at(ih).histo->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fMatrixHistoBunch[mod][ch].at(ih).histo->GetName() << ENDM; + return false; + } + + delete h2d.histo; + h2d.condHisto.clear(); + } + return true; + } else { + for (int i = 0; i < (int)fMatrixHistoBunch[mod][ch].size(); i++) { + fMatrixHistoBunch[mod][ch].at(i).histo->Reset(); + fMatrixHistoBunch[mod][ch].at(i).histo->SetStats(0); + } + return true; + } + } + + if (type == "FIRECHANNEL") { + fFireChannel = new TH2I(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + fFireChannel->SetStats(0); + getObjectsManager()->startPublishing(fFireChannel); + try { + getObjectsManager()->addMetadata(fFireChannel->GetName(), fFireChannel->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fFireChannel->GetName() << ENDM; + return false; + } + } + if (type == "DATALOSS") { + fDataLoss = new TH2F(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + getObjectsManager()->startPublishing(fDataLoss); + try { + getObjectsManager()->addMetadata(fDataLoss->GetName(), fDataLoss->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fDataLoss->GetName() << ENDM; + return false; + } + } + if (type == "TRASMITTEDCHANNEL") { + fTrasmChannel = new TH2I(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + fTrasmChannel->SetStats(0); + getObjectsManager()->startPublishing(fTrasmChannel); + try { + getObjectsManager()->addMetadata(fTrasmChannel->GetName(), fTrasmChannel->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fTrasmChannel->GetName() << ENDM; + return false; + } + } + if (type == "TRIGGER_BIT") { + fTriggerBits = new TH2F(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + fTriggerBits->GetYaxis()->SetBinLabel(10, "Alice_3"); + fTriggerBits->GetYaxis()->SetBinLabel(9, "Alice_2"); + fTriggerBits->GetYaxis()->SetBinLabel(8, "Alice_1"); + fTriggerBits->GetYaxis()->SetBinLabel(7, "Alice_0"); + fTriggerBits->GetYaxis()->SetBinLabel(6, "Auto_3"); + fTriggerBits->GetYaxis()->SetBinLabel(5, "Auto_2"); + fTriggerBits->GetYaxis()->SetBinLabel(4, "Auto_1"); + fTriggerBits->GetYaxis()->SetBinLabel(3, "Auto_0"); + fTriggerBits->GetYaxis()->SetBinLabel(2, "Auto_m"); + fTriggerBits->GetYaxis()->SetBinLabel(1, "None"); + for (int im = 0; im < o2::zdc::NModules; im++) { + for (int ic = 0; ic < o2::zdc::NChPerModule; ic++) { + fTriggerBits->GetXaxis()->SetBinLabel(im * o2::zdc::NChPerModule + ic + 1, TString::Format("%d%d", im, ic)); + } + } + fTriggerBits->SetStats(0); + getObjectsManager()->startPublishing(fTriggerBits); + try { + getObjectsManager()->addMetadata(fTriggerBits->GetName(), fTriggerBits->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fTriggerBits->GetName() << ENDM; + return false; + } + } + if (type == "TRIGGER_BIT_HIT") { + fTriggerBitsHits = new TH2F(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + fTriggerBitsHits->GetYaxis()->SetBinLabel(10, "Alice_3"); + fTriggerBitsHits->GetYaxis()->SetBinLabel(9, "Alice_2"); + fTriggerBitsHits->GetYaxis()->SetBinLabel(8, "Alice_1"); + fTriggerBitsHits->GetYaxis()->SetBinLabel(7, "Alice_0"); + fTriggerBitsHits->GetYaxis()->SetBinLabel(6, "Auto_3"); + fTriggerBitsHits->GetYaxis()->SetBinLabel(5, "Auto_2"); + fTriggerBitsHits->GetYaxis()->SetBinLabel(4, "Auto_1"); + fTriggerBitsHits->GetYaxis()->SetBinLabel(3, "Auto_0"); + fTriggerBitsHits->GetYaxis()->SetBinLabel(2, "Auto_m"); + fTriggerBitsHits->GetYaxis()->SetBinLabel(1, "None"); + fTriggerBitsHits->SetStats(0); + for (int im = 0; im < o2::zdc::NModules; im++) { + for (int ic = 0; ic < o2::zdc::NChPerModule; ic++) { + fTriggerBitsHits->GetXaxis()->SetBinLabel(im * o2::zdc::NChPerModule + ic + 1, TString::Format("%d%d", im, ic)); + } + } + getObjectsManager()->startPublishing(fTriggerBitsHits); + try { + getObjectsManager()->addMetadata(fTriggerBitsHits->GetName(), fTriggerBitsHits->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fTriggerBitsHits->GetName() << ENDM; + return false; + } + } + if (type == "OVER_BC") { + fOverBc = new TH1F(hname, htit, fNumBinX, fMinBinX, fMaxBinX); + for (int im = 0; im < o2::zdc::NModules; im++) { + for (int ic = 0; ic < o2::zdc::NChPerModule; ic++) { + fOverBc->GetXaxis()->SetBinLabel(im * o2::zdc::NChPerModule + ic + 1, TString::Format("%d%d", im, ic)); + } + } + fOverBc->SetStats(0); + getObjectsManager()->startPublishing(fOverBc); + try { + getObjectsManager()->addMetadata(fOverBc->GetName(), fOverBc->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << fOverBc->GetName() << ENDM; + return false; + } + } + + if ((type == "SUMMARYBASELINE") || (type == "SUMMARYRATE") || (type == "SUMMARY_ALIGN") || (type == "SUMMARY_ALIGN_SHIFT") || (type == "SUMMARY_ERROR") || (type == "BC_ALIGN_PLOT")) { + // Begin Stefan addition + if (type == "BC_ALIGN_PLOT") { + fBCalignment = new TH2D(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, FirstEventBC - 6 - 0.5, FirstEventBC + 6 - 0.5); + fBCalignment->GetXaxis()->LabelsOption("v"); + fBCalignment->SetStats(0); + fBCalignment->GetYaxis()->SetTitle("Bunch Crossing [#]"); + } + // End Stefan addition + if (type == "SUMMARYBASELINE") { + fSummaryPedestal = new TH1F(hname, htit, fNumBinX, fMinBinX, fMaxBinX); + fSummaryPedestal->GetXaxis()->LabelsOption("v"); + fSummaryPedestal->SetStats(0); + } + if (type == "SUMMARYRATE") { + fSummaryRate = new TH1F(hname, htit, fNumBinX, fMinBinX, fMaxBinX); + fSummaryRate->GetXaxis()->LabelsOption("v"); + fSummaryRate->SetStats(0); + } + if (type == "SUMMARY_ALIGN") { + fSummaryAlign = new TH2D(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + fSummaryAlign->GetXaxis()->LabelsOption("v"); + fSummaryAlign->SetStats(0); + } + if (type == "SUMMARY_ALIGN_SHIFT") { + fSummaryAlignShift = new TH2D(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + fSummaryAlignShift->GetXaxis()->LabelsOption("v"); + fSummaryAlignShift->SetStats(0); + } + if (type == "SUMMARY_ERROR") { + fSummaryError = new TH2D(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + fSummaryError->GetXaxis()->LabelsOption("v"); + fSummaryError->SetStats(0); + } + int i = 0; + for (uint32_t imod = 0; imod < o2::zdc::NModules; imod++) { + for (uint32_t ich = 0; ich < o2::zdc::NChPerModule; ich++) { + chName = getNameChannel(imod, ich); + if (chName.find("_SP") != std::string::npos || chName.find("_OTR") != std::string::npos) { + continue; + } else { + i++; + // Begin Stefan addition + if (type == "BC_ALIGN_PLOT") { + fBCalignment->GetXaxis()->SetBinLabel(fMatrixAlign[imod][ich].bin, TString::Format("%s", fMatrixAlign[imod][ich].name_ch.c_str())); + } + // Begin Stefan addition + if (type == "SUMMARYBASELINE") { + fSummaryPedestal->GetXaxis()->SetBinLabel(i, TString::Format("%s", getNameChannel(imod, ich).c_str())); + } + if (type == "SUMMARYRATE") { + fSummaryRate->GetXaxis()->SetBinLabel(i, TString::Format("%s", getNameChannel(imod, ich).c_str())); + } + if (type == "SUMMARY_ERROR") { + fSummaryError->GetXaxis()->SetBinLabel(fMatrixAlign[imod][ich].bin, TString::Format("%s", fMatrixAlign[imod][ich].name_ch.c_str())); + } + if (type == "SUMMARY_ALIGN") { + fSummaryAlign->GetXaxis()->SetBinLabel(fMatrixAlign[imod][ich].bin, TString::Format("%s", fMatrixAlign[imod][ich].name_ch.c_str())); + } + if (type == "SUMMARY_ALIGN_SHIFT") { + fSummaryAlignShift->GetXaxis()->SetBinLabel(fMatrixAlign[imod][ich].bin, TString::Format("%s", fMatrixAlign[imod][ich].name_ch.c_str())); + } + fMapBinNameIdSummaryHisto.insert(std::pair(chName, i)); + } + } + } + // Begin Stefan addition + if (type == "BC_ALIGN_PLOT") { + getObjectsManager()->startPublishing(fBCalignment); + } + // Begin Stefan addition + if (type == "SUMMARYBASELINE") { + getObjectsManager()->startPublishing(fSummaryPedestal); + } + if (type == "SUMMARYRATE") { + getObjectsManager()->startPublishing(fSummaryRate); + } + if (type == "SUMMARY_ALIGN") { + getObjectsManager()->startPublishing(fSummaryAlign); + } + if (type == "SUMMARY_ALIGN_SHIFT") { + getObjectsManager()->startPublishing(fSummaryAlignShift); + } + if (type == "SUMMARY_ERROR") { + fSummaryError->GetYaxis()->SetBinLabel(1, TString::Format("Bit Error")); + fSummaryError->GetYaxis()->SetBinLabel(2, TString::Format("Data Loss")); + fSummaryError->GetYaxis()->SetBinLabel(3, TString::Format("Data Corrupted")); + getObjectsManager()->startPublishing(fSummaryError); + } + try { + // Begin Stefan addition + if (type == "BC_ALIGN_PLOT") { + getObjectsManager()->addMetadata(fBCalignment->GetName(), fBCalignment->GetName(), "34"); + } + // Begin Stefan addition + if (type == "SUMMARYBASELINE") { + getObjectsManager()->addMetadata(fSummaryPedestal->GetName(), fSummaryPedestal->GetName(), "34"); + } + if (type == "SUMMARYRATE") { + getObjectsManager()->addMetadata(fSummaryRate->GetName(), fSummaryRate->GetName(), "34"); + } + if (type == "SUMMARY_ALIGN") { + getObjectsManager()->addMetadata(fSummaryAlign->GetName(), fSummaryAlign->GetName(), "34"); + } + if (type == "SUMMARY_ALIGN_SHIFT") { + getObjectsManager()->addMetadata(fSummaryAlign->GetName(), fSummaryAlignShift->GetName(), "34"); + } + if (type == "SUMMARY_ERROR") { + getObjectsManager()->addMetadata(fSummaryError->GetName(), fSummaryError->GetName(), "34"); + } + return true; + } catch (...) { + // Begin Stefan addition + if (type == "BC_ALIGN_PLOT") { + ILOG(Warning, Support) << "Metadata could not be added to " << fBCalignment->GetName() << ENDM; + } + // Begin Stefan addition + if (type == "SUMMARYBASELINE") { + ILOG(Warning, Support) << "Metadata could not be added to " << fSummaryPedestal->GetName() << ENDM; + } + if (type == "SUMMARYRATE") { + ILOG(Warning, Support) << "Metadata could not be added to " << fSummaryRate->GetName() << ENDM; + } + if (type == "SUMMARY_ERROR") { + ILOG(Warning, Support) << "Metadata could not be added to " << fSummaryError->GetName() << ENDM; + } + if (type == "SUMMARY_ALIGN") { + ILOG(Warning, Support) << "Metadata could not be added to " << fSummaryAlign->GetName() << ENDM; + } + if (type == "SUMMARY_ALIGN_SHIFT") { + ILOG(Warning, Support) << "Metadata could not be added to " << fSummaryAlignShift->GetName() << ENDM; + } + return false; + } + } + } + return false; +} + +// Decode Configuration file + +std::vector ZDCRawDataTask::tokenLine(std::string Line, std::string Delimiter) +{ + std::string token; + size_t pos = 0; + int i = 0; + std::vector stringToken; + while ((pos = Line.find(Delimiter)) != std::string::npos) { + token = Line.substr(i, pos); + stringToken.push_back(token); + Line.erase(0, pos + Delimiter.length()); + } + stringToken.push_back(Line); + return stringToken; +} + +std::string ZDCRawDataTask::removeSpaces(std::string s) +{ + std::string result = boost::trim_copy(s); + while (result.find(" ") != result.npos) + boost::replace_all(result, " ", ""); + return result; +} + +bool ZDCRawDataTask::configureRawDataTask() +{ + + std::ifstream file(std::getenv("CONF_HISTO_QCZDCRAW")); + std::string line; + std::vector tokenString; + int line_number = 0; + bool check; + bool error = true; + if (!file) { + initHisto(); + } else { + ILOG(Debug, Devel) << "initialize ZDC RAW DATA HISTOGRAMS FROM FILE" << ENDM; + while (getline(file, line)) { + line_number++; + tokenString = tokenLine(line, ";"); + // ILOG(Info, Support) << TString::Format("Decode Line number %d %s",line_number, line.c_str()) << ENDM; + if (line.compare(0, 1, "#") == 0 || line.compare(0, 1, "\n") == 0 || line.compare(0, 1, " ") == 0 || line.compare(0, 1, "\t") == 0 || line.compare(0, 1, "") == 0) + check = true; + else { + check = decodeConfLine(tokenString, line_number); + } + if (check == false) { + error = false; + } + tokenString.clear(); + } + } + if (error == false) { + initHisto(); + } + file.close(); + return true; +} + +bool ZDCRawDataTask::decodeConfLine(std::vector TokenString, int LineNumber) +{ + // Module Mapping + // ILOG(Info, Support) << TString::Format("Key Word %s",TokenString.at(0).c_str()) << ENDM; + + if (strcmp(TokenString.at(0).c_str(), "MODULE") == 0) { + return decodeModule(TokenString, LineNumber); + } + // Bin Histograms + if (TokenString.at(0) == "BIN") { + return decodeBinHistogram(TokenString, LineNumber); + } + if (TokenString.at(0) == "BASELINE") { + return decodeBaseline(TokenString, LineNumber); + } + if (TokenString.at(0) == "COUNTS") { + return decodeCounts(TokenString, LineNumber); + } + if (TokenString.at(0) == "SIGNAL") { + return decodeSignal(TokenString, LineNumber); + } + if (TokenString.at(0) == "BUNCH") { + return decodeBunch(TokenString, LineNumber); + } + if (TokenString.at(0) == "TRASMITTEDCHANNEL") { + return decodeTrasmittedChannel(TokenString, LineNumber); + } + if (TokenString.at(0) == "FIRECHANNEL") { + return decodeFireChannel(TokenString, LineNumber); + } + if (TokenString.at(0) == "DATALOSS") { + return decodeDataLoss(TokenString, LineNumber); + } + if (TokenString.at(0) == "TRIGGER_BIT") { + return decodeTriggerBitChannel(TokenString, LineNumber); + } + if (TokenString.at(0) == "TRIGGER_BIT_HIT") { + return decodeTriggerBitHitChannel(TokenString, LineNumber); + } + if (TokenString.at(0) == "OVER_BC") { + return decodeOverBc(TokenString, LineNumber); + } + if ((TokenString.at(0) == "SUMMARYBASELINE") || (TokenString.at(0) == "SUMMARYRATE")) { + return decodeSummary(TokenString, LineNumber); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d Key word %s does not exist.", LineNumber, TokenString.at(0).c_str()) << ENDM; + return false; +} + +bool ZDCRawDataTask::decodeModule(std::vector TokenString, int LineNumber) +{ + int imod = atoi(TokenString.at(1).c_str()); + int ich = 0; + // Module Number + if (imod >= o2::zdc::NModules) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d Module Number %d is too big.", LineNumber, imod) << ENDM; + return false; + } + // Check number channels + if (TokenString.size() - 2 > o2::zdc::NChPerModule) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d Insert too channels", LineNumber) << ENDM; + return false; + } + // set Name Channels + for (int i = 2; i < (int)TokenString.size(); i++) { + setNameChannel(imod, ich, TokenString.at(i), 0); + ich = ich + 1; + } + return true; +} + +bool ZDCRawDataTask::decodeBinHistogram(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 4) { + setBinHisto1D(atoi(TokenString.at(1).c_str()), atoi(TokenString.at(2).c_str()), atoi(TokenString.at(3).c_str())); + return true; + } + if (TokenString.size() == 7) { + setBinHisto2D(atoi(TokenString.at(1).c_str()), atoi(TokenString.at(2).c_str()), atoi(TokenString.at(3).c_str()), atoi(TokenString.at(4).c_str()), atoi(TokenString.at(5).c_str()), atoi(TokenString.at(6).c_str())); + return true; + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d %s;%s;%s;%s BIN is not correct number of paramiter %d", LineNumber, TokenString.at(0).c_str(), TokenString.at(1).c_str(), TokenString.at(2).c_str(), TokenString.at(3).c_str(), (int)TokenString.size()) << ENDM; + return false; +} + +bool ZDCRawDataTask::decodeBaseline(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} + +bool ZDCRawDataTask::decodeCounts(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} + +bool ZDCRawDataTask::decodeSignal(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} + +bool ZDCRawDataTask::decodeBunch(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} + +bool ZDCRawDataTask::decodeFireChannel(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} + +bool ZDCRawDataTask::decodeDataLoss(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} +bool ZDCRawDataTask::decodeOverBc(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} +bool ZDCRawDataTask::decodeTrasmittedChannel(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} + +bool ZDCRawDataTask::decodeTriggerBitChannel(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} + +bool ZDCRawDataTask::decodeTriggerBitHitChannel(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} + +bool ZDCRawDataTask::decodeSummary(std::vector TokenString, int LineNumber) +{ + if (TokenString.size() == 5) { + return addNewHisto(TokenString.at(0), TokenString.at(1), TokenString.at(2), TokenString.at(3), TokenString.at(4)); + } + ILOG(Error, Support) << TString::Format("ERROR Line number %d not correct number of paramiter", LineNumber) << ENDM; + if (checkCondition(TokenString.at(4)) == false) { + ILOG(Error, Support) << TString::Format("ERROR Line number %d the condition specified don't exist", LineNumber) << ENDM; + } + return false; +} + +bool ZDCRawDataTask::checkCondition(std::string cond) +{ + + if (cond == "A0") { + return true; // Alice Trigger 0 + } + if (cond == "T0") { + return true; // Auto Trigger 0 + } + if (cond == "A0eT0") { + return true; // Alice Trigger 0 AND Auto Trigger 0 + } + if (cond == "A0oT0") { + return true; // Alice Trigger 0 OR Auto Trigger 0 + } + if (cond == "AoT") { + return true; // Alice Trigger 0 OR Auto Trigger 0 + } + if (cond == "LBC") { + return true; // Last BC + } + if (cond == "ALL") { + return true; // no trigger + } + return false; +} + +void ZDCRawDataTask::resetAlign() +{ + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < NChPerModule; j++) { + fMatrixAlign[i][j].minSample.id_min_sample = -1; + fMatrixAlign[i][j].minSample.min_mean = 2048.0; + fMatrixAlign[i][j].minSample.num_entry = 0; + for (int k = 0; k < 12; k++) { + fMatrixAlign[i][j].minSample.vSamples[k].id_sample = k; + fMatrixAlign[i][j].minSample.vSamples[k].num_entry = 0; + fMatrixAlign[i][j].minSample.vSamples[k].sum = 0; + fMatrixAlign[i][j].minSample.vSamples[k].mean = 0; + } + } + } +} + +void ZDCRawDataTask::dumpHistoStructure() +{ + std::ofstream dumpFile; + dumpFile.open("dumpStructures.txt"); + dumpFile << "Matrix Name Channel \n"; + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + dumpFile << fNameChannel[i][j] << " \t"; + } + dumpFile << "\n"; + } + + dumpFile << "\nChannel Name Coordinate \n"; + for (auto it = fMapChNameModCh.cbegin(); it != fMapChNameModCh.cend(); ++it) { + dumpFile << it->first << "[" << it->second.at(0) << "][" << it->second.at(1) << "] " + << "\n"; + } + + dumpFile << "\n Summary Histo Channel Name Index Histogram \n"; + for (auto it = fMapBinNameIdSummaryHisto.cbegin(); it != fMapBinNameIdSummaryHisto.cend(); ++it) { + dumpFile << it->first << "[" << it->second << "] " + << "\n"; + } + dumpFile << "\nMatrix id Histo Baseline \n"; + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + for (int k = 0; k < (int)fMatrixHistoBaseline[i][j].size(); k++) { + dumpFile << "[" << i << "][" << j << "] " << fMatrixHistoBaseline[i][j].at(k).histo->GetName() << " \t"; + } + dumpFile << "\n"; + } + dumpFile << "\n"; + } + + dumpFile << "\nMatrix id Histo Counts \n"; + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + for (int k = 0; k < (int)fMatrixHistoCounts[i][j].size(); k++) { + dumpFile << "[" << i << "][" << j << "] " << fMatrixHistoCounts[i][j].at(k).histo->GetName() << " \t"; + } + dumpFile << "\n"; + } + dumpFile << "\n"; + } + + dumpFile << "\nMatrix id Histo Signal\n"; + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + for (int k = 0; k < (int)fMatrixHistoSignal[i][j].size(); k++) { + dumpFile << "[" << i << "][" << j << "] " << fMatrixHistoSignal[i][j].at(k).histo->GetName() << " Condition " << fMatrixHistoSignal[i][j].at(k).condHisto.at(0) << " \t"; + } + dumpFile << "\n"; + } + dumpFile << "\n"; + } + + dumpFile << "\nMatrix id Histo Bunch \n"; + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + for (int k = 0; k < (int)fMatrixHistoBunch[i][j].size(); k++) { + dumpFile << "[" << i << "][" << j << "] " << fMatrixHistoBunch[i][j].at(k).histo->GetName() << " \t"; + } + dumpFile << "\n"; + } + dumpFile << "\n"; + } + + dumpFile << "\nAlign Struct \n"; + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + dumpFile << "[" << i << "][" << j << "] " << fMatrixAlign[i][j].name_ch << " \t pos_histo " << fMatrixAlign[i][j].bin << " \t" << fMatrixAlign[i][j].minSample.id_min_sample << " \t"; + dumpFile << "\n"; + } + dumpFile << "\n"; + } + + dumpFile << "\nAlign Struct details Num Cycle: " << fNumCycle << "\n"; + for (int i = 0; i < o2::zdc::NModules; i++) { + for (int j = 0; j < o2::zdc::NChPerModule; j++) { + dumpFile << "[" << i << "][" << j << "] " << fMatrixAlign[i][j].name_ch << " \t pos_histo " << fMatrixAlign[i][j].bin << " \t id " << fMatrixAlign[i][j].minSample.id_min_sample << " \t mean: " << fMatrixAlign[i][j].minSample.min_mean << " \t entry: " << fMatrixAlign[i][j].minSample.num_entry << " \t"; + dumpFile << "\n"; + for (int k = 0; k < 12; k++) { + dumpFile << "\t id [" << k << "] sample " << fMatrixAlign[i][j].minSample.vSamples[k].id_sample << " \t mean: " << fMatrixAlign[i][j].minSample.vSamples[k].mean << " \t sum: " << fMatrixAlign[i][j].minSample.vSamples[k].sum << " \t entry: " << fMatrixAlign[i][j].minSample.vSamples[k].num_entry << " \t"; + dumpFile << "\n"; + } + dumpFile << "\n"; + } + dumpFile << "\n"; + dumpFile << "\nAlign Param Num Cycle: " << fAlignCycle << "\n"; + dumpFile << "\nAlign Param Num entries: " << fAlignNumEntries << "\n"; + dumpFile << "\nAlign Param Num Cycle Errors: " << fNumCycleErr << "\n"; + dumpFile << "\nAlign Param Num cycle errors reset: " << fErrorCycle << "\n"; + } + dumpFile.close(); +} + +} // namespace o2::quality_control_modules::zdc diff --git a/Modules/ZDC/src/ZDCRecBeautifyPlots.cxx b/Modules/ZDC/src/ZDCRecBeautifyPlots.cxx new file mode 100644 index 0000000000..3f9117515c --- /dev/null +++ b/Modules/ZDC/src/ZDCRecBeautifyPlots.cxx @@ -0,0 +1,92 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRecBeautifyPlots.cxx +/// \author Stefan Cristi Zugravel +/// + +#include "ZDC/ZDCRecBeautifyPlots.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::zdc +{ + +void ZDCRecBeautifyPlots::configure() +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + // This method is called whenever CustomParameters are set. + + // Example of retrieving a custom parameter + std::string parameter = mCustomParameters.atOrDefaultValue("myOwnKey1", "default"); +} + +Quality ZDCRecBeautifyPlots::check(std::map>* moMap) +{ + Quality result = Quality::Null; + return result; +} + +void ZDCRecBeautifyPlots::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "h_CENTR_ZNA" || mo->getName() == "h_CENTR_ZNC" || mo->getName() == "h_CENTR_ZNA_cut_ZEM" || mo->getName() == "h_CENTR_ZNC_cut_ZEM") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast '" << mo->getName() << "' to TH2*" << ENDM; + return; + } + auto* lineH = new TLine(0.5, 0, -0.5, 0); + auto* lineV = new TLine(0, 0.5, 0, -0.5); + auto* marker = new TMarker(h->GetMean(1), h->GetMean(2), 20); + lineH->SetLineColor(kBlack); + lineV->SetLineColor(kBlack); + lineH->SetLineWidth(2); + lineV->SetLineWidth(2); + marker->SetMarkerColor(2); + h->GetListOfFunctions()->Add(lineH); + h->GetListOfFunctions()->Add(lineV); + h->GetListOfFunctions()->Add(marker); + } +} + +void ZDCRecBeautifyPlots::reset() +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "ZDCRecBeautifyPlots::reset" << ENDM; + // please reset the state of the check here to allow for reuse between consecutive runs. +} + +void ZDCRecBeautifyPlots::startOfActivity(const Activity& activity) +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "ZDCRecBeautifyPlots::start : " << activity.mId << ENDM; +} + +void ZDCRecBeautifyPlots::endOfActivity(const Activity& activity) +{ + // THUS FUNCTION BODY IS AN EXAMPLE. PLEASE REMOVE EVERYTHING YOU DO NOT NEED. + ILOG(Debug, Devel) << "ZDCRecBeautifyPlots::end : " << activity.mId << ENDM; +} + +} // namespace o2::quality_control_modules::zdc diff --git a/Modules/ZDC/src/ZDCRecDataCheck.cxx b/Modules/ZDC/src/ZDCRecDataCheck.cxx new file mode 100644 index 0000000000..f83bebb9ea --- /dev/null +++ b/Modules/ZDC/src/ZDCRecDataCheck.cxx @@ -0,0 +1,638 @@ +// Copyright 2019-2022 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRecDataCheck.cxx +/// \author Carlo Puggioni +/// +/* + * The task checks the two histograms: h_summary_ADC and h_summary_TDC . + * - h_summary_ADC: Each bin contains the average value of an ADC channel. Checking this histogram verifies that all ADC channels have a value within a threshold defined in the json file. + * - h_summary_TDC: Each bin contains the average value of an TDC channel. Checking this histogram verifies that all TDC channels have a value within a threshold defined in the json file. + */ + +#include "ZDC/ZDCRecDataCheck.h" +#include "QualityControl/MonitorObject.h" +#include "QualityControl/Quality.h" +#include "QualityControl/QcInfoLogger.h" +// ROOT +#include +#include +#include +#include + +#include +#include + +#include // chrono::system_clock +#include // localtime +#include // stringstream +#include // put_time +#include +using namespace std; +using namespace o2::quality_control; + +namespace o2::quality_control_modules::zdc +{ + +void ZDCRecDataCheck::configure() {} + +void ZDCRecDataCheck::startOfActivity(const Activity& activity) +{ + // ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + init(activity); +} + +Quality ZDCRecDataCheck::check(std::map>* moMap) +{ + Quality result = Quality::Null; + // ADC + mNumEADC = 0; + mNumWADC = 0; + mStringWADC = ""; + mStringEADC = ""; + int ib = 0; + for (auto& [moName, mo] : *moMap) { + (void)moName; + if (mo->getName() == "h_summary_ADC") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast '" << mo->getName() << "' to TH1*" << ENDM; + return Quality::Null; + } + // dumpVecParam((int)h->GetNbinsX(),(int)mVectParamADC.size()); + if ((int)h->GetNbinsX() != (int)mVectParamADC.size()) { + return Quality::Null; + } + for (int i = 0; i < h->GetNbinsX(); i++) { + ib = i + 1; + if ((((float)h->GetBinContent(ib) < (float)mVectParamADC.at(i).minW && (float)h->GetBinContent(ib) >= (float)mVectParamADC.at(i).minE)) || ((float)h->GetBinContent(ib) > (float)mVectParamADC.at(i).maxW && (float)h->GetBinContent(ib) < (float)mVectParamADC.at(i).maxE)) { + mNumWADC += 1; + mStringWADC = mStringWADC + mVectParamADC.at(i).ch + " "; + // ILOG(Warning, Support) << "Rec Warning in " << mVectParamADC.at(i).ch << " intervall: " << mVectParamADC.at(i).minW << " - " << mVectParamADC.at(i).maxW << " Value: " << h->GetBinContent(ib) << ENDM; + } + if (((float)h->GetBinContent(ib) < (float)mVectParamADC.at(i).minE) || ((float)h->GetBinContent(ib) > (float)mVectParamADC.at(i).maxE)) { + mNumEADC += 1; + mStringEADC = mStringEADC + mVectParamADC.at(i).ch + " "; + // ILOG(Error, Support) << "Rec Error in " << mVectParamADC.at(i).ch << " intervall: " << mVectParamADC.at(i).minE << " - " << mVectParamADC.at(i).maxE << " Value: " << h->GetBinContent(ib) << ENDM; + } + } + if (mNumWADC == 0 && mNumEADC == 0) { + mQADC = 1; + } + if (mNumWADC > 0) { + mQADC = 2; + } + if (mNumEADC > 0) { + mQADC = 3; + } + } + + // TDC TIME + mNumETDC = 0; + mNumWTDC = 0; + mStringWTDC = ""; + mStringETDC = ""; + if (mo->getName() == "h_summary_TDC") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast '" << mo->getName() << "' to TH1*" << ENDM; + return Quality::Null; + } + // dumpVecParam((int)h->GetNbinsX(),(int)mVectParamTDC.size()); + if ((int)h->GetNbinsX() != (int)mVectParamTDC.size()) { + return Quality::Null; + } + for (int i = 0; i < h->GetNbinsX(); i++) { + ib = i + 1; + if ((((float)h->GetBinContent(ib) < (float)mVectParamTDC.at(i).minW && (float)h->GetBinContent(ib) >= (float)mVectParamTDC.at(i).minE)) || ((float)h->GetBinContent(ib) > (float)mVectParamTDC.at(i).maxW && (float)h->GetBinContent(ib) < (float)mVectParamTDC.at(i).maxE)) { + mNumWTDC += 1; + mStringWTDC = mStringWTDC + mVectParamTDC.at(i).ch + " "; + // ILOG(Warning, Support) << "Rec Warning in " << mVectParamTDC.at(i).ch << " intervall: " << mVectParamTDC.at(i).minW << " - " << mVectParamTDC.at(i).maxW << " Value: " << h->GetBinContent(ib) << ENDM; + } + if (((float)h->GetBinContent(ib) < (float)mVectParamTDC.at(i).minE) || ((float)h->GetBinContent(ib) > (float)mVectParamTDC.at(i).maxE)) { + mNumETDC += 1; + mStringETDC = mStringETDC + mVectParamTDC.at(i).ch + " "; + // ILOG(Error, Support) << "Rec Error in " << mVectParamTDC.at(i).ch << " intervall: " << mVectParamTDC.at(i).minE << " - " << mVectParamTDC.at(i).maxE << " Value: " << h->GetBinContent(ib) << ENDM; + } + } + if (mNumWTDC == 0 && mNumETDC == 0) { + mQTDC = 1; + } + if (mNumWTDC > 0) { + mQTDC = 2; + } + if (mNumETDC > 0) { + mQTDC = 3; + } + } + + // summary TDC Amplitude + mNumETDCA = 0; + mNumWTDCA = 0; + mStringWTDCA = ""; + mStringETDCA = ""; + if (mo->getName() == "h_summary_TDCA") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast '" << mo->getName() << "' to TH1*" << ENDM; + return Quality::Null; + } + // dumpVecParam((int)h->GetNbinsX(),(int)mVectParamTDC.size()); + if ((int)h->GetNbinsX() != (int)mVectParamTDCA.size()) { + return Quality::Null; + } + for (int i = 0; i < h->GetNbinsX(); i++) { + ib = i + 1; + if ((((float)h->GetBinContent(ib) < (float)mVectParamTDCA.at(i).minW && (float)h->GetBinContent(ib) >= (float)mVectParamTDCA.at(i).minE)) || ((float)h->GetBinContent(ib) > (float)mVectParamTDCA.at(i).maxW && (float)h->GetBinContent(ib) < (float)mVectParamTDCA.at(i).maxE)) { + mNumWTDCA += 1; + mStringWTDCA = mStringWTDCA + mVectParamTDCA.at(i).ch + " "; + // ILOG(Warning, Support) << "Rec Warning in " << mVectParamTDCA.at(i).ch << " intervall: " << mVectParamTDCA.at(i).minW << " - " << mVectParamTDCA.at(i).maxW << " Value: " << h->GetBinContent(ib) << ENDM; + } + if (((float)h->GetBinContent(ib) < (float)mVectParamTDCA.at(i).minE) || ((float)h->GetBinContent(ib) > (float)mVectParamTDCA.at(i).maxE)) { + mNumETDCA += 1; + mStringETDCA = mStringETDCA + mVectParamTDCA.at(i).ch + " "; + // ILOG(Error, Support) << "Rec Error in " << mVectParamTDCA.at(i).ch << " intervall: " << mVectParamTDCA.at(i).minE << " - " << mVectParamTDCA.at(i).maxE << " Value: " << h->GetBinContent(ib) << ENDM; + } + } + if (mNumWTDCA == 0 && mNumETDCA == 0) { + mQTDCA = 1; + } + if (mNumWTDCA > 0) { + mQTDCA = 2; + } + if (mNumETDCA > 0) { + mQTDCA = 3; + } + } + + // summary TDC Amplitude Peak 1n + mNumEPeak1n = 0; + mNumWPeak1n = 0; + mStringWPeak1n = ""; + mStringEPeak1n = ""; + if (mo->getName() == "h_summary_Peak1n") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast '" << mo->getName() << "' to TH1*" << ENDM; + return Quality::Null; + } + // dumpVecParam((int)h->GetNbinsX(),(int)mVectParamTDC.size()); + if ((int)h->GetNbinsX() != (int)mVectParamPeak1n.size()) { + return Quality::Null; + } + for (int i = 0; i < h->GetNbinsX(); i++) { + ib = i + 1; + if ((((float)h->GetBinContent(ib) < (float)mVectParamPeak1n.at(i).minW && (float)h->GetBinContent(ib) >= (float)mVectParamPeak1n.at(i).minE)) || ((float)h->GetBinContent(ib) > (float)mVectParamPeak1n.at(i).maxW && (float)h->GetBinContent(ib) < (float)mVectParamPeak1n.at(i).maxE)) { + mNumWPeak1n += 1; + mStringWPeak1n = mStringWPeak1n + mVectParamPeak1n.at(i).ch + " "; + // ILOG(Warning, Support) << "Rec Warning in " << mVectParamPeak1n.at(i).ch << " intervall: " << mVectParamPeak1n.at(i).minW << " - " << mVectParamPeak1n.at(i).maxW << " Value: " << h->GetBinContent(ib) << ENDM; + } + if (((float)h->GetBinContent(ib) < (float)mVectParamPeak1n.at(i).minE) || ((float)h->GetBinContent(ib) > (float)mVectParamPeak1n.at(i).maxE)) { + mNumEPeak1n += 1; + mStringEPeak1n = mStringEPeak1n + mVectParamPeak1n.at(i).ch + " "; + // ILOG(Error, Support) << "Rec Error in " << mVectParamPeak1n.at(i).ch << " intervall: " << mVectParamPeak1n.at(i).minE << " - " << mVectParamPeak1n.at(i).maxE << " Value: " << h->GetBinContent(ib) << ENDM; + } + } + if (mNumWPeak1n == 0 && mNumEPeak1n == 0) { + mQPeak1n = 1; + } + if (mNumWPeak1n > 0) { + mQPeak1n = 2; + } + if (mNumEPeak1n > 0) { + mQPeak1n = 3; + } + } + + // summary TDC Amplitude peak 1p + mNumEPeak1p = 0; + mNumWPeak1p = 0; + mStringWPeak1p = ""; + mStringEPeak1p = ""; + if (mo->getName() == "h_summary_Peak1p") { + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast '" << mo->getName() << "' to TH1*" << ENDM; + return Quality::Null; + } + // dumpVecParam((int)h->GetNbinsX(),(int)mVectParamTDC.size()); + if ((int)h->GetNbinsX() != (int)mVectParamPeak1p.size()) { + return Quality::Null; + } + for (int i = 0; i < h->GetNbinsX(); i++) { + ib = i + 1; + if ((((float)h->GetBinContent(ib) < (float)mVectParamPeak1p.at(i).minW && (float)h->GetBinContent(ib) >= (float)mVectParamPeak1p.at(i).minE)) || ((float)h->GetBinContent(ib) > (float)mVectParamPeak1p.at(i).maxW && (float)h->GetBinContent(ib) < (float)mVectParamPeak1p.at(i).maxE)) { + mNumWPeak1p += 1; + mStringWPeak1p = mStringWPeak1p + mVectParamPeak1p.at(i).ch + " "; + // ILOG(Warning, Support) << "Rec Warning in " << mVectParamPeak1p.at(i).ch << " intervall: " << mVectParamPeak1p.at(i).minW << " - " << mVectParamPeak1p.at(i).maxW << " Value: " << h->GetBinContent(ib) << ENDM; + } + if (((float)h->GetBinContent(ib) < (float)mVectParamPeak1p.at(i).minE) || ((float)h->GetBinContent(ib) > (float)mVectParamPeak1p.at(i).maxE)) { + mNumEPeak1p += 1; + mStringEPeak1p = mStringEPeak1p + mVectParamPeak1p.at(i).ch + " "; + // ILOG(Error, Support) << "Rec Error in " << mVectParamPeak1p.at(i).ch << " intervall: " << mVectParamPeak1p.at(i).minE << " - " << mVectParamPeak1p.at(i).maxE << " Value: " << h->GetBinContent(ib) << ENDM; + } + } + if (mNumWPeak1p == 0 && mNumEPeak1p == 0) { + mQPeak1p = 1; + } + if (mNumWPeak1p > 0) { + mQPeak1p = 2; + } + if (mNumEPeak1p > 0) { + mQPeak1p = 3; + } + } + + // Global Check + if (mQADC == 1 && mQTDC == 1 && mQTDCA == 1 && mQPeak1n == 1 && mQPeak1p == 1) { + result = Quality::Good; + } else if (mQADC == 3 || mQTDC == 3 || mQTDCA == 3 && mQPeak1n == 3 && mQPeak1p == 3) { + result = Quality::Bad; + if (mQADC == 3) { + result.addFlag(FlagTypeFactory::Unknown(), + "Task quality is bad because in ADC Summary " + std::to_string(mNumWADC) + " channels:" + mStringEADC + "have a value in the bad range"); + } + + if (mQTDC == 3) { + result.addFlag(FlagTypeFactory::Unknown(), + "It is bad because in TDC Summary" + std::to_string(mNumWTDC) + " channels:" + mStringETDC + "have a value in the bad range"); + } + if (mQTDCA == 3) { + result.addFlag(FlagTypeFactory::Unknown(), + "It is bad because in TDC Amplitude Summary" + std::to_string(mNumWTDCA) + " channels:" + mStringETDCA + "have a value in the bad range"); + } + if (mQPeak1n == 3) { + result.addFlag(FlagTypeFactory::Unknown(), + "It is bad because in TDC Amplitude Peak1n Summary" + std::to_string(mNumWPeak1n) + " channels:" + mStringEPeak1n + "have a value in the bad range"); + } + if (mQPeak1p == 3) { + result.addFlag(FlagTypeFactory::Unknown(), + "It is bad because in TDC Amplitude Peak1p Summary" + std::to_string(mNumWPeak1p) + " channels:" + mStringEPeak1p + "have a value in the bad range"); + } + } else { + result = Quality::Medium; + if (mQADC == 2) { + result.addFlag(FlagTypeFactory::Unknown(), + "It is medium because in ADC Summary " + std::to_string(mNumWADC) + " channels:" + mStringWADC + "have a value in the medium range"); + } + if (mQTDC == 2) { + result.addFlag(FlagTypeFactory::Unknown(), + "It is medium because in TDC Summary " + std::to_string(mNumWTDC) + " channels:" + mStringWTDC + "have a value in the medium range"); + } + if (mQTDCA == 2) { + result.addFlag(FlagTypeFactory::Unknown(), + "It is medium because in TDC Amplitude Summary " + std::to_string(mNumWTDCA) + " channels:" + mStringWTDCA + "have a value in the medium range"); + } + if (mQPeak1n == 2) { + result.addFlag(FlagTypeFactory::Unknown(), + "It is medium because in TDC Amplitude Peak1n Summary " + std::to_string(mNumWPeak1n) + " channels:" + mStringWPeak1n + "have a value in the medium range"); + } + if (mQPeak1p == 2) { + result.addFlag(FlagTypeFactory::Unknown(), + "It is medium because in TDC Amplitude Peak1p Summary " + std::to_string(mNumWPeak1p) + " channels:" + mStringWPeak1p + "have a value in the medium range"); + } + } + } + return result; +} + +void ZDCRecDataCheck::beautify(std::shared_ptr mo, Quality checkResult) +{ + if (mo->getName() == "h_summary_ADC") { + if (mQADC == 1) { + setQualityInfo(mo, kGreen, getCurrentDataTime() + "ADC OK"); + } else if (mQADC == 3) { + std::string errorSt = getCurrentDataTime() + "Error ADC value in the channels: " + mStringEADC + "is not correct. Call the expert"; + setQualityInfo(mo, kRed, errorSt); + } else if (mQADC == 2) { + std::string errorSt = getCurrentDataTime() + "Warning ADC value in the channels: " + mStringWADC + "is not correct. Send mail to the expert"; + setQualityInfo(mo, kOrange, errorSt); + } + } + + if (mo->getName() == "h_summary_TDC") { + if (mQTDC == 1) { + setQualityInfo(mo, kGreen, getCurrentDataTime() + "TDC OK"); + } else if (mQTDC == 3) { + std::string errorSt = getCurrentDataTime() + "Error TDC value in the channels: " + mStringETDC + "is not correct. Call the expert"; + setQualityInfo(mo, kRed, errorSt); + } else if (mQTDC == 2) { + std::string errorSt = getCurrentDataTime() + "Warning TDC value in the channels: " + mStringWTDC + "is not correct. Send mail to the expert"; + setQualityInfo(mo, kOrange, errorSt); + } + } + + if (mo->getName() == "h_summary_TDCA") { + if (mQTDCA == 1) { + setQualityInfo(mo, kGreen, getCurrentDataTime() + "TDC Amplitude OK"); + } else if (mQTDCA == 3) { + std::string errorSt = getCurrentDataTime() + "Error TDC Amplitude value in the channels: " + mStringETDCA + "is not correct. Call the expert"; + setQualityInfo(mo, kRed, errorSt); + } else if (mQTDCA == 2) { + std::string errorSt = getCurrentDataTime() + "Warning TDC Amplitude value in the channels: " + mStringWTDCA + "is not correct. Send mail to the expert"; + setQualityInfo(mo, kOrange, errorSt); + } + } + + if (mo->getName() == "h_summary_Peak1n") { + if (mQPeak1n == 1) { + setQualityInfo(mo, kGreen, getCurrentDataTime() + "TDC Amplitude Peak1n OK"); + } else if (mQPeak1n == 3) { + std::string errorSt = getCurrentDataTime() + "Error TDC Amplitude Peak1n value in the channels: " + mStringEPeak1n + "is not correct. Call the expert"; + setQualityInfo(mo, kRed, errorSt); + } else if (mQPeak1n == 2) { + std::string errorSt = getCurrentDataTime() + "Warning TDC Amplitude Peak1n value in the channels: " + mStringWPeak1n + "is not correct. Send mail to the expert"; + setQualityInfo(mo, kOrange, errorSt); + } + } + + if (mo->getName() == "h_summary_Peak1p") { + if (mQPeak1p == 1) { + setQualityInfo(mo, kGreen, getCurrentDataTime() + "TDC Amplitude OK"); + } else if (mQPeak1p == 3) { + std::string errorSt = getCurrentDataTime() + "Error TDC Amplitude Peak1p value in the channels: " + mStringEPeak1p + "is not correct. Call the expert"; + setQualityInfo(mo, kRed, errorSt); + } else if (mQPeak1p == 2) { + std::string errorSt = getCurrentDataTime() + "Warning TDC Amplitude Peak1p value in the channels: " + mStringWPeak1p + "is not correct. Send mail to the expert"; + setQualityInfo(mo, kOrange, errorSt); + } + } +} +void ZDCRecDataCheck::setQualityInfo(std::shared_ptr mo, int color, std::string text) +{ + auto* h = dynamic_cast(mo->getObject()); + if (h == nullptr) { + ILOG(Error, Support) << "could not cast '" << mo->getName() << "' to TH1*" << ENDM; + return; + } + TLatex* msg = new TLatex(mPosMsgADCX, mPosMsgADCY, text.c_str()); + msg->SetNDC(); + msg->SetTextSize(16); + msg->SetTextFont(43); + h->GetListOfFunctions()->Add(msg); + h->SetFillColor(color); + msg->SetTextColor(color); + msg->Draw(); + h->SetLineColor(kBlack); +} + +void ZDCRecDataCheck::init(const Activity& activity) +{ + mVectParamADC.clear(); + mVectParamTDC.clear(); + mVectParamTDCA.clear(); + mVectParamPeak1n.clear(); + mVectParamPeak1p.clear(); + + setChName("ADC_ZNAC", "ADC"); + setChName("ADC_ZNA1", "ADC"); + setChName("ADC_ZNA2", "ADC"); + setChName("ADC_ZNA3", "ADC"); + setChName("ADC_ZNA4", "ADC"); + setChName("ADC_ZNAS", "ADC"); + + setChName("ADC_ZPAC", "ADC"); + setChName("ADC_ZPA1", "ADC"); + setChName("ADC_ZPA2", "ADC"); + setChName("ADC_ZPA3", "ADC"); + setChName("ADC_ZPA4", "ADC"); + setChName("ADC_ZPAS", "ADC"); + + setChName("ADC_ZEM1", "ADC"); + setChName("ADC_ZEM2", "ADC"); + + setChName("ADC_ZNCC", "ADC"); + setChName("ADC_ZNC1", "ADC"); + setChName("ADC_ZNC2", "ADC"); + setChName("ADC_ZNC3", "ADC"); + setChName("ADC_ZNC4", "ADC"); + setChName("ADC_ZNCS", "ADC"); + + setChName("ADC_ZPCC", "ADC"); + setChName("ADC_ZPC1", "ADC"); + setChName("ADC_ZPC2", "ADC"); + setChName("ADC_ZPC3", "ADC"); + setChName("ADC_ZPC4", "ADC"); + setChName("ADC_ZPCS", "ADC"); + + setChName("TDC_ZNAC", "TDC"); + setChName("TDC_ZNAS", "TDC"); + setChName("TDC_ZPAC", "TDC"); + setChName("TDC_ZPAS", "TDC"); + setChName("TDC_ZEM1", "TDC"); + setChName("TDC_ZEM2", "TDC"); + setChName("TDC_ZNCC", "TDC"); + setChName("TDC_ZNCS", "TDC"); + setChName("TDC_ZPCC", "TDC"); + setChName("TDC_ZPCS", "TDC"); + + setChName("TDCA_ZNAC", "TDCA"); + setChName("TDCA_ZNAS", "TDCA"); + setChName("TDCA_ZPAC", "TDCA"); + setChName("TDCA_ZPAS", "TDCA"); + setChName("TDCA_ZEM1", "TDCA"); + setChName("TDCA_ZEM2", "TDCA"); + setChName("TDCA_ZNCC", "TDCA"); + setChName("TDCA_ZNCS", "TDCA"); + setChName("TDCA_ZPCC", "TDCA"); + setChName("TDCA_ZPCS", "TDCA"); + + setChName("PEAK1N_ZNAC", "PEAK1N"); + setChName("PEAK1N_ZNAS", "PEAK1N"); + setChName("PEAK1N_ZNCC", "PEAK1N"); + setChName("PEAK1N_ZNCS", "PEAK1N"); + + setChName("PEAK1P_ZPAC", "PEAK1P"); + setChName("PEAK1P_ZPAS", "PEAK1P"); + setChName("PEAK1P_ZPCC", "PEAK1P"); + setChName("PEAK1P_ZPCS", "PEAK1P"); + + for (int i = 0; i < (int)mVectParamADC.size(); i++) { + setChCheck(i, "ADC", activity); + } + for (int i = 0; i < (int)mVectParamTDC.size(); i++) { + setChCheck(i, "TDC", activity); + } + for (int i = 0; i < (int)mVectParamTDCA.size(); i++) { + setChCheck(i, "TDCA", activity); + } + for (int i = 0; i < (int)mVectParamPeak1n.size(); i++) { + setChCheck(i, "PEAK1N", activity); + } + for (int i = 0; i < (int)mVectParamPeak1p.size(); i++) { + setChCheck(i, "PEAK1P", activity); + } + if (auto param = mCustomParameters.atOptional("ADC_POS_MSG_X", activity)) { + mPosMsgADCX = atof(param.value().c_str()); + } + if (auto param = mCustomParameters.atOptional("ADC_POS_MSG_Y", activity)) { + mPosMsgADCY = atof(param.value().c_str()); + } + if (auto param = mCustomParameters.atOptional("TDC_POS_MSG_X", activity)) { + mPosMsgTDCX = atof(param.value().c_str()); + } + if (auto param = mCustomParameters.atOptional("TDC_POS_MSG_Y", activity)) { + mPosMsgTDCY = atof(param.value().c_str()); + } + if (auto param = mCustomParameters.atOptional("TDCA_POS_MSG_X", activity)) { + mPosMsgTDCAX = atof(param.value().c_str()); + } + if (auto param = mCustomParameters.atOptional("TDCA_POS_MSG_Y", activity)) { + mPosMsgTDCAY = atof(param.value().c_str()); + } + if (auto param = mCustomParameters.atOptional("PEAK1N_POS_MSG_X", activity)) { + mPosMsgPeak1nX = atof(param.value().c_str()); + } + if (auto param = mCustomParameters.atOptional("PEAK1N_POS_MSG_Y", activity)) { + mPosMsgPeak1nY = atof(param.value().c_str()); + } + if (auto param = mCustomParameters.atOptional("PEAK1P_POS_MSG_X", activity)) { + mPosMsgPeak1pX = atof(param.value().c_str()); + } + if (auto param = mCustomParameters.atOptional("PEAK1P_POS_MSG_Y", activity)) { + mPosMsgPeak1pY = atof(param.value().c_str()); + } +} + +void ZDCRecDataCheck::setChName(std::string channel, std::string type) +{ + sCheck chCheck; + chCheck.ch = channel; + if (type == "ADC") { + mVectParamADC.push_back(chCheck); + } + if (type == "TDC") { + mVectParamTDC.push_back(chCheck); + } + if (type == "TDCA") { + mVectParamTDCA.push_back(chCheck); + } + if (type == "PEAK1N") { + mVectParamPeak1n.push_back(chCheck); + } + if (type == "PEAK1P") { + mVectParamPeak1p.push_back(chCheck); + } +} + +void ZDCRecDataCheck::setChCheck(int index, std::string type, const Activity& activity) +{ + std::vector tokenString; + if (type == "ADC" && index < (int)mVectParamADC.size()) { + if (auto param = mCustomParameters.atOptional(mVectParamADC.at(index).ch, activity)) { + tokenString = tokenLine(param.value(), ";"); + + mVectParamADC.at(index).minW = atof(tokenString.at(0).c_str()) - atof(tokenString.at(1).c_str()); + mVectParamADC.at(index).maxW = atof(tokenString.at(0).c_str()) + atof(tokenString.at(1).c_str()); + mVectParamADC.at(index).minE = atof(tokenString.at(0).c_str()) - atof(tokenString.at(2).c_str()); + mVectParamADC.at(index).maxE = atof(tokenString.at(0).c_str()) + atof(tokenString.at(2).c_str()); + } + } + if (type == "TDC" && index < (int)mVectParamTDC.size()) { + if (auto param = mCustomParameters.atOptional(mVectParamTDC.at(index).ch, activity)) { + tokenString = tokenLine(param.value(), ";"); + + mVectParamTDC.at(index).minW = atof(tokenString.at(0).c_str()) - atof(tokenString.at(1).c_str()); + mVectParamTDC.at(index).maxW = atof(tokenString.at(0).c_str()) + atof(tokenString.at(1).c_str()); + mVectParamTDC.at(index).minE = atof(tokenString.at(0).c_str()) - atof(tokenString.at(2).c_str()); + mVectParamTDC.at(index).maxE = atof(tokenString.at(0).c_str()) + atof(tokenString.at(2).c_str()); + } + } + if (type == "TDCA" && index < (int)mVectParamTDCA.size()) { + if (auto param = mCustomParameters.atOptional(mVectParamTDCA.at(index).ch, activity)) { + tokenString = tokenLine(param.value(), ";"); + + mVectParamTDCA.at(index).minW = atof(tokenString.at(0).c_str()) - atof(tokenString.at(1).c_str()); + mVectParamTDCA.at(index).maxW = atof(tokenString.at(0).c_str()) + atof(tokenString.at(1).c_str()); + mVectParamTDCA.at(index).minE = atof(tokenString.at(0).c_str()) - atof(tokenString.at(2).c_str()); + mVectParamTDCA.at(index).maxE = atof(tokenString.at(0).c_str()) + atof(tokenString.at(2).c_str()); + } + } + if (type == "PEAK1N" && index < (int)mVectParamPeak1n.size()) { + if (auto param = mCustomParameters.atOptional(mVectParamPeak1n.at(index).ch, activity)) { + tokenString = tokenLine(param.value(), ";"); + + mVectParamPeak1n.at(index).minW = atof(tokenString.at(0).c_str()) - atof(tokenString.at(1).c_str()); + mVectParamPeak1n.at(index).maxW = atof(tokenString.at(0).c_str()) + atof(tokenString.at(1).c_str()); + mVectParamPeak1n.at(index).minE = atof(tokenString.at(0).c_str()) - atof(tokenString.at(2).c_str()); + mVectParamPeak1n.at(index).maxE = atof(tokenString.at(0).c_str()) + atof(tokenString.at(2).c_str()); + } + } + if (type == "PEAK1P" && index < (int)mVectParamPeak1p.size()) { + if (auto param = mCustomParameters.atOptional(mVectParamPeak1p.at(index).ch, activity)) { + tokenString = tokenLine(param.value(), ";"); + + mVectParamPeak1p.at(index).minW = atof(tokenString.at(0).c_str()) - atof(tokenString.at(1).c_str()); + mVectParamPeak1p.at(index).maxW = atof(tokenString.at(0).c_str()) + atof(tokenString.at(1).c_str()); + mVectParamPeak1p.at(index).minE = atof(tokenString.at(0).c_str()) - atof(tokenString.at(2).c_str()); + mVectParamPeak1p.at(index).maxE = atof(tokenString.at(0).c_str()) + atof(tokenString.at(2).c_str()); + } + } +} +std::vector ZDCRecDataCheck::tokenLine(std::string Line, std::string Delimiter) +{ + std::string token; + size_t pos = 0; + int i = 0; + std::vector stringToken; + while ((pos = Line.find(Delimiter)) != std::string::npos) { + token = Line.substr(i, pos); + stringToken.push_back(token); + Line.erase(0, pos + Delimiter.length()); + } + stringToken.push_back(Line); + return stringToken; +} + +std::string ZDCRecDataCheck::getCurrentDataTime() +{ + auto now = std::chrono::system_clock::now(); + auto in_time_t = std::chrono::system_clock::to_time_t(now); + + std::stringstream ss; + ss << std::put_time(std::localtime(&in_time_t), "%d-%m-%Y %X"); + return ss.str(); +} + +void ZDCRecDataCheck::dumpVecParam(int numBinHisto, int num_ch) +{ + std::ofstream dumpFile; + dumpFile.open("dumpStructuresRecCheck.txt"); + if (dumpFile.good()) { + dumpFile << "\n Vector Param ADC \n"; + for (int i = 0; i < (int)mVectParamADC.size(); i++) { + dumpFile << mVectParamADC.at(i).ch << " \t" << std::to_string(mVectParamADC.at(i).minW) << " \t" << std::to_string(mVectParamADC.at(i).maxW) << " \t" << std::to_string(mVectParamADC.at(i).minE) << " \t" << std::to_string(mVectParamADC.at(i).maxE) << " \n"; + } + dumpFile << "\n Vector Param TDC \n"; + for (int i = 0; i < (int)mVectParamTDC.size(); i++) { + dumpFile << mVectParamTDC.at(i).ch << " \t" << std::to_string(mVectParamTDC.at(i).minW) << " \t" << std::to_string(mVectParamTDC.at(i).maxW) << " \t" << std::to_string(mVectParamTDC.at(i).minE) << " \t" << std::to_string(mVectParamTDC.at(i).maxE) << " \n"; + } + dumpFile << "\n Vector Param TDCA \n"; + for (int i = 0; i < (int)mVectParamTDCA.size(); i++) { + dumpFile << mVectParamTDCA.at(i).ch << " \t" << std::to_string(mVectParamTDCA.at(i).minW) << " \t" << std::to_string(mVectParamTDCA.at(i).maxW) << " \t" << std::to_string(mVectParamTDCA.at(i).minE) << " \t" << std::to_string(mVectParamTDCA.at(i).maxE) << " \n"; + } + dumpFile << "\n Vector Param PEAK1N \n"; + for (int i = 0; i < (int)mVectParamPeak1n.size(); i++) { + dumpFile << mVectParamPeak1n.at(i).ch << " \t" << std::to_string(mVectParamPeak1n.at(i).minW) << " \t" << std::to_string(mVectParamPeak1n.at(i).maxW) << " \t" << std::to_string(mVectParamPeak1n.at(i).minE) << " \t" << std::to_string(mVectParamPeak1n.at(i).maxE) << " \n"; + } + + dumpFile << "\n Vector Param PEAK1P \n"; + for (int i = 0; i < (int)mVectParamPeak1n.size(); i++) { + dumpFile << mVectParamPeak1p.at(i).ch << " \t" << std::to_string(mVectParamPeak1p.at(i).minW) << " \t" << std::to_string(mVectParamPeak1p.at(i).maxW) << " \t" << std::to_string(mVectParamPeak1p.at(i).minE) << " \t" << std::to_string(mVectParamPeak1p.at(i).maxE) << " \n"; + } + + dumpFile << "Num Bin Histo " << std::to_string(numBinHisto) << " \n"; + dumpFile << "Num ch " << std::to_string(num_ch) << " \n"; + dumpFile.close(); + } +} +} // namespace o2::quality_control_modules::zdc diff --git a/Modules/ZDC/src/ZDCRecDataPostProcessing.cxx b/Modules/ZDC/src/ZDCRecDataPostProcessing.cxx new file mode 100644 index 0000000000..d39d1dc755 --- /dev/null +++ b/Modules/ZDC/src/ZDCRecDataPostProcessing.cxx @@ -0,0 +1,381 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRecDataPostProcessing.cxx +/// \author Andrea Ferrero andrea.ferrero@cern.ch +/// \brief Post-processing of the ZDC ADC and TDC (Time and amplitudes) plots +/// \since 30/08/2023 +/// + +#include "ZDC/ZDCRecDataPostProcessing.h" +#include "ZDC/PostProcessingConfigZDC.h" +#include "QualityControl/DatabaseInterface.h" +#include "QualityControl/ObjectMetadataKeys.h" +#include "QualityControl/QcInfoLogger.h" + +using namespace o2::quality_control_modules::zdc; + +//_________________________________________________________________________________________ + +static void splitDataSourceName(std::string s, std::string& histName, std::string& moName) +{ + std::string delimiter = ":"; + size_t pos = s.find(delimiter); + if (pos != std::string::npos) { + histName = s.substr(0, pos); + s.erase(0, pos + delimiter.length()); + } + + moName = s; +} + +//_________________________________________________________________________________________ + +static long getMoTimeStamp(std::shared_ptr mo) +{ + long timeStamp{ 0 }; + + auto iter = mo->getMetadataMap().find(repository::metadata_keys::created); + if (iter != mo->getMetadataMap().end()) { + timeStamp = std::stol(iter->second); + } + + return timeStamp; +} + +//_________________________________________________________________________________________ + +static bool checkMoTimeStamp(std::shared_ptr mo, uint64_t& timeStampOld) +{ + auto timeStamp = getMoTimeStamp(mo); + bool result = timeStamp != timeStampOld; + timeStampOld = timeStamp; + return result; +} + +//_________________________________________________________________________________________ + +template +T* getPlotFromMO(std::shared_ptr mo) +{ + // Get ROOT object + TObject* obj = mo ? mo->getObject() : nullptr; + if (!obj) { + return nullptr; + } + + // Get histogram object + T* h = dynamic_cast(obj); + return h; +} + +//_________________________________________________________________________________________ + +MOHelper::MOHelper() +{ + setStartIme(); +} + +//_________________________________________________________________________________________ + +MOHelper::MOHelper(std::string p, std::string n) : mPath(p), mName(n) +{ + setStartIme(); +} + +//_________________________________________________________________________________________ + +void MOHelper::setStartIme() +{ + mTimeStart = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +//_________________________________________________________________________________________ + +bool MOHelper::update(repository::DatabaseInterface* qcdb, long timeStamp, const core::Activity& activity) +{ + mObject = qcdb->retrieveMO(mPath, mName, timeStamp, activity); + if (!mObject) { + return false; + } + + if (timeStamp > mTimeStart) { + // we are requesting a new object, so check that the retrieved one + // was created after the start of the processing + if (getMoTimeStamp(mObject) < mTimeStart) { + return false; + } + } + + if (!checkMoTimeStamp(mObject, mTimeStamp)) { + return false; + } + + return true; +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::configure(const boost::property_tree::ptree& config) +{ + PostProcessingConfigZDC zdcConfig(getID(), config); + + // ADC summary plot + for (auto source : zdcConfig.dataSourcesADC) { + std::string histName, moName; + splitDataSourceName(source.name, histName, moName); + if (moName.empty()) { + continue; + } + + mBinLabelsADC.emplace_back(histName); + mMOsADC.emplace(mBinLabelsADC.size(), MOHelper(source.path, moName)); + } + + // TDC Time summary plot + for (auto source : zdcConfig.dataSourcesTDC) { + std::string histName, moName; + splitDataSourceName(source.name, histName, moName); + if (moName.empty()) { + continue; + } + + mBinLabelsTDC.emplace_back(histName); + mMOsTDC.emplace(mBinLabelsTDC.size(), MOHelper(source.path, moName)); + } + + // TDC Amplitude summary plot + for (auto source : zdcConfig.dataSourcesTDCA) { + std::string histName, moName; + splitDataSourceName(source.name, histName, moName); + if (moName.empty()) { + continue; + } + + mBinLabelsTDCA.emplace_back(histName); + mMOsTDCA.emplace(mBinLabelsTDCA.size(), MOHelper(source.path, moName)); + } + + // TDC Peak 1n summary plot + for (auto source : zdcConfig.dataSourcesPeak1n) { + std::string histName, moName; + splitDataSourceName(source.name, histName, moName); + if (moName.empty()) { + continue; + } + + mBinLabelsPeak1n.emplace_back(histName); + mMOsPeak1n.emplace(mBinLabelsPeak1n.size(), MOHelper(source.path, moName)); + } + + // TDC Peak 1p summary plot + for (auto source : zdcConfig.dataSourcesPeak1p) { + std::string histName, moName; + splitDataSourceName(source.name, histName, moName); + if (moName.empty()) { + continue; + } + + mBinLabelsPeak1p.emplace_back(histName); + mMOsPeak1p.emplace(mBinLabelsPeak1p.size(), MOHelper(source.path, moName)); + } +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::createSummaryADCHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + mSummaryADCHisto.reset(); + mSummaryADCHisto = std::make_unique("h_summary_ADC", "Summary ADC", mBinLabelsADC.size(), 0, mBinLabelsADC.size()); + for (size_t bin = 0; bin < mBinLabelsADC.size(); bin++) { + mSummaryADCHisto->GetXaxis()->SetBinLabel(bin + 1, mBinLabelsADC[bin].c_str()); + } + publishHisto(mSummaryADCHisto.get(), false, "E", ""); +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::createSummaryTDCHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + mSummaryTDCHisto.reset(); + mSummaryTDCHisto = std::make_unique("h_summary_TDC", "Summary TDC", mBinLabelsTDC.size(), 0, mBinLabelsTDC.size()); + for (size_t bin = 0; bin < mBinLabelsTDC.size(); bin++) { + mSummaryTDCHisto->GetXaxis()->SetBinLabel(bin + 1, mBinLabelsTDC[bin].c_str()); + } + publishHisto(mSummaryTDCHisto.get(), false, "E", ""); +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::createSummaryTDCAHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + mSummaryTDCAHisto.reset(); + mSummaryTDCAHisto = std::make_unique("h_summary_TDCA", "Summary TDC Amplitude", mBinLabelsTDCA.size(), 0, mBinLabelsTDCA.size()); + for (size_t bin = 0; bin < mBinLabelsTDCA.size(); bin++) { + mSummaryTDCAHisto->GetXaxis()->SetBinLabel(bin + 1, mBinLabelsTDCA[bin].c_str()); + } + publishHisto(mSummaryTDCAHisto.get(), false, "E", ""); +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::createSummaryPeak1nHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + mSummaryPeak1nHisto.reset(); + mSummaryPeak1nHisto = std::make_unique("h_summary_Peak1n", "Summary TDC Amplitude Peak 1n", mBinLabelsPeak1n.size(), 0, mBinLabelsPeak1n.size()); + for (size_t bin = 0; bin < mBinLabelsPeak1n.size(); bin++) { + mSummaryPeak1nHisto->GetXaxis()->SetBinLabel(bin + 1, mBinLabelsPeak1n[bin].c_str()); + } + publishHisto(mSummaryPeak1nHisto.get(), false, "E", ""); +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::createSummaryPeak1pHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + mSummaryPeak1pHisto.reset(); + mSummaryPeak1pHisto = std::make_unique("h_summary_Peak1p", "Summary TDC Amplitude Peak 1p", mBinLabelsPeak1p.size(), 0, mBinLabelsPeak1p.size()); + for (size_t bin = 0; bin < mBinLabelsPeak1p.size(); bin++) { + mSummaryPeak1pHisto->GetXaxis()->SetBinLabel(bin + 1, mBinLabelsPeak1p[bin].c_str()); + } + publishHisto(mSummaryPeak1pHisto.get(), false, "E", ""); +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::initialize(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + createSummaryADCHistos(t, &qcdb); + createSummaryTDCHistos(t, &qcdb); + createSummaryTDCAHistos(t, &qcdb); + createSummaryPeak1nHistos(t, &qcdb); + createSummaryPeak1pHistos(t, &qcdb); +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::updateSummaryADCHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + for (auto& [bin, mo] : mMOsADC) { + if (!mo.update(qcdb, t.timestamp, t.activity)) { + continue; + } + TH1F* h = mo.get(); + if (!h) { + continue; + } + mSummaryADCHisto->SetBinContent(bin, h->GetMean()); + mSummaryADCHisto->SetBinError(bin, h->GetMeanError()); + } +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::updateSummaryTDCHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + for (auto& [bin, mo] : mMOsTDC) { + if (!mo.update(qcdb, t.timestamp, t.activity)) { + continue; + } + TH1F* h = mo.get(); + if (!h) { + continue; + } + mSummaryTDCHisto->SetBinContent(bin, h->GetMean()); + mSummaryTDCHisto->SetBinError(bin, h->GetMeanError()); + } +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::updateSummaryTDCAHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + for (auto& [bin, mo] : mMOsTDCA) { + if (!mo.update(qcdb, t.timestamp, t.activity)) { + continue; + } + TH1F* h = mo.get(); + if (!h) { + continue; + } + mSummaryTDCAHisto->SetBinContent(bin, h->GetMean()); + mSummaryTDCAHisto->SetBinError(bin, h->GetMeanError()); + } +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::updateSummaryPeak1nHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + for (auto& [bin, mo] : mMOsPeak1n) { + if (!mo.update(qcdb, t.timestamp, t.activity)) { + continue; + } + TH1F* h = mo.get(); + if (!h) { + continue; + } + h->GetXaxis()->SetRangeUser(1., 200.); + mSummaryPeak1nHisto->SetBinContent(bin, h->GetBinCenter(h->GetMaximumBin())); + } +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::updateSummaryPeak1pHistos(Trigger t, repository::DatabaseInterface* qcdb) +{ + float minBin1p = 2; + float maxBin1p = 250; + for (auto& [bin, mo] : mMOsPeak1p) { + if (!mo.update(qcdb, t.timestamp, t.activity)) { + continue; + } + TH1F* h = mo.get(); + if (!h) { + continue; + } + if (auto param = mCustomParameters.find("minBin1p"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - minBin1p: " << param->second << ENDM; + minBin1p = atof(param->second.c_str()); + } else { + minBin1p = 2; + } + if (auto param = mCustomParameters.find("maxBin1p"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - maxBin1p: " << param->second << ENDM; + maxBin1p = atof(param->second.c_str()); + } else { + maxBin1p = 250; + } + h->GetXaxis()->SetRangeUser(minBin1p, maxBin1p); + mSummaryPeak1pHisto->SetBinContent(bin, h->GetBinCenter(h->GetMaximumBin())); + } +} +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::update(Trigger t, framework::ServiceRegistryRef services) +{ + auto& qcdb = services.get(); + + updateSummaryADCHistos(t, &qcdb); + updateSummaryTDCHistos(t, &qcdb); + updateSummaryTDCAHistos(t, &qcdb); + updateSummaryPeak1nHistos(t, &qcdb); + updateSummaryPeak1pHistos(t, &qcdb); +} + +//_________________________________________________________________________________________ + +void ZDCRecDataPostProcessing::finalize(Trigger t, framework::ServiceRegistryRef) +{ +} diff --git a/Modules/ZDC/src/ZDCRecDataTask.cxx b/Modules/ZDC/src/ZDCRecDataTask.cxx new file mode 100644 index 0000000000..a72ebc036f --- /dev/null +++ b/Modules/ZDC/src/ZDCRecDataTask.cxx @@ -0,0 +1,1037 @@ +// Copyright 2019-2022 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file ZDCRecDataTask.cxx +/// \author Carlo Puggioni +/// + +#include +#include + +#include "QualityControl/QcInfoLogger.h" +#include "ZDC/ZDCRecDataTask.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ZDCBase/Constants.h" +#include "CommonUtils/NameConf.h" +#include "ZDCReconstruction/RecoConfigZDC.h" +#include "ZDCReconstruction/ZDCEnergyParam.h" +#include "ZDCReconstruction/ZDCTowerParam.h" +#include "DataFormatsZDC/RecEventFlat.h" +#include "ZDCSimulation/ZDCSimParam.h" + +using namespace o2::zdc; +namespace o2::quality_control_modules::zdc +{ + +ZDCRecDataTask::~ZDCRecDataTask() +{ + mVecCh.clear(); + mVecType.clear(); + mNameHisto.clear(); + + for (auto h : mHisto1D) { + delete h.histo; + } + mHisto1D.clear(); + for (auto h : mHisto2D) { + delete h.histo; + } + mHisto2D.clear(); +} + +void ZDCRecDataTask::initialize(o2::framework::InitContext& /*ctx*/) +{ + ILOG(Debug, Devel) << "initialize ZDCRecDataTask" << ENDM; // QcInfoLogger is used. FairMQ logs will go to there as well. + init(); +} + +void ZDCRecDataTask::startOfActivity(const Activity& activity) +{ + ILOG(Debug, Devel) << "startOfActivity " << activity.mId << ENDM; + reset(); +} + +void ZDCRecDataTask::startOfCycle() +{ + ILOG(Debug, Devel) << "startOfCycle" << ENDM; +} + +void ZDCRecDataTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + + auto bcrec = ctx.inputs().get>("zdc-bcrec"); + auto energy = ctx.inputs().get>("zdc-energyrec"); + auto tdc = ctx.inputs().get>("zdc-tdcrec"); + auto info = ctx.inputs().get>("zdc-inforec"); + process(bcrec, energy, tdc, info); +} + +void ZDCRecDataTask::endOfCycle() +{ + ILOG(Debug, Devel) << "endOfCycle" << ENDM; +} + +void ZDCRecDataTask::endOfActivity(const Activity& /*activity*/) +{ + ILOG(Debug, Devel) << "endOfActivity" << ENDM; +} + +void ZDCRecDataTask::reset() +{ + // clean all the monitor objects here + + ILOG(Debug, Devel) << "Resetting the histograms" << ENDM; + for (int i = 0; i < (int)mHisto1D.size(); i++) { + mHisto1D.at(i).histo->Reset(); + } + for (int i = 0; i < (int)mHisto2D.size(); i++) { + mHisto2D.at(i).histo->Reset(); + } +} +void ZDCRecDataTask::init() +{ + initVecCh(); + initVecType(); + initHisto(); + // dumpHistoStructure(); +} + +void ZDCRecDataTask::initVecCh() +{ + // ZNA + insertChVec("ZNAC"); + insertChVec("ZNA1"); + insertChVec("ZNA2"); + insertChVec("ZNA3"); + insertChVec("ZNA4"); + insertChVec("ZNAS"); + // ZPA + insertChVec("ZPAC"); + insertChVec("ZPA1"); + insertChVec("ZPA2"); + insertChVec("ZPA3"); + insertChVec("ZPA4"); + insertChVec("ZPAS"); + // ZNC + insertChVec("ZNCC"); + insertChVec("ZNC1"); + insertChVec("ZNC2"); + insertChVec("ZNC3"); + insertChVec("ZNC4"); + insertChVec("ZNCS"); + // ZPC + insertChVec("ZPCC"); + insertChVec("ZPC1"); + insertChVec("ZPC2"); + insertChVec("ZPC3"); + insertChVec("ZPC4"); + insertChVec("ZPCS"); + // ZEM + insertChVec("ZEM1"); + insertChVec("ZEM2"); + // Particular channels + insertChVec("ZNC-ZNA"); + insertChVec("ZNC+ZNA"); + insertChVec("CH"); + insertChVec("MSG"); + insertChVec("CXZNA"); + insertChVec("CYZNA"); + insertChVec("CXZNC"); + insertChVec("CYZNC"); + insertChVec("CXZPA"); + insertChVec("CXZPC"); +} + +void ZDCRecDataTask::initVecType() +{ + insertTypeVec("ADC"); + insertTypeVec("TDCV"); + insertTypeVec("TDCA"); + insertTypeVec("TDCAC"); + insertTypeVec("ADCAC"); + insertTypeVec("BC"); + insertTypeVec("INFO"); +} + +void ZDCRecDataTask::insertChVec(std::string ch) +{ + mVecCh.push_back(ch); +} + +void ZDCRecDataTask::insertTypeVec(std::string type) +{ + mVecType.push_back(type); +} + +void ZDCRecDataTask::setBinHisto1D(int numBinX, double minBinX, double maxBinX) +{ + setNumBinX(numBinX); + setMinBinX(minBinX); + setMaxBinX(maxBinX); +} + +void ZDCRecDataTask::setBinHisto2D(int numBinX, double minBinX, double maxBinX, int numBinY, double minBinY, double maxBinY) +{ + setNumBinX(numBinX); + setMinBinX(minBinX); + setMaxBinX(maxBinX); + setNumBinY(numBinY); + setMinBinY(minBinY); + setMaxBinY(maxBinY); +} + +// CENTRAL_EVENT_CONFIG -> tdcLimit [ns] ; centraleventconfig [discrete value] +void ZDCRecDataTask::SetConfigCentralEvent(float tdcLimit, int centraleventconfig) +{ + settdcLimit(tdcLimit); + setcentraleventconfigvalue(centraleventconfig); +} + +void ZDCRecDataTask::dumpHistoStructure() +{ + std::ofstream dumpFile; + dumpFile.open("dumpStructuresRec.txt"); + + dumpFile << "\n Vector Channels\n"; + for (int i = 0; i < (int)mVecCh.size(); i++) { + dumpFile << mVecCh.at(i) << ", "; + } + dumpFile << "\n"; + + dumpFile << "\n Vector Type\n"; + for (int i = 0; i < (int)mVecType.size(); i++) { + dumpFile << mVecType.at(i) << ", "; + } + dumpFile << "\n"; + dumpFile << "\n Histos 1D \n"; + for (int i = 0; i < (int)mHisto1D.size(); i++) { + dumpFile << mHisto1D.at(i).typeh << mHisto1D.at(i).histo->GetName() << " \t" << mHisto1D.at(i).ch << " \t" << mHisto1D.at(i).typech << "\n"; + } + dumpFile << "\n Histos 2D \n"; + for (int i = 0; i < (int)mHisto2D.size(); i++) { + dumpFile << mHisto2D.at(i).typeh << mHisto2D.at(i).histo->GetName() << " \t" << mHisto2D.at(i).typech1 << " \t" << mHisto2D.at(i).ch1 << " \t" << mHisto2D.at(i).typech2 << " \t" << mHisto2D.at(i).ch2 << "\n"; + } + dumpFile << "\n HistoName \n"; + for (int i = 0; i < (int)mNameHisto.size(); i++) { + dumpFile << mNameHisto.at(i) << "\n"; + } + dumpFile.close(); +} + +void ZDCRecDataTask::initHisto() +{ + ILOG(Debug, Devel) << "initialize ZDC REC DATA HISTOGRAMS" << ENDM; + std::vector tokenString; + + if (auto param = mCustomParameters.find("ADC"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ADC: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(1051, -202.5, 4002.5); + } + addNewHisto("ADC1D", "h_ADC_ZNA_TC", "ADC ZNA TC ", "ADC", "ZNAC", "", "", 1); + addNewHisto("ADC1D", "h_ADC_ZNA_T1", "ADC ZNA T1 ", "ADC", "ZNA1", "", "", 2); + addNewHisto("ADC1D", "h_ADC_ZNA_T2", "ADC ZNA T2 ", "ADC", "ZNA2", "", "", 3); + addNewHisto("ADC1D", "h_ADC_ZNA_T3", "ADC ZNA T3 ", "ADC", "ZNA3", "", "", 4); + addNewHisto("ADC1D", "h_ADC_ZNA_T4", "ADC ZNA T4 ", "ADC", "ZNA4", "", "", 5); + addNewHisto("ADC1D", "h_ADC_ZNA_SUM", "ADC ZNA SUM ", "ADC", "ZNAS", "", "", 6); + + addNewHisto("ADC1D", "h_ADC_ZPA_TC", "ADC ZPA TC ", "ADC", "ZPAC", "", "", 7); + addNewHisto("ADC1D", "h_ADC_ZPA_T1", "ADC ZPA T1 ", "ADC", "ZPA1", "", "", 8); + addNewHisto("ADC1D", "h_ADC_ZPA_T2", "ADC ZPA T2 ", "ADC", "ZPA2", "", "", 9); + addNewHisto("ADC1D", "h_ADC_ZPA_T3", "ADC ZPA T3 ", "ADC", "ZPA3", "", "", 10); + addNewHisto("ADC1D", "h_ADC_ZPA_T4", "ADC ZPA T4 ", "ADC", "ZPA4", "", "", 11); + addNewHisto("ADC1D", "h_ADC_ZPA_SUM", "ADC ZPA SUM ", "ADC", "ZPAS", "", "", 12); + + addNewHisto("ADC1D", "h_ADC_ZNC_TC", "ADC ZNC TC ", "ADC", "ZNCC", "", "", 15); + addNewHisto("ADC1D", "h_ADC_ZNC_T1", "ADC ZNC T1 ", "ADC", "ZNC1", "", "", 16); + addNewHisto("ADC1D", "h_ADC_ZNC_T2", "ADC ZNC T2 ", "ADC", "ZNC2", "", "", 17); + addNewHisto("ADC1D", "h_ADC_ZNC_T3", "ADC ZNC T3 ", "ADC", "ZNC3", "", "", 18); + addNewHisto("ADC1D", "h_ADC_ZNC_T4", "ADC ZNC T4 ", "ADC", "ZNC4", "", "", 19); + addNewHisto("ADC1D", "h_ADC_ZNC_SUM", "ADC ZNC SUM ", "ADC", "ZNCS", "", "", 20); + + addNewHisto("ADC1D", "h_ADC_ZPC_TC", "ADC ZPC TC ", "ADC", "ZPCC", "", "", 21); + addNewHisto("ADC1D", "h_ADC_ZPC_T1", "ADC ZPC T1 ", "ADC", "ZPC1", "", "", 22); + addNewHisto("ADC1D", "h_ADC_ZPC_T2", "ADC ZPC T2 ", "ADC", "ZPC2", "", "", 23); + addNewHisto("ADC1D", "h_ADC_ZPC_T3", "ADC ZPC T3 ", "ADC", "ZPC3", "", "", 24); + addNewHisto("ADC1D", "h_ADC_ZPC_T4", "ADC ZPC T4 ", "ADC", "ZPC4", "", "", 25); + addNewHisto("ADC1D", "h_ADC_ZPC_SUM", "ADC ZPC SUM ", "ADC", "ZPCS", "", "", 26); + if (auto param = mCustomParameters.find("ADCZEM"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ADCZEM: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(1051, -202.5, 4002.5); + } + addNewHisto("ADC1D", "h_ADC_ZEM1", "ADC ZEM1 ", "ADC", "ZEM1", "", "", 13); + addNewHisto("ADC1D", "h_ADC_ZEM2", "ADC ZEM2 ", "ADC", "ZEM2", "", "", 14); + + if (auto param = mCustomParameters.find("ADCH"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ADCH: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(1051, -202.5, 4002.5); + } + addNewHisto("ADC1D", "h_ADC_ZNA_TC_H", "ADC ZNA TC ZOOM", "ADC", "ZNAC", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZNA_SUM_H", "ADC ZNA SUM ZOOM", "ADC", "ZNAS", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZPA_TC_H", "ADC ZPA TC ZOOM", "ADC", "ZPAC", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZPA_SUM_H", "ADC ZPA SUM ZOOM", "ADC", "ZPAS", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZNC_TC_H", "ADC ZNC TC ZOOM", "ADC", "ZNCC", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZNC_SUM_H", "ADC ZNC SUM ZOOM", "ADC", "ZNCS", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZPC_TC_H", "ADC ZPC TC ZOOM", "ADC", "ZPCC", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZPC_SUM_H", "ADC ZPC SUM ZOOM", "ADC", "ZPCS", "", "", 0); + + addNewHisto("ADC1D", "h_ADC_ZPA_TC_H_CUT", "ADC ZPA TC ZOOM with cut", "ADCAC", "ZPAC", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZPA_SUM_H_CUT", "ADC ZPA SUM ZOOM with cut", "ADCAC", "ZPAS", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZPC_TC_H_CUT", "ADC ZPC TC ZOOM with cut", "ADCAC", "ZPCC", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZPC_SUM_H_CUT", "ADC ZPC SUM ZOOM with cut", "ADCAC", "ZPCS", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZNA_TC_H_CUT", "ADC ZNA TC ZOOM with cut", "ADCAC", "ZNAC", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZNA_SUM_H_CUT", "ADC ZNA SUM ZOOM with cut", "ADCAC", "ZNAS", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZNC_TC_H_CUT", "ADC ZNC TC ZOOM with cut", "ADCAC", "ZNCC", "", "", 0); + addNewHisto("ADC1D", "h_ADC_ZNC_SUM_H_CUT", "ADC ZNC SUM ZOOM with cut", "ADCAC", "ZNCS", "", "", 0); + + if (auto param = mCustomParameters.find("TDCT"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TDCT: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(2500, -5.5, 245.5); + } + addNewHisto("TDC1D", "h_TDC_ZNA_TC_V", "TDC Time (ns) ZNA TC", "TDCV", "ZNAC", "", "", 1); + addNewHisto("TDC1D", "h_TDC_ZNA_SUM_V", "TDC Time (ns) ZNA SUM", "TDCV", "ZNAS", "", "", 2); + addNewHisto("TDC1D", "h_TDC_ZPA_TC_V", "TDC Time (ns) ZPA TC", "TDCV", "ZPAC", "", "", 3); + addNewHisto("TDC1D", "h_TDC_ZPA_SUM_V", "TDC Time (ns) ZPA SUM", "TDCV", "ZPAS", "", "", 4); + addNewHisto("TDC1D", "h_TDC_ZNC_TC_V", "TDC Time (ns) ZNC TC", "TDCV", "ZNCC", "", "", 7); + addNewHisto("TDC1D", "h_TDC_ZNC_SUM_V", "TDC Time (ns) ZNC SUM", "TDCV", "ZNCS", "", "", 8); + addNewHisto("TDC1D", "h_TDC_ZPC_TC_V", "TDC Time (ns) ZPC TC", "TDCV", "ZPCC", "", "", 9); + addNewHisto("TDC1D", "h_TDC_ZPC_SUM_V", "TDC Time (ns) ZPC SUM", "TDCV", "ZPCS", "", "", 10); + addNewHisto("TDC1D", "h_TDC_ZEM1_V", "TDC Time (ns) ZEM1", "TDCV", "ZEM1", "", "", 5); + addNewHisto("TDC1D", "h_TDC_ZEM2_V", "TDC Time (ns) ZEM2", "TDCV", "ZEM2", "", "", 6); + + if (auto param = mCustomParameters.find("TDCA"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TDCA: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(2000, -0.5, 3999.5); + } + addNewHisto("TDC1D", "h_TDC_ZNA_TC_A", "TDC Amplitude ZNA TC", "TDCA", "ZNAC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZNA_SUM_A", "TDC Amplitude ZNA SUM", "TDCA", "ZNAS", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPA_TC_A", "TDC Amplitude ZPA TC", "TDCA", "ZPAC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPA_SUM_A", "TDC Amplitude ZPA SUM", "TDCA", "ZPAS", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZNC_TC_A", "TDC Amplitude ZNC TC", "TDCA", "ZNCC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZNC_SUM_A", "TDC Amplitude ZNC SUM", "TDCA", "ZNCS", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPC_TC_A", "TDC Amplitude ZPC TC", "TDCA", "ZPCC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPC_SUM_A", "TDC Amplitude ZPC SUM", "TDCA", "ZPCS", "", "", 0); + if (auto param = mCustomParameters.find("TDCAZEM"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TDCAZEM: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(2000, -0.5, 3999.5); + } + addNewHisto("TDC1D", "h_TDC_ZEM1_A", "TDC Amplitude ZEM1", "TDCA", "ZEM1", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZEM2_A", "TDC Amplitude ZEM2", "TDCA", "ZEM2", "", "", 0); + // TDC_A ZOOM + if (auto param = mCustomParameters.find("TDCAH"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TDCAH: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(1051, -202.5, 4002.5); + } + addNewHisto("TDC1D", "h_TDC_ZNA_TC_A_H", "TDC Amplitude ZNA TC ZOOM", "TDCA", "ZNAC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZNA_SUM_A_H", "TDC Amplitude ZNA SUM ZOOM", "TDCA", "ZNAS", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPA_TC_A_H", "TDC Amplitude ZPA TC ZOOM", "TDCA", "ZPAC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPA_SUM_A_H", "TDC Amplitude ZPA SUM ZOOM", "TDCA", "ZPAS", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZNC_TC_A_H", "TDC Amplitude ZNC TC ZOOM", "TDCA", "ZNCC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZNC_SUM_A_H", "TDC Amplitude ZNC SUM ZOOM", "TDCA", "ZNCS", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPC_TC_A_H", "TDC Amplitude ZPC TC ZOOM", "TDCA", "ZPCC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPC_SUM_A_H", "TDC Amplitude ZPC SUM ZOOM", "TDCA", "ZPCS", "", "", 0); + + addNewHisto("TDC1D", "h_TDC_ZPA_TC_A_H_CUT", "TDC Amplitude ZPA TC ZOOM with cut", "TDCAC", "ZPAC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPA_SUM_A_H_CUT", "TDC Amplitude ZPA SUM ZOOM with cut", "TDCAC", "ZPAS", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPC_TC_A_H_CUT", "TDC Amplitude ZPC TC ZOOM with cut", "TDCAC", "ZPCC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZPC_SUM_A_H_CUT", "TDC Amplitude ZPC SUM ZOOM with cut", "TDCAC", "ZPCS", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZNA_TC_A_H_CUT", "TDC Amplitude ZNA TC ZOOM with cut", "TDCAC", "ZNAC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZNA_SUM_A_H_CUT", "TDC Amplitude ZNA SUM ZOOM with cut", "TDCAC", "ZNAS", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZNC_TC_A_H_CUT", "TDC Amplitude ZNC TC ZOOM with cut", "TDCAC", "ZNCC", "", "", 0); + addNewHisto("TDC1D", "h_TDC_ZNC_SUM_A_H_CUT", "TDC Amplitude ZNC SUM ZOOM with cut", "TDCAC", "ZNCS", "", "", 0); + + // Centroid ZPA + if (auto param = mCustomParameters.find("CENTR_ZPA"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - CENTR_ZPA: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(2240, 0, 22.4); + } + addNewHisto("CENTR_ZPA", "h_CENTR_ZPA", "ZPA Centroid (cm)", "ADC", "CXZPA", "", "", 0); + // Centroid ZPA + if (auto param = mCustomParameters.find("CENTR_ZPC"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - CENTR_ZPC: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto1D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str())); + } else { + setBinHisto1D(2240, -22.4, 0); + } + addNewHisto("CENTR_ZPC", "h_CENTR_ZPC", "ZPC Centroid (cm)", "ADC", "CXZPC", "", "", 0); + + // 2D Histos + + if (auto param = mCustomParameters.find("ADCSUMvsTC"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ADCSUMvsTC: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(1051, -202.5, 4002.5, 1051, -202.5, 4002.5); + } + addNewHisto("ADCSUMvsTC", "h_ADC_ZNAS_ZNAC", "ADC ZNA SUM vs ADC ZNA TC", "ADC", "ZNAC", "ADC", "ZNAS", 0); + addNewHisto("ADCSUMvsTC", "h_ADC_ZPAS_ZPAC", "ADC ZPA SUM vs ADC ZPA TC", "ADC", "ZPAC", "ADC", "ZPAS", 0); + addNewHisto("ADCSUMvsTC", "h_ADC_ZNCS_ZNCC", "ADC ZNC SUM vs ADC ZNC TC", "ADC", "ZNCC", "ADC", "ZNCS", 0); + addNewHisto("ADCSUMvsTC", "h_ADC_ZPCS_ZPCC", "ADC ZPC SUM vs ADC ZPC TC", "ADC", "ZPCC", "ADC", "ZPCS", 0); + + addNewHisto("ADCSUMvsTC", "h_ADC_ZNAC_ZPAC", "ADC ZNA TC vs ADC ZPA TC", "ADC", "ZPAC", "ADC", "ZNAC", 0); + addNewHisto("ADCSUMvsTC", "h_ADC_ZNCC_ZPCC", "ADC ZNC TC vs ADC ZPC TC", "ADC", "ZPCC", "ADC", "ZNCC", 0); + if (auto param = mCustomParameters.find("ADCZEMvsADCZEM"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ADCZEMvsADCZEM: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(1051, -202.5, 4002.5, 1051, -202.5, 4002.5); + } + addNewHisto("ADCSUMvsTC", "h_ADC_ZEM1_ZEM2", "ADC ZEM1 vs ADC ZEM2", "ADC", "ZEM2", "ADC", "ZEM1", 0); + if (auto param = mCustomParameters.find("ADCZEMvsTC"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ADCZEMvsTC: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(1051, -202.5, 4002.5, 1051, -202.5, 4002.5); + } + addNewHisto("ADCSUMvsTC", "h_ADC_ZNA_ZEM1", "ADC ZNA TC vs ADC ZEM1", "ADC", "ZEM1", "ADC", "ZNAC", 0); + addNewHisto("ADCSUMvsTC", "h_ADC_ZNA_ZEM2", "ADC ZNA TC vs ADC ZEM2", "ADC", "ZEM2", "ADC", "ZNAC", 0); + addNewHisto("ADCSUMvsTC", "h_ADC_ZNC_ZEM1", "ADC ZNC TC vs ADC ZEM1", "ADC", "ZEM1", "ADC", "ZNCC", 0); + addNewHisto("ADCSUMvsTC", "h_ADC_ZNC_ZEM2", "ADC ZNC TC vs ADC ZEM2", "ADC", "ZEM2", "ADC", "ZNCC", 0); + + addNewHisto("ADCSUMvsTC", "h_ADC_ZPA_ZEM1", "ADC ZPA TC vs ADC ZEM1", "ADC", "ZEM1", "ADC", "ZPAC", 0); + addNewHisto("ADCSUMvsTC", "h_ADC_ZPA_ZEM2", "ADC ZPA TC vs ADC ZEM2", "ADC", "ZEM2", "ADC", "ZPAC", 0); + addNewHisto("ADCSUMvsTC", "h_ADC_ZPC_ZEM1", "ADC ZPC TC vs ADC ZEM1", "ADC", "ZEM1", "ADC", "ZPCC", 0); + addNewHisto("ADCSUMvsTC", "h_ADC_ZPC_ZEM2", "ADC ZPC TC vs ADC ZEM2", "ADC", "ZEM2", "ADC", "ZPCC", 0); + + if (auto param = mCustomParameters.find("ADCvsTDCT"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ADCvsTDCT: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(250, -5.5, 24.5, 1051, -202.5, 4002.5); + } + addNewHisto("ADCvsTDC", "h_ADC_TDC_ZNAC", "ADC ZNA TC vs TDC Time (ns) ZNA TC", "TDCV", "ZNAC", "ADC", "ZNAC", 0); + addNewHisto("ADCvsTDC", "h_ADC_TDC_ZNAS", "ADC ZNA SUM vs TDC Time (ns) ZNA SUM", "TDCV", "ZNAS", "ADC", "ZNAS", 0); + addNewHisto("ADCvsTDC", "h_ADC_TDC_ZPAC", "ADC ZPA TC vs TDC Time (ns) ZPA TC", "TDCV", "ZPAC", "ADC", "ZPAC", 0); + addNewHisto("ADCvsTDC", "h_ADC_TDC_ZPAS", "ADC ZPA SUM vs TDC Time (ns) ZPA SUM", "TDCV", "ZPAS", "ADC", "ZPAS", 0); + addNewHisto("ADCvsTDC", "h_ADC_TDC_ZNCC", "ADC ZNC TC vs TDC Time (ns) ZNC TC", "TDCV", "ZNCC", "ADC", "ZNCC", 0); + addNewHisto("ADCvsTDC", "h_ADC_TDC_ZNCS", "ADC ZNC SUM vs TDC Time (ns) ZNC SUM", "TDCV", "ZNCS", "ADC", "ZNCS", 0); + addNewHisto("ADCvsTDC", "h_ADC_TDC_ZPCC", "ADC ZPC TC vs TDC Time (ns) ZPC TC", "TDCV", "ZPCC", "ADC", "ZPCC", 0); + addNewHisto("ADCvsTDC", "h_ADC_TDC_ZPCS", "ADC ZPC SUM vs TDC Time (ns) ZPC SUM", "TDCV", "ZPCS", "ADC", "ZPCS", 0); + if (auto param = mCustomParameters.find("ADCZEMvsTDCT"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - ADCZEMvsTDCT: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(250, -5.5, 24.5, 1051, -202.5, 4002.5); + } + addNewHisto("ADCvsTDC", "h_ADC_TDC_ZEM1", "ADC ZEM1 vs TDC Time (ns) ZEM1", "TDCV", "ZEM1", "ADC", "ZEM1", 0); + addNewHisto("ADCvsTDC", "h_ADC_TDC_ZEM2", "ADC ZEM2 vs TDC Time (ns) ZEM2", "TDCV", "ZEM2", "ADC", "ZEM2", 0); + + if (auto param = mCustomParameters.find("TDCDIFF"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TDCDIFF: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(100, -10.5, 10.5, 100, -10.5, 10.5); + } + addNewHisto("TDC-DIFF", "h_TDC_ZNC_DIFF_ZNA_ZNC_SUM_ZNA_V", "TDC Time (ns) TDC ZNC + ZNA vs ZNC - ZNA", "TDCV", "ZNC-ZNA", "TDCV", "ZNC+ZNA", 0); + addNewHisto("TDC-DIFF", "h_TDC_ZNC_DIFF_ZNA_ZNC_SUM_ZNA_V_cut", "TDC Time (ns) TDC ZNC + ZNA vs ZNC - ZNA with cut on ZEMs", "TDCV", "ZNC-ZNA", "TDCV", "ZNC+ZNA", 0); + + if (auto param = mCustomParameters.find("TDCAvsTDCT"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TDCAvsTDCT: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(250, -5.5, 24.5, 2000, -0.5, 3999.5); + } + addNewHisto("TDC_T_A", "h_TDC_ZNAC_V_A", "ZNA TC TDC amplitude vs time (ns)", "TDCV", "ZNAC", "TDCA", "ZNAC", 0); + addNewHisto("TDC_T_A", "h_TDC_ZPAC_V_A", "ZPA TC TDC amplitude vs time (ns)", "TDCV", "ZPAC", "TDCA", "ZPAC", 0); + addNewHisto("TDC_T_A", "h_TDC_ZNCC_V_A", "ZNC TC TDC amplitude vs time (ns)", "TDCV", "ZNCC", "TDCA", "ZNCC", 0); + addNewHisto("TDC_T_A", "h_TDC_ZPCC_V_A", "ZPC TC TDC amplitude vs time (ns)", "TDCV", "ZPCC", "TDCA", "ZPCC", 0); + addNewHisto("TDC_T_A", "h_TDC_ZNAS_V_A", "ZNA SUM TDC amplitude vs time (ns)", "TDCV", "ZNAS", "TDCA", "ZNAS", 0); + addNewHisto("TDC_T_A", "h_TDC_ZPAS_V_A", "ZPA SUM TDC amplitude vs time (ns)", "TDCV", "ZPAS", "TDCA", "ZPAS", 0); + addNewHisto("TDC_T_A", "h_TDC_ZNCS_V_A", "ZNC SUM TDC amplitude vs time (ns)", "TDCV", "ZNCS", "TDCA", "ZNCS", 0); + addNewHisto("TDC_T_A", "h_TDC_ZPCS_V_A", "ZPC SUM TDC amplitude vs time (ns)", "TDCV", "ZPCS", "TDCA", "ZPCS", 0); + if (auto param = mCustomParameters.find("TDCAZEMvsTDCT"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TDCAZEMvsTDCT: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(250, -5.5, 24.5, 2000, -0.5, 3999.5); + } + addNewHisto("TDC_T_A", "h_TDC_ZEM1_V_A", "ZEM1 TDC amplitude vs time (ns)", "TDCV", "ZEM1", "TDCA", "ZEM1", 0); + addNewHisto("TDC_T_A", "h_TDC_ZEM2_V_A", "ZEM2 TDC amplitude vs time (ns)", "TDCV", "ZEM2", "TDCA", "ZEM2", 0); + if (auto param = mCustomParameters.find("TDCAvsTDCA"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TDCAvsTDCA: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(1000, -0.5, 3999.5, 1000, -0.5, 3999.5); + } + addNewHisto("TDC_A_A", "h_TDC_ZNA_ZPA", "ZNA TDC amplitude vs ZPA TDC amplitude", "TDCA", "ZPAC", "TDCA", "ZNAC", 0); + addNewHisto("TDC_A_A", "h_TDC_ZNC_ZPC", "ZNC TDC amplitude vs ZPC TDC amplitude", "TDCA", "ZPCC", "TDCA", "ZNCC", 0); + + addNewHisto("TDC_A_A", "h_TDC_ZNAS_ZNAC", "TDC amplitude ZNA SUM vs TDC amplitude ZNA TC", "TDCA", "ZNAC", "TDCA", "ZNAS", 0); + addNewHisto("TDC_A_A", "h_TDC_ZPAS_ZPAC", "TDC amplitude ZPA SUM vs TDC amplitude ZPA TC", "TDCA", "ZPAC", "TDCA", "ZPAS", 0); + addNewHisto("TDC_A_A", "h_TDC_ZNCS_ZNCC", "TDC amplitude ZNC SUM vs TDC amplitude ZNC TC", "TDCA", "ZNCC", "TDCA", "ZNCS", 0); + addNewHisto("TDC_A_A", "h_TDC_ZPCS_ZPCC", "TDC amplitude ZPC SUM vs TDC amplitude ZPC TC", "TDCA", "ZPCC", "TDCA", "ZPCS", 0); + if (auto param = mCustomParameters.find("TDCAZEMvsTDCAZEM"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TDCAZEMvsTDCAZEM: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(1000, -0.5, 3999.5, 1000, -0.5, 3999.5); + } + addNewHisto("TDC_A_A", "h_TDC_ZEM1_ZEM2", "ZEM1 TDC amplitude vs ZEM2 TDC amplitude", "TDCA", "ZEM2", "TDCA", "ZEM1", 0); + if (auto param = mCustomParameters.find("TDCAZEMvsTDCA"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - TDCAZEMvsTDCA: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(1000, -0.5, 3999.5, 1000, -0.5, 3999.5); + } + addNewHisto("TDC_A_A", "h_TDC_ZNA_ZEM1", "ZNA TDC amplitude vs ZEM1 TDC amplitude", "TDCA", "ZEM1", "TDCA", "ZNAC", 0); + addNewHisto("TDC_A_A", "h_TDC_ZNA_ZEM2", "ZNA TDC amplitude vs ZEM2 TDC amplitude", "TDCA", "ZEM2", "TDCA", "ZNAC", 0); + addNewHisto("TDC_A_A", "h_TDC_ZNC_ZEM1", "ZNC TDC amplitude vs ZEM1 TDC amplitude", "TDCA", "ZEM1", "TDCA", "ZNCC", 0); + addNewHisto("TDC_A_A", "h_TDC_ZNC_ZEM2", "ZNC TDC amplitude vs ZEM2 TDC amplitude", "TDCA", "ZEM2", "TDCA", "ZNCC", 0); + + addNewHisto("TDC_A_A", "h_TDC_ZPA_ZEM1", "ZPA TDC amplitude vs ZEM1 TDC amplitude", "TDCA", "ZEM1", "TDCA", "ZPAC", 0); + addNewHisto("TDC_A_A", "h_TDC_ZPA_ZEM2", "ZPA TDC amplitude vs ZEM2 TDC amplitude", "TDCA", "ZEM2", "TDCA", "ZPAC", 0); + addNewHisto("TDC_A_A", "h_TDC_ZPC_ZEM1", "ZPC TDC amplitude vs ZEM1 TDC amplitude", "TDCA", "ZEM1", "TDCA", "ZPCC", 0); + addNewHisto("TDC_A_A", "h_TDC_ZPC_ZEM2", "ZPC TDC amplitude vs ZEM2 TDC amplitude", "TDCA", "ZEM2", "TDCA", "ZPCC", 0); + + // msg histo + setBinHisto2D(26, -0.5, 26.0 - 0.5, 19, -0.5, 19.0 - 0.5); + addNewHisto("MSG_REC", "h_msg", "Reconstruction messages", "INFO", "CH", "INFO", "MSG", 0); + int idh_msg = (int)mHisto2D.size() - 1; + mHisto2D.at(idh_msg).histo->SetStats(0); + // Centroid ZNA + if (auto param = mCustomParameters.find("CENTR_ZNA"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - CENTR_ZNA: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(200, -2, 2, 200, -2, 2); + } + addNewHisto("CENTR_ZNA", "h_CENTR_ZNA", "ZNA Centroid (cm)", "ADC", "CXZNA", "ADC", "CYZNA", 0); + addNewHisto("CENTR_ZNA", "h_CENTR_ZNA_cut_ZEM", "ZNA Centroid (cm)", "ADC", "CXZNA", "ADC", "CYZNA", 0); + + // Centroid ZNC + if (auto param = mCustomParameters.find("CENTR_ZNC"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - CENTR_ZNC: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + setBinHisto2D(atoi(tokenString.at(0).c_str()), atof(tokenString.at(1).c_str()), atof(tokenString.at(2).c_str()), atoi(tokenString.at(3).c_str()), atof(tokenString.at(4).c_str()), atof(tokenString.at(5).c_str())); + } else { + setBinHisto2D(200, -2, 2, 200, -2, 2); + } + addNewHisto("CENTR_ZNC", "h_CENTR_ZNC", "ZNC Centroid (cm)", "ADC", "CXZNC", "ADC", "CYZNC", 0); + addNewHisto("CENTR_ZNC", "h_CENTR_ZNC_cut_ZEM", "ZNC Centroid (cm)", "ADC", "CXZNC", "ADC", "CYZNC", 0); + + // Here we set the parameters for the configuration of the logic which selects the central events + if (auto param = mCustomParameters.find("CENTRAL_EVENT_CONFIG"); param != mCustomParameters.end()) { + ILOG(Debug, Devel) << "Custom parameter - CENTRAL_EVENT_CONFIG: " << param->second << ENDM; + tokenString = tokenLine(param->second, ";"); + SetConfigCentralEvent(atof(tokenString.at(0).c_str()), atoi(tokenString.at(1).c_str())); + } else { + SetConfigCentralEvent(0.0, 0); + } +} + +bool ZDCRecDataTask::add1DHisto(std::string typeH, std::string name, std::string title, std::string typeCh1, std::string ch1, int bin) +{ + + TString hname = TString::Format("%s", name.c_str()); + TString htit = TString::Format("%s", title.c_str()); + mNameHisto.push_back(name); + sHisto1D h1d; + h1d.histo = new TH1F(hname, htit, fNumBinX, fMinBinX, fMaxBinX); + h1d.typeh = typeH; + h1d.typech = typeCh1; + h1d.ch = ch1; + h1d.bin = bin; + int ih = (int)mHisto1D.size(); + mHisto1D.push_back(h1d); + h1d.typeh.clear(); + h1d.typech.clear(); + h1d.ch.clear(); + // delete h1d.histo; + if (ih < (int)mHisto1D.size()) { + getObjectsManager()->startPublishing(mHisto1D.at(ih).histo); + try { + getObjectsManager()->addMetadata(mHisto1D.at(ih).histo->GetName(), mHisto1D.at(ih).histo->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << mHisto1D.at(ih).histo->GetName() << ENDM; + return false; + } + } else { + return false; + } +} + +bool ZDCRecDataTask::add2DHisto(std::string typeH, std::string name, std::string title, std::string typeCh1, std::string ch1, std::string typeCh2, std::string ch2) +{ + TString hname = TString::Format("%s", name.c_str()); + TString htit = TString::Format("%s", title.c_str()); + mNameHisto.push_back(name); + sHisto2D h2d; + h2d.histo = new TH2F(hname, htit, fNumBinX, fMinBinX, fMaxBinX, fNumBinY, fMinBinY, fMaxBinY); + h2d.typeh = typeH; + h2d.typech1 = typeCh1; + h2d.ch1 = ch1; + h2d.typech2 = typeCh2; + h2d.ch2 = ch2; + int ih = (int)mHisto2D.size(); + mHisto2D.push_back(h2d); + h2d.typeh.clear(); + h2d.typech1.clear(); + h2d.typech2.clear(); + h2d.ch1.clear(); + h2d.ch2.clear(); + h2d.typeh.clear(); + // delete h1d.histo; + if (ih < (int)mHisto2D.size()) { + getObjectsManager()->startPublishing(mHisto2D.at(ih).histo); + try { + getObjectsManager()->addMetadata(mHisto2D.at(ih).histo->GetName(), mHisto2D.at(ih).histo->GetName(), "34"); + return true; + } catch (...) { + ILOG(Warning, Support) << "Metadata could not be added to " << mHisto2D.at(ih).histo->GetName() << ENDM; + return false; + } + } else + return false; +} + +bool ZDCRecDataTask::addNewHisto(std::string typeH, std::string name, std::string title, std::string typeCh1, std::string ch1, std::string typeCh2, std::string ch2, int bin) +{ + // Check if Histogram Exist + if (std::find(mNameHisto.begin(), mNameHisto.end(), name) == mNameHisto.end()) { + + // ADC 1D (ENERGY) OR TDC 1D + if (typeH == "ADC1D" || typeH == "TDC1D" || typeH == "CENTR_ZPA" || typeH == "CENTR_ZPC") { + if (add1DHisto(typeH, name, title, typeCh1, ch1, bin)) { + return true; + } else { + return false; + } + } else if (typeH == "ADCSUMvsTC" || typeH == "ADCvsTDC" || typeH == "TDC-DIFF" || typeH == "TDC_T_A" || typeH == "TDC_A_A" || typeH == "MSG_REC" || typeH == "CENTR_ZNA" || typeH == "CENTR_ZNC") { + if (add2DHisto(typeH, name, title, typeCh1, ch1, typeCh2, ch2)) { + return true; + } else { + return false; + } + } else { + return false; + } + } else { + reset(); + return true; + } + return false; +} + +int ZDCRecDataTask::process(const gsl::span& RecBC, + const gsl::span& Energy, + const gsl::span& TDCData, + const gsl::span& Info) +{ + LOG(info) << "o2::zdc::InterCalibEPN processing " << RecBC.size(); + float x, y; + mEv.init(RecBC, Energy, TDCData, Info); + while (mEv.next()) { + // Histo 1D + for (int i = 0; i < (int)mHisto1D.size(); i++) { + // Fill ADC 1D + if (mHisto1D.at(i).typeh == "ADC1D" && (mHisto1D.at(i).typech == "ADC" || mHisto1D.at(i).typech == "ADCAC")) { + + if (mHisto1D.at(i).typech == "ADC") { + mHisto1D.at(i).histo->Fill(getADCRecValue(mHisto1D.at(i).typech, mHisto1D.at(i).ch)); + } + + if (mHisto1D.at(i).typech == "ADCAC") { + if (mHisto1D.at(i).ch == "ZPAC" || mHisto1D.at(i).ch == "ZPAS") { + if (mEv.NtdcA(o2::zdc::TDCZNAC) == 0 && mEv.NtdcA(o2::zdc::TDCZNAS) == 0) { + if (getADCRecValue("ADC", mHisto1D.at(i).ch) > -8000) { + mHisto1D.at(i).histo->Fill(getADCRecValue("ADC", mHisto1D.at(i).ch)); + } + } + } + if (mHisto1D.at(i).ch == "ZPCC" || mHisto1D.at(i).ch == "ZPCS") { + if (mEv.NtdcA(o2::zdc::TDCZNCC) == 0 && mEv.NtdcA(o2::zdc::TDCZNCS) == 0) { + if (getADCRecValue("ADC", mHisto1D.at(i).ch) > -8000) { + mHisto1D.at(i).histo->Fill(getADCRecValue("ADC", mHisto1D.at(i).ch)); + } + } + } + if (mHisto1D.at(i).ch == "ZNAC" || mHisto1D.at(i).ch == "ZNAS") { + if (mEv.NtdcA(o2::zdc::TDCZPAC) == 0 && mEv.NtdcA(o2::zdc::TDCZPAS) == 0) { + if (getADCRecValue("ADC", mHisto1D.at(i).ch) > -8000) { + mHisto1D.at(i).histo->Fill(getADCRecValue("ADC", mHisto1D.at(i).ch)); + } + } + } + if (mHisto1D.at(i).ch == "ZNCC" || mHisto1D.at(i).ch == "ZNCS") { + if (mEv.NtdcA(o2::zdc::TDCZPCC) == 0 && mEv.NtdcA(o2::zdc::TDCZPCS) == 0) { + if (getADCRecValue("ADC", mHisto1D.at(i).ch) > -8000) { + mHisto1D.at(i).histo->Fill(getADCRecValue("ADC", mHisto1D.at(i).ch)); + } + } + } + } + } + + // Fill TDC 1D + if (mHisto1D.at(i).typeh == "TDC1D" && (mHisto1D.at(i).typech == "TDCV" || mHisto1D.at(i).typech == "TDCA")) { + int tdcid = getIdTDCch(mHisto1D.at(i).typech, mHisto1D.at(i).ch); + auto nhitv = mEv.NtdcV(tdcid); + if (mEv.NtdcA(tdcid) == nhitv && nhitv > 0) { + for (int ihit = 0; ihit < nhitv; ihit++) { + if (mHisto1D.at(i).typech == "TDCV") { + mHisto1D.at(i).histo->Fill(mEv.tdcV(tdcid, ihit)); + } + if (mHisto1D.at(i).typech == "TDCA") { + mHisto1D.at(i).histo->Fill(mEv.tdcA(tdcid, ihit)); + } + } + } + } + + // Fill TDCA with cut 1D + if (mHisto1D.at(i).typeh == "TDC1D" && (mHisto1D.at(i).typech == "TDCAC")) { + int tdcid = getIdTDCch("TDCA", mHisto1D.at(i).ch); + auto nhitv = mEv.NtdcV(tdcid); + if (tdcid == o2::zdc::TDCZPAC || tdcid == o2::zdc::TDCZPAS) { + if (mEv.NtdcA(o2::zdc::TDCZNAC) == 0 && mEv.NtdcA(o2::zdc::TDCZNAS) == 0) { + if (mEv.NtdcA(tdcid) == nhitv && nhitv > 0) { + for (int ihit = 0; ihit < nhitv; ihit++) { + if (mHisto1D.at(i).typech == "TDCAC") { + if ((mEv.tdcV(tdcid, ihit) > -2.5 && mEv.tdcV(tdcid, ihit) < 2.5)) { + mHisto1D.at(i).histo->Fill(mEv.tdcA(tdcid, ihit)); + } + } + } + } + } + } + if (tdcid == o2::zdc::TDCZPCC || tdcid == o2::zdc::TDCZPCS) { + if (mEv.NtdcA(o2::zdc::TDCZNCC) == 0 && mEv.NtdcA(o2::zdc::TDCZNCS) == 0) { + if (mEv.NtdcA(tdcid) == nhitv && nhitv > 0) { + for (int ihit = 0; ihit < nhitv; ihit++) { + if (mHisto1D.at(i).typech == "TDCAC") { + if ((mEv.tdcV(tdcid, ihit) > -2.5 && mEv.tdcV(tdcid, ihit) < 2.5)) { + mHisto1D.at(i).histo->Fill(mEv.tdcA(tdcid, ihit)); + } + } + } + } + } + } + if (tdcid == o2::zdc::TDCZNAC || tdcid == o2::zdc::TDCZNAS) { + if (mEv.NtdcA(o2::zdc::TDCZPAC) == 0 && mEv.NtdcA(o2::zdc::TDCZPAS) == 0) { + if (mEv.NtdcA(tdcid) == nhitv && nhitv > 0) { + for (int ihit = 0; ihit < nhitv; ihit++) { + if (mHisto1D.at(i).typech == "TDCAC") { + if ((mEv.tdcV(tdcid, ihit) > -2.5 && mEv.tdcV(tdcid, ihit) < 2.5)) { + mHisto1D.at(i).histo->Fill(mEv.tdcA(tdcid, ihit)); + } + } + } + } + } + } + if (tdcid == o2::zdc::TDCZNCC || tdcid == o2::zdc::TDCZNCS) { + if (mEv.NtdcA(o2::zdc::TDCZPCC) == 0 && mEv.NtdcA(o2::zdc::TDCZPCS) == 0) { + if (mEv.NtdcA(tdcid) == nhitv && nhitv > 0) { + for (int ihit = 0; ihit < nhitv; ihit++) { + if (mHisto1D.at(i).typech == "TDCAC") { + if ((mEv.tdcV(tdcid, ihit) > -2.5 && mEv.tdcV(tdcid, ihit) < 2.5)) { + mHisto1D.at(i).histo->Fill(mEv.tdcA(tdcid, ihit)); + } + } + } + } + } + } + } + + // Fill CENTROID ZP + if (mHisto1D.at(i).typeh == "CENTR_ZPA" && mHisto1D.at(i).typech == "ADC") { + mHisto1D.at(i).histo->Fill(mEv.xZPA()); + } + if (mHisto1D.at(i).typeh == "CENTR_ZPC" && mHisto1D.at(i).typech == "ADC") { + mHisto1D.at(i).histo->Fill(mEv.xZPC()); + } + } // for histo 1D + + // Histo 2D + for (int i = 0; i < (int)mHisto2D.size(); i++) { + if (mHisto2D.at(i).typeh == "ADCSUMvsTC" && mHisto2D.at(i).typech1 == "ADC" && mHisto2D.at(i).typech2 == "ADC") { + mHisto2D.at(i).histo->Fill((Double_t)getADCRecValue(mHisto2D.at(i).typech1, mHisto2D.at(i).ch1), getADCRecValue(mHisto2D.at(i).typech2, mHisto2D.at(i).ch2)); + } + if (mHisto2D.at(i).typeh == "ADCvsTDC" && mHisto2D.at(i).typech1 == "TDCV" && mHisto2D.at(i).typech2 == "ADC") { + int tdcid = getIdTDCch(mHisto2D.at(i).typech1, mHisto2D.at(i).ch1); + auto nhit = mEv.NtdcV(tdcid); + if (mEv.NtdcA(tdcid) == nhit && nhit > 0) { + mHisto2D.at(i).histo->Fill(mEv.tdcV(tdcid, 0), getADCRecValue(mHisto2D.at(i).typech2, mHisto2D.at(i).ch2)); + } + } + if (mHisto2D.at(i).typeh == "TDC-DIFF" && mHisto2D.at(i).typech1 == "TDCV" && mHisto2D.at(i).typech2 == "TDCV") { + int zncc_id = getIdTDCch("TDCV", "ZNCC"); + int znac_id = getIdTDCch("TDCV", "ZNAC"); + auto nhit_zncc = mEv.NtdcV(zncc_id); + auto nhit_znac = mEv.NtdcV(znac_id); + if (mHisto2D.at(i).histo->GetName() == TString::Format("h_TDC_ZNC_DIFF_ZNA_ZNC_SUM_ZNA_V")) { + if ((mEv.NtdcA(zncc_id) == nhit_zncc && nhit_zncc > 0) && (mEv.NtdcA(znac_id) == nhit_znac && nhit_znac > 0)) { + auto sum = mEv.tdcV(zncc_id, 0) + mEv.tdcV(znac_id, 0); + auto diff = mEv.tdcV(zncc_id, 0) - mEv.tdcV(znac_id, 0); + mHisto2D.at(i).histo->Fill(diff, sum); + } + } + if (mHisto2D.at(i).histo->GetName() == TString::Format("h_TDC_ZNC_DIFF_ZNA_ZNC_SUM_ZNA_V_cut")) { + // if (( (float)o2::zdc::TDCZEM2 > -2.5 && (float)o2::zdc::TDCZEM2 < 2.5 ) && ( (float)o2::zdc::TDCZEM1 > -2.5 && (float)o2::zdc::TDCZEM1 < 2.5 ) ){ + if ((mEv.NtdcA(zncc_id) == nhit_zncc && nhit_zncc > 0) && (mEv.NtdcA(znac_id) == nhit_znac && nhit_znac > 0) && ((float)mEv.tdcV(5, 0) > -12.5 && (float)mEv.tdcV(5, 0) < 12.5) && ((float)mEv.tdcV(4, 0) > -12.5 && (float)mEv.tdcV(4, 0) < 12.5)) { + auto sum = mEv.tdcV(zncc_id, 0) + mEv.tdcV(znac_id, 0); + auto diff = mEv.tdcV(zncc_id, 0) - mEv.tdcV(znac_id, 0); + mHisto2D.at(i).histo->Fill(diff, sum); + } + } + } + if (mHisto2D.at(i).typeh == "TDC_T_A" && mHisto2D.at(i).typech1 == "TDCV" && mHisto2D.at(i).typech2 == "TDCA") { + int tdcid = getIdTDCch(mHisto2D.at(i).typech1, mHisto2D.at(i).ch1); + auto nhitv = mEv.NtdcV(tdcid); + if (mEv.NtdcA(tdcid) == nhitv && nhitv > 0) { + for (int ihit = 0; ihit < nhitv; ihit++) { + mHisto2D.at(i).histo->Fill(mEv.tdcV(tdcid, ihit), mEv.tdcA(tdcid, ihit)); + } + } + } + if (mHisto2D.at(i).typeh == "TDC_A_A" && mHisto2D.at(i).typech1 == "TDCA" && mHisto2D.at(i).typech2 == "TDCA") { + int tdcid1 = getIdTDCch(mHisto2D.at(i).typech1, mHisto2D.at(i).ch1); + auto nhitv1 = mEv.NtdcV(tdcid1); + int tdcid2 = getIdTDCch(mHisto2D.at(i).typech2, mHisto2D.at(i).ch2); + auto nhitv2 = mEv.NtdcV(tdcid2); + if ((mEv.NtdcA(tdcid1) == nhitv1 && nhitv1 > 0) && (mEv.NtdcA(tdcid2) == nhitv1 && nhitv2 > 0)) { + mHisto2D.at(i).histo->Fill(mEv.tdcA(tdcid1, 0), mEv.tdcA(tdcid2, 0)); + } + } + + if (mEv.getNInfo() > 0 && mHisto2D.at(i).typech1 == "INFO") { + auto& decodedInfo = mEv.getDecodedInfo(); + for (uint16_t info : decodedInfo) { + uint8_t ch = (info >> 10) & 0x1f; + uint16_t code = info & 0x03ff; + mHisto2D.at(i).histo->Fill(ch, code); + } + } + if (mHisto2D.at(i).typeh == "CENTR_ZNA" && mHisto2D.at(i).typech1 == "ADC" && mHisto2D.at(i).typech2 == "ADC") { + if (mHisto2D.at(i).histo->GetName() == TString::Format("h_CENTR_ZNA")) { + mEv.centroidZNA(x, y); + mHisto2D.at(i).histo->Fill(x, y); + } else { + if (IsEventCentral()) { + mEv.centroidZNA(x, y); + mHisto2D.at(i).histo->Fill(x, y); + } + } + } + if (mHisto2D.at(i).typeh == "CENTR_ZNC" && mHisto2D.at(i).typech1 == "ADC" && mHisto2D.at(i).typech2 == "ADC") { + if (mHisto2D.at(i).histo->GetName() == TString::Format("h_CENTR_ZNC")) { + mEv.centroidZNC(x, y); + mHisto2D.at(i).histo->Fill(x, y); + } else { + if (IsEventCentral()) { + mEv.centroidZNC(x, y); + mHisto2D.at(i).histo->Fill(x, y); + } + } + } + } + } + return 0; +} + +bool ZDCRecDataTask::IsEventCentral() +{ + if (fcentraleventconfigvalue == 1) { + // Both ZEMs between a configurable value + if (((float)mEv.tdcV(5, 0) > -ftdcLimit && (float)mEv.tdcV(5, 0) < ftdcLimit) && ((float)mEv.tdcV(4, 0) > -ftdcLimit && (float)mEv.tdcV(4, 0) < ftdcLimit)) { + return true; + } else { + return false; + } + } else { + return false; + } +} + +float ZDCRecDataTask::getADCRecValue(std::string typech, std::string ch) +{ + if (typech == "ADC" && ch == "ZNAC") { + return mEv.EZNAC(); + } + if (typech == "ADC" && ch == "ZNA1") { + return mEv.EZNA1(); + } + if (typech == "ADC" && ch == "ZNA2") { + return mEv.EZNA2(); + } + if (typech == "ADC" && ch == "ZNA3") { + return mEv.EZNA3(); + } + if (typech == "ADC" && ch == "ZNA4") { + return mEv.EZNA4(); + } + if (typech == "ADC" && ch == "ZNAS") { + return mEv.EZNASum(); + } + if (typech == "ADC" && ch == "ZPAC") { + return mEv.EZPAC(); + } + if (typech == "ADC" && ch == "ZPA1") { + return mEv.EZPA1(); + } + if (typech == "ADC" && ch == "ZPA2") { + return mEv.EZPA2(); + } + if (typech == "ADC" && ch == "ZPA3") { + return mEv.EZPA3(); + } + if (typech == "ADC" && ch == "ZPA4") { + return mEv.EZPA4(); + } + if (typech == "ADC" && ch == "ZPAS") { + return mEv.EZPASum(); + } + if (typech == "ADC" && ch == "ZNCC") { + return mEv.EZNCC(); + } + if (typech == "ADC" && ch == "ZNC1") { + return mEv.EZNC1(); + } + if (typech == "ADC" && ch == "ZNC2") { + return mEv.EZNC2(); + } + if (typech == "ADC" && ch == "ZNC3") { + return mEv.EZNC3(); + } + if (typech == "ADC" && ch == "ZNC4") { + return mEv.EZNC4(); + } + if (typech == "ADC" && ch == "ZNCS") { + return mEv.EZNCSum(); + } + if (typech == "ADC" && ch == "ZPCC") { + return mEv.EZPCC(); + } + if (typech == "ADC" && ch == "ZPC1") { + return mEv.EZPC1(); + } + if (typech == "ADC" && ch == "ZPC2") { + return mEv.EZPC2(); + } + if (typech == "ADC" && ch == "ZPC3") { + return mEv.EZPC3(); + } + if (typech == "ADC" && ch == "ZPC4") { + return mEv.EZPC4(); + } + if (typech == "ADC" && ch == "ZPCS") { + return mEv.EZPCSum(); + } + if (typech == "ADC" && ch == "ZEM1") { + return mEv.EZEM1(); + } + if (typech == "ADC" && ch == "ZEM2") { + return mEv.EZEM2(); + } + return -9000.0; +} + +int ZDCRecDataTask::getIdTDCch(std::string typech, std::string ch) +{ + if ((typech == "TDCV" || typech == "TDCA") && ch == "ZNAC") { + return o2::zdc::TDCZNAC; + } + if ((typech == "TDCV" || typech == "TDCA") && ch == "ZNAS") { + return o2::zdc::TDCZNAS; + } + if ((typech == "TDCV" || typech == "TDCA") && ch == "ZPAC") { + return o2::zdc::TDCZPAC; + } + if ((typech == "TDCV" || typech == "TDCA") && ch == "ZPAS") { + return o2::zdc::TDCZPAS; + } + if ((typech == "TDCV" || typech == "TDCA") && ch == "ZNCC") { + return o2::zdc::TDCZNCC; + } + if ((typech == "TDCV" || typech == "TDCA") && ch == "ZNCS") { + return o2::zdc::TDCZNCS; + } + if ((typech == "TDCV" || typech == "TDCA") && ch == "ZPCC") { + return o2::zdc::TDCZPCC; + } + if ((typech == "TDCV" || typech == "TDCA") && ch == "ZPCS") { + return o2::zdc::TDCZPCS; + } + if ((typech == "TDCV" || typech == "TDCA") && ch == "ZEM1") { + return o2::zdc::TDCZEM1; + } + if ((typech == "TDCV" || typech == "TDCA") && ch == "ZEM2") { + return o2::zdc::TDCZEM2; + } + return 0; +} + +std::vector ZDCRecDataTask::tokenLine(std::string Line, std::string Delimiter) +{ + std::string token; + size_t pos = 0; + int i = 0; + std::vector stringToken; + while ((pos = Line.find(Delimiter)) != std::string::npos) { + token = Line.substr(i, pos); + stringToken.push_back(token); + Line.erase(0, pos + Delimiter.length()); + } + stringToken.push_back(Line); + return stringToken; +} + +} // namespace o2::quality_control_modules::zdc diff --git a/Modules/ZDC/test/testQcZDC.cxx b/Modules/ZDC/test/testQcZDC.cxx new file mode 100644 index 0000000000..f6c446e759 --- /dev/null +++ b/Modules/ZDC/test/testQcZDC.cxx @@ -0,0 +1,30 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// +/// \file testZDC.cxx +/// \author My Name +/// + +#include "QualityControl/TaskFactory.h" + +#define BOOST_TEST_MODULE Publisher test +#define BOOST_TEST_MAIN +#define BOOST_TEST_DYN_LINK + +#include + +namespace o2::quality_control_modules::zdc +{ + +BOOST_AUTO_TEST_CASE(instantiate_task) { BOOST_CHECK(true); } + +} // namespace o2::quality_control_modules::zdc diff --git a/Modules/modulesHelper.sh b/Modules/modulesHelper.sh deleted file mode 100755 index e7f0e926ac..0000000000 --- a/Modules/modulesHelper.sh +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env bash -set -e ;# exit on error -set -u ;# exit when using undeclared variable -#set -x ;# debugging - -DONOR=Skeleton -DONOR_LC=skeleton -DONOR_TASK=SkeletonTask -DONOR_TASK_INCLUDE_GUARD=QC_MODULE_`echo ${DONOR} | tr a-z A-Z`_`echo ${DONOR_TASK} | tr a-z A-Z`_H -DONOR_CHECK=SkeletonCheck -DONOR_CHECK_INCLUDE_GUARD=QC_MODULE_`echo ${DONOR} | tr a-z A-Z`_`echo ${DONOR_CHECK} | tr a-z A-Z`_H - -OS=`uname` - -# Checks if current pwd matches the location of this script, exits if it is not -function check_pwd { - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" - if [[ $DIR != $PWD ]] ; then - echo 'Please execute this script while being in its directory' - exit 1 - fi -} - -# Create new empty module -# \param 1 : module_name -function create_module { - if [ -d $1 ] ; then - echo 'Module '$1' already exists.' - else - echo 'Module '$1' does not exist, generating...' - if [ ! -d ${DONOR} ] ; then - echo '> Donor template '${DONOR}' does not exist, exiting...' - exit 1 - fi - - MODULE_LC=`echo $1 | tr A-Z a-z` - # prepare folder structure - mkdir $1 $1/src $1/include/ $1/include/$1 $1/test - - # prepare CMakeLists.txt - sed 's/'${DONOR}'/'$1'/' ${DONOR}/.CMakeListsEmpty.txt > $1/CMakeLists.txt - # prepare LinkDef.h - sed '/#pragma link C++ class o2::quality_control_modules::'${DONOR_LC}'::/ d' ${DONOR}/include/${DONOR}/LinkDef.h > $1/include/$1/LinkDef.h - # prepare test - sed 's/.testEmpty/test'$1'/; s/'${DONOR_LC}'/'${MODULE_LC}'/' ${DONOR}/test/.testEmpty.cxx > $1'/test/test'$1'.cxx' - - if [[ $OS == Linux ]] ; then - sed -i '/set(TEST_SRCS/ a \ \ test/test'$1'.cxx' $1/CMakeLists.txt - else #Darwin/BSD - sed -i '' -e '/set(TEST_SRCS/ a\ -\ \ test/test'$1'.cxx -' $1/CMakeLists.txt - fi - - # add new module to the project - echo 'add_subdirectory('$1')' >> CMakeLists.txt - - echo '> Module created.' - fi -} - -# Create new task in specified module -# \param 1 : module_name -# \param 2 : task_name -function create_task { - echo 'Creating task '$2' in module '$1'.' - if [ -f $1'/include/'$1'/'$2'.h' ] ; then - echo '> Task '$2' already exists, returning...' - return - fi - - MODULE_LC=`echo $1 | tr A-Z a-z` - INCLUDE_GUARD_NAME=QC_MODULE_`echo $1 | tr a-z A-Z`_`echo $2 | tr a-z A-Z`_H - - # add header - sed 's/'${DONOR_TASK}'/'$2'/g; s/'${DONOR_LC}'/'${MODULE_LC}'/g; s/'${DONOR}'/'${MODULE}'/g; s/'${DONOR_TASK_INCLUDE_GUARD}'/'${INCLUDE_GUARD_NAME}'/g' ${DONOR}'/include/'${DONOR}'/'${DONOR_TASK}'.h' > $1'/include/'$1'/'$2'.h' - if [[ $OS == Linux ]] ; then - sed -i '/#endif/ i #pragma link C++ class o2::quality_control_modules::'${MODULE_LC}'::'$2'+;' $1/include/$1/LinkDef.h - sed -i '/set(HEADERS/ a \ \ include/'$1'/'$2'.h' $1/CMakeLists.txt - else #Darwin/BSD - sed -i '' -e '/#endif/ i\ -\#pragma link C++ class o2::quality_control_modules::'${MODULE_LC}'::'$2'+; -' $1/include/$1/LinkDef.h - sed -i '' -e '/set(HEADERS/ a\ -\ \ include/'$1'/'$2'.h -' $1/CMakeLists.txt - fi - - # add src - sed 's/'${DONOR_TASK}'/'$2'/g; s/'${DONOR_LC}'/'${MODULE_LC}'/g; s/'${DONOR}'/'${MODULE}'/g' ${DONOR}'/src/'${DONOR_TASK}'.cxx' > $1'/src/'$2'.cxx' - if [[ $OS == Linux ]] ; then - sed -i '/set(SRCS/ a \ \ src/'$2'.cxx' $1/CMakeLists.txt - else #Darwin/BSD - sed -i '' -e '/set(SRCS/ a\ -\ \ src/'$2'.cxx -' $1/CMakeLists.txt - fi - echo '> Task created.' -} - -# Create new check in specified module -# \param 1 : module_name -# \param 2 : check_name -function create_check { - echo 'Creating check '$2' in module '$1'.' - - if [ -f $1'/include/'$1'/'$2'.h' ] ; then - echo '> Check '$2' already exists, returning...' - return - fi - - MODULE_LC=`echo $1 | tr A-Z a-z` - INCLUDE_GUARD_NAME=QC_MODULE_`echo $1 | tr a-z A-Z`_`echo $2 | tr a-z A-Z`_H - - # add header - sed 's/'${DONOR_CHECK}'/'$2'/g; s/'${DONOR_LC}'/'${MODULE_LC}'/g; s/'${DONOR}'/'${MODULE}'/g; s/'${DONOR_CHECK_INCLUDE_GUARD}'/'${INCLUDE_GUARD_NAME}'/g' ${DONOR}'/include/'${DONOR}'/'${DONOR_CHECK}'.h' > $1'/include/'$1'/'$2'.h' - if [[ $OS == Linux ]] ; then - sed -i '/#endif/ i #pragma link C++ class o2::quality_control_modules::'${MODULE_LC}'::'$2'+;' $1/include/$1/LinkDef.h - sed -i '/set(HEADERS/ a \ \ include/'$1'/'$2'.h' $1/CMakeLists.txt - else #Darwin/BSD - sed -i '' -e '/#endif/ i\ -#pragma link C++ class o2::quality_control_modules::'${MODULE_LC}'::'$2'+; -' $1/include/$1/LinkDef.h - sed -i '' -e '/set(HEADERS/ a\ -\ \ include/'$1'/'$2'.h -' $1/CMakeLists.txt - fi - - # add src - sed 's/'${DONOR_CHECK}'/'$2'/g; s/'${DONOR_LC}'/'${MODULE_LC}'/g; s/'${DONOR}'/'${MODULE}'/g' ${DONOR}'/src/'${DONOR_CHECK}'.cxx' > $1'/src/'$2'.cxx' - if [[ $OS == Linux ]] ; then - sed -i '/set(SRCS/ a \ \ src/'$2'.cxx' $1/CMakeLists.txt - else #Darwin/BSD - sed -i '' -e '/set(SRCS/ a\ -\ \ src/'$2'.cxx -' $1/CMakeLists.txt - fi - - echo '> Check created.' -} - -function print_usage { - echo "Usage: ./modulesHelper.sh -m MODULE_NAME [OPTION] - -Generate template QC module and/or tasks, checks. -If a module with specified name already exists, new tasks and checks are inserted to the existing one. -Please follow UpperCamelCase convention for modules', tasks' and checks' names. - -Example: -# create new module and some task -./modulesHelper.sh -m MyModule -t SuperTask -# add one task and two checks -./modulesHelper.sh -m MyModule -t EvenBetterTask -c HistoUniformityCheck -c MeanTest - -Options: - -h print this message - -m MODULE_NAME create module named MODULE_NAME or add there some task/checker - -t TASK_NAME create task named TASK_NAME - -c CHECK_NAME create check named CHECK_NAME -" -} - -MODULE= -while getopts 'hm:t:c:' option; do - case "${option}" in - \?) print_usage - exit 1;; - h) print_usage - exit 0;; - m) check_pwd - create_module ${OPTARG} - MODULE=${OPTARG};; - t) if [ -z ${MODULE} ] ; then - echo 'Cannot add a task, module name not specified, exiting...' - exit 1 - fi - create_task ${MODULE} ${OPTARG};; - c) if [ -z ${MODULE} ] ; then - echo 'Cannot add a check, module name not specified, exiting...' - exit 1 - fi - create_check ${MODULE} ${OPTARG};; - esac -done - -# If no options are specified -if [ ${OPTIND} -eq 1 ]; then - print_usage -fi diff --git a/Modules/o2-qc-module-configurator.sh b/Modules/o2-qc-module-configurator.sh new file mode 100755 index 0000000000..1abd3006b0 --- /dev/null +++ b/Modules/o2-qc-module-configurator.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +set -e # exit on error +set -u # exit when using undeclared variable +#set -x ;# debugging + +DONOR=Skeleton +DONOR_LC=skeleton + +OS=`uname` + +function inplace_sed() { + sed -ibck "$1" $2 && rm $2bck +} + +# Checks if current pwd matches the location of this script, exits if it is not +function check_pwd() { + DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + if [[ $DIR != $PWD ]]; then + echo 'Please execute this script while being in its directory' + exit 1 + fi +} + +# Create new empty module +# \param 1 : module_name +function create_module() { + if [ -d $1 ]; then + echo 'Module '$1' already exists.' + else + echo 'Module '$1' does not exist, generating...' + if [ ! -d ${DONOR} ]; then + echo '> Donor template '${DONOR}' does not exist, exiting...' + exit 1 + fi + + MODULE_LC=$(echo $1 | tr A-Z a-z) + # prepare folder structure + mkdir $1 $1/src $1/include/ $1/include/$1 $1/test + + # prepare CMakeLists.txt + sed 's/'${DONOR}'/'$1'/' ${DONOR}/.CMakeListsEmpty.txt >$1/CMakeLists.txt + # prepare LinkDef.h + sed '/#pragma link C++ class o2::quality_control_modules::'${DONOR_LC}'::/ d' ${DONOR}/include/${DONOR}/LinkDef.h >$1/include/$1/LinkDef.h + # prepare test + sed 's/.testEmpty/test'$1'/; s/'${DONOR_LC}'/'${MODULE_LC}'/' ${DONOR}/test/.testEmpty.cxx >$1'/test/testQc'$1'.cxx' + + inplace_sed '/testQcSkeleton/s/Skeleton/'$1'/g' $1/CMakeLists.txt + + # add new module to the project + if ! grep -c $1 CMakeLists.txt; then + echo 'add_subdirectory('$1')' >>CMakeLists.txt + fi + + echo '> Module created.' + fi +} + +function cmake_format() { + if command cmake-format >/dev/null 2>&1; then + cmake-format -i $1 -c ../.cmake-format.py + fi +} + +# Return the name of the include guard +function include_guard() { + local modulename=$1 + local classname=$2 + echo QC_MODULE_$(echo $modulename | tr a-z A-Z)_$(echo $modulename$classname | tr a-z A-Z)_H +} + +# Return the name of the include file +function include_file() { + local modulename=$1 + local classname=$2 + echo $modulename'/include/'$modulename'/'$classname'.h' +} + +# Create new Task or Check in specified module +function create_class() { + + local modulename=$1 + local classname=$2 + local typename=$3 + + if [ "$typename" != "Task" ] && [ "$typename" != "Check" ] && [ "$typename" != "PostProcessing" ] && [ "$typename" != "Aggregator" ]; then + echo "3rd parameter can only be Task, Check, Aggregator or PostProcessing" + return + fi + + INCLUDE_FILENAME=$(include_file $modulename $classname) + + echo 'Creating '$typename' '$classname' in module '$modulename'.' + if [ -f $INCLUDE_FILENAME ]; then + echo '> '$typename' '$classname' already exists, returning...' + return + fi + + MODULE_LC=$(echo $modulename | tr A-Z a-z) + DONOR_INCLUDE_GUARD=$(include_guard $DONOR $typename) + INCLUDE_GUARD=$(include_guard $modulename $classname) + DONOR_INCLUDE_FILENAME=$(include_file $DONOR ${DONOR}${typename}) + + # add header + sed ' + s/'${DONOR}${typename}'/'${classname}'/g; + s/'${DONOR_LC}'/'${MODULE_LC}'/g; + s/'${DONOR}'/'${MODULE}'/g; + s/'${DONOR_INCLUDE_GUARD}'/'${INCLUDE_GUARD}'/g; + ' $DONOR_INCLUDE_FILENAME >$INCLUDE_FILENAME + + # add LinkDef.h + if [[ $OS == Linux ]] ; then + sed -i '/#endif/ i #pragma link C++ class o2::quality_control_modules::'${MODULE_LC}'::'$classname'+;' \ + $modulename/include/$modulename/LinkDef.h + sed -i '/HEADERS/ a \ \ include/'$modulename'/'$classname'.h' $modulename/CMakeLists.txt + else #Darwin/BSD + inplace_sed '/#endif/ i\ + #pragma link C++ class o2::quality_control_modules::'${MODULE_LC}'::'$classname'+;\ + \ + ' $modulename/include/$modulename/LinkDef.h + inplace_sed '/LINKDEF include/ i\ + include/'$modulename'/'$classname'.h\ + ' $modulename/CMakeLists.txt + fi + + # add src + sed ' + s/'${DONOR}${typename}'/'${classname}'/g; + s/'${DONOR_LC}'/'${MODULE_LC}'/g; + s/'${DONOR}'/'${MODULE}'/g; + ' ${DONOR}'/src/'${DONOR}${typename}'.cxx' >$modulename'/src/'$classname'.cxx' + + # the sources are on the same line as the PRIVATE + inplace_sed '/target_sources(O2Qc'$modulename' PRIVATE/ s_PRIVATE_PRIVATE src/'$classname'.cxx _' $modulename/CMakeLists.txt + + # the PRIVATE is on its own line (because there are more sources than + # what fits on a single line) + inplace_sed '/target_sources(O2Qc'$modulename'$/ { + N + /PRIVATE/ { + s_PRIVATE_PRIVATE src/'$classname'.cxx _ + } + }' $modulename/CMakeLists.txt + + echo '> '$typename' created.' + + cmake_format $modulename/CMakeLists.txt +} + +function print_usage() { + echo "Usage: ./o2-qc-module-configurator.sh -m MODULE_NAME [OPTION] + +Generate template QC module and/or tasks, checks, aggregators and postprocessing. +If a module with specified name already exists, new tasks, checks, aggregators and postprocessing are inserted to the existing module. +Please follow UpperCamelCase convention for modules', tasks' and checks' names. + +Example: +# create new module and some task +./o2-qc-module-configurator.sh -m MyModule -t SuperTask +# add one task and two checks +./o2-qc-module-configurator.sh -m MyModule -t EvenBetterTask -c HistoUniformityCheck -c MeanTest + +Options: + -h print this message + -m MODULE_NAME create a module named MODULE_NAME or add there some task/checker + -t TASK_NAME create a task named TASK_NAME + -c CHECK_NAME create a check named CHECK_NAME + -p PP_NAME create a postprocessing task named PP_NAME + -a AGG_NAME create an aggregator named AGG_NAME +" +} + +MODULE= +while getopts 'hm:t:c:p:a:' option; do + case "${option}" in + \?) + print_usage + exit 1 + ;; + h) + print_usage + exit 0 + ;; + m) + check_pwd + create_module ${OPTARG} + MODULE=${OPTARG} + ;; + t) + if [ -z ${MODULE} ]; then + echo 'Cannot add a task, module name not specified, exiting...' + exit 1 + fi + create_class ${MODULE} ${OPTARG} Task + ;; + c) + if [ -z ${MODULE} ]; then + echo 'Cannot add a check, module name not specified, exiting...' + exit 1 + fi + create_class ${MODULE} ${OPTARG} Check + ;; + p) + if [ -z ${MODULE} ]; then + echo 'Cannot add a postprocessing task, module name not specified, exiting...' + exit 1 + fi + create_class ${MODULE} ${OPTARG} PostProcessing + ;; + a) + if [ -z ${MODULE} ]; then + echo 'Cannot add an aggregator, module name not specified, exiting...' + exit 1 + fi + create_class ${MODULE} ${OPTARG} Aggregator + ;; + esac +done + +# If no options are specified +if [ ${OPTIND} -eq 1 ]; then + print_usage +fi diff --git a/README.md b/README.md index 55db06066b..7c8875615b 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,117 @@ -[![aliBuild](https://img.shields.io/badge/aliBuild-dashboard-lightgrey.svg)](https://alisw.cern.ch/dashboard/d/000000001/main-dashboard?orgId=1&var-storagename=All&var-reponame=All&var-checkname=build%2FQualityControl%2Fo2-dataflow%2F0&var-upthreshold=30m&var-minuptime=30) [![JIRA](https://img.shields.io/badge/JIRA-Report%20issue-blue.svg)](https://alice.its.cern.ch/jira/secure/CreateIssue.jspa?pid=11201&issuetype=1) - +[![doxygen](https://img.shields.io/badge/doxygen-documentation-blue.svg)](https://aliceo2group.github.io/QualityControl/) +[![Discourse](https://img.shields.io/badge/discourse-Get%20help-blue.svg)](https://alice-talk.web.cern.ch/) +[![FLP doc](https://img.shields.io/badge/FLP-documentation-blue.svg)](https://alice-flp.docs.cern.ch/) + + This is the repository of the data quality control (QC) software for the ALICE O2 system. + +For a general overview of our (O2) software, organization and processes, please see this [page](https://aliceo2group.github.io/). + +* [QuickStart](doc/QuickStart.md) + * [Read this first!](doc/QuickStart.md#read-this-first) + * [Requirements](doc/QuickStart.md#requirements) + * [Setup](doc/QuickStart.md#setup) + * [Environment loading](doc/QuickStart.md#environment-loading) + * [Execution](doc/QuickStart.md#execution) + * [Basic workflow](doc/QuickStart.md#basic-workflow) + * [Post-processing example](doc/QuickStart.md#post-processing-example) +* [Modules development](doc/ModulesDevelopment.md) + * [Context](doc/ModulesDevelopment.md#context) + * [QC architecture](doc/ModulesDevelopment.md#qc-architecture) + * [DPL](doc/ModulesDevelopment.md#dpl) + * [Data Sampling](doc/ModulesDevelopment.md#data-sampling) + * [Code Organization](doc/ModulesDevelopment.md#code-organization) + * [Developing with aliBuild/alienv](doc/ModulesDevelopment.md#developing-with-alibuildalienv) + * [User-defined modules](doc/ModulesDevelopment.md#user-defined-modules) + * [Repository](doc/ModulesDevelopment.md#repository) + * [Module creation](doc/ModulesDevelopment.md#module-creation) + * [Test run](doc/ModulesDevelopment.md#test-run) + * [Modification of the Task](doc/ModulesDevelopment.md#modification-of-the-task) + * [Check](doc/ModulesDevelopment.md#check) + * [Configuration](doc/ModulesDevelopment.md#configuration) + * [Implementation](doc/ModulesDevelopment.md#implementation) + * [Results](doc/ModulesDevelopment.md#results) + * [Quality Aggregation](doc/ModulesDevelopment.md#quality-aggregation) + * [Quick try](doc/ModulesDevelopment.md#quick-try) + * [Configuration](doc/ModulesDevelopment.md#configuration-1) + * [Implementation](doc/ModulesDevelopment.md#implementation-1) + * [Naming convention](doc/ModulesDevelopment.md#naming-convention) + * [Committing code](doc/ModulesDevelopment.md#committing-code) + * [Data sources](doc/ModulesDevelopment.md#data-sources) + * [Run number and other run attributes (period, pass type, provenance)](doc/ModulesDevelopment.md#run-number-and-other-run-attributes-period-pass-type-provenance) + * [A more advanced example](doc/ModulesDevelopment.md#a-more-advanced-example) +* [Framework](doc/Framework.md) + * [Plugging the QC to an existing DPL workflow](doc/Framework.md#plugging-the-qc-to-an-existing-dpl-workflow) + * [Production of QC objects outside this framework](doc/Framework.md#production-of-qc-objects-outside-this-framework) + * [Multi-node setups](doc/Framework.md#multi-node-setups) + * [Batch processing](doc/Framework.md#batch-processing) + * [Moving window](doc/Framework.md#moving-window) + * [Monitor cycles](doc/Framework.md#monitor-cycles) + * [Custom merging](doc/Framework.md#custom-merging) + * [Critical, resilient and non-critical tasks](doc/Framework.md#critical-resilient-and-non-critical-tasks) + * [QC with DPL Analysis](doc/Framework.md#qc-with-dpl-analysis) + * [Propagating Check results to RCT in Bookkeeping](doc/Framework.md#propagating-check-results-to-rct-in-bookkeeping) + * [Solving performance issues](doc/Framework.md#solving-performance-issues) + * [Understanding and reducing memory footprint](doc/Framework.md#understanding-and-reducing-memory-footprint) + * [Monitoring](doc/Framework.md#monitoring) +* [Post-processing](doc/PostProcessing.md) + * [The post-processing framework](doc/PostProcessing#the-post-processing-framework) + * [Post-processing interface](doc/PostProcessing#post-processing-interface) + * [Configuration](doc/PostProcessing#configuration) + * [Running it](doc/PostProcessing#running-it) + * [Convenience classes](doc/PostProcessing#convenience-classes) + * [The TrendingTask class](doc/PostProcessing#the-trendingtask-class) + * [The SliceTrendingTask class](doc/PostProcessing#the-slicetrendingtask-class) + * [The ReferenceComparatorTask class](doc/PostProcessing#the-referencecomparatortask-class) + * [The CcdbInspectorTask class](doc/PostProcessing#the-ccdbinspectortask-class) + * [The QualityTask class](doc/PostProcessing#the-qualitytask-class) + * [The BigScreen class](doc/PostProcessing#the-bigscreen-class) + * [More examples](#more-examples) +* [Configuration reference](doc/Configuration.md) + * [Global configuration structure](doc/Configuration.md#global-configuration-structure) + * [Common configuration](doc/Configuration.md#common-configuration) + * [QC Tasks configuration](doc/Configuration.md#qc-tasks-configuration) + * [QC Checks configuration](doc/Configuration.md#qc-checks-configuration) + * [QC Aggregators configuration](doc/Configuration.md#qc-aggregators-configuration) + * [QC Post-processing configuration](doc/Configuration.md#qc-post-processing-configuration) + * [External tasks configuration](doc/Configuration.md#external-tasks-configuration) + * [Merging multiple configuration files into one](doc/Configuration.md#merging-multiple-configuration-files-into-one) + * [Templating config files](doc/Configuration.md#templating-config-files) + * [Definition and access of simple user-defined task configuration ("taskParameters")](doc/Configuration.md#definition-and-access-of-simple-user-defined-task-configuration-taskparameters) + * [Definition and access of user-defined configuration ("extendedTaskParameters")](doc/Configuration.md#definition-and-access-of-user-defined-configuration-extendedtaskparameters) +* [QCDB and CCDB](doc/QCDB.md) + * [QCDB vs CCDB](doc/QCDB.md#qcdb-vs-ccdb) + * [Details on the data storage format in the QCDB](doc/QCDB.md#details-on-the-data-storage-format-in-the-qcdb) + * [Custom metadata for QC objects in the QCDB](doc/QCDB.md#custom-metadata-for-qc-objects-in-the-qcdb) + * [Instructions to move an object in the QCDB](doc/QCDB.md#instructions-to-move-an-object-in-the-qcdb) + * [Accessing objects in CCDB](doc/QCDB.md#accessing-objects-in-ccdb) + * [Access GRP objects with GRP Geom Helper](doc/QCDB.md#access-grp-objects-with-grp-geom-helper) + * [Global Tracking Data Request helper](doc/QCDB.md#global-tracking-data-request-helper) + * [Local CCDB setup](doc/QCDB.md#local-ccdb-setup) +* [FLP Suite](doc/FLPsuite.md) + * [Developing QC modules on a machine with FLP suite](doc/FLPsuite.md#developing-qc-modules-on-a-machine-with-flp-suite) + * [Switch detector in the workflow readout-dataflow](doc/FLPsuite.md#switch-detector-in-the-workflow-readout-dataflow) + * [Get all the task output to the infologger](doc/FLPsuite.md#get-all-the-task-output-to-the-infologger) + * [Using a different config file with the general QC](doc/FLPsuite.md#using-a-different-config-file-with-the-general-qc) + * [Enable the repo cleaner](doc/FLPsuite.md#enable-the-repo-cleaner) + * [Reference data](doc/FLPsuite.md#reference-data) +* [Miscellaneous](doc/Miscellaneous.md) + * [Asynchronous Data and Monte Carlo QC operations](doc/Miscellaneous.md#asynchronous-data-and-monte-carlo-qc-operations) + * [QCG](doc/Miscellaneous.md#qcg) + * [Data Sampling monitoring](doc/Miscellaneous.md#data-sampling-monitoring) + * [Monitoring metrics](doc/Miscellaneous.md#monitoring-metrics) + * [Common check IncreasingEntries](doc/Miscellaneous.md#common-check-increasingentries) + * [Common check TrendCheck](doc/Miscellaneous.md#common-check-trendcheck) + * [Update the shmem segment size of a detector](doc/Miscellaneous.md#update-the-shmem-segment-size-of-a-detector) + * [Readout chain](doc/Miscellaneous.md#readout-chain) + * [Writing a DPL data producer](doc/Miscellaneous.md#writing-a-dpl-data-producer) - - - - * [QuickStart](doc/QuickStart.md) - * [Requirements](doc/QuickStart.md#requirements) - * [Setup](doc/QuickStart.md#setup) - * [Execution](doc/QuickStart.md#execution) - * [Basic workflow](doc/QuickStart.md#basic-workflow) - * [Readout chain](doc/QuickStart.md#readout-chain) - * [Modules development](doc/ModulesDevelopment.md) - * [Context](doc/ModulesDevelopment.md#context) - * [QC architecture](doc/ModulesDevelopment.md#qc-architecture) - * [DPL](doc/ModulesDevelopment.md#dpl) - * [Data Sampling](doc/ModulesDevelopment.md#data-sampling) - * [Bypassing the Data Sampling](doc/ModulesDevelopment.md#bypassing-the-data-sampling) - * [Code Organization](doc/ModulesDevelopment.md#code-organization) - * [User-defined modules](doc/ModulesDevelopment.md#user-defined-modules) - * [Module creation](doc/ModulesDevelopment.md#module-creation) - * [Test run](doc/ModulesDevelopment.md#test-run) - * [Modification of a Task](doc/ModulesDevelopment.md#modification-of-a-task) - * [Addition of a Check](doc/ModulesDevelopment.md#addition-of-a-check) - * [Commit Code](doc/ModulesDevelopment.md#commit-code) - * [DPL workflow customization](doc/ModulesDevelopment.md#dpl-workflow-customization) - * [Plugging Data Sampling and QC into an existing DPL workflow](doc/ModulesDevelopment.md#usage-of-ds-and-qc-in-an-existing-dpl-workflow) - * [Advanced topics](doc/Advanced.md) - * [Data Inspector](doc/Advanced.md#data-inspector) - * [Use MySQL as QC backend](doc/Advanced.md#use-mysql-as-qc-backend) - * [Local CCDB setup](doc/Advanced.md#local-ccdb-setup) - * [Local QCG (QC GUI) setup](doc/Advanced.md#local-qcg-qc-gui-setup) - * [Information Service](doc/Advanced.md#information-service) - * [Configuration files details](doc/Advanced.md#configuration-files-details) - * [Frequently Asked Questions](doc/FAQ.md) - +### Where to get help - +* By email at [alice-o2-qc-support@cern.ch](mailto:alice-o2-qc-support@cern.ch) +* Discourse: https://alice-talk.web.cern.ch/ +* JIRA: https://alice.its.cern.ch +* O2 development newcomers' guide: https://aliceo2group.github.io/quickstart diff --git a/cmake/FindAliceO2.cmake b/cmake/FindAliceO2.cmake deleted file mode 100644 index 011bf3800c..0000000000 --- a/cmake/FindAliceO2.cmake +++ /dev/null @@ -1,63 +0,0 @@ - -# - Try to find the O2 framework package include dirs and libraries -# Author: Barthelemy von Haller -# -# This script will set the following variables: -# AliceO2_FOUND - System has AliceO2 -# AliceO2_INCLUDE_DIRS - The AliceO2 include directories -# AliceO2_LIBRARIES - The libraries needed to use AliceO2 -# AliceO2_DEFINITIONS - Compiler switches required for using AliceO2 - -# Init -include(FindPackageHandleStandardArgs) - -# find includes -find_path(AliceO2_INCLUDE_DIR runDataProcessing.h - HINTS ${O2_ROOT}/include ENV LD_LIBRARY_PATH PATH_SUFFIXES "../include/Framework" "../../include/Framework") - -# Remove the final "Headers" -get_filename_component(AliceO2_INCLUDE_DIR ${AliceO2_INCLUDE_DIR} DIRECTORY) -set(AliceO2_INCLUDE_DIRS ${AliceO2_INCLUDE_DIR}) -list(APPEND AliceO2_INCLUDE_DIRS ${MS_GSL_INCLUDE_DIR}) - -# find libraries -# TODO SEARCH *ALL* LIBRARIES --> AliceO2 should ideally provide the list !!! -set(O2_LIBRARIES_NAMES - Framework - Headers - CCDB - DebugGUI - DetectorsBase - ITSBase - ITSSimulation - ITSReconstruction - ITSWorkflow - ITSMFTReconstruction - ITSMFTBase - DetectorsCommonDataFormats - ) -foreach(lib_name ${O2_LIBRARIES_NAMES}) - find_library(AliceO2_LIBRARY_${lib_name} NAMES ${lib_name} HINTS ${O2_ROOT}/lib ENV LD_LIBRARY_PATH) - list(APPEND AliceO2_LIBRARIES_VAR_NAMES AliceO2_LIBRARY_${lib_name}) - list(APPEND AliceO2_LIBRARIES ${AliceO2_LIBRARY_${lib_name}}) -endforeach() - -# handle the QUIETLY and REQUIRED arguments and set AliceO2_FOUND to TRUE -# if all listed variables are TRUE -find_package_handle_standard_args(AliceO2 "AliceO2 could not be found. Install package AliceO2." ${AliceO2_LIBRARIES_VAR_NAMES} AliceO2_INCLUDE_DIR) - -if(${AliceO2_FOUND}) - message(STATUS "AliceO2 found, libraries: ${AliceO2_LIBRARIES}") - - mark_as_advanced(AliceO2_INCLUDE_DIRS AliceO2_LIBRARIES) - - # add target - if(NOT TARGET AliceO2::AliceO2) - add_library(AliceO2::AliceO2 INTERFACE IMPORTED) - set_target_properties(AliceO2::AliceO2 PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${AliceO2_INCLUDE_DIRS}" - INTERFACE_LINK_LIBRARIES "${AliceO2_LIBRARIES}" - ) - endif() -endif() - diff --git a/cmake/FindArrow.cmake b/cmake/FindArrow.cmake deleted file mode 100644 index b5475494f0..0000000000 --- a/cmake/FindArrow.cmake +++ /dev/null @@ -1,130 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -# - Find ARROW (arrow/api.h, libarrow.a, libarrow.so) -# This module defines -# ARROW_INCLUDE_DIR, directory containing headers -# ARROW_STATIC_LIB, path to libarrow.a -# ARROW_SHARED_LIB, path to libarrow's shared library -# ARROW_FOUND, whether arrow has been found - -if (DEFINED ENV{ARROW_HOME}) - set(ARROW_HOME "$ENV{ARROW_HOME}") -endif() - -if ("${ARROW_HOME}" STREQUAL "") - # PARQUET-955. If the user has set $ARROW_HOME in the environment, we respect - # this, otherwise try to locate the pkgconfig in the system environment - if (ARROW_FOUND) - # We found the pkgconfig - set(ARROW_INCLUDE_DIR ${ARROW_INCLUDE_DIRS}) - - if (COMMAND pkg_get_variable) - pkg_get_variable(ARROW_ABI_VERSION arrow abi_version) - else() - set(ARROW_ABI_VERSION "") - endif() - if (ARROW_ABI_VERSION STREQUAL "") - set(ARROW_SHARED_LIB_SUFFIX "") - else() - set(ARROW_SHARED_LIB_SUFFIX ".${ARROW_ABI_VERSION}") - endif() - - set(ARROW_LIB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}arrow) - - if (APPLE) - set(ARROW_SHARED_LIB ${ARROW_LIBDIR}/${ARROW_LIB_NAME}${ARROW_SHARED_LIB_SUFFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}) - else() - set(ARROW_SHARED_LIB ${ARROW_LIBDIR}/${ARROW_LIB_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${ARROW_SHARED_LIB_SUFFIX}) - endif() - set(ARROW_STATIC_LIB ${ARROW_LIBDIR}/${ARROW_LIB_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}) - endif() -else() - set(ARROW_HOME "${ARROW_HOME}") - - if (MSVC AND NOT ARROW_MSVC_STATIC_LIB_SUFFIX) - set(ARROW_MSVC_STATIC_LIB_SUFFIX _static) - endif() - - set(ARROW_SEARCH_HEADER_PATHS - ${ARROW_HOME}/include - ) - - set(ARROW_SEARCH_LIB_PATH - ${ARROW_HOME}/lib - ) - - find_path(ARROW_INCLUDE_DIR arrow/array.h PATHS - ${ARROW_SEARCH_HEADER_PATHS} - # make sure we don't accidentally pick up a different version - NO_DEFAULT_PATH - ) - - find_library(ARROW_LIB_PATH NAMES arrow arrow${ARROW_MSVC_STATIC_LIB_SUFFIX} - PATHS - ${ARROW_SEARCH_LIB_PATH} - NO_DEFAULT_PATH) - - if (ARROW_INCLUDE_DIR AND (PARQUET_MINIMAL_DEPENDENCY OR ARROW_LIB_PATH)) - set(ARROW_FOUND TRUE) - set(ARROW_HEADER_NAME arrow/api.h) - set(ARROW_HEADER ${ARROW_INCLUDE_DIR}/${ARROW_HEADER_NAME}) - set(ARROW_LIB_NAME arrow) - - get_filename_component(ARROW_LIBS ${ARROW_LIB_PATH} DIRECTORY) - set(ARROW_STATIC_LIB ${ARROW_LIBS}/${CMAKE_STATIC_LIBRARY_PREFIX}${ARROW_LIB_NAME}${ARROW_MSVC_STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}) - set(ARROW_SHARED_LIB ${ARROW_LIBS}/${CMAKE_SHARED_LIBRARY_PREFIX}${ARROW_LIB_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(ARROW_SHARED_IMPLIB ${ARROW_LIBS}/${ARROW_LIB_NAME}.lib) - endif () -endif() - -if (ARROW_FOUND) - if (NOT Arrow_FIND_QUIETLY) - message(STATUS "Arrow include path: ${ARROW_INCLUDE_DIR}") - if (PARQUET_MINIMAL_DEPENDENCY) - message(STATUS "Found the Arrow header: ${ARROW_HEADER}") - else () - message(STATUS "Found the Arrow library: ${ARROW_LIB_PATH}") - endif () - endif () - # add target - if(NOT TARGET Arrow::Arrow) - add_library(Arrow::Arrow INTERFACE IMPORTED) - set_target_properties(Arrow::Arrow PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${ARROW_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES "${ARROW_LIB_PATH}" - ) - endif() -else() - if (NOT Arrow_FIND_QUIETLY) - set(ARROW_ERR_MSG "Could not find the Arrow library. Looked for headers") - set(ARROW_ERR_MSG "${ARROW_ERR_MSG} in ${ARROW_SEARCH_HEADER_PATHS}, and for libs") - set(ARROW_ERR_MSG "${ARROW_ERR_MSG} in ${ARROW_SEARCH_LIB_PATH}") - if (Arrow_FIND_REQUIRED) - message(FATAL_ERROR "${ARROW_ERR_MSG}") - else (Arrow_FIND_REQUIRED) - message(STATUS "${ARROW_ERR_MSG}") - endif (Arrow_FIND_REQUIRED) - endif () -endif() - -mark_as_advanced( - ARROW_FOUND - ARROW_INCLUDE_DIR - ARROW_STATIC_LIB - ARROW_SHARED_LIB -) diff --git a/cmake/FindCURL.cmake b/cmake/FindCURL.cmake deleted file mode 100644 index c102f36b5f..0000000000 --- a/cmake/FindCURL.cmake +++ /dev/null @@ -1,67 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#.rst: -# FindCURL -# -------- -# -# Find curl -# -# IMPORTED Targets -# ^^^^^^^^^^^^^^^^ -# -# This module defines :prop_tgt:`IMPORTED` target ``CURL::CURL``, if -# curl has been found. -# -# Find the native CURL headers and libraries. -# -# :: -# -# CURL_INCLUDE_DIRS - where to find curl/curl.h, etc. -# CURL_LIBRARIES - List of libraries when using curl. -# CURL_FOUND - True if curl found. -# CURL_VERSION_STRING - the version of curl found (since CMake 2.8.8) - -# Look for the header file. -find_path(CURL_INCLUDE_DIR NAMES curl/curl.h) -mark_as_advanced(CURL_INCLUDE_DIR) - -# Look for the library (sorted from most current/relevant entry to least). -find_library(CURL_LIBRARY NAMES - curl - # Windows MSVC prebuilts: - curllib - libcurl_imp - curllib_static - # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): - libcurl -) -mark_as_advanced(CURL_LIBRARY) - -if(CURL_INCLUDE_DIR) - foreach(_curl_version_header curlver.h curl.h) - if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}") - file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"") - - string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}") - unset(curl_version_str) - break() - endif() - endforeach() -endif() - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(CURL - REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR - VERSION_VAR CURL_VERSION_STRING) - -if(CURL_FOUND) - set(CURL_LIBRARIES ${CURL_LIBRARY}) - set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR}) - - if(NOT TARGET CURL::CURL) - add_library(CURL::CURL UNKNOWN IMPORTED) - set_target_properties(CURL::CURL PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}") - set_property(TARGET CURL::CURL APPEND PROPERTY IMPORTED_LOCATION "${CURL_LIBRARY}") - endif() -endif() diff --git a/cmake/FindFairRoot.cmake b/cmake/FindFairRoot.cmake deleted file mode 100644 index b07856cd05..0000000000 --- a/cmake/FindFairRoot.cmake +++ /dev/null @@ -1,65 +0,0 @@ - ################################################################################ - # Copyright (C) 2014 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH # - # # - # This software is distributed under the terms of the # - # GNU Lesser General Public Licence (LGPL) version 3, # - # copied verbatim in the file "LICENSE" # - ################################################################################ - # Find FairRoot installation - # Check the environment variable "FAIRROOTPATH" - - if(FairRoot_DIR) - set(FAIRROOTPATH ${FairRoot_DIR}) - else() - if(NOT DEFINED ENV{FAIRROOTPATH}) - set(user_message "You did not define the environment variable FAIRROOTPATH which is needed to find FairRoot.\ - Please set this variable and execute cmake again." ) - if(FairRoot_FIND_REQUIRED) - MESSAGE(FATAL_ERROR ${user_message}) - else(FairRoot_FIND_REQUIRED) - MESSAGE(WARNING ${user_message}) - return() - endif(FairRoot_FIND_REQUIRED) - endif(NOT DEFINED ENV{FAIRROOTPATH}) - - set(FAIRROOTPATH $ENV{FAIRROOTPATH}) - endif() - - MESSAGE(STATUS "Setting FairRoot environment…") - - FIND_PATH(FAIRROOT_INCLUDE_DIR NAMES FairRun.h PATHS - ${FAIRROOTPATH}/include - NO_DEFAULT_PATH - ) - - FIND_PATH(FAIRROOT_LIBRARY_DIR NAMES libBase.so libBase.dylib PATHS - ${FAIRROOTPATH}/lib - NO_DEFAULT_PATH - ) - - FIND_PATH(FAIRROOT_CMAKEMOD_DIR NAMES CMakeLists.txt PATHS - ${FAIRROOTPATH}/share/fairbase/cmake - NO_DEFAULT_PATH - ) - - # look for exported FairMQ targets and include them - find_file(_fairroot_fairmq_cmake - NAMES FairMQ.cmake - HINTS ${FAIRROOTPATH}/include/cmake - ) - if(_fairroot_fairmq_cmake) - include(${_fairroot_fairmq_cmake}) - endif() - - if(FAIRROOT_INCLUDE_DIR AND FAIRROOT_LIBRARY_DIR) - set(FAIRROOT_FOUND TRUE) - MESSAGE(STATUS "FairRoot ... - found ${FAIRROOTPATH}") - MESSAGE(STATUS "FairRoot Library directory : ${FAIRROOT_LIBRARY_DIR}") - MESSAGE(STATUS "FairRoot Include path… : ${FAIRROOT_INCLUDE_DIR}") - MESSAGE(STATUS "FairRoot Cmake Modules : ${FAIRROOT_CMAKEMOD_DIR}") - - else(FAIRROOT_INCLUDE_DIR AND FAIRROOT_LIBRARY_DIR) - set(FAIRROOT_FOUND FALSE) - MESSAGE(FATAL_ERROR "FairRoot installation not found") - endif (FAIRROOT_INCLUDE_DIR AND FAIRROOT_LIBRARY_DIR) - diff --git a/cmake/FindGLFW.cmake b/cmake/FindGLFW.cmake deleted file mode 100644 index 30ae99aec0..0000000000 --- a/cmake/FindGLFW.cmake +++ /dev/null @@ -1,287 +0,0 @@ -# -# Copyright 2013 Pixar -# -# Licensed under the Apache License, Version 2.0 (the "Apache License") -# with the following modification; you may not use this file except in -# compliance with the Apache License and the following modification to it: -# Section 6. Trademarks. is deleted and replaced with: -# -# 6. Trademarks. This License does not grant permission to use the trade -# names, trademarks, service marks, or product names of the Licensor -# and its affiliates, except as required to comply with Section 4(c) of -# the License and to reproduce the content of the NOTICE file. -# -# You may obtain a copy of the Apache License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the Apache License with the above modification is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the Apache License for the specific -# language governing permissions and limitations under the Apache License. -# - -# Copyright 2017 Giulio Eulisse -# -# Modified to allow for optional installation in case X11 libraries are -# not found. Same terms as above apply. - -# Try to find GLFW library and include path. -# Once done this will define -# -# GLFW_FOUND -# GLFW_INCLUDE_DIR -# GLFW_LIBRARIES -# - - -find_path( GLFW_INCLUDE_DIR - NAMES - GLFW/glfw3.h - HINTS - "${GLFW_LOCATION}/include" - "$ENV{GLFW_LOCATION}/include" - PATHS - "$ENV{PROGRAMFILES}/GLFW/include" - "${OPENGL_INCLUDE_DIR}" - /usr/openwin/share/include - /usr/openwin/include - /usr/X11R6/include - /usr/include/X11 - /opt/graphics/OpenGL/include - /opt/graphics/OpenGL/contrib/libglfw - /usr/local/include - /usr/include/GL - /usr/include - DOC - "The directory where GLFW/glfw3.h resides" -) - -# -# XXX: Do we still need to search for GL/glfw.h? -# -find_path( GLFW_INCLUDE_DIR - NAMES - GL/glfw.h - HINTS - "${GLFW_LOCATION}/include" - "$ENV{GLFW_LOCATION}/include" - PATHS - "$ENV{PROGRAMFILES}/GLFW/include" - "${OPENGL_INCLUDE_DIR}" - /usr/openwin/share/include - /usr/openwin/include - /usr/X11R6/include - /usr/include/X11 - /opt/graphics/OpenGL/include - /opt/graphics/OpenGL/contrib/libglfw - /usr/local/include - /usr/include/GL - /usr/include - DOC - "The directory where GL/glfw.h resides" -) - -# This will be set to yes, if any of the X11 libraries -# is not present -set(GLFW_MISSING_DEPENDENCIES FALSE) - -if (WIN32) - if(CYGWIN) - find_library( GLFW_glfw_LIBRARY - NAMES - glfw32 - HINTS - "${GLFW_LOCATION}/lib" - "${GLFW_LOCATION}/lib/x64" - "$ENV{GLFW_LOCATION}/lib" - PATHS - "${OPENGL_LIBRARY_DIR}" - /usr/lib - /usr/lib/w32api - /usr/local/lib - /usr/X11R6/lib - DOC - "The GLFW library" - ) - else() - find_library( GLFW_glfw_LIBRARY - NAMES - glfw32 - glfw32s - glfw - glfw3 - HINTS - "${GLFW_LOCATION}/lib" - "${GLFW_LOCATION}/lib/x64" - "${GLFW_LOCATION}/lib-msvc110" - "${GLFW_LOCATION}/lib-vc2012" - "$ENV{GLFW_LOCATION}/lib" - "$ENV{GLFW_LOCATION}/lib/x64" - "$ENV{GLFW_LOCATION}/lib-msvc110" - "$ENV{GLFW_LOCATION}/lib-vc2012" - PATHS - "$ENV{PROGRAMFILES}/GLFW/lib" - "${OPENGL_LIBRARY_DIR}" - DOC - "The GLFW library" - ) - endif() -else () - if (APPLE) - find_library( GLFW_glfw_LIBRARY glfw - NAMES - glfw - glfw3 - HINTS - "${GLFW_LOCATION}/lib" - "${GLFW_LOCATION}/lib/cocoa" - "$ENV{GLFW_LOCATION}/lib" - "$ENV{GLFW_LOCATION}/lib/cocoa" - PATHS - /usr/local/lib - ) - set(GLFW_cocoa_LIBRARY "-framework Cocoa" CACHE STRING "Cocoa framework for OSX") - set(GLFW_corevideo_LIBRARY "-framework CoreVideo" CACHE STRING "CoreVideo framework for OSX") - set(GLFW_iokit_LIBRARY "-framework IOKit" CACHE STRING "IOKit framework for OSX") - else () - # (*)NIX - - find_package(Threads) - if (NOT Threads_FOUND) - set(GLFW_MISSING_DEPENDENCIES TRUE) - message("Threads not found") - endif() - - find_package(X11) - if (NOT X11_FOUND) - set(GLFW_MISSING_DEPENDENCIES TRUE) - message("X11 not found") - endif() - - if(NOT X11_Xrandr_FOUND) - set(GLFW_MISSING_DEPENDENCIES TRUE) - message("Xrandr library not found - required for GLFW") - endif() - - if(NOT X11_xf86vmode_FOUND) - set(GLFW_MISSING_DEPENDENCIES TRUE) - message("xf86vmode library not found - required for GLFW") - endif() - - if(NOT X11_Xcursor_FOUND) - set(GLFW_MISSING_DEPENDENCIES TRUE) - message("Xcursor library not found - required for GLFW") - endif() - - if(NOT X11_Xinerama_FOUND) - set(GLFW_MISSING_DEPENDENCIES TRUE) - message("Xinerama library not found - required for GLFW") - endif() - - if(NOT X11_Xi_FOUND) - set(GLFW_MISSING_DEPENDENCIES TRUE) - message("Xi library not found - required for GLFW") - endif() - - list(APPEND GLFW_x11_LIBRARY "${X11_Xrandr_LIB}" "${X11_Xxf86vm_LIB}" "${X11_Xcursor_LIB}" "${X11_Xinerama_LIB}" "${X11_Xi_LIB}" "${X11_LIBRARIES}" "${CMAKE_THREAD_LIBS_INIT}" -lrt -ldl) - - find_library( GLFW_glfw_LIBRARY - NAMES - glfw - glfw3 - HINTS - "${GLFW_LOCATION}/lib" - "$ENV{GLFW_LOCATION}/lib" - "${GLFW_LOCATION}/lib/x11" - "$ENV{GLFW_LOCATION}/lib/x11" - PATHS - /usr/lib64 - /usr/lib - /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} - /usr/local/lib64 - /usr/local/lib - /usr/local/lib/${CMAKE_LIBRARY_ARCHITECTURE} - /usr/openwin/lib - /usr/X11R6/lib - DOC - "The GLFW library" - ) - endif (APPLE) -endif (WIN32) - -set( GLFW_FOUND "NO" ) - -if(GLFW_INCLUDE_DIR) - - if(GLFW_glfw_LIBRARY) - if (NOT GLFW_MISSING_DEPENDENCIES) - set( GLFW_LIBRARIES "${GLFW_glfw_LIBRARY}" - "${GLFW_x11_LIBRARY}" - "${GLFW_cocoa_LIBRARY}" - "${GLFW_iokit_LIBRARY}" - "${GLFW_corevideo_LIBRARY}" ) - set( GLFW_FOUND "YES" ) - set (GLFW_LIBRARY "${GLFW_LIBRARIES}") - set (GLFW_INCLUDE_PATH "${GLFW_INCLUDE_DIR}") - endif(NOT GLFW_MISSING_DEPENDENCIES) - endif(GLFW_glfw_LIBRARY) - - - # Tease the GLFW_VERSION numbers from the lib headers - function(parseVersion FILENAME VARNAME) - set(PATTERN "^#define ${VARNAME}.*$") - file(STRINGS "${GLFW_INCLUDE_DIR}/${FILENAME}" TMP REGEX ${PATTERN}) - string(REGEX MATCHALL "[0-9]+" TMP ${TMP}) - set(${VARNAME} ${TMP} PARENT_SCOPE) - endfunction() - - - if(EXISTS "${GLFW_INCLUDE_DIR}/GL/glfw.h") - - parseVersion(GL/glfw.h GLFW_VERSION_MAJOR) - parseVersion(GL/glfw.h GLFW_VERSION_MINOR) - parseVersion(GL/glfw.h GLFW_VERSION_REVISION) - - elseif(EXISTS "${GLFW_INCLUDE_DIR}/GLFW/glfw3.h") - - parseVersion(GLFW/glfw3.h GLFW_VERSION_MAJOR) - parseVersion(GLFW/glfw3.h GLFW_VERSION_MINOR) - parseVersion(GLFW/glfw3.h GLFW_VERSION_REVISION) - - endif() - - if(${GLFW_VERSION_MAJOR} OR ${GLFW_VERSION_MINOR} OR ${GLFW_VERSION_REVISION}) - set(GLFW_VERSION "${GLFW_VERSION_MAJOR}.${GLFW_VERSION_MINOR}.${GLFW_VERSION_REVISION}") - set(GLFW_VERSION_STRING "${GLFW_VERSION}") - mark_as_advanced(GLFW_VERSION) - endif() - -endif(GLFW_INCLUDE_DIR) - -include(FindPackageHandleStandardArgs) - -find_package_handle_standard_args(GLFW - REQUIRED_VARS - GLFW_INCLUDE_DIR - GLFW_LIBRARIES - VERSION_VAR - GLFW_VERSION -) - -mark_as_advanced( - GLFW_INCLUDE_DIR - GLFW_LIBRARIES - GLFW_glfw_LIBRARY - GLFW_cocoa_LIBRARY -) - -# add target -if(NOT TARGET GLFW::GLFW) - add_library(GLFW::GLFW INTERFACE IMPORTED) - set_target_properties(GLFW::GLFW PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${GLFW_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES "${GLFW_LIBRARIES}" - ) -endif() \ No newline at end of file diff --git a/cmake/FindInfoLogger.cmake b/cmake/FindInfoLogger.cmake deleted file mode 100644 index 90809ffaf6..0000000000 --- a/cmake/FindInfoLogger.cmake +++ /dev/null @@ -1,45 +0,0 @@ -# - Try to find the O2 InfoLogger package include dirs and libraries -# Author: Barthelemy von Haller -# -# This script will set the following variables: -# InfoLogger_FOUND - System has InfoLogger -# InfoLogger_INCLUDE_DIRS - The InfoLogger include directories -# InfoLogger_LIBRARIES - The libraries needed to use InfoLogger -# InfoLogger_DEFINITIONS - Compiler switches required for using InfoLogger -# -# This script can use the following variables: -# InfoLogger_ROOT - Installation root to tell this module where to look. (it tries LD_LIBRARY_PATH otherwise) - -# Init -include(FindPackageHandleStandardArgs) - -# find includes -find_path(INFOLOGGER_INCLUDE_DIR InfoLogger.hxx - HINTS ${InfoLogger_ROOT}/include ENV LD_LIBRARY_PATH PATH_SUFFIXES "../include/InfoLogger" "../../include/InfoLogger" ) -# Remove the final "InfoLogger" -get_filename_component(INFOLOGGER_INCLUDE_DIR ${INFOLOGGER_INCLUDE_DIR} DIRECTORY) -set(InfoLogger_INCLUDE_DIRS ${INFOLOGGER_INCLUDE_DIR}) - -# find library -find_library(INFOLOGGER_LIBRARY NAMES InfoLogger HINTS ${InfoLogger_ROOT}/lib ENV LD_LIBRARY_PATH) -set(InfoLogger_LIBRARIES ${INFOLOGGER_LIBRARY}) - -# handle the QUIETLY and REQUIRED arguments and set INFOLOGGER_FOUND to TRUE -# if all listed variables are TRUE -find_package_handle_standard_args(InfoLogger "InfoLogger could not be found. Install package InfoLogger or set InfoLogger_ROOT to its root installation directory." - INFOLOGGER_LIBRARY INFOLOGGER_INCLUDE_DIR) - -if(${InfoLogger_FOUND}) - message(STATUS "InfoLogger found : ${InfoLogger_LIBRARIES}") - mark_as_advanced(INFOLOGGER_INCLUDE_DIR INFOLOGGER_LIBRARY) - - # add target - if(NOT TARGET AliceO2::InfoLogger) - add_library(AliceO2::InfoLogger INTERFACE IMPORTED) - set_target_properties(AliceO2::InfoLogger PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${INFOLOGGER_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES "${INFOLOGGER_LIBRARY}" - ) - endif() -endif() - diff --git a/cmake/FindMySQL.cmake b/cmake/FindMySQL.cmake deleted file mode 100644 index b1bd25a383..0000000000 --- a/cmake/FindMySQL.cmake +++ /dev/null @@ -1,58 +0,0 @@ -# - Find mysqlclient -# Find the native MySQL includes and library -# -# MYSQL_INCLUDE_DIRS - where to find mysql.h, etc. -# MYSQL_LIBRARIES - List of libraries when using MySQL. -# MYSQL_FOUND - True if MySQL found. - -# MYSQL_PATH_SUFFIXES - by default it includes "mysql". Set it if you want to force extra suffixes to be used. - -if(NOT MYSQL_PATH_SUFFIXES) - set(MYSQL_PATH_SUFFIXES "" "mysql") -endif() - -FIND_PATH(MYSQL_INCLUDE_DIRS mysql.h - /usr/local/include/mysql - /usr/include/mysql -) - -SET(MYSQL_NAMES mysqlclient mysqlclient_r) -FIND_LIBRARY(MYSQL_LIBRARY - NAMES ${MYSQL_NAMES} - PATHS /usr/lib /usr/lib64 /usr/local/lib - PATH_SUFFIXES ${MYSQL_PATH_SUFFIXES} -) - -IF (MYSQL_INCLUDE_DIRS AND MYSQL_LIBRARY) - SET(MYSQL_FOUND TRUE) - SET( MYSQL_LIBRARIES ${MYSQL_LIBRARY} ) -ELSE (MYSQL_INCLUDE_DIRS AND MYSQL_LIBRARY) - SET(MYSQL_FOUND FALSE) - UNSET(MYSQL_LIBRARIES CACHE) - UNSET(MYSQL_INCLUDE_DIRS CACHE) -ENDIF (MYSQL_INCLUDE_DIRS AND MYSQL_LIBRARY) - -IF (MYSQL_FOUND) - MESSAGE(STATUS "Found MySQL: ${MYSQL_LIBRARY}") - - MARK_AS_ADVANCED( - MYSQL_LIBRARY - MYSQL_INCLUDE_DIRS - ) - - # add target - if(NOT TARGET MySQL::MySQL) - add_library(MySQL::MySQL INTERFACE IMPORTED) - set_target_properties(MySQL::MySQL PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${MYSQL_INCLUDE_DIRS}" - INTERFACE_LINK_LIBRARIES "${MYSQL_LIBRARY}" - ) - endif() -ELSE (MYSQL_FOUND) - IF (MYSQL_FIND_REQUIRED) - MESSAGE(STATUS "Looked for MySQL libraries named ${MYSQL_NAMES}.") - MESSAGE(FATAL_ERROR "Could NOT find MySQL library") - ENDIF (MYSQL_FIND_REQUIRED) -ENDIF (MYSQL_FOUND) - - diff --git a/cmake/FindROOT.cmake b/cmake/FindROOT.cmake deleted file mode 100644 index 598248d315..0000000000 --- a/cmake/FindROOT.cmake +++ /dev/null @@ -1,38 +0,0 @@ -################################################################################ -# Copyright (C) 2014 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH # -# # -# This software is distributed under the terms of the # -# GNU Lesser General Public Licence (LGPL) version 3, # -# copied verbatim in the file "LICENSE" # -################################################################################ - -# search ROOTConfig.cmake -find_package(ROOT QUIET CONFIG - HINTS ${ROOT_ROOT} $ENV{ROOT_ROOT} ${ROOTSYS} $ENV{ROOTSYS} ${SIMPATH} - ) - -# process results of above search -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(ROOT CONFIG_MODE) - -# contains upstream ROOT_GENERATE_DICTIONARY and ROOT_LINKER_LIBRARY macros -include(${ROOT_USE_FILE}) - -get_filename_component(ROOTSYS ${ROOT_BINARY_DIR} DIRECTORY) - -# will be filled by add_fairroot_library -set_property(GLOBAL PROPERTY ROOT_INCLUDE_PATH "") - -if(ROOT_FOUND) - list(APPEND LD_LIBRARY_PATH ${ROOT_LIBRARY_DIR}) - - # Add missing include dirs TODO this should be done by upstream - foreach(t ROOT::vectorDict ROOT::listDict ROOT::forward_listDict ROOT::dequeDict ROOT::mapDict ROOT::map2Dict ROOT::unordered_mapDict ROOT::multimapDict ROOT::multimap2Dict ROOT::unordered_multimapDict ROOT::setDict ROOT::unordered_setDict ROOT::multisetDict ROOT::unordered_multisetDict ROOT::complexDict ROOT::valarrayDict ROOT::Cling ROOT::MultiProc ROOT::Rint ROOT::Thread ROOT::Imt ROOT::New ROOT::Core ROOT::rmkdepend ROOT::MathCore ROOT::MathMore ROOT::Matrix ROOT::Minuit ROOT::Minuit2 ROOT::Fumili ROOT::Physics ROOT::MLP ROOT::Quadp ROOT::Foam ROOT::Smatrix ROOT::SPlot ROOT::GenVector ROOT::Genetic ROOT::Hist ROOT::HistPainter ROOT::Spectrum ROOT::SpectrumPainter ROOT::Unfold ROOT::Hbook ROOT::Tree ROOT::TreePlayer ROOT::TreeViewer ROOT::RIO ROOT::SQLIO ROOT::XMLIO ROOT::XMLParser ROOT::Net ROOT::RootAuth ROOT::Krb5Auth ROOT::SrvAuth ROOT::rootd ROOT::Netx ROOT::NetxNG ROOT::RHTTP ROOT::Gpad ROOT::Graf ROOT::Postscript ROOT::mathtext ROOT::GX11 ROOT::GX11TTF ROOT::ASImage ROOT::ASImageGui ROOT::Graf3d ROOT::X3d ROOT::Eve ROOT::RGL ROOT::GLEW ROOT::FTGL ROOT::Gviz3d ROOT::Gui ROOT::Ged ROOT::FitPanel ROOT::GuiBld ROOT::GuiHtml ROOT::Recorder ROOT::SessionViewer ROOT::Proof ROOT::ProofPlayer ROOT::ProofDraw ROOT::ProofBench ROOT::proofd ROOT::XrdProofd ROOT::proofexecv ROOT::Proofx ROOT::pq2 ROOT::Html ROOT::EG ROOT::VMC ROOT::EGPythia6 ROOT::EGPythia8 ROOT::Geom ROOT::GeomBuilder ROOT::GeomPainter ROOT::Gdml ROOT::root ROOT::minicern ROOT::MemStat ROOT::RMySQL ROOT::rootn.exe ROOT::roots.exe ROOT::ssh2rpd ROOT::xpdtest ROOT::root.exe ROOT::proofserv.exe ROOT::hadd ROOT::rootnb.exe ROOT::g2root ROOT::h2root ROOT::rootcling ROOT::PyROOT ROOT::JupyROOT ROOT::PgSQL ROOT::RSQLite ROOT::TMVA ROOT::TMVAGui ROOT::RooFitCore ROOT::RooFit ROOT::RooStats ROOT::HistFactory ROOT::hist2workspace) - if(TARGET ${t}) - get_target_property(incdirs ${t} INTERFACE_INCLUDE_DIRECTORIES) - if(NOT incdirs) - set_target_properties(${t} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${ROOT_INCLUDE_DIRS}) - endif() - endif() - endforeach() -endif() diff --git a/cmake/FindZeroMQ.cmake b/cmake/FindZeroMQ.cmake deleted file mode 100644 index 84bc49237b..0000000000 --- a/cmake/FindZeroMQ.cmake +++ /dev/null @@ -1,159 +0,0 @@ -################################################################################ -# Copyright (C) 2012-2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH # -# # -# This software is distributed under the terms of the # -# GNU Lesser General Public Licence (LGPL) version 3, # -# copied verbatim in the file "LICENSE" # -################################################################################ -# -# Authors: -# -# Mohammad Al-Turany -# Dario Berzano -# Dennis Klein -# Matthias Richter -# Alexey Rybalchenko -# Florian Uhlig -# -# -# ############################# -# # Locate the ZeroMQ library # -# ############################# -# -# -# Usage: -# -# find_package(ZeroMQ [version] [QUIET] [REQUIRED]) -# -# -# Defines the following variables: -# -# ZeroMQ_FOUND - Found the ZeroMQ library -# ZeroMQ_INCLUDE_DIR (CMake cache) - Include directory -# ZeroMQ_LIBRARY_SHARED (CMake cache) - Path to shared libzmq -# ZeroMQ_LIBRARY_STATIC (CMake cache) - Path to static libzmq -# ZeroMQ_VERSION - full version string -# ZeroMQ_VERSION_MAJOR - major version component -# ZeroMQ_VERSION_MINOR - minor version component -# ZeroMQ_VERSION_PATCH - patch version component -# -# -# Accepts the following variables as hints for installation directories: -# -# ZMQ_DIR (CMake var) -# AlFa_DIR (CMake var) -# SIMPATH (CMake var) -# ZEROMQ_ROOT (CMake var, ENV var) -# -# -# If the above variables are not defined, or if ZeroMQ could not be found there, -# it will look for it in the system directories. Custom ZeroMQ installations -# will always have priority over system ones. -# - -if(NOT ZeroMQ_FIND_QUIETLY) - message(STATUS "Looking for ZeroMQ") -endif() - -if(DEFINED ENV{ZEROMQ_ROOT}) - set(ZEROMQ_ROOT $ENV{ZEROMQ_ROOT}) -endif() - -find_path(ZeroMQ_INCLUDE_DIR NAMES "zmq.h" "zmq_utils.h" - HINTS "${ZMQ_DIR}/include" - "${AlFa_DIR}/include" - "${SIMPATH}/include" - "${ZEROMQ_ROOT}/include" - DOC "ZeroMQ include directories" -) - -find_library(ZeroMQ_LIBRARY_SHARED NAMES "libzmq.dylib" "libzmq.so" - HINTS "${ZMQ_DIR}/lib" - "${AlFa_DIR}/lib" - "${SIMPATH}/lib" - "${ZEROMQ_ROOT}/lib" - DOC "Path to libzmq.dylib or libzmq.so" -) - -find_library(ZeroMQ_LIBRARY_STATIC NAMES "libzmq.a" - HINTS "${ZMQ_DIR}/lib" - "${AlFa_DIR}/lib" - "${SIMPATH}/lib" - "${ZEROMQ_ROOT}/lib" - DOC "Path to libzmq.a" -) - -if(ZeroMQ_INCLUDE_DIR AND ZeroMQ_LIBRARY_SHARED AND ZeroMQ_LIBRARY_STATIC) - set(ZeroMQ_FOUND TRUE) -else() - set(ZeroMQ_FOUND FALSE) -endif() - -set(ERROR_STRING "Looking for ZeroMQ - NOT FOUND") - -if(ZeroMQ_FOUND) - find_file(ZeroMQ_HEADER_FILE "zmq.h" - ${ZeroMQ_INCLUDE_DIR} - NO_DEFAULT_PATH - ) - if (DEFINED ZeroMQ_HEADER_FILE) - file(READ "${ZeroMQ_HEADER_FILE}" _ZeroMQ_HEADER_FILE_CONTENT) - string(REGEX MATCH "#define ZMQ_VERSION_MAJOR ([0-9])" _MATCH "${_ZeroMQ_HEADER_FILE_CONTENT}") - set(ZeroMQ_VERSION_MAJOR ${CMAKE_MATCH_1}) - string(REGEX MATCH "#define ZMQ_VERSION_MINOR ([0-9])" _MATCH "${_ZeroMQ_HEADER_FILE_CONTENT}") - set(ZeroMQ_VERSION_MINOR ${CMAKE_MATCH_1}) - string(REGEX MATCH "#define ZMQ_VERSION_PATCH ([0-9])" _MATCH "${_ZeroMQ_HEADER_FILE_CONTENT}") - set(ZeroMQ_VERSION_PATCH ${CMAKE_MATCH_1}) - set(ZeroMQ_VERSION "${ZeroMQ_VERSION_MAJOR}.${ZeroMQ_VERSION_MINOR}.${ZeroMQ_VERSION_PATCH}") - if(DEFINED ZeroMQ_FIND_VERSION AND ZeroMQ_VERSION VERSION_LESS ZeroMQ_FIND_VERSION) - set(ZeroMQ_FOUND FALSE) - set(ERROR_STRING "Looking for ZeroMQ - Installed version ${ZeroMQ_VERSION} does not meet the minimum required version ${ZeroMQ_FIND_VERSION}") - endif () - unset(ZeroMQ_HEADER_FILE CACHE) - endif () - - add_library(ZeroMQ SHARED IMPORTED) - set_target_properties(ZeroMQ PROPERTIES - IMPORTED_LOCATION ${ZeroMQ_LIBRARY_SHARED} - INTERFACE_INCLUDE_DIRECTORIES ${ZeroMQ_INCLUDE_DIR} - ) -endif() - -if(ZeroMQ_FOUND) - set(ZeroMQ_LIBRARIES "${ZeroMQ_LIBRARY_STATIC};${ZeroMQ_LIBRARY_SHARED}") - if(NOT ZeroMQ_FIND_QUIETLY) - message(STATUS "Looking for ZeroMQ - Found ${ZeroMQ_INCLUDE_DIR}") - message(STATUS "Looking for ZeroMQ - Found version ${ZeroMQ_VERSION}") - endif(NOT ZeroMQ_FIND_QUIETLY) - - mark_as_advanced( - ZeroMQ_LIBRARIES - ZeroMQ_LIBRARY_SHARED - ZeroMQ_LIBRARY_STATIC - ZeroMQ_VERSION_MAJOR - ZeroMQ_VERSION_MINOR - ZeroMQ_VERSION_PATCH - ) - - # add target - if(NOT TARGET ZeroMQ::ZeroMQ) - add_library(ZeroMQ::ZeroMQ INTERFACE IMPORTED) - set_target_properties(ZeroMQ::ZeroMQ PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${ZeroMQ_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES "${ZeroMQ_LIBRARIES}" - ) - endif() -else() - if(ZeroMQ_FIND_REQUIRED) - message(FATAL_ERROR "${ERROR_STRING}") - else() - if(NOT ZeroMQ_FIND_QUIETLY) - message(STATUS "${ERROR_STRING}") - endif(NOT ZeroMQ_FIND_QUIETLY) - endif() -endif() - -unset(ERROR_STRING) -unset(ZEROMQ_ROOT) - - diff --git a/cmake/GenerateUniquePort.cmake b/cmake/GenerateUniquePort.cmake new file mode 100644 index 0000000000..ae1ade97e1 --- /dev/null +++ b/cmake/GenerateUniquePort.cmake @@ -0,0 +1,27 @@ +# Copyright CERN and copyright holders of ALICE O2. This software is distributed +# under the terms of the GNU General Public License v3 (GPL Version 3), copied +# verbatim in the file "COPYING". +# +# See http://alice-o2.web.cern.ch/license for full licensing information. +# +# In applying this license CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization or +# submit itself to any jurisdiction. + +include_guard() + + +# +# o2_generate_unique_port(VAR_NAME) generates a random TCP/UDP port number in +# the range 30000 - 59999 and puts it under the name specified in the first argument. + +function(o2_generate_unique_port VAR_NAME) + + string(RANDOM LENGTH 1 ALPHABET 345 FIRST_DIGIT) + string(RANDOM LENGTH 3 ALPHABET 0123456789 OTHER_DIGITS) + string(RANDOM LENGTH 1 ALPHABET 02468 LAST_DIGIT) + string(CONCAT ${VAR_NAME} ${FIRST_DIGIT} ${OTHER_DIGITS} ${LAST_DIGIT}) + + set(${VAR_NAME} ${${VAR_NAME}} PARENT_SCOPE) + +endfunction() diff --git a/cmake/QCModulesUtils.cmake b/cmake/QCModulesUtils.cmake deleted file mode 100644 index 87aaaec0de..0000000000 --- a/cmake/QCModulesUtils.cmake +++ /dev/null @@ -1,70 +0,0 @@ -#------------------------------------------------------------------------------ -# CHECK_VARIABLE -macro(CHECK_VARIABLE VARIABLE_NAME ERROR_MESSAGE) - if (NOT ${VARIABLE_NAME}) - message(FATAL_ERROR "${ERROR_MESSAGE}") - endif (NOT ${VARIABLE_NAME}) -endmacro(CHECK_VARIABLE) - -#------------------------------------------------------------------------------ -# GENERATE_ROOT_DICT -# Generate ROOT dictionary. -# The bulk of the code is an ugly mess that will redeclare the dependencies -# because we need to set INCLUDE_DIRECTORIES. -# arg MODULE_NAME - Module name -# arg LINKDEF - Path to the linkdef -# arg DICT_CLASS - name of the class that is generated without extension -function(GENERATE_ROOT_DICT) - cmake_parse_arguments( - PARSED_ARGS - "" # bool args - "MODULE_NAME;LINKDEF;DICT_CLASS" # mono-valued arguments - "" # multi-valued arguments - ${ARGN} # arguments - ) - CHECK_VARIABLE(PARSED_ARGS_MODULE_NAME "You must provide a module name") - CHECK_VARIABLE(PARSED_ARGS_LINKDEF "You must provide a path to a linkdef") - CHECK_VARIABLE(PARSED_ARGS_DICT_CLASS "You must provide a name for the generated class") - # ROOT dictionary - # the following root macros expect include dirs to be set as directory property - # TODO how to generate this automatically ? ? ? - # https://gitlab.kitware.com/cmake/cmake/issues/12435 - # Maybe using this property ? - # get_target_property(asdf ${MODULE_NAME} INCLUDE_DIRECTORIES) - # message("test : ${asdf}") - get_directory_property(include_dirs INCLUDE_DIRECTORIES) - list(APPEND include_dirs "${CMAKE_CURRENT_SOURCE_DIR}/include") # this module - list(APPEND include_dirs "${CMAKE_CURRENT_SOURCE_DIR}/../../Framework/include") # the framework - get_target_property(qc_inc_dir QualityControl INTERFACE_INCLUDE_DIRECTORIES) - list(APPEND include_dirs "${qc_inc_dir}") - get_target_property(o2_inc_dir AliceO2::AliceO2 INTERFACE_INCLUDE_DIRECTORIES) - list(APPEND include_dirs "${o2_inc_dir}") - get_target_property(config_inc_dir AliceO2::Configuration INTERFACE_INCLUDE_DIRECTORIES) - list(APPEND include_dirs "${config_inc_dir}") - list(APPEND include_dirs "${FAIRROOT_INCLUDE_DIR}") - list(APPEND include_dirs "${FairMQ_INCDIR}") - list(APPEND include_dirs "${FairMQ_INCDIR}/fairmq") - get_target_property(fairlogger_inc_dir FairLogger::FairLogger INTERFACE_INCLUDE_DIRECTORIES) - list(APPEND include_dirs "${fairlogger_inc_dir}") - get_target_property(arrow_inc_dir Arrow::Arrow INTERFACE_INCLUDE_DIRECTORIES) - list(APPEND include_dirs "${arrow_inc_dir}") - get_target_property(common_inc_dir AliceO2::Common INTERFACE_INCLUDE_DIRECTORIES) - list(APPEND include_dirs "${common_inc_dir}") - get_target_property(boost_inc_dir Boost::container INTERFACE_INCLUDE_DIRECTORIES) - list(APPEND include_dirs "${boost_inc_dir}") - list(REMOVE_DUPLICATES include_dirs) - include_directories(${include_dirs}) - - set(dict_src ${CMAKE_CURRENT_BINARY_DIR}/${PARSED_ARGS_DICT_CLASS}.cxx) - set_source_files_properties(${dict_src} PROPERTIES COMPILE_FLAGS "-Wno-old-style-cast") - set_source_files_properties(${dict_src} PROPERTIES GENERATED TRUE) - - ROOT_GENERATE_DICTIONARY("${PARSED_ARGS_DICT_CLASS}" ${HEADERS} LINKDEF ${PARSED_ARGS_LINKDEF}) - - # TODO review how and what to install for dictionary - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lib${PARSED_ARGS_MODULE_NAME}Dict_rdict.pcm - ${CMAKE_CURRENT_BINARY_DIR}/lib${PARSED_ARGS_MODULE_NAME}Dict.rootmap - DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -endfunction() - diff --git a/doc/Advanced.md b/doc/Advanced.md deleted file mode 100644 index c502482d2a..0000000000 --- a/doc/Advanced.md +++ /dev/null @@ -1,304 +0,0 @@ - -# Advanced topics - - - - - * [Advanced topics](#advanced-topics) - * [Data Inspector](#data-inspector) - * [Prerequisite](#prerequisite) - * [Compilation](#compilation) - * [Execution](#execution) - * [Configuration](#configuration) - * [Use MySQL as QC backend](#use-mysql-as-qc-backend) - * [Local CCDB setup](#local-ccdb-setup) - * [Local QCG (QC GUI) setup](#local-qcg-qc-gui-setup) - * [Information Service](#information-service) - * [Usage](#usage) - * [Configuration files details](#configuration-files-details) - - - - - - -[← Go back to Modules Development](ModulesDevelopment.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) - -## Data Inspector - -This is a GUI to inspect the data coming out of the DataSampling, in -particular the Readout. - -![alt text](doc/images/dataDump.png) - -### Prerequisite - -If not already done, install GLFW for your platform. On CC7 install `glfw-devel` from epel repository : `sudo yum install glfw-devel --enablerepo=epel` - -### Compilation - -Build the QualityControl as usual. - -### Execution - -To monitor the readout, 3 processes have to be started : the Readout, -the Data Sampling and the Data Inspector. - -First make sure that the datasampling is enabled in the readout : -``` -[consumer-data-sampling] -consumerType=DataSampling -enabled=1 -``` - -In 3 separate terminals, do respectively - -1. `readout.exe file:///absolute/path/to/config.cfg` -2. `qcRunReadoutForDataDump --batch` -3. `dataDump --mq-config $QUALITYCONTROL_ROOT/etc/dataDump.json --id dataDump --control static` - -### Configuration - -__Fraction of data__ -The Data Sampling tries to take 100% of the events by default. -Edit `$QUALITYCONTROL_ROOT/etc/readoutForDataDump.json` -to change it. Look for the parameter `fraction` that is set to 1. - -__Port__ -The Data Sampling sends data to the GUI via the port `26525`. -If this port is not free, edit the config file `$QUALITYCONTROL_ROOT/etc/readoutForDataDump.json` -and `$QUALITYCONTROL_ROOT/etc/dataDump.json`. - -## Use MySQL as QC backend - -1. Install the MySQL/MariaDB development package - * CC7 : `sudo yum install mariadb-server` - * Mac (or download the dmg from Oracle) : `brew install mysql` - -2. Rebuild the QualityControl (so that the mysql backend classes are compiled) - -3. Start and populate database : - - ``` - sudo systemctl start mariadb # for CC7, check for your specific OS - alienv enter qcg/latest - qcDatabaseSetup.sh - ``` - -## Local CCDB setup - -Having a central ccdb for test (ccdb-test) is handy but also means that everyone can access, modify or delete the data. If you prefer to have a local instance of the CCDB, for example in your lab or on your development machine, follow these instructions. - -1. Download the local repository service from http://alimonitor.cern.ch/download/local.jar - -2. The service can simply be run with - `java -jar local.jar` - -It will start listening by default on port 8080. This can be changed either with the java parameter “tomcat.port” or with the environment variable “TOMCAT_PORT”. Similarly the default listening address is 127.0.0.1 and it can be changed with the java parameter “tomcat.address” or with the environment variable “TOMCAT_ADDRESS” to something else (for example ‘*’ to listen on all interfaces). - -By default the local repository is located in /tmp/QC (or java.io.tmpdir/QC to be more precise). You can change this location in a similar way by setting the java parameter “file.repository.location” or the environment variable “FILE_REPOSITORY_LOCATION”. - -The address of the CCDB will have to be updated in the Tasks config file. - -At the moment, the description of the REST api can be found in this document : https://docs.google.com/presentation/d/1PJ0CVW7QHgnFzi0LELc06V82LFGPgmG3vsmmuurPnUg - -## Local QCG (QC GUI) setup - -To install and run the QCG locally, and its fellow process tobject2json, please follow these instructions : https://github.com/AliceO2Group/WebUi/tree/dev/QualityControl#run-qcg-locally - -## Information Service - -The information service publishes information about the tasks currently -running and the objects they publish. It is needed by some GUIs, or -other clients. - -By default it will publish on port 5561 the json description of a task -when it is updated. A client can also request on port 5562 the information -about a specific task or about all the tasks, by passing the name of the -task as a parameter or "all" respectively. - -The JSON for a task looks like : -``` -{ - "name": "myTask_1", - "objects": [ - { - "id": "array-0" - }, - { - "id": "array-1" - }, - { - "id": "array-2" - }, - { - "id": "array-3" - }, - { - "id": "array-4" - } - ] -} -``` - -The JSON for all tasks looks like : -``` -{ - "tasks": [ - { - "name": "myTask_1", - "objects": [ - { - "id": "array-0" - }, - { - "id": "array-1" - } - ] - }, - { - "name": "myTask_2", - "objects": [ - { - "id": "array-0" - }, - { - "id": "array-1" - } - ] - } - ] -} -``` -### Usage -``` -qcInfoService -c /absolute/path/to/InformationService.json -n information_service \ - --id information_service --mq-config /absolute/path/to/InformationService.json -``` - -The `qcInfoService` can provide fake data from a file. This is useful -to test the clients. Use the option `--fake-data-file` and provide the -absolute path to the file. The file `infoServiceFake.json` is provided -as an example. - -To check what is being output by the Information Service, one can -run the InformationServiceDump : -``` -qcInfoServiceDump -c /absolute/path/to/InformationService.json -n information_service_dump \ - --id information_service_dump --mq-config /absolute/path/to/InformationService.json - --request-task myTask1 -``` -The last parameter can be omitted to receive information about all tasks. - -## Configuration files details - -TODO : this is to be rewritten once we stabilize the configuration file format. - -TODO : task, checker, general parameters - -TODO review : - -The QC requires a number of configuration items. An example config file is -provided in the repo under the name _example-default.json_. Moreover, the -outgoing channel over which histograms are sent is defined in a JSON -file such as the one called _alfa.json_ in the repo. - -**QC tasks** must be defined in the configuration within the element `qc/tasks_config` : - -``` - "tasks_config": { - "myTask_1": { - "taskDefinition": "taskDefinition_1" - }, - ... -``` - -We use an indirect system to allow multiple tasks to share -most of their definition (`myTask_1` uses defintion `taskDefinition_1`): - -``` - ... - "taskDefinition_1": { - "className": "o2::quality_control_modules::example::ExampleTask", - "moduleName": "QcExample", - "cycleDurationSeconds": "10", - "maxNumberCycles": "-1" - }, -``` -The `moduleName` refers to which library contains the class `className`. - -The data source for the task is defined in the section `qc/config/DataSampling` : - -``` -{ - "qc": { - "config": { - "DataSampling": { - "implementation": "MockSampler" - }, -... -``` - -Implementation can be `FairSampler` to get data from readout or -`MockSampler` to get random data. - -The JSON `alfa.json` file contains a typical FairMQ device definition. One can - change the port or the address there: -``` -{ - "fairMQOptions": { - "devices": [ - { - "id": "myTask_1", - "channels": [ - { - "name": "data-out", - "sockets": [ - { - "type": "pub", - "method": "bind", - "address": "tcp://*:5556", - "sndBufSize": 100, - "rcvBufSize": 100, - "rateLogging": 0 - } - ] - } - ] - } - ] - } -} -``` - -**QC checkers** are defined in the config file in section `checkers_config`: - -``` - "checkers_config": { - "checker_0": { - "broadcast": "0", - "broadcastAddress": "tcp://*:5600", - "id": "0" - }, - ... -``` - -Here, `checker_0` is not going to broadcast its data but just store -it in the database. - -And for the time, the mapping between checkers and tasks must be explicit : - -``` - ... - "numberCheckers": "1", - "numberTasks": "1", - "tasksAddresses": "tcp://localhost:5556,tcp://localhost:5557,tcp://localhost:5558,tcp://localhost:5559", -``` -It is needed for the time being because we don't have an information service. - -There are configuration items for many other aspects, for example the -database connection, the monitoring or the data sampling. - ---- - -[← Go back to Modules Development](ModulesDevelopment.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) \ No newline at end of file diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 22384203d0..95f25575c7 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -8,7 +8,6 @@ if (DOXYGEN_FOUND) set(DOXYGEN_EXCLUDE "${CMAKE_SOURCE_DIR}/Framework/src/imgui") set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "README.md") set(DOXYGEN_MARKDOWN_SUPPORT "YES") - set(DOXYGEN_PROJECT_LOGO "${CMAKE_SOURCE_DIR}/doc/images/o2_logo.png") set(DOXYGEN_GENERATE_TREEVIEW YES) set(DOXYGEN_HIDE_UNDOC_RELATIONS NO) set(DOXYGEN_HAVE_DOT YES) @@ -22,16 +21,48 @@ if (DOXYGEN_FOUND) set(DOXYGEN_DOT_TRANSPARENT YES) set(DOXYGEN_INPUT_FILTER "${CMAKE_SOURCE_DIR}/doc/scripts/filter_for_doxygen.sh") set(DOXYGEN_FILTER_PATTERNS "*.md") + set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + set(HTML_TIMESTAMP NO) # add target "doc" doxygen_add_docs(doc - ${CMAKE_SOURCE_DIR}/README.md ${CMAKE_SOURCE_DIR}/Framework ${CMAKE_SOURCE_DIR}/Modules/ ${CMAKE_SOURCE_DIR}/Framework/include - ${CMAKE_SOURCE_DIR}/doc WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Generating doxygen documentation for ${PROJECT_NAME}" ) + # installation + option(DOC_INSTALL "Install the documentation when calling \"make install\"" OFF) + if(DOC_INSTALL) + # doxygen + message(STATUS "Documentation will be installed but you *must* run `make doc`") + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT doc + PATTERN "*.md5" EXCLUDE PATTERN "*.map" EXCLUDE) + + # all the rest + file(GLOB_RECURSE selected RELATIVE ${CMAKE_SOURCE_DIR}/doc "../*.md" "../*.png" "../*.json") # + # remove unwanted stuff + string(REGEX REPLACE "\.\./.idea/[^;]+;?" "" selected "${selected}") + string(REGEX REPLACE "\.\./.git/[^;]+;?" "" selected "${selected}") + string(REGEX REPLACE "\.\./cmake[^/]*/[^;]+;?" "" selected "${selected}") + string(REGEX REPLACE "\.\./build[^/]*/[^;]+;?" "" selected "${selected}") + # prepare each item for installation + foreach ( file ${selected} ) + get_filename_component( dir ${file} DIRECTORY ) + if(${file} MATCHES "^\\.\\./.*") + string(REGEX REPLACE "\\.\\./([^;]*;?)" "\\1" dir "${dir}") # remove ../ + string(REGEX REPLACE "\\.\\.([^;]*;?)" "\\1" dir "${dir}") # remove .. (in case of README.md stupid CMake) + install( FILES ${file} DESTINATION ${CMAKE_INSTALL_DOCDIR}/${dir} COMPONENT doc) + else() + install( FILES ${file} DESTINATION ${CMAKE_INSTALL_DOCDIR}/doc/${dir} COMPONENT doc) + endif() + endforeach() + + endif(DOC_INSTALL) + + message(STATUS "Documentation will be built in ${CMAKE_CURRENT_BINARY_DIR}") + message(STATUS "Documentation will be installed in ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DOCDIR}") + endif(DOXYGEN_FOUND) diff --git a/doc/Configuration.md b/doc/Configuration.md new file mode 100644 index 0000000000..4baeda8b8b --- /dev/null +++ b/doc/Configuration.md @@ -0,0 +1,723 @@ + +Configuration reference +--- + + + + + * [Global configuration structure](#global-configuration-structure) + * [Common configuration](#common-configuration) + * [QC Tasks configuration](#qc-tasks-configuration) + * [QC Checks configuration](#qc-checks-configuration) + * [QC Aggregators configuration](#qc-aggregators-configuration) + * [QC Post-processing configuration](#qc-post-processing-configuration) + * [External tasks configuration](#external-tasks-configuration) + * [Merging multiple configuration files into one](#merging-multiple-configuration-files-into-one) + * [Templating config files](#templating-config-files) + * [Definition and access of simple user-defined task configuration ("taskParameters")](#definition-and-access-of-simple-user-defined-task-configuration-taskparameters) + * [Definition and access of user-defined configuration ("extendedTaskParameters")](#definition-and-access-of-user-defined-configuration-extendedtaskparameters) + + + +The QC requires a number of configuration items. An example config file is +provided in the repo under the name _example-default.json_. This is a quick reference for all the parameters. + +## Global configuration structure + +This is the global structure of the configuration in QC. + +```json +{ + "qc": { + "config": { + + }, + "tasks": { + + }, + "externalTasks": { + + }, + "checks": { + + }, + "aggregators": { + + }, + "postprocessing": { + + } + }, + "dataSamplingPolicies": [ + + ] +} +``` + +There are six QC-related components: +* "config" - contains global configuration of QC which apply to any component. It is required in any configuration + file. +* "tasks" - contains declarations of QC Tasks. It is mandatory for running topologies with Tasks and + Checks. +* "externalTasks" - contains declarations of external devices which sends objects to the QC to be checked and stored. +* "checks" - contains declarations of QC Checks. It is mandatory for running topologies with + Tasks and Checks. +* "aggregators" - contains declarations of QC Aggregators. It is not mandatory. +* "postprocessing" - contains declarations of PostProcessing Tasks. It is only needed only when Post-Processing is + run. + +The configuration file can also include a list of Data Sampling Policies. +Please refer to the [Data Sampling documentation](https://github.com/AliceO2Group/AliceO2/tree/dev/Utilities/DataSampling) to find more information. + +## Common configuration + +This is how a typical "config" structure looks like. Each configuration element is described with a relevant comment +afterwards. The `"": "",` formatting is to keep the JSON structure valid. Please note that these comments +should not be present in real configuration files. + +```json +{ + "qc": { + "config": { + "database": { "": "Configuration of a QC database (the place where QC results are stored).", + "username": "qc_user", "": "Username to log into a DB. Relevant only to the MySQL implementation.", + "password": "qc_user", "": "Password to log into a DB. Relevant only to the MySQL implementation.", + "name": "quality_control", "": "Name of a DB. Relevant only to the MySQL implementation.", + "implementation": "CCDB", "": "Implementation of a DB. It can be CCDB, or MySQL (deprecated).", + "host": "ccdb-test.cern.ch:8080", "": "URL of a DB.", + "maxObjectSize": "2097152", "": "[Bytes, default=2MB] Maximum size allowed, larger objects are rejected." + }, + "Activity": { "": ["Configuration of a QC Activity (Run). DO NOT USE IN PRODUCTION! " ], + "number": "42", "": "Activity number. ", + "type": "PHYSICS", "": "Activity type.", + "periodName": "", "": "Period name - e.g. LHC22c, LHC22c1b_test", + "passName": "", "": "Pass type - e.g. spass, cpass1", + "provenance": "qc", "": "Provenance - qc or qc_mc depending whether it is normal data or monte carlo data", + "start" : "0", "": "Activity start time in ms since epoch. One can use it as a filter in post-processing", + "end" : "1234", "": "Activity end time in ms since epoch. One can use it as a filter in post-processing", + "beamType" : "pp", "": "Beam type: `pp`, `PbPb`, `pPb` ", + "partitionName" : "", "": "Partition name", + "fillNumber" : "123", "": "Fill Number", + "originalRunNumber" : "100", "": "When it is a REPLAY run, this contains the original run number" + }, + "monitoring": { "": "Configuration of the Monitoring library.", + "url": "infologger:///debug?qc", "": ["URI to the Monitoring backend. Refer to the link below for more info:", + "https://github.com/AliceO2Group/Monitoring#monitoring-instance"] + }, + "consul": { "": "Configuration of the Consul library (used for Service Discovery).", + "url": "", "": "URL of the Consul backend" + }, + "conditionDB": { "": ["Configuration of the Conditions and Calibration DataBase (CCDB).", + "Do not mistake with the CCDB which is used as QC repository."], + "url": "ccdb-test.cern.ch:8080", "": "URL of a CCDB" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", "": "Set to 1 to discard debug and trace messages (default: false)", + "filterDiscardLevel": "2", "": "Message at this level or above are discarded (default: 21 - Trace)", + "filterDiscardFile": "", "": ["If set, the discarded messages will go to this file (default: )", + "The keyword _ID_, if used, is replaced by the device ID."], + "filterRotateMaxBytes": "", "": "Maximum size of the discard file.", + "filterRotateMaxFiles": "", "": "Maximum number of discard files.", + "debugInDiscardFile": "false", "": "If true, the debug discarded messages go to the file (default: false)." + }, + "bookkeeping": { "": "Configuration of the bookkeeping (optional)", + "url": "localhost:4001", "": "Url of the bookkeeping API (port is usually different from web interface)" + }, + "kafka": { + "url": "kafka-broker:123", "": "url of the kafka broker", + "topicAliecsRun":"aliecs.run", "": "the topic where AliECS publishes Run Events, 'aliecs.run' by default" + }, + "postprocessing": { "": "Configuration parameters for post-processing", + "periodSeconds": 10.0, "": "Sets the interval of checking all the triggers. One can put a very small value", + "": "for async processing, but use 10 or more seconds for synchronous operations", + "matchAnyRunNumber": "false", "": "Forces post-processing triggers to match any run, useful when running with AliECS" + } + } + } +} +``` + +### Common configuration in production + +In production at P2 and in staging, some common items are defined globally in the file `QC/general-config-params`: + +* QCDB +* monitoring +* consul +* conditionDB +* bookkeeping + +It is mandatory to use them by including the file: + +``` +"config": { + {% include "QC/general-config-params" %} + }, +``` + +Other configuration items can still be added in your files as such (note the comma after the inclusion) : + +``` + "config": { + {% include "QC/general-config-params" %}, + "infologger": { + "filterDiscardDebug": "false", + "filterDiscardLevel": "22" + }, + "postprocessing": { + "matchAnyRunNumber": "true" + } + }, +``` + +Please, do not use `Activity` in production ! + +## QC Tasks configuration + +Below the full QC Task configuration structure is described. Note that more than one task might be declared inside in +the "tasks" path. + + ```json +{ + "qc": { + "tasks": { + "QcTaskID": { "": ["ID of the QC Task. Less than 14 character names are preferred.", + "If \"taskName\" is empty or missing, the ID is used"], + "active": "true", "": "Activation flag. If not \"true\", the Task will not be created.", + "taskName": "MyTaskName", "": ["Name of the task, used e.g. in the QCDB. If empty, the ID is used.", + "Less than 14 character names are preferred."], + "className": "namespace::of::Task", "": "Class name of the QC Task with full namespace.", + "moduleName": "QcSkeleton", "": "Library name. It can be found in CMakeLists of the detector module.", + "detectorName": "TST", "": "3-letter code of the detector.", + "critical": "true", "": "if false the task is allowed to die without stopping the workflow, default: true", + "cycleDurationSeconds": "60", "": "Cycle duration (how often objects are published), 10 seconds minimum.", + "": "The first cycle will be randomly shorter. ", + "": "Alternatively, one can specify different cycle durations for different periods. The last item in cycleDurations will be used for the rest of the duration whatever the period. The first cycle will be randomly shorter.", + "cycleDurations": [ + {"cycleDurationSeconds": 60, "validitySeconds": 35}, + {"cycleDurationSeconds": 120, "validitySeconds": 1} + ], + "maxNumberCycles": "-1", "": "Number of cycles to perform. Use -1 for infinite.", + "disableLastCycle": "true", "": "Last cycle, upon EndOfStream, is not published. (default: false)", + "dataSources": [{ "": "Data sources of the QC Task. The following are supported", + "type": "dataSamplingPolicy", "": "Type of the data source", + "name": "tst-raw", "": "Name of Data Sampling Policy" + }, { + "type": "direct", "": "connects directly to another output", + "query": "raw:TST/RAWDATA/0", "": "input spec query, as expected by DataDescriptorQueryBuilder" + }], + "taskParameters": { "": "User Task parameters which are then accessible as a key-value map.", + "myOwnKey": "myOwnValue", "": "An example of a key and a value. Nested structures are not supported" + }, + "resetAfterCycles" : "0", "": "Makes the Task or Merger reset MOs each n cycles.", + "": "0 (default) means that MOs should cover the full run.", + "location": "local", "": ["Location of the QC Task, it can be local or remote. Needed only for", + "multi-node setups, not respected in standalone development setups."], + "localMachines": [ "", "List of local machines where the QC task should run. Required only", + "", "for multi-node setups. An alias can be used if merging deltas.", + "o2flp1", "", "Hostname of a local machine.", + "o2flp2", "", "Hostname of a local machine." + ], + "remoteMachine": "o2qc1", "": "Remote QC machine hostname. Required only for multi-node setups with EPNs.", + "remotePort": "30432", "": "Remote QC machine TCP port. Required only for multi-node setups with EPNs.", + "localControl": "aliecs", "": ["Control software specification, \"aliecs\" (default) or \"odc\").", + "Needed only for multi-node setups."], + "mergingMode": "delta", "": "Merging mode, \"delta\" (default) or \"entire\" objects are expected", + "mergerCycleMultiplier": "1", "": "Multiplies the Merger cycle duration with respect to the QC Task cycle" + "mergersPerLayer": [ "3", "1" ], "": "Defines the number of Mergers per layer, the default is [\"1\"]", + "grpGeomRequest" : { "": "Requests to retrieve GRP objects, then available in GRPGeomHelper::instance()", + "geomRequest": "None", "": "Available options are \"None\", \"Aligned\", \"Ideal\", \"Alignements\"", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "false", + "needPropagatorD": "false" + }, + "globalTrackingDataRequest": { "": "A helper to add tracks or clusters to inputs of the task", + "canProcessTracks" : "ITS,ITS-TPC", "": "tracks that the QC task can process, usually should not change", + "requestTracks" : "ITS,TPC-TRD", "": ["tracks that the QC task should process, TPC-TRD will not be", + "requested, as it is not listed in the previous parameter"], + "canProcessClusters" : "TPC", "": "clusters that the QC task can process", + "requestClusters" : "TPC", "": "clusters that the QC task should process", + "mc" : "false", "": "mc boolean flag for the data request" + } + } + } + } +} +``` + +## QC Checks configuration + +Below the full QC Checks configuration structure is described. Note that more than one check might be declared inside in +the "checks" path. Please also refer to [the Checks documentation](ModulesDevelopment.md#configuration) for more details. + + ```json +{ + "qc": { + "checks": { + "MeanIsAbove": { "": ["ID of the Check. Less than 12 character names are preferred.", + "If \"checkName\" is empty or missing, the ID is used"], + "active": "true", "": "Activation flag. If not \"true\", the Check will not be run.", + "checkName": "MeanIsAbove", "": ["Name of the check, used e.g. in the QCDB. If empty, the ID is used.", + "Less than 12 character names are preferred."], + "className": "ns::of::Check", "": "Class name of the QC Check with full namespace.", + "moduleName": "QcCommon", "": "Library name. It can be found in CMakeLists of the detector module.", + "detectorName": "TST", "": "3-letter code of the detector.", + "policy": "OnAny", "": ["Policy which determines when MOs should be checked. See the documentation", + "of Checks for the list of available policies and their behaviour."], + "exportToBookkeeping": "true","": "Flag that toggles reporting of QcFlags created by this check into BKP via gRPC.", + "When not presented it equals to "false"" + "dataSource": [{ "": "List of data source of the Check.", + "type": "Task", "": "Type of the data source, \"Task\", \"ExternalTask\" or \"PostProcessing\"", + "name": "myTask_1", "": "Name of the Task", + "MOs": [ "example" ], "": ["List of MOs to be checked. " + "Can be omitted to mean \"all\"."] + }], + "checkParameters": { "": "User Check parameters which are then accessible as a key-value map.", + "myOwnKey": "myOwnValue", "": "An example of a key and a value. Nested structures are not supported" + } + } + } + } +} +``` + +## QC Aggregators configuration + +Below the full QC Aggregators configuration structure is described. Note that more than one aggregator might be declared inside in +the "aggregators" path. Please also refer to [the Aggregators documentation](ModulesDevelopment.md#quality-aggregation) for more details. + +```json +{ + "qc": { + "aggregators": { + "MyAggregator1": { "": "ID of the Aggregator. Less than 12 character names are preferred.", + "active": "true", "": "Activation flag. If not \"true\", the Aggregator will not be run.", + "aggregatorName" : "MyAggregator1", "": ["Name of the Aggregator, used e.g. in the QCDB. If empty, the ID is used.", + "Less than 12 character names are preferred."], + "className": "ns::of::Aggregator", "": "Class name of the QC Aggregator with full namespace.", + "moduleName": "QcCommon", "": "Library name. It can be found in CMakeLists of the detector module.", + "policy": "OnAny", "": ["Policy which determines when QOs should be aggregated. See the documentation", + "of Aggregators for the list of available policies and their behaviour."], + "exportToBookkeeping": "true", "": "Flag that toggles reporting of QcFlags created by all of the sources into + "the BKP via gRPC. "When not presented it equals to "false"" + "detectorName": "TST", "": "3-letter code of the detector.", + "dataSource": [{ "": "List of data source of the Aggregator.", + "type": "Check",, "": "Type of the data source: \"Check\" or \"Aggregator\"", + "name": "dataSizeCheck", "": "Name of the Check or Aggregator", + "QOs": ["newQuality", "another"], "": ["List of QOs to be checked.", + "Can be omitted for Checks", + "that publish a single Quality or to mean \"all\"."] + }] + } + } + } +} +``` + +## QC Post-processing configuration + +Below the full QC Post-processing (PP) configuration structure is described. Note that more than one PP Task might be +declared inside in the "postprocessing" path. Please also refer to [the Post-processing documentation](PostProcessing.md) for more details. + +```json +{ + "qc": { + "postprocessing": { + "ExamplePostprocessingID": { "": "ID of the PP Task.", + "active": "true", "": "Activation flag. If not \"true\", the PP Task will not be run.", + "taskName": "MyPPTaskName", "": ["Name of the task, used e.g. in the QCDB. If empty, the ID is used.", + "Less than 14 character names are preferred."], + "className": "namespace::of::PPTask", "": "Class name of the PP Task with full namespace.", + "moduleName": "QcSkeleton", "": "Library name. It can be found in CMakeLists of the detector module.", + "detectorName": "TST", "": "3-letter code of the detector.", + "initTrigger": [ "", "List of initialization triggers", + "userorcontrol", "", "An example of an init trigger" + ], + "updateTrigger": [ "", "List of update triggers", + "10min", "", "An example of an update trigger" + ], + "stopTrigger": [ "", "List of stop triggers", + "userorcontrol", "", "An example of a stop trigger" + ], + "validityFromLastTriggerOnly": "false", "": "If true, the output objects will use validity of the last trigger,", + "": "otherwise a union of all triggers' validity is used by default.", + "sourceRepo": { "": "It allows to specify a different repository for the input objects.", + "implementation": "CCDB", + "host": "another-test.cern.ch:8080" + } + } + } + } +} +``` + +## External tasks configuration + +Below the external task configuration structure is described. Note that more than one external task might be declared inside in the "externalTasks" path. + +```json +{ + "qc": { + "externalTasks": { + "External-1": { "": "ID of the task", + "active": "true", "": "Activation flag. If not \"true\", the Task will not be created.", + "taskName": "External-1", "": "Name of the task, used e.g. in the QCDB. If empty, the ID is used.", + "query": "External-1:TST/HISTO/0", "": "Query specifying where the objects to be checked and stored are coming from. Use the task name as binding." + } + } + } +} +``` + +## Merging multiple configuration files into one + +To merge multiple QC configuration files into one, one can use `jq` in the following way: + +``` +jq -n 'reduce inputs as $s (input; .qc.tasks += ($s.qc.tasks) | .qc.checks += ($s.qc.checks) | .qc.externalTasks += ($s.qc.externalTasks) | .qc.postprocessing += ($s.qc.postprocessing)| .dataSamplingPolicies += ($s.dataSamplingPolicies))' $QC_JSON_GLOBAL $JSON_FILES > $MERGED_JSON_FILENAME +``` + +However, one should pay attention to avoid duplicate task definition keys (e.g. having RawTask twice, each for a different detector), otherwise only one of them would find its way to a merged file. +In such case, one can add the `taskName` parameter in the body of a task configuration structure to use the preferred name and change the root key to a unique id, which shall be used only for the purpose of navigating a configuration file. +If `taskName` does not exist, it is taken from the root key value. +Please remember to update also the references to the task in other actors which refer it (e.g. in Check's data source). + +These two tasks will **not** be merged correctly: + +```json + "RawTask": { + "className": "o2::quality_control_modules::abc::RawTask", + "moduleName": "QcA", + "detectorName": "A", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-a" + } + } +``` + +```json + "RawTask": { + "className": "o2::quality_control_modules::xyz::RawTask", + "moduleName": "QcB", + "detectorName": "B", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-b" + } + } +``` + +The following tasks will be merged correctly: + +```json + "RawTaskA": { + "taskName": "RawTask", + "className": "o2::quality_control_modules::abc::RawTask", + "moduleName": "QcA", + "detectorName": "A", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-a" + } + } +``` + +```json + "RawTaskB": { + "taskName": "RawTask" + "className": "o2::quality_control_modules::xyz::RawTask", + "moduleName": "QcB", + "detectorName": "B", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-b" + } + } +``` + +The same approach can be applied to other actors in the QC framework, like Checks (`checkName`), Aggregators(`aggregatorName`), External Tasks (`taskName`) and Postprocessing Tasks (`taskName`). + +## Templating config files + +> [!WARNING] +> Templating only works when using aliECS, i.e. in production and staging. + +The templating is provided by a template engine called `jinja`. You can use any of its feature. A couple are described below and should satisfy the vast majority of the needs. + +### Preparation + +> [!IMPORTANT] +> Workflows have already been migrated to apricot. This should not be needed anymore. + +To template a config file, modify the corresponding workflow in `ControlWorkflows`. This is needed because we won't use directly `Consul` but instead go through `apricot` to template it. + +1. Replace `consul-json` by `apricot` +2. Replace `consul_endpoint` by `apricot_endpoint` +3. Make sure to have single quotes around the URI + +Example: + +``` +o2-qc --config consul-json://{{ consul_endpoint }}/o2/components/qc/ANY/any/mch-qcmn-epn-full-track-matching --remote -b +``` + +becomes + +``` +o2-qc --config 'apricot://{{ apricot_endpoint }}/o2/components/qc/ANY/any/mch-qcmn-epn-full-track-matching' --remote -b +``` + +Make sure that you are able to run with the new workflow before actually templating. + +### Include a config file + +To include a config file (e.g. named `mch_digits`) add this line : + +``` +{% include "MCH/mch_digits" %} +``` + +The content of the file `mch_digits` is then copied into the config file. Thus make sure that you include all the commas and stuff. + +#### Configuration files organisation + +Once you start including files, you must put the included files inside the corresponding detector subfolder (that have already been created for you). + +Common config files includes are provided in the `COMMON` subfolder. + +### Conditionals + +The `if` looks like + +``` +{% if [condition] %} … {% endif %} +``` + +The condition probably requires some external info, such as the run type or a detectors list. Thus you must pass the info in the ControlWorkflows. + +It could look like this + +``` +o2-qc --config 'apricot://{{ apricot_endpoint }}/o2/components/qc/ANY/any/tpc-pulser-calib-qcmn?run_type={{ run_type }}' ... +``` + +or + +``` +o2-qc --config 'apricot://{{ apricot_endpoint }}/o2/components/qc/ANY/any/mch-qcmn-epn-full-track-matching?detectors={{ detectors }}' ... +``` + +Then use it like this: + +``` +{% if run_type == "PHYSICS" %} +... +{% endif %} +``` + +or like this respectively: + +``` +{% if "mch" in detectors|lower %} +... +{% endif %} +``` + +### Test and debug + +To see how a config file will look like once templated, simply open a browser at this address: `{{apricot_endpoint}}/components/qc/ANY/any/tpc-pulser-calib-qcmn?process=true` +Replace `{{apricot_endpoint}}` by the value you can find in Consul under `o2/runtime/aliecs/vars/apricot_endpoint` (it is different on staging and prod). +_Note that there is no `o2` in the path!!!_ + +### Example + +We are going to create in staging a small example to demonstrate the above. +First create 2 files if they don't exist yet: + +**o2/components/qc/ANY/any/templating_demo** + +``` +{ + "qc": { + "config": {% include "TST/templating_included" %} + {% if run_type == "PHYSICS" %} ,"aggregators": "included" {% endif %} + } +} +``` + +Here we simply include 1 file from a subfolder and add a piece if a certain condition is successful. + +**o2/components/qc/ANY/any/TST/templating_included** + +``` +{ + bookkeeping": { + "url": "alio2-cr1-hv-web01.cern.ch:4001" + } +} +``` + +And now you can try it out: + +``` +http://alio2-cr1-hv-mvs00.cern.ch:32188/components/qc/ANY/any/templating_demo?process=true +``` + +--> the file is included inside the other. + +``` +[http://alio2-cr1-hv-mvs00.cern.ch:32188/components/qc/ANY/any/templating_demo?process=true](http://alio2-cr1-hv-mvs00.cern.ch:32188/components/qc/ANY/any/templating_demo?process=true&run_type=PHYSICS) +``` + +--> the file is included and the condition is true thus we have an extra line. + +## Definition and access of user-defined task configuration + +### Simple and limited approach ("taskParameters") + +The new, extended, way of defining such parameters, not only in Tasks but also in Checks, Aggregators and PP tasks, +is described in the next section. + +A task can access custom parameters declared in the configuration file at `qc.tasks..taskParameters`. They are stored inside an object of type `CustomParameters` named `mCustomParameters`, which is a protected member of `TaskInterface`. + +The syntax is + +```json + "tasks": { + "QcTask": { + "taskParameters": { + "myOwnKey1": "myOwnValue1" + }, +``` + +It is accessed with : `mCustomParameters["myOwnKey"]`. + +### Advanced approach with beam and run type parametrization ("extendedTaskParameters") + +User code, whether it is a Task, a Check, an Aggregator or a PostProcessing task, can access custom parameters declared in the configuration file. +They are stored inside an object of type `CustomParameters` named `mCustomParameters`, which is a protected member of `TaskInterface`. + +The following table gives the path in the config file and the name of the configuration parameter for the various types of user code: + +| User code | Config File item | +|----------------|--------------------------------------------------------| +| Task | `qc.tasks..extendedTaskParameters` | +| Check | `qc.checks..extendedCheckParameters` | +| Aggregator | `qc.aggregators..extendedAggregatorParameters` | +| PostProcessing | `qc.postprocessing..extendedTaskParameters` | + +The new syntax is + +```json + "tasks": { + "QcTask": { + "extendedTaskParameters": { + "default": { + "default": { + "myOwnKey1": "myOwnValue1", + "myOwnKey2": "myOwnValue2", + "myOwnKey3": "myOwnValue3" + } + }, + "PHYSICS": { + "default": { + "myOwnKey1": "myOwnValue1b", + "myOwnKey2": "myOwnValue2b" + }, + "pp": { + "myOwnKey1": "myOwnValue1c" + }, + "PbPb": { + "myOwnKey1": "myOwnValue1d" + } + }, + "COSMICS": { + "myOwnKey1": "myOwnValue1e", + "myOwnKey2": "myOwnValue2e" + } + }, +``` + +It allows to have variations of the parameters depending on the run and beam types. The proper run types can be found here: [ECSDataAdapters.h](https://github.com/AliceO2Group/AliceO2/blob/dev/DataFormats/Parameters/include/DataFormatsParameters/ECSDataAdapters.h#L54). The `default` can be used +to ignore the run or the beam type. +The beam type comes from the parameter `pdp_beam_type` set by ECS and can be one of the following: `pp`, `PbPb`, `pPb`, `pO`, `OO`, `NeNe`, `cosmic`, `technical`. +See `[readout-dataflow](https://github.com/AliceO2Group/ControlWorkflows/blob/master/workflows/readout-dataflow.yaml)` to verify the possible values. + +The values can be accessed in various ways described in the following sub-sections. + +### Access optional values with or without activity + +The value for the key, runType and beamType is returned if found, or an empty value otherwise. +However, before returning an empty value we try to substitute the runType and the beamType with "default". + +```c++ +// returns an Optional if it finds the key `myOwnKey` for the runType and beamType of the provided activity, +// or if it can find the key with the runType or beamType substituted with "default". +auto param = mCustomParameters.atOptional("myOwnKey1", activity); // activity is "PHYSICS", "PbPb" , returns "myOwnValue1d" +// same but passing directly the run and beam types +auto param = mCustomParameters.atOptional("myOwnKey1", "PHYSICS", "PbPb"); // returns "myOwnValue1d" +// or with only the run type +auto param = mCustomParameters.atOptional("myOwnKey1", "PHYSICS"); // returns "myOwnValue1b" +``` + +### Access values directly specifying the run and beam type or an activity + +The value for the key, runType and beamType is returned if found, or an exception is thrown otherwise.. +However, before throwing we try to substitute the runType and the beamType with "default". + +```c++ +mCustomParameters["myOwnKey"]; // considering that run and beam type are `default` --> returns `myOwnValue` +mCustomParameters.at("myOwnKey"); // returns `myOwnValue` +mCustomParameters.at("myOwnKey", "default"); // returns `myOwnValue` +mCustomParameters.at("myOwnKey", "default", "default"); // returns `myOwnValue` + +mCustomParameters.at("myOwnKey1", "PHYSICS", "pp"); // returns `myOwnValue1c` +mCustomParameters.at("myOwnKey1", "PHYSICS", "PbPb"); // returns `myOwnValue1d` +mCustomParameters.at("myOwnKey2", "COSMICS"); // returns `myOwnValue2e` + +mCustomParameters.at("myOwnKey1", activity); // result will depend on activity +``` + +### Access values and return default if not found + +The correct way of accessing a parameter and to default to a value if it is not there, is the following: + +```c++ + std::string param = mCustomParameters.atOrDefaultValue("myOwnKey1", "1" /*default value*/, "physics", "pp"); + int casted = std::stoi(param); + + // alternatively + std::string param = mCustomParameters.atOrDefaultValue("myOwnKey1", "1" /*default value*/, activity); // see below how to get the activity +``` + +### Find a value + +Finally the way to search for a value and only act if it is there is the following: + +```c++ + if (auto param2 = mCustomParameters.find("myOwnKey1", "physics", "pp"); param2 != cp.end()) { + int casted = std::stoi(param); + } +``` + +### Retrieve the activity in the modules + +In a task, the `activity` is provided in `startOfActivity`. + +In a Check, it is returned by `getActivity()`. + +In an Aggregator, it is returned by `getActivity()`. + +In a postprocessing task, it is available in the objects manager: `getObjectsManager()->getActivity()` + +TODO we miss the definition of the datasampling policies + + +--- + +[← Go back to Post-Processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to QCDB →](QCDB.md) diff --git a/doc/DevelopersTips.md b/doc/DevelopersTips.md index 87cbf19a2d..2109b98a5d 100644 --- a/doc/DevelopersTips.md +++ b/doc/DevelopersTips.md @@ -3,11 +3,108 @@ This is a resource meant for the developers of the QC. Whenever we learn something useful we put it here. It is not sanitized or organized. Just a brain dump. +### Release procedure / check list + + +One can use the script `release.sh` : +```shell +release.sh +``` +It is able to work out what is the release number and will drive the user through all the steps. + +Alternatively do it manually: +1. Update the version number in [CMakeLists.txt](../CMakeLists.txt), commit and push +2. Release in JIRA +2. Prepare the release notes using the commits since the last release in github (see [this template](ReleaseNotesTemplate.md)). +3. Release in github, paste the release notes +4. A PR is automatically created in alidist +5. Once merged, send an email to alice-o2-wp7@cern.ch, alice-o2-qc-contact@cern.ch and alice-dpg-qa-tools@cern.ch to announce the new release. Use the email for the previous release as a template. + +### Create a fix version + +One can use the script `createPatch.sh` : +```shell +patch.sh v1.9.1 # current version +``` + +Alternatively do it manually: +1. checkout last tagged version, e.g. `git checkout v0.26.1` +2. branch, e.g. `git checkout -b branch_v0.26.2` +2. cherry-pick the commit from master, e.g. `git cherry-pick b187ddbe52058d53a9bbf3cbdd53121c6b936cd8` +3. change version in CMakeLists and commit +2. push the branch upstream, e.g. `git push upstream -u branch_v0.26.2` +5. tag, e.g. `git tag -a v0.26.2 -m "v0.26.2"` +4. push the tag upstream, e.g. `git push upstream v0.26.2` +6. Release in github using this tag +4. A PR is automatically created in alidist + +### Where and how to configure the repo_cleaner of the ccdb-test + +The config file is stored in git in the branch `repo_cleaner` (careful not to update in master instead !). Check out the branch, update the file Framework/script/RepoCleaner/config.yaml and commit it. A PR is necessary but in case of emergency, force-merge it. As soon as it is merged, it will be used by the script. + +The config file used to be in `aldaqci@aidrefflp01:~/alice-ccdb/config.yaml` but it is not the case any more. + +The different cleaning policies available at the moment are: 1_per_hour, 1_per_run, last_only, none_kept, skip + +The repo_cleaner is launched every 5 minutes by [Jenkins](https://alijenkins.cern.ch/job/FLP/job/CCDB%20Clean%20up/). + +Documentation of the repo_cleaner can be found [here](../Framework/script/RepoCleaner/README.md). + +### How to backup the local data of the QCDB + +Four things to backup: + +1. database dump +2. postgresql.conf +3. QC directory (this is big) +4. repocleaner.yaml (if not stored in consul) + +``` +ssh root@ali-qcdb + +# you could let them run but the data might not be 100% consistent +systemctl stop ccdb-sql +systemctl stop postgresql + +su - postgres +cd /data/pgsql/backups/ +pg_dumpall -l ccdb -p 5432 -v | pbzip2 > ccdb-dd-mm-yy.sql.bz2 +cp ../data/postgresql.conf postgresql-dd.mm.yy.conf +cp /etc/flp.d/ccdb-sql/repocleaner.yaml repocleaner-dd.mm.yy.yaml +exit + +cd /data/pgsql +cp -r QC QC-dd-mm-yy +``` + +### Automatic QCDB backups + +The script `qcdb-backup.sh` on the qcdb machine is called by a cron every night to create a tarball. +It is then taken by the backup system to the central backup machine before being moved to EOS. + +The script and the crontab installation are stored in ansible (in gitlab). + +### Trick used to load old data +Until version 3 of the class MonitorObject, objects were stored in the repository directly. They are now stored within TFiles. The issue with the former way is that the StreamerInfo are lost. To be able to load old data, the StreamerInfos have been saved in a root file "streamerinfos.root". The CcdbDatabase access class loads this file and the StreamerInfos upon creation which allows for a smooth reading of the old objects. The day we are certain nobody will add objects in the old format and that the old objects have been removed from the database, we can delete this file and remove the loading from CcdbDatabase. Moreover, the following lines can be removed : +``` +// We could not open a TFile we should now try to open an object directly serialized +object = ccdbApi.retrieve(path, metadata, getCurrentTimestamp()); +cout << "We could retrieve the object " << path << " as a streamed object." << endl; +if(object == nullptr) { + return nullptr; +} +``` + +### Doxygen generation + +To generate locally the doxygen doc, do `cd sw/BUILD/QualityControl-latest/QualityControl; make doc`. +It will be available in doc/html, thus to open it quickly do `[xdg-]open doc/html/index.html`. + ### Monitoring debug When we don't see the monitoring data in grafana, here is what to do to pinpoint the source of the problem. -1. `ssh root@aido2mon-gpn.cern.ch` +1. `ssh root@alio2-cr1-mvs01.cern.ch` 2. See if the data reach the servers : 1. `systemctl stop influxdb` 2. `nc -u -l 8087` <-- make sure to use the proper port (cf your monitoring url) @@ -23,6 +120,37 @@ When we don't see the monitoring data in grafana, here is what to do to pinpoint 4. `show series` 5. `select count(*) from cpuUsedPercentage` <-- use the correct metrics name 6. Repeat the last command and see if the number increases. If it increases it denotes that the metrics is stored correctly in the database. If it is the case, the problem lies in your grafana. + +### Monitoring setup for building the grafana dashboard + +1. Go to `root@flptest1` +2. Edit telegraf config file, add following lines: +``` +echo "[[inputs.socket_listener]] + service_address = \"udp://:8089\"" >> /etc/telegraf/telegraf.conf +``` +3. Restart telegraf: `systemctl restart telegraf` +4. Open port +``` +firewall-cmd --add-port=8089/udp --zone=public --permanent +firewall-cmd --reload +``` +5. Go to Grafana (flptest1.cern.ch:3000) and login as `admin`, the password is in here: https://gitlab.cern.ch/AliceO2Group/system-configuration/-/blob/dev/ansible/roles/grafana/vars/main.yml#L2 +6. Go to "Explore" tab (4th icon from top), and select `qc` as data source +7. Run your workflow with `--monitoring-backend influxdb-udp://flptest1.cern.ch:8089 --resources-monitoring 2` +8. The metrics should be there +9. Make a copy of the QC dashboard that you can edit. +10. Set the monitoring url to `"url": "stdout://?qc,influxdb-udp://flptest1.cern.ch:8089"` +11. Once the dashboard is ready, tell Adam. + +### Monitoring setup for building the grafana dashboard with prod data + +1. Go to http://pcald24.cern.ch:3000/?orgId=1 + + +### Avoid writing QC objects to a repository + +In case of a need to avoid writing QC objects to a repository, one can choose the "Dummy" database implementation in the config file. This is might be useful when one expects very large amounts of data that would be stored, but not actually needed (e.g. benchmarks). ### QCG @@ -37,3 +165,424 @@ Any one in alice-member has access. We use the egroup alice-o2-qcg-access to gra #### Start and stop `systemctl restart qcg` + +### Update host certificate for qcg-test + +1. Create a new host certificate and download it +2. scp the p12 file to qcg-test +3. `openssl pkcs12 -in /tmp/qcg-test-new.p12 -out qcg-test.pem -clcerts -nokeys` +4. `openssl pkcs12 -in /tmp/qcg-test-new.p12 -out qcg-test.key -nocerts -nodes` +5. `cd /etc/pki/tls/certs/ ; mv qcg-test.pem qcg-test.pem-old ; mv path/to/qcg-test.pem .` +6. `cd /etc/pki/tls/private/ ; mv qcg-test.key qcg-test.key-old ; mv path/to/qcg-test.key .` +7. Check that it works by connecting to the website and clicking the little lock in the bar to display the certificate. + +### Logging + +We use the infologger. There is a utility class, `QcInfoLogger`, that can be used. It is a singleton. See [the header](../Framework/include/QualityControl/QcInfoLogger.h) for its usage. + +Related issues : https://alice.its.cern.ch/jira/browse/QC-224 + +To have the full details of what is sent to the logs, do `export O2_INFOLOGGER_MODE=raw`. + +### Service Discovery (Online mode) + +Service discovery (Online mode) is used to list currently published objects by running QC tasks and checkers. It uses Consul to store: + - List of running QC tasks that respond to health check, known as "services" in Consul + - List of published object by each QC task ("service"), knows as "tags" of a "service" in Consul + - List of published Quality Objects by each CheckRunner. + +Both lists are updated from within QC task using [Service Discovery C++ API](#Service-Discovery-C++-API-and-Consul-HTTP-API): +- `register` - when a tasks starts +- `deregister` - when tasks ends + +If the "health check" is failing, make sure the ports 7777 (Tasks) and 7778 (CheckRuners) are open. + +#### Register (and health check) +When a QC task starts, it register its presence in Consul by calling [register endpoit of Consul HTTP API](https://www.consul.io/api/agent/service.html#register-service). The request needs the following fields: +- `Id` - Task ID (must be unique) +- `Name` - Task name, tasks can have same name when they run on mutiple machines +- `Tags` - List of published objects +- `Checks` - Array of health check details for Consul, each should contain `Name`, `Interval`, type of check with endpoint to be check by Consul (eg. `"TCP": "localhost:1234"`) and `DeregisterCriticalServiceAfter` that defines timeout to automatically deregister service when fails health checks (minimum value `1m`). + +#### Deregister +In order to deregister a service [`deregister/:Id` endpoint of Consul HTTP API](https://www.consul.io/api/agent/service.html#deregister-service) needs to be called. It does not need any additional parameters. + +### QCG and QC integration tests + +What are the QC integration tests in the FLP Pipeline doing? + +- They pretend to be one of us when doing a test for an FLP release. +- Start a QC environment +- Opening QCG and check existence of certain objects in offline & online mode and that when you click on them they open a plot +- Stop/Destroy that env + +Those object names are configurable from Ansible so that we do not have to release a new QCG rpm if we need to update the objects we check. So, if you know something will change +modify the following file: https://gitlab.cern.ch/AliceO2Group/system-configuration/-/blob/dev/ansible/roles/flp-deployment-checks/templates/qcg-test-config.js.j2 + +If this test fail and one wants to investigate, they should first resume the VM in openstack. Then the normal web interfaces are available. + +### Check the logs of the QCG + +``` +journalctl -u o2-qcg +``` + +### Deploy a modified version of the ansible recipes + +When working on the ansible recipes and deploying with o2-flp-setup, the recipes to modify are in +`.local/share/o2-flp-setup/system-configuration/`. + +### Test with STFBuilder +https://alice.its.cern.ch/jira/browse/O2-169 +``` +o2-o2-readout-exe file:///afs/cern.ch/user/b/bvonhall/dev/alice/sw/slc7_x86-64/DataDistribution/latest/config/readout_emu.cfg + +StfBuilder \ + --id stf_builder-0 \ + --transport shmem \ + --detector TPC \ + --dpl-channel-name=dpl-chan \ + --channel-config "name=dpl-chan,type=push,method=bind,address=ipc:///tmp/stf-builder-dpl-pipe-0,transport=shmem,rateLogging=1" \ + --channel-config "name=readout,type=pull,method=connect,address=ipc:///tmp/readout-pipe-0,transport=shmem,rateLogging=1" + --detector-rdh=4 + +o2-dpl-raw-proxy \ + -b \ + --session default \ + --dataspec "B:TPC/RAWDATA" \ + --channel-config "name=readout-proxy,type=pull,method=connect,address=ipc:///tmp/stf-builder-dpl-pipe-0,transport=shmem,rateLogging=1" \ + | o2-qc \ + --config json://$PWD/datadistribution.json \ + -b \ + --session default +``` + + +### How to connect to one of the builder nodes used in GH PRs + +Install aurora. +https://alisw.github.io/infrastructure-aurora +The only one that worked for me was the precompiled macos binary. + +Don't forget to install the CERN CA (see the troubleshooting at the bottom). +* `scp lxplus.cern.ch:/etc/ssl/certs/ca-bundle.crt ca-bundle.crt` +* `export REQUESTS_CA_BUNDLE=$PWD/ca-bundle.crt` +* `aurora task ssh -l root build/mesosci/prod/ci_alisw_slc8-gpu-builder_latest/1` + +You might also have to ask Costin to install your public key if it a node prepared by him. + +Then +* `docker ps` to see what is running +* `docker exec -it id /bin/bash` to enter the environment "id" +* `cd /mnt/mesos/sandbox/sandbox/` +* `cd [check-name]` +* `WORK_DIR=$PWD/sw source sw/slc7_x86-64/QualityControl/latest/etc/profile.d/init.sh ` +* `cd sw/BUILD/QualityControl-latest/QualityControl/tests/` +* run the test + + +The problem is that builds continuously happen in the machine. So you cannot just do `cd sw/BUILD/O2-latest/O2/` and `make` + +### How to find from which IP an object was sent to the QCDB ? + +Use `curl 'http://ccdb-test.cern.ch:8080/browse/qc/path/to/object?Accept=text/json' | jq '.["objects"][] | .UploadedFrom'`. + +Example: +``` +~ $ curl 'http://ccdb-test.cern.ch:8080/browse/qc/ITS/MO/ITSTrackTask/AngularDistribution?Accept=text/json' | jq '.["objects"][] | .UploadedFrom' +% Total % Received % Xferd Average Speed Time Time Time Current +Dload Upload Total Spent Left Speed +100 974k 0 974k 0 0 6015k 0 --:--:-- --:--:-- --:--:-- 6015k +"128.141.19.252" +"165.132.27.119" +"128.141.19.252" +"165.132.27.119" +"128.141.19.252" +"165.132.27.119" +"128.141.19.252" +``` + +### Remove empty folders in QCDB + +```bash +psql -h localhost ccdb ccdb_user -c "delete from ccdb_paths where pathid in (select pathid from ccdb_stats where object_count=0);" +``` + +### Count number of objects (not versions) in a path + +`curl -s 'http://ali-qcdb-gpn.cern.ch:8083/latest/qc/EMC.*' | grep -c ^Path:` + +### Update the certificate of the QCDB + +1. go to [ca.cern.ch](ca.cern.ch) -> New Grid Host Certificate. Subject: alio2-cr1-hv-qcdb-gpn.cern.ch (alternative: alio2-cr1-hv-qcdb.cern.ch) +2. download +3. scp the p12 file to qcdb +4. `openssl pkcs12 -in /tmp/new-certif.p12 -out hostcert.pem -clcerts -nokeys` +5. `openssl pkcs12 -in /tmp/new-certif.p12 -out hostkey.pem -nocerts -nodes` +6. `cd /var/lib/pgsql/.globus ` +7. backup the old files +8. copy hostcert and hostkey +9 chmod 600 them, make sure they are owned by `postgres:postgres` +10. Make sure that `.globus` and the two files are owned by the user `postgres` + +### Enable the access log of the QCDB + +Add the following to the service file of the ccdb-sql (`/etc/systemd/system/ccdb-sql.service`): +``` +Environment="ACCESS_LOG=/home/ccdb/logs/access_log" +Environment="ACCESS_LOG_MIN_LEVEL=200" +``` + +### ccdb-test connection + +Ask Costin to put your key on the server. + +Address: root@alicdb1 +So the file repo is in the default location, `/root/QC`, but the database is also there. Careful. + + +### Config file on EPNs + +The config files on EPNs are merged to build a humongous config file used for the whole workflow. +The common part used in prod is stored here: https://github.com/AliceO2Group/O2DPG/blob/master/DATA/production/qc-sync/qc-global-epn.json +THe common part used in staging is stored here: https://github.com/AliceO2Group/O2DPG/blob/master/DATA/production/qc-sync/qc-global-epn-staging.json +The config file to use for each detector are defined [here](https://github.com/AliceO2Group/O2DPG/blob/master/DATA/production/qc-workflow.sh) + +To use a different common part file, one can copy the `qc-global-epn.json` to some path on the EPNs (from the gateway, in your home as it is shared) and change in that json what we need for the tests. Then you can add to `pdp_extra_env_vars` the variable (they are space separated) : `QC_JSON_GLOBAL=/path/to/your/qc-global-epn.json` and that will be used. + +If you want not only to change the header (cf above) but the whole file, change these variables: +1. `QC_JSON_FROM_OUTSIDE=1` +2. `GEN_TOPO_QC_JSON_FILE=/path/to/file.json` + +### How to set an environment variable on the EPNs + +In ECS, in the EPN panel, go to `Extra ENV variables`. + +1. Add a space and then your variable. E.g. `O2_QC_REGISTER_IN_BK=1 ` +2. Add this variable name to another variable: `GEN_TOPO_ENV_AT_RUNTIME=O2_QC_REGISTER_IN_BK` . If `GEN_TOPO_ENV_AT_RUNTIME` is already there, just add it to it with a comma: `GEN_TOPO_ENV_AT_RUNTIME=DPL_PIPELINE_LENGTH,VAR_NAME_YOU_WANT_TO_SET VAR_NAME_YOU_WANT_TO_SET=1234 DPL_PIPELINE_LENGTH=555` + +## run locally multi-node +Terminal 1 +```bash +cd sw/BUILD/QualityControl-latest/QualityControl +export JSON_DIR=${PWD}/tests +export UNIQUE_PORT_1=12345 +export UNIQUE_PORT_2=12346 +o2-qc-run-producer --producers 2 --message-amount 1500 --message-rate 10 -b | o2-qc --config json://${JSON_DIR}/multinode-test.json -b --local --host localhost --run +``` +Terminal 2 +```bash +cd sw/BUILD/QualityControl-latest/QualityControl +export JSON_DIR=${PWD}/tests +export UNIQUE_PORT_1=12345 +export UNIQUE_PORT_2=12346 +o2-qc --config json://${JSON_DIR}/multinode-test.json -b --remote --run + +## Notes on the host certificate for qcdb + +- It must be a "grid host certificate", not a "cern host certificate". +- subject must be `alio2-cr1-hv-qcdb-gpn.cern.ch` +- no password +- SAN: don't forget to add the alias: `ali-qcdb-gpn.cern.ch` +- Convert the p12 to the pem: + ``` + openssl pkcs12 -in alio2-cr1-hv-qcdb-gpn.p12 -out alio2-cr1-hv-qcdb-gpn.crt.pem -clcerts -nokeys + openssl pkcs12 -in alio2-cr1-hv-qcdb-gpn.p12 -out alio2-cr1-hv-qcdb-gpn.key.pem -nocerts -nodes + ``` +- The name and path of the files must be : $HOME/.globus/host{cert,key}.pem + +======= +``` + +## Collect statistics about versions published by detectors + +On the QCDB, become `postgres` and launch `psql`. + +To get the number of objects in a given run : +``` +select count(distinct pathid) from ccdb where ccdb.metadata -> '1048595860' = '539908'; +select count(distinct pathid) from ccdb, ccdb_paths where ccdb.metadata -> '1048595860' = '529439' and ccdb_paths.pathid = ccdb.pathid and ccdb_paths.path like 'qc/%; +``` + +(1048595860 is the metadata id for RunNumber obtained with `select metadataid from ccdb_metadata where metadatakey = 'RunNumber';`) + +Metadata: + +- RunNumber=1048595860 +- qc_detector_name=1337188343 +- qc_task_name=1411267816 +- qc_task_class=809471350 + +query to see for one or several given runs the number of objects per path: +``` +select substring(path from '^qc\/\w*\/MO\/\w*\/') as task, count(distinct path) from ccdb, ccdb_paths where ccdb_paths.pathid = ccdb.pathid AND ccdb.metadata -> '1048595860' in ('539908') group by task; +``` + +query all the versions for a run: +``` +select path, createtime from ccdb, ccdb_paths where ccdb.metadata -> '1048595860' = '541344' and ccdb_paths.pathid = ccdb.pathid order by path; +``` +For a given detector add condition: `and ccdb_paths.path like 'qc/MCH%'` + +Query the paths that were edited during a run for a detector: + +```sql +select path from ccdb, ccdb_paths where ccdb_paths.pathid = ccdb.pathid AND ccdb.metadata -> '1048595860' in ('539908') and ccdb_paths.path like 'qc/MCH%'; +``` + +Number of versions: check the web interface of the qcdb. + +List the tasks of a certain class in a certain run +``` +select substring(path from '^qc\/\w*\/MO\/\w*\/') as task from ccdb, ccdb_paths where ccdb_paths.pathid = ccdb.pathid AND ccdb.metadata -> '1048595860' in ('539908') and ccdb.metadata -> '809471350' = 'o2::quality_control::postprocessing::SliceTrendingTask' group by task; +``` + +List average number of version per run per detector ? + +```sql +SELECT task, + path_count/40 as average_path_count +FROM (SELECT substring(ccdb_paths.path from '^qc\/\w*\/') as task, + count(ccdb.pathid) as path_count + FROM ccdb + JOIN ccdb_paths ON ccdb_paths.pathid = ccdb.pathid + WHERE ccdb.metadata -> '1048595860' in ('541814','541620','541616','541600','541599','541598','541597','541595','541486','541485','541468','541466','540894','540893','540888','540887','540884','540882','540881','540879','540855','540854','540852','540851','540848','540847','540846','540834','540831','540825','540824','540781','540778','540766','540721','540711','540646','540644','540643','540602') + GROUP BY task + ) subquery ; +``` + +#### for the review + +Get the total number of versions and size from QCDB. + +This can also be done on the MC machine : http://ali-qcdbmc-gpn.cern.ch:8083/browse/?report=true +Then the metadata for the run number is 1048595861 +But of course stuff linked to run numbers won't work. + +List the detectors and the number of objects for each (in a list of runs produced in BK), replace MO by QO for the number of quality objects +```sql +select substring(path from '^qc_async\/\w*\/MO\/') as task, count(distinct path) from ccdb, ccdb_paths where ccdb_paths.pathid = ccdb.pathid AND ccdb.metadata -> '1048595860' in ('557926','557876','557862','557744','555259','555254','555232','555226','555208','555202','555187','555172','555160','555156','555124','555121','555071') group by task; +``` + +List the detectors and the number of versions (Qo+MO) +```sql +select substring(path from '^qc_async\/\w*\/') as task, count( path) from ccdb, ccdb_paths where ccdb_paths.pathid = ccdb.pathid AND ccdb.metadata -> '1048595860' in ('557926','557876','557862','557744','555259','555254','555232','555226','555208','555202','555187','555172','555160','555156','555124','555121','555071') group by task; +``` + +List the tasks and the number of objects for each (in a number of runs) +--> this gives the number of tasks in general as well (number of rows) +--> replace MO by QO to see the checks +```sql +select substring(path from '^qc_async\/\w*\/MO\/\w*\/') as task, count(distinct path) from ccdb, ccdb_paths where ccdb_paths.pathid = ccdb.pathid AND ccdb.metadata -> '1048595860' in ('557926','557876','557862','557744','555259','555254','555232','555226','555208','555202','555187','555172','555160','555156','555124','555121','555071') group by task; +``` +Replace the | with tab in a text editor and paste in Excel to then manipulate it to know the number of tasks per det. + +Total number of paths +```sql +select count(distinct ccdb_paths.pathid) from ccdb, ccdb_paths where ccdb_paths.pathid = ccdb.pathid AND ccdb_paths.path like 'qc/%'; +``` + +Total number of tasks and checks +```sql +select distinct(substring(path from '^qc_mc\/\w*\/MO\/\w*\/')) as task from ccdb_paths; +select distinct(substring(path from '^qc_mc\/\w*\/QO\/\w*\/')) as task from ccdb_paths; +``` + +### Merge and upload QC results for all subjobs of a grid job + +Please keep in mind that the file pattern in Grid could have changed since this was written. +``` +#!/usr/bin/env bash +set -e +set -x +set -u + +# we get the list of all QC.root files for o2_ctf and o2_rawtf directories +alien_find /alice/data/2022/LHC22m/523821/apass2_cpu 'o2_*/QC.root' > qc.list +# we add alien:// prefix, so ROOT knows to look for them in alien +sed -i -e 's/^/alien:\/\//' qc.list +# we split the big list into smallers ones of -l lines, so we can parallelize the processing +# one can play with the -l parameter +split -d -l 25 qc.list qc_list_ + +# for each split file run the merger executable +for QC_LIST in qc_list_* +do + o2-qc-file-merger --enable-alien --input-files-list "${QC_LIST}" --output-file "merged_${QC_LIST}.root" & +done + +# wait for the jobs started in the loop +wait $(jobs -p) + +# we merge the files of the first "stage" +o2-qc-file-merger --input-files merged_* --output-file QC_fullrun.root + +# we take the first QC config file we find and use it to perform the remote-batch QC +CONFIG=$(alien_find /alice/data/2022/LHC22m/523821/apass2_cpu QC_production.json | head -n 1) +if [ -n "$CONFIG" ] +then + alien_cp "$CONFIG" file://QC_production.json + # we override activity values, as QC_production.json might have only placeholders + o2-qc --remote-batch QC_fullrun.root --config "json://QC_production.json" -b --override-values "qc.config.Activity.number=523897;qc.config.Activity.passName=apass2;qc.config.Activity.periodName=LHC22m" +fi +``` + +### How to debug the config templating + +It is sometimes useful to see what is being exactly executed by the JIT, in particular to see what the variables passed to the workflow command are. +To do so + +- restart the core with option --veryVerbose + - emacs /etc/systemd/system/o2-aliecs-core.service + - systemctl daemon-reload + - service o2-aliecs-core restart +- in ILG search for "Resolved DPL command:" + +## Common QCDB cleanup tasks + +In general: +- DO NOT USE `truncate` ! `truncate is unstable and any mistake can have dire consequences. +- All the scripts take the arguments `--dry-run`, `--print-list` and `--one-by-one`. Use them. Proceed with the actual deletion only, when you have thoroughly verified the output of a `print-list` and a `dry-run` +- Deletion commands can only be run on the QCDB server itself. + +#### Remove all objects in a path +We use the script `o2-qc-repo-delete-time-interval` with an interval covering 0 to many years in the future. +```shell +o2-qc-repo-delete-time-interval --url http://localhost:8083 --log-level 10 --from 0 --to 1994150839053 --only-path-no-subdir --path qc/TRD/MO/Tracklets/layer1 --dry-run +``` + +#### Move objects from one path to another +It is important to put the full destination path, including the name of the folder we are moving. +```shell +o2-qc-repo-move-objects --url http://localhost:8083 --log-level 10 --path qc/TRD/MO/TrackletsTask/triggerspertimeframe --new-path qc/TRD/MO/Tracklets/triggerspertimeframe --dry-run +``` + +## Build QC the same way how CI/CD does +Just run aliBuild with following parameters from the folder with prepared alidist +``` +aliBuild build QualityControl --defaults o2 --docker --architecture slc8_x86-64 +``` + +## Instructions to move an object in the QCDB + +The script `o2-qc-repo-move-objects` lets the user move an object, and thus all the versions attached to it. E.g.: + +``` +python3 o2-qc-repo-move-objects --url http://ccdb-test.cern.ch:8080 --path qc/TST/MO/Bob --new-path qc/TST/MO/Bob2 --log-level 10 +``` + +## Definition of new arguments + +One can also tell the DPL driver to accept new arguments. This is done using the `customize` method at the top of your workflow definition (usually called "runXXX" in the QC). + +For example, to add two parameters of different types do : + +``` +void customize(std::vector& workflowOptions) +{ + workflowOptions.push_back( + ConfigParamSpec{ "config-path", VariantType::String, "", { "Path to the config file. Overwrite the default paths. Do not use with no-data-sampling." } }); + workflowOptions.push_back( + ConfigParamSpec{ "no-data-sampling", VariantType::Bool, false, { "Skips data sampling, connects directly the task to the producer." } }); +} +``` \ No newline at end of file diff --git a/doc/FAQ.md b/doc/FAQ.md index 3c04167735..2cd9d87451 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -3,20 +3,40 @@ - * [Frequently asked questions](#frequently-asked-questions) - * [Build](#build) - * [How do I add a dependency to my module ?](#how-do-i-add-a-dependency-to-my-module-) - * [How do I make my module depend on library XXX from AliceO2 ?](#how-do-i-make-my-module-depend-on-library-xxx-from-aliceo2-) - * [CCDB repository](#ccdb-repository) - * [How to see which objects are stored in the CCDB ?](#how-to-see-which-objects-are-stored-in-the-ccdb-) - * [How to delete objects from the CCDB ?](#how-to-delete-objects-from-the-ccdb-) - - - +* [Frequently asked questions](#frequently-asked-questions) + * [Git](#git) + * [Are there instructions how to use Git ?](#are-there-instructions-how-to-use-git-) + * [Coding Guidelines](#coding-guidelines) + * [Where are the Coding Guidelines ?](#where-are-the-coding-guidelines-) + * [How do I check my code or apply the guidelines ?](#how-do-i-check-my-code-or-apply-the-guidelines-) + * [Build](#build) + * [How do I add a dependency to my module ?](#how-do-i-add-a-dependency-to-my-module-) + * [How do I make my module depend on library XXX from AliceO2 ?](#how-do-i-make-my-module-depend-on-library-xxx-from-aliceo2-) + * [Run](#run) + * [Why are my QC processes using 100% CPU ?](#why-are-my-qc-processes-using-100-cpu-) + * [QCDB](#qcdb) + * [How to see which objects are stored in the QCDB ?](#how-to-see-which-objects-are-stored-in-the-ccdb-) + * [My objects are not stored due to their size. What can I do ?](#my-objects-are-not-stored-due-to-their-size-what-can-i-do-) [← Go back to Advanced Topics](Advanced.md) | [↑ Go to the Table of Content ↑](../README.md) +## Git + +### Are there instructions how to use Git ? + +See [here](https://alisw.github.io/git-tutorial/). + +## Coding Guidelines + +### Where are the Coding Guidelines ? + +They are [here](https://github.com/AliceO2Group/CodingGuidelines). + +### How do I check my code or apply the guidelines ? + +See [here](https://github.com/AliceO2Group/CodingGuidelines#formatting-tool). + ## Build ### How do I add a dependency to my module ? @@ -28,16 +48,49 @@ For AliceO2 libraries see the next question. Add the library name to the list `O2_LIBRARIES_NAMES` in [FindAliceO2.cmake](../cmake/FindAliceO2.cmake) -## CCDB repository +## Run -### How to see which objects are stored in the CCDB ? +### Why are my QC processes using 100% CPU ? -The easiest is to use the QCG (QC GUI). If you use the central test CCDB, you can use the central test QCG. Simply direct your browser to [https://qcg-test.cern.ch](https://qcg-test.cern.ch). +When running `o2-qc` or other qc binaries, the system will show that the processes use 100% of the CPU. This is due to the DPL default rate for the event loop of devices with inputs. -If for some reason you don't want or can't use the QCG, the CCDB provides a web interface accessible at [http://ccdb-test.cern.ch:8080/browse/](http://ccdb-test.cern.ch:8080/browse/). +Simply start your binary with `--rate 10000` and it should solve the problem. The rate might have to be adapted to your workflow : +```bash +o2-qc-run-producer | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/basic.json --rate 10000 +``` -### How to delete objects from the CCDB ? +### Why are some of my log messages not appearing ? -By accessing http://ccdb-test.cern.ch:8080/truncate/path/to/folder you will delete all the objects at the given path. Careful with that please ! Don't delete data of others.
In production it will of course not be possible to do so. +If they are `Debug` messages, it is expected. +To enable debug messages, edit your config file : +``` + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", "": "Set to 1 to discard debug and trace messages (default: false)", +``` +There are more options in the "Advanced" section of this guide. -[← Go back to Advanced Topics](Advanced.md) | [↑ Go to the Table of Content ↑](../README.md) +## QCDB + +### How to see which objects are stored in the QCDB ? + +The easiest is to use the QCG (QC GUI). If you use the central test CCDB, you can use the central test QCG. Simply direct your browser to [https://qcg-test.cern.ch](https://qcg-test.cern.ch). + +If for some reason you don't want or can't use the QCG, the CCDB provides a web interface accessible at [http://ccdb-test.cern.ch:8080/browse/](http://ccdb-test.cern.ch:8080/browse/). + +### My objects are not stored due to their size. What can I do ? + +You see warnings in the logs: +``` +Warning - object qc/DET/MO/xxx/xxx is bigger than the maximum allowed size (2097152B) - skipped +``` +This is because the maximum allowed size for an object is 2MiB. It can be increased by updating the following item +in the config: +``` +{ + "qc": { + "config": { + "database": { + "maxObjectSize": "2097152", "": "[Bytes, default=2MB] Maximum size allowed, larger objects are rejected." +``` + +[← Go back to Miscellaneous](Miscellaneous.md) | [↑ Go to the Table of Content ↑](../README.md) diff --git a/doc/FLPsuite.md b/doc/FLPsuite.md new file mode 100644 index 0000000000..373c3ee7dd --- /dev/null +++ b/doc/FLPsuite.md @@ -0,0 +1,196 @@ + +FLP Suite +--- + + + + + * [Developing QC modules on a machine with FLP suite](#developing-qc-modules-on-a-machine-with-flp-suite) + * [Switch detector in the workflow readout-dataflow](#switch-detector-in-the-workflow-readout-dataflow) + * [Get all the task output to the infologger](#get-all-the-task-output-to-the-infologger) + * [Using a different config file with the general QC](#using-a-different-config-file-with-the-general-qc) + * [Enable the repo cleaner](#enable-the-repo-cleaner) + * [Reference data](#reference-data) + + +# FLP Suite + +The QC is part of the FLP Suite. The Suite is installed on FLPs through RPMs and is configured with ansible. As a consequence a few things are different in this context compared to a pure development setup. + +## Developing QC modules on a machine with FLP suite + +Development RPMs are available on the FLPs. Start by installing them, then compile QC and finally tell aliECS to use it. + +**Installation** + +As root do: + +``` +yum install o2-QualityControl-devel git -y +``` + +**Compilation** + +As user `flp` do: + +``` +git clone https://github.com/AliceO2Group/QualityControl.git +cd QualityControl +git checkout # use the release included in the installed FLP suite +mkdir build +cd build +mkdir /tmp/installdir +cmake -DCMAKE_INSTALL_PREFIX=/tmp/installdir -G Ninja -DCLANG_EXECUTABLE=/opt/o2/bin-safe/clang -DCMAKE_BUILD_TYPE=RelWithDebugInfo .. +ninja -j16 install +``` + +_**Compilation on top of a local O2**_ + +If you want to build also O2 locally do + +``` +# O2 +git clone https://github.com/AliceO2Group/AliceO2.git +cd AliceO2 +git checkout # use the release included in the installed FLP suite +mkdir build +cd build +cmake -DCMAKE_INSTALL_PREFIX=/tmp/installdir -G Ninja -DCLANG_EXECUTABLE=/opt/o2/bin-safe/clang -DCMAKE_BUILD_TYPE=RelWithDebugInfo .. +ninja -j8 install + +# QC +git clone https://github.com/AliceO2Group/QualityControl.git +cd QualityControl +git checkout # use the release included in the installed FLP suite +mkdir build +cd build +cmake -DCMAKE_INSTALL_PREFIX=/tmp/installdir -G Ninja -DCLANG_EXECUTABLE=/opt/o2/bin-safe/clang -DCMAKE_BUILD_TYPE=RelWithDebugInfo -DO2_ROOT=/tmp/installdir .. +ninja -j8 install +``` + +_**Important step in case several nodes are involved**_ + +In case the workflows will span over several FLPs and/or QC machines, one should `scp` the `installdir` to the other machines in the same directory. + +**Use it in aliECS** + +In the aliECS gui, in the panel "Advanced Configuration", et an extra variable `extra_env_vars` and set it to + +``` +PATH=/tmp/installdir/bin/:$PATH; LD_LIBRARY_PATH=/tmp/installdir/lib/:/tmp/installdir/lib64/:$LD_LIBRARY_PATH; QUALITYCONTROL_ROOT=/tmp/installdir/; echo +``` + +Replace `/tmp/installdir` with your own path. Make sure that the directory is anyway readable and traversable by users `flp` and `qc` + +## Switch detector in the workflow _readout-dataflow_ + +The workflow readout-dataflow works by default with the detector code TST. To run with another detector (e.g. EMC) do: + +2. Replace all instances of `TST` in the QC config file in consul with the one of the detector (e.g. `EMC`). +2. Set the variable `detector` in aliECS to the detector (e.g. `EMC`). + +## Get all the task output to the infologger + +Set the variable log_task_output=all + +## Using a different config file with the general QC + +One can set the `QC URI` to a different config file that is used by the general QC when enabled. However, this is not the recommended way. One must make sure that the name of the task and the check are left untouched and that they are both enabled. + +## Enable the repo cleaner + +If the CCDB used in an FLP setup is the local one, the repo cleaner might be necessary as to avoid filling up the disk of the machine. + +By defaults there is a _disabled_ cron job : + +```shell +*/10 * * * * /opt/o2/bin/o2-qc-repo-cleaner --config /etc/flp.d/ccdb-sql/repocleaner.yaml --dry-run > /dev/null 2>> /tmp/cron-errors.txt +``` + +1. copy the config file /etc/flp.d/ccdb-sql/repocleaner.yaml +2. modify the config file to suit your needs +3. run by hand the repo-cleaner to check that the config file is ok +3. update the cron job to use the modified config file +4. uncomment the cron job + +## Reference data + +A reference object is an object from a previous run. It is usually used as a point of comparison. + +### Get a reference plot in a check + +To retrieve a reference plot in your Check, use + +``` + std::shared_ptr CheckInterface::retrieveReference(std::string path, Activity referenceActivity); +``` +* `path` : the path of the object _without the provenance (e.g. `qc`)_ +* `referenceActivity` : the activity of reference (usually the current activity with a different run number) + +If the reference is not found it will return a `nullptr` and the quality is `Null`. + +### Compare to a reference plot + +The check `ReferenceComparatorCheck` in `Common` compares objects to their reference. + +The configuration looks like + +``` + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::common::ReferenceComparatorCheck", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "QcTask", + "MOs": ["example"] + }], + "extendedCheckParameters": { + "default": { + "default": { + "referenceRun" : "500", + "moduleName" : "QualityControl", + "comparatorName" : "o2::quality_control_modules::common::ObjectComparatorChi2", + "threshold" : "0.2", + "ratioPlotRange" : "0.5", + "ignorePeriodForReference" : "true", + "ignorePassForReference" : "true" + } + }, + "PHYSICS": { + "pp": { + "referenceRun" : "551890" + } + } + } + } +``` + +The check needs the following parameters +* `referenceRun` to specify what is the run of reference and retrieve the reference data. +* `comparatorName` to decide how to compare, see below for their descriptions. +* `threshold` to specify the value used to discriminate between good and bad matches between the histograms. +* `ratioPlotRange` to specify a custom vertical scale for the ratio plot. The vertical values are between 1.0 - range and 1.0 + range. +* `ignorePeriodForReference`, `ignorePassForReference`: boolean flags specifying wether to ignore the period or pass names of the reference run; needed for comparing runs from different periods and/or reconstruction passes. + +Three comparators are provided: + +1. `o2::quality_control_modules::common::ObjectComparatorDeviation`: comparison based on the average relative deviation between the bins of the current and reference histograms; the `threshold` parameter represent in this case the maximum allowed deviation +2. `o2::quality_control_modules::common::ObjectComparatorChi2`: comparison based on a standard chi2 test between the current and reference histograms; the `threshold` parameter represent in this case the minimum allowed chi2 probability +3. `o2::quality_control_modules::common::ObjectComparatorKolmogorov`: comparison based on a standard Kolmogorov test between the current and reference histograms; the `threshold` parameter represent in this case the minimum allowed Kolmogorov probability + +Note that you can easily specify different reference runs for different run types and beam types. + +The plot is beautified by the addition of a `TPaveText` containing the quality and the reason for the quality. + +### Generate a canvas combining both the current and reference ratio histogram + +The postprocessing task ReferenceComparatorTask draws a given set of plots in comparison with their corresponding references, both as superimposed histograms and as current/reference ratio histograms. +See the details [here](https://github.com/AliceO2Group/QualityControl/blob/master/doc/PostProcessing.md#the-referencecomparatortask-class). + + +--- + +[← Go back to QCDB](QCDB.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Miscellaneous →](Miscellaneous.md) diff --git a/doc/Framework.md b/doc/Framework.md new file mode 100644 index 0000000000..e420e8498c --- /dev/null +++ b/doc/Framework.md @@ -0,0 +1,778 @@ + +Framework +--- + + + + + * [Plugging the QC to an existing DPL workflow](#plugging-the-qc-to-an-existing-dpl-workflow) + * [Production of QC objects outside this framework](#production-of-qc-objects-outside-this-framework) + * [Multi-node setups](#multi-node-setups) + * [Batch processing](#batch-processing) + * [Moving window](#moving-window) + * [Monitor cycles](#monitor-cycles) + * [Custom merging](#custom-merging) + * [Critical, resilient and non-critical tasks](#critical-resilient-and-non-critical-tasks) + * [QC with DPL Analysis](#qc-with-dpl-analysis) + * [Propagating Check results to RCT in Bookkeeping](#propagating-check-results-to-rct-in-bookkeeping) + * [Solving performance issues](#solving-performance-issues) + * [Understanding and reducing memory footprint](#understanding-and-reducing-memory-footprint) + * [Monitoring](#monitoring) + + +## Plugging the QC to an existing DPL workflow + +Your existing DPL workflow can simply be considered a publisher. Therefore, you can simply replace `o2-qc-run-producer` with your own workflow in the examples we have seen so far. + +As an example, if TPC wants to monitor the workflow `o2-qc-run-tpcpid`, modify the config file to point to the correct data and do : + +``` +o2-qc-run-tpcpid | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/tpcQCPID.json +``` + +The config file `tpcQCPID.json` has to specify what output we want to monitor. E.g. if the tpc workflow has the output `{"TPC", "CLUSTERS"}`, we should add a dataSamplingPolicy for it and refer to it in a task : + +``` + "dataSamplingPolicies": [ + { + "id": "tpc-cluster", "":"ID we refer to" + "active": "true", + "machines": [], + "query" : "data:TPC/CLUSTERS", "":"The query to match the output in the workflow" + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + } + ], + "blocking": "false" + }, +(...) + "someTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tpc-cluster", "":"Refer to the ID of the policy" + } + } +``` +The "query" syntax of the policy is the same as the one used in the DPL. It must match the output of another device, whether it is in the same workflow or in a piped one. +The `binding` (first part, before the colon) is used in the path of the stored objects and thus we encourage to use the task name to avoid confusion. Moreover, the `origin` (first element after the colon) is used as detectorName. + +## Production of QC objects outside this framework + +QC objects (e.g. histograms) are typically produced in a QC task. +This is however not the only way. Some processing tasks such as the calibration +might have already processed the data and produced histograms that should be +monitored. Instead of re-processing and doing twice the work, one can simply +push this QC object to the QC framework where it will be checked and stored. + +### Configuration + +Let be a device in the main data flow that produces a histogram on a channel defined as `TST/HISTO/0`. To get this histogram in the QC and check it, add to the configuration file an "external device": + +```yaml + "externalTasks": { + "External-1": { + "active": "true", + "query": "External-1:TST/HISTO/0" , "":"Query specifying where the objects to be checked and stored are coming from. Use the task name as binding. The origin (e.g. TST) is used as detector name for the objects." + } + }, + "checks": { +``` + +### Example 1: basic + +As a basic example, we are going to produce histograms with the HistoProducer and collect them with the QC. The configuration is in [basic-external-histo.json](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/basic-external-histo.json). An external task is defined and named "External-1" (see subsection above). It is then used in the Check QCCheck : + +```yaml + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "ExternalTask", + "name": "External-1", + "MOs": ["hello"] + }] + } +``` + +When using this feature, make sure that the name of the MO in the Check definition matches the name of the object you are sending from the external device. + +To run it, do: + +```yaml +o2-qc-run-histo-producer | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/basic-external-histo.json +``` + +The object is visible in the QCG or the CCDB at `qc/TST/MO/External-1/hello_0`. In general we publish the objects of an external device at `qc//MO//object`. + +The check results are stored at `qc//QO//object`. + +### Example 2: advanced + +This second, more advanced, example mixes QC tasks and external tasks. It is defined in [advanced-external-histo.json](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/advanced-external-histo.json). It is represented here: + +![alt text](images/Advanced-external.png) + +First, it runs 1 QC task (QC-TASK-RUNNER-QcTask) getting data from a data producer (bottom boxes, typical QC worfklow). + +On top we see 3 histogram producers. `histoProducer-2` is not part of the QC, it is not an external device defined in the configuration file. The two other histogram producers are configured as external devices in the configuration file. + +`histoProducer-0` produces an object that is used in a check (`QcCheck-External-1`). `histoProducer-1` objects are not used in any check but we generate one automatically to take care of the storage in the database. + +To run it, do: + +```yaml +o2-qc-run-producer | o2-qc-run-histo-producer --producers 3 --histograms 3 | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/advanced-external-histo.json +``` + +### Limitations + +1. Objects sent by the external device must be either a TObject or a TObjArray. In the former case, the object will be sent to the checker encapsulated in a MonitorObject. In the latter case, each TObject of the TObjArray is encapsulated in a MonitorObject and is sent to the checker. + +## Multi-node setups + +During the data-taking, Quality Control runs on a distributed computing system. Some QC Tasks are +executed on dedicated QC servers, while others run on FLPs and EPNs. In the latter case, messages +coming from Data Sampling should reach QC servers where they are processed. In the first case, +locally produced Monitor Objects should be merged on QC servers and then have Checks run on them. +By **remote QC tasks** we mean those which run on QC servers (**remote machines**), while **local QC Tasks** +run on FLPs and EPNs (**local machines**). + +Setting up a multinode setup to run standalone or with AliECS requires different amount of parameters, +as some of them are overwritten by AliECS anyway. Such parameters are marked accordingly. Please note +that for now we support cases with one or more local machines, but just only one remote machine. + +In our example, we assume having two local processing nodes (`localnode1`, `localnode2`) and one +QC node (`qcnode`). There are two types of QC Tasks declared: +* `MultiNodeLocal` which are executed on the local nodes and their results are merged and checked + on the QC server. +* `MultiNodeRemote` which runs on the QC server, receiving a small percent of data from + `localnode2` only. Mergers are not needed in this case, but there is a process running Checks against + Monitor Objects generated by this Task. + +We use the `SkeletonTask` class for both, but any Task can be used of course. Should a Task be local, +all its `MonitorObject`s need to be mergeable - they should be one of the mergeable ROOT types (histograms, TTrees) +or inherit [MergeInterface](https://github.com/AliceO2Group/AliceO2/blob/dev/Utilities/Mergers/include/Mergers/MergeInterface.h). + +These are the steps to follow to get a multinode setup: + +1. Prepare a configuration file. + +In this example we will use the `Framework/multiNode.json` config file. A config file should look +almost like the usual one, but with a few additional parameters. In case of a local task, these parameters should be +added: + +```json + "tasks": { + "MultiNodeLocal": { + "active": "true", + ... + "location": "local", + "localMachines": [ + "localnode1", + "localnode2" + ], + "remoteMachine": "qcnode", "":"not needed with FLP+QC, needed with EPN+QC", + "remotePort": "30132", "":"not needed with FLP+QC, needed with EPN+QC", + "localControl": "aliecs", "":"if absent, aliecs is default", + "mergingMode": "delta", "":"if absent, delta is default", + "mergersPerLayer": ["3", "1"], "":"if absent, one Merger is used" + } + }, +``` + +List the local processing machines in the `localMachines` array. `remoteMachine` should contain the host name which +will serve as a QC server and `remotePort` should be a port number on which Mergers will wait for upcoming MOs. Make +sure it is not used by other service. If different QC Tasks are run in parallel, use separate ports for each. The +`localControl` parameter allows to properly configure QC with respect to the control software it is run with. It can +be either `aliecs` (on FLPs) or `odc` (EPNs). It has no influence when running the software by hand. + +One also may choose the merging mode - `delta` is the default and recommended (tasks are reset after each cycle, so they +send only updates), but if it is not feasible, Mergers may expect `entire` objects - tasks are not reset, they +always send entire objects and the latest versions are combined in Mergers. +With the `delta` mode, one can cheat by specifying just one local machine name and using only that one during execution. +This is not possible with `entire` mode, because then Mergers need identifiable data sources to merge objects correctly. +If one merger process is not enough to sustain the input data throughput, one may define multiple Merger layers with +`mergersPerLayer` option. + +In case of a remote task, choosing `"remote"` option for the `"location"` parameter is needed. In standalone setups +and those controlled by ODC, one should also specify the `"remoteMachine"`, so sampled data reaches the right node. +Also, `"localControl"` should be specified to generate the correct AliECS workflow template. + +```json + "tasks": { + ... + "MultiNodeRemote": { + "active": "true", + ... + "dataSource": { + "type": "dataSamplingPolicy", + "name": "rnd-little" + }, + "taskParameters": {}, + "location": "remote", + "remoteMachine": "qcnode", "":"not needed with FLP+QC, needed with EPN+QC", + "localControl": "aliecs", "":"aliecs is default, not needed with FLP+QC, needed with EPN+QC" + } + } +``` + +In case the task is running remotely, data should be sampled. The minimal-effort approach requires adding a port number +(see the example below). Use separate ports for each Data Sampling Policy. If the same configuration file will be used +on many nodes, but only some of them should apply a given sampling policy, one should also specify the list of +machines to match (or generalized aliases, e.g. "flp", "epn"). + +```json +{ + "dataSamplingPolicies": [ + ... + { + "id": "rnd-little", + "active": "true", + "machines": [ "","only needed when the policy should run on a subgroup of nodes", + "localnode2" + ], + "port": "30333", "":"compulsory on standalone and ODC setups (EPN), not needed for FLPs", + ... + } + ] +} +``` + +By default, the channel is bound on the QC Task side. If this is not what you need, add `"bindLocation" : "local"` in +the policy configuration (`"remote"` is the default value) and make sure to use valid host names. + +2. Make sure that the firewalls are properly configured. If your machines block incoming/outgoing connections by + default, you can add these rules to the firewall (run as sudo). Consider enabling only concrete ports or a small + range of those. + +``` +# localnode1 and localnode2 : +iptables -I INPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -s qcnode -j ACCEPT +iptables -I OUTPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -d qcnode -j ACCEPT +# qcnode: +iptables -I INPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -s localnode1 -j ACCEPT +iptables -I OUTPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -d localnode1 -j ACCEPT +iptables -I INPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -s localnode2 -j ACCEPT +iptables -I OUTPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -d localnode2 -j ACCEPT +``` + +If your network is isolated, you might consider disabling the firewall as an alternative. Be wary of the security risks. + +``` +systemctl stop firewalld # to disable until reboot +systemctl disable firewalld # to disable permanently +``` + +3. Install the same version of the QC software on each of these nodes. We cannot guarantee that different QC versions will talk to each other without problems. Also, make sure the configuration file that you will use is the same everywhere. + +4. Run each part of the workflow. In this example `o2-qc-run-producer` represents any DPL workflow, here it is just a process which produces some random data. + The `--host` argument is matched against the `machines` lists in the configuration files. + +``` +# On localnode1: +o2-qc-run-producer | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/multiNode.json --local --host localnode1 -b +# On localnode2: +o2-qc-run-producer | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/multiNode.json --local --host localnode2 -b +# On qcnode: +o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/multiNode.json --remote +``` + +If there are no problems, on QCG you should see the `example` histogram updated under the paths `qc/TST/MO/MultiNodeLocal` +and `qc/TST/MO/MultiNodeRemote`, and corresponding Checks under the path `qc/TST/QO/`. + +When using AliECS, one has to generate workflow templates and upload them to the corresponding repository. Please +contact the QC or AliECS developers to receive assistance or instructions on how to do that. + +## Batch processing + +In certain cases merging results of parallel QC Tasks cannot be performed in form of message passing. +An example of this are the simulation workflows, which exchange data between processing stages via files +and produce (and process) consecutive TimeFrames in different directories in parallel. +Then, one can run QC Tasks on incomplete data and save the results to a file. +If the file already exists, the new objects will be merged with those obtained so far. +At the end, one can run the rest of processing chain (Checks, Aggregators) on the complete objects. + +Here is a simple example: + +```bash +# Remove any existing results +rm results.root +# Run the Tasks 3 times, merge results into the file. +o2-qc-run-producer --message-amount 100 | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --local-batch results.root +o2-qc-run-producer --message-amount 100 | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --local-batch results.root +o2-qc-run-producer --message-amount 100 | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --local-batch results.root +# Run Checks and Aggregators, publish results to QCDB +o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --remote-batch results.root +``` + +Please note, that the local batch QC workflow should not work on the same file at the same time. +A semaphore mechanism is required if there is a risk they might be executed in parallel. + +The file is organized into directories named after 3-letter detector codes and sub-directories representing Monitor Object Collections for specific tasks. +To browse the file, one needs the associated Quality Control environment loaded, since it contains QC-specific data structures. +It is worth remembering, that this file is considered as intermediate storage, thus Monitor Object do not have Checks applied and cannot be considered the final results. +The quick and easy way to inspect the contents of the file is to load a recent environment (e.g. on lxplus) and open it with ROOT's `TBrowser`: + +```shell +alienv enter O2PDPSuite/nightly-20221219-1 +root +TBrowser t; // a browser window will pop-up +``` + +...or by browsing the file manually: + +```shell +alienv enter O2PDPSuite/nightly-20221219-1 +root +root [0] auto f = new TFile("QC_fullrun.root") +(TFile *) @0x7ffe84833dc8 +root [1] f->ls() +TFile** QC_fullrun.root + TFile* QC_fullrun.root + KEY: TDirectoryFile CPV;1 CPV + KEY: TDirectoryFile EMC;1 EMC + KEY: TDirectoryFile FDD;1 FDD + KEY: TDirectoryFile FT0;1 FT0 + KEY: TDirectoryFile FV0;1 FV0 + KEY: TDirectoryFile GLO;1 GLO + KEY: TDirectoryFile ITS;1 ITS +... +root [2] f->cd("GLO") +(bool) true +root [3] f->ls() +TFile** QC_fullrun.root + TFile* QC_fullrun.root + TDirectoryFile* GLO GLO + KEY: o2::quality_control::core::MonitorObjectCollection MTCITSTPC;1 + KEY: o2::quality_control::core::MonitorObjectCollection Vertexing;1 + KEY: TDirectoryFile CPV;1 CPV +... +root [4] auto vtx = dynamic_cast(f->Get("GLO/Vertexing")) +(o2::quality_control::core::MonitorObjectCollection *) @0x7ffe84833dc8 +root [5] auto vtx_x = dynamic_cast(vtx->FindObject("vertex_X")) +(o2::quality_control::core::MonitorObject *) @0x7ffe84833dc8 +root [6] vtx_x->getObject()->ClassName() +(const char *) "TH1F" +``` + +To merge several incomplete QC files, one can use the `o2-qc-file-merger` executable. +It takes a list of input files, which may or may not reside on alien, and produces a merged file. +One can select whether the executable should fail upon any error or continue for as long as possible. +Please see its `--help` output for usage details. + +## Moving window + +### Moving window for all plots generated by a task + +By default QC Tasks are never reset, thus the MOs they produce contain data from the full run. +However, if objects should have a shorter validity range, one may add the following options to QC Task configuration: + +```json + "MovingWindowTaskA": { + ... + "resetAfterCycles": "10", + } +``` + +In the case above the QC Task will have the `TaskInterface::reset()` method invoked each 10 cycles. +Thus, all the plots generated by this task will by affected. + +If the QC Task runs in parallel on many nodes and its results are merged, the effects will be different +depending on the chosen merging mode: +* If `"delta"` mode is used, the Merger in the last layer will implement the moving window, while the QC Tasks will + still reset after each cycle. Please note, that QC Tasks will fall out of sync during data acquisition, so the moving + window might contain slightly misaligned data time ranges coming from different sources. Also, due to fluctuations of + the data transfer, objects coming from different sources might appear more frequently than others. Thus, one might + notice higher occupancy on stave A one time, but the next object might contain less than average data for the same stave. +* In the `"entire"` mode, QC Tasks will reset MOs, while Mergers will use the latest available object version from each + Task. Please note that if one of the Tasks dies, an old version of MO will be still used over and over. Thus, `"delta"` + mode is advised in most use cases. + +In setups with Mergers one may also extend the Mergers cycle duration, which can help to even out any data fluctuations: + +```json + "MovingWindowTaskB": { + ... + "cycleDurationSeconds" : "60", + "mergingMode" : "delta", + "mergerCycleMultiplier": "10", "": "multiplies cycleDurationSeconds in Mergers", + "resetAfterCycles": "1", "": "it could be still larger than 1" + } + ``` + +In the presented case, the Merger will publish one set of complete MOs per 10 minutes, which should contain all deltas +received during this last period. Since the QC Tasks cycle is 10 times shorter, the occupancy fluctuations should be +less apparent. Please also note, that using this parameter in the `"entire"` merging mode does not make much sense, +since Mergers would use every 10th incomplete MO version when merging. + +### Moving windows of selected plots only + +The following applies to synchronous setups which use Mergers in the delta mode and all asynchronous setups. +One can obtain objects containing data from one cycle alongside the ones covering the whole run. +These are saved in QCDB in the task subdirectory `mw` and also can be requested by Checks. +To specify which objects should get a moving window variant, add a `"movingWindows"` list to the task configuration: + +```json + "MyTask": { + ... + "cycleDurationSeconds" : "60", + "mergingMode" : "delta", + "movingWindows" : [ "plotA", "plotB" ] + } +``` + +To request these objects in a Check, use `TaskMovingWindow` data source, as in the example: + +```json + "QcCheckMW": { + "dataSource": [{ + "type": "TaskMovingWindow", + "name": "MyTask", + "MOs": ["plotA"], "": "MOs can be omitted if all moving windows of a task are requested" + }] + } +``` + +It is possible to request both the integrated and single cycle plots by the same Check. + +To test it in a small setup, one can run `o2-qc` with `--full-chain` flag, which creates a complete workflow with a Merger for **local** QC tasks, even though it runs just one instance of them. +Please remember to use `"location" : "local"` in such case. + +In asynchronous QC, the moving window plots will appear in the intermediate QC file in the directory `mw` and will be uploaded to QCDB to `/mw`. +When testing, please make sure to let DPL know that it has to run in Grid mode, so that QC can compute object validity based on timestamps in the data: + +``` +export O2_DPL_DEPLOYMENT_MODE=Grid && o2-qc --local-batch QC.root ... +``` + +## Monitor cycles + +The QC tasks monitor and process data continuously during a so-called "monitor cycle". At the end of such a cycle they publish the QC objects that will then continue their way in the QC data flow. +Since v1.178.0 cycles are stored in the metadata of QOs and MOs under the key `CycleNumber` inside the QCDB. + +A monitor cycle lasts typically between **1 and 5 minutes**, some reaching 10 minutes but never less than 1 minute for performance reasons. +It is defined in the config file this way: + +``` + "tasks": { + "dataSizeTask": { + "cycleDurationSeconds": "60", + ... +``` + +It is possible to specify various durations for different period of times. It is particularly useful to have shorter cycles at the beginning of the run and longer afterwards: + +``` + "tasks": { + "dataSizeTask": { + "cycleDurations": [ + {"cycleDurationSeconds": 60, "validitySeconds": 300}, + {"cycleDurationSeconds": 180, "validitySeconds": 600}, + {"cycleDurationSeconds": 300, "validitySeconds": 1} + ], + ... +``` + +In this example, a cycle of 60 seconds is used for the first 5 minutes (300 seconds), then a cycle of 3 minutes (180 seconds) between 5 minutes and 10 minutes after SOR, and finally a cycle of 5 minutes for the rest of the run. The last `validitySeconds` is not used and is just applied for the rest of the run. + + + +## Custom merging + +When needed, one may define their own algorithm to merge a Monitor Object. +To do so, inherit the [MergeInterface](https://github.com/AliceO2Group/AliceO2/blob/dev/Utilities/Mergers/include/Mergers/MergeInterface.h) class and override the corresponding methods. +Please pay special attention to delete all the allocated resources in the destructor to avoid any memory leaks. +Feel free to consult the existing usage examples among other modules in the QC repository. + +Once a custom class is implemented, one should let QCG know how to display it correctly, which is explained in the subsection [Display a non-standard ROOT object in QCG](#display-a-non-standard-root-object-in-qcg). + +## Critical, resilient and non-critical tasks + +DPL devices can be marked as expendable, resilient or critical. Expendable tasks can die without affecting the run. +Resilient tasks can survive having one or all their inputs coming from an expendable task but they will stop the system if they themselves die. +Critical tasks (default) will stop the system if they die and will not accept input from expendable tasks. + +In QC we use these `labels`. + +### QC tasks + +In QC, one can mark a task as critical or non-critical: + +```json + "tasks": { + "QcTask": { + "active": "true", + "critical": "false", "": "if false the task is allowed to die without stopping the workflow, default: true", +``` + +By default they are `critical` meaning that their failure will stop the run. +If they are not critical, they will be `expendable` and will not stop the run if they die. + +### Auto-generated proxies + +They adopt the criticality of the task they are proxying. + +### QC mergers + +Mergers are `resilient`. + +### QC check runners + +CheckRunners are `resilient`. + +### QC aggregators + +Aggregators are `resilient`. + +### QC post-processing tasks + +Post-processing tasks can be marked as critical or non-critical: + +```json + "postprocessing": { + "ExamplePostprocessing": { + "active": "true", + "critical": "false", "": "if false the task is allowed to die without stopping the workflow, default: true", +``` + +By default, they are critical meaning that their failure will stop the run. +If they are not critical, they will be `expendable` and will not stop the run if they die. + +## QC with DPL Analysis + +### Uploading objects to QCDB + +To upload objects written to a file by an Analysis Task to QCDB, one may use the following command: + +```shell script +o2-qc-upload-root-objects \ + --input-file ./QAResults.root \ + --qcdb-url ccdb-test.cern.ch:8080 \ + --task-name AnalysisFromFileTest \ + --detector-code TST \ + --provenance qc_mc \ + --pass-name passMC \ + --period-name SimChallenge \ + --run-number 49999 +``` + +See the `--help` message for explanation of the arguments. +If everything went well, the objects should be accessible in [the test QCG instance](https://qcg-test.cern.ch) under +the directories listed in the logs: + +``` +2021-10-05 10:59:41.408998 QC infologger initialized +2021-10-05 10:59:41.409053 Input file './QAResults.root' successfully open. +... +2021-10-05 10:59:41.585893 Storing MonitorObject qc_mc/TST/MO/AnalysisFromFileTest/hMcEventCounter +2021-10-05 10:59:41.588649 Storing MonitorObject qc_mc/TST/MO/AnalysisFromFileTest/hGlobalBcFT0 +2021-10-05 10:59:41.591542 Storing MonitorObject qc_mc/TST/MO/AnalysisFromFileTest/hTimeT0Aall +2021-10-05 10:59:41.594386 Storing MonitorObject qc_mc/TST/MO/AnalysisFromFileTest/hTimeT0Call +2021-10-05 10:59:41.597743 Successfully uploaded 10 objects to the QCDB. +``` + +Notice that by default the executable will ignore the directory structure in the input file and upload all objects to one directory. +If you need the directory structure preserved, add the argument `--preserve-directories`. + +## Propagating Check results to RCT in Bookkeeping + +The framework allows to propagate Quality Objects (QOs) produced by Checks and Aggregators to RCT in Bookkeeping. +The synchronisation is done once, at the end of workflow runtime, i.e. at the End of Run or in the last stage of QC merging on Grid. +Check results are converted into Flags, which are documented in [O2/DataFormats/QualityControl](https://github.com/AliceO2Group/AliceO2/tree/dev/DataFormats/QualityControl). +Information about the object validity is preserved, which allows for time-based flagging of good/bad data. + +### Configuration details + +Propagation can be enabled by adding the following key-value pair to Check/Aggregator configuration: + +```json + "exportToBookkeeping": "true" +``` + +Using it for Aggregators is discouraged, as the information on which exact Check failed is lost or at least obfuscated. + +To allow QC to connect to Bookkeeping, include the its URL in the QC configuration file, e.g.: + +```json +{ + "qc": { + "config": { + "bookkeeping": { + "url": "bookkeeping.cern.ch:12345" + } + } + } +} +``` + +For setups external to P2, one also needs to provide a BKP client token. +It can be done by creating a file named `qc_bkp_client_token.txt` in the working directory, containing just the token. +In such case, please ensure minimal permissions for the file, so that it is not readable by other users. +Alternatively, it can be provided as an environment variable `QC_BKP_CLIENT_TOKEN`. +Then, avoid printing the environment variable in the logs. + +### Conversion details + +Below we describe some details of how the conversion is done. +Good QOs are marked with green, Medium QOs are marked with orange and Bad QOs are marked with red. +Null QOs are marked with purple. + +* **Good QOs with no Flags associated are not converted to any Flags.** + According to the preliminary design for Data Tagging, "bad" Flags always win, thus there is no need for explicit "good" Flags. + It also implies that there is no need to explicitly add Good Flag to Good Quality. + +![](images/qo_flag_conversion_01.svg) + +* **Bad and Medium QOs with no Flags are converted to Flag 14 (Unknown).** + This means that Medium Quality data is by default bad for Analysis. + +![](images/qo_flag_conversion_02.svg) + +* **Null QOs with no Flags are converted to Flag 1 (Unknown Quality).** + +![](images/qo_flag_conversion_03.svg) + +* **All QOs with Flags are converted to Flags, while the Quality is ignored.** + As a consequence, one can customize the meaning of any Quality (Medium in particular) in terms of data usability. + A warning is printed if a Check associates a good Flag to bad Quality or a bad Flag to good Quality. + +![](images/qo_flag_conversion_04.svg) + +* **Timespans not covered by a given QO are filled with Flag 1 (Unknown Quality).** + In other words, if an object was missing during a part of the run, we can state that the data quality is not known. + +![](images/qo_flag_conversion_05.svg) + +* **Overlapping or adjacent Flags with the same ID, comment and source (QO name) are merged.**. + This happens even if they were associated with different Qualities, e.g. Bad and Medium. + Order of Flag arrival does not matter. + +![](images/qo_flag_conversion_06.svg) +![](images/qo_flag_conversion_07.svg) + +* **Flag 1 (Unknown Quality) is overwritten by any other Flag.** + This allows us to return Null Quality when there is not enough statistics to determine data quality, but it can be suppressed later, once we can return Good/Medium/Bad. + +![](images/qo_flag_conversion_08.svg) + +* **Good and Bad flags do not affect each other, they may coexist.** + +![](images/qo_flag_conversion_09.svg) + +* **Flags for different QOs (QO names) do not affect each other. + Flag 1 (Unknown Quality) is added separately for each.** + +![](images/qo_flag_conversion_10.svg) + +## Registration of QC devices into the BookKeeping + +By default, the QC tasks, PP tasks, check runners, and aggregators are registered in the BK. +To disable this behaviour, pass the following environment variable : `O2_QC_DONT_REGISTER_IN_BK` (in the ECS). + +## Solving performance issues + +Problems with performance in message passing systems like QC usually manifest in backpressure seen in input channels of processes which are too slow. +QC processes usually use one worker thread, thus one can also observe that they use a full CPU core when struggling to consume incoming data. +When observing performance issues with QC setups, consider the following actions to improve it. + +### Dispatcher + +Dispatcher will usually cause backpressure when it is requested to sample too much data. +In particular, copying many small messages takes more time than less messages of equivalent size. +To improve the performance: +* reduce the sampling rate +* disable unused sampling policies +* adapt the data format to pack data in fewer messages +* when in need of 100% data, do not use Data Sampling, but connect to the data source directly + +### QC Tasks + +QC Tasks are implemented by the users, thus the maximum possible input data throughput largely depends on the task implementation. +If a QC Task cannot cope with the input messages, consider: +* sampling less data +* using performance measurement tools (like `perf top`) to understand where the task spends the most time and optimize this part of code +* if one task instance processes data, spawn one task per machine and merge the result objects instead + +### Mergers + +The performance of Mergers depends on the type of objects being merged, as well as their number and size. +The following points might help avoid backpressure: +* increase QC tasks cycle duration +* use less or smaller objects +* if an object has its custom Merge() method, check if it could be optimized +* enable multi-layer Mergers to split the computations across multiple processes (config parameter "mergersPerLayer") + +## Understanding and reducing memory footprint + +When developing a QC module, please be considerate in terms of memory usage. +Large histograms could be optionally enabled/disabled depending on the context that the QC is ran. +Investigate if reducing the bin size (e.g. TH2D to TH2F) would still provide satisfactory results. +Consider loading only the parts of detector geometry which are being used by a given task. + +### Analysing memory usage with valgrind + +0) Install valgrind, if not yet installed + +1) Run the QC workflow with argument `--child-driver 'valgrind --tool=massif'` (as well as any file reader / processing workflow you need to obtain data in QC) + +2) The workflow will run and save files massif.out. + +3) Generate a report for the file corresponding to the PID of the QC task: + +``` +ms_print massif.out.976329 > massif_abc_task.log +``` + +4) The generated report contains: +* the command used to run the process +* graph of the memory usage +* grouped call stacks of all memory allocations on the heap (above certain threshold) within certain time intervals. + The left-most call contains all the calls which lead to it, represented on the right. + For example, the call stack below means that the AbcTask created a TH2F histogram in the initalize method at the line + AbcTask.cxx:82, which was 51,811,760B. In total, 130,269,568B worth of TH2F histograms were created in this time interval. + +``` +98.56% (256,165,296B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. +->50.12% (130,269,568B) 0xFCBD1A6: TArrayF::Set(int) [clone .part.0] (TArrayF.cxx:111) +| ->50.12% (130,269,568B) 0xEC1DB1C: TH2F::TH2F(char const*, char const*, int, double, double, int, double, double) (TH2.cxx:3573) +| ->19.93% (51,811,760B) 0x32416518: make_unique (unique_ptr.h:1065) +| | ->19.93% (51,811,760B) 0x32416518: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:82) +``` + +5) To get a lightweight and more digestible output, consider running the massif report through the following command to get the summary of the calls only within a QC module. This essentially tells you how much memory a given line allocates. + +``` +[O2PDPSuite/latest] ~/alice/test-rss $> grep quality_control_modules massif_abc_task.log | sed 's/^.*[0-9][0-9]\.[0-9][0-9]\% //g' | sort | uniq +(242,371,376B) 0x324166B2: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:88) +(4,441,008B) 0x3241633F: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:76) +(4,441,008B) 0x32416429: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:79) +(51,811,760B) 0x32416518: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:82) +(51,811,760B) 0x324165EB: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:85) +``` + +6) Consider reducing the size and number of the biggest histogram. Consider disabling histograms which will not be useful for async QC (no allocations, no startPublishing). + +## Monitoring + +The QC uses the [O2 Monitoring](https://github.com/AliceO2Group/Monitoring/) library to monitor metrics. +The user code has access to an instance of the Monitoring via the variable `mMonitoring`. +It can be used this way: +``` +mMonitoring->send({ 42, "my/metric" }); // send the value 42 keyed with "my/metric" +``` +By default the Monitoring will be printed in the terminal. If a proper Monitoring system +is setup, one can update the monitoring url in the config file to point to it. + +--- + +[← Go back to Modules development](ModulesDevelopment.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) diff --git a/doc/Miscellaneous.md b/doc/Miscellaneous.md new file mode 100644 index 0000000000..09c3c8e92b --- /dev/null +++ b/doc/Miscellaneous.md @@ -0,0 +1,380 @@ + +Miscellaneous +--- + + + + +* [Asynchronous Data and Monte Carlo QC operations](#asynchronous-data-and-monte-carlo-qc-operations) +* [QCG](#qcg) + * [Display a non-standard ROOT object in QCG](#display-a-non-standard-root-object-in-qcg) + * [Canvas options](#canvas-options) + * [Local QCG (QC GUI) setup](#local-qcg-qc-gui-setup) +* [Data Sampling monitoring](#data-sampling-monitoring) +* [Monitoring metrics](#monitoring-metrics) +* [Common check IncreasingEntries](#common-check-increasingentries) +* [Common check TrendCheck](#common-check-trendcheck) +* [Update the shmem segment size of a detector](#update-the-shmem-segment-size-of-a-detector) +* [Readout chain](#readout-chain) +* [Writing a DPL data producer](#writing-a-dpl-data-producer) + + + +# Asynchronous Data and Monte Carlo QC operations + +QC can accompany workflows reconstructing real and simulated data asynchronously. +Usually these are distributed among thousands of nodes which might not have access to each other, thus partial results are stored and merged in form of files with mechanism explained in [Batch processing](#batch-processing). + +QC workflows for asynchronous data reconstructions are listed in [O2DPG/Data/production/qc-workflow.sh](https://github.com/AliceO2Group/O2DPG/blob/master/DATA/production/qc-workflow.sh). +The script includes paths to corresponding QC configuration files for subsystems which take part in the reconstruction. +All the enabled files are merged into a combined QC workflow. +Thus, it is crucial that unique keys are used in `tasks`, `checks` and `aggregators` structures, as explained in [Merging multiple configuration files into one](#merging-multiple-configuration-files-into-one). +Post-processing tasks can be added in the script [O2DPG/DATA/production/o2dpg_qc_postproc_workflow.py](https://github.com/AliceO2Group/O2DPG/blob/master/DATA/production/o2dpg_qc_postproc_workflow.py). +Please see the included example and the in-code documentation for further guidelines in this matter. + +Generating and reconstructing simulated data is ran by a framework which organizes specific workflows in a directed acyclic graph and executes them in an order which satisfies all the dependencies and allocated computing resources. +In contrast to data reconstruction, here, QC workflows are executed separately and pick up corresponding input files. +For further details, please refer to [Adding QC Tasks to the simulation script](https://github.com/AliceO2Group/O2DPG/tree/master/MC#adding-qc-tasks-to-the-simulation-script). + +Data and simulation productions are typically executed on Grid and EPNs, and the outcomes can be inspected in [MonALISA](http://alimonitor.cern.ch/). +In both cases, QC runs alongside of each subjob and incomplete QC results are stored in files. +For asynchronous data reconstruction, one file `QC.root` is created. +Simulation subjobs contain a `QC` directory with separate files for each QC workflow. +Relevant logs can be found in files like `stdout`, `stderr` as well as archives `debug_log_archive.zip` and `log_archive.zip`. + +Once an expected percentage of subjobs completes, several QC merging stages are executed, each producing a merged file for certain range of subjobs. +The last stage produces the complete file for given masterjob. +This file is read by the `o2-qc --remote-batch` executable to run Checks on the complete objects and all the results to the QCDB. +Post-Processing Tasks and associated Checks are executed right after. + +Some runs contain too much data to be processed with one masterjob. +In such case, several masterjobs are run in parallel. +Each produces a `QC.root` file which contains all the statistics for a masterjob. +The last masterjob to complete recognizes this fact and merges all `QC.root` into `QC_fullrun.root` and only then uploads the results to QCDB. +To find it, one can use `alien_find`: + +``` +> alien_find /alice/data/2022/LHC22m/523897/apass1_epn QC_fullrun.root +/alice/data/2022/LHC22m/523897/apass1_epn/0750/QC/001/QC_fullrun.root +``` + +TODO explain how a connection to QCDB is made from Grid sites. + +# QCG + +## Display a non-standard ROOT object in QCG + +Users can publish objects inheriting from a custom class, e.g. not a TH2F but a MyCustomClass, as long as a dictionary is available. By default, JSROOT and in turn the QCG won't be able to display such objects. + +The solution depends on the strategy to adopt to display the object. + +1. The custom class has multiple inheritance and one of them is a standard ROOT object which the QCG can display (e.g. a histogram). In such case, add a member `mTreatMeAs` to your custom class and set it to the name of the class that should be used to interpret and display the data. There is an example in the Example module : + +```c++ + std::string mTreatMeAs = "TH2F"; // the name of the class this object should be considered as when drawing in QCG. +``` + +2. [Not ready yet] The class encapsulates the object that should actually be drawn. Contact us if you need this feature, we can easily add it. +3. [Not ready yet] The class cannot be drawn in the ways outlined above and need a custom piece of JS to actually do it. Contact us if you need this feature, it is not a trivial thing to do. + +## Canvas options + +The developer of a Task might perfectly know how to display a plot or a graph but cannot set these options if they belong to the Canvas. This is typically the case of `drawOptions` such as `colz` or `alp`. It is also the case for canvases' properties such as logarithmic scale and grid. These options can be set by the end user in the QCG but it is likely that the developer wants to give pertinent default options. + +To do so, one can use one of the two following methods. + +* `getObjectsManager()->setDefaultDrawOptions(, string& drawOptions)` where + `drawOptions` is a space-separated list of drawing options. E.g. "colz" or "alp lego1". +* `getObjectsManager()->setDisplayHint(, string& hints)` where + `hints` is a space-separated list of hints on how to draw the object. E.g. "logz" or "gridy logy". + Currently supported by QCG: logx, logy, logz, gridx, gridy, gridz. + +These methods must be called after the objects has been published, i.e. after the call to `getObjectsManager()->startPublishing() + +## Local QCG (QC GUI) setup + +To install and run the QCG locally please follow these instructions : + + + +# Data Sampling monitoring + +To have the monitoring metrics for the Data Sampling (the Dispatcher) sent to a specific sink (like influxdb), add the option `--monitoring-backend` when launching the DPL workflow. For example: + +```shell +--monitoring-backend 'influxdb-udp://influxdb-server.cern.ch:8086' +``` + +This will actually send the monitoring data of _all_ DPL devices to this database. + +**Note for mac users**: if you get a crash and the message "std::exception::what: send_to: Message too long", it means that you have to adapt a `udp` parameter. You can check the datagram size via `sudo sysctl net.inet.udp.maxdgram`. If it says something less than 64 kB, then increase size: `sudo sysctl -w net.inet.udp.maxdgram=65535` + +# Monitoring metrics + +The QC framework publishes monitoring metrics concerning data/object rates, which are published to the monitoring backend +specified in the `"monitoring.url"` parameter in config files. If QC is run in standalone mode (no AliECS) and with +`"infologger:///debug?qc"` as the monitoring backend, the metrics will appear in logs in buffered chunks. To force +printing them as soon as they are reported, please also add `--monitoring-backend infologger://` as the argument. + +One can also enable publishing metrics related to CPU/memory usage. To do so, use `--resources-monitoring `. + +# Common check `IncreasingEntries` + +This check make sures that the number of entries has increased in the past cycle(s). If not, it will display a pavetext +on the plot and set the quality to bad. + +If you use `SetBinContent` the number of entries does not increase creating a false positive. Please call `ResetStats()` +after using `SetBinContent`. + +The behaviour of the check can be inverted by setting the customparameter "mustIncrease" to "false" : + +``` + "checkParameters": { + "mustIncrease": "false" + } +``` + +The number of cycles during which we tolerate increasing (or not respectively) the number of entries can be set with the custom parameter `nBadCyclesLimit`: + +``` + "extendedCheckParameters": { + "default": { + "default": { + "nBadCyclesLimit": "3", + } + } + } +``` + +In the example above, the quality goes to bad when there are 3 cycles in a row with no increase in the number of entries. + +# Common check `TrendCheck` + +This check compares the last point of a trending plot with some minimum and maximum thresholds. + +The thresholds can be defined in different ways, controlled by the `trendCheckMode` parameter: + +* `"trendCheckMode": "ExpectedRange"` ==> fixed threshold values: the thresholds represent the minimum and maximum allowed values for the last point +* `"trendCheckMode": "DeviationFromMean"` ==> the thresholds represent the relative variation with respect to the mean of the N points preceding the last one (which is checked) +For example: + +``` + "thresholdsBad": "-0.1,0.2", +``` + +means that the last point should not be lower than `(mean - 0.1 * |mean|)` and not higher than `(mean + 0.2 * |mean|)`. + +* `"trendCheckMode": "StdDeviation"` ==> the thresholds represent the relative variation with respect to the total error of the N points preceding the last one (which is checked) +For example: + +``` + "thresholdsBad": "-1,2", +``` + +means that the last point should not be lower than `(mean - 1 * TotError)` and not higher than `(mean + 2 * TotError)`. +The total error takes into account the standard deviation of the N points before the current one, as well as the error associated to the current point. + +In general, the threshold values are configured separately for the Bad and Medium qualities, like this: + +``` + "thresholdsBad": "min,max", + "thresholdsMedium": "min,max", +``` + +It is also possible to customize the threshold values for specific plots: + +``` + "thresholdsBad:PlotName": "min,max", + "thresholdsMedium:PlotName": "min,max", +``` + +Here `PlotName` represents the name of the plot, stripped from all the QCDB path. + +The position and size of the text label that shows the check result can also be customized in the configuration: + +``` + "qualityLabelPosition": "0.5,0.8", + "qualityLabelSize": "0.5,0.1" +``` + +The values are relative to the canvas size, so in the example above the label width is 50% of the canvas width and the label height is 10% of the canvas height. + +### Full configuration example + +```json + "MyTrendingCheckFixed": { + "active": "true", + "className": "o2::quality_control_modules::common::TrendCheck", + "moduleName": "QualityControl", + "detectorName": "TST", + "policy": "OnAll", + "extendedCheckParameters": { + "default": { + "default": { + "trendCheckMode": "ExpectedRange", + "nPointsForAverage": "3", + "": "default threshold values not specifying the plot name", + "thresholdsMedium": "3000,7000", + "thresholdsBad": "2000,8000" + "": "thresholds specific to one plot", + "thresholdsBad:mean_of_histogram_2": "1000,9000", + "": "customize the position and size of the text label showing the quality" + "qualityLabelPosition": "0.5,0.8", + "qualityLabelSize": "0.5,0.1" + } + } + }, + "dataSource": [ + { + "type": "PostProcessing", + "name": "MyTrendingTask", + "MOs" : [ + "mean_of_histogram_1", "mean_of_histogram_2" + ] + } + ] + }, + "MyTrendingCheckMean": { + "active": "true", + "className": "o2::quality_control_modules::common::TrendCheck", + "moduleName": "QualityControl", + "detectorName": "TST", + "policy": "OnAll", + "extendedCheckParameters": { + "default": { + "default": { + "trendCheckMode": "DeviationFromMean", + "nPointsForAverage": "3", + "thresholdsBad": "-0.2,0.5", "": "from -20% to +50%", + "thresholdsMedium": "-0.1,0.25" + } + } + }, + "dataSource": [ + { + "type": "PostProcessing", + "name": "MyTrendingTask", + "MOs" : [ + "mean_of_histogram_3" + ] + } + ] + }, + "MyTrendingCheckStdDev": { + "active": "true", + "className": "o2::quality_control_modules::common::TrendCheck", + "moduleName": "QualityControl", + "detectorName": "TST", + "policy": "OnAll", + "extendedCheckParameters": { + "default": { + "default": { + "trendCheckMode": "StdDeviation", + "nPointsForAverage": "5", + "thresholdsBad:mean_of_histogram_3": "-2,5", "": "from -2sigma to +5sigma", + "thresholdsMedium:mean_of_histogram_3": "-1,2.5" + } + } + }, + "dataSource": [ + { + "type": "PostProcessing", + "name": "MyTrendingTask", + "MOs" : [ + "mean_of_histogram_4" + ] + } + ] + } +``` + +# Update the shmem segment size of a detector + +In consul go to `o2/runtime/aliecs/defaults` and modify the file corresponding to the detector: [det]_qc_shm_segment_size + +# Readout chain + +In this section we are going to use the Readout as our data source. This is a rare occurence nowadays as detectors have +DPL workflows they can plug QC to. +This example assumes that Readout has been compiled beforehand (`aliBuild build Readout --defaults o2`). + +![alt text](images/readout-schema.png) + +This workflow is a bit different. The _Readout_ is not a DPL, nor a FairMQ, device and thus we have to have a _proxy_ to get data from it. This is the extra box going to the _Data Sampling_, which then injects data to the task. This is handled in the _Readout_ as long as you enable the corresponding configuration flag. + +The first thing is to load the environment for the readout in a new terminal: `alienv enter Readout/latest`. + +Then enable the data sampling channel in readout by opening the readout config file located at `$READOUT_ROOT/etc/readout-qc.cfg` and make sure that the following properties are correct: + +``` +# Enable the data sampling +[consumer-fmq-qc] +consumerType=FairMQChannel +enableRawFormat=1 +fmq-name=readout-qc +fmq-address=ipc:///tmp/readout-pipe-1 +fmq-type=pub +fmq-transport=zeromq +unmanagedMemorySize=2G +memoryPoolNumberOfPages=500 +memoryPoolPageSize=1M +enabled=1 +(...) +``` + +Start Readout in a terminal: +``` +o2-readout-exe file://$READOUT_ROOT/etc/readout-qc.cfg +``` + +Start in another terminal the proxy, DataSampling and QC workflows: +``` +o2-qc-run-readout | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/readout.json +``` + +The data sampling is configured to sample 1% of the data as the readout should run by default at full speed. + +### Getting real data from readout + +See [these instructions for readout](ModulesDevelopment.md#readout) and [these for O2 utilities](ModulesDevelopment.md#dpl-workflow). + +### Readout data format as received by the Task + +The header is an O2 header populated with data from the header built by the Readout. +The payload received is a 2MB (configurable) data page made of CRU pages (8kB). + +__Configuration file__ + +The configuration file is installed in `$QUALITYCONTROL_ROOT/etc`. Each time you rebuild the code, `$QUALITYCONTROL_ROOT/etc/readout.json` is overwritten by the file in the source directory (`~/alice/QualityControl/Framework/readout.json`). +To avoid this behaviour and preserve the changes you do to the configuration, you can copy the file and specify the path to it with the parameter `--config` when launch `o2-qc`. + +To change the fraction of the data being monitored, change the option `fraction`. + +``` +"fraction": "0.01", +``` + +# Writing a DPL data producer + +For your convenience, and although it does not lie within the QC scope, we would like to document how to write a simple data producer in the DPL. The DPL documentation can be found [here](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md) and for questions please head to the [forum](https://alice-talk.web.cern.ch/). + +As an example we take the `DataProducerExample` that you can find in the QC repository. It is produces a number. By default it will be 1s but one can specify with the parameter `my-param` a different number. It is made of 3 files : + +* [runDataProducerExample.cxx](../Framework/src/runDataProducerExample.cxx) : + This is an executable with a basic data producer in the Data Processing Layer. + There are 2 important functions here : + * `customize(...)` to add parameters to the executable. Note that it must be written before the includes for the dataProcessing. + * `defineDataProcessing(...)` to define the workflow to be ran, in our case the device(s) publishing the number. +* [DataProducerExample.h](../Framework/include/QualityControl/DataProducerExample.h) : + The key elements are : + 1. The include `#include ` + 2. The function `getDataProducerExampleSpec(...)` which must return a `DataProcessorSpec` i.e. the description of a device (name, inputs, outputs, algorithm) + 3. The function `getDataProducerExampleAlgorithm` which must return an `AlgorithmSpec` i.e. the actual algorithm that produces the data. +* [DataProducerExample.cxx](../Framework/src/DataProducerExample.cxx) : + This is just the implementation of the header described just above. You will probably want to modify `getDataProducerExampleSpec` and the inner-most block of `getDataProducerExampleAlgorithm`. You might be taken aback by the look of this function, if you don't know what a _lambda_ is just ignore it and write your code inside the accolades. + +You will probably write it in your detector's O2 directory rather than in the QC repository. + +--- + +[← Go back to FLP Suite](FLPsuite.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to FAQ →](FAQ.md) diff --git a/doc/ModulesDevelopment.md b/doc/ModulesDevelopment.md index b868c4d037..624d37fa78 100644 --- a/doc/ModulesDevelopment.md +++ b/doc/ModulesDevelopment.md @@ -1,31 +1,34 @@ # Modules development - + - * [Modules development](#modules-development) - * [Context](#context) - * [QC architecture](#qc-architecture) - * [DPL](#dpl) - * [Data Sampling](#data-sampling) - * [Bypassing the Data Sampling](#bypassing-the-data-sampling) - * [Code Organization](#code-organization) - * [User-defined modules](#user-defined-modules) - * [Module creation](#module-creation) - * [Test run](#test-run) - * [Modification of a Task](#modification-of-a-task) - * [Addition of a Check](#addition-of-a-check) - * [Commit Code](#commit-code) - * [DPL workflow customization](#dpl-workflow-customization) - * [Usage of DS and QC in an existing DPL workflow](#usage-of-ds-and-qc-in-an-existing-dpl-workflow) - * [Addition of parameters to a task](#addition-of-parameters-to-a-task) - - - +* [Context](#context) + * [QC architecture](#qc-architecture) + * [DPL](#dpl) + * [Data Sampling](#data-sampling) + * [Code Organization](#code-organization) + * [Developing with aliBuild/alienv](#developing-with-alibuildalienv) + * [User-defined modules](#user-defined-modules) + * [Repository](#repository) +* [Module creation](#module-creation) +* [Test run](#test-run) +* [Modification of the Task](#modification-of-the-task) +* [Check](#check) + * [Configuration](#configuration) + * [Implementation](#implementation) + * [Results](#results) +* [Quality Aggregation](#quality-aggregation) + * [Quick try](#quick-try) + * [Configuration](#configuration-1) + * [Implementation](#implementation-1) +* [Naming convention](#naming-convention) +* [Committing code](#committing-code) +* [Data sources](#data-sources) +* [Run number and other run attributes (period, pass type, provenance)](#run-number-and-other-run-attributes-period-pass-type-provenance) +* [A more advanced example](#a-more-advanced-example) -[← Go back to Quickstart](QuickStart.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Advanced Topics →](Advanced.md) - ## Context Before developing a module, one should have a bare idea of what the QualityControl is and how it is designed. The following sections explore these aspects. @@ -34,69 +37,19 @@ Before developing a module, one should have a bare idea of what the QualityContr ![alt text](images/Architecture.png) -The main data flow is represented in blue. Data samples are selected by the Data Sampling (not represented) and sent to the QC tasks, either on the same machines or on other machines. The tasks produce TObjects, usually histograms, that are merged (if needed) and then checked. The checkers output the received TObject along with a quality flag. The TObject can be modified by the Checker. Finally the TObject and its quality are stored in the repository. - -### DPL - -[Data Processing Layer](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md) is a software framework developed as a part of O2 project. It structurizes the computing into units called _Data Processors_ - processes that communicate with each other via messages. DPL takes care of generating and running the processing topology out of user declaration code, serializing and deserializing messages, providing the data processors with all the anticipated messages for a given timestamp and much more. Each piece of data is characterized by its `DataHeader`, which consists (among others) of `dataOrigin`, `dataDescription` and `SubSpecification` - for example `{"MFT", "TRACKS", 0}`. - -An example of a workflow definition which describes the processing steps (_Data Processors_), their inputs and their outputs can be seen in [runBasic.cxx](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/runBasic.cxx). In the QC we define the workflows in files whose names are prefixed with `run`. - - +An example of a workflow definition which describes the processing steps (_Data Processors_), their inputs and their outputs can be seen in [runBasic.cxx](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/src/runBasic.cxx). In the QC we define the workflows in files whose names are prefixed with `run`. ### Data Sampling -The Data Sampling provides the possibility to sample data in DPL workflows, based on certain conditions ( 5% randomly, when payload is greater than 4234 bytes, etc.). The job of passing the right data is done by a data processor called `Dispatcher`. A desired data stream is specified in the form of Data Sampling Policies, defined in the QC JSON configuration file. Please refer to the main [Data Sampling readme](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md#data-sampling) for more details. +The Data Sampling provides the possibility to sample data in DPL workflows, based on certain conditions ( 5% randomly, when payload is greater than 4234 bytes or others, including custom conditions). The job of passing the right data is done by a data processor called `Dispatcher`. A desired data stream is specified in the form of Data Sampling Policies, defined in the QC JSON configuration file. Please refer to the main [Data Sampling readme](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md#data-sampling) for more details. Data Sampling is used by Quality Control to feed the tasks with data. Below we present an example of a configuration file. It instructs Data Sampling to provide a QC task with 10% randomly selected data that has the header `{"ITS", "RAWDATA", 0}`. The data will be accessible inside the QC task by the binding `"raw"`. ```json @@ -119,19 +72,12 @@ Data Sampling is used by Quality Control to feed the tasks with data. Below we p "id": "its-raw", "active": "true", "machines": [], - "dataHeaders": [ - { - "binding": "raw", - "dataOrigin": "ITS", - "dataDescription": "RAWDATA" - } - ], - "subSpec": "0", + "query_comment" : "query is in the format of binding1:origin1/description1/subSpec1[;binding2:...]", + "query": "raw:ITS/RAWDATA/0", "samplingConditions": [ { "condition": "random", - "fraction": "0.1", - "seed": "1234" + "fraction": "0.1" } ], "blocking": "false" @@ -140,7 +86,13 @@ Data Sampling is used by Quality Control to feed the tasks with data. Below we p } ``` -An example of using the data sampling in a DPL workflow is visible in [runAdvanced.cxx](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/runAdvanced.cxx). +QC framework takes care of creating the necessary Data Sampling infrastructure when it finds it in the configuration file. + +#### Custom Data Sampling Condition + +If needed, a custom data selection can be performed by inheriting the `DataSamplingCondition` class and implementing the `configure` and `decide` methods. Then, to use it, one needs to specify the library and class names in the config file. + +The class [ExampleCondition](https://github.com/AliceO2Group/QualityControl/blob/master/Modules/Example/include/Example/ExampleCondition.h) presents the how to write one's own condition, while in [example-default.json](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/example-default.json) the policy `ex1` shows how it should be configured. #### Bypassing the Data Sampling @@ -155,10 +107,8 @@ In case one needs to sample at a very high rate, or even monitor 100% of the dat ... "dataSource": { "type": "direct", - "binding": "its-rawdata", - "dataOrigin": "ITS", - "dataDescription": "RAWDATA", - "subSpec": "0" + "query_comment" : "query is in the format of binding1:origin1/description1/subSpec1[;binding2:...]", + "query" : "its-raw-data:ITS/RAWDATA/0" }, ... } @@ -169,7 +119,12 @@ In case one needs to sample at a very high rate, or even monitor 100% of the dat } ``` -The file `basic-no-sampling.json` is provided as an example. To test it, you can run `qcRunBasic` with the option `--no-data-sampling` (it makes it use this config file instead of `basic.json`). +The file `basic-no-sampling.json` is provided as an example. To test it, you can run `o2-qc` with that configuration file instead of `basic.json`. + +To use multiple direct data sources, just place them one after another in the value of `"query"`, separated with a semicolon. For example: +``` +"query" : "emcal-digits:EMC/DIGITS/0;emcal-triggers:EMC/TRIGGERS/0" +``` ### Code Organization @@ -177,97 +132,322 @@ The repository QualityControl contains the _Framework_ and the _Modules_ in the The Data Sampling code is part of the AliceO2 repository. +### Developing with aliBuild/alienv + +One can of course build using `aliBuild` (`aliBuild build --defaults o2 QualityControl`). However, that will take quite some time as it checks all dependencies and builds everything. + +After the initial use of `aliBuild`, which is necessary, the quicker way of building is to load the build environment and run `ninja`: + +``` +cd sw/BUILD/QualityControl-latest/QualityControl + +# option 1 +WORK_DIR=~/alice/sw source ~/alice/sw/ubuntu2204_x86-64/QualityControl/latest/etc/profile.d/init.sh # replace paths as needed +# option 2 +direnv allow # needs direnv installed, which will then load the build environment automatically + +ninja -j8 install # adapt to the number of cores available +``` + ### User-defined modules -The Quality Control uses _plugins_ to load the actual code to be executed by the _Tasks_ and the _Checkers_. A module, or plugin, can contain one or several _Tasks_ and/or one or several _Checks_. They must subclass `TaskInterface.h` and `CheckInterface.h` respectively. We use the Template Method Design Pattern. +The Quality Control uses _plugins_ to load the actual code to be executed by the _Tasks_, the _Checks_, the _Aggregators_ and the _PostProcessing_. A module, or plugin, can contain one or several of these classes. They must subclass the corresponding interfaces, for example `TaskInterface.h` or `CheckInterface.h`. We use the Template Method Design Pattern. + +The same code, the same class, can be run many times in parallel. It means that one can run several copies of the same qc Task in parallel, each executing the same code but on different data samples, typically on different nodes. + +### Repository + +QC results (MonitorObjects and QualityObjects) are stored in a repository that we usually call the QCDB. Indeed, the underlying technology is the one of the CCDB (Condition and Calibration DataBase). As a matter of fact we use the test instance of the CCDB for the tests and development (ccdb-test.cern.ch:8080). In production, we will have a different instance distinct from the CCDB. + +#### Paths + +We use a consistent system of path for the objects we store in the QCDB: +* MonitorObjects: ```qc//MO//``` +* QualityObjects: ```qc//QO/[/]``` +The last, optional, part depends on the policy (read more about that [later](#Check)). ## Module creation Before starting to develop the code, one should create a new module if it does not exist yet. Typically each detector team should prepare a module. -The script `modulesHelper.sh`, in the directory _Modules_, is able to prepare a new module or to add a new _Task_ or a new _Check_ to an existing module. It must be run from __within QualityControl/Modules__. See the help message below: +The script `o2-qc-module-configurator.sh`, in the directory _Modules_, is able to prepare a new module or to add a new _Task_, _Check_, _Aggregator_ or PostProcessing task to an existing module. It must be run from __within QualityControl/Modules__. See the help message below: ``` -Usage: ./modulesHelper.sh -m MODULE_NAME [OPTION] +Usage: ./o2-qc-module-configurator.sh -m MODULE_NAME [OPTION] -Generate template QC module and/or tasks, checks. -If a module with specified name already exists, new tasks and checks are inserted to the existing one. +Generate template QC module and/or tasks, checks, aggregators and postprocessing. +If a module with specified name already exists, new tasks, checks, aggregators and postprocessing are inserted to the existing module. Please follow UpperCamelCase convention for modules', tasks' and checks' names. Example: # create new module and some task -./modulesHelper.sh -m MyModule -t SuperTask +./o2-qc-module-configurator.sh -m MyModule -t SuperTask # add one task and two checks -./modulesHelper.sh -m MyModule -t EvenBetterTask -c HistoUniformityCheck -c MeanTest +./o2-qc-module-configurator.sh -m MyModule -t EvenBetterTask -c HistoUniformityCheck -c MeanTest Options: -h print this message - -m MODULE_NAME create module named MODULE_NAME or add there some task/checker - -t TASK_NAME create task named TASK_NAME - -c CHECK_NAME create check named CHECK_NAME + -m MODULE_NAME create a module named MODULE_NAME or add there some task/checker + -t TASK_NAME create a task named TASK_NAME + -c CHECK_NAME create a check named CHECK_NAME + -p PP_NAME create a postprocessing task named PP_NAME + -a AGG_NAME create an aggregator named AGG_NAME ``` -For example, if your detector 3-letter code is ABC you might want to do +For example, if your detector 3-letter code is TST you might want to do ``` # we are in ~/alice cd QualityControl/Modules -./modulesHelper.sh -m Abc # create the module -./modulesHelper.sh -t RawDataQcTask # add a task +./o2-qc-module-configurator.sh -m TST -t RawDataQcTask # create the module and a task ``` +IMPORTANT: Make sure that your detector code is listed in TaskRunner::validateDetectorName. If it is not, feel free to add it. + +We will refer in the following section to the module as `Tst` and the task as `RawDataQcTask`. Make sure to use your own code and names. + ## Test run Now that there is a module, we can build it and test it. First let's build it : ``` -# We are in ~/alice -# Go to the build directory of QualityControl -cd sw/slc7_x86-64/BUILD/QualityControl-latest/QualityControl -make -j8 install # replace 8 by the number of cores on your machine +cd sw/BUILD/QualityControl-latest/QualityControl + +# option 1 +WORK_DIR=~/alice/sw source ~/alice/sw/ubuntu2204_x86-64/QualityControl/latest/etc/profile.d/init.sh # replace paths as needed +# option 2 +direnv allow # needs direnv installed, which will then load the build environment automatically + +ninja -j8 install # adapt to the number of cores available ``` -To test whether it works, we are going to run a basic DPL workflow defined in `runBasic.cxx`. -We need to modify slightly the config file to indicate our freshly created module and classes. -The config file is called `basic.json` and is located in `$QUALITYCONTROL_ROOT/etc/` (after installation, if you want to modify the original one it is in the source directory `Framework`). +To test whether it works, we are going to run a basic workflow made of a producer and the qc, which corresponds to the one we saw in the [QuickStart](QuickStart.md#basic-workflow). + +We are going to duplicate the config file we used previously, i.e. `basic.json`: +``` +cp ~/alice/QualityControl/Framework/basic.json ~/alice/QualityControl/Modules/TST/basic-tst.json +``` + +We need to modify it slightly to indicate our freshly created module and classes. Change the lines as indicated below : ``` -"QcTask": { - "className": "o2::quality_control_modules::abc::RawDataQcTask", - "moduleName": "QcAbc", +"tasks": { + "MyRawDataQcTask": { + "active": "true", + "className": "o2::quality_control_modules::abc::RawDataQcTask", + "moduleName": "QcTST", + "detectorName": "TST", + ... +``` +and +``` +"checks": { + "QcCheck": { + ... + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "MyRawDataQcTask", + ... ``` Now we can run it ``` -qcRunBasic +alienv enter QualityControl/latest # enter runtime environment +o2-qc-run-producer | o2-qc --config json://$HOME/alice/QualityControl/Modules/TST/basic-tst.json ``` -You should see the QcTask at qcg-test.cern.ch with an object `Example` updating. +You should see an object `example` in `/qc/TST/MO/MyRawDataQcTask` at qcg-test.cern.ch. -## Modification of a Task +### Saving the QC objects in a local file -Fill in the methods in RawDataQcTask.cxx. For example, make it send a second histogram. -Once done, recompile it (see section above) and run it. You should see the second object published in the qcg. +When debugging and developping, and only in this case, it can be handy to save the objects being produced to a file. Simply add the parameter `saveObjectsToFile` to the task definition and set it to file name with or without a path. For example: -TODO give actual steps +``` +"saveObjectsToFile": "test.root", "": "For debugging, path to the file where to save. If empty it won't save." +``` + +## Modification of the Task -You can rename the task by simply changing its name in the config file. Change the name from -`QcTask` to whatever you like and run it again (no need to recompile). You should see the new name -appear in the QCG. +We are going to modify our task to make it publish a second histogram. Objects must be published only once and they will then be updated automatically every cycle (10 seconds for our example, 1 minute in general, the first cycle randomly shorter). Modify `RawDataQcTask.cxx` and its header to add a new histogram, build it and publish it with `getObjectsManager()->startPublishing(mHistogram);`. +Once done, recompile it (see section above, `make -j8 install` in the build directory) and run it (same as above). You should see the second object published in the qcg. -## Addition of a Check +## Check -TODO +A Check is a function (actually `Check::check()`) that determines the quality of the Monitor Objects produced in the previous step (the Task). It can receive multiple Monitor Objects from several Tasks. Along with the `check()` method, the `beautify()` method is a function that can modify the MO itself. It is typically used to add colors or texts on the object to express the quality. -## Commit Code +### Configuration + +```json +{ + "qc" : { + "config" : { ... }, + "tasks" : { ... }, + + "checks": { + "CheckName": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "dataSource": [{ + "type": "Task", + "name": "TaskName" + }, + { + "type": "Task", + "name": "QcTask", + "MOs": ["example", "other"] + }], + "exportToBookkeeping": "false" + }, + "QcCheck": { + ... + } + } + +} +``` + +* __active__ - Boolean to indicate whether the checker is active or not +* __moduleName__ - Name of the module which implements the check class (like in tasks) +* __className__ - Name and namespace of the class, which is part of the module specified above (like in tasks) +* __policy__ - Policy for triggering the _check_ function defined in the class: + * _OnAny_ (default) - Triggers if ANY of the listed monitor objects changes. + * _OnAnyNonZero_ - Triggers if ANY of the declared monitor objects changes, but only after all listed objects have been received at least once. + * _OnAll_ - Triggers if ALL the listed monitor objects have changed. + * _OnEachSeparately_ - Triggers separately for EACH of the listed objects whenever one of them changes. + * In case the list of monitor objects is empty, the policy is simply ignored and the `check` will be triggered whenever a new MonitorObject is received. +* __dataSource__ - declaration of the `check` input + * _type_ - currently only supported are _Task_ and _ExternalTask_ + * _name_ - name of the _Task_ + * _MOs_ - list of MonitorObjects names or can be omitted to mean that all objects should be taken. +* __exportToBookkeeping__ - allows to propagate the results of this Check to Bookkeeping, where they are visualized as time-based Flags (disabled by default). + +### Implementation +After the creation of the module described in the above section, every Check functionality requires a separate implementation. The module might implement several Check classes. +```c++ +Quality check(std::map>* moMap) {} + +void beautify(std::shared_ptr mo, Quality = Quality::Null) {} + +``` + +The `check()` function is called whenever the _policy_ is satisfied. It gets a map with all declared MonitorObjects. +It is expected to return Quality of the given MonitorObjects. +Optionally one can associate one or more Flags to a Quality by using `addFlag` on it. + +For each MO or group of MOs, `beautify()` is invoked after `check()` if +1. the check() did not raise an exception +2. there is a single `dataSource` in the configuration of the check + +### Results + +Checks return Qualities with associated Flags. +The framework wraps them with a QualityObject, then makes it available to Aggregators (see the next section) and stores them in the repository. +It is also possible to propagate Check results to the Run Condition Table (RCT) in Bookkeeping. +Details are explained at [Propagating Check results to RCT in Bookkeeping](Advanced.md#propagating-check-results-to-rct-in-bookkeeping) + +## Quality Aggregation + +The _Aggregators_ are able to collect the QualityObjects produced by the checks or other _Aggregators_ and to produce new Qualities. This is especially useful to determine the overall quality of a detector or a set of detectors. + +![alt text](images/Aggregation.png) + +### Quick try + +One can try it with this simple example: + +```c++ +o2-qc-run-basic --config-path ${QUALITYCONTROL_ROOT}/etc/basic-aggregator.json +``` + +Notice the AggregatorRunner after the CheckRunner. + +A more complex example with a producer and the `o2-qc`: + +```c++ +o2-qc-run-advanced --no-qc --no-debug-output | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/advanced-aggregator.json +``` + +### Configuration + +```json +{ + "qc": { + "config": {...}, + "tasks": {...}, + "checks": {...}, + "aggregators": { + "MyAggregator": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonAggregator", + "moduleName": "QcSkeleton", + "policy": "OnAll", + "detectorName": "TST", + "dataSource": [{ + "type": "Check", + "name": "QcCheck", "": "If the check produces multiple objects, specify \"QOs\"" + }, + { + "type": "Aggregator", + "name": "MyOtherAggregator", + "QOs": ["newQuality", "another"] + }] + } + } + }, + "dataSamplingPolicies": [...] +} +``` + +* __active__ - Boolean to indicate whether the aggregator is active or not +* __moduleName__ - Name of the module which implements the aggregator class (like in tasks) +* __className__ - Name and namespace of the class, which is part of the module specified above (like in tasks) +* __policy__ - Policy for triggering the _check_ function defined in the class: + * _OnAny_ (default) - Triggers if ANY of the listed quality objects changes. + * _OnAnyNonZero_ - Triggers if ANY of the declared monitor objects changes, but only after all listed objects have been received at least once. Please see the notes on the dataSource `QOs` below. + * _OnAll_ - Triggers if ALL the listed quality objects have changed. + * In case the list of QualityObject is empty for any of the data sources, the policy is simply ignored for all sources and the `aggregator` will be triggered whenever a new QualityObject is received. +* __dataSource__ - declaration of the `check` input + * _type_ - _Check_ or _Aggregator_ + * _names_ - name of the Check or Aggregator + * _QOs_ - list of QualityObjects names or can be omitted to mean that all objects should be taken. In case of `OnAnyNonZero` one must list the objects and if the the check produces only 1 then it should list only an empty string. + +### Implementation + +With `o2-qc-module-configurator.sh` (see [here](#module-creation)), create a new Aggregator that can be then used in the config file. + +An aggregator inherits from `AggregatorInterface` and in particular this method: +```c++ + virtual std::vector aggregate(std::map>& qoMap) = 0; +``` + +The `aggregate` method is called whenever the _policy_ is satisfied. It gets a map with all the declared QualityObjects. It is expected to return a new Quality based on the inputs. + +## Naming convention + +We apply a naming convention for Task, Check and Aggregator names, i.e. how they are named in QCDB, not their class names. Here are the rules: +- minimally describe the scope of the task/check +- do not add detetor code, it will be there anyway in the QCDB path. If an actor processes data from a different detector/subsystem, it is allowed to add its code +- do not add "QC", it is obvious it is QC +- do not add "Task" or "Check", process names are marked as such and corresponding objects go to "MO" and "QO" directories +- do not add "sync", "async" or "mc", it is obvious from the context we run QC and we use a correct path prefix in QCDB +- ideally use less than 16 characters, which is the limit of data description in DPL. + +Good name examples are "Tracks", "Clusters", "PID", "Cells". +In case that a QC config file include multiple detectors, possibly with the same task names, one can add the `taskName` parameter in the body of a task configuration structure to use the preferred name and change the root key to a unique id, which shall be used only for the purpose of navigating a configuration file. + +## Committing code To commit your new or modified code, please follow this procedure -1. Fork the [QualityControl](github.com/AliceO2Group/QualityControl) repo using github webpage or github desktop app. +1. Fork the [QualityControl](https://github.com/AliceO2Group/QualityControl) repo using github webpage or github desktop app. + ![alt text](images/fork.png) 1. Clone it : `git clone https://github.com//QualityControl.git` 1. Before you start working on your code, create a branch in your fork : `git checkout -b feature-new-stuff` 2. Push the branch : `git push --set-upstream origin feature-new-stuff` 2. Add and commit your changes onto this branch : `git add Abc.cxx ; git commit Abc.cxx` 3. Push your commits : `git push` -4. Once you are satisfied with your changes, make a _Pull Request_ (PR). Go to your branches on the github webpage, and click "New Pull Request". Explain what you did. +4. Once you are satisfied with your changes, make a _Pull Request_ (PR). Go to your branches on the github webpage, and click "New Pull Request". Explain what you did. If you only wanted to share the progress, but your PR is not ready for a review yet, please put **[WIP]** (Work In Progress) in the beginning of its name. 5. One of the QC developers will check your code. It will also be automatically tested. 6. Once approved the changes will be merged in the main repo. You can delete your branch. @@ -275,32 +455,90 @@ For a new feature, just create a new branch for it and use the same procedure. D General ALICE Git guidelines can be accessed [here](https://alisw.github.io/git-tutorial/). -## DPL workflow customization +## Data sources + +In the final system, the qc gets real data from the DPL devices or the readout processes. During development a number of possibilities are available for the detector teams to develop their QC. We list them below. -If you want to change the workflow, edit or copy `runBasic.cxx` or `runReadout.cxx`. For example... +### DPL workflow -TODO +When plugging the QC on a DPL workflow to monitor the output of one or several devices, one should of course have some data producer in the workflow itself. Several options exist, listed below. -## Usage of DS and QC in an existing DPL workflow +__Random data__ -TODO +As shown earlier in this documentation we provide a random data generator. The binary is called `o2-qc-run-producer` and many options are available (use `--help` to see them). -## Addition of parameters to a task +__Data file__ -One can tell the DPL driver to accept new arguments. This is done using the `customize` method at the top of your workflow definition (usually called "runXXX" in the QC). +To write and read data files in the DPL, please refer to the [RawFileWriter](https://github.com/AliceO2Group/AliceO2/tree/dev/Detectors/Raw#rawfilewriter) and [RawFileReader](https://github.com/AliceO2Group/AliceO2/tree/dev/Detectors/Raw#rawfilereader). -For example, to add two parameters of different types do : +On the same page, there are instructions to write such file from Simulation. + +Another option to read a raw data file, produced by Simulation or recorded with `o2-readout-exe` per instance, is to use directly the program `o2-raw-file-reader-workflow` in O2 as described [here](https://github.com/AliceO2Group/AliceO2/tree/dev/Detectors/Raw#raw-data-file-reader-workflow) (the config file is described [earlier in the page](https://github.com/AliceO2Group/AliceO2/tree/dev/Detectors/Raw#rawfilereader)). +``` +o2-raw-file-reader-workflow --conf myConf.cfg | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/readout.json ``` -void customize(std::vector& workflowOptions) + +__Live detector data__ + +If the detector is ready and connected to the CRU(s), one can of course start the full data taking workflow, including the SubTimeFrameBuilder and the DPL processing and plug the QC onto it. + +## Run number and other run attributes (period, pass type, provenance) + +The run attributes, such as the run number, are provided to the modules through the object `activity`: +```c++ +void ExampleTask::startOfActivity(const Activity& activity) { - workflowOptions.push_back( - ConfigParamSpec{ "config-path", VariantType::String, "", { "Path to the config file. Overwrite the default paths. Do not use with no-data-sampling." } }); - workflowOptions.push_back( - ConfigParamSpec{ "no-data-sampling", VariantType::Bool, false, { "Skips data sampling, connects directly the task to the producer." } }); -} + ILOG(Info, Support) << "Run number : " << activity.mId << ENDM; ``` +The other attributes are +- `type`, i.e. the run type +- `periodName` +- `passName` +- `provenance`, can be either `qc` for normal data or `qc_mc` for Monte Carlo data. + +When running with the aliECS the run number is automatically provided to the modules' code. + +To set a run number and the other attributes in an "uncontrolled" environment such as a development setup, +one can set it in the config file: +```yaml + "Activity": { + "number": "42", + "type": "NONE", + "periodName": "", "": "Period name - e.g. LHC22c, LHC22c1b_test", + "passName": "", "": "Pass type - e.g. spass, cpass1", + "provenance": "qc", "": "Provenance - qc or qc_mc" + }, +``` + +These attributes are also stored in the QCDB as metadata under the names `PeriodName`, `RunNumber` and `PassName`. +The provenance is treated differently and is used to modify the path to the object by changing the top level directory: +`qc/TST/QcTask/example` vs `qc_mc/TST/QcTask/example`. + +The way we compute the run number is done in this order: +1. Pick the run number from aliECS +2. If not found, pick the run number from the config file +3. If not found, set it to `0` otherwise +## A more advanced example + +A more complete example is available. The config file is called [advanced.json](../Framework/advanced.json). +The workflow is made of 3 sources, intermediate processing steps, 3 sinks and a Dispatcher connecting two QC tasks to selected data outputs. +It also includes a few Checks, Aggregators and PostProcessing tasks. +The topology is not intended to represent any particular physics processing, it is just an example with multiple data processors. + +![alt text](images/advanced.png) + +To run it do either +``` +# runs a fake processing workflow and associated QC +o2-qc-run-advanced +``` +or +``` +# o2-qc-run-advanced creates a fake processing workflow, while the QC is spawned by o2-qc +o2-qc-run-advanced --no-qc | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/advanced.json +``` --- -[← Go back to Quickstart](QuickStart.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Advanced Topics →](Advanced.md) +[← Go back to Quickstart](QuickStart.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Framework →](Framework.md) diff --git a/doc/PostProcessing.md b/doc/PostProcessing.md new file mode 100644 index 0000000000..8d097a365c --- /dev/null +++ b/doc/PostProcessing.md @@ -0,0 +1,1222 @@ +# Post-processing + + + + + + + +* [The post-processing framework](#the-post-processing-framework) + * [Post-processing interface](#post-processing-interface) + * [Configuration](#configuration) + * [Running it](#running-it) +* [Convenience classes](#convenience-classes) + * [The TrendingTask class](#the-trendingtask-class) + * [The SliceTrendingTask class](#the-slicetrendingtask-class) + * [The ReferenceComparatorTask class](#the-referencecomparatortask-class) + * [The CcdbInspectorTask class](#the-ccdbinspectortask-class) + * [The QualityTask class](#the-qualitytask-class) + * [The BigScreen class](#the-bigscreen-class) +* [More examples](#more-examples) + + + +## The post-processing framework + +This framework is intended for planned post-processing of objects generated by QC Tasks, Checks and correlating them with other data. The most common use-cases include correlation and trending of different properties of the detectors. + + The users can write their own Post-processing Tasks or use the ones provided by the framework (see [Convenience classes](#convenience-classes)) which are supposed to cover the usual needs. Post-processing Tasks run asynchronously to data-taking, but can be triggered by a set of selected events. + +### Post-processing interface + +Any Post-processing Task should inherit PostProcessingInterface, which includes four methods: + +* `configure` (optional) - provides the task with configuration. +* `initialize` - initializes the task and its data, given the event which it was triggered by. +* `update` - updates the task and its data, given the event which it was triggered by. +* `finalize` - finalizes the processing, given the event which it was triggered by. + +Interfaces to databases and other services are accesible via `ServiceRegistry`, which is an argument to the last three methods. They are invoked when any of the specified triggers is up, which can be: + +* Start Of Run - triggers when receives SOSOR message from `aliecs.run` kafka topic which has **DIFFERENT** run number **AND** environment id than `Activity` class in config +* End Of Run - triggers when receives SOEOR message from `aliecs.run` kafka topic which has **DIFFERENT** run number **AND** environment id than `Activity` class in config +* Start Of Fill (SOF, not implemented yet) +* End Of Fill (EOF, not implemented yet) +* Periodic - triggers when a specified period of time passes +* New Object - triggers when an object in QCDB is updated +* For Each Object - triggers for each object in QCDB which matches an Activity +* For Each Latest - triggers for the latest object in QCDB for each matching Activity, sorted by Period, Pass and Run +* Once - triggers only first time it is checked +* Always - triggers each time it is checked + +Triggers are complemented with: + +* timestamps which correspond the time when trigger started to be valid, in form of ms since epoch, just like in CCDB and QCDB, +* `last` flag, being `true` if it is the last time trigger will fire, +* `Activity` object, which contains metadata such as run type and number, pass name, period name, data provenance. + +For example, the periodic trigger will provide evenly spaced timestamps, even if the trigger is checked more rarely. +The New Object trigger provide the timestamp of the updated object. The timestamps and Activites should be used to +access databases, so any Post-processing Task can be rerun at any time for any run and reconstruction pass. + +The Activity specified at the top of the configuration file is used to for triggers to match objects which belong to +certain run, pass, period. A lack of a parameter or a default value are treated as a wildcard. Since AliECS overwrites +the run number during initialization, one may force the run number wildcard by adding the following key-value pair: + +```json +{ + "qc": { + "config": { + "postprocessing": { + "matchAnyRunNumber": "true" + } + }, +``` + +MonitorObjects may be saved by registering them in ObjectManager, similarly to normal QC Tasks (recommended, see + examples linked below), or by using DatabaseInterface directly. Please note, that created objects have to + registered in ObjectManager to make them accessible by Checks. + +Please refer to [`SkeletonPostProcessing`](https://github.com/AliceO2Group/QualityControl/blob/master/Modules/Skeleton/include/Skeleton/SkeletonPostProcessing.h) for a minimal illustration of inheriting the interface, or to [`TrendingTask`](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/include/QualityControl/TrendingTask.h) for a fully functional example. One can generate their own post-processing task by using the `o2-qc-module-configurator` helper, as described in the [Module Creation](ModulesDevelopment.md#module-creation) chapter. + +### Configuration + +Running the post-processing is configured in a similar manner as it is for QC Tasks and Checks - the configuration parameters are stored in a JSON file or in the Configuration database (at later development stage). The configuration's path should be passed to the application running a task. + +This is a snippet of a JSON structure which configures a post-processing task: + +```json +{ + "qc": { + "config": { + ... + "postprocessing": { + "periodSeconds": 10.0 + } + }, + "postprocessing": { + "MyPostProcessingTaskID": { + "active": "true", + "taskName": "MyPostProcessingTask", + "className": "o2::quality_control_modules::my_module::MyPPTask", + "moduleName": "QcMyModule", + "detectorName": "TST", + "initTrigger": [ + "SOR" + ], + "updateTrigger": [ + "10mins" + ], + "stopTrigger": [ + "EOR", + "10hours" + ] + }, + ... + } + } +} +``` + +Each task is uniquely identified by its id (`MyPostProcessingTaskID`). One can activate it by setting the `"active"` field to `"true"`. The task is loaded given its full `"className"` and a `"moduleName"` where it is located. The `"detectorName"` might be used by tasks to store generated data in correct paths in QCDB. The `"initTrigger"`, `"updateTrigger"` and `"stopTrigger"` lists contain triggers which should invoke corresponding interface methods. The `"periodSeconds"` parameter in the common section defines how often should the triggers be checked. Values larger than 10 seconds should be applied when running synchronously to data taking, while very small periods can be used when processing batches of already existing objects. + +Checks can be applied to the results of Post-processing Tasks just as for normal QC Tasks. However, one should use + data source type of `"PostProcessing"` instead of `"Task"`: + +``` +... + "checks": { + "ExamplePPCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "PostProcessing", + "name": "ExampleTrend", + "MOs": ["mean_of_histogram"] + }] + } + }, +... +``` + +#### Definition and access of user-specific configuration + +A postprocessing task can access custom parameters declared in the configuration file at `qc.postprocessing..extendedTaskParameters`. They are stored inside an object of type `CustomParameters` named `mCustomParameters`, which is a protected member of `TaskInterface`. + +[More details](Advanced.md#definition-and-access-of-user-specific-configuration) can be found about this feature in the Tasks (same behaviour). + +#### Triggers configuration + +Each of the three methods can be invoked by one or more triggers. Below are listed the possible options (case insensitive). + +* `"sor"` or `"startofrun"` - start of a **different** run (useful for long-running post-processing which observes many data taking runs). Please ensure that the configuration file includes a kafka broker. +* `"eor"` or `"endofrun"` - end of a **different** run (useful for long-running post-processing which observes many data taking runs). Please ensure that the configuration file includes a kafka broker. +* `"sof"` or `"startoffill"` - Start Of Fill (not implemented yet) +* `"eof"` or `"endoffill"` - End Of Fill (not implemented yet) +* `""` - Periodic - triggers when a specified period of time passes. For example: "5min", "0.001 seconds", "10sec", "2hours". +* `"newobject:[qcdb/ccdb]:"` - New Object - triggers when an object in QCDB or CCDB is updated (applicable for synchronous processing). For example: `"newobject:qcdb:qc/TST/MO/QcTask/Example"` +* `"foreachobject:[qcdb/ccdb]:"` - For Each Object - triggers for each object in QCDB or CCDB which matches the activity indicated in the QC config file (applicable for both synchronous and asynchronous processing). This trigger contains monitor cycle of required object in its metadata since v1.178.0 +* `"foreachlatest:[qcdb/ccdb]:"` - For Each Latest - triggers for the latest object version in QCDB or CCDB + for each matching activity (applicable for asynchronous processing). It sorts objects in ascending order by period, + pass and run. +* `"once"` - Once - triggers only first time it is checked +* `"always"` - Always - triggers each time it is checked +* `"userorcontrol"` - triggers when upon corresponding START and STOP state transitions. This is the recommended trigger for `initTrigger` and `stopTrigger`. + +#### Using different databases + +It might happen that one wants to get data and store data in different databases. Typically if you want to test with +production data but store the object in test. + +This can be achieved by setting the extra parameter `sourceRepo` in the task. You have to add it to all your tasks as this is not a global +parameter. It is optional. + +The destination repository is always the global one defined in the global configuration under `qc.config.database`. + +``` + "postprocessing": { + "MyPostProcessingTaskID": { + ... + "sourceRepo": { + "implementation": "CCDB", + "host": "another-test.cern.ch:8080" + }, + ... + } + ... + } +``` + +#### Output object validity + +By default, the objects published by post-processing tasks use narrowest validity which contains all past triggers (except of `userorcontrol`). +In other words, a trend's validity covers all of the input objects' validity. + +If a post-processing task is not used for trending, but e.g. to decorate or correlate some moving window objects while preserving their validity, one can set the `validityFromLastTriggerOnly` parameter: + +``` + "postprocessing": { + "MyPostProcessingTaskID": { + ... + "validityFromLastTriggerOnly": "true", "": "false by default" + ... + } + ... +``` + +### Running it + +The post-processing tasks can be run in three ways. First uses the usual `o2-qc` executable which relies on DPL and + it is the only one which allows to run checks over objects generated in post-processing tasks. This is will be one + of two ways to run PP tasks in production. +To try it out, use it like for any other QC configuration: + +``` +o2-qc -b --config json://${QUALITYCONTROL_ROOT}/etc/postprocessing.json +``` + +All declared and active tasks in the configuration file will be run in parallel. +To change how often triggers are evaluated, modify the value for `qc.config.postprocessing.periodSeconds` in the config file. + +To run a different configuration which trends all the `qc/TST/MO/QcTask/example` objects existing in QCDB, try the following: + +``` +o2-qc -b --config json://${QUALITYCONTROL_ROOT}/etc/postprocessing-async.json +``` + +Debugging post-processing tasks might be easier when using the `o2-qc-run-postprocessing` application (only for + development) or with `o2-qc-run-postprocessing-occ` (both development and production), as they are one-process + executables, running only one, chosen task. + +To run the basic example, use the command below. The `--config` parameter should point to the configuration file. + +``` +o2-qc-run-postprocessing --config json://${QUALITYCONTROL_ROOT}/etc/postprocessing.json --id ExamplePostprocessing +``` + +As it is configured to invoke each method only `"once"`, you will see it initializing, entering the update method, then finalizing the task and exiting. + +This executable also allows to run a Post-processing task in batch mode, i.e. with selected timestamps (see the + `--timestamps` argument). This way, one can rerun a task over old data, if such a task actually respects given + timestamps. + +To have more control over the state transitions or to run a standalone post-processing task in production, one should + use `o2-qc-run-postprocessing-occ`. It is run almost exactly as the previously mentioned application, however one has + to use [`peanut`](https://github.com/AliceO2Group/Control/tree/master/occ#single-process-control-with-peanut) to drive + its state transitions and push the configuration. + +To try it out locally, run the following in the first terminal window (we will try out a different task this time): + +``` +o2-qc-run-postprocessing-occ --name ExampleTrend --period 10 +``` + +In the logs you will see a port number which listens for RPC commands. Remember it. + +``` +no control port configured, defaulting to 47100 +no role configured, defaulting to default-role +gRPC server listening on port 47100 +``` + +In the second window, run the following. Use the port number from the output of the QC executable. + +``` +# If you haven't built it: +# aliBuild build Coconut --defaults o2-dataflow +alienv enter coconut/latest +OCC_CONTROL_PORT=47100 peanut +``` + +A simple terminal user interface will open, which will allow you to trigger state transitions. Use it to load the configuration by entering the path to the configuration file. The usual transition sequence, which you might want to try out, is CONFIGURE, START, STOP, RESET, EXIT. + +## Convenience classes + +We aim to provide some convenience classes which should cover the most common post-processing use-cases. Everyone is free to propose extensions to them or write their own tasks for more specific usages taking these as a starting point. + +### The TrendingTask class + +`TrendingTask` is a post-processing task which uses a TTree to trend objects in the QC database and produce basic plots. The [Post-processing example](QuickStart.md#post-processing-example) in the QuickStart showcases the possibilities of this class. + +The following scheme shows how the class is designed. +It can access **data sources** which are Monitor Objects and Quality Objects from the QCDB, as well as custom +objects from the CCDB. + +The objects' characteristics which should be tracked are extracted by **Reductors** - simple plugins. +The framework provides a set of Reductors for commonly used data structures, but any custom Reductor might be used as well. +In such case, to reduce a Monitor Object or Quality Object, one has to inherit ReductorTObject, while reducing objects from CCDB can be achieved by implementing ReductorConditionAny. + +All the values are stored in a **TTree**. +Each data source forms a separate branch, with its leaves being the individual values. +Additionally added columns include a `time` branch and a `metadata` branch, consisting of `runNumber` (integer) and `runNumberStr` (string/label). + +The TTree is stored back to the **QC database** each time it is updated. +In addition, the class exposes the [`TTree::Draw`](https://root.cern/doc/master/classTTree.html#a73450649dc6e54b5b94516c468523e45) interface, which allows to instantaneously generate **plots** with trends, correlations or histograms that are also sent to the QC database. +Multiple graphs can be drawn on one plot, if needed. + +![TrendingTask](images/trending-task.png) + +#### Configuration + +As this class is a post-processing task, it inherits also its configuration JSON template. It extends it, though, +some additional parameters. + +```json +{ + "qc": { + ... + "postprocessing": { + "ExampleTrend": { + "active": "true", + "className": "o2::quality_control::postprocessing::TrendingTask", + "moduleName": "QualityControl", + "detectorName": "TST", + "resumeTrend": "false", + "producePlotsOnUpdate": "true", + "trendingTimestamp": "validUntil", + "dataSources": [], + "plots": [], + "initTrigger": [ "once" ], + "updateTrigger": [ "5 seconds" ], + "stopTrigger": [] + } + } + } +} +``` + +Data sources are defined by filling the corresponding structure, as in the example below. For the key `"type"` use: + +* `"repository"` if you access a Monitor Object. +* `"repository-quality"` if that should be a Quality Object. +* `"condition"` for trending an object of any type in CCDB. + +The `"names"` array should point to one or more objects under a common `"path"` in the repository. The values of `"reductorName"` and `"moduleName"` should point to a full name of a data Reductor and a library where it is located. One can use the Reductors available in the `Common` module or write their own by inheriting the interface class. + +Field `"reductorParameters"` is used to configure Reductors (classes inherited from `"o2::quality_control::postprocessing::Reductor"` interface). It uses same format as `"extendedTaskParameters"` field. + +```json +{ + ... + "dataSources": [ + { + "type": "repository", + "path": "TST/MO/QcTask", + "names": [ "example" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + }, + { + "type": "repository-quality", + "path": "TST/QO", + "names": [ "QcCheck" ], + "reductorName": "o2::quality_control_modules::common::QualityReductor", + "moduleName": "QcCommon" + }, + { + "type": "condition", + "path": "GRP/Calib", + "names": [ "LHCClockPhase" ], + "reductorName": "o2::quality_control_modules::common::LHCClockPhaseReductor", + "reductorParameters": { + "default": { + "default": { + "key":"value" + } + } + } + + "moduleName": "QcCommon" + } + ], + ... +} +``` + +Similarly, plots are defined by adding proper structures to the `"plots"` list, as shown below. The plot will be + stored under the `"name"` value and it will have the `"title"` value shown on the top. +The `"graphs"` list can include one more objects, where the `"varexp"`, `"selection"` and `"option"` fields correspond to the arguments of the [`TTree::Draw`](https://root.cern/doc/master/classTTree.html#a73450649dc6e54b5b94516c468523e45) method. +If there is more than one graph drawn on a plot, a legend will be automatically created. +Optionally, one can use `"graphError"` to add x and y error bars to a graph, as in the first plot example. +The `"name"` and `"varexp"` are the only compulsory arguments, others can be omitted to reduce configuration files size. +`"graphAxisLabel"` allows the user to set axis labels in the form of `"Label Y axis: Label X axis"`. With `"graphYRange"` numerical values for fixed ranges of the y axis can be provided in the form of `"Min:Max"`. + +```json +{ + ... + "plots": [ + { + "name": "mean_of_histogram", + "title": "Mean trend of the example histogram", + "graphAxisLabel": "Mean X:time", + "graphYRange": "-2.3:6.7", + "graphs" : [{ + "title": "mean trend", + "varexp": "example.mean:time", + "selection": "", + "option": "*L", + "graphErrors": "5:example.stddev" + }] + }, + { + "name": "histogram_of_means", + "title": "Distribution of mean values in the example histogram", + "graphs" : [{ + "varexp": "example.mean", + "selection": "", + "option": "*L" + }] + }, + { + "name": "example_quality", + "title": "Trend of the example histogram's quality", + "graphs" : [{ + "varexp": "QcCheck.name:time", + "selection": "", + "option": "*" + }] + } + ], + ... +} +``` + +There is also a possibility to explicitly change the legend, and line, fill and marker styles in case of a need for more customized plots. Example is given below: + +```json +{ + ... + "plots": [ + { + "name": "trend_cycle_duration_ntf_corr", + "title": "cycle duration: ns/TF;time;cycle duration [ns/TimeFrames]", + "legend": { "x1": 0.70, "y1": 0.70, "x2": 0.93, "y2": 0.90, "nColumns": 1 }, + "graphs": [ + { + "title": "cycle duration [ns]", + "varexp": "CycleDuration.entries:time", + "selection": "", + "option": "*LP", + "style": { "lineColor": 38, "markerColor": 4, "markerStyle": 20, "lineWidth": 2 } + }, + { + "title": "cycle duration [TimeFrames]", + "varexp": "CycleDurationNTF.entries:time", + "selection": "", + "option": "*LP", + "style": { "lineColor": 8, "markerColor": 3, "markerStyle": 30, "lineWidth": 2 } + }, + { + "title": "cycle duration: ns/TF;time;cycle duration [ns/TimeFrames]", + "varexp": "CycleDuration.entries/CycleDurationNTF.entries:time", + "selection": "", + "option": "*LP", + "style": { "lineColor": 31, "markerColor": 30, "markerStyle": 23, "lineWidth": 2 } + } + ] + } + ], + ... +} +``` + +The specific options are: + +* `"legend"` ~ allows to specify precise location and shape of legend, as well as disable it completely + * `"x1"`, `"y1"`, `"x2"`, `"y2"` ~ manual coordinates of the legend box, all need to be present for custom legend placement to take account + * `"nColumns"` ~ number of columns in the legend +* `"style"` ~ allows to change the style of the line and marker + * `"lineWidth"` ~ width of the line, integer as defined by ROOT [TAttLine class](https://root.cern.ch/doc/master/classTAttLine.html) + * `"lineStyle"` ~ style of the line, integer as defined by ROOT [TAttLine class](https://root.cern.ch/doc/master/classTAttLine.html) + * `"lineColor"` ~ color of the line, an integer as defined by ROOT [TColor class](https://root.cern.ch/doc/master/classTColor.html) + * `"markerColor"` ~ color of the marker, integer as defined by ROOT [TAttMarker class](https://root.cern.ch/doc/master/classTAttMarker.html) + * `"markerStyle"` ~ style of the marker, integer as defined by ROOT [TAttMarker class](https://root.cern.ch/doc/master/classTAttMarker.html) + * `"markerSize"` ~ size of the marker, float as defined by ROOT [TAttMarker class](https://root.cern.ch/doc/master/classTAttMarker.html) + * `"fillColor"` ~ color of the fill, an integer as defined by ROOT [TColor class](https://root.cern.ch/doc/master/classTColor.html) + * `"fillStyle"` ~ style of the fill, integer as defined by ROOT [TAttFill class](https://root.cern.ch/doc/master/classTAttFill.html) + + +WARNING: Any style parameters specified will override colliding parameters in `"option"`. + +However, lines, fills and markers still have to be enabled in `"option"` to appear (e.g. with `"option" : "LPF"`). + +To decide whether plots should be generated during each update or just during finalization, +use the boolean flag `"producePlotsOnUpdate"`. + +To pick up the last existing trend which matches the specified Activity, set `"resumeTrend"` to `"true"`. + +To generate plots only when all input objects are available, set `"trendIfAllInputs"`. + +`"trendingTimestamp"` allows to select which timestamp should be used as the trending point. +The available options are `"trigger"` (timestamp provided by the trigger), `"validFrom"` (validity start in activity provided by the trigger), `"validUntil"` (validity end in activity provided by the trigger, default). + +### The SliceTrendingTask class + +The `SliceTrendingTask` is a complementary task to the standard `TrendingTask`. This task allows the trending of canvas objects that hold multiple histograms (which have to be of the same dimension, e.g. TH1) and the slicing of histograms. The latter option allows the user to divide a histogram into multiple subsections along one or two dimensions which are trended in parallel to each other. The task has specific reductors for `TH1` and `TH2` objects which are `o2::quality_control_modules::common::TH1SliceReductor` and `o2::quality_control_modules::common::TH2SliceReductor`. + +#### Configuration + +Similar to the `TrendingTask`, the configuration of the `SliceTrendingTask` is divided into `"dataSources"` and `"plots"`, where both parts have been extended in respect to the standard trending. Here, only changes in respect to the standard trending task are highlighted. + +The data sources are extended by `"axisDivision"` which configures the slicing of the histograms. The inner most brackets relate the the actual axis. Its configuration can be understood as `"axisDivision": [ [x-Axis], [y-Axis] ]` where `[y-Axis]` does not need to be provided in case of one-dimensional objects. The values provided in `[x(y)-Axis]` are the numerical boundaries of the x(y)-axis. For *n* slices, one thus needs to provide *n*+1 values in ascending order. Protections are added such that each bin is part of only one slice. If the outer brackets are left empty (i.e. `"axisDivision": [ ]`), no slicing is applied and the whole histogram is trended as in the standard trending task. + +``` +{ + ... + "dataSources": [ + { + "type": "repository", + "path": "TST/MO/QcTask", + "names": [ "example" ], + "reductorName": "o2::quality_control_modules::common::TH1SliceReductor", + "axisDivision": [ [ "0", "4500", "10500" ] ], + "moduleName": "QcCommon" + } + ], + ... +} +``` + +The `"plot"` configuration has changed in respect to the standard trending task as follows: +The `"varexp"` selection is still set up as `"Histogram.Var:TrendingType"` where `"Histogram.Var"` is trended vs `"TrendingType"`. The options for `"Var"`are: + +* `"entries"`: Number of entries of the slice +* `"meanX"`: Mean along the x-axis of the slice +* `"stddevX"`: Stddev along the x-axis of the slice +* `"errMeanX"`: Error of the mean along the x-axis of the slice +* `"meanY"`: Mean along the y-axis of the slice. +* `"stddevY"`: Stddev along the y-axis of the slice +* `"errMeanY"`: Error of the mean along the y-axis of the slice + +In case of 1 dimensional objects, `"meanY"` is calculated as the arithmetic mean of all the bin values in the slice. The respective `"stddevY"` and `"errMeanY"` are provided as well. + +The options for `"TrendingType"` are limited to: + +* `"time"`: The quantity `"Histogram.Var"` of all slices is trended as a function of time. Each slice-trending has its own graph which are all published on one canvas. +* `"multigraphtime"`: The quantity `"Histogram.Var"` of all slices is trended as a function of time. All slice-trendings are published on one `"TMultiGraph"`. A legend is provided which contains the numerical boundaries of the slices. +* `"slices"`: The quantity `"Histogram.Var"` of all slices is trended as a function of the geometrical center of the slices. Always the latest timestamp is plotted. +* `"slices2D"`: The quantity `"Histogram.Var"` of all slices is trended as a function of the geometrical center of the slices in two dimensions. Always the latest timestamp is plotted. Errors (if used) are stored per bin but are not visualized. + +The field `"graphErrors"` is set up as `"graphErrors":"Var1:Var2"` where `Var1` is the error along y and `Var2` the error along x. For `Var1(2)` numerical values or the options listed for `Var` above can be set. The original histogram does not need to be provided as the task will take the histogram specified in `"varexp": "Histogram.Var:TrendingType"`. In `"graphYRange"` and `"graphXRange"` numerical values for fixed ranges of the x and y axis can be provided in the form of `"Min:Max"`. If provided, the task will set all x (or y) axis on the canvas to this range. `"graphAxisLabel"` allows the user to set axis labels in the form of `"Label Y axis: Label X axis"`. + +``` +{ + ... + "plots": [ + { + "name": "ExtendedTrending_meanX_of_histogram", + "title": "Mean X trend of the example histogram", + "varexp": "example.meanX:time", + "selection": "", + "option": "*L", + "graphErrors": "errMeanX:0.5", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "Mean X:time" + }, + { + "name": "ExtendedTrending_meanY_of_histogram_slices", + "title": "Mean Y trend of the example histogram", + "varexp": "example.meanY:slices", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:errMeanX", + "graphYRange": "", + "graphXRange": "-500.0:10000", + "graphAxisLabel": "Mean Y:Center of slices along x" + }, + { + "name": "ExtendedTrending_meanY_of_histogram_timeMultigraph", + "title": "Mean Y trend of the example histogram", + "varexp": "example.meanY:multigraphtime", + "selection": "", + "option": "*L", + "graphErrors": "errMeanY:0.5", + "graphYRange": "", + "graphXRange": "", + "graphAxisLabel": "Mean Y:time" + } + ], + ... +} +``` + +### The ReferenceComparatorTask class + +This post-processing task draws a given set of plots in comparison with their corresponding references, both as superimposed histograms and as current/reference ratio histograms. + +#### Configuration + +Currently the source of reference data is specified as a run-type and beam-type specific `referenceRun` number. This will be modified once a centralized way of accessing reference plots will become available in the framework. +The `notOlderThan` option allows to ignore monitor objects that are older than a given number of seconds. A value of -1 means "no limit". +The `ignorePeriodForReference` and `ignorePassForReference` boolean parameters control whether the period and/or pass names should be matched or not when querying the reference plots from the database. +A value of `"true"` (default) means that the reference plots are not required to match the period and/or pass names of the current run, while a value of `"false"` means that the reference plot is retrieved only if the corresponding period and/or pass names match those of the current run. + +The input MonitorObjects to be processed are logically divided in **dataGroups**. Each group is configured via the following parameters: + +* `inputPath`: path in the QCDB where the input objects are located +* `referencePath` (optional): specifies the path for the reference objects, if not set the `inputPath` is used +* `outputPath`: path in the QCDB where the output objects are stored +* `drawRatioOnly`: boolean parameter specifying wether to only draw the ratio plots, or the current/reference comparisons as well +* `legendHeight`: space reserved for the legend above the histograms, in fractions of the pad height; if the height is set to zero, the legend is not shown +* `drawOption1D`: the ROOT draw option to be used for the 1-D histograms +* `drawOption2D`: the ROOT draw option to be used for the 2-D histograms +* `logScale`: boolean parameter specifying wether to draw the value axis in log or linear scale (default is `false`) + + The input objects are searched within the `inputPath`, and the output plots are stored inside the `outputPath`. +It is also possible to optionally specify a different path for the reference objects, via the `referencePath` parameter. If not given, the `referencePath` will coincide with the `inputPath`. +The `normalizeReference` boolean parameter controls wether the reference histogram is scaled such that its integral matches that of the current plot. + +The checker extracts the current and reference plots from the stored MO, and compares them using external modules, specified via the `moduleName` and `comparatorName` parameters. The `threshold` parameter specifies the value used to discriminate between good and bad matches between the histograms. + +Four comparison modules are provided in the framework: + +1. `o2::quality_control_modules::common::ObjectComparatorDeviation`: comparison based on the average relative deviation between the bins of the current and reference histograms; the module accepts the following configuration parameters: + * `threshold`: the maximum allowed average relative deviation between current and reference histograms + * `rangeX`, `rangeY`: if set, the comparison is restricted to the bins in the specified X and Y ranges; bins outside the ranges are ignored +2. `o2::quality_control_modules::common::ObjectComparatorBinByBinDeviation`: comparison based on the relative deviation between each bin of the current and reference histograms; the module accepts the following configuration parameters: + * `threshold`: the maximum allowed relative deviation for each bin between current and reference histograms + * `rangeX`, `rangeY`: if set, the comparison is restricted to the bins in the specified X and Y ranges; bins outside the ranges are ignored + * `maxAllowedBadBins`: the maximum number of bins above threshold for which the quality is still considered Good +3. `o2::quality_control_modules::common::ObjectComparatorChi2`: comparison based on a standard chi2 test between the current and reference histograms; the module accepts the following configuration parameters: + * `threshold`: the minimum allowed chi2 probability + * `rangeX`, `rangeY`: if set, the Chi2 test is restricted to the bins in the specified X and Y ranges; bins outside the ranges are ignored +4. `o2::quality_control_modules::common::ObjectComparatorKolmogorov`: comparison based on a standard Kolmogorov test between the current and reference histograms; the module accepts the following configuration parameters: + * `threshold`: the minimum allowed Kolmogorov probability + +All the configuration parameters of the comparison modules optionally allow to restrict their validity to specific plots. +The following example specifies a threshold value common to all the plots, and then overrides the threshold and X range for all plots named `TrackEta`: + +```json + "extendedCheckParameters": { + "default": { + "default": { + "moduleName" : "QualityControl", + "comparatorName" : "o2::quality_control_modules::common::ObjectComparatorChi2", + "threshold" : "0.5", + "threshold:TrackEta" : "0.2", + "rangeX:TrackEta" : "-3.5,-2.5" + } + } + } +``` + +#### Full configuration example + +In the example configuration below, the relationship between the input and output histograms is the following: + +* `MCH/MO/Tracks/WithCuts/TrackEta` (1-D histogram) + * `MCH/MO/RefComp/TracksMCH/WithCuts/TrackEta` + * 1-D version, current and reference plots drawn superimposed in the same canvas, with the ratio below + * comparison with a chi2 test method +* `MCH/MO/Tracks/WithCuts/TrackEtaPhi` (2-D histogram) + * `MCH/MO/RefComp/TracksMCH/WithCuts/TrackEtaPhi` + * 2-D version, ratio between plots drawn on top with the current and reference plots drawn smaller at the bottom + * comparison with a chi2 test method (`"comparatorName" : "o2::quality_control_modules::common::ObjectComparatorChi2`) + +```json +{ + "qc": { + "config": { + "": "The usual global configuration variables" + }, + "postprocessing": { + "ExampleRefComp": { + "active": "true", + "className": "o2::quality_control_modules::common::ReferenceComparatorTask", + "moduleName": "QualityControl", + "detectorName": "MCH", + "extendedTaskParameters": { + "default": { + "default": { + "notOlderThan" : "300", + "referenceRun" : "551875", + "ignorePeriodForReference": "true", + "ignorePassForReference": "true" + } + }, + "PHYSICS": { + "pp": { + "referenceRun" : "551890" + } + } + }, + "dataGroups": [ + { + "name": "Tracks", + "inputPath": "MCH/MO/Tracks/WithCuts", + "referencePath": "MCH/MO/Tracks", + "outputPath": "Tracks/WithCuts", + "normalizeReference": "true", + "drawRatioOnly": "false", + "legendHeight": "0.2", + "drawOption1D": "E", + "drawOption2D": "COL", + "logScale": "false", + "inputObjects": [ + "TrackEta", + "TrackEtaPhi" + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks": { + "ExampleRefCheck": { + "active": "true", + "className": "o2::quality_control_modules::common::ReferenceComparatorCheck", + "moduleName": "QualityControl", + "detectorName": "MCH", + "policy": "OnAny", + "extendedCheckParameters": { + "default": { + "default": { + "moduleName" : "QualityControl", + "comparatorName" : "o2::quality_control_modules::common::ObjectComparatorChi2", + "threshold" : "0.5", + "threshold:TrackEta" : "0.2", + "rangeX:TrackEta" : "-3.5,-2.5", + "ratioPlotRange" : "0.5" + } + } + }, + "dataSource": [ + { + "type": "PostProcessing", + "name": "ExampleRefComp", + "MOs" : [ + "Tracks/WithCuts/TrackEta", + "Tracks/WithCuts/TrackEtaPhi" + ] + } + ] + } + } + } +} +``` + +### The CcdbInspectorTask class + +A post-processing task that checks the existence, time stamp and validity of CCDB/QCDB objects. +The task produces a 2-D plot with object indexes in the X-axis (the bin labels are set to the mnemonic name of the object defined in the configuration) and the check status on the Y-axis, where the first bin corresponds to OK and the following bins to different errors. The 2-D bins are populated according to the result of the object inspection. + +A `CcdbInspectorCheck` task receives the 2-D histogram produced by the CCDB inspector and outputs an overall quality based on the status flag of each object in the histogram. + +#### Configuration + +The input objects are specified in the `DataSources` section. Each object is identified by the following parameters: + +* `name`: the name of the object, which is used to label the X-axis bins of the output histogram +* `path`: the path of the object in the database +* `updatePolicy`: the policy with wich the object is updated. Possible values are: + * `atSOR`: the object is only created once after start-of-run + * `atEOR`: the object is only created once at end-of-run + * `periodic`: the object is created periodically during the run +* `cycleDuration`: for periodic objects, the time interval between updates +* `validatorName`: (optional) name of the software module used to validate the contents of the object +* `moduleName`: library where the validator module is located + +The task accepts the following configuration parameters: + +* `timeStampTolerance`: tolerance (in seconds) applied when comparing the actual and expected object time stamp +* `databaseType`: type of input database. Possible values are `ccdb` or `qcdb` (default: `ccdb`) +* `databaseUrl`: address of the database (default: `https://alice-ccdb.cern.ch`) +* `retryTimeout`: timeout (in seconds) for accessing the objects at the task finalization +* `retryDelay`: delay (in seconds) between the retries when accessing the objects at the task finalization +* `verbose`: print additional debugging messages + +```json +{ + "qc": { + "config": { + "": "The usual global configuration variables" + }, + "postprocessing": { + "CcdbInspector": { + "active": "true", + "className": "o2::quality_control_modules::common::CcdbInspectorTask", + "moduleName": "QualityControl", + "detectorName": "GLO", + "extendedTaskParameters": { + "default": { + "default": { + "verbose" : "1", + "timeStampTolerance": "60", + "retryTimeout" : "60", + "retryDelay": "10", + "databaseType": "ccdb", + "databaseUrl": "https://alice-ccdb.cern.ch" + } + } + }, + "dataSources": [ + { + "name": "Mean Vertex", + "path": "GLO/Calib/MeanVertex", + "updatePolicy": "periodic", + "cycleDuration": "200", + "validatorName": "o2::quality_control_modules::glo::MeanVertexValidator", + "moduleName": "QcGLO" + }, + { + "name": "CTP Config", + "path": "CTP/Config/Config", + "updatePolicy": "atSOR" + }, + { + "name": "CTP Scalers", + "path": "CTP/Calib/Scalers", + "updatePolicy": "atEOR" + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "30 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + }, + "checks": { + "CcdbInspectorCheck": { + "active": "true", + "className": "o2::quality_control_modules::common::CcdbInspectorCheck", + "moduleName": "QualityControl", + "detectorName": "GLO", + "policy": "OnAll", + "dataSource": [ + { + "type": "PostProcessing", + "name": "CcdbInspector", + "MOs" : [ + "ObjectsStatus" + ] + } + ] + } + } + } +} +``` + +### The QualityTask class + +This task allows to trend a set of QualityObjects (QO) stored in the QCDB, and to display their name and value in human-readable format on a canvas (see the figure below). +For each QualityObject, it also creates a 1-D distribution of the corresponding quality values (*Good/Medium/Bad/Null*). +Each Quality can be optionally accompanied by a message that can provide instructions or further details based on the quality value. +Each time the post-processing task is triggered, the QualityObjects are fetched from the CCDB and the canvas is updated with the current values. +Moreover, the trending plot and 1-D distribution associated to each QO is updated accordingly. +Hence, the trending and 1-D distribution can be used to estimate the fraction of good data for a given run. + +![QualityTask](images/quality-task.png) + +#### Configuration + +The QualityObjects to be monitored and displayed are passed as **qualityGroups**, each containing **inputObjects** for a specific, line-separated group. +Each group requires a **name** and **path** to the contained objects. +A **title** can be added optionally, which appears at the top of the group in the canvas. +By listing certain qualities in **ignoreQualitiesDetails** one can ask to ignore Flags associated to QualityObjects. + +The **inputObjects** list should contain Quality Object names in a given group. +A **title** can be added, which is used in the summary canvas to denote given Quality Object. +If it is absent, **name** is used instead. +Optionally, one can add **messageBad**, **messageMedium**, **messageGood**, **messageNull** to add a message when a particular Quality is seen. + +At each update, the task retrieves the latest version of each input QualityObject, even if their validity range ends in the past. A task configuration parameter, called `maxObjectAgeSeconds`, allows to define the maximum allowed age (in seconds) of the retrieved objects. The age is defined as the difference between the the time stamp of the task update and the creation time stamp of the retrieved object. + +Here is a complete example of `QualityTask` configuration: + +```json +{ + "qc": { + "config": { + "": "The usual global configuration variables" + }, + "postprocessing": { + "ExampleQualityTask": { + "active": "true", + "className": "o2::quality_control_modules::common::QualityTask", + "moduleName": "QualityControl", + "detectorName": "TST", + "taskParameters": { + "maxObjectAgeSeconds": "300" + }, + "qualityGroups": [ + { + "name" : "global", + "title" : "GLOBAL TST QUALITY", + "path": "TST/QO", + "ignoreQualitiesDetails" : ["Null", "Good", "Medium", "Bad"], + "inputObjects": [ + { + "name" : "QcCheck", + "title" : "Aggregated TST Quality", + "messageBad" : "Inform XYZ on-call immediately", + "messageMedium": "Add bookkeeping entry", + "messageGood": "All checks are OK", + "messageNull": "Some histograms are empty!!!" + } + ] + }, + { + "name" : "details", + "title" : "TST DETAILS", + "path": "TST/QO", + "ignoreQualitiesDetails" : [], + "inputObjects": [ + { + "name" : "QcCheck", + "title" : "" + }, + { + "name" : "someNumbersCheck", + "title" : "" + }, + { + "name" : "XYZCheck", + "title" : "" + } + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol", "10 minutes" + ] + } + } + } +} +``` + +### The BigScreen class + +This task allows to display the aggregated quality flags of each system (detectors + global QC) in a single canvas, arranged as a matrix of colored boxes. +The system names are displayed above each box, while the quality flag is displayed inside the box. The box color follows the usual convention: + +* Good: green +* Medium: orange +* Bad: red +* Null: magenta + +In addition, the boxes are filled with a grey color is the corresponding QualityObjects cannot be retrieved or are too old. + +The color of the canvas background and of the detector labels can be customized with the `"foregroundColor"` and `"backgroundColor"` parameters. They accept interger values corresponding to the indexes of the [default ROOT colors](https://root.cern.ch/doc/master/classTColor.html#C01) or the indexes defined in the [color wheel](https://root.cern.ch/doc/master/classTColor.html#C02). The example below shows a color combination with white text over a dark gray background. + +The task is configured as follows: + +```json +{ + "qc": { + "config": { + "": "The usual global configuration variables" + }, + "postprocessing": { + "BigScreen": { + "active": "true", + "className": "o2::quality_control_modules::common::BigScreen", + "moduleName": "QualityControl", + "detectorName": "GLO", + "extendedTaskParameters": { + "default": { + "default": { + "nRows": "4", + "nCols": "5", + "borderWidth": "1", + "": "white text over dark gray background", + "foregroundColor": "0", + "backgroundColor": "923", + "maxObjectTimeShift": "10000", + "ignoreActivity": "0", + "labels": "CPV,EMC,FDD,FT0,FV0,HMP,ITS,MCH,MFT,MID,PHS,TPC,TOF,TRD,,TRK,MTK,VTX,PID" + } + } + }, + "dataSources": [ + { + "names": [ + "CPV:MCH/QO/CPVQuality/CPVQuality", + "EMC:MCH/QO/EMCQuality/EMCQuality", + "FDD:MCH/QO/FDDQuality/FDDQuality", + "FT0:MCH/QO/FT0Quality/FT0Quality", + "FV0:MCH/QO/FV0Quality/FV0Quality", + "HMP:MCH/QO/HMPQuality/HMPQuality", + "ITS:MCH/QO/ITSQuality/ITSQuality", + "MCH:MCH/QO/MCHQuality/MCHQuality", + "MFT:MCH/QO/MFTQuality/MFTQuality", + "MID:MCH/QO/MIDQuality/MIDQuality", + "PHS:MCH/QO/PHSQuality/PHSQuality", + "TOF:MCH/QO/TOFQuality/TOFQuality", + "TPC:MCH/QO/TPCQuality/TPCQuality", + "TRD:MCH/QO/TRDQuality/TRDQuality", + "ZDC:MCH/QO/ZDCQuality/ZDCQuality", + "TRK:MCH/QO/TRKQuality/TRKQuality", + "MTK:MCH/QO/MTKQuality/MTKQuality", + "VTX:MCH/QO/VTXQuality/VTXQuality", + "PID:MCH/QO/PIDQuality/PIDQuality" + ] + } + ], + "initTrigger": [ + "userorcontrol" + ], + "updateTrigger": [ + "60 seconds" + ], + "stopTrigger": [ + "userorcontrol" + ] + } + } + } +} +``` + +The following options allow to configure the appearence and behavior of the task: + +* `nRows`/`nCols`: size of the X-Y grid +* `borderWidth`: size of the border around the boxes +* `maxObjectTimeShift`: ignore quality objects that are older than a given number of seconds. A value of -1 means "no limit". +* `ignoreActivity`: if different from 0, the task will fetch objects regardless of their activity number and type. +* `labels`: comma-separated list of labels with boxes to be displayed in the canvas. Some places in the grid of boxes can be left empty by inserting two consecutive commas in the list, like between `TRD` and `TRK` in the example above + +The names in the data sources are composed of two parts, separated by a colon: + +``` +LABEL:OBJECT_PATH +``` + +The `LABEL` should match one of the elements of the `labels` parameter. The quality object will be associated to the corresponding box. + +## More examples + +This section contains examples of how to approach usual use-cases. + +### I want to run postprocessing alongside of synchronous QC and trend some QC object parameters + +Use NewObject as the update trigger: + +```json + "updateTrigger": [ "newobject:qcdb:TST/MO/QcTask/example" ], +``` + +If the post-processing runs in a different AliECS environment than the acquisition run, one should add the following +flag. Since AliECS adds a concrete run number to the workflow, the triggers would match only objects from the same run. + +```json + "qc": { + "config": { + ... + "postprocessing": { + "matchAnyRunNumber": "true" + } + } +``` + +### I want to run postprocessing synchronously and trend an object in CCDB + +Use Periodic or NewObject (but with `ccdb` as the DB) as the update trigger: + +```json + "updateTrigger": [ "newobject:ccdb:TPC/Calib/IDC_0_A" ], +``` + +or + +```json + "updateTrigger": [ "60s" ], +``` + +In your trending task, make sure to retrieve the object from the CCDB, not QCDB. + +If you want to keep the task running regardless of the data-taking activity, please contact the QC developers to set +up a long-running workflow in Nomad. + +### I want to run trend a moving window in a synchronous QC + +In your QC task, enable the moving window feature on the selected plot. +More details can be found in [Advanced/Moving window](Advanced.md#moving-window). + +Use the NewObject trigger on the moving window to update the task: + +```json + "updateTrigger": [ "newobject:qcdb:TST/MO/QcTask/mw/example" ], +``` + +In your postprocessing task, retrieve the object you want to trend. +For TrendingTask, it would be: + +```json + "dataSources": [ + { + "type": "repository", + "path": "TST/MO/QcTask/mw", + "names": [ "example" ], + "reductorName": "o2::quality_control_modules::common::TH1Reductor", + "moduleName": "QcCommon" + } + ] +``` + +### I want to run postprocessing on all already existing objects for a run (usable in sync AND async) + +Use ForEachObject as the update trigger: + +```json + "updateTrigger": [ "foreachobject:qcdb:TST/MO/QcTask/example" ], +``` + +Since objects are usually published in collections at the same time, you can use a path for one object to be triggered + for a collection of them (all objects produced by a QC Task). + +Use the Activity which matches the run, and (optionally) period and pass name: + +```json + "Activity": { + "number": "3212", + "type": "", + "passName": "apass2", + "periodName" : "OCT", + "provenance" : "qc" + }, + "postprocessing": { + "periodSeconds": 0.01 + } +``` + +### I want to run postprocessing for all objects in all the runs of a given reconstruction pass and period + +Use ForEachObject as the update trigger: + +```json + "updateTrigger": [ "foreachobject:qcdb:TST/MO/QcTask/example" ], +``` + +Use the Activity which leaves the run number empty, but indicate the pass and period names. + +```json + "Activity": { + "number": "", + "type": "", + "passName": "apass2", + "periodName" : "OCT", + "provenance" : "qc" + }, + "postprocessing": { + "periodSeconds": 0.01 + } +``` + +### I want to run postprocessing for all objects in all the runs of a given reconstruction pass and period which are valid in given time interval + +Use ForEachObject as the update trigger: + +```json + "updateTrigger": [ "foreachobject:qcdb:TST/MO/QcTask/example" ], +``` + +Use the Activity which leaves the run number empty, but indicate the pass and period names. +Add `start` and `end` values in ms since epoch to restrict the validity start of objects. + +```json + "Activity": { + "number": "", + "type": "", + "passName": "apass2", + "periodName" : "OCT", + "provenance" : "qc", + "start" : "1649417693630", + "end" : "1649417800000" + }, + "postprocessing": { + "periodSeconds": 0.01 + } +``` + +### I want to run postprocessing for the latest object for each available run in a given pass and period + +Use ForEachObject as the update trigger: + +```json + "updateTrigger": [ "foreachlatest:qcdb:TST/MO/QcTask/example" ], +``` + +This way you will avoid iterating on potential duplicates and intermediate objects, and get only the final versions instead. + +Use the Activity which leaves the run number empty, but indicate the pass and period names. + +```json + "Activity": { + "number": "", + "type": "", + "passName": "apass2", + "periodName" : "OCT", + "provenance" : "qc" + }, + "postprocessing": { + "periodSeconds": 0.01 + } +``` + +[← Go back to Framework](Framework.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) diff --git a/doc/QCDB.md b/doc/QCDB.md new file mode 100644 index 0000000000..f64479b80a --- /dev/null +++ b/doc/QCDB.md @@ -0,0 +1,192 @@ + +QCDB +--- + + + + + * [QCDB vs CCDB](#qcdb-vs-ccdb) + * [Details on the data storage format in the QCDB](#details-on-the-data-storage-format-in-the-qcdb) + * [Custom metadata for QC objects in the QCDB](#custom-metadata-for-qc-objects-in-the-qcdb) + * [Instructions to move an object in the QCDB](#instructions-to-move-an-object-in-the-qcdb) + * [Accessing objects in CCDB](#accessing-objects-in-ccdb) + * [Access GRP objects with GRP Geom Helper](#access-grp-objects-with-grp-geom-helper) + * [Global Tracking Data Request helper](#global-tracking-data-request-helper) + * [Local CCDB setup](#local-ccdb-setup) + + +## QCDB vs CCDB + +The MonitorObjects generated by Quality Control are stored in a dedicated repository called **QCDB**. +The run conditions, on the other hand, are located in another, separate database, called **CCDB**. + +Both are based on a technology called _CCDB_ which does not help with the confusion... + +## Details on the data storage format in the QCDB + +Each MonitorObject is stored as a TFile in the QCDB. +It is therefore possible to easily open it with ROOT when loaded with alienv. It also seamlessly supports class schema evolution. + +The MonitorObjects are stored at a path which is enforced by the qc framework : `/qc//MO//object/name` +Note that the name of the object can contain slashes (`/`) in order to build a sub-tree visible in the GUI. +The detector name and the taskname are set in the config file : + +```json +"tasks": { + "QcTask": { <---------- task ID + "active": "true", + "taskName": "QcTask", <--------- task name + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", <---------- detector name +``` + +If the task name is not specified then we use the task ID. +The quality is stored as a CCDB metadata of the object. + +## Custom metadata for QC objects in the QCDB + +One can add custom metadata on the QC objects produced in a QC task. +Simply call `ObjectsManager::addMetadata(...)`, like in + +``` + // add a metadata on histogram mHistogram, key is "custom" and value "34" + getObjectsManager()->addMetadata(mHistogram->GetName(), "custom", "34"); +``` + +This metadata will end up in the _QCDB_. + +It is also possible to add or update metadata of a MonitorObject directly: + +``` + MonitorObject* mo = getMonitorObject(objectName); + mo->addOrUpdateMetadata(key, value); +``` + +## Accessing objects in CCDB + +The recommended way (excluding postprocessing) to access the run conditions in the _CCDB_ is to use a `Lifetime::Condition` DPL input, which can be requested as in the query below: + +```json + "tasks": { + "MyTask": { + ... + "dataSource": { + "type": "direct", + "query": "randomcluster:MFT/COMPCLUSTERS/0;cldict:MFT/CLUSDICT/0?lifetime=condition&ccdb-path=MFT/Calib/ClusterDictionary" + }, + } + }, +``` + +The timestamp of the _CCDB_ object will be aligned with the data timestamp. + +If a task needs both sampled input and a _CCDB_ object, it is advised to use two data sources as such: + +```json + "tasks": { + "MyTask": { + ... + "dataSources": [{ + "type": "dataSamplingPolicy", + "name": "mftclusters" + }, { + "type": "direct", + "query": "cldict:MFT/CLUSDICT/0?lifetime=condition&ccdb-path=MFT/Calib/ClusterDictionary" + }], + } + }, +``` + +The requested _CCDB_ object can be accessed like any other DPL input in `monitorData`: + +``` +void QcMFTClusterTask::monitorData(o2::framework::ProcessingContext& ctx) +{ +... + auto mDictPtr = ctx.inputs().get("cldict"); +``` + +Geometry and General Run Parameters (GRP) can be also accessed with the [GRP Geom Helper](#access-grp-objects-with-grp-geom-helper). + +If your task accesses _CCDB_ objects using `UserCodeInterface::retrieveConditionAny`, please migrate to using one of the methods mentioned above. + +### Accessing from a Postprocessing task + +PostProcessingTasks do not take DPL inputs, so in this case `ConditionAccess::retrieveConditionAny` should be used (it's inherited by `PostProcessingInterface` and any children). + +## Access GRP objects with GRP Geom Helper + +To get GRP objects via a central facility, add the following structure to the task definition and set its values +according to the needs. + +```json + "myTask": { + ... + "grpGeomRequest" : { + "geomRequest": "None", "": "Available options are \"None\", \"Aligned\", \"Ideal\", \"Alignements\"", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "false", + "needPropagatorD": "false" + } + } +``` + +The requested objects will be available via [`GRPGeomHelper::instance()`](https://github.com/AliceO2Group/AliceO2/blob/dev/Detectors/Base/include/DetectorsBase/GRPGeomHelper.h) singleton. + +## Global Tracking Data Request helper + +To retrieve tracks and clusters for specific detectors or detector combinations, one can use the [`DataRequest`](https://github.com/AliceO2Group/AliceO2/blob/dev/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/RecoContainer.h) helper. +By adding the following structure to a QC task, the corresponding `InputSpecs` will be automatically added to the task. + +```json + "myTask": { + ... + "globalTrackingDataRequest": { + "canProcessTracks" : "ITS,ITS-TPC", + "requestTracks" : "ITS,TPC-TRD", + "canProcessClusters" : "TPC", + "requestClusters" : "TPC", + "mc" : "false" + } + } +``` + +Then, the corresponding tracks and clusters can be retrieved in the code using `RecoContainer`: + +```c++ +void MyTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + o2::globaltracking::RecoContainer recoData; + if (auto dataRequest = getGlobalTrackingDataRequest()) { + recoData.collectData(ctx, *dataRequest); + } +} +``` + +## Local CCDB setup + +Having a central ccdb for test (ccdb-test) is handy but also means that everyone can access, modify or delete the data. If you prefer to have a local instance of the CCDB, for example in your lab or on your development machine, follow these instructions. + +1. Download the local repository service from + +2. The service can simply be run with + `java -jar local.jar` + +It will start listening by default on port 8080. This can be changed either with the java parameter “tomcat.port” or with the environment variable “TOMCAT_PORT”. Similarly the default listening address is 127.0.0.1 and it can be changed with the java parameter “tomcat.address” or with the environment variable “TOMCAT_ADDRESS” to something else (for example ‘*’ to listen on all interfaces). + +By default the local repository is located in /tmp/QC (or java.io.tmpdir/QC to be more precise). You can change this location in a similar way by setting the java parameter “file.repository.location” or the environment variable “FILE_REPOSITORY_LOCATION”. + +The address of the CCDB will have to be updated in the Tasks config file. + +At the moment, the description of the REST api can be found in this document : + + +--- + +[← Go back to Configuration](Configuration.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to FLP Suite →](FLPsuite.md) + diff --git a/doc/QuickStart.md b/doc/QuickStart.md index a5c5bd9ecc..d6a1942ecf 100644 --- a/doc/QuickStart.md +++ b/doc/QuickStart.md @@ -1,133 +1,138 @@ # QuickStart - + - * [QuickStart](#quickstart) - * [Requirements](#requirements) - * [Setup](#setup) - * [Environment loading](#environment-loading) - * [Execution](#execution) - * [Basic workflow](#basic-workflow) - * [Readout chain](#readout-chain) +* [Read this first!](#read-this-first) +* [Requirements](#requirements) +* [Setup](#setup) + * [Environment loading](#environment-loading) +* [Execution](#execution) + * [Basic workflow](#basic-workflow) + * [Post-processing example](#post-processing-example) + - +## Read this first! - +This page will give you a basic idea of the QC and how to run it. Please read it *in its entirety* and run the commands along the way. Do not start developing your module before you have reached the next section called "Modules Development". Also, make sure you have pulled the latest QC version. -[↑ Go to the Table of Content ↑](../README.md) | [Continue to Modules Development →](ModulesDevelopment.md) +We would be very grateful if you could report to us any error or inaccuracy you find. + +Thanks! ## Requirements -A Linux machine (CC7 or Ubuntu) or a Mac. See the O2 instructions below for the exact supported versions. +A RHEL9 machine or a Mac (although you might have some issues running Readout). Ubuntu is only supported on a best-effort basis. ## Setup -2. Install O2 - * We use alibuild, see complete instructions [here](https://alice-doc.github.io/alice-analysis-tutorial/building/). +1. Setup O2 environment and tools
We use aliBuild, **follow the complete instructions** [here](https://alice-doc.github.io/alice-analysis-tutorial/building/). Make sure to: + 1. Install GLFW to have GUIs in the DPL (optional, **the DPL GUI do not work in containers nor over SSH**). + * RHEL: should come with the standard installation + * Mac: `brew install glfw` -3. Prepare the QualityControl development package +2. Prepare the QualityControl development package * `aliBuild init QualityControl@master --defaults o2` -4. Build/install the QualityControl, its GUI (qcg) and the readout. The simplest is to use the metapackage `flpproto`. - * `aliBuild build flpproto --defaults o2` - - At this point you might encounter a message about missing system requirements. Run `aliDoctor flpproto` to get a full information about what is missing and how to install it. -5. Install GLFW to have GUIs in the DPL (optional, DPL GUIs do not work in containers). - * On CC7 : `sudo yum install glfw-devel --enablerepo=epel` - * On Mac : `brew install glfw` +4. Build/install the QualityControl. + * `aliBuild build QualityControl --defaults o2` + * At this point you might encounter a message about missing system requirements. Run `aliDoctor --defaults o2 QualityControl` to get a full information about what is missing and how to install it. -Note : you can also use the alibuild "defaults" called `o2-dataflow` to avoid building simulation related packages. +Note: you can also use the alibuild "defaults" called `o2-dataflow` to avoid building simulation related packages. If you plan to use `Readout`, you can build both with one `aliBuild` command by listing them one after another, space-separated. ### Environment loading -Whenever you want to work with O2 and QualityControl, do either `alienv enter flpproto/latest` or `alienv load flpproto/latest`. +Whenever you want to work with O2 and QualityControl, do +```alienv enter QualityControl/latest``` + +To load multiple independent packages, list them one after the other, space-separated (e.g. `alienv enter QualityControl/latest Readout/latest`). O2 is automatically pulled by QualityControl, thus no need to specify it explicitly. ## Execution -To make sure that your system is correctly setup, we are going to run a basic QC workflow. We will use central services for the repository and the GUI. If you want to set them up on your computer or in your lab, please have a look [here](#local-ccdb-setup) and [here](#local-qcg-setup). +To make sure that your system is correctly setup, we are going to run a basic QC workflow attached to a simple data producer. + +The QC repository and the GUI are central services, thus they don't have to be installed on your machine. If you want to set them up on your computer or in your lab, please have a look [here](Advanced.md#local-ccdb-setup) and [here](Advanced.md#local-qcg-qc-gui-setup). ### Basic workflow -We will run a basic workflow described in the following schema. +This is the workflow we want to run: -![alt text](images/basic-schema.png) +![basic-schema](images/basic-schema.png) -The _Producer_ is a random data generator. In a more realistic setup it would be a processing device or the _Readout_. The _Data Sampling_ is the system in charge of dispatching data samples from the main data flow to the _QC tasks_. It can be configured to dispatch different proportion or different types of data. The _Checker_ is in charge of evaluating the _MonitorObjects_ produced by the _QC tasks_, for example by checking that the mean is above a certain limit. It can also modify the aspect of the histogram, e.g. by changing the background color or adding a PaveText. Finally the _Checker_ is also in charge of storing the resulting _MonitorObject_ into the repository where it will be accessible by the web GUI. It also pushes it to a _Printer_ for the sake of this tutorial. +- The _Producer_ is a random data generator. In a more realistic setup it would be a processing device or the _Readout_. +- The _Data Sampling_ is the system in charge of dispatching data samples from the main data flow to the _QC tasks_. It can be configured to dispatch different proportion or different types of data. +- The __QC tasks__ are in charge of analyzing the data and preparing QC objects, often histograms, that are then pushed forward every cycle. A cycle is 10 second in this example. In production it is at least 1 minute. +- The _Checker_ is in charge of evaluating the _MonitorObjects_ produced by the _QC tasks_. It runs _Checks_ defined by the users, for example checking that the mean is above a certain limit. It can also modify the aspect of the histogram, e.g. by changing the background color or adding a PaveText. +- Finally the _Checker_ is also in charge of storing the resulting _MonitorObject_ into the repository where it will be accessible by the web GUI. +- It also pushes it to a _Printer_ for the sake of this tutorial. -To run it simply do +To run it simply do: - qcRunBasic + o2-qc-run-basic -Thanks to the Data Processing Layer (DPL, more details later) it is a single process that steers all the _devices_, i.e. processes making up the workflow. A window should appear that shows a graphical representation of the workflow. The output of any of the processes is available by double clicking a box. If a box is red it means that the process has stopped, probably abnormaly. +Thanks to the _Data Processing Layer_ (DPL, more details later) it is a single process that steers all the processes in the workflow. Processes are called _devices_. -![alt text](images/basic-dpl-gui.png) +When running on your computer, locally, a window should appear that shows a graphical representation of the workflow. If you are running remotely via ssh, the DPL Debug GUI will not open. In some cases, it then requires to run with `-b`. -__Repository and GUI__ +The output of any of the processes is available by double clicking a box. If a box is red it means that the process has stopped, probably abnormally. -The data is stored in the [ccdb-test](ccdb-test.cern.ch:8080/browse) at CERN. If everything works fine you should see the objects being published in the QC web GUI (QCG) at this address : [https://qcg-test.cern.ch](https://qcg-test.cern.ch/?page=layoutShow&layoutId=5bb34a1d18a82bb283a487bd). The link actually brings you to a "layout" that shows the object (a histo titled "example") published by the task. +This is not the GUI we will use to see the histograms. -TODO add a link to the user documentation of the QCG +![basic-dpl-gui](images/basic-dpl-gui.png) -![alt text](images/basic-qcg.png) +The example above consists of a single DPL workflow containing both the main processing and the QC infrastructure. In a more real case, we would prefer attaching the QC without modifying the original topology. It can be done by merging two (or more) workflows with a pipe (`|`), as shown below: -__Configuration file__ + o2-qc-run-producer | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/basic.json + +![basic-schema-2-exe](images/basic-schema-2-exe.png) -The devices are configured in the config file named `basic.json`. It is installed in `$QUALITYCONTROL_ROOT/etc`. Each time you rebuild the code, `$QUALITYCONTROL_ROOT/etc/basic.json` is overwritten by the file in the source directory (`~/alice/QualityControl/Framework/basic.json`). +This command uses two executables. The first one contains only the _Producer_ (see Figure above), which represents the data flow to which we want to apply the QC. The second executable generates the QC workflow based on the given configuration file (more details in a few sections). These two workflows are joined together using the pipe `|` character. -### Readout chain +This example illustrates how to add QC to any DPL workflow by using `o2-qc` and passing it a configuration file. -In this second example, we are going to use the Readout as data source. +#### Repository and GUI -![alt text](images/readout-schema.png) +The plots are stored in the [ccdb-test](ccdb-test.cern.ch:8080/browse) at CERN. If everything works fine you should see the objects being published in the QC web GUI (QCG) at this address: [https://qcg-test.cern.ch/?page=objectTree](https://qcg-test.cern.ch/?page=objectTree). The link brings you to the hierarchy of objects (see screenshot below). Open "qc/TST/MO/QcTask" (the task you are running) and click on "example" which is the name of your histogram. -This workflow is a bit different from the basic one. The _Readout_ is not a device and thus we have to have a _proxy_ to get data from it. This is the extra box going to the dispatcher, which then injects data to the task. This is handled in the _Readout_ if you enable the corresponding configuration flag. +If you click on the item in the tree again, you will see an updated version of the plot. -TODO make the qc task use the daq code +![alt text](images/basic-qcg1.png) -To do so, open the readout config file located at `$READOUT_ROOT/etc/readout.cfg` and make sure that the following properties are correct : +#### Configuration file -``` -# First make sure we never exit -[readout] -(...) -exitTimeout=-1 +In the example above, the devices are configured in the config file named `basic.json`. It is installed in `$QUALITYCONTROL_ROOT/etc`. Each time you rebuild the code, `$QUALITYCONTROL_ROOT/etc/basic.json` is overwritten by the file in the source directory (`~/alice/QualityControl/Framework/basic.json`). + +The configuration for the QC is made of many parameters described in an [this chapter of the documentation](Configuration.md). + +For now we can see below the definition of a task. `moduleName` and `className` specify respectively the library and the class to load and instantiate to do the actual job of the task. +```json (...) -# And enable the data sampling -[consumer-data-sampling] -consumerType=DataSampling -enabled=1 +"tasks": { + "QcTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "cycleDurationSeconds": "60", "": "60 seconds minimum", (...) ``` +Try and change the name of the task by replacing `QcTask` by a name of your choice (there are 2 places to update in the config file!). Relaunch the workflows. You should now see the object published under a different directory in the QCG. -Start Readout : -``` -readout.exe file://$READOUT_ROOT/etc/readout.cfg -``` +### Post-processing example + +Now we will run an additional application performing further processing of data generated by the basic workflow. Run it again in one terminal window: -Start the QC and DS (DataSampling) workflow : ``` -qcRunReadout +o2-qc-run-basic ``` -The data sampling is configured to sample 1% of the data as the readout should run by default at full speed. - -#### Readout data received by the Task - -The header is a O2 header populated with data from the header built by the Readout. -The payload received is a 2MB (configurable) data page made of CRU pages (8kB). - -__Configuration file__ - -The configuration file is installed in `$QUALITYCONTROL_ROOT/etc`. Each time you rebuild the code, `$QUALITYCONTROL_ROOT/etc/readout.json` is overwritten by the file in the source directory (`~/alice/QualityControl/Framework/readout.json`). -To avoid this behaviour and preserve the changes you do to the configuration, you can copy the file and specify the path to it with the parameter `--config-path` when launch `qcRunReadout`. - -To change the fraction of the data being monitored, change the option `fraction`. +In another terminal window run the ExampleTrend post-processing task, as follows: ``` -"fraction": "0.01", +o2-qc-run-postprocessing --config json://${QUALITYCONTROL_ROOT}/etc/postprocessing.json --id ExampleTrend ``` ---- +On the [QCG website](https://qcg-test.cern.ch/?page=objectTree) you will see a TTree and additional plots visible under the path `/qc/TST/MO/ExampleTrend`. They show how different properties of the Example histogram change during time. The longer the applications are running, the more data will be visible. -[↑ Go to the Table of Content ↑](../README.md) | [Continue to Modules Development →](ModulesDevelopment.md) \ No newline at end of file +The [post-processing component](doc/PostProcessing.md) and its convenience classes allow to trend and correlate various characteristics of histograms and other data structures generated by QC tasks and checks. One can create their own post-processing tasks or use the ones included in the framework and configure them for one's own needs. + +[↑ Go to the Table of Content ↑](../README.md) | [Continue to Modules Development →](ModulesDevelopment.md) diff --git a/doc/ReleaseNotesTemplate.md b/doc/ReleaseNotesTemplate.md new file mode 100644 index 0000000000..eefd863ad9 --- /dev/null +++ b/doc/ReleaseNotesTemplate.md @@ -0,0 +1,10 @@ +## New features +- +## Bug fixes +- +## Detectors code +- +## Documentation +- +## Misc +- \ No newline at end of file diff --git a/doc/benchmark-repo.md b/doc/benchmark-repo.md index 5a8e6aafdc..368b700c70 100644 --- a/doc/benchmark-repo.md +++ b/doc/benchmark-repo.md @@ -11,7 +11,7 @@ Executable is called `repositoryBenchmark`. _Example execution :_ ``` -repositoryBenchmark --max-iterations 30 +o2-qc-repo-benchmark --max-iterations 30 --id benchmarkTask_0 --mq-config ~/alice/QualityControl/Framework/alfa.json --number-tasks 1 diff --git a/doc/images/Advanced-external.png b/doc/images/Advanced-external.png new file mode 100644 index 0000000000..2aae6f6b38 Binary files /dev/null and b/doc/images/Advanced-external.png differ diff --git a/doc/images/Aggregation.png b/doc/images/Aggregation.png new file mode 100644 index 0000000000..86c1500b42 Binary files /dev/null and b/doc/images/Aggregation.png differ diff --git a/doc/images/Architecture.png b/doc/images/Architecture.png index 0a5920313c..176bd39fc9 100644 Binary files a/doc/images/Architecture.png and b/doc/images/Architecture.png differ diff --git a/doc/images/advanced.png b/doc/images/advanced.png new file mode 100644 index 0000000000..5d35b60d48 Binary files /dev/null and b/doc/images/advanced.png differ diff --git a/doc/images/basic-qcg.png b/doc/images/basic-qcg.png deleted file mode 100644 index aa81d95dd7..0000000000 Binary files a/doc/images/basic-qcg.png and /dev/null differ diff --git a/doc/images/basic-qcg1.png b/doc/images/basic-qcg1.png new file mode 100644 index 0000000000..bfc6f831f4 Binary files /dev/null and b/doc/images/basic-qcg1.png differ diff --git a/doc/images/basic-qcg2.png b/doc/images/basic-qcg2.png new file mode 100644 index 0000000000..5187f804dd Binary files /dev/null and b/doc/images/basic-qcg2.png differ diff --git a/doc/images/basic-schema-2-exe.png b/doc/images/basic-schema-2-exe.png new file mode 100644 index 0000000000..0b4dc70dab Binary files /dev/null and b/doc/images/basic-schema-2-exe.png differ diff --git a/doc/images/basic-schema.graffle b/doc/images/basic-schema.graffle index 05bbc38184..b41fc40512 100644 Binary files a/doc/images/basic-schema.graffle and b/doc/images/basic-schema.graffle differ diff --git a/doc/images/basic-schema.png b/doc/images/basic-schema.png index 411a0c106b..7101ac2c6f 100644 Binary files a/doc/images/basic-schema.png and b/doc/images/basic-schema.png differ diff --git a/doc/images/fork.png b/doc/images/fork.png new file mode 100644 index 0000000000..6fb43efdfa Binary files /dev/null and b/doc/images/fork.png differ diff --git a/doc/images/o2_logo.png b/doc/images/o2_logo.png new file mode 100644 index 0000000000..c3f7c4eeac Binary files /dev/null and b/doc/images/o2_logo.png differ diff --git a/doc/images/qcRunAdvanced.png b/doc/images/qcRunAdvanced.png index 902d1c1bed..a0aebfae5d 100644 Binary files a/doc/images/qcRunAdvanced.png and b/doc/images/qcRunAdvanced.png differ diff --git a/doc/images/qo_flag_conversion_01.svg b/doc/images/qo_flag_conversion_01.svg new file mode 100644 index 0000000000..169a1fa953 --- /dev/null +++ b/doc/images/qo_flag_conversion_01.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOR + + + + + + + + + + + + EOR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + QO1 + + + + + + Flags for QO1 + + + + + diff --git a/doc/images/qo_flag_conversion_02.svg b/doc/images/qo_flag_conversion_02.svg new file mode 100644 index 0000000000..abd7555792 --- /dev/null +++ b/doc/images/qo_flag_conversion_02.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOR + + + + + + + + + + + + EOR + + + + + + + + + + + + + + + + + + + + + + + + + + + 14, “Unexplained Bad Quality” + + + + + + + + + + + + + + + + + + + + + + + + + QO1 + + + + + + Flags for QO1 + + + + + + + + 14, “Unexplained Medium Quality” + + + + + \ No newline at end of file diff --git a/doc/images/qo_flag_conversion_03.svg b/doc/images/qo_flag_conversion_03.svg new file mode 100644 index 0000000000..91c9b79fb4 --- /dev/null +++ b/doc/images/qo_flag_conversion_03.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOR + + + + + + + + + + + + EOR + + + + + + + + + + + + + + + + + + + + + + + + + + + 1, “Unexplained Null Quality” + + + + + + + + + + + + + + + + + + QO1 + + + + + + Flags for QO1 + + + + + \ No newline at end of file diff --git a/doc/images/qo_flag_conversion_04.svg b/doc/images/qo_flag_conversion_04.svg new file mode 100644 index 0000000000..5ed7115691 --- /dev/null +++ b/doc/images/qo_flag_conversion_04.svg @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOR + + + + + + + + + + + + EOR + + + + + + + + 6, 9 + + + + + + + + + + + + + + + + + + + + 9 + + + + + + + + + + + + + + 9 + + + + + + + + 9 + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + 10 + + + + + + + + + + + + + + + + + + + + 9 + + + + + + + + 9 + + + + + + + + 6 + + + + + + + + 10 + + + + + + + + 5 + + + + + + QO1 + + + + + + Flags for QO1 + + + + + \ No newline at end of file diff --git a/doc/images/qo_flag_conversion_05.svg b/doc/images/qo_flag_conversion_05.svg new file mode 100644 index 0000000000..01f3d0d84c --- /dev/null +++ b/doc/images/qo_flag_conversion_05.svg @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOR + + + + + + + + + + + + EOR + + + + + + + + 6 + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + 6 + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + QO1 + + + + + + Flags for QO1 + + + + + \ No newline at end of file diff --git a/doc/images/qo_flag_conversion_06.svg b/doc/images/qo_flag_conversion_06.svg new file mode 100644 index 0000000000..c3e7e0bf08 --- /dev/null +++ b/doc/images/qo_flag_conversion_06.svg @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOR + + + + + + + + + + + + EOR + + + + + + + + 14 + + + + + + + + + + + + + + + + + + + + 14 + + + + + + + + + + + + + + 6 + + + + + + + + + + + + + + + + + + + + 14 + + + + + + + + 14 + + + + + + + + 6 + + + + + + + + 6 + + + + + + + + + + + + QO1 + + + + + + Flags for QO1 + + + + + \ No newline at end of file diff --git a/doc/images/qo_flag_conversion_07.svg b/doc/images/qo_flag_conversion_07.svg new file mode 100644 index 0000000000..631d85cabb --- /dev/null +++ b/doc/images/qo_flag_conversion_07.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOR + + + + + + + + + + + + EOR + + + + + + + + + + + + + + + + + + + + 6, “Sector A off” + + + + + + + + + + + + + + 6, “Sector B off” + + + + + + + + + + + + + + + + + + + + 6, “Sector B off” + + + + + + + + 6, “Sector A off” + + + + + + QO1 + + + + + + Flags for QO1 + + + + + \ No newline at end of file diff --git a/doc/images/qo_flag_conversion_08.svg b/doc/images/qo_flag_conversion_08.svg new file mode 100644 index 0000000000..8ac438dcd3 --- /dev/null +++ b/doc/images/qo_flag_conversion_08.svg @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOR + + + + + + + + + + + + EOR + + + + + + + + + + + + + + + + + + + + 14 + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + 14 + + + + + + + + 1 + + + + + + + + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + + + + + + + + + + + QO1 + + + + + + Flags for QO1 + + + + + \ No newline at end of file diff --git a/doc/images/qo_flag_conversion_09.svg b/doc/images/qo_flag_conversion_09.svg new file mode 100644 index 0000000000..1b7a18a466 --- /dev/null +++ b/doc/images/qo_flag_conversion_09.svg @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOR + + + + + + + + + + + + EOR + + + + + + + + + + + + + + + + + + + + 3 + + + + + + + + + + + + + + 6 + + + + + + + + + + + + + + + + + + + + 3 + + + + + + + + 6 + + + + + + + + 9 + + + + + + + + + + + + + + 9 + + + + + + + + + + + + + + + + + + QO1 + + + + + + Flags for QO1 + + + + + \ No newline at end of file diff --git a/doc/images/qo_flag_conversion_10.svg b/doc/images/qo_flag_conversion_10.svg new file mode 100644 index 0000000000..749e7f44fd --- /dev/null +++ b/doc/images/qo_flag_conversion_10.svg @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOR + + + + + + + + + + + + EOR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + 5 + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + 5 + + + + + + QO1 + + + + + + QO2 + + + + + + Flags for QO1 + + + + + + Flags for QO2 + + + + + \ No newline at end of file diff --git a/doc/images/quality-task.png b/doc/images/quality-task.png new file mode 100644 index 0000000000..33d01dee4d Binary files /dev/null and b/doc/images/quality-task.png differ diff --git a/doc/images/readout-schema.png b/doc/images/readout-schema.png index ffd0c3409e..a4366637fd 100644 Binary files a/doc/images/readout-schema.png and b/doc/images/readout-schema.png differ diff --git a/doc/images/trending-task.png b/doc/images/trending-task.png new file mode 100644 index 0000000000..cd85167f0f Binary files /dev/null and b/doc/images/trending-task.png differ diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 8178c76d62..0000000000 --- a/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -readme diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index c741881743..0000000000 --- a/docs/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-slate \ No newline at end of file diff --git a/docs/test.md b/docs/test.md deleted file mode 100644 index 17475c2b9a..0000000000 --- a/docs/test.md +++ /dev/null @@ -1,3 +0,0 @@ -# Test - -test diff --git a/getTestDataDirectory.cxx.in b/getTestDataDirectory.cxx.in new file mode 100644 index 0000000000..97ef403fc1 --- /dev/null +++ b/getTestDataDirectory.cxx.in @@ -0,0 +1,6 @@ +#include "getTestDataDirectory.h" + +const char* getTestDataDirectory() +{ + return "@CMAKE_BINARY_DIR@/tests/"; +} diff --git a/getTestDataDirectory.h b/getTestDataDirectory.h new file mode 100644 index 0000000000..d9b77b3d71 --- /dev/null +++ b/getTestDataDirectory.h @@ -0,0 +1,6 @@ +#ifndef QC_TEST_GETTESTDATADIRECTORY_H +#define QC_TEST_GETTESTDATADIRECTORY_H + +const char* getTestDataDirectory(); + +#endif